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

Develop #2930

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open

Develop #2930

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
23 changes: 0 additions & 23 deletions src/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,3 @@ button {
.error {
color: red;
}

.TodoInfo {
width: max-content;
padding: 8px;
margin: 12px 0;
border: 1px solid #000;
border-radius: 8px;
background-color: antiquewhite;

&__title {
margin: 4px 0;
font-size: inherit;
color: #f00;
}

&--completed &__title {
color: #080;
}
}

.UserInfo {
font-style: italic;
}
151 changes: 116 additions & 35 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,142 @@
import './App.scss';
import React, { useState } from 'react';

// import usersFromServer from './api/users';
// import todosFromServer from './api/todos';
import usersFromServer from './api/users';
import todosFromServer from './api/todos';
import { TodoWithUser } from './App.types';
import { TodoList } from './components/TodoList';

const todosWithUser: TodoWithUser[] = todosFromServer.map(
(todo): TodoWithUser => {
let user = usersFromServer.find(({ id }) => id === todo.userId);

if (!user) {
user = usersFromServer[0];
}

return {
...todo,
user,
};
},
);
const defaultUserId = '0';
const titleAllowedSymbols = /^[a-zA-Zа-яА-ЯёЁїЇіІєЄґҐ0-9\s]*$/;

const validateTitleSymbols = (title: string): boolean =>
titleAllowedSymbols.test(title);

const getTitleErrorMessage = (title: string): string | null => {
if (!title) {
return 'Please enter a title';
}

if (!validateTitleSymbols(title)) {
return 'Title contains special symbols';
}

return null;
};

export const App = () => {
const [todos, setTodos] = useState<TodoWithUser[]>([...todosWithUser]);
const [title, setTitle] = useState('');
const [userId, setUserId] = useState(defaultUserId);
const [hasTitleError, setHasTitleError] = useState(false);
const [hasUserError, setHasUserError] = useState(false);
const isSelectedUser = userId !== defaultUserId;

const clearForm = () => {
setTitle('');
setUserId(defaultUserId);
};

const handleSubmit = (
event: React.FormEvent<HTMLFormElement>,
): null | void => {
event.preventDefault();
if (!title || !validateTitleSymbols(title)) {
setHasTitleError(true);
}

if (!isSelectedUser) {
setHasUserError(true);
}

if (title && isSelectedUser) {
const user =
usersFromServer.find(({ id }) => id === Number(userId)) ||
usersFromServer[0];
const newId = Math.max(...todos.map(todo => todo.id)) + 1;

Choose a reason for hiding this comment

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

you don't have to use spread operator here, since map is not mutable

Copy link
Author

@djtyrf312 djtyrf312 Jan 23, 2025

Choose a reason for hiding this comment

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

Without this, I would have an error with the types. So maybe better to leave it to split the array to separate arguments for math max
image


const newTodo: TodoWithUser = {
userId: Number(userId),
id: newId,
title,
completed: false,

Choose a reason for hiding this comment

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

isCompleted would be a cleaner approach

user,
};

setTodos([...todos, newTodo]);
clearForm();
}
};

return (
<div className="App">
<h1>Add todo form</h1>

<form action="/api/todos" method="POST">
<form
action="/api/todos"
method="POST"
onSubmit={event => {
handleSubmit(event);
}}
>
<div className="field">
<input type="text" data-cy="titleInput" />
<span className="error">Please enter a title</span>
<input
type="text"
value={title}
data-cy="titleInput"
placeholder="Please enter a title"
onChange={event => {
setTitle(event.target.value);
setHasTitleError(false);
}}
/>
{hasTitleError && (
<span className="error">{getTitleErrorMessage(title)}</span>
)}
</div>

<div className="field">
<select data-cy="userSelect">
<option value="0" disabled>
<select
data-cy="userSelect"
value={userId}
onChange={event => {
setUserId(event.target.value);
setHasUserError(false);
}}
>
<option value={defaultUserId} disabled>
Choose a user
</option>
{usersFromServer.map(user => (
<option key={user.id} value={user.id}>
{user.name}
</option>
))}
</select>

<span className="error">Please choose a user</span>
{hasUserError && <span className="error">Please choose a user</span>}
</div>

<button type="submit" data-cy="submitButton">
Add
</button>
</form>

<section className="TodoList">
<article data-id="1" className="TodoInfo TodoInfo--completed">
<h2 className="TodoInfo__title">delectus aut autem</h2>

<a className="UserInfo" href="mailto:[email protected]">
Leanne Graham
</a>
</article>

<article data-id="15" className="TodoInfo TodoInfo--completed">
<h2 className="TodoInfo__title">delectus aut autem</h2>

<a className="UserInfo" href="mailto:[email protected]">
Leanne Graham
</a>
</article>

<article data-id="2" className="TodoInfo">
<h2 className="TodoInfo__title">
quis ut nam facilis et officia qui
</h2>

<a className="UserInfo" href="mailto:[email protected]">
Patricia Lebsack
</a>
</article>
</section>
<TodoList todos={todos} />
</div>
);
};
17 changes: 17 additions & 0 deletions src/App.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export type User = {
id: number;
name: string;
username: string;
email: string;
};

export type Todo = {
userId: number;
id: number;
title: string;
completed: boolean;
};

export interface TodoWithUser extends Todo {
user: User;
}
22 changes: 22 additions & 0 deletions src/components/TodoInfo/TodoInfo.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.TodoInfo {
width: max-content;
padding: 8px;
margin: 12px 0;
border: 1px solid #000;
border-radius: 8px;
background-color: antiquewhite;

&__title {
margin: 4px 0;
font-size: inherit;
color: #f00;
}

&--completed &__title {
color: #080;
}
}

.UserInfo {
font-style: italic;
}
26 changes: 25 additions & 1 deletion src/components/TodoInfo/TodoInfo.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
export const TodoInfo = () => {};
import classNames from 'classnames';
import { TodoWithUser } from '../../App.types';
import './TodoInfo.scss';
import { UserInfo } from '../UserInfo';

interface Props {
todo: TodoWithUser;
}

export const TodoInfo = ({ todo }: Props) => {
const { title, user, completed } = todo;

return (
<article
data-id={todo.id}
className={classNames('TodoInfo', {
'TodoInfo--completed': completed,
})}
>
<h2 className="TodoInfo__title">{title}</h2>

<UserInfo user={user} />
</article>
);
};
15 changes: 14 additions & 1 deletion src/components/TodoList/TodoList.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
export const TodoList = () => {};
import { TodoWithUser } from '../../App.types';
import { TodoInfo } from '../TodoInfo';

type Props = {
todos: TodoWithUser[];
};

export const TodoList = ({ todos }: Props) => (
<section className="TodoList">
{todos.map(todo => (
<TodoInfo todo={todo} key={todo.id} />
))}
</section>
);
16 changes: 15 additions & 1 deletion src/components/UserInfo/UserInfo.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
export const UserInfo = () => {};
import { User } from '../../App.types';

type Props = {
user: User;
};

export const UserInfo = ({ user }: Props) => {
const { name, email } = user;

return (
<a className="UserInfo" href={`mailto:${email}`}>
{name}
</a>
);
};
Loading