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,658 @@
1
+ "use client"
2
+
3
+ import { createContext, type ReactNode, useMemo } from "react"
4
+ import { toast } from "sonner"
5
+
6
+ import { RecaptchaV3 } from "../components/captcha/recaptcha-v3"
7
+ import { useAuthData } from "../hooks/use-auth-data"
8
+ import {
9
+ type AuthLocalization,
10
+ authLocalization
11
+ } from "../localization/auth-localization"
12
+ import type {
13
+ AccountOptions,
14
+ AccountOptionsContext
15
+ } from "../types/account-options"
16
+ import type { AdditionalFields } from "../types/additional-fields"
17
+ import type { AnyAuthClient } from "../types/any-auth-client"
18
+ import type { AuthClient } from "../types/auth-client"
19
+ import type { AuthHooks } from "../types/auth-hooks"
20
+ import type { AuthMutators } from "../types/auth-mutators"
21
+ import type { AvatarOptions } from "../types/avatar-options"
22
+ import type { CaptchaOptions } from "../types/captcha-options"
23
+ import type { CredentialsOptions } from "../types/credentials-options"
24
+ import type { DeleteUserOptions } from "../types/delete-user-options"
25
+ import type { GenericOAuthOptions } from "../types/generic-oauth-options"
26
+ import type { GravatarOptions } from "../types/gravatar-options"
27
+ import type { Link } from "../types/link"
28
+ import type {
29
+ OrganizationOptions,
30
+ OrganizationOptionsContext
31
+ } from "../types/organization-options"
32
+ import type { RenderToast } from "../types/render-toast"
33
+ import type { SignUpOptions } from "../types/sign-up-options"
34
+ import type { SocialOptions } from "../types/social-options"
35
+ import { OrganizationRefetcher } from "./organization-refetcher"
36
+ import type { AuthViewPaths } from "./view-paths"
37
+ import {
38
+ accountViewPaths,
39
+ authViewPaths,
40
+ organizationViewPaths
41
+ } from "./view-paths"
42
+
43
+ const DefaultLink: Link = ({ href, className, children }) => (
44
+ <a className={className} href={href}>
45
+ {children}
46
+ </a>
47
+ )
48
+
49
+ const defaultNavigate = (href: string) => {
50
+ window.location.href = href
51
+ }
52
+
53
+ const defaultReplace = (href: string) => {
54
+ window.location.replace(href)
55
+ }
56
+
57
+ const defaultToast: RenderToast = ({ variant = "default", message }) => {
58
+ if (variant === "default") {
59
+ toast(message)
60
+ } else {
61
+ toast[variant](message)
62
+ }
63
+ }
64
+
65
+ export type AuthUIContextType = {
66
+ authClient: AuthClient
67
+ /**
68
+ * Additional fields for users
69
+ */
70
+ additionalFields?: AdditionalFields
71
+ /**
72
+ * API Key plugin configuration
73
+ */
74
+ apiKey?:
75
+ | {
76
+ /**
77
+ * Prefix for API Keys
78
+ */
79
+ prefix?: string
80
+ /**
81
+ * Metadata for API Keys
82
+ */
83
+ metadata?: Record<string, unknown>
84
+ }
85
+ | boolean
86
+ /**
87
+ * Avatar configuration
88
+ * @default undefined
89
+ */
90
+ avatar?: AvatarOptions
91
+ /**
92
+ * Base path for the auth views
93
+ * @default "/auth"
94
+ */
95
+ basePath: string
96
+ /**
97
+ * Front end base URL for auth API callbacks
98
+ */
99
+ baseURL?: string
100
+ /**
101
+ * Captcha configuration
102
+ */
103
+ captcha?: CaptchaOptions
104
+ credentials?: CredentialsOptions
105
+ /**
106
+ * Default redirect URL after authenticating
107
+ * @default "/"
108
+ */
109
+ redirectTo: string
110
+ /**
111
+ * Enable or disable user change email support
112
+ * @default true
113
+ */
114
+ changeEmail?: boolean
115
+ /**
116
+ * User Account deletion configuration
117
+ * @default undefined
118
+ */
119
+ deleteUser?: DeleteUserOptions
120
+ /**
121
+ * Show Verify Email card for unverified emails
122
+ */
123
+ emailVerification?: boolean
124
+ /**
125
+ * Freshness age for Session data
126
+ * @default 60 * 60 * 24
127
+ */
128
+ freshAge: number
129
+ /**
130
+ * Generic OAuth provider configuration
131
+ */
132
+ genericOAuth?: GenericOAuthOptions
133
+ /**
134
+ * Gravatar configuration
135
+ */
136
+ gravatar?: boolean | GravatarOptions
137
+ hooks: AuthHooks
138
+ localization: typeof authLocalization
139
+ /**
140
+ * Enable or disable Magic Link support
141
+ * @default false
142
+ */
143
+ magicLink?: boolean
144
+ /**
145
+ * Enable or disable Email OTP support
146
+ * @default false
147
+ */
148
+ emailOTP?: boolean
149
+ /**
150
+ * Enable or disable Multi Session support
151
+ * @default false
152
+ */
153
+ multiSession?: boolean
154
+ mutators: AuthMutators
155
+ /**
156
+ * Whether the name field should be required
157
+ * @default true
158
+ */
159
+ nameRequired?: boolean
160
+ /**
161
+ * Enable or disable One Tap support
162
+ * @default false
163
+ */
164
+ oneTap?: boolean
165
+ /**
166
+ * Perform some User updates optimistically
167
+ * @default false
168
+ */
169
+ optimistic?: boolean
170
+ /**
171
+ * Organization configuration
172
+ */
173
+ organization?: OrganizationOptionsContext
174
+ /**
175
+ * Enable or disable Passkey support
176
+ * @default false
177
+ */
178
+ passkey?: boolean
179
+ /**
180
+ * Forces better-auth-tanstack to refresh the Session on the auth callback page
181
+ * @default false
182
+ */
183
+ persistClient?: boolean
184
+ /**
185
+ * Account configuration
186
+ */
187
+ account?: AccountOptionsContext
188
+ /**
189
+ * Sign Up configuration
190
+ */
191
+ signUp?: SignUpOptions
192
+ /**
193
+ * Social provider configuration
194
+ */
195
+ social?: SocialOptions
196
+ toast: RenderToast
197
+ /**
198
+ * Enable or disable two-factor authentication support
199
+ * @default undefined
200
+ */
201
+ twoFactor?: ("otp" | "totp")[]
202
+ viewPaths: AuthViewPaths
203
+ /**
204
+ * Navigate to a new URL
205
+ * @default window.location.href
206
+ */
207
+ navigate: (href: string) => void
208
+ /**
209
+ * Called whenever the Session changes
210
+ */
211
+ onSessionChange?: () => void | Promise<void>
212
+ /**
213
+ * Replace the current URL
214
+ * @default navigate
215
+ */
216
+ replace: (href: string) => void
217
+ /**
218
+ * Custom Link component for navigation
219
+ * @default <a>
220
+ */
221
+ Link: Link
222
+ }
223
+
224
+ export type AuthUIProviderProps = {
225
+ children: ReactNode
226
+ /**
227
+ * Better Auth client returned from createAuthClient
228
+ * @default Required
229
+ * @remarks `AuthClient`
230
+ */
231
+ authClient: AnyAuthClient
232
+ /**
233
+ * Enable account view & account configuration
234
+ * @default { fields: ["image", "name"] }
235
+ */
236
+ account?: boolean | Partial<AccountOptions>
237
+ /**
238
+ * Avatar configuration
239
+ * @default undefined
240
+ */
241
+ avatar?: boolean | Partial<AvatarOptions>
242
+ /**
243
+ * User Account deletion configuration
244
+ * @default undefined
245
+ */
246
+ deleteUser?: DeleteUserOptions | boolean
247
+ /**
248
+ * ADVANCED: Custom hooks for fetching auth data
249
+ */
250
+ hooks?: Partial<AuthHooks>
251
+ /**
252
+ * Customize the paths for the auth views
253
+ * @default authViewPaths
254
+ * @remarks `AuthViewPaths`
255
+ */
256
+ viewPaths?: Partial<AuthViewPaths>
257
+ /**
258
+ * Render custom Toasts
259
+ * @default Sonner
260
+ */
261
+ toast?: RenderToast
262
+ /**
263
+ * Customize the Localization strings
264
+ * @default authLocalization
265
+ * @remarks `AuthLocalization`
266
+ */
267
+ localization?: AuthLocalization
268
+ /**
269
+ * ADVANCED: Custom mutators for updating auth data
270
+ */
271
+ mutators?: Partial<AuthMutators>
272
+ /**
273
+ * Organization plugin configuration
274
+ */
275
+ organization?: OrganizationOptions | boolean
276
+ /**
277
+ * Enable or disable Credentials support
278
+ * @default { forgotPassword: true }
279
+ */
280
+ credentials?: boolean | CredentialsOptions
281
+ /**
282
+ * Enable or disable Sign Up form
283
+ * @default { fields: ["name"] }
284
+ */
285
+ signUp?: SignUpOptions | boolean
286
+ } & Partial<
287
+ Omit<
288
+ AuthUIContextType,
289
+ | "authClient"
290
+ | "viewPaths"
291
+ | "localization"
292
+ | "mutators"
293
+ | "toast"
294
+ | "hooks"
295
+ | "avatar"
296
+ | "account"
297
+ | "deleteUser"
298
+ | "credentials"
299
+ | "signUp"
300
+ | "organization"
301
+ >
302
+ >
303
+
304
+ export const AuthUIContext = createContext<AuthUIContextType>(
305
+ {} as unknown as AuthUIContextType
306
+ )
307
+
308
+ export const AuthUIProvider = ({
309
+ children,
310
+ authClient: authClientProp,
311
+ account: accountProp,
312
+ avatar: avatarProp,
313
+ deleteUser: deleteUserProp,
314
+ social: socialProp,
315
+ genericOAuth: genericOAuthProp,
316
+ basePath = "/auth",
317
+ baseURL = "",
318
+ captcha,
319
+ redirectTo = "/",
320
+ credentials: credentialsProp,
321
+ changeEmail = true,
322
+ freshAge = 60 * 60 * 24,
323
+ hooks: hooksProp,
324
+ mutators: mutatorsProp,
325
+ localization: localizationProp,
326
+ nameRequired = true,
327
+ organization: organizationProp,
328
+ signUp: signUpProp = true,
329
+ toast = defaultToast,
330
+ viewPaths: viewPathsProp,
331
+ navigate,
332
+ replace,
333
+ Link = DefaultLink,
334
+ ...props
335
+ }: AuthUIProviderProps) => {
336
+ const authClient = authClientProp as AuthClient
337
+
338
+ const avatar = useMemo<AvatarOptions | undefined>(() => {
339
+ if (!avatarProp) return
340
+
341
+ if (avatarProp === true) {
342
+ return {
343
+ extension: "png",
344
+ size: 128
345
+ }
346
+ }
347
+
348
+ return {
349
+ upload: avatarProp.upload,
350
+ delete: avatarProp.delete,
351
+ extension: avatarProp.extension || "png",
352
+ size: avatarProp.size || (avatarProp.upload ? 256 : 128)
353
+ }
354
+ }, [avatarProp])
355
+
356
+ const account = useMemo<AccountOptionsContext | undefined>(() => {
357
+ if (accountProp === false) return
358
+
359
+ if (accountProp === true || accountProp === undefined) {
360
+ return {
361
+ basePath: "/account",
362
+ fields: ["image", "name"],
363
+ viewPaths: accountViewPaths
364
+ }
365
+ }
366
+
367
+ // Remove trailing slash from basePath
368
+ const basePath = accountProp.basePath?.endsWith("/")
369
+ ? accountProp.basePath.slice(0, -1)
370
+ : accountProp.basePath
371
+
372
+ return {
373
+ basePath: basePath ?? "/account",
374
+ fields: accountProp.fields || ["image", "name"],
375
+ viewPaths: { ...accountViewPaths, ...accountProp.viewPaths }
376
+ }
377
+ }, [accountProp])
378
+
379
+ const deleteUser = useMemo<DeleteUserOptions | undefined>(() => {
380
+ if (!deleteUserProp) return
381
+
382
+ if (deleteUserProp === true) {
383
+ return {}
384
+ }
385
+
386
+ return deleteUserProp
387
+ }, [deleteUserProp])
388
+
389
+ const social = useMemo<SocialOptions | undefined>(() => {
390
+ if (!socialProp) return
391
+
392
+ return socialProp
393
+ }, [socialProp])
394
+
395
+ const genericOAuth = useMemo<GenericOAuthOptions | undefined>(() => {
396
+ if (!genericOAuthProp) return
397
+
398
+ return genericOAuthProp
399
+ }, [genericOAuthProp])
400
+
401
+ const credentials = useMemo<CredentialsOptions | undefined>(() => {
402
+ if (credentialsProp === false) return
403
+
404
+ if (credentialsProp === true) {
405
+ return {
406
+ forgotPassword: true
407
+ }
408
+ }
409
+
410
+ return {
411
+ ...credentialsProp,
412
+ forgotPassword: credentialsProp?.forgotPassword ?? true
413
+ }
414
+ }, [credentialsProp])
415
+
416
+ const signUp = useMemo<SignUpOptions | undefined>(() => {
417
+ if (signUpProp === false) return
418
+
419
+ if (signUpProp === true || signUpProp === undefined) {
420
+ return {
421
+ fields: ["name"]
422
+ }
423
+ }
424
+
425
+ return {
426
+ fields: signUpProp.fields || ["name"]
427
+ }
428
+ }, [signUpProp])
429
+
430
+ const organization = useMemo<OrganizationOptionsContext | undefined>(() => {
431
+ if (!organizationProp) return
432
+
433
+ if (organizationProp === true) {
434
+ return {
435
+ basePath: "/organization",
436
+ viewPaths: organizationViewPaths,
437
+ customRoles: []
438
+ }
439
+ }
440
+
441
+ let logo: OrganizationOptionsContext["logo"] | undefined
442
+
443
+ if (organizationProp.logo === true) {
444
+ logo = {
445
+ extension: "png",
446
+ size: 128
447
+ }
448
+ } else if (organizationProp.logo) {
449
+ logo = {
450
+ upload: organizationProp.logo.upload,
451
+ delete: organizationProp.logo.delete,
452
+ extension: organizationProp.logo.extension || "png",
453
+ size:
454
+ organizationProp.logo.size || organizationProp.logo.upload
455
+ ? 256
456
+ : 128
457
+ }
458
+ }
459
+
460
+ // Remove trailing slash from basePath
461
+ const basePath = organizationProp.basePath?.endsWith("/")
462
+ ? organizationProp.basePath.slice(0, -1)
463
+ : organizationProp.basePath
464
+
465
+ return {
466
+ ...organizationProp,
467
+ logo,
468
+ basePath: basePath ?? "/organization",
469
+ customRoles: organizationProp.customRoles || [],
470
+ viewPaths: {
471
+ ...organizationViewPaths,
472
+ ...organizationProp.viewPaths
473
+ }
474
+ }
475
+ }, [organizationProp])
476
+
477
+ const defaultMutators = useMemo(() => {
478
+ return {
479
+ deleteApiKey: (params) =>
480
+ authClient.apiKey.delete({
481
+ ...params,
482
+ fetchOptions: { throw: true }
483
+ }),
484
+ deletePasskey: (params) =>
485
+ authClient.passkey.deletePasskey({
486
+ ...params,
487
+ fetchOptions: { throw: true }
488
+ }),
489
+ revokeDeviceSession: (params) =>
490
+ authClient.multiSession.revoke({
491
+ ...params,
492
+ fetchOptions: { throw: true }
493
+ }),
494
+ revokeSession: (params) =>
495
+ authClient.revokeSession({
496
+ ...params,
497
+ fetchOptions: { throw: true }
498
+ }),
499
+ setActiveSession: (params) =>
500
+ authClient.multiSession.setActive({
501
+ ...params,
502
+ fetchOptions: { throw: true }
503
+ }),
504
+ updateOrganization: (params) =>
505
+ authClient.organization.update({
506
+ ...params,
507
+ fetchOptions: { throw: true }
508
+ }),
509
+ updateUser: (params) =>
510
+ authClient.updateUser({
511
+ ...params,
512
+ fetchOptions: { throw: true }
513
+ }),
514
+ unlinkAccount: (params) =>
515
+ authClient.unlinkAccount({
516
+ ...params,
517
+ fetchOptions: { throw: true }
518
+ })
519
+ } as AuthMutators
520
+ }, [authClient])
521
+
522
+ const defaultHooks = useMemo(() => {
523
+ return {
524
+ useSession: authClient.useSession,
525
+ useListAccounts: () =>
526
+ useAuthData({
527
+ queryFn: authClient.listAccounts,
528
+ cacheKey: "listAccounts"
529
+ }),
530
+ useAccountInfo: (params) =>
531
+ useAuthData({
532
+ queryFn: () => authClient.accountInfo(params),
533
+ cacheKey: `accountInfo:${JSON.stringify(params)}`
534
+ }),
535
+ useListDeviceSessions: () =>
536
+ useAuthData({
537
+ queryFn: authClient.multiSession.listDeviceSessions,
538
+ cacheKey: "listDeviceSessions"
539
+ }),
540
+ useListSessions: () =>
541
+ useAuthData({
542
+ queryFn: authClient.listSessions,
543
+ cacheKey: "listSessions"
544
+ }),
545
+ useListPasskeys: authClient.useListPasskeys,
546
+ useListApiKeys: () =>
547
+ useAuthData({
548
+ queryFn: authClient.apiKey.list,
549
+ cacheKey: "listApiKeys"
550
+ }),
551
+ useActiveOrganization: authClient.useActiveOrganization,
552
+ useListOrganizations: authClient.useListOrganizations,
553
+ useHasPermission: (params) =>
554
+ useAuthData({
555
+ queryFn: () =>
556
+ authClient.$fetch("/organization/has-permission", {
557
+ method: "POST",
558
+ body: params
559
+ }),
560
+ cacheKey: `hasPermission:${JSON.stringify(params)}`
561
+ }),
562
+ useInvitation: (params) =>
563
+ useAuthData({
564
+ queryFn: () =>
565
+ authClient.organization.getInvitation(params),
566
+ cacheKey: `invitation:${JSON.stringify(params)}`
567
+ }),
568
+ useListInvitations: (params) =>
569
+ useAuthData({
570
+ queryFn: () =>
571
+ authClient.$fetch(
572
+ `/organization/list-invitations?organizationId=${params?.query?.organizationId || ""}`
573
+ ),
574
+ cacheKey: `listInvitations:${JSON.stringify(params)}`
575
+ }),
576
+ useListUserInvitations: () =>
577
+ useAuthData({
578
+ queryFn: () =>
579
+ authClient.$fetch(
580
+ "/organization/list-user-invitations"
581
+ ),
582
+ cacheKey: `listUserInvitations`
583
+ }),
584
+ useListMembers: (params) =>
585
+ useAuthData({
586
+ queryFn: () =>
587
+ authClient.$fetch(
588
+ `/organization/list-members?organizationId=${params?.query?.organizationId || ""}`
589
+ ),
590
+ cacheKey: `listMembers:${JSON.stringify(params)}`
591
+ })
592
+ } as AuthHooks
593
+ }, [authClient])
594
+
595
+ const viewPaths = useMemo(() => {
596
+ return { ...authViewPaths, ...viewPathsProp }
597
+ }, [viewPathsProp])
598
+
599
+ const localization = useMemo(() => {
600
+ return { ...authLocalization, ...localizationProp }
601
+ }, [localizationProp])
602
+
603
+ const hooks = useMemo(() => {
604
+ return { ...defaultHooks, ...hooksProp }
605
+ }, [defaultHooks, hooksProp])
606
+
607
+ const mutators = useMemo(() => {
608
+ return { ...defaultMutators, ...mutatorsProp }
609
+ }, [defaultMutators, mutatorsProp])
610
+
611
+ // Remove trailing slash from baseURL
612
+ baseURL = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL
613
+
614
+ // Remove trailing slash from basePath
615
+ basePath = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath
616
+
617
+ const { data: sessionData } = hooks.useSession()
618
+
619
+ return (
620
+ <AuthUIContext.Provider
621
+ value={{
622
+ authClient,
623
+ avatar,
624
+ basePath: basePath === "/" ? "" : basePath,
625
+ baseURL,
626
+ captcha,
627
+ redirectTo,
628
+ changeEmail,
629
+ credentials,
630
+ deleteUser,
631
+ freshAge,
632
+ genericOAuth,
633
+ hooks,
634
+ mutators,
635
+ localization,
636
+ nameRequired,
637
+ organization,
638
+ account,
639
+ signUp,
640
+ social,
641
+ toast,
642
+ navigate: navigate || defaultNavigate,
643
+ replace: replace || navigate || defaultReplace,
644
+ viewPaths,
645
+ Link,
646
+ ...props
647
+ }}
648
+ >
649
+ {sessionData && organization && <OrganizationRefetcher />}
650
+
651
+ {captcha?.provider === "google-recaptcha-v3" ? (
652
+ <RecaptchaV3>{children}</RecaptchaV3>
653
+ ) : (
654
+ children
655
+ )}
656
+ </AuthUIContext.Provider>
657
+ )
658
+ }
@@ -0,0 +1,58 @@
1
+ import { sha256 } from "@noble/hashes/sha2.js"
2
+ import { bytesToHex } from "@noble/hashes/utils.js"
3
+ import type { GravatarOptions } from "../types/gravatar-options"
4
+
5
+ /**
6
+ * Generate a Gravatar URL for an email address
7
+ * @param email - Email address
8
+ * @param options - Gravatar options
9
+ * @returns Gravatar URL or null if email is invalid
10
+ */
11
+ export function getGravatarUrl(
12
+ email?: string | null,
13
+ options?: GravatarOptions
14
+ ): string | null {
15
+ if (!email) return null
16
+
17
+ try {
18
+ // Normalize email: trim and lowercase
19
+ const normalizedEmail = email.trim().toLowerCase()
20
+ // sha256 expects Uint8Array, so encode string to Uint8Array
21
+ const encoder = new TextEncoder()
22
+ const emailBytes = encoder.encode(normalizedEmail)
23
+ const hash = bytesToHex(sha256(emailBytes))
24
+ const extension = options?.jpg ? ".jpg" : ""
25
+ let url = `https://gravatar.com/avatar/${hash}${extension}`
26
+
27
+ const params = new URLSearchParams()
28
+
29
+ // Add size parameter
30
+ if (options?.size) {
31
+ params.append(
32
+ "s",
33
+ Math.min(Math.max(options.size, 1), 2048).toString()
34
+ )
35
+ }
36
+
37
+ // Add default image parameter
38
+ if (options?.d) {
39
+ params.append("d", options.d)
40
+ }
41
+
42
+ // Add force default parameter
43
+ if (options?.forceDefault) {
44
+ params.append("f", "y")
45
+ }
46
+
47
+ // Append parameters if any
48
+ const queryString = params.toString()
49
+ if (queryString) {
50
+ url += `?${queryString}`
51
+ }
52
+
53
+ return url
54
+ } catch (error) {
55
+ console.error("Error generating Gravatar URL:", error)
56
+ return null
57
+ }
58
+ }