better-auth-ui-svelte 0.2.3 → 0.3.6

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/README.md +25 -4
  2. package/dist/auth-client.d.ts +8 -0
  3. package/dist/auth-client.js +3 -2
  4. package/dist/components/account/account-view.svelte +8 -8
  5. package/dist/components/account/account-view.svelte.d.ts +1 -3
  6. package/dist/components/auth/auth-form.svelte +1 -4
  7. package/dist/components/auth/auth-view.svelte +8 -2
  8. package/dist/components/auth/forms/email-otp-form.svelte +0 -2
  9. package/dist/components/auth/forms/email-otp-form.svelte.d.ts +0 -1
  10. package/dist/components/auth/forms/forgot-password-form.svelte +2 -2
  11. package/dist/components/auth/forms/magic-link-form.svelte +17 -4
  12. package/dist/components/auth/forms/sign-in-form.svelte +2 -2
  13. package/dist/components/auth/forms/sign-up-form.svelte +50 -25
  14. package/dist/components/auth/forms/two-factor-form.svelte +1 -5
  15. package/dist/components/auth/magic-link-button.svelte +1 -1
  16. package/dist/components/auth/magic-link-sent.svelte +204 -0
  17. package/dist/components/auth/magic-link-sent.svelte.d.ts +18 -0
  18. package/dist/components/auth/verify-email.svelte +319 -0
  19. package/dist/components/auth/verify-email.svelte.d.ts +18 -0
  20. package/dist/components/auth-ui-provider.svelte +55 -8
  21. package/dist/components/auth-ui-provider.svelte.d.ts +29 -5
  22. package/dist/components/captcha/captcha.svelte +1 -3
  23. package/dist/components/captcha/captcha.svelte.d.ts +1 -1
  24. package/dist/components/captcha/recaptcha-v2.svelte +2 -7
  25. package/dist/components/captcha/recaptcha-v2.svelte.d.ts +1 -1
  26. package/dist/components/captcha/recaptcha-v3.svelte +5 -6
  27. package/dist/components/default-link.svelte +2 -1
  28. package/dist/components/organization/accept-invitation-card.svelte +1 -1
  29. package/dist/components/organization/accept-invitation-card.svelte.d.ts +1 -2
  30. package/dist/components/organization/create-organization-dialog.svelte +0 -2
  31. package/dist/components/organization/create-organization-dialog.svelte.d.ts +0 -1
  32. package/dist/components/organization/delete-organization-card.svelte +1 -1
  33. package/dist/components/organization/delete-organization-card.svelte.d.ts +1 -3
  34. package/dist/components/organization/delete-organization-dialog.svelte +2 -2
  35. package/dist/components/organization/delete-organization-form.svelte +1 -1
  36. package/dist/components/organization/invitation-cell.svelte +18 -14
  37. package/dist/components/organization/invite-member-dialog.svelte +1 -7
  38. package/dist/components/organization/invite-member-dialog.svelte.d.ts +0 -1
  39. package/dist/components/organization/leave-organization-dialog.svelte +1 -1
  40. package/dist/components/organization/member-cell.svelte +4 -2
  41. package/dist/components/organization/organization-invitations-card.svelte +1 -1
  42. package/dist/components/organization/organization-invitations-card.svelte.d.ts +1 -3
  43. package/dist/components/organization/organization-logo-card.svelte +1 -1
  44. package/dist/components/organization/organization-logo-card.svelte.d.ts +1 -3
  45. package/dist/components/organization/organization-logo-form.svelte +2 -2
  46. package/dist/components/organization/organization-logo-form.svelte.d.ts +1 -3
  47. package/dist/components/organization/organization-members-card.svelte +3 -3
  48. package/dist/components/organization/organization-members-card.svelte.d.ts +1 -3
  49. package/dist/components/organization/organization-name-card.svelte +1 -1
  50. package/dist/components/organization/organization-name-card.svelte.d.ts +1 -3
  51. package/dist/components/organization/organization-name-form.svelte +2 -2
  52. package/dist/components/organization/organization-name-form.svelte.d.ts +1 -3
  53. package/dist/components/organization/organization-settings-cards.svelte +1 -1
  54. package/dist/components/organization/organization-settings-cards.svelte.d.ts +1 -3
  55. package/dist/components/organization/organization-slug-card.svelte +1 -1
  56. package/dist/components/organization/organization-slug-card.svelte.d.ts +1 -3
  57. package/dist/components/organization/organization-slug-form.svelte +2 -2
  58. package/dist/components/organization/organization-slug-form.svelte.d.ts +1 -3
  59. package/dist/components/organization/organization-switcher.svelte +1 -1
  60. package/dist/components/organization/organization-view.svelte +6 -4
  61. package/dist/components/organization/organization-view.svelte.d.ts +1 -3
  62. package/dist/components/organization/organizations-card.svelte +1 -1
  63. package/dist/components/organization/organizations-card.svelte.d.ts +1 -3
  64. package/dist/components/organization/remove-member-dialog.svelte +0 -2
  65. package/dist/components/organization/remove-member-dialog.svelte.d.ts +0 -1
  66. package/dist/components/organization/update-member-role-dialog.svelte +1 -3
  67. package/dist/components/organization/user-invitations-card.svelte +1 -1
  68. package/dist/components/organization/user-invitations-card.svelte.d.ts +1 -3
  69. package/dist/components/organization-refetcher.svelte +8 -3
  70. package/dist/components/settings/account/account-cell.svelte +18 -18
  71. package/dist/components/settings/account/account-settings-cards.svelte +1 -3
  72. package/dist/components/settings/account/account-settings-cards.svelte.d.ts +1 -3
  73. package/dist/components/settings/account/accounts-card.svelte +1 -1
  74. package/dist/components/settings/account/accounts-card.svelte.d.ts +1 -3
  75. package/dist/components/settings/account/delete-account-card.svelte +1 -1
  76. package/dist/components/settings/account/delete-account-card.svelte.d.ts +1 -3
  77. package/dist/components/settings/account/delete-account-dialog.svelte +0 -9
  78. package/dist/components/settings/account/update-avatar-card.svelte +1 -1
  79. package/dist/components/settings/account/update-avatar-card.svelte.d.ts +1 -3
  80. package/dist/components/settings/account/update-field-card.svelte +4 -4
  81. package/dist/components/settings/account/update-field-card.svelte.d.ts +1 -3
  82. package/dist/components/settings/account/update-name-card.svelte +1 -1
  83. package/dist/components/settings/account/update-name-card.svelte.d.ts +1 -3
  84. package/dist/components/settings/account/update-username-card.svelte +1 -1
  85. package/dist/components/settings/account/update-username-card.svelte.d.ts +1 -3
  86. package/dist/components/settings/api-key/api-key-cell.svelte +11 -20
  87. package/dist/components/settings/api-key/api-key-cell.svelte.d.ts +1 -3
  88. package/dist/components/settings/api-key/api-key-delete-dialog.svelte +3 -2
  89. package/dist/components/settings/api-key/api-key-display-dialog.svelte +1 -1
  90. package/dist/components/settings/api-key/api-keys-card.svelte +7 -10
  91. package/dist/components/settings/api-key/api-keys-card.svelte.d.ts +1 -3
  92. package/dist/components/settings/api-key/create-api-key-dialog.svelte +32 -33
  93. package/dist/components/settings/api-key/create-api-key-dialog.svelte.d.ts +0 -1
  94. package/dist/components/settings/passkey/passkey-cell.svelte +2 -8
  95. package/dist/components/settings/passkey/passkey-cell.svelte.d.ts +1 -3
  96. package/dist/components/settings/passkey/passkeys-card.svelte +11 -15
  97. package/dist/components/settings/passkey/passkeys-card.svelte.d.ts +1 -3
  98. package/dist/components/settings/providers/provider-cell.svelte +12 -3
  99. package/dist/components/settings/providers/providers-card.svelte +3 -10
  100. package/dist/components/settings/providers/providers-card.svelte.d.ts +1 -3
  101. package/dist/components/settings/security/change-email-card.svelte +13 -6
  102. package/dist/components/settings/security/change-email-card.svelte.d.ts +1 -3
  103. package/dist/components/settings/security/change-password-card.svelte +2 -4
  104. package/dist/components/settings/security/change-password-card.svelte.d.ts +1 -3
  105. package/dist/components/settings/security/session-cell.svelte +3 -3
  106. package/dist/components/settings/security/session-cell.svelte.d.ts +1 -3
  107. package/dist/components/settings/security/sessions-card.svelte +3 -10
  108. package/dist/components/settings/security/sessions-card.svelte.d.ts +1 -3
  109. package/dist/components/settings/security-settings-cards.svelte +4 -12
  110. package/dist/components/settings/security-settings-cards.svelte.d.ts +1 -3
  111. package/dist/components/settings/shared/session-freshness-dialog.svelte +1 -1
  112. package/dist/components/settings/shared/session-freshness-dialog.svelte.d.ts +1 -3
  113. package/dist/components/settings/shared/settings-card-footer.svelte +0 -1
  114. package/dist/components/settings/shared/settings-card-footer.svelte.d.ts +0 -1
  115. package/dist/components/settings/shared/settings-card.svelte +0 -5
  116. package/dist/components/settings/shared/settings-card.svelte.d.ts +0 -3
  117. package/dist/components/settings/two-factor/backup-codes-dialog.svelte +1 -4
  118. package/dist/components/settings/two-factor/two-factor-card.svelte +10 -4
  119. package/dist/components/settings/two-factor/two-factor-card.svelte.d.ts +1 -3
  120. package/dist/components/ui/button/button.svelte +2 -0
  121. package/dist/components/ui/dropdown-menu/index.d.ts +19 -0
  122. package/dist/components/ui/input-otp/input-otp.svelte +0 -1
  123. package/dist/components/ui/qr-code/qr-code.js +9 -9
  124. package/dist/components/ui/qr-code/qr-code.svelte +1 -0
  125. package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
  126. package/dist/components/user-view.svelte +1 -1
  127. package/dist/config/auth-config.js +3 -2
  128. package/dist/context/auth-ui-config.svelte.d.ts +21 -0
  129. package/dist/hooks/use-authenticate.svelte.d.ts +1 -1
  130. package/dist/hooks/use-captcha.svelte.js +2 -1
  131. package/dist/index.d.ts +2 -0
  132. package/dist/index.js +2 -0
  133. package/dist/localization/auth-localization.d.ts +42 -0
  134. package/dist/localization/auth-localization.js +46 -2
  135. package/dist/localization/stripe-localization.js +1 -1
  136. package/dist/server/auth.d.ts +2653 -10870
  137. package/dist/server/auth.js +39 -4
  138. package/dist/server/db/index.d.ts +1 -1
  139. package/dist/server/db/schema.d.ts +115 -115
  140. package/dist/social-providers.d.ts +7 -0
  141. package/dist/types/any-auth-client.d.ts +1 -1
  142. package/dist/types/index.d.ts +24 -2
  143. package/dist/utils/view-paths.d.ts +4 -0
  144. package/dist/utils/view-paths.js +5 -1
  145. package/package.json +17 -8
package/README.md CHANGED
@@ -14,6 +14,12 @@ This is a **complete Svelte 5 port** of the [Better Auth UI React library](https
14
14
 
15
15
  > **Important:** This library is in early stage development. While we have achieved full feature parity with the React version and all components have been ported, the library has not been battle-tested in production environments yet. Issues may arise. **Use at your own risk until we reach v1.0 stable.**
16
16
 
17
+ ## About This Port
18
+
19
+ This Svelte port was primarily built to support my own projects including [stacksee.com](https://stacksee.com) and [textatlas.com](https://textatlas.com). While it currently maintains full feature parity with the original React library, **this port may evolve independently over time**. I may add new features, make different architectural decisions, or implement functionality specifically tailored to my project needs that diverge from the original library.
20
+
21
+ If you're looking for a library that strictly mirrors the React version, please be aware that this port's API and features may change to better serve the needs of my projects and the Svelte ecosystem.
22
+
17
23
  ## Why Choose Better Auth UI?
18
24
 
19
25
  - **Easy** – Plug & play authentication components
@@ -34,12 +40,16 @@ This is a **complete Svelte 5 port** of the [Better Auth UI React library](https
34
40
 
35
41
  ## Installation
36
42
 
37
- > **Note:** The package is not yet published to NPM. For now, you can clone the repository and link it locally.
43
+ Ensure you have Better Auth and shadcn set up in your project first.
38
44
 
39
- Once published, install the package using your preferred package manager:
45
+ Them install the package:
40
46
 
41
47
  ```bash
42
- pnpm install better-auth-ui-svelte better-auth zod svelte-sonner
48
+ pnpm add better-auth-ui-svelte
49
+
50
+ ## bun add better-auth-ui-svelte
51
+ ## npm install better-auth-ui-svelte
52
+ ## yarn add better-auth-ui-svelte
43
53
  ```
44
54
 
45
55
  ### Peer Dependencies
@@ -345,7 +355,18 @@ interface AuthUIProviderProps {
345
355
  };
346
356
 
347
357
  // Additional auth methods
348
- magicLink?: boolean;
358
+ magicLink?:
359
+ | boolean
360
+ | {
361
+ resendCooldown?: number;
362
+ redirectToSentPage?: boolean;
363
+ };
364
+ emailVerification?:
365
+ | boolean
366
+ | {
367
+ resendCooldown?: number;
368
+ redirectToVerifyPage?: boolean;
369
+ };
349
370
  passkey?: boolean;
350
371
 
351
372
  // Two-factor authentication
@@ -0,0 +1,8 @@
1
+ import { createAuthClient, type InferSessionFromClient } from 'better-auth/svelte';
2
+ import type { Atom } from 'nanostores';
3
+ /**
4
+ * Better Auth Svelte client
5
+ * This provides reactive stores for authentication state
6
+ */
7
+ export declare const authClient: ReturnType<typeof createAuthClient>;
8
+ export declare const useSession: () => Atom<InferSessionFromClient<typeof authClient>>;
@@ -1,5 +1,6 @@
1
1
  import { createAuthClient } from 'better-auth/svelte';
2
- import { organizationClient, apiKeyClient, twoFactorClient, usernameClient, magicLinkClient, emailOTPClient, lastLoginMethodClient, oneTapClient, genericOAuthClient, anonymousClient, multiSessionClient, passkeyClient } from 'better-auth/client/plugins';
2
+ import { organizationClient, apiKeyClient, twoFactorClient, usernameClient, magicLinkClient, emailOTPClient, lastLoginMethodClient, oneTapClient, genericOAuthClient, anonymousClient, multiSessionClient } from 'better-auth/client/plugins';
3
+ import { passkeyClient } from '@better-auth/passkey/client';
3
4
  /**
4
5
  * Better Auth Svelte client
5
6
  * This provides reactive stores for authentication state
@@ -23,4 +24,4 @@ export const authClient = createAuthClient({
23
24
  ]
24
25
  });
25
26
  // Export convenience methods
26
- export const { useSession } = authClient;
27
+ export const useSession = authClient.useSession;
@@ -34,7 +34,7 @@
34
34
  import { Label } from '../ui/label/index.js';
35
35
  import { Menu } from '@lucide/svelte';
36
36
 
37
- interface Props extends AccountViewProps {}
37
+ type Props = AccountViewProps;
38
38
 
39
39
  let {
40
40
  className,
@@ -101,9 +101,7 @@
101
101
  {#if !accountOptions}
102
102
  <!-- Return nothing if account options not configured -->
103
103
  {:else}
104
- <div
105
- class={cn('flex w-full grow flex-col gap-4 md:flex-row md:gap-12', className, classNames?.base)}
106
- >
104
+ <div class={cn('flex w-full flex-col gap-4 md:flex-row md:gap-12', className, classNames?.base)}>
107
105
  {#if !hideNav}
108
106
  <!-- Mobile Navigation (Drawer) -->
109
107
  <div class="flex justify-between gap-2 md:hidden">
@@ -113,9 +111,11 @@
113
111
 
114
112
  <Drawer.Root bind:open={drawerOpen}>
115
113
  <Drawer.Trigger>
116
- <Button variant="outline">
117
- <Menu />
118
- </Button>
114
+ {#snippet child({ props })}
115
+ <Button {...props} variant="outline">
116
+ <Menu />
117
+ </Button>
118
+ {/snippet}
119
119
  </Drawer.Trigger>
120
120
  <Drawer.Content>
121
121
  <Drawer.Header>
@@ -180,7 +180,7 @@
180
180
  {:else if view === 'SECURITY'}
181
181
  <SecuritySettingsCards {classNames} {localization} />
182
182
  {:else if view === 'API_KEYS'}
183
- <ApiKeysCard classNames={classNames?.card} {localization} /> -->
183
+ <ApiKeysCard classNames={classNames?.card} {localization} />
184
184
  {:else if view === 'ORGANIZATIONS' && organization}
185
185
  <div class="grid w-full gap-4 md:gap-6">
186
186
  <OrganizationsCard classNames={classNames?.card} {localization} />
@@ -22,8 +22,6 @@ export interface AccountViewProps {
22
22
  view?: AccountViewPath;
23
23
  hideNav?: boolean;
24
24
  }
25
- interface Props extends AccountViewProps {
26
- }
27
- declare const AccountView: import("svelte").Component<Props, {}, "">;
25
+ declare const AccountView: import("svelte").Component<AccountViewProps, {}, "">;
28
26
  type AccountView = ReturnType<typeof AccountView>;
29
27
  export default AccountView;
@@ -64,15 +64,12 @@
64
64
  const config = getAuthUIConfig();
65
65
 
66
66
  const {
67
- basePath,
68
67
  credentials,
69
68
  localization: contextLocalization,
70
69
  magicLink,
71
70
  emailOTP,
72
71
  signUp,
73
- twoFactor: twoFactorEnabled,
74
- viewPaths,
75
- replace
72
+ viewPaths
76
73
  } = config;
77
74
 
78
75
  const signUpEnabled = !!signUp;
@@ -17,6 +17,8 @@
17
17
  import OneTap from './one-tap.svelte';
18
18
  import AuthCallback from './auth-callback.svelte';
19
19
  import SignOut from './sign-out.svelte';
20
+ import VerifyEmail from './verify-email.svelte';
21
+ import MagicLinkSent from './magic-link-sent.svelte';
20
22
  import type { AuthViewPath } from '../../utils/view-paths.js';
21
23
  import type { AuthLocalization } from '../../localization/auth-localization.js';
22
24
  import AcceptInvitationCard from '../organization/accept-invitation-card.svelte';
@@ -128,6 +130,10 @@
128
130
  <AuthCallback {redirectTo} />
129
131
  {:else if view === 'SIGN_OUT'}
130
132
  <SignOut />
133
+ {:else if view === 'VERIFY_EMAIL'}
134
+ <VerifyEmail {className} {classNames} {localization} />
135
+ {:else if view === 'MAGIC_LINK_SENT'}
136
+ <MagicLinkSent {className} {classNames} {localization} />
131
137
  {:else if view === 'ACCEPT_INVITATION'}
132
138
  <AcceptInvitationCard {localization} />
133
139
  {:else}
@@ -201,7 +207,7 @@
201
207
  socialLayout === 'grid' && 'grid grid-cols-2'
202
208
  )}
203
209
  >
204
- {#each social?.providers || [] as providerName}
210
+ {#each social?.providers || [] as providerName (providerName)}
205
211
  {@const provider = socialProviders.find((p) => p.provider === providerName)}
206
212
  {#if provider}
207
213
  <ProviderButton
@@ -217,7 +223,7 @@
217
223
  {/if}
218
224
  {/each}
219
225
 
220
- {#each genericOAuth?.providers || [] as provider}
226
+ {#each genericOAuth?.providers || [] as provider (provider.providerId)}
221
227
  <ProviderButton
222
228
  {classNames}
223
229
  {callbackURL}
@@ -17,7 +17,6 @@
17
17
  interface Props {
18
18
  className?: string;
19
19
  classNames?: AuthFormClassNames;
20
- callbackURL?: string;
21
20
  isSubmitting?: boolean;
22
21
  localization?: Partial<AuthLocalization>;
23
22
  otpSeparators?: 0 | 1 | 2;
@@ -28,7 +27,6 @@
28
27
  let {
29
28
  className,
30
29
  classNames,
31
- callbackURL,
32
30
  isSubmitting: isSubmittingProp,
33
31
  localization: localizationProp,
34
32
  otpSeparators = 0,
@@ -3,7 +3,6 @@ import type { AuthFormClassNames } from '../auth-form.svelte';
3
3
  interface Props {
4
4
  className?: string;
5
5
  classNames?: AuthFormClassNames;
6
- callbackURL?: string;
7
6
  isSubmitting?: boolean;
8
7
  localization?: Partial<AuthLocalization>;
9
8
  otpSeparators?: 0 | 1 | 2;
@@ -45,7 +45,7 @@
45
45
  const captchaHook = $derived(useCaptcha({ localization }));
46
46
 
47
47
  // Local state for captcha binding
48
- let captchaRef = $state<any>(null);
48
+ let captchaRef = $state<unknown>(null);
49
49
 
50
50
  // Sync captchaRef with the hook
51
51
  $effect(() => {
@@ -75,7 +75,7 @@
75
75
  const basePath = config.basePath || '/auth';
76
76
  const resetPasswordPath = config.viewPaths.RESET_PASSWORD || 'reset-password';
77
77
 
78
- const fetchOptions: any = {
78
+ const fetchOptions: Record<string, unknown> = {
79
79
  throw: true,
80
80
  headers: await captchaHook.getCaptchaHeaders('/forget-password')
81
81
  };
@@ -5,7 +5,7 @@
5
5
  import { createForm } from '@tanstack/svelte-form';
6
6
  import { useCaptcha } from '../../../hooks/use-captcha.svelte';
7
7
  import { useIsHydrated } from '../../../hooks/use-hydrated.svelte';
8
- import { getAuthUIConfig, getLocalization } from '../../../context/auth-ui-config.svelte';
8
+ import { getAuthUIConfig } from '../../../context/auth-ui-config.svelte';
9
9
  import { cn, getLocalizedError, getSearchParam, getFieldError } from '../../../utils/utils.js';
10
10
  import type { AuthLocalization } from '../../../localization/auth-localization.js';
11
11
  import Captcha from '../../captcha/captcha.svelte';
@@ -41,7 +41,7 @@
41
41
  const { getCaptchaHeaders, resetCaptcha } = captchaHook;
42
42
 
43
43
  // Local state for captcha binding
44
- let captchaRef = $state<any>(null);
44
+ let captchaRef = $state<unknown>(null);
45
45
 
46
46
  // Sync captchaRef with the hook
47
47
  $effect(() => {
@@ -57,7 +57,9 @@
57
57
  localization: contextLocalization,
58
58
  redirectTo: contextRedirectTo,
59
59
  viewPaths,
60
- toast
60
+ toast,
61
+ navigate,
62
+ magicLink: magicLinkConfig
61
63
  } = config;
62
64
 
63
65
  const localization = $derived({ ...contextLocalization, ...localizationProp });
@@ -105,7 +107,18 @@
105
107
  fetchOptions
106
108
  });
107
109
 
108
- toast.success(localization.MAGIC_LINK_EMAIL || 'Magic link sent to your email');
110
+ // Check if we should redirect to the sent page
111
+ const shouldRedirect =
112
+ typeof magicLinkConfig === 'object' && magicLinkConfig.redirectToSentPage;
113
+
114
+ if (shouldRedirect) {
115
+ // Navigate to the magic-link-sent page with the email as a query param
116
+ const magicLinkSentPath = `${basePath}/${viewPaths.MAGIC_LINK_SENT}`;
117
+ navigate(`${magicLinkSentPath}?email=${encodeURIComponent(value.email)}`);
118
+ } else {
119
+ // Show toast notification (original behavior)
120
+ toast.success(localization.MAGIC_LINK_EMAIL || 'Magic link sent to your email');
121
+ }
109
122
 
110
123
  // Reset form
111
124
  form.reset();
@@ -6,7 +6,7 @@
6
6
  import { useCaptcha } from '../../../hooks/use-captcha.svelte';
7
7
  import { useIsHydrated } from '../../../hooks/use-hydrated.svelte';
8
8
  import { useOnSuccessTransition } from '../../../hooks/use-success-transition.svelte';
9
- import { getAuthUIConfig, getLocalization } from '../../../context/auth-ui-config.svelte';
9
+ import { getAuthUIConfig } from '../../../context/auth-ui-config.svelte';
10
10
  import {
11
11
  cn,
12
12
  getLocalizedError,
@@ -51,7 +51,7 @@
51
51
  const { getCaptchaHeaders, resetCaptcha } = captchaHook;
52
52
 
53
53
  // Local state for captcha binding
54
- let captchaRef = $state<any>(null);
54
+ let captchaRef = $state<unknown>(null);
55
55
 
56
56
  // Sync captchaRef with the hook
57
57
  $effect(() => {
@@ -66,6 +66,7 @@
66
66
  const basePath = config.basePath;
67
67
  const baseURL = config.baseURL;
68
68
  const credentials = config.credentials;
69
+ const emailVerification = config.emailVerification;
69
70
  const nameRequired = config.nameRequired;
70
71
  const persistClient = config.persistClient;
71
72
  const contextRedirectTo = config.redirectTo;
@@ -89,7 +90,7 @@
89
90
  const { getCaptchaHeaders, resetCaptcha } = captchaHook;
90
91
 
91
92
  // Local state for captcha binding
92
- let captchaRef = $state<any>(null);
93
+ let captchaRef = $state<unknown>(null);
93
94
 
94
95
  // Sync captchaRef with the hook
95
96
  $effect(() => {
@@ -277,8 +278,7 @@
277
278
 
278
279
  // Sign up function
279
280
  async function signUp(values: z.infer<typeof formSchema>) {
280
- const { email, password, name, username, confirmPassword, image, ...additionalFieldValues } =
281
- values;
281
+ const { email, password, name, username, image, ...additionalFieldValues } = values;
282
282
 
283
283
  try {
284
284
  // Validate additional fields with custom validators if provided
@@ -287,7 +287,7 @@
287
287
  if (!additionalField?.validate) continue;
288
288
 
289
289
  if (typeof value === 'string' && !(await additionalField.validate(value))) {
290
- form.setFieldMeta(field as any, (prev) => ({
290
+ form.setFieldMeta(field as never, (prev) => ({
291
291
  ...prev,
292
292
  errorMap: {
293
293
  ...prev.errorMap,
@@ -299,7 +299,7 @@
299
299
  }
300
300
 
301
301
  // Prepare fetch options with captcha headers
302
- const fetchOptions: any = {
302
+ const fetchOptions: Record<string, unknown> = {
303
303
  throw: true,
304
304
  headers: await getCaptchaHeaders('/sign-up/email')
305
305
  };
@@ -314,13 +314,22 @@
314
314
  additionalParams.image = image;
315
315
  }
316
316
 
317
+ // Determine the callback URL for email verification
318
+ // If redirectToVerifyPage is true, redirect to verify-email page after verification
319
+ let signUpCallbackURL = getCallbackURL();
320
+ if (emailVerification?.redirectToVerifyPage) {
321
+ const origin = baseURL || (typeof window !== 'undefined' ? window.location.origin : '');
322
+ const verifyEmailPath = `${basePath}/${viewPaths.VERIFY_EMAIL}`;
323
+ signUpCallbackURL = `${origin}${verifyEmailPath}?verified=true&email=${encodeURIComponent(email as string)}`;
324
+ }
325
+
317
326
  const data = await authClient.signUp.email({
318
327
  email: email as string,
319
328
  password: password as string,
320
329
  name: (name as string) || '',
321
330
  ...additionalParams,
322
331
  ...additionalFieldValues,
323
- callbackURL: getCallbackURL(),
332
+ callbackURL: signUpCallbackURL,
324
333
  fetchOptions
325
334
  });
326
335
 
@@ -329,9 +338,14 @@
329
338
  // User is signed in immediately
330
339
  await onSuccess();
331
340
  } else {
332
- // Email verification required
333
- navigate(`${basePath}/${viewPaths.SIGN_IN}${window.location.search}`);
334
- toast.success(localization.SIGN_UP_EMAIL!);
341
+ // Email verification required - redirect to verify-email view
342
+ // Note: This handles token-based email verification (default).
343
+ // If the server uses emailOTP with overrideDefaultEmailVerification,
344
+ // users receive OTP codes via email instead of verification links.
345
+ const url = new URL(window.location.href);
346
+ url.searchParams.set('email', email as string); // URLSearchParams handles encoding automatically
347
+ navigate(`${basePath}/${viewPaths.VERIFY_EMAIL}?${url.searchParams.toString()}`);
348
+ // Don't show a toast here since the verify-email view will show all needed info
335
349
  }
336
350
  } catch (error) {
337
351
  toast.error(getLocalizedError({ error, localization }));
@@ -368,7 +382,7 @@
368
382
 
369
383
  // Create validators for additional fields dynamically
370
384
  function getAdditionalFieldValidator(field: string) {
371
- return (formSchema.shape as any)[field];
385
+ return (formSchema.shape as Record<string, z.ZodTypeAny>)[field];
372
386
  }
373
387
 
374
388
  // Combine isSubmitting states
@@ -416,18 +430,29 @@
416
430
  <div class="flex items-center gap-4">
417
431
  <DropdownMenu.Root>
418
432
  <DropdownMenu.Trigger class="size-fit rounded-full">
419
- <UserAvatar
420
- isPending={uploadingAvatar}
421
- className="size-16"
422
- user={avatarImage
423
- ? {
424
- name: form.getFieldValue('name') as string,
425
- email: form.getFieldValue('email') as string,
426
- image: avatarImage
427
- }
428
- : null}
429
- {localization}
430
- />
433
+ {#snippet child({ props })}
434
+ <Button
435
+ {...props}
436
+ class="size-fit rounded-full"
437
+ size="icon"
438
+ type="button"
439
+ variant="ghost"
440
+ disabled={uploadingAvatar}
441
+ >
442
+ <UserAvatar
443
+ isPending={uploadingAvatar}
444
+ className="size-16"
445
+ user={avatarImage
446
+ ? {
447
+ name: form.getFieldValue('name') as string,
448
+ email: form.getFieldValue('email') as string,
449
+ image: avatarImage
450
+ }
451
+ : null}
452
+ {localization}
453
+ />
454
+ </Button>
455
+ {/snippet}
431
456
  </DropdownMenu.Trigger>
432
457
 
433
458
  <DropdownMenu.Content align="start">
@@ -607,11 +632,11 @@
607
632
 
608
633
  <!-- Additional Fields -->
609
634
  {#if signUpFields}
610
- {#each signUpFields.filter((field) => field !== 'name' && field !== 'image') as field}
635
+ {#each signUpFields.filter((field) => field !== 'name' && field !== 'image') as field (field)}
611
636
  {@const additionalField = additionalFields?.[field]}
612
637
  {#if additionalField}
613
638
  <form.Field
614
- name={field as any}
639
+ name={field as never}
615
640
  validators={{ onChange: getAdditionalFieldValidator(field) }}
616
641
  >
617
642
  {#snippet children(fieldState)}
@@ -623,7 +648,7 @@
623
648
  id={field}
624
649
  checked={fieldState.state.value as boolean}
625
650
  onCheckedChange={(checked) => {
626
- fieldState.handleChange(checked as any);
651
+ fieldState.handleChange(checked as never);
627
652
  }}
628
653
  disabled={isSubmitting}
629
654
  />
@@ -6,11 +6,7 @@
6
6
  import SendIcon from '@lucide/svelte/icons/send';
7
7
  import { useIsHydrated } from '../../../hooks/use-hydrated.svelte';
8
8
  import { useOnSuccessTransition } from '../../../hooks/use-success-transition.svelte';
9
- import {
10
- getAuthUIConfig,
11
- getAuthClient,
12
- getLocalization
13
- } from '../../../context/auth-ui-config.svelte';
9
+ import { getAuthUIConfig, getAuthClient } from '../../../context/auth-ui-config.svelte';
14
10
  import { cn, getLocalizedError, getSearchParam, getFieldError } from '../../../utils/utils.js';
15
11
  import type { AuthLocalization } from '../../../localization/auth-localization.js';
16
12
  import type { User } from '../../../types/index.js';
@@ -45,6 +45,6 @@
45
45
  {:else}
46
46
  <Mail class={classNames?.form?.icon} />
47
47
  {/if}
48
- {localization.SIGN_IN_WITH}{' '}
48
+ {localization.SIGN_IN_WITH}
49
49
  {view === 'MAGIC_LINK' ? localization.PASSWORD : localization.MAGIC_LINK}
50
50
  </Button>