Scaffold the mobile member app: PWA shell, push, and app-shell tokens

New `mobile` Django app mounted at /app/ -- the installed PWA for Member
mode (M1-M7, see design_handoff_rosterchief_platform/README.md). Coach
mode (C1-C6) is a later phase and has no routes yet.

Foundation pieces:
- assets/mobile.css: Tailwind v4 theme reusing management.css's design
  tokens (same per-club --tenant-* theming pattern), plus the ice/coach
  accent and mobile's 14px card radius.
- PushSubscription model + pywebpush-based sender (mobile/services/push.py),
  wired to notifications.Notification via a post_save signal so the
  existing notification system gains a push channel without knowing about
  PWAs itself.
- Per-club manifest.webmanifest + service worker (served at /app/sw.js)
  + a server-rendered fallback home-screen icon (club initials on
  secondary_color) for clubs without an uploaded logo -- confirmed with
  the user as the fallback, never a generic RosterChief mark.
- App shell (base.html): navy header, person switcher (every child a
  signed-in parent manages, plus "Me"), bottom tab bar, safe-area insets.
  Vendored htmx + Alpine for the screens built on top of it.
- Placeholder views/routes for all seven M1-M7 screens so the shell is
  fully wired end-to-end before each screen is built out individually.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
2026-08-21 10:27:13 +02:00
parent 0ecdeac354
commit 09df5d25b8
29 changed files with 1657 additions and 6 deletions

View File

@@ -78,8 +78,11 @@ INSTALLED_APPS = [
"features.apps.FeaturesConfig",
"controlpanel.apps.ControlpanelConfig",
# Club-facing UI for team managers, coaches and admins -- not controlpanel (platform
# staff managing every club) and not the future parent/player app.
# staff managing every club) and not the mobile member/parent app below.
"management.apps.ManagementConfig",
# The installed PWA -- Member mode first (see design_handoff_rosterchief_platform/
# README.md); Coach mode is a later phase, deliberately not built yet.
"mobile.apps.MobileConfig",
# Public, read-only JSON API for a club's own external website -- see api/urls.py.
"api.apps.ApiConfig",
]
@@ -418,6 +421,15 @@ SERVER_EMAIL = config("DJANGO_SERVER_EMAIL", default=DEFAULT_FROM_EMAIL)
#: Where a club admin is told to direct a billing question. Shown in reminder emails.
BILLING_CONTACT_EMAIL = config("ROSTERCHIEF_BILLING_CONTACT_EMAIL", default=DEFAULT_FROM_EMAIL)
# Web Push (mobile app, see mobile/services/push.py) -- one VAPID keypair for the whole
# platform, not per club: it identifies the *sender* (RosterChief) to a browser's push
# service, the same way DEFAULT_FROM_EMAIL identifies the sender of an email, and has
# nothing to do with club branding. Generate a pair with `uv run vapid --gen`. Blank in
# dev is fine -- mobile.services.push.send_push_to_member no-ops without a private key.
VAPID_PUBLIC_KEY = config("DJANGO_VAPID_PUBLIC_KEY", default="")
VAPID_PRIVATE_KEY = config("DJANGO_VAPID_PRIVATE_KEY", default="")
VAPID_ADMIN_EMAIL = config("DJANGO_VAPID_ADMIN_EMAIL", default="admin@rosterchief.app")
# Phone numbers (django-phonenumber-field)

View File

@@ -34,6 +34,7 @@ urlpatterns = [
path("", include("members.urls")),
path("controlpanel/", include("controlpanel.urls")),
path("manage/", include("management.urls")),
path("app/", include("mobile.urls")),
path("api/v1/", api.urls),
# "/" resolves per tenant: a club subdomain lands on the club, the base domain
# hands off to the control panel. This is why LOGIN_REDIRECT_URL can stay "/".