5.9 KiB
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
FormBannerErrorinside dialog, keep open (BR-FE-15) - Network error: show
FormBannerErrorinside 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:
nullunless 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"
}
}