├── .eslintrc.json ├── .gitignore ├── README.md ├── components.json ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── next.svg └── vercel.svg ├── src ├── actions │ ├── getUserData.ts │ └── supabase.ts ├── app │ ├── favicon.ico │ ├── layout.tsx │ ├── page.tsx │ └── profile │ │ └── page.tsx ├── components │ ├── AuthModal.tsx │ ├── CreateProfileModal.tsx │ ├── Navbar.tsx │ ├── UserCard.tsx │ └── ui │ │ ├── button.tsx │ │ ├── dialog.tsx │ │ ├── form.tsx │ │ ├── input.tsx │ │ └── label.tsx ├── context │ ├── AuthModalContext.ts │ └── CreateProfileContext.ts ├── lib │ └── utils.ts ├── providers │ └── ModalProvider.tsx ├── styles │ └── globals.css ├── types │ └── app.ts └── utils │ ├── supabaseClient.ts │ └── supabaseServer.ts ├── tailwind.config.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/styles/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth-complete", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@hookform/resolvers": "^3.3.4", 13 | "@radix-ui/react-dialog": "^1.0.5", 14 | "@radix-ui/react-label": "^2.0.2", 15 | "@radix-ui/react-slot": "^1.0.2", 16 | "@supabase/auth-helpers-nextjs": "^0.9.0", 17 | "@supabase/auth-helpers-react": "^0.4.2", 18 | "@supabase/auth-ui-react": "^0.4.7", 19 | "@supabase/auth-ui-shared": "^0.1.8", 20 | "@supabase/ssr": "^0.1.0", 21 | "@supabase/supabase-js": "^2.39.7", 22 | "class-variance-authority": "^0.7.0", 23 | "clsx": "^2.1.0", 24 | "lucide-react": "^0.344.0", 25 | "next": "14.1.1", 26 | "react": "^18", 27 | "react-dom": "^18", 28 | "react-hook-form": "^7.51.0", 29 | "tailwind-merge": "^2.2.1", 30 | "tailwindcss-animate": "^1.0.7", 31 | "uuid": "^9.0.1", 32 | "zod": "^3.22.4" 33 | }, 34 | "devDependencies": { 35 | "@types/node": "^20", 36 | "@types/react": "^18", 37 | "@types/react-dom": "^18", 38 | "@types/uuid": "^9.0.8", 39 | "autoprefixer": "^10.0.1", 40 | "eslint": "^8", 41 | "eslint-config-next": "14.1.1", 42 | "postcss": "^8", 43 | "supabase": "^1.145.4", 44 | "tailwindcss": "^3.3.0", 45 | "typescript": "^5" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/actions/getUserData.ts: -------------------------------------------------------------------------------- 1 | import { User } from '@/types/app'; 2 | 3 | const getUserData = async (): Promise => {}; 4 | 5 | export default getUserData; 6 | -------------------------------------------------------------------------------- /src/actions/supabase.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | 3 | export async function registerWithEmailAndPasword({ 4 | email, 5 | }: { 6 | email: string; 7 | }) {} 8 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laribright/supabase-nextjs/8285826069313d39e387601f9049e8024fe28ad2/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next'; 2 | import { Inter } from 'next/font/google'; 3 | 4 | import '@/styles/globals.css'; 5 | import Navbar from '@/components/Navbar'; 6 | import ModalProvider from '@/providers/ModalProvider'; 7 | 8 | const inter = Inter({ subsets: ['latin'] }); 9 | 10 | export const metadata: Metadata = { 11 | title: 'Complete auth', 12 | description: 'codewithlari', 13 | }; 14 | 15 | export default function RootLayout({ 16 | children, 17 | }: Readonly<{ 18 | children: React.ReactNode; 19 | }>) { 20 | return ( 21 | 22 | 23 |
24 | 25 | 26 | {children} 27 | 28 |
29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import getUserData from '@/actions/getUserData'; 2 | import UserCard from '@/components/UserCard'; 3 | 4 | export default async function Home() { 5 | const userData = await getUserData(); 6 | 7 | if (!userData) { 8 | // throw new error 9 | } 10 | 11 | return ( 12 |
13 | 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/app/profile/page.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@/components/ui/button'; 2 | 3 | const Profile = async () => { 4 | const handleAddSkill = async (formData: FormData) => {}; 5 | 6 | return ( 7 |
8 |
9 |
10 |
11 |
12 |
13 | {''} 18 |

{''}

19 |

{''}

20 | 34 |
35 |
36 |
37 |
38 | 44 | 45 |
46 | 47 | Skills 48 | 49 |
    50 |
  • JavaScript
  • 51 |
  • React
  • 52 |
  • Node.js
  • 53 |
  • HTML/CSS
  • 54 |
  • Tailwind CSS
  • 55 |
56 |
57 |
58 |
59 |
60 |
61 |

About Me

62 |

63 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed 64 | finibus est vitae tortor ullamcorper, ut vestibulum velit 65 | convallis. Aenean posuere risus non velit egestas suscipit. Nunc 66 | finibus vel ante id euismod. Vestibulum ante ipsum primis in 67 | faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam 68 | erat volutpat. Nulla vulputate pharetra tellus, in luctus risus 69 | rhoncus id. 70 |

71 |

Experience

72 |
73 |
74 | Web Developer 75 |

76 | at ABC Company 77 | 2017 - 2019 78 |

79 |
80 |

81 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed 82 | finibus est vitae tortor ullamcorper, ut vestibulum velit 83 | convallis. Aenean posuere risus non velit egestas suscipit. 84 |

85 |
86 |
87 | 88 |
89 | 90 |
91 |
92 |

About Me

93 |

94 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed 95 | finibus est vitae tortor ullamcorper, ut vestibulum velit 96 | convallis. Aenean posuere risus non velit egestas suscipit. 97 | Nunc finibus vel ante id euismod. Vestibulum ante ipsum primis 98 | in faucibus orci luctus et ultrices posuere cubilia Curae; 99 | Aliquam erat volutpat. Nulla vulputate pharetra tellus, in 100 | luctus risus rhoncus id. 101 |

102 |

Experience

103 |
104 |
105 | 106 | Web Developer 107 | 108 |

109 | at ABC Company 110 | 2017 - 2019 111 |

112 |
113 |

114 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed 115 | finibus est vitae tortor ullamcorper, ut vestibulum velit 116 | convallis. Aenean posuere risus non velit egestas suscipit. 117 |

118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | ); 126 | }; 127 | 128 | export default Profile; 129 | -------------------------------------------------------------------------------- /src/components/AuthModal.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useContext } from 'react'; 4 | 5 | import { 6 | Dialog, 7 | DialogContent, 8 | DialogHeader, 9 | DialogTitle, 10 | } from '@/components/ui/dialog'; 11 | import AuthModalContext from '@/context/AuthModalContext'; 12 | import { Button } from './ui/button'; 13 | import { useForm } from 'react-hook-form'; 14 | import { 15 | FormField, 16 | FormItem, 17 | FormLabel, 18 | FormControl, 19 | FormDescription, 20 | FormMessage, 21 | Form, 22 | } from './ui/form'; 23 | import { z } from 'zod'; 24 | import { zodResolver } from '@hookform/resolvers/zod'; 25 | import { Input } from './ui/input'; 26 | 27 | const AuthModal = () => { 28 | const { isAuthModalOpen, toggleAuthModal } = useContext(AuthModalContext); 29 | 30 | const formSchema = z.object({ 31 | email: z 32 | .string() 33 | .email() 34 | .min(5, { message: 'Job title must be at least 2 characters' }), 35 | password: z 36 | .string() 37 | .min(3, { message: 'Password must at least be 3 characters' }), 38 | }); 39 | 40 | const form = useForm>({ 41 | resolver: zodResolver(formSchema), 42 | defaultValues: { 43 | email: '', 44 | password: '', 45 | }, 46 | }); 47 | 48 | async function onSubmit(values: z.infer) {} 49 | 50 | async function githubAuth() {} 51 | 52 | return ( 53 | 54 | 55 | 56 | Authenticate 57 | 58 | 59 | 60 | 61 | 62 |
63 | 64 | ( 68 | 69 | Email 70 | 71 | 72 | 73 | Please enter your email 74 | 75 | 76 | )} 77 | /> 78 | 79 | ( 83 | 84 | Password 85 | 86 | 87 | 88 | Min 3 characters 89 | 90 | 91 | )} 92 | /> 93 | 94 | 95 | 96 | 97 |
98 |
99 | ); 100 | }; 101 | 102 | export default AuthModal; 103 | -------------------------------------------------------------------------------- /src/components/CreateProfileModal.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useContext } from 'react'; 4 | import { useForm } from 'react-hook-form'; 5 | import { zodResolver } from '@hookform/resolvers/zod'; 6 | import { z } from 'zod'; 7 | 8 | import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'; 9 | import { 10 | Form, 11 | FormControl, 12 | FormDescription, 13 | FormField, 14 | FormItem, 15 | FormLabel, 16 | FormMessage, 17 | } from '@/components/ui/form'; 18 | import { Input } from '@/components/ui/input'; 19 | 20 | import CreateProfileContext from '@/context/CreateProfileContext'; 21 | import { Button } from '@/components/ui/button'; 22 | 23 | const CreateProfileModal = () => { 24 | const { 25 | closeCreateProfileModal, 26 | isCreateProfileModalOpen, 27 | toggleCreateProfileModal, 28 | } = useContext(CreateProfileContext); 29 | 30 | const formSchema = z.object({ 31 | image: z 32 | .instanceof(FileList) 33 | .refine(file => file?.length == 1, 'Image is required'), 34 | jobtitle: z 35 | .string() 36 | .min(2, { message: 'Job title must be at least 2 characters' }), 37 | }); 38 | 39 | const form = useForm>({ 40 | resolver: zodResolver(formSchema), 41 | defaultValues: { 42 | jobtitle: '', 43 | }, 44 | }); 45 | 46 | const imageRef = form.register('image'); 47 | 48 | async function onSubmit(values: z.infer) {} 49 | 50 | return ( 51 | 55 | 56 | Set your profile 57 |
58 | 59 | ( 63 | 64 | Job title 65 | 66 | 67 | 68 | Your Job Title 69 | 70 | 71 | )} 72 | /> 73 | ( 77 | 78 | Logo 79 | 80 | { 85 | field.onChange(event.target?.files?.[0] ?? undefined); 86 | }} 87 | /> 88 | 89 | Your logo 90 | 91 | 92 | )} 93 | /> 94 | 95 | 98 | 99 | 100 |
101 |
102 | ); 103 | }; 104 | 105 | export default CreateProfileModal; 106 | -------------------------------------------------------------------------------- /src/components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import Link from 'next/link'; 4 | import { useContext } from 'react'; 5 | 6 | import { Button } from '@/components/ui/button'; 7 | import AuthModalContext from '@/context/AuthModalContext'; 8 | import CreateProfileContext from '@/context/CreateProfileContext'; 9 | 10 | const Navbar = () => { 11 | const { toggleAuthModal } = useContext(AuthModalContext); 12 | const { toggleCreateProfileModal } = useContext(CreateProfileContext); 13 | 14 | return ( 15 | 41 | ); 42 | }; 43 | 44 | export default Navbar; 45 | -------------------------------------------------------------------------------- /src/components/UserCard.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import { FC } from 'react'; 3 | 4 | import { User } from '@/types/app'; 5 | 6 | const UserCard: FC<{ userData: User | null }> = ({ userData }) => { 7 | return ( 8 |
9 |
10 | {userData?.full_name} 15 |
16 | {userData?.full_name} 17 |
18 |

{userData?.email}

19 |
20 | 24 | Profile 25 | 26 |
27 |
28 |
29 | ); 30 | }; 31 | 32 | export default UserCard; 33 | -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ) 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button" 45 | return ( 46 | 51 | ) 52 | } 53 | ) 54 | Button.displayName = "Button" 55 | 56 | export { Button, buttonVariants } 57 | -------------------------------------------------------------------------------- /src/components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DialogPrimitive from "@radix-ui/react-dialog" 5 | import { X } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Dialog = DialogPrimitive.Root 10 | 11 | const DialogTrigger = DialogPrimitive.Trigger 12 | 13 | const DialogPortal = DialogPrimitive.Portal 14 | 15 | const DialogClose = DialogPrimitive.Close 16 | 17 | const DialogOverlay = React.forwardRef< 18 | React.ElementRef, 19 | React.ComponentPropsWithoutRef 20 | >(({ className, ...props }, ref) => ( 21 | 29 | )) 30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName 31 | 32 | const DialogContent = React.forwardRef< 33 | React.ElementRef, 34 | React.ComponentPropsWithoutRef 35 | >(({ className, children, ...props }, ref) => ( 36 | 37 | 38 | 46 | {children} 47 | 48 | 49 | Close 50 | 51 | 52 | 53 | )) 54 | DialogContent.displayName = DialogPrimitive.Content.displayName 55 | 56 | const DialogHeader = ({ 57 | className, 58 | ...props 59 | }: React.HTMLAttributes) => ( 60 |
67 | ) 68 | DialogHeader.displayName = "DialogHeader" 69 | 70 | const DialogFooter = ({ 71 | className, 72 | ...props 73 | }: React.HTMLAttributes) => ( 74 |
81 | ) 82 | DialogFooter.displayName = "DialogFooter" 83 | 84 | const DialogTitle = React.forwardRef< 85 | React.ElementRef, 86 | React.ComponentPropsWithoutRef 87 | >(({ className, ...props }, ref) => ( 88 | 96 | )) 97 | DialogTitle.displayName = DialogPrimitive.Title.displayName 98 | 99 | const DialogDescription = React.forwardRef< 100 | React.ElementRef, 101 | React.ComponentPropsWithoutRef 102 | >(({ className, ...props }, ref) => ( 103 | 108 | )) 109 | DialogDescription.displayName = DialogPrimitive.Description.displayName 110 | 111 | export { 112 | Dialog, 113 | DialogPortal, 114 | DialogOverlay, 115 | DialogClose, 116 | DialogTrigger, 117 | DialogContent, 118 | DialogHeader, 119 | DialogFooter, 120 | DialogTitle, 121 | DialogDescription, 122 | } 123 | -------------------------------------------------------------------------------- /src/components/ui/form.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as LabelPrimitive from "@radix-ui/react-label" 3 | import { Slot } from "@radix-ui/react-slot" 4 | import { 5 | Controller, 6 | ControllerProps, 7 | FieldPath, 8 | FieldValues, 9 | FormProvider, 10 | useFormContext, 11 | } from "react-hook-form" 12 | 13 | import { cn } from "@/lib/utils" 14 | import { Label } from "@/components/ui/label" 15 | 16 | const Form = FormProvider 17 | 18 | type FormFieldContextValue< 19 | TFieldValues extends FieldValues = FieldValues, 20 | TName extends FieldPath = FieldPath 21 | > = { 22 | name: TName 23 | } 24 | 25 | const FormFieldContext = React.createContext( 26 | {} as FormFieldContextValue 27 | ) 28 | 29 | const FormField = < 30 | TFieldValues extends FieldValues = FieldValues, 31 | TName extends FieldPath = FieldPath 32 | >({ 33 | ...props 34 | }: ControllerProps) => { 35 | return ( 36 | 37 | 38 | 39 | ) 40 | } 41 | 42 | const useFormField = () => { 43 | const fieldContext = React.useContext(FormFieldContext) 44 | const itemContext = React.useContext(FormItemContext) 45 | const { getFieldState, formState } = useFormContext() 46 | 47 | const fieldState = getFieldState(fieldContext.name, formState) 48 | 49 | if (!fieldContext) { 50 | throw new Error("useFormField should be used within ") 51 | } 52 | 53 | const { id } = itemContext 54 | 55 | return { 56 | id, 57 | name: fieldContext.name, 58 | formItemId: `${id}-form-item`, 59 | formDescriptionId: `${id}-form-item-description`, 60 | formMessageId: `${id}-form-item-message`, 61 | ...fieldState, 62 | } 63 | } 64 | 65 | type FormItemContextValue = { 66 | id: string 67 | } 68 | 69 | const FormItemContext = React.createContext( 70 | {} as FormItemContextValue 71 | ) 72 | 73 | const FormItem = React.forwardRef< 74 | HTMLDivElement, 75 | React.HTMLAttributes 76 | >(({ className, ...props }, ref) => { 77 | const id = React.useId() 78 | 79 | return ( 80 | 81 |
82 | 83 | ) 84 | }) 85 | FormItem.displayName = "FormItem" 86 | 87 | const FormLabel = React.forwardRef< 88 | React.ElementRef, 89 | React.ComponentPropsWithoutRef 90 | >(({ className, ...props }, ref) => { 91 | const { error, formItemId } = useFormField() 92 | 93 | return ( 94 |