sign-up-form.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import { useState } from 'react'
  2. import { z } from 'zod'
  3. import { useForm } from 'react-hook-form'
  4. import { zodResolver } from '@hookform/resolvers/zod'
  5. import { Loader2, UserPlus } from 'lucide-react'
  6. import { toast } from 'sonner'
  7. import { IconFacebook, IconGithub } from '@/assets/brand-icons'
  8. import { sleep, cn } from '@/lib/utils'
  9. import { Button } from '@/components/ui/button'
  10. import {
  11. Form,
  12. FormControl,
  13. FormField,
  14. FormItem,
  15. FormLabel,
  16. FormMessage,
  17. } from '@/components/ui/form'
  18. import { Input } from '@/components/ui/input'
  19. import { PasswordInput } from '@/components/password-input'
  20. const formSchema = z
  21. .object({
  22. email: z.email({
  23. error: (iss) =>
  24. iss.input === '' ? 'Please enter your email.' : undefined,
  25. }),
  26. password: z
  27. .string()
  28. .min(1, 'Please enter your password.')
  29. .min(7, 'Password must be at least 7 characters long.'),
  30. confirmPassword: z.string().min(1, 'Please confirm your password.'),
  31. })
  32. .refine((data) => data.password === data.confirmPassword, {
  33. message: "Passwords don't match.",
  34. path: ['confirmPassword'],
  35. })
  36. export function SignUpForm({
  37. className,
  38. ...props
  39. }: React.HTMLAttributes<HTMLFormElement>) {
  40. const [isLoading, setIsLoading] = useState(false)
  41. const form = useForm<z.infer<typeof formSchema>>({
  42. resolver: zodResolver(formSchema),
  43. defaultValues: {
  44. email: '',
  45. password: '',
  46. confirmPassword: '',
  47. },
  48. })
  49. function onSubmit(data: z.infer<typeof formSchema>) {
  50. setIsLoading(true)
  51. toast.promise(sleep(2000), {
  52. loading: 'Creating account...',
  53. success: () => {
  54. setIsLoading(false)
  55. return `Account created for ${data.email}.`
  56. },
  57. error: 'Error',
  58. })
  59. }
  60. return (
  61. <Form {...form}>
  62. <form
  63. onSubmit={form.handleSubmit(onSubmit)}
  64. className={cn('grid gap-3', className)}
  65. {...props}
  66. >
  67. <FormField
  68. control={form.control}
  69. name='email'
  70. render={({ field }) => (
  71. <FormItem>
  72. <FormLabel>Email</FormLabel>
  73. <FormControl>
  74. <Input placeholder='name@example.com' {...field} />
  75. </FormControl>
  76. <FormMessage />
  77. </FormItem>
  78. )}
  79. />
  80. <FormField
  81. control={form.control}
  82. name='password'
  83. render={({ field }) => (
  84. <FormItem>
  85. <FormLabel>Password</FormLabel>
  86. <FormControl>
  87. <PasswordInput placeholder='********' {...field} />
  88. </FormControl>
  89. <FormMessage />
  90. </FormItem>
  91. )}
  92. />
  93. <FormField
  94. control={form.control}
  95. name='confirmPassword'
  96. render={({ field }) => (
  97. <FormItem>
  98. <FormLabel>Confirm Password</FormLabel>
  99. <FormControl>
  100. <PasswordInput placeholder='********' {...field} />
  101. </FormControl>
  102. <FormMessage />
  103. </FormItem>
  104. )}
  105. />
  106. <Button className='mt-2' disabled={isLoading}>
  107. {isLoading ? <Loader2 className='animate-spin' /> : <UserPlus />}
  108. Create Account
  109. </Button>
  110. <div className='relative my-2'>
  111. <div className='absolute inset-0 flex items-center'>
  112. <span className='w-full border-t' />
  113. </div>
  114. <div className='relative flex justify-center text-xs uppercase'>
  115. <span className='bg-background px-2 text-muted-foreground'>
  116. Or continue with
  117. </span>
  118. </div>
  119. </div>
  120. <div className='grid grid-cols-2 gap-2'>
  121. <Button
  122. variant='outline'
  123. className='w-full'
  124. type='button'
  125. disabled={isLoading}
  126. >
  127. <IconGithub className='h-4 w-4' /> GitHub
  128. </Button>
  129. <Button
  130. variant='outline'
  131. className='w-full'
  132. type='button'
  133. disabled={isLoading}
  134. >
  135. <IconFacebook className='h-4 w-4' /> Facebook
  136. </Button>
  137. </div>
  138. </form>
  139. </Form>
  140. )
  141. }