This is a solution to the Multi-step form challenge on Frontend Mentor. Frontend Mentor challenges help you improve your coding skills by building realistic projects.
Users should be able to:
- Complete each step of the sequence
- Go back to a previous step to update their selections
- See a summary of their selections on the final step and confirm their order
- View the optimal layout for the interface depending on their device's screen size
- See hover and focus states for all interactive elements on the page
- Receive form validation messages if:
- A field has been missed
- The email address is not formatted correctly
- A step is submitted, but no selection has been made
- Tailwind
- React - JS library
- Next.js 13 - React framework
- Tailwind - For styles
- Jotai - For global state management
- Shadcn/ui - As a component library
- React hook form - For form rendering
- Zod - For form validation
For certain fields, I had to create a field with multiple options. First, I just added these in the jsx manually, but I learned quickly that I had to do it differently if I wanted to keep the code maintainable. So I created Map
and learned how to loop over it:
export const planPricing = new Map([
[
"arcade",
{
name: "Arcade",
price: {
monthly: 9,
yearly: 90,
},
},
],
[
"advanced",
{
name: "Advanced",
price: {
monthly: 12,
yearly: 120,
},
},
],
[
"pro",
{
name: "Pro",
price: {
monthly: 15,
yearly: 150,
},
},
],
] as const)
{Array.from(planPricing.entries()).map(([key, value]) => (
<FormItem key={key}>
<FormLabel htmlFor={key}>
<FormControl>
<RadioGroupItem
className="sr-only"
value={key}
id={key}
/>
</FormControl>
<Card className="cursor-pointer border hover:border-primary">
<CardContent className="flex gap-4 p-4 lg:flex-col lg:gap-8">
<Image
src={`/icon-${key}.svg`}
width={40}
height={40}
alt={key}
/>
<div className="flex flex-col gap-2">
<span className="font-bold">{value.name}</span>
{yearlyBillingValue ? (
<>
<span className="text-muted">
€{value.price.yearly}/yr
</span>
<span>2 months free</span>
</>
) : (
<span className="text-muted">
€{value.price.monthly}/mo
</span>
)}
</div>
</CardContent>
</Card>
</FormLabel>
</FormItem>
))}