better-auth-ui 3.2.5

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 (226) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/dist/auth-hooks-IOEvlYYv.d.cts +6966 -0
  4. package/dist/auth-hooks-IOEvlYYv.d.ts +6966 -0
  5. package/dist/auth-mutators-DdqOmQ32.d.cts +29 -0
  6. package/dist/auth-mutators-DdqOmQ32.d.ts +29 -0
  7. package/dist/auth-ui-provider-BsH3xJDw.d.ts +697 -0
  8. package/dist/auth-ui-provider-DhZfncd3.d.cts +697 -0
  9. package/dist/chunk-BDFQSFBU.js +750 -0
  10. package/dist/chunk-CRAHKL2C.cjs +801 -0
  11. package/dist/chunk-MJPOA6PK.js +801 -0
  12. package/dist/chunk-SV64DXGW.cjs +750 -0
  13. package/dist/index.cjs +12618 -0
  14. package/dist/index.d.cts +771 -0
  15. package/dist/index.d.ts +771 -0
  16. package/dist/index.js +12618 -0
  17. package/dist/instantdb.cjs +189 -0
  18. package/dist/instantdb.d.cts +36 -0
  19. package/dist/instantdb.d.ts +36 -0
  20. package/dist/instantdb.js +189 -0
  21. package/dist/metafile-cjs.json +1 -0
  22. package/dist/metafile-esm.json +1 -0
  23. package/dist/server.cjs +194 -0
  24. package/dist/server.d.cts +35 -0
  25. package/dist/server.d.ts +35 -0
  26. package/dist/server.js +194 -0
  27. package/dist/style.css +1 -0
  28. package/dist/tanstack.cjs +153 -0
  29. package/dist/tanstack.d.cts +18 -0
  30. package/dist/tanstack.d.ts +18 -0
  31. package/dist/tanstack.js +153 -0
  32. package/dist/triplit.cjs +201 -0
  33. package/dist/triplit.d.cts +31 -0
  34. package/dist/triplit.d.ts +31 -0
  35. package/dist/triplit.js +201 -0
  36. package/dist/utils-C5R37WDe.d.cts +3 -0
  37. package/dist/utils-C5R37WDe.d.ts +3 -0
  38. package/dist/view-paths-CHSJf5dv.d.cts +645 -0
  39. package/dist/view-paths-CHSJf5dv.d.ts +645 -0
  40. package/package.json +156 -0
  41. package/src/components/account/account-view.tsx +220 -0
  42. package/src/components/auth/auth-callback.tsx +36 -0
  43. package/src/components/auth/auth-form.tsx +277 -0
  44. package/src/components/auth/auth-view.tsx +389 -0
  45. package/src/components/auth/email-otp-button.tsx +53 -0
  46. package/src/components/auth/forms/email-otp-form.tsx +288 -0
  47. package/src/components/auth/forms/forgot-password-form.tsx +168 -0
  48. package/src/components/auth/forms/magic-link-form.tsx +191 -0
  49. package/src/components/auth/forms/recover-account-form.tsx +138 -0
  50. package/src/components/auth/forms/reset-password-form.tsx +215 -0
  51. package/src/components/auth/forms/sign-in-form.tsx +289 -0
  52. package/src/components/auth/forms/sign-up-form.tsx +788 -0
  53. package/src/components/auth/forms/two-factor-form.tsx +372 -0
  54. package/src/components/auth/magic-link-button.tsx +54 -0
  55. package/src/components/auth/one-tap.tsx +48 -0
  56. package/src/components/auth/otp-input-group.tsx +65 -0
  57. package/src/components/auth/passkey-button.tsx +85 -0
  58. package/src/components/auth/provider-button.tsx +141 -0
  59. package/src/components/auth/sign-out.tsx +25 -0
  60. package/src/components/auth-loading.tsx +21 -0
  61. package/src/components/captcha/captcha.tsx +79 -0
  62. package/src/components/captcha/recaptcha-badge.tsx +61 -0
  63. package/src/components/captcha/recaptcha-v2.tsx +58 -0
  64. package/src/components/captcha/recaptcha-v3.tsx +73 -0
  65. package/src/components/email/email-template.tsx +216 -0
  66. package/src/components/form-error.tsx +27 -0
  67. package/src/components/organization/accept-invitation-card.tsx +362 -0
  68. package/src/components/organization/create-organization-dialog.tsx +395 -0
  69. package/src/components/organization/delete-organization-card.tsx +101 -0
  70. package/src/components/organization/delete-organization-dialog.tsx +209 -0
  71. package/src/components/organization/invitation-cell.tsx +156 -0
  72. package/src/components/organization/invite-member-dialog.tsx +258 -0
  73. package/src/components/organization/leave-organization-dialog.tsx +150 -0
  74. package/src/components/organization/member-cell.tsx +187 -0
  75. package/src/components/organization/organization-cell-view.tsx +122 -0
  76. package/src/components/organization/organization-cell.tsx +154 -0
  77. package/src/components/organization/organization-invitations-card.tsx +94 -0
  78. package/src/components/organization/organization-logo-card.tsx +308 -0
  79. package/src/components/organization/organization-logo.tsx +120 -0
  80. package/src/components/organization/organization-members-card.tsx +155 -0
  81. package/src/components/organization/organization-name-card.tsx +204 -0
  82. package/src/components/organization/organization-settings-cards.tsx +67 -0
  83. package/src/components/organization/organization-slug-card.tsx +223 -0
  84. package/src/components/organization/organization-switcher.tsx +512 -0
  85. package/src/components/organization/organization-view.tsx +228 -0
  86. package/src/components/organization/organizations-card.tsx +72 -0
  87. package/src/components/organization/personal-account-view.tsx +115 -0
  88. package/src/components/organization/remove-member-dialog.tsx +144 -0
  89. package/src/components/organization/update-member-role-dialog.tsx +213 -0
  90. package/src/components/organization/user-invitations-card.tsx +238 -0
  91. package/src/components/password-input.tsx +56 -0
  92. package/src/components/provider-icons.tsx +385 -0
  93. package/src/components/redirect-to-sign-in.tsx +16 -0
  94. package/src/components/redirect-to-sign-up.tsx +16 -0
  95. package/src/components/settings/account/account-cell.tsx +158 -0
  96. package/src/components/settings/account/accounts-card.tsx +75 -0
  97. package/src/components/settings/account/delete-account-card.tsx +65 -0
  98. package/src/components/settings/account/delete-account-dialog.tsx +231 -0
  99. package/src/components/settings/account/update-avatar-card.tsx +198 -0
  100. package/src/components/settings/account/update-field-card.tsx +282 -0
  101. package/src/components/settings/account/update-name-card.tsx +39 -0
  102. package/src/components/settings/account/update-username-card.tsx +42 -0
  103. package/src/components/settings/account-settings-cards.tsx +123 -0
  104. package/src/components/settings/api-key/api-key-cell.tsx +108 -0
  105. package/src/components/settings/api-key/api-key-delete-dialog.tsx +162 -0
  106. package/src/components/settings/api-key/api-key-display-dialog.tsx +110 -0
  107. package/src/components/settings/api-key/api-keys-card.tsx +98 -0
  108. package/src/components/settings/api-key/create-api-key-dialog.tsx +376 -0
  109. package/src/components/settings/passkey/passkey-cell.tsx +113 -0
  110. package/src/components/settings/passkey/passkeys-card.tsx +111 -0
  111. package/src/components/settings/providers/provider-cell.tsx +152 -0
  112. package/src/components/settings/providers/providers-card.tsx +108 -0
  113. package/src/components/settings/security/change-email-card.tsx +200 -0
  114. package/src/components/settings/security/change-password-card.tsx +326 -0
  115. package/src/components/settings/security/session-cell.tsx +120 -0
  116. package/src/components/settings/security/sessions-card.tsx +58 -0
  117. package/src/components/settings/security-settings-cards.tsx +111 -0
  118. package/src/components/settings/shared/session-freshness-dialog.tsx +97 -0
  119. package/src/components/settings/shared/settings-action-button.tsx +51 -0
  120. package/src/components/settings/shared/settings-card-footer.tsx +94 -0
  121. package/src/components/settings/shared/settings-card-header.tsx +67 -0
  122. package/src/components/settings/shared/settings-card.tsx +106 -0
  123. package/src/components/settings/skeletons/input-field-skeleton.tsx +18 -0
  124. package/src/components/settings/skeletons/settings-cell-skeleton.tsx +37 -0
  125. package/src/components/settings/two-factor/backup-codes-dialog.tsx +113 -0
  126. package/src/components/settings/two-factor/two-factor-card.tsx +63 -0
  127. package/src/components/settings/two-factor/two-factor-password-dialog.tsx +226 -0
  128. package/src/components/signed-in.tsx +20 -0
  129. package/src/components/signed-out.tsx +20 -0
  130. package/src/components/ui/alert.tsx +66 -0
  131. package/src/components/ui/avatar.tsx +53 -0
  132. package/src/components/ui/button.tsx +59 -0
  133. package/src/components/ui/card.tsx +92 -0
  134. package/src/components/ui/checkbox.tsx +32 -0
  135. package/src/components/ui/dialog.tsx +143 -0
  136. package/src/components/ui/drawer.tsx +135 -0
  137. package/src/components/ui/dropdown-menu.tsx +257 -0
  138. package/src/components/ui/form.tsx +167 -0
  139. package/src/components/ui/input-otp.tsx +77 -0
  140. package/src/components/ui/input.tsx +21 -0
  141. package/src/components/ui/label.tsx +24 -0
  142. package/src/components/ui/select.tsx +185 -0
  143. package/src/components/ui/separator.tsx +28 -0
  144. package/src/components/ui/skeleton.tsx +13 -0
  145. package/src/components/ui/tabs.tsx +66 -0
  146. package/src/components/ui/textarea.tsx +18 -0
  147. package/src/components/user-avatar.tsx +147 -0
  148. package/src/components/user-button.tsx +409 -0
  149. package/src/components/user-view.tsx +138 -0
  150. package/src/hooks/use-auth-data.ts +184 -0
  151. package/src/hooks/use-authenticate.ts +62 -0
  152. package/src/hooks/use-captcha.tsx +138 -0
  153. package/src/hooks/use-current-organization.ts +59 -0
  154. package/src/hooks/use-hydrated.ts +13 -0
  155. package/src/hooks/use-lang.ts +32 -0
  156. package/src/hooks/use-success-transition.ts +51 -0
  157. package/src/hooks/use-theme.ts +39 -0
  158. package/src/index.ts +65 -0
  159. package/src/instantdb.ts +1 -0
  160. package/src/lib/auth-data-cache.ts +90 -0
  161. package/src/lib/auth-ui-provider.tsx +658 -0
  162. package/src/lib/gravatar-utils.ts +58 -0
  163. package/src/lib/image-utils.ts +55 -0
  164. package/src/lib/instantdb/model-names.ts +24 -0
  165. package/src/lib/instantdb/use-instant-options.ts +98 -0
  166. package/src/lib/instantdb/use-list-accounts.ts +38 -0
  167. package/src/lib/instantdb/use-list-sessions.ts +53 -0
  168. package/src/lib/instantdb/use-session.ts +55 -0
  169. package/src/lib/organization-refetcher.tsx +56 -0
  170. package/src/lib/social-providers.ts +144 -0
  171. package/src/lib/tanstack/auth-ui-provider-tanstack.tsx +49 -0
  172. package/src/lib/tanstack/use-tanstack-options.ts +112 -0
  173. package/src/lib/triplit/model-names.ts +24 -0
  174. package/src/lib/triplit/use-conditional-query.ts +82 -0
  175. package/src/lib/triplit/use-list-accounts.ts +31 -0
  176. package/src/lib/triplit/use-list-sessions.ts +33 -0
  177. package/src/lib/triplit/use-session.ts +42 -0
  178. package/src/lib/triplit/use-triplit-hooks.ts +68 -0
  179. package/src/lib/triplit/use-triplit-token.ts +44 -0
  180. package/src/lib/utils.ts +105 -0
  181. package/src/lib/view-paths.ts +55 -0
  182. package/src/localization/admin-error-codes.ts +20 -0
  183. package/src/localization/anonymous-error-codes.ts +6 -0
  184. package/src/localization/api-key-error-codes.ts +32 -0
  185. package/src/localization/auth-localization.ts +740 -0
  186. package/src/localization/base-error-codes.ts +27 -0
  187. package/src/localization/captcha-error-codes.ts +17 -0
  188. package/src/localization/email-otp-error-codes.ts +7 -0
  189. package/src/localization/generic-oauth-error-codes.ts +3 -0
  190. package/src/localization/haveibeenpwned-error-codes.ts +4 -0
  191. package/src/localization/multi-session-error-codes.ts +3 -0
  192. package/src/localization/organization-error-codes.ts +57 -0
  193. package/src/localization/passkey-error-codes.ts +10 -0
  194. package/src/localization/phone-number-error-codes.ts +10 -0
  195. package/src/localization/stripe-localization.ts +12 -0
  196. package/src/localization/two-factor-error-codes.ts +12 -0
  197. package/src/localization/username-error-codes.ts +9 -0
  198. package/src/server.ts +4 -0
  199. package/src/style.css +1 -0
  200. package/src/tanstack.ts +1 -0
  201. package/src/triplit.ts +1 -0
  202. package/src/types/account-options.ts +35 -0
  203. package/src/types/additional-fields.ts +21 -0
  204. package/src/types/any-auth-client.ts +6 -0
  205. package/src/types/api-key.ts +9 -0
  206. package/src/types/auth-client.ts +37 -0
  207. package/src/types/auth-hooks.ts +61 -0
  208. package/src/types/auth-mutators.ts +17 -0
  209. package/src/types/avatar-options.ts +29 -0
  210. package/src/types/captcha-options.ts +32 -0
  211. package/src/types/captcha-provider.ts +6 -0
  212. package/src/types/credentials-options.ts +32 -0
  213. package/src/types/delete-user-options.ts +7 -0
  214. package/src/types/fetch-error.ts +6 -0
  215. package/src/types/generic-oauth-options.ts +16 -0
  216. package/src/types/gravatar-options.ts +21 -0
  217. package/src/types/image.ts +7 -0
  218. package/src/types/invitation.ts +10 -0
  219. package/src/types/link.ts +7 -0
  220. package/src/types/organization-options.ts +106 -0
  221. package/src/types/password-validation.ts +16 -0
  222. package/src/types/profile.ts +15 -0
  223. package/src/types/refetch.ts +1 -0
  224. package/src/types/render-toast.ts +9 -0
  225. package/src/types/sign-up-options.ts +7 -0
  226. package/src/types/social-options.ts +16 -0
@@ -0,0 +1,97 @@
1
+ import { type ComponentProps, useContext } from "react"
2
+ import { AuthUIContext } from "../../../lib/auth-ui-provider"
3
+ import { cn } from "../../../lib/utils"
4
+ import type { AuthLocalization } from "../../../localization/auth-localization"
5
+ import { Button } from "../../ui/button"
6
+ import {
7
+ Dialog,
8
+ DialogContent,
9
+ DialogDescription,
10
+ DialogFooter,
11
+ DialogHeader,
12
+ DialogTitle
13
+ } from "../../ui/dialog"
14
+ import type { SettingsCardClassNames } from "./settings-card"
15
+
16
+ export interface SessionFreshnessDialogProps
17
+ extends ComponentProps<typeof Dialog> {
18
+ classNames?: SettingsCardClassNames
19
+ localization?: AuthLocalization
20
+ title?: string
21
+ description?: string
22
+ }
23
+
24
+ export function SessionFreshnessDialog({
25
+ classNames,
26
+ localization,
27
+ title,
28
+ description,
29
+ onOpenChange,
30
+ ...props
31
+ }: SessionFreshnessDialogProps) {
32
+ const {
33
+ basePath,
34
+ localization: contextLocalization,
35
+ viewPaths,
36
+ navigate
37
+ } = useContext(AuthUIContext)
38
+
39
+ localization = { ...contextLocalization, ...localization }
40
+
41
+ const handleSignOut = () => {
42
+ navigate(`${basePath}/${viewPaths.SIGN_OUT}`)
43
+ onOpenChange?.(false)
44
+ }
45
+
46
+ return (
47
+ <Dialog onOpenChange={onOpenChange} {...props}>
48
+ <DialogContent
49
+ className={cn("sm:max-w-md", classNames?.dialog?.content)}
50
+ >
51
+ <DialogHeader className={classNames?.dialog?.header}>
52
+ <DialogTitle
53
+ className={cn("text-lg md:text-xl", classNames?.title)}
54
+ >
55
+ {title ||
56
+ localization?.SESSION_EXPIRED ||
57
+ "Session Expired"}
58
+ </DialogTitle>
59
+
60
+ <DialogDescription
61
+ className={cn(
62
+ "text-xs md:text-sm",
63
+ classNames?.description
64
+ )}
65
+ >
66
+ {description || localization?.SESSION_NOT_FRESH}
67
+ </DialogDescription>
68
+ </DialogHeader>
69
+
70
+ <DialogFooter className={classNames?.dialog?.footer}>
71
+ <Button
72
+ type="button"
73
+ variant="secondary"
74
+ className={cn(
75
+ classNames?.button,
76
+ classNames?.secondaryButton
77
+ )}
78
+ onClick={() => onOpenChange?.(false)}
79
+ >
80
+ {localization.CANCEL}
81
+ </Button>
82
+
83
+ <Button
84
+ className={cn(
85
+ classNames?.button,
86
+ classNames?.primaryButton
87
+ )}
88
+ variant="default"
89
+ onClick={handleSignOut}
90
+ >
91
+ {localization?.SIGN_OUT}
92
+ </Button>
93
+ </DialogFooter>
94
+ </DialogContent>
95
+ </Dialog>
96
+ )
97
+ }
@@ -0,0 +1,51 @@
1
+ "use client"
2
+
3
+ import { Loader2 } from "lucide-react"
4
+ import type { ComponentProps, ReactNode } from "react"
5
+ import { useFormState } from "react-hook-form"
6
+
7
+ import { cn } from "../../../lib/utils"
8
+ import { Button } from "../../ui/button"
9
+ import type { SettingsCardClassNames } from "./settings-card"
10
+
11
+ interface SettingsActionButtonProps extends ComponentProps<typeof Button> {
12
+ classNames?: SettingsCardClassNames
13
+ actionLabel: ReactNode
14
+ disabled?: boolean
15
+ isSubmitting?: boolean
16
+ }
17
+
18
+ export function SettingsActionButton({
19
+ classNames,
20
+ actionLabel,
21
+ disabled,
22
+ isSubmitting,
23
+ variant,
24
+ onClick,
25
+ ...props
26
+ }: SettingsActionButtonProps) {
27
+ if (!onClick) {
28
+ const formState = useFormState()
29
+ isSubmitting = formState.isSubmitting
30
+ }
31
+
32
+ return (
33
+ <Button
34
+ className={cn(
35
+ "md:ms-auto",
36
+ classNames?.button,
37
+ variant === "default" && classNames?.primaryButton,
38
+ variant === "destructive" && classNames?.destructiveButton
39
+ )}
40
+ disabled={isSubmitting || disabled}
41
+ size="sm"
42
+ type={onClick ? "button" : "submit"}
43
+ variant={variant}
44
+ onClick={onClick}
45
+ {...props}
46
+ >
47
+ {isSubmitting && <Loader2 className="animate-spin" />}
48
+ {actionLabel}
49
+ </Button>
50
+ )
51
+ }
@@ -0,0 +1,94 @@
1
+ "use client"
2
+
3
+ import type { ReactNode } from "react"
4
+
5
+ import { cn } from "../../../lib/utils"
6
+ import { CardDescription, CardFooter } from "../../ui/card"
7
+ import { Skeleton } from "../../ui/skeleton"
8
+ import { SettingsActionButton } from "./settings-action-button"
9
+ import type { SettingsCardClassNames } from "./settings-card"
10
+
11
+ export interface SettingsCardFooterProps {
12
+ className?: string
13
+ classNames?: SettingsCardClassNames
14
+ actionLabel?: ReactNode
15
+ disabled?: boolean
16
+ instructions?: ReactNode
17
+ isPending?: boolean
18
+ isSubmitting?: boolean
19
+ optimistic?: boolean
20
+ variant?: "default" | "destructive"
21
+ action?: () => Promise<unknown> | unknown
22
+ }
23
+
24
+ export function SettingsCardFooter({
25
+ className,
26
+ classNames,
27
+ actionLabel,
28
+ disabled,
29
+ instructions,
30
+ isPending,
31
+ isSubmitting,
32
+ variant,
33
+ action
34
+ }: SettingsCardFooterProps) {
35
+ return (
36
+ <CardFooter
37
+ className={cn(
38
+ "flex flex-col justify-between gap-4 rounded-b-xl md:flex-row",
39
+ (actionLabel || instructions) && "!py-4 border-t",
40
+ variant === "destructive"
41
+ ? "border-destructive/30 bg-destructive/15"
42
+ : "bg-sidebar",
43
+ className,
44
+ classNames?.footer
45
+ )}
46
+ >
47
+ {isPending ? (
48
+ <>
49
+ {instructions && (
50
+ <Skeleton
51
+ className={cn(
52
+ "my-0.5 h-3 w-48 max-w-full md:h-4 md:w-56",
53
+ classNames?.skeleton
54
+ )}
55
+ />
56
+ )}
57
+
58
+ {actionLabel && (
59
+ <Skeleton
60
+ className={cn(
61
+ "h-8 w-14 md:ms-auto",
62
+ classNames?.skeleton
63
+ )}
64
+ />
65
+ )}
66
+ </>
67
+ ) : (
68
+ <>
69
+ {instructions && (
70
+ <CardDescription
71
+ className={cn(
72
+ "text-center text-muted-foreground text-xs md:text-start md:text-sm",
73
+ classNames?.instructions
74
+ )}
75
+ >
76
+ {instructions}
77
+ </CardDescription>
78
+ )}
79
+
80
+ {actionLabel && (
81
+ <SettingsActionButton
82
+ classNames={classNames}
83
+ actionLabel={actionLabel}
84
+ disabled={disabled}
85
+ isSubmitting={isSubmitting}
86
+ variant={variant}
87
+ onClick={action}
88
+ />
89
+ )}
90
+ </>
91
+ )}
92
+ </CardFooter>
93
+ )
94
+ }
@@ -0,0 +1,67 @@
1
+ "use client"
2
+
3
+ import type { ReactNode } from "react"
4
+
5
+ import { cn } from "../../../lib/utils"
6
+ import { CardDescription, CardHeader, CardTitle } from "../../ui/card"
7
+ import { Skeleton } from "../../ui/skeleton"
8
+ import type { SettingsCardClassNames } from "./settings-card"
9
+
10
+ export interface SettingsCardHeaderProps {
11
+ className?: string
12
+ classNames?: SettingsCardClassNames
13
+ description?: ReactNode
14
+ isPending?: boolean
15
+ title: ReactNode
16
+ }
17
+
18
+ export function SettingsCardHeader({
19
+ className,
20
+ classNames,
21
+ description,
22
+ isPending,
23
+ title
24
+ }: SettingsCardHeaderProps) {
25
+ return (
26
+ <CardHeader className={cn(classNames?.header, className)}>
27
+ {isPending ? (
28
+ <>
29
+ <Skeleton
30
+ className={cn(
31
+ "my-0.5 h-5 w-1/3 md:h-5.5",
32
+ classNames?.skeleton
33
+ )}
34
+ />
35
+
36
+ {description && (
37
+ <Skeleton
38
+ className={cn(
39
+ "mt-1.5 mb-0.5 h-3 w-2/3 md:h-3.5",
40
+ classNames?.skeleton
41
+ )}
42
+ />
43
+ )}
44
+ </>
45
+ ) : (
46
+ <>
47
+ <CardTitle
48
+ className={cn("text-lg md:text-xl", classNames?.title)}
49
+ >
50
+ {title}
51
+ </CardTitle>
52
+
53
+ {description && (
54
+ <CardDescription
55
+ className={cn(
56
+ "text-xs md:text-sm",
57
+ classNames?.description
58
+ )}
59
+ >
60
+ {description}
61
+ </CardDescription>
62
+ )}
63
+ </>
64
+ )}
65
+ </CardHeader>
66
+ )
67
+ }
@@ -0,0 +1,106 @@
1
+ "use client"
2
+
3
+ import type { ComponentProps, ReactNode } from "react"
4
+
5
+ import { cn } from "../../../lib/utils"
6
+ import type { AuthLocalization } from "../../../localization/auth-localization"
7
+ import { Card } from "../../ui/card"
8
+ import type { UserAvatarClassNames } from "../../user-avatar"
9
+ import { SettingsCardFooter } from "./settings-card-footer"
10
+ import { SettingsCardHeader } from "./settings-card-header"
11
+
12
+ export type SettingsCardClassNames = {
13
+ base?: string
14
+ avatar?: UserAvatarClassNames
15
+ button?: string
16
+ cell?: string
17
+ checkbox?: string
18
+ destructiveButton?: string
19
+ content?: string
20
+ description?: string
21
+ dialog?: {
22
+ content?: string
23
+ footer?: string
24
+ header?: string
25
+ }
26
+ error?: string
27
+ footer?: string
28
+ header?: string
29
+ icon?: string
30
+ input?: string
31
+ instructions?: string
32
+ label?: string
33
+ primaryButton?: string
34
+ secondaryButton?: string
35
+ outlineButton?: string
36
+ skeleton?: string
37
+ title?: string
38
+ }
39
+
40
+ export interface SettingsCardProps
41
+ extends Omit<ComponentProps<typeof Card>, "title"> {
42
+ children?: ReactNode
43
+ className?: string
44
+ classNames?: SettingsCardClassNames
45
+ title?: ReactNode
46
+ description?: ReactNode
47
+ instructions?: ReactNode
48
+ actionLabel?: ReactNode
49
+ isSubmitting?: boolean
50
+ disabled?: boolean
51
+ isPending?: boolean
52
+ optimistic?: boolean
53
+ variant?: "default" | "destructive"
54
+ localization?: AuthLocalization
55
+ action?: () => Promise<unknown> | unknown
56
+ }
57
+
58
+ export function SettingsCard({
59
+ children,
60
+ className,
61
+ classNames,
62
+ title,
63
+ description,
64
+ instructions,
65
+ actionLabel,
66
+ disabled,
67
+ isPending,
68
+ isSubmitting,
69
+ optimistic,
70
+ variant,
71
+ action,
72
+ ...props
73
+ }: SettingsCardProps) {
74
+ return (
75
+ <Card
76
+ className={cn(
77
+ "w-full pb-0 text-start",
78
+ variant === "destructive" && "border-destructive/40",
79
+ className,
80
+ classNames?.base
81
+ )}
82
+ {...props}
83
+ >
84
+ <SettingsCardHeader
85
+ classNames={classNames}
86
+ description={description}
87
+ isPending={isPending}
88
+ title={title}
89
+ />
90
+
91
+ {children}
92
+
93
+ <SettingsCardFooter
94
+ classNames={classNames}
95
+ actionLabel={actionLabel}
96
+ disabled={disabled}
97
+ isPending={isPending}
98
+ isSubmitting={isSubmitting}
99
+ instructions={instructions}
100
+ optimistic={optimistic}
101
+ variant={variant}
102
+ action={action}
103
+ />
104
+ </Card>
105
+ )
106
+ }
@@ -0,0 +1,18 @@
1
+ "use client"
2
+
3
+ import { cn } from "../../../lib/utils"
4
+ import { Skeleton } from "../../ui/skeleton"
5
+ import type { SettingsCardClassNames } from "../shared/settings-card"
6
+
7
+ export function InputFieldSkeleton({
8
+ classNames
9
+ }: {
10
+ classNames?: SettingsCardClassNames
11
+ }) {
12
+ return (
13
+ <div className="flex flex-col gap-1.5">
14
+ <Skeleton className={cn("h-4 w-32", classNames?.skeleton)} />
15
+ <Skeleton className={cn("h-9 w-full", classNames?.skeleton)} />
16
+ </div>
17
+ )
18
+ }
@@ -0,0 +1,37 @@
1
+ "use client"
2
+
3
+ import { cn } from "../../../lib/utils"
4
+ import { Card } from "../../ui/card"
5
+ import { Skeleton } from "../../ui/skeleton"
6
+ import type { SettingsCardClassNames } from "../shared/settings-card"
7
+
8
+ export function SettingsCellSkeleton({
9
+ classNames
10
+ }: {
11
+ classNames?: SettingsCardClassNames
12
+ }) {
13
+ return (
14
+ <Card
15
+ className={cn(
16
+ "flex-row items-center gap-3 px-4 py-3",
17
+ classNames?.cell
18
+ )}
19
+ >
20
+ <div className="flex items-center gap-2">
21
+ <Skeleton
22
+ className={cn("size-5 rounded-full", classNames?.skeleton)}
23
+ />
24
+
25
+ <div>
26
+ <Skeleton
27
+ className={cn("h-4 w-24", classNames?.skeleton)}
28
+ />
29
+ </div>
30
+ </div>
31
+
32
+ <Skeleton
33
+ className={cn("ms-auto size-8 w-12", classNames?.skeleton)}
34
+ />
35
+ </Card>
36
+ )
37
+ }
@@ -0,0 +1,113 @@
1
+ "use client"
2
+
3
+ import { CheckIcon, CopyIcon } from "lucide-react"
4
+ import { type ComponentProps, useContext, useState } from "react"
5
+
6
+ import { AuthUIContext } from "../../../lib/auth-ui-provider"
7
+ import { cn } from "../../../lib/utils"
8
+ import { Button } from "../../ui/button"
9
+ import {
10
+ Dialog,
11
+ DialogContent,
12
+ DialogDescription,
13
+ DialogFooter,
14
+ DialogHeader,
15
+ DialogTitle
16
+ } from "../../ui/dialog"
17
+ import type { SettingsCardClassNames } from "../shared/settings-card"
18
+
19
+ interface BackupCodesDialogProps extends ComponentProps<typeof Dialog> {
20
+ classNames?: SettingsCardClassNames
21
+ backupCodes: string[]
22
+ }
23
+
24
+ export function BackupCodesDialog({
25
+ classNames,
26
+ backupCodes,
27
+ onOpenChange,
28
+ ...props
29
+ }: BackupCodesDialogProps) {
30
+ const { localization } = useContext(AuthUIContext)
31
+ const [copied, setCopied] = useState(false)
32
+
33
+ const handleCopy = () => {
34
+ const codeText = backupCodes.join("\n")
35
+ navigator.clipboard.writeText(codeText)
36
+ setCopied(true)
37
+ setTimeout(() => setCopied(false), 2000)
38
+ }
39
+
40
+ return (
41
+ <Dialog onOpenChange={onOpenChange} {...props}>
42
+ <DialogContent
43
+ onOpenAutoFocus={(e) => e.preventDefault()}
44
+ className={classNames?.dialog?.content}
45
+ >
46
+ <DialogHeader className={classNames?.dialog?.header}>
47
+ <DialogTitle
48
+ className={cn("text-lg md:text-xl", classNames?.title)}
49
+ >
50
+ {localization.BACKUP_CODES}
51
+ </DialogTitle>
52
+
53
+ <DialogDescription
54
+ className={cn(
55
+ "text-xs md:text-sm",
56
+ classNames?.description
57
+ )}
58
+ >
59
+ {localization.BACKUP_CODES_DESCRIPTION}
60
+ </DialogDescription>
61
+ </DialogHeader>
62
+
63
+ <div className="grid grid-cols-2 gap-2">
64
+ {backupCodes.map((code, index) => (
65
+ <div
66
+ key={index}
67
+ className="rounded-md bg-muted p-2 text-center font-mono text-sm"
68
+ >
69
+ {code}
70
+ </div>
71
+ ))}
72
+ </div>
73
+
74
+ <DialogFooter className={classNames?.dialog?.footer}>
75
+ <Button
76
+ type="button"
77
+ variant="outline"
78
+ onClick={handleCopy}
79
+ disabled={copied}
80
+ className={cn(
81
+ classNames?.button,
82
+ classNames?.outlineButton
83
+ )}
84
+ >
85
+ {copied ? (
86
+ <>
87
+ <CheckIcon className={classNames?.icon} />
88
+ {localization.COPIED_TO_CLIPBOARD}
89
+ </>
90
+ ) : (
91
+ <>
92
+ <CopyIcon className={classNames?.icon} />
93
+ {localization.COPY_ALL_CODES}
94
+ </>
95
+ )}
96
+ </Button>
97
+
98
+ <Button
99
+ type="button"
100
+ variant="default"
101
+ onClick={() => onOpenChange?.(false)}
102
+ className={cn(
103
+ classNames?.button,
104
+ classNames?.primaryButton
105
+ )}
106
+ >
107
+ {localization.CONTINUE}
108
+ </Button>
109
+ </DialogFooter>
110
+ </DialogContent>
111
+ </Dialog>
112
+ )
113
+ }
@@ -0,0 +1,63 @@
1
+ "use client"
2
+
3
+ import { useContext, useState } from "react"
4
+ import { AuthUIContext } from "../../../lib/auth-ui-provider"
5
+ import type { AuthLocalization } from "../../../localization/auth-localization"
6
+ import type { User } from "../../../types/auth-client"
7
+ import type { SettingsCardClassNames } from "../shared/settings-card"
8
+ import { SettingsCard } from "../shared/settings-card"
9
+ import { TwoFactorPasswordDialog } from "./two-factor-password-dialog"
10
+
11
+ export interface TwoFactorCardProps {
12
+ className?: string
13
+ classNames?: SettingsCardClassNames
14
+ localization?: AuthLocalization
15
+ }
16
+
17
+ export function TwoFactorCard({
18
+ className,
19
+ classNames,
20
+ localization
21
+ }: TwoFactorCardProps) {
22
+ const {
23
+ localization: contextLocalization,
24
+ hooks: { useSession }
25
+ } = useContext(AuthUIContext)
26
+
27
+ const [showPasswordDialog, setShowPasswordDialog] = useState(false)
28
+
29
+ localization = { ...contextLocalization, ...localization }
30
+
31
+ const { data: sessionData, isPending } = useSession()
32
+ const isTwoFactorEnabled = (sessionData?.user as User)?.twoFactorEnabled
33
+
34
+ return (
35
+ <div>
36
+ <SettingsCard
37
+ className={className}
38
+ classNames={classNames}
39
+ actionLabel={
40
+ isTwoFactorEnabled
41
+ ? localization.DISABLE_TWO_FACTOR
42
+ : localization.ENABLE_TWO_FACTOR
43
+ }
44
+ description={localization.TWO_FACTOR_CARD_DESCRIPTION}
45
+ instructions={
46
+ isTwoFactorEnabled
47
+ ? localization.TWO_FACTOR_DISABLE_INSTRUCTIONS
48
+ : localization.TWO_FACTOR_ENABLE_INSTRUCTIONS
49
+ }
50
+ isPending={isPending}
51
+ title={localization.TWO_FACTOR}
52
+ action={() => setShowPasswordDialog(true)}
53
+ />
54
+
55
+ <TwoFactorPasswordDialog
56
+ classNames={classNames}
57
+ open={showPasswordDialog}
58
+ onOpenChange={setShowPasswordDialog}
59
+ isTwoFactorEnabled={!!isTwoFactorEnabled}
60
+ />
61
+ </div>
62
+ )
63
+ }