diff --git a/apps/common/features/src/app/providers/app-provider.tsx b/apps/common/features/src/app/providers/app-provider.tsx index 6d7cb4480..b82fc1d71 100644 --- a/apps/common/features/src/app/providers/app-provider.tsx +++ b/apps/common/features/src/app/providers/app-provider.tsx @@ -5,8 +5,9 @@ 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 { StoreProvider } from "@/shared/providers"; +import { StoreProvider, PromptProvider } 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 { initializeNextClientConfig } from "@common/config"; @@ -18,13 +19,16 @@ const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => { return ( - - - {children} - - - - + + + + {children} + + + + + +
diff --git a/apps/common/features/src/shared/lib/index.ts b/apps/common/features/src/shared/lib/index.ts index ce8092221..42ec7d509 100644 --- a/apps/common/features/src/shared/lib/index.ts +++ b/apps/common/features/src/shared/lib/index.ts @@ -2,3 +2,4 @@ export { getCookie, setCookie, removeCookie } from "./cookie"; export { useAlertStore } from "./use-alert-store"; export { useMenuStore } from "./use-menu-store"; export { useUserStore } from "./use-user-store"; +export { usePrompt } from "./use-prompt"; diff --git a/apps/common/features/src/shared/lib/use-prompt.tsx b/apps/common/features/src/shared/lib/use-prompt.tsx new file mode 100644 index 000000000..5547f004c --- /dev/null +++ b/apps/common/features/src/shared/lib/use-prompt.tsx @@ -0,0 +1,26 @@ +import { useContext } from "react"; +import { PromptContext } from "../providers/prompy-provider"; + +export const usePrompt = () => { + const { message, resolve, setMessage, setResolve } = + useContext(PromptContext); + + const prompt = (message: string) => { + setMessage(message); + return new Promise((resolve) => { + setResolve(() => resolve); + }); + }; + + const onSubmit = (value: string) => { + resolve?.(value); + setMessage(""); + }; + + const onCancel = () => { + resolve?.(""); + setMessage(""); + }; + + return { message, isOpen: !!message, prompt, onSubmit, onCancel }; +}; diff --git a/apps/common/features/src/shared/providers/index.ts b/apps/common/features/src/shared/providers/index.ts index f2e092170..f95070d0f 100644 --- a/apps/common/features/src/shared/providers/index.ts +++ b/apps/common/features/src/shared/providers/index.ts @@ -1 +1,2 @@ export { StoreProvider } from "./store-provider"; +export { PromptProvider } from "./prompy-provider"; diff --git a/apps/common/features/src/shared/providers/prompy-provider.tsx b/apps/common/features/src/shared/providers/prompy-provider.tsx new file mode 100644 index 000000000..267b7aafc --- /dev/null +++ b/apps/common/features/src/shared/providers/prompy-provider.tsx @@ -0,0 +1,28 @@ +import { createContext, useState } from "react"; + +interface PromptContext { + message?: string; + resolve?: (value: string) => void; + setMessage: (message: string) => void; + setResolve: (resolve: (value: string) => void) => void; +} + +export const PromptContext = createContext({ + message: "", + resolve() {}, + setMessage() {}, + setResolve() {}, +}); + +export const PromptProvider = ({ children }: { children: React.ReactNode }) => { + const [message, setMessage] = useState(); + const [resolve, setResolve] = useState<(value: string) => void>(); + + return ( + + {children} + + ); +}; diff --git a/apps/common/features/src/shared/ui/prompt-dialog.tsx b/apps/common/features/src/shared/ui/prompt-dialog.tsx new file mode 100644 index 000000000..cfbcf2987 --- /dev/null +++ b/apps/common/features/src/shared/ui/prompt-dialog.tsx @@ -0,0 +1,45 @@ +import { useState } from "react"; +import { + Button, + Dialog, + DialogContent, + DialogFooter, + DialogClose, + Input, +} from "@common/components"; +import { usePrompt } from "@/shared/lib/use-prompt"; + +function PromptDialog() { + const { message, isOpen, onSubmit, onCancel } = usePrompt(); + const [value, setValue] = useState(""); + + return ( + + +
+

+ {message} +

+ setValue(e.target.value)} + /> +
+ + + + + + + + +
+
+ ); +} + +export { PromptDialog }; diff --git a/apps/common/features/src/widgets/post/ui/post-action.tsx b/apps/common/features/src/widgets/post/ui/post-action.tsx index f427136dd..9d4bae22f 100644 --- a/apps/common/features/src/widgets/post/ui/post-action.tsx +++ b/apps/common/features/src/widgets/post/ui/post-action.tsx @@ -1,34 +1,22 @@ import { useRouter, usePathname } from "next/navigation"; -import { useMutation } from "@tanstack/react-query"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { Button } from "@common/components"; -import { PostApi } from "@/entities/post"; -import { useAlertStore, useUserStore } from "@/shared/lib"; type PostActionProps = { bbsId: string; pstNo: string; children: React.ReactNode; + handleDelete: () => Promise; }; -function PostAction({ children, bbsId, pstNo }: PostActionProps) { +function PostAction({ children, handleDelete }: PostActionProps) { const router = useRouter(); const pathname = usePathname(); const { confirm } = useConfirm(); - const { setMessage: alert } = useAlertStore((state) => state); - const { userInfo } = useUserStore((state) => state); - - const { mutate: deletePost } = useMutation({ - mutationFn: () => PostApi.deletePost(bbsId, pstNo, userInfo?.userUnqId), - onSuccess: () => { - alert("삭제되었습니다.", router.back); - }, - }); - const onDelete = async () => { - if (await confirm("삭제하시겠습니까?")) deletePost(); + if (await confirm("삭제하시겠습니까?")) handleDelete(); }; return ( diff --git a/apps/common/features/src/widgets/post/ui/post.tsx b/apps/common/features/src/widgets/post/ui/post.tsx index 6ec857b7b..4a199af84 100644 --- a/apps/common/features/src/widgets/post/ui/post.tsx +++ b/apps/common/features/src/widgets/post/ui/post.tsx @@ -2,15 +2,21 @@ import dayjs from "dayjs"; import { useEffect, useState } from "react"; -import { useQuery } from "@tanstack/react-query"; +import { useRouter } from "next/navigation"; +import { useMutation, useQuery } from "@tanstack/react-query"; import { Button, Skeleton } from "@common/components"; import { AttachmentApi } from "@/entities/attachment"; import { PostApi } from "@/entities/post"; +import { useAlertStore, useUserStore, usePrompt } from "@/shared/lib"; import { ENDPOINT } from "@/shared/config"; import { PostAction } from "./post-action"; import { PostComment } from "./post-comment"; function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { + const router = useRouter(); + const { prompt } = usePrompt(); + const { setMessage: alert } = useAlertStore((state) => state); + const { userInfo } = useUserStore((state) => state); const [filename, setFilename] = useState(""); const { data: postData } = useQuery({ @@ -25,6 +31,42 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { enabled: !!postData?.atflCd, }); + const { mutate: deletePost } = useMutation({ + mutationFn: (pstPswd: string) => PostApi.deletePost(bbsId, pstNo, pstPswd), + onSuccess: () => { + alert("삭제되었습니다.", router.back); + }, + }); + + const handleDelete = async () => { + let pstPswd = ""; + if (userInfo?.userUnqId) { + pstPswd = userInfo?.userUnqId; + } else { + pstPswd = await prompt("비밀번호를 입력해주세요."); + } + deletePost(pstPswd); + }; + + const handleDownload = async () => { + if (!downloadResponse) return; + try { + const blob = new Blob([downloadResponse.data]); + const url = window.URL.createObjectURL(blob); + const link = document.createElement("a"); + + link.href = url; + link.download = filename || "download"; + document.body.appendChild(link); + link.click(); + + window.URL.revokeObjectURL(url); + document.body.removeChild(link); + } catch (error) { + console.error(error); + } + }; + useEffect(() => { if (!downloadResponse) return; try { @@ -45,28 +87,9 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { } }, [downloadResponse]); - const handleDownload = async () => { - if (!downloadResponse) return; - try { - const blob = new Blob([downloadResponse.data]); - const url = window.URL.createObjectURL(blob); - const link = document.createElement("a"); - - link.href = url; - link.download = filename || "download"; - document.body.appendChild(link); - link.click(); - - window.URL.revokeObjectURL(url); - document.body.removeChild(link); - } catch (error) { - console.error(error); - } - }; - return ( <> - +

{postData?.pstTtl}