9.1 KiB
9.1 KiB
type, title, description, tags, timestamp
| type | title | description | tags | timestamp | |||||
|---|---|---|---|---|---|---|---|---|---|
| guide | Admin Shell & Side Navigation | How an authenticated admin navigates the post-login admin area, the side-nav layout, and how the General and Categories pages are reached. |
|
2026-07-22T12:00:00Z |
When this view is available
The admin shell is the post-login UI for users with role === 'admin'.
It is gated by:
| Layer | File | Check |
|---|---|---|
| Client route | frontend/src/app/app.routes.ts |
/admin parent uses adminGuard; children inherit it. |
| Client nav link | frontend/src/app/features/home/home.component.ts |
Renders only when showAdminNav() is true. |
| Client predicate | frontend/src/app/features/home/home.shell.ts (shouldShowAdminNav) |
isAuthenticated && role === 'admin'. |
| Server route | backend/src/modules/admin/admin.controller.ts + admin-general.controller.ts + admin-categories.controller.ts (mounted under @UseGuards(AdminGuard) + @Roles('admin')) |
Requires admin JWT. |
A non-admin (or unauthenticated) request to /admin is intercepted by
the client guard before any HTTP call is made, so the page never
renders.
How to access (tester steps)
- Ensure the instance is initialized (
/api/v1/bootstrapreturnsinitialized: true). If not, follow First-Run Bootstrap. - Sign in as a user with
role === 'admin'via/login. - The browser lands on
/and renders the Home shell with a header containing the page title and a sign-in status line. - Click the Admin nav link (
data-testid="nav-admin") in the shell header, or open the username menu and choose Admin area, or navigate directly to/admin. - The admin area renders inside the shell:
- Aside (
data-testid="admin-aside"): heading "Admin area" and a vertical side-nav (data-testid="admin-nav"). - Body (
data-testid="admin-body"): holds the<router-outlet />that renders the active child page.
- Aside (
Side-nav entries
The side-nav is defined as a static ENTRIES array in
frontend/src/app/features/admin/admin-shell.component.ts:
| Label | Path | Enabled | Renders |
|---|---|---|---|
| General | /admin/general |
yes | Admin — General Settings (default — /admin redirects here). |
| Challenges | /admin/challenges |
yes | Admin — Challenges — sortable list, search, Add/Edit modal, file staging, import/export. |
| Players | /admin/players |
no | Greyed-out placeholder. No route registered. |
| Blog | /admin/blog |
no | Greyed-out placeholder. No route registered. |
| System | /admin/system |
no | Greyed-out placeholder. No route registered. |
Each <li> carries data-testid="admin-nav-{id}". Disabled entries
have cursor: not-allowed and opacity: 0.45. Clicking a disabled
entry is a no-op (go() returns early when !e.enabled).
The active entry is highlighted by [class.active] whenever the
current URL starts with the entry's path. General is the default child
(/admin → /admin/general).
Expected behavior
| User role | Visiting /admin |
|---|---|
| Unauthenticated | Redirected to /login by adminGuard. |
| Authenticated player | Redirected to / by adminGuard. |
| Authenticated admin | Page renders, defaults to /admin/general. |
- When
BootstrapService.initialized()is stillfalsethe guard redirects to/bootstrapinstead of/login, so the first admin flow is honored. - The Admin nav link in the shell header is hidden for non-admin users
— there is no visible UI affordance to reach
/adminwithout the role. - All admin HTTP requests use the existing
authInterceptor+csrfInterceptor, so no extra plumbing is required.
Visual elements
| Element | Selector | Purpose |
|---|---|---|
| Shell aside | [data-testid="admin-aside"] |
Side-nav container. Heading "Admin area". |
| Side-nav list | [data-testid="admin-nav"] |
<ul> of all five entries. |
| Side-nav entry | [data-testid="admin-nav-{id}"] |
One <li> per entry (General / Challenges / Players / Blog / System). |
| Shell body | [data-testid="admin-body"] |
Wraps <router-outlet /> for the active child page. |
Architecture map
| Step | Where | What happens |
|---|---|---|
| 1 | frontend/src/app/app.routes.ts |
/ is a parent route with authGuard; /admin child uses adminGuard + lazy-loads AdminShellComponent with its own child routes. |
| 2 | frontend/src/app/core/guards/admin.guard.ts |
adminGuard calls decideAdminGuard(...) with bootstrap + auth state. |
| 3 | frontend/src/app/core/guards/admin.guard.decision.ts |
Pure function returning {kind: 'allow'} or {kind: 'redirect', path}. |
| 4 | frontend/src/app/features/admin/admin-shell.component.ts |
Renders the aside + body; ENTRIES is the static side-nav list. |
| 5 | frontend/src/app/features/admin/general.component.ts |
AdminGeneralComponent (default /admin/general); embeds AdminCategoriesComponent. |
| 6 | frontend/src/app/features/admin/categories/categories.component.ts |
AdminCategoriesComponent (/admin/categories); also rendered inside the General page. |
| 7 | frontend/src/app/features/home/home.shell.ts |
shouldShowAdminNav({isAuthenticated, role}) predicate (exported and unit-tested). |
| 8 | frontend/src/app/features/home/home.component.ts |
Shell template renders header, conditional nav (*ngIf="showAdminNav()"), and <router-outlet>. |
| 9 | frontend/src/app/core/services/admin.service.ts |
Typed wrappers for /api/v1/admin/users, /admin/general/*, /admin/categories/*, /uploads/{logo,category-icon}. |
| 10 | backend/src/modules/admin/admin.module.ts |
Registers AdminController, AdminGeneralController, AdminCategoriesController + their services. Imports AuthModule, UsersModule, SettingsModule, CommonModule, and TypeOrmModule.forFeature([UserEntity, CategoryEntity, ChallengeEntity]). |
Notes
- The shell deliberately keeps
AdminShellComponentas a thin layout owner — child routes (general,categories) render into its<router-outlet>. New admin sub-pages can be added by enabling entries inENTRIESand registering a child route inapp.routes.ts. - The guard decision function (
decideAdminGuard) is a pure module so it is straightforward to unit-test without Angular DI. Seetests/frontend/admin-shell.spec.tsandtests/frontend/admin-navigation.spec.ts. - The Categories management page is intentionally embedded inside the General settings page (see Admin — General Settings) so an admin can adjust platform settings and challenge taxonomy in one place.
See also
- First-Run Bootstrap
- Frontend Structure
- Authenticated Shell — header / LED / quick tabs / change-password modal / user menu (the surrounding shell this page renders into).
- Change Password
- Admin — General Settings
- Admin — Categories
- REST API Overview
- Admin Endpoints