Files
slp-modular-cms/aidlc-docs/features/master-cms-module/construction/frontend-cms-page/functional-design/frontend-components.md
T

5.9 KiB

Frontend Components — Unit 3: frontend-cms-page

Component Hierarchy

CmsPage                          (src/pages/CmsPage.tsx)
├── AddCmsInstanceDialog         (src/components/cms/AddCmsInstanceDialog.tsx)
├── SetStatusDialog              (src/components/cms/SetStatusDialog.tsx)
└── CmsInstanceList              (src/components/cms/CmsInstanceList.tsx)

CmsPage

File: src/pages/CmsPage.tsx

State:

State Type Initial Description
addDialogOpen boolean false Controls AddCmsInstanceDialog visibility
statusTarget CmsInstance | null null Instance passed to SetStatusDialog; null = closed

Hooks: useCmsInstances()

Render logic:

Header row: title + "Add CMS" button (always visible)
isPending → loading text
isError   → error text
data === [] → EmptyState (icon + heading + description + Add button)
data.length > 0 → CmsInstanceList
AddCmsInstanceDialog (controlled by addDialogOpen)
SetStatusDialog (controlled by statusTarget !== null)

CmsInstanceList

File: src/components/cms/CmsInstanceList.tsx

Props:

interface CmsInstanceListProps {
    instances: CmsInstance[];
    onSetStatus: (instance: CmsInstance) => void;
}

Columns (BR-FE-01):

Column Source field Notes
Name instance.name
URL instance.url
Status instance.status Rendered as badge (BR-FE-05..07)
Last Contact instance.lastContactedAt Formatted date; "—" when null (BR-FE-02)
Disable Message instance.disableMessage "—" when null (BR-FE-03)
Actions Dropdown with "Set Status" item

Row styling: <TableRow className={instance.status === 'Inactive' ? 'opacity-50' : ''}> (BR-FE-04)

Interactions: Clicking "Set Status" in the actions dropdown calls onSetStatus(instance).


AddCmsInstanceDialog

File: src/components/cms/AddCmsInstanceDialog.tsx

Props:

interface AddCmsInstanceDialogProps {
    open: boolean;
    onOpenChange: (open: boolean) => void;
}

Form fields:

Field Type Validation
Name text Required, non-empty
URL text Required, valid URL format
ApiKey password Required, non-empty

Hooks: useAddCmsInstance()

Submit behaviour:

  • Success (HTTP 201): call onOpenChange(false), show success toast
  • HTTP 400: show FormBannerError inside dialog, keep open (BR-FE-15)
  • Network error: show FormBannerError inside dialog, keep open (BR-FE-16)

SetStatusDialog

File: src/components/cms/SetStatusDialog.tsx

Props:

interface SetStatusDialogProps {
    instance: CmsInstance | null;
    open: boolean;
    onOpenChange: (open: boolean) => void;
}

Form fields:

Field Type Condition Validation
Status select (Available | NotAvailable | Inactive) always Required
DisableMessage text only when status === 'NotAvailable' Required when visible

Hooks: useUpdateCmsInstanceStatus()

Submit behaviour:

  • HTTP 200 + slaveContactSuccess=true: close dialog, toast "Status updated — cliënt confirmed" (BR-FE-12)
  • HTTP 200 + slaveContactSuccess=false: close dialog, toast "Status saved — cliënt unreachable" (BR-FE-13)
  • HTTP 404: toast error, keep open
  • HTTP 400 / network error: toast error, keep open

DisableMessage field logic (BR-FE-08..11):

  • Rendered only when selected status is 'NotAvailable'
  • On status change away from 'NotAvailable': clear field value
  • Value sent to API: null unless status is 'NotAvailable'

Hooks

useCmsInstances

File: src/api/useCmsInstances.ts

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

useAddCmsInstance

File: src/api/useAddCmsInstance.ts

useMutation<CmsInstance, Error, CreateCmsInstanceRequest>({
    mutationFn: (data) => api.post<CmsInstance>('/api/v1/CmsInstances', data),
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ['cmsInstances'] }),
})

useUpdateCmsInstanceStatus

File: src/api/useUpdateCmsInstanceStatus.ts

useMutation<UpdateStatusResult, Error, { id: string } & UpdateCmsInstanceStatusRequest>({
    mutationFn: ({ id, ...body }) =>
        api.put<UpdateStatusResult>(`/api/v1/CmsInstances/${id}/status`, body),
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ['cmsInstances'] }),
})

i18n Keys (additions to cms namespace)

"cms": {
    "title": "CMS Instances",
    "addButton": "Add CMS",
    "emptyState": {
        "heading": "No CMS instances yet",
        "description": "Add your first CMS instance to get started."
    },
    "table": {
        "name": "Name",
        "url": "URL",
        "status": "Status",
        "lastContact": "Last Contact",
        "disableMessage": "Disable Message",
        "actions": "Actions",
        "never": "—"
    },
    "status": {
        "Available": "Available",
        "NotAvailable": "Unavailable",
        "Inactive": "Inactive"
    },
    "actions": {
        "setStatus": "Set Status"
    },
    "add": {
        "title": "Add CMS Instance",
        "nameLabel": "Name",
        "urlLabel": "URL",
        "apiKeyLabel": "API Key",
        "submitButton": "Add",
        "successToast": "CMS instance added successfully"
    },
    "setStatus": {
        "title": "Set Status",
        "statusLabel": "Status",
        "disableMessageLabel": "Disable Message",
        "disableMessagePlaceholder": "Reason for disabling…",
        "submitButton": "Save",
        "successContactedToast": "Status updated — cliënt confirmed",
        "successUnreachableToast": "Status saved — cliënt unreachable"
    }
}