otp-form.tsx 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  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 { useNavigate } from '@tanstack/react-router'
  6. import { cn } from '@/lib/utils'
  7. import { showSubmittedData } from '@/utils/show-submitted-data'
  8. import { Button } from '@/components/ui/button'
  9. import {
  10. Form,
  11. FormControl,
  12. FormField,
  13. FormItem,
  14. FormMessage,
  15. } from '@/components/ui/form'
  16. import { Input } from '@/components/ui/input'
  17. import { Separator } from '@/components/ui/separator'
  18. import { PinInput, PinInputField } from '@/components/pin-input'
  19. type OtpFormProps = HTMLAttributes<HTMLFormElement>
  20. const formSchema = z.object({
  21. otp: z.string().min(1, { message: 'Please enter your otp code.' }),
  22. })
  23. export function OtpForm({ className, ...props }: OtpFormProps) {
  24. const navigate = useNavigate()
  25. const [isLoading, setIsLoading] = useState(false)
  26. const [disabledBtn, setDisabledBtn] = useState(true)
  27. const form = useForm<z.infer<typeof formSchema>>({
  28. resolver: zodResolver(formSchema),
  29. defaultValues: { otp: '' },
  30. })
  31. function onSubmit(data: z.infer<typeof formSchema>) {
  32. setIsLoading(true)
  33. showSubmittedData(data)
  34. setTimeout(() => {
  35. setIsLoading(false)
  36. navigate({ to: '/' })
  37. }, 1000)
  38. }
  39. return (
  40. <Form {...form}>
  41. <form
  42. onSubmit={form.handleSubmit(onSubmit)}
  43. className={cn('grid gap-2', className)}
  44. {...props}
  45. >
  46. <FormField
  47. control={form.control}
  48. name='otp'
  49. render={({ field }) => (
  50. <FormItem className='space-y-1'>
  51. <FormControl>
  52. <PinInput
  53. {...field}
  54. className='flex h-10 justify-between'
  55. onComplete={() => setDisabledBtn(false)}
  56. onIncomplete={() => setDisabledBtn(true)}
  57. >
  58. {Array.from({ length: 7 }, (_, i) => {
  59. if (i === 3)
  60. return <Separator key={i} orientation='vertical' />
  61. return (
  62. <PinInputField
  63. key={i}
  64. component={Input}
  65. className={`${form.getFieldState('otp').invalid ? 'border-red-500' : ''}`}
  66. />
  67. )
  68. })}
  69. </PinInput>
  70. </FormControl>
  71. <FormMessage />
  72. </FormItem>
  73. )}
  74. />
  75. <Button className='mt-2' disabled={disabledBtn || isLoading}>
  76. Verify
  77. </Button>
  78. </form>
  79. </Form>
  80. )
  81. }