import { useEffect, useState, useCallback } from 'react'; import { useCollection, usePresence, useBroadcast, exportCSV } from '@deplixo/sdk'; import { PlaylistView } from './components/PlaylistView.jsx'; import { AddSongModal } from './components/AddSongModal.jsx'; function App() { const [showAddModal, setShowAddModal] = useState(false); const [identity, setIdentity] = useState(null); const [identityLoading, setIdentityLoading] = useState(true); const [nowPlaying, setNowPlaying] = useState(null); const [shareStatus, setShareStatus] = useState(''); const [exportStatus, setExportStatus] = useState(''); const { items: identityItems, loading: identityCollectionLoading, add: addIdentity, update: updateIdentity, } = useCollection('user-identities', { personal: true }); const { items: archivedPlaylistHistory, loading: archivedHistoryLoading, } = useCollection('playlist-history', { personal: true }); const presenceUserData = identity ? { userId: identity.userId, displayName: identity.displayName, } : null; const { users: presentUsers, update: updatePresence } = usePresence(presenceUserData); const handleNowPlayingBroadcast = useCallback((data) => { if (!data) return; if (data.action === 'now-playing-sync' && data.track) { setNowPlaying(data.track); } }, []); const { send: sendNowPlaying } = useBroadcast('now-playing-sync', handleNowPlayingBroadcast); useEffect(() => { const ensureIdentity = async () => { try { if (identityCollectionLoading) return; let current = identityItems?.[0]; if (!current) { const generatedId = typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : `user_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`; const record = { displayName: `Listener ${generatedId.slice(0, 4).toUpperCase()}`, userId: generatedId, createdAt: new Date().toISOString(), }; await addIdentity(record); current = record; } else if (!current.userId) { const generatedId = typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : `user_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`; const updated = { ...current, userId: generatedId, displayName: current.displayName || `Listener ${generatedId.slice(0, 4).toUpperCase()}`, }; if (current.id) { await updateIdentity(current.id, updated); } current = updated; } setIdentity(current); } catch (error) { console.error('Failed to initialize user identity:', error); } finally { setIdentityLoading(false); } }; ensureIdentity(); }, [identityCollectionLoading, identityItems, addIdentity, updateIdentity]); useEffect(() => { if (identity && updatePresence) { updatePresence({ userId: identity.userId, displayName: identity.displayName, }); } }, [identity, updatePresence]); const handleAddSongClick = useCallback(() => { setShowAddModal(true); }, []); const handleNowPlayingChange = useCallback( (track) => { setNowPlaying(track); if (sendNowPlaying) { sendNowPlaying({ action: 'now-playing-sync', track, updatedAt: new Date().toISOString(), }); } }, [sendNowPlaying] ); const handleSharePlaylist = useCallback(async () => { const playlistUrl = typeof window !== 'undefined' ? window.location.href : ''; try { if (navigator?.clipboard?.writeText) { await navigator.clipboard.writeText(playlistUrl); setShareStatus('Link copied!'); } else { window.prompt('Copy this playlist link:', playlistUrl); setShareStatus('Link ready to copy'); } } catch (error) { console.error('Failed to copy playlist link:', error); setShareStatus('Could not copy link'); } window.clearTimeout(handleSharePlaylist._timer); handleSharePlaylist._timer = window.setTimeout(() => setShareStatus(''), 2500); }, []); const handleExportArchivedHistory = useCallback(() => { try { const rows = (archivedPlaylistHistory || []).map((snapshot, index) => { const playlistItems = snapshot?.items || snapshot?.tracks || snapshot?.songs || []; const itemCount = Array.isArray(playlistItems) ? playlistItems.length : 0; const topSong = Array.isArray(playlistItems) && playlistItems[0] ? playlistItems[0]?.title || playlistItems[0]?.name || '' : ''; return { snapshotId: snapshot?.id || `snapshot-${index + 1}`, week: snapshot?.week || snapshot?.weekStart || snapshot?.date || snapshot?.createdAt || '', createdAt: snapshot?.createdAt || '', itemCount, topSong, archivedBy: snapshot?.archivedBy || snapshot?.createdBy || snapshot?.userId || '', source: snapshot?.source || 'weekly snapshot', }; }); exportCSV(rows, { filename: 'playlist-history.csv', columns: ['snapshotId', 'week', 'createdAt', 'itemCount', 'topSong', 'archivedBy', 'source'], }); setExportStatus('CSV downloaded'); window.clearTimeout(handleExportArchivedHistory._timer); handleExportArchivedHistory._timer = window.setTimeout(() => setExportStatus(''), 2500); } catch (error) { console.error('Failed to export playlist history CSV:', error); setExportStatus('Export failed'); window.clearTimeout(handleExportArchivedHistory._timer); handleExportArchivedHistory._timer = window.setTimeout(() => setExportStatus(''), 2500); } }, [archivedPlaylistHistory]); return (
🎵

Collab Playlist

Add songs, vote on the vibe

{presentUsers?.length ? `${presentUsers.length} here` : 'Room empty'}
{identity && (
{identity.displayName}
)}
{(shareStatus || exportStatus) && (
{shareStatus || exportStatus}
)}
Currently in the room {presentUsers?.length ? `${presentUsers.length} connected` : 'No active listeners'}
{(presentUsers || []).length > 0 ? ( presentUsers.map((user, index) => { const userId = user?.userId || user?.id || `presence-${index}`; const displayName = user?.displayName || user?.name || 'Anonymous Listener'; return (
{displayName.slice(0, 1).toUpperCase()} {displayName}
); }) ) : (
Be the first one here — presence updates in real time.
)}
{nowPlaying && (
Now playing
{nowPlaying.title || 'Unknown track'} {nowPlaying.artist ? `by ${nowPlaying.artist}` : ''}
)}
{showAddModal && ( setShowAddModal(false)} /> )}
); } export { App }; ReactDOM.createRoot(document.getElementById('root')).render();