@erikey/react 0.4.26 → 0.4.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/dist/index.mjs +1 -1
  2. package/dist/index.mjs.map +1 -1
  3. package/package.json +2 -1
  4. package/src/__tests__/auth-client.test.ts +105 -0
  5. package/src/__tests__/security/localStorage-encryption.test.ts +171 -0
  6. package/src/auth-client.ts +158 -0
  7. package/src/dashboard-client.ts +60 -0
  8. package/src/index.ts +88 -0
  9. package/src/kv-client.ts +316 -0
  10. package/src/lib/cross-origin-auth.ts +99 -0
  11. package/src/stubs/captcha.ts +24 -0
  12. package/src/stubs/hashes.ts +16 -0
  13. package/src/stubs/index.ts +17 -0
  14. package/src/stubs/passkey.ts +12 -0
  15. package/src/stubs/qr-code.ts +10 -0
  16. package/src/stubs/query.ts +16 -0
  17. package/src/stubs/realtime.ts +17 -0
  18. package/src/stubs/use-sync-external-store.ts +12 -0
  19. package/src/styles.css +141 -0
  20. package/src/types.ts +14 -0
  21. package/src/ui/components/auth/auth-callback.tsx +36 -0
  22. package/src/ui/components/auth/auth-form.tsx +310 -0
  23. package/src/ui/components/auth/auth-view.tsx +435 -0
  24. package/src/ui/components/auth/email-otp-button.tsx +53 -0
  25. package/src/ui/components/auth/forms/email-otp-form.tsx +312 -0
  26. package/src/ui/components/auth/forms/email-verification-form.tsx +271 -0
  27. package/src/ui/components/auth/forms/forgot-password-form.tsx +173 -0
  28. package/src/ui/components/auth/forms/magic-link-form.tsx +196 -0
  29. package/src/ui/components/auth/forms/recover-account-form.tsx +143 -0
  30. package/src/ui/components/auth/forms/reset-password-form.tsx +220 -0
  31. package/src/ui/components/auth/forms/sign-in-form.tsx +323 -0
  32. package/src/ui/components/auth/forms/sign-up-form.tsx +820 -0
  33. package/src/ui/components/auth/forms/two-factor-form.tsx +381 -0
  34. package/src/ui/components/auth/magic-link-button.tsx +54 -0
  35. package/src/ui/components/auth/one-tap.tsx +53 -0
  36. package/src/ui/components/auth/otp-input-group.tsx +65 -0
  37. package/src/ui/components/auth/passkey-button.tsx +91 -0
  38. package/src/ui/components/auth/provider-button.tsx +155 -0
  39. package/src/ui/components/auth/sign-out.tsx +25 -0
  40. package/src/ui/components/auth/wallet-button.tsx +192 -0
  41. package/src/ui/components/auth-loading.tsx +21 -0
  42. package/src/ui/components/captcha/captcha.tsx +91 -0
  43. package/src/ui/components/captcha/recaptcha-badge.tsx +61 -0
  44. package/src/ui/components/captcha/recaptcha-v2.tsx +58 -0
  45. package/src/ui/components/captcha/recaptcha-v3.tsx +73 -0
  46. package/src/ui/components/email/email-template.tsx +216 -0
  47. package/src/ui/components/form-error.tsx +27 -0
  48. package/src/ui/components/password-input.tsx +56 -0
  49. package/src/ui/components/provider-icons.tsx +404 -0
  50. package/src/ui/components/redirect-to-sign-in.tsx +16 -0
  51. package/src/ui/components/redirect-to-sign-up.tsx +16 -0
  52. package/src/ui/components/signed-in.tsx +20 -0
  53. package/src/ui/components/signed-out.tsx +20 -0
  54. package/src/ui/components/ui/alert.tsx +66 -0
  55. package/src/ui/components/ui/button.tsx +70 -0
  56. package/src/ui/components/ui/card.tsx +92 -0
  57. package/src/ui/components/ui/checkbox.tsx +66 -0
  58. package/src/ui/components/ui/field.tsx +248 -0
  59. package/src/ui/components/ui/form.tsx +165 -0
  60. package/src/ui/components/ui/input-otp.tsx +77 -0
  61. package/src/ui/components/ui/input.tsx +21 -0
  62. package/src/ui/components/ui/label.tsx +23 -0
  63. package/src/ui/components/ui/separator.tsx +34 -0
  64. package/src/ui/components/ui/skeleton.tsx +13 -0
  65. package/src/ui/components/ui/textarea.tsx +18 -0
  66. package/src/ui/components/user-avatar.tsx +151 -0
  67. package/src/ui/hooks/use-auth-data.ts +193 -0
  68. package/src/ui/hooks/use-authenticate.ts +64 -0
  69. package/src/ui/hooks/use-captcha.tsx +151 -0
  70. package/src/ui/hooks/use-hydrated.ts +13 -0
  71. package/src/ui/hooks/use-lang.ts +32 -0
  72. package/src/ui/hooks/use-success-transition.ts +41 -0
  73. package/src/ui/hooks/use-theme.ts +39 -0
  74. package/src/ui/index.ts +46 -0
  75. package/src/ui/instantdb.ts +1 -0
  76. package/src/ui/lib/auth-data-cache.ts +90 -0
  77. package/src/ui/lib/auth-ui-provider.tsx +769 -0
  78. package/src/ui/lib/gravatar-utils.ts +58 -0
  79. package/src/ui/lib/image-utils.ts +55 -0
  80. package/src/ui/lib/instantdb/model-names.ts +24 -0
  81. package/src/ui/lib/instantdb/use-instant-options.ts +98 -0
  82. package/src/ui/lib/instantdb/use-list-accounts.ts +38 -0
  83. package/src/ui/lib/instantdb/use-list-sessions.ts +53 -0
  84. package/src/ui/lib/instantdb/use-session.ts +55 -0
  85. package/src/ui/lib/social-providers.ts +150 -0
  86. package/src/ui/lib/tanstack/auth-ui-provider-tanstack.tsx +49 -0
  87. package/src/ui/lib/tanstack/use-tanstack-options.ts +112 -0
  88. package/src/ui/lib/triplit/model-names.ts +24 -0
  89. package/src/ui/lib/triplit/use-conditional-query.ts +82 -0
  90. package/src/ui/lib/triplit/use-list-accounts.ts +31 -0
  91. package/src/ui/lib/triplit/use-list-sessions.ts +33 -0
  92. package/src/ui/lib/triplit/use-session.ts +42 -0
  93. package/src/ui/lib/triplit/use-triplit-hooks.ts +68 -0
  94. package/src/ui/lib/triplit/use-triplit-token.ts +44 -0
  95. package/src/ui/lib/utils.ts +119 -0
  96. package/src/ui/lib/view-paths.ts +61 -0
  97. package/src/ui/lib/wallet.ts +129 -0
  98. package/src/ui/localization/admin-error-codes.ts +20 -0
  99. package/src/ui/localization/anonymous-error-codes.ts +6 -0
  100. package/src/ui/localization/api-key-error-codes.ts +32 -0
  101. package/src/ui/localization/auth-localization.ts +865 -0
  102. package/src/ui/localization/base-error-codes.ts +27 -0
  103. package/src/ui/localization/captcha-error-codes.ts +17 -0
  104. package/src/ui/localization/email-otp-error-codes.ts +7 -0
  105. package/src/ui/localization/generic-oauth-error-codes.ts +3 -0
  106. package/src/ui/localization/haveibeenpwned-error-codes.ts +4 -0
  107. package/src/ui/localization/multi-session-error-codes.ts +3 -0
  108. package/src/ui/localization/organization-error-codes.ts +57 -0
  109. package/src/ui/localization/passkey-error-codes.ts +10 -0
  110. package/src/ui/localization/phone-number-error-codes.ts +10 -0
  111. package/src/ui/localization/stripe-localization.ts +12 -0
  112. package/src/ui/localization/team-error-codes.ts +12 -0
  113. package/src/ui/localization/two-factor-error-codes.ts +12 -0
  114. package/src/ui/localization/username-error-codes.ts +9 -0
  115. package/src/ui/server.ts +4 -0
  116. package/src/ui/style.css +146 -0
  117. package/src/ui/tanstack.ts +1 -0
  118. package/src/ui/triplit.ts +1 -0
  119. package/src/ui/types/account-options.ts +35 -0
  120. package/src/ui/types/additional-fields.ts +21 -0
  121. package/src/ui/types/any-auth-client.ts +6 -0
  122. package/src/ui/types/api-key.ts +9 -0
  123. package/src/ui/types/auth-client.ts +41 -0
  124. package/src/ui/types/auth-hooks.ts +81 -0
  125. package/src/ui/types/auth-mutators.ts +21 -0
  126. package/src/ui/types/avatar-options.ts +29 -0
  127. package/src/ui/types/captcha-options.ts +32 -0
  128. package/src/ui/types/captcha-provider.ts +7 -0
  129. package/src/ui/types/credentials-options.ts +38 -0
  130. package/src/ui/types/delete-user-options.ts +7 -0
  131. package/src/ui/types/email-verification-options.ts +7 -0
  132. package/src/ui/types/fetch-error.ts +6 -0
  133. package/src/ui/types/generic-oauth-options.ts +16 -0
  134. package/src/ui/types/gravatar-options.ts +21 -0
  135. package/src/ui/types/image.ts +7 -0
  136. package/src/ui/types/invitation.ts +10 -0
  137. package/src/ui/types/link.ts +7 -0
  138. package/src/ui/types/organization-options.ts +106 -0
  139. package/src/ui/types/password-validation.ts +16 -0
  140. package/src/ui/types/profile.ts +15 -0
  141. package/src/ui/types/refetch.ts +1 -0
  142. package/src/ui/types/render-toast.ts +9 -0
  143. package/src/ui/types/sign-up-options.ts +7 -0
  144. package/src/ui/types/social-options.ts +16 -0
  145. package/src/ui/types/team-options.ts +47 -0
@@ -0,0 +1,173 @@
1
+ "use client"
2
+
3
+ import { zodResolver } from "@hookform/resolvers/zod"
4
+ import type { BetterFetchOption } from "better-auth/react"
5
+ import { Loader2 } from "lucide-react"
6
+ import { useContext, useEffect } from "react"
7
+ import { useForm } from "react-hook-form"
8
+ import * as z from "zod"
9
+ import { useCaptcha } from "../../../hooks/use-captcha"
10
+ import { useIsHydrated } from "../../../hooks/use-hydrated"
11
+ import { AuthUIContext } from "../../../lib/auth-ui-provider"
12
+ import { cn, getLocalizedError } from "../../../lib/utils"
13
+ import type { AuthLocalization } from "../../../localization/auth-localization"
14
+ import { Captcha } from "../../captcha/captcha"
15
+ import { Button } from "../../ui/button"
16
+ import {
17
+ Form,
18
+ FormControl,
19
+ FormField,
20
+ FormItem,
21
+ FormLabel,
22
+ FormMessage
23
+ } from "../../ui/form"
24
+ import { Input } from "../../ui/input"
25
+ import type { AuthFormClassNames } from "../auth-form"
26
+
27
+ export interface ForgotPasswordFormProps {
28
+ className?: string
29
+ classNames?: AuthFormClassNames
30
+ isSubmitting?: boolean
31
+ localization: Partial<AuthLocalization>
32
+ setIsSubmitting?: (value: boolean) => void
33
+ }
34
+
35
+ export function ForgotPasswordForm({
36
+ className,
37
+ classNames,
38
+ isSubmitting,
39
+ localization,
40
+ setIsSubmitting
41
+ }: ForgotPasswordFormProps) {
42
+ const isHydrated = useIsHydrated()
43
+ const { captchaRef, getCaptchaHeaders, resetCaptcha } = useCaptcha({
44
+ localization
45
+ })
46
+
47
+ const {
48
+ authClient,
49
+ basePath,
50
+ baseURL,
51
+ localization: contextLocalization,
52
+ navigate,
53
+ toast,
54
+ viewPaths,
55
+ localizeErrors
56
+ } = useContext(AuthUIContext)
57
+
58
+ localization = { ...contextLocalization, ...localization }
59
+
60
+ const formSchema = z.object({
61
+ email: z
62
+ .string()
63
+ .email({
64
+ message: `${localization.EMAIL} ${localization.IS_INVALID}`
65
+ })
66
+ .min(1, {
67
+ message: `${localization.EMAIL} ${localization.IS_REQUIRED}`
68
+ })
69
+ })
70
+
71
+ const form = useForm({
72
+ resolver: zodResolver(formSchema),
73
+ defaultValues: {
74
+ email: ""
75
+ }
76
+ })
77
+
78
+ isSubmitting = isSubmitting || form.formState.isSubmitting
79
+
80
+ useEffect(() => {
81
+ setIsSubmitting?.(form.formState.isSubmitting)
82
+ }, [form.formState.isSubmitting, setIsSubmitting])
83
+
84
+ async function forgotPassword({ email }: z.infer<typeof formSchema>) {
85
+ try {
86
+ const fetchOptions: BetterFetchOption = {
87
+ throw: true,
88
+ headers: await getCaptchaHeaders("/forget-password")
89
+ }
90
+
91
+ await authClient.requestPasswordReset({
92
+ email,
93
+ redirectTo: `${baseURL}${basePath}/${viewPaths.RESET_PASSWORD}`,
94
+ fetchOptions
95
+ })
96
+
97
+ toast({
98
+ variant: "success",
99
+ message: localization.FORGOT_PASSWORD_EMAIL
100
+ })
101
+
102
+ navigate(
103
+ `${basePath}/${viewPaths.SIGN_IN}${window.location.search}`
104
+ )
105
+ } catch (error) {
106
+ toast({
107
+ variant: "error",
108
+ message: getLocalizedError({
109
+ error,
110
+ localization,
111
+ localizeErrors
112
+ })
113
+ })
114
+ resetCaptcha()
115
+ }
116
+ }
117
+
118
+ return (
119
+ <Form {...form}>
120
+ <form
121
+ onSubmit={form.handleSubmit(forgotPassword)}
122
+ noValidate={isHydrated}
123
+ className={cn("grid w-full gap-6", className, classNames?.base)}
124
+ >
125
+ <FormField
126
+ control={form.control}
127
+ name="email"
128
+ render={({ field }) => (
129
+ <FormItem>
130
+ <FormLabel className={classNames?.label}>
131
+ {localization.EMAIL}
132
+ </FormLabel>
133
+
134
+ <FormControl>
135
+ <Input
136
+ className={classNames?.input}
137
+ type="email"
138
+ placeholder={localization.EMAIL_PLACEHOLDER}
139
+ disabled={isSubmitting}
140
+ {...field}
141
+ />
142
+ </FormControl>
143
+
144
+ <FormMessage className={classNames?.error} />
145
+ </FormItem>
146
+ )}
147
+ />
148
+
149
+ <Captcha
150
+ ref={captchaRef}
151
+ localization={localization}
152
+ action="/forget-password"
153
+ />
154
+
155
+ <Button
156
+ type="submit"
157
+ disabled={isSubmitting}
158
+ className={cn(
159
+ "w-full",
160
+ classNames?.button,
161
+ classNames?.primaryButton
162
+ )}
163
+ >
164
+ {isSubmitting ? (
165
+ <Loader2 className="animate-spin" />
166
+ ) : (
167
+ localization.FORGOT_PASSWORD_ACTION
168
+ )}
169
+ </Button>
170
+ </form>
171
+ </Form>
172
+ )
173
+ }
@@ -0,0 +1,196 @@
1
+ "use client"
2
+
3
+ import { zodResolver } from "@hookform/resolvers/zod"
4
+ import type { BetterFetchOption } from "better-auth/react"
5
+ import { Loader2 } from "lucide-react"
6
+ import { useCallback, useContext, useEffect } from "react"
7
+ import { useForm } from "react-hook-form"
8
+ import * as z from "zod"
9
+
10
+ import { useCaptcha } from "../../../hooks/use-captcha"
11
+ import { useIsHydrated } from "../../../hooks/use-hydrated"
12
+ import { AuthUIContext } from "../../../lib/auth-ui-provider"
13
+ import { cn, getLocalizedError, getSearchParam } from "../../../lib/utils"
14
+ import type { AuthLocalization } from "../../../localization/auth-localization"
15
+ import { Captcha } from "../../captcha/captcha"
16
+ import { Button } from "../../ui/button"
17
+ import {
18
+ Form,
19
+ FormControl,
20
+ FormField,
21
+ FormItem,
22
+ FormLabel,
23
+ FormMessage
24
+ } from "../../ui/form"
25
+ import { Input } from "../../ui/input"
26
+ import type { AuthFormClassNames } from "../auth-form"
27
+
28
+ export interface MagicLinkFormProps {
29
+ className?: string
30
+ classNames?: AuthFormClassNames
31
+ callbackURL?: string
32
+ isSubmitting?: boolean
33
+ localization: Partial<AuthLocalization>
34
+ redirectTo?: string
35
+ setIsSubmitting?: (value: boolean) => void
36
+ }
37
+
38
+ export function MagicLinkForm({
39
+ className,
40
+ classNames,
41
+ callbackURL: callbackURLProp,
42
+ isSubmitting,
43
+ localization,
44
+ redirectTo: redirectToProp,
45
+ setIsSubmitting
46
+ }: MagicLinkFormProps) {
47
+ const isHydrated = useIsHydrated()
48
+ const { captchaRef, getCaptchaHeaders, resetCaptcha } = useCaptcha({
49
+ localization
50
+ })
51
+
52
+ const {
53
+ authClient,
54
+ basePath,
55
+ baseURL,
56
+ persistClient,
57
+ localization: contextLocalization,
58
+ redirectTo: contextRedirectTo,
59
+ viewPaths,
60
+ toast,
61
+ localizeErrors
62
+ } = useContext(AuthUIContext)
63
+
64
+ localization = { ...contextLocalization, ...localization }
65
+
66
+ const getRedirectTo = useCallback(
67
+ () =>
68
+ redirectToProp || getSearchParam("redirectTo") || contextRedirectTo,
69
+ [redirectToProp, contextRedirectTo]
70
+ )
71
+
72
+ const getCallbackURL = useCallback(
73
+ () =>
74
+ `${baseURL}${
75
+ callbackURLProp ||
76
+ (persistClient
77
+ ? `${basePath}/${viewPaths.CALLBACK}?redirectTo=${encodeURIComponent(getRedirectTo())}`
78
+ : getRedirectTo())
79
+ }`,
80
+ [
81
+ callbackURLProp,
82
+ persistClient,
83
+ basePath,
84
+ viewPaths,
85
+ baseURL,
86
+ getRedirectTo
87
+ ]
88
+ )
89
+
90
+ const formSchema = z.object({
91
+ email: z.string().email({
92
+ message: `${localization.EMAIL} ${localization.IS_INVALID}`
93
+ })
94
+ })
95
+
96
+ const form = useForm({
97
+ resolver: zodResolver(formSchema),
98
+ defaultValues: {
99
+ email: ""
100
+ }
101
+ })
102
+
103
+ isSubmitting = isSubmitting || form.formState.isSubmitting
104
+
105
+ useEffect(() => {
106
+ setIsSubmitting?.(form.formState.isSubmitting)
107
+ }, [form.formState.isSubmitting, setIsSubmitting])
108
+
109
+ async function sendMagicLink({ email }: z.infer<typeof formSchema>) {
110
+ try {
111
+ const fetchOptions: BetterFetchOption = {
112
+ throw: true,
113
+ headers: await getCaptchaHeaders("/sign-in/magic-link")
114
+ }
115
+
116
+ await authClient.signIn.magicLink({
117
+ email,
118
+ callbackURL: getCallbackURL(),
119
+ fetchOptions
120
+ })
121
+
122
+ toast({
123
+ variant: "success",
124
+ message: localization.MAGIC_LINK_EMAIL
125
+ })
126
+
127
+ form.reset()
128
+ } catch (error) {
129
+ toast({
130
+ variant: "error",
131
+ message: getLocalizedError({
132
+ error,
133
+ localization,
134
+ localizeErrors
135
+ })
136
+ })
137
+ resetCaptcha()
138
+ }
139
+ }
140
+
141
+ return (
142
+ <Form {...form}>
143
+ <form
144
+ onSubmit={form.handleSubmit(sendMagicLink)}
145
+ noValidate={isHydrated}
146
+ className={cn("grid w-full gap-6", className, classNames?.base)}
147
+ >
148
+ <FormField
149
+ control={form.control}
150
+ name="email"
151
+ render={({ field }) => (
152
+ <FormItem>
153
+ <FormLabel className={classNames?.label}>
154
+ {localization.EMAIL}
155
+ </FormLabel>
156
+
157
+ <FormControl>
158
+ <Input
159
+ className={classNames?.input}
160
+ type="email"
161
+ placeholder={localization.EMAIL_PLACEHOLDER}
162
+ disabled={isSubmitting}
163
+ {...field}
164
+ />
165
+ </FormControl>
166
+
167
+ <FormMessage className={classNames?.error} />
168
+ </FormItem>
169
+ )}
170
+ />
171
+
172
+ <Captcha
173
+ ref={captchaRef}
174
+ localization={localization}
175
+ action="/sign-in/magic-link"
176
+ />
177
+
178
+ <Button
179
+ type="submit"
180
+ disabled={isSubmitting}
181
+ className={cn(
182
+ "w-full",
183
+ classNames?.button,
184
+ classNames?.primaryButton
185
+ )}
186
+ >
187
+ {isSubmitting ? (
188
+ <Loader2 className="animate-spin" />
189
+ ) : (
190
+ localization.MAGIC_LINK_ACTION
191
+ )}
192
+ </Button>
193
+ </form>
194
+ </Form>
195
+ )
196
+ }
@@ -0,0 +1,143 @@
1
+ "use client"
2
+ import { zodResolver } from "@hookform/resolvers/zod"
3
+ import { Loader2 } from "lucide-react"
4
+ import { useContext, useEffect } from "react"
5
+ import { useForm } from "react-hook-form"
6
+ import * as z from "zod"
7
+
8
+ import { useOnSuccessTransition } from "../../../hooks/use-success-transition"
9
+ import { AuthUIContext } from "../../../lib/auth-ui-provider"
10
+ import { cn, getLocalizedError } from "../../../lib/utils"
11
+ import type { AuthLocalization } from "../../../localization/auth-localization"
12
+ import { Button } from "../../ui/button"
13
+ import {
14
+ Form,
15
+ FormControl,
16
+ FormField,
17
+ FormItem,
18
+ FormLabel,
19
+ FormMessage
20
+ } from "../../ui/form"
21
+ import { Input } from "../../ui/input"
22
+ import type { AuthFormClassNames } from "../auth-form"
23
+
24
+ export interface RecoverAccountFormProps {
25
+ className?: string
26
+ classNames?: AuthFormClassNames
27
+ isSubmitting?: boolean
28
+ localization: Partial<AuthLocalization>
29
+ redirectTo?: string
30
+ setIsSubmitting?: (value: boolean) => void
31
+ }
32
+
33
+ export function RecoverAccountForm({
34
+ className,
35
+ classNames,
36
+ isSubmitting,
37
+ localization,
38
+ redirectTo,
39
+ setIsSubmitting
40
+ }: RecoverAccountFormProps) {
41
+ const {
42
+ authClient,
43
+ localization: contextLocalization,
44
+ toast,
45
+ localizeErrors
46
+ } = useContext(AuthUIContext)
47
+
48
+ localization = { ...contextLocalization, ...localization }
49
+
50
+ const { onSuccess, isPending: transitionPending } = useOnSuccessTransition({
51
+ redirectTo
52
+ })
53
+
54
+ const formSchema = z.object({
55
+ code: z.string().min(1, { message: localization.BACKUP_CODE_REQUIRED })
56
+ })
57
+
58
+ const form = useForm({
59
+ resolver: zodResolver(formSchema),
60
+ defaultValues: {
61
+ code: ""
62
+ }
63
+ })
64
+
65
+ isSubmitting =
66
+ isSubmitting || form.formState.isSubmitting || transitionPending
67
+
68
+ useEffect(() => {
69
+ setIsSubmitting?.(form.formState.isSubmitting || transitionPending)
70
+ }, [form.formState.isSubmitting, transitionPending, setIsSubmitting])
71
+
72
+ async function verifyBackupCode({ code }: z.infer<typeof formSchema>) {
73
+ try {
74
+ await authClient.twoFactor.verifyBackupCode({
75
+ code,
76
+ fetchOptions: { throw: true }
77
+ })
78
+
79
+ await onSuccess()
80
+ } catch (error) {
81
+ toast({
82
+ variant: "error",
83
+ message: getLocalizedError({
84
+ error,
85
+ localization,
86
+ localizeErrors
87
+ })
88
+ })
89
+
90
+ form.reset()
91
+ }
92
+ }
93
+
94
+ return (
95
+ <Form {...form}>
96
+ <form
97
+ onSubmit={form.handleSubmit(verifyBackupCode)}
98
+ className={cn("grid gap-6", className, classNames?.base)}
99
+ >
100
+ <FormField
101
+ control={form.control}
102
+ name="code"
103
+ render={({ field }) => (
104
+ <FormItem>
105
+ <FormLabel className={classNames?.label}>
106
+ {localization.BACKUP_CODE}
107
+ </FormLabel>
108
+
109
+ <FormControl>
110
+ <Input
111
+ placeholder={
112
+ localization.BACKUP_CODE_PLACEHOLDER
113
+ }
114
+ autoComplete="off"
115
+ className={classNames?.input}
116
+ disabled={isSubmitting}
117
+ {...field}
118
+ />
119
+ </FormControl>
120
+
121
+ <FormMessage className={classNames?.error} />
122
+ </FormItem>
123
+ )}
124
+ />
125
+
126
+ <Button
127
+ type="submit"
128
+ disabled={isSubmitting}
129
+ className={cn(
130
+ classNames?.button,
131
+ classNames?.primaryButton
132
+ )}
133
+ >
134
+ {isSubmitting ? (
135
+ <Loader2 className="animate-spin" />
136
+ ) : (
137
+ localization.RECOVER_ACCOUNT_ACTION
138
+ )}
139
+ </Button>
140
+ </form>
141
+ </Form>
142
+ )
143
+ }