diff --git a/README.md b/README.md index 6001d15be..5bfcfa7a0 100644 --- a/README.md +++ b/README.md @@ -30,4 +30,4 @@ const pattern = /^((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|( - Implement a solution following the [React task guideline](https://github.com/mate-academy/react_task-guideline#react-tasks-guideline). - Use the [React TypeScript cheat sheet](https://mate-academy.github.io/fe-program/js/extra/react-typescript). - Open one more terminal and run tests with `npm test` to ensure your solution is correct. -- Replace `` with your Github username in the [DEMO LINK](https://.github.io/react_movies-list-add-form/) and add it to the PR description. +- Replace `` with your Github username in the [DEMO LINK](https://hyrych-max.github.io/react_movies-list-add-form/) and add it to the PR description. diff --git a/package-lock.json b/package-lock.json index 70de659d2..814f3b97c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3378,9 +3378,9 @@ } }, "@mate-academy/scripts": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.2.12.tgz", - "integrity": "sha512-ZXmhAzDKVVDqJmO8soZ7EUYDdWm5BkRL4iV4dKTnpUSrWuv3ch8dUmvXZnXhpE/ptP3JqkAb5lfS8CCCj8Yeng==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.7.0.tgz", + "integrity": "sha512-lXQk6b246J8owPy7owFwJaHiDO5Tip9uaDTWYqs/S9YrcGek7ASqIjfDwOckv7pgV8PFPjI5HBAmVY8ZUNS2ig==", "dev": true, "requires": { "@octokit/rest": "^17.11.2", diff --git a/package.json b/package.json index af418a7a4..0622b25ed 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@mate-academy/cypress-tools": "^1.0.4", "@mate-academy/eslint-config-react": "^0.0.11", "@mate-academy/eslint-config-react-typescript": "^1.0.12", - "@mate-academy/scripts": "^1.2.12", + "@mate-academy/scripts": "^1.7.0", "@mate-academy/students-ts-config": "*", "@mate-academy/stylelint-config": "^0.0.11", "@types/node": "^17.0.23", diff --git a/src/App.tsx b/src/App.tsx index 34be670b0..c2dcda710 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,26 @@ -import './App.scss'; +import { useState } from 'react'; + import { MoviesList } from './components/MoviesList'; import { NewMovie } from './components/NewMovie'; import moviesFromServer from './api/movies.json'; +import { Movie } from './types/Movie'; +import './App.scss'; + export const App = () => { + const [films, setFilms] = useState(moviesFromServer); + + const addFilms = (newFilms: Movie) => { + setFilms(currentFilms => [...currentFilms, newFilms]); + }; + return (
- +
- {}} */ /> +
); diff --git a/src/components/NewMovie/NewMovie.tsx b/src/components/NewMovie/NewMovie.tsx index 34f22fb0a..c393f78c0 100644 --- a/src/components/NewMovie/NewMovie.tsx +++ b/src/components/NewMovie/NewMovie.tsx @@ -1,45 +1,117 @@ -import { useState } from 'react'; +import React, { useState } from 'react'; + import { TextField } from '../TextField'; +import { Movie } from '../../types/Movie'; + +type Props = { + onAdd: (count: Movie) => void; +}; + +function isValidUrl(url: string) { + const pattern = new RegExp( + '^((([A-Za-z]{3,9}:(?:\\/\\/)?)(?:[-;:&=+$,\\w]+@)?[A-Za-z0-9.-]+|' + + '(?:www\\.|[-;:&=+$,\\w]+@)[A-Za-z0-9.-]+)((?:\\/[+~%/\\.\\w-_]*)?' + + '\\??(?:[-+=&;%@,\\.\\w_]*)#?(?:[,.!/\\\\\\w]*))?)$', + ); + + return pattern.test(url); +} -export const NewMovie = () => { +export const NewMovie: React.FC = ({ onAdd }) => { // Increase the count after successful form submission // to reset touched status of all the `Field`s - const [count] = useState(0); + const [count, setCount] = useState(0); + const [newMovie, setNewMovie] = useState({ + title: '', + description: '', + imgUrl: '', + imdbUrl: '', + imdbId: '', + }); + + let isDisabled = true; + + if (newMovie.title.trim() + && newMovie.imgUrl.trim() + && newMovie.imdbUrl.trim() + && newMovie.imdbId.trim() + && isValidUrl(newMovie.imgUrl) + && isValidUrl(newMovie.imdbUrl)) { + isDisabled = false; + } + + const reset = () => { + setNewMovie({ + title: '', + description: '', + imgUrl: '', + imdbUrl: '', + imdbId: '', + }); + }; + + const handleSumbit = (event: React.FormEvent) => { + event.preventDefault(); + + if (!newMovie.title || !newMovie.imgUrl + || !newMovie.imdbUrl || !newMovie.imdbId) { + return; + } + + onAdd(newMovie); + setCount(currentCount => currentCount + 1); + + reset(); + }; return ( -
+ { + setCount(count + 1); + handleSumbit(el); + }} + >

Add a movie

{}} + value={newMovie.title} + onChange={(value) => setNewMovie({ ...newMovie, title: value })} required /> setNewMovie({ ...newMovie, description: value })} /> setNewMovie({ ...newMovie, imgUrl: value })} + required /> setNewMovie({ ...newMovie, imdbUrl: value })} + required /> setNewMovie({ ...newMovie, imdbId: value })} + required />
@@ -48,6 +120,7 @@ export const NewMovie = () => { type="submit" data-cy="submit-button" className="button is-link" + disabled={isDisabled} > Add diff --git a/src/components/TextField/TextField.tsx b/src/components/TextField/TextField.tsx index 307b19865..dc3e53d0a 100644 --- a/src/components/TextField/TextField.tsx +++ b/src/components/TextField/TextField.tsx @@ -24,12 +24,20 @@ export const TextField: React.FC = ({ required = false, onChange = () => {}, }) => { + const pattern = new RegExp( + '^((([A-Za-z]{3,9}:(?:\\/\\/)?)(?:[-;:&=+$,\\w]+@)?[A-Za-z0-9.-]+|' + + '(?:www\\.|[-;:&=+$,\\w]+@)[A-Za-z0-9.-]+)((?:\\/[+~%/\\.\\w-_]*)?' + + '\\??(?:[-+=&;%@,\\.\\w_]*)#?(?:[,.!/\\\\\\w]*))?)$', + ); // 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; + const validation = pattern.test(value); + const invalidData = !validation && (name === 'imgUrl' || name === 'imdbUrl') + && touched; return (
@@ -43,7 +51,7 @@ export const TextField: React.FC = ({ id={id} data-cy={`movie-${name}`} className={classNames('input', { - 'is-danger': hasError, + 'is-danger': hasError || invalidData, })} placeholder={placeholder} value={value} @@ -55,6 +63,10 @@ export const TextField: React.FC = ({ {hasError && (

{`${label} is required`}

)} + + {invalidData && ( +

{`${label} is not valid`}

+ )}
); }; diff --git a/tsconfig.json b/tsconfig.json index 95c932e51..cda0eaa88 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,9 @@ -{ - "extends": "@mate-academy/students-ts-config", - "include": [ - "src" - ] -} +{ + "compilerOptions": { + "jsx": "react-jsx" + }, + "extends": "@mate-academy/students-ts-config", + "include": [ + "src" + ] +}