diff --git a/package-lock.json b/package-lock.json index d7fd1edba..604c17859 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ }, "devDependencies": { "@cypress/react18": "^2.0.1", - "@mate-academy/scripts": "^1.8.5", + "@mate-academy/scripts": "^1.9.12", "@mate-academy/students-ts-config": "*", "@mate-academy/stylelint-config": "*", "@types/node": "^20.14.10", @@ -1170,10 +1170,11 @@ } }, "node_modules/@mate-academy/scripts": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.8.5.tgz", - "integrity": "sha512-mHRY2FkuoYCf5U0ahIukkaRo5LSZsxrTSgMJheFoyf3VXsTvfM9OfWcZIDIDB521kdPrScHHnRp+JRNjCfUO5A==", + "version": "1.9.12", + "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.9.12.tgz", + "integrity": "sha512-/OcmxMa34lYLFlGx7Ig926W1U1qjrnXbjFJ2TzUcDaLmED+A5se652NcWwGOidXRuMAOYLPU2jNYBEkKyXrFJA==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/rest": "^17.11.2", "@types/get-port": "^4.2.0", diff --git a/package.json b/package.json index dda6b26a3..8bd99f7a5 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@cypress/react18": "^2.0.1", - "@mate-academy/scripts": "^1.8.5", + "@mate-academy/scripts": "^1.9.12", "@mate-academy/students-ts-config": "*", "@mate-academy/stylelint-config": "*", "@types/node": "^20.14.10", diff --git a/src/App.tsx b/src/App.tsx index d570e763a..589e26ba3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,9 +3,36 @@ import './App.scss'; import { MoviesList } from './components/MoviesList'; import { FindMovie } from './components/FindMovie'; import { Movie } from './types/Movie'; +import { getMovie } from './api'; +import { normalizeMovieData } from './components/utils/normalize'; +import { isResponseError } from './components/utils/typeGuards'; export const App = () => { - const [movies] = useState([]); + const [movies, setMovies] = useState([]); + const [previewMovie, setPreviewMovie] = useState(null); + const [error, setError] = useState(null); + + const handleSearch = async (query: string) => { + const result = await getMovie(query); + + if (isResponseError(result)) { + setError(result.Error); + setPreviewMovie(null); + + return; + } + + const movie = normalizeMovieData(result); + + setPreviewMovie(movie); + setError(null); + }; + + const handleAddMovie = (movie: Movie) => { + if (!movies.some(existingMovie => existingMovie.imdbId === movie.imdbId)) { + setMovies([...movies, movie]); + } + }; return (
@@ -14,7 +41,14 @@ export const App = () => {
- + + {error &&

{error}

}{' '} + {/* Display error message */}
); diff --git a/src/api.ts b/src/api.ts index 0d449f883..3062c6700 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,7 +1,7 @@ import { MovieData } from './types/MovieData'; -import { ResponseError } from './types/ReponseError'; +import { ResponseError } from './types/ResponseError'; -const API_URL = 'https://www.omdbapi.com/?apikey=your-key'; +const API_URL = 'https://www.omdbapi.com/?apikey=bbd52085'; export function getMovie(query: string): Promise { return fetch(`${API_URL}&t=${query}`) diff --git a/src/components/FindMovie/FindMovie.tsx b/src/components/FindMovie/FindMovie.tsx index ba0b418bf..8709c222e 100644 --- a/src/components/FindMovie/FindMovie.tsx +++ b/src/components/FindMovie/FindMovie.tsx @@ -1,57 +1,108 @@ -import React from 'react'; +import React, { useState } from 'react'; import './FindMovie.scss'; +import { Movie } from '../../types/Movie'; +import { MovieCard } from '../MovieCard'; -export const FindMovie: React.FC = () => { - return ( - <> -
-
- +type Props = { + onSearch: (query: string) => void; + onAddMovie: (movie: Movie) => void; + previewMovie: Movie | null; + setPreviewMovie: (movie: Movie | null) => void; +}; -
- -
+export const FindMovie: React.FC = ({ onSearch, onAddMovie, previewMovie, setPreviewMovie }) => { + const [title, setTitle] = useState(''); + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(false); + + const handleInputChange = (event: React.ChangeEvent) => { + setTitle(event.target.value); + if (error) setError(null); + }; + + const handleSearch = async (event: React.FormEvent) => { + event.preventDefault(); + if (!title.trim()) return; + setIsLoading(true); + setError(null); + + try { + await onSearch(title.trim()); + } catch { + setError("An unexpected error occurred."); + } finally { + setIsLoading(false); + } + }; + + const handleAddMovie = () => { + if (previewMovie) { + onAddMovie(previewMovie); + setPreviewMovie(null); + setTitle(''); + } + }; + + const handleKeyPress = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + handleSearch(event as any); + } + }; + + return ( + +
+ +
+ +
+ {error && (

- Can't find a movie with such a title + {error}

-
+ )} +
-
-
- -
+
+
+ +
+ {previewMovie && (
-
- - -
-

Preview

- {/* */} + )}
- + + {previewMovie && ( +
+ +
+ )} + ); }; diff --git a/src/components/utils/normalize.tsx b/src/components/utils/normalize.tsx new file mode 100644 index 000000000..d28ff9332 --- /dev/null +++ b/src/components/utils/normalize.tsx @@ -0,0 +1,12 @@ +import { MovieData } from '../../types/MovieData'; +import { Movie } from '../../types/Movie'; + +export function normalizeMovieData(data: MovieData): Movie { + return { + imdbId: data.imdbID, + title: data.Title, + description: data.Plot, + imgUrl: data.Poster !== 'N/A' ? data.Poster : 'default_poster_url.jpg', // Replace with an actual default URL if desired + imdbUrl: `https://www.imdb.com/title/${data.imdbID}`, + }; +} diff --git a/src/components/utils/typeGuards.ts b/src/components/utils/typeGuards.ts new file mode 100644 index 000000000..3e8fcdc4b --- /dev/null +++ b/src/components/utils/typeGuards.ts @@ -0,0 +1,8 @@ +import { ResponseError } from '../../types/ResponseError'; +import { MovieData } from '../../types/MovieData'; + +export function isResponseError( + response: MovieData | ResponseError, +): response is ResponseError { + return (response as ResponseError).Response === 'False'; +} diff --git a/src/types/ReponseError.ts b/src/types/ResponseError.ts similarity index 100% rename from src/types/ReponseError.ts rename to src/types/ResponseError.ts