mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 13:38:51 +00:00
Update sample code
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React</title>
|
<title>usePopcorn</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
+168
-38
@@ -1,5 +1,6 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import StarRating from "./StarRating";
|
||||||
|
|
||||||
const average = (arr) =>
|
const average = (arr) =>
|
||||||
arr.reduce((acc, cur, i, arr) => acc + cur / arr.length, 0);
|
arr.reduce((acc, cur, i, arr) => acc + cur / arr.length, 0);
|
||||||
@@ -23,13 +24,25 @@ export default function App() {
|
|||||||
setSelectedId(null);
|
setSelectedId(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddWatched = (movie) => {
|
||||||
|
setWatched((watched) => [...watched, movie]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteWatched = (id) => {
|
||||||
|
setWatched((watched) => watched.filter((movie) => movie.imdbID !== id));
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
|
||||||
const fetchMovies = async () => {
|
const fetchMovies = async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError("");
|
setError("");
|
||||||
|
|
||||||
const res = await fetch(BASE_URL + `?apikey=${KEY}&s=${query}`);
|
const res = await fetch(BASE_URL + `?apikey=${KEY}&s=${query}`, {
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
if (!res.ok)
|
if (!res.ok)
|
||||||
throw new Error("Something went wrong with fetching movies");
|
throw new Error("Something went wrong with fetching movies");
|
||||||
@@ -39,8 +52,9 @@ export default function App() {
|
|||||||
if (data.Response === "False") throw new Error("Movie not found!");
|
if (data.Response === "False") throw new Error("Movie not found!");
|
||||||
|
|
||||||
setMovies(data.Search);
|
setMovies(data.Search);
|
||||||
|
setError("");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message);
|
if (err.name !== "AbortError") setError(err.message);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@@ -53,6 +67,10 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchMovies();
|
fetchMovies();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
controller.abort();
|
||||||
|
};
|
||||||
}, [query]);
|
}, [query]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -77,11 +95,16 @@ export default function App() {
|
|||||||
<MovieDetails
|
<MovieDetails
|
||||||
selectedId={selectedId}
|
selectedId={selectedId}
|
||||||
onCloseMovie={handleCloseMovie}
|
onCloseMovie={handleCloseMovie}
|
||||||
|
onAddWatched={handleAddWatched}
|
||||||
|
watched={watched}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<WatchedSummary watched={watched} />
|
<WatchedSummary watched={watched} />
|
||||||
<WatchMovieList watched={watched} />
|
<WatchMovieList
|
||||||
|
watched={watched}
|
||||||
|
onDeleteWatched={handleDeleteWatched}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
@@ -153,28 +176,6 @@ function Box({ children }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// function WatchedBox() {
|
|
||||||
// const [watched, setWatched] = useState(tempWatchedData);
|
|
||||||
// const [isOpen2, setIsOpen2] = useState(true);
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <div className="box">
|
|
||||||
// <button
|
|
||||||
// className="btn-toggle"
|
|
||||||
// onClick={() => setIsOpen2((open) => !open)}
|
|
||||||
// >
|
|
||||||
// {isOpen2 ? "–" : "+"}
|
|
||||||
// </button>
|
|
||||||
// {isOpen2 && (
|
|
||||||
// <>
|
|
||||||
// <WatchedSummary watched={watched} />
|
|
||||||
// <WatchMovieList watched={watched} />
|
|
||||||
// </>
|
|
||||||
// )}
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
function MovieList({ movies, onSelectMovie }) {
|
function MovieList({ movies, onSelectMovie }) {
|
||||||
return (
|
return (
|
||||||
<ul className="list list-movies">
|
<ul className="list list-movies">
|
||||||
@@ -200,13 +201,132 @@ function Movie({ movie, onSelectMovie }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MovieDetails({ selectedId, onCloseMovie }) {
|
function MovieDetails({ selectedId, onCloseMovie, onAddWatched, watched }) {
|
||||||
|
const [movie, setMovie] = useState({});
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [userRating, setUserRating] = useState(0);
|
||||||
|
|
||||||
|
const isWatched = watched.map((movie) => movie.imdbID).includes(selectedId);
|
||||||
|
const watchedUserRating = watched.find(
|
||||||
|
(movie) => movie.imdbID === selectedId
|
||||||
|
)?.userRating;
|
||||||
|
|
||||||
|
const {
|
||||||
|
Title: title,
|
||||||
|
Year: year,
|
||||||
|
Poster: poster,
|
||||||
|
Runtime: runtime,
|
||||||
|
imdbRating,
|
||||||
|
Plot: plot,
|
||||||
|
Released: released,
|
||||||
|
Actors: actors,
|
||||||
|
Director: director,
|
||||||
|
Genre: genre,
|
||||||
|
} = movie;
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
const newWatchedMovie = {
|
||||||
|
imdbID: selectedId,
|
||||||
|
title,
|
||||||
|
year,
|
||||||
|
poster,
|
||||||
|
imdbRating: Number(imdbRating),
|
||||||
|
runtime: Number(runtime.split(" ").at(0)),
|
||||||
|
userRating,
|
||||||
|
};
|
||||||
|
|
||||||
|
onAddWatched(newWatchedMovie);
|
||||||
|
onCloseMovie();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!title) return;
|
||||||
|
document.title = `Movie | ${title}`;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.title = "usePopcorn";
|
||||||
|
};
|
||||||
|
}, [title]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function getMovieDetails() {
|
||||||
|
setIsLoading(true);
|
||||||
|
const res = await fetch(BASE_URL + `?apikey=${KEY}&i=${selectedId}`);
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
setMovie(data);
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
getMovieDetails();
|
||||||
|
}, [selectedId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const callback = (e) => {
|
||||||
|
if (e.code === "Escape") {
|
||||||
|
onCloseMovie();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("keydown", callback);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("keydown", callback);
|
||||||
|
};
|
||||||
|
}, [onCloseMovie]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="details">
|
<div className="details">
|
||||||
<button className="btn-back" onClick={onCloseMovie}>
|
{isLoading ? (
|
||||||
←
|
<Loader />
|
||||||
</button>
|
) : (
|
||||||
{selectedId}
|
<>
|
||||||
|
<header>
|
||||||
|
<button className="btn-back" onClick={onCloseMovie}>
|
||||||
|
←
|
||||||
|
</button>
|
||||||
|
<img src={poster} alt={`Poster of ${movie}`} />
|
||||||
|
<div className="details-overview">
|
||||||
|
<h2>{title}</h2>
|
||||||
|
<p>
|
||||||
|
{released} • {runtime}
|
||||||
|
</p>
|
||||||
|
<p>{genre}</p>
|
||||||
|
<p>
|
||||||
|
<span>⭐</span>
|
||||||
|
{imdbRating} IMD rating
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div className="rating">
|
||||||
|
{!isWatched ? (
|
||||||
|
<>
|
||||||
|
<StarRating
|
||||||
|
maxRating={10}
|
||||||
|
size={24}
|
||||||
|
onSetRating={setUserRating}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{userRating > 0 && (
|
||||||
|
<button className="btn-add" onClick={handleAdd}>
|
||||||
|
+ Add to list
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<p>You rated this movie {watchedUserRating} ⭐</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<em>{plot}</em>
|
||||||
|
</p>
|
||||||
|
<p>Starring {actors}</p>
|
||||||
|
<p>Directed by {director}</p>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -226,36 +346,40 @@ function WatchedSummary({ watched }) {
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span>⭐️</span>
|
<span>⭐️</span>
|
||||||
<span>{avgImdbRating}</span>
|
<span>{avgImdbRating.toFixed(2)}</span>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span>🌟</span>
|
<span>🌟</span>
|
||||||
<span>{avgUserRating}</span>
|
<span>{avgUserRating.toFixed(2)}</span>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span>⏳</span>
|
<span>⏳</span>
|
||||||
<span>{avgRuntime} min</span>
|
<span>{avgRuntime.toFixed(2)} min</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function WatchMovieList({ watched }) {
|
function WatchMovieList({ watched, onDeleteWatched }) {
|
||||||
return (
|
return (
|
||||||
<ul className="list">
|
<ul className="list">
|
||||||
{watched.map((movie) => (
|
{watched.map((movie) => (
|
||||||
<WatchedMovie movie={movie} key={movie.imdbID} />
|
<WatchedMovie
|
||||||
|
movie={movie}
|
||||||
|
key={movie.imdbID}
|
||||||
|
onDeleteWatched={onDeleteWatched}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function WatchedMovie({ movie }) {
|
function WatchedMovie({ movie, onDeleteWatched }) {
|
||||||
return (
|
return (
|
||||||
<li key={movie.imdbID}>
|
<li key={movie.imdbID}>
|
||||||
<img src={movie.Poster} alt={`${movie.Title} poster`} />
|
<img src={movie.poster} alt={`${movie.title} poster`} />
|
||||||
<h3>{movie.Title}</h3>
|
<h3>{movie.title}</h3>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<span>⭐️</span>
|
<span>⭐️</span>
|
||||||
@@ -269,6 +393,12 @@ function WatchedMovie({ movie }) {
|
|||||||
<span>⏳</span>
|
<span>⏳</span>
|
||||||
<span>{movie.runtime} min</span>
|
<span>{movie.runtime} min</span>
|
||||||
</p>
|
</p>
|
||||||
|
<button
|
||||||
|
className="btn-delete"
|
||||||
|
onClick={() => onDeleteWatched(movie.imdbID)}
|
||||||
|
>
|
||||||
|
X
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export default function StarRating({
|
|||||||
const handleRating = (rating) => {
|
const handleRating = (rating) => {
|
||||||
setRating(rating);
|
setRating(rating);
|
||||||
|
|
||||||
if (onSetRating) setRating(rating);
|
if (onSetRating) onSetRating(rating);
|
||||||
};
|
};
|
||||||
|
|
||||||
const textStyle = {
|
const textStyle = {
|
||||||
|
|||||||
Reference in New Issue
Block a user