feat: Add persistent sign-in toast for orphaned passwords

This commit is contained in:
gpt-engineer-app[bot]
2025-10-14 16:01:12 +00:00
parent 4ce2c266eb
commit 69c16c2b1e
3 changed files with 58 additions and 3 deletions

View File

@@ -196,7 +196,7 @@ export function SecurityTab() {
const handleSendConfirmationEmail = async () => { const handleSendConfirmationEmail = async () => {
setAddingPassword(true); setAddingPassword(true);
const result = await triggerOrphanedPasswordConfirmation(); const result = await triggerOrphanedPasswordConfirmation('security_settings');
if (result.success) { if (result.success) {
sonnerToast.success("Confirmation Email Sent!", { sonnerToast.success("Confirmation Email Sent!", {

View File

@@ -111,6 +111,58 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
} else { } else {
setAal(null); 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); }, 0);
// Detect confirmed email change: email changed AND no longer pending // Detect confirmed email change: email changed AND no longer pending

View File

@@ -293,7 +293,9 @@ export async function hasOrphanedPassword(): Promise<boolean> {
* Trigger email confirmation for orphaned password * Trigger email confirmation for orphaned password
* Direct trigger without requiring password re-entry * Direct trigger without requiring password re-entry
*/ */
export async function triggerOrphanedPasswordConfirmation(): Promise<IdentityOperationResult> { export async function triggerOrphanedPasswordConfirmation(
source?: string
): Promise<IdentityOperationResult> {
try { try {
const { data: { user } } = await supabase.auth.getUser(); const { data: { user } } = await supabase.auth.getUser();
@@ -313,7 +315,8 @@ export async function triggerOrphanedPasswordConfirmation(): Promise<IdentityOpe
if (error) throw error; if (error) throw error;
await logIdentityChange(user.id, 'orphaned_password_confirmation_triggered', { await logIdentityChange(user.id, 'orphaned_password_confirmation_triggered', {
method: 'manual_button_click' method: source || 'manual_button_click',
timestamp: new Date().toISOString()
}); });
return { return {