From c3122c45eebb7cfa13ee9854cef3768385f879f8 Mon Sep 17 00:00:00 2001 From: alimpens Date: Mon, 3 Mar 2025 17:26:20 +0100 Subject: [PATCH 01/16] Get contact texts from BE --- apps/public/src/app/(general)/Home.tsx | 5 ++-- .../[classification]/[panelId]/page.tsx | 2 +- .../src/app/(general)/bijlage/Bijlage.tsx | 2 +- .../bijlage/_components/FileUpload.tsx | 2 +- .../src/app/(general)/contact/Contact.tsx | 30 ++++++++++++++++--- .../src/app/(general)/contact/actions.test.ts | 4 +-- .../public/src/app/(general)/contact/page.tsx | 17 ++++++++++- .../app/(general)/locatie/actions.test.tsx | 4 +-- apps/public/src/app/(general)/page.tsx | 7 ++++- .../samenvatting/_utils/getSummaryData.ts | 2 +- libs/form-renderer/src/FormRenderer.tsx | 4 +-- 11 files changed, 61 insertions(+), 18 deletions(-) diff --git a/apps/public/src/app/(general)/Home.tsx b/apps/public/src/app/(general)/Home.tsx index 3543e47c..b7bdf2d8 100644 --- a/apps/public/src/app/(general)/Home.tsx +++ b/apps/public/src/app/(general)/Home.tsx @@ -1,14 +1,15 @@ 'use client' import { Paragraph } from '@amsterdam/design-system-react' -import type { StaticFormPanelComponentOutput, StaticFormTextFieldInputComponentOutput } from '@meldingen/api-client' import { FormRenderer } from '@meldingen/form-renderer' import { Grid } from '@meldingen/ui' import { useActionState } from 'react' +import type { StaticFormTextAreaComponentOutput } from 'apps/public/src/apiClientProxy' + import { postPrimaryForm } from './actions' -type Component = StaticFormPanelComponentOutput | StaticFormTextFieldInputComponentOutput +type Component = StaticFormTextAreaComponentOutput const initialState: { message?: string } = {} diff --git a/apps/public/src/app/(general)/aanvullende-vragen/[classification]/[panelId]/page.tsx b/apps/public/src/app/(general)/aanvullende-vragen/[classification]/[panelId]/page.tsx index b8b1c73f..9d6ea42c 100644 --- a/apps/public/src/app/(general)/aanvullende-vragen/[classification]/[panelId]/page.tsx +++ b/apps/public/src/app/(general)/aanvullende-vragen/[classification]/[panelId]/page.tsx @@ -1,4 +1,4 @@ -import type { FormPanelComponentOutput } from '@meldingen/api-client' +import type { FormPanelComponentOutput } from 'apps/public/src/apiClientProxy' import type { Metadata } from 'next' import { getFormClassificationByClassificationId } from 'apps/public/src/apiClientProxy' diff --git a/apps/public/src/app/(general)/bijlage/Bijlage.tsx b/apps/public/src/app/(general)/bijlage/Bijlage.tsx index af5218fb..700567d9 100644 --- a/apps/public/src/app/(general)/bijlage/Bijlage.tsx +++ b/apps/public/src/app/(general)/bijlage/Bijlage.tsx @@ -1,7 +1,7 @@ 'use client' import { Column, Field, Heading, Label, Paragraph, UnorderedList } from '@amsterdam/design-system-react' -import type { AttachmentOutput } from '@meldingen/api-client' +import type { AttachmentOutput } from 'apps/public/src/apiClientProxy' import { Grid, SubmitButton } from '@meldingen/ui' import { useState } from 'react' import type { ChangeEvent } from 'react' diff --git a/apps/public/src/app/(general)/bijlage/_components/FileUpload.tsx b/apps/public/src/app/(general)/bijlage/_components/FileUpload.tsx index 35f86d4f..50e814db 100644 --- a/apps/public/src/app/(general)/bijlage/_components/FileUpload.tsx +++ b/apps/public/src/app/(general)/bijlage/_components/FileUpload.tsx @@ -1,4 +1,4 @@ -import type { AttachmentOutput } from '@meldingen/api-client' +import type { AttachmentOutput } from 'apps/public/src/apiClientProxy' import type { InputHTMLAttributes, ChangeEvent } from 'react' import styles from './FileUpload.module.css' diff --git a/apps/public/src/app/(general)/contact/Contact.tsx b/apps/public/src/app/(general)/contact/Contact.tsx index ebc05cb8..bc579673 100644 --- a/apps/public/src/app/(general)/contact/Contact.tsx +++ b/apps/public/src/app/(general)/contact/Contact.tsx @@ -4,13 +4,17 @@ import { Alert, Heading, Label, Paragraph, TextInput } from '@amsterdam/design-s import { Grid, SubmitButton } from '@meldingen/ui' import { useActionState } from 'react' +import type { StaticFormTextAreaComponentOutput } from 'apps/public/src/apiClientProxy' + import { BackLink } from '../_components/BackLink' import { postContactForm } from './actions' +type Component = StaticFormTextAreaComponentOutput + const initialState: { message?: string } = {} -export const Contact = () => { +export const Contact = ({ formData }: { formData: Component[] }) => { const [formState, formAction] = useActionState(postContactForm, initialState) return ( @@ -39,9 +43,15 @@ export const Contact = () => { )} + {formData[0].description && ( + + {formData[0].description} + + )} { className="ams-mb--sm" /> - + {formData[1].description && ( + + {formData[1].description} + + )} + Volgende vraag diff --git a/apps/public/src/app/(general)/contact/actions.test.ts b/apps/public/src/app/(general)/contact/actions.test.ts index 56e58f2b..658f80b8 100644 --- a/apps/public/src/app/(general)/contact/actions.test.ts +++ b/apps/public/src/app/(general)/contact/actions.test.ts @@ -1,11 +1,11 @@ -import { postMeldingByMeldingIdContact } from '@meldingen/api-client' +import { postMeldingByMeldingIdContact } from 'apps/public/src/apiClientProxy' import { cookies } from 'next/headers' import { redirect } from 'next/navigation' import type { Mock } from 'vitest' import { postContactForm } from './actions' -vi.mock('@meldingen/api-client', () => ({ +vi.mock('apps/public/src/apiClientProxy', () => ({ postMeldingByMeldingIdContact: vi.fn(), })) diff --git a/apps/public/src/app/(general)/contact/page.tsx b/apps/public/src/app/(general)/contact/page.tsx index 36e6eafb..7baf5f34 100644 --- a/apps/public/src/app/(general)/contact/page.tsx +++ b/apps/public/src/app/(general)/contact/page.tsx @@ -1,9 +1,24 @@ import type { Metadata } from 'next' +import type { StaticFormTextAreaComponentOutput } from 'apps/public/src/apiClientProxy' +import { getStaticForm, getStaticFormByStaticFormId } from 'apps/public/src/apiClientProxy' + import { Contact } from './Contact' export const metadata: Metadata = { title: 'Stap 3 van 4 - Gegevens - Gemeente Amsterdam', } -export default async () => +export default async () => { + const contactFormId = await getStaticForm().then((response) => response.find((form) => form.type === 'contact')?.id) + + if (!contactFormId) return undefined + + const contactForm = (await getStaticFormByStaticFormId({ staticFormId: contactFormId }))?.components + + const textareaComponents = + contactForm?.filter((component): component is StaticFormTextAreaComponentOutput => component.type === 'textarea') || + [] + + return +} diff --git a/apps/public/src/app/(general)/locatie/actions.test.tsx b/apps/public/src/app/(general)/locatie/actions.test.tsx index cba666d2..a4f9e4ae 100644 --- a/apps/public/src/app/(general)/locatie/actions.test.tsx +++ b/apps/public/src/app/(general)/locatie/actions.test.tsx @@ -1,4 +1,4 @@ -import { postMeldingByMeldingIdLocation } from '@meldingen/api-client' +import { postMeldingByMeldingIdLocation } from 'apps/public/src/apiClientProxy' import { cookies } from 'next/headers' import { redirect } from 'next/navigation' import type { Mock } from 'vitest' @@ -13,7 +13,7 @@ vi.mock('next/navigation', () => ({ redirect: vi.fn(), })) -vi.mock('@meldingen/api-client', () => ({ +vi.mock('apps/public/src/apiClientProxy', () => ({ postMeldingByMeldingIdLocation: vi.fn(), })) diff --git a/apps/public/src/app/(general)/page.tsx b/apps/public/src/app/(general)/page.tsx index 2668650c..d925dbbf 100644 --- a/apps/public/src/app/(general)/page.tsx +++ b/apps/public/src/app/(general)/page.tsx @@ -1,5 +1,6 @@ import type { Metadata } from 'next' +import type { StaticFormTextAreaComponentOutput } from 'apps/public/src/apiClientProxy' import { getStaticForm, getStaticFormByStaticFormId } from 'apps/public/src/apiClientProxy' import { Home } from './Home' @@ -19,5 +20,9 @@ export default async () => { const primaryForm = (await getStaticFormByStaticFormId({ staticFormId: primaryFormId }))?.components - return + const textareaComponents = + primaryForm?.filter((component): component is StaticFormTextAreaComponentOutput => component.type === 'textarea') || + [] + + return } diff --git a/apps/public/src/app/(general)/samenvatting/_utils/getSummaryData.ts b/apps/public/src/app/(general)/samenvatting/_utils/getSummaryData.ts index b6437ddd..ad66d524 100644 --- a/apps/public/src/app/(general)/samenvatting/_utils/getSummaryData.ts +++ b/apps/public/src/app/(general)/samenvatting/_utils/getSummaryData.ts @@ -1,4 +1,4 @@ -import type { GetMeldingByMeldingIdAnswersResponse, MeldingOutput } from '@meldingen/api-client' +import type { GetMeldingByMeldingIdAnswersResponse, MeldingOutput } from 'apps/public/src/apiClientProxy' import type { Coordinates } from 'apps/public/src/types' diff --git a/libs/form-renderer/src/FormRenderer.tsx b/libs/form-renderer/src/FormRenderer.tsx index 26b82f3d..a89eedda 100644 --- a/libs/form-renderer/src/FormRenderer.tsx +++ b/libs/form-renderer/src/FormRenderer.tsx @@ -1,11 +1,11 @@ +import { SubmitButton } from '@meldingen/ui' import type { FormCheckboxComponentOutput, FormRadioComponentOutput, FormSelectComponentOutput, FormTextAreaComponentOutput, FormTextFieldInputComponentOutput, -} from '@meldingen/api-client' -import { SubmitButton } from '@meldingen/ui' +} from 'apps/public/src/apiClientProxy' import { Checkbox, Radio, Select, TextArea, TextInput } from './components' From 5484de537bb5c000ba583246f1153637020cd764 Mon Sep 17 00:00:00 2001 From: alimpens Date: Mon, 3 Mar 2025 17:30:11 +0100 Subject: [PATCH 02/16] Remove unnecessary abstraction --- apps/public/src/app/(general)/Home.tsx | 4 +--- apps/public/src/app/(general)/contact/Contact.tsx | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/public/src/app/(general)/Home.tsx b/apps/public/src/app/(general)/Home.tsx index b7bdf2d8..3dcffa34 100644 --- a/apps/public/src/app/(general)/Home.tsx +++ b/apps/public/src/app/(general)/Home.tsx @@ -9,11 +9,9 @@ import type { StaticFormTextAreaComponentOutput } from 'apps/public/src/apiClien import { postPrimaryForm } from './actions' -type Component = StaticFormTextAreaComponentOutput - const initialState: { message?: string } = {} -export const Home = ({ formData }: { formData: Component[] }) => { +export const Home = ({ formData }: { formData: StaticFormTextAreaComponentOutput[] }) => { const [formState, formAction] = useActionState(postPrimaryForm, initialState) return ( diff --git a/apps/public/src/app/(general)/contact/Contact.tsx b/apps/public/src/app/(general)/contact/Contact.tsx index bc579673..d0a676bd 100644 --- a/apps/public/src/app/(general)/contact/Contact.tsx +++ b/apps/public/src/app/(general)/contact/Contact.tsx @@ -10,11 +10,9 @@ import { BackLink } from '../_components/BackLink' import { postContactForm } from './actions' -type Component = StaticFormTextAreaComponentOutput - const initialState: { message?: string } = {} -export const Contact = ({ formData }: { formData: Component[] }) => { +export const Contact = ({ formData }: { formData: StaticFormTextAreaComponentOutput[] }) => { const [formState, formAction] = useActionState(postContactForm, initialState) return ( From 9978249d98aaccb64e336fe4e6cf187e15d3a0e1 Mon Sep 17 00:00:00 2001 From: alimpens Date: Mon, 3 Mar 2025 17:30:54 +0100 Subject: [PATCH 03/16] Revert changes to FormRenderer --- libs/form-renderer/src/FormRenderer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/form-renderer/src/FormRenderer.tsx b/libs/form-renderer/src/FormRenderer.tsx index a89eedda..26b82f3d 100644 --- a/libs/form-renderer/src/FormRenderer.tsx +++ b/libs/form-renderer/src/FormRenderer.tsx @@ -1,11 +1,11 @@ -import { SubmitButton } from '@meldingen/ui' import type { FormCheckboxComponentOutput, FormRadioComponentOutput, FormSelectComponentOutput, FormTextAreaComponentOutput, FormTextFieldInputComponentOutput, -} from 'apps/public/src/apiClientProxy' +} from '@meldingen/api-client' +import { SubmitButton } from '@meldingen/ui' import { Checkbox, Radio, Select, TextArea, TextInput } from './components' From 999143e03add584c6f28f72644e8e299abd7c869 Mon Sep 17 00:00:00 2001 From: alimpens Date: Mon, 3 Mar 2025 17:40:45 +0100 Subject: [PATCH 04/16] Revert api client type import changes --- .../aanvullende-vragen/[classification]/[panelId]/page.tsx | 2 +- apps/public/src/app/(general)/bijlage/Bijlage.tsx | 2 +- .../src/app/(general)/bijlage/_components/FileUpload.tsx | 2 +- apps/public/src/app/(general)/contact/actions.test.ts | 4 ++-- apps/public/src/app/(general)/locatie/actions.test.tsx | 4 ++-- .../src/app/(general)/samenvatting/_utils/getSummaryData.ts | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/public/src/app/(general)/aanvullende-vragen/[classification]/[panelId]/page.tsx b/apps/public/src/app/(general)/aanvullende-vragen/[classification]/[panelId]/page.tsx index 9d6ea42c..b8b1c73f 100644 --- a/apps/public/src/app/(general)/aanvullende-vragen/[classification]/[panelId]/page.tsx +++ b/apps/public/src/app/(general)/aanvullende-vragen/[classification]/[panelId]/page.tsx @@ -1,4 +1,4 @@ -import type { FormPanelComponentOutput } from 'apps/public/src/apiClientProxy' +import type { FormPanelComponentOutput } from '@meldingen/api-client' import type { Metadata } from 'next' import { getFormClassificationByClassificationId } from 'apps/public/src/apiClientProxy' diff --git a/apps/public/src/app/(general)/bijlage/Bijlage.tsx b/apps/public/src/app/(general)/bijlage/Bijlage.tsx index 700567d9..af5218fb 100644 --- a/apps/public/src/app/(general)/bijlage/Bijlage.tsx +++ b/apps/public/src/app/(general)/bijlage/Bijlage.tsx @@ -1,7 +1,7 @@ 'use client' import { Column, Field, Heading, Label, Paragraph, UnorderedList } from '@amsterdam/design-system-react' -import type { AttachmentOutput } from 'apps/public/src/apiClientProxy' +import type { AttachmentOutput } from '@meldingen/api-client' import { Grid, SubmitButton } from '@meldingen/ui' import { useState } from 'react' import type { ChangeEvent } from 'react' diff --git a/apps/public/src/app/(general)/bijlage/_components/FileUpload.tsx b/apps/public/src/app/(general)/bijlage/_components/FileUpload.tsx index 50e814db..35f86d4f 100644 --- a/apps/public/src/app/(general)/bijlage/_components/FileUpload.tsx +++ b/apps/public/src/app/(general)/bijlage/_components/FileUpload.tsx @@ -1,4 +1,4 @@ -import type { AttachmentOutput } from 'apps/public/src/apiClientProxy' +import type { AttachmentOutput } from '@meldingen/api-client' import type { InputHTMLAttributes, ChangeEvent } from 'react' import styles from './FileUpload.module.css' diff --git a/apps/public/src/app/(general)/contact/actions.test.ts b/apps/public/src/app/(general)/contact/actions.test.ts index 658f80b8..56e58f2b 100644 --- a/apps/public/src/app/(general)/contact/actions.test.ts +++ b/apps/public/src/app/(general)/contact/actions.test.ts @@ -1,11 +1,11 @@ -import { postMeldingByMeldingIdContact } from 'apps/public/src/apiClientProxy' +import { postMeldingByMeldingIdContact } from '@meldingen/api-client' import { cookies } from 'next/headers' import { redirect } from 'next/navigation' import type { Mock } from 'vitest' import { postContactForm } from './actions' -vi.mock('apps/public/src/apiClientProxy', () => ({ +vi.mock('@meldingen/api-client', () => ({ postMeldingByMeldingIdContact: vi.fn(), })) diff --git a/apps/public/src/app/(general)/locatie/actions.test.tsx b/apps/public/src/app/(general)/locatie/actions.test.tsx index a4f9e4ae..cba666d2 100644 --- a/apps/public/src/app/(general)/locatie/actions.test.tsx +++ b/apps/public/src/app/(general)/locatie/actions.test.tsx @@ -1,4 +1,4 @@ -import { postMeldingByMeldingIdLocation } from 'apps/public/src/apiClientProxy' +import { postMeldingByMeldingIdLocation } from '@meldingen/api-client' import { cookies } from 'next/headers' import { redirect } from 'next/navigation' import type { Mock } from 'vitest' @@ -13,7 +13,7 @@ vi.mock('next/navigation', () => ({ redirect: vi.fn(), })) -vi.mock('apps/public/src/apiClientProxy', () => ({ +vi.mock('@meldingen/api-client', () => ({ postMeldingByMeldingIdLocation: vi.fn(), })) diff --git a/apps/public/src/app/(general)/samenvatting/_utils/getSummaryData.ts b/apps/public/src/app/(general)/samenvatting/_utils/getSummaryData.ts index ad66d524..b6437ddd 100644 --- a/apps/public/src/app/(general)/samenvatting/_utils/getSummaryData.ts +++ b/apps/public/src/app/(general)/samenvatting/_utils/getSummaryData.ts @@ -1,4 +1,4 @@ -import type { GetMeldingByMeldingIdAnswersResponse, MeldingOutput } from 'apps/public/src/apiClientProxy' +import type { GetMeldingByMeldingIdAnswersResponse, MeldingOutput } from '@meldingen/api-client' import type { Coordinates } from 'apps/public/src/types' From 47eae02d92571b690fd9c3d7a84f662557400278 Mon Sep 17 00:00:00 2001 From: alimpens Date: Mon, 3 Mar 2025 17:59:31 +0100 Subject: [PATCH 05/16] Fix tests --- apps/public/src/app/(general)/Home.test.tsx | 2 ++ .../app/(general)/contact/Contact.test.tsx | 29 +++++++++++++++-- .../src/app/(general)/contact/page.test.tsx | 32 +++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/apps/public/src/app/(general)/Home.test.tsx b/apps/public/src/app/(general)/Home.test.tsx index 4423a33d..036ed8a5 100644 --- a/apps/public/src/app/(general)/Home.test.tsx +++ b/apps/public/src/app/(general)/Home.test.tsx @@ -16,6 +16,8 @@ const mockFormData = [ inputType: 'text', showCharCount: false, position: 0, + maxCharCount: 0, + autoExpand: false, }, ] diff --git a/apps/public/src/app/(general)/contact/Contact.test.tsx b/apps/public/src/app/(general)/contact/Contact.test.tsx index 7a10e75e..24222662 100644 --- a/apps/public/src/app/(general)/contact/Contact.test.tsx +++ b/apps/public/src/app/(general)/contact/Contact.test.tsx @@ -12,9 +12,34 @@ vi.mock('react', async (importOriginal) => { } }) +const mockFormData = [ + { + type: 'textarea', + label: 'Wat is uw e-mailadres?', + description: 'Test 1', + key: 'email', + input: true, + inputType: 'text', + maxCharCount: 0, + position: 0, + autoExpand: false, + }, + { + type: 'textarea', + label: 'Wat is uw telefoonnummer?', + description: 'Test 2', + key: 'tel', + input: true, + inputType: 'text', + maxCharCount: 0, + position: 1, + autoExpand: false, + }, +] + describe('Contact', () => { it('should render page and form', async () => { - render() + render() const emailInput = screen.getByRole('textbox', { name: 'Wat is uw e-mailadres? (niet verplicht)' }) const phoneInput = screen.getByRole('textbox', { name: 'Wat is uw telefoonnummer? (niet verplicht)' }) @@ -28,7 +53,7 @@ describe('Contact', () => { it('should render an error message', () => { ;(useActionState as Mock).mockReturnValue([{ message: 'Test error message' }, vi.fn()]) - render() + render() expect(screen.queryByText('Test error message')).toBeInTheDocument() }) diff --git a/apps/public/src/app/(general)/contact/page.test.tsx b/apps/public/src/app/(general)/contact/page.test.tsx index d0203c84..66de88ab 100644 --- a/apps/public/src/app/(general)/contact/page.test.tsx +++ b/apps/public/src/app/(general)/contact/page.test.tsx @@ -1,4 +1,8 @@ import { render, screen } from '@testing-library/react' +import { http, HttpResponse } from 'msw' + +import { ENDPOINTS } from 'apps/public/src/mocks/endpoints' +import { server } from 'apps/public/src/mocks/node' import Page from './page' @@ -8,10 +12,38 @@ vi.mock('./Contact', () => ({ describe('Page', () => { it('renders the Contact component', async () => { + server.use( + http.get(ENDPOINTS.STATIC_FORM, () => + HttpResponse.json([ + { + id: '123', + type: 'contact', + }, + ]), + ), + ) + const PageComponent = await Page() render(PageComponent) expect(screen.getByText('Contact Component')).toBeInTheDocument() }) + + it('returns undefined if no contact form is found', async () => { + server.use( + http.get(ENDPOINTS.STATIC_FORM, () => + HttpResponse.json([ + { + id: '123', + type: 'not-contact', + }, + ]), + ), + ) + + const PageComponent = await Page() + + expect(PageComponent).toBeUndefined() + }) }) From ad07fc389d99af4afb008aef22ae405bde49eb4b Mon Sep 17 00:00:00 2001 From: alimpens Date: Mon, 3 Mar 2025 18:03:18 +0100 Subject: [PATCH 06/16] Fix build --- apps/public/src/app/(general)/contact/page.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/public/src/app/(general)/contact/page.tsx b/apps/public/src/app/(general)/contact/page.tsx index 7baf5f34..3d33ccae 100644 --- a/apps/public/src/app/(general)/contact/page.tsx +++ b/apps/public/src/app/(general)/contact/page.tsx @@ -5,6 +5,10 @@ import { getStaticForm, getStaticFormByStaticFormId } from 'apps/public/src/apiC import { Contact } from './Contact' +// TODO: Force dynamic rendering for now, because the api isn't accessible in the pipeline yet. +// We can remove this when the api is deployed. +export const dynamic = 'force-dynamic' + export const metadata: Metadata = { title: 'Stap 3 van 4 - Gegevens - Gemeente Amsterdam', } From 0e3733529535f77d7bd894e53469dd97d705c407 Mon Sep 17 00:00:00 2001 From: alimpens Date: Thu, 6 Mar 2025 15:51:25 +0100 Subject: [PATCH 07/16] Use explicit type guard --- apps/public/src/app/(general)/contact/page.tsx | 14 +++++++------- apps/public/src/app/(general)/page.tsx | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/public/src/app/(general)/contact/page.tsx b/apps/public/src/app/(general)/contact/page.tsx index 3d33ccae..73ac2d4c 100644 --- a/apps/public/src/app/(general)/contact/page.tsx +++ b/apps/public/src/app/(general)/contact/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from 'next' -import type { StaticFormTextAreaComponentOutput } from 'apps/public/src/apiClientProxy' +import type { StaticFormOutput, StaticFormTextAreaComponentOutput } from 'apps/public/src/apiClientProxy' import { getStaticForm, getStaticFormByStaticFormId } from 'apps/public/src/apiClientProxy' import { Contact } from './Contact' @@ -13,16 +13,16 @@ export const metadata: Metadata = { title: 'Stap 3 van 4 - Gegevens - Gemeente Amsterdam', } +const isTextArea = ( + component: StaticFormOutput['components'][number], +): component is StaticFormTextAreaComponentOutput => component.type === 'textarea' + export default async () => { const contactFormId = await getStaticForm().then((response) => response.find((form) => form.type === 'contact')?.id) if (!contactFormId) return undefined - const contactForm = (await getStaticFormByStaticFormId({ staticFormId: contactFormId }))?.components - - const textareaComponents = - contactForm?.filter((component): component is StaticFormTextAreaComponentOutput => component.type === 'textarea') || - [] + const contactForm = (await getStaticFormByStaticFormId({ staticFormId: contactFormId })).components.filter(isTextArea) - return + return } diff --git a/apps/public/src/app/(general)/page.tsx b/apps/public/src/app/(general)/page.tsx index d925dbbf..400e309f 100644 --- a/apps/public/src/app/(general)/page.tsx +++ b/apps/public/src/app/(general)/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from 'next' -import type { StaticFormTextAreaComponentOutput } from 'apps/public/src/apiClientProxy' +import type { StaticFormTextAreaComponentOutput, StaticFormOutput } from 'apps/public/src/apiClientProxy' import { getStaticForm, getStaticFormByStaticFormId } from 'apps/public/src/apiClientProxy' import { Home } from './Home' @@ -13,16 +13,16 @@ export const metadata: Metadata = { title: 'Stap 1 van 4 - Beschrijf uw melding - Gemeente Amsterdam', } +const isTextArea = ( + component: StaticFormOutput['components'][number], +): component is StaticFormTextAreaComponentOutput => component.type === 'textarea' + export default async () => { const primaryFormId = await getStaticForm().then((response) => response.find((form) => form.type === 'primary')?.id) if (!primaryFormId) return undefined - const primaryForm = (await getStaticFormByStaticFormId({ staticFormId: primaryFormId }))?.components - - const textareaComponents = - primaryForm?.filter((component): component is StaticFormTextAreaComponentOutput => component.type === 'textarea') || - [] + const primaryForm = (await getStaticFormByStaticFormId({ staticFormId: primaryFormId })).components.filter(isTextArea) - return + return } From 19be41560935e05754af8f8b11f7ea37a2a5ed92 Mon Sep 17 00:00:00 2001 From: alimpens Date: Thu, 6 Mar 2025 16:32:45 +0100 Subject: [PATCH 08/16] Wrap fetches in try...catch block --- apps/public/src/app/(general)/contact/page.tsx | 12 +++++++++--- apps/public/src/app/(general)/page.tsx | 12 +++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/apps/public/src/app/(general)/contact/page.tsx b/apps/public/src/app/(general)/contact/page.tsx index 73ac2d4c..75f1a3b6 100644 --- a/apps/public/src/app/(general)/contact/page.tsx +++ b/apps/public/src/app/(general)/contact/page.tsx @@ -18,11 +18,17 @@ const isTextArea = ( ): component is StaticFormTextAreaComponentOutput => component.type === 'textarea' export default async () => { - const contactFormId = await getStaticForm().then((response) => response.find((form) => form.type === 'contact')?.id) + let contactForm - if (!contactFormId) return undefined + try { + const contactFormId = await getStaticForm().then((response) => response.find((form) => form.type === 'contact')?.id) - const contactForm = (await getStaticFormByStaticFormId({ staticFormId: contactFormId })).components.filter(isTextArea) + if (!contactFormId) throw new Error('Contact form id not found') + + contactForm = (await getStaticFormByStaticFormId({ staticFormId: contactFormId })).components.filter(isTextArea) + } catch (error) { + throw new Error((error as Error).message) + } return } diff --git a/apps/public/src/app/(general)/page.tsx b/apps/public/src/app/(general)/page.tsx index 400e309f..d7bb89ec 100644 --- a/apps/public/src/app/(general)/page.tsx +++ b/apps/public/src/app/(general)/page.tsx @@ -18,11 +18,17 @@ const isTextArea = ( ): component is StaticFormTextAreaComponentOutput => component.type === 'textarea' export default async () => { - const primaryFormId = await getStaticForm().then((response) => response.find((form) => form.type === 'primary')?.id) + let primaryForm - if (!primaryFormId) return undefined + try { + const primaryFormId = await getStaticForm().then((response) => response.find((form) => form.type === 'primary')?.id) - const primaryForm = (await getStaticFormByStaticFormId({ staticFormId: primaryFormId })).components.filter(isTextArea) + if (!primaryFormId) throw new Error('Primary form id not found') + + primaryForm = (await getStaticFormByStaticFormId({ staticFormId: primaryFormId })).components.filter(isTextArea) + } catch (error) { + throw new Error((error as Error).message) + } return } From 9cd061f5c77b755c9130722cab24c63f61d20990 Mon Sep 17 00:00:00 2001 From: alimpens Date: Thu, 6 Mar 2025 16:43:42 +0100 Subject: [PATCH 09/16] Fix tests --- apps/public/src/app/(general)/contact/page.test.tsx | 6 ++---- apps/public/src/app/(general)/page.test.tsx | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/public/src/app/(general)/contact/page.test.tsx b/apps/public/src/app/(general)/contact/page.test.tsx index 66de88ab..49096509 100644 --- a/apps/public/src/app/(general)/contact/page.test.tsx +++ b/apps/public/src/app/(general)/contact/page.test.tsx @@ -30,7 +30,7 @@ describe('Page', () => { expect(screen.getByText('Contact Component')).toBeInTheDocument() }) - it('returns undefined if no contact form is found', async () => { + it('throws an error if no contact form is found', async () => { server.use( http.get(ENDPOINTS.STATIC_FORM, () => HttpResponse.json([ @@ -42,8 +42,6 @@ describe('Page', () => { ), ) - const PageComponent = await Page() - - expect(PageComponent).toBeUndefined() + expect(() => render()).toThrow() }) }) diff --git a/apps/public/src/app/(general)/page.test.tsx b/apps/public/src/app/(general)/page.test.tsx index 85943c82..e105f827 100644 --- a/apps/public/src/app/(general)/page.test.tsx +++ b/apps/public/src/app/(general)/page.test.tsx @@ -22,7 +22,7 @@ describe('Page', () => { expect(Home).toHaveBeenCalledWith({ formData: mockFormData.components[0].components }, {}) }) - it('returns undefined if no primary form is found', async () => { + it('throws an error if no primary form is found', async () => { server.use( http.get(ENDPOINTS.STATIC_FORM, () => HttpResponse.json([ @@ -34,8 +34,6 @@ describe('Page', () => { ), ) - const PageComponent = await Page() - - expect(PageComponent).toBeUndefined() + expect(() => render()).toThrow() }) }) From f7750b867178ab540a448f93ca2a4561286f5487 Mon Sep 17 00:00:00 2001 From: alimpens Date: Thu, 6 Mar 2025 17:02:42 +0100 Subject: [PATCH 10/16] Handle error --- apps/public/src/app/(general)/contact/page.test.tsx | 8 ++++++-- apps/public/src/app/(general)/contact/page.tsx | 2 +- apps/public/src/app/(general)/page.test.tsx | 6 +++++- apps/public/src/app/(general)/page.tsx | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/public/src/app/(general)/contact/page.test.tsx b/apps/public/src/app/(general)/contact/page.test.tsx index 49096509..bf6f599d 100644 --- a/apps/public/src/app/(general)/contact/page.test.tsx +++ b/apps/public/src/app/(general)/contact/page.test.tsx @@ -30,7 +30,7 @@ describe('Page', () => { expect(screen.getByText('Contact Component')).toBeInTheDocument() }) - it('throws an error if no contact form is found', async () => { + it('shows an error message if no contact form is found', async () => { server.use( http.get(ENDPOINTS.STATIC_FORM, () => HttpResponse.json([ @@ -42,6 +42,10 @@ describe('Page', () => { ), ) - expect(() => render()).toThrow() + const PageComponent = await Page() + + render(PageComponent) + + expect(screen.getByText('Contact form id not found')).toBeInTheDocument() }) }) diff --git a/apps/public/src/app/(general)/contact/page.tsx b/apps/public/src/app/(general)/contact/page.tsx index 75f1a3b6..06f5b85d 100644 --- a/apps/public/src/app/(general)/contact/page.tsx +++ b/apps/public/src/app/(general)/contact/page.tsx @@ -27,7 +27,7 @@ export default async () => { contactForm = (await getStaticFormByStaticFormId({ staticFormId: contactFormId })).components.filter(isTextArea) } catch (error) { - throw new Error((error as Error).message) + return (error as Error).message } return diff --git a/apps/public/src/app/(general)/page.test.tsx b/apps/public/src/app/(general)/page.test.tsx index e105f827..7680b0a4 100644 --- a/apps/public/src/app/(general)/page.test.tsx +++ b/apps/public/src/app/(general)/page.test.tsx @@ -34,6 +34,10 @@ describe('Page', () => { ), ) - expect(() => render()).toThrow() + const PageComponent = await Page() + + render(PageComponent) + + expect(screen.getByText('Primary form id not found')).toBeInTheDocument() }) }) diff --git a/apps/public/src/app/(general)/page.tsx b/apps/public/src/app/(general)/page.tsx index d7bb89ec..58413833 100644 --- a/apps/public/src/app/(general)/page.tsx +++ b/apps/public/src/app/(general)/page.tsx @@ -27,7 +27,7 @@ export default async () => { primaryForm = (await getStaticFormByStaticFormId({ staticFormId: primaryFormId })).components.filter(isTextArea) } catch (error) { - throw new Error((error as Error).message) + return (error as Error).message } return From 54b7dfb18748fc850a2882ef793642284e46b853 Mon Sep 17 00:00:00 2001 From: alimpens Date: Mon, 10 Mar 2025 14:18:25 +0100 Subject: [PATCH 11/16] Split labels and descriptions in Contact component, make functions immutable --- .../src/app/(general)/contact/Contact.tsx | 21 ++++++++++++------- .../public/src/app/(general)/contact/page.tsx | 12 ++++++----- apps/public/src/app/(general)/page.tsx | 10 ++++----- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/apps/public/src/app/(general)/contact/Contact.tsx b/apps/public/src/app/(general)/contact/Contact.tsx index d0a676bd..0f891a0c 100644 --- a/apps/public/src/app/(general)/contact/Contact.tsx +++ b/apps/public/src/app/(general)/contact/Contact.tsx @@ -15,6 +15,11 @@ const initialState: { message?: string } = {} export const Contact = ({ formData }: { formData: StaticFormTextAreaComponentOutput[] }) => { const [formState, formAction] = useActionState(postContactForm, initialState) + const emailLabel = formData[0].label + const emailDescription = formData[0].description + const telLabel = formData[1].label + const telDescription = formData[1].description + return ( @@ -41,15 +46,15 @@ export const Contact = ({ formData }: { formData: StaticFormTextAreaComponentOut )} - {formData[0].description && ( + {emailDescription && ( - {formData[0].description} + {emailDescription} )} - {formData[1].description && ( + {telDescription && ( - {formData[1].description} + {telDescription} )} component.type === 'textarea' export default async () => { - let contactForm - try { const contactFormId = await getStaticForm().then((response) => response.find((form) => form.type === 'contact')?.id) if (!contactFormId) throw new Error('Contact form id not found') - contactForm = (await getStaticFormByStaticFormId({ staticFormId: contactFormId })).components.filter(isTextArea) + const contactForm = (await getStaticFormByStaticFormId({ staticFormId: contactFormId })).components + // A contact form is always an array of text area components, but TypeScript doesn't know that + // We use a type guard here to make sure we're always working with the right type + const filteredContactForm = contactForm.filter(isTextArea) + + if (!filteredContactForm[0].label || !filteredContactForm[1].label) throw new Error('Contact form labels not found') + return } catch (error) { return (error as Error).message } - - return } diff --git a/apps/public/src/app/(general)/page.tsx b/apps/public/src/app/(general)/page.tsx index 58413833..53402220 100644 --- a/apps/public/src/app/(general)/page.tsx +++ b/apps/public/src/app/(general)/page.tsx @@ -18,17 +18,17 @@ const isTextArea = ( ): component is StaticFormTextAreaComponentOutput => component.type === 'textarea' export default async () => { - let primaryForm - try { const primaryFormId = await getStaticForm().then((response) => response.find((form) => form.type === 'primary')?.id) if (!primaryFormId) throw new Error('Primary form id not found') - primaryForm = (await getStaticFormByStaticFormId({ staticFormId: primaryFormId })).components.filter(isTextArea) + const primaryForm = (await getStaticFormByStaticFormId({ staticFormId: primaryFormId })).components + // A primary form is always an array with 1 text area component, but TypeScript doesn't know that + // We use a type guard here to make sure we're always working with the right type + const filteredPrimaryForm = primaryForm.filter(isTextArea) + return } catch (error) { return (error as Error).message } - - return } From 2199d3191b3269a3aca8538c092c38013f3a1cdd Mon Sep 17 00:00:00 2001 From: alimpens Date: Mon, 10 Mar 2025 14:34:27 +0100 Subject: [PATCH 12/16] Change static form mocks --- .../src/app/(general)/contact/page.test.tsx | 11 -------- apps/public/src/mocks/handlers.ts | 17 +++++++++-- .../public/src/mocks/mockContactFormData.json | 28 +++++++++++++++++++ 3 files changed, 43 insertions(+), 13 deletions(-) create mode 100644 apps/public/src/mocks/mockContactFormData.json diff --git a/apps/public/src/app/(general)/contact/page.test.tsx b/apps/public/src/app/(general)/contact/page.test.tsx index bf6f599d..ceb685ad 100644 --- a/apps/public/src/app/(general)/contact/page.test.tsx +++ b/apps/public/src/app/(general)/contact/page.test.tsx @@ -12,17 +12,6 @@ vi.mock('./Contact', () => ({ describe('Page', () => { it('renders the Contact component', async () => { - server.use( - http.get(ENDPOINTS.STATIC_FORM, () => - HttpResponse.json([ - { - id: '123', - type: 'contact', - }, - ]), - ), - ) - const PageComponent = await Page() render(PageComponent) diff --git a/apps/public/src/mocks/handlers.ts b/apps/public/src/mocks/handlers.ts index d72b9058..5e297e52 100644 --- a/apps/public/src/mocks/handlers.ts +++ b/apps/public/src/mocks/handlers.ts @@ -2,6 +2,7 @@ import { http, HttpResponse } from 'msw' import { ENDPOINTS } from './endpoints' import mockAdditionalQuestionsAnswerData from './mockAdditionalQuestionsAnswerData.json' +import mockContactFormData from './mockContactFormData.json' import mockFormData from './mockFormData.json' import mockMeldingData from './mockMeldingData.json' @@ -67,13 +68,25 @@ export const handlers = [ http.get(ENDPOINTS.STATIC_FORM, () => HttpResponse.json([ { - id: '123', + id: '1', type: 'primary', }, + { + id: '2', + type: 'contact', + }, ]), ), - http.get(ENDPOINTS.STATIC_FORM_BY_STATIC_FORM_ID, () => HttpResponse.json(mockFormData.components[0])), + http.get(ENDPOINTS.STATIC_FORM_BY_STATIC_FORM_ID, ({ params }) => { + if (params.staticFormId === '1') { + return HttpResponse.json(mockFormData.components[0]) + } + if (params.staticFormId === '2') { + return HttpResponse.json(mockContactFormData) + } + return undefined + }), http.get(ENDPOINTS.MELDING_BY_ID, () => HttpResponse.json(mockMeldingData)), diff --git a/apps/public/src/mocks/mockContactFormData.json b/apps/public/src/mocks/mockContactFormData.json new file mode 100644 index 00000000..5b03400d --- /dev/null +++ b/apps/public/src/mocks/mockContactFormData.json @@ -0,0 +1,28 @@ +{ + "components": [ + { + "label": "First question", + "description": "", + "key": "textArea1", + "type": "textarea", + "input": true, + "autoExpand": false, + "showCharCount": false, + "position": 1, + "question": 1, + "values": null + }, + { + "label": "Second question", + "description": "", + "key": "textArea2", + "type": "textarea", + "input": true, + "autoExpand": false, + "showCharCount": false, + "position": 2, + "question": 2, + "values": null + } + ] +} From bc029de536b4f2b5732cabb9d4732b0c3db2a34c Mon Sep 17 00:00:00 2001 From: alimpens Date: Mon, 10 Mar 2025 14:45:03 +0100 Subject: [PATCH 13/16] Add tests --- .../app/(general)/contact/Contact.test.tsx | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/apps/public/src/app/(general)/contact/Contact.test.tsx b/apps/public/src/app/(general)/contact/Contact.test.tsx index 24222662..0f705712 100644 --- a/apps/public/src/app/(general)/contact/Contact.test.tsx +++ b/apps/public/src/app/(general)/contact/Contact.test.tsx @@ -42,11 +42,11 @@ describe('Contact', () => { render() const emailInput = screen.getByRole('textbox', { name: 'Wat is uw e-mailadres? (niet verplicht)' }) - const phoneInput = screen.getByRole('textbox', { name: 'Wat is uw telefoonnummer? (niet verplicht)' }) + const telInput = screen.getByRole('textbox', { name: 'Wat is uw telefoonnummer? (niet verplicht)' }) const submitButton = screen.getByRole('button') expect(emailInput).toBeInTheDocument() - expect(phoneInput).toBeInTheDocument() + expect(telInput).toBeInTheDocument() expect(submitButton).toBeInTheDocument() }) @@ -57,4 +57,37 @@ describe('Contact', () => { expect(screen.queryByText('Test error message')).toBeInTheDocument() }) + + it('should render descriptions that are connected the e-mail and tel inputs', () => { + render() + + const emailInput = screen.getByRole('textbox', { name: 'Wat is uw e-mailadres? (niet verplicht)' }) + const telInput = screen.getByRole('textbox', { name: 'Wat is uw telefoonnummer? (niet verplicht)' }) + + expect(emailInput).toHaveAccessibleDescription('Test 1') + expect(telInput).toHaveAccessibleDescription('Test 2') + }) + + it('should not render descriptions that are connected the e-mail and tel inputs when they are not provided', () => { + const mockFormDataWithoutDescriptions = [ + { + ...mockFormData[0], + description: '', + }, + { + ...mockFormData[1], + description: '', + }, + ] + + render() + + const emailInput = screen.getByRole('textbox', { name: 'Wat is uw e-mailadres? (niet verplicht)' }) + const telInput = screen.getByRole('textbox', { name: 'Wat is uw telefoonnummer? (niet verplicht)' }) + + expect(emailInput).not.toHaveAccessibleDescription() + expect(emailInput).not.toHaveAttribute('aria-describedby', 'email-input-description') + expect(telInput).not.toHaveAccessibleDescription() + expect(telInput).not.toHaveAttribute('aria-describedby', 'tel-input-description') + }) }) From d51a4c5c082a58c3a0200a465ac209827cd2d6d2 Mon Sep 17 00:00:00 2001 From: alimpens Date: Mon, 10 Mar 2025 14:54:03 +0100 Subject: [PATCH 14/16] Change test description --- apps/public/src/app/(general)/contact/Contact.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/public/src/app/(general)/contact/Contact.test.tsx b/apps/public/src/app/(general)/contact/Contact.test.tsx index 0f705712..8a49619f 100644 --- a/apps/public/src/app/(general)/contact/Contact.test.tsx +++ b/apps/public/src/app/(general)/contact/Contact.test.tsx @@ -68,7 +68,7 @@ describe('Contact', () => { expect(telInput).toHaveAccessibleDescription('Test 2') }) - it('should not render descriptions that are connected the e-mail and tel inputs when they are not provided', () => { + it('should not render descriptions when they are not provided', () => { const mockFormDataWithoutDescriptions = [ { ...mockFormData[0], From 9369250db76cf0f3cfeb09ffa249e38fd85b89ca Mon Sep 17 00:00:00 2001 From: Aram <37216945+alimpens@users.noreply.github.com> Date: Mon, 10 Mar 2025 16:33:44 +0100 Subject: [PATCH 15/16] Update comment Co-authored-by: Vincent de Graaf --- apps/public/src/app/(general)/contact/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/public/src/app/(general)/contact/page.tsx b/apps/public/src/app/(general)/contact/page.tsx index 9fa04c49..078468f9 100644 --- a/apps/public/src/app/(general)/contact/page.tsx +++ b/apps/public/src/app/(general)/contact/page.tsx @@ -24,7 +24,7 @@ export default async () => { if (!contactFormId) throw new Error('Contact form id not found') const contactForm = (await getStaticFormByStaticFormId({ staticFormId: contactFormId })).components - // A contact form is always an array of text area components, but TypeScript doesn't know that + // A contact form is always an array of two text area components, but TypeScript doesn't know that // We use a type guard here to make sure we're always working with the right type const filteredContactForm = contactForm.filter(isTextArea) From e712f325bbbfc2ace0fb50b0ed31d8345f103e79 Mon Sep 17 00:00:00 2001 From: Aram <37216945+alimpens@users.noreply.github.com> Date: Mon, 10 Mar 2025 16:34:02 +0100 Subject: [PATCH 16/16] Update test description Co-authored-by: Vincent de Graaf --- apps/public/src/app/(general)/contact/Contact.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/public/src/app/(general)/contact/Contact.test.tsx b/apps/public/src/app/(general)/contact/Contact.test.tsx index 8a49619f..75d733cf 100644 --- a/apps/public/src/app/(general)/contact/Contact.test.tsx +++ b/apps/public/src/app/(general)/contact/Contact.test.tsx @@ -58,7 +58,7 @@ describe('Contact', () => { expect(screen.queryByText('Test error message')).toBeInTheDocument() }) - it('should render descriptions that are connected the e-mail and tel inputs', () => { + it('should render descriptions that are connected to the e-mail and tel inputs', () => { render() const emailInput = screen.getByRole('textbox', { name: 'Wat is uw e-mailadres? (niet verplicht)' })