Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 58 additions & 17 deletions src/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -45,77 +47,116 @@ const NotFoundPage = lazy(() => import("@/pages/NotFoundPage"));
const authRoutes: RouteObject[] = [
{
path: ROUTES.AUTH.SIGNUP,
Component: SignupPage,
element: <SignupPage />,
},
{
path: ROUTES.AUTH.SIGNIN,
Component: SigninPage,
element: <SigninPage />,
},
];

const shopRoutes: RouteObject[] = [
{
path: ROUTES.SHOP.ROOT,
Component: ShopPage,
element: <ShopPage />,
},
{
path: ROUTES.SHOP.REGISTER,
Component: ShopRegisterPage,
element: <ShopRegisterPage />,
handle: { hideFooter: true },
},
{
path: ROUTES.SHOP.EDIT,
Component: ShopEditPage,
element: <ShopEditPage />,
handle: { hideFooter: true },
},
];

const profileRoutes: RouteObject[] = [
{
path: ROUTES.PROFILE.ROOT,
Component: ProfilePage,
element: (
<ProtectedRoute
conditions={({ isLoggedIn, user }) => [
loginProtectCondition(isLoggedIn),
{
isPass: user?.type === "employee",
redirectPath: ROUTES.SHOP.ROOT,
message: "알바생 계정으로만 이용 가능한 기능입니다.",
},
]}
>
<ProfilePage />
</ProtectedRoute>
),
loader: profileLoader,
},
{
path: ROUTES.PROFILE.REGISTER,
Component: ProfileRegisterPage,
element: <ProfileRegisterPage />,
handle: { hideFooter: true },
},
{
path: ROUTES.PROFILE.EDIT,
Component: ProfileEditPage,
element: <ProfileEditPage />,
handle: { hideFooter: true },
},
];

const noticeRoutes: RouteObject[] = [
{
path: ROUTES.NOTICE.ROOT,
Component: NoticeListPage,
element: <NoticeListPage />,
},
{
path: ROUTES.NOTICE.SEARCH,
Component: NoticeSearchPage,
element: <NoticeSearchPage />,
},
{
path: ROUTES.NOTICE.REGISTER,
Component: NoticeRegisterPage,
element: <NoticeRegisterPage />,
handle: { hideFooter: true },
},
{
path: ROUTES.NOTICE.EDIT,
Component: NoticeEditPage,
element: <NoticeEditPage />,
handle: { hideFooter: true },
},
{
path: ROUTES.NOTICE.NOTICE_ID.EMPLOYER,
Component: NoticeEmployerPage,
element: (
<ProtectedRoute
conditions={({ isLoggedIn, user }) => [
loginProtectCondition(isLoggedIn),
{
isPass: user?.type === "employer",
redirectPath: ROUTES.PROFILE.ROOT,
message: "사장님 계정으로만 이용 가능한 기능입니다.",
},
]}
>
<NoticeEmployerPage />
</ProtectedRoute>
),
loader: noticeEmployerLoader,
hydrateFallbackElement: <NoticeDetailSkeleton />,
},
{
path: ROUTES.NOTICE.NOTICE_ID.EMPLOYEE,
Component: NoticeEmployeePage,
element: (
<ProtectedRoute
conditions={({ isLoggedIn, user }) => [
loginProtectCondition(isLoggedIn),
{
isPass: user?.type === "employee",
redirectPath: ROUTES.SHOP.ROOT,
message: "알바생 계정으로만 이용 가능한 기능입니다.",
},
]}
>
<NoticeEmployeePage />
</ProtectedRoute>
),
loader: noticeEmployeeLoader,
hydrateFallbackElement: <NoticeDetailSkeleton />,
},
Expand All @@ -125,12 +166,12 @@ const appRoutes: RouteObject[] = [
...shopRoutes,
...profileRoutes,
...noticeRoutes,
{ path: "*", Component: NotFoundPage },
{ path: "*", element: <NotFoundPage /> },
];

export const router = createBrowserRouter([
{
Component: AuthLayout,
element: <AuthLayout />,
children: [
{
children: authRoutes,
Expand All @@ -139,7 +180,7 @@ export const router = createBrowserRouter([
],
},
{
Component: MainLayout,
element: <MainLayout />,
children: [
{
children: appRoutes,
Expand Down
60 changes: 60 additions & 0 deletions src/components/ProtectedRoute.tsx
Original file line number Diff line number Diff line change
@@ -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;
9 changes: 7 additions & 2 deletions src/constants/router.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const ROUTES = {
export const ROUTES = {
AUTH: {
SIGNUP: "/signup",
SIGNIN: "/signin",
Expand Down Expand Up @@ -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;
10 changes: 6 additions & 4 deletions src/pages/ProfilePage/ProfilePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 (
<>
<section>
Expand Down
41 changes: 23 additions & 18 deletions src/pages/ProfilePage/hooks/useUserApplications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
};
Expand Down
5 changes: 5 additions & 0 deletions src/pages/ProfilePage/loader/profileLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down