test
This commit is contained in:
		| @@ -0,0 +1,164 @@ | ||||
| "use client" | ||||
|  | ||||
| import * as React from "react" | ||||
| import { | ||||
|   Card, | ||||
|   CardTitle, | ||||
|   CardHeader, | ||||
|   CardDescription, | ||||
|   CardContent, | ||||
|   CardFooter, | ||||
| } from "@/components/ui/card" | ||||
| import { Input } from "@/components/ui/input" | ||||
| import { Label } from "@/components/ui/label" | ||||
| import { Button } from "@/components/ui/button" | ||||
| import { Textarea } from "@/components/ui/textarea" | ||||
| import { z } from "zod" | ||||
|  | ||||
| const exampleFormSchema = z.object({ | ||||
|   name: z.string().min(1), | ||||
|   email: z.string().email(), | ||||
|   message: z.string().min(1), | ||||
| }) | ||||
|  | ||||
| export function ExampleForm() { | ||||
|   const [pending, setPending] = React.useState(false) | ||||
|   const [state, setState] = React.useState({ | ||||
|     defaultValues: { | ||||
|       name: "", | ||||
|       email: "", | ||||
|       message: "", | ||||
|     }, | ||||
|     success: false, | ||||
|     errors: { | ||||
|       name: "", | ||||
|       email: "", | ||||
|       message: "", | ||||
|     }, | ||||
|   }) | ||||
|  | ||||
|   const handleSubmit = React.useCallback( | ||||
|     (e: React.FormEvent<HTMLFormElement>) => { | ||||
|       e.preventDefault() | ||||
|       setPending(true) | ||||
|  | ||||
|       const formData = new FormData(e.target as HTMLFormElement) | ||||
|       const data = Object.fromEntries(formData.entries()) | ||||
|       const result = exampleFormSchema.safeParse(data) | ||||
|  | ||||
|       if (!result.success) { | ||||
|         setState({ | ||||
|           ...state, | ||||
|           errors: Object.fromEntries( | ||||
|             Object.entries(result.error.flatten().fieldErrors).map( | ||||
|               ([key, value]) => [key, value?.[0] ?? ""] | ||||
|             ) | ||||
|           ) as Record<keyof typeof state.errors, string>, | ||||
|         }) | ||||
|         setPending(false) | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       setPending(false) | ||||
|     }, | ||||
|     [state] | ||||
|   ) | ||||
|  | ||||
|   return ( | ||||
|     <Card className="w-full max-w-sm"> | ||||
|       <CardHeader> | ||||
|         <CardTitle>How can we help?</CardTitle> | ||||
|         <CardDescription> | ||||
|           Need help with your project? We're here to assist you. | ||||
|         </CardDescription> | ||||
|       </CardHeader> | ||||
|       <form onSubmit={handleSubmit}> | ||||
|         <CardContent className="flex flex-col gap-6"> | ||||
|           <div | ||||
|             className="group/field grid gap-2" | ||||
|             data-invalid={!!state.errors?.name} | ||||
|           > | ||||
|             <Label | ||||
|               htmlFor="name" | ||||
|               className="group-data-[invalid=true]/field:text-destructive" | ||||
|             > | ||||
|               Name <span aria-hidden="true">*</span> | ||||
|             </Label> | ||||
|             <Input | ||||
|               id="name" | ||||
|               name="name" | ||||
|               placeholder="Lee Robinson" | ||||
|               className="group-data-[invalid=true]/field:border-destructive focus-visible:group-data-[invalid=true]/field:ring-destructive" | ||||
|               disabled={pending} | ||||
|               aria-invalid={!!state.errors?.name} | ||||
|               aria-errormessage="error-name" | ||||
|               defaultValue={state.defaultValues.name} | ||||
|             /> | ||||
|             {state.errors?.name && ( | ||||
|               <p id="error-name" className="text-destructive text-sm"> | ||||
|                 {state.errors.name} | ||||
|               </p> | ||||
|             )} | ||||
|           </div> | ||||
|           <div | ||||
|             className="group/field grid gap-2" | ||||
|             data-invalid={!!state.errors?.email} | ||||
|           > | ||||
|             <Label | ||||
|               htmlFor="email" | ||||
|               className="group-data-[invalid=true]/field:text-destructive" | ||||
|             > | ||||
|               Email <span aria-hidden="true">*</span> | ||||
|             </Label> | ||||
|             <Input | ||||
|               id="email" | ||||
|               name="email" | ||||
|               placeholder="leerob@acme.com" | ||||
|               className="group-data-[invalid=true]/field:border-destructive focus-visible:group-data-[invalid=true]/field:ring-destructive" | ||||
|               disabled={pending} | ||||
|               aria-invalid={!!state.errors?.email} | ||||
|               aria-errormessage="error-email" | ||||
|               defaultValue={state.defaultValues.email} | ||||
|             /> | ||||
|             {state.errors?.email && ( | ||||
|               <p id="error-email" className="text-destructive text-sm"> | ||||
|                 {state.errors.email} | ||||
|               </p> | ||||
|             )} | ||||
|           </div> | ||||
|           <div | ||||
|             className="group/field grid gap-2" | ||||
|             data-invalid={!!state.errors?.message} | ||||
|           > | ||||
|             <Label | ||||
|               htmlFor="message" | ||||
|               className="group-data-[invalid=true]/field:text-destructive" | ||||
|             > | ||||
|               Message <span aria-hidden="true">*</span> | ||||
|             </Label> | ||||
|             <Textarea | ||||
|               id="message" | ||||
|               name="message" | ||||
|               placeholder="Type your message here..." | ||||
|               className="group-data-[invalid=true]/field:border-destructive focus-visible:group-data-[invalid=true]/field:ring-destructive" | ||||
|               disabled={pending} | ||||
|               aria-invalid={!!state.errors?.message} | ||||
|               aria-errormessage="error-message" | ||||
|               defaultValue={state.defaultValues.message} | ||||
|             /> | ||||
|             {state.errors?.message && ( | ||||
|               <p id="error-message" className="text-destructive text-sm"> | ||||
|                 {state.errors.message} | ||||
|               </p> | ||||
|             )} | ||||
|           </div> | ||||
|         </CardContent> | ||||
|         <CardFooter> | ||||
|           <Button type="submit" size="sm" disabled={pending}> | ||||
|             {pending ? "Sending..." : "Send Message"} | ||||
|           </Button> | ||||
|         </CardFooter> | ||||
|       </form> | ||||
|     </Card> | ||||
|   ) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user