// ============================================================================ // detail.jsx — Pane détails d'une entrée mot de passe. // TOTP calculés dynamiquement via CPCrypto.computeTotp (RFC 6238). // ============================================================================ const { useState, useEffect, useRef } = React; // useCopy + Field exportés globaux (réutilisés par wifi.jsx). const useCopy = () => { const [copied, setCopied] = useState(null); const copy = (id, text) => { try { navigator.clipboard.writeText(text); } catch (e) {} setCopied(id); setTimeout(() => setCopied(null), 1500); }; return [copied, copy]; }; window.useCopy = useCopy; const TotpRing = ({ remaining, period, size = 28 }) => { const r = (size - 4) / 2; const c = 2 * Math.PI * r; const pct = remaining / period; const danger = remaining <= 5; return ( {remaining} ); }; const AccountBlock = ({ account, copied, onCopy, isPrimary }) => { const [show, setShow] = useState(false); return (
{account.label} {isPrimary && principal}
onCopy(`u-${account.id}`, account.username)} /> onCopy(`p-${account.id}`, account.password)} right={ } /> {account.created &&
Créé le {account.created}
}
); }; // TOTP : calcul live via WebCrypto. Re-render chaque seconde. const TotpBlock = ({ totp, copied, onCopy }) => { const [code, setCode] = useState('------'); const [remaining, setRemaining] = useState(totp.period || 30); const period = totp.period || 30; useEffect(() => { let cancelled = false; const recompute = async () => { try { const c = await CPCrypto.computeTotp(totp.secret, period); if (!cancelled) { setCode(c); setRemaining(CPCrypto.totpRemaining(period)); } } catch { if (!cancelled) setCode('------'); } }; recompute(); const id = setInterval(recompute, 1000); return () => { cancelled = true; clearInterval(id); }; }, [totp.secret, period]); const fmt = code && code.length === 6 ? code.slice(0, 3) + ' ' + code.slice(3) : code; return (
{totp.label}
{fmt.slice(0, 3)} {fmt.slice(4)}
); }; const Field = ({ label, value, mono, copied, onCopy, right }) => (
{label}
{value}
{right}
); const SecurityCard = ({ entry }) => { const score = entry.strength === 'strong' ? 92 : entry.strength === 'medium' ? 58 : 24; const colors = { strong: '#10B981', medium: '#F59E0B', weak: '#EF4444' }; const labels = { strong: 'Mot de passe robuste', medium: 'Force moyenne', weak: 'Mot de passe faible', }; const sub = { strong: 'Long, unique, difficile à deviner.', medium: 'Pourrait être plus long ou plus aléatoire.', weak: 'Trop court ou réutilisé. Régénérez-le.', }; return (
Sécurité
{labels[entry.strength] || 'Force inconnue'}
{sub[entry.strength] || ''}
); }; // Petit menu déroulant pour le bouton "..." du hero. const MoreMenu = ({ favorite, onToggleFavorite, onDelete }) => { const [open, setOpen] = useState(false); const ref = useRef(null); useEffect(() => { if (!open) return; const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown', onDoc); return () => document.removeEventListener('mousedown', onDoc); }, [open]); return (
{open && (
)}
); }; const Detail = ({ entry, onEncryptionInfo, onEdit, onDelete, onToggleFavorite }) => { const [copied, copy] = useCopy(); if (!entry) { return (

Sélectionnez une entrée

Cliquez sur un mot de passe pour afficher ses détails.

); } return (
{onEdit && ( )} {(onDelete || onToggleFavorite) && ( )}

Comptes

{entry.accounts.length} {onEdit && ( )}
{entry.accounts.map((a, i) => ( ))}

Codes 2FA

{entry.totp.length} {onEdit && ( )}
{entry.totp.length === 0 ? (
Aucun code 2FA configuré
Renforcez la sécurité de ce compte avec une vérification en deux étapes.
{onEdit && }
) : (
{entry.totp.map(t => ( ))}
)}
{entry.notes && (

Notes

{entry.notes}
)}
); }; window.Detail = Detail; window.Field = Field; window.MoreMenu = MoreMenu;