diff --git a/apps/cs/src/app/providers/app-provider.tsx b/apps/cs/src/app/providers/app-provider.tsx index 1a2189b03..137f32f80 100644 --- a/apps/cs/src/app/providers/app-provider.tsx +++ b/apps/cs/src/app/providers/app-provider.tsx @@ -7,10 +7,11 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { ConfirmProvider } from "@frontend-opensource/use-react-hooks"; import { AuthWrapper } from "@/entities/auth"; import { MenuWrapper } from "@/entities/menu"; -import { PromptProvider, StoreProvider } from "@/shared/providers"; import { AlertDialog } from "@/shared/ui/alert-dialog"; import { PromptDialog } from "@/shared/ui/prompt-dialog"; import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; +import { SelectPromptDialog } from "@/shared/ui/select-prompt-dialog"; +import { PromptProvider, StoreProvider } from "@/shared/providers"; const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => { const queryClient = new QueryClient(); @@ -31,6 +32,7 @@ const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => { + diff --git a/apps/cs/src/entities/post/index.ts b/apps/cs/src/entities/post/index.ts index 0f8d14e32..87cdd758c 100644 --- a/apps/cs/src/entities/post/index.ts +++ b/apps/cs/src/entities/post/index.ts @@ -13,4 +13,5 @@ export { type PostUpdateFormData, type PostService, } from "./model/post-interface"; +export { deleteReasonMessages } from "./model/delete-reason-messages"; export { PostPageGuard } from "./lib/post-page-guard"; diff --git a/apps/cs/src/entities/post/model/delete-reason-messages.ts b/apps/cs/src/entities/post/model/delete-reason-messages.ts new file mode 100644 index 000000000..3f1587472 --- /dev/null +++ b/apps/cs/src/entities/post/model/delete-reason-messages.ts @@ -0,0 +1,11 @@ +export const deleteReasonMessages = { + "저작권 침해": + "타인의 저작물을 허락 없이 게시하는 경우 저작권 침해로 간주되어 삭제될 수 있습니다.", + 명예훼손: "타인의 명예를 훼손하는 내용의 게시물은 삭제될 수 있습니다.", + "개인정보 노출": "타인의 개인정보를 노출하는 게시물도 삭제될 수 있습니다.", + "불법 정보 유통": + "불법적인 상품이나 서비스를 판매하거나 홍보하는 게시물은 삭제될 수 있습니다.", + "욕설 및 비방": + "심한 욕설이나 비방, 모욕적인 내용이 포함된 게시물은 삭제될 수 있습니다.", + "광고성 정보": "지나치게 광고성이 짙은 게시물은 삭제될 수 있습니다.", +}; diff --git a/apps/cs/src/pages/(post)/faq/ui/faq-page.tsx b/apps/cs/src/pages/(post)/faq/ui/faq-page.tsx index 1057fd2b2..8ddd3006a 100644 --- a/apps/cs/src/pages/(post)/faq/ui/faq-page.tsx +++ b/apps/cs/src/pages/(post)/faq/ui/faq-page.tsx @@ -21,20 +21,28 @@ import { Skeleton, } from "@common/components"; import { AttachmentApi } from "@/entities/attachment"; -import { PostApi, type PostParams } from "@/entities/post"; +import { + PostApi, + deleteReasonMessages, + type PostParams, +} from "@/entities/post"; import { AuthorityButton } from "@/entities/menu-authority"; import { ENDPOINT } from "@/shared/config"; import { useAlertStore, useMenuStore, - usePrompt, + useSelectPrompt, useMutationWrapper, } from "@/shared/lib"; +const selectData = Object.entries(deleteReasonMessages).map( + ([label, value]) => ({ label, value }), +); + function FaqPage({ bbsId }: { bbsId: string }) { const router = useRouter(); const pathname = usePathname(); - const { prompt } = usePrompt(); + const { prompt } = useSelectPrompt(selectData); const alert = useAlertStore((state) => state.setMessage); const bbsInfo = useMenuStore((state) => state.bbsInfo); @@ -75,11 +83,13 @@ function FaqPage({ bbsId }: { bbsId: string }) { const handleDeletePost = async (pstNo: number) => { const delRsn = await getPromptValue("삭제 사유를 입력해주세요."); + if (!delRsn) return; deletePost({ pstNo, delRsn }); }; const onSelectedPostDelete = async () => { const delRsn = await getPromptValue("삭제 사유를 입력해주세요."); + if (!delRsn) return; await Promise.all( selectedPostNumbers.map((pstNo) => PostApi.deletePost(parseInt(bbsId), pstNo, delRsn), diff --git a/apps/cs/src/pages/recent/ui/recent-page.tsx b/apps/cs/src/pages/recent/ui/recent-page.tsx index f99ee2339..681c25e34 100644 --- a/apps/cs/src/pages/recent/ui/recent-page.tsx +++ b/apps/cs/src/pages/recent/ui/recent-page.tsx @@ -23,13 +23,14 @@ import { } from "@common/components"; import { PostApi, + deleteReasonMessages, type DeletePost, type RecentPost, type RecentPostParams, } from "@/entities/post"; import { BoardApi } from "@/entities/board"; import { AuthorityButton } from "@/entities/menu-authority"; -import { useAlertStore, usePrompt, useDataTable } from "@/shared/lib"; +import { useAlertStore, useSelectPrompt, useDataTable } from "@/shared/lib"; import { ENDPOINT } from "@/shared/config"; import { columns } from "../model/table-columns"; import { MovePostDialog } from "./move-post-dialog"; @@ -39,9 +40,13 @@ type DateRange = { to?: Date | undefined; }; +const selectData = Object.entries(deleteReasonMessages).map( + ([label, value]) => ({ label, value }), +); + function RecentPage() { const router = useRouter(); - const { prompt } = usePrompt(); + const { prompt } = useSelectPrompt(selectData); const { confirm } = useConfirm(); const alert = useAlertStore((state) => state.setMessage); const [dateRange, setDateRange] = useState(); diff --git a/apps/cs/src/shared/lib/index.ts b/apps/cs/src/shared/lib/index.ts index 20ba33c30..5ac79bf08 100644 --- a/apps/cs/src/shared/lib/index.ts +++ b/apps/cs/src/shared/lib/index.ts @@ -5,3 +5,4 @@ export { useDataTable } from "./use-data-table"; export { useMenuStore } from "./use-menu-store"; export { useUserStore } from "./use-user-store"; export { usePrompt } from "./use-prompt"; +export { useSelectPrompt } from "./use-select-prompt"; diff --git a/apps/cs/src/shared/lib/use-select-prompt.tsx b/apps/cs/src/shared/lib/use-select-prompt.tsx new file mode 100644 index 000000000..357be7a36 --- /dev/null +++ b/apps/cs/src/shared/lib/use-select-prompt.tsx @@ -0,0 +1,48 @@ +import { useContext, useEffect } from "react"; +import { PromptContext } from "../providers/prompt-provider"; + +type SelectData = { + label: string; + value: string; +}; + +export const useSelectPrompt = (data?: SelectData[]) => { + const { + selectData, + selectionGuide, + resolve, + setResolve, + setSelectData, + setSelectionGuide, + } = useContext(PromptContext); + + const prompt = (message: string) => { + setSelectionGuide(message); + return new Promise<{ resolved: boolean; value?: string }>((resolve) => { + setResolve(() => resolve); + }); + }; + + const onSubmit = (value: string) => { + resolve?.({ resolved: true, value }); + setSelectionGuide(""); + }; + + const onCancel = () => { + resolve?.({ resolved: false }); + setSelectionGuide(""); + }; + + useEffect(() => { + if (data) setSelectData(data); + }, [data]); + + return { + selectData, + selectionGuide, + isOpen: !!selectionGuide, + prompt, + onSubmit, + onCancel, + }; +}; diff --git a/apps/cs/src/shared/providers/prompt-provider.tsx b/apps/cs/src/shared/providers/prompt-provider.tsx index 10338c338..71b86388c 100644 --- a/apps/cs/src/shared/providers/prompt-provider.tsx +++ b/apps/cs/src/shared/providers/prompt-provider.tsx @@ -1,7 +1,14 @@ import { createContext, useState } from "react"; +type SelectData = { + label: string; + value: string; +}; + interface PromptContext { message?: string; + selectData?: SelectData[]; + selectionGuide?: string; resolve?: ({ resolved, value, @@ -10,18 +17,26 @@ interface PromptContext { value?: string; }) => void; setMessage: (message: string) => void; + setSelectData: (selectData: SelectData[]) => void; + setSelectionGuide: (message: string) => void; setResolve: (resolve: () => void) => void; } export const PromptContext = createContext({ message: "", + selectData: [], + selectionGuide: "", resolve() {}, setMessage() {}, + setSelectData() {}, + setSelectionGuide() {}, setResolve() {}, }); export const PromptProvider = ({ children }: { children: React.ReactNode }) => { const [message, setMessage] = useState(); + const [selectData, setSelectData] = useState([]); + const [selectionGuide, setSelectionGuide] = useState(); const [resolve, setResolve] = useState< ({ resolved, value }: { resolved: boolean; value?: string }) => void @@ -29,7 +44,16 @@ export const PromptProvider = ({ children }: { children: React.ReactNode }) => { return ( {children} diff --git a/apps/cs/src/shared/ui/prompt-dialog.tsx b/apps/cs/src/shared/ui/prompt-dialog.tsx index 54c2f3fcc..c7e1ebc57 100644 --- a/apps/cs/src/shared/ui/prompt-dialog.tsx +++ b/apps/cs/src/shared/ui/prompt-dialog.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { Button, Dialog, @@ -13,6 +13,10 @@ function PromptDialog() { const { message, isOpen, onSubmit, onCancel } = usePrompt(); const [value, setValue] = useState(""); + useEffect(() => { + if (isOpen) setValue(""); + }, [isOpen]); + return ( diff --git a/apps/cs/src/shared/ui/select-prompt-dialog.tsx b/apps/cs/src/shared/ui/select-prompt-dialog.tsx new file mode 100644 index 000000000..19092c8a1 --- /dev/null +++ b/apps/cs/src/shared/ui/select-prompt-dialog.tsx @@ -0,0 +1,68 @@ +import { useEffect, useState } from "react"; +import { + Button, + Dialog, + DialogContent, + DialogFooter, + DialogClose, + Select, + SelectTrigger, + SelectValue, + SelectContent, + SelectItem, + Textarea, +} from "@common/components"; +import { useSelectPrompt } from "../lib/use-select-prompt"; + +function SelectPromptDialog() { + const { selectData, selectionGuide, isOpen, onSubmit, onCancel } = + useSelectPrompt(); + const [value, setValue] = useState(""); + + useEffect(() => { + if (isOpen) setValue(""); + }, [isOpen]); + + return ( + + + + + {selectionGuide} + + + + + + + + {selectData?.map((data) => ( + + {data.label} + + ))} + + + setValue(e.target.value)} + /> + + + + + onSubmit(value)}>확인 + + + + 취소 + + + + + + ); +} + +export { SelectPromptDialog }; diff --git a/apps/cs/src/widgets/post/ui/post-action.tsx b/apps/cs/src/widgets/post/ui/post-action.tsx index 006eaec50..36b3d7146 100644 --- a/apps/cs/src/widgets/post/ui/post-action.tsx +++ b/apps/cs/src/widgets/post/ui/post-action.tsx @@ -1,6 +1,7 @@ import { useRouter, usePathname } from "next/navigation"; import { Button } from "@common/components"; -import { useMenuStore, usePrompt, useUserStore } from "@/shared/lib"; +import { deleteReasonMessages } from "@/entities/post"; +import { useMenuStore, useSelectPrompt, useUserStore } from "@/shared/lib"; type PostActionProps = { bbsId: string; @@ -9,10 +10,14 @@ type PostActionProps = { handleDelete: (delRsn?: string) => Promise; }; +const selectData = Object.entries(deleteReasonMessages).map( + ([label, value]) => ({ label, value }), +); + function PostAction({ children, handleDelete }: PostActionProps) { const router = useRouter(); const pathname = usePathname(); - const { prompt } = usePrompt(); + const { prompt } = useSelectPrompt(selectData); const bbsInfo = useMenuStore((state) => state.bbsInfo); const userInfo = useUserStore((state) => state.userInfo); diff --git a/apps/cs/src/widgets/post/ui/post-page.tsx b/apps/cs/src/widgets/post/ui/post-page.tsx index 320134d7d..0a91f08e9 100644 --- a/apps/cs/src/widgets/post/ui/post-page.tsx +++ b/apps/cs/src/widgets/post/ui/post-page.tsx @@ -25,19 +25,24 @@ import { AuthorityButton } from "@/entities/menu-authority"; import { PostApi, PostPageGuard, + deleteReasonMessages, type Post, type PostParams, } from "@/entities/post"; import { ENDPOINT } from "@/shared/config"; -import { useMenuStore, usePrompt } from "@/shared/lib"; +import { useMenuStore, useSelectPrompt } from "@/shared/lib"; import { GridViewDataTable } from "@/shared/ui/grid-view-data-table"; import { columns as tableColumns } from "../model/post-table-columns"; import { createColumns as createGridViewColumns } from "../model/grid-view-table-columns"; +const selectData = Object.entries(deleteReasonMessages).map( + ([label, value]) => ({ label, value }), +); + function PostPage({ bbsId }: { bbsId: string }) { const router = useRouter(); const pathname = usePathname(); - const { prompt } = usePrompt(); + const { prompt } = useSelectPrompt(selectData); const bbsInfo = useMenuStore((state) => state.bbsInfo); const thumbRefs = useRef>(new Map());
+ {selectionGuide} +