Untitled
unknown
plain_text
20 days ago
4.9 kB
1
Indexable
Never
'use client'; import React, { useState, useEffect } from 'react'; import BackButton from '@/components/ui/back-button'; import FadeIn from '@/components/ui/fade-in'; import DateOfBirth, { AgeLabel } from '@/components/ui/dob'; import { Form, FormControl, FormDescription, FormItem, FormLabel, FormMessage, } from '@/components/ui/form'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { Button } from '@/components/ui/button'; import { parse, format, isValid } from 'date-fns'; import { toast } from 'sonner'; const thisYear = new Date().getFullYear(); const thisMonth = new Date().getMonth(); const today = new Date().getDate(); const formSchema = z .object({ 'bday-day': z .string() .min(1, 'Day is required') .refine((val) => parseInt(val) >= 1 && parseInt(val) <= 31, { message: 'Day must be between 01 and 31', }), 'bday-month': z .string() .min(1, 'Month is required') .refine((val) => parseInt(val) >= 1 && parseInt(val) <= 12, { message: 'Month must be between 01 and 12', }), 'bday-year': z .string() .min(4, 'Year is required') .refine((val) => parseInt(val) >= 1900 && parseInt(val) <= thisYear, { message: `Year must be between 1900 and ${thisYear}`, }), }) .refine( (data) => { const date = parse( `${data['bday-year']}-${data['bday-month']}-${data['bday-day']}`, 'yyyy-MM-dd', new Date(), ); return isValid(date) && date <= new Date(); }, { message: 'Date of birth cannot be in the future', path: ['bday-year'], }, ); const Page: React.FC = () => { const [age, setAge] = useState<number | null>(null); const form = useForm<z.infer<typeof formSchema>>({ resolver: zodResolver(formSchema), defaultValues: { 'bday-day': '', 'bday-month': '', 'bday-year': '', }, }); const { handleSubmit, formState: { errors }, control, setValue, } = form; // Load saved date of birth from local storage on component mount useEffect(() => { const savedDOB = localStorage.getItem('userDateOfBirth'); if (savedDOB) { const [year, month, day] = savedDOB.split('-'); setValue('bday-year', year); setValue('bday-month', month); setValue('bday-day', day); } }, [setValue]); function onSubmit(values: z.infer<typeof formSchema>) { const { 'bday-day': day, 'bday-month': month, 'bday-year': year } = values; // Parse the input into a Date object const dateOfBirth = parse(`${year}-${month}-${day}`, 'yyyy-MM-dd', new Date()); // Check if the date is valid if (isValid(dateOfBirth)) { // Format the date as ISO 8601 (YYYY-MM-DD) const formattedDateOfBirth = format(dateOfBirth, 'yyyy-MM-dd'); // Save to local storage localStorage.setItem('userDateOfBirth', formattedDateOfBirth); // Show toast with the formatted date toast.success(`Date of Birth Saved · ${formattedDateOfBirth}`); } } return ( <FadeIn> <BackButton /> <div className="mx-auto w-full max-w-md px-[calc(theme('spacing[8]')+2vw)] py-12 sm:px-8 sm:py-16"> <div className="mb-12"> <div className="mb-4 text-lg font-medium sm:text-xl">Date of Birth Input</div> <div className="text-balance text-sm/normal text-gray-600 sm:text-base"> This input component autofills, handles invalid date errors, and shows the user's age to prevent typos. It's been implemented at three companies, all of which continue to use it successfully, and has been thoroughly tested on users across various devices. </div> </div> <div> <Form {...form}> <form onSubmit={handleSubmit(onSubmit)} className="space-y-8"> <FormItem> <FormLabel> Date of birth <AgeLabel age={age} year={form.watch('bday-year')} /> </FormLabel> <FormControl> <DateOfBirth control={control} setAge={setAge} errors={errors} /> </FormControl> <FormDescription>Example · 30/04/1970</FormDescription> <FormMessage className="not-sr-only"> {errors['bday-day']?.message || errors['bday-month']?.message || errors['bday-year']?.message || (errors as any)?.root?.message} </FormMessage> </FormItem> <Button variant="primary" type="submit"> Submit </Button> </form> </Form> </div> </div> </FadeIn> ); }; export default Page;
Leave a Comment