// fp-dashboard.jsx — Brand list + creation // Exports: Dashboard function Dashboard({ onSelectBrand, onManageUsers }) { const { profile, setProfile } = useAuth(); const isAdmin = profile?.role === 'admin'; const [brands, setBrands] = useState([]); const [loading, setLoading] = useState(true); const [showNew, setShowNew] = useState(false); const [newName, setNewName] = useState(''); const [newUsername, setNewUsername] = useState(''); const [creating, setCreating] = useState(false); // Edit own profile const [showEditProfile, setShowEditProfile] = useState(false); const [editName, setEditName] = useState(''); const [editPassword, setEditPassword] = useState(''); const [editPassword2, setEditPassword2] = useState(''); const [savingProfile, setSavingProfile] = useState(false); const [profileMsg, setProfileMsg] = useState({ text: '', ok: true }); const openEditProfile = () => { setEditName(profile?.name || ''); setEditPassword(''); setEditPassword2(''); setProfileMsg({ text: '', ok: true }); setShowEditProfile(true); }; const saveProfile = async e => { e.preventDefault(); setProfileMsg({ text: '', ok: true }); if (editPassword && editPassword !== editPassword2) { setProfileMsg({ text: 'Las contraseñas no coinciden.', ok: false }); return; } setSavingProfile(true); if (editName.trim() && editName.trim() !== profile?.name) { await sbClient.from('profiles').update({ name: editName.trim() }).eq('id', profile.id); setProfile(p => ({ ...p, name: editName.trim() })); } if (editPassword) { const { error } = await sbClient.auth.updateUser({ password: editPassword }); if (error) { setProfileMsg({ text: 'Error al cambiar contraseña: ' + error.message, ok: false }); setSavingProfile(false); return; } } setSavingProfile(false); setProfileMsg({ text: '¡Cambios guardados correctamente!', ok: true }); setEditPassword(''); setEditPassword2(''); }; const loadBrands = async () => { setLoading(true); let q = sbClient.from('brands').select('*, brand_access(count)').order('created_at', { ascending: false }); if (!isAdmin) { const { data: access } = await sbClient.from('brand_access').select('brand_id').eq('user_id', profile.id); const ids = (access || []).map(a => a.brand_id); if (!ids.length) { setBrands([]); setLoading(false); return; } q = q.in('id', ids); } const { data } = await q; setBrands(data || []); setLoading(false); }; useEffect(() => { if (profile) loadBrands(); }, [profile]); const createBrand = async e => { e.preventDefault(); if (!newName.trim()) return; setCreating(true); const { data, error } = await sbClient.from('brands').insert({ name: newName.trim(), username: newUsername.trim() || newName.toLowerCase().replace(/\s+/g,''), created_by: profile.id, }).select().single(); if (!error && data) { setBrands(b => [data, ...b]); setShowNew(false); setNewName(''); setNewUsername(''); } setCreating(false); }; const deleteBrand = async (id, e) => { e.stopPropagation(); if (!confirm('¿Eliminar esta marca y todos sus posts?')) return; await sbClient.from('brands').delete().eq('id', id); setBrands(b => b.filter(x => x.id !== id)); }; const logout = () => sbClient.auth.signOut(); return (
{/* Top bar */}
Feed Planner
{isAdmin && ( )}
{profile?.name || profile?.email}
{profile?.role}

Marcas

{brands.length} marca{brands.length !== 1 ? 's' : ''} activa{brands.length !== 1 ? 's' : ''}

{isAdmin && ( )}
{loading ? (
) : brands.length === 0 ? (

Sin marcas todavía

{isAdmin &&

Crea tu primera marca para comenzar

}
) : (
{brands.map(b => (
onSelectBrand(b)}>
{b.avatar_url ? ( {b.name} ) : ( {(b.username || b.name).slice(0,1).toUpperCase()} )}
{b.name}
@{b.username}
{b.bio &&
{b.bio}
}
{isAdmin && ( )}
))}
)}
{/* New brand modal */} {showNew && (
setShowNew(false)}>
e.stopPropagation()}>

Nueva marca

setNewName(e.target.value)} required autoFocus/>
setNewUsername(e.target.value.replace('@',''))}/>
)} {/* Edit profile modal */} {showEditProfile && (
setShowEditProfile(false)}>
e.stopPropagation()}>

Mi perfil

{profile?.email}

{profileMsg.text && (
{profileMsg.text}
)}
setEditName(e.target.value)} placeholder="Tu nombre"/>
setEditPassword(e.target.value)} placeholder="Dejar vacío para no cambiar" minLength={6}/>
{editPassword && (
setEditPassword2(e.target.value)} placeholder="Repite la contraseña" minLength={6}/>
)}
)}
); } const dbStyles = { page: { minHeight:'100vh', background:'var(--cream)', display:'flex', flexDirection:'column' }, topbar: { background:'#fff', borderBottom:'1px solid var(--border)', padding:'14px 32px', display:'flex', alignItems:'center', justifyContent:'space-between' }, logo: { fontFamily:"'Playfair Display',serif", fontSize:20, fontWeight:600, display:'flex', alignItems:'center', gap:8 }, dot: { width:8, height:8, borderRadius:'50%', background:'var(--accent)', display:'inline-block' }, content: { flex:1, maxWidth:960, margin:'0 auto', width:'100%', padding:'36px 24px' }, header: { display:'flex', alignItems:'flex-end', justifyContent:'space-between', marginBottom:28 }, title: { fontFamily:"'Playfair Display',serif", fontSize:28, fontWeight:600, letterSpacing:'-0.5px' }, subtitle:{ fontSize:13, color:'var(--ink-muted)', marginTop:4 }, grid: { display:'grid', gridTemplateColumns:'repeat(auto-fill,minmax(260px,1fr))', gap:16 }, card: { background:'#fff', borderRadius:16, overflow:'hidden', cursor:'pointer', boxShadow:'0 1px 4px rgba(28,25,23,0.07)', transition:'all 0.18s', position:'relative' }, cardColor:{ height:90, display:'flex', alignItems:'center', justifyContent:'center', overflow:'hidden' }, cardBody:{ padding:'14px 16px 16px' }, cardDel: { position:'absolute', top:8, right:8, width:28, height:28, background:'rgba(255,255,255,0.85)', border:'none', borderRadius:8, display:'flex', alignItems:'center', justifyContent:'center', cursor:'pointer', color:'var(--ink-soft)', opacity:0, transition:'opacity 0.15s' }, empty: { textAlign:'center', padding:'80px 24px', color:'var(--ink-soft)' }, btnPrimary: { background:'var(--accent)', color:'#fff', border:'none', borderRadius:9, padding:'9px 16px', fontFamily:"'DM Sans',sans-serif", fontSize:13, fontWeight:600, cursor:'pointer', display:'flex', alignItems:'center', gap:6 }, btnOutline: { background:'#fff', color:'var(--ink-soft)', border:'1px solid var(--border)', borderRadius:9, padding:'9px 14px', fontFamily:"'DM Sans',sans-serif", fontSize:13, fontWeight:500, cursor:'pointer', display:'flex', alignItems:'center', gap:6 }, btnIcon: { background:'none', border:'none', cursor:'pointer', color:'var(--ink-soft)', padding:6, borderRadius:8, display:'flex' }, modalBackdrop: { position:'fixed', inset:0, background:'rgba(28,25,23,0.45)', display:'flex', alignItems:'center', justifyContent:'center', zIndex:100, backdropFilter:'blur(4px)' }, modalCard: { background:'#fff', borderRadius:18, padding:'28px 28px 24px', width:380, boxShadow:'0 12px 40px rgba(28,25,23,0.16)' }, field: { display:'flex', flexDirection:'column', gap:5 }, fieldLabel: { fontSize:11, fontWeight:600, color:'var(--ink-soft)', letterSpacing:'0.06em', textTransform:'uppercase' }, input: { background:'var(--cream)', border:'1px solid var(--cream-dark)', borderRadius:8, padding:'9px 12px', fontFamily:"'DM Sans',sans-serif", fontSize:13.5, color:'var(--ink)', outline:'none', width:'100%' }, }; // hover effect via CSS class injection const dashboardCSS = document.createElement('style'); dashboardCSS.textContent = `.db-card:hover { box-shadow: 0 6px 24px rgba(28,25,23,0.12) !important; transform: translateY(-2px); } .db-card:hover .card-del { opacity: 1 !important; }`; document.head.appendChild(dashboardCSS); Object.assign(window, { Dashboard });