3a1da1482c
- FastAPI + SQLite + Alpine.js + HTMX PWA for Bingo Bango Bongo golf scoring - Passwordless magic link auth with email (aiosmtplib) and admin invite system - Real-time score entry via WebSocket with drag-and-drop player ordering - Course management with per-hole par and stroke index - Scorecard with golf score markers (birdie, eagle, bogey, etc.) - Two-step new game form with tee selection per player - Dashboard with stats, live games social stream, and recent game history - Game summary view (read-only scorecard with leaderboard) - User profile pages with win stats - PWA install support with generated PNG icons - Admin panel for users, courses, games, and invitations Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
106 lines
4.3 KiB
HTML
106 lines
4.3 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}Dashboard – Skins & Pins{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="page">
|
||
<div class="page-header">
|
||
<h1>
|
||
{% if user.avatar and user.avatar.startswith('/') %}
|
||
<img src="{{ user.avatar }}" alt="avatar" style="width:2rem; height:2rem; border-radius:50%; object-fit:cover; vertical-align:middle;">
|
||
{% elif user.avatar %}
|
||
{{ user.avatar }}
|
||
{% endif %}
|
||
<a href="/users/{{ user.id }}" style="color:inherit; text-decoration:none;">{{ user.name }}</a>
|
||
</h1>
|
||
<p>Handicap {{ user.handicap }}</p>
|
||
</div>
|
||
|
||
{% if games_played > 0 %}
|
||
{# Stats row #}
|
||
<div class="stats-row">
|
||
<div class="stat-card">
|
||
<div class="stat-value">{{ games_played }}</div>
|
||
<div class="stat-label">Games played</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-value">{{ victories }}</div>
|
||
<div class="stat-label">Victories</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-value">
|
||
{{ ((victories / games_played) * 100) | round | int }}%
|
||
</div>
|
||
<div class="stat-label">Win rate</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{# Social stream: active games #}
|
||
{% if active_games %}
|
||
<h2 style="font-size:0.85rem; color:var(--text-muted); margin-bottom:0.6rem; text-transform:uppercase; letter-spacing:0.06em;">Live games</h2>
|
||
{% for game in active_games %}
|
||
<div class="card game-result-card" style="cursor:pointer;" onclick="window.location='/games/{{ game.id }}/summary'">
|
||
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:0.15rem;">
|
||
<div style="font-weight:600;">{{ game.course_name or '—' }}</div>
|
||
{% if game.is_participant %}
|
||
<a href="/games/{{ game.id }}" style="font-size:0.75rem; color:var(--accent); text-decoration:none; font-weight:600;" onclick="event.stopPropagation()">▶ Play</a>
|
||
{% endif %}
|
||
</div>
|
||
<div style="font-size:0.8rem; color:var(--text-muted); margin-bottom:0.5rem;">
|
||
{{ game.format | replace('_', ' ') | title }} · {{ game.holes_count }} holes · {{ game.created_at[:10] }}
|
||
</div>
|
||
{% for p in game.players %}
|
||
<div class="result-row">
|
||
<span class="result-rank">{{ loop.index }}</span>
|
||
<span class="result-avatar">
|
||
{% if p.avatar and p.avatar.startswith('/') %}
|
||
<img src="{{ p.avatar }}" style="width:1.3rem;height:1.3rem;border-radius:50%;object-fit:cover;vertical-align:middle;">
|
||
{% else %}
|
||
{{ p.avatar or '👤' }}
|
||
{% endif %}
|
||
</span>
|
||
<span class="result-name">{{ p.name }}</span>
|
||
<span class="result-pts">{{ p.total | round(1) }} pts</span>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% endfor %}
|
||
{% endif %}
|
||
|
||
{# Recent games #}
|
||
{% if recent_games %}
|
||
<h2 style="font-size:0.85rem; color:var(--text-muted); margin-bottom:0.6rem; text-transform:uppercase; letter-spacing:0.06em;">Recent games</h2>
|
||
{% for game in recent_games %}
|
||
<div class="card game-result-card" style="cursor:pointer;" onclick="window.location='/games/{{ game.id }}/summary'">
|
||
<div style="font-weight:600; margin-bottom:0.15rem;">{{ game.course_name or '—' }}</div>
|
||
<div style="font-size:0.8rem; color:var(--text-muted); margin-bottom:0.5rem;">
|
||
{{ game.format | replace('_', ' ') | title }} · {{ game.holes_count }} holes · {{ game.created_at[:10] }}
|
||
</div>
|
||
{% for r in game.results %}
|
||
<div class="result-row {% if loop.first %}result-winner{% endif %}">
|
||
<span class="result-rank">{% if loop.first %}🏆{% else %}{{ loop.index }}{% endif %}</span>
|
||
<span class="result-avatar">
|
||
{% if r.avatar and r.avatar.startswith('/') %}
|
||
<img src="{{ r.avatar }}" style="width:1.3rem;height:1.3rem;border-radius:50%;object-fit:cover;vertical-align:middle;">
|
||
{% else %}
|
||
{{ r.avatar or '👤' }}
|
||
{% endif %}
|
||
</span>
|
||
<span class="result-name">{{ r.name }}</span>
|
||
<span class="result-pts">{{ r.total | round(1) }} pts</span>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% endfor %}
|
||
{% endif %}
|
||
|
||
{% if not games_played and not active_games %}
|
||
<div class="empty-state">
|
||
<div class="empty-icon">⛳</div>
|
||
<p>No games played yet.</p>
|
||
<a href="/games/new" class="btn btn-primary" style="margin-top:1rem; max-width:200px;">Start a game</a>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endblock %}
|