diff --git a/package-lock.json b/package-lock.json index 374722b0..780baf57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,17 @@ "name": "codeit-mission-next", "version": "0.1.0", "dependencies": { + "@hookform/resolvers": "^5.0.1", + "@tanstack/react-query": "^5.76.1", "axios": "^1.8.4", "framer-motion": "^12.6.3", "next": "15.2.4", "pretendard": "^1.3.9", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-icons": "^5.5.0" + "react-hook-form": "^7.56.4", + "react-icons": "^5.5.0", + "zod": "^3.25.3" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -41,16 +45,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@emnapi/runtime": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.0.tgz", - "integrity": "sha512-64WYIf4UYcdLnbKn/umDlNjQDSS8AgZrI/R9+x5ilkUVFxXcA1Ebl+gQLc/6mERA4407Xof0R7wEyEuj091CVw==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", @@ -202,6 +196,18 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@hookform/resolvers": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.0.1.tgz", + "integrity": "sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -338,6 +344,111 @@ "node": ">= 10" } }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.4.tgz", + "integrity": "sha512-3qK2zb5EwCwxnO2HeO+TRqCubeI/NgCe+kL5dTJlPldV/uwCnUgC7VbEzgmxbfrkbjehL4H9BPztWOEtsoMwew==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.4.tgz", + "integrity": "sha512-HFN6GKUcrTWvem8AZN7tT95zPb0GUGv9v0d0iyuTb303vbXkkbHDp/DxufB04jNVD+IN9yHy7y/6Mqq0h0YVaQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.4.tgz", + "integrity": "sha512-Oioa0SORWLwi35/kVB8aCk5Uq+5/ZIumMK1kJV+jSdazFm2NzPDztsefzdmzzpx5oGCJ6FkUC7vkaUseNTStNA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.4.tgz", + "integrity": "sha512-yb5WTRaHdkgOqFOZiu6rHV1fAEK0flVpaIN2HB6kxHVSy/dIajWbThS7qON3W9/SNOH2JWkVCyulgGYekMePuw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.4.tgz", + "integrity": "sha512-Dcdv/ix6srhkM25fgXiyOieFUkz+fOYkHlydWCtB0xMST6X9XYI3yPDKBZt1xuhOytONsIFJFB08xXYsxUwJLw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.4.tgz", + "integrity": "sha512-dW0i7eukvDxtIhCYkMrZNQfNicPDExt2jPb9AZPpL7cfyUo7QSNl1DjsHjmmKp6qNAqUESyT8YFl/Aw91cNJJg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.4.tgz", + "integrity": "sha512-SbnWkJmkS7Xl3kre8SdMF6F/XDh1DTFEhp0jRTj/uB8iPKoU2bb2NDfcu+iifv1+mxQEd1g2vvSxcZbXSKyWiQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -400,6 +511,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -482,6 +599,32 @@ "tailwindcss": "4.1.3" } }, + "node_modules/@tanstack/query-core": { + "version": "5.76.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.76.0.tgz", + "integrity": "sha512-FN375hb8ctzfNAlex5gHI6+WDXTNpe0nbxp/d2YJtnP+IBM6OUm7zcaoCW6T63BawGOYZBbKC0iPvr41TteNVg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.76.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.76.1.tgz", + "integrity": "sha512-YxdLZVGN4QkT5YT1HKZQWiIlcgauIXEIsMOTSjvyD5wLYK8YVvKZUPAysMqossFJJfDpJW3pFn7WNZuPOqq+fw==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.76.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -3853,6 +3996,22 @@ "react": "^19.1.0" } }, + "node_modules/react-hook-form": { + "version": "7.56.4", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.56.4.tgz", + "integrity": "sha512-Rob7Ftz2vyZ/ZGsQZPaRdIefkgOSrQSPXfqBdvOPwJfoGnjwRJUs7EM7Kc1mcoDv3NOtqBzPGbcMB8CGn9CKgw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-icons": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", @@ -4873,109 +5032,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@next/swc-darwin-x64": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.4.tgz", - "integrity": "sha512-3qK2zb5EwCwxnO2HeO+TRqCubeI/NgCe+kL5dTJlPldV/uwCnUgC7VbEzgmxbfrkbjehL4H9BPztWOEtsoMwew==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.4.tgz", - "integrity": "sha512-HFN6GKUcrTWvem8AZN7tT95zPb0GUGv9v0d0iyuTb303vbXkkbHDp/DxufB04jNVD+IN9yHy7y/6Mqq0h0YVaQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.4.tgz", - "integrity": "sha512-Oioa0SORWLwi35/kVB8aCk5Uq+5/ZIumMK1kJV+jSdazFm2NzPDztsefzdmzzpx5oGCJ6FkUC7vkaUseNTStNA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.4.tgz", - "integrity": "sha512-yb5WTRaHdkgOqFOZiu6rHV1fAEK0flVpaIN2HB6kxHVSy/dIajWbThS7qON3W9/SNOH2JWkVCyulgGYekMePuw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.4.tgz", - "integrity": "sha512-Dcdv/ix6srhkM25fgXiyOieFUkz+fOYkHlydWCtB0xMST6X9XYI3yPDKBZt1xuhOytONsIFJFB08xXYsxUwJLw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.4.tgz", - "integrity": "sha512-dW0i7eukvDxtIhCYkMrZNQfNicPDExt2jPb9AZPpL7cfyUo7QSNl1DjsHjmmKp6qNAqUESyT8YFl/Aw91cNJJg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.4.tgz", - "integrity": "sha512-SbnWkJmkS7Xl3kre8SdMF6F/XDh1DTFEhp0jRTj/uB8iPKoU2bb2NDfcu+iifv1+mxQEd1g2vvSxcZbXSKyWiQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" + "node_modules/zod": { + "version": "3.25.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.3.tgz", + "integrity": "sha512-VGZqnyYNrl8JpEJRZaFPqeVNIuqgXNu4cXZ5cOb6zEUO1OxKbRnWB4UdDIXMmiERWncs0yDQukssHov8JUxykQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" } } } diff --git a/package.json b/package.json index ea9804e5..5d33a17f 100644 --- a/package.json +++ b/package.json @@ -9,13 +9,17 @@ "lint": "next lint" }, "dependencies": { + "@hookform/resolvers": "^5.0.1", + "@tanstack/react-query": "^5.76.1", "axios": "^1.8.4", "framer-motion": "^12.6.3", "next": "15.2.4", "pretendard": "^1.3.9", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-icons": "^5.5.0" + "react-hook-form": "^7.56.4", + "react-icons": "^5.5.0", + "zod": "^3.25.3" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -27,5 +31,5 @@ "eslint-config-next": "15.2.4", "tailwindcss": "^4", "typescript": "^5" + } } -} \ No newline at end of file diff --git a/public/images/Img_home_01.png b/public/images/Img_home_01.png new file mode 100644 index 00000000..a2d4be5f Binary files /dev/null and b/public/images/Img_home_01.png differ diff --git a/public/images/Img_home_02.png b/public/images/Img_home_02.png new file mode 100644 index 00000000..faccb9f7 Binary files /dev/null and b/public/images/Img_home_02.png differ diff --git a/public/images/Img_home_03.png b/public/images/Img_home_03.png new file mode 100644 index 00000000..ef5972b3 Binary files /dev/null and b/public/images/Img_home_03.png differ diff --git a/public/images/Img_home_bottom.png b/public/images/Img_home_bottom.png new file mode 100644 index 00000000..31853d71 Binary files /dev/null and b/public/images/Img_home_bottom.png differ diff --git a/public/images/Img_home_top.png b/public/images/Img_home_top.png new file mode 100644 index 00000000..83a4881c Binary files /dev/null and b/public/images/Img_home_top.png differ diff --git a/public/images/google-icon.png b/public/images/google-icon.png new file mode 100644 index 00000000..55700030 Binary files /dev/null and b/public/images/google-icon.png differ diff --git a/public/images/ic_facebook.png b/public/images/ic_facebook.png new file mode 100644 index 00000000..58333d45 Binary files /dev/null and b/public/images/ic_facebook.png differ diff --git a/public/images/ic_instagram.png b/public/images/ic_instagram.png new file mode 100644 index 00000000..98e24ea6 Binary files /dev/null and b/public/images/ic_instagram.png differ diff --git a/public/images/ic_twitter.png b/public/images/ic_twitter.png new file mode 100644 index 00000000..5df0852d Binary files /dev/null and b/public/images/ic_twitter.png differ diff --git a/public/images/ic_youtube.png b/public/images/ic_youtube.png new file mode 100644 index 00000000..f51731d4 Binary files /dev/null and b/public/images/ic_youtube.png differ diff --git a/public/images/kakao-icon.png b/public/images/kakao-icon.png new file mode 100644 index 00000000..000d07e3 Binary files /dev/null and b/public/images/kakao-icon.png differ diff --git a/src/app/boards/page.tsx b/src/app/boards/page.tsx index 7f63f74c..103c7243 100644 --- a/src/app/boards/page.tsx +++ b/src/app/boards/page.tsx @@ -1,11 +1,13 @@ "use client"; import Article from "@/components/Article"; import BestArticle from "@/components/BestArticle"; +import Header from "@/components/Header"; import Loading from "@/components/Loading"; import Search from "@/components/Search"; import useArticle from "@/hooks/useArticle"; import useBestArticle from "@/hooks/useBestArticle"; import { Articles } from "@/types/article"; +import { div } from "framer-motion/client"; import Link from "next/link"; import { useState } from "react"; @@ -19,45 +21,50 @@ export default function Boards() { }); return ( -
-
- 베스트 게시글 -
- {bestArticles.map((article, index) => ( - - - - ))} -
+ <> +
+
- -
-
- 게시글 - - 글쓰기 - +
+
+ 베스트 게시글 +
+ {bestArticles.map((article, index) => ( + + + + ))} +
- +
+
+ 게시글 + + 글쓰기 + +
- {articleResults?.map((article, index) => ( - -
- - ))} -
+ - {loading ?? } -
+ {articleResults?.map((article, index) => ( + +
+ + ))} +
+ + {loading ?? } +
+ ); } diff --git a/src/app/landingPage/page.tsx b/src/app/landingPage/page.tsx new file mode 100644 index 00000000..54e245a5 --- /dev/null +++ b/src/app/landingPage/page.tsx @@ -0,0 +1,29 @@ +import Footer from "@/components/Footer"; +import HotItem from "@/components/HotItem"; +import LandingBottomComponent from "@/components/LandingBottomComponent"; +import LandingExploreSection from "@/components/LandingExploreSection"; +import LandingPageHeader from "@/components/LandingPageHeader"; +import LandingRegisterComponent from "@/components/LandingRegisterComponent"; +import LandingSearchComponent from "@/components/LandingSearchComponent"; + +export default function LandingPage() { + return ( +
+
+ +
+ +
+ + + + + + + +
+ +
+
+ ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index df1f50a1..48af5667 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,10 +1,12 @@ import type { Metadata } from "next"; import "./globals.css"; import Header from "@/components/Header"; +import QueryProvider from "./query-provider"; export const metadata: Metadata = { title: "PandaMarket", - description: "우리 모두가 함께 즐길 수 있는 최고의 중고마켓인 '판다마켓'의 자유게시판입니다.", + description: + "우리 모두가 함께 즐길 수 있는 최고의 중고마켓인 '판다마켓'의 자유게시판입니다.", }; export default function RootLayout({ @@ -18,8 +20,7 @@ export default function RootLayout({ -
- {children} + {children} ); diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx new file mode 100644 index 00000000..c15a72be --- /dev/null +++ b/src/app/login/page.tsx @@ -0,0 +1,122 @@ +"use client"; +import ButtonComponent from "@/components/ButtonComponent"; +import InputComponent from "@/components/InputComponent"; +import Link from "next/link"; +import { z } from "zod"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import SimpleLogin from "@/components/SimpleLogin"; +import { useMutation } from "@tanstack/react-query"; +import signUp from "@/services/signUp"; +import { useRouter } from "next/navigation"; +import signin from "@/services/login"; + +export default function Login() { + const router = useRouter(); + + const signInSchema = z.object({ + email: z + .string() + .nonempty({ message: "이메일은 필수 입력입니다." }) + .email({ message: "이메일 형식으로 작성해 주세요." }), + password: z + .string() + .nonempty({ message: "비밀번호는 필수 입력입니다." }) + .min(8, { message: "비밀번호를 8자 이상 입력해주세요." }) + .regex(/^[A-Za-z0-9!@#$%^&*]+$/, { + message: "비밀번호는 숫자, 영문, 특수문자로만 가능합니다.", + }), + }); + + const { + register, + handleSubmit, + setError, + formState: { errors, isSubmitting, isValid }, + } = useForm>({ + resolver: zodResolver(signInSchema), + reValidateMode: "onChange", + mode: "onBlur", + }); + + const mutation = useMutation({ + mutationFn: signin, + onSuccess: (data) => { + console.log("로그인 성공!"); + + const { accessToken, refreshToken } = data; + + localStorage.setItem("accessToken", accessToken); + localStorage.setItem("refreshToken", refreshToken); + + router.push("/boards"); + }, + onError: (error) => { + console.log(`로그인 실패 : ${error}`); + setError("email", { + type: "manual", + message: "잘못된 이메일입니다", + }); + }, + }); + + const onSubmit = async (data: z.infer) => { + await mutation.mutateAsync({ + email: data.email, + password: data.password, + }); + }; + + return ( +
+ +
+ logo + + 판다마켓 + +
+ + +
+
+ + + + + +
+ + + + + 판다마켓이 처음이신가요?{" "} + + 회원가입 + + +
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index ef977da5..9a59ffb8 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,5 +1,7 @@ import { redirect } from "next/navigation"; +import LandingPage from "./landingPage/page"; +import Boards from "./boards/page"; export default function Home() { - redirect("/boards"); + return ; } diff --git a/src/app/query-provider.tsx b/src/app/query-provider.tsx new file mode 100644 index 00000000..c0a42ad9 --- /dev/null +++ b/src/app/query-provider.tsx @@ -0,0 +1,12 @@ +"use client"; + +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactNode, useState } from "react"; + +export default function QueryProvider({ children }: { children: ReactNode }) { + const [queryClient] = useState(() => new QueryClient()); + + return ( + {children} + ); +} diff --git a/src/app/signup/page.tsx b/src/app/signup/page.tsx new file mode 100644 index 00000000..d216d9aa --- /dev/null +++ b/src/app/signup/page.tsx @@ -0,0 +1,148 @@ +"use client"; +import ButtonComponent from "@/components/ButtonComponent"; +import InputComponent from "@/components/InputComponent"; +import Link from "next/link"; +import { z } from "zod"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import SimpleLogin from "@/components/SimpleLogin"; +import { useMutation } from "@tanstack/react-query"; +import signUp from "@/services/signUp"; +import { useRouter } from "next/navigation"; + +export default function SignUp() { + const router = useRouter(); + + const signUpSchema = z + .object({ + email: z + .string() + .nonempty({ message: "이메일은 필수 입력입니다." }) + .email({ message: "잘못된 이메일입니다" }), + password: z + .string() + .nonempty({ message: "비밀번호는 필수 입력입니다." }) + .min(8, { message: "비밀번호를 8자 이상 입력해주세요." }) + .regex(/^[A-Za-z0-9!@#$%^&*]+$/, { + message: "비밀번호는 숫자, 영문, 특수문자로만 가능합니다.", + }), + nickname: z.string().nonempty({ message: "닉네임은 필수 입력입니다." }), + passwordConfirmation: z + .string() + .nonempty({ message: "비밀번호 확인은 필수 입력입니다." }), + }) + .superRefine((data, context) => { + if (data.password !== data.passwordConfirmation) { + context.addIssue({ + code: z.ZodIssueCode.custom, + message: "비밀번호가 일치하지 않습니다", + path: ["passwordConfirmation"], + }); + } + }); + + const { + register, + handleSubmit, + formState: { errors, isSubmitting, isValid }, + } = useForm>({ + resolver: zodResolver(signUpSchema), + reValidateMode: "onChange", + mode: "onBlur", + }); + + const mutation = useMutation({ + mutationFn: signUp, + onSuccess: (data) => { + console.log("회원가입 성공!"); + + const { accessToken, refreshToken } = data; + + localStorage.setItem("accessToken", accessToken); + localStorage.setItem("refreshToken", refreshToken); + + router.push("/login"); + }, + onError: (error) => { + console.log(`회원가입 실패 : ${error}`); + }, + }); + + const onSubmit = async (data: z.infer) => { + await mutation.mutateAsync({ + email: data.email, + nickname: data.nickname, + password: data.password, + passwordConfirmation: data.passwordConfirmation, + }); + }; + + return ( +
+ +
+ logo + + 판다마켓 + +
+ + +
+
+ + + + + + + +
+ + + + + 이미 회원이신가요?{" "} + + 로그인 + + +
+ ); +} diff --git a/src/components/ButtonComponent.tsx b/src/components/ButtonComponent.tsx new file mode 100644 index 00000000..7b5e340c --- /dev/null +++ b/src/components/ButtonComponent.tsx @@ -0,0 +1,23 @@ +import React from "react"; + +interface Props { + placeholder: string; + isClear: boolean; + isSubmitting: boolean; +} + +const ButtonComponent = ({ placeholder, isClear, isSubmitting }: Props) => { + return ( + + ); +}; + +export default ButtonComponent; diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx new file mode 100644 index 00000000..08426f1d --- /dev/null +++ b/src/components/Footer.tsx @@ -0,0 +1,62 @@ +import Image from "next/image"; +import Link from "next/link"; +import React from "react"; + +type Props = {}; + +const Footer = (props: Props) => { + return ( +
+
+
+ + © codeit - 2024 + + + Privacy Policy FAQ + + +
+ + facebook + + + facebook + + + facebook + + + facebook + +
+
+ + © codeit - 2024 + +
+
+ ); +}; + +export default Footer; diff --git a/src/components/Header.tsx b/src/components/Header.tsx index a51c49b6..d9f40a00 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,8 +1,12 @@ +"use client"; import Image from "next/image"; import Link from "next/link"; -import React from "react"; +import React, { useState } from "react"; +import HeaderDropDownComponent from "./HeaderDropDownComponent"; const Header = () => { + const [click, setClick] = useState(false); + return ( <>
@@ -30,8 +34,16 @@ const Header = () => {
-
+
setClick(!click)} + > profile + {click && ( +
+ +
+ )}
diff --git a/src/components/HeaderDropDownComponent.tsx b/src/components/HeaderDropDownComponent.tsx new file mode 100644 index 00000000..a0224de7 --- /dev/null +++ b/src/components/HeaderDropDownComponent.tsx @@ -0,0 +1,29 @@ +import { useRouter } from "next/navigation"; +import React from "react"; + +interface Props {} + +const HeaderDropDownComponent = (props: Props) => { + const router = useRouter(); + + const handleLogOut = () => { + localStorage.removeItem("accessToken"); + localStorage.removeItem("refreshToken"); + router.push("/landingPage"); + }; + + return ( +
+
+ + 로그아웃 + +
+
+ ); +}; + +export default HeaderDropDownComponent; diff --git a/src/components/HotItem.tsx b/src/components/HotItem.tsx new file mode 100644 index 00000000..1a5f0417 --- /dev/null +++ b/src/components/HotItem.tsx @@ -0,0 +1,30 @@ +import React from "react"; + +type Props = {}; + +const HotItem = (props: Props) => { + return ( +
+ HotItem +
+
+ + Hot item + + + 인기 상품을 확인해 보세요 + + + 가장 HOT한 중고거래 물품을 판다 마켓에서 확인해 보세요 + +
+
+
+ ); +}; + +export default HotItem; diff --git a/src/components/InputComponent.tsx b/src/components/InputComponent.tsx new file mode 100644 index 00000000..85637a52 --- /dev/null +++ b/src/components/InputComponent.tsx @@ -0,0 +1,67 @@ +"use client"; +import React, { useState } from "react"; +import { UseFormRegister } from "react-hook-form"; +import { FaRegEye } from "react-icons/fa"; +import { FaRegEyeSlash } from "react-icons/fa"; + +interface Props { + id: string; + label: string; + placeholder: string; + type: string; + register: UseFormRegister; + error?: string; +} + +const InputComponent = ({ + id, + label, + placeholder, + type, + register, + error, +}: Props) => { + const [hide, setHide] = useState(true); + + return ( +
+ + + {type == "password" && + (hide ? ( + setHide(!hide)} + /> + ) : ( + setHide(!hide)} + /> + ))} + + + {error} + +
+ ); +}; + +export default InputComponent; diff --git a/src/components/LandingBottomComponent.tsx b/src/components/LandingBottomComponent.tsx new file mode 100644 index 00000000..df15cb98 --- /dev/null +++ b/src/components/LandingBottomComponent.tsx @@ -0,0 +1,20 @@ +import React from "react"; + +type Props = {}; + +const LandingBottomComponent = (props: Props) => { + return ( +
+ + 믿을 수 있는 판다마켓 중고 거래 + + home_bottom +
+ ); +}; + +export default LandingBottomComponent; diff --git a/src/components/LandingExploreSection.tsx b/src/components/LandingExploreSection.tsx new file mode 100644 index 00000000..09ada5a4 --- /dev/null +++ b/src/components/LandingExploreSection.tsx @@ -0,0 +1,32 @@ +import Link from "next/link"; +import React from "react"; + +type Props = {}; + +const LandingExploreSection = (props: Props) => { + return ( +
+
+
+ + 일상의 모든 물건을 거래해 보세요 + + + 구경하러 가기 + +
+ + landingPageExploreSectionImage +
+
+ ); +}; + +export default LandingExploreSection; diff --git a/src/components/LandingPageHeader.tsx b/src/components/LandingPageHeader.tsx new file mode 100644 index 00000000..30353f3e --- /dev/null +++ b/src/components/LandingPageHeader.tsx @@ -0,0 +1,22 @@ +import Image from "next/image"; +import Link from "next/link"; + +const LandingPageHeader = () => { + return ( +
+ logo + 판다마켓 + + 로그인 + +
+ ); +}; + +export default LandingPageHeader; diff --git a/src/components/LandingRegisterComponent.tsx b/src/components/LandingRegisterComponent.tsx new file mode 100644 index 00000000..6b70bd8b --- /dev/null +++ b/src/components/LandingRegisterComponent.tsx @@ -0,0 +1,30 @@ +import React from 'react' + +type Props = {} + +const LandingRegisterComponent = (props: Props) => { + return ( +
+ HotItem +
+
+ + Register + + + 판매를 원하는 상품을 등록하세요 + + + 어떤 물건이든 판매하고 싶은 상품을 쉽게 등록하세요 + +
+
+
+ ); +} + +export default LandingRegisterComponent; \ No newline at end of file diff --git a/src/components/LandingSearchComponent.tsx b/src/components/LandingSearchComponent.tsx new file mode 100644 index 00000000..5fb56e47 --- /dev/null +++ b/src/components/LandingSearchComponent.tsx @@ -0,0 +1,43 @@ +import React from "react"; + +type Props = {}; + +const LandingSearchComponent = (props: Props) => { + return ( +
+
+
+ + Search + + + 구매를 원하는 상품을 검색하세요 + + + 구매하고 싶은 물품은 검색해서 쉽게 찾아보세요 + +
+
+ HotItem +
+
+ + Search + + + 구매를 원하는 상품을 검색하세요 + + + 구매하고 싶은 물품은 검색해서 쉽게 찾아보세요 + +
+
+
+ ); +}; + +export default LandingSearchComponent; diff --git a/src/components/SimpleLogin.tsx b/src/components/SimpleLogin.tsx new file mode 100644 index 00000000..d7ff1b30 --- /dev/null +++ b/src/components/SimpleLogin.tsx @@ -0,0 +1,31 @@ +import Image from "next/image"; +import React from "react"; + +type Props = {}; + +const SimpleLogin = (props: Props) => { + return ( +
+ + 간편 로그인하기 + + +
+ google + google +
+
+ ); +}; + +export default SimpleLogin; diff --git a/src/services/login.ts b/src/services/login.ts new file mode 100644 index 00000000..84025aa6 --- /dev/null +++ b/src/services/login.ts @@ -0,0 +1,13 @@ +import axios from "axios"; + +const Base_URL = "https://panda-market-api.vercel.app"; + +interface Props { + email: string; + password: string; +} + +export default async function signin(newUser: Props) { + const response = await axios.post(`${Base_URL}/auth/signIn`, newUser); + return response.data; +} diff --git a/src/services/signUp.ts b/src/services/signUp.ts new file mode 100644 index 00000000..86e9f08e --- /dev/null +++ b/src/services/signUp.ts @@ -0,0 +1,15 @@ +import axios from "axios"; + +const Base_URL = "https://panda-market-api.vercel.app"; + +interface Props { + email: string; + nickname: string; + password: string; + passwordConfirmation: string; +} + +export default async function signUp(newUser: Props) { + const response = await axios.post(`${Base_URL}/auth/signUp`, newUser); + return response.data; +}