diff --git a/client/.env.example b/client/.env.example
new file mode 100644
index 0000000..61ed0e9
--- /dev/null
+++ b/client/.env.example
@@ -0,0 +1,6 @@
+# Client Environment Variables
+# Copy this file to .env.local and update the values as needed
+
+# URL of the Express API server
+# Default: http://localhost:3001 (for local development)
+API_URL=http://localhost:3001
\ No newline at end of file
diff --git a/client/.gitignore b/client/.gitignore
new file mode 100644
index 0000000..189ae56
--- /dev/null
+++ b/client/.gitignore
@@ -0,0 +1,43 @@
+# 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*
+!.env.example
+
+# 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 ? (
+
+ ) : (
+
+ 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..949301a
--- /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 (
+
+
Error
+
{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/home.module.css b/client/app/home.module.css
new file mode 100644
index 0000000..03326c6
--- /dev/null
+++ b/client/app/home.module.css
@@ -0,0 +1,95 @@
+/**
+ * Home Page Styles
+ *
+ * Styles specific to the home/landing page
+ */
+
+.page {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 100vh;
+ padding: 2rem;
+ text-align: center;
+}
+
+.main {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 2rem;
+ max-width: 600px;
+}
+
+.title {
+ font-size: 3rem;
+ font-weight: 700;
+ color: #333;
+ margin: 0;
+ letter-spacing: -0.025em;
+}
+
+.description {
+ font-size: 1.25rem;
+ color: #666;
+ margin: 0;
+ line-height: 1.6;
+}
+
+.button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1rem 2rem;
+ font-size: 1.1rem;
+ font-weight: 600;
+ color: white;
+ background: #0066cc;
+ border: none;
+ border-radius: 8px;
+ text-decoration: none;
+ transition: all 0.2s ease;
+ cursor: pointer;
+ min-width: 180px;
+}
+
+.button:hover {
+ background: #0052a3;
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0, 102, 204, 0.3);
+}
+
+.button:active {
+ transform: translateY(0);
+}
+
+/* Responsive Design */
+@media (max-width: 768px) {
+ .title {
+ font-size: 2.5rem;
+ }
+
+ .description {
+ font-size: 1.1rem;
+ }
+
+ .page {
+ padding: 1rem;
+ }
+}
+
+@media (max-width: 480px) {
+ .title {
+ font-size: 2rem;
+ }
+
+ .description {
+ font-size: 1rem;
+ }
+
+ .button {
+ padding: 0.875rem 1.5rem;
+ font-size: 1rem;
+ }
+}
\ No newline at end of file
diff --git a/client/app/layout.tsx b/client/app/layout.tsx
new file mode 100644
index 0000000..2bea842
--- /dev/null
+++ b/client/app/layout.tsx
@@ -0,0 +1,19 @@
+import type { Metadata } from 'next';
+import "./globals.css";
+export const metadata: Metadata = {
+ title: 'Sample MFlix - MongoDB Movie Database',
+ description: 'Explore movies from the MongoDB sample_mflix database',
+};
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+ {children}
+
+
+ );
+}
\ No newline at end of file
diff --git a/client/app/lib/api.ts b/client/app/lib/api.ts
new file mode 100644
index 0000000..7235435
--- /dev/null
+++ b/client/app/lib/api.ts
@@ -0,0 +1,62 @@
+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);
+
+ // In development, throw the error to help with debugging
+ if (process.env.NODE_ENV === 'development') {
+ throw error;
+ }
+
+ // In production, return empty array with logged error 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..7d6c498
--- /dev/null
+++ b/client/app/movies/loading.tsx
@@ -0,0 +1,22 @@
+
+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..37ec2c2
--- /dev/null
+++ b/client/app/movies/movies.module.css
@@ -0,0 +1,33 @@
+/**
+ * Movies Card Styles
+ *
+ * CSS Module for the movies card 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;
+}
+
diff --git a/client/app/movies/page.module.css b/client/app/movies/page.module.css
new file mode 100644
index 0000000..e32b3f3
--- /dev/null
+++ b/client/app/movies/page.module.css
@@ -0,0 +1,29 @@
+/**
+ * Movies Page Layout Styles
+ *
+ * Layout-specific styles for the movies page structure
+ */
+
+.page {
+ min-height: 100vh;
+ padding: 2rem;
+}
+
+.main {
+ max-width: 1200px;
+ margin: 0 auto;
+ width: 100%;
+}
+
+/* Responsive padding */
+@media (max-width: 768px) {
+ .page {
+ padding: 1rem;
+ }
+}
+
+@media (max-width: 480px) {
+ .page {
+ padding: 0.5rem;
+ }
+}
\ 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..4cb2db3
--- /dev/null
+++ b/client/app/movies/page.tsx
@@ -0,0 +1,30 @@
+import pageStyles 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.tsx b/client/app/page.tsx
new file mode 100644
index 0000000..c74794d
--- /dev/null
+++ b/client/app/page.tsx
@@ -0,0 +1,18 @@
+import Link from "next/link";
+import styles from "./home.module.css";
+
+export default function Home() {
+ return (
+
+
+ Sample Mflix
+
+ Explore movies from the sample MFlix database
+
+
+ See movies
+
+
+
+ );
+}
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..3cc711d
--- /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.2.0",
+ "react-dom": "19.2.0",
+ "next": "16.0.0"
+ },
+ "devDependencies": {
+ "typescript": "^5",
+ "@types/node": "^20",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "eslint": "^9",
+ "eslint-config-next": "16.0.0",
+ "@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"]
+}
diff --git a/server/express/package.json b/server/express/package.json
index 299ceb0..29b341c 100644
--- a/server/express/package.json
+++ b/server/express/package.json
@@ -16,9 +16,9 @@
},
"dependencies": {
"cors": "^2.8.5",
- "dotenv": "^16.3.1",
+ "dotenv": "^17.2.3",
"express": "^5.1.0",
- "mongodb": "^6.3.0"
+ "mongodb": "^6.20.0"
},
"devDependencies": {
"@types/cors": "^2.8.17",