Add Skins format, GolfCourseAPI import, and UX improvements

- Add Skins game format with optional carryover, mirroring BBB scoring pattern
- Add GolfCourseAPI integration for course search and import (httpx)
- Unwrap {"course": {}} API response envelope on import
- Add searchable course combobox and player filter to new game form
- Redirect to summary page after finishing a game
- Fix auth to allow registered users without a pending invitation
- Fix hardcoded BBB format in create_game; store carryover flag in DB
- Add game summary/scorecard view for completed games
- Add live games social stream to dashboard
- Add course name + subtitle to recent game cards
- Reorder nav: Home → Games → Admin → Profile

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Rolf
2026-03-22 09:18:04 +01:00
parent ef22436c76
commit c51c872c33
12 changed files with 554 additions and 46 deletions
+86
View File
@@ -0,0 +1,86 @@
{% extends "base.html" %}
{% block title %}Search Courses Skins &amp; Pins{% endblock %}
{% block content %}
<div class="page">
<div class="page-header">
<h1>⚙️ Admin</h1>
</div>
<div class="admin-subnav">
<a href="/admin" class="admin-subnav-item">Users</a>
<a href="/admin/courses" class="admin-subnav-item active">Courses</a>
<a href="/admin/games" class="admin-subnav-item">Games</a>
</div>
<div style="display:flex; gap:0.5rem; justify-content:flex-end; margin-bottom:1rem;">
<a href="/admin/courses" class="btn" style="width:auto; padding:0.5rem 1rem; background:var(--surface2); color:var(--text);">← Courses</a>
<a href="/admin/courses/new" class="btn btn-primary" style="width:auto; padding:0.5rem 1rem;">+ Manual</a>
</div>
{% if not api_configured %}
<div class="alert alert-error">
<strong>GOLF_COURSE_API_KEY is not configured.</strong>
Add it to your <code>.env</code> file to enable course search.
Get a key at <a href="https://www.golfcourseapi.com" target="_blank" style="color:inherit;">golfcourseapi.com</a>.
</div>
{% endif %}
<div class="card">
<form method="get" action="/admin/courses/search" style="display:flex; gap:0.5rem;">
<input type="text" name="q" value="{{ q }}" placeholder="Search by course or club name…"
style="flex:1;" {% if not api_configured %}disabled{% endif %} autofocus>
<button type="submit" class="btn btn-primary" style="width:auto; padding:0.65rem 1.25rem;"
{% if not api_configured %}disabled{% endif %}>Search</button>
</form>
</div>
{% if error %}
<div class="alert alert-error" style="margin-top:0.75rem;">{{ error }}</div>
{% endif %}
{% if q and results is not none %}
{% if results %}
<p style="font-size:0.85rem; color:var(--text-muted); margin:0.75rem 0 0.4rem;">
{{ results | length }} result{{ 's' if results | length != 1 else '' }} for "<strong>{{ q }}</strong>"
</p>
<div class="card" style="padding:0;">
<table class="data-table" style="margin:0;">
<thead>
<tr><th>Club</th><th>Course</th><th>Location</th><th></th></tr>
</thead>
<tbody>
{% for c in results %}
{% set loc = c.location or {} %}
{% if loc.state and loc.country == 'United States' %}
{% set location_str = (loc.city or '') + ', ' + loc.state %}
{% elif loc.city and loc.country %}
{% set location_str = loc.city + ', ' + loc.country %}
{% else %}
{% set location_str = '' %}
{% endif %}
<tr>
<td style="font-weight:500;">{{ c.club_name or '—' }}</td>
<td style="color:var(--text-muted);">
{% if c.course_name and c.course_name != c.club_name %}{{ c.course_name }}{% else %}—{% endif %}
</td>
<td style="color:var(--text-muted);">{{ location_str or '—' }}</td>
<td>
<form method="post" action="/admin/courses/import/{{ c.id }}">
<button type="submit" class="btn-table-action">Import</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="empty-state" style="padding:2rem 0;">
<div class="empty-icon">🔍</div>
<p>No courses found for "<strong>{{ q }}</strong>".</p>
</div>
{% endif %}
{% endif %}
</div>
{% endblock %}