From 51afcd6641590d4eb3ba7a8bc8e33646546127cf Mon Sep 17 00:00:00 2001 From: byeolee1221 Date: Wed, 13 Nov 2024 00:09:52 +0900 Subject: [PATCH 01/24] =?UTF-8?q?refactor:=20=EB=A6=AC=EC=95=A1=ED=8A=B8?= =?UTF-8?q?=20=EC=BF=BC=EB=A6=AC=EB=A5=BC=20=EC=A0=81=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=EC=A7=84?= =?UTF-8?q?=ED=96=89=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.json | 6 +- .gitignore | 12 +- README.md | 58 +- app/(auth)/authTypeShare.ts | 16 - app/(auth)/error.tsx | 33 - app/(auth)/layout.tsx | 44 +- app/(auth)/signin/page.tsx | 118 +- app/(auth)/signin/signinConstants.ts | 16 - app/(auth)/signin/zodSchema/signinSchema.ts | 10 + app/(auth)/signup/page.tsx | 181 +- .../signupSchema.ts} | 17 +- app/(home)/components/Footer.tsx | 36 + app/(home)/components/ImgBottom.tsx | 13 + app/(home)/components/ImgUpper.tsx | 14 + app/(home)/components/ImgWrapperSection.tsx | 42 + app/(home)/components/MainContents.tsx | 14 + app/(home)/components/MainContentsSection.tsx | 51 + app/(home)/constants/mainContents.ts | 24 + app/(home)/page.tsx | 21 +- app/(home)/types/content.ts | 7 + app/addboard/addBoardConstants.ts | 11 - app/addboard/error.tsx | 33 - app/addboard/layout.tsx | 13 - app/addboard/page.tsx | 194 - app/additem/layout.tsx | 10 +- app/additem/page.tsx | 198 +- .../addItemSchema.ts} | 6 +- app/api/auth/refresh/route.ts | 23 + app/api/auth/refreshToken/route.ts | 27 - app/api/auth/signin/route.ts | 38 +- app/api/auth/signup/route.ts | 23 +- app/boards/[postId]/commentConstants.ts | 5 - app/boards/[postId]/page.tsx | 127 - app/boards/error.tsx | 33 - app/boards/layout.tsx | 21 - app/boards/page.tsx | 9 - app/globals.css | 61 +- app/icon.png | Bin 4174 -> 0 bytes app/items/[itemId]/page.tsx | 144 +- app/items/components/AllItems.tsx | 131 + app/items/components/BestItems.tsx | 57 + .../items/components/ItemContent.tsx | 22 +- app/items/components/ItemSearch.tsx | 67 + app/items/error.tsx | 33 - app/items/layout.tsx | 13 +- app/items/page.tsx | 12 +- app/items/types/Items.ts | 19 + .../zodSchema/ItemsSchema.ts} | 0 app/layout.tsx | 33 +- app/not-found.tsx | 23 - atom/additemAtom.ts | 4 + atom/authAtom.ts | 4 + bun.lockb | Bin 0 -> 161969 bytes components/BackToListBtn.tsx | 15 - components/CommentsContents.tsx | 39 - components/Pagination.tsx | 64 +- components/QueryProvider.tsx | 17 + components/SelectMenu.tsx | 23 +- components/auth/SigninForm.tsx | 95 - components/auth/SignoutMenu.tsx | 20 - components/auth/SignupForm.tsx | 142 - components/boards/AllPostContents.tsx | 48 - components/boards/BestPost.tsx | 69 - components/boards/BestPostContents.tsx | 40 - components/boards/PostCommentForm.tsx | 81 - components/boards/PostList.tsx | 62 - components/boards/SearchForm.tsx | 103 - components/footer/Footer.tsx | 32 - components/home/ImgWrapperBottom.tsx | 27 - components/home/ImgWrapperUpper.tsx | 33 - components/home/MainContents.tsx | 45 - components/items/AllItems.tsx | 111 - components/items/BestItems.tsx | 56 - components/items/ItemCommentForm.tsx | 78 - components/items/ItemSearchForm.tsx | 79 - components/navBar/Logout.tsx | 32 + components/navBar/NavBar.tsx | 64 +- context/token.tsx | 96 - hooks/useAddItem.tsx | 33 + hooks/useCalculateWidth.tsx | 6 +- hooks/useImageUpload.tsx | 33 + hooks/useToken.tsx | 33 +- lib/axios.ts | 47 +- lib/utils.ts | 97 - middleware.ts | 15 + next.config.mjs | 19 - next.config.ts | 20 + package-lock.json | 5470 ----------------- package.json | 20 +- public/{images => icons}/logo.png | Bin tailwind.config.ts | 35 +- tsconfig.json | 2 +- types/boardsTypeShare.ts | 25 - types/itemsTypeShare.ts | 14 - 94 files changed, 1381 insertions(+), 8086 deletions(-) delete mode 100644 app/(auth)/authTypeShare.ts delete mode 100644 app/(auth)/error.tsx delete mode 100644 app/(auth)/signin/signinConstants.ts create mode 100644 app/(auth)/signin/zodSchema/signinSchema.ts rename app/(auth)/signup/{signupContants.ts => zodSchema/signupSchema.ts} (70%) create mode 100644 app/(home)/components/Footer.tsx create mode 100644 app/(home)/components/ImgBottom.tsx create mode 100644 app/(home)/components/ImgUpper.tsx create mode 100644 app/(home)/components/ImgWrapperSection.tsx create mode 100644 app/(home)/components/MainContents.tsx create mode 100644 app/(home)/components/MainContentsSection.tsx create mode 100644 app/(home)/constants/mainContents.ts create mode 100644 app/(home)/types/content.ts delete mode 100644 app/addboard/addBoardConstants.ts delete mode 100644 app/addboard/error.tsx delete mode 100644 app/addboard/layout.tsx delete mode 100644 app/addboard/page.tsx rename app/additem/{addItemConstants.ts => zodSchema/addItemSchema.ts} (83%) create mode 100644 app/api/auth/refresh/route.ts delete mode 100644 app/api/auth/refreshToken/route.ts delete mode 100644 app/boards/[postId]/commentConstants.ts delete mode 100644 app/boards/[postId]/page.tsx delete mode 100644 app/boards/error.tsx delete mode 100644 app/boards/layout.tsx delete mode 100644 app/boards/page.tsx delete mode 100644 app/icon.png create mode 100644 app/items/components/AllItems.tsx create mode 100644 app/items/components/BestItems.tsx rename components/items/ItemsContents.tsx => app/items/components/ItemContent.tsx (68%) create mode 100644 app/items/components/ItemSearch.tsx delete mode 100644 app/items/error.tsx create mode 100644 app/items/types/Items.ts rename app/{boards/searchConstants.ts => items/zodSchema/ItemsSchema.ts} (100%) delete mode 100644 app/not-found.tsx create mode 100644 atom/additemAtom.ts create mode 100644 atom/authAtom.ts create mode 100644 bun.lockb delete mode 100644 components/BackToListBtn.tsx delete mode 100644 components/CommentsContents.tsx create mode 100644 components/QueryProvider.tsx delete mode 100644 components/auth/SigninForm.tsx delete mode 100644 components/auth/SignoutMenu.tsx delete mode 100644 components/auth/SignupForm.tsx delete mode 100644 components/boards/AllPostContents.tsx delete mode 100644 components/boards/BestPost.tsx delete mode 100644 components/boards/BestPostContents.tsx delete mode 100644 components/boards/PostCommentForm.tsx delete mode 100644 components/boards/PostList.tsx delete mode 100644 components/boards/SearchForm.tsx delete mode 100644 components/footer/Footer.tsx delete mode 100644 components/home/ImgWrapperBottom.tsx delete mode 100644 components/home/ImgWrapperUpper.tsx delete mode 100644 components/home/MainContents.tsx delete mode 100644 components/items/AllItems.tsx delete mode 100644 components/items/BestItems.tsx delete mode 100644 components/items/ItemCommentForm.tsx delete mode 100644 components/items/ItemSearchForm.tsx create mode 100644 components/navBar/Logout.tsx delete mode 100644 context/token.tsx create mode 100644 hooks/useAddItem.tsx create mode 100644 hooks/useImageUpload.tsx create mode 100644 middleware.ts delete mode 100644 next.config.mjs create mode 100644 next.config.ts delete mode 100644 package-lock.json rename public/{images => icons}/logo.png (100%) delete mode 100644 types/boardsTypeShare.ts delete mode 100644 types/itemsTypeShare.ts diff --git a/.eslintrc.json b/.eslintrc.json index 372241854..39e67abdf 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,7 @@ { - "extends": ["next/core-web-vitals", "next/typescript"] + "extends": ["next/core-web-vitals", "next/typescript"], + "rules": { + "no-unused-vars": "off" + } } + diff --git a/.gitignore b/.gitignore index fd3dbb571..26b002aac 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,12 @@ # dependencies /node_modules /.pnp -.pnp.js -.yarn/install-state.gz +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions # testing /coverage @@ -25,8 +29,8 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -# local env files -.env*.local +# env files (can opt-in for commiting if needed) +.env* # vercel .vercel diff --git a/README.md b/README.md index 5ead7c991..e215bc4cc 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,36 @@ -# 스프린트 미션 11 +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). -## 요구사항 +## Getting Started -- Javascript -- React 18 -- Next.js 14.2.13 -- Tailwind CSS 3.4.1 -- axios 1.7.7 -- react-hook-form 7.53.0 -- zod 3.23.8 -- react-hot-toast 2.4.1 -- typescript 5 +First, run the development server: -### 배포 웹사이트: https://codeit-nextjs-mission.netlify.app/ +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` -### 기본 +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. -- [x] 유효한 정보를 입력하고 스웨거 명세된 “/auth/signUp”으로 POST 요청해서 성공 응답을 받으면 회원가입이 완료됩니다. -- [x] 회원가입이 완료되면 “/login”로 이동합니다. -- [x] 회원가입 페이지에 접근시 로컬 스토리지에 accessToken이 있는 경우 ‘/’ 페이지로 이동합니다. -- [x] 회원가입을 성공한 정보를 입력하고 스웨거 명세된 “/auth/signIp”으로 POST 요청을 하면 로그인이 완료됩니다. -- [x] 로그인이 완료되면 로컬 스토리지에 accessToken을 저장하고 “/” 로 이동합니다. -- [x] 로그인/회원가입 페이지에 접근시 로컬 스토리지에 accessToken이 있는 경우 ‘/’ 페이지로 이동합니다. -- [x] 로컬 스토리지에 accessToken이 있는 경우 상단바 ‘로그인’ 버튼이 판다 이미지로 바뀝니다. +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. -### 심화 +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. -- [x] 로그인, 회원가입 기능에 react-hook-form을 활용해봅니다. +## Learn More -### 변경사항 +To learn more about Next.js, take a look at the following resources: -- 스프린트 미션 10에서의 개선사항 일부를 반영하였습니다. +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. -## 스크린샷 +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! -| 메인 페이지 (데스크탑) | 로그인페이지 (데스크탑) | -| :--------------------------------------------------------------------: | :-------------------------------------------------------------------: | -| | | -| 회원가입페이지 (데스크탑) | -| | +## Deploy on Vercel +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. -## 멘토에게 - -- 감사합니다. -- 제출기간(토요일)까지 발생한 문제해결 및 모든 개선사항의 반영이 포함하기 어려워져서 일부 문제 및 개선사항이 그대로 있는 상태입니다. -- 추가적인 작업을 해서 문제해결 및 개선사항 반영을 해보겠습니다. \ No newline at end of file +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/app/(auth)/authTypeShare.ts b/app/(auth)/authTypeShare.ts deleted file mode 100644 index 889e93c50..000000000 --- a/app/(auth)/authTypeShare.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { FieldErrors, UseFormReturn } from "react-hook-form"; -import { z } from "zod"; -import { SigninSchema } from "./signin/signinConstants"; -import { SignupSchema } from "./signup/signupContants"; - -export interface ISigninForm { - form: UseFormReturn> - isLoading: boolean; - error: FieldErrors>; -} - -export interface ISignupForm { - form: UseFormReturn> - isLoading: boolean; - error: FieldErrors>; -} \ No newline at end of file diff --git a/app/(auth)/error.tsx b/app/(auth)/error.tsx deleted file mode 100644 index c3c9c3203..000000000 --- a/app/(auth)/error.tsx +++ /dev/null @@ -1,33 +0,0 @@ -"use client"; - -import Image from "next/image"; - -const Error = ({ reset }: { reset: () => void }) => { - return ( -
-
- 에러 -

- 오류가 발생했습니다. 아래를 확인해주세요. -

-
-
    -
  • 알 수 없는 오류가 발생하여 이 페이지가 보일 수 있어요.
  • -
- -
- ); -}; - -export default Error; diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx index 7c6cc6be0..113cc698b 100644 --- a/app/(auth)/layout.tsx +++ b/app/(auth)/layout.tsx @@ -1,4 +1,4 @@ -"use client" +"use client"; import Image from "next/image"; import Link from "next/link"; @@ -13,28 +13,28 @@ const AuthLayout = ({ children }: { children: React.ReactNode }) => { ]; return ( -
- - 로고 -

판다마켓

- - {children} -
-

간편 로그인하기

-
- {footerArr.map((link, i) => ( - - {link.imgAlt} - - ))} +
+ + 판다마켓 로고 +

판다마켓

+ + {children} +
+

간편 로그인하기

+
+ {footerArr.map((link) => ( + + {link.imgAlt} + + ))} +
+
+
+

{pathname === "/signin" ? "판다마켓이 처음이신가요?" : "이미 회원이신가요?"}

+ {pathname === "/signin" ? "회원가입" : "로그인"}
-
-

{pathname === "/signin" ? "판다마켓이 처음이신가요?" : "이미 회원이신가요?"}

- {pathname === "/signin" ? 회원가입 : 로그인} -
-
); -} +}; -export default AuthLayout; \ No newline at end of file +export default AuthLayout; diff --git a/app/(auth)/signin/page.tsx b/app/(auth)/signin/page.tsx index b1f09a8a5..d8dc36f87 100644 --- a/app/(auth)/signin/page.tsx +++ b/app/(auth)/signin/page.tsx @@ -1,39 +1,109 @@ -"use client" +"use client"; +import { useRouter } from "next/navigation"; import { useForm } from "react-hook-form"; -import { z } from "zod"; -import { SigninSchema } from "./signinConstants"; +import { signinSchema } from "./zodSchema/signinSchema"; import { zodResolver } from "@hookform/resolvers/zod"; -import SigninForm from "@/components/auth/SigninForm"; -import { useRouter } from "next/navigation"; -import { useEffect } from "react"; +import { z } from "zod"; +import { useState } from "react"; +import Image from "next/image"; +import toast from "react-hot-toast"; +import axios from "axios"; import useToken from "@/hooks/useToken"; -const Signin = () => { +const SigninPage = () => { const router = useRouter(); - const context = useToken(); - - useEffect(() => { - if (context?.session) { - router.push("/"); - } - }, [router, context?.session]); - - const form = useForm>({ - resolver: zodResolver(SigninSchema), - mode: "all", + const { setTokens } = useToken(); + const { + register, + handleSubmit, + reset, + formState: { errors, isSubmitting, isValid }, + } = useForm>({ + resolver: zodResolver(signinSchema), + mode: "onChange", defaultValues: { userEmail: "", userPassword: "", }, }); + const [visiblePassword, setVisiblePassword] = useState(false); + + const handleVisiblePassword = () => { + setVisiblePassword((prev) => !prev); + }; - const isLoading = form.formState.isSubmitting; - const error = form.formState.errors; + const onSubmit = async (data: z.infer) => { + try { + const response = await axios.post("/api/auth/signin", { + email: data.userEmail, + password: data.userPassword, + }); + + if (response.status === 200) { + setTokens(response.data.accessToken, response.data.refreshToken); + toast.success("로그인이 완료되었습니다."); + reset(); + router.push("/"); + } + } catch (error) { + if (axios.isAxiosError(error)) { + toast.error(error.response?.data || "로그인에 실패하였습니다."); + } + } + }; return ( - - ) -} +
+
+ + + {errors.userEmail && {errors.userEmail.message}} +
+
+ +
+ + 보이기 버튼 +
+ {errors.userPassword && ( + {errors.userPassword.message} + )} +
+ +
+ ); +}; -export default Signin; \ No newline at end of file +export default SigninPage; diff --git a/app/(auth)/signin/signinConstants.ts b/app/(auth)/signin/signinConstants.ts deleted file mode 100644 index 20b360619..000000000 --- a/app/(auth)/signin/signinConstants.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as z from "zod"; - -export const SigninSchema = z.object({ - userEmail: z - .string() - .min(1, { message: "이메일을 입력해주세요." }) - .regex(/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/i, { - message: "잘못된 이메일입니다.", - }) - .trim(), - userPassword: z - .string() - .min(1, { message: "비밀번호를 입력해주세요." }) - .regex(/^([a-z]|[A-Z]|[0-9]|[!@#$%^&*])+$/, { message: "비밀번호를 8자 이상 입력해주세요." }) - .trim(), -}); \ No newline at end of file diff --git a/app/(auth)/signin/zodSchema/signinSchema.ts b/app/(auth)/signin/zodSchema/signinSchema.ts new file mode 100644 index 000000000..9c7d256e0 --- /dev/null +++ b/app/(auth)/signin/zodSchema/signinSchema.ts @@ -0,0 +1,10 @@ +import { z } from "zod"; + +export const signinSchema = z.object({ + userEmail: z.string().email({ message: "잘못된 이메일입니다." }).trim(), + userPassword: z + .string() + .min(1, { message: "비밀번호를 입력해주세요." }) + .regex(/^([a-z]|[A-Z]|[0-9]|[!@#$%^&*])+$/, { message: "비밀번호를 8자 이상 입력해주세요." }) + .trim(), +}); diff --git a/app/(auth)/signup/page.tsx b/app/(auth)/signup/page.tsx index ccde1150a..02656d4c3 100644 --- a/app/(auth)/signup/page.tsx +++ b/app/(auth)/signup/page.tsx @@ -1,27 +1,34 @@ -"use client" +"use client"; -import { useForm } from "react-hook-form"; +import toast from "react-hot-toast"; +import axios from "axios"; +import Image from "next/image"; +import { useState } from "react"; import { z } from "zod"; -import { SignupSchema } from "./signupContants"; -import { zodResolver } from "@hookform/resolvers/zod"; -import SignupForm from "@/components/auth/SignupForm"; -import { useEffect } from "react"; import { useRouter } from "next/navigation"; -import useToken from "@/hooks/useToken"; - -const Signup = () => { - const router = useRouter(); - const session = useToken(); +import { signupSchema } from "./zodSchema/signupSchema"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; - useEffect(() => { - if (session?.accessToken !== null) { - router.push("/"); - } - }, [router, session?.accessToken]); +interface IPasswordField { + title: string; + name: keyof z.infer; + visibleFunction: boolean; + placeholder: string; + clickFunction: () => void; + error?: string; +} - const form = useForm>({ - resolver: zodResolver(SignupSchema), - mode: "all", +const SignupPage = () => { + const router = useRouter(); + const { + register, + reset, + handleSubmit, + formState: { errors, isSubmitting, isValid }, + } = useForm>({ + resolver: zodResolver(signupSchema), + mode: "onChange", defaultValues: { userEmail: "", userNickname: "", @@ -29,13 +36,137 @@ const Signup = () => { userPassword2: "", }, }); + const [visiblePassword, setVisiblePassword] = useState(false); + const [visiblePassword2, setVisiblePassword2] = useState(false); - const isLoading = form.formState.isSubmitting; - const error = form.formState.errors; + const handleVisiblePassword = () => { + setVisiblePassword((prev) => !prev); + }; + + const handleVisiblePassword2 = () => { + setVisiblePassword2((prev) => !prev); + }; + + const passwordArr: IPasswordField[] = [ + { + title: "비밀번호", + name: "userPassword", + visibleFunction: visiblePassword, + placeholder: "비밀번호를 입력해주세요.", + clickFunction: handleVisiblePassword, + error: errors.userPassword?.message, + }, + { + title: "비밀번호 확인", + name: "userPassword2", + visibleFunction: visiblePassword2, + placeholder: "비밀번호를 다시 한 번 입력해주세요.", + clickFunction: handleVisiblePassword2, + error: errors.userPassword2?.message, + }, + ]; + + const onSubmit = async (data: z.infer) => { + try { + const response = await axios.post("/api/auth/signup", { + email: data.userEmail, + nickname: data.userNickname, + password: data.userPassword, + passwordConfirmation: data.userPassword2, + }); + + if (response.status === 201) { + toast.success("회원가입이 완료되었습니다."); + reset(); + router.push("/signin"); + } + } catch (error) { + if (axios.isAxiosError(error)) { + console.error("회원가입 실패", error.response?.data); + toast.error(error.response?.data || "회원가입에 실패하였습니다."); + } + } + }; return ( - - ) -} +
+
+ + + {errors.userEmail && {errors.userEmail.message}} +
+
+ + + {errors.userNickname && ( + {errors.userNickname.message} + )} +
+ {passwordArr.map((item) => ( +
+ +
+ + 보이기 버튼 +
+ {item.error && {item.error}} +
+ ))} + +
+ ); +}; -export default Signup; \ No newline at end of file +export default SignupPage; diff --git a/app/(auth)/signup/signupContants.ts b/app/(auth)/signup/zodSchema/signupSchema.ts similarity index 70% rename from app/(auth)/signup/signupContants.ts rename to app/(auth)/signup/zodSchema/signupSchema.ts index 5c6688065..77b8c156b 100644 --- a/app/(auth)/signup/signupContants.ts +++ b/app/(auth)/signup/zodSchema/signupSchema.ts @@ -1,31 +1,26 @@ -import * as z from "zod"; +import { z } from "zod"; -export const SignupSchema = z +export const signupSchema = z .object({ userEmail: z .string() .min(1, { message: "이메일을 입력해주세요." }) - .regex(/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/i, { - message: "잘못된 이메일입니다.", - }) + .email({ message: "잘못된 이메일입니다." }) .trim(), userNickname: z .string() .min(1, { message: "닉네임을 입력해주세요." }) - .max(20, { message: "닉네임은 20자를 넘을 수 없습니다."}) + .max(20, { message: "닉네임은 20자를 넘을 수 없습니다." }) .regex(/([A-Za-zㄱ-ㅎ가-힣0-9])/, { message: "닉네임에 특수문자는 사용할 수 없습니다." }) .trim(), userPassword: z .string() .min(1, { message: "비밀번호를 입력해주세요." }) - .regex( - /^([a-z]|[A-Z]|[0-9]|[!@#$%^&*])+$/, - { message: "비밀번호를 8자 이상 입력해주세요." } - ) + .regex(/^([a-z]|[A-Z]|[0-9]|[!@#$%^&*])+$/, { message: "비밀번호를 8자 이상 입력해주세요." }) .trim(), userPassword2: z.string().min(1, { message: "비밀번호를 다시 한 번 입력해주세요." }).trim(), }) .refine((data) => data.userPassword === data.userPassword2, { message: "비밀번호가 일치하지 않습니다.", path: ["userPassword2"], - }); \ No newline at end of file + }); diff --git a/app/(home)/components/Footer.tsx b/app/(home)/components/Footer.tsx new file mode 100644 index 000000000..9f6f0d0a9 --- /dev/null +++ b/app/(home)/components/Footer.tsx @@ -0,0 +1,36 @@ +import Image from "next/image"; +import Link from "next/link"; + +const Footer = () => { + const socialLinks = [ + { + href: "https://www.facebook.com/?locale=ko_KR", + icon: "/icons/ic_facebook.svg", + alt: "페이스북 로고", + }, + { href: "https://x.com/", icon: "/icons/ic_twitter.svg", alt: "트위터 로고" }, + { href: "https://www.youtube.com/", icon: "/icons/ic_youtube.svg", alt: "유튜브 로고" }, + { href: "https://www.instagram.com/", icon: "/icons/ic_instagram.svg", alt: "인스타그램 로고" }, + ]; + + return ( +
+
+
©codeit - 2024
+
+ Privacy Policy + FAQ +
+
+ {socialLinks.map((link) => ( + + {link.alt} + + ))} +
+
+
+ ); +}; + +export default Footer; diff --git a/app/(home)/components/ImgBottom.tsx b/app/(home)/components/ImgBottom.tsx new file mode 100644 index 000000000..249eaeb86 --- /dev/null +++ b/app/(home)/components/ImgBottom.tsx @@ -0,0 +1,13 @@ +import ImgWrapperSection from "./ImgWrapperSection"; + +const ImgBottom = () => { + return ( + + ); +}; + +export default ImgBottom; diff --git a/app/(home)/components/ImgUpper.tsx b/app/(home)/components/ImgUpper.tsx new file mode 100644 index 000000000..b8e7574fe --- /dev/null +++ b/app/(home)/components/ImgUpper.tsx @@ -0,0 +1,14 @@ +import ImgWrapperSection from "./ImgWrapperSection"; + +const ImgUpper = () => { + return ( + + ); +}; + +export default ImgUpper; diff --git a/app/(home)/components/ImgWrapperSection.tsx b/app/(home)/components/ImgWrapperSection.tsx new file mode 100644 index 000000000..641f7905a --- /dev/null +++ b/app/(home)/components/ImgWrapperSection.tsx @@ -0,0 +1,42 @@ +import Image from "next/image"; +import Link from "next/link"; + +interface ImgWrapperSectionProps { + title: string; + imageSrc: string; + imageAlt: string; + showLink?: boolean; +} + +const ImgWrapperSection = ({ title, imageSrc, imageAlt, showLink = false }: ImgWrapperSectionProps) => { + return ( +
+
+
+

+ {title} +

+ {showLink && ( + + 구경하러 가기 + + )} +
+
+ {imageAlt} +
+
+
+ ); +}; + +export default ImgWrapperSection; diff --git a/app/(home)/components/MainContents.tsx b/app/(home)/components/MainContents.tsx new file mode 100644 index 000000000..14b2343aa --- /dev/null +++ b/app/(home)/components/MainContents.tsx @@ -0,0 +1,14 @@ +import { MAIN_CONTENTS } from "../constants/mainContents"; +import MainContentsSection from "./MainContentsSection"; + +const MainContents = () => { + return ( +
+ {MAIN_CONTENTS.map((content, i) => ( + + ))} +
+ ); +}; + +export default MainContents; diff --git a/app/(home)/components/MainContentsSection.tsx b/app/(home)/components/MainContentsSection.tsx new file mode 100644 index 000000000..36b361bd9 --- /dev/null +++ b/app/(home)/components/MainContentsSection.tsx @@ -0,0 +1,51 @@ +import Image from "next/image"; +import { ContentSection as ContentSectionType } from "../types/content"; + +interface Props extends ContentSectionType { + className?: string; +} + +const MainContentsSection = ({ + imageSrc, + title, + subTitle, + description, + reverse, + className = "", +}: Props) => { + const containerClassName = reverse + ? "flex flex-col-reverse space-y-6 py-6 break-keep md:w-[720px] lg:flex-row lg:space-x-16 lg:space-y-0 lg:items-center lg:m-auto lg:justify-between bg-[#FCFCFC] rounded-xl lg:px-6 lg:w-[988px]" + : "landingPage-contentsBox"; + + return ( +
+ {!reverse && ( + 메인이미지 + )} +
+

{title}

+

{subTitle}

+

+ {description} +

+
+ {reverse && ( + 메인이미지 + )} +
+ ); +}; + +export default MainContentsSection; diff --git a/app/(home)/constants/mainContents.ts b/app/(home)/constants/mainContents.ts new file mode 100644 index 000000000..88c965e5b --- /dev/null +++ b/app/(home)/constants/mainContents.ts @@ -0,0 +1,24 @@ +import { ContentSection } from "../types/content"; + +export const MAIN_CONTENTS: ContentSection[] = [ + { + imageSrc: "/images/Img_home_01.png", + title: "Hot Items", + subTitle: "인기 상품을 확인해 보세요", + description: "가장 HOT한 중고거래 물품을\n판다 마켓에서 확인해 보세요", + }, + { + imageSrc: "/images/Img_home_02.png", + title: "Search", + subTitle: "구매를 원하는 상품을 검색하세요", + description: "구매하고 싶은 물품은 검색해서\n쉽게 찾아보세요", + reverse: true, + }, + { + imageSrc: "/images/Img_home_03.png", + title: "Register", + subTitle: "판매를 원하는 상품을 등록하세요", + description: "어떤 물건이든 판매하고 싶은 상품을\n쉽게 등록하세요", + }, +]; + diff --git a/app/(home)/page.tsx b/app/(home)/page.tsx index 30065afb2..79002aa29 100644 --- a/app/(home)/page.tsx +++ b/app/(home)/page.tsx @@ -1,18 +1,17 @@ -import Footer from "@/components/footer/Footer"; -import ImgWrapperBottom from "@/components/home/ImgWrapperBottom"; -import ImgWrapperUpper from "@/components/home/ImgWrapperUpper"; -import MainContents from "@/components/home/MainContents"; -import NavBar from "@/components/navBar/NavBar"; +import Footer from "./components/Footer"; +import ImgBottom from "./components/ImgBottom"; +import ImgUpper from "./components/ImgUpper"; +import MainContents from "./components/MainContents"; const Home = () => { return ( - - + <> + - +