3.5 KiB
Tech Stack Decisions — Unit 3: frontend-cms-page
Existing stack — no changes
| Layer | Technology | Notes |
|---|---|---|
| UI components | Shadcn/UI | Dialog, Table, Select, Badge, DropdownMenu — all already present |
| Styling | Tailwind CSS | opacity-50 for Inactive rows (BR-FE-04) |
| Data fetching | TanStack Query v5 | useQuery + useMutation pattern already used by useUsers |
| Form state | react-hook-form + zodResolver | Used in InviteUserDialog, LoginPage, SetupPage |
| Schema validation | Zod | z.string().url() for URL field (NFR-FE-13) |
| Routing | TanStack Router | /cms route already exists; RoleGuard already wired |
| i18n | react-i18next | New cms.* keys added to EN + NL locales |
| Toasts | Sonner | toast.success() / toast.error() — already used throughout |
| Testing | Vitest + React Testing Library + MSW | All already configured |
| HTTP client | Custom api-client (NetworkError, ProblemDetailsError) |
Same error types used across all existing hooks |
Decisions made in this unit
TSD-01 — URL validation: z.string().url() (strict)
Decision: Use Zod's built-in z.string().url() for the URL field.
Rationale: Q1: A. Users must enter a fully-qualified URL with protocol. The backend validates anyway, but client-side strict validation stops obviously malformed input early and improves error feedback.
Implication: Users entering 192.168.1.5:8080 will see a validation error. They must enter http://192.168.1.5:8080.
TSD-02 — Test scope: hooks + page + component tests
Decision: Three test layers:
- Hook unit tests (
useCmsInstances.test.ts,useAddCmsInstance.test.ts,useUpdateCmsInstanceStatus.test.ts) usingrenderHook+ MSW server - Page integration test (
CmsPage.test.tsx) covering load, empty state, add flow, status flow - Component tests (
AddCmsInstanceDialog.test.tsx,SetStatusDialog.test.tsx) for error states and conditional logic
Rationale: Q2: C. Matches InviteUserDialog.test.tsx precedent. Dialog component tests are necessary to cover FormErrorBanner on HTTP 400 and the DisableMessage conditional show/hide (BR-FE-08) without a full page mount.
TSD-03 — ApiKey field: PasswordField with show/hide toggle
Decision: Reuse the existing PasswordField component (src/components/ui/PasswordField.tsx) for the apiKey input.
Rationale: Q3: A. Hides the credential from shoulder-surfing while allowing the user to reveal it for verification. No new component needed — PasswordField already implements the show/hide pattern.
TSD-04 — Toast content: differentiated on slaveContactSuccess
Decision: Two distinct toast messages for PUT /api/v1/CmsInstances/{id}/status:
slaveContactSuccess === true→t('cms.setStatus.successContactedToast')= "Status updated — cliënt confirmed"slaveContactSuccess === false→t('cms.setStatus.successUnreachableToast')= "Status saved — cliënt unreachable"
Rationale: Q3 FD: B + user terminology preference (no "slave" in UI). Both cases are HTTP 200; the distinction is surfaced via the UpdateStatusResult.slaveContactSuccess field from the backend.
TSD-05 — FormErrorBanner component name
The project has both FormBannerError (src/components/ui/FormBannerError.tsx) and FormErrorBanner (src/components/ui/FormErrorBanner.tsx). The dialogs use FormErrorBanner (as used in InviteUserDialog). The AddCmsInstanceDialog and SetStatusDialog will import from @/components/ui/FormErrorBanner.