diff --git a/src/components/settings/SecurityTab.tsx b/src/components/settings/SecurityTab.tsx index d59dddbc..a3389575 100644 --- a/src/components/settings/SecurityTab.tsx +++ b/src/components/settings/SecurityTab.tsx @@ -196,7 +196,7 @@ export function SecurityTab() { const handleSendConfirmationEmail = async () => { setAddingPassword(true); - const result = await triggerOrphanedPasswordConfirmation(); + const result = await triggerOrphanedPasswordConfirmation('security_settings'); if (result.success) { sonnerToast.success("Confirmation Email Sent!", { diff --git a/src/hooks/useAuth.tsx b/src/hooks/useAuth.tsx index f0cd114f..4d88a25c 100644 --- a/src/hooks/useAuth.tsx +++ b/src/hooks/useAuth.tsx @@ -111,6 +111,58 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { } else { setAal(null); } + + // Check for orphaned password on SIGNED_IN events + if (event === 'SIGNED_IN' && session?.user) { + try { + // Import identityService functions + const { getUserIdentities, hasOrphanedPassword, triggerOrphanedPasswordConfirmation } = + await import('@/lib/identityService'); + + // Check if user has email identity + const identities = await getUserIdentities(); + const hasEmailIdentity = identities.some(i => i.provider === 'email'); + + // If no email identity but has other identities, check for orphaned password + if (!hasEmailIdentity && identities.length > 0) { + const isOrphaned = await hasOrphanedPassword(); + + if (isOrphaned) { + // 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 + action: { + label: "Resend Email", + onClick: async () => { + const result = await triggerOrphanedPasswordConfirmation('signin_toast'); + + if (result.success) { + sonnerToast.success("Confirmation Email Sent!", { + description: `Check ${result.email} for the confirmation link.`, + duration: 10000, + }); + } else { + sonnerToast.error("Failed to Send Email", { + description: result.error, + duration: 8000, + }); + } + } + }, + cancel: { + label: "Dismiss", + onClick: () => {} // Allow dismissal + } + }); + } + } + } catch (error) { + authError('[Auth] Failed to check for orphaned password:', error); + } + } }, 0); // Detect confirmed email change: email changed AND no longer pending diff --git a/src/lib/identityService.ts b/src/lib/identityService.ts index a861851d..7197d524 100644 --- a/src/lib/identityService.ts +++ b/src/lib/identityService.ts @@ -293,7 +293,9 @@ export async function hasOrphanedPassword(): Promise { * Trigger email confirmation for orphaned password * Direct trigger without requiring password re-entry */ -export async function triggerOrphanedPasswordConfirmation(): Promise { +export async function triggerOrphanedPasswordConfirmation( + source?: string +): Promise { try { const { data: { user } } = await supabase.auth.getUser(); @@ -313,7 +315,8 @@ export async function triggerOrphanedPasswordConfirmation(): Promise