// Cofre Pessoal — senhas, contas bancárias, e-mails, anotações // Armazenado em state.vault, sincronizado via cloud sync. // ⚠️ Sem criptografia: aviso é mostrado no topo da tela. const VAULT_TYPES = [ { id: 'senha', label: 'Senha', icon: 'lock', color: '#B197FC' }, { id: 'conta', label: 'Conta bancária', icon: 'wallet', color: '#5EE3A8' }, { id: 'email', label: 'E-mail', icon: 'sparkle', color: '#6FB8FF' }, { id: 'nota', label: 'Anotação', icon: 'edit', color: '#FFB547' }, ]; function vaultIcon(type) { const t = VAULT_TYPES.find(x => x.id === type); return t ? t.icon : 'lock'; } function vaultColor(type) { const t = VAULT_TYPES.find(x => x.id === type); return t ? t.color : CDV.brand; } function vaultLabel(type) { const t = VAULT_TYPES.find(x => x.id === type); return t ? t.label : type; } const VaultScreen = ({ state, setState, navigate, showToast }) => { const items = (state && state.vault) || []; const [filter, setFilter] = React.useState('todos'); const [search, setSearch] = React.useState(''); const [showNew, setShowNew] = React.useState(false); const [editingId, setEditingId] = React.useState(null); const [detailId, setDetailId] = React.useState(null); const filtered = items .filter(i => filter === 'todos' || i.type === filter) .filter(i => !search || (i.title || '').toLowerCase().includes(search.toLowerCase())); const detail = items.find(i => i.id === detailId); const editing = items.find(i => i.id === editingId); const addItem = (item) => { const now = new Date().toISOString(); setState(s => ({ ...s, vault: [...((s && s.vault) || []), { ...item, id: 'v' + Date.now(), createdAt: now, updatedAt: now }], })); setShowNew(false); showToast('Item salvo no cofre'); }; const updateItem = (id, patch) => { const now = new Date().toISOString(); setState(s => ({ ...s, vault: (s.vault || []).map(i => i.id === id ? { ...i, ...patch, updatedAt: now } : i), })); setEditingId(null); showToast('Item atualizado'); }; const deleteItem = (id) => { if (window.confirm && !window.confirm('Excluir este item do cofre?')) return; setState(s => ({ ...s, vault: (s.vault || []).filter(i => i.id !== id) })); setDetailId(null); setEditingId(null); showToast('Item removido'); }; const counts = { todos: items.length, senha: items.filter(i => i.type === 'senha').length, conta: items.filter(i => i.type === 'conta').length, email: items.filter(i => i.type === 'email').length, nota: items.filter(i => i.type === 'nota').length, }; return (
navigate('more')} right={ } /> {/* Aviso de segurança */}
Atenção
Os itens são salvos sem criptografia avançada. Para senhas críticas (banco, e-mail principal), recomendamos um gerenciador dedicado como 1Password ou Bitwarden.
{/* Busca */}
setSearch(e.target.value)} placeholder="Buscar no cofre..." style={{ width: '100%', height: 44, borderRadius: 12, border: `1px solid ${CDV.stroke}`, background: CDV.surface, color: CDV.text, padding: '0 14px', fontSize: 13, outline: 'none', fontFamily: 'inherit', }} />
{/* Filtros por tipo */}
{[ { id: 'todos', label: `Todos · ${counts.todos}` }, { id: 'senha', label: `Senhas · ${counts.senha}` }, { id: 'conta', label: `Contas · ${counts.conta}` }, { id: 'email', label: `E-mails · ${counts.email}` }, { id: 'nota', label: `Anotações · ${counts.nota}` }, ].map(f => ( ))}
{/* Lista */} {filtered.length === 0 ? (
{items.length === 0 ? 'Cofre vazio' : 'Nada encontrado'}
{items.length === 0 ? 'Adicione senhas, contas, e-mails e anotações pessoais.' : 'Ajuste a busca ou filtro.'}
) : (
{filtered.map(item => ( setDetailId(item.id)} /> ))}
)} setShowNew(false)} title="Novo item no cofre"> setDetailId(null)} title="Detalhes"> {detail && ( { setEditingId(detail.id); setDetailId(null); }} onDelete={() => deleteItem(detail.id)} showToast={showToast} /> )} setEditingId(null)} title="Editar item"> {editing && ( updateItem(editing.id, patch)} /> )}
); }; // ── Linha da lista ───────────────────────────────────────── const VaultListItem = ({ item, onClick }) => { const c = vaultColor(item.type); return (
{item.title}
{vaultLabel(item.type)}{item.data && item.data.user ? ` · ${item.data.user}` : ''}{item.data && item.data.email ? ` · ${item.data.email}` : ''}
); }; // ── Form de criação/edição ───────────────────────────────── const NewVaultItemForm = ({ onSubmit, initial }) => { const [type, setType] = React.useState((initial && initial.type) || 'senha'); const [title, setTitle] = React.useState((initial && initial.title) || ''); const [data, setData] = React.useState((initial && initial.data) || {}); const setField = (k, v) => setData(d => ({ ...d, [k]: v })); const c = vaultColor(type); const canSave = title.trim().length > 0; return (
{!initial && ( <>
Tipo
{VAULT_TYPES.map(t => { const active = type === t.id; return ( ); })}
)} setTitle(e.target.value)} placeholder="Título (ex: Nubank, Gmail pessoal...)" autoFocus style={{ width: '100%', height: 50, borderRadius: 12, border: `1px solid ${CDV.stroke}`, background: CDV.surfaceHi, color: CDV.text, padding: '0 14px', fontSize: 14, outline: 'none', fontFamily: 'inherit', marginBottom: 12, }} /> {/* Campos por tipo */} {type === 'senha' && <> setField('site', v)} placeholder="nubank.com.br" /> setField('user', v)} placeholder="lucas@email.com" /> setField('password', v)} placeholder="••••••••" sensitive /> setField('notes', v)} placeholder="..." textarea /> } {type === 'conta' && <> setField('banco', v)} placeholder="Nubank" />
setField('agencia', v)} placeholder="0001" /> setField('conta', v)} placeholder="12345-6" />
setField('tipoConta', v)} placeholder="Corrente / Poupança" /> setField('notes', v)} placeholder="..." textarea /> } {type === 'email' && <> setField('provedor', v)} placeholder="Gmail, Outlook..." /> setField('email', v)} placeholder="lucas@email.com" /> setField('password', v)} placeholder="••••••••" sensitive /> setField('notes', v)} placeholder="..." textarea /> } {type === 'nota' && <> setField('text', v)} placeholder="Escreva aqui..." textarea big /> }
); }; const VaultField = ({ label, value, onChange, placeholder, sensitive, textarea, big, inline }) => { const [show, setShow] = React.useState(false); const isPassword = sensitive && !show; return (
{label}
{textarea ? (