better-auth-ui-svelte 0.12.3 → 0.12.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.
@@ -45,9 +45,8 @@
45
45
  let verifiedEmail = $state<string | undefined>(undefined);
46
46
 
47
47
  // Transition for OTP verification success
48
- const { onSuccess, isPending: transitionPending } = useOnSuccessTransition({
49
- redirectTo
50
- });
48
+ const transition = useOnSuccessTransition({ redirectTo });
49
+ const { onSuccess } = transition;
51
50
 
52
51
  // Reactive validation schemas
53
52
  const emailFormSchema = $derived(
@@ -106,10 +105,13 @@
106
105
  }
107
106
  }));
108
107
 
108
+ const emailFormIsSubmitting = emailForm.useStore((s) => s.isSubmitting);
109
+ const otpFormIsSubmitting = otpForm.useStore((s) => s.isSubmitting);
110
+
109
111
  // Computed submitting states
110
- const emailFormSubmitting = $derived(isSubmittingProp || emailForm.state.isSubmitting);
112
+ const emailFormSubmitting = $derived(isSubmittingProp || emailFormIsSubmitting.current);
111
113
  const otpFormSubmitting = $derived(
112
- isSubmittingProp || otpForm.state.isSubmitting || transitionPending
114
+ isSubmittingProp || otpFormIsSubmitting.current || transition.isPending
113
115
  );
114
116
 
115
117
  // Handle email form submission with error handling
@@ -137,7 +139,7 @@
137
139
  // Update parent isSubmitting state
138
140
  $effect(() => {
139
141
  setIsSubmitting?.(
140
- emailForm.state.isSubmitting || otpForm.state.isSubmitting || transitionPending
142
+ emailFormIsSubmitting.current || otpFormIsSubmitting.current || transition.isPending
141
143
  );
142
144
  });
143
145
 
@@ -103,10 +103,12 @@
103
103
  }
104
104
  }));
105
105
 
106
+ const formIsSubmitting = form.useStore((s) => s.isSubmitting);
107
+
106
108
  // Sync isSubmitting state
107
109
  $effect(() => {
108
- isSubmitting = form.state.isSubmitting;
109
- setIsSubmitting?.(form.state.isSubmitting);
110
+ isSubmitting = formIsSubmitting.current;
111
+ setIsSubmitting?.(formIsSubmitting.current);
110
112
  });
111
113
  </script>
112
114
 
@@ -130,7 +132,7 @@
130
132
  value={field.state.value}
131
133
  oninput={(e) => field.handleChange(e.currentTarget.value)}
132
134
  onblur={field.handleBlur}
133
- disabled={form.state.isSubmitting}
135
+ disabled={formIsSubmitting.current}
134
136
  class={classNames?.input}
135
137
  />
136
138
 
@@ -147,10 +149,10 @@
147
149
 
148
150
  <Button
149
151
  type="submit"
150
- disabled={form.state.isSubmitting}
152
+ disabled={formIsSubmitting.current}
151
153
  class={cn('w-full', classNames?.button, classNames?.primaryButton)}
152
154
  >
153
- {#if form.state.isSubmitting}
155
+ {#if formIsSubmitting.current}
154
156
  <Loader2 class="animate-spin" />
155
157
  {:else}
156
158
  {localization.FORGOT_PASSWORD_ACTION}
@@ -129,11 +129,12 @@
129
129
  }
130
130
  }));
131
131
 
132
- const isSubmitting = $derived(isSubmittingProp || form.state.isSubmitting);
132
+ const formIsSubmitting = form.useStore((s) => s.isSubmitting);
133
+ const isSubmitting = $derived(isSubmittingProp || formIsSubmitting.current);
133
134
 
134
135
  // Update parent isSubmitting state
135
136
  $effect(() => {
136
- setIsSubmitting?.(form.state.isSubmitting);
137
+ setIsSubmitting?.(formIsSubmitting.current);
137
138
  });
138
139
  </script>
139
140
 
@@ -41,9 +41,8 @@
41
41
  // Merge localization from context and props
42
42
  const localization = { ...contextLocalization, ...localizationProp };
43
43
 
44
- const { onSuccess, isPending: transitionPending } = useOnSuccessTransition({
45
- redirectTo
46
- });
44
+ const transition = useOnSuccessTransition({ redirectTo });
45
+ const { onSuccess } = transition;
47
46
 
48
47
  const schema = z.object({
49
48
  code: z.string().min(1, { message: localization.BACKUP_CODE_REQUIRED })
@@ -71,11 +70,12 @@
71
70
  }));
72
71
 
73
72
  // Compute final isSubmitting state
74
- const isSubmitting = $derived(isSubmittingProp ?? (form.state.isSubmitting || transitionPending));
73
+ const formIsSubmitting = form.useStore((s) => s.isSubmitting);
74
+ const isSubmitting = $derived(isSubmittingProp ?? (formIsSubmitting.current || transition.isPending));
75
75
 
76
76
  // Sync isSubmitting state
77
77
  $effect(() => {
78
- setIsSubmitting?.(form.state.isSubmitting || transitionPending);
78
+ setIsSubmitting?.(formIsSubmitting.current || transition.isPending);
79
79
  });
80
80
  </script>
81
81
 
@@ -125,6 +125,8 @@
125
125
  }
126
126
  }
127
127
  }));
128
+
129
+ const formIsSubmitting = form.useStore((s) => s.isSubmitting);
128
130
  </script>
129
131
 
130
132
  <form
@@ -147,7 +149,7 @@
147
149
  value={field.state.value}
148
150
  oninput={(e) => field.handleChange(e.currentTarget.value)}
149
151
  onblur={field.handleBlur}
150
- disabled={form.state.isSubmitting}
152
+ disabled={formIsSubmitting.current}
151
153
  class={classNames?.input}
152
154
  />
153
155
  {#if field.state.meta.errors.length > 0}
@@ -183,7 +185,7 @@
183
185
  value={field.state.value}
184
186
  oninput={(e) => field.handleChange(e.currentTarget.value)}
185
187
  onblur={field.handleBlur}
186
- disabled={form.state.isSubmitting}
188
+ disabled={formIsSubmitting.current}
187
189
  class={classNames?.input}
188
190
  />
189
191
  {#if field.state.meta.errors.length > 0}
@@ -198,10 +200,10 @@
198
200
 
199
201
  <Button
200
202
  type="submit"
201
- disabled={form.state.isSubmitting}
203
+ disabled={formIsSubmitting.current}
202
204
  class={cn('w-full', classNames?.button, classNames?.primaryButton)}
203
205
  >
204
- {#if form.state.isSubmitting}
206
+ {#if formIsSubmitting.current}
205
207
  <Loader2 class="animate-spin" />
206
208
  {:else}
207
209
  {loc.RESET_PASSWORD_ACTION}
@@ -79,9 +79,8 @@
79
79
  captchaHook.captchaRef = captchaRef;
80
80
  });
81
81
 
82
- const { onSuccess, isPending: transitionPending } = useOnSuccessTransition({
83
- redirectTo
84
- });
82
+ const transition = useOnSuccessTransition({ redirectTo });
83
+ const { onSuccess } = transition;
85
84
 
86
85
  // Form schema
87
86
  const formSchema = $derived(
@@ -162,11 +161,12 @@
162
161
  }
163
162
  }));
164
163
 
165
- const isSubmitting = $derived(isSubmittingProp || form.state.isSubmitting || transitionPending);
164
+ const formIsSubmitting = form.useStore((s) => s.isSubmitting);
165
+ const isSubmitting = $derived(isSubmittingProp || formIsSubmitting.current || transition.isPending);
166
166
 
167
167
  // Update parent isSubmitting state
168
168
  $effect(() => {
169
- setIsSubmitting?.(form.state.isSubmitting || transitionPending);
169
+ setIsSubmitting?.(formIsSubmitting.current || transition.isPending);
170
170
  });
171
171
  </script>
172
172
 
@@ -98,9 +98,8 @@
98
98
  });
99
99
 
100
100
  // Success transition for navigation
101
- const { onSuccess, isPending: transitionPending } = useOnSuccessTransition({
102
- redirectTo
103
- });
101
+ const transition = useOnSuccessTransition({ redirectTo });
102
+ const { onSuccess } = transition;
104
103
 
105
104
  // Helper functions
106
105
  function getRedirectTo() {
@@ -391,7 +390,8 @@
391
390
  }
392
391
 
393
392
  // Combine isSubmitting states
394
- const isSubmitting = $derived(form.state.isSubmitting || transitionPending);
393
+ const formIsSubmitting = form.useStore((s) => s.isSubmitting);
394
+ const isSubmitting = $derived(formIsSubmitting.current || transition.isPending);
395
395
 
396
396
  // Sync external isSubmitting
397
397
  $effect(() => {
@@ -56,9 +56,8 @@
56
56
 
57
57
  const localization = $derived({ ...contextLocalization, ...localizationProp });
58
58
 
59
- const { onSuccess, isPending: transitionPending } = useOnSuccessTransition({
60
- redirectTo
61
- });
59
+ const transition = useOnSuccessTransition({ redirectTo });
60
+ const { onSuccess } = transition;
62
61
 
63
62
  // Get session data to check if 2FA is being enabled
64
63
  const sessionQuery = hooks?.useSession?.();
@@ -128,12 +127,13 @@
128
127
  }
129
128
  }));
130
129
 
131
- const isSubmitting = $derived(isSubmittingProp || form.state.isSubmitting || transitionPending);
130
+ const formIsSubmitting = form.useStore((s) => s.isSubmitting);
131
+ const isSubmitting = $derived(isSubmittingProp || formIsSubmitting.current || transition.isPending);
132
132
 
133
133
  // Update parent isSubmitting state
134
134
  $effect(() => {
135
135
  if (setIsSubmitting) {
136
- setIsSubmitting(form.state.isSubmitting || transitionPending);
136
+ setIsSubmitting(formIsSubmitting.current || transition.isPending);
137
137
  }
138
138
  });
139
139
 
@@ -5,6 +5,7 @@
5
5
  import * as Card from '../ui/card/index.js';
6
6
  import { Button } from '../ui/button/index.js';
7
7
  import type { AuthLocalization } from '../../localization/auth-localization.js';
8
+ import Loader2 from '@lucide/svelte/icons/loader-2';
8
9
  import MailOpen from '@lucide/svelte/icons/mail-open';
9
10
 
10
11
  interface Props {
@@ -186,10 +187,10 @@
186
187
  onclick={handleResendMagicLink}
187
188
  class="w-full"
188
189
  >
189
- {#if countdown > 0}
190
+ {#if isResending}
191
+ <Loader2 class="animate-spin" />
192
+ {:else if countdown > 0}
190
193
  {localization.RESEND_MAGIC_LINK} ({countdown}s)
191
- {:else if isResending}
192
- {localization.RESEND_MAGIC_LINK}...
193
194
  {:else}
194
195
  {localization.RESEND_MAGIC_LINK}
195
196
  {/if}
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import Fingerprint from '@lucide/svelte/icons/fingerprint';
3
+ import Loader2 from '@lucide/svelte/icons/loader-2';
3
4
  import {
4
5
  getAuthClient,
5
6
  getAuthUIConfig,
@@ -31,8 +32,11 @@
31
32
 
32
33
  const { onSuccess } = useOnSuccessTransition({ redirectTo });
33
34
 
35
+ let isPending = $state(false);
36
+
34
37
  async function signInPasskey() {
35
38
  setIsSubmitting?.(true);
39
+ isPending = true;
36
40
 
37
41
  try {
38
42
  const response = await authClient.signIn.passkey({
@@ -48,6 +52,7 @@
48
52
  );
49
53
 
50
54
  setIsSubmitting?.(false);
55
+ isPending = false;
51
56
  } else {
52
57
  await onSuccess();
53
58
  }
@@ -55,6 +60,7 @@
55
60
  config.toast.error(getLocalizedError({ error, localization: loc }));
56
61
 
57
62
  setIsSubmitting?.(false);
63
+ isPending = false;
58
64
  }
59
65
  }
60
66
  </script>
@@ -67,7 +73,11 @@
67
73
  variant="secondary"
68
74
  onclick={signInPasskey}
69
75
  >
70
- <Fingerprint class={classNames?.form?.icon} />
71
- {loc.SIGN_IN_WITH}
72
- {loc.PASSKEY}
76
+ {#if isPending}
77
+ <Loader2 class="animate-spin" />
78
+ {:else}
79
+ <Fingerprint class={classNames?.form?.icon} />
80
+ {loc.SIGN_IN_WITH}
81
+ {loc.PASSKEY}
82
+ {/if}
73
83
  </Button>
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import Loader2 from '@lucide/svelte/icons/loader-2';
2
3
  import { Button } from '../ui/button/index.js';
3
4
  import { getAuthClient, getAuthUIConfig } from '../../context/auth-ui-config.svelte';
4
5
  import { cn } from '../../utils/ui.js';
@@ -37,6 +38,8 @@
37
38
  const authClient = getAuthClient();
38
39
  const config = getAuthUIConfig();
39
40
 
41
+ let isPending = $state(false);
42
+
40
43
  function getRedirectTo() {
41
44
  return redirectToProp || getSearchParam('redirectTo') || config.redirectTo;
42
45
  }
@@ -53,6 +56,7 @@
53
56
 
54
57
  async function doSignInSocial() {
55
58
  setIsSubmitting(true);
59
+ isPending = true;
56
60
 
57
61
  try {
58
62
  if (other) {
@@ -94,6 +98,7 @@
94
98
  config.toast.error(getLocalizedError({ error, localization }));
95
99
 
96
100
  setIsSubmitting(false);
101
+ isPending = false;
97
102
  }
98
103
  }
99
104
  </script>
@@ -110,14 +115,18 @@
110
115
  variant="outline"
111
116
  onclick={doSignInSocial}
112
117
  >
113
- {#if provider.icon}
114
- <provider.icon class={classNames?.form?.icon} />
115
- {/if}
118
+ {#if isPending}
119
+ <Loader2 class="animate-spin" />
120
+ {:else}
121
+ {#if provider.icon}
122
+ <provider.icon class={classNames?.form?.icon} />
123
+ {/if}
116
124
 
117
- {#if socialLayout === 'grid'}
118
- {provider.name}
119
- {/if}
120
- {#if socialLayout === 'vertical'}
121
- {localization.SIGN_IN_WITH} {provider.name}
125
+ {#if socialLayout === 'grid'}
126
+ {provider.name}
127
+ {/if}
128
+ {#if socialLayout === 'vertical'}
129
+ {localization.SIGN_IN_WITH} {provider.name}
130
+ {/if}
122
131
  {/if}
123
132
  </Button>
@@ -5,6 +5,7 @@
5
5
  import * as Card from '../ui/card/index.js';
6
6
  import { Button } from '../ui/button/index.js';
7
7
  import type { AuthLocalization } from '../../localization/auth-localization.js';
8
+ import Loader2 from '@lucide/svelte/icons/loader-2';
8
9
  import MailOpen from '@lucide/svelte/icons/mail-open';
9
10
  import CheckCircle from '@lucide/svelte/icons/check-circle';
10
11
 
@@ -274,7 +275,7 @@
274
275
 
275
276
  <Button onclick={handleVerifyEmail} disabled={isVerifying} class="w-full">
276
277
  {#if isVerifying}
277
- {localization.VERIFYING || 'Verifying'}...
278
+ <Loader2 class="animate-spin" />
278
279
  {:else}
279
280
  {localization.VERIFY_EMAIL_BUTTON || 'Verify Email'}
280
281
  {/if}
@@ -299,10 +300,10 @@
299
300
  onclick={handleResendEmail}
300
301
  class="w-full"
301
302
  >
302
- {#if countdown > 0}
303
+ {#if isResending}
304
+ <Loader2 class="animate-spin" />
305
+ {:else if countdown > 0}
303
306
  {localization.RESEND_VERIFICATION_EMAIL} ({countdown}s)
304
- {:else if isResending}
305
- {localization.RESEND_VERIFICATION_EMAIL}...
306
307
  {:else}
307
308
  {localization.RESEND_VERIFICATION_EMAIL}
308
309
  {/if}
@@ -157,11 +157,11 @@
157
157
  const organizationRefetching = $derived(currentOrgResult.isRefetching);
158
158
  const organizationRefetch = currentOrgResult.refetch;
159
159
 
160
- t// Whether there are any non-active orgs or personal account rendered above additionalOrganizations
161
- tconst hasItemsAboveAdditional = $derived(
162
- tt(!hidePersonal && !!activeOrganization) ||
163
- tt(organizations ?? []).some((o) => o.id !== activeOrganization?.id)
164
- t);
160
+ // Whether there are any non-active orgs or personal account rendered above additionalOrganizations
161
+ const hasItemsAboveAdditional = $derived(
162
+ (!hidePersonal && !!activeOrganization) ||
163
+ (organizations ?? []).some((o) => o.id !== activeOrganization?.id)
164
+ );
165
165
 
166
166
  // Smarter pending logic: Only show loading if we're truly waiting for data
167
167
  // If we have organizations list, we can show the UI even if active org is still loading
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-auth-ui-svelte",
3
- "version": "0.12.3",
3
+ "version": "0.12.5",
4
4
  "description": "Pre-built authentication UI components for Better Auth in Svelte 5",
5
5
  "scripts": {
6
6
  "dev": "vite dev",