From 850ee02e7f14c6946c99a882bfa60d627102ffb7 Mon Sep 17 00:00:00 2001 From: Bohdan Liutyi Date: Thu, 18 Jan 2024 12:57:53 +0200 Subject: [PATCH 1/3] 1 --- src/App.tsx | 14 ++++-- src/components/NewMovie/NewMovie.tsx | 66 +++++++++++++++++++++++----- 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 34be670b0..31489489a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,24 @@ import './App.scss'; +import React, { useState } from 'react'; import { MoviesList } from './components/MoviesList'; import { NewMovie } from './components/NewMovie'; +import { Movie } from './types/Movie'; import moviesFromServer from './api/movies.json'; -export const App = () => { +export const App: React.FC = () => { + const [movies, setMovies] = useState(moviesFromServer); + + const addMovie = (NewMovie: Movie) => { + setMovies(currentMovies => [...currentMovies, NewMovie]); + }; + return (
- +
- {}} */ /> +
); diff --git a/src/components/NewMovie/NewMovie.tsx b/src/components/NewMovie/NewMovie.tsx index 34f22fb0a..5d18153a3 100644 --- a/src/components/NewMovie/NewMovie.tsx +++ b/src/components/NewMovie/NewMovie.tsx @@ -1,45 +1,86 @@ -import { useState } from 'react'; +import React, { useState } from 'react'; import { TextField } from '../TextField'; +import { Movie } from '../../types/Movie'; -export const NewMovie = () => { - // Increase the count after successful form submission - // to reset touched status of all the `Field`s - const [count] = useState(0); +type Props = { + onAdd: (movie: Movie) => void; +}; + +const defaultMovie = { + title: '', + description: '', + imgUrl: '', + imdbUrl: '', + imdbId: '', +}; + +export const NewMovie: React.FC = ({ onAdd }) => { + const [count, setCount] = useState(0); + const [movie, setMovie] = useState(defaultMovie); + + const handleInputChange = (key: string, value: string) => { + setMovie(currentState => ({ ...currentState, [key]: value })); + }; + + // eslint-disable-next-line @typescript-eslint/no-shadow + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + + onAdd(movie); + + setCount(currentCount => currentCount + 1); + + setMovie(defaultMovie); + }; + + const hasError = !movie.title.trim() || !movie.imdbUrl.trim() + || !movie.imdbUrl.trim() || !movie.imdbId.trim(); return ( -
+

Add a movie

{}} + value={movie.title} + onChange={(value) => handleInputChange('title', value)} required /> handleInputChange('description', value)} /> handleInputChange('imgUrl', value)} + required /> handleInputChange('imdbUrl', value)} + required /> handleInputChange('imdbId', value)} + required />
@@ -48,6 +89,7 @@ export const NewMovie = () => { type="submit" data-cy="submit-button" className="button is-link" + disabled={hasError} > Add From 148479b33a8bef57acf89d4a332107bd9080200a Mon Sep 17 00:00:00 2001 From: Bohdan Liutyi Date: Thu, 18 Jan 2024 13:02:14 +0200 Subject: [PATCH 2/3] created added movies list --- src/App.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 31489489a..dcfd961b5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,8 +8,8 @@ import moviesFromServer from './api/movies.json'; export const App: React.FC = () => { const [movies, setMovies] = useState(moviesFromServer); - const addMovie = (NewMovie: Movie) => { - setMovies(currentMovies => [...currentMovies, NewMovie]); + const addMovie = (newMovie: Movie) => { + setMovies(currentMovies => [...currentMovies, newMovie]); }; return ( From 6a35c78777694529af1e0b3502c218c8ee52938f Mon Sep 17 00:00:00 2001 From: Bohdan Liutyi Date: Fri, 19 Jan 2024 18:59:20 +0200 Subject: [PATCH 3/3] error spacing in image and imdb section --- src/App.tsx | 25 ++++--- src/components/NewMovie/NewMovie.tsx | 99 +++++++++++++++----------- src/components/TextField/TextField.tsx | 4 ++ 3 files changed, 79 insertions(+), 49 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index dcfd961b5..6a403ae7d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,25 +1,32 @@ import './App.scss'; -import React, { useState } from 'react'; import { MoviesList } from './components/MoviesList'; import { NewMovie } from './components/NewMovie'; -import { Movie } from './types/Movie'; import moviesFromServer from './api/movies.json'; +import { useState } from 'react'; -export const App: React.FC = () => { - const [movies, setMovies] = useState(moviesFromServer); +export type State = { + title: string, + description: string, + imgUrl: string, + imdbUrl: string, + imdbId: string +}; + +export const App = () => { + const [movieList, setMovieList] = useState([...moviesFromServer]); - const addMovie = (newMovie: Movie) => { - setMovies(currentMovies => [...currentMovies, newMovie]); + const handleAddMovie = (newMovie: State) => { + setMovieList((prevMovies) => [...prevMovies, newMovie]); }; return (
- +
- +
-
+
); }; diff --git a/src/components/NewMovie/NewMovie.tsx b/src/components/NewMovie/NewMovie.tsx index 5d18153a3..bcec5cfc0 100644 --- a/src/components/NewMovie/NewMovie.tsx +++ b/src/components/NewMovie/NewMovie.tsx @@ -1,85 +1,101 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { TextField } from '../TextField'; -import { Movie } from '../../types/Movie'; -type Props = { - onAdd: (movie: Movie) => void; -}; - -const defaultMovie = { - title: '', - description: '', - imgUrl: '', - imdbUrl: '', - imdbId: '', -}; +interface Props { + onAdd: ( + movie: { + title: string, + description: string, + imgUrl: string, + imdbUrl: string, + imdbId: string, + } + ) => void +} export const NewMovie: React.FC = ({ onAdd }) => { const [count, setCount] = useState(0); - const [movie, setMovie] = useState(defaultMovie); - - const handleInputChange = (key: string, value: string) => { - setMovie(currentState => ({ ...currentState, [key]: value })); - }; - - // eslint-disable-next-line @typescript-eslint/no-shadow - const handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + const [imgUrl, setImgUrl] = useState(''); + const [imdbUrl, setimdbUrl] = useState(''); + const [imdbId, setImdbId] = useState(''); - onAdd(movie); + const urlValidator = (urlString: string): boolean => { + // eslint-disable-next-line max-len + const pattern = /^((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www\.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@,.\w_]*)#?(?:[,.!/\\\w]*))?)$/; - setCount(currentCount => currentCount + 1); + if (!urlString.match(pattern)) { + return false; + } - setMovie(defaultMovie); + return true; }; - const hasError = !movie.title.trim() || !movie.imdbUrl.trim() - || !movie.imdbUrl.trim() || !movie.imdbId.trim(); + function addMovie(event: React.FormEvent) { + event.preventDefault(); + onAdd({ + title: title.trim(), + description: description.trim(), + imgUrl: imgUrl.trim(), + imdbUrl: imdbUrl.trim(), + imdbId: imdbId.trim(), + }); + + setTitle(''); + setDescription(''); + setImgUrl(''); + setimdbUrl(''); + setImdbId(''); + + setCount(prev => prev + 1); + } return (

Add a movie

handleInputChange('title', value)} + value={title} + onChange={setTitle} required /> handleInputChange('description', value)} + value={description} + onChange={setDescription} /> handleInputChange('imgUrl', value)} + value={imgUrl} + onChange={setImgUrl} + checkUrl={urlValidator} required /> handleInputChange('imdbUrl', value)} + value={imdbUrl} + onChange={setimdbUrl} + checkUrl={urlValidator} required /> handleInputChange('imdbId', value)} + value={imdbId} + onChange={setImdbId} required /> @@ -89,7 +105,10 @@ export const NewMovie: React.FC = ({ onAdd }) => { type="submit" data-cy="submit-button" className="button is-link" - disabled={hasError} + disabled={!title + || !urlValidator(imgUrl) + || !urlValidator(imdbUrl) + || !imdbId} > Add diff --git a/src/components/TextField/TextField.tsx b/src/components/TextField/TextField.tsx index 307b19865..0a641b6b6 100644 --- a/src/components/TextField/TextField.tsx +++ b/src/components/TextField/TextField.tsx @@ -8,6 +8,7 @@ type Props = { placeholder?: string, required?: boolean, onChange?: (newValue: string) => void, + checkUrl?: (urlString: string) => boolean, }; function getRandomDigits() { @@ -23,6 +24,7 @@ export const TextField: React.FC = ({ placeholder = `Enter ${label}`, required = false, onChange = () => {}, + checkUrl = undefined, }) => { // generage a unique id once on component load const [id] = useState(() => `${name}-${getRandomDigits()}`); @@ -55,6 +57,8 @@ export const TextField: React.FC = ({ {hasError && (

{`${label} is required`}

)} + + {((touched && value) && (checkUrl && !checkUrl(value))) && (

{`${label} incorrect URL`}

)} ); };