diff --git a/src/App.tsx b/src/App.tsx index 34be670b0..4107e3e49 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'; +import { NewMovie } from './components/NewMovie/NewMovie'; + +export const App: React.FC = () => { + const [movies, setMovies] = useState(moviesFromServer); + + const addMovie = (newMovie: Movie) => { + setMovies(currentMovies => [...currentMovies, newMovie]); + }; -export const App = () => { return (
- +
- {}} */ /> +
); diff --git a/src/components/MoviesList/MoviesList.tsx b/src/components/MoviesList/MoviesList.tsx index e2cf5980e..d75f2c16a 100644 --- a/src/components/MoviesList/MoviesList.tsx +++ b/src/components/MoviesList/MoviesList.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import './MoviesList.scss'; import { MovieCard } from '../MovieCard'; import { Movie } from '../../types/Movie'; +import './MoviesList.scss'; interface Props { movies: Movie[]; diff --git a/src/components/NewMovie/NewMovie.scss b/src/components/NewMovie/NewMovie.scss index 71bc413aa..da38762ee 100644 --- a/src/components/NewMovie/NewMovie.scss +++ b/src/components/NewMovie/NewMovie.scss @@ -1 +1,16 @@ -// not empty +.new-movie-form { + display: flex; + flex-direction: column; + align-items: flex-end; /* Align items to the right */ +} + +.form-field { + margin-bottom: 10px; /* Add some spacing between fields */ + display: flex; + flex-direction: column; + align-items: flex-end; /* Align items to the right */ +} + +label { + margin-bottom: 5px; +} diff --git a/src/components/NewMovie/NewMovie.tsx b/src/components/NewMovie/NewMovie.tsx index 34f22fb0a..0b35fc1a8 100644 --- a/src/components/NewMovie/NewMovie.tsx +++ b/src/components/NewMovie/NewMovie.tsx @@ -1,45 +1,120 @@ import { useState } from 'react'; import { TextField } from '../TextField'; +import { Movie } from '../../types/Movie'; +import { urlValidate } from '../utsils/UrlValidate'; -export const NewMovie = () => { - // Increase the count after successful form submission - // to reset touched status of all the `Field`s - const [count] = useState(0); +interface Props { + onAdd: (movie: Movie) => void +} + +export const NewMovie: React.FC = ({ onAdd }) => { + const [count, setCount] = useState(0); + + const [newMovie, setNewMovie] = useState({ + title: '', + description: '', + imgUrl: '', + imdbUrl: '', + imdbId: '', + }); + + const { + title, + description, + imgUrl, + imdbUrl, + imdbId, + } = newMovie; + + const isSubmitDisabled = !title.trim() + || !imgUrl.trim() + || !imdbId.trim() + || !imdbUrl.trim(); + + const [hasImgUrlError, setHasImgUrlError] = useState(false); + const [hasImdbUrlError, setHasImdbUrlError] = useState(false); + + const handleInput = (event: React.ChangeEvent): void => { + const { name, value } = event.target; + + setNewMovie(prev => ({ + ...prev, + [name]: value, + })); + }; + + const onUrlCheck = () => { + setHasImdbUrlError(urlValidate(imdbUrl)); + setHasImgUrlError(urlValidate(imgUrl)); + }; + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + + if (hasImdbUrlError || hasImgUrlError) { + return; + } + + onAdd(newMovie); + + setNewMovie({ + title: '', + description: '', + imgUrl: '', + imdbUrl: '', + imdbId: '', + }); + + setCount(prevCount => prevCount + 1); + }; return ( -
+

Add a movie

{}} + value={title} + onChange={handleInput} required />
@@ -48,6 +123,8 @@ export const NewMovie = () => { type="submit" data-cy="submit-button" className="button is-link" + disabled={isSubmitDisabled} + onClick={onUrlCheck} > Add diff --git a/src/components/NewMovie/index.ts b/src/components/NewMovie/index.ts deleted file mode 100644 index b63e22098..000000000 --- a/src/components/NewMovie/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './NewMovie'; diff --git a/src/components/TextField/TextField.tsx b/src/components/TextField/TextField.tsx index 307b19865..3997972e4 100644 --- a/src/components/TextField/TextField.tsx +++ b/src/components/TextField/TextField.tsx @@ -1,5 +1,5 @@ -import classNames from 'classnames'; import React, { useState } from 'react'; +import classNames from 'classnames'; type Props = { name: string, @@ -7,7 +7,8 @@ type Props = { label?: string, placeholder?: string, required?: boolean, - onChange?: (newValue: string) => void, + onChange: (newValue: React.ChangeEvent) => void, + hasUrlError?: boolean, }; function getRandomDigits() { @@ -23,11 +24,10 @@ export const TextField: React.FC = ({ placeholder = `Enter ${label}`, required = false, onChange = () => {}, + hasUrlError, }) => { - // generage a unique id once on component load const [id] = useState(() => `${name}-${getRandomDigits()}`); - // To show errors only if the field was touched (onBlur) const [touched, setTouched] = useState(false); const hasError = touched && required && !value; @@ -39,15 +39,16 @@ export const TextField: React.FC = ({
onChange(event.target.value)} + onChange={event => onChange(event)} onBlur={() => setTouched(true)} />
@@ -55,6 +56,10 @@ export const TextField: React.FC = ({ {hasError && (

{`${label} is required`}

)} + + {hasUrlError && value && ( +

{`${label} is incorrect`}

+ )}
); }; diff --git a/src/components/utsils/UrlValidate.ts b/src/components/utsils/UrlValidate.ts new file mode 100644 index 000000000..e853334c5 --- /dev/null +++ b/src/components/utsils/UrlValidate.ts @@ -0,0 +1,6 @@ +// 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]*))?)$/; + +export const urlValidate = (url: string): boolean => { + return !pattern.test(url); +};