recuperando informacion y tratandola
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getMovieDetails } from "@/lib/tmdb";
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
const { id } = await params;
|
||||
const movieId = Number(id);
|
||||
|
||||
if (!Number.isFinite(movieId) || movieId <= 0) {
|
||||
return NextResponse.json({ error: "Invalid movie ID" }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const movie = await getMovieDetails(movieId);
|
||||
return NextResponse.json(movie);
|
||||
} catch (error) {
|
||||
console.error("Movie details fetch failed:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to load movie details" },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
+121
-17
@@ -1,29 +1,133 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { SearchBar } from "@/components/SearchBar";
|
||||
import { MovieHeader } from "@/components/MovieHeader";
|
||||
import { MovieInsightPanel } from "@/components/MovieInsightPanel";
|
||||
import type { TmdbMovieDetails, TmdbSearchResult } from "@/lib/tmdb";
|
||||
|
||||
export default function HomePage() {
|
||||
const [selectedMovie, setSelectedMovie] = useState<TmdbSearchResult | null>(
|
||||
null,
|
||||
);
|
||||
const [movieDetails, setMovieDetails] = useState<TmdbMovieDetails | null>(
|
||||
null,
|
||||
);
|
||||
const [isLoadingMovie, setIsLoadingMovie] = useState(false);
|
||||
const [movieError, setMovieError] = useState<string | null>(null);
|
||||
|
||||
const handleSelect = async (movie: TmdbSearchResult) => {
|
||||
setSelectedMovie(movie);
|
||||
setMovieDetails(null);
|
||||
setMovieError(null);
|
||||
setIsLoadingMovie(true);
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/movie/${movie.id}`);
|
||||
if (!res.ok) {
|
||||
throw new Error("Failed to load movie details");
|
||||
}
|
||||
const details: TmdbMovieDetails = await res.json();
|
||||
setMovieDetails(details);
|
||||
} catch (err) {
|
||||
setMovieError(
|
||||
err instanceof Error ? err.message : "Failed to load movie details",
|
||||
);
|
||||
} finally {
|
||||
setIsLoadingMovie(false);
|
||||
}
|
||||
};
|
||||
|
||||
const clearSelection = () => {
|
||||
setSelectedMovie(null);
|
||||
setMovieDetails(null);
|
||||
setIsLoadingMovie(false);
|
||||
setMovieError(null);
|
||||
};
|
||||
|
||||
const hasFullMovie = movieDetails != null;
|
||||
|
||||
return (
|
||||
<main className="mx-auto flex min-h-screen w-full max-w-5xl flex-col items-center justify-center px-6 py-16">
|
||||
<div className="relative z-10 w-full max-w-2xl space-y-8 text-center">
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm font-medium uppercase tracking-[0.2em] text-amber-300/80">
|
||||
Film Intel
|
||||
</p>
|
||||
<h1 className="text-4xl font-semibold tracking-tight text-white md:text-5xl">
|
||||
Movie insight you won't find in one click
|
||||
</h1>
|
||||
<p className="text-slate-400">
|
||||
Ratings explained, skip guides, sensitivity warnings, and deep
|
||||
production context for any film.
|
||||
</p>
|
||||
<>
|
||||
{/* Compact navbar: title + slogan on left, search on right */}
|
||||
<nav className="sticky top-0 z-50 border-b border-white/10 bg-slate-950/95 backdrop-blur supports-[backdrop-filter]:bg-slate-950/80">
|
||||
<div className="mx-auto flex h-14 max-w-6xl items-center justify-between gap-4 px-6">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<span className="font-semibold tracking-tight text-white">
|
||||
Film Intel
|
||||
</span>
|
||||
<span className="hidden truncate text-xs text-slate-400 sm:inline">
|
||||
Movie insight you won't find in one click
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="w-full max-w-[320px]">
|
||||
<SearchBar
|
||||
autoFocus={!selectedMovie}
|
||||
onSelect={handleSelect}
|
||||
compact
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<SearchBar autoFocus />
|
||||
</div>
|
||||
<main className="mx-auto min-h-[calc(100vh-3.5rem)] w-full max-w-6xl px-6">
|
||||
{!selectedMovie && (
|
||||
<div className="flex flex-col items-center justify-center py-16 text-center">
|
||||
<p className="max-w-md text-sm text-slate-400">
|
||||
Search for any film using the bar above. We'll generate
|
||||
ratings explained, skip guides, sensitivity warnings, production
|
||||
facts, and more.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<footer className="mt-auto pt-16 text-center text-xs text-slate-500">
|
||||
{selectedMovie && (
|
||||
<div className="space-y-8 py-8">
|
||||
{hasFullMovie ? (
|
||||
<MovieHeader movie={movieDetails} onClear={clearSelection} />
|
||||
) : (
|
||||
<div className="rounded-2xl border border-white/10 bg-white/[0.03] p-6">
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-2xl font-semibold text-white">
|
||||
{selectedMovie.title}
|
||||
{selectedMovie.release_date ? (
|
||||
<span className="ml-2 font-normal text-slate-400">
|
||||
({selectedMovie.release_date.slice(0, 4)})
|
||||
</span>
|
||||
) : null}
|
||||
</h2>
|
||||
{selectedMovie.overview && (
|
||||
<p className="text-sm leading-relaxed text-slate-400">
|
||||
{selectedMovie.overview}
|
||||
</p>
|
||||
)}
|
||||
{isLoadingMovie && (
|
||||
<p className="text-sm text-amber-300/80">
|
||||
Loading extended details...
|
||||
</p>
|
||||
)}
|
||||
{movieError && (
|
||||
<p className="text-sm text-red-400">
|
||||
{movieError}. Card content will still load.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<MovieInsightPanel
|
||||
movieId={selectedMovie.id}
|
||||
genres={movieDetails?.genres.map((g) => g.name) ?? []}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
|
||||
<footer className="border-t border-white/10 py-6 text-center text-xs text-slate-500">
|
||||
AI-generated content for informational purposes. Verify timestamps on
|
||||
your copy.
|
||||
</footer>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user