Vibe coded a bit more ... now we have session, attached picture and analysis, MOA group computation
This commit is contained in:
22
templates/auth/confirm_pending.html
Normal file
22
templates/auth/confirm_pending.html
Normal file
@@ -0,0 +1,22 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Confirm your email — Ballistic Analyzer{% endblock %}
|
||||
{% block content %}
|
||||
<h1>Check your inbox</h1>
|
||||
|
||||
<p style="color:#555;margin-bottom:1.25rem;">
|
||||
A confirmation link has been sent to <strong>{{ email }}</strong>.
|
||||
Click the link in that email to activate your account.
|
||||
</p>
|
||||
|
||||
<p style="color:#888;font-size:0.9rem;margin-bottom:1.5rem;">
|
||||
Didn't receive it? Check your spam folder, or request a new link below.
|
||||
</p>
|
||||
|
||||
<form method="post" action="{{ url_for('auth.resend_confirmation') }}">
|
||||
<input type="hidden" name="email" value="{{ email }}">
|
||||
<button type="submit"
|
||||
style="background:#f0f4ff;color:#1f77b4;border:1px solid #c0d4f0;border-radius:4px;padding:0.55rem 1.2rem;font-size:0.92rem;cursor:pointer;">
|
||||
Resend confirmation email
|
||||
</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
62
templates/auth/login.html
Normal file
62
templates/auth/login.html
Normal file
@@ -0,0 +1,62 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Login — Ballistic Analyzer{% endblock %}
|
||||
{% block content %}
|
||||
<h1>Sign in</h1>
|
||||
|
||||
<form method="post" action="{{ url_for('auth.login') }}" style="max-width:360px;margin-bottom:1.5rem;">
|
||||
<div style="margin-bottom:1rem;">
|
||||
<label style="display:block;font-size:0.88rem;font-weight:600;color:#444;margin-bottom:0.3rem;">Email</label>
|
||||
<input type="email" name="email" value="{{ prefill_email or '' }}" required autocomplete="email"
|
||||
style="width:100%;padding:0.55rem 0.75rem;border:1px solid #ccc;border-radius:4px;font-size:0.95rem;">
|
||||
</div>
|
||||
<div style="margin-bottom:1.25rem;">
|
||||
<label style="display:block;font-size:0.88rem;font-weight:600;color:#444;margin-bottom:0.3rem;">Password</label>
|
||||
<input type="password" name="password" required autocomplete="current-password"
|
||||
style="width:100%;padding:0.55rem 0.75rem;border:1px solid #ccc;border-radius:4px;font-size:0.95rem;">
|
||||
</div>
|
||||
<button type="submit"
|
||||
style="width:100%;background:#1a1a2e;color:#fff;border:none;border-radius:4px;padding:0.65rem;font-size:0.95rem;cursor:pointer;">
|
||||
Sign in
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% if show_resend %}
|
||||
<form method="post" action="{{ url_for('auth.resend_confirmation') }}" style="margin-bottom:1.5rem;">
|
||||
<input type="hidden" name="email" value="{{ resend_email }}">
|
||||
<button type="submit" class="btn-link" style="color:#1f77b4;font-size:0.88rem;">
|
||||
Resend confirmation email
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<p style="font-size:0.9rem;color:#555;margin-bottom:1.5rem;">
|
||||
Don't have an account? <a href="{{ url_for('auth.register') }}">Create one</a>
|
||||
</p>
|
||||
|
||||
<div style="display:flex;align-items:center;gap:0.75rem;margin-bottom:1.5rem;max-width:360px;">
|
||||
<hr style="flex:1;border:none;border-top:1px solid #ddd;">
|
||||
<span style="font-size:0.8rem;color:#999;">or continue with</span>
|
||||
<hr style="flex:1;border:none;border-top:1px solid #ddd;">
|
||||
</div>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:0.75rem;max-width:360px;">
|
||||
<a href="{{ url_for('auth.login_google') }}"
|
||||
style="display:flex;align-items:center;gap:0.75rem;padding:0.65rem 1.1rem;border:1px solid #dadce0;border-radius:6px;color:#3c4043;text-decoration:none;font-size:0.92rem;font-weight:500;background:#fff;">
|
||||
<svg width="17" height="17" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844a4.14 4.14 0 0 1-1.796 2.716v2.259h2.908c1.702-1.567 2.684-3.875 2.684-6.615z" fill="#4285F4"/>
|
||||
<path d="M9 18c2.43 0 4.467-.806 5.956-2.18l-2.908-2.259c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332A8.997 8.997 0 0 0 9 18z" fill="#34A853"/>
|
||||
<path d="M3.964 10.71A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.957A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.957 4.042l3.007-2.332z" fill="#FBBC05"/>
|
||||
<path d="M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0A8.997 8.997 0 0 0 .957 4.958L3.964 7.29C4.672 5.163 6.656 3.58 9 3.58z" fill="#EA4335"/>
|
||||
</svg>
|
||||
Continue with Google
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('auth.login_github') }}"
|
||||
style="display:flex;align-items:center;gap:0.75rem;padding:0.65rem 1.1rem;border:1px solid #d0d7de;border-radius:6px;color:#24292f;text-decoration:none;font-size:0.92rem;font-weight:500;background:#f6f8fa;">
|
||||
<svg width="17" height="17" viewBox="0 0 98 96" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#24292f"/>
|
||||
</svg>
|
||||
Continue with GitHub
|
||||
</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
107
templates/auth/profile.html
Normal file
107
templates/auth/profile.html
Normal file
@@ -0,0 +1,107 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Profile — The Shooter's Network{% endblock %}
|
||||
{% block content %}
|
||||
<h1>Profile</h1>
|
||||
|
||||
{# ---- Avatar + display name ---- #}
|
||||
<h2>Account</h2>
|
||||
<form method="post" action="{{ url_for('auth.profile') }}"
|
||||
enctype="multipart/form-data"
|
||||
style="max-width:480px;">
|
||||
<input type="hidden" name="action" value="update_profile">
|
||||
|
||||
<div style="display:flex;align-items:center;gap:1.5rem;margin-bottom:1.5rem;">
|
||||
{% set av = current_user.effective_avatar_url %}
|
||||
{% if av %}
|
||||
<img src="{{ av }}" alt="Avatar"
|
||||
style="width:80px;height:80px;border-radius:50%;object-fit:cover;border:2px solid #e0e0e0;">
|
||||
{% else %}
|
||||
<div style="width:80px;height:80px;border-radius:50%;background:#e0e6f0;display:flex;align-items:center;justify-content:center;font-size:2rem;color:#888;">
|
||||
👤
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
<label style="display:block;font-size:.85rem;font-weight:600;color:#444;margin-bottom:.3rem;">
|
||||
Profile picture
|
||||
</label>
|
||||
<input type="file" name="avatar" accept="image/*" style="font-size:0.9rem;">
|
||||
<div style="font-size:0.78rem;color:#aaa;margin-top:.2rem;">JPEG/PNG, max 1200 px, auto-resized.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom:1rem;">
|
||||
<label style="display:block;font-size:.85rem;font-weight:600;color:#444;margin-bottom:.3rem;">Display name</label>
|
||||
<input type="text" name="display_name"
|
||||
value="{{ current_user.display_name or '' }}"
|
||||
required
|
||||
style="width:100%;padding:.55rem .75rem;border:1px solid #ccc;border-radius:4px;font-size:0.95rem;">
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom:1rem;">
|
||||
<label style="display:block;font-size:.85rem;font-weight:600;color:#444;margin-bottom:.3rem;">Bio</label>
|
||||
<textarea name="bio" rows="4" placeholder="Tell others a bit about yourself…"
|
||||
style="width:100%;padding:.55rem .75rem;border:1px solid #ccc;border-radius:4px;font-size:0.95rem;resize:vertical;">{{ current_user.bio or '' }}</textarea>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom:1rem;">
|
||||
<label style="display:block;font-size:.85rem;font-weight:600;color:#444;margin-bottom:.3rem;">Email</label>
|
||||
<input type="text" value="{{ current_user.email }}" disabled
|
||||
style="width:100%;padding:.55rem .75rem;border:1px solid #e0e0e0;border-radius:4px;font-size:0.95rem;background:#f5f5f5;color:#888;">
|
||||
<div style="font-size:0.78rem;color:#aaa;margin-top:.2rem;">
|
||||
Logged in via <strong>{{ current_user.provider.title() }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom:1.25rem;">
|
||||
<label style="display:flex;align-items:center;gap:.6rem;cursor:pointer;font-size:.95rem;">
|
||||
<input type="checkbox" name="show_equipment_public"
|
||||
{% if current_user.show_equipment_public %}checked{% endif %}
|
||||
style="width:1rem;height:1rem;">
|
||||
Show my equipment on my public profile
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;align-items:center;gap:1.5rem;flex-wrap:wrap;">
|
||||
<button type="submit"
|
||||
style="background:#1a1a2e;color:#fff;border:none;border-radius:4px;padding:.6rem 1.5rem;font-size:.95rem;cursor:pointer;">
|
||||
Save changes
|
||||
</button>
|
||||
<a href="{{ url_for('public_profile', user_id=current_user.id) }}"
|
||||
style="font-size:0.9rem;color:#1f77b4;" target="_blank">
|
||||
View my public profile →
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{# ---- Change password (local accounts only) ---- #}
|
||||
{% if current_user.provider == 'local' %}
|
||||
<h2>Change password</h2>
|
||||
<form method="post" action="{{ url_for('auth.profile') }}"
|
||||
style="max-width:480px;">
|
||||
<input type="hidden" name="action" value="change_password">
|
||||
|
||||
<div style="margin-bottom:1rem;">
|
||||
<label style="display:block;font-size:.85rem;font-weight:600;color:#444;margin-bottom:.3rem;">Current password</label>
|
||||
<input type="password" name="current_password" required
|
||||
style="width:100%;padding:.55rem .75rem;border:1px solid #ccc;border-radius:4px;font-size:.95rem;">
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-bottom:1rem;">
|
||||
<div>
|
||||
<label style="display:block;font-size:.85rem;font-weight:600;color:#444;margin-bottom:.3rem;">New password</label>
|
||||
<input type="password" name="new_password" required minlength="8"
|
||||
style="width:100%;padding:.55rem .75rem;border:1px solid #ccc;border-radius:4px;font-size:.95rem;">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:.85rem;font-weight:600;color:#444;margin-bottom:.3rem;">Confirm</label>
|
||||
<input type="password" name="confirm_password" required minlength="8"
|
||||
style="width:100%;padding:.55rem .75rem;border:1px solid #ccc;border-radius:4px;font-size:.95rem;">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit"
|
||||
style="background:#1a1a2e;color:#fff;border:none;border-radius:4px;padding:.6rem 1.5rem;font-size:.95rem;cursor:pointer;">
|
||||
Change password
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
80
templates/auth/public_profile.html
Normal file
80
templates/auth/public_profile.html
Normal file
@@ -0,0 +1,80 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ profile_user.display_name or profile_user.email.split('@')[0] }} — The Shooter's Network{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div style="display:flex;align-items:center;gap:1.5rem;margin-bottom:2rem;flex-wrap:wrap;">
|
||||
{% set av = profile_user.effective_avatar_url %}
|
||||
{% if av %}
|
||||
<img src="{{ av }}" alt="Avatar"
|
||||
style="width:80px;height:80px;border-radius:50%;object-fit:cover;border:2px solid #e0e0e0;flex-shrink:0;">
|
||||
{% else %}
|
||||
<div style="width:80px;height:80px;border-radius:50%;background:#e0e6f0;display:flex;align-items:center;justify-content:center;font-size:2.5rem;color:#888;flex-shrink:0;">
|
||||
👤
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
<h1 style="margin:0 0 .25rem;">{{ profile_user.display_name or profile_user.email.split('@')[0] }}</h1>
|
||||
<div style="font-size:0.85rem;color:#888;">
|
||||
Member since {{ profile_user.created_at.strftime('%B %Y') }}
|
||||
</div>
|
||||
{% if profile_user.bio %}
|
||||
<p style="margin-top:.75rem;color:#444;white-space:pre-wrap;max-width:600px;">{{ profile_user.bio }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# ---- Public Sessions ---- #}
|
||||
<h2>Sessions{% if public_sessions %} ({{ public_sessions|length }}){% endif %}</h2>
|
||||
|
||||
{% if public_sessions %}
|
||||
<table style="margin-bottom:1.5rem;">
|
||||
<thead>
|
||||
<tr><th>Session</th><th>Location</th><th>Distance</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for s in public_sessions %}
|
||||
<tr style="cursor:pointer;" onclick="location.href='{{ url_for('sessions.detail', session_id=s.id) }}'">
|
||||
<td>
|
||||
<a href="{{ url_for('sessions.detail', session_id=s.id) }}" style="color:inherit;text-decoration:none;">
|
||||
{{ s.session_date.strftime('%d %b %Y') }}
|
||||
</a>
|
||||
</td>
|
||||
<td style="color:#666;">{{ s.location_name or '—' }}</td>
|
||||
<td style="color:#666;">{% if s.distance_m %}{{ s.distance_m }} m{% else %}—{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p style="color:#888;margin-bottom:1.5rem;">No public sessions yet.</p>
|
||||
{% endif %}
|
||||
|
||||
{# ---- Equipment (optional) ---- #}
|
||||
{% if equipment is not none %}
|
||||
<h2>Equipment</h2>
|
||||
{% if equipment %}
|
||||
<table style="margin-bottom:1.5rem;">
|
||||
<thead>
|
||||
<tr><th>Name</th><th>Category</th><th>Brand / Model</th><th>Caliber</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in equipment %}
|
||||
<tr>
|
||||
<td>{{ item.name }}</td>
|
||||
<td style="color:#666;font-size:0.88rem;">{{ item.category.title() }}</td>
|
||||
<td style="color:#666;font-size:0.88rem;">
|
||||
{% if item.brand or item.model %}
|
||||
{{ item.brand or '' }}{% if item.brand and item.model %} {% endif %}{{ item.model or '' }}
|
||||
{% else %}—{% endif %}
|
||||
</td>
|
||||
<td style="color:#666;font-size:0.88rem;">{{ item.caliber or '—' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p style="color:#888;margin-bottom:1.5rem;">No equipment listed.</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
33
templates/auth/register.html
Normal file
33
templates/auth/register.html
Normal file
@@ -0,0 +1,33 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Create account — Ballistic Analyzer{% endblock %}
|
||||
{% block content %}
|
||||
<h1>Create account</h1>
|
||||
|
||||
<form method="post" action="{{ url_for('auth.register') }}" style="max-width:360px;">
|
||||
<div style="margin-bottom:1rem;">
|
||||
<label style="display:block;font-size:0.88rem;font-weight:600;color:#444;margin-bottom:0.3rem;">Email</label>
|
||||
<input type="email" name="email" value="{{ prefill_email or '' }}" required autocomplete="email"
|
||||
style="width:100%;padding:0.55rem 0.75rem;border:1px solid #ccc;border-radius:4px;font-size:0.95rem;">
|
||||
</div>
|
||||
<div style="margin-bottom:1rem;">
|
||||
<label style="display:block;font-size:0.88rem;font-weight:600;color:#444;margin-bottom:0.3rem;">Password
|
||||
<span style="font-weight:400;color:#888;">(min. 8 characters)</span>
|
||||
</label>
|
||||
<input type="password" name="password" required autocomplete="new-password" minlength="8"
|
||||
style="width:100%;padding:0.55rem 0.75rem;border:1px solid #ccc;border-radius:4px;font-size:0.95rem;">
|
||||
</div>
|
||||
<div style="margin-bottom:1.5rem;">
|
||||
<label style="display:block;font-size:0.88rem;font-weight:600;color:#444;margin-bottom:0.3rem;">Confirm password</label>
|
||||
<input type="password" name="confirm_password" required autocomplete="new-password"
|
||||
style="width:100%;padding:0.55rem 0.75rem;border:1px solid #ccc;border-radius:4px;font-size:0.95rem;">
|
||||
</div>
|
||||
<button type="submit"
|
||||
style="width:100%;background:#1a1a2e;color:#fff;border:none;border-radius:4px;padding:0.65rem;font-size:0.95rem;cursor:pointer;">
|
||||
Create account
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p style="font-size:0.9rem;color:#555;margin-top:1.25rem;">
|
||||
Already have an account? <a href="{{ url_for('auth.login') }}">Sign in</a>
|
||||
</p>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user