diff --git a/pages/api/articleApi.ts b/pages/api/articleApi.ts
index a65aa92bc..e890eb900 100644
--- a/pages/api/articleApi.ts
+++ b/pages/api/articleApi.ts
@@ -55,39 +55,6 @@ const fetchInquiryById = async (id: string, cursor: string | null = null) => {
}
};
-const SetTokensToLocalStorage = async () => {
- try {
- const response = await fetch(
- `${process.env.NEXT_PUBLIC_SERVER_URL}/auth/signIn`,
- {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- email: process.env.NEXT_PUBLIC_USER_EMAIL,
- password: process.env.NEXT_PUBLIC_USER_PASSWORD,
- }),
- }
- );
-
- if (!response.ok) {
- throw new Error("로그인 실패: 서버에서 인증 정보를 확인하세요.");
- }
-
- const { accessToken, refreshToken } = await response.json();
-
- // localStorage에 토큰 저장
- localStorage.setItem("accessToken", accessToken);
- localStorage.setItem("refreshToken", refreshToken);
-
- console.log("토큰이 성공적으로 설정되었습니다.");
- } catch (error) {
- console.error("초기 로그인 요청 실패:", error);
- throw error;
- }
-};
-
const retryFetch = async (
url: string,
options: RequestInit
@@ -233,5 +200,4 @@ export {
fetchInquiryById,
postArticle,
postArticleComment,
- SetTokensToLocalStorage,
};
diff --git a/pages/api/authApi.ts b/pages/api/authApi.ts
new file mode 100644
index 000000000..63c80e6db
--- /dev/null
+++ b/pages/api/authApi.ts
@@ -0,0 +1,65 @@
+import { LoginInterface, SignupInterface } from "@/types/auth";
+
+const loginAndSetToken = async ({ email, password }: LoginInterface) => {
+ try {
+ const response = await fetch(
+ `${process.env.NEXT_PUBLIC_SERVER_URL}/auth/signIn`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ email: email,
+ password: password,
+ }),
+ }
+ );
+
+ if (response.ok) {
+ const { accessToken, refreshToken } = await response.json();
+
+ // localStorage에 토큰 저장
+ localStorage.setItem("accessToken", accessToken);
+ localStorage.setItem("refreshToken", refreshToken);
+
+ console.log("토큰이 성공적으로 설정되었습니다.");
+ } else {
+ throw new Error("로그인 실패: 서버에서 인증 정보를 확인하세요.");
+ }
+ } catch (error) {
+ console.error("초기 로그인 요청 실패:", error);
+ throw error;
+ }
+};
+
+const signUp = async ({ email, nickname, password }: SignupInterface) => {
+ try {
+ const response = await fetch(
+ `${process.env.NEXT_PUBLIC_SERVER_URL}/auth/signUp`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ email,
+ nickname,
+ password,
+ passwordConfirmation: password,
+ }),
+ }
+ );
+
+ if (!response.ok) {
+ console.log(response.statusText);
+ }
+
+ return await response.json();
+ } catch (error) {
+ console.error("초기 로그인 요청 실패:", error);
+ throw error;
+ }
+};
+
+export { loginAndSetToken, signUp };
diff --git a/pages/boards/[id].tsx b/pages/boards/[id].tsx
index 2861ddf7b..e67d8acab 100644
--- a/pages/boards/[id].tsx
+++ b/pages/boards/[id].tsx
@@ -12,6 +12,7 @@ import { ArticleCommentInterface, ArticleInterface } from "@/types/article";
import PrimaryButton from "@/components/common/PrimaryButton";
import DetailInquiry from "@/components/detail/DetailInquiry";
import InquiryEmpty from "@/components/detail/InquiryEmpty";
+import BackIcon from "@/components/Icons/BackIcon";
const INITIAL_DETAILS: ArticleInterface = {
id: 0,
@@ -69,7 +70,7 @@ function Detail() {
throw new Error("Id is null");
}
const response = await postArticleComment({
- id: +id,
+ id: Number(id),
content: newComment,
});
setComments((prev) => ({
@@ -119,7 +120,7 @@ function Detail() {
}, [handleObserver]);
return (
<>
-
+
@@ -153,21 +154,7 @@ function Detail() {
목록으로 돌아가기
- {/* Sprint10에 추가 예정 */}
-
+
diff --git a/pages/boards/index.tsx b/pages/boards/index.tsx
index aa695470c..e8a0be55d 100644
--- a/pages/boards/index.tsx
+++ b/pages/boards/index.tsx
@@ -5,7 +5,7 @@ import Header from "@/components/common/Header";
function index() {
return (
<>
-
+
diff --git a/pages/index.tsx b/pages/index.tsx
index a803c0dd0..c2097d5eb 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -2,13 +2,8 @@ import Header from "@/components/common/Header";
import Link from "next/link";
import Footer from "@/components/common/Footer";
import Image from "next/image";
-import { useEffect } from "react";
-import { SetTokensToLocalStorage } from "./api/articleApi";
export default function Home() {
- useEffect(() => {
- SetTokensToLocalStorage();
- }, []);
return (
<>
diff --git a/pages/items/[id].tsx b/pages/items/[id].tsx
index 6f2a0eb0b..e0434c3e9 100644
--- a/pages/items/[id].tsx
+++ b/pages/items/[id].tsx
@@ -11,6 +11,7 @@ import DetailInquiry from "@/components/detail/DetailInquiry";
import InquiryEmpty from "@/components/detail/InquiryEmpty";
import Link from "next/link";
import { useRouter } from "next/router";
+import BackIcon from "@/components/Icons/BackIcon";
interface InputState {
name: string;
@@ -144,7 +145,7 @@ function Detail() {
}, [handleObserver]);
return (
<>
-
+
@@ -178,20 +179,7 @@ function Detail() {
목록으로 돌아가기
-
+
diff --git a/pages/items/index.tsx b/pages/items/index.tsx
index 348680a49..825329bd2 100644
--- a/pages/items/index.tsx
+++ b/pages/items/index.tsx
@@ -5,7 +5,7 @@ import ProductList from "@/components/items/ProductList";
function Items() {
return (
<>
-
+
diff --git a/pages/login.tsx b/pages/login.tsx
index 638e1362e..f51975dd3 100644
--- a/pages/login.tsx
+++ b/pages/login.tsx
@@ -1,31 +1,45 @@
import EmailInput from "@/components/auth/EmailInput";
import Form from "@/components/auth/Form";
import PassWordInput from "@/components/auth/PassWordInput";
-import useInputReducer from "@/reducers/useInputReducer";
-import { AuthFormState } from "@/types/authForm";
import Image from "next/image";
import Link from "next/link";
-import { useReducer } from "react";
+import { SubmitHandler, useForm } from "react-hook-form";
+import { loginAndSetToken } from "./api/authApi";
+import { LoginInterface } from "@/types/auth";
+import { useRouter } from "next/router";
+import { useEffect, useState } from "react";
-const INITIAL_FORM_STATE: AuthFormState = {
- email: {
- value: "",
- isValid: false,
- errorMessage: "",
- hasFocused: false,
- },
- password: {
- value: "",
- isValid: false,
- errorMessage: "",
- hasFocused: false,
- },
- isFormValid: false,
+const INITIAL_FORM_STATE: LoginInterface = {
+ email: "",
+ password: "",
};
function Login() {
- const [state, dispatch] = useReducer(useInputReducer, INITIAL_FORM_STATE);
- const { email, password, isFormValid } = state;
+ const router = useRouter();
+ const {
+ register,
+ handleSubmit,
+ formState: { errors, isValid },
+ } = useForm({
+ mode: "onBlur",
+ defaultValues: INITIAL_FORM_STATE,
+ });
+ const [isLoading, setIsLoading] = useState(true);
+
+ const onSubmit: SubmitHandler = async ({
+ email,
+ password,
+ }) => {
+ await loginAndSetToken({ email, password });
+ router.push("/");
+ };
+
+ useEffect(() => {
+ if (localStorage.getItem("accessToken")) router.push("/");
+ else setIsLoading(false);
+ }, [router]);
+
+ if (isLoading) return null;
return (
@@ -39,9 +53,27 @@ function Login() {
/>
-
);
diff --git a/pages/signup.tsx b/pages/signup.tsx
index 32067b5fa..f17f8b2a1 100644
--- a/pages/signup.tsx
+++ b/pages/signup.tsx
@@ -3,43 +3,53 @@ import Form from "@/components/auth/Form";
import NickNameInput from "@/components/auth/NickNameInput";
import PassWordInput from "@/components/auth/PassWordInput";
import PassWordInputConfirm from "@/components/auth/PassWordInputConfirm";
-import useInputReducer from "@/reducers/useInputReducer";
-import { AuthFormState } from "@/types/authForm";
+import { SignupInterface } from "@/types/auth";
import Image from "next/image";
import Link from "next/link";
-import { useReducer } from "react";
+import { SubmitHandler, useForm } from "react-hook-form";
+import { signUp } from "./api/authApi";
+import { useEffect, useState } from "react";
+import { useRouter } from "next/router";
-const INITIAL_FORM_STATE: AuthFormState = {
- email: {
- value: "",
- isValid: false,
- errorMessage: "",
- hasFocused: false,
- },
- nickname: {
- value: "",
- isValid: false,
- errorMessage: "",
- hasFocused: false,
- },
- password: {
- value: "",
- isValid: false,
- errorMessage: "",
- hasFocused: false,
- },
- passwordConfirm: {
- value: "",
- isValid: false,
- errorMessage: "",
- hasFocused: false,
- },
- isFormValid: false,
+const INITIAL_FORM_STATE: SignupInterface = {
+ email: "",
+ nickname: "",
+ password: "",
+ passwordConfirm: "",
};
function Signup() {
- const [state, dispatch] = useReducer(useInputReducer, INITIAL_FORM_STATE);
- const { email, nickname, password, passwordConfirm, isFormValid } = state;
+ const router = useRouter();
+ const {
+ register,
+ handleSubmit,
+ watch,
+ formState: { errors, isValid },
+ } = useForm({
+ mode: "onBlur",
+ defaultValues: INITIAL_FORM_STATE,
+ });
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState("");
+
+ const onSubmit: SubmitHandler = async ({
+ email,
+ nickname,
+ password,
+ }) => {
+ const res = await signUp({ email, nickname, password });
+
+ if (res.message) setError(res.message);
+ else router.push("/login");
+ };
+
+ useEffect(() => {
+ if (localStorage.getItem("accessToken")) router.push("/");
+ else setIsLoading(false);
+ }, [router]);
+
+ if (isLoading) return null;
+
return (
@@ -52,18 +62,51 @@ function Signup() {
/>
-
+ {error &&
{error}
}
);
}
diff --git a/styles/css/style.css b/styles/css/style.css
index fe57815c2..6ff5df027 100644
--- a/styles/css/style.css
+++ b/styles/css/style.css
@@ -474,6 +474,7 @@ template {
--gray600: #4b5563;
--gray500: #6b7280;
--gray400: #9ca3af;
+ --gray300: #d1d5db;
--gray200: #e5e7eb;
--gray100: #f3f4f6;
--gray50: #f9fafb;
@@ -577,10 +578,25 @@ header .login-area .content-link a + a {
header .login-area .content-link a.active {
color: var(--primary100);
}
-header .login-area .link-profile {
+header .login-area .btn-profile {
position: relative;
width: 4rem;
height: 4rem;
+ border: 0;
+ background-color: transparent;
+}
+header .login-area .btn-profile .btn-logout {
+ position: absolute;
+ bottom: -6rem;
+ right: 0;
+ width: 13.9rem;
+ padding: 1.6rem 0;
+ border: 0.1rem solid var(--gray300);
+ border-radius: 0.8rem;
+ background-color: var(--white);
+ font-size: 1.6rem;
+ line-height: 1.9rem;
+ color: var(--gray500);
}
header .link-login {
display: flex;
@@ -1954,7 +1970,6 @@ footer .footer-wrap .sns a img {
height: 100%;
}
.container form .input-area .msg-error {
- display: none;
margin: 0.8rem 0 0 1.6rem;
font-size: 1.4rem;
font-weight: var(--semiBold);
@@ -2013,6 +2028,12 @@ footer .footer-wrap .sns a img {
color: var(--primary100);
text-decoration: underline;
}
+.container .res-message {
+ margin-top: 1.5rem;
+ font-size: 1.8rem;
+ font-weight: var(--bold);
+ color: var(--error);
+}
/* BreakPoint Mobile */
@media (max-width: 767px) {
diff --git a/styles/scss/components/_header.scss b/styles/scss/components/_header.scss
index c2d577573..58b75519e 100644
--- a/styles/scss/components/_header.scss
+++ b/styles/scss/components/_header.scss
@@ -45,10 +45,25 @@ header {
}
}
}
- .link-profile {
+ .btn-profile {
position: relative;
width: 4rem;
height: 4rem;
+ border: 0;
+ background-color: transparent;
+ .btn-logout {
+ position: absolute;
+ bottom: -6rem;
+ right: 0;
+ width: 13.9rem;
+ padding: 1.6rem 0;
+ border: 0.1rem solid var(--gray300);
+ border-radius: 0.8rem;
+ background-color: var(--white);
+ font-size: 1.6rem;
+ line-height: 1.9rem;
+ color: var(--gray500);
+ }
}
}
.link-login {
diff --git a/styles/scss/helpers/_common.scss b/styles/scss/helpers/_common.scss
index 392b9177e..a0812a9eb 100644
--- a/styles/scss/helpers/_common.scss
+++ b/styles/scss/helpers/_common.scss
@@ -19,6 +19,7 @@
--gray600: #4b5563;
--gray500: #6b7280;
--gray400: #9ca3af;
+ --gray300: #d1d5db;
--gray200: #e5e7eb;
--gray100: #f3f4f6;
--gray50: #f9fafb;
diff --git a/styles/scss/pages/_auth.scss b/styles/scss/pages/_auth.scss
index 87e640c11..d5ba83973 100644
--- a/styles/scss/pages/_auth.scss
+++ b/styles/scss/pages/_auth.scss
@@ -83,7 +83,6 @@
}
}
.msg-error {
- display: none;
margin: 0.8rem 0 0 1.6rem;
font-size: 1.4rem;
font-weight: var(--semiBold);
@@ -144,6 +143,12 @@
}
}
}
+ .res-message {
+ margin-top: 1.5rem;
+ font-size: 1.8rem;
+ font-weight: var(--bold);
+ color: var(--error);
+ }
}
/* BreakPoint Mobile */
diff --git a/types/auth.ts b/types/auth.ts
new file mode 100644
index 000000000..1622cd505
--- /dev/null
+++ b/types/auth.ts
@@ -0,0 +1,13 @@
+interface LoginInterface {
+ email: string;
+ password: string;
+}
+
+interface SignupInterface {
+ email: string;
+ nickname: string;
+ password: string;
+ passwordConfirm?: string;
+}
+
+export type { LoginInterface, SignupInterface };
diff --git a/utils/formatDate.ts b/utils/formatDate.ts
index a8424dbb0..812b03ba0 100644
--- a/utils/formatDate.ts
+++ b/utils/formatDate.ts
@@ -11,7 +11,7 @@ const calculateGapTime = (date: string) => {
const currentDate = new Date();
const targetDate = new Date(date);
const differenceInMinutes = Math.floor(
- (+currentDate - +targetDate) / (1000 * 60)
+ (Number(currentDate) - Number(targetDate)) / (1000 * 60)
);
const differenceInHour = Math.floor(differenceInMinutes / 60);
const differenceInDays = Math.floor(differenceInHour / 24);