import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { initializeApp } from 'firebase/app'; import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from 'firebase/auth'; import { getFirestore, doc, collection, onSnapshot, setDoc, deleteDoc, query, where, Timestamp } from 'firebase/firestore'; import { Film, DollarSign, Clock, Play, User, Loader, Home, List, ShoppingCart, AlertTriangle, X } from 'lucide-react'; // --- Global Firebase Configuration Access --- const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : {}; const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null; const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; // --- Movie Data Definition --- const MOVIE_LIST = [ // UPDATED: Added YouTube URL for deep linking { id: 'decaf-origins', title: 'The Decafinator Series (Playlist)', genre: 'Sci-Fi/Comedy', price_rent: 0.00, price_buy: 0.00, is_free: true, duration: 'Playlist', youtube_url: 'https://youtube.com/playlist?list=PLUNRIaIndf2bnq2kTo9iA05-Jtsi8aVfV' }, { id: 'space-odyssey', title: 'A Space Odyssey 2001', genre: 'Sci-Fi', price_rent: 3.99, price_buy: 14.99, is_free: false, duration: '2h 29m' }, { id: 'starship-troopers', title: 'Starship Troopers', genre: 'Action/Sci-Fi', price_rent: 4.99, price_buy: 19.99, is_free: false, duration: '2h 9m' }, { id: 'plan-9', title: 'Plan 9 from Outer Space', genre: 'B-Movie', price_rent: 0.00, price_buy: 0.00, is_free: true, duration: '1h 19m' }, { id: 'grand-budapest', title: 'The Grand Budapest Hotel', genre: 'Comedy/Drama', price_rent: 2.99, price_buy: 12.99, is_free: false, duration: '1h 39m' }, { id: 'ex-machina', title: 'Ex Machina', genre: 'Sci-Fi/Thriller', price_rent: 4.99, price_buy: 15.99, is_free: false, duration: '1h 48m' }, ]; // Utility to calculate rental expiry (48 hours) const getRentalExpiry = () => { const now = new Date(); now.setHours(now.getHours() + 48); return Timestamp.fromDate(now); }; // --- Firebase Initialization and Auth Hook --- function useFirebase() { const [db, setDb] = useState(null); const [auth, setAuth] = useState(null); const [userId, setUserId] = useState(null); const [isLoading, setIsLoading] = useState(true); useEffect(() => { try { if (!firebaseConfig.apiKey) { console.error("Firebase config is missing API key. Check environment setup."); setIsLoading(false); return; } const app = initializeApp(firebaseConfig); const firestore = getFirestore(app); const firebaseAuth = getAuth(app); setDb(firestore); setAuth(firebaseAuth); const unsubscribe = onAuthStateChanged(firebaseAuth, async (user) => { if (user) { setUserId(user.uid); setIsLoading(false); } else { // If no user is authenticated, attempt sign in try { if (initialAuthToken) { await signInWithCustomToken(firebaseAuth, initialAuthToken); } else { await signInAnonymously(firebaseAuth); } // onAuthStateChanged will be triggered again upon successful sign-in } catch (error) { console.error("Firebase Auth Error:", error); // Fallback for environments where auth fails setUserId(crypto.randomUUID()); setIsLoading(false); } } }); return () => unsubscribe(); } catch (e) { console.error("Failed to initialize Firebase:", e); setIsLoading(false); } }, []); return { db, auth, userId, isLoading }; } // --- Movie Library Hook --- function useUserLibrary(db, userId) { const [library, setLibrary] = useState({}); // { movieId: { type: 'OWNED/RENTED', expiry: Timestamp } } const [libraryLoading, setLibraryLoading] = useState(true); const [error, setError] = useState(null); const libraryPath = useMemo(() => { if (userId) { return `artifacts/${appId}/users/${userId}/user_library`; } return null; }, [userId]); useEffect(() => { if (!db || !libraryPath) { setLibraryLoading(false); return; } const q = collection(db, libraryPath); const unsubscribe = onSnapshot(q, (snapshot) => { const newLibrary = {}; snapshot.forEach((doc) => { newLibrary[doc.id] = doc.data(); }); setLibrary(newLibrary); setLibraryLoading(false); }, (err) => { console.error("Firestore Snapshot Error:", err); setError("Failed to load library: " + err.message); setLibraryLoading(false); }); return () => unsubscribe(); }, [db, libraryPath]); // Function to add or update an item in the library const updateLibrary = useCallback(async (movieId, title, type) => { if (!db || !libraryPath) { setError("Database not initialized."); return; } try { const docRef = doc(db, libraryPath, movieId); const data = { title: title, type: type, // 'OWNED' or 'RENTED' rental_expiry: type === 'RENTED' ? getRentalExpiry() : null, purchased_at: Timestamp.now(), }; await setDoc(docRef, data); return true; } catch (e) { console.error("Error updating library:", e); setError(`Failed to ${type === 'OWNED' ? 'buy' : 'rent'} ${title}. Please try again.`); return false; } }, [db, libraryPath]); // Function to remove a movie from the library (e.g., rental expired) - not used in UI but good practice const removeFromLibrary = useCallback(async (movieId) => { if (!db || !libraryPath) return; try { await deleteDoc(doc(db, libraryPath, movieId)); return true; } catch (e) { console.error("Error removing from library:", e); setError(`Failed to remove movie ID ${movieId}.`); return false; } }, [db, libraryPath]); // Logic to determine a movie's current status for the UI const getMovieStatus = useCallback((movie) => { const libraryItem = library[movie.id]; if (libraryItem) { if (libraryItem.type === 'OWNED') { return { status: 'OWNED', label: 'Owned' }; } if (libraryItem.type === 'RENTED') { if (libraryItem.rental_expiry && libraryItem.rental_expiry.toDate() > new Date()) { const hoursLeft = Math.ceil((libraryItem.rental_expiry.toDate() - new Date()) / (1000 * 60 * 60)); return { status: 'RENTED', label: `Rented (${hoursLeft}h left)` }; } else { // Rental expired - theoretically should be removed, but we'll treat it as needing re-rent return { status: 'EXPIRED', label: 'Rental Expired' }; } } } if (movie.is_free) { return { status: 'FREE', label: 'Watch Free' }; } return { status: 'AVAILABLE', label: 'Available' }; }, [library]); return { library, libraryLoading, error, setError, updateLibrary, getMovieStatus }; } // --- Components --- // New handler function to open YouTube link or show generic watch modal const useWatchHandler = (setModal) => useCallback((movie) => { if (movie.youtube_url) { window.open(movie.youtube_url, '_blank'); } else { setModal({ type: 'WATCH', title: `Watching ${movie.title}`, message: `Enjoy watching ${movie.title}! Since this is a simulation, you can now consider it watched.` }); } }, [setModal]); const MovieCard = ({ movie, updateLibrary, getMovieStatus, setModal, handleWatch }) => { const { title, genre, price_rent, price_buy, duration } = movie; const { status, label } = getMovieStatus(movie); const handleAction = (type) => { // Paid movie actions: RENT / BUY if (type === 'RENT' || type === 'BUY') { const action = type === 'RENT' ? 'rent' : 'buy'; setModal({ type: 'CONFIRM', title: `Confirm ${type} Movie`, message: `Do you want to ${action} "${title}" for $${type === 'RENT' ? price_rent.toFixed(2) : price_buy.toFixed(2)}? (This is a simulation, no real money will be charged.)`, onConfirm: async () => { const success = await updateLibrary(movie.id, title, type === 'RENT' ? 'RENTED' : 'OWNED'); if (success) { setModal({ type: 'SUCCESS', title: `${type} Successful!`, message: `You have successfully ${action === 'rent' ? 'rented' : 'bought'} ${title}. Enjoy!` }); } } }); return; } // Free/Watched movie action: FREE if (type === 'FREE') { handleWatch(movie); return; } }; const getButtonClass = (base) => `w-full px-3 py-2 text-sm font-semibold transition duration-200 rounded-lg shadow-md hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-offset-2 ${base}`; return (

{title}

{genre} | {duration}

{label}
{/* Action Button for Free, Owned, and Rented Movies */} {(status === 'FREE' || status === 'OWNED' || status === 'RENTED') && ( )} {/* Rent/Buy options for Available or Expired Movies */} {(status === 'AVAILABLE' || status === 'EXPIRED') && (
)}
); }; const Modal = ({ modal, setModal }) => { if (!modal) return null; const { type, title, message, onConfirm } = modal; const handleClose = () => { setModal(null); }; const handleConfirm = () => { if (onConfirm) { onConfirm(); } handleClose(); }; return (

{title}

{message}

{type === 'CONFIRM' && ( )}
); }; const LibraryView = ({ library, getMovieStatus, handleWatch }) => { const libraryMovies = useMemo(() => { return Object.entries(library).map(([id, item]) => { const movieData = MOVIE_LIST.find(m => m.id === id); if (!movieData) return null; const status = getMovieStatus(movieData); const isExpired = status.status === 'EXPIRED'; return { ...movieData, ...item, statusLabel: status.label, isExpired, expiryDate: item.rental_expiry ? item.rental_expiry.toDate().toLocaleString() : null, }; }).filter(m => m !== null && !m.isExpired); // Filter out expired rentals for a clean view }, [library, getMovieStatus]); if (libraryMovies.length === 0) { return (

Your library is empty. Start renting or buying a movie!

); } return (

Your Collection

{libraryMovies.map(movie => (
{movie.type === 'OWNED' ? : }

{movie.title}

{movie.type === 'OWNED' ? 'Permanent Ownership' : `Rented | Expires: ${movie.expiryDate}`}

{/* UPDATED: Use the dedicated watch handler */}
))}
); }; // --- Main Application Component --- const App = () => { const { db, userId, isLoading } = useFirebase(); const { library, libraryLoading, error, setError, updateLibrary, getMovieStatus } = useUserLibrary(db, userId); const [view, setView] = useState('HOME'); // 'HOME' or 'LIBRARY' const [modal, setModal] = useState(null); // NEW: Memoized watch handler that includes the deep-linking logic const handleWatch = useWatchHandler(setModal); useEffect(() => { if (error) { setModal({ type: 'ERROR', title: 'Database Error', message: error, }); setError(null); // Clear error after showing modal } }, [error, setError]); if (isLoading || libraryLoading) { return (
Loading App...
); } return (
{modal && } {/* Header */}

Movie Stream Sim

{/* User Status */}
User ID: {userId} Balance: $100.00 (Simulated)
{/* Main Content */}
{view === 'HOME' && ( <>

Featured Movies

{MOVIE_LIST.map(movie => ( ))}
)} {view === 'LIBRARY' && ( <>

My Library

Your collection of owned and currently rented movies.

)}
); }; export default App;