From 3c70579ac9b668f15a0cf174a11fdfc46da506b5 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 16:47:49 +0000 Subject: [PATCH] Fix: Deduplicate toast and add session dismissal --- src/hooks/useAuth.tsx | 22 ++++++++++++++++++++-- src/lib/sessionFlags.ts | 22 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/hooks/useAuth.tsx b/src/hooks/useAuth.tsx index 4d88a25c..64981871 100644 --- a/src/hooks/useAuth.tsx +++ b/src/hooks/useAuth.tsx @@ -34,6 +34,7 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { // Refs for lifecycle and cleanup management const novuUpdateTimeoutRef = useRef(null); const previousEmailRef = useRef(null); + const orphanedPasswordToastShownRef = useRef(false); // Verify session is still valid - simplified const verifySession = async () => { @@ -98,6 +99,7 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { setUser(null); setAal(null); setLoading(false); + orphanedPasswordToastShownRef.current = false; return; } @@ -115,6 +117,16 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { // Check for orphaned password on SIGNED_IN events if (event === 'SIGNED_IN' && session?.user) { try { + // Import sessionFlags + const { isOrphanedPasswordDismissed, setOrphanedPasswordDismissed } = + await import('@/lib/sessionFlags'); + + // Skip if already shown in this auth cycle or dismissed this session + if (orphanedPasswordToastShownRef.current || isOrphanedPasswordDismissed()) { + authLog('[Auth] Skipping orphaned password toast - already shown or dismissed'); + return; + } + // Import identityService functions const { getUserIdentities, hasOrphanedPassword, triggerOrphanedPasswordConfirmation } = await import('@/lib/identityService'); @@ -128,12 +140,15 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { const isOrphaned = await hasOrphanedPassword(); if (isOrphaned) { + // Mark as shown to prevent duplicates + orphanedPasswordToastShownRef.current = true; + // Show persistent toast with Resend button const { toast: sonnerToast } = await import('sonner'); sonnerToast.warning("Password Activation Pending", { description: "Your password needs email confirmation to be fully activated.", - duration: Infinity, // Persistent until dismissed + duration: Infinity, action: { label: "Resend Email", onClick: async () => { @@ -154,7 +169,10 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { }, cancel: { label: "Dismiss", - onClick: () => {} // Allow dismissal + onClick: () => { + setOrphanedPasswordDismissed(); + authLog('[Auth] User dismissed orphaned password warning'); + } } }); } diff --git a/src/lib/sessionFlags.ts b/src/lib/sessionFlags.ts index 24f9b0a6..633761ff 100644 --- a/src/lib/sessionFlags.ts +++ b/src/lib/sessionFlags.ts @@ -7,6 +7,7 @@ export const SessionFlags = { MFA_INTENDED_PATH: 'mfa_intended_path', MFA_CHALLENGE_ID: 'mfa_challenge_id', AUTH_METHOD: 'auth_method', + ORPHANED_PASSWORD_DISMISSED: 'orphaned_password_dismissed', } as const; export type SessionFlagKey = typeof SessionFlags[keyof typeof SessionFlags]; @@ -73,6 +74,27 @@ export function clearAuthMethod(): void { sessionStorage.removeItem(SessionFlags.AUTH_METHOD); } +/** + * Set the orphaned password dismissed flag + */ +export function setOrphanedPasswordDismissed(): void { + sessionStorage.setItem(SessionFlags.ORPHANED_PASSWORD_DISMISSED, 'true'); +} + +/** + * Check if orphaned password warning has been dismissed this session + */ +export function isOrphanedPasswordDismissed(): boolean { + return sessionStorage.getItem(SessionFlags.ORPHANED_PASSWORD_DISMISSED) === 'true'; +} + +/** + * Clear the orphaned password dismissed flag + */ +export function clearOrphanedPasswordDismissed(): void { + sessionStorage.removeItem(SessionFlags.ORPHANED_PASSWORD_DISMISSED); +} + /** * Clear all authentication-related session flags */