user-auth-form.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import { HTMLAttributes, useState } from 'react'
  2. import { z } from 'zod'
  3. import { useForm } from 'react-hook-form'
  4. import { zodResolver } from '@hookform/resolvers/zod'
  5. import { Link } from '@tanstack/react-router'
  6. import { IconBrandFacebook, IconBrandGithub } from '@tabler/icons-react'
  7. import { cn } from '@/lib/utils'
  8. import {
  9. Form,
  10. FormControl,
  11. FormField,
  12. FormItem,
  13. FormLabel,
  14. FormMessage,
  15. } from '@/components/ui/form'
  16. import { Input } from '@/components/ui/input'
  17. import { Button } from '@/components/button'
  18. import { PasswordInput } from '@/components/password-input'
  19. type UserAuthFormProps = HTMLAttributes<HTMLDivElement>
  20. const formSchema = z.object({
  21. email: z
  22. .string()
  23. .min(1, { message: 'Please enter your email' })
  24. .email({ message: 'Invalid email address' }),
  25. password: z
  26. .string()
  27. .min(1, {
  28. message: 'Please enter your password',
  29. })
  30. .min(7, {
  31. message: 'Password must be at least 7 characters long',
  32. }),
  33. })
  34. export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
  35. const [isLoading, setIsLoading] = useState(false)
  36. const form = useForm<z.infer<typeof formSchema>>({
  37. resolver: zodResolver(formSchema),
  38. defaultValues: {
  39. email: '',
  40. password: '',
  41. },
  42. })
  43. function onSubmit(data: z.infer<typeof formSchema>) {
  44. setIsLoading(true)
  45. // eslint-disable-next-line no-console
  46. console.log(data)
  47. setTimeout(() => {
  48. setIsLoading(false)
  49. }, 3000)
  50. }
  51. return (
  52. <div className={cn('grid gap-6', className)} {...props}>
  53. <Form {...form}>
  54. <form onSubmit={form.handleSubmit(onSubmit)}>
  55. <div className='grid gap-2'>
  56. <FormField
  57. control={form.control}
  58. name='email'
  59. render={({ field }) => (
  60. <FormItem className='space-y-1'>
  61. <FormLabel>Email</FormLabel>
  62. <FormControl>
  63. <Input placeholder='name@example.com' {...field} />
  64. </FormControl>
  65. <FormMessage />
  66. </FormItem>
  67. )}
  68. />
  69. <FormField
  70. control={form.control}
  71. name='password'
  72. render={({ field }) => (
  73. <FormItem className='space-y-1'>
  74. <div className='flex items-center justify-between'>
  75. <FormLabel>Password</FormLabel>
  76. <Link
  77. to='/forgot-password'
  78. className='text-sm font-medium text-muted-foreground hover:opacity-75'
  79. >
  80. Forgot password?
  81. </Link>
  82. </div>
  83. <FormControl>
  84. <PasswordInput placeholder='********' {...field} />
  85. </FormControl>
  86. <FormMessage />
  87. </FormItem>
  88. )}
  89. />
  90. <Button className='mt-2' loading={isLoading}>
  91. Login
  92. </Button>
  93. <div className='relative my-2'>
  94. <div className='absolute inset-0 flex items-center'>
  95. <span className='w-full border-t' />
  96. </div>
  97. <div className='relative flex justify-center text-xs uppercase'>
  98. <span className='bg-background px-2 text-muted-foreground'>
  99. Or continue with
  100. </span>
  101. </div>
  102. </div>
  103. <div className='flex items-center gap-2'>
  104. <Button
  105. variant='outline'
  106. className='w-full'
  107. type='button'
  108. loading={isLoading}
  109. leftSection={<IconBrandGithub className='h-4 w-4' />}
  110. >
  111. GitHub
  112. </Button>
  113. <Button
  114. variant='outline'
  115. className='w-full'
  116. type='button'
  117. loading={isLoading}
  118. leftSection={<IconBrandFacebook className='h-4 w-4' />}
  119. >
  120. Facebook
  121. </Button>
  122. </div>
  123. </div>
  124. </form>
  125. </Form>
  126. </div>
  127. )
  128. }