diff --git a/src/Router.tsx b/src/Router.tsx
index 8c15694..052674c 100644
--- a/src/Router.tsx
+++ b/src/Router.tsx
@@ -8,6 +8,8 @@ import profileLoader from "./pages/ProfilePage/loader/profileLoader";
import NoticeDetailSkeleton from "./components/NoticeDetailSkeleton";
import PageErrorElement from "./components/PageErrorElement";
+import ProtectedRoute from "./components/ProtectedRoute";
+import { loginProtectCondition } from "./constants/router";
import { ROUTES } from "./constants/router";
import AuthLayout from "./layouts/AuthLayout";
import MainLayout from "./layouts/MainLayout";
@@ -45,27 +47,27 @@ const NotFoundPage = lazy(() => import("@/pages/NotFoundPage"));
const authRoutes: RouteObject[] = [
{
path: ROUTES.AUTH.SIGNUP,
- Component: SignupPage,
+ element: ,
},
{
path: ROUTES.AUTH.SIGNIN,
- Component: SigninPage,
+ element: ,
},
];
const shopRoutes: RouteObject[] = [
{
path: ROUTES.SHOP.ROOT,
- Component: ShopPage,
+ element: ,
},
{
path: ROUTES.SHOP.REGISTER,
- Component: ShopRegisterPage,
+ element: ,
handle: { hideFooter: true },
},
{
path: ROUTES.SHOP.EDIT,
- Component: ShopEditPage,
+ element: ,
handle: { hideFooter: true },
},
];
@@ -73,17 +75,30 @@ const shopRoutes: RouteObject[] = [
const profileRoutes: RouteObject[] = [
{
path: ROUTES.PROFILE.ROOT,
- Component: ProfilePage,
+ element: (
+ [
+ loginProtectCondition(isLoggedIn),
+ {
+ isPass: user?.type === "employee",
+ redirectPath: ROUTES.SHOP.ROOT,
+ message: "알바생 계정으로만 이용 가능한 기능입니다.",
+ },
+ ]}
+ >
+
+
+ ),
loader: profileLoader,
},
{
path: ROUTES.PROFILE.REGISTER,
- Component: ProfileRegisterPage,
+ element: ,
handle: { hideFooter: true },
},
{
path: ROUTES.PROFILE.EDIT,
- Component: ProfileEditPage,
+ element: ,
handle: { hideFooter: true },
},
];
@@ -91,31 +106,57 @@ const profileRoutes: RouteObject[] = [
const noticeRoutes: RouteObject[] = [
{
path: ROUTES.NOTICE.ROOT,
- Component: NoticeListPage,
+ element: ,
},
{
path: ROUTES.NOTICE.SEARCH,
- Component: NoticeSearchPage,
+ element: ,
},
{
path: ROUTES.NOTICE.REGISTER,
- Component: NoticeRegisterPage,
+ element: ,
handle: { hideFooter: true },
},
{
path: ROUTES.NOTICE.EDIT,
- Component: NoticeEditPage,
+ element: ,
handle: { hideFooter: true },
},
{
path: ROUTES.NOTICE.NOTICE_ID.EMPLOYER,
- Component: NoticeEmployerPage,
+ element: (
+ [
+ loginProtectCondition(isLoggedIn),
+ {
+ isPass: user?.type === "employer",
+ redirectPath: ROUTES.PROFILE.ROOT,
+ message: "사장님 계정으로만 이용 가능한 기능입니다.",
+ },
+ ]}
+ >
+
+
+ ),
loader: noticeEmployerLoader,
hydrateFallbackElement: ,
},
{
path: ROUTES.NOTICE.NOTICE_ID.EMPLOYEE,
- Component: NoticeEmployeePage,
+ element: (
+ [
+ loginProtectCondition(isLoggedIn),
+ {
+ isPass: user?.type === "employee",
+ redirectPath: ROUTES.SHOP.ROOT,
+ message: "알바생 계정으로만 이용 가능한 기능입니다.",
+ },
+ ]}
+ >
+
+
+ ),
loader: noticeEmployeeLoader,
hydrateFallbackElement: ,
},
@@ -125,12 +166,12 @@ const appRoutes: RouteObject[] = [
...shopRoutes,
...profileRoutes,
...noticeRoutes,
- { path: "*", Component: NotFoundPage },
+ { path: "*", element: },
];
export const router = createBrowserRouter([
{
- Component: AuthLayout,
+ element: ,
children: [
{
children: authRoutes,
@@ -139,7 +180,7 @@ export const router = createBrowserRouter([
],
},
{
- Component: MainLayout,
+ element: ,
children: [
{
children: appRoutes,
diff --git a/src/components/ProtectedRoute.tsx b/src/components/ProtectedRoute.tsx
new file mode 100644
index 0000000..2c4098d
--- /dev/null
+++ b/src/components/ProtectedRoute.tsx
@@ -0,0 +1,60 @@
+import { ReactNode, useLayoutEffect } from "react";
+
+import { useNavigate } from "react-router-dom";
+
+import { User, useUserStore } from "@/hooks/useUserStore";
+import { useModalStore } from "@/store/useModalStore";
+
+interface ProtectedRouteConditionType {
+ isPass: boolean;
+ redirectPath: string;
+ message: string;
+}
+
+interface ProtectedRouteParamType {
+ user: User | null;
+ isLoggedIn: boolean;
+}
+
+interface ProtectedRouteProps {
+ children: ReactNode;
+ conditions: (
+ params: ProtectedRouteParamType,
+ ) => ProtectedRouteConditionType[];
+}
+
+function ProtectedRoute({ children, conditions }: ProtectedRouteProps) {
+ const navigate = useNavigate();
+ const user = useUserStore((state) => state.user);
+ const isLoggedIn = useUserStore((state) => state.isLoggedIn);
+ const openModal = useModalStore((state) => state.openModal);
+
+ useLayoutEffect(() => {
+ if (user === undefined) return;
+ const receivedConditions = conditions({ user, isLoggedIn });
+
+ for (const { isPass, redirectPath, message } of receivedConditions) {
+ if (!isPass) {
+ if (message) {
+ openModal({
+ type: "alert",
+ iconType: "warning",
+ message,
+ onClose: () => navigate(redirectPath),
+ });
+ } else {
+ navigate(redirectPath);
+ }
+ break;
+ }
+ }
+ }, [user, isLoggedIn, conditions, openModal, navigate]);
+
+ if (user === undefined) {
+ return null;
+ }
+
+ return children;
+}
+
+export default ProtectedRoute;
diff --git a/src/constants/router.ts b/src/constants/router.ts
index 5b86a64..b4dcc36 100644
--- a/src/constants/router.ts
+++ b/src/constants/router.ts
@@ -1,4 +1,4 @@
-const ROUTES = {
+export const ROUTES = {
AUTH: {
SIGNUP: "/signup",
SIGNIN: "/signin",
@@ -26,4 +26,9 @@ const ROUTES = {
notFound: "*",
} as const;
-export { ROUTES };
+export const loginProtectCondition = (isLoggedIn: boolean) =>
+ ({
+ isPass: isLoggedIn,
+ redirectPath: ROUTES.AUTH.SIGNIN,
+ message: "로그인 후에 이용 가능한 기능입니다.",
+ }) as const;
diff --git a/src/pages/ProfilePage/ProfilePage.tsx b/src/pages/ProfilePage/ProfilePage.tsx
index 6245db9..20225af 100644
--- a/src/pages/ProfilePage/ProfilePage.tsx
+++ b/src/pages/ProfilePage/ProfilePage.tsx
@@ -7,7 +7,6 @@ import useUserApplications from "./hooks/useUserApplications";
import EmptyStateCard from "@/components/EmptyStateCard";
import { ROUTES } from "@/constants/router";
-import { UserApplicationList } from "@/types/application";
import { UserItem } from "@/types/user";
const LIMIT = 5;
@@ -16,14 +15,17 @@ const PAGE_LIMIT = 7;
export default function ProfilePage() {
const { userInfo } = useLoaderData<{
userInfo: UserItem;
- count: number;
- userApplications: UserApplicationList[];
}>();
+
const navigate = useNavigate();
const { isLoading, totalCount, userApplications } = useUserApplications({
- userId: userInfo.id,
+ userId: userInfo?.id,
});
+ if (!userInfo) {
+ return null;
+ }
+
return (
<>
diff --git a/src/pages/ProfilePage/hooks/useUserApplications.ts b/src/pages/ProfilePage/hooks/useUserApplications.ts
index c4d4b0d..ef424ed 100644
--- a/src/pages/ProfilePage/hooks/useUserApplications.ts
+++ b/src/pages/ProfilePage/hooks/useUserApplications.ts
@@ -24,26 +24,31 @@ const useUserApplications = ({
>([]);
const page = Number(searchParams.get("page")) || 1;
- const fetchUserApplication = async () => {
- setIsLoading(true);
- const userApplications = await getUserApplications(
- userId,
- (page - 1) * offset,
- limit,
- );
-
- const nextUserApplications = userApplications.data.items.map(
- ({ item }) => item,
- );
-
- setTotalCount(userApplications.data.count);
- setUserApplications(nextUserApplications);
- setIsLoading(false);
- };
-
useEffect(() => {
+ if (!userId) return;
+
+ const fetchUserApplication = async () => {
+ try {
+ setIsLoading(true);
+ const userApplications = await getUserApplications(
+ userId,
+ (page - 1) * offset,
+ limit,
+ );
+
+ const nextUserApplications = userApplications.data.items.map(
+ ({ item }) => item,
+ );
+
+ setTotalCount(userApplications.data.count);
+ setUserApplications(nextUserApplications);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
fetchUserApplication();
- }, [page]);
+ }, [userId, page, offset, limit]); // 의존성 보완
return { userApplications, isLoading, totalCount };
};
diff --git a/src/pages/ProfilePage/loader/profileLoader.ts b/src/pages/ProfilePage/loader/profileLoader.ts
index 1ed7e38..82953f0 100644
--- a/src/pages/ProfilePage/loader/profileLoader.ts
+++ b/src/pages/ProfilePage/loader/profileLoader.ts
@@ -3,6 +3,11 @@ import { useUserStore } from "@/hooks/useUserStore";
const profileLoader = async () => {
const userId = useUserStore.getState().user?.id;
+
+ if (!userId) {
+ return {};
+ }
+
const userInfo = await getUser(userId ?? "");
if (userInfo.status === 200) {