diff --git a/.eslintrc.json b/.eslintrc.json
index bffb357a7..cce2d8873 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,3 +1,9 @@
{
- "extends": "next/core-web-vitals"
+ "extends": ["next/core-web-vitals", "airbnb", "plugin:prettier/recommended"],
+ "rules": {
+ "quotes": ["error", "double"],
+ "prettier/prettier": ["error", { "singleQuote": false }],
+ "react/react-in-jsx-scope": "off",
+ "import/extensions": "off"
+ }
}
diff --git a/.prettierrc b/.prettierrc
index b4bfed357..51a02cc08 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,3 +1,4 @@
{
- "plugins": ["prettier-plugin-tailwindcss"]
+ "plugins": ["prettier-plugin-tailwindcss"],
+ "singleQuote": false
}
diff --git a/assets/images/ic_google.png b/assets/images/ic_google.png
new file mode 100644
index 000000000..d97024347
Binary files /dev/null and b/assets/images/ic_google.png differ
diff --git a/assets/images/ic_kakao.png b/assets/images/ic_kakao.png
new file mode 100644
index 000000000..2a6cb101d
Binary files /dev/null and b/assets/images/ic_kakao.png differ
diff --git a/components/AddComment.tsx b/components/AddComment.tsx
index 24fdeeee3..c8bafbe6b 100644
--- a/components/AddComment.tsx
+++ b/components/AddComment.tsx
@@ -1,6 +1,7 @@
import { Dispatch, SetStateAction, useState } from "react";
import axios from "@/lib/axios";
import { IComment } from "@/types/comment";
+import { API_PATH } from "@/lib/path";
import TextInput from "./Inputs/TextInput";
import AddButton from "./Buttons/AddButton";
@@ -9,8 +10,8 @@ const INPUT_CONTENT = [
{
name: "content",
label: "댓글달기",
+ type: "textarea",
placeholder: "댓글을 입력해주세요",
- isTextArea: true,
},
];
@@ -37,7 +38,7 @@ function AddComment({ id, setCommentList }: AddCommentProps) {
let newComment: IComment;
try {
const response = await axios.post(
- `/articles/${id}/comments`,
+ API_PATH.articleComments(id),
inputValue,
{
headers: {
@@ -47,7 +48,6 @@ function AddComment({ id, setCommentList }: AddCommentProps) {
);
newComment = response.data ?? [];
- console.log("post succeed: ", newComment);
setCommentList((prevCommentList) => [newComment, ...prevCommentList]);
} catch (error) {
console.error("댓글 등록 중 오류가 발생했습니다: ", error);
@@ -81,37 +81,3 @@ function AddComment({ id, setCommentList }: AddCommentProps) {
}
export default AddComment;
-
-// const [inputValue, setInputValue] = useState({
-// content: "",
-// });
-// const [isFormComplete, setIsFormComplete] = useState(false);
-
-// const handleValueChange = (name: string, value: string) => {
-// setInputValue((prevValues) => ({
-// ...prevValues,
-// [name]: value,
-// }));
-// setIsFormComplete(value.trim() !== "");
-// };
-
-// async function postComment() {
-// const accessToken =
-// "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MjUsInNjb3BlIjoiYWNjZXNzIiwiaWF0IjoxNzIzNzczNzEzLCJleHAiOjE3MjM3NzU1MTMsImlzcyI6InNwLXBhbmRhLW1hcmtldCJ9.heZocGCQOejK4JPnWgWzJ438vW1sE2RAsj5d6ZHIhbc";
-
-// let newComment: IComment;
-// try {
-// const data = {
-// ...inputValue,
-// };
-// const response = await axios.post(`/articles/${id}/comments`, data, {
-// headers: {
-// Authorization: `Bearer ${accessToken}`,
-// },
-// });
-// newComment = response.data ?? [];
-// setCommentList((prevCommentList) => [newComment, ...prevCommentList]);
-// } catch (error) {
-// console.error("댓글 등록 중 오류가 발생했습니다: ", error);
-// }
-// }
diff --git a/components/AllArticleList/AllArticle.tsx b/components/AllArticleList/AllArticle.tsx
index bf830fe76..db6721b5e 100644
--- a/components/AllArticleList/AllArticle.tsx
+++ b/components/AllArticleList/AllArticle.tsx
@@ -45,7 +45,9 @@ function AllArticle({ article }: AllArticleProps) {
height={24}
/>
{writer.nickname}
- {formatDate(createdAt)}
+
+ {formatDate(new Date(createdAt))}
+
- {formatDate(createdAt)}
+ {formatDate(new Date(createdAt))}
);
diff --git a/components/BigLogo.tsx b/components/BigLogo.tsx
new file mode 100644
index 000000000..1e8217d05
--- /dev/null
+++ b/components/BigLogo.tsx
@@ -0,0 +1,25 @@
+import Link from "next/link";
+import Image from "next/image";
+import logoIcon from "@/assets/images/ic_logo_icon.png";
+import logoText from "@/assets/images/ic_logo_text.png";
+
+function BigLogo() {
+ return (
+
+
+
+
+ );
+}
+
+export default BigLogo;
diff --git a/components/Buttons/FormButton.tsx b/components/Buttons/FormButton.tsx
new file mode 100644
index 000000000..732b267ae
--- /dev/null
+++ b/components/Buttons/FormButton.tsx
@@ -0,0 +1,19 @@
+interface FormButtonProps {
+ isButtonDisabled: boolean;
+ text: string;
+}
+
+function FormButton({ isButtonDisabled, text }: FormButtonProps) {
+ return (
+
+ );
+}
+
+export default FormButton;
diff --git a/components/CommentList/CommentList.tsx b/components/CommentList/CommentList.tsx
index ec88d49dc..6db61abb5 100644
--- a/components/CommentList/CommentList.tsx
+++ b/components/CommentList/CommentList.tsx
@@ -14,9 +14,9 @@ function CommentList({ commentList: comments }: CommentListProps) {
{!!comments.length ? (
{comments.map((comment: IComment) => (
-
+
-
-
+
))}
) : (
@@ -24,12 +24,12 @@ function CommentList({ commentList: comments }: CommentListProps) {
- 아직 문의가 없습니다.
+ 아직 댓글이 없습니다.
)}
diff --git a/components/DetailArticle.tsx b/components/DetailArticle.tsx
index e8788542a..9a00dac13 100644
--- a/components/DetailArticle.tsx
+++ b/components/DetailArticle.tsx
@@ -47,7 +47,7 @@ function DetailArticle({ article }: DetailArticleProps) {
{writer.nickname || "Anonymous"}
- {formatDate(createdAt)}
+ {formatDate(new Date(createdAt))}
diff --git a/components/EasyLogin/EasyLogin.tsx b/components/EasyLogin/EasyLogin.tsx
new file mode 100644
index 000000000..7166d3fa9
--- /dev/null
+++ b/components/EasyLogin/EasyLogin.tsx
@@ -0,0 +1,22 @@
+import Link from "next/link";
+import Image from "next/image";
+import googleIcon from "@/assets/images/ic_google.png";
+import kakaoIcon from "@/assets/images/ic_kakao.png";
+
+function EasyLogin() {
+ return (
+
+ 간편 로그인하기
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default EasyLogin;
diff --git a/components/Inputs/FileInput.tsx b/components/Inputs/FileInput.tsx
index df2b154d5..1a634abb0 100644
--- a/components/Inputs/FileInput.tsx
+++ b/components/Inputs/FileInput.tsx
@@ -1,4 +1,4 @@
-import { useState } from "react";
+import { useEffect, useState } from "react";
import Image from "next/image";
import plusIcon from "@/assets/images/ic_plus.png";
@@ -26,6 +26,14 @@ function FileInput({ name, label, onChange }: FileInputProps) {
setPreview("");
};
+ useEffect(() => {
+ return () => {
+ if (preview) {
+ URL.revokeObjectURL(preview);
+ }
+ };
+ }, [preview]);
+
return (
{label}
diff --git a/components/Inputs/FormPasswordInput.tsx b/components/Inputs/FormPasswordInput.tsx
new file mode 100644
index 000000000..e11e2322b
--- /dev/null
+++ b/components/Inputs/FormPasswordInput.tsx
@@ -0,0 +1,58 @@
+import { useState } from "react";
+import { UseFormRegister } from "react-hook-form";
+import Image from "next/image";
+import passwordHideIcon from "@/assets/images/ic_password_hide.png";
+import passwordShowIcon from "@/assets/images/ic_password_show.png";
+
+interface Content {
+ name: string;
+ label: string;
+ type?: string;
+ placeholder: string;
+}
+
+interface FormPasswordInputProps {
+ content: Content;
+ register: UseFormRegister
>;
+}
+
+function FormPasswordInput({ content, register }: FormPasswordInputProps) {
+ const { name, label, placeholder } = content;
+
+ const [isPasswordShow, setIsPasswordShow] = useState(false);
+ const handlePasswordShowButtonClick = () => {
+ setIsPasswordShow((prevIsPasswordShow) => !prevIsPasswordShow);
+ };
+
+ return (
+
+
+
+
+
+
+ );
+}
+
+export default FormPasswordInput;
diff --git a/components/Inputs/FormTextInput.tsx b/components/Inputs/FormTextInput.tsx
new file mode 100644
index 000000000..c62a6ee6d
--- /dev/null
+++ b/components/Inputs/FormTextInput.tsx
@@ -0,0 +1,35 @@
+import { UseFormRegister } from "react-hook-form";
+
+interface Content {
+ name: string;
+ label: string;
+ type: string;
+ placeholder: string;
+}
+
+interface FormTextInputProps {
+ content: Content;
+ register: UseFormRegister>;
+}
+
+function FormTextInput({ content, register }: FormTextInputProps) {
+ const { name, label, type, placeholder } = content;
+
+ return (
+
+
+
+
+
+ );
+}
+
+export default FormTextInput;
diff --git a/components/Inputs/TextInput.tsx b/components/Inputs/TextInput.tsx
index ee5c48af5..eb006e863 100644
--- a/components/Inputs/TextInput.tsx
+++ b/components/Inputs/TextInput.tsx
@@ -1,8 +1,8 @@
interface Content {
name: string;
label: string;
+ type: string;
placeholder: string;
- isTextArea: boolean;
}
interface TextInputProps {
@@ -11,7 +11,7 @@ interface TextInputProps {
}
function TextInput({ content, onChange }: TextInputProps) {
- const { name, label, placeholder, isTextArea } = content;
+ const { name, label, type, placeholder } = content;
const handleTextInputChange = (
e: React.ChangeEvent,
@@ -26,20 +26,20 @@ function TextInput({ content, onChange }: TextInputProps) {
{label}
- {!isTextArea ? (
-
) : (
-
diff --git a/components/IntroCard.tsx b/components/IntroCard.tsx
new file mode 100644
index 000000000..77e254d0e
--- /dev/null
+++ b/components/IntroCard.tsx
@@ -0,0 +1,30 @@
+import Image, { StaticImageData } from "next/image";
+
+interface Data {
+ label: string;
+ title: string;
+ content: string;
+ image: StaticImageData;
+ alt: string;
+}
+
+interface IntroCardProps {
+ data: Data;
+}
+
+function IntroCard({ data }: IntroCardProps) {
+ const { label, title, content, image, alt } = data;
+
+ return (
+
+
+
+
{label}
+
{title}
+
{content}
+
+
+ );
+}
+
+export default IntroCard;
diff --git a/components/Nav.tsx b/components/Nav.tsx
index 3e148ce6e..66338647e 100644
--- a/components/Nav.tsx
+++ b/components/Nav.tsx
@@ -1,17 +1,25 @@
-import { useState } from "react";
+import { useEffect, useState } from "react";
import Link from "next/link";
-import Image from "next/image";
import { usePathname } from "next/navigation";
+import LinkButton from "./Buttons/LinkButton";
import styles from "./Nav.module.css";
+import Image from "next/image";
import logoIcon from "@/assets/images/ic_logo_icon.png";
import logoText from "@/assets/images/ic_logo_text.png";
import profileImage from "@/assets/images/img_profile.png";
function Nav() {
- const [isLogin, setIsLogin] = useState(true);
+ const [isLogin, setIsLogin] = useState(false);
const pathname = usePathname();
+ if (typeof window !== "undefined") {
+ const isSignedIn = !!localStorage.getItem("user_information");
+ useEffect(() => {
+ setIsLogin(isSignedIn);
+ }, [isSignedIn]);
+ }
+
return (
);
diff --git a/pages/boards.tsx b/pages/boards/index.tsx
similarity index 93%
rename from pages/boards.tsx
rename to pages/boards/index.tsx
index 658f08ab2..fa081b2fa 100644
--- a/pages/boards.tsx
+++ b/pages/boards/index.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState } from "react";
import { Article } from "@/types/article";
import axios from "@/lib/axios";
+import { API_PATH } from "@/lib/path";
import styles from "@/styles/boards.module.css";
@@ -39,9 +40,7 @@ function Board() {
}, []);
async function getBestArticles(pageSize: number) {
- const response = await axios.get(
- `/articles/?orderBy=like&pageSize=${pageSize}`,
- );
+ const response = await axios.get(API_PATH.articles("like", pageSize));
setBestArticles(response.data.list ?? []);
}
@@ -50,7 +49,7 @@ function Board() {
}, [pageSize]);
async function getArticles(option: string) {
- const response = await axios.get(`/articles/?orderBy=${option}`);
+ const response = await axios.get(API_PATH.articles(option, 10));
setArticles(response.data.list ?? []);
}
diff --git a/pages/index.tsx b/pages/index.tsx
index 11a8dea63..02045b5d8 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -1,11 +1,146 @@
+import { ReactElement } from "react";
+import Link from "next/link";
import Image from "next/image";
+import Head from "next/head";
+
+import Nav from "@/components/Nav";
+import Layout from "@/components/Layout";
+import IntroCard from "@/components/IntroCard";
+
+import headerImage from "@/assets/images/Img_home_top.png";
+import pandaWithTShirtImage from "@/assets/images/Img_home_01.png";
+import readingGlassImage from "@/assets/images/Img_home_02.png";
+import fileOfProductImage from "@/assets/images/Img_home_03.png";
+import facebookIcon from "@/assets/images/ic_facebook.png";
+import twitterIcon from "@/assets/images/ic_twitter.png";
+import youtubeIcon from "@/assets/images/ic_youtube.png";
+import instagramIcon from "@/assets/images/ic_instagram.png";
+
+const CARD_LIST = [
+ {
+ label: "Hot item",
+ title: "인기 상품을 확인해 보세요",
+ content: "가장 HOT한 중고거래 물품을 판다 마켓에서 확인해 보세요",
+ image: pandaWithTShirtImage,
+ alt: "판다가 반팔티를 보고 있는 이미지",
+ },
+ {
+ label: "Search",
+ title: "구매를 원하는 상품을 검색하세요",
+ content: "구매하고 싶은 물품은 검색해서 쉽게 찾아보세요",
+ image: readingGlassImage,
+ alt: "돋보기로 물음표를 보는 이미지",
+ },
+ {
+ label: "Register",
+ title: "판매를 원하는 상품을 등록하세요",
+ content: "어떤 물건이든 판매하고 싶은 상품을 쉽게 등록하세요",
+ image: fileOfProductImage,
+ alt: "상품 파일이 담긴 폴더를 고르는 이미지",
+ },
+];
+
+const SNS_LIST = [
+ {
+ link: "https://www.facebook.com",
+ icon: facebookIcon,
+ alt: "facebook",
+ },
+ {
+ link: "https://www.twitter.com",
+ icon: twitterIcon,
+ alt: "twitter",
+ },
+ {
+ link: "https://www.youtube.com",
+ icon: youtubeIcon,
+ alt: "youtube",
+ },
+ {
+ link: "https://www.instagram.com",
+ icon: instagramIcon,
+ alt: "instagram",
+ },
+];
function Home() {
return (
<>
- main page
+
+ 판다마켓
+
+
+
+
+
+
+ {CARD_LIST.map((content, index) => {
+ return ;
+ })}
+
+
+
+
+ 믿을 수 있는
+ 판다마켓 중고거래
+
+
+
+
+
>
);
}
export default Home;
+
+Home.getLayout = function getLayout(page: ReactElement) {
+ return <>{page}>;
+};
diff --git a/pages/items/index.tsx b/pages/items/index.tsx
new file mode 100644
index 000000000..09655711c
--- /dev/null
+++ b/pages/items/index.tsx
@@ -0,0 +1,7 @@
+import React from "react";
+
+function index() {
+ return index
;
+}
+
+export default index;
diff --git a/pages/login/index.tsx b/pages/login/index.tsx
new file mode 100644
index 000000000..0a3f2d792
--- /dev/null
+++ b/pages/login/index.tsx
@@ -0,0 +1,183 @@
+import { ReactElement, useState } from "react";
+import { useForm, SubmitHandler } from "react-hook-form";
+import Link from "next/link";
+import Head from "next/head";
+import { useRouter } from "next/router";
+import axios from "@/lib/axios";
+import { FormValues } from "@/types/formValues";
+import { API_PATH } from "@/lib/path";
+
+import Layout from "@/components/Layout";
+import BigLogo from "@/components/BigLogo";
+import FormButton from "@/components/Buttons/FormButton";
+import EasyLogin from "@/components/EasyLogin/EasyLogin";
+
+import Image from "next/image";
+import passwordHideIcon from "@/assets/images/ic_password_hide.png";
+import passwordShowIcon from "@/assets/images/ic_password_show.png";
+
+const VALID_EMAIL_PATTERN = /^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-za-z0-9\-]+/;
+const INPUT_CONTENT = [
+ {
+ name: "email",
+ label: "이메일",
+ type: "email",
+ placeholder: "이메일을 입력해주세요",
+ },
+ {
+ name: "password",
+ label: "비밀번호",
+ type: "password",
+ placeholder: "비밀번호를 입력해주세요",
+ },
+];
+
+function Login() {
+ const {
+ watch,
+ register,
+ handleSubmit,
+ formState: { isSubmitting, errors },
+ } = useForm({ mode: "onChange" });
+
+ const email = watch("email");
+ const password = watch("password");
+
+ const isFormCompleted = email && password;
+ const isButtonDisabled = !isFormCompleted || isSubmitting;
+
+ const types = ["password", "text"];
+ const [isPasswordShow, setIsPasswordShow] = useState(false);
+ const handlePasswordShowButtonClick = () => {
+ setIsPasswordShow((prev) => !prev);
+ };
+
+ const router = useRouter();
+
+ // TODO: interceptor
+ // TODO: refresh
+ const onSubmit: SubmitHandler = async (data) => {
+ try {
+ const response = await axios.post(API_PATH.signIn(), data, {
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ const userData = response.data ?? [];
+
+ if (userData.accessToken && userData.refreshToken) {
+ const userToken = {
+ id: userData.user.id,
+ accessToken: userData.accessToken,
+ refreshToken: userData.refreshToken,
+ };
+ localStorage.setItem("user_information", JSON.stringify(userToken));
+ }
+ // TODO: toast 메시지 - 로그인 완료
+ router.push("/");
+ } catch (error) {
+ console.error("회원가입 중 오류가 발생했습니다: ", error);
+ }
+ };
+
+ return (
+ <>
+
+ 판다마켓 - 로그인
+
+
+
+
+
+
+
+
+
+
+ 판다마켓이 처음이신가요?
+
+ 회원가입
+
+
+
+
+ >
+ );
+}
+
+export default Login;
+
+Login.getLayout = function getLayout(page: ReactElement) {
+ return <>{page}>;
+};
diff --git a/pages/search.tsx b/pages/search/index.tsx
similarity index 100%
rename from pages/search.tsx
rename to pages/search/index.tsx
diff --git a/pages/signup/index.tsx b/pages/signup/index.tsx
new file mode 100644
index 000000000..b8bde6ae3
--- /dev/null
+++ b/pages/signup/index.tsx
@@ -0,0 +1,273 @@
+import { ReactElement, useRef, useState } from "react";
+import { useForm, SubmitHandler } from "react-hook-form";
+import Link from "next/link";
+import Head from "next/head";
+import { useRouter } from "next/router";
+import axios from "@/lib/axios";
+import { FormValues } from "@/types/formValues";
+import { API_PATH } from "@/lib/path";
+
+import Layout from "@/components/Layout";
+import BigLogo from "@/components/BigLogo";
+import FormButton from "@/components/Buttons/FormButton";
+import EasyLogin from "@/components/EasyLogin/EasyLogin";
+
+import Image from "next/image";
+import passwordHideIcon from "@/assets/images/ic_password_hide.png";
+import passwordShowIcon from "@/assets/images/ic_password_show.png";
+
+const VALID_EMAIL_PATTERN = /^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-za-z0-9\-]+/;
+const INPUT_CONTENT = [
+ {
+ name: "email",
+ label: "이메일",
+ type: "email",
+ placeholder: "이메일을 입력해주세요",
+ },
+ {
+ name: "nickname",
+ label: "닉네임",
+ type: "text",
+ placeholder: "닉네임을 입력해주세요",
+ },
+ {
+ name: "password",
+ label: "비밀번호",
+ type: "password",
+ placeholder: "비밀번호를 입력해주세요",
+ },
+ {
+ name: "passwordConfirmation",
+ label: "비밀번호 확인",
+ type: "password",
+ placeholder: "비밀번호를 다시 한 번 입력해주세요",
+ },
+];
+
+function SignUp() {
+ const {
+ watch,
+ register,
+ handleSubmit,
+ formState: { isSubmitting, errors },
+ } = useForm({ mode: "onChange" });
+
+ const email = watch("email");
+ const nickname = watch("nickname");
+ const password = watch("password");
+ const passwordConfirmation = watch("passwordConfirmation");
+
+ const isFormCompleted = email && nickname && password && passwordConfirmation;
+ const isPasswordValid = password === passwordConfirmation;
+ const isButtonDisabled = !isFormCompleted || !isPasswordValid || isSubmitting;
+
+ const types = ["password", "text"];
+ const [isPasswordShow, setIsPasswordShow] = useState(false);
+ const handlePasswordShowButtonClick = () => {
+ setIsPasswordShow((prev) => !prev);
+ };
+ const [isPasswordConfirmationShow, setIsPasswordConfirmationShow] =
+ useState(false);
+ const handlePasswordConfirmationShowButtonClick = () => {
+ setIsPasswordConfirmationShow((prev) => !prev);
+ };
+
+ const currentPassword = useRef();
+ currentPassword.current = watch("password");
+
+ const router = useRouter();
+ const isSignedUp = !!localStorage.getItem("user_information");
+ if (isSignedUp) {
+ // TODO: toast 메시지 - 회원가입 내역 존재
+ router.push(`/login`);
+ }
+
+ const onSubmit: SubmitHandler = async (data) => {
+ try {
+ const response = await axios.post(API_PATH.signUp(), data, {
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ const userData = response.data ?? [];
+
+ if (userData.accessToken && userData.refreshToken) {
+ const userToken = {
+ id: userData.user.id,
+ accessToken: userData.accessToken,
+ refreshToken: userData.refreshToken,
+ };
+ localStorage.setItem("user_information", JSON.stringify(userToken));
+ }
+ // TODO: toast 메시지 - 회원가입 완료
+ router.push(`/login`);
+ } catch (error) {
+ console.error("회원가입 중 오류가 발생했습니다: ", error);
+ }
+ };
+
+ return (
+ <>
+
+ 판다마켓 - 회원가입
+
+
+
+
+
+
+
+
+
+
+ 이미 회원이신가요?
+
+ 로그인
+
+
+
+
+ >
+ );
+}
+
+export default SignUp;
+
+SignUp.getLayout = function getLayout(page: ReactElement) {
+ return <>{page}>;
+};
diff --git a/pages/style.css b/pages/style.css
deleted file mode 100644
index 122da93e6..000000000
--- a/pages/style.css
+++ /dev/null
@@ -1,366 +0,0 @@
-/* --------------------- PC: 1200px 이상 --------------------- */
-nav {
- display: flex;
- justify-content: space-between;
- align-items: center;
- width: 100%;
- height: 70px;
- padding-left: 200px;
- padding-right: 200px;
- border-bottom: solid 1px #dfdfdf;
- background-color: #ffffff;
- position: fixed;
- top: 0;
- z-index: 1;
-}
-
-.logo-wrapper {
- display: flex;
- justify-content: space-between;
- align-items: center;
- flex-grow: 0;
- width: 153px;
- height: 51px;
- cursor: pointer;
-}
-
-.logo-icon {
- width: 40px;
- height: auto;
-}
-
-.logo-text {
- width: 103px;
- height: auto;
-}
-
-.login-link {
- flex-grow: 0;
- flex-shrink: 0;
- width: 128px;
- height: 48px;
- padding: 14.5px 0;
- border-radius: 8px;
- background-color: var(--blue);
- font-size: 16px;
- font-weight: 600;
- text-align: center;
- color: var(--light-blue);
- text-decoration: none;
- cursor: pointer;
-}
-
-header {
- display: flex;
- justify-content: flex-start;
- align-items: center;
- height: 540px;
- margin-top: 70px;
- background-color: var(--light-blue);
- background-image: url("./asset/image/Img_home_top.png");
- background-repeat: no-repeat;
- background-size: 52% auto;
- background-position: bottom 0 right 223px;
-}
-
-.header-wrapper {
- width: 1200px;
- margin: 0 auto;
-}
-
-.title {
- font-weight: bold;
- font-size: 40px;
- color: var(--gray700);
- line-height: 140%;
-}
-
-.header-wrapper .title {
- margin-bottom: 32px;
-}
-
-.header-link {
- display: block;
- width: 357px;
- height: 56px;
- padding: 16px 0;
- border-radius: 40px;
- background-color: var(--blue);
- font-weight: 600;
- font-size: 20px;
- text-align: center;
- color: #ffffff;
- text-decoration: none;
- cursor: pointer;
-}
-
-main {
- display: flex;
- justify-content: center;
- align-items: center;
-}
-
-.main-wrapper {
- width: 1200px;
-}
-
-section {
- display: flex;
- justify-content: start;
- align-items: center;
- padding: 137.5px 0;
-}
-
-.section-img {
- width: 50%;
- height: auto;
-}
-
-.section-content {
- margin: 64px;
-}
-
-.section-content .title {
- flex-shrink: 0;
- margin-bottom: 24px;
- letter-spacing: 2%;
-}
-
-.section-content-label {
- margin-bottom: 12px;
- font-size: 18px;
- font-weight: bold;
- line-height: 140%;
- color: var(--blue);
-}
-
-.section-content-description {
- font-size: 24px;
- font-weight: 500;
- letter-spacing: 8%;
- color: var(--gray700);
- line-height: 120%;
-}
-
-.main-wrapper section:nth-child(even) {
- display: flex;
- justify-content: end;
- align-items: center;
- padding: 137.5px 0;
- text-align: right;
-}
-
-.banner {
- display: flex;
- justify-content: flex-start;
- align-items: center;
- height: 540px;
- background-color: var(--light-blue);
- background-image: url("./asset/image/Img_home_bottom.png");
- background-repeat: no-repeat;
- background-size: 52% auto;
- background-position: bottom 0 right 223px;
-}
-
-.banner-wrapper {
- width: 1200px;
- margin: 0 auto;
-}
-
-footer {
- height: 160px;
- padding: 32px 200px 108px;
- background-color: var(--gray900);
-}
-
-.footer-wrapper {
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.footer-copyright {
- flex-grow: 0;
- flex-shrink: 0;
- font-size: 16px;
- color: var(--gray400);
-}
-
-.footer-menu {
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 30px;
- flex-grow: 0;
- flex-shrink: 0;
-}
-
-.footer-menu-link {
- font-size: 16px;
- text-decoration: none;
- color: var(--gray200);
- cursor: pointer;
-}
-
-.footer-sns {
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 12px;
-}
-
-/* --------------------- Tablet: 1199px 이하 --------------------- */
-@media (max-width: 1199px) {
- br.onlyPC {
- display: none;
- }
-
- nav {
- padding-left: 24px;
- padding-right: 24px;
- }
-
- header {
- display: block;
- height: 771px;
- background-size: 996px auto;
- background-position: center bottom 0;
- }
-
- .header-wrapper {
- width: 744px;
- text-align: center;
- }
-
- .header-wrapper > .title {
- padding-top: 84px;
- }
-
- .header-link {
- margin: 0 auto;
- }
-
- .main-wrapper {
- width: 100%;
- }
-
- section {
- display: block;
- padding: 24px;
- }
-
- .section-img {
- width: 100%;
- height: auto;
- margin-bottom: 20px;
- }
-
- .section-content {
- margin: 0 0 64px 0;
- }
-
- .section-content .title {
- font-size: 32px;
- }
-
- .section-content-description {
- font-size: 18px;
- font-weight: 500;
- letter-spacing: 8%;
- color: var(--gray700);
- line-height: 120%;
- }
-
- .main-wrapper section:nth-child(even) {
- display: flex;
- flex-direction: column-reverse;
- align-items: end;
- padding: 24px;
- text-align: right;
- }
-
- .banner {
- height: 927px;
- background-size: 996px auto;
- background-position: center bottom 0;
- }
-
- .banner-wrapper {
- height: 927px;
- text-align: center;
- }
-
- .banner-wrapper > .title {
- margin-top: 200px;
- }
-}
-
-/* --------------------- Mobile: 767px 이하 --------------------- */
-@media (max-width: 767px) {
- nav {
- padding-left: 16px;
- padding-right: 16px;
- }
-
- .logo-icon {
- display: none;
- }
-
- .header-wrapper {
- width: 540px;
- text-align: center;
- }
-
- .header-wrapper > .title {
- padding-top: 48px;
- }
-
- header .title > br.onlyPC {
- display: block;
- }
-
- .header-link {
- width: 154px;
- height: 48px;
- font-size: 1rem;
- }
-
- .banner {
- height: 540px;
- background-size: 500px auto;
- background-position: center bottom 0;
- }
-
- .banner-wrapper {
- height: 540px;
- text-align: center;
- }
-
- .banner-wrapper > .title {
- margin-top: 121px;
- font-size: 32px;
- }
-
- footer {
- padding: 32px;
- }
-
- .footer-wrapper {
- display: flex;
- justify-content: space-between;
- align-items: center;
- flex-wrap: wrap; /* 요소들이 영역을 벗어난 경우의 개행 여부를 설정해줌 */
- height: 100%;
- }
-
- /* .footer-wrapper 내에서 보여질 순서 */
- .footer-copyright {
- order: 3;
- }
- .footer-menu {
- order: 1;
- }
- .footer-sns {
- order: 2;
- }
-}
diff --git a/types/formValues.ts b/types/formValues.ts
new file mode 100644
index 000000000..f0d78dd97
--- /dev/null
+++ b/types/formValues.ts
@@ -0,0 +1,6 @@
+export interface FormValues {
+ email: string;
+ nickname?: string;
+ password: string;
+ passwordConfirmation?: string;
+}