feat: 게시글 삭제 기능(prompt) 작업

This commit is contained in:
2025-05-12 11:23:04 +09:00
parent c41058dae9
commit 0954e21b25
8 changed files with 160 additions and 44 deletions
@@ -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 (
<QueryClientProvider client={queryClient}>
<StoreProvider>
<ConfirmProvider>
<AuthWrapper>
<MenuWrapper>{children}</MenuWrapper>
<AlertDialog />
<ConfirmDialog />
</AuthWrapper>
</ConfirmProvider>
<PromptProvider>
<ConfirmProvider>
<AuthWrapper>
<MenuWrapper>{children}</MenuWrapper>
<AlertDialog />
<PromptDialog />
<ConfirmDialog />
</AuthWrapper>
</ConfirmProvider>
</PromptProvider>
</StoreProvider>
<div className="text-2xl">
<ReactQueryDevtools initialIsOpen={false} />
@@ -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";
@@ -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<string>((resolve) => {
setResolve(() => resolve);
});
};
const onSubmit = (value: string) => {
resolve?.(value);
setMessage("");
};
const onCancel = () => {
resolve?.("");
setMessage("");
};
return { message, isOpen: !!message, prompt, onSubmit, onCancel };
};
@@ -1 +1,2 @@
export { StoreProvider } from "./store-provider";
export { PromptProvider } from "./prompy-provider";
@@ -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<PromptContext>({
message: "",
resolve() {},
setMessage() {},
setResolve() {},
});
export const PromptProvider = ({ children }: { children: React.ReactNode }) => {
const [message, setMessage] = useState<string>();
const [resolve, setResolve] = useState<(value: string) => void>();
return (
<PromptContext.Provider
value={{ message, resolve, setMessage, setResolve }}
>
{children}
</PromptContext.Provider>
);
};
@@ -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 (
<Dialog open={isOpen} onOpenChange={onCancel}>
<DialogContent aria-describedby={undefined}>
<div className="flex flex-col gap-6 pb-4 pt-8">
<p className="whitespace-pre-line text-center text-[1.4rem] text-foreground">
{message}
</p>
<Input
type="password"
value={value}
className="!text-[1.3rem]"
onChange={(e) => setValue(e.target.value)}
/>
</div>
<DialogFooter className="!justify-center">
<DialogClose asChild>
<Button onClick={() => onSubmit(value)}>확인</Button>
</DialogClose>
<DialogClose asChild>
<Button onClick={onCancel} color="white">
취소
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
export { PromptDialog };
@@ -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<void>;
};
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 (
@@ -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 (
<>
<PostAction bbsId={bbsId} pstNo={pstNo}>
<PostAction bbsId={bbsId} pstNo={pstNo} handleDelete={handleDelete}>
<div className="card card-border !p-8">
<h3 className="text-[2rem] font-bold">{postData?.pstTtl}</h3>
<div className="card !m-0 !mt-8 bg-accent">