A club member signing in at ajax-united.rosterchief.app now sees their club's
logo, name and colours; the base domain keeps the RosterChief skin for the
control panel and Django admin.
The mechanism is `{% extends base_template %}` -- Django lets the parent be a
context variable, so the `branding` context processor picks the skin from
request.club and *every* auth screen allauth ships (login, password reset, MFA,
passkeys, and whatever it adds next) follows the tenant without a single one of
them knowing that clubs exist.
Templates split three ways: _base.html is the skeleton with no branding, and
_platform_base.html / _club_base.html dress it. The control panel extends the
platform base *explicitly* rather than through the variable, so a bug in
branding resolution can never dress the panel up as a club.
Club gains an optional logo and primary_color. Notes on both:
- No logo falls back to the club's initials, never the RosterChief mark, which
would pass our branding off as theirs.
- Club colours land in an inline :root. daisyUI declares its theme variables
inside `@layer base`, and unlayered styles beat every layered rule regardless
of specificity, so this needs no !important. --color-primary-content is derived
from WCAG relative luminance, so a club that picks pale yellow gets black text
instead of invisible white.
- primary_color is a text input, not <input type="color">: a colour picker cannot
express "no colour", so every club that never touched it would submit #000000
and silently get a black theme.
"/" now resolves per tenant (club home, or hand off to the control panel), which
is why LOGIN_REDIRECT_URL can stay "/" and allauth needs no redirect adapter.
Also folds in the theme toggle gaining a third "auto" state and the logo
switching from `content:` to background-image (content-replacement on a real
element is not supported in Firefox), both of which lived in the base template
this commit replaces.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Features tab: create/edit flags, flip global switches, and toggle a flag per
club from the club detail page. Where `everyone` is set the per-club toggle is
replaced by a badge, because a toggle there would have no effect and so would
lie about what is on.
Admins tab: grant, promote, demote and revoke platform access. Gated on
is_superuser, not is_staff -- the panel itself is staff-accessible, so letting
staff grant is_superuser would collapse the two levels into one and stop
is_superuser being a boundary we can later hang anything on.
Two guardrails, enforced in the service so they hold regardless of caller:
you cannot strip your own access (you would lose the panel mid-click), and the
last superuser can never be demoted (the platform would be locked out of
itself). Granted users get an unusable password and must enrol 2FA before they
can sign in.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the `controlpanel` app: a platform-wide (not club-scoped) admin panel for
creating clubs, archiving/restoring them, managing club admins, and per-club
statistics (members, teams & staff, events, shop). Statistics are annotated in
one query so the club list cannot fan out into N+1, and are returned as stat
*groups* so growing the domain means adding one entry.
Two access rules, both enforced by PlatformStaffRequiredMixin:
- staff only (is_staff/is_superuser); anonymous are sent to login, signed-in
non-staff get a 403. Staff already need a second factor, so the panel is
2FA-protected for free.
- base domain only: the panel manages *all* clubs, so it 404s if the tenant
middleware resolved a club from the subdomain.
Granting admin to an unknown email creates the account (unusable password —
they set one via password reset) and the Member behind it, since a ClubRole
hangs off a Member. A member who already holds a role is promoted in place,
because there is only one role per member per club.
UI is Tailwind + daisyUI. allauth ships an element system, so overriding
allauth/layouts/base.html plus ~13 element partials restyles *every* auth and
2FA screen at once — login, signup, password reset, the 2FA challenge, TOTP
enrolment, passkeys and recovery codes — rather than templating 20+ pages.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>