AI Implementation feature(890): Admin Area General Settings and Categories 1.08 (#30)

This commit was merged in pull request #30.
This commit is contained in:
2026-07-22 15:11:01 +00:00
parent 282fcbab94
commit 2a6182b938
6 changed files with 112 additions and 263 deletions
+60 -3
View File
@@ -2,8 +2,8 @@
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:50:25Z
tags: [guide, admin, settings, general, tester, datetime, validation, utc, timezone]
timestamp: 2026-07-22T15:09:39Z
---
# When this view is available
@@ -56,7 +56,8 @@ The page is a single reactive form with these controls (every
* **Initial load:** `loading() === true` renders `general-loading`. After
both requests resolve, the form is patched with the values from the
backend (UTC timestamps are converted to `datetime-local` strings via
`toDatetimeLocal` so the native picker shows them).
`toDatetimeLocal` so the native picker shows them — see
[Datetime UTC round-trip](#datetime-utc-round-trip) below).
* **Logo upload:** selecting a file fires `POST /api/v1/uploads/logo`,
then writes the returned `publicUrl` into the hidden `logo` control.
If the upload fails, `general-logo-error` shows the message; the
@@ -179,6 +180,62 @@ is rendered inside the per-field error region
(`data-testid="general-eventEnd-error"`) by the
`eventEndFieldMessage` helper.
# Datetime UTC round-trip
Both Event-window fields (`general-eventStart`, `general-eventEnd`) are
backed by HTML `<input type="datetime-local">` controls, which the
browser renders as a wall-clock picker **without** any timezone
indicator. The two pure helpers exported from
`frontend/src/app/features/admin/general.pure.ts` make the wall-clock
values behave as if they were already UTC, so the admin sees the same
instant no matter where the browser is running:
| Helper | Direction | Purpose |
|------------------------------------------------|-------------------------------------|---------|
| `toDatetimeLocal(iso: string): string` | ISO-8601 UTC → `YYYY-MM-DDTHH:mm` | Renders a backend UTC instant in the native picker using `getUTC*` getters. |
| `toIsoUtc(local: string): string` | `YYYY-MM-DDTHH:mm[:ss[.fff]]` → ISO-8601 UTC | Parses the wall-clock components as **UTC** (not local time) via `Date.UTC(...)` and returns `toISOString()`. |
The `toIsoUtc` helper explicitly does **not** call `new Date(local)`,
which would interpret the components in the browser's local timezone
and shift the round-tripped instant by the browser's UTC offset. Instead
it regex-parses the year / month / day / hour / minute components (and
optional seconds / milliseconds), constructs the instant with
`new Date(Date.UTC(...))`, and returns `toISOString()`. As a result,
`toIsoUtc(toDatetimeLocal(iso)) === iso` for any minute-precision
`datetime-local` value the picker can produce, regardless of the
browser's `Intl.DateTimeFormat().resolvedOptions().timeZone` value.
The helper preserves its existing fallback contract:
| Input | Output |
|------------------------------------|---------------------------------|
| `''` (empty) | `''` |
| Non-matching `YYYY-MM-DDTHH:mm…` | Returns the input verbatim so the reactive-form `invalidDatetime` validator still fires. |
| Matching `YYYY-MM-DDTHH:mm` | `<value>:00.000Z` |
| Matching `YYYY-MM-DDTHH:mm:ss` | `<value>.000Z` |
| Matching `YYYY-MM-DDTHH:mm:ss.fff` | `<value>Z` (fraction padded to ms) |
`AdminGeneralComponent` calls `toIsoUtc` on both `eventStartUtc` and
`eventEndUtc` before issuing `PUT /api/v1/admin/general/settings`, so
correcting the shared helper fixes start and end submissions without
any further component, form, or backend rewiring. The backend zod
schema (`GeneralSettingsSchema` in
`backend/src/modules/admin/dto/general.dto.ts`) accepts the resulting
timezone-aware ISO-8601 strings unchanged and enforces
`eventEndUtc > eventStartUtc` via `superRefine`.
The regression for this fix lives in
`tests/frontend/admin-general-pure.spec.ts` under the `datetime helpers`
suite, with cases that lock in:
* `toIsoUtc('2027-03-15T08:30')``'2027-03-15T08:30:00.000Z'`
(would previously have returned `'2027-03-15T15:30:00.000Z'` from a
PDT browser).
* `toIsoUtc('2027-03-15T08:30:15')``'2027-03-15T08:30:15.000Z'`.
* `toIsoUtc('2027-03-15T08:30:15.123')``'2027-03-15T08:30:15.123Z'`.
* `toIsoUtc(toDatetimeLocal('2027-03-15T08:30:00.000Z'))` round-trips
back to the original ISO string.
# Visual elements
| Element | Selector |