import { useState } from 'react'; import { Download, Copy, Eye, EyeOff, Shield, AlertTriangle, CheckCircle, ExternalLink, KeyRound, QrCode } from 'lucide-react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { QRCodeCanvas } from '@/components/ui/qrcode'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useLocalStorage } from '@/hooks/useLocalStorage'; import { backupConfirmedKey } from '@/lib/backup'; import { useNostrLogin } from '@nostrify/react/login'; import { toast } from '@/hooks/useToast'; import { useTranslation } from '@/contexts/language'; const sanitizeFilename = (filename: string) => filename.replace(/[^a-z0-9_.-]/gi, '_'); export function SecuritySettings() { const { user } = useCurrentUser(); const { logins } = useNostrLogin(); const { t } = useTranslation(); const [confirmed, setConfirmed] = useLocalStorage(backupConfirmedKey(user?.pubkey), false); const [showWarning, setShowWarning] = useState(!confirmed); // Recovery reveal state const [revealed, setRevealed] = useState(false); const [showQr, setShowQr] = useState(false); // The active login. For nsec logins, the recovery code (nsec) is stored on // the login object itself — this is how we can reveal it later for users who // skipped backup at signup. For bunker/extension/Amber logins, the app never // held the key, so there is nothing to reveal here. const activeLogin = logins[0] as | (typeof logins[number] & { nsec?: string; data?: { nsec?: string } }) | undefined; const loginType = activeLogin?.type; // Nostrify stores the bech32 nsec directly on the login (older builds nested // it under `data`), so check both to be safe. const recoveryCode = loginType === 'nsec' ? (activeLogin?.nsec ?? activeLogin?.data?.nsec) : undefined; const handleReveal = () => setRevealed(true); const handleCopyRecovery = () => { if (!recoveryCode) return; navigator.clipboard.writeText(recoveryCode); // Copying the recovery code counts as a real backup — mark confirmed so the // gentle reminder stops nagging a user who has genuinely saved their login. setConfirmed(true); setShowWarning(false); toast({ title: t('common.copied'), description: t('settingsUi.security.recoveryCopied') }); }; const handleDownloadRecovery = () => { if (!recoveryCode) return; try { const blob = new Blob([recoveryCode], { type: 'text/plain; charset=utf-8' }); const url = globalThis.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = sanitizeFilename('skatespots-recovery-code.txt'); a.style.display = 'none'; document.body.appendChild(a); a.click(); globalThis.URL.revokeObjectURL(url); document.body.removeChild(a); // A downloaded recovery file is a genuine backup — mark confirmed so the // reminder stops for good. setConfirmed(true); setShowWarning(false); toast({ title: t('settingsUi.security.savedTitle'), description: t('settingsUi.security.recoveryDownloaded') }); } catch { toast({ title: t('settingsUi.security.downloadFailedTitle'), description: t('settingsUi.security.downloadFailedDesc'), variant: 'destructive' }); } }; if (!user) { return (

{t('settingsUi.security.loginRequired')}

{t('settingsUi.security.loginRequiredDesc')}

); } const handleConfirmBackup = () => { setConfirmed(true); setShowWarning(false); toast({ title: t('settingsUi.security.backupConfirmedTitle'), description: t('settingsUi.security.backupConfirmedDesc'), }); }; return (
{/* Recover / reveal recovery code — the real backup path for skaters who skipped it at signup. Only possible for nsec logins. */} {t('settingsUi.security.yourRecoveryCode')} {t('settingsUi.security.recoveryCodeDesc')} {recoveryCode ? ( <>

{t('settingsUi.security.recoveryCodeBefore')}{' '} {t('settingsUi.security.only')}{' '} {t('settingsUi.security.recoveryCodeAfter')}

{!revealed ? ( ) : (
{recoveryCode}
{showQr && (

{t('settingsUi.security.qrHint')}

)}

{t('settingsUi.security.neverShareHide')}

)} ) : ( {loginType === 'x-amber' && ( <>{t('settingsUi.security.amberNoteBefore')} Amber{t('settingsUi.security.amberNoteAfter')} )} {loginType === 'bunker' && ( <>{t('settingsUi.security.bunkerNoteBefore')} {t('settingsUi.security.remoteSigner')}{t('settingsUi.security.bunkerNoteAfter')} )} {(loginType as string) === 'extension' && ( <>{t('settingsUi.security.extensionNoteBefore')} {t('settingsUi.security.browserExtension')}{t('settingsUi.security.extensionNoteAfter')} )} {!loginType && ( <>{t('settingsUi.security.otherNote')} )} )}
{/* Backup Warning */} {showWarning && (

{t('settingsUi.security.backupWarningTitle')}

{t('settingsUi.security.backupWarningBody')}

)} {/* Backup Status */} {confirmed ? t('settingsUi.security.backupConfirmed') : t('settingsUi.security.backupNotConfirmed')} {confirmed ? t('settingsUi.security.backupConfirmedStatusDesc') : t('settingsUi.security.backupNotConfirmedDesc')} {!confirmed && (

{t('settingsUi.security.notSavedYetBefore')} {t('settingsUi.security.yourRecoveryCode')} {t('settingsUi.security.notSavedYetAfter')}

)}
{/* Security Information */} {t('settingsUi.security.bestPracticesTitle')} {t('settingsUi.security.bestPracticesDesc')}
✍️

{t('settingsUi.security.bp1Title')}

{t('settingsUi.security.bp1Desc')}

💾

{t('settingsUi.security.bp2Title')}

{t('settingsUi.security.bp2Desc')}

🔒

{t('settingsUi.security.bp3Title')}

{t('settingsUi.security.bp3Desc')}

🚫

{t('settingsUi.security.bp4Title')}

{t('settingsUi.security.bp4Desc')}

🔐

{t('settingsUi.security.bp5Title')}

{t('settingsUi.security.bp5Desc')}

{t('settingsUi.security.importantReminders')}

  • • {t('settingsUi.security.rem1')}
  • • {t('settingsUi.security.rem2')}
  • • {t('settingsUi.security.rem3')}
  • • {t('settingsUi.security.rem4')}
{/* Browser Extension & Mobile App Recommendations */} {t('settingsUi.security.recommendedSigners')} {t('settingsUi.security.recommendedSignersDesc')}
{/* Amber - Android */}
Amber Android

{t('settingsUi.security.amberDesc')}

Alby {t('settingsUi.security.recommended')}

{t('settingsUi.security.albyDesc')}

Flamingo

{t('settingsUi.security.flamingoDesc')}

nos2x

{t('settingsUi.security.nos2xDesc')}

{/* Nostr Connect (Bunkers) */} {t('settingsUi.security.nostrConnectTitle')} {t('settingsUi.security.nostrConnectDesc')}

{t('settingsUi.security.nostrConnectBody')}

nsec.app {t('common.popular')}

{t('settingsUi.security.nsecAppDesc')}

Nostrum

{t('settingsUi.security.nostrumDesc')}

{t('settingsUi.security.howToUse')} {t('settingsUi.security.howToUseDesc')}

); }