diff --git a/.gitignore b/.gitignore index 061c94bba..9f97aec5d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ # misc .DS_Store +.env .env.local .env.development.local .env.test.local diff --git a/package-lock.json b/package-lock.json index 323039dbb..dc18301f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "1-weekly-mission", "version": "0.1.0", "dependencies": { + "@tanstack/react-query": "^5.56.2", + "@tanstack/react-query-devtools": "^5.58.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -3574,6 +3576,55 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@tanstack/query-core": { + "version": "5.56.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.56.2.tgz", + "integrity": "sha512-gor0RI3/R5rVV3gXfddh1MM+hgl0Z4G7tj6Xxpq6p2I03NGPaJ8dITY9Gz05zYYb/EJq9vPas/T4wn9EaDPd4Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.58.0.tgz", + "integrity": "sha512-iFdQEFXaYYxqgrv63ots+65FGI+tNp5ZS5PdMU1DWisxk3fez5HG3FyVlbUva+RdYS5hSLbxZ9aw3yEs97GNTw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.56.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.56.2.tgz", + "integrity": "sha512-SR0GzHVo6yzhN72pnRhkEFRAHMsUo5ZPzAxfTMvUxFIDVS6W9LYUp6nXW3fcHVdg0ZJl8opSH85jqahvm6DSVg==", + "dependencies": { + "@tanstack/query-core": "5.56.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.58.0.tgz", + "integrity": "sha512-qF0xCyBeVuNLygTO1sAl1X4Gv52w52SeaDdbjYQmtTOooUJ3aAVlBEtiRJFfJblWQ9p/UQG8NIcC/65RjX8Jkw==", + "dependencies": { + "@tanstack/query-devtools": "5.58.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.56.2", + "react": "^18 || ^19" + } + }, "node_modules/@testing-library/dom": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.1.tgz", @@ -20207,6 +20258,32 @@ "loader-utils": "^2.0.0" } }, + "@tanstack/query-core": { + "version": "5.56.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.56.2.tgz", + "integrity": "sha512-gor0RI3/R5rVV3gXfddh1MM+hgl0Z4G7tj6Xxpq6p2I03NGPaJ8dITY9Gz05zYYb/EJq9vPas/T4wn9EaDPd4Q==" + }, + "@tanstack/query-devtools": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.58.0.tgz", + "integrity": "sha512-iFdQEFXaYYxqgrv63ots+65FGI+tNp5ZS5PdMU1DWisxk3fez5HG3FyVlbUva+RdYS5hSLbxZ9aw3yEs97GNTw==" + }, + "@tanstack/react-query": { + "version": "5.56.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.56.2.tgz", + "integrity": "sha512-SR0GzHVo6yzhN72pnRhkEFRAHMsUo5ZPzAxfTMvUxFIDVS6W9LYUp6nXW3fcHVdg0ZJl8opSH85jqahvm6DSVg==", + "requires": { + "@tanstack/query-core": "5.56.2" + } + }, + "@tanstack/react-query-devtools": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.58.0.tgz", + "integrity": "sha512-qF0xCyBeVuNLygTO1sAl1X4Gv52w52SeaDdbjYQmtTOooUJ3aAVlBEtiRJFfJblWQ9p/UQG8NIcC/65RjX8Jkw==", + "requires": { + "@tanstack/query-devtools": "5.58.0" + } + }, "@testing-library/dom": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.1.tgz", diff --git a/package.json b/package.json index f12648f83..6c8e431d9 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,8 @@ "version": "0.1.0", "private": true, "dependencies": { + "@tanstack/react-query": "^5.56.2", + "@tanstack/react-query-devtools": "^5.58.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/src/@types/index.d.ts b/src/@types/index.d.ts index a46834571..16af78e09 100644 --- a/src/@types/index.d.ts +++ b/src/@types/index.d.ts @@ -9,6 +9,7 @@ interface ProductProps { isFavorite?: boolean; updateAt?: Date; createAt?: Date; + ownerId: string; } interface CommentProps { @@ -30,3 +31,28 @@ type NavigationType = { path: string; name: string; }[]; + +interface LoginType { + email: string; + password: string; +} + +interface RegisterType { + email: string; + nickname: string; + password: string; + passwordCheck: string; +} + +interface LoginResponseReturnType { + accessToken: string; + refreshToken: string; + user: { + id: string; + email: string; + image: null; + nickname: string; + updatedAt: Date; + createdAt: Date; + }; +} diff --git a/src/App.tsx b/src/App.tsx index 92bdcee0c..58f05ac60 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,13 +1,18 @@ +import { QueryClientProvider } from "@tanstack/react-query"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import Root from "./router/Root"; import ProductsPage from "./router/ProductsPage"; import AddItemPage from "./router/AddItemPage"; +import EditItemPage from "./router/EditItemPage"; import ProductDetailPage, { loader as productLoader, } from "./router/ProductDetailPage"; import Register from "./components/Auth/Register"; import Login from "./components/Auth/Login"; import Home from "./router/Home"; +import { queryClient } from "./utils/http"; +import PrivateRoute from "./router/PrivateRoute"; const router = createBrowserRouter([ { @@ -28,6 +33,10 @@ const router = createBrowserRouter([ element: , loader: productLoader, }, + { + path: "items/:productId/edit", + element: , + }, { path: "additem", element: , @@ -36,16 +45,29 @@ const router = createBrowserRouter([ }, { path: "signin", - element: , + element: ( + + + + ), }, { path: "signup", - element: , + element: ( + + + + ), }, ]); function App() { - return ; + return ( + + + + + ); } export default App; diff --git a/src/components/AddProduct/@types/ProductForm.ts b/src/components/AddProduct/@types/ProductForm.ts index 4bd5470f1..7cbc51156 100644 --- a/src/components/AddProduct/@types/ProductForm.ts +++ b/src/components/AddProduct/@types/ProductForm.ts @@ -1,14 +1,14 @@ interface Tag { id: string; - name: string; + name?: string; } interface FormInitialValues { - imgFile: File | null; - title: string; + images: string | string[]; + name: string; description: string; price: string; - tag: Tag[]; + tags: Tag[]; } type ChangeValueType = ( diff --git a/src/components/AddProduct/AddProduct.module.css b/src/components/AddProduct/AddProduct.module.css index 4def98530..5b0cec1de 100644 --- a/src/components/AddProduct/AddProduct.module.css +++ b/src/components/AddProduct/AddProduct.module.css @@ -1,4 +1,5 @@ .formContainer { + margin-top: 2.4rem; min-height: calc(1233px - 70px); } .titleContainer { diff --git a/src/components/AddProduct/AddProduct.tsx b/src/components/AddProduct/AddProduct.tsx index 36a1b1a37..b5e09c3ba 100644 --- a/src/components/AddProduct/AddProduct.tsx +++ b/src/components/AddProduct/AddProduct.tsx @@ -1,10 +1,37 @@ +import { useNavigate } from "react-router-dom"; +import { useMutation } from "@tanstack/react-query"; import Section from "../../ui/Section/Section"; import ProductForm from "./ProductForm"; +import { postProduct, queryClient } from "../../utils/http"; + +interface Tag { + id: string; + name?: string; +} + +interface ProductType { + images: string[]; + tags: Tag[]; + price: string; + description: string; + name: string; +} export default function AddProduct() { + const navigate = useNavigate(); + const { mutate, isPending } = useMutation({ + mutationFn: (data) => postProduct(data, "POST"), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["products"] }); + navigate("/items"); + }, + }); + const handleAddProductSubmit = (formData: ProductType) => { + mutate(formData); + }; return (
- +
); } diff --git a/src/components/AddProduct/ProductForm.module.css b/src/components/AddProduct/ProductForm.module.css index b9ebaa0b5..94aa06bd1 100644 --- a/src/components/AddProduct/ProductForm.module.css +++ b/src/components/AddProduct/ProductForm.module.css @@ -1,4 +1,5 @@ .formContainer { + padding-top: 2.4rem; min-height: calc(1233px - 70px); } .titleContainer { diff --git a/src/components/AddProduct/ProductForm.tsx b/src/components/AddProduct/ProductForm.tsx index 1ab429333..68290ac31 100644 --- a/src/components/AddProduct/ProductForm.tsx +++ b/src/components/AddProduct/ProductForm.tsx @@ -1,5 +1,4 @@ -import { useEffect, useState } from "react"; -import { Form } from "react-router-dom"; +import { FormEvent, useEffect, useState } from "react"; import FileInput from "../../ui/FormComponents/FileInput"; import TagInput from "../../ui/FormComponents/TagInput"; import Input from "../../ui/FormComponents/Input"; @@ -8,25 +7,40 @@ import Button from "../../ui/Button/LinkButton"; import styles from "./ProductForm.module.css"; import { FormInitialValues, ChangeValueType } from "./@types/ProductForm"; +interface ProductFormType { + isPending: boolean; + inputData?: FormInitialValues; + onSubmit: (formData: FormInitialValues) => void; +} const INITIAL_VALUES: FormInitialValues = { - imgFile: null, - title: "", + images: "", + name: "", description: "", price: "", - tag: [], + tags: [], }; -export default function ProductForm() { - const [formValues, setFormValues] = - useState(INITIAL_VALUES); - const [isActive, setIsActive] = useState(false); - const { title, description, price, tag } = formValues; +export default function ProductForm({ + isPending, + inputData, + onSubmit, +}: ProductFormType) { + const [formValues, setFormValues] = useState( + inputData ?? INITIAL_VALUES + ); + const [isActive, setIsActive] = useState(false); + const { name, description, price, tags } = formValues; const submitActive: boolean = - title.trim() !== "" && + name.trim() !== "" && description.trim() !== "" && price !== "" && - tag.length > 0; + tags.length > 0; + + const handleSubmitAddProduct = (event: FormEvent) => { + event.preventDefault(); + onSubmit(formValues); + }; const handleChangeValue: ChangeValueType = (name, value) => { setFormValues((prevValue) => ({ @@ -44,28 +58,29 @@ export default function ProductForm() { }, [formValues]); return ( -
+

상품 등록하기

-

상품 이미지

@@ -75,7 +90,7 @@ export default function ProductForm() { name="description" className={styles.inputBox} variant="addProduct" - value={formValues.description} + value={formValues?.description ?? ""} placeholder="상품 소개를 입력해주세요" changeValue={handleChangeInput} /> @@ -85,19 +100,19 @@ export default function ProductForm() { label="판매가격" type="number" name="price" - value={formValues.price} + value={formValues?.price ?? ""} placeholder="판매 가격을 입력해주세요" changeValue={handleChangeInput} /> - + ); } diff --git a/src/components/Auth/Login.tsx b/src/components/Auth/Login.tsx index 46c7e68e3..0da0e7497 100644 --- a/src/components/Auth/Login.tsx +++ b/src/components/Auth/Login.tsx @@ -1,46 +1,39 @@ -import { useEffect, useContext } from "react"; +import useLogin from "../../hooks/useLogin"; +import { useContext } from "react"; import AuthHeader from "./AuthHeader"; import LoginMenu from "./LoginMenu"; import Section from "../../ui/Section/Section"; -import Input from "../../ui/FormComponents/Input"; +import ValidateInput from "../../ui/FormComponents/ValidateInput"; import LinkButton from "../../ui/Button/LinkButton"; import styles from "./Auth.module.css"; import { LoginInitialValue } from "./@types/Auth"; import AuthContext from "../../store/AuthContext"; -import { useNavigate } from "react-router-dom"; import { useForm } from "react-hook-form"; +import ErrorComponent from "../Error/ErrorComponent"; -export default function Register() { +export default function Login() { const { register, handleSubmit, formState: { errors, isValid }, } = useForm({ mode: "onChange" }); - const authCtx = useContext(AuthContext); - const navigate = useNavigate(); - useEffect(() => { - if (authCtx.isLoggedIn) { - navigate("/"); - } - }, [authCtx.isLoggedIn]); + const { mutate, isError } = useLogin(); const handleLogin = async (data: LoginInitialValue) => { - const successs = await authCtx.login(data); - if (successs) { - navigate("/"); - } + mutate(data); }; const onLoginSubmit = handleSubmit(handleLogin); return (
+
- - ({ mode: "onChange" }); - const navigate = useNavigate(); - const authCtx = useContext(AuthContext); - useEffect(() => { - if (authCtx.isLoggedIn) { - navigate("/"); - } - }, [authCtx.isLoggedIn]); - const validateConfirmPassword = (value: string) => { const { password } = getValues(); if (value !== password) { @@ -37,13 +29,10 @@ export default function Register() { return true; }; + const { mutate, isError } = useSignup(); + const handleRegister = async (data: RegisterInitialValue) => { - try { - const res = await signUp(data); - navigate("/signin"); - } catch (error) { - console.log(error); - } + mutate(data); }; const handleSubmitRegister = (data: RegisterInitialValue) => { @@ -55,9 +44,10 @@ export default function Register() { return (
+
- - - - (""); + const { productId } = useParams(); + + const uploadComment = useMutation({ + mutationFn: postProductComment, + }); + + const handleSubmitComment = () => { + const commentData = { + id: productId, + data: commentValue, + }; + uploadComment.mutate(commentData); + }; const handleChangeValue = (name: string = "comment", value: string) => { setCommentValue(value); @@ -14,7 +30,7 @@ export default function CommnetForm() { return ( <> - +