Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/api/tmdb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -31,4 +31,11 @@ export function fetchCurrentMovies(): Promise<TmdbResponse<Movie>> {

export function fetchPopularPeople(): Promise<TmdbResponse<Person>> {
return tmdbFetch('/person/popular?language=en-US&page=1', 'Klarte ikke hente skuespillere...');
}

export function fetchMovieCredits(movieId: number): Promise<CreditsResponse> {
return tmdbFetch<CreditsResponse>(
`/movie/${movieId}/credits?language=en-US`,
'Klarte ikke hente skuespillere...',
);
}
15 changes: 15 additions & 0 deletions src/components/MovieList.css
Original file line number Diff line number Diff line change
@@ -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;
}
}
4 changes: 2 additions & 2 deletions src/components/MovieList.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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}
/>
))}
</div>
Expand Down
98 changes: 98 additions & 0 deletions src/components/Movie_card.css
Original file line number Diff line number Diff line change
@@ -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;
}
}
51 changes: 25 additions & 26 deletions src/components/Movie_card.tsx
Original file line number Diff line number Diff line change
@@ -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 <img>
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"
<div className="movie-card">
{/* Viser filmplakaten. alt={title} sørger for skjermlesere kan beskrive bildet */}
<img className="movie-card__poster" src={posterUrl} alt={title}/>
{/* Wrapper rundt all tekstinfoen under/ved siden av bildet */}
<span className="movie-card__rating">{rating}%</span>

<img className="movie-card__poster" src={posterUrl} alt={title} />

<div className="movie-card__info">
{/* Rad med tittel og rating side om side */}
<div className="movie-card__header">
{/* Tittelen settes inn med {title}, som henter verdien fra props */}
<h3 className="movie-card__title">{title}</h3>
{/* Rating vises som prosent, {rating} settes inn fra props */}
<span className="movie-card__rating">{rating}%</span>
</div>
{/* actors er en array, .join(", ") slår den sammen til én tekststreng med komma mellom */}
<p className="movie-card__actors">{actors.join(", ")}</p>
<h3 className="movie-card__title">{title}</h3>

{isLoading && <p className="movie-card__actors-loading">Laster skuespillere...</p>}

{!isLoading && topActors.length > 0 && (
<p className="movie-card__actors">
{topActors.map((actor) => actor.name).join(", ")}
</p>
)}
</div>
</div>
);
}

// 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.
// <MovieCard
// title="Placeholder-tittel"
//
export default MovieCard;
12 changes: 12 additions & 0 deletions src/hooks/useMovieCredits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useQuery } from '@tanstack/react-query';
import { fetchMovieCredits } from '../api/tmdb';

// Henter skuespillere for én spesifikk film via TMDB sitt credits-endepunkt.
// Caches per movieId, så samme film aldri hentes på nytt unødvendig.
export function useMovieCredits(movieId: number) {
return useQuery({
queryKey: ['movieCredits', movieId],
queryFn: () => fetchMovieCredits(movieId),
staleTime: 1000 * 60 * 60, // skuespillere endrer seg ikke ofte, cache 1 time
});
}
14 changes: 13 additions & 1 deletion src/types/tmdb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,16 @@ export type TmdbResponse<T> = {
results: T[];
total_pages: number;
total_results: number;
};
};

export interface CastMember {
id: number;
name: string;
character: string;
order: number;
}

export interface CreditsResponse {
id: number;
cast: CastMember[];
}