Files
slp-modular-cms/aidlc-docs/features/master-cms-module/construction/plans/frontend-cms-page-code-generation-plan.md
T
2026-06-30 23:23:50 +02:00

11 KiB

Code Generation Plan — Unit 3: frontend-cms-page

Unit Context

Workspace root: K:\Development\Projects\SlpModularCms
Frontend root: frontend/src/
Project type: Brownfield
FD artifacts: aidlc-docs/features/master-cms-module/construction/frontend-cms-page/functional-design/
NFR artifacts: aidlc-docs/features/master-cms-module/construction/frontend-cms-page/nfr-requirements/ + nfr-design/

Dependencies

  • Backend API (CmsInstanceController) implemented in Unit 1 (master-backend)
  • Existing frontend: src/api/types.ts, src/lib/schemas/auth.ts, src/mocks/index.ts, src/pages/CmsPage.tsx (placeholder)
  • MSW: existing mocks/index.ts pattern with feature folders

Cross-cutting migrations (part of this unit)

  • InviteUserDialog.tsx: schema → @/lib/schemas/users, error state → useState<string | null>
  • New schema files: src/lib/schemas/users.ts, src/lib/schemas/cms.ts

Steps

Step 1 — src/api/types.ts (MODIFY)

Add CMS domain types after existing types:

  • CmsInstanceStatus union type ('Available' | 'NotAvailable' | 'Inactive')
  • CmsInstance interface (id, name, url, status, disableMessage, lastContactedAt, lastStatusPushedAt, lastIntegrityCheckFailedAt)
  • CreateCmsInstanceRequest interface (name, url, apiKey)
  • UpdateCmsInstanceStatusRequest interface (status, disableMessage)
  • UpdateStatusResult interface (success, slaveContactSuccess)

Step 2 — src/lib/schemas/users.ts (CREATE)

Migrate inviteSchema from InviteUserDialog.tsx to src/lib/schemas/users.ts:

  • Export inviteUserSchema (z.object: email z.string().email(), role z.enum(['Administrator','User']))
  • Export InviteUserFormData inferred type

Step 3 — src/lib/schemas/cms.ts (CREATE)

  • addCmsInstanceSchema: name (min 1, max 255), url (z.string().url()), apiKey (min 1)
  • AddCmsInstanceFormData inferred type
  • setStatusSchema: status enum + .refine() requiring disableMessage when NotAvailable
  • SetStatusFormData inferred type

Step 4 — src/lib/schemas/users.test.ts (CREATE)

Tests for migrated inviteUserSchema:

  • Valid email + role → passes
  • Invalid email → fails
  • Missing role → fails

Step 5 — src/lib/schemas/cms.test.ts (CREATE)

Tests for both schemas:

  • addCmsInstanceSchema: valid data passes; missing name/apiKey fails; z.string().url()http://valid.url passes, no-protocol fails
  • setStatusSchema: Available without disableMessage passes; NotAvailable without disableMessage fails; NotAvailable with disableMessage passes

Step 6 — src/components/users/InviteUserDialog.tsx (MODIFY)

Two changes (pattern migration):

  1. Replace inline inviteSchema + InviteFormData with import from @/lib/schemas/users
  2. Replace inviteUser.error passed to <FormErrorBanner> with local useState<string | null>(null) + onError callback setting generic error message

Step 7 — src/mocks/cms/handlers.ts (CREATE)

In-memory MSW handlers:

  • let mockCmsInstances: CmsInstance[] with 2 seed entries (one Available, one Inactive)
  • resetMockCmsInstances() export
  • GET /api/v1/CmsInstances → return array
  • POST /api/v1/CmsInstances → create instance (status=Available), push to array, return 201 + instance
  • PUT /api/v1/CmsInstances/:id/status → update instance in array, return UpdateStatusResult { success: true, slaveContactSuccess: true }

Step 8 — src/mocks/index.ts (MODIFY)

  • Import cmsHandlers from ./cms/handlers
  • Add ...cmsHandlers to handlers array
  • Re-export cmsHandlers and resetMockCmsInstances

Step 9 — src/api/useCmsInstances.ts (CREATE)

export function useCmsInstances() {
    return useQuery<CmsInstance[], Error>({
        queryKey: ['cmsInstances'],
        queryFn: () => api.get<CmsInstance[]>('/api/v1/CmsInstances'),
        staleTime: 30_000,
    });
}

Step 10 — src/api/useAddCmsInstance.ts (CREATE)

export function useAddCmsInstance(options?: MutationOptions) {
    const queryClient = useQueryClient();
    return useMutation<CmsInstance, Error, CreateCmsInstanceRequest>({
        mutationFn: (data) => api.post<CmsInstance>('/api/v1/CmsInstances', data),
        onSuccess: () => queryClient.invalidateQueries({ queryKey: ['cmsInstances'] }),
        ...options,
    });
}

Step 11 — src/api/useUpdateCmsInstanceStatus.ts (CREATE)

export function useUpdateCmsInstanceStatus(options?: MutationOptions) {
    const queryClient = useQueryClient();
    return useMutation<UpdateStatusResult, Error, { id: string } & UpdateCmsInstanceStatusRequest>({
        mutationFn: ({ id, ...body }) =>
            api.put<UpdateStatusResult>(`/api/v1/CmsInstances/${id}/status`, body),
        onSuccess: () => queryClient.invalidateQueries({ queryKey: ['cmsInstances'] }),
        ...options,
    });
}

Step 12 — src/i18n/locales/en/translation.json (MODIFY)

Replace existing "cms": { "title": ..., "description": ... } with full cms.* key tree (from frontend-components.md i18n section).

Step 13 — src/i18n/locales/nl/translation.json (MODIFY)

Replace existing "cms": { "title": ..., "description": ... } with full cms.* key tree in Dutch.

Step 14 — src/components/cms/CmsInstanceList.tsx (CREATE)

Table component:

  • Props: instances: CmsInstance[], onSetStatus: (instance: CmsInstance) => void
  • Shadcn Table with 6 columns (Name, URL, Status, Last Contact, Disable Message, Actions)
  • Status badge: green/red/muted per BR-FE-05..07
  • Inactive row: className={instance.status === 'Inactive' ? 'opacity-50' : ''}
  • Actions: DropdownMenu with "Set Status" item
  • data-testid on row (cms-instance-row), badge, and actions trigger

Step 15 — src/components/cms/AddCmsInstanceDialog.tsx (CREATE)

Dialog with react-hook-form + addCmsInstanceSchema:

  • Fields: name (text), url (text), apiKey (PasswordField)
  • useState<string | null>(null) for serverError
  • onError callback: ProblemDetailsError 409 → specific message, NetworkError → errors.network, else → errors.generic
  • useEffect on open → reset form + serverError + mutation
  • FormErrorBanner, FieldError, data-testid on all fields and submit button

Step 16 — src/components/cms/SetStatusDialog.tsx (CREATE)

Dialog with react-hook-form + setStatusSchema:

  • Fields: status (Shadcn Select), disableMessage (conditional text, only when NotAvailable)
  • useState<string | null>(null) for serverError
  • onSuccess: close dialog, toast message based on result.slaveContactSuccess (BR-FE-12/13)
  • onError: set serverError from toast (generic)
  • useEffect on open or instance change → reset
  • data-testid on select, disableMessage input, submit button

Step 17 — src/pages/CmsPage.tsx (MODIFY)

Replace placeholder with full page:

  • State: addDialogOpen, statusTarget
  • useCmsInstances() hook
  • Header row: title + "Add CMS" button (data-testid="cms-add-button")
  • Loading/error text per NFR-FE-10/11
  • Empty state (BR-FE-18) vs CmsInstanceList
  • AddCmsInstanceDialog + SetStatusDialog
  • Remove old data-testid="cms-placeholder" (test file updated in step 18)

Step 18 — src/pages/CmsPage.test.tsx (MODIFY)

Replace existing placeholder-only tests with:

  • renders list of CMS instances (happy path with seed data)
  • renders empty state when no instances (MSW returns [])
  • opens AddCmsInstanceDialog on button click
  • adds a CMS instance successfully
  • redirects unauthenticated users to login (existing — keep)

Step 19 — src/api/useCmsInstances.test.ts (CREATE)

  • Returns list on success
  • Handles network error

Step 20 — src/api/useAddCmsInstance.test.ts (CREATE)

  • Creates instance + invalidates cache on success (HTTP 201)
  • Calls onError on HTTP 400

Step 21 — src/api/useUpdateCmsInstanceStatus.test.ts (CREATE)

  • Updates status + invalidates cache on success
  • Returns UpdateStatusResult with slaveContactSuccess flag

Step 22 — src/components/cms/AddCmsInstanceDialog.test.tsx (CREATE)

  • Renders fields and submit button
  • Shows FieldError for missing name
  • Shows FieldError for invalid URL (non-URL format)
  • Shows FormErrorBanner on HTTP 400
  • Resets form on dialog close

Step 23 — src/components/cms/SetStatusDialog.test.tsx (CREATE)

  • DisableMessage field hidden when status ≠ NotAvailable
  • DisableMessage field shown and required when status = NotAvailable
  • Form blocks submit when DisableMessage empty + NotAvailable

File Summary

# File Action
1 frontend/src/api/types.ts MODIFY — add 5 CMS types
2 frontend/src/lib/schemas/users.ts CREATE — migrated inviteUserSchema
3 frontend/src/lib/schemas/cms.ts CREATE — addCmsInstanceSchema, setStatusSchema
4 frontend/src/lib/schemas/users.test.ts CREATE
5 frontend/src/lib/schemas/cms.test.ts CREATE
6 frontend/src/components/users/InviteUserDialog.tsx MODIFY — schema import + serverError migration
7 frontend/src/mocks/cms/handlers.ts CREATE — MSW handlers
8 frontend/src/mocks/index.ts MODIFY — register cmsHandlers
9 frontend/src/api/useCmsInstances.ts CREATE
10 frontend/src/api/useAddCmsInstance.ts CREATE
11 frontend/src/api/useUpdateCmsInstanceStatus.ts CREATE
12 frontend/src/i18n/locales/en/translation.json MODIFY — cms.* keys
13 frontend/src/i18n/locales/nl/translation.json MODIFY — cms.* keys
14 frontend/src/components/cms/CmsInstanceList.tsx CREATE
15 frontend/src/components/cms/AddCmsInstanceDialog.tsx CREATE
16 frontend/src/components/cms/SetStatusDialog.tsx CREATE
17 frontend/src/pages/CmsPage.tsx MODIFY — replace placeholder
18 frontend/src/pages/CmsPage.test.tsx MODIFY — full test suite
19 frontend/src/api/useCmsInstances.test.ts CREATE
20 frontend/src/api/useAddCmsInstance.test.ts CREATE
21 frontend/src/api/useUpdateCmsInstanceStatus.test.ts CREATE
22 frontend/src/components/cms/AddCmsInstanceDialog.test.tsx CREATE
23 frontend/src/components/cms/SetStatusDialog.test.tsx CREATE

Total: 16 new files, 7 modified files


Step Completion Tracking

  • Step 1 — types.ts
  • Step 2 — schemas/users.ts
  • Step 3 — schemas/cms.ts
  • Step 4 — schemas/users.test.ts
  • Step 5 — schemas/cms.test.ts
  • Step 6 — InviteUserDialog.tsx migration
  • Step 7 — mocks/cms/handlers.ts
  • Step 8 — mocks/index.ts
  • Step 9 — useCmsInstances.ts
  • Step 10 — useAddCmsInstance.ts
  • Step 11 — useUpdateCmsInstanceStatus.ts
  • Step 12 — en/translation.json
  • Step 13 — nl/translation.json
  • Step 14 — CmsInstanceList.tsx
  • Step 15 — AddCmsInstanceDialog.tsx
  • Step 16 — SetStatusDialog.tsx
  • Step 17 — CmsPage.tsx
  • Step 18 — CmsPage.test.tsx
  • Step 19 — useCmsInstances.test.ts
  • Step 20 — useAddCmsInstance.test.ts
  • Step 21 — useUpdateCmsInstanceStatus.test.ts
  • Step 22 — AddCmsInstanceDialog.test.tsx
  • Step 23 — SetStatusDialog.test.tsx