// Movie Night — Frontend Logic const API = ''; let currentUser = null; let selectedUserIds = []; // --- Auth --- async function checkAuth() { try { const res = await fetch(`${API}/api/auth/me`); if (res.ok) { currentUser = await res.json(); showMainScreen(); } else { showLoginScreen(); } } catch { showLoginScreen(); } } async function login(username, password) { const res = await fetch(`${API}/api/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); if (!res.ok) { const err = await res.json().catch(() => ({ detail: 'Login failed' })); throw new Error(err.detail || 'Login failed'); } currentUser = await res.json(); showMainScreen(); } async function logout() { await fetch(`${API}/api/auth/logout`, { method: 'POST' }); currentUser = null; showLoginScreen(); } // --- Screens --- function showLoginScreen() { document.getElementById('login-screen').classList.remove('hidden'); document.getElementById('main-screen').classList.add('hidden'); } function showMainScreen() { document.getElementById('login-screen').classList.add('hidden'); document.getElementById('main-screen').classList.remove('hidden'); document.getElementById('user-name').textContent = currentUser.name; selectedUserIds = [currentUser.id]; loadUsers(); loadStats(); } // --- Users --- async function loadUsers() { try { const res = await fetch(`${API}/api/users`); if (!res.ok) return; const users = await res.json(); const container = document.getElementById('user-pills'); container.innerHTML = ''; users.forEach(user => { const pill = document.createElement('button'); pill.className = `user-pill border rounded-full px-4 py-1.5 text-sm ${selectedUserIds.includes(user.id) ? 'active' : ''}`; pill.textContent = user.name; pill.onclick = () => toggleUser(user.id, pill); container.appendChild(pill); }); } catch { /* ignore */ } } function toggleUser(userId, pill) { if (selectedUserIds.includes(userId)) { if (selectedUserIds.length > 1) { selectedUserIds = selectedUserIds.filter(id => id !== userId); pill.classList.remove('active'); } } else { selectedUserIds.push(userId); pill.classList.add('active'); } } // --- Mood / Recommendations --- async function findMovies() { const mood = document.getElementById('mood-input').value.trim(); if (!mood) return; document.getElementById('empty-state').classList.add('hidden'); document.getElementById('results').classList.add('hidden'); document.getElementById('error-state').classList.add('hidden'); document.getElementById('loading').classList.remove('hidden'); try { const additionalIds = selectedUserIds.filter(id => id !== currentUser.id); const res = await fetch(`${API}/api/mood`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mood, additional_user_ids: additionalIds }) }); if (!res.ok) { const err = await res.json().catch(() => ({ detail: 'Something went wrong' })); throw new Error(err.detail || 'Failed to get recommendations'); } const data = await res.json(); renderResults(data); } catch (err) { document.getElementById('loading').classList.add('hidden'); document.getElementById('error-message').textContent = err.message; document.getElementById('error-state').classList.remove('hidden'); } } function renderResults(data) { document.getElementById('loading').classList.add('hidden'); const grid = document.getElementById('results-grid'); grid.innerHTML = ''; if (!data.recommendations || data.recommendations.length === 0) { document.getElementById('empty-state').classList.remove('hidden'); return; } data.recommendations.forEach(movie => { const card = document.createElement('div'); card.className = 'movie-card bg-dark-200 rounded-xl overflow-hidden border border-gray-800'; card.innerHTML = `
${movie.title}

${movie.title}

${movie.year ? `${movie.year}` : ''} ${movie.runtime_minutes ? `· ${movie.runtime_minutes}m` : ''} ${movie.community_rating ? `· ★ ${movie.community_rating.toFixed(1)}` : ''} ${movie.content_rating ? `· ${movie.content_rating}` : ''}
${movie.genres.slice(0, 3).map(g => `${g}`).join('')}

${movie.reasoning}

Watch
`; grid.appendChild(card); }); const meta = data.meta; document.getElementById('results-meta').textContent = `Evaluated ${meta.candidates_evaluated} of ${meta.total_unwatched} unwatched movies in ${(meta.processing_time_ms / 1000).toFixed(1)}s`; document.getElementById('results').classList.remove('hidden'); } // --- Library Stats --- async function loadStats() { try { const res = await fetch(`${API}/api/library/stats`); if (!res.ok) return; const stats = await res.json(); document.getElementById('library-stats').textContent = `${stats.total_movies} movies in library${stats.last_sync ? ` · Last synced ${new Date(stats.last_sync).toLocaleString()}` : ''}`; } catch { /* ignore */ } } async function refreshLibrary() { document.getElementById('refresh-btn').textContent = 'Syncing...'; try { await fetch(`${API}/api/library/sync`, { method: 'POST' }); setTimeout(loadStats, 5000); } catch { /* ignore */ } setTimeout(() => { document.getElementById('refresh-btn').textContent = 'Refresh Library'; }, 3000); } // --- Event Listeners --- document.getElementById('login-form').addEventListener('submit', async (e) => { e.preventDefault(); const errorEl = document.getElementById('login-error'); errorEl.classList.add('hidden'); try { await login( document.getElementById('login-username').value, document.getElementById('login-password').value ); } catch (err) { errorEl.textContent = err.message; errorEl.classList.remove('hidden'); } }); document.getElementById('find-btn').addEventListener('click', findMovies); document.getElementById('mood-input').addEventListener('keydown', (e) => { if (e.key === 'Enter') findMovies(); }); document.getElementById('logout-btn').addEventListener('click', logout); document.getElementById('refresh-btn').addEventListener('click', refreshLibrary); // --- Init --- checkAuth();