diff --git a/src/api/tmdb.ts b/src/api/tmdb.ts index 91d1e69..af9db24 100644 --- a/src/api/tmdb.ts +++ b/src/api/tmdb.ts @@ -5,7 +5,7 @@ // BASE_URL er lenken vi bruker i en API request, her til TMDB // TOKEN er lagret i .env.local, denne må alle legge til manuelt selv -import type { Movie, Person, TmdbResponse } from '../types/tmdb'; +import type { Movie, Person, TmdbResponse, CreditsResponse } from '../types/tmdb'; const TOKEN = import.meta.env.VITE_TMDB_TOKEN; // v4 read access token const BASE_URL = 'https://api.themoviedb.org/3'; @@ -31,4 +31,11 @@ export function fetchCurrentMovies(): Promise> { export function fetchPopularPeople(): Promise> { return tmdbFetch('/person/popular?language=en-US&page=1', 'Klarte ikke hente skuespillere...'); +} + +export function fetchMovieCredits(movieId: number): Promise { + return tmdbFetch( + `/movie/${movieId}/credits?language=en-US`, + 'Klarte ikke hente skuespillere...', + ); } \ No newline at end of file diff --git a/src/components/MovieList.css b/src/components/MovieList.css new file mode 100644 index 0000000..ad44340 --- /dev/null +++ b/src/components/MovieList.css @@ -0,0 +1,15 @@ +.movie-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + gap: 1rem; + padding: 1.5rem; + list-style: none; +} + +@media (max-width: 480px) { + .movie-list { + grid-template-columns: 1fr; + gap: 0.75rem; + padding: 1rem; + } +} \ No newline at end of file diff --git a/src/components/MovieList.tsx b/src/components/MovieList.tsx index 0bc4c80..794d4f4 100644 --- a/src/components/MovieList.tsx +++ b/src/components/MovieList.tsx @@ -1,7 +1,7 @@ // Henter inn MovieCard-komponenten vår, slik at vi kan bruke den til å vise hver film. import MovieCard from "./Movie_card"; -// Typen for én film fra TMDB, så vi slipper any og får autocomplete på feltene. import type { Movie } from "../types/tmdb"; +import "./MovieList.css"; // MovieList tar imot ferdig hentede filmer som prop og viser dem som en liste av MovieCard. // Selve hentingen skjer via hookene i App.tsx, og Section håndterer laster- og feilmelding, @@ -34,7 +34,7 @@ function MovieList({ movies }: MovieListProps) { rating={Math.round(movie.vote_average * 10)} // Skuespillere ligger ikke i dette API-svaret (krever eget kall til /credits), // så vi sender en tom liste som placeholder inntil videre - actors={[]} + movieId={movie.id} /> ))} diff --git a/src/components/Movie_card.css b/src/components/Movie_card.css new file mode 100644 index 0000000..607d594 --- /dev/null +++ b/src/components/Movie_card.css @@ -0,0 +1,98 @@ +.movie-card { + position: relative; + display: flex; + flex-direction: row; + align-items: stretch; + background-color: #ffffff; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + transition: transform 0.15s ease, box-shadow 0.15s ease; +} + +.movie-card:hover { + transform: translateY(-2px); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2); +} + +/* Plakat til venstre, fast bredde, resten av kortet strekker seg */ +.movie-card__poster { + width: 110px; + flex-shrink: 0; + aspect-ratio: 2 / 3; + object-fit: cover; + background-color: #e0e0e0; + display: block; +} + +/* Info til høyre for bildet, fyller resten av kortet, venstrejustert */ +.movie-card__info { + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + text-align: left; + gap: 0.5rem; + padding: 0.75rem 1rem; + padding-right: 3.5rem; /* plass til rating-badgen i hjørnet */ + min-width: 0; +} + +/* Tittel som overskrift, tydelig større og fetere */ +.movie-card__title { + margin: 0; + font-size: 1.6rem; + font-weight: 700; + line-height: 1.2; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +.movie-card__actors { + margin: 0; + font-size: 0.9rem; + color: #666; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 100%; +} + +.movie-card__actors-loading { + margin: 0; + font-size: 0.9rem; + color: #999; + font-style: italic; +} + +/* Rating som badge i øvre høyre hjørne av kortet */ +.movie-card__rating { + position: absolute; + top: 0.5rem; + right: 0.5rem; + font-size: 0.8rem; + font-weight: 600; + color: #2e7d32; + background-color: #e8f5e9; + padding: 0.2rem 0.55rem; + border-radius: 999px; + z-index: 1; +} + +@media (max-width: 480px) { + .movie-card__poster { + width: 80px; + } + + .movie-card__title { + font-size: 1.2rem; + } + + .movie-card__info { + padding-right: 3rem; + } +} \ No newline at end of file diff --git a/src/components/Movie_card.tsx b/src/components/Movie_card.tsx index a52e4ba..c856d10 100644 --- a/src/components/Movie_card.tsx +++ b/src/components/Movie_card.tsx @@ -1,42 +1,41 @@ +import { useMovieCredits } from "../hooks/useMovieCredits"; +import "./Movie_card.css"; + // Definerer hvilke props (data utenfra) komponenten forventer å motta, // og hvilken type hver av dem skal ha. interface MovieCardProps { + movieId: number; // TMDB sin filmid, brukes til å hente skuespillere title: string; // Filmens tittel, skal vises som overskrift posterUrl: string; // URL til filmplakat-bildet, brukes i rating: number; // Terningkast/prosent (0-100), f.eks. vote_average * 10 fra TMDB - actors: string[]; // Liste med skuespillernavn, vises samlet som tekst } -// Selve komponenten: en funksjon som tar imot props (destrukturert direkte i parameterlista) -// og returnerer JSX-en som beskriver hvordan filmkortet skal se ut. -function MovieCard({ title, posterUrl, rating, actors }: MovieCardProps) { +function MovieCard({ movieId, title, posterUrl, rating }: MovieCardProps) { + // Henter skuespillere for akkurat denne filmen når kortet vises + const { data: credits, isLoading } = useMovieCredits(movieId); + + // Viser kun de 3 første, sortert etter TMDB sin billed-rekkefølge + const topActors = credits?.cast.slice(0, 3) ?? []; + return ( - // Ytterste container for hele kortet, styles med CSS-klassen "movie-card"
- {/* Viser filmplakaten. alt={title} sørger for skjermlesere kan beskrive bildet */} - {title}/ - {/* Wrapper rundt all tekstinfoen under/ved siden av bildet */} + {rating}% + + {title} +
- {/* Rad med tittel og rating side om side */} -
- {/* Tittelen settes inn med {title}, som henter verdien fra props */} -

{title}

- {/* Rating vises som prosent, {rating} settes inn fra props */} - {rating}% -
- {/* actors er en array, .join(", ") slår den sammen til én tekststreng med komma mellom */} -

{actors.join(", ")}

+

{title}

+ + {isLoading &&

Laster skuespillere...

} + + {!isLoading && topActors.length > 0 && ( +

+ {topActors.map((actor) => actor.name).join(", ")} +

+ )}
); } -// Eksporterer komponenten slik at den kan importeres og brukes i andre filer, -// f.eks. i en liste over filmer (App.tsx eller en MovieList-komponent). -export default MovieCard; - -// Eksempel på hvordan komponenten brukes med placeholder-data. -// Dette fjernes/erstattes når du henter ekte data fra TMDB. -// fetchMovieCredits(movieId), + staleTime: 1000 * 60 * 60, // skuespillere endrer seg ikke ofte, cache 1 time + }); +} \ No newline at end of file diff --git a/src/types/tmdb.ts b/src/types/tmdb.ts index 843b689..053c623 100644 --- a/src/types/tmdb.ts +++ b/src/types/tmdb.ts @@ -18,4 +18,16 @@ export type TmdbResponse = { results: T[]; total_pages: number; total_results: number; -}; \ No newline at end of file +}; + +export interface CastMember { + id: number; + name: string; + character: string; + order: number; +} + +export interface CreditsResponse { + id: number; + cast: CastMember[]; +} \ No newline at end of file