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
20 changes: 15 additions & 5 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState } from 'react';
import { usePopMovies, useCurMovies, useSearchMovies } from './hooks/useMovies';
import { usePopularPeople } from './hooks/usePeople';
import { Sidebar } from './components/sidebar';
import { Sidebar } from './components/Sidebar';
import { Section } from './components/Section';
import { MovieList } from './components/MovieList';
import { PersonList } from './components/PersonList';
Expand All @@ -12,11 +12,16 @@ import { sortAndFilterMovies, type SortOption } from './utils/sortAndFilterMovie
import { SortFilterBar } from './components/SortFilterBar';
import { useFavorites } from './hooks/useFavorites';
import { TrendingPreview } from './components/TrendingPreview';
import { MovieDetails } from './components/MovieDetails';

import type { Movie } from './types/tmdb';

import './App.css';

function App() {

const [query, setQuery] = useState('');
const [selectedMovie, setSelectedMovie] = useState<Movie | null>(null);

const [sortBy, setSortBy] = useSessionStorage<SortOption>('sortBy', 'popularity');
const [minRating, setMinRating] = useSessionStorage<number>('minRating', 0);
Expand All @@ -42,19 +47,20 @@ function App() {
/>
{isSearching ? (
<Section id="sok" title={`Søkeresultater for "${query}"`} isLoading={search.isLoading} error={search.error}>
{search.data && <MovieList movies={search.data} isFavorite={isFavorite} onToggleFavorite={toggleFavorite} />}
{search.data && <MovieList movies={search.data} isFavorite={isFavorite} onToggleFavorite={toggleFavorite} onSelectMovie={setSelectedMovie} />}
</Section>
) : (
<>
<Section id="trendingPreview" title="" isLoading={current.isLoading} error={current.error}>
{current.data && <TrendingPreview movies={current.data}/>}
{current.data && <TrendingPreview movies={current.data} onSelectMovie={setSelectedMovie}/>}
</Section>

<Section id="populaere" title="Populære filmer" isLoading={popular.isLoading} error={popular.error}>
{popular.data && <MovieList movies={popular.data} isFavorite={isFavorite} onToggleFavorite={toggleFavorite} />}
{popular.data && <MovieList movies={popular.data} isFavorite={isFavorite} onToggleFavorite={toggleFavorite} onSelectMovie={setSelectedMovie} />}
</Section>

<Section id="nye-filmer" title="Nye filmer" isLoading={current.isLoading} error={current.error}>
{current.data && <MovieList movies={current.data} isFavorite={isFavorite} onToggleFavorite={toggleFavorite} />}
{current.data && <MovieList movies={current.data} isFavorite={isFavorite} onToggleFavorite={toggleFavorite} onSelectMovie={setSelectedMovie} />}
</Section>

<Section id="skuespillere" title="Skuespillere" isLoading={people.isLoading} error={people.error}>
Expand All @@ -63,6 +69,10 @@ function App() {
</>
)}
</main>

{selectedMovie && (
<MovieDetails movie={selectedMovie} isFavorite={isFavorite(selectedMovie.id)} onToggleFavorite={toggleFavorite} onClose={() => setSelectedMovie(null)} />
)}
<div className="page-spacer" aria-hidden="true"></div>
</div>
);
Expand Down
9 changes: 9 additions & 0 deletions src/components/FavoriteButton.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.favorite-button {
background: none;
border: none;
padding: 0;
cursor: pointer;
line-height: 1;
font-size: 1.5rem;
color: #fbc02d;
}
27 changes: 27 additions & 0 deletions src/components/FavoriteButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Movie } from "../types/tmdb";

import "./FavoriteButton.css";

type FavoriteButtonProps = {
movie: Movie;
className?: string;
isFavorite: boolean;
onToggleFavorite: (id: number) => void;
};

export function FavoriteButton({ movie, className, isFavorite, onToggleFavorite }: FavoriteButtonProps) {
return (
<button
type="button"
className={`favorite-button ${className??""}`}
aria-pressed={isFavorite}
aria-label={isFavorite ? `Fjern ${movie.title} fra favoritter` : `Legg ${movie.title} til favoritter`}
onClick={(e) => {
e.stopPropagation();
onToggleFavorite(movie.id);
}}
>
{isFavorite ? "★" : "☆"}
</button>
);
}
9 changes: 1 addition & 8 deletions src/components/MovieCard.css
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,6 @@
bottom: 0.5rem;
right: 0.5rem;
z-index: 1;
background: none;
border: none;
padding: 0;
font-size: 1.5rem;
line-height: 1;
color: #fbc02d;
cursor: pointer;
}

@media (max-width: 480px) {
Expand All @@ -110,4 +103,4 @@
.movie-card__info {
padding-right: 3rem;
}
}
}
39 changes: 22 additions & 17 deletions src/components/MovieCard.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,50 @@
import { useMovieCredits } from "../hooks/useMovieCredits";
import type { Movie } from "../types/tmdb";
import { FavoriteButton } from "./FavoriteButton";
import "./MovieCard.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
isFavorite: boolean; // Om denne filmen er markert som favoritt
onToggleFavorite: (id: number) => void; // Kalles når stjernen klikkes
onSelectMovie: (movie: Movie) => void;
}
*/

function MovieCard({ movieId, title, posterUrl, rating, isFavorite, onToggleFavorite }: MovieCardProps) {
interface MovieCardProps {
movie: Movie;
isFavorite: boolean; // Om denne filmen er markert som favoritt
onToggleFavorite: (id: number) => void; // Kalles når stjernen klikkes
onSelectMovie: (movie: Movie) => void;
}

function MovieCard({ movie, isFavorite, onToggleFavorite, onSelectMovie }: MovieCardProps) {
// Henter skuespillere for akkurat denne filmen når kortet vises
const { data: credits, isLoading } = useMovieCredits(movieId);
const { data: credits, isLoading } = useMovieCredits(movie.id);

// Viser kun de 3 første, sortert etter TMDB sin billed-rekkefølge
const topActors = credits?.cast.slice(0, 3) ?? [];
// TMDB sin vote_average går fra 0-10, vi ganger med 10 for å få prosent (0-100)
const rating = Math.round(movie.vote_average * 10);
const path = movie.poster_path?`https://image.tmdb.org/t/p/w200${movie.poster_path}`:"";

return (
<div className="movie-card">
<button
type="button"
className="movie-card__favorite"
aria-pressed={isFavorite}
aria-label={isFavorite ? `Fjern ${title} fra favoritter` : `Legg ${title} til favoritter`}
onClick={() => onToggleFavorite(movieId)}
>
{isFavorite ? "★" : "☆"}
</button>
<div className="movie-card" onClick={() => {onSelectMovie(movie) }}>
<FavoriteButton className="movie-card__favorite" movie={movie} isFavorite={isFavorite} onToggleFavorite={onToggleFavorite} />

<span className="movie-card__rating">{rating}%</span>

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

<div className="movie-card__info">
<h3 className="movie-card__title">{title}</h3>

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

<h3 className="movie-card__title">{movie.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(", ")}
Expand Down
86 changes: 86 additions & 0 deletions src/components/MovieDetails.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
.details-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}


.details-card {
position: relative;
width: 60vw;
max-width: 800px;
max-height: 80vh;
overflow-y: auto;
background: #111;
border-radius: 1rem;
color: white;
}

.details-backdrop {
width: 100%;
height: 300px;
object-fit: cover;
border-radius: 1rem 1rem 0 0;
}

.details-close {
position: absolute;
top: 1rem;
right: 1rem;
background: rgba(0, 0, 0, 0.5);
color: white;
border: none;
border-radius: 50%;
width: 2rem;
height: 2rem;
cursor: pointer;
}

.details-content {
padding: 1.25rem 1.5rem;
text-align: center;
}

.details-content h2 {
margin: 0 0 0.25rem;
}

.details-meta {
margin: 0 0 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
color: rgba(255, 255, 255, 0.7);
}

.details-content h3 {
margin: 1rem 0 0.5rem;
font-size: 1rem;
}

.details-overview {
margin: 0 0 0.5rem;
line-height: 1.4;
}

.details-actors {
margin: 0;
}

/* MovieDetails.css */
.details-favorite {
position: absolute;
top: 1rem;
left: 1rem;
z-index: 1;

background: rgba(0, 0, 0, 0.5);
border: none;
border-radius: 50%;
width: 2rem;
height: 2rem;
cursor: pointer;
}
55 changes: 55 additions & 0 deletions src/components/MovieDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useMovieCredits } from "../hooks/useMovieCredits";
import type { Movie } from "../types/tmdb";
import { FavoriteButton } from "./FavoriteButton";
import "./MovieDetails.css"

type MovieDetailsProps = {
movie: Movie;
isFavorite: boolean;
onToggleFavorite: (id: number) => void;
onClose: () => void;
};

export function MovieDetails({ movie, isFavorite, onToggleFavorite, onClose }: MovieDetailsProps) {

const { data: credits, isLoading } = useMovieCredits(movie.id);

return (
<div className="details-overlay" onClick={onClose}>
<div className="details-card" onClick={(e) => e.stopPropagation()}>
<img
src={`https://image.tmdb.org/t/p/original${movie.backdrop_path}`}
alt={movie.title}
className="details-backdrop"
/>

<button className="details-close" onClick={onClose}></button>

<FavoriteButton
movie={movie}
isFavorite={isFavorite}
onToggleFavorite={onToggleFavorite}
className="details-favorite"
/>

<div className="details-content">
<h2>{movie.title}</h2>
<p className="details-meta">
{movie.release_date} · ★ {movie.vote_average.toFixed(1)}
</p>

<h3>Sammendrag</h3>
<p className="details-overview">{movie.overview}</p>

<h3>Skuespillere</h3>
<div className="details-actors">
{isLoading && <p className="details-actors-loading">Laster skuespillere...</p>}
{credits && (
<p>{credits.cast.slice(0, 6).map((actor) => actor.name).join(", ")}</p>
)}
</div>
</div>
</div>
</div>
);
}
29 changes: 10 additions & 19 deletions src/components/MovieList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,26 @@ type MovieListProps = {
movies: Movie[];
isFavorite: (id: number) => boolean;
onToggleFavorite: (id: number) => void;
onSelectMovie: (movie: Movie) => void;
};

export function MovieList({ movies, isFavorite, onToggleFavorite }: MovieListProps) {
export function MovieList({ movies, isFavorite, onToggleFavorite, onSelectMovie }: MovieListProps) {
return (
// Ytre container for hele filmlisten, kan style'es med CSS senere
<div className="movie-list">
{movies.map((movie) => (
// .map() går gjennom hver film og returnerer én MovieCard per film.
// key={movie.id} er påkrevd av React for å holde styr på hvert element i lista.
<MovieCard
key={movie.id}
// TMDB sitt feltnavn er "title", som passer direkte med vår prop
title={movie.title}
// TMDB gir bare en delsti (poster_path), så vi setter sammen full bilde-URL selv.
// Noen filmer mangler plakat (poster_path er null), da sender vi tom streng
// i stedet for å be om ".../w200null" og få et ødelagt bilde.
posterUrl={
movie.poster_path
? `https://image.tmdb.org/t/p/w200${movie.poster_path}`
: ""
}
// TMDB sin vote_average går fra 0-10, vi ganger med 10 for å få prosent (0-100)
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
movieId={movie.id}
<MovieCard
movie={movie}
isFavorite={isFavorite(movie.id)}
onToggleFavorite={onToggleFavorite}
onSelectMovie={onSelectMovie}
/>
/*
isFavorite={isFavorite(movie.id)}
onToggleFavorite={onToggleFavorite}
/>
*/
))}
</div>
);
Expand Down
File renamed without changes.
Loading