# CineMuse Movie Web Application - Project Documentation
## Table of Contents
1. [Project Overview](#project-overview)
2. [Technology Stack](#technology-stack)
3. [Project Structure](#project-structure)
4. [Key Components](#key-components)
5. [Code Samples](#code-samples)
6. [Setup and Installation](#setup-and-installation)
7. [Deployment](#deployment)
## Project Overview
CineMuse is a modern React-based web application that allows users to search and
discover movies using the OMDB API. The application features a responsive design
with a cinema-themed UI, providing an intuitive interface for movie exploration.
### Key Features
- Movie search functionality with real-time results
- Responsive grid layout for movie display
- Detailed movie information
- Error handling and user feedback
- Mobile-friendly design
## Technology Stack
- **Frontend Framework**: React 18 with TypeScript
- **Build Tool**: Vite
- **Styling**: Tailwind CSS
- **UI Components**: shadcn/ui with Radix UI
- **State Management**: React Hooks and Context
- **Data Fetching**: Fetch API with React Query
- **Routing**: React Router DOM
## Project Structure
```
src/
├── components/
│ ├── ui/ # Reusable UI components
│ ├── [Link] # Movie display component
│ ├── [Link] # Grid layout for movies
│ └── [Link] # Search input component
├── hooks/
│ └── [Link] # Custom hook for movie data
├── lib/
│ └── [Link] # Utility functions
├── pages/
│ ├── [Link] # Main page
│ ├── [Link] # Movie detail page
│ └── [Link] # 404 page
├── [Link] # Main application component
└── [Link] # Entry point
```
## Key Components
### 1. Main Application Component ([Link])
The main application component sets up routing, providers, and lazy loading.
### 2. Index Page ([Link])
The main page that includes the search functionality and movie grid display.
### 3. Movie Card Component ([Link])
A reusable component that displays individual movie information.
### 4. Movie Search Component ([Link])
The search input component with debounced search functionality.
### 5. useMovies Hook ([Link])
A custom hook that handles movie data fetching from the OMDB API.
## Code Samples
### 1. Main Application Component (src/[Link])
```tsx
import { Suspense, lazy } from "react";
import { Toaster } from "@/components/ui/toaster";
import { Toaster as Sonner } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Index from "./pages/Index";
import MovieDetail from "./pages/MovieDetail";
// Lazy load non-critical pages
const NotFound = lazy(() => import("./pages/NotFound"));
const queryClient = new QueryClient();
const App = () => (
<QueryClientProvider client={queryClient}>
<TooltipProvider>
<Toaster />
<Sonner />
<BrowserRouter>
<Routes>
<Route path="/" element={<Index />} />
<Route path="/movie/:id" element={<MovieDetail />} />
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={
<Suspense fallback={<div className="min-h-screen bg-background flex
items-center justify-center">
<div className="text-center">
<div className="w-8 h-8 border-4 border-primary border-t-
transparent rounded-full animate-spin mx-auto mb-4"></div>
<p className="text-muted-foreground">Loading...</p>
</div>
</div>}>
<NotFound />
</Suspense>
} />
</Routes>
</BrowserRouter>
</TooltipProvider>
</QueryClientProvider>
);
export default App;
```
### 2. Main Page Component (src/pages/[Link])
```tsx
import { useState } from "react";
import MovieSearch from "@/components/MovieSearch";
import MovieGrid from "@/components/MovieGrid";
import { useMovies } from "@/hooks/useMovies";
import { Film, Sparkles } from "lucide-react";
const Index = () => {
const { movies, isLoading, searchMovies } = useMovies();
const [hasSearched, setHasSearched] = useState(false);
const handleSearch = (query: string) => {
setHasSearched(true);
searchMovies(query);
};
return (
<div className="min-h-screen bg-background">
{/* Hero Section */}
<div className="relative overflow-hidden">
<div className="absolute inset-0 bg-cinema-gradient opacity-20" />
<div className="relative container mx-auto safe-area-padding px-4 py-12
sm:py-16 md:py-20">
<div className="text-center mb-12 sm:mb-16">
<div className="flex items-center justify-center gap-2 sm:gap-3 mb-4
sm:mb-6">
<Film className="w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 text-primary
flex-shrink-0" />
<h1 className="text-3xl sm:text-4xl md:text-5xl lg:text-7xl font-bold
bg-gradient-to-r from-primary via-primary to-accent bg-clip-text text-transparent">
CineMuse
</h1>
<Sparkles className="w-6 h-6 sm:w-7 sm:h-7 md:w-8 md:h-8 text-accent
flex-shrink-0" />
</div>
<p className="text-base sm:text-lg md:text-xl lg:text-2xl text-muted-
foreground mb-6 sm:mb-8 max-w-3xl mx-auto px-2">
Discover your next favorite movie, series, or episode with our
powerful search engine
</p>
<MovieSearch onSearch={handleSearch} isLoading={isLoading} />
</div>
</div>
</div>
{/* Results Section */}
<div className="container mx-auto safe-area-padding px-4 pb-16 sm:pb-20">
{hasSearched && (
<div className="mb-6 sm:mb-8">
<h2 className="text-2xl sm:text-3xl font-bold text-foreground mb-2">
Search Results
</h2>
<div className="w-16 sm:w-20 h-1 bg-primary rounded-full" />
</div>
)}
<MovieGrid
movies={movies}
isLoading={isLoading}
onMovieClick={(id) => {
[Link] = `/movie/${id}`;
}}
/>
{!hasSearched && !isLoading && (
<div className="text-center py-12 sm:py-16 md:py-20">
<div className="text-6xl sm:text-7xl md:text-8xl mb-6 sm:mb-8">🎭</div>
<h3 className="text-2xl sm:text-3xl font-bold text-foreground mb-3
sm:mb-4 px-4">
Welcome to CineMuse
</h3>
<p className="text-lg sm:text-xl text-muted-foreground mb-6 sm:mb-8
max-w-2xl mx-auto px-4">
Your ultimate destination for movie discovery. Search through
millions of movies,
TV series, and episodes to find your next entertainment obsession.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6
sm:gap-8 max-w-4xl mx-auto px-4">
<div className="text-center p-4 sm:p-6 bg-card/30 rounded-xl border
border-border/50 mobile-shadow">
<div className="text-3xl sm:text-4xl mb-3 sm:mb-4">🔍</div>
<h4 className="text-base sm:text-lg font-semibold text-foreground
mb-2">Smart Search</h4>
<p className="text-sm sm:text-base text-muted-foreground">Find
movies by title, year, or genre with our intelligent search</p>
</div>
<div className="text-center p-4 sm:p-6 bg-card/30 rounded-xl border
border-border/50 mobile-shadow">
<div className="text-3xl sm:text-4xl mb-3 sm:mb-4">⭐</div>
<h4 className="text-base sm:text-lg font-semibold text-foreground
mb-2">Detailed Info</h4>
<p className="text-sm sm:text-base text-muted-foreground">Get
comprehensive details including ratings and cast information</p>
</div>
<div className="text-center p-4 sm:p-6 bg-card/30 rounded-xl border
border-border/50 mobile-shadow sm:col-span-2 lg:col-span-1">
<div className="text-3xl sm:text-4xl mb-3 sm:mb-4">🎬</div>
<h4 className="text-base sm:text-lg font-semibold text-foreground
mb-2">All Formats</h4>
<p className="text-sm sm:text-base text-muted-foreground">Explore
movies, TV series, documentaries, and more</p>
</div>
</div>
</div>
)}
</div>
</div>
);
};
export default Index;
```
### 3. Movie Card Component (src/components/[Link])
```tsx
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Star, Calendar } from "lucide-react";
interface MovieCardProps {
movie: {
imdbID: string;
Title: string;
Year: string;
Poster: string;
Type: string;
imdbRating?: string;
};
onClick?: (id: string) => void;
}
const MovieCard = ({ movie, onClick }: MovieCardProps) => {
const handleClick = () => {
if (onClick) {
onClick([Link]);
}
};
return (
<Card
className="group cursor-pointer transition-all duration-300 hover:shadow-
cinema bg-card-gradient border-border/50 sm:hover:scale-105 sm:hover:-translate-y-1
touch-target"
onClick={handleClick}
>
<CardContent className="p-0">
<div className="relative overflow-hidden rounded-lg">
<img
src={[Link] !== 'N/A' ? [Link] : '/[Link]'}
alt={[Link]}
className="w-full aspect-[2/3] object-cover transition-transform
duration-300 sm:group-hover:scale-110"
onError={(e) => {
[Link] = '/[Link]';
}}
/>
<div className="absolute inset-0 bg-gradient-to-t from-background/80 via-
transparent to-transparent opacity-0 sm:group-hover:opacity-100 transition-opacity
duration-300" />
{/* Mobile-friendly overlay - always visible on mobile */}
<div className="absolute bottom-0 left-0 right-0 p-2 sm:p-4 bg-gradient-
to-t from-background/90 to-transparent sm:transform sm:translate-y-full sm:group-
hover:translate-y-0 sm:transition-transform sm:duration-300">
<Badge variant="secondary" className="mb-1 sm:mb-2 text-xs
bg-primary/20 text-primary-foreground border-primary/30">
{[Link](0).toUpperCase() + [Link](1)}
</Badge>
<h3 className="font-bold text-foreground text-sm sm:text-lg mb-1 line-
clamp-2">
{[Link]}
</h3>
<div className="flex items-center gap-1 sm:gap-2 text-muted-
foreground">
<Calendar className="w-3 h-3 sm:w-4 sm:h-4 flex-shrink-0" />
<span className="text-xs sm:text-sm">{[Link]}</span>
{[Link] && (
<>
<Star className="w-3 h-3 sm:w-4 sm:h-4 ml-1 sm:ml-2 text-cinema-
gold flex-shrink-0" />
<span className="text-xs sm:text-sm text-cinema-
gold">{[Link]}</span>
</>
)}
</div>
</div>
</div>
</CardContent>
</Card>
);
};
export default MovieCard;
```
### 4. Custom Hook for Movie Data (src/hooks/[Link])
```tsx
import { useState } from "react";
import { useToast } from "@/hooks/use-toast";
interface Movie {
imdbID: string;
Title: string;
Year: string;
Poster: string;
Type: string;
}
interface MovieResponse {
Search: Movie[];
totalResults: string;
Response: string;
Error?: string;
}
export const useMovies = () => {
const [movies, setMovies] = useState<Movie[]>([]);
const [isLoading, setIsLoading] = useState(false);
const { toast } = useToast();
const searchMovies = async (query: string) => {
setIsLoading(true);
try {
// Using a demo API key - in production, this should be handled more securely
const API_KEY = 'd99019f'; // Updated API key
const response = await fetch(
`[Link]
{encodeURIComponent(query)}&type=movie`
);
const data: MovieResponse = await [Link]();
if ([Link] === "True" && [Link]) {
setMovies([Link]);
toast({
title: "Movies found!",
description: `Found ${[Link]} movies matching "${query}"`,
});
} else {
setMovies([]);
toast({
title: "No movies found",
description: [Link] || `No movies found for "${query}"`,
variant: "destructive",
});
}
} catch (error) {
[Link]("Error fetching movies:", error);
setMovies([]);
toast({
title: "Error",
description: "Failed to fetch movies. Please check your API key and try
again.",
variant: "destructive",
});
} finally {
setIsLoading(false);
}
};
return {
movies,
isLoading,
searchMovies,
};
};
```
### 5. UI Button Component (src/components/ui/[Link])
```tsx
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-
sm font-medium ring-offset-background transition-colors focus-visible:outline-none
focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2
disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none
[&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-
foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends [Link]<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = [Link]<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
[Link] = "Button"
export { Button, buttonVariants }
```
## Setup and Installation
### Prerequisites
- [Link] (version 18 or higher)
- npm or yarn package manager
### Installation Steps
1. Clone the repository:
```bash
git clone <YOUR_GIT_URL>
```
2. Navigate to the project directory:
```bash
cd vido-muse-react-43
```
3. Install dependencies:
```bash
npm install
```
4. Start the development server:
```bash
npm run dev
```
The application will be available at `[Link]
### Build for Production
To create a production build:
```bash
npm run build
```
To preview the production build:
```bash
npm run preview
```
## Deployment
This project can be deployed using Lovable platform:
1. Open [Lovable]([Link]
83655123491a)
2. Click on "Share" -> "Publish"
For custom domain configuration:
1. Navigate to Project > Settings > Domains
2. Click "Connect Domain"
3. Follow the instructions in the [Custom Domain
Guide]([Link]
---
*This documentation provides a comprehensive overview of the CineMuse movie web
application, including its architecture, key components, and implementation
details. All code samples are extracted directly from the project source files.*