Registration form example
This is a simple login form:
- If you enter an email address that doesn't end with @functn.com you will see an error message from the server.
registration-form.tsx
'use client';
import Link from 'next/link';
import { Button } from '@/ui/components/button/button';
import { Checkbox } from '@/ui/components/forms/checkbox';
import { Form } from '@/ui/components/forms/form';
import { useFormAction } from '@/ui/components/forms/hooks/use-form-action';
import { Radio, RadioGroup } from '@/ui/components/forms/radio';
import { Select, SelectItem } from '@/ui/components/forms/select';
import { TextField } from '@/ui/components/forms/text-field';
import {
FieldErrorSummary,
FormErrors,
ServerError,
validators,
} from '@/ui/components/forms/validation';
import { registerAction } from './action';
export const RegistrationForm = () => {
const form = useFormAction<{
name: string;
email: string;
phone: string;
password: string;
favoriteAnimal: string;
contactMethod: string;
'terms-and-conditions': string;
}>(registerAction);
return (
<Form
action={form.action}
className="flex max-w-lg flex-col gap-4 rounded-lg border-2 border-gray-200 p-4 shadow-xl"
>
<h2 className="text-3xl font-bold">Create an account</h2>
<FormErrors>
<ServerError
errorMessage={form.serverError}
title="Could not register"
/>
<FieldErrorSummary
title="There are errors in your submission"
subtitle="Please correct your input in these fields:"
fieldLabels={{
name: 'Name',
email: 'Email Address',
phone: 'Phone Number',
password: 'Password',
favoriteAnimal: 'Favorite Animal',
contactMethod: 'Preferred Contact Method',
'terms-and-conditions': 'Terms and Conditions',
}}
/>
</FormErrors>
<TextField
id="name"
name="name"
label="Name"
type="text"
description="Please enter your full name without any middle names"
defaultValue={form.defaultValues.name}
autoComplete="name"
isRequired
validate={(value) => {
if (!value) {
return 'Please enter your name';
}
}}
/>
<TextField
id="email"
name="email"
label="Email Address"
type="email"
description="Please enter a valid email address. Example: test@test.com"
defaultValue={form.defaultValues.email}
autoComplete="email"
validate={(value) => {
if (!validators.email(value)) {
return 'Invalid email address';
}
}}
isRequired
/>
<TextField
id="phone"
name="phone"
label="Phone Number"
type="tel"
description="Please enter your phone number with the country code. Example: +43"
defaultValue={form.defaultValues.phone}
autoComplete="tel"
/>
<TextField
id="password"
name="password"
label="Password"
type="password"
description="Password must be at least 8 characters long and contain at least one
uppercase letter, one lowercase letter, and one number."
defaultValue={form.defaultValues.password}
autoComplete="new-password"
isRequired
validate={(value) => {
if (value.length < 8) {
return 'Password must be at least 8 characters long';
}
if (!/[A-Z]/.test(value)) {
return 'Password must contain at least one uppercase letter';
}
if (!/[a-z]/.test(value)) {
return 'Password must contain at least one lowercase letter';
}
if (!/[0-9]/.test(value)) {
return 'Password must contain at least one number';
}
}}
/>
<Select
id="favoriteAnimal"
name="favoriteAnimal"
label="Favorite Animal"
isRequired
defaultValue={form.defaultValues.favoriteAnimal}
validate={(value) => {
if (!value) {
return 'Please select an animal';
}
if (value !== 'dog') {
return 'Please select dogs as your favorite animal';
}
}}
>
<SelectItem id="cat">Cat</SelectItem>
<SelectItem id="dog">Dog</SelectItem>
<SelectItem id="kangaroo">Kangaroo</SelectItem>
<SelectItem id="panda">Panda</SelectItem>
<SelectItem id="snake">Snake</SelectItem>
<SelectItem id="lion">Lion</SelectItem>
<SelectItem id="tiger">Tiger</SelectItem>
<SelectItem id="elephant">Elephant</SelectItem>
<SelectItem id="giraffe">Giraffe</SelectItem>
<SelectItem id="zebra">Zebra</SelectItem>
<SelectItem id="hippo">Hippo</SelectItem>
<SelectItem id="rhino">Rhino</SelectItem>
<SelectItem id="leopard">Leopard</SelectItem>
<SelectItem id="cheetah">Cheetah</SelectItem>
<SelectItem id="monkey">Monkey</SelectItem>
<SelectItem id="fox">Fox</SelectItem>
<SelectItem id="wolf">Wolf</SelectItem>
</Select>
<RadioGroup
id="contactMethod"
name="contactMethod"
label="Preferred Contact Method"
isRequired
defaultValue={form.defaultValues.contactMethod}
validate={(value) => {
console.log('RADIO VALIDATION', value);
if (!value) {
return 'Please select a contact method';
}
if (value === 'pigeon') {
return 'Please select a modern contact method';
}
}}
orientation="vertical"
>
<Radio value="email">Email</Radio>
<Radio value="phone">Phone</Radio>
<Radio value="pigeon">Carrier Pigeon</Radio>
</RadioGroup>
<Checkbox
id="terms-and-conditions"
name="terms-and-conditions"
value="terms-and-conditions"
label="I agree to the terms and conditions"
defaultSelected={!!form.defaultValues['terms-and-conditions']}
description={
<>
Read our full{' '}
<Link
href="/terms-and-conditions"
className="text-primary-500 hover:underline"
>
Terms and Conditions
</Link>
</>
}
isRequired
validate={(value) => {
if (!value) {
return 'Please agree to the terms and conditions';
}
}}
className="pt-2"
/>
<div>
<Button type="submit">Submit</Button>
</div>
</Form>
);
};action.ts
import { createUser } from '@/api/auth';
import { FormAction } from '@/ui/components/forms/types';
import { getFormValues } from '@/ui/components/forms/util';
export const registerAction: FormAction = async (currentState, formData) => {
const values = getFormValues<{
name: string;
email: string;
phone: string;
password: string;
favoriteAnimal: string;
contactMethod: string;
'terms-and-conditions': string;
}>(formData);
// We can do some basic input validation, this should have all been caught
// by the client-side validation so in the case this fails we either have
// a bug with the client code or someone is calling this API maliciously.
// Either way, just return a generic error message to the user and bail out.
if (!values.email || !values.password) {
return {
status: 'error',
formData,
serverError: 'Something went wrong',
};
}
try {
await createUser(values.email, values.password);
} catch (error) {
return {
status: 'error',
formData,
serverError: 'Account must be created with an @functn.com email address',
};
}
return {
status: 'success',
formData,
};
};