diff --git a/src/App.tsx b/src/App.tsx index 34be670b0..45a1ee03d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,24 @@ +import { useState } from 'react'; import './App.scss'; import { MoviesList } from './components/MoviesList'; import { NewMovie } from './components/NewMovie'; +import { Movie } from './types/Movie'; import moviesFromServer from './api/movies.json'; export const App = () => { + const [movies, setMovies] = useState(moviesFromServer); + + const handleAddMovie = (newMovie: Movie) => { + setMovies(prevMovies => [...prevMovies, newMovie]); + }; + return (
- +
- {}} */ /> +
); diff --git a/src/components/NewMovie/NewMovie.tsx b/src/components/NewMovie/NewMovie.tsx index 85bace9dd..1013dda9c 100644 --- a/src/components/NewMovie/NewMovie.tsx +++ b/src/components/NewMovie/NewMovie.tsx @@ -1,30 +1,109 @@ import { 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 urlPattern = + // eslint-disable-next-line + /^((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www\.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@,.\w_]*)#?(?:[,.!/\\\w]*))?)$/; + +const initialFormData = { + title: '', + description: '', + imgUrl: '', + imdbUrl: '', + imdbId: '', +}; + +export const NewMovie: React.FC = ({ onAdd }) => { + const [count, setCount] = useState(0); + const [formData, setFormData] = useState(initialFormData); + + const { title, description, imgUrl, imdbUrl, imdbId } = formData; + + const validateUrl = (value: string) => { + return !urlPattern.test(value) ? 'Please enter a valid URL' : null; + }; + + const isFormValid = + title.trim() && + validateUrl(imgUrl) === null && + validateUrl(imdbUrl) === null && + imdbId.trim(); + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + + setFormData(prevData => ({ ...prevData, [name]: value })); + }; + + const clearForm = () => { + setFormData(initialFormData); + }; + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + + const trimmedData = Object.keys(formData).reduce((acc, key) => { + const typedKey = key as keyof Movie; + + return { ...acc, [typedKey]: formData[typedKey].trim() }; + }, {} as Movie); + + onAdd(trimmedData); + + setCount(prevCount => prevCount + 1); + + clearForm(); + }; return ( -
+

Add a movie

{}} + value={title} + onChange={handleChange} required /> - + - + - + - +
@@ -32,6 +111,7 @@ export const NewMovie = () => { type="submit" data-cy="submit-button" className="button is-link" + disabled={!isFormValid} > Add diff --git a/src/components/TextField/TextField.tsx b/src/components/TextField/TextField.tsx index e24856c4b..68038b7f3 100644 --- a/src/components/TextField/TextField.tsx +++ b/src/components/TextField/TextField.tsx @@ -7,7 +7,8 @@ type Props = { label?: string; placeholder?: string; required?: boolean; - onChange?: (newValue: string) => void; + onChange?: (event: React.ChangeEvent) => void; + validate?: (value: string) => string | null; }; function getRandomDigits() { @@ -21,13 +22,18 @@ export const TextField: React.FC = ({ placeholder = `Enter ${label}`, required = false, onChange = () => {}, + validate = () => null, }) => { // generate 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; + + const requiredError = + required && !value.trim() ? `${label} is required` : null; + const customError = validate(value); + const error = requiredError || customError; return (
@@ -41,16 +47,17 @@ export const TextField: React.FC = ({ id={id} data-cy={`movie-${name}`} className={classNames('input', { - 'is-danger': hasError, + 'is-danger': touched && error, })} placeholder={placeholder} + name={name} value={value} - onChange={event => onChange(event.target.value)} + onChange={onChange} onBlur={() => setTouched(true)} />
- {hasError &&

{`${label} is required`}

} + {touched && error &&

{error}

}
); };