From 0b029bf49cb34b3b6071080f820695e8923d5d06 Mon Sep 17 00:00:00 2001 From: Jordan Smith Date: Thu, 16 Oct 2025 15:51:39 -0400 Subject: [PATCH 1/3] feat: implement front-end scaffolding --- client/.gitignore | 42 +++++ .../components/MovieCard/MovieCard.module.css | 156 ++++++++++++++++++ client/app/components/MovieCard/MovieCard.tsx | 60 +++++++ client/app/components/MovieCard/index.ts | 1 + client/app/components/index.ts | 2 + client/app/components/ui/ErrorDisplay.tsx | 55 ++++++ client/app/components/ui/LoadingSpinner.tsx | 59 +++++++ client/app/components/ui/index.ts | 3 + client/app/globals.css | 11 ++ client/app/layout.tsx | 15 ++ client/app/lib/api.ts | 56 +++++++ client/app/lib/constants.ts | 21 +++ client/app/lib/utils.ts | 40 +++++ client/app/movies/error.tsx | 21 +++ client/app/movies/loading.module.css | 45 +++++ client/app/movies/loading.tsx | 23 +++ client/app/movies/movies.module.css | 55 ++++++ client/app/movies/page.tsx | 30 ++++ client/app/page.module.css | 33 ++++ client/app/page.tsx | 15 ++ client/app/types/movie.ts | 37 +++++ client/eslint.config.mjs | 25 +++ client/next.config.ts | 26 +++ client/package.json | 25 +++ client/tsconfig.json | 28 ++++ 25 files changed, 884 insertions(+) create mode 100644 client/.gitignore create mode 100644 client/app/components/MovieCard/MovieCard.module.css create mode 100644 client/app/components/MovieCard/MovieCard.tsx create mode 100644 client/app/components/MovieCard/index.ts create mode 100644 client/app/components/index.ts create mode 100644 client/app/components/ui/ErrorDisplay.tsx create mode 100644 client/app/components/ui/LoadingSpinner.tsx create mode 100644 client/app/components/ui/index.ts create mode 100644 client/app/globals.css create mode 100644 client/app/layout.tsx create mode 100644 client/app/lib/api.ts create mode 100644 client/app/lib/constants.ts create mode 100644 client/app/lib/utils.ts create mode 100644 client/app/movies/error.tsx create mode 100644 client/app/movies/loading.module.css create mode 100644 client/app/movies/loading.tsx create mode 100644 client/app/movies/movies.module.css create mode 100644 client/app/movies/page.tsx create mode 100644 client/app/page.module.css create mode 100644 client/app/page.tsx create mode 100644 client/app/types/movie.ts create mode 100644 client/eslint.config.mjs create mode 100644 client/next.config.ts create mode 100644 client/package.json create mode 100644 client/tsconfig.json diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..b092b8c --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,42 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions +package-lock.json + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/client/app/components/MovieCard/MovieCard.module.css b/client/app/components/MovieCard/MovieCard.module.css new file mode 100644 index 0000000..770da86 --- /dev/null +++ b/client/app/components/MovieCard/MovieCard.module.css @@ -0,0 +1,156 @@ +/** + * Movies Page Styles + * + * CSS Module for the movies page component. + * Provides responsive grid layout and movie card styling. + */ + +.moviesGrid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 24px; + width: 100%; + max-width: 1200px; +} + +.movieCard { + border: 1px solid #ddd; + border-radius: 8px; + padding: 16px; + background: white; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.movieCard:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +.moviePoster { + position: relative; + width: 100%; + height: 300px; + margin-bottom: 16px; + background: #f5f5f5; + border-radius: 4px; + overflow: hidden; +} + +.moviePoster img { + border-radius: 4px; +} + +.posterPlaceholder { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + color: #666; + font-size: 14px; + text-align: center; + background: #f5f5f5; + border-radius: 4px; +} + +.movieInfo { + margin-bottom: 16px; +} + +.movieTitle { + margin: 0 0 8px 0; + font-size: 18px; + font-weight: 600; + line-height: 1.3; + color: #333; +} + +.movieYear { + margin: 0 0 4px 0; + color: #666; + font-size: 14px; +} + +.movieRating { + margin: 0 0 4px 0; + color: #666; + font-size: 14px; +} + +.movieGenres { + margin: 0; + color: #888; + font-size: 12px; + font-style: italic; +} + +.detailsButton { + width: 100%; + background: #0066cc; + color: white; + border: none; + padding: 12px 16px; + border-radius: 4px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: background-color 0.2s ease; +} + +.detailsButton:hover { + background: #0052a3; +} + +.noMovies { + text-align: center; + padding: 40px; + color: #666; +} + +.pageTitle { + margin: 0 0 16px 0; + font-size: 32px; + color: #333; +} + +.movieCount { + margin: 0 0 32px 0; + color: #666; + font-size: 16px; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .moviesGrid { + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); + gap: 16px; + } + + .moviePoster { + height: 240px; + } + + .pageTitle { + font-size: 28px; + } +} + +@media (max-width: 480px) { + .moviesGrid { + grid-template-columns: 1fr; + gap: 16px; + } + + .movieCard { + padding: 12px; + } + + .moviePoster { + height: 200px; + } + + .pageTitle { + font-size: 24px; + } +} \ No newline at end of file diff --git a/client/app/components/MovieCard/MovieCard.tsx b/client/app/components/MovieCard/MovieCard.tsx new file mode 100644 index 0000000..28551ff --- /dev/null +++ b/client/app/components/MovieCard/MovieCard.tsx @@ -0,0 +1,60 @@ +'use client'; + +import Image from 'next/image'; +import movieStyles from "./MovieCard.module.css"; +import { MovieCardProps } from "../../types/movie"; + +/** + * Movie Card Client Component + * + * This component handles the interactive parts of the movie card, + * such as image error handling, while the parent remains a Server Component. + */ +export default function MovieCard({ movie }: MovieCardProps) { + const handleImageError = () => { + // This will be handled by the Image component's onError prop + console.warn(`Failed to load poster for: ${movie.title}`); + }; + + return ( +
+
+ {movie.poster ? ( + {`${movie.title} + ) : ( +
+ No Poster Available +
+ )} +
+ +
+

{movie.title}

+ {movie.year && ( +

({movie.year})

+ )} + {movie.imdb?.rating && ( +

⭐ {movie.imdb.rating}/10

+ )} + {movie.genres && movie.genres.length > 0 && ( +

+ {movie.genres.slice(0, 3).join(', ')} +

+ )} +
+ + +
+ ); +} \ No newline at end of file diff --git a/client/app/components/MovieCard/index.ts b/client/app/components/MovieCard/index.ts new file mode 100644 index 0000000..43524e4 --- /dev/null +++ b/client/app/components/MovieCard/index.ts @@ -0,0 +1 @@ +export { default } from './MovieCard'; \ No newline at end of file diff --git a/client/app/components/index.ts b/client/app/components/index.ts new file mode 100644 index 0000000..596d313 --- /dev/null +++ b/client/app/components/index.ts @@ -0,0 +1,2 @@ +// Components +export { default as MovieCard } from './MovieCard'; \ No newline at end of file diff --git a/client/app/components/ui/ErrorDisplay.tsx b/client/app/components/ui/ErrorDisplay.tsx new file mode 100644 index 0000000..15b96d4 --- /dev/null +++ b/client/app/components/ui/ErrorDisplay.tsx @@ -0,0 +1,55 @@ +/** + * Error component for displaying error states + */ + +interface ErrorDisplayProps { + message?: string; + onRetry?: () => void; +} + +export default function ErrorDisplay({ + message = "Something went wrong", + onRetry +}: ErrorDisplayProps) { + return ( +
+

Oops!

+

{message}

+ {onRetry && ( + + )} + + +
+ ); +} \ No newline at end of file diff --git a/client/app/components/ui/LoadingSpinner.tsx b/client/app/components/ui/LoadingSpinner.tsx new file mode 100644 index 0000000..31ad4d0 --- /dev/null +++ b/client/app/components/ui/LoadingSpinner.tsx @@ -0,0 +1,59 @@ +'use client'; + +/** + * Loading spinner component + */ + +interface LoadingSpinnerProps { + size?: 'small' | 'medium' | 'large'; + message?: string; +} + +export default function LoadingSpinner({ + size = 'medium', + message = 'Loading...' +}: LoadingSpinnerProps) { + const sizeClasses = { + small: 'w-4 h-4', + medium: 'w-8 h-8', + large: 'w-12 h-12' + }; + + return ( +
+
+ {message &&

{message}

} + + +
+ ); +} \ No newline at end of file diff --git a/client/app/components/ui/index.ts b/client/app/components/ui/index.ts new file mode 100644 index 0000000..81db7a9 --- /dev/null +++ b/client/app/components/ui/index.ts @@ -0,0 +1,3 @@ +// UI Components +export { default as ErrorDisplay } from './ErrorDisplay'; +export { default as LoadingSpinner } from './LoadingSpinner'; \ No newline at end of file diff --git a/client/app/globals.css b/client/app/globals.css new file mode 100644 index 0000000..1d8a143 --- /dev/null +++ b/client/app/globals.css @@ -0,0 +1,11 @@ +html, +body { + max-width: 100vw; + overflow-x: hidden; +} + +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} \ No newline at end of file diff --git a/client/app/layout.tsx b/client/app/layout.tsx new file mode 100644 index 0000000..88c28f9 --- /dev/null +++ b/client/app/layout.tsx @@ -0,0 +1,15 @@ +import "./globals.css"; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} diff --git a/client/app/lib/api.ts b/client/app/lib/api.ts new file mode 100644 index 0000000..1bb94d9 --- /dev/null +++ b/client/app/lib/api.ts @@ -0,0 +1,56 @@ +import { Movie, MoviesApiResponse } from '../types/movie'; + +/** + * API configuration and helper functions + */ + +const API_BASE_URL = process.env.API_URL || 'http://localhost:3001'; + +/** + * Fetches all movies from the Express API + * This function runs on the server during SSR + */ +export async function fetchMovies(limit: number = 50): Promise { + try { + const response = await fetch(`${API_BASE_URL}/api/movies?limit=${limit}`, { + next: { revalidate: 300 }, // Revalidate every 5 minutes + }); + + if (!response.ok) { + throw new Error(`Failed to fetch movies: ${response.status}`); + } + + const result: MoviesApiResponse = await response.json(); + + if (!result.success) { + throw new Error('API returned error response'); + } + + return result.data; + } catch (error) { + console.error('Error fetching movies:', error); + // Return empty array instead of throwing to prevent page crash + return []; + } +} + +/** + * Fetch a single movie by ID + */ +export async function fetchMovieById(id: string): Promise { + try { + const response = await fetch(`${API_BASE_URL}/api/movies/${id}`, { + next: { revalidate: 300 }, + }); + + if (!response.ok) { + return null; + } + + const result = await response.json(); + return result.success ? result.data : null; + } catch (error) { + console.error('Error fetching movie:', error); + return null; + } +} \ No newline at end of file diff --git a/client/app/lib/constants.ts b/client/app/lib/constants.ts new file mode 100644 index 0000000..961d8b2 --- /dev/null +++ b/client/app/lib/constants.ts @@ -0,0 +1,21 @@ +/** + * Application constants + */ + +export const APP_CONFIG = { + name: 'MFlix', + description: 'Browse movies from the sample MFlix database', + defaultMovieLimit: 50, + imageFormats: ['image/avif', 'image/webp'], +} as const; + +export const ROUTES = { + home: '/', + movies: '/movies', + movie: (id: string) => `/movie/${id}`, +} as const; + +export const API_ENDPOINTS = { + movies: '/api/movies', + movie: (id: string) => `/api/movies/${id}`, +} as const; \ No newline at end of file diff --git a/client/app/lib/utils.ts b/client/app/lib/utils.ts new file mode 100644 index 0000000..d3d00cd --- /dev/null +++ b/client/app/lib/utils.ts @@ -0,0 +1,40 @@ +/** + * Utility functions for the application + */ + +/** + * Formats a movie year for display + */ +export function formatYear(year?: number): string { + return year ? `(${year})` : ''; +} + +/** + * Formats movie genres for display + */ +export function formatGenres(genres?: string[], maxGenres: number = 3): string { + if (!genres || genres.length === 0) return ''; + return genres.slice(0, maxGenres).join(', '); +} + +/** + * Formats IMDB rating for display + */ +export function formatRating(rating?: number): string { + return rating ? `⭐ ${rating}/10` : ''; +} + +/** + * Truncates text to a specified length + */ +export function truncateText(text: string, maxLength: number): string { + if (text.length <= maxLength) return text; + return `${text.substring(0, maxLength)}...`; +} + +/** + * Generates a placeholder image URL for broken images + */ +export function getPlaceholderImage(width: number = 300, height: number = 450): string { + return `data:image/svg+xml,%3Csvg width='${width}' height='${height}' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' fill='%23f5f5f5'/%3E%3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' fill='%23666' font-family='Arial, sans-serif' font-size='16'%3ENo Poster Available%3C/text%3E%3C/svg%3E`; +} \ No newline at end of file diff --git a/client/app/movies/error.tsx b/client/app/movies/error.tsx new file mode 100644 index 0000000..e063d3c --- /dev/null +++ b/client/app/movies/error.tsx @@ -0,0 +1,21 @@ +'use client'; + +import { ErrorDisplay } from '../components/ui'; + +/** + * Error boundary for movies page + */ +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + return ( + + ); +} \ No newline at end of file diff --git a/client/app/movies/loading.module.css b/client/app/movies/loading.module.css new file mode 100644 index 0000000..8f2d1d2 --- /dev/null +++ b/client/app/movies/loading.module.css @@ -0,0 +1,45 @@ +/** + * Loading Component Styles + * + * CSS Module for the movies loading component. + */ + +.loadingContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 60px 20px; + text-align: center; +} + +.loadingSpinner { + width: 40px; + height: 40px; + border: 4px solid #f3f3f3; + border-top: 4px solid #0066cc; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-bottom: 16px; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.pageTitle { + margin: 0 0 32px 0; + font-size: 32px; + color: #333; +} + +.loadingMessage { + color: #666; + font-size: 16px; + margin: 0; +} \ No newline at end of file diff --git a/client/app/movies/loading.tsx b/client/app/movies/loading.tsx new file mode 100644 index 0000000..e927f59 --- /dev/null +++ b/client/app/movies/loading.tsx @@ -0,0 +1,23 @@ + +import styles from "../page.module.css"; +import loadingStyles from "./loading.module.css"; + +/** + * Loading Component for Movies Page + * + * This component is automatically displayed by Next.js while the movies page + * is loading during Server Side Rendering or when navigating to the page. + */ +export default function Loading() { + return ( +
+
+

Movies

+
+
+

Loading movies from the database...

+
+
+
+ ); +} \ No newline at end of file diff --git a/client/app/movies/movies.module.css b/client/app/movies/movies.module.css new file mode 100644 index 0000000..262e7e4 --- /dev/null +++ b/client/app/movies/movies.module.css @@ -0,0 +1,55 @@ +/** + * Movies Page Styles + * + * CSS Module for the movies page component. + * Provides responsive grid layout for the movies listing page. + */ + +.moviesGrid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 24px; + width: 100%; + max-width: 1200px; +} + +.noMovies { + text-align: center; + padding: 40px; + color: #666; +} + +.pageTitle { + margin: 0 0 16px 0; + font-size: 32px; + color: #333; +} + +.movieCount { + margin: 0 0 32px 0; + color: #666; + font-size: 16px; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .moviesGrid { + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); + gap: 16px; + } + + .pageTitle { + font-size: 28px; + } +} + +@media (max-width: 480px) { + .moviesGrid { + grid-template-columns: 1fr; + gap: 16px; + } + + .pageTitle { + font-size: 24px; + } +} \ No newline at end of file diff --git a/client/app/movies/page.tsx b/client/app/movies/page.tsx new file mode 100644 index 0000000..2fe0126 --- /dev/null +++ b/client/app/movies/page.tsx @@ -0,0 +1,30 @@ +import styles from "../page.module.css"; +import movieStyles from "./movies.module.css"; +import MovieCard from "../components/MovieCard"; +import { fetchMovies } from "../lib/api"; +import { APP_CONFIG } from "../lib/constants"; + +export default async function Movies() { + const movies = await fetchMovies(APP_CONFIG.defaultMovieLimit); + + return ( +
+
+

Movies

+

Displaying {movies.length} movies from the sample_mflix database

+ + {movies.length === 0 ? ( +
+

No movies found. Make sure the Express server is running on port 3001.

+
+ ) : ( +
+ {movies.map((movie) => ( + + ))} +
+ )} +
+
+ ); +} diff --git a/client/app/page.module.css b/client/app/page.module.css new file mode 100644 index 0000000..12f5b0f --- /dev/null +++ b/client/app/page.module.css @@ -0,0 +1,33 @@ +.page { + display: grid; + grid-template-rows: 20px 1fr 20px; + align-items: center; + justify-items: center; + min-height: 100svh; + padding: 80px; + gap: 64px; + font-family: var(--font-geist-sans); +} + +.main { + display: flex; + flex-direction: column; + gap: 32px; + grid-row-start: 2; +} + +.footer { + grid-row-start: 3; + display: flex; + gap: 24px; +} + +.footer a { + display: flex; + align-items: center; + gap: 8px; +} + +.footer img { + flex-shrink: 0; +} diff --git a/client/app/page.tsx b/client/app/page.tsx new file mode 100644 index 0000000..8a5a218 --- /dev/null +++ b/client/app/page.tsx @@ -0,0 +1,15 @@ +import Link from "next/link"; +import styles from "./page.module.css"; + +export default function Home() { + return ( +
+
+ See all movies +
+
+ footer content here +
+
+ ); +} diff --git a/client/app/types/movie.ts b/client/app/types/movie.ts new file mode 100644 index 0000000..ea5b8d8 --- /dev/null +++ b/client/app/types/movie.ts @@ -0,0 +1,37 @@ +/** + * Shared type definitions for the Movie application + * These types match the backend API response structure + */ + +/** + * Movie interface for type safety + * Matches the Movie type from the Express backend + */ +export interface Movie { + _id: string; + title: string; + year?: number; + plot?: string; + poster?: string; + genres?: string[]; + imdb?: { + rating?: number; + }; +} + +/** + * API Response interface for the movies endpoint + * Matches the SuccessResponse type from the Express backend + */ +export interface MoviesApiResponse { + success: boolean; + data: Movie[]; + message?: string; +} + +/** + * Props interface for MovieCard component + */ +export interface MovieCardProps { + movie: Movie; +} \ No newline at end of file diff --git a/client/eslint.config.mjs b/client/eslint.config.mjs new file mode 100644 index 0000000..719cea2 --- /dev/null +++ b/client/eslint.config.mjs @@ -0,0 +1,25 @@ +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals", "next/typescript"), + { + ignores: [ + "node_modules/**", + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ], + }, +]; + +export default eslintConfig; diff --git a/client/next.config.ts b/client/next.config.ts new file mode 100644 index 0000000..701bc37 --- /dev/null +++ b/client/next.config.ts @@ -0,0 +1,26 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + images: { + // Allow images from external domains (movie poster URLs) + remotePatterns: [ + { + protocol: 'https', + hostname: '**', + }, + { + protocol: 'http', + hostname: '**', + }, + ], + // Optimize image formats + formats: ['image/avif', 'image/webp'], + // Image sizes for responsive images + deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], + imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], + }, + // Enable compression for better performance + compress: true, +}; + +export default nextConfig; diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..704f307 --- /dev/null +++ b/client/package.json @@ -0,0 +1,25 @@ +{ + "name": "sample-mflix-front-end", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev --turbopack", + "build": "next build --turbopack", + "start": "next start", + "lint": "eslint" + }, + "dependencies": { + "react": "19.1.0", + "react-dom": "19.1.0", + "next": "15.5.5" + }, + "devDependencies": { + "typescript": "^5", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "15.5.5", + "@eslint/eslintrc": "^3" + } +} diff --git a/client/tsconfig.json b/client/tsconfig.json new file mode 100644 index 0000000..fec0bb6 --- /dev/null +++ b/client/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./app/*"], + "@/types/*": ["./app/types/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} From 791e6e3db1612f42c85d68109205506a03021cbf Mon Sep 17 00:00:00 2001 From: Jordan Smith Date: Wed, 22 Oct 2025 14:06:17 -0400 Subject: [PATCH 2/3] chore: update homepage and movies page --- client/app/components/ui/ErrorDisplay.tsx | 2 +- client/app/home.module.css | 95 +++++++++++++++++++++++ client/app/movies/loading.tsx | 5 +- client/app/movies/movies.module.css | 22 ------ client/app/movies/page.module.css | 29 +++++++ client/app/movies/page.tsx | 6 +- client/app/page.module.css | 33 -------- client/app/page.tsx | 13 ++-- 8 files changed, 138 insertions(+), 67 deletions(-) create mode 100644 client/app/home.module.css create mode 100644 client/app/movies/page.module.css delete mode 100644 client/app/page.module.css diff --git a/client/app/components/ui/ErrorDisplay.tsx b/client/app/components/ui/ErrorDisplay.tsx index 15b96d4..949301a 100644 --- a/client/app/components/ui/ErrorDisplay.tsx +++ b/client/app/components/ui/ErrorDisplay.tsx @@ -13,7 +13,7 @@ export default function ErrorDisplay({ }: ErrorDisplayProps) { return (
-

Oops!

+

Error

{message}

{onRetry && (