feat: 게시판 페이지 가드 로직 작성

This commit is contained in:
2025-06-02 13:34:19 +09:00
parent b59501c68c
commit e91818e130
8 changed files with 262 additions and 204 deletions
@@ -2,7 +2,6 @@
import { useEffect } from "react";
import { usePathname, useRouter } from "next/navigation";
import { useLocalStorage } from "@frontend-opensource/use-react-hooks";
import { MenuAuthorityApi } from "@/entities/menu-authority/@x/menu";
import { getCookie, useAlertStore, useMenuStore } from "@/shared/lib";
import { SITE_ID } from "@/shared/config";
@@ -14,14 +13,8 @@ const MenuWrapper = ({ children }: { children: React.ReactNode }) => {
const pathname = usePathname();
const alert = useAlertStore((state) => state.setMessage);
const {
pageMenuAuthority,
setBbsInfo,
setCurrentMenuInfo,
setPageMenuAuthority,
} = useMenuStore((state) => state);
const [storedBbsInfo] = useLocalStorage("bbs-info", "");
const { pageMenuAuthority, setCurrentMenuInfo, setPageMenuAuthority } =
useMenuStore((state) => state);
const getCurrentMenuInfo = async () => {
const menuRoles = await MenuApi.getMenuRoles(SITE_ID);
@@ -60,11 +53,6 @@ const MenuWrapper = ({ children }: { children: React.ReactNode }) => {
alert("페이지에 대한 조회 권한이 없습니다.", () => router.push("/"));
}, [pageMenuAuthority]);
useEffect(() => {
if (!storedBbsInfo) return;
setBbsInfo(JSON.parse(storedBbsInfo));
}, [storedBbsInfo]);
return <>{children}</>;
};
@@ -9,3 +9,4 @@ export {
type PostFormData,
type PostUpdateFormData,
} from "./model/post-interface";
export { PostPageGuard } from "./lib/post-page-guard";
@@ -0,0 +1,39 @@
import { useEffect } from "react";
import { useQuery } from "@tanstack/react-query";
import { usePathname, useRouter } from "next/navigation";
import { BoardApi } from "@/entities/board";
import { useAlertStore, useMenuStore } from "@/shared/lib";
function PostPageGuard({
bbsId,
children,
}: {
bbsId: string;
children: React.ReactNode;
}) {
const router = useRouter();
const pathname = usePathname();
const alert = useAlertStore((state) => state.setMessage);
const setBbsInfo = useMenuStore((state) => state.setBbsInfo);
const { data: boardInfo, isLoading } = useQuery({
queryKey: [bbsId],
queryFn: () => BoardApi.getBoardInfo(bbsId),
});
useEffect(() => {
if (!boardInfo) return;
setBbsInfo(boardInfo);
}, [boardInfo]);
if (!isLoading && pathname && boardInfo) {
if (!pathname.includes(boardInfo.bbsTypeCd.toLowerCase())) {
alert("잘못된 경로로 접속하였습니다.", router.back);
return null;
}
}
return children;
}
export { PostPageGuard };
@@ -1,6 +1,6 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { useRef, useState } from "react";
import { usePathname, useRouter } from "next/navigation";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { useQuery } from "@tanstack/react-query";
@@ -22,7 +22,12 @@ import {
SelectValue,
Skeleton,
} from "@common/components";
import { PostApi, type Post, type PostParams } from "@/entities/post";
import {
PostApi,
PostPageGuard,
type Post,
type PostParams,
} from "@/entities/post";
import { ENDPOINT } from "@/shared/config";
import { useMenuStore, useUserStore } from "@/shared/lib";
import { GridViewDataTable } from "@/shared/ui/grid-view-data-table";
@@ -35,6 +40,7 @@ function PostPage({ bbsId }: { bbsId: string }) {
const { confirm } = useConfirm();
const userInfo = useUserStore((state) => state.userInfo);
const { bbsInfo, pageMenuAuthority } = useMenuStore((state) => state);
const thumbRefs = useRef<Map<number, string>>(new Map());
const [postParams, setPostParams] = useState<PostParams>({
keywordType: "title",
@@ -46,8 +52,6 @@ function PostPage({ bbsId }: { bbsId: string }) {
});
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const thumbRefs = useRef<Map<number, string>>(new Map());
const { data: upndFixPostList } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, bbsInfo],
queryFn: () =>
@@ -70,7 +74,7 @@ function PostPage({ bbsId }: { bbsId: string }) {
const {
data: postList,
isFetched,
isLoading,
refetch,
} = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId],
@@ -80,7 +84,6 @@ function PostPage({ bbsId }: { bbsId: string }) {
page: pagination.pageIndex,
size: pagination.pageSize,
}),
enabled: false,
});
const isGridViewPage =
@@ -128,94 +131,92 @@ function PostPage({ bbsId }: { bbsId: string }) {
router.push(`${pathname}/${row.pstNo}`);
};
useEffect(() => {
refetch();
}, [pagination]);
return (
<div className="card card-border !mt-6">
<div className="card card-border">
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
<Label className="mr-2 text-label">검색</Label>
<Select
value={postParams.keywordType}
onValueChange={(keywordType) => {
setPostParams({ ...postParams, keywordType });
}}
>
<SelectTrigger className="h-[3.2rem] w-[10rem]">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="title">제목</SelectItem>
<SelectItem value="all">제목+내용</SelectItem>
<SelectItem value="writer">작성자</SelectItem>
</SelectContent>
</Select>
<Input
value={postParams.keyword}
onChange={(e) =>
setPostParams({ ...postParams, keyword: e.target.value })
}
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
/>
<Button className="h-[3.2rem]" type="submit">
조회
</Button>
</form>
</div>
<div className="card card-border">
<div className="card-header">
<div className="flex w-full items-center justify-between">
<h3 className="text-2xl font-medium text-label">콘텐츠 목록</h3>
<div className="flex gap-2">
{pageMenuAuthority?.regAuthrtYn === "Y" && (
<Button onClick={() => router.push(`${pathname}/register`)}>
등록
</Button>
)}
{pageMenuAuthority?.delAuthrtYn === "Y" && (
<Button
color="red"
onClick={onPostDelete}
disabled={!table.getFilteredSelectedRowModel().rows.length}
<PostPageGuard bbsId={bbsId}>
<div className="card card-border !mt-6">
<div className="card card-border">
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
<Label className="mr-2 text-label">검색</Label>
<Select
value={postParams.keywordType}
onValueChange={(keywordType) => {
setPostParams({ ...postParams, keywordType });
}}
>
<SelectTrigger className="h-[3.2rem] w-[10rem]">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="title">제목</SelectItem>
<SelectItem value="all">제목+내용</SelectItem>
<SelectItem value="writer">작성자</SelectItem>
</SelectContent>
</Select>
<Input
value={postParams.keyword}
onChange={(e) =>
setPostParams({ ...postParams, keyword: e.target.value })
}
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
/>
<Button className="h-[3.2rem]" type="submit">
조회
</Button>
</form>
</div>
<div className="card card-border">
<div className="card-header">
<div className="flex w-full items-center justify-between">
<h3 className="text-2xl font-medium text-label">콘텐츠 목록</h3>
<div className="flex gap-2">
{pageMenuAuthority?.regAuthrtYn === "Y" && (
<Button onClick={() => router.push(`${pathname}/register`)}>
등록
</Button>
)}
{pageMenuAuthority?.delAuthrtYn === "Y" && (
<Button
color="red"
onClick={onPostDelete}
disabled={!table.getFilteredSelectedRowModel().rows.length}
>
선택삭제
</Button>
)}
<Select
value={table.getState().pagination.pageSize.toString()}
onValueChange={(value) => {
table.setPageSize(parseInt(value));
}}
>
선택삭제
</Button>
)}
<Select
value={table.getState().pagination.pageSize.toString()}
onValueChange={(value) => {
table.setPageSize(parseInt(value));
}}
>
<SelectTrigger className="h-[3.2rem] w-[12rem]">
<SelectValue placeholder="페이지 크기" />
</SelectTrigger>
<SelectContent>
{[10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem key={pageSize} value={pageSize.toString()}>
{pageSize}개씩 보기
</SelectItem>
))}
</SelectContent>
</Select>
<SelectTrigger className="h-[3.2rem] w-[12rem]">
<SelectValue placeholder="페이지 크기" />
</SelectTrigger>
<SelectContent>
{[10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem key={pageSize} value={pageSize.toString()}>
{pageSize}개씩 보기
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>
</div>
<div className="card !m-[1.2rem] h-full overflow-auto !p-0">
{isFetched ? (
<DataTableComponent
table={table}
onRowClick={handleRowClick}
viewPagination
/>
) : (
<Skeleton className="h-[36rem] w-full" />
)}
<div className="card !m-[1.2rem] h-full overflow-auto !p-0">
{isLoading ? (
<Skeleton className="h-[36rem] w-full" />
) : (
<DataTableComponent
table={table}
onRowClick={handleRowClick}
viewPagination
/>
)}
</div>
</div>
</div>
</div>
</PostPageGuard>
);
}
+1
View File
@@ -13,3 +13,4 @@ export {
type PostUpdateFormData,
type PostService,
} from "./model/post-interface";
export { PostPageGuard } from "./lib/post-page-guard";
@@ -0,0 +1,39 @@
import { useEffect } from "react";
import { useQuery } from "@tanstack/react-query";
import { usePathname, useRouter } from "next/navigation";
import { BoardApi } from "@/entities/board";
import { useAlertStore, useMenuStore } from "@/shared/lib";
function PostPageGuard({
bbsId,
children,
}: {
bbsId: string;
children: React.ReactNode;
}) {
const router = useRouter();
const pathname = usePathname();
const alert = useAlertStore((state) => state.setMessage);
const setBbsInfo = useMenuStore((state) => state.setBbsInfo);
const { data: boardInfo, isLoading } = useQuery({
queryKey: [bbsId],
queryFn: () => BoardApi.getBoardInfo(bbsId),
});
useEffect(() => {
if (!boardInfo) return;
setBbsInfo(boardInfo);
}, [boardInfo]);
if (!isLoading && pathname && boardInfo) {
if (!pathname.includes(boardInfo.bbsTypeCd.toLowerCase())) {
alert("잘못된 경로로 접속하였습니다.", router.back);
return null;
}
}
return children;
}
export { PostPageGuard };
@@ -2,9 +2,7 @@
import dayjs from "dayjs";
import { usePathname, useRouter } from "next/navigation";
import type { ColumnDef, Row } from "@tanstack/react-table";
import { useLocalStorage } from "@frontend-opensource/use-react-hooks";
import type { Board } from "@/entities/board/model/board-interface";
import { useMenuStore } from "@/shared/lib";
import { Button } from "@common/components";
export const columns: ColumnDef<Board>[] = [
@@ -52,19 +50,14 @@ export const columns: ColumnDef<Board>[] = [
const ManageButton = ({ row }: { row: Row<Board> }) => {
const router = useRouter();
const pathname = usePathname();
const [, setBbsInfo] = useLocalStorage("bbs-info", "");
const setStoreBbsInfo = useMenuStore((state) => state.setBbsInfo);
const { bbsTypeCd, bbsId } = row.original;
return (
<Button
size="sm"
onClick={(e) => {
e.stopPropagation();
router.push(`${pathname}/${bbsTypeCd.toLowerCase()}/${bbsId}`);
// router.push(`/pst/${bbsTypeCd.toLowerCase()}/${bbsId}`); // 공통 개발용
setBbsInfo(JSON.stringify(row.original));
setStoreBbsInfo(row.original);
}}
>
관리
+90 -94
View File
@@ -1,6 +1,6 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { useRef, useState } from "react";
import { usePathname, useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import {
@@ -21,9 +21,13 @@ import {
SelectValue,
Skeleton,
} from "@common/components";
import { BoardApi } from "@/entities/board";
import { AuthorityButton } from "@/entities/menu-authority";
import { PostApi, type Post, type PostParams } from "@/entities/post";
import {
PostApi,
PostPageGuard,
type Post,
type PostParams,
} from "@/entities/post";
import { ENDPOINT } from "@/shared/config";
import { useMenuStore, usePrompt } from "@/shared/lib";
import { GridViewDataTable } from "@/shared/ui/grid-view-data-table";
@@ -34,7 +38,7 @@ function PostPage({ bbsId }: { bbsId: string }) {
const router = useRouter();
const pathname = usePathname();
const { prompt } = usePrompt();
const { bbsInfo, setBbsInfo } = useMenuStore((state) => state);
const bbsInfo = useMenuStore((state) => state.bbsInfo);
const thumbRefs = useRef<Map<number, string>>(new Map());
const [postParams, setPostParams] = useState<PostParams>({
@@ -47,11 +51,6 @@ function PostPage({ bbsId }: { bbsId: string }) {
});
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const { data: boardInfo } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.BBS, bbsId],
queryFn: () => BoardApi.getBoardInfo(bbsId),
});
const { data: upndFixPostList } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsId, bbsInfo],
queryFn: () =>
@@ -71,7 +70,7 @@ function PostPage({ bbsId }: { bbsId: string }) {
const {
data: postList,
isFetched,
isLoading,
refetch,
} = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsInfo?.bbsTypeCd],
@@ -131,95 +130,92 @@ function PostPage({ bbsId }: { bbsId: string }) {
router.push(`${pathname}/${row.pstNo}`);
};
useEffect(() => {
if (!boardInfo) return;
setBbsInfo(boardInfo);
}, [boardInfo]);
return (
<div className="card card-border !mt-6">
<div className="card card-border">
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
<Label className="mr-2 text-label">검색</Label>
<Select
value={postParams.keywordType}
onValueChange={(keywordType) => {
setPostParams({ ...postParams, keywordType });
}}
>
<SelectTrigger className="h-[3.2rem] w-[10rem]">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="title">제목</SelectItem>
<SelectItem value="all">제목+내용</SelectItem>
<SelectItem value="writer">작성자</SelectItem>
</SelectContent>
</Select>
<Input
value={postParams.keyword}
onChange={(e) =>
setPostParams({ ...postParams, keyword: e.target.value })
}
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
/>
<Button className="h-[3.2rem]" type="submit">
조회
</Button>
</form>
</div>
<div className="card card-border">
<div className="card-header">
<div className="flex w-full items-center justify-between">
<h3 className="text-2xl font-medium text-label">콘텐츠 목록</h3>
<div className="flex gap-2">
<AuthorityButton
authority="regAuthrtYn"
onClick={() => router.push(`${pathname}/register`)}
>
등록
</AuthorityButton>
<AuthorityButton
authority="delAuthrtYn"
color="red"
onClick={onPostDelete}
disabled={!table.getFilteredSelectedRowModel().rows.length}
>
선택삭제
</AuthorityButton>
<Select
value={table.getState().pagination.pageSize.toString()}
onValueChange={(value) => {
table.setPageSize(parseInt(value));
}}
>
<SelectTrigger className="h-[3.2rem] w-[12rem]">
<SelectValue placeholder="페이지 크기" />
</SelectTrigger>
<SelectContent>
{[10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem key={pageSize} value={pageSize.toString()}>
{pageSize}개씩 보기
</SelectItem>
))}
</SelectContent>
</Select>
<PostPageGuard bbsId={bbsId}>
<div className="card card-border !mt-6">
<div className="card card-border">
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
<Label className="mr-2 text-label">검색</Label>
<Select
value={postParams.keywordType}
onValueChange={(keywordType) => {
setPostParams({ ...postParams, keywordType });
}}
>
<SelectTrigger className="h-[3.2rem] w-[10rem]">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="title">제목</SelectItem>
<SelectItem value="all">제목+내용</SelectItem>
<SelectItem value="writer">작성자</SelectItem>
</SelectContent>
</Select>
<Input
value={postParams.keyword}
onChange={(e) =>
setPostParams({ ...postParams, keyword: e.target.value })
}
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
/>
<Button className="h-[3.2rem]" type="submit">
조회
</Button>
</form>
</div>
<div className="card card-border">
<div className="card-header">
<div className="flex w-full items-center justify-between">
<h3 className="text-2xl font-medium text-label">콘텐츠 목록</h3>
<div className="flex gap-2">
<AuthorityButton
authority="regAuthrtYn"
onClick={() => router.push(`${pathname}/register`)}
>
등록
</AuthorityButton>
<AuthorityButton
authority="delAuthrtYn"
color="red"
onClick={onPostDelete}
disabled={!table.getFilteredSelectedRowModel().rows.length}
>
선택삭제
</AuthorityButton>
<Select
value={table.getState().pagination.pageSize.toString()}
onValueChange={(value) => {
table.setPageSize(parseInt(value));
}}
>
<SelectTrigger className="h-[3.2rem] w-[12rem]">
<SelectValue placeholder="페이지 크기" />
</SelectTrigger>
<SelectContent>
{[10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem key={pageSize} value={pageSize.toString()}>
{pageSize}개씩 보기
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>
</div>
<div className="card !m-[1.2rem] h-full overflow-auto !p-0">
{isFetched ? (
<DataTableComponent
table={table}
onRowClick={handleRowClick}
viewPagination
/>
) : (
<Skeleton className="h-[36rem] w-full" />
)}
<div className="card !m-[1.2rem] h-full overflow-auto !p-0">
{isLoading ? (
<Skeleton className="h-[36rem] w-full" />
) : (
<DataTableComponent
table={table}
onRowClick={handleRowClick}
viewPagination
/>
)}
</div>
</div>
</div>
</div>
</PostPageGuard>
);
}