Phase 3: Post-login passkey prompt toast

Show a one-time toast suggesting passkey setup after login when:
- User has no passkeys registered
- Browser supports WebAuthn
- Prompt hasn't been shown this session (sessionStorage gate)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-03-17 22:51:06 +08:00
parent cc460df5d4
commit 51d98173a6

View File

@ -1,7 +1,9 @@
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { Outlet } from 'react-router-dom'; import { Outlet } from 'react-router-dom';
import { toast } from 'sonner';
import { Menu } from 'lucide-react'; import { Menu } from 'lucide-react';
import { useTheme } from '@/hooks/useTheme'; import { useTheme } from '@/hooks/useTheme';
import { useAuth } from '@/hooks/useAuth';
import { usePrefetch } from '@/hooks/usePrefetch'; import { usePrefetch } from '@/hooks/usePrefetch';
import { AlertsProvider } from '@/hooks/useAlerts'; import { AlertsProvider } from '@/hooks/useAlerts';
import { LockProvider, useLock } from '@/hooks/useLock'; import { LockProvider, useLock } from '@/hooks/useLock';
@ -17,7 +19,20 @@ function AppContent({ mobileOpen, setMobileOpen }: {
setMobileOpen: (v: boolean) => void; setMobileOpen: (v: boolean) => void;
}) { }) {
const { isLocked, isLockResolved } = useLock(); const { isLocked, isLockResolved } = useLock();
const { hasPasskeys } = useAuth();
usePrefetch(isLockResolved && !isLocked); usePrefetch(isLockResolved && !isLocked);
// Post-login passkey prompt — show once per session if user has no passkeys
useEffect(() => {
if (
isLockResolved && !isLocked && !hasPasskeys &&
window.PublicKeyCredential &&
!sessionStorage.getItem('passkey-prompt-shown')
) {
sessionStorage.setItem('passkey-prompt-shown', '1');
toast.info('Simplify your login \u2014 set up a passkey in Settings', { duration: 8000 });
}
}, [isLockResolved, isLocked, hasPasskeys]);
const [collapsed, setCollapsed] = useState(() => { const [collapsed, setCollapsed] = useState(() => {
try { return JSON.parse(localStorage.getItem('umbra-sidebar-collapsed') || 'false'); } try { return JSON.parse(localStorage.getItem('umbra-sidebar-collapsed') || 'false'); }
catch { return false; } catch { return false; }