Files
Keith SmithandClaude Sonnet 5 7e905a0566 Add Web Push notifications (PWA)
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>
2026-09-04 22:30:56 -06:00

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 %}