|
|
|
@@ -3,7 +3,7 @@ type: guide
|
|
|
|
|
title: Admin — General Settings
|
|
|
|
|
description: How an admin edits required platform-wide settings, including the validated event window, from the /admin/general page.
|
|
|
|
|
tags: [guide, admin, settings, general, tester, datetime, validation]
|
|
|
|
|
timestamp: 2026-07-22T14:24:08Z
|
|
|
|
|
timestamp: 2026-07-22T14:50:25Z
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# When this view is available
|
|
|
|
@@ -17,7 +17,7 @@ by navigating directly to the URL.
|
|
|
|
|
|------------------|----------------------------------------------------------------------------|------------------------------------------------|
|
|
|
|
|
| Client route | `frontend/src/app/app.routes.ts` | `/admin/general` child of `adminGuard`. |
|
|
|
|
|
| Client component | `frontend/src/app/features/admin/general.component.ts` | `AdminGeneralComponent.ngOnInit` fetches settings + themes. |
|
|
|
|
|
| Client predicate | `frontend/src/app/features/admin/general.pure.ts` (`deriveEventState`, `pageTitleError`, `pageTitleMessage`) | Pure helpers for event-state derivation and Page-title error/message mapping. |
|
|
|
|
|
| Client predicate | `frontend/src/app/features/admin/general.pure.ts` (`deriveEventState`, `pageTitleError`, `pageTitleMessage`, `eventEndFieldMessage`) | Pure helpers for event-state derivation, Page-title error/message mapping, and the merged Event End field message (per-field `invalidDatetime` + cross-field `endBeforeStart`). |
|
|
|
|
|
| Server route | `backend/src/modules/admin/admin-general.controller.ts` | `GET/PUT /api/v1/admin/general/settings` + `GET /api/v1/admin/general/themes`. |
|
|
|
|
|
|
|
|
|
|
# How to access (tester steps)
|
|
|
|
@@ -43,7 +43,7 @@ The page is a single reactive form with these controls (every
|
|
|
|
|
| Logo upload status | `general-logo-uploading` / `general-logo-error` / `general-logo-current` | — | Inline status text under the file picker. |
|
|
|
|
|
| Global theme | `general-themeKey` | `themeKey` | `<select>` populated from `/themes`. Value is one of `THEME_IDS`. |
|
|
|
|
|
| Event start (UTC) | `general-eventStart` | `eventStartUtc` | Required valid ISO-8601 datetime. Empty or malformed input renders `general-eventStart-error` and disables Save. |
|
|
|
|
|
| Event end (UTC) | `general-eventEnd` | `eventEndUtc` | Required valid ISO-8601 datetime. Empty or malformed input renders `general-eventEnd-error`; it must also be strictly after Event start (`general-endBeforeStart`). |
|
|
|
|
|
| Event end (UTC) | `general-eventEnd` | `eventEndUtc` | Required valid ISO-8601 datetime. Empty or malformed input renders `general-eventEnd-error`; it must also be strictly after Event start — when it is not, the same `general-eventEnd-error` region displays "Event end must be after event start." |
|
|
|
|
|
| Default challenge IP | `general-defaultIp` | `defaultChallengeIp` | Required, max 255 chars. |
|
|
|
|
|
| Enable registrations | `general-registrations` | `registrationsEnabled` | Boolean checkbox. When `false`, the public register endpoint returns `REGISTRATIONS_DISABLED`. |
|
|
|
|
|
| Welcome description | `general-welcome` | `welcomeMarkdown` | Multi-line textarea; a live preview is rendered into `general-welcome-preview`. |
|
|
|
|
@@ -71,9 +71,9 @@ The page is a single reactive form with these controls (every
|
|
|
|
|
* `start <= now < end` → `RUNNING`
|
|
|
|
|
* `now >= end` → `STOPPED`
|
|
|
|
|
* **End-before-start validation:** if the user picks an end that is not
|
|
|
|
|
strictly after the start, the form becomes invalid and
|
|
|
|
|
`general-endBeforeStart` appears under the end input. Save remains
|
|
|
|
|
disabled.
|
|
|
|
|
strictly after the start, the form becomes invalid and the
|
|
|
|
|
`general-eventEnd-error` region under the end input renders
|
|
|
|
|
"Event end must be after event start." Save remains disabled.
|
|
|
|
|
* **Per-field datetime validation:** clearing a field or supplying a value
|
|
|
|
|
that cannot be parsed as an ISO-8601 datetime makes that control invalid.
|
|
|
|
|
The affected input receives `aria-invalid="true"`, references its inline
|
|
|
|
@@ -136,6 +136,16 @@ two pure helpers exported from
|
|
|
|
|
* `datetimeMessage(fieldLabel, value, controlErrors)` — renders the
|
|
|
|
|
human-readable message `"<Field> must be a valid ISO-8601 datetime."`
|
|
|
|
|
when `invalidDatetime` is set or when the raw value is unparseable.
|
|
|
|
|
* `eventEndFieldMessage(controlErrors, crossFieldErrors)` — returns the
|
|
|
|
|
canonical message for the Event End field's own error region. It
|
|
|
|
|
prefers the per-field ISO message
|
|
|
|
|
(`"Event end must be a valid ISO-8601 datetime."`) when
|
|
|
|
|
`controlErrors?.['invalidDatetime']` is set, otherwise it surfaces the
|
|
|
|
|
cross-field `endBeforeStart` message
|
|
|
|
|
(`"Event end must be after event start."`), otherwise `null`. The
|
|
|
|
|
component reads this helper through the `eventEndFieldMessageText`
|
|
|
|
|
computed signal so both error kinds render inside the single
|
|
|
|
|
`general-eventEnd-error` element.
|
|
|
|
|
|
|
|
|
|
The component wires both controls (`eventStartUtc`, `eventEndUtc`) with
|
|
|
|
|
this validator and keeps three local `signal`s per field — `Value`,
|
|
|
|
@@ -160,11 +170,14 @@ The error is shown only when the control is both `touched || dirty`
|
|
|
|
|
AND `invalid`, so the inputs do not flash errors on initial load. After
|
|
|
|
|
a successful Save, both controls are reset to untouched/pristine via
|
|
|
|
|
`resetTouchedState()` and their `TouchedOrDirty` signals are cleared.
|
|
|
|
|
The cross-field `endBeforeStart` error (`general-endBeforeStart`) is
|
|
|
|
|
emitted by the form-level validator whenever both valid timestamps are
|
|
|
|
|
present and end is not strictly after start. The Event end input also
|
|
|
|
|
receives `aria-invalid="true"`, references both possible error elements,
|
|
|
|
|
and exposes `Event end must be after event start.` as its title.
|
|
|
|
|
The cross-field `endBeforeStart` error is emitted by the form-level
|
|
|
|
|
validator whenever both valid timestamps are present and end is not
|
|
|
|
|
strictly after start. The Event end input receives `aria-invalid="true"`,
|
|
|
|
|
references `general-eventEnd-error` via `aria-describedby`, and exposes
|
|
|
|
|
`Event end must be after event start.` as its title — the message itself
|
|
|
|
|
is rendered inside the per-field error region
|
|
|
|
|
(`data-testid="general-eventEnd-error"`) by the
|
|
|
|
|
`eventEndFieldMessage` helper.
|
|
|
|
|
|
|
|
|
|
# Visual elements
|
|
|
|
|
|
|
|
|
@@ -175,9 +188,8 @@ and exposes `Event end must be after event start.` as its title.
|
|
|
|
|
| Load error | `[data-testid="general-error"]` |
|
|
|
|
|
| Form | `[data-testid="general-form"]` |
|
|
|
|
|
| Welcome preview | `[data-testid="general-welcome-preview"]` |
|
|
|
|
|
| End-before-start message | `[data-testid="general-endBeforeStart"]` |
|
|
|
|
|
| Event start inline error | `[data-testid="general-eventStart-error"]` |
|
|
|
|
|
| Event end inline error | `[data-testid="general-eventEnd-error"]` |
|
|
|
|
|
| Event end inline error | `[data-testid="general-eventEnd-error"]` (renders both the per-field `invalidDatetime` message and the cross-field `endBeforeStart` "Event end must be after event start." message) |
|
|
|
|
|
| Page-title inline error | `[data-testid="general-pageTitle-error"]` |
|
|
|
|
|
|
|
|
|
|
# Architecture map
|
|
|
|
|