diff --git a/my-app/src/App.scss b/my-app/src/App.scss index 1c35be7..68f8a27 100644 --- a/my-app/src/App.scss +++ b/my-app/src/App.scss @@ -1,4 +1,5 @@ -.App, #root { +.App, +#root { text-align: center; display: flex; justify-content: center; @@ -14,4 +15,4 @@ margin: 0px; padding: 0px; box-sizing: border-box; -} \ No newline at end of file +} diff --git a/my-app/src/App.tsx b/my-app/src/App.tsx index 5a4830a..311a3c4 100644 --- a/my-app/src/App.tsx +++ b/my-app/src/App.tsx @@ -1,48 +1,30 @@ import "./App.scss"; import { MemberCard } from "../src/components/memberCard"; import { useEffect, useState } from "react"; -import { UserProps } from "./components/memberCard/types"; -import { ButtonWithLabel } from "./components/buttonWithLabel"; +import { UserProps } from "./types/types"; import Form from "./components/form"; import { Tabs } from "./components/tabs"; +import { UserList } from "./components/userList/UserList"; +import { fetchUsers } from "./client/api"; export default function App() { const [users, setUsers] = useState([]); - const [moreUsers, setMoreUsers] = useState([]); - const [addedUser, setAddedUser] = useState(null); const [tabForm, setTabForm] = useState(true); + const [addedUser, setAddedUser] = useState(null); useEffect(() => { - fetch('https://jsonplaceholder.typicode.com/users') - .then((response) => response.json()) - .then((res) => setUsers(res)); + fetchUsers().then((res) => setUsers(res)); }, []); - const onButtonClick = () => { - fetch('https://jsonplaceholder.typicode.com/users') - .then((response) => response.json()) - .then((res) => setMoreUsers(res)); - }; - - const handleUserAddition = (user: UserProps) => { + const onUserAddition = (user: UserProps) => { setAddedUser(user); -}; + }; return (
- - {!tabForm && users.map((user) => )} - {!tabForm && moreUsers.map((user) => )} - {!tabForm && more users} - {tabForm &&
} - {addedUser && ( - - )} + + {tabForm ? : } + {addedUser && }
); } diff --git a/my-app/src/client/api.ts b/my-app/src/client/api.ts new file mode 100644 index 0000000..ce76723 --- /dev/null +++ b/my-app/src/client/api.ts @@ -0,0 +1,15 @@ +import { UserProps } from "../types/types"; + +const apiUrl = "https://jsonplaceholder.typicode.com/users"; + +export const fetchUsers = (): Promise => + fetch(apiUrl).then((response) => response.json()); + +export const addUser = (user: UserProps): Promise => + fetch(apiUrl, { + method: "POST", + body: JSON.stringify(user), + headers: { + "Content-type": "application/json; charset=UTF-8", + }, + }).then((response) => response.json()); diff --git a/my-app/src/components/button/index.tsx b/my-app/src/components/button/index.tsx index c986301..30c11fe 100644 --- a/my-app/src/components/button/index.tsx +++ b/my-app/src/components/button/index.tsx @@ -1,8 +1,11 @@ import "./style.scss"; +import { FC } from "react"; +import { ButtonProps } from "../../types/types"; -export const Button = ({ onClick, children }: {onClick: () => void, children?: string}) => { - - return ( - - ); -}; \ No newline at end of file +export const Button: FC = ({ onClick, children }) => { + return ( + + ); +}; diff --git a/my-app/src/components/button/style.scss b/my-app/src/components/button/style.scss index 7463d18..f21d1c9 100644 --- a/my-app/src/components/button/style.scss +++ b/my-app/src/components/button/style.scss @@ -1,6 +1,6 @@ .button { - padding: 10px; - border-radius: 5px; - background-color: antiquewhite; - cursor: pointer; -} \ No newline at end of file + padding: 10px; + border-radius: 5px; + background-color: #a1068d; + cursor: pointer; +} diff --git a/my-app/src/components/buttonWithLabel/index.tsx b/my-app/src/components/buttonWithLabel/index.tsx index 4401bc4..02db5a6 100644 --- a/my-app/src/components/buttonWithLabel/index.tsx +++ b/my-app/src/components/buttonWithLabel/index.tsx @@ -1,12 +1,13 @@ import { Button } from "../button"; import "./style.scss"; +import { FC } from "react"; +import { ButtonWithLabelProps } from "../../types/types"; -export const ButtonWithLabel = ({ onClick, children }: { onClick: () => void, children: string }) => { - const text =

нажми меня!

; - return ( -
- {text} - -
- ); +export const ButtonWithLabel: FC = ({label, ...restProps}) => { + return ( +
+

{label}

+
+ ); }; \ No newline at end of file diff --git a/my-app/src/components/buttonWithLabel/style.scss b/my-app/src/components/buttonWithLabel/style.scss index 614b4db..8d11229 100644 --- a/my-app/src/components/buttonWithLabel/style.scss +++ b/my-app/src/components/buttonWithLabel/style.scss @@ -1,7 +1,7 @@ .button-with-label { - display: flex; - flex-direction: column; - gap: 5px; - justify-content: center; - align-items: center; -} \ No newline at end of file + display: flex; + flex-direction: column; + gap: 5px; + justify-content: center; + align-items: center; +} diff --git a/my-app/src/components/form/index.tsx b/my-app/src/components/form/index.tsx index 08e5b8b..8f08a4b 100644 --- a/my-app/src/components/form/index.tsx +++ b/my-app/src/components/form/index.tsx @@ -1,68 +1,55 @@ -import React, { useState, FormEvent, ChangeEvent } from 'react'; -import './style.scss'; - -interface FormProps { - onUserAddition: (user: any) => void; // Принимаем функцию для обновления состояния верхнего компонента -} +import React, { useState, FormEvent, ChangeEvent } from "react"; +import "./style.scss"; +import { FormProps } from "../../types/types"; +import { addUser } from "../../client/api"; const Form: React.FC = ({ onUserAddition }) => { - const [username, setUsername] = useState(''); - const [phone, setPhone] = useState(''); - const [website, setWebsite] = useState(''); - - const handleUsernameChange = (event: ChangeEvent) => { - setUsername(event.target.value); - }; - - const handlePhoneChange = (event: ChangeEvent) => { - setPhone(event.target.value); - }; - - const handlewebsiteChange = (event: ChangeEvent) => { - setWebsite(event.target.value); - }; - - const handleSubmit = (event: FormEvent) => { - event.preventDefault(); - - fetch('https://jsonplaceholder.typicode.com/users', { - method: 'POST', - body: JSON.stringify({ - username, - phone, - website, - }), - headers: { - 'Content-type': 'application/json; charset=UTF-8', - }, - }) - .then((response) => response.json()) - .then((user) => onUserAddition(user)); - }; - - return ( - -
- -
-
- -
-
- -
- - - ); + const [username, setUsername] = useState(""); + const [phone, setPhone] = useState(""); + const [website, setWebsite] = useState(""); + + const handleUsernameChange = (event: ChangeEvent) => { + setUsername(event.target.value); + }; + + const handlePhoneChange = (event: ChangeEvent) => { + setPhone(event.target.value); + }; + + const handlewebsiteChange = (event: ChangeEvent) => { + setWebsite(event.target.value); + }; + + const handleSubmit = (event: FormEvent) => { + event.preventDefault(); + addUser({ username, phone, website }).then((user) => onUserAddition(user)); + }; + + return ( +
+
+ +
+
+ +
+
+ +
+ +
+ ); }; export default Form; diff --git a/my-app/src/components/form/style.scss b/my-app/src/components/form/style.scss index f00c4b1..4c91320 100644 --- a/my-app/src/components/form/style.scss +++ b/my-app/src/components/form/style.scss @@ -1,32 +1,31 @@ .form-container { - width: 300px; - margin: 0 auto; - } - - label { - display: block; - margin-bottom: 10px; - } - - input { - width: 100%; - padding: 5px; - margin-top: 5px; - border-radius: 4px; - border: 1px solid #ccc; - } - - .button { - margin-top: 10px; - padding: 8px 16px; - border: none; - border-radius: 4px; - background-color: #007bff; - color: #fff; - cursor: pointer; - } - - button:hover { - background-color: #0056b3; - } - \ No newline at end of file + width: 300px; + margin: 0 auto; +} + +label { + display: block; + margin-bottom: 10px; +} + +input { + width: 100%; + padding: 5px; + margin-top: 5px; + border-radius: 4px; + border: 1px solid #ccc; +} + +.button { + margin-top: 10px; + padding: 8px 16px; + border: none; + border-radius: 4px; + background-color: #007bff; + color: #fff; + cursor: pointer; +} + +button:hover { + background-color: #0056b3; +} diff --git a/my-app/src/components/memberCard/index.tsx b/my-app/src/components/memberCard/index.tsx index d4e3a95..1c16e91 100644 --- a/my-app/src/components/memberCard/index.tsx +++ b/my-app/src/components/memberCard/index.tsx @@ -1,14 +1,13 @@ import { FC } from "react"; import "./style.scss"; - -import { UserProps } from "./types"; +import { UserProps } from "../../types/types"; import { CardInfo } from "./parts/cardInfo"; -export const MemberCard: FC = ({ name, username, phone, website }) => { +export const MemberCard: FC = ({ name, ...restProps }) => { return (

{name}

- +
); }; diff --git a/my-app/src/components/memberCard/parts/cardInfo.tsx b/my-app/src/components/memberCard/parts/cardInfo.tsx index 77b279a..f493e34 100644 --- a/my-app/src/components/memberCard/parts/cardInfo.tsx +++ b/my-app/src/components/memberCard/parts/cardInfo.tsx @@ -1,15 +1,13 @@ -import { FC, useEffect } from "react"; +import { FC } from "react"; import "../style.scss"; - -import { UserProps } from "../types"; +import { UserProps } from "../../../types/types"; export const CardInfo: FC = ({ phone, username, website }) => { - - return ( -
-

Username: {username}

-

Phone: {phone}

-

Website: {website}

-
- ); -}; \ No newline at end of file + return ( +
+

Username: {username}

+

Phone: {phone}

+

Website: {website}

+
+ ); +}; diff --git a/my-app/src/components/memberCard/style.scss b/my-app/src/components/memberCard/style.scss index 86c83a0..f6f21bc 100644 --- a/my-app/src/components/memberCard/style.scss +++ b/my-app/src/components/memberCard/style.scss @@ -1,33 +1,32 @@ .member-card { - border: 1px solid black; - width: 200px; - border-radius: 10px; - .title { - font-size: 18px; - color: black; - font-weight: 700; - padding: 15px; - } - - .info { - border-top: 1px solid black; - background-color: pink; - width: 198px; - padding: 5px; - display: flex; - align-items: center; - justify-items: center; - flex-direction: column; - border-radius: 0px 0px 10px 10px; - - .list-item { - font-size: 15px; - color: dark-gray; - margin-top: 10px; - display: flex; - align-items: center; - justify-items: center; - } - } + border: 1px solid black; + width: 200px; + border-radius: 10px; + .title { + font-size: 18px; + color: black; + font-weight: 700; + padding: 15px; } - \ No newline at end of file + + .info { + border-top: 1px solid black; + background-color: pink; + width: 198px; + padding: 5px; + display: flex; + align-items: center; + justify-items: center; + flex-direction: column; + border-radius: 0px 0px 10px 10px; + + .list-item { + font-size: 15px; + color: dark-gray; + margin-top: 10px; + display: flex; + align-items: center; + justify-items: center; + } + } +} diff --git a/my-app/src/components/memberCard/types.ts b/my-app/src/components/memberCard/types.ts deleted file mode 100644 index 2cb301f..0000000 --- a/my-app/src/components/memberCard/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface UserProps { - name?: string; - username: string; - phone: string; - website: string; - } - \ No newline at end of file diff --git a/my-app/src/components/tabs/index.tsx b/my-app/src/components/tabs/index.tsx index 18693d6..0b7607d 100644 --- a/my-app/src/components/tabs/index.tsx +++ b/my-app/src/components/tabs/index.tsx @@ -1,11 +1,13 @@ import { Button } from "../button"; +import "./style.scss"; +import { FC } from "react"; +import { TabsProps } from "../../types/types"; -export const Tabs = ({ onChange }: { onChange: (tab: boolean) => void }) => { - - return ( -
- - -
- ); -}; \ No newline at end of file +export const Tabs: FC = ({ onChange }) => { + return ( +
+ + +
+ ); +}; diff --git a/my-app/src/components/tabs/style.scss b/my-app/src/components/tabs/style.scss new file mode 100644 index 0000000..fd5a109 --- /dev/null +++ b/my-app/src/components/tabs/style.scss @@ -0,0 +1,7 @@ +.tab { + width: 100%; + display: flex; + gap: 15px; + justify-content: center; + margin-bottom: 20px; +} diff --git a/my-app/src/components/userList/UserList.tsx b/my-app/src/components/userList/UserList.tsx new file mode 100644 index 0000000..7706df2 --- /dev/null +++ b/my-app/src/components/userList/UserList.tsx @@ -0,0 +1,19 @@ +import { memo } from "react"; +import { MemberCard } from "../memberCard"; +import { ButtonWithLabel } from "../buttonWithLabel"; +import { useGetUsers } from "../../hooks/useGetUsers"; + +export const UserList = memo(() => { + const { users, getUsers } = useGetUsers(); + + return ( + <> + {users.map((user, idx) => ( + + ))} + + more users + + + ); +}); diff --git a/my-app/src/hooks/useGetUsers.ts b/my-app/src/hooks/useGetUsers.ts new file mode 100644 index 0000000..c2a8733 --- /dev/null +++ b/my-app/src/hooks/useGetUsers.ts @@ -0,0 +1,23 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { UserProps } from "../types/types"; +import { fetchUsers } from "../client/api"; + +interface UserListProps { + users: UserProps[]; + getUsers: () => void; +} + +export const useGetUsers = (): UserListProps => { + const [users, setUsers] = useState([]); + + const getUsers = useCallback(() => { + fetchUsers().then((res) => setUsers([...users, ...res])); + }, [users]); + + useEffect(() => { + if (users.length) return; + getUsers(); + }, [getUsers, users.length]); + + return useMemo(() => ({ users, getUsers }), [getUsers, users]); +}; diff --git a/my-app/src/index.css b/my-app/src/index.css index ec2585e..4a1df4d 100644 --- a/my-app/src/index.css +++ b/my-app/src/index.css @@ -1,13 +1,13 @@ body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } diff --git a/my-app/src/types/types.ts b/my-app/src/types/types.ts new file mode 100644 index 0000000..a37b12b --- /dev/null +++ b/my-app/src/types/types.ts @@ -0,0 +1,25 @@ +export interface ButtonProps { + onClick: () => void; + children?: string; +} + +export interface ButtonWithLabelProps { + onClick: () => void; + children: string; + label: string; +} + +export interface UserProps { + name?: string; + username: string; + phone: string; + website: string; +} + +export interface FormProps { + onUserAddition: (user: UserProps) => void; // Принимаем функцию для обновления состояния верхнего компонента +} + +export interface TabsProps { + onChange: (tab: boolean) => void; +}