Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

solution #2674

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
import React, { useState } from 'react';
import './App.scss';
import { MoviesList } from './components/MoviesList';
import { NewMovie } from './components/NewMovie';
import moviesFromServer from './api/movies.json';

export const App = () => {
// write code
export const App: React.FC = () => {
const [movies, setMovies] = useState(moviesFromServer);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that moviesFromServer is correctly imported and contains the expected data structure. It should be an array of movie objects matching the structure used in handleAddMovie.


const handleAddMovie = (newMovie: {
title: string;
description?: string;
imgUrl: string;
imdbUrl: string;
imdbId: string;
}) => {
setMovies(prevMovies => [newMovie, ...prevMovies]);
};

return (
<div className="page">
<div className="page-content">
<MoviesList movies={moviesFromServer} />
<MoviesList movies={movies} />

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verify that the MoviesList component is implemented to correctly receive and display the movies prop. It should be able to handle an array of movie objects.

</div>
<div className="sidebar">
<NewMovie /* onAdd={(movie) => {}} */ />
<NewMovie onAdd={handleAddMovie} />

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that the NewMovie component is implemented to correctly call the onAdd prop function with a new movie object. The object should match the structure expected by handleAddMovie.

</div>
</div>
);
Expand Down
131 changes: 119 additions & 12 deletions src/components/NewMovie/NewMovie.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,144 @@
import { useState } from 'react';
import React, { useState } from 'react';
import { TextField } from '../TextField';

export const NewMovie = () => {
// Increase the count after successful form submission
// to reset touched status of all the `Field`s
const [count] = useState(0);
const urlPattern =

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that the urlPattern is correctly defined to match valid URLs. It should be comprehensive enough to cover the expected URL formats for imgUrl and imdbUrl.

// eslint-disable-next-line max-len
/^((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www\.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@,.\w_]*)#?(?:[,.!/\\\w]*))?)$/;

type NewMovieProps = {
onAdd: (movie: {
title: string;
description?: string;
imgUrl: string;
imdbUrl: string;
imdbId: string;
}) => void;
};

export const NewMovie: React.FC<NewMovieProps> = ({ onAdd }) => {
const [formValues, setFormValues] = useState({
title: '',
description: '',
imgUrl: '',
imdbUrl: '',
imdbId: '',
});

const [touched, setTouched] = useState({

Check failure on line 27 in src/components/NewMovie/NewMovie.tsx

View workflow job for this annotation

GitHub Actions / run_linter (20.x)

'touched' is assigned a value but never used
title: false,
imgUrl: false,
imdbUrl: false,
imdbId: false,
});

const [key, setKey] = useState(0);

const handleInputChange = (field: string, value: string) => {
setFormValues(prev => ({ ...prev, [field]: value }));
};

const handleBlur = (field: string) => {
setTouched(prev => ({ ...prev, [field]: true }));
};

const isFieldValid = (field: string) => {
const value = formValues[field];

if (field === 'imgUrl' || field === 'imdbUrl') {
return urlPattern.test(value.trim());
}

return value.trim() !== '';
};

const isFormValid = () =>
['title', 'imgUrl', 'imdbUrl', 'imdbId'].every(field =>
isFieldValid(field),
);

const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
if (!isFormValid()) {
return;
}

onAdd(formValues);

// Reset form state
setFormValues({
title: '',
description: '',
imgUrl: '',
imdbUrl: '',
imdbId: '',
});
setTouched({
title: false,
imgUrl: false,
imdbUrl: false,
imdbId: false,
});

// Reset form key to reinitialize fields
setKey(prev => prev + 1);
};

return (
<form className="NewMovie" key={count}>
<form className="NewMovie" key={key} onSubmit={handleSubmit}>
<h2 className="title">Add a movie</h2>

<TextField
name="title"
label="Title"
value=""
onChange={() => {}}
value={formValues.title}
onChange={value => handleInputChange('title', value)}
onBlur={() => handleBlur('title')}
required
/>

<TextField name="description" label="Description" value="" />
<TextField
name="description"
label="Description"
value={formValues.description}
onChange={value => handleInputChange('description', value)}
/>

<TextField name="imgUrl" label="Image URL" value="" />
<TextField
name="imgUrl"
label="Image URL"
value={formValues.imgUrl}
onChange={value => handleInputChange('imgUrl', value)}
onBlur={() => handleBlur('imgUrl')}
required
validationCallback={value => urlPattern.test(value)}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verify that the TextField component correctly utilizes the validationCallback prop to validate the input value. It should provide feedback based on the result of the validation.

/>

<TextField name="imdbUrl" label="Imdb URL" value="" />
<TextField
name="imdbUrl"
label="IMDB URL"
value={formValues.imdbUrl}
onChange={value => handleInputChange('imdbUrl', value)}
onBlur={() => handleBlur('imdbUrl')}
required
validationCallback={value => urlPattern.test(value)}
/>

<TextField name="imdbId" label="Imdb ID" value="" />
<TextField
name="imdbId"
label="IMDB ID"
value={formValues.imdbId}
onChange={value => handleInputChange('imdbId', value)}
onBlur={() => handleBlur('imdbId')}
required
/>

<div className="field is-grouped">
<div className="control">
<button
type="submit"
data-cy="submit-button"
className="button is-link"
disabled={!isFormValid()}
>
Add
</button>
Expand Down
14 changes: 9 additions & 5 deletions src/components/TextField/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type Props = {
placeholder?: string;
required?: boolean;
onChange?: (newValue: string) => void;
validationCallback?: (value: string) => boolean; // for custom validation

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that the validationCallback function is correctly passed from the parent component and that it performs the necessary validation logic for the input value.

};

function getRandomDigits() {
Expand All @@ -21,13 +22,16 @@ export const TextField: React.FC<Props> = ({
placeholder = `Enter ${label}`,
required = false,
onChange = () => {},
validationCallback = () => true, // default to always valid
}) => {
// generate a unique id once on component load
const [id] = useState(() => `${name}-${getRandomDigits()}`);

// To show errors only if the field was touched (onBlur)
// Track whether the field was touched
const [touched, setTouched] = useState(false);
const hasError = touched && required && !value;

// Check if the field has an error
const hasError =
touched && ((required && !value.trim()) || !validationCallback(value));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hasError logic correctly checks if the field is required and if the custom validation fails. Ensure that the error message is appropriate for the validation being performed.


return (
<div className="field">
Expand All @@ -41,12 +45,12 @@ export const TextField: React.FC<Props> = ({
id={id}
data-cy={`movie-${name}`}
className={classNames('input', {
'is-danger': hasError,
'is-danger': hasError, // Add red border for errors
})}
placeholder={placeholder}
value={value}
onChange={event => onChange(event.target.value)}
onBlur={() => setTouched(true)}
onBlur={() => setTouched(true)} // Mark as touched on blur
/>
</div>

Expand Down
Loading