@erikey/react 0.4.36 → 0.5.1
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.
- package/dist/styles.css +144 -1
- package/dist/styles.css.map +1 -1
- package/dist/ui/index.mjs +326 -194
- package/dist/ui/index.mjs.map +1 -1
- package/dist/ui/style.css +28 -0
- package/dist/ui/style.css.map +1 -1
- package/package.json +1 -1
- package/src/ui/components/auth/auth-flow.tsx +118 -6
- package/src/ui/components/auth/auth-form.tsx +6 -0
- package/src/ui/components/auth/auth-view.tsx +6 -0
- package/src/ui/components/auth/forms/email-verification-form.tsx +12 -8
- package/src/ui/components/auth/forms/forgot-password-form.tsx +11 -4
- package/src/ui/components/auth/forms/reset-password-form.tsx +18 -8
- package/src/ui/components/auth/forms/sign-in-form.tsx +16 -9
- package/src/ui/components/auth/forms/sign-up-form.tsx +28 -14
- package/src/ui/lib/auth-ui-provider.tsx +5 -0
- package/src/ui/style.css +32 -1
package/dist/ui/style.css
CHANGED
|
@@ -5,6 +5,34 @@
|
|
|
5
5
|
--team-3: oklch(0.398 0.07 227.392);
|
|
6
6
|
--team-4: oklch(0.828 0.189 84.429);
|
|
7
7
|
--team-5: oklch(0.769 0.188 70.08);
|
|
8
|
+
--card: 0 0% 100%;
|
|
9
|
+
--card-foreground: 240 10% 3.9%;
|
|
10
|
+
--background: 0 0% 100%;
|
|
11
|
+
--foreground: 240 10% 3.9%;
|
|
12
|
+
--muted: 240 4.8% 95.9%;
|
|
13
|
+
--muted-foreground: 240 3.8% 46.1%;
|
|
14
|
+
--border: 240 5.9% 90%;
|
|
15
|
+
--input: 240 5.9% 90%;
|
|
16
|
+
--ring: 240 5.9% 10%;
|
|
17
|
+
--primary: 240 5.9% 10%;
|
|
18
|
+
--primary-foreground: 0 0% 98%;
|
|
19
|
+
--destructive: 0 84.2% 60.2%;
|
|
20
|
+
--destructive-foreground: 0 0% 98%;
|
|
21
|
+
}
|
|
22
|
+
.dark {
|
|
23
|
+
--card: 240 10% 3.9%;
|
|
24
|
+
--card-foreground: 0 0% 98%;
|
|
25
|
+
--background: 240 10% 3.9%;
|
|
26
|
+
--foreground: 0 0% 98%;
|
|
27
|
+
--muted: 240 3.7% 15.9%;
|
|
28
|
+
--muted-foreground: 240 5% 64.9%;
|
|
29
|
+
--border: 240 3.7% 15.9%;
|
|
30
|
+
--input: 240 3.7% 15.9%;
|
|
31
|
+
--ring: 240 4.9% 83.9%;
|
|
32
|
+
--primary: 0 0% 98%;
|
|
33
|
+
--primary-foreground: 240 5.9% 10%;
|
|
34
|
+
--destructive: 0 62.8% 30.6%;
|
|
35
|
+
--destructive-foreground: 0 0% 98%;
|
|
8
36
|
}
|
|
9
37
|
@keyframes enter {
|
|
10
38
|
from {
|
package/dist/ui/style.css.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ui/style.css"],"sourcesContent":["/*
|
|
1
|
+
{"version":3,"sources":["../../src/ui/style.css"],"sourcesContent":["/* Default theme variables - users can override these in their own CSS */\n:root {\n\t--team-1: oklch(0.646 0.222 41.116);\n\t--team-2: oklch(0.6 0.118 184.704);\n\t--team-3: oklch(0.398 0.07 227.392);\n\t--team-4: oklch(0.828 0.189 84.429);\n\t--team-5: oklch(0.769 0.188 70.08);\n\n\t/* Card and UI component default colors */\n\t--card: 0 0% 100%;\n\t--card-foreground: 240 10% 3.9%;\n\t--background: 0 0% 100%;\n\t--foreground: 240 10% 3.9%;\n\t--muted: 240 4.8% 95.9%;\n\t--muted-foreground: 240 3.8% 46.1%;\n\t--border: 240 5.9% 90%;\n\t--input: 240 5.9% 90%;\n\t--ring: 240 5.9% 10%;\n\t--primary: 240 5.9% 10%;\n\t--primary-foreground: 0 0% 98%;\n\t--destructive: 0 84.2% 60.2%;\n\t--destructive-foreground: 0 0% 98%;\n}\n\n.dark {\n\t--card: 240 10% 3.9%;\n\t--card-foreground: 0 0% 98%;\n\t--background: 240 10% 3.9%;\n\t--foreground: 0 0% 98%;\n\t--muted: 240 3.7% 15.9%;\n\t--muted-foreground: 240 5% 64.9%;\n\t--border: 240 3.7% 15.9%;\n\t--input: 240 3.7% 15.9%;\n\t--ring: 240 4.9% 83.9%;\n\t--primary: 0 0% 98%;\n\t--primary-foreground: 240 5.9% 10%;\n\t--destructive: 0 62.8% 30.6%;\n\t--destructive-foreground: 0 0% 98%;\n}\n\n/*\n * Animation CSS for SDK components\n *\n * These styles are bundled with the SDK to ensure they work regardless of\n * consumer's Tailwind configuration. Uses @layer for cascade control so\n * consumers can override with their own Tailwind utilities if needed.\n *\n * This solves the problem where consumer's Tailwind purges animation classes\n * because they only exist in the SDK's minified bundle.\n */\n\n/* Keyframes for enter/exit animations (from tailwindcss-animate) */\n@keyframes enter {\n\tfrom {\n\t\topacity: var(--tw-enter-opacity, 1);\n\t\ttransform: translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0)\n\t\t\tscale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1))\n\t\t\trotate(var(--tw-enter-rotate, 0));\n\t}\n}\n\n@keyframes exit {\n\tto {\n\t\topacity: var(--tw-exit-opacity, 1);\n\t\ttransform: translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0)\n\t\t\tscale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1))\n\t\t\trotate(var(--tw-exit-rotate, 0));\n\t}\n}\n\n/* Animation base classes */\n.animate-in {\n\tanimation-name: enter;\n\tanimation-duration: 150ms;\n\t--tw-enter-opacity: initial;\n\t--tw-enter-scale: initial;\n\t--tw-enter-rotate: initial;\n\t--tw-enter-translate-x: initial;\n\t--tw-enter-translate-y: initial;\n}\n\n.animate-out {\n\tanimation-name: exit;\n\tanimation-duration: 150ms;\n\t--tw-exit-opacity: initial;\n\t--tw-exit-scale: initial;\n\t--tw-exit-rotate: initial;\n\t--tw-exit-translate-x: initial;\n\t--tw-exit-translate-y: initial;\n}\n\n/* Opacity variants */\n.fade-in-0 { --tw-enter-opacity: 0; }\n.fade-out-0 { --tw-exit-opacity: 0; }\n\n/* Scale variants */\n.zoom-in-95 { --tw-enter-scale: 0.95; }\n.zoom-out-95 { --tw-exit-scale: 0.95; }\n\n/* Slide variants */\n.slide-in-from-top-2 { --tw-enter-translate-y: -0.5rem; }\n.slide-in-from-bottom-2 { --tw-enter-translate-y: 0.5rem; }\n.slide-in-from-left-2 { --tw-enter-translate-x: -0.5rem; }\n.slide-in-from-right-2 { --tw-enter-translate-x: 0.5rem; }\n\n/* Data attribute variants for Radix UI components */\n[data-state=\"open\"].animate-in {\n\tanimation-name: enter;\n\tanimation-duration: 150ms;\n}\n\n[data-state=\"closed\"].animate-out {\n\tanimation-name: exit;\n\tanimation-duration: 150ms;\n}\n\n[data-state=\"open\"].fade-in-0,\n.data-\\[state\\=open\\]\\:fade-in-0[data-state=\"open\"] {\n\t--tw-enter-opacity: 0;\n}\n\n[data-state=\"closed\"].fade-out-0,\n.data-\\[state\\=closed\\]\\:fade-out-0[data-state=\"closed\"] {\n\t--tw-exit-opacity: 0;\n}\n\n[data-state=\"open\"].zoom-in-95,\n.data-\\[state\\=open\\]\\:zoom-in-95[data-state=\"open\"] {\n\t--tw-enter-scale: 0.95;\n}\n\n[data-state=\"closed\"].zoom-out-95,\n.data-\\[state\\=closed\\]\\:zoom-out-95[data-state=\"closed\"] {\n\t--tw-exit-scale: 0.95;\n}\n\n/* Slide variants with data-side for dropdown positioning */\n[data-side=\"bottom\"].slide-in-from-top-2,\n.data-\\[side\\=bottom\\]\\:slide-in-from-top-2[data-side=\"bottom\"] {\n\t--tw-enter-translate-y: -0.5rem;\n}\n\n[data-side=\"top\"].slide-in-from-bottom-2,\n.data-\\[side\\=top\\]\\:slide-in-from-bottom-2[data-side=\"top\"] {\n\t--tw-enter-translate-y: 0.5rem;\n}\n\n[data-side=\"left\"].slide-in-from-right-2,\n.data-\\[side\\=left\\]\\:slide-in-from-right-2[data-side=\"left\"] {\n\t--tw-enter-translate-x: 0.5rem;\n}\n\n[data-side=\"right\"].slide-in-from-left-2,\n.data-\\[side\\=right\\]\\:slide-in-from-left-2[data-side=\"right\"] {\n\t--tw-enter-translate-x: -0.5rem;\n}\n\n/* Tailwind's data attribute variant syntax support */\n.data-\\[state\\=open\\]\\:animate-in[data-state=\"open\"] {\n\tanimation-name: enter;\n\tanimation-duration: 150ms;\n\t--tw-enter-opacity: initial;\n\t--tw-enter-scale: initial;\n\t--tw-enter-rotate: initial;\n\t--tw-enter-translate-x: initial;\n\t--tw-enter-translate-y: initial;\n}\n\n.data-\\[state\\=closed\\]\\:animate-out[data-state=\"closed\"] {\n\tanimation-name: exit;\n\tanimation-duration: 150ms;\n\t--tw-exit-opacity: initial;\n\t--tw-exit-scale: initial;\n\t--tw-exit-rotate: initial;\n\t--tw-exit-translate-x: initial;\n\t--tw-exit-translate-y: initial;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -177,6 +177,46 @@ function isAuthPath(href: string): boolean {
|
|
|
177
177
|
)
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
// Session storage key for persisting view state across remounts
|
|
181
|
+
const AUTH_FLOW_STATE_KEY = "erikey_auth_flow_state"
|
|
182
|
+
|
|
183
|
+
function getPersistedState(): {
|
|
184
|
+
view: AuthFlowView
|
|
185
|
+
email?: string
|
|
186
|
+
} | null {
|
|
187
|
+
if (typeof window === "undefined") return null
|
|
188
|
+
try {
|
|
189
|
+
const stored = sessionStorage.getItem(AUTH_FLOW_STATE_KEY)
|
|
190
|
+
if (stored) {
|
|
191
|
+
return JSON.parse(stored)
|
|
192
|
+
}
|
|
193
|
+
} catch {
|
|
194
|
+
// Ignore parse errors
|
|
195
|
+
}
|
|
196
|
+
return null
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function persistState(view: AuthFlowView, email?: string) {
|
|
200
|
+
if (typeof window === "undefined") return
|
|
201
|
+
try {
|
|
202
|
+
sessionStorage.setItem(
|
|
203
|
+
AUTH_FLOW_STATE_KEY,
|
|
204
|
+
JSON.stringify({ view, email })
|
|
205
|
+
)
|
|
206
|
+
} catch {
|
|
207
|
+
// Ignore storage errors
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function clearPersistedState() {
|
|
212
|
+
if (typeof window === "undefined") return
|
|
213
|
+
try {
|
|
214
|
+
sessionStorage.removeItem(AUTH_FLOW_STATE_KEY)
|
|
215
|
+
} catch {
|
|
216
|
+
// Ignore storage errors
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
180
220
|
export function AuthFlow({
|
|
181
221
|
mode = "internal",
|
|
182
222
|
onEvent,
|
|
@@ -196,8 +236,23 @@ export function AuthFlow({
|
|
|
196
236
|
...providerProps
|
|
197
237
|
}: AuthFlowProps) {
|
|
198
238
|
// Internal state for "internal" mode
|
|
199
|
-
|
|
200
|
-
const [
|
|
239
|
+
// Restore from sessionStorage if available (survives remounts caused by parent re-renders)
|
|
240
|
+
const [currentView, setCurrentView] = useState<AuthFlowView>(() => {
|
|
241
|
+
if (mode === "internal") {
|
|
242
|
+
const persisted = getPersistedState()
|
|
243
|
+
if (persisted) {
|
|
244
|
+
return persisted.view
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return initialView
|
|
248
|
+
})
|
|
249
|
+
const [verifyEmail, setVerifyEmail] = useState<string | undefined>(() => {
|
|
250
|
+
if (mode === "internal") {
|
|
251
|
+
const persisted = getPersistedState()
|
|
252
|
+
return persisted?.email
|
|
253
|
+
}
|
|
254
|
+
return undefined
|
|
255
|
+
})
|
|
201
256
|
|
|
202
257
|
// Handle navigation based on mode
|
|
203
258
|
const handleNavigate = useCallback(
|
|
@@ -291,16 +346,71 @@ export function AuthFlow({
|
|
|
291
346
|
)
|
|
292
347
|
|
|
293
348
|
// Combined event handler that wraps user's onEvent
|
|
349
|
+
// In internal mode, this handles ALL view transitions centrally
|
|
294
350
|
const handleAuthEvent = useCallback(
|
|
295
351
|
(event: AuthFlowEvent) => {
|
|
296
352
|
// Forward to user's handler
|
|
297
353
|
onEvent?.(event)
|
|
298
354
|
|
|
299
|
-
// In internal mode,
|
|
300
|
-
|
|
301
|
-
|
|
355
|
+
// In internal mode, handle view transitions based on events
|
|
356
|
+
if (mode === "internal") {
|
|
357
|
+
switch (event.type) {
|
|
358
|
+
// Sign-up flow
|
|
359
|
+
case "SIGN_UP_REQUIRES_VERIFICATION":
|
|
360
|
+
setCurrentView("EMAIL_VERIFICATION")
|
|
361
|
+
setVerifyEmail(event.email)
|
|
362
|
+
persistState("EMAIL_VERIFICATION", event.email)
|
|
363
|
+
break
|
|
364
|
+
|
|
365
|
+
// Sign-in flow
|
|
366
|
+
case "SIGN_IN_REQUIRES_2FA":
|
|
367
|
+
setCurrentView("TWO_FACTOR")
|
|
368
|
+
persistState("TWO_FACTOR")
|
|
369
|
+
break
|
|
370
|
+
case "SIGN_IN_REQUIRES_VERIFICATION":
|
|
371
|
+
setCurrentView("EMAIL_VERIFICATION")
|
|
372
|
+
setVerifyEmail(event.email)
|
|
373
|
+
persistState("EMAIL_VERIFICATION", event.email)
|
|
374
|
+
break
|
|
375
|
+
|
|
376
|
+
// Verification success without token → sign in
|
|
377
|
+
case "VERIFICATION_SUCCESS":
|
|
378
|
+
if (!event.session?.token) {
|
|
379
|
+
setCurrentView("SIGN_IN")
|
|
380
|
+
clearPersistedState()
|
|
381
|
+
}
|
|
382
|
+
// If token present, clear state - auth succeeded
|
|
383
|
+
if (event.session?.token) {
|
|
384
|
+
clearPersistedState()
|
|
385
|
+
}
|
|
386
|
+
break
|
|
387
|
+
|
|
388
|
+
// Password reset flow
|
|
389
|
+
case "PASSWORD_RESET_SUCCESS":
|
|
390
|
+
setCurrentView("SIGN_IN")
|
|
391
|
+
clearPersistedState()
|
|
392
|
+
break
|
|
393
|
+
|
|
394
|
+
// View toggles (footer links) - handled by InternalLink
|
|
395
|
+
case "VIEW_CHANGE":
|
|
396
|
+
setCurrentView(event.view)
|
|
397
|
+
if (event.email) setVerifyEmail(event.email)
|
|
398
|
+
// Only persist non-initial views
|
|
399
|
+
if (event.view !== "SIGN_IN") {
|
|
400
|
+
persistState(event.view, event.email)
|
|
401
|
+
} else {
|
|
402
|
+
clearPersistedState()
|
|
403
|
+
}
|
|
404
|
+
break
|
|
405
|
+
|
|
406
|
+
// AUTH_SUCCESS - clear persisted state, let React re-render via useSession
|
|
407
|
+
case "AUTH_SUCCESS":
|
|
408
|
+
clearPersistedState()
|
|
409
|
+
break
|
|
410
|
+
}
|
|
411
|
+
}
|
|
302
412
|
},
|
|
303
|
-
[onEvent]
|
|
413
|
+
[onEvent, mode]
|
|
304
414
|
)
|
|
305
415
|
|
|
306
416
|
// Get the view key for AuthView component
|
|
@@ -317,6 +427,7 @@ export function AuthFlow({
|
|
|
317
427
|
basePath={basePath}
|
|
318
428
|
navigate={handleNavigate}
|
|
319
429
|
onAuthEvent={handleAuthEvent}
|
|
430
|
+
authFlowMode={mode}
|
|
320
431
|
redirectTo={redirectTo}
|
|
321
432
|
Link={InternalLink}
|
|
322
433
|
{...providerProps}
|
|
@@ -326,6 +437,7 @@ export function AuthFlow({
|
|
|
326
437
|
classNames={classNames}
|
|
327
438
|
cardHeader={cardHeader}
|
|
328
439
|
cardFooter={cardFooter}
|
|
440
|
+
email={verifyEmail}
|
|
329
441
|
socialLayout={socialLayout}
|
|
330
442
|
otpSeparators={otpSeparators}
|
|
331
443
|
redirectTo={redirectTo}
|
|
@@ -41,6 +41,10 @@ export interface AuthFormProps {
|
|
|
41
41
|
className?: string
|
|
42
42
|
classNames?: AuthFormClassNames
|
|
43
43
|
callbackURL?: string
|
|
44
|
+
/**
|
|
45
|
+
* Email for verification flows (passed in internal mode)
|
|
46
|
+
*/
|
|
47
|
+
email?: string
|
|
44
48
|
isSubmitting?: boolean
|
|
45
49
|
localization?: Partial<AuthLocalization>
|
|
46
50
|
pathname?: string
|
|
@@ -71,6 +75,7 @@ export function AuthForm({
|
|
|
71
75
|
className,
|
|
72
76
|
classNames,
|
|
73
77
|
callbackURL,
|
|
78
|
+
email,
|
|
74
79
|
isSubmitting,
|
|
75
80
|
localization,
|
|
76
81
|
pathname,
|
|
@@ -261,6 +266,7 @@ export function AuthForm({
|
|
|
261
266
|
className={className}
|
|
262
267
|
classNames={classNames}
|
|
263
268
|
callbackURL={callbackURL}
|
|
269
|
+
email={email}
|
|
264
270
|
localization={localization}
|
|
265
271
|
otpSeparators={otpSeparators}
|
|
266
272
|
redirectTo={redirectTo}
|
|
@@ -47,6 +47,10 @@ export interface AuthViewProps {
|
|
|
47
47
|
callbackURL?: string
|
|
48
48
|
cardHeader?: ReactNode
|
|
49
49
|
cardFooter?: ReactNode
|
|
50
|
+
/**
|
|
51
|
+
* Email for verification flows (passed in internal mode)
|
|
52
|
+
*/
|
|
53
|
+
email?: string
|
|
50
54
|
localization?: AuthLocalization
|
|
51
55
|
path?: string
|
|
52
56
|
pathname?: string
|
|
@@ -62,6 +66,7 @@ export function AuthView({
|
|
|
62
66
|
callbackURL,
|
|
63
67
|
cardHeader,
|
|
64
68
|
cardFooter,
|
|
69
|
+
email,
|
|
65
70
|
localization,
|
|
66
71
|
path: pathProp,
|
|
67
72
|
pathname,
|
|
@@ -173,6 +178,7 @@ export function AuthView({
|
|
|
173
178
|
<AuthForm
|
|
174
179
|
classNames={classNames?.form}
|
|
175
180
|
callbackURL={callbackURL}
|
|
181
|
+
email={email}
|
|
176
182
|
isSubmitting={isSubmitting}
|
|
177
183
|
localization={localization}
|
|
178
184
|
otpSeparators={otpSeparators}
|
|
@@ -63,7 +63,8 @@ export function EmailVerificationForm({
|
|
|
63
63
|
basePath,
|
|
64
64
|
viewPaths,
|
|
65
65
|
emailVerification,
|
|
66
|
-
onAuthEvent
|
|
66
|
+
onAuthEvent,
|
|
67
|
+
authFlowMode
|
|
67
68
|
} = useContext(AuthUIContext)
|
|
68
69
|
|
|
69
70
|
localization = { ...contextLocalization, ...localization }
|
|
@@ -159,13 +160,16 @@ export function EmailVerificationForm({
|
|
|
159
160
|
// No token - verification succeeded but no session
|
|
160
161
|
const session = { user }
|
|
161
162
|
onAuthEvent?.({ type: "VERIFICATION_SUCCESS", user, session })
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
163
|
+
// In internal mode, AuthFlow handles view transitions via events
|
|
164
|
+
if (authFlowMode !== "internal") {
|
|
165
|
+
navigate(
|
|
166
|
+
`${basePath}/${viewPaths.SIGN_IN}${window.location.search}`
|
|
167
|
+
)
|
|
168
|
+
toast({
|
|
169
|
+
variant: "success",
|
|
170
|
+
message: localization.EMAIL_VERIFICATION_SUCCESS!
|
|
171
|
+
})
|
|
172
|
+
}
|
|
169
173
|
}
|
|
170
174
|
} catch (error) {
|
|
171
175
|
const errorMessage = getLocalizedError({
|
|
@@ -52,7 +52,9 @@ export function ForgotPasswordForm({
|
|
|
52
52
|
navigate,
|
|
53
53
|
toast,
|
|
54
54
|
viewPaths,
|
|
55
|
-
localizeErrors
|
|
55
|
+
localizeErrors,
|
|
56
|
+
onAuthEvent,
|
|
57
|
+
authFlowMode
|
|
56
58
|
} = useContext(AuthUIContext)
|
|
57
59
|
|
|
58
60
|
localization = { ...contextLocalization, ...localization }
|
|
@@ -94,14 +96,19 @@ export function ForgotPasswordForm({
|
|
|
94
96
|
fetchOptions
|
|
95
97
|
})
|
|
96
98
|
|
|
99
|
+
onAuthEvent?.({ type: "FORGOT_PASSWORD_EMAIL_SENT", email })
|
|
100
|
+
|
|
97
101
|
toast({
|
|
98
102
|
variant: "success",
|
|
99
103
|
message: localization.FORGOT_PASSWORD_EMAIL
|
|
100
104
|
})
|
|
101
105
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
// In internal mode, AuthFlow handles view transitions via events
|
|
107
|
+
if (authFlowMode !== "internal") {
|
|
108
|
+
navigate(
|
|
109
|
+
`${basePath}/${viewPaths.SIGN_IN}${window.location.search}`
|
|
110
|
+
)
|
|
111
|
+
}
|
|
105
112
|
} catch (error) {
|
|
106
113
|
toast({
|
|
107
114
|
variant: "error",
|
|
@@ -45,7 +45,9 @@ export function ResetPasswordForm({
|
|
|
45
45
|
viewPaths,
|
|
46
46
|
navigate,
|
|
47
47
|
toast,
|
|
48
|
-
localizeErrors
|
|
48
|
+
localizeErrors,
|
|
49
|
+
onAuthEvent,
|
|
50
|
+
authFlowMode
|
|
49
51
|
} = useContext(AuthUIContext)
|
|
50
52
|
|
|
51
53
|
const confirmPasswordEnabled = credentials?.confirmPassword
|
|
@@ -99,12 +101,15 @@ export function ResetPasswordForm({
|
|
|
99
101
|
const token = searchParams.get("token")
|
|
100
102
|
|
|
101
103
|
if (!token || token === "INVALID_TOKEN") {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
// In internal mode, AuthFlow handles view transitions via events
|
|
105
|
+
if (authFlowMode !== "internal") {
|
|
106
|
+
navigate(
|
|
107
|
+
`${basePath}/${viewPaths.SIGN_IN}${window.location.search}`
|
|
108
|
+
)
|
|
109
|
+
}
|
|
105
110
|
toast({ variant: "error", message: localization.INVALID_TOKEN })
|
|
106
111
|
}
|
|
107
|
-
}, [basePath, navigate, toast, viewPaths, localization])
|
|
112
|
+
}, [basePath, navigate, toast, viewPaths, localization, authFlowMode])
|
|
108
113
|
|
|
109
114
|
async function resetPassword({ newPassword }: z.infer<typeof formSchema>) {
|
|
110
115
|
try {
|
|
@@ -117,14 +122,19 @@ export function ResetPasswordForm({
|
|
|
117
122
|
fetchOptions: { throw: true }
|
|
118
123
|
})
|
|
119
124
|
|
|
125
|
+
onAuthEvent?.({ type: "PASSWORD_RESET_SUCCESS" })
|
|
126
|
+
|
|
120
127
|
toast({
|
|
121
128
|
variant: "success",
|
|
122
129
|
message: localization.RESET_PASSWORD_SUCCESS
|
|
123
130
|
})
|
|
124
131
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
132
|
+
// In internal mode, AuthFlow handles view transitions via events
|
|
133
|
+
if (authFlowMode !== "internal") {
|
|
134
|
+
navigate(
|
|
135
|
+
`${basePath}/${viewPaths.SIGN_IN}${window.location.search}`
|
|
136
|
+
)
|
|
137
|
+
}
|
|
128
138
|
} catch (error) {
|
|
129
139
|
toast({
|
|
130
140
|
variant: "error",
|
|
@@ -69,7 +69,8 @@ export function SignInForm({
|
|
|
69
69
|
Link,
|
|
70
70
|
localizeErrors,
|
|
71
71
|
emailVerification,
|
|
72
|
-
onAuthEvent
|
|
72
|
+
onAuthEvent,
|
|
73
|
+
authFlowMode
|
|
73
74
|
} = useContext(AuthUIContext)
|
|
74
75
|
|
|
75
76
|
const rememberMeEnabled = credentials?.rememberMe
|
|
@@ -150,9 +151,12 @@ export function SignInForm({
|
|
|
150
151
|
|
|
151
152
|
if (response.twoFactorRedirect) {
|
|
152
153
|
onAuthEvent?.({ type: "SIGN_IN_REQUIRES_2FA", email })
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
// In internal mode, AuthFlow handles view transitions via events
|
|
155
|
+
if (authFlowMode !== "internal") {
|
|
156
|
+
navigate(
|
|
157
|
+
`${basePath}/${viewPaths.TWO_FACTOR}${window.location.search}`
|
|
158
|
+
)
|
|
159
|
+
}
|
|
156
160
|
} else {
|
|
157
161
|
// Build user and session for events
|
|
158
162
|
const user = response.user as {
|
|
@@ -202,11 +206,14 @@ export function SignInForm({
|
|
|
202
206
|
|
|
203
207
|
if (errorCode === "EMAIL_NOT_VERIFIED") {
|
|
204
208
|
onAuthEvent?.({ type: "SIGN_IN_REQUIRES_VERIFICATION", email })
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
209
|
+
// In internal mode, AuthFlow handles view transitions via events
|
|
210
|
+
if (authFlowMode !== "internal") {
|
|
211
|
+
navigate(
|
|
212
|
+
`${basePath}/${
|
|
213
|
+
viewPaths.EMAIL_VERIFICATION
|
|
214
|
+
}?email=${encodeURIComponent(email)}`
|
|
215
|
+
)
|
|
216
|
+
}
|
|
210
217
|
}
|
|
211
218
|
}
|
|
212
219
|
}
|
|
@@ -93,7 +93,8 @@ export function SignUpForm({
|
|
|
93
93
|
avatar,
|
|
94
94
|
localizeErrors,
|
|
95
95
|
emailVerification,
|
|
96
|
-
onAuthEvent
|
|
96
|
+
onAuthEvent,
|
|
97
|
+
authFlowMode
|
|
97
98
|
} = useContext(AuthUIContext)
|
|
98
99
|
|
|
99
100
|
const confirmPasswordEnabled = credentials?.confirmPassword
|
|
@@ -415,9 +416,16 @@ export function SignUpForm({
|
|
|
415
416
|
|
|
416
417
|
// Check for 2FA redirect first (same pattern as sign-in form)
|
|
417
418
|
if (response.twoFactorRedirect) {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
419
|
+
onAuthEvent?.({
|
|
420
|
+
type: "SIGN_IN_REQUIRES_2FA",
|
|
421
|
+
email: email as string
|
|
422
|
+
})
|
|
423
|
+
// In internal mode, AuthFlow handles view transitions via events
|
|
424
|
+
if (authFlowMode !== "internal") {
|
|
425
|
+
navigate(
|
|
426
|
+
`${basePath}/${viewPaths.TWO_FACTOR}${window.location.search}`
|
|
427
|
+
)
|
|
428
|
+
}
|
|
421
429
|
} else if (response.token && user) {
|
|
422
430
|
// Token present = verified or no verification required
|
|
423
431
|
const session = {
|
|
@@ -433,22 +441,28 @@ export function SignUpForm({
|
|
|
433
441
|
type: "SIGN_UP_REQUIRES_VERIFICATION",
|
|
434
442
|
email: email as string
|
|
435
443
|
})
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
444
|
+
// In internal mode, AuthFlow handles view transitions via events
|
|
445
|
+
if (authFlowMode !== "internal") {
|
|
446
|
+
navigate(
|
|
447
|
+
`${basePath}/${viewPaths.EMAIL_VERIFICATION}?email=${encodeURIComponent(email as string)}`
|
|
448
|
+
)
|
|
449
|
+
}
|
|
439
450
|
} else {
|
|
440
451
|
// Fallback: redirect to sign-in (e.g., link-based verification sent)
|
|
441
452
|
onAuthEvent?.({
|
|
442
453
|
type: "SIGN_UP_REQUIRES_VERIFICATION",
|
|
443
454
|
email: email as string
|
|
444
455
|
})
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
456
|
+
// In internal mode, AuthFlow handles view transitions via events
|
|
457
|
+
if (authFlowMode !== "internal") {
|
|
458
|
+
navigate(
|
|
459
|
+
`${basePath}/${viewPaths.SIGN_IN}${window.location.search}`
|
|
460
|
+
)
|
|
461
|
+
toast({
|
|
462
|
+
variant: "success",
|
|
463
|
+
message: localization.SIGN_UP_EMAIL!
|
|
464
|
+
})
|
|
465
|
+
}
|
|
452
466
|
}
|
|
453
467
|
} catch (error) {
|
|
454
468
|
const errorMessage = getLocalizedError({
|
|
@@ -225,6 +225,11 @@ export type AuthUIContextType = {
|
|
|
225
225
|
* Called with typed events during sign-in, sign-up, verification, etc.
|
|
226
226
|
*/
|
|
227
227
|
onAuthEvent?: AuthFlowEventHandler
|
|
228
|
+
/**
|
|
229
|
+
* AuthFlow mode - when 'internal', forms skip navigation and let AuthFlow handle view transitions.
|
|
230
|
+
* undefined = standalone form usage (normal navigation)
|
|
231
|
+
*/
|
|
232
|
+
authFlowMode?: "internal" | "route"
|
|
228
233
|
/**
|
|
229
234
|
* Replace the current URL
|
|
230
235
|
* @default navigate
|
package/src/ui/style.css
CHANGED
|
@@ -1,10 +1,41 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/* Default theme variables - users can override these in their own CSS */
|
|
2
2
|
:root {
|
|
3
3
|
--team-1: oklch(0.646 0.222 41.116);
|
|
4
4
|
--team-2: oklch(0.6 0.118 184.704);
|
|
5
5
|
--team-3: oklch(0.398 0.07 227.392);
|
|
6
6
|
--team-4: oklch(0.828 0.189 84.429);
|
|
7
7
|
--team-5: oklch(0.769 0.188 70.08);
|
|
8
|
+
|
|
9
|
+
/* Card and UI component default colors */
|
|
10
|
+
--card: 0 0% 100%;
|
|
11
|
+
--card-foreground: 240 10% 3.9%;
|
|
12
|
+
--background: 0 0% 100%;
|
|
13
|
+
--foreground: 240 10% 3.9%;
|
|
14
|
+
--muted: 240 4.8% 95.9%;
|
|
15
|
+
--muted-foreground: 240 3.8% 46.1%;
|
|
16
|
+
--border: 240 5.9% 90%;
|
|
17
|
+
--input: 240 5.9% 90%;
|
|
18
|
+
--ring: 240 5.9% 10%;
|
|
19
|
+
--primary: 240 5.9% 10%;
|
|
20
|
+
--primary-foreground: 0 0% 98%;
|
|
21
|
+
--destructive: 0 84.2% 60.2%;
|
|
22
|
+
--destructive-foreground: 0 0% 98%;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.dark {
|
|
26
|
+
--card: 240 10% 3.9%;
|
|
27
|
+
--card-foreground: 0 0% 98%;
|
|
28
|
+
--background: 240 10% 3.9%;
|
|
29
|
+
--foreground: 0 0% 98%;
|
|
30
|
+
--muted: 240 3.7% 15.9%;
|
|
31
|
+
--muted-foreground: 240 5% 64.9%;
|
|
32
|
+
--border: 240 3.7% 15.9%;
|
|
33
|
+
--input: 240 3.7% 15.9%;
|
|
34
|
+
--ring: 240 4.9% 83.9%;
|
|
35
|
+
--primary: 0 0% 98%;
|
|
36
|
+
--primary-foreground: 240 5.9% 10%;
|
|
37
|
+
--destructive: 0 62.8% 30.6%;
|
|
38
|
+
--destructive-foreground: 0 0% 98%;
|
|
8
39
|
}
|
|
9
40
|
|
|
10
41
|
/*
|