// Prodz — Shoutout detail. Reached by tapping a shoutout on a profile. // · owner === 'me' → MINE: a summary card of the call, the people who applied // as tappable avatars, and the chat you had for that role opening inline at // the bottom (no page change). // · owner === other → THEIRS: view the call, Save it, or Apply & reply inline. function statusMeta(L, st) { switch (st) { case 'shortlist': return { label: L('In shortlist', 'Shortlisted'), color: 'var(--prodz-teal)' }; case 'hired': return { label: L('Scelto', 'Hired'), color: 'var(--green)' }; case 'closed': return { label: L('Chiusa', 'Closed'), color: 'var(--text-subtle)' }; default: return { label: L('Nuova', 'New'), color: 'var(--prodz-gold)' }; } } // Shared summary card at the top of the detail. function ShoutoutSummary({ s, owner, L }) { const { Avatar, Tag } = window.ProdzDesignSystem_d0b87b; const mine = owner === 'me'; const open = mine ? s.status === 'open' : true; const prod = s.prod ? PRODUCTIONS.find((x) => x.id === s.prod) : null; const author = mine ? PEOPLE.me : (s.person ? PEOPLE[s.person] : null); const authorName = author ? author.name : s.author; const authorRole = author ? tr(L, author.role) : tr(L, s.role); const authorCity = author ? author.city : s.city; return (
{s.kind === 'job' ? L('Job Call', 'Job Call') : 'Collab'} {s.paid && {L('Retribuito', 'Paid')}} {mine && ( {open ? L('Aperta', 'Open') : L('Chiusa', 'Closed')} )}

{tr(L, s.title)}

{/* author line — for someone else's call, who posted it */} {!mine && (
{authorName}
{authorRole}{authorCity ? ' · ' + authorCity : ''}
)} {s.blurb &&

{tr(L, s.blurb)}

}
{prod && {prod.title}} {tr(L, s.date)} {mine && {s.applicants} {L('candidature', 'applicants')}}
{s.tags.map((t) => {t})}
); } function ShoutoutDetail({ id, owner = 'me' }) { const { L } = useL(); const nav = useNav(); const { Avatar, Button, Tag } = window.ProdzDesignSystem_d0b87b; const mine = owner === 'me'; const s = (mine ? MY_SHOUTOUTS : shoutoutsOf(owner)).find((x) => x.id === id); // ---- MINE: applicants + inline chat ---- const applicants = (s && s.applicantList) || []; const [sel, setSel] = useStateS(null); // selected applicant person id const [threads, setThreads] = useStateS(() => { const o = {}; applicants.forEach((a) => { o[a.person] = a.messages.map((m) => ({ ...m })); }); return o; }); const [draft, setDraft] = useStateS(''); const panelRef = useRefS(null); const selApp = applicants.find((a) => a.person === sel); useEffectS(() => { if (sel && panelRef.current) { const sc = panelRef.current.closest('.prodz-scroll'); if (sc) sc.scrollTo({ top: panelRef.current.offsetTop - 64, behavior: 'smooth' }); } }, [sel]); // ---- THEIRS: save / apply / reply ---- const [saved, setSaved] = useStateS(false); const [applied, setApplied] = useStateS(false); const [showApply, setShowApply] = useStateS(false); const [reply, setReply] = useStateS(''); if (!s) { return (

{L('Shoutout non trovato.', 'Shoutout not found.')}

); } const sendInChat = () => { const t = draft.trim(); if (!t || !sel) return; setThreads((m) => ({ ...m, [sel]: [...(m[sel] || []), { me: true, time: L('adesso', 'now'), text: { it: t, en: t } }] })); setDraft(''); }; const submitApply = () => { nav.applyShoutout(s, reply.trim()); setApplied(true); setShowApply(false); setReply(''); }; return (
{/* ============ MINE: applicants + inline chat ============ */} {mine && ( <>
{L('Candidature', 'Applicants')} · {applicants.length} {applicants.length === 0 ? (

{L('Ancora nessuna candidatura.', 'No applications yet.')}

) : (
{applicants.map((a) => { const p = PEOPLE[a.person]; if (!p) return null; const active = a.person === sel; const st = statusMeta(L, a.status); return ( ); })}
)} {applicants.length > 0 && !sel && (
{L('Tocca un candidato per leggere la chat che hai avuto per il ruolo.', 'Tap an applicant to read the chat you had for the role.')}
)}
{/* inline chat panel — opens below, page stays put */} {selApp && (() => { const p = PEOPLE[selApp.person]; const st = statusMeta(L, selApp.status); const msgs = threads[selApp.person] || []; return (
{/* chat header */}
{p.name}
{tr(L, p.role)} · {p.city}
{st.label}
{/* messages */}
{msgs.map((m, i) => (
{!m.me && }
{tr(L, m.text)}
{m.time}
))}
{/* quick actions */}
{s.status === 'open' && }
{/* composer */} {s.status === 'open' ? (
setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); sendInChat(); } }} placeholder={L('Rispondi a ' + p.name.split(' ')[0] + '…', 'Reply to ' + p.name.split(' ')[0] + '…')} style={{ flex: 1, height: 40, padding: '0 14px', borderRadius: 'var(--radius-pill)', border: 'none', outline: 'none', background: 'var(--surface-card)', boxShadow: 'var(--ring-inset-hairline)', color: 'var(--text-body)', fontSize: 13.5, fontFamily: 'var(--font-ui)' }} />
) : (
{L('Questa call è chiusa.', 'This call is closed.')}
)}
); })()} )} {/* ============ THEIRS: save / apply / reply ============ */} {!mine && (
{!showApply && !applied && (
)} {showApply && (
{s.kind === 'job' ? L('La tua candidatura', 'Your application') : L('La tua proposta', 'Your proposal')}