diff --git a/src/App.tsx b/src/App.tsx index 3f51411..12bc5ef 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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'; @@ -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(null); const [sortBy, setSortBy] = useSessionStorage('sortBy', 'popularity'); const [minRating, setMinRating] = useSessionStorage('minRating', 0); @@ -42,19 +47,20 @@ function App() { /> {isSearching ? (
- {search.data && } + {search.data && }
) : ( <>
- {current.data && } + {current.data && }
+
- {popular.data && } + {popular.data && }
- {current.data && } + {current.data && }
@@ -63,6 +69,10 @@ function App() { )} + + {selectedMovie && ( + setSelectedMovie(null)} /> + )} ); diff --git a/src/components/FavoriteButton.css b/src/components/FavoriteButton.css new file mode 100644 index 0000000..6c48dc5 --- /dev/null +++ b/src/components/FavoriteButton.css @@ -0,0 +1,9 @@ +.favorite-button { + background: none; + border: none; + padding: 0; + cursor: pointer; + line-height: 1; + font-size: 1.5rem; + color: #fbc02d; +} \ No newline at end of file diff --git a/src/components/FavoriteButton.tsx b/src/components/FavoriteButton.tsx new file mode 100644 index 0000000..75d2313 --- /dev/null +++ b/src/components/FavoriteButton.tsx @@ -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 ( + + ); +} \ No newline at end of file diff --git a/src/components/MovieCard.css b/src/components/MovieCard.css index 0e7996b..0cfb3af 100644 --- a/src/components/MovieCard.css +++ b/src/components/MovieCard.css @@ -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) { @@ -110,4 +103,4 @@ .movie-card__info { padding-right: 3rem; } -} +} \ No newline at end of file diff --git a/src/components/MovieCard.tsx b/src/components/MovieCard.tsx index 527203a..7396674 100644 --- a/src/components/MovieCard.tsx +++ b/src/components/MovieCard.tsx @@ -1,8 +1,11 @@ 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 @@ -10,36 +13,38 @@ interface MovieCardProps { 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 ( -
- +
{onSelectMovie(movie) }}> + {rating}% - {title} + {movie.title}
-

{title}

- - {isLoading &&

Laster skuespillere...

} - +

{movie.title}

+ {isLoading &&

Laster skuespillere...

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

{topActors.map((actor) => actor.name).join(", ")} diff --git a/src/components/MovieDetails.css b/src/components/MovieDetails.css new file mode 100644 index 0000000..c5478ed --- /dev/null +++ b/src/components/MovieDetails.css @@ -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; +} diff --git a/src/components/MovieDetails.tsx b/src/components/MovieDetails.tsx new file mode 100644 index 0000000..5da3174 --- /dev/null +++ b/src/components/MovieDetails.tsx @@ -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 ( +

+
e.stopPropagation()}> + {movie.title} + + + + + +
+

{movie.title}

+

+ {movie.release_date} · ★ {movie.vote_average.toFixed(1)} +

+ +

Sammendrag

+

{movie.overview}

+ +

Skuespillere

+
+ {isLoading &&

Laster skuespillere...

} + {credits && ( +

{credits.cast.slice(0, 6).map((actor) => actor.name).join(", ")}

+ )} +
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/MovieList.tsx b/src/components/MovieList.tsx index fa9e86a..5af9167 100644 --- a/src/components/MovieList.tsx +++ b/src/components/MovieList.tsx @@ -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
{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. - + /* isFavorite={isFavorite(movie.id)} onToggleFavorite={onToggleFavorite} - /> + */ ))}
); diff --git a/src/components/sidebar.css b/src/components/Sidebar.css similarity index 100% rename from src/components/sidebar.css rename to src/components/Sidebar.css diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx new file mode 100644 index 0000000..c78f3c0 --- /dev/null +++ b/src/components/Sidebar.tsx @@ -0,0 +1,45 @@ +import { SearchField } from './SearchField'; +import './Sidebar.css'; + +type SidebarProps = { + onSearch?: (query: string) => void; + searchValue?: string; +}; + +//under kommer seksjonene i midtkolonnen som sidebaren skal kunne hoppe mellom +const sections = [ + { id: "populaere", label: "Populære" }, + { id: "nye-filmer", label: "Nye Filmer" }, + { id: "skuespillere", label: "Skuespillere" }, +]; + +export function Sidebar({ onSearch, searchValue }: SidebarProps) { + return ( + + + ); +} \ No newline at end of file diff --git a/src/components/TrendingPreview.css b/src/components/TrendingPreview.css index 32702d2..0ef0e66 100644 --- a/src/components/TrendingPreview.css +++ b/src/components/TrendingPreview.css @@ -1,7 +1,8 @@ .hero { position: relative; + /* skal dekke 100% av bredden og ha konstant høyde*/ width: 100%; - height: 30vh; /* adjust to taste, or set a fixed px value */ + height: 30vh; overflow: hidden; border-radius: 1rem; } @@ -20,7 +21,7 @@ color: white; margin: 0; z-index: 2; - cursor: pointer; + font-size: clamp(1.5rem, 4vh, 3rem); } .hero-track { diff --git a/src/components/TrendingPreview.tsx b/src/components/TrendingPreview.tsx index ae18d56..ef53660 100644 --- a/src/components/TrendingPreview.tsx +++ b/src/components/TrendingPreview.tsx @@ -1,12 +1,15 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import type { Movie } from "../types/tmdb"; import "./TrendingPreview.css" +const tmdbURL = `https://image.tmdb.org/t/p/original`; + type TrendingPreviewProps = { movies : Movie[]; + onSelectMovie: (movie: Movie) => void; }; -export function TrendingPreview({ movies }: TrendingPreviewProps) { +export function TrendingPreview({ movies, onSelectMovie }: TrendingPreviewProps) { const [index, setIndex] = useState(0); const [offset, setOffset] = useState(0); const [transitioning, setTransitioning] = useState(false); @@ -27,9 +30,13 @@ export function TrendingPreview({ movies }: TrendingPreviewProps) { }, 400); } - function openDetails() { - return; - } + useEffect(() => { + const timer = setInterval(() => { + handleClick(1); + }, 5000); + + return () => clearInterval(timer); + }, [index, transitioning]); return (
@@ -41,26 +48,26 @@ export function TrendingPreview({ movies }: TrendingPreviewProps) { }} >
-

{movie.title}

+

onSelectMovie(movie)}>{movie.title}

handleClick(-1)}> {"❮"}
-
+
onSelectMovie(movie)} />
handleClick(1)}> {"❯"} diff --git a/src/components/sidebar.tsx b/src/components/sidebar.tsx deleted file mode 100644 index 90b6ca1..0000000 --- a/src/components/sidebar.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import './sidebar.css'; -import { SearchField } from './SearchField'; - -type SidebarProps = { - onSearch?: (query: string) => void; - searchValue?: string; -}; - -//under kommer seksjonene i midtkolonnen som sidebaren skal kunne hoppe mellom -const sections = [ - { id: "populaere", label: "Populære"}, - { id: "nye-filmer", label: "Nye Filmer"}, - { id: "skuespillere", label: "Skuespillere"}, -]; - -export function Sidebar({onSearch, searchValue}: SidebarProps) { - return ( - - - ); -} \ No newline at end of file diff --git a/src/hooks/bak.usePopMovies b/src/hooks/bak.usePopMovies deleted file mode 100644 index 9fe33f4..0000000 --- a/src/hooks/bak.usePopMovies +++ /dev/null @@ -1,10 +0,0 @@ -// hooks/useMovies.ts -import { useQuery } from '@tanstack/react-query' -import { fetchPopularMovies } from '../api/tmdb' - -export function usePopMovies() { - return useQuery({ - queryKey: ['movies', 'popular'], - queryFn: fetchPopularMovies, - }) -} \ No newline at end of file diff --git a/src/tmp.txt b/src/tmp.txt deleted file mode 100644 index 9ca8e8d..0000000 --- a/src/tmp.txt +++ /dev/null @@ -1,122 +0,0 @@ -import { useState } from 'react' -import heroImg from './assets/hero.png' -import reactLogo from './assets/react.svg' -import viteLogo from './assets/vite.svg' -import './App.css' - -function App() { - const [count, setCount] = useState(0) - - return ( - <> -
-
- - React logo - Vite logo -
-
-

Get started

-

- Edit src/App.tsx and save to test HMR -

-
- -
- -
- -
-
- -

Documentation

-

Your questions, answered

- -
-
- -

Connect with us

-

Join the Vite community

- -
-
- -
-
- - ) -} - -export default App