AI Implementation feature(898): Admin Area General Settings and Categories 1.16 (#38)

This commit was merged in pull request #38.
This commit is contained in:
2026-07-22 19:19:46 +00:00
parent f62c77cf41
commit 113dd47371
11 changed files with 224 additions and 120 deletions
+43 -6
View File
@@ -3,7 +3,7 @@ type: guide
title: Admin — Categories
description: How an admin lists, creates, edits, and deletes challenge categories from the /admin/categories page, including system-row protection and challenge-attached protection.
tags: [guide, admin, categories, tester]
timestamp: 2026-07-22T18:37:00Z
timestamp: 2026-07-22T19:18:00Z
---
# When this view is available
@@ -45,6 +45,7 @@ side-nav (categories are also embedded inside the General settings page).
| Form modal | `[data-testid="cat-form-modal"]` | Create/edit dialog (`cat-form-backdrop` is the click-outside dismiss layer). |
| Delete modal | `[data-testid="cat-delete-modal"]` | Confirmation dialog (`cat-delete-backdrop` is the dismiss layer). |
| Delete error | `[data-testid="cat-delete-error"]` | Inline error message after a failed delete. |
| Form error | `[data-testid="cf-error"]` | Inline error rendered inside the form modal after a failed create/update/upload. |
# Form modal fields
@@ -54,7 +55,7 @@ side-nav (categories are also embedded inside the General settings page).
| Abbreviation (uppercase) | `cf-abbr` | Required, 26 chars; server upper-cases on save. `readonly` when editing a system row. |
| Description | `cf-desc` | Optional, max 2000 chars. |
| Icon (file picker) | `cf-icon` | Optional image; uploaded to `POST /api/v1/uploads/category-icon` and resized/normalized server-side. |
| Cancel / OK | `cf-cancel`, `cf-ok` | OK disabled while form invalid or already submitting. |
| Cancel / OK | `cf-cancel`, `cf-ok` | OK disabled while form invalid, already submitting, or while the parent is performing the async save (bound `saving` input). |
# Expected behavior
@@ -84,10 +85,23 @@ side-nav (categories are also embedded inside the General settings page).
1. Click `cat-edit-{ABBR}` → modal opens in edit mode, pre-filled with
the row's values.
2. For system rows, the abbreviation field is `readonly`.
3. On save the component issues `updateCategory(id, {...})`. If an icon
file is selected, the new icon is uploaded first and the returned
`publicUrl` replaces `iconPath` in the same update.
2. For system rows, the abbreviation field is `readonly`. For user
(non-system) rows the abbreviation is editable and is uppercased
server-side on save (the existing category row is re-sorted
alphabetically by the defensive client sort after a successful
update).
3. On save the component issues `updateCategory(id, {...})` with
`name`, `abbreviation`, `description`, and (if an icon file was
chosen) `iconPath`. When an icon file is selected, the new icon is
uploaded first via `POST /api/v1/uploads/category-icon` and the
returned `publicUrl` replaces `iconPath` in the same update.
4. On either an upload rejection (HTTP 400) or an update rejection
(HTTP 400/404/409), the modal stays open, the OK button is disabled
while the request is in flight via the bound `saving` flag, and the
server message is rendered inside the modal at
`data-testid="cf-error"`. The list is not refreshed and any
previously-valid icon on disk is preserved (the upload endpoint
does not overwrite when Sharp decode fails).
### Edit prefill mechanics
@@ -112,6 +126,29 @@ input. `syncCategoryForm` is exported from the modal file so it can be
exercised in isolation by
`tests/frontend/admin-categories-form-modal.spec.ts`.
### Save error and loading state
`CategoryFormModalComponent` is purely presentational and does not own
HTTP state. It exposes two additional signal inputs:
* `errorMessage: string | null` — when truthy, an inline error paragraph
is rendered at `data-testid="cf-error"` showing the supplied message.
* `saving: boolean` — when `true`, the OK button is disabled in addition
to the existing `form.invalid` and `submitting` guards.
The parent `AdminCategoriesComponent` owns both signals:
* `formError` — set from the server's `error.message` (or a generic
fallback) when create/update/upload fails, and cleared every time the
modal is opened or a submission begins.
* `saving` — set to `true` at the start of `onFormSubmit` and reset to
`false` in `finally`, so the modal can never get stuck in a
"submitting" state if the request rejects.
This separation matters because `deleteError` (rendered by the delete
modal at `data-testid="cat-delete-error"`) is a different state machine
and must not be reused for create/update failures.
## Delete behavior
* **System rows:** the delete modal renders