Files
KeepItGoingServer/templates/users/profile.html
T
Keith SmithandClaude Sonnet 4.5 21d9d01885 Add email verification, admin approval, and user profile enhancements
Implement comprehensive email verification and admin approval system for user registration. Users must verify their email before logging in, and admins must approve new users before they can access the system.

Major features:
- Email verification with UUID tokens (24hr expiry, one-time use)
- Admin approval workflow via Django admin interface
- Custom authentication backend enforcing verification and approval
- Password change functionality for authenticated users
- Enhanced profile page with proper styling and theme support
- Collect first name, last name, and timezone during registration
- Profile link added to header navigation

Email notifications:
- Verification email sent after registration
- Admin notification when users need approval
- Approval notification sent to users

Security features:
- Cryptographically secure UUID tokens
- Token expiration and one-time use enforcement
- Email enumeration protection
- Custom JWT token validation for API access
- Existing users auto-approved via data migration

Templates added:
- Email templates (verification, approval, admin notification)
- Web templates (password change, verification pages)
- Enhanced profile page with dark/light mode support

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 18:13:38 -07:00

128 lines
8.0 KiB
HTML

{% extends 'base.html' %}
{% block title %}Profile - KeepItGoing{% endblock %}
{% block content %}
<div class="page-header" style="margin-bottom: 2rem;">
<h1 style="font-size: 1.875rem; font-weight: 700;">Profile Settings</h1>
<p style="color: var(--text-secondary); margin-top: 0.5rem;">Manage your account information and preferences</p>
</div>
{% if success %}
<div class="alert alert-success" style="margin-bottom: 1.5rem;">
<strong>✓ Success!</strong> {{ success }}
</div>
{% endif %}
<!-- Personal Information -->
<div class="card" style="background: var(--surface); padding: 1.5rem; border-radius: 0.75rem; margin-bottom: 1.5rem; border: 1px solid var(--border);">
<h2 style="font-size: 1.25rem; font-weight: 600; margin-bottom: 1.5rem;">Personal Information</h2>
<form method="post">
{% csrf_token %}
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-bottom: 1.5rem;">
<div class="form-group">
<label for="first_name" style="display: block; margin-bottom: 0.5rem; font-weight: 500; font-size: 0.875rem;">First Name</label>
<input type="text"
id="first_name"
name="first_name"
value="{{ user.first_name }}"
style="width: 100%; padding: 0.625rem; background: var(--bg); border: 1px solid var(--border); border-radius: 0.375rem; color: var(--text-primary); font-size: 0.875rem;">
</div>
<div class="form-group">
<label for="last_name" style="display: block; margin-bottom: 0.5rem; font-weight: 500; font-size: 0.875rem;">Last Name</label>
<input type="text"
id="last_name"
name="last_name"
value="{{ user.last_name }}"
style="width: 100%; padding: 0.625rem; background: var(--bg); border: 1px solid var(--border); border-radius: 0.375rem; color: var(--text-primary); font-size: 0.875rem;">
</div>
</div>
<div class="form-group" style="margin-bottom: 1.5rem;">
<label for="email" style="display: block; margin-bottom: 0.5rem; font-weight: 500; font-size: 0.875rem;">Email</label>
<input type="email"
id="email"
value="{{ user.email }}"
disabled
style="width: 100%; padding: 0.625rem; background: var(--surface); border: 1px solid var(--border); border-radius: 0.375rem; color: var(--text-secondary); font-size: 0.875rem; cursor: not-allowed; opacity: 0.6;">
<small style="display: block; margin-top: 0.375rem; color: var(--text-secondary); font-size: 0.75rem;">Email cannot be changed</small>
</div>
<div class="form-group" style="margin-bottom: 1.5rem;">
<label for="timezone" style="display: block; margin-bottom: 0.5rem; font-weight: 500; font-size: 0.875rem;">Timezone</label>
<select id="timezone"
name="timezone"
style="width: 100%; padding: 0.625rem; background: var(--bg); border: 1px solid var(--border); border-radius: 0.375rem; color: var(--text-primary); font-size: 0.875rem;">
<option value="UTC" {% if user.timezone == 'UTC' %}selected{% endif %}>UTC</option>
<option value="America/New_York" {% if user.timezone == 'America/New_York' %}selected{% endif %}>Eastern Time (ET)</option>
<option value="America/Chicago" {% if user.timezone == 'America/Chicago' %}selected{% endif %}>Central Time (CT)</option>
<option value="America/Denver" {% if user.timezone == 'America/Denver' %}selected{% endif %}>Mountain Time (MT)</option>
<option value="America/Los_Angeles" {% if user.timezone == 'America/Los_Angeles' %}selected{% endif %}>Pacific Time (PT)</option>
<option value="Europe/London" {% if user.timezone == 'Europe/London' %}selected{% endif %}>London (GMT)</option>
<option value="Europe/Paris" {% if user.timezone == 'Europe/Paris' %}selected{% endif %}>Paris (CET)</option>
<option value="Asia/Tokyo" {% if user.timezone == 'Asia/Tokyo' %}selected{% endif %}>Tokyo (JST)</option>
</select>
</div>
<div class="form-group" style="margin-bottom: 1.5rem;">
<label for="default_reminder_minutes" style="display: block; margin-bottom: 0.5rem; font-weight: 500; font-size: 0.875rem;">Default Reminder</label>
<select id="default_reminder_minutes"
name="default_reminder_minutes"
style="width: 100%; padding: 0.625rem; background: var(--bg); border: 1px solid var(--border); border-radius: 0.375rem; color: var(--text-primary); font-size: 0.875rem;">
<option value="0" {% if user.default_reminder_minutes == 0 %}selected{% endif %}>No reminder</option>
<option value="5" {% if user.default_reminder_minutes == 5 %}selected{% endif %}>5 minutes before</option>
<option value="15" {% if user.default_reminder_minutes == 15 %}selected{% endif %}>15 minutes before</option>
<option value="30" {% if user.default_reminder_minutes == 30 %}selected{% endif %}>30 minutes before</option>
<option value="60" {% if user.default_reminder_minutes == 60 %}selected{% endif %}>1 hour before</option>
<option value="1440" {% if user.default_reminder_minutes == 1440 %}selected{% endif %}>1 day before</option>
</select>
<small style="display: block; margin-top: 0.375rem; color: var(--text-secondary); font-size: 0.75rem;">How long before a task is due should you be reminded?</small>
</div>
<div style="margin-bottom: 1.5rem;">
<label style="display: flex; align-items: center; cursor: pointer; padding: 0.75rem; background: var(--surface); border: 1px solid var(--border); border-radius: 0.375rem; margin-bottom: 0.75rem;">
<input type="checkbox"
name="email_notifications"
{% if user.email_notifications %}checked{% endif %}
style="margin-right: 0.75rem; width: 1rem; height: 1rem;">
<div>
<div style="font-weight: 500; font-size: 0.875rem;">Email Notifications</div>
<div style="color: var(--text-secondary); font-size: 0.75rem;">Receive task reminders and updates via email</div>
</div>
</label>
<label style="display: flex; align-items: center; cursor: pointer; padding: 0.75rem; background: var(--surface); border: 1px solid var(--border); border-radius: 0.375rem;">
<input type="checkbox"
name="push_notifications"
{% if user.push_notifications %}checked{% endif %}
style="margin-right: 0.75rem; width: 1rem; height: 1rem;">
<div>
<div style="font-weight: 500; font-size: 0.875rem;">Push Notifications</div>
<div style="color: var(--text-secondary); font-size: 0.75rem;">Receive push notifications on your devices</div>
</div>
</label>
</div>
<button type="submit"
class="btn btn-primary"
style="padding: 0.625rem 1.25rem; font-size: 0.875rem;">
Save Changes
</button>
</form>
</div>
<!-- Security Section -->
<div class="card" style="background: var(--surface); padding: 1.5rem; border-radius: 0.75rem; border: 1px solid var(--border);">
<h2 style="font-size: 1.25rem; font-weight: 600; margin-bottom: 0.5rem;">Security</h2>
<p style="color: var(--text-secondary); margin-bottom: 1.25rem; font-size: 0.875rem;">Manage your account security settings</p>
<a href="{% url 'change-password' %}"
class="btn btn-secondary"
style="display: inline-block; padding: 0.625rem 1.25rem; text-decoration: none; font-size: 0.875rem;">
Change Password
</a>
</div>
{% endblock %}