// 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={
setShowNew(true)} style={{
background: CDV.brandGrad, border: 'none', width: 38, height: 38, borderRadius: 99,
display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff',
boxShadow: '0 6px 20px -6px rgba(177,151,252,0.6)',
}}>
} />
{/* 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 => (
setFilter(f.id)} style={{
flex: '0 0 auto', height: 32, padding: '0 14px', borderRadius: 99,
background: filter === f.id ? CDV.text : CDV.surface,
color: filter === f.id ? '#000' : CDV.textDim,
border: `1px solid ${filter === f.id ? 'transparent' : CDV.stroke}`,
fontSize: 12, fontWeight: 600, fontFamily: 'inherit',
}}>{f.label}
))}
{/* 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 (
setType(t.id)} style={{
padding: '10px 12px', borderRadius: 12, textAlign: 'left',
background: active ? t.color + '22' : CDV.surfaceHi,
border: `1.5px solid ${active ? t.color : CDV.stroke}`,
color: active ? t.color : CDV.textDim,
fontFamily: 'inherit', display: 'flex', alignItems: 'center', gap: 8,
fontSize: 13, fontWeight: 600,
}}>
{t.label}
);
})}
>
)}
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 />
>}
canSave && onSubmit({ type, title: title.trim(), data })} style={{
marginTop: 18, width: '100%', height: 54, borderRadius: 16,
background: CDV.brandGrad, border: 'none', color: '#fff', fontWeight: 600, fontSize: 15,
opacity: canSave ? 1 : 0.4, cursor: canSave ? 'pointer' : 'not-allowed',
fontFamily: 'inherit',
}}>{initial ? 'Salvar alterações' : 'Salvar no cofre'}
);
};
const VaultField = ({ label, value, onChange, placeholder, sensitive, textarea, big, inline }) => {
const [show, setShow] = React.useState(false);
const isPassword = sensitive && !show;
return (
);
};
// ── Detalhe do item ────────────────────────────────────────
const VaultDetail = ({ item, onEdit, onDelete, showToast }) => {
const c = vaultColor(item.type);
const copy = (label, val) => {
if (!val) return;
try { navigator.clipboard.writeText(val); showToast && showToast(`${label} copiado`); } catch (e) {}
};
return (
{vaultLabel(item.type)}
{item.title}
{item.type === 'senha' && <>
copy('Site', item.data.site)} />
copy('Usuário', item.data.user)} />
copy('Senha', item.data.password)} />
{item.data.notes && }
>}
{item.type === 'conta' && <>
copy('Banco', item.data.banco)} />
copy('Agência', item.data.agencia)} />
copy('Conta', item.data.conta)} />
{item.data.notes && }
>}
{item.type === 'email' && <>
copy('E-mail', item.data.email)} />
copy('Senha', item.data.password)} />
{item.data.notes && }
>}
{item.type === 'nota' && (
copy('Anotação', item.data.text)} />
)}
Criado em {new Date(item.createdAt).toLocaleDateString('pt-BR')}
{item.updatedAt !== item.createdAt ? ` · atualizado ${new Date(item.updatedAt).toLocaleDateString('pt-BR')}` : ''}
Editar
Excluir
);
};
const VaultRow = ({ label, value, sensitive, multiline, onCopy }) => {
const [show, setShow] = React.useState(false);
if (!value) return null;
const display = sensitive && !show ? '•'.repeat(Math.min(value.length, 12)) : value;
return (
{label}
{sensitive && (
setShow(s => !s)} style={{
background: 'transparent', border: 'none', color: CDV.textDim, cursor: 'pointer',
width: 24, height: 24, borderRadius: 6, display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>
)}
{onCopy && (
)}
{display}
);
};
Object.assign(window, { VaultScreen });