@erikey/react 0.5.0 → 0.5.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erikey/react",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "React SDK for Erikey - B2B authentication and user management. UI components based on better-auth-ui.",
5
5
  "main": "./dist/index.mjs",
6
6
  "module": "./dist/index.mjs",
@@ -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
- const [currentView, setCurrentView] = useState<AuthFlowView>(initialView)
200
- const [verifyEmail, setVerifyEmail] = useState<string | undefined>()
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(
@@ -304,38 +359,54 @@ export function AuthFlow({
304
359
  case "SIGN_UP_REQUIRES_VERIFICATION":
305
360
  setCurrentView("EMAIL_VERIFICATION")
306
361
  setVerifyEmail(event.email)
362
+ persistState("EMAIL_VERIFICATION", event.email)
307
363
  break
308
364
 
309
365
  // Sign-in flow
310
366
  case "SIGN_IN_REQUIRES_2FA":
311
367
  setCurrentView("TWO_FACTOR")
368
+ persistState("TWO_FACTOR")
312
369
  break
313
370
  case "SIGN_IN_REQUIRES_VERIFICATION":
314
371
  setCurrentView("EMAIL_VERIFICATION")
315
372
  setVerifyEmail(event.email)
373
+ persistState("EMAIL_VERIFICATION", event.email)
316
374
  break
317
375
 
318
376
  // Verification success without token → sign in
319
377
  case "VERIFICATION_SUCCESS":
320
378
  if (!event.session?.token) {
321
379
  setCurrentView("SIGN_IN")
380
+ clearPersistedState()
381
+ }
382
+ // If token present, clear state - auth succeeded
383
+ if (event.session?.token) {
384
+ clearPersistedState()
322
385
  }
323
- // If token present, let session state handle UI
324
386
  break
325
387
 
326
388
  // Password reset flow
327
389
  case "PASSWORD_RESET_SUCCESS":
328
390
  setCurrentView("SIGN_IN")
391
+ clearPersistedState()
329
392
  break
330
393
 
331
394
  // View toggles (footer links) - handled by InternalLink
332
395
  case "VIEW_CHANGE":
333
396
  setCurrentView(event.view)
334
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
+ }
335
404
  break
336
405
 
337
- // AUTH_SUCCESS - let React re-render via useSession
338
- // No action needed - session state triggers re-render
406
+ // AUTH_SUCCESS - clear persisted state, let React re-render via useSession
407
+ case "AUTH_SUCCESS":
408
+ clearPersistedState()
409
+ break
339
410
  }
340
411
  }
341
412
  },
@@ -77,6 +77,7 @@ export function AuthView({
77
77
  }: AuthViewProps) {
78
78
  const isHydrated = useIsHydrated()
79
79
  const {
80
+ authFlowMode,
80
81
  basePath,
81
82
  credentials,
82
83
  localization: contextLocalization,
@@ -421,6 +422,25 @@ export function AuthView({
421
422
  : localization.SIGN_IN}
422
423
  </Button>
423
424
  </Link>
425
+ ) : authFlowMode === "internal" ? (
426
+ <Link
427
+ className={cn(
428
+ "text-foreground underline",
429
+ classNames?.footerLink
430
+ )}
431
+ href={`${basePath}/${viewPaths.SIGN_IN}${isHydrated ? window.location.search : ""}`}
432
+ >
433
+ <Button
434
+ variant="link"
435
+ size="sm"
436
+ className={cn(
437
+ "px-0 text-foreground underline",
438
+ classNames?.footerLink
439
+ )}
440
+ >
441
+ {localization.GO_BACK}
442
+ </Button>
443
+ </Link>
424
444
  ) : (
425
445
  <Button
426
446
  variant="link"