diff --git a/client/src/app/auth/forgot-password/page.tsx b/client/src/app/(logoutOnly)/auth/forgot-password/page.tsx
similarity index 100%
rename from client/src/app/auth/forgot-password/page.tsx
rename to client/src/app/(logoutOnly)/auth/forgot-password/page.tsx
diff --git a/client/src/app/auth/login/page.tsx b/client/src/app/(logoutOnly)/auth/login/page.tsx
similarity index 100%
rename from client/src/app/auth/login/page.tsx
rename to client/src/app/(logoutOnly)/auth/login/page.tsx
diff --git a/client/src/app/auth/signup/page.tsx b/client/src/app/(logoutOnly)/auth/signup/page.tsx
similarity index 100%
rename from client/src/app/auth/signup/page.tsx
rename to client/src/app/(logoutOnly)/auth/signup/page.tsx
diff --git a/client/src/app/(logoutOnly)/layout.tsx b/client/src/app/(logoutOnly)/layout.tsx
new file mode 100644
index 0000000..edc2ceb
--- /dev/null
+++ b/client/src/app/(logoutOnly)/layout.tsx
@@ -0,0 +1,9 @@
+import useLogoutOnlyProtector from "@/hooks/useLogoutOnlyProtector";
+import { ReactNode } from "react";
+
+const LogoutOnlyLayout = async ({ children }: { children: ReactNode }) => {
+ useLogoutOnlyProtector();
+ return <>{children}>;
+};
+
+export default LogoutOnlyLayout;
diff --git a/client/src/app/(protectedRoute)/layout.tsx b/client/src/app/(protectedRoute)/layout.tsx
new file mode 100644
index 0000000..9ad09ad
--- /dev/null
+++ b/client/src/app/(protectedRoute)/layout.tsx
@@ -0,0 +1,9 @@
+import useAuthProtector from "@/hooks/useAuthProtector";
+import { ReactNode } from "react";
+
+const AuthProtectorlayout = async ({ children }: { children: ReactNode }) => {
+ useAuthProtector();
+ return <>{children}>;
+};
+
+export default AuthProtectorlayout;
diff --git a/client/src/app/(protectedRoute)/new-post/page.tsx b/client/src/app/(protectedRoute)/new-post/page.tsx
new file mode 100644
index 0000000..e7e70ef
--- /dev/null
+++ b/client/src/app/(protectedRoute)/new-post/page.tsx
@@ -0,0 +1,3 @@
+export default function NewpostPage() {
+ return <>페지이>;
+}
diff --git a/client/src/app/@Modal/(.)new-post/page.tsx b/client/src/app/@Modal/(.)new-post/page.tsx
new file mode 100644
index 0000000..28dc5f0
--- /dev/null
+++ b/client/src/app/@Modal/(.)new-post/page.tsx
@@ -0,0 +1,12 @@
+import ModalWrapper from "@/components/ModalWrapper";
+import React, { ReactNode } from "react";
+
+type Props = {
+ children: ReactNode;
+};
+
+const NewPostPage = ({ children }: Props) => {
+ return {children};
+};
+
+export default NewPostPage;
diff --git a/client/src/app/api/auth/login/route.ts b/client/src/app/api/auth/login/route.ts
new file mode 100644
index 0000000..495a405
--- /dev/null
+++ b/client/src/app/api/auth/login/route.ts
@@ -0,0 +1,19 @@
+import { LOGIN_API_PATH } from "@/const/serverPath";
+import { setCookie } from "@/hooks/useSetCookie";
+import axios from "@/libs/axios";
+import { SigninResponseInterface } from "@/types/auth/signinResponse";
+import { NextRequest, NextResponse } from "next/server";
+
+export async function POST(request: NextRequest) {
+ const { id, password } = await request.json();
+ try {
+ const { data } = await axios.post(LOGIN_API_PATH, {
+ id,
+ password,
+ });
+ setCookie({ key: "accessToken", value: data.token, httpOnly: true });
+ return NextResponse.json({ ...data });
+ } catch {
+ return NextResponse.json({ message: "로그인 실패" }, { status: 400 });
+ }
+}
diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx
index 051987a..66976f4 100644
--- a/client/src/app/page.tsx
+++ b/client/src/app/page.tsx
@@ -1,4 +1,4 @@
-"use client";
+
import PostCardList from "@/components/post/PostCardList";
// import { getPostListQueryFn } from "@/queries/post/useGetPostListQuery";
import { Container } from "@mui/material";
diff --git a/client/src/components/NavigationBar.tsx b/client/src/components/NavigationBar.tsx
index b966057..5aabaec 100644
--- a/client/src/components/NavigationBar.tsx
+++ b/client/src/components/NavigationBar.tsx
@@ -6,7 +6,7 @@ import SearchIcon from "~/assets/icons/SearchIcon.svg";
import PostIcon from "~/assets/icons/PostIcon.svg";
import BeverageIcon from "~/assets/icons/BeverageIcon.svg";
import MyIcon from "~/assets/icons/MyIcon.svg";
-import HOME, { MY_PROFILE, SEARCH, WIKI } from "@/const/clientPath";
+import HOME, { MY_PROFILE, NEW_POST, SEARCH, WIKI } from "@/const/clientPath";
import Link from "next/link";
import { usePathname } from "next/navigation";
@@ -19,10 +19,11 @@ const NavbarData = [
{
iconComponent: ,
label: "검색",
- href:SEARCH
+ href: SEARCH,
},
{
iconComponent: ,
+ href: NEW_POST,
},
{
iconComponent: ,
@@ -55,7 +56,7 @@ const NavigationBar = () => {
sx={{
borderRadius: "12px 12px 0 0",
border: "1px solid",
- borderBottom:'none',
+ borderBottom: "none",
borderColor: "gray.secondary",
boxSizing: "border-box",
}}
diff --git a/client/src/components/user/signin/SigninForm.tsx b/client/src/components/user/signin/SigninForm.tsx
index d158fb8..48b42ca 100644
--- a/client/src/components/user/signin/SigninForm.tsx
+++ b/client/src/components/user/signin/SigninForm.tsx
@@ -1,18 +1,13 @@
"use client";
-import HOME from "@/const/clientPath";
import useLoginMutation from "@/queries/auth/useLoginMutation";
-import errorHandler from "@/utils/errorHandler";
import { Box, Button, TextField, Typography } from "@mui/material";
-import { useRouter } from "next/navigation";
import { useState } from "react";
const SigninForm = () => {
const [id, setId] = useState("");
const [password, setPassword] = useState("");
-
- const router = useRouter();
- const { mutate: loginHandler, isError } = useLoginMutation();
+ const { mutate: loginMutation, isError } = useLoginMutation();
return (
{
if (!id || !password) {
return;
}
- try {
- loginHandler({ id, password });
- router.push(HOME);
- } catch {
- errorHandler("로그인에 실패했습다다");
- }
+ loginMutation({ id, password });
}}
sx={{ mt: 1 }}
>
diff --git a/client/src/const/clientPath.ts b/client/src/const/clientPath.ts
index 2de5514..480b2bd 100644
--- a/client/src/const/clientPath.ts
+++ b/client/src/const/clientPath.ts
@@ -43,6 +43,10 @@ export const POST_DETAIL = (userId: string, postId: string) => {
return `/post/@${trimmedUserId}/${trimmedPostId}`;
};
+/**
+ * 새로운 포스트를 작성하는 페이지
+ */
+export const NEW_POST = '/new-post'
export default HOME;
diff --git a/client/src/const/serverPath.ts b/client/src/const/serverPath.ts
index f1d85a2..2b1011f 100644
--- a/client/src/const/serverPath.ts
+++ b/client/src/const/serverPath.ts
@@ -5,4 +5,9 @@ export const LOGIN_API_PATH = '/user/login' as const
/**
* 내 정보를 받아오는 Path
*/
-export const MY_INFO = '/user/me' as const
\ No newline at end of file
+export const MY_INFO = '/user/me' as const
+
+/**
+ * 쿠키를 심어주는 로그인 BFF
+ */
+export const LOGIN_BFF = '/api/auth/login' as const
\ No newline at end of file
diff --git a/client/src/hooks/useAuthProtector.tsx b/client/src/hooks/useAuthProtector.tsx
new file mode 100644
index 0000000..297f962
--- /dev/null
+++ b/client/src/hooks/useAuthProtector.tsx
@@ -0,0 +1,14 @@
+import { SIGNIN } from "@/const/clientPath";
+import { cookies } from "next/headers";
+import { redirect } from "next/navigation";
+/**
+ * 쿠키에 accessToken 이나 refreshToken이 없을 경우 로그인 페이지로 리다이렉트 시키는 훅스
+ */
+export default function useAuthProtector() {
+ const cookieStore = cookies();
+ const accessToken = cookieStore.get("accessToken")?.value;
+
+ if (!accessToken) {
+ redirect(SIGNIN);
+ }
+}
diff --git a/client/src/hooks/useLogin.ts b/client/src/hooks/useLogin.ts
index fb8813d..6e599e4 100644
--- a/client/src/hooks/useLogin.ts
+++ b/client/src/hooks/useLogin.ts
@@ -1,4 +1,4 @@
-import { LOGIN_API_PATH } from "@/const/serverPath";
+import { LOGIN_BFF } from "@/const/serverPath";
import axios from "@/libs/axios";
import { SigninRequirement } from "@/types/auth/signinRequirement";
import { SigninResponseInterface } from "@/types/auth/signinResponse";
@@ -10,12 +10,16 @@ import { SigninResponseInterface } from "@/types/auth/signinResponse";
export default function useLogin() {
const loginHandler = async (props: SigninRequirement) => {
const { id, password } = props;
- const { data } = await axios.post(LOGIN_API_PATH, {
- id,
- password,
- });
+ const { data } = await axios.post(
+ LOGIN_BFF,
+ {
+ id,
+ password,
+ },
+ { baseURL: process.env.NEXT_PUBLIC_CLIENT_BASE_URL }
+ );
return data;
};
- return {loginHandler};
+ return { loginHandler };
}
diff --git a/client/src/hooks/useLogoutOnlyProtector.tsx b/client/src/hooks/useLogoutOnlyProtector.tsx
new file mode 100644
index 0000000..3876a65
--- /dev/null
+++ b/client/src/hooks/useLogoutOnlyProtector.tsx
@@ -0,0 +1,14 @@
+import HOME from "@/const/clientPath";
+import { cookies } from "next/headers";
+import { redirect } from "next/navigation";
+/**
+ * 쿠키에 accessToken 이나 refreshToken이 있을경우 메인페이지로 리다이렉트 시키는 훅스
+ */
+export default function useLogoutProtector() {
+ const cookieStore = cookies();
+ const accessToken = cookieStore.get("accessToken")?.value;
+
+ if (accessToken) {
+ redirect(HOME);
+ }
+}
diff --git a/client/src/queries/auth/useLoginMutation.tsx b/client/src/queries/auth/useLoginMutation.tsx
index 4f103d2..6d887cb 100644
--- a/client/src/queries/auth/useLoginMutation.tsx
+++ b/client/src/queries/auth/useLoginMutation.tsx
@@ -3,10 +3,15 @@ import useLogin from "@/hooks/useLogin";
import { SigninRequirement } from "@/types/auth/signinRequirement";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { userInfoQueryKeys } from "./useUserInfoQuery";
+import { useRouter } from "next/navigation";
+import HOME from "@/const/clientPath";
+import errorHandler from "@/utils/errorHandler";
+import { AxiosError } from "axios";
const useLoginMutation = () => {
const { loginHandler } = useLogin();
const queryClient = useQueryClient();
+ const router = useRouter();
return useMutation({
mutationKey: LoginMuataionKey.all,
@@ -16,7 +21,10 @@ const useLoginMutation = () => {
onSuccess: async ({ token }) => {
localStorage?.setItem("accessToken", token);
queryClient.invalidateQueries({ queryKey: userInfoQueryKeys.all });
+ router.push(HOME);
},
+ onError: (error: AxiosError<{ detailMessage: string }>) =>
+ errorHandler(error.response?.data.detailMessage ?? "에러가 발생했니다"),
});
};