/** * Component: BookDate Main Page * Documentation: documentation/features/bookdate-prd.md */ 'use client'; import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { Header } from '@/components/layout/Header'; import { CardStack } from '@/components/bookdate/CardStack'; import { LoadingScreen } from '@/components/bookdate/LoadingScreen'; import { SettingsWidget } from '@/components/bookdate/SettingsWidget'; import { AudiobookDetailsModal } from '@/components/audiobooks/AudiobookDetailsModal'; export default function BookDatePage() { const [recommendations, setRecommendations] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [currentIndex, setCurrentIndex] = useState(0); const [lastSwipe, setLastSwipe] = useState(null); const [showUndo, setShowUndo] = useState(false); const [showSettings, setShowSettings] = useState(false); const [isOnboarding, setIsOnboarding] = useState(false); const [checkingOnboarding, setCheckingOnboarding] = useState(true); const [showDetailsModal, setShowDetailsModal] = useState(false); const router = useRouter(); useEffect(() => { checkOnboardingStatus(); }, []); const checkOnboardingStatus = async () => { setCheckingOnboarding(true); try { const accessToken = localStorage.getItem('accessToken'); if (!accessToken) { router.push('/login'); return; } const response = await fetch('/api/bookdate/preferences', { headers: { 'Authorization': `Bearer ${accessToken}`, }, }); const data = await response.json(); if (!response.ok) { console.error('Failed to check onboarding status:', data.error); // Continue to recommendations anyway on error loadRecommendations(); return; } // Check if user has completed onboarding if (!data.onboardingComplete) { // First time user - show onboarding settings setIsOnboarding(true); setShowSettings(true); setLoading(false); } else { // Existing user - load recommendations normally loadRecommendations(); } } catch (error: any) { console.error('Check onboarding error:', error); // Continue to recommendations anyway on error loadRecommendations(); } finally { setCheckingOnboarding(false); } }; const handleOnboardingComplete = () => { // Onboarding is done, now load recommendations setIsOnboarding(false); setShowSettings(false); loadRecommendations(); }; const loadRecommendations = async () => { setLoading(true); setError(null); try { const accessToken = localStorage.getItem('accessToken'); if (!accessToken) { router.push('/login'); return; } const response = await fetch('/api/bookdate/recommendations', { headers: { 'Authorization': `Bearer ${accessToken}`, }, }); const data = await response.json(); if (!response.ok) { setError(data.error || 'Failed to load recommendations'); return; } setRecommendations(data.recommendations || []); setCurrentIndex(0); } catch (error: any) { console.error('Load recommendations error:', error); setError(error.message || 'Failed to load recommendations'); } finally { setLoading(false); } }; const handleSwipe = async ( action: 'left' | 'right' | 'up', markedAsKnown = false ) => { const recommendation = recommendations[currentIndex]; try { const accessToken = localStorage.getItem('accessToken'); await fetch('/api/bookdate/swipe', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}`, }, body: JSON.stringify({ recommendationId: recommendation.id, action, markedAsKnown, }), }); // Store last swipe for undo functionality if (action !== 'right') { setLastSwipe({ recommendation, action, index: currentIndex }); setShowUndo(true); // Hide undo button after 3 seconds setTimeout(() => { setShowUndo(false); }, 3000); } // Note: currentIndex is now incremented in handleSwipeComplete // after animations finish } catch (error) { console.error('Swipe error:', error); // Don't block user, just log error } }; const handleSwipeComplete = () => { // Increment currentIndex after animations complete setCurrentIndex((prev) => prev + 1); // Check if we need to load more recommendations if (currentIndex + 1 >= recommendations.length) { // At the end - could auto-load more or show empty state } }; const handleUndo = async () => { if (!lastSwipe || lastSwipe.action === 'right') { return; } try { const accessToken = localStorage.getItem('accessToken'); const response = await fetch('/api/bookdate/undo', { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, }, }); if (response.ok) { setLastSwipe(null); setShowUndo(false); // Reload recommendations to include restored card (which will be at index 0) await loadRecommendations(); // Reset to first card (the restored card is now at the front) setCurrentIndex(0); } } catch (error) { console.error('Undo error:', error); } }; const handleGenerateMore = async () => { setLoading(true); setError(null); try { const accessToken = localStorage.getItem('accessToken'); const response = await fetch('/api/bookdate/generate', { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, }, }); const data = await response.json(); if (!response.ok) { setError(data.error || 'Failed to generate recommendations'); return; } setRecommendations(data.recommendations || []); setCurrentIndex(0); } catch (error: any) { console.error('Generate error:', error); setError(error.message || 'Failed to generate recommendations'); } finally { setLoading(false); } }; const handleShowDetails = () => { console.log('Opening details modal for:', recommendations[currentIndex]); const currentRec = recommendations[currentIndex]; const asin = currentRec?.asin || currentRec?.audnexusAsin; if (asin) { setShowDetailsModal(true); } else { console.error('No ASIN available for current recommendation'); } }; const handleCloseDetails = () => { setShowDetailsModal(false); }; // Loading state (checking onboarding or loading recommendations) if (loading || checkingOnboarding) { return ; } // Onboarding state - show settings modal only if (isOnboarding) { return (

Welcome to BookDate!

Let's customize your recommendations to get started

{/* Settings Widget */} setShowSettings(false)} isOnboarding={isOnboarding} onOnboardingComplete={handleOnboardingComplete} />
); } // Error state if (error) { return (

âš ī¸ Could not load recommendations

{error}

); } // Empty state - no recommendations if (recommendations.length === 0 || currentIndex >= recommendations.length) { return (

🎉 You've seen all our current recommendations!

Want more suggestions based on your preferences?

); } return (
{/* Settings button - positioned to avoid card overlap */} {/* Progress indicator */}
{currentIndex + 1} / {recommendations.length}
{/* Card Stack */} {/* Undo button */} {showUndo && lastSwipe && ( )} {/* Mobile swipe hint - more compact on mobile */}

Swipe left to reject, right to request, up to dismiss

{/* Settings Widget */} setShowSettings(false)} isOnboarding={isOnboarding} onOnboardingComplete={handleOnboardingComplete} /> {/* Audiobook Details Modal */} {showDetailsModal && recommendations[currentIndex] && (() => { const currentRec = recommendations[currentIndex]; const asin = currentRec.asin || currentRec.audnexusAsin; return asin ? ( ) : null; })()}
); }