/* === library.jsx ==================================================== Login screen + Demo library home page for UpFlux Studio. ===================================================================== */ const DEFAULT_STUDIO_PASSWORD = 'upflux2026'; /* Read the current password from localStorage; falls back to the default on first run. Persisted plain (no hashing) — this is a soft access gate for sales team, not a security boundary. */ function getStudioPassword() { try { const stored = localStorage.getItem('studio_password'); return (stored && stored.length > 0) ? stored : DEFAULT_STUDIO_PASSWORD; } catch { return DEFAULT_STUDIO_PASSWORD; } } function setStudioPassword(next) { try { localStorage.setItem('studio_password', next); } catch {} } /* ================================================================== LOGIN SCREEN ================================================================== */ function LoginScreen({ onAuth }) { const [pwd, setPwd] = React.useState(''); const [err, setErr] = React.useState(false); const submit = (e) => { e?.preventDefault(); if (pwd === getStudioPassword()) onAuth(); else { setErr(true); setTimeout(() => setErr(false), 1800); } }; return (
U UpFlux Studio

Acessar o editor de demos

Apenas AEs e Sales Engineers da UpFlux. O acesso público à demo do prospect não exige senha.

setPwd(e.target.value)} placeholder="••••••••" />
{err && (
Senha incorreta — tente novamente
)}
Studio {window.STUDIO_DATA.STUDIO_VERSION} · build {window.STUDIO_DATA.STUDIO_BUILD_DATE}
); } /* ================================================================== LIBRARY SCREEN ================================================================== */ function LibraryScreen({ library, onOpen, onClone, onDelete, onCreate, onLogout }) { const [filter, setFilter] = React.useState('all'); const [query, setQuery] = React.useState(''); const [menuOpenId, setMenuOpenId] = React.useState(null); const [showNewForm, setShowNewForm] = React.useState(false); const [showSettings, setShowSettings] = React.useState(false); React.useEffect(() => { const close = () => setMenuOpenId(null); window.addEventListener('click', close); return () => window.removeEventListener('click', close); }, []); const filtered = library.filter(d => { if (filter !== 'all' && d.status !== filter) return false; if (query && !`${d.name} ${d.prospect} ${d.industry}`.toLowerCase().includes(query.toLowerCase())) return false; return true; }); const counts = { all: library.length, draft: library.filter(d => d.status === 'draft').length, ready: library.filter(d => d.status === 'ready').length, shared: library.filter(d => d.status === 'shared').length, }; return (
U UpFlux Studio
{window.STUDIO_DATA.STUDIO_VERSION}
Abrir Nous

Biblioteca de demos

Crie uma demo nova, abra uma existente ou clone para começar rápido a partir de um template.

{[ { id: 'all', label: 'Todas' }, { id: 'draft', label: 'Rascunho' }, { id: 'ready', label: 'Prontas' }, { id: 'shared', label: 'Compartilhadas' }, ].map(f => ( ))}
setQuery(e.target.value)} />
{filtered.map((d) => ( { e.stopPropagation(); setMenuOpenId(menuOpenId === d.id ? null : d.id); }} onOpen={() => onOpen(d.id)} onClone={() => { onClone(d.id); setMenuOpenId(null); }} onDelete={() => { if (confirm(`Excluir a demo "${d.name}"? Esta ação não pode ser desfeita.`)) { onDelete(d.id); } setMenuOpenId(null); }} /> ))}
{filtered.length === 0 && (
Nenhuma demo encontrada para esses filtros.
)} {showNewForm && ( setShowNewForm(false)} onCreate={(name) => { setShowNewForm(false); onCreate(name); }} onClone={(id) => { setShowNewForm(false); onClone(id); }} /> )} {showSettings && ( setShowSettings(false)} /> )}
); } /* ================================================================== SETTINGS MODAL — change password + studio info ================================================================== */ function SettingsModal({ onClose }) { const [current, setCurrent] = React.useState(''); const [next, setNext] = React.useState(''); const [confirm, setConfirm] = React.useState(''); const [feedback, setFeedback] = React.useState(null); /* { kind: 'ok'|'err', msg } */ const [show, setShow] = React.useState(false); const submit = (e) => { e?.preventDefault(); const stored = getStudioPassword(); if (current !== stored) { setFeedback({ kind: 'err', msg: 'Senha atual incorreta.' }); return; } if (!next || next.length < 4) { setFeedback({ kind: 'err', msg: 'A nova senha precisa ter ao menos 4 caracteres.' }); return; } if (next !== confirm) { setFeedback({ kind: 'err', msg: 'A confirmação não bate com a nova senha.' }); return; } setStudioPassword(next); setFeedback({ kind: 'ok', msg: 'Senha atualizada. Use a nova senha na próxima vez que entrar.' }); setCurrent(''); setNext(''); setConfirm(''); }; const reset = () => { if (confirm('Restaurar a senha padrão "upflux2026"?')) { try { localStorage.removeItem('studio_password'); } catch {} setFeedback({ kind: 'ok', msg: 'Senha restaurada para o padrão "upflux2026".' }); } }; return (
e.stopPropagation()}>
Configurações do Studio
Acesso interno · {window.STUDIO_DATA.STUDIO_VERSION} · build {window.STUDIO_DATA.STUDIO_BUILD_DATE}
Senha do editor Quem precisa entrar em {location.host || 'demo.upflux.ai'} usa esta senha. Persiste no navegador — cada AE precisa configurar a sua vez ou compartilhar.
{feedback && (
{feedback.msg}
)}
Resetar senha
Esqueceu a senha? Restaure para a padrão de fábrica (qualquer pessoa no time pode entrar com upflux2026 e configurar a nova).
Sobre o Studio
  • Versão: {window.STUDIO_DATA.STUDIO_VERSION}
  • Build: {window.STUDIO_DATA.STUDIO_BUILD_DATE}
  • Demos no navegador: {JSON.parse(localStorage.getItem('studio_library_v1') || '[]').length}
  • URL pública (após deploy):{' '} demo.upflux.ai
); } /* ================================================================== DEMO CARD ================================================================== */ function DemoCard({ demo, menuOpen, onToggleMenu, onOpen, onClone, onDelete }) { const STATUS_LABEL = { draft: { label: 'Rascunho', cls: 'st-draft' }, ready: { label: 'Pronta', cls: 'st-ready' }, shared: { label: 'Compartilhada', cls: 'st-shared' }, }; const st = STATUS_LABEL[demo.status] || STATUS_LABEL.draft; const versionMismatch = demo.version !== window.STUDIO_DATA.STUDIO_VERSION; return (
{demo.prospect.charAt(0).toUpperCase()}
{/* Subtle decorative shapes */}
{menuOpen && (
e.stopPropagation()}>
)}

{demo.name}

{st.label}
{demo.industry} {demo.persona}
{demo.thumbHint}
{demo.ownerInitials} Editada em {demo.updatedAt} {versionMismatch && ( {demo.version} )} {demo.views > 0 && ( {demo.views} )}
); } /* ================================================================== NEW DEMO MODAL — blank or clone-from-template ================================================================== */ function NewDemoModal({ library, onClose, onCreate, onClone }) { const [name, setName] = React.useState(''); const [mode, setMode] = React.useState('blank'); /* 'blank' | 'clone' */ const [pickedId, setPickedId] = React.useState(library[0]?.id); return (
e.stopPropagation()}>
Nova demo
Comece em branco ou clone uma existente como ponto de partida.
{mode === 'blank' && (
Nome do prospect
setName(e.target.value)} placeholder="Ex: Unimed BH, Magazine Luiza..." />
Aparece no sidebar do Nous, nas saudações e no header da demo.
)} {mode === 'clone' && (
Escolha a base
{library.map(d => ( ))}
)}
); } Object.assign(window, { LoginScreen, LibraryScreen, DemoCard, NewDemoModal, SettingsModal, getStudioPassword, setStudioPassword });