// memories.jsx — MemoriesPage, CirclePage, BequestsPage, ProfilePage
// t, useState etc. available from components.jsx (loaded first)
// ─── MemoryDrawer ─────────────────────────────────────────────────────────────
function MemoryDrawer({ memory, people, onClose, onSave }) {
const isNew = !memory.id;
const [editing, setEditing] = useState(isNew);
const [kind, setKind] = useState(memory.kind || 'letter');
const [title, setTitle] = useState(memory.title || '');
const [body, setBody] = useState(memory.body || '');
const [assignees, setAssignees] = useState(memory.assignees || []);
const kindOptions = [
{ id: 'letter', label: t.memories.kinds.letter, icon: 'letter' },
{ id: 'audio', label: t.memories.kinds.audio, icon: 'audio' },
{ id: 'photo', label: t.memories.kinds.photo, icon: 'photo' },
{ id: 'video', label: t.memories.kinds.video, icon: 'video' },
{ id: 'doc', label: t.memories.kinds.doc, icon: 'doc' },
];
const kindClass = { letter: 'mem-letter', audio: 'mem-audio', photo: 'mem-photo', video: 'mem-video', doc: 'mem-doc' };
const kindIconColor = { letter: 'var(--ink)', audio: '#fff', photo: 'var(--jade-800)', video: '#fff', doc: 'var(--gr-500)' };
const cls = kindClass[kind] || 'mem-doc';
const iconColor = kindIconColor[kind] || 'var(--ink)';
function toggleAssignee(id) {
setAssignees(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]);
}
function handleSave() {
onSave({
...memory,
id: memory.id || ('m' + Date.now()),
kind,
title,
body,
assignees,
date: memory.date || new Date().toISOString().split('T')[0],
});
onClose();
}
const assignedPeople = assignees.map(id => people.find(p => p.id === id)).filter(Boolean);
return (
<>
{/* Colored cover at top */}
{t.memories.kinds[kind]}
{/* Large centered icon */}
{/* Letter preview inside cover */}
{!editing && kind === 'letter' && body && (
{body}
)}
{/* Kind selector (edit/new only) */}
{editing && (
Tipo
{kindOptions.map(k => (
))}
)}
{/* Title */}
{editing ? (
setTitle(e.target.value)}
style={{ marginBottom: 12, fontSize: 15, fontWeight: 500 }}
autoFocus={isNew}
/>
) : (
{title}
)}
{/* Date (view) */}
{!editing && memory.date && (
{new Date(memory.date).toLocaleDateString('it-IT', { year: 'numeric', month: 'long', day: 'numeric' })}
)}
{/* Body textarea (edit) */}
{editing && (kind === 'letter' || kind === 'doc') && (
{editing ? (
<>
>
) : (
<>
>
)}
>
);
}
// ─── MemoriesPage ─────────────────────────────────────────────────────────────
function MemoriesPage({ data, onUpdate }) {
const seed = data || DOPO.SEED;
const { memories, people } = seed;
const [kindFilter, setKindFilter] = useState('all');
const [detailMemory, setDetailMemory] = useState(null);
const [showNew, setShowNew] = useState(false);
function saveMemory(updated) {
onUpdate(d => ({
...d,
memories: d.memories.some(m => m.id === updated.id)
? d.memories.map(m => m.id === updated.id ? updated : m)
: [...d.memories, updated],
}));
}
const kinds = [
{ id: 'all', label: 'Tutte' },
{ id: 'letter', label: t.memories.kinds.letter, icon: 'letter' },
{ id: 'audio', label: t.memories.kinds.audio, icon: 'audio' },
{ id: 'photo', label: t.memories.kinds.photo, icon: 'photo' },
{ id: 'video', label: t.memories.kinds.video, icon: 'video' },
{ id: 'doc', label: t.memories.kinds.doc, icon: 'doc' },
];
const kindClass = { letter: 'mem-letter', audio: 'mem-audio', photo: 'mem-photo', video: 'mem-video', doc: 'mem-doc' };
const kindIconColor = { letter: 'var(--ink)', audio: '#fff', photo: 'var(--jade-800)', video: '#fff', doc: 'var(--gr-500)' };
const filtered = kindFilter === 'all' ? memories : memories.filter(m => m.kind === kindFilter);
return (
setShowNew(true)}>
{t.memories.add}
}
/>
{/* Kind filter chips */}
{kinds.map(k => (
))}
{/* Memory grid */}
{filtered.map(m => {
const cls = kindClass[m.kind] || 'mem-doc';
const iconColor = kindIconColor[m.kind] || 'var(--ink)';
const label = t.memories.kinds[m.kind] || m.kind;
const assignedPeople = m.assignees.map(id => people.find(p => p.id === id)).filter(Boolean);
return (
setDetailMemory(m)}>
{/* Kind tag */}
{label}
{/* Large centered icon */}
{/* Letter preview */}
{m.kind === 'letter' && (
{m.body}
)}
{/* Audio duration */}
{m.kind === 'audio' && (
)}
{/* Video duration */}
{m.kind === 'video' && (
)}
{/* Photo count */}
{m.kind === 'photo' && (
{m.count} foto
)}
{/* Doc label */}
{m.kind === 'doc' && (
PDF
)}
{/* Assignees */}
{assignedPeople.length > 0 && (
)}
{m.title}
{assignedPeople.length > 0 && (
{t.memories.for_} {assignedPeople.map(p => p.name.split(' ')[0]).join(', ')} ·
)}
{new Date(m.date).toLocaleDateString('it-IT', { year: 'numeric', month: 'short' })}
);
})}
{/* Add new card */}
{/* Detail drawer */}
{detailMemory && (
setDetailMemory(null)}
onSave={saveMemory}
/>
)}
{/* New memory drawer */}
{showNew && (
setShowNew(false)}
onSave={saveMemory}
/>
)}
);
}
// ─── CirclePage ──────────────────────────────────────────────────────────────
function CirclePage({ data }) {
const seed = data || DOPO.SEED;
const { people, services, assets, memories } = seed;
function getPersonCounts(personId) {
const svcCount = services.filter(s => s.assignees.includes(personId)).length;
const astCount = assets.filter(a => a.assignees.includes(personId)).length;
const memCount = memories.filter(m => m.assignees.includes(personId)).length;
return svcCount + astCount + memCount;
}
return (
{t.circle.add}
}
/>
{people.map(p => {
const count = getPersonCounts(p.id);
const itemLabel = count === 1 ? t.circle.items_left_one : t.circle.items_left;
const assignedServices = services.filter(s => s.assignees.includes(p.id));
const assignedAssets = assets.filter(a => a.assignees.includes(p.id));
const assignedMems = memories.filter(m => m.assignees.includes(p.id));
return (
{/* Header */}
{p.invited ? (
{t.circle.invited}
) : (
{t.circle.not_invited}
)}
{/* Contact info */}
{/* Assignment counts */}
{assignedServices.length > 0 && (
{assignedServices.length} servizi
)}
{assignedAssets.length > 0 && (
{assignedAssets.length} asset
)}
{assignedMems.length > 0 && (
{assignedMems.length} memorie
)}
{count === 0 && (
Niente assegnato
)}
{/* Actions */}
{!p.invited && (
)}
);
})}
{/* Add person card */}
{ e.currentTarget.style.borderColor = 'var(--ink)'; e.currentTarget.style.background = 'var(--paper-2)'; }}
onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--line)'; e.currentTarget.style.background = 'transparent'; }}
>
{t.circle.add}
);
}
// ─── BequestsPage ────────────────────────────────────────────────────────────
function BequestsPage({ data }) {
const seed = data || DOPO.SEED;
const { bequests } = seed;
const kindIcon = { letter: 'letter', photo: 'photo', video: 'video', audio: 'audio', service: 'service', doc: 'doc' };
const kindLabel = { letter: 'Lettera', photo: 'Foto', video: 'Video', audio: 'Audio', service: 'Servizio', doc: 'Documento' };
const kindClass = { letter: 'mem-letter', photo: 'mem-photo', video: 'mem-video', audio: 'mem-audio', service: 'mem-doc', doc: 'mem-doc' };
return (
{bequests.map(b => (
{/* Cover */}
{kindLabel[b.kind] || b.kind}
{b.locked && (
)}
{/* From */}
{t.bequests.from}
{b.from.name}
— {b.from.rel}
{/* Title */}
{b.locked ? (
{b.title}
) : b.title}
{/* Date */}
{!b.locked && (
{b.date !== '—' ? new Date(b.date).toLocaleDateString('it-IT', { year: 'numeric', month: 'long', day: 'numeric' }) : b.date}
)}
{/* Locked overlay */}
{b.locked ? (
) : (
)}
))}
);
}
// ─── ProfilePage ─────────────────────────────────────────────────────────────
function ProfilePage({ data, onUpdate }) {
const seed = data || DOPO.SEED;
const { user, mailboxes, inactivity, people, profileProgress } = seed;
const [inact, setInact] = useState(inactivity);
const [saved, setSaved] = useState(false);
const [connectingId, setConnectingId] = useState(null);
function updateInact(updater) {
setInact(prev => {
const next = updater(prev);
onUpdate(d => ({ ...d, inactivity: next }));
return next;
});
setSaved(true);
setTimeout(() => setSaved(false), 1800);
}
function connectMailbox(mbId) {
setConnectingId(mbId);
setTimeout(() => {
onUpdate(d => ({
...d,
mailboxes: d.mailboxes.map(mb =>
mb.id === mbId
? { ...mb, connected: true, lastScan: 'adesso', servicesFound: Math.floor(Math.random() * 50) + 20 }
: mb
),
}));
setConnectingId(null);
}, 1800);
}
const trustedPeople = inact.trustedContacts
.map(id => people.find(p => p.id === id))
.filter(Boolean);
const mailKindLabel = { google: 'Gmail', apple: 'iCloud Mail', microsoft: 'Outlook' };
const mailKindColor = { google: '#B22D2D', apple: '#63939A', microsoft: '#307D86' };
return (
{/* Left column */}
{/* Inactivity */}
{t.profile.inactivity}
{saved && (
Salvato
)}
{t.profile.inactivity_desc}
{inact.enabled && (
Periodo di inattività
{t.profile.months}
{inact.months}
Tentativi di ping
{t.profile.pings}
{inact.pingCount}
Contatti di fiducia
Saranno avvisati dopo i tentativi falliti
{trustedPeople.map(p => (
{p.name.split(' ')[0]}
))}
)}
{/* Mailboxes */}
{t.profile.mailboxes}
{mailboxes.map(mb => (
{mailKindLabel[mb.kind]}
{mb.email}
{mb.connected ? (
<>
{mb.servicesFound} servizi · {mb.lastScan}
>
) : connectingId === mb.id ? (
Connessione…
) : (
)}
))}
{/* Right column */}
{/* User card */}
{/* Progress */}
Profilo completato
{profileProgress}%
{/* Sensitive flows */}
{t.profile.sensitive}
Azioni delicate che richiedono verifica manuale.
{t.profile.report_death}
{t.profile.report_death_desc}
{/* Settings quick links */}
{[
{ icon: 'bell', label: 'Notifiche', desc: 'Email, push, SMS' },
{ icon: 'lock', label: 'Sicurezza', desc: 'Password, 2FA' },
{ icon: 'key', label: 'API & Integrazioni', desc: 'Connessioni esterne' },
{ icon: 'trash', label: 'Elimina account', desc: 'Azione irreversibile', danger: true },
].map((item, i) => (
e.currentTarget.style.background = 'var(--paper-2)'}
onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
>
))}
);
}
Object.assign(window, { MemoriesPage, CirclePage, BequestsPage, ProfilePage });