/* @component-map * App — Main container, tab navigation, layout shell [app.jsx] * EpisodeBoard — Drag-and-drop episode lineup with status management [components/EpisodeBoard.jsx] * EpisodeForm — Create/edit episode modal form [components/EpisodeForm.jsx] * EpisodeCard — Individual episode card with status badge [components/EpisodeCard.jsx] * StatsHeader — Season overview stats [components/StatsHeader.jsx] * @end-component-map */ import { useMemo, useState } from 'react'; import { useAuth, useCollection } from '@deplixo/sdk'; import { EpisodeBoard } from './components/EpisodeBoard.jsx'; import { EpisodeForm } from './components/EpisodeForm.jsx'; import { StatsHeader } from './components/StatsHeader.jsx'; function App() { const { user, loading, logout } = useAuth(); const { items: guests, add: addGuest, update: updateGuest, loading: guestsLoading } = useCollection('guests', { personal: true, }); const [showForm, setShowForm] = useState(false); const [editingEpisode, setEditingEpisode] = useState(null); const [showGuestForm, setShowGuestForm] = useState(false); const [guestFormError, setGuestFormError] = useState(''); const [guestDraft, setGuestDraft] = useState({ name: '', email: '', phone: '', company: '', bio: '', notes: '', }); const [selectedGuestId, setSelectedGuestId] = useState(''); const teamDisplayName = useMemo(() => { if (!user) return 'Podcast Team'; return user.name || user.email || 'Podcast Team'; }, [user]); const guestOptions = useMemo(() => { return [...(guests || [])].sort((a, b) => { const aName = (a?.name || '').toLowerCase(); const bName = (b?.name || '').toLowerCase(); return aName.localeCompare(bName); }); }, [guests]); function handleEdit(episode) { setEditingEpisode(episode); setShowForm(true); } function handleCloseForm() { setShowForm(false); setEditingEpisode(null); } function handleNewEpisode() { setEditingEpisode(null); setShowForm(true); } function handleGuestInputChange(event) { const { name, value } = event.target; setGuestDraft((current) => ({ ...current, [name]: value })); } async function handleCreateGuest(event) { event.preventDefault(); setGuestFormError(''); if (!guestDraft.name.trim()) { setGuestFormError('Guest name is required.'); return; } const payload = { name: guestDraft.name.trim(), email: guestDraft.email.trim(), phone: guestDraft.phone.trim(), company: guestDraft.company.trim(), bio: guestDraft.bio.trim(), notes: guestDraft.notes.trim(), pastAppearances: [], }; const created = await addGuest(payload); const createdId = created?.id || created?._id || created?.docId; setGuestDraft({ name: '', email: '', phone: '', company: '', bio: '', notes: '' }); setShowGuestForm(false); if (createdId) { setSelectedGuestId(createdId); } } async function handleLinkGuestToEpisode(event) { event.preventDefault(); if (!editingEpisode || !selectedGuestId) return; const guest = (guests || []).find((item) => String(item.id || item._id || item.docId) === String(selectedGuestId)); if (!guest) return; const appearance = { episodeId: editingEpisode.id || editingEpisode._id || editingEpisode.docId || null, episodeTitle: editingEpisode.title || '', linkedAt: new Date().toISOString(), }; const previousAppearances = Array.isArray(guest.pastAppearances) ? guest.pastAppearances : []; await updateGuest(guest.id || guest._id || guest.docId, { pastAppearances: [...previousAppearances, appearance], }); } if (loading) { return
Use your Google account to join the podcast team, manage episode plans, and keep the season schedule in sync.
Create a guest record with contact details and track where they’ve appeared.
Select an existing guest and record this episode in their past appearances.