Skip to content

Commit

Permalink
feat: add form validation
Browse files Browse the repository at this point in the history
  • Loading branch information
pooriamehregan committed Apr 26, 2024
1 parent 908ba47 commit f4c8dbc
Show file tree
Hide file tree
Showing 36 changed files with 444 additions and 220 deletions.
7 changes: 5 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,11 @@
"no-implicit-coercion": "warn",
"no-magic-numbers": ["warn", {"ignoreArrayIndexes": true }],
"no-param-reassign": "error",
"no-shadow": "error",
"prefer-const": "error"
"no-shadow": "off",
"@typescript-eslint/no-shadow": "error",
"prefer-const": "error",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error"
}
},
{
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
"editor.formatOnSaveMode": "file",
"editor.inlineSuggest.suppressSuggestions": false,
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true
"files.trimTrailingWhitespace": true,
"css.lint.validProperties": ["composes"]
}
103 changes: 103 additions & 0 deletions apps/contact-form/app/[lang]/contact-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
'use client';

import { Paragraph, Textarea, Textfield } from '@digdir/designsystemet-react';
import { LabelWithTag, SubmitButton } from '@fdk-frontend/ui';
import { type Dictionary } from '@fdk-frontend/dictionaries';
import { useFormState } from 'react-dom';
import styles from './page.module.css';
import { sendEmailAction } from '../actions';
import { EMPTY_FORM_STATE, extractErrorMessages } from '@fdk-frontend/utils';

type Props = {
dictionary: Dictionary;
};

const ContactForm = ({ dictionary }: Props) => {
const [state, formAction] = useFormState(sendEmailAction, EMPTY_FORM_STATE);
const textAreaCols = 100;
const textAreaRows = 5;

return (
<form action={formAction}>
<Textarea
name='dataset'
className={styles.textArea}
required
cols={textAreaCols}
rows={textAreaRows}
label={
<LabelWithTag
labelText={dictionary.contactForm.dataset.label}
tagText={dictionary.shouldBeFilledOut}
/>
}
description={<Paragraph>{dictionary.contactForm.dataset.description}</Paragraph>}
error={extractErrorMessages('dataset', state, dictionary)}
/>
<Textarea
name='location'
className={styles.textArea}
cols={textAreaCols}
rows={textAreaRows}
label={<LabelWithTag labelText={dictionary.contactForm.location.label} />}
description={<Paragraph>{dictionary.contactForm.location.description}</Paragraph>}
/>
<Textarea
name='efforts'
className={styles.textArea}
cols={textAreaCols}
rows={textAreaRows}
label={<LabelWithTag labelText={dictionary.contactForm.efforts.label} />}
description={<Paragraph>{dictionary.contactForm.efforts.description}</Paragraph>}
/>
<Textfield
name='name'
required
className={styles.textField}
error={extractErrorMessages('name', state, dictionary)}
label={
<LabelWithTag
labelText={dictionary.name}
tagText={dictionary.shouldBeFilledOut}
/>
}
/>
<Textfield
name='email'
required
className={styles.textField}
error={extractErrorMessages('email', state, dictionary)}
type='email'
label={
<LabelWithTag
labelText={dictionary.email}
tagText={dictionary.shouldBeFilledOut}
/>
}
/>
<Textfield
name='organizationNumber'
required
className={styles.textFieldHalfWith}
error={extractErrorMessages('organizationNumber', state, dictionary)}
label={
<LabelWithTag
labelText={dictionary.organizationNumber}
tagText={dictionary.shouldBeFilledOut}
/>
}
/>
<Textfield
name='phoneNumber'
className={styles.textFieldHalfWith}
label={dictionary.phoneNumber}
/>
<SubmitButton
buttonText={dictionary.submitRequest}
extendedClassName={styles.button}
/>
</form>
);
};

export default ContactForm;
18 changes: 4 additions & 14 deletions apps/contact-form/app/[lang]/page.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,18 @@
flex-direction: column;
align-items: center;
justify-content: center;
width: 843px;
margin-top: 5rem;
max-width: 843px;
margin: 5rem auto;
}

.contentContainer :nth-child(7) {
margin-bottom: 2rem;
.contentContainer .paragraph {
margin-bottom: 3rem;
}

.paragraph {
text-align: center;
}

.alert {
margin: 5rem 0 1rem 0;
width: 100%;
box-sizing: border-box;
}

.textArea {
margin-top: 2rem;
}
Expand All @@ -40,7 +34,3 @@
margin-top: 3rem;
align-self: flex-start;
}

.button > p {
color: #fff;
}
130 changes: 20 additions & 110 deletions apps/contact-form/app/[lang]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,126 +1,36 @@
import 'server-only';

import { getDictionary, type Locale } from '@fdk-frontend/dictionaries';
import styles from './page.module.css';
import { Alert, Button, Heading, Paragraph, Textarea, Textfield } from '@digdir/designsystemet-react';
import { Container } from '@fdk-frontend/ui/server';
import { LabelWithTag } from '@fdk-frontend/ui';
import ContactForm from './contact-form';
import { Heading, Paragraph } from '@digdir/designsystemet-react';
import styles from './page.module.css';
import React from 'react';

interface Props {
type Props = {
params: {
lang: Locale['code'];
};
}
};

const Index = async ({ params: { lang } }: Props) => {
const dictionary = await getDictionary(lang);

return (
<Container flexDirection={'column'}>
<div className={styles.contentContainer}>
<Heading
size='xlarge'
spacing
>
{dictionary.contactForm.title}
</Heading>
<Paragraph
size='large'
className={styles.paragraph}
>
{dictionary.contactForm.description}
</Paragraph>
<Alert
severity='info'
className={styles.alert}
>
<Heading
size='xsmall'
spacing
>
{dictionary.fillTheFormUnder}
</Heading>
<Paragraph>{dictionary.contactForm.alertDescription}</Paragraph>
</Alert>
<Textarea
className={styles.textArea}
cols={100}
rows={5}
label={
<LabelWithTag
labelText={dictionary.contactForm.textArea[0].label}
tagText={dictionary.shouldBeFilledOut}
/>
}
description={<Paragraph>{dictionary.contactForm.textArea[0].description}</Paragraph>}
error=''
/>
<Textarea
className={styles.textArea}
cols={100}
rows={5}
label={<LabelWithTag labelText={dictionary.contactForm.textArea[1].label} />}
description={<Paragraph>{dictionary.contactForm.textArea[1].description}</Paragraph>}
error=''
/>
<Textarea
className={styles.textArea}
cols={100}
rows={5}
label={<LabelWithTag labelText={dictionary.contactForm.textArea[2].label} />}
description={<Paragraph>{dictionary.contactForm.textArea[2].description}</Paragraph>}
error=''
/>
<Textarea
className={styles.textArea}
cols={100}
rows={5}
label={<LabelWithTag labelText={dictionary.contactForm.textArea[3].label} />}
description={<Paragraph>{dictionary.contactForm.textArea[3].description}</Paragraph>}
error=''
/>
<Textfield
className={styles.textField}
error=''
label={
<LabelWithTag
labelText={dictionary.name}
tagText={dictionary.shouldBeFilledOut}
/>
}
/>
<Textfield
className={styles.textField}
description=''
error=''
label={
<LabelWithTag
labelText={dictionary.email}
tagText={dictionary.shouldBeFilledOut}
/>
}
/>
<Textfield
className={styles.textFieldHalfWith}
description=''
error=''
label={
<LabelWithTag
labelText={dictionary.organizationNumber}
tagText={dictionary.shouldBeFilledOut}
/>
}
/>
<Textfield
className={styles.textFieldHalfWith}
description=''
error=''
label={dictionary.phoneNumber}
/>
<Button className={styles.button}>
<Paragraph>{dictionary.submitRequest}</Paragraph>
</Button>
</div>
<Container extendedClassName={styles.contentContainer}>
<Heading
size='xlarge'
spacing
>
{dictionary.contactForm.title}
</Heading>
<Paragraph
size='large'
className={styles.paragraph}
>
{dictionary.contactForm.description}
</Paragraph>
<ContactForm dictionary={dictionary} />
</Container>
);
};
Expand Down
20 changes: 20 additions & 0 deletions apps/contact-form/app/[lang]/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* eslint-disable camelcase */
/* eslint-disable no-magic-numbers */
import { z } from 'zod';

/**
* The validation schema for the contact form.
* Form more information on the schema library, see https://zod.dev/?id=table-of-contents
*/
export const schema = z.object({
dataset: z.string({ required_error: 'required' }).min(3, { message: 'minimumLength,3'}),
location: z.string().optional(),
efforts: z.string().optional(),
email: z.string({ required_error: 'required' }).email({ message: 'invalidEmail' }),
phoneNumber: z.string().optional(),
name: z.string({ required_error: 'required' }),
organizationNumber: z.string({ required_error: 'required' }).length(9, { message: 'invalidOrganizationNumber' }),
});

export type SchemaType = z.infer<typeof schema>;

28 changes: 28 additions & 0 deletions apps/contact-form/app/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* eslint-disable no-console */
'use server';

import { revalidatePath } from 'next/cache';
import { extractFormEntries, FormState, FormStatusEnum, getFormState } from '@fdk-frontend/utils';
import { schema } from './[lang]/schema';

export const sendEmailAction = async (prevState: FormState, formData: FormData) => {
const parse = schema.safeParse(extractFormEntries(formData));

if (!parse.success) {
return getFormState(FormStatusEnum.ERROR, parse.error.formErrors.fieldErrors);
}

try {
// TODO: Send email with form data
// eslint-disable-next-line @typescript-eslint/no-unused-vars
// const data = parse.data;

revalidatePath('/');
return getFormState(FormStatusEnum.SUCCESS, 'Mail was sent successfully');
} catch (e) {
// TODO: Log error to some error tracking service, not logging out to console because of security reasons
// eslint-disable-next-line no-console
console.error('Sending email for Contact Form failed');
return getFormState(FormStatusEnum.ERROR, 'Sending email failed');
}
};
4 changes: 0 additions & 4 deletions apps/contact-form/app/api/hello/route.ts

This file was deleted.

4 changes: 4 additions & 0 deletions apps/contact-form/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
"resolveJsonModule": true,
"isolatedModules": true,
"incremental": true,
"lib": [
"dom",
"dom.iterable",
],
"plugins": [
{
"name": "next"
Expand Down
Loading

0 comments on commit f4c8dbc

Please sign in to comment.