docs(product): spec CMS public landing page (Phase 13)
Splash owns /, catalogue moves to /catalogue, authed users redirected via HierarchicalRoleAuthorizeView. Skipper's public-layout pattern, branded to DeepDrft. Adds Phase 13 to PLAN.md.
This commit is contained in:
@@ -387,6 +387,44 @@ a lava lamp on the landing page is no surprise.
|
||||
|
||||
---
|
||||
|
||||
## Phase 13 — CMS Public Landing
|
||||
|
||||
Give `DeepDrftManager` (the CMS) a true public face: an unauthenticated splash at `/` with DeepDrft
|
||||
branding and a single **Login** CTA; authenticated admins are redirected past it to the catalogue.
|
||||
Today `/` is the `[Authorize]`-gated catalogue, so an anonymous hit falls straight through to the login
|
||||
form — there is no front door. Pattern borrowed from Skipper's `MainHomeLayout` / `Home.razor`
|
||||
(dedicated public layout + `HierarchicalRoleAuthorizeView` redirect-the-authed-user idiom), branded to
|
||||
the DeepDrft navy/green/off-white identity (`DeepDrftPalettes.Cms`), **not** Skipper's nautical look.
|
||||
Additive — the admin experience is intact; only the catalogue's *route* moves. Full spec, routing-reshape
|
||||
rationale, file responsibilities, hero/CTA composition, asset path, and acceptance criteria:
|
||||
`product-notes/cms-public-landing.md`.
|
||||
|
||||
**Routing decision (recommended, spec §2): Option A — splash owns `/`, catalogue moves to `/catalogue`.**
|
||||
A new `Home.razor` (`@page "/"`, no `[Authorize]`, new `CmsHomeLayout`) wraps its body in
|
||||
`<HierarchicalRoleAuthorizeView>`: `Authorized` → redirect to `/catalogue`; `NotAuthorized` → hero +
|
||||
Login CTA. `Index.razor` changes `@page "/"` → `@page "/catalogue"` (one-line; otherwise untouched).
|
||||
`Routes.razor` and `Program.cs` need no change — the host is already `AllowAnonymous` at the endpoint and
|
||||
page auth is owned by `AuthorizeRouteView`, so a no-`[Authorize]` page renders for everyone. The entire
|
||||
cost of Option A is a small, mechanical link-repoint (every internal `/` that meant *catalogue* →
|
||||
`/catalogue`; spec §6). Options B (layout-by-auth-state, conflates page concerns) and C (splash at a
|
||||
side route, defeats the goal) were weighed and rejected.
|
||||
|
||||
- **New files:** `Components/Layout/CmsHomeLayout.razor` (lean public layout — same `DeepDrftPalettes.Cms`
|
||||
theme, front-door AppBar, centered narrow `MudContainer`), `Components/Pages/Home.razor` (the splash),
|
||||
`Components/RedirectToCatalogue.razor` (mirrors existing `RedirectToAccessDenied`; inline redirect
|
||||
acceptable). **Changed:** `Index.razor` route line; `CmsLayout.razor` "Back to site" `Href="/"` →
|
||||
`/catalogue`.
|
||||
- **Hero asset (Daniel-supplied):** `DeepDrftManager/wwwroot/img/cms-hero.png` (creates the `wwwroot/img/`
|
||||
dir, net-new); referenced `Src="img/cms-hero.png"`. The page must compile/render without it.
|
||||
- **No new shared component, no new palette, no `@rendermode` override, no Register CTA** (CMS is
|
||||
invite/seed-only — single Login button). DRY/MudBlazor-first throughout; the only bespoke CSS is the
|
||||
layout's one viewport `min-height`.
|
||||
- **One open copy question for Daniel (non-structural):** AppBar/title wording — "Deep Drft" vs.
|
||||
"Deep Drft — Admin" (spec §4 recommends "Deep Drft — Admin" in the bar, "Deep Drft" as the hero title).
|
||||
Implementable either way without rework.
|
||||
|
||||
---
|
||||
|
||||
## Working with this file
|
||||
|
||||
- **Add items by extending an existing phase first**; only create a new phase when the addition genuinely doesn't fit any of 1–5. Phase numbers are organisational, not sequencing.
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
# CMS Public Landing — a true public face for DeepDrftManager — Design Spec
|
||||
|
||||
Status: **proposed** (2026-06-17). Author: product-designer. Date: 2026-06-17.
|
||||
Implementer: staff-engineer. Reference pattern: Skipper `Home.razor` / `MainHomeLayout.razor`.
|
||||
|
||||
## 0. Goal
|
||||
|
||||
Give `DeepDrftManager` (the CMS) a public face: an unauthenticated splash at `/` with DeepDrft
|
||||
branding and a single clear **Login** call-to-action. Authentication happens beyond it. Today an
|
||||
anonymous hit to `/` falls straight through to the login form (the catalogue page at `/` carries
|
||||
`[Authorize]`, so `AuthorizeRouteView` redirects). After this change, an anonymous visitor sees a
|
||||
composed, on-brand splash; an authenticated admin is sent onward to the catalogue without ever seeing
|
||||
the splash.
|
||||
|
||||
This is **additive**. The authenticated admin experience (catalogue dashboard, `CmsLayout`, all CMS
|
||||
pages) is preserved unchanged except for the catalogue's route moving off `/`.
|
||||
|
||||
---
|
||||
|
||||
## 1. What exists today (confirmed from live source 2026-06-17)
|
||||
|
||||
- **`Components/Pages/Index.razor`** — `@page "/"`, `@attribute [Authorize]`, `@layout
|
||||
Layout.CmsLayout`. This **is** the authenticated catalogue dashboard (Tracks / Releases / Genres
|
||||
summary cards, each loading a count concurrently). No public face exists.
|
||||
- **`Components/Routes.razor`** — `AuthorizeRouteView` with `DefaultLayout = Layout.CmsLayout`.
|
||||
`NotAuthorized`: authenticated-but-unauthorized → `RedirectToAccessDenied`; unauthenticated →
|
||||
`RedirectToLogin` (the AuthBlocksWeb component, not a Manager-local one). `AdditionalAssemblies`
|
||||
already includes `typeof(AuthBlocksWeb._Imports).Assembly`.
|
||||
- **`Components/Layout/CmsLayout.razor`** — `MudThemeProvider IsDarkMode="false" Theme="DeepDrftPalettes.Cms"`
|
||||
(light-only, solid navy AppBar). Dense AppBar "Deep Drft — Admin" + a "Back to site" home button
|
||||
(`Href="/"`). Providers (Popover/Dialog/Snackbar) live here.
|
||||
- **`Program.cs`** — `MapRazorComponents<App>().AddInteractiveServerRenderMode()...AllowAnonymous()`.
|
||||
Endpoint auth is anonymous by design (JWT lives in browser localStorage, never reaches the server on
|
||||
a nav); **page authorization is owned entirely by `AuthorizeRouteView`**. Consequence: a routable
|
||||
page with **no `[Authorize]` attribute renders for everyone**, no server 401, no redirect. This is
|
||||
the seam the splash uses.
|
||||
- **Auth components available** (from `Cerebellum.AuthBlocks.Web`, assembly already wired into the
|
||||
router): `[HierarchicalRoleAuthorize]` attribute and `<HierarchicalRoleAuthorizeView>` view
|
||||
component. Bare = "requires authenticated"; with roles = walks the role hierarchy.
|
||||
- **No `wwwroot/img/` directory exists** in `DeepDrftManager` today. The host's only wwwroot asset is
|
||||
`app.css`. The hero asset and its folder are net-new.
|
||||
- **Theme** — `DeepDrftPalettes.Cms` (in `DeepDrftShared.Client.Common`): navy `#0D1B2A` ground /
|
||||
green-accent `#3D7A68` / warm off-white `#FAFAF8`; typography Cormorant Garamond (display) / Geist
|
||||
Mono (`Subtitle1`/`Button`/`Caption`) / DM Sans (body). This is the DeepDrft identity — **do not
|
||||
import Skipper's nautical styling.** Borrow Skipper's *structure*, not its look.
|
||||
|
||||
### What we borrow from Skipper (pattern, not pixels)
|
||||
|
||||
Skipper splits a public **`MainHomeLayout`** (app bar with logo + dark toggle, centered
|
||||
`MudContainer`) from the authenticated app layout, routes both at `/`, and inside `Home.razor` wraps
|
||||
the splash body in `<HierarchicalRoleAuthorizeView>`: `Authorized` → `<RedirectToDashboard/>`,
|
||||
`NotAuthorized` → hero image + title + subtitle + Login/Register buttons in a centered `MudStack`.
|
||||
We adopt: (a) a **dedicated public layout** distinct from `CmsLayout`, (b) the
|
||||
**`HierarchicalRoleAuthorizeView` redirect-the-authed-user** idiom at `/`, (c) the **centered hero +
|
||||
single CTA** composition. We diverge on branding and drop Register (CMS access is invite/seed-only —
|
||||
there is no public registration path; see §4).
|
||||
|
||||
---
|
||||
|
||||
## 2. Routing reshape — the load-bearing decision
|
||||
|
||||
**Recommendation: Option A — splash owns `/`, catalogue moves to `/catalogue`, authed users are
|
||||
redirected onward from the splash via `HierarchicalRoleAuthorizeView`.**
|
||||
|
||||
This is the Skipper pattern transplanted directly, and it is the right one here. Three options were
|
||||
weighed:
|
||||
|
||||
### Option A (recommended) — splash at `/`, catalogue at `/catalogue`, redirect authed from `/`
|
||||
|
||||
- New `Home.razor` takes `@page "/"`, **no `[Authorize]`**, public layout. Body wrapped in
|
||||
`<HierarchicalRoleAuthorizeView>`: `Authorized` → redirect to `/catalogue`; `NotAuthorized` → the
|
||||
hero + Login CTA.
|
||||
- `Index.razor` (the catalogue) changes its route from `@page "/"` to `@page "/catalogue"`,
|
||||
everything else unchanged (keeps `[Authorize]` + `CmsLayout`).
|
||||
- **Pros:** Mirrors Skipper exactly (proven). `/` is a clean public URL. Anonymous and authed users
|
||||
share one entry point; the redirect is declarative and lives next to the splash it guards. No
|
||||
`Routes.razor` change. The catalogue page is otherwise untouched.
|
||||
- **Cons:** The catalogue's URL changes — internal links to `/` that *meant* "the dashboard" must
|
||||
repoint to `/catalogue` (see §6 migration checklist; the live count is small — `CmsLayout`'s
|
||||
"Back to site" button and any nav). One new redirect component (`RedirectToCatalogue`) or an inline
|
||||
`NavigationManager` redirect.
|
||||
|
||||
### Option B — splash at `/`, catalogue stays at `/`, layout chosen by auth state in `Routes.razor`
|
||||
|
||||
Skipper's *other* mechanism: `Routes.razor` computes `MainHomeLayout` vs app layout from auth state in
|
||||
`OnParametersSetAsync`, and both the splash and catalogue claim `@page "/"` is **not** possible (route
|
||||
collision). To keep the catalogue at `/`, the splash would have to be conditionally rendered *inside*
|
||||
the catalogue page or the layout — collapsing two concerns into one component.
|
||||
|
||||
- **Pros:** Catalogue URL never changes; zero link migration.
|
||||
- **Cons:** Forces splash + dashboard logic into one routable component or bleaks auth-branching into
|
||||
the layout. Violates the project's preference for one-source/clean seams. The splash is structurally
|
||||
a *different page* with a *different layout*; conflating them is the wrong abstraction. Rejected.
|
||||
|
||||
### Option C — splash at a distinct route (`/welcome`), `/` unchanged
|
||||
|
||||
Splash lives at `/welcome`; `/` keeps the catalogue with `[Authorize]`, so anonymous `/` still
|
||||
redirects to login (today's behavior), and nothing lands on the splash unless explicitly routed there.
|
||||
|
||||
- **Pros:** Smallest diff; no link migration.
|
||||
- **Cons:** Defeats the goal. The "public face" is the thing an anonymous visitor sees when they
|
||||
arrive at the app root. A splash nobody routes to is dead. Rejected.
|
||||
|
||||
**Why A over the rest:** the goal is *"`/` is the public face."* That demands the splash own `/`,
|
||||
which demands the catalogue move. The redirect-authed-user idiom keeps the admin's experience
|
||||
seamless (they never see the splash). Option A is the only one that satisfies the goal without
|
||||
conflating page concerns. The link-migration cost is real but small and mechanical.
|
||||
|
||||
### Where AuthBlocks fits
|
||||
|
||||
- The splash body is wrapped in **`<HierarchicalRoleAuthorizeView>`** (bare, no roles — "is this
|
||||
visitor authenticated at all?").
|
||||
- `Authorized` slot → redirect to `/catalogue` (mirrors Skipper's `RedirectToDashboard`). The CMS
|
||||
further gates the catalogue on the `Admin` role via the existing `[Authorize]` /
|
||||
`AuthorizeRouteView` path — so a logged-in non-admin still hits `RedirectToAccessDenied` at
|
||||
`/catalogue`, which is correct and unchanged.
|
||||
- `NotAuthorized` slot → the hero + Login CTA.
|
||||
- The Login button links to **`/account/login?returnUrl={Uri.EscapeDataString("catalogue")}`** so a
|
||||
successful login lands the admin on the catalogue, not back on the splash. (Skipper uses
|
||||
`returnUrl=dashboard`; same idiom, our route.)
|
||||
- `Routes.razor` needs **no change** — its `AuthorizeRouteView` already renders no-`[Authorize]` pages
|
||||
for anonymous users, and the host is already `AllowAnonymous` at the endpoint.
|
||||
|
||||
---
|
||||
|
||||
## 3. New + changed files and their responsibilities
|
||||
|
||||
### 3.1 New: `Components/Layout/CmsHomeLayout.razor` (public splash layout)
|
||||
|
||||
A public counterpart to `CmsLayout`, deliberately lighter. Responsibilities:
|
||||
|
||||
- Own its **own `MudThemeProvider`** — `IsDarkMode="false" Theme="DeepDrftPalettes.Cms"` (same theme
|
||||
as the admin surface, so the splash is of-a-piece with the CMS, navy AppBar and all). Splash is
|
||||
light-only like the rest of the CMS; no dark toggle (the CMS has none today — do not introduce one
|
||||
here).
|
||||
- Own a **minimal provider set**: include `MudPopoverProvider` (tooltips on the splash, if any need
|
||||
it) and skip `MudDialogProvider` / `MudSnackbarProvider` unless a splash interaction needs them
|
||||
(it does not in this spec — omit them to keep the layout lean).
|
||||
- A **slim AppBar** consistent with `CmsLayout`'s navy bar but read as a *front door*, not an admin
|
||||
console: brand text "Deep Drft" (or "Deep Drft — Admin" to set expectation; see §4 open question),
|
||||
`Typo.h6`, DM-Sans, letter-spacing as in `CmsLayout`. No "Back to site" home button (the splash
|
||||
*is* the front door). No nav drawer.
|
||||
- `MudMainContent` wrapping a centered `MudContainer MaxWidth="Small"` (the hero is a focused column,
|
||||
not full-bleed). Vertical centering via MudBlazor utility classes (`d-flex`, `flex-column`,
|
||||
`justify-center`, `align-center`) and a min-height tied to the viewport (`Style="min-height:
|
||||
calc(100vh - <appbar>);"` or the `mud-height-full` utility on the content) so the hero sits centered
|
||||
in the page, not jammed at the top. Prefer MudBlazor utilities; the single inline `min-height` is the
|
||||
one justified bespoke style (no utility expresses "viewport minus app bar" cleanly).
|
||||
- Include the `<div id="blazor-error-ui">` block (copy from `CmsLayout`) so the InteractiveServer
|
||||
error UI is present on the public surface too.
|
||||
|
||||
**Why a separate layout rather than reusing `CmsLayout`:** `CmsLayout` is built for the admin console
|
||||
(full-width container `MaxWidth.False`, "Back to site" affordance, dialog/snackbar providers for CRUD
|
||||
flows). The splash wants a centered narrow column and a front-door AppBar. Forcing one layout to do
|
||||
both would mean auth-branching inside the layout — the same conflation Option B was rejected for.
|
||||
This mirrors Skipper's `MainHomeLayout` vs app-layout split exactly.
|
||||
|
||||
### 3.2 New: `Components/Pages/Home.razor` (the splash page)
|
||||
|
||||
- `@page "/"`, `@layout Layout.CmsHomeLayout`, **no `[Authorize]`**.
|
||||
- `<PageTitle>Deep Drft — Admin</PageTitle>` (or "Deep Drft" — see §4).
|
||||
- Body = `<HierarchicalRoleAuthorizeView>` with the two slots from §2 (`Authorized` → redirect to
|
||||
`/catalogue`; `NotAuthorized` → hero + CTA per §5).
|
||||
- No data fetch, no injected services beyond `NavigationManager` (only if doing an inline redirect
|
||||
rather than a `RedirectToCatalogue` component). Keep it presentational.
|
||||
|
||||
### 3.3 New: `Components/RedirectToCatalogue.razor` (optional, recommended)
|
||||
|
||||
Mirrors the existing `RedirectToAccessDenied.razor` exactly (inject `NavigationManager`, redirect in
|
||||
`OnInitialized`). Target `/catalogue`. Use `NavigateTo("/catalogue", forceLoad: false)` —
|
||||
client-side nav is fine here (unlike Skipper's `RedirectToDashboard` which uses `forceLoad: true`;
|
||||
that was a Skipper-specific concern. Default to no force-load and let staff-engineer confirm against
|
||||
the JWT-in-localStorage auth-state timing — if the authed redirect mis-fires on first paint, escalate
|
||||
to `forceLoad: true` as Skipper does). Keeping this as a named component matches the existing
|
||||
`RedirectToAccessDenied` convention; an inline redirect in `Home.razor` is acceptable if staff-engineer
|
||||
prefers fewer files.
|
||||
|
||||
### 3.4 Changed: `Components/Pages/Index.razor` (the catalogue)
|
||||
|
||||
- **One-line change:** `@page "/"` → `@page "/catalogue"`. Everything else (the `[Authorize]`,
|
||||
`CmsLayout`, the three summary cards, the concurrent loaders) is unchanged.
|
||||
- Optional rename for clarity (`Index.razor` → `Catalogue.razor`): **defer.** Not required; a rename
|
||||
ripples through `ILogger<Index>` and any references. Out of scope for this spec — flag only if
|
||||
staff-engineer is already touching those lines.
|
||||
|
||||
### 3.5 Changed: `Components/Layout/CmsLayout.razor` (the "Back to site" button)
|
||||
|
||||
- The "Back to site" home button currently has `Href="/"`. Post-reshape, `/` is the **public splash**,
|
||||
not the catalogue. Two readings:
|
||||
- If "Back to site" means *the admin home / catalogue*, repoint to `Href="/catalogue"`.
|
||||
- If "Back to site" means *the public DeepDrft site* (i.e., `DeepDrftPublic`), it should point at the
|
||||
public site's URL, not anything in Manager — but that cross-host URL isn't configured in the CMS
|
||||
today, so this is likely **not** the intent.
|
||||
- **Recommendation:** repoint to `/catalogue` (the in-CMS home). The tooltip text "Back to site" is
|
||||
then slightly off — consider "Catalogue" or "Home". Staff-engineer's call on wording; the *Href*
|
||||
must change to `/catalogue` regardless, or the admin's home button lands them on the splash, which
|
||||
then bounces them back to `/catalogue` via the authed-redirect — functional but an ugly double-hop.
|
||||
|
||||
---
|
||||
|
||||
## 4. Branding, copy, and the no-Register decision
|
||||
|
||||
- **Identity:** DeepDrft navy/green/off-white via `DeepDrftPalettes.Cms`. Display type Cormorant
|
||||
Garamond (it comes free from the theme's `Typo.h2`/`h3` etc. — do **not** hand-set font-family in
|
||||
markup; use `Typo` and let the theme resolve it). Body DM Sans, button/caption Geist Mono — again,
|
||||
theme-resolved.
|
||||
- **No Register CTA.** Skipper offers Register (marina staff self-onboard with a code). The DeepDrft
|
||||
CMS is gated by AuthBlocks login + hierarchical `Admin` role, seeded/invited — there is no public
|
||||
registration page in `DeepDrftManager`, and AuthBlocksWeb's registration (if present) is not a path
|
||||
we want to advertise on the front door of an admin tool. **Single Login CTA only.** If Daniel later
|
||||
wants an invite-code flow, that is a separate feature, not part of this splash.
|
||||
- **Copy (proposed, Daniel may revise):**
|
||||
- Title: **"Deep Drft"** (Cormorant, `Typo.h2`, the brand) — or stylized **"DEEP DRFT"** uppercase
|
||||
to echo Skipper's `SKIPPER` treatment. Recommend brand-cased "Deep Drft" to match the public
|
||||
site's wordmark rather than Skipper's all-caps.
|
||||
- Subtitle: **"Catalogue Management"** or **"Collective Content Management"** (Geist Mono,
|
||||
`Typo.subtitle1`, uppercase via `text-uppercase`, muted/secondary) — names what's behind the door
|
||||
without overselling. Recommend **"Catalogue Management"**.
|
||||
- CTA button label: **"Login"**.
|
||||
- **Open question for Daniel (one):** should the AppBar + title say **"Deep Drft"** (front-door, clean)
|
||||
or **"Deep Drft — Admin"** (sets the expectation that this is the admin tool, consistent with
|
||||
`CmsLayout`'s AppBar)? Recommend **"Deep Drft — Admin"** in the AppBar for continuity with the
|
||||
authenticated surface, and **"Deep Drft"** as the large hero title. This is a copy call, not a
|
||||
structural one — staff-engineer can implement either without rework.
|
||||
|
||||
---
|
||||
|
||||
## 5. Hero + CTA composition
|
||||
|
||||
A centered, single-column hero in `MudContainer MaxWidth="Small"`, vertically centered in the
|
||||
viewport. Composition top-to-bottom inside a `MudStack AlignItems="AlignItems.Center" Spacing="4"`:
|
||||
|
||||
1. **Hero image** — `MudImage Fluid="true" Src="img/cms-hero.png"` (asset path §5.1). Constrain its
|
||||
width with the container (`MaxWidth="Small"`) so it reads as a focused emblem, not a full-bleed
|
||||
banner. If Daniel's asset is wide/cinematic rather than emblem-shaped, wrap it in a
|
||||
`MudPaper`-backed rounded region with `Style="border-radius: 8px; overflow: hidden;"` — but default
|
||||
to the plain `MudImage` and let the asset dictate (see §5.1 open note).
|
||||
2. **Title** — `MudText Typo="Typo.h2"` "Deep Drft" (Cormorant via theme), centered
|
||||
(`Align="Align.Center"`).
|
||||
3. **Subtitle** — `MudText Typo="Typo.subtitle1"` "Catalogue Management" (Geist Mono via theme),
|
||||
`Class="text-uppercase mud-text-secondary"`, centered.
|
||||
4. **Spacer** — `MudSpacer` or a `Class="my-4"` gap.
|
||||
5. **Login CTA** — `MudButton Variant="Variant.Filled" Color="Color.Primary"
|
||||
Href="/account/login?returnUrl=..."` (see §2), full-width within a `MaxWidth.Small` inner region or
|
||||
a fixed comfortable width (`Style="min-width: 200px;"`). Label "Login". Single button, centered.
|
||||
|
||||
This is the Skipper hero stack (image / title / subtitle / spacer / CTA) with the Register row and the
|
||||
two-column button grid removed (one CTA needs no grid). No bespoke CSS beyond the optional rounded-image
|
||||
wrapper and the layout's one `min-height` — everything else is MudBlazor `Typo`, `Color`, and spacing
|
||||
utilities.
|
||||
|
||||
### 5.1 Hero asset path and reference
|
||||
|
||||
- **Asset location:** `DeepDrftManager/wwwroot/img/cms-hero.png` (Daniel supplies the file). This
|
||||
creates the `wwwroot/img/` directory, which does not exist today — staff-engineer creates the folder
|
||||
when the asset is dropped in.
|
||||
- **Reference in markup:** `Src="img/cms-hero.png"` (relative; `MapStaticAssets()` in `Program.cs`
|
||||
serves `wwwroot/`). Matches Skipper's `Src="img/skipper-hero.png"` idiom exactly. Do **not** wrap in
|
||||
`@Assets[...]` / fingerprinting unless the project's static-asset fingerprinting convention requires
|
||||
it for cache-busting — for a single rarely-changing hero, the plain relative path is fine and matches
|
||||
the public site's `img/...` references.
|
||||
- **Filename:** `cms-hero.png` recommended. If Daniel's asset is a JPEG, name it `cms-hero.jpg` and
|
||||
update the `Src`. One asset, one reference — keep it boring.
|
||||
- **Open note (non-blocking):** the spec assumes an emblem/portrait-ish hero suited to a centered
|
||||
narrow column (Skipper uses a boat illustration). If Daniel's asset is a wide cinematic photo, the
|
||||
composition still works but consider `MaxWidth="Medium"` on the container and the rounded-paper wrap
|
||||
from §5.1. Staff-engineer adjusts container width to the asset; no design rework needed.
|
||||
|
||||
---
|
||||
|
||||
## 6. Migration checklist (link repointing)
|
||||
|
||||
Every internal reference that meant "the catalogue lived at `/`" must move to `/catalogue`:
|
||||
|
||||
1. **`CmsLayout.razor`** "Back to site" button `Href="/"` → `Href="/catalogue"` (§3.5).
|
||||
2. **`Index.razor`** summary-card "View" buttons → already navigate to `/tracks`, unaffected.
|
||||
3. **Any nav menu / breadcrumb / link in CMS components pointing at `/`** as "home/dashboard" →
|
||||
`/catalogue`. staff-engineer: grep `DeepDrftManager` for `Href="/"` and `NavigateTo("/")` /
|
||||
`NavigateTo("/", ` and repoint the ones that mean *catalogue* (leave any that genuinely mean *the
|
||||
public front door* pointing at `/`).
|
||||
4. **Login `returnUrl`** anywhere it defaults to the old root → `catalogue`.
|
||||
|
||||
This grep-and-repoint is the entire cost of Option A. It is mechanical and small (the CMS is a compact
|
||||
host), but it must be exhaustive or an admin lands on the splash and double-hops.
|
||||
|
||||
---
|
||||
|
||||
## 7. Constraints honored
|
||||
|
||||
- **DRY / MudBlazor-first:** reuses `DeepDrftPalettes.Cms` (no new palette), reuses the
|
||||
`RedirectToAccessDenied` redirect idiom for `RedirectToCatalogue`, reuses `HierarchicalRoleAuthorizeView`
|
||||
(no new auth machinery), composes from `MudAppBar`/`MudContainer`/`MudStack`/`MudText`/`MudImage`/
|
||||
`MudButton`. Bespoke CSS limited to one layout `min-height` and an optional rounded-image wrapper.
|
||||
- **No new shared component worth extracting.** The splash is host-specific (CMS front door); it does
|
||||
not belong in `DeepDrftShared.Client`. The public site's hero idiom (`ReleaseHeroOverlay`,
|
||||
full-bleed background + overlaid metadata) is a *different* composition (content hero, not a login
|
||||
gate) — do not try to reuse it here; the shapes don't match and forcing it would be the wrong borrow.
|
||||
If a *second* gated host ever needs a login splash, revisit extracting a `LoginSplash` shared
|
||||
component then — not now (YAGNI; one consumer).
|
||||
- **Render mode:** no explicit `@rendermode` override anywhere — the host's global
|
||||
`AddInteractiveServerRenderMode()` governs, per DeepDrftManager convention. The splash is static
|
||||
presentational content + one auth-view + links; it needs no interactivity of its own, but inherits
|
||||
the host render mode without an override (correct).
|
||||
- **Admin experience intact:** only the catalogue's *route* changes; its page, layout, and behavior are
|
||||
untouched. Authed admins are redirected past the splash and never see it.
|
||||
|
||||
---
|
||||
|
||||
## 8. Acceptance criteria
|
||||
|
||||
1. An **anonymous** visitor navigating to `/` sees the splash: navy CMS-themed front-door AppBar,
|
||||
centered hero image (from `wwwroot/img/cms-hero.png`), "Deep Drft" title (Cormorant), a
|
||||
"Catalogue Management" subtitle (Geist Mono, uppercase, muted), and a single filled "Login" button.
|
||||
No redirect to the login form occurs; the splash renders.
|
||||
2. Clicking **Login** navigates to `/account/login` with `returnUrl` resolving to `/catalogue`; a
|
||||
successful login lands the admin on the catalogue.
|
||||
3. An **authenticated admin** navigating to `/` is redirected to `/catalogue` (the catalogue
|
||||
dashboard) without the splash flashing/persisting.
|
||||
4. An **authenticated non-admin** (logged in, lacking `Admin` role) hitting `/` is redirected toward
|
||||
`/catalogue` and then to access-denied via the existing `AuthorizeRouteView` path — behavior
|
||||
unchanged from today's catalogue gating.
|
||||
5. The **catalogue dashboard** is reachable at `/catalogue`, renders under `CmsLayout` with
|
||||
`[Authorize]`, and its three summary cards load as before. No regression.
|
||||
6. `CmsLayout`'s home/"Back to site" button lands an admin on `/catalogue`, not on the splash.
|
||||
7. The splash uses the **shared `DeepDrftPalettes.Cms` theme** (navy AppBar, off-white ground) — it
|
||||
reads as the same product as the admin console, not as Skipper and not as a generic MudBlazor page.
|
||||
8. No `@rendermode` override is introduced; no new palette is introduced; no new entry appears in
|
||||
`DeepDrftShared.Client`.
|
||||
9. (If `cms-hero.png` is absent at build time) the page still compiles and renders — only the image is
|
||||
broken. The splash must not hard-depend on the asset to function (it is an `<img>` `src`, so this
|
||||
holds by construction).
|
||||
|
||||
---
|
||||
|
||||
## 9. Out of scope / deferred
|
||||
|
||||
- **Register / invite-code flow** — explicitly omitted (§4). Separate feature if ever wanted.
|
||||
- **Dark-mode toggle on the splash** — the CMS is light-only today; do not introduce a toggle here.
|
||||
- **Renaming `Index.razor` → `Catalogue.razor`** — optional cleanup, deferred (§3.4).
|
||||
- **Cross-host "back to the public site" link** — if "Back to site" should point at `DeepDrftPublic`
|
||||
rather than `/catalogue`, that needs the public site URL configured in the CMS (it isn't today).
|
||||
Deferred; default to `/catalogue` (§3.5).
|
||||
- **Extracting a shared `LoginSplash` component** — YAGNI with one consumer (§7).
|
||||
Reference in New Issue
Block a user