-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use server actions for form submits (#637)
- Loading branch information
Showing
19 changed files
with
313 additions
and
450 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
...rc/app/(general)/aanvullende-vragen/[classification]/[panelId]/AanvullendeVragen.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { render, screen } from '@testing-library/react' | ||
import { useActionState } from 'react' | ||
import { vi } from 'vitest' | ||
|
||
import mockFormData from 'apps/public/src/mocks/mockFormData.json' | ||
|
||
import { AanvullendeVragen } from './AanvullendeVragen' | ||
|
||
vi.mock('react', async (importOriginal) => { | ||
const actual = await importOriginal() | ||
return { | ||
...(typeof actual === 'object' ? actual : {}), | ||
useActionState: vi.fn().mockReturnValue([{}, vi.fn()]), | ||
} | ||
}) | ||
|
||
describe('AanvullendeVragen', () => { | ||
it('renders a heading', () => { | ||
const action = vi.fn() | ||
|
||
render( | ||
<AanvullendeVragen action={action} formData={mockFormData.components[0].components} previousPanelPath="/prev" />, | ||
) | ||
|
||
const heading = screen.getByRole('heading', { name: 'Beschrijf uw melding' }) | ||
|
||
expect(heading).toBeInTheDocument() | ||
}) | ||
|
||
it('renders form data', () => { | ||
const action = vi.fn() | ||
|
||
render( | ||
<AanvullendeVragen action={action} formData={mockFormData.components[0].components} previousPanelPath="/prev" />, | ||
) | ||
|
||
const question = screen.getByRole('textbox', { name: /First question/ }) | ||
|
||
expect(question).toBeInTheDocument() | ||
}) | ||
|
||
it('should render an error message', () => { | ||
;(useActionState as jest.Mock).mockReturnValue([{ message: 'Test error message' }, vi.fn()]) | ||
|
||
const action = vi.fn() | ||
|
||
render( | ||
<AanvullendeVragen action={action} formData={mockFormData.components[0].components} previousPanelPath="/prev" />, | ||
) | ||
|
||
expect(screen.queryByText('Test error message')).toBeInTheDocument() | ||
}) | ||
}) |
31 changes: 31 additions & 0 deletions
31
...lic/src/app/(general)/aanvullende-vragen/[classification]/[panelId]/AanvullendeVragen.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
'use client' | ||
|
||
import { Grid, Heading, Paragraph } from '@amsterdam/design-system-react' | ||
import { FormRenderer } from '@meldingen/form-renderer' | ||
import { useActionState } from 'react' | ||
|
||
import { BackLink } from '../../../_components/BackLink' | ||
|
||
// TODO: fix types | ||
type Props = { | ||
action: any | ||
formData: any[] | ||
previousPanelPath: string | ||
} | ||
|
||
const initialState: { message?: string } = {} | ||
|
||
export const AanvullendeVragen = ({ action, formData, previousPanelPath }: Props) => { | ||
const [formState, formAction] = useActionState(action, initialState) | ||
|
||
return ( | ||
<Grid paddingBottom="large" paddingTop="medium"> | ||
<Grid.Cell span={{ narrow: 4, medium: 6, wide: 7 }} start={{ narrow: 1, medium: 2, wide: 2 }}> | ||
{formState?.message && <Paragraph>{formState.message}</Paragraph>} | ||
<BackLink href={previousPanelPath}>Vorige vraag</BackLink> | ||
<Heading>Beschrijf uw melding</Heading> | ||
<FormRenderer formData={formData} action={formAction} /> | ||
</Grid.Cell> | ||
</Grid> | ||
) | ||
} |
File renamed without changes.
File renamed without changes.
59 changes: 59 additions & 0 deletions
59
apps/public/src/app/(general)/aanvullende-vragen/[classification]/[panelId]/actions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
'use server' | ||
|
||
import { postMeldingByMeldingIdQuestionByQuestionId } from '@meldingen/api-client' | ||
import { cookies } from 'next/headers' | ||
import { redirect } from 'next/navigation' | ||
|
||
import { mergeCheckboxAnswers } from './_utils/mergeCheckboxAnswers' | ||
|
||
type ArgsType = { | ||
questionIds: { key: string; id: number }[] | ||
lastPanelPath: string | ||
nextPanelPath: string | ||
} | ||
|
||
export const postForm = async (args: ArgsType, _: unknown, formData: FormData) => { | ||
// Get session variables from cookies | ||
const cookieStore = await cookies() | ||
const meldingId = cookieStore.get('id')?.value | ||
const token = cookieStore.get('token')?.value | ||
|
||
// Set last panel path in cookies | ||
cookieStore.set('lastPanelPath', args.lastPanelPath) | ||
|
||
// Checkbox answers are stored as separate key-value pairs in the FormData object. | ||
// This function merges these answers into a single string value per question, using an identifier in the Checkbox component. | ||
// TODO: This isn't the most robust solution. | ||
const formDataObj = Object.fromEntries(formData) | ||
const entries = Object.entries(formDataObj) | ||
const entriesWithMergedCheckboxes = Object.entries(mergeCheckboxAnswers(entries)) | ||
|
||
const promiseArray = entriesWithMergedCheckboxes.map(([key, value]) => { | ||
if (value instanceof File) return undefined | ||
|
||
// Filter out empty answers | ||
if (value.length === 0) return undefined | ||
|
||
const questionId = args.questionIds.find((component) => component.key === key)?.id | ||
|
||
if (!meldingId || !questionId || !token) return undefined | ||
|
||
return postMeldingByMeldingIdQuestionByQuestionId({ | ||
meldingId: parseInt(meldingId, 10), | ||
questionId, | ||
token, | ||
requestBody: { text: value }, | ||
}).catch((error) => error) | ||
}) | ||
|
||
const results = await Promise.all(promiseArray) | ||
|
||
// Return a string of all error messages and do not redirect if one of the requests failed | ||
const erroredResults = results.filter((result) => result instanceof Error) | ||
|
||
if (erroredResults.length > 0) { | ||
return { message: erroredResults.map((error) => error.message).join(', ') } | ||
} | ||
|
||
return redirect(args.nextPanelPath) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.