Internal
Public Access
Wires up the previously-scaffolded VAPID/DeviceToken infrastructure end to end: browser subscription flow on the Profile page's existing "Push Notifications" toggle, a service worker push/notificationclick handler, and server-side sending from the daily task digest. Also broadens that digest's eligibility query so push-only users (email notifications off) aren't silently skipped, and adds `generate_vapid_keys` since the pinned py-vapid's own key generator is broken against current cryptography. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
129 lines
8.1 KiB
HTML
129 lines
8.1 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" onsubmit="handleProfileFormSubmit(event, this)">
|
|
{% 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"
|
|
data-vapid-public-key="{{ vapid_public_key }}"
|
|
{% 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 %}
|