{% extends 'base.html' %}
{% block title %}Settings — AgentForms{% endblock %}
{% block head_extra %}
<style nonce="{{ csp_nonce }}">
.settings-header {
margin-bottom: 24px;
display: flex;
align-items: center;
justify-content: space-between;
}
.settings-header h1 {
font-size: 24px;
font-weight: 700;
}
.settings-layout {
display: grid;
grid-template-columns: 200px 1fr;
gap: 24px;
}
.settings-sidebar {
position: sticky;
top: 20px;
align-self: start;
}
.settings-sidebar a {
display: block;
padding: 8px 16px;
color: #666;
text-decoration: none;
font-size: 14px;
border-radius: 6px;
margin-bottom: 2px;
transition: background 0.15s, color 0.15s;
}
.settings-sidebar a:hover {
background: #e9ecef;
color: #333;
}
.settings-sidebar a.active {
background: var(--accent-dim);
color: var(--accent);
font-weight: 600;
}
.settings-content { min-width: 0; }
.card {
background: white;
border-radius: 8px;
padding: 24px;
margin-bottom: 20px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.card h2 {
font-size: 18px;
margin-bottom: 8px;
}
.card .description {
font-size: 14px;
color: #666;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
font-size: 14px;
font-weight: 600;
margin-bottom: 6px;
color: #374151;
}
.form-group input[type="text"],
.form-group input[type="email"],
.form-group input[type="password"] {
width: 100%;
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
font-family: inherit;
transition: border-color 0.15s;
}
.form-group input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
}
.form-group .hint {
font-size: 12px;
color: #6b7280;
margin-top: 4px;
}
.btn {
display: inline-block;
padding: 8px 16px;
background: var(--accent);
color: white;
text-decoration: none;
border-radius: 6px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: background 0.15s;
}
.btn:hover { background: var(--accent-bright); }
.btn-sm { padding: 6px 12px; font-size: 13px; }
.btn-danger { background: #dc3545; }
.btn-danger:hover { background: #bb2d3b; }
.btn-outline {
background: transparent;
border: 1px solid #d1d5db;
color: #374151;
}
.btn-outline:hover { background: #f9fafb; }
.success { background: #d4edda; color: #155724; padding: 12px; border-radius: 6px; margin-bottom: 16px; }
.error { background: #f8d7da; color: #721c24; padding: 12px; border-radius: 6px; margin-bottom: 16px; }
.info { background: #dbeafe; color: #1e40af; padding: 12px; border-radius: 6px; margin-bottom: 16px; }
.user-info-bar {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
background: #f9fafb;
border-radius: 8px;
margin-bottom: 20px;
}
.user-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
background: var(--accent);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 18px;
}
.user-details { flex: 1; }
.user-details .name { font-weight: 600; font-size: 16px; }
.user-details .email { color: #666; font-size: 13px; margin-top: 2px; }
.user-details .tier { display: inline-block; margin-top: 4px; }
.badge { display: inline-block; padding: 2px 8px; background: #e9ecef; border-radius: 12px; font-size: 12px; }
.badge-free { background: #e9ecef; color: #333; }
.badge-starter { background: var(--accent-dim); color: var(--accent); }
.badge-pro { background: #fef3c7; color: #92400e; }
.badge-ai { background: #f3e8ff; color: #6b21a8; font-weight: 600; }
.verified-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
background: #dcfce7;
color: #166534;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
margin-left: 8px;
}
.unverified-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
background: #fef3c7;
color: #92400e;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
margin-left: 8px;
}
.danger-zone {
border: 1px solid #fecaca;
}
.danger-zone h2 { color: #dc3545; }
.toggle-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid #f3f4f6;
}
.toggle-row:last-child { border-bottom: none; }
.toggle-label { font-size: 14px; }
.toggle-label .label-title { font-weight: 600; }
.toggle-label .label-desc { color: #6b7280; font-size: 12px; margin-top: 2px; }
.toggle-switch {
position: relative;
width: 44px;
height: 24px;
}
.toggle-switch input { opacity: 0; width: 0; height: 0; }
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0; left: 0; right: 0; bottom: 0;
background: #d1d5db;
border-radius: 24px;
transition: 0.2s;
}
.toggle-slider:before {
content: "";
position: absolute;
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background: white;
border-radius: 50%;
transition: 0.2s;
}
.toggle-switch input:checked + .toggle-slider {
background: var(--accent);
}
.toggle-switch input:checked + .toggle-slider:before {
transform: translateX(20px);
}
/* MOBILE */
@media (max-width: 640px) {
.container { padding: 8px; }
.settings-layout {
grid-template-columns: 1fr;
}
.settings-sidebar {
position: static;
display: flex;
overflow-x: auto;
gap: 4px;
padding-bottom: 8px;
border-bottom: 1px solid #e5e7eb;
}
.settings-sidebar a {
white-space: nowrap;
padding: 6px 12px;
margin-bottom: 0;
}
}
</style>
{% endblock %}
{% block content %}
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="settings-header">
<h1>Settings</h1>
<a href="/auth/dashboard" style="color:#666;text-decoration:none;font-size:14px;">← Back to Dashboard</a>
</div>
<div class="settings-layout">
<!-- Sidebar Navigation -->
<div class="settings-sidebar">
<a href="#profile" class="active">Profile</a>
<a href="#password">Password</a>
<a href="#email">Email</a>
<a href="#security">Security</a>
<a href="{{ url_for('user_settings.api_keys_page') }}">API Keys</a>
<a href="{{ url_for('referral.referral_page') }}">Refer & Earn</a>
</div>
<!-- Content -->
<div class="settings-content">
<!-- User Info Bar -->
<div class="user-info-bar">
<div class="user-avatar">{{ user.name[:1] if user.name else user.email[:1] | upper }}</div>
<div class="user-details">
<div class="name">{{ user.name or user.email }}</div>
<div class="email">
{{ user.email }}
{% if user.email_verified %}
<span class="verified-badge">✓ Verified</span>
{% else %}
<span class="unverified-badge">⚠ Not verified</span>
{% endif %}
</div>
<div class="tier">
<span class="badge badge-{{ user.tier or 'free' }}">{{ user.tier or 'free' | upper }}</span>
</div>
</div>
</div>
<!-- Profile Section -->
<div class="card" id="profile">
<h2>Profile</h2>
<p class="description">Manage your account name and display information.</p>
<form method="POST" action="{{ url_for('user_settings.update_profile') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" name="name" value="{{ user.name or '' }}" placeholder="Your full name">
</div>
<div class="form-group">
<label for="display_name">Display Name</label>
<input type="text" id="display_name" name="display_name" value="{{ user.display_name or '' }}" placeholder="Name shown on your sites">
<div class="hint">Optional — used as the account identifier on form submissions</div>
</div>
<button type="submit" class="btn">Save Profile</button>
</form>
</div>
<!-- Password Section -->
<div class="card" id="password">
<h2>Password</h2>
<p class="description">Change your password to keep your account secure.</p>
<form method="POST" action="{{ url_for('user_settings.change_password') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<div class="form-group">
<label for="current_password">Current Password</label>
<input type="password" id="current_password" name="current_password" required>
</div>
<div class="form-group">
<label for="new_password">New Password</label>
<input type="password" id="new_password" name="new_password" required minlength="8">
<div class="hint">Minimum 8 characters</div>
</div>
<div class="form-group">
<label for="confirm_password">Confirm New Password</label>
<input type="password" id="confirm_password" name="confirm_password" required minlength="8">
</div>
<button type="submit" class="btn">Change Password</button>
</form>
</div>
<!-- Email Section -->
<div class="card" id="email">
<h2>Email Address</h2>
<p class="description">Update your email address. A verification link will be sent to the new address.</p>
<form method="POST" action="{{ url_for('user_settings.change_email') }}"
data-onsubmit="return confirm('Change email to ' + this.new_email.value + '? You will receive a verification link.')">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<div class="form-group">
<label>Current Email</label>
<div style="color:#333;font-size:14px;">{{ user.email }}</div>
</div>
<div class="form-group">
<label for="current_password_email">Current Password</label>
<input type="password" id="current_password_email" name="current_password" required>
<div class="hint">Enter your current password to confirm this change</div>
</div>
<div class="form-group">
<label for="new_email">New Email Address</label>
<input type="email" id="new_email" name="new_email" required placeholder="new-email@example.com">
</div>
<button type="submit" class="btn">Change Email</button>
</form>
</div>
<!-- Security Section -->
<div class="card" id="security">
<h2>Security</h2>
<p class="description">Manage authentication and security preferences.</p>
<div class="toggle-row">
<div class="toggle-label">
<div class="label-title">Magic Login (Passwordless)</div>
<div class="label-desc">Receive login links via email instead of using a password</div>
</div>
<form method="POST" action="/settings/security/magic-login" style="margin:0;">
<label class="toggle-switch">
<input type="checkbox" name="magic_login_enabled" {% if magic_login_enabled %}checked{% endif %}
data-onchange="this.form.submit()">
<span class="toggle-slider"></span>
</label>
</form>
</div>
<div class="toggle-row">
<div class="toggle-label">
<div class="label-title">API Key Authentication</div>
<div class="label-desc">Manage API keys for programmatic access to forms</div>
</div>
<a href="{{ url_for('user_settings.api_keys_page') }}" class="btn btn-sm btn-outline">Manage Keys</a>
</div>
</div>
<!-- Danger Zone -->
<div class="card danger-zone">
<h2>Danger Zone</h2>
<p class="description">Irreversible actions. Proceed with caution.</p>
<div class="toggle-row">
<div class="toggle-label">
<div class="label-title">Delete Account</div>
<div class="label-desc">Permanently delete your account and all associated data</div>
</div>
<form method="POST" action="/settings/account/delete" data-onsubmit="return confirm('Are you sure? This will delete your account and all your sites, forms, and submissions. This cannot be undone.');">
<input type="password" name="current_password" required placeholder="Enter password" class="btn btn-sm btn-danger" style="width: 200px; margin-right: 8px;">
<button type="submit" class="btn btn-sm btn-danger">Delete Account</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="/static/js/settings-inline.js"></script>
{% endblock %}