feat: 이미지 및 동영상 게시글 작업(cs, features)

This commit is contained in:
2025-05-15 16:32:30 +09:00
parent 0d9d3c2fc5
commit 8c84ef2be8
36 changed files with 456 additions and 278 deletions
@@ -52,7 +52,15 @@ export class AttachmentApiService implements AttachmentService {
return URL.createObjectURL(blob);
}
public async getMovieThumbnailUrl(atflId: string): Promise<string> {
public async getStreamVideoUrlByAtflId(atflId: string): Promise<string> {
const response = await fetch(
`${ENDPOINT.ATTACH_SERVICE.STREAM_VIDEO}/${atflId}`,
);
const blob = await response.blob();
return URL.createObjectURL(blob);
}
public async getMovieThumbnailUrlByAtflId(atflId: string): Promise<string> {
const response = await fetch(
`${ENDPOINT.ATTACH_SERVICE.MOVIE_THUMBNAIL}/${atflId}`,
);
@@ -60,6 +68,14 @@ export class AttachmentApiService implements AttachmentService {
return URL.createObjectURL(blob);
}
public async getMovieThumbnailUrlByAtflCd(atflCd: string): Promise<string> {
const response = await fetch(
`${ENDPOINT.ATTACH_SERVICE.MOVIE_THUMBNAIL}/atflCd/${atflCd}`,
);
const blob = await response.blob();
return URL.createObjectURL(blob);
}
public async downloadAttachmentByAtflCd(
atflCd: string,
): Promise<AxiosResponse> {
@@ -40,6 +40,8 @@ export interface AttachmentService {
deleteAttachmentByAtflCd: (atflCd: string) => Promise<void>;
getImageUrlByAtflId: (atflId: string) => Promise<string>;
getImageUrlByAtflCd: (atflCd: string) => Promise<string>;
getMovieThumbnailUrl: (atflId: string) => Promise<string>;
getStreamVideoUrlByAtflId: (atflId: string) => Promise<string>;
getMovieThumbnailUrlByAtflId: (atflId: string) => Promise<string>;
getMovieThumbnailUrlByAtflCd: (atflCd: string) => Promise<string>;
downloadAttachmentByAtflCd: (atflCd: string) => Promise<AxiosResponse>;
}
@@ -14,7 +14,7 @@ import {
export class PostApiService implements PostService {
public async getPostList(
bbsId: string,
bbsId: number,
params: PostParams,
): Promise<PostList> {
const response = await client.get<PostList>(
@@ -24,7 +24,7 @@ export class PostApiService implements PostService {
return response.data;
}
public async getPostInfo(bbsId: string, pstNo: string): Promise<PostInfo> {
public async getPostInfo(bbsId: number, pstNo: number): Promise<PostInfo> {
const response = await client.get<PostInfo>(
`${ENDPOINT.BOARD_SERVICE.POSTS_USER}/${bbsId}/${pstNo}`,
);
@@ -41,8 +41,8 @@ export class PostApiService implements PostService {
}
public async updatePost(
bbsId: string,
pstNo: string,
bbsId: number,
pstNo: number,
data: PostUpdateFormData,
): Promise<Post> {
const validatedData = postUpdateFormSchema.parse(data);
@@ -54,8 +54,8 @@ export class PostApiService implements PostService {
}
public async deletePost(
bbsId: string,
pstNo: string,
bbsId: number,
pstNo: number,
pstPswd?: string,
): Promise<void> {
await client.delete(
@@ -8,13 +8,15 @@ export interface PostParams extends PageableParams {
upndFixYn?: string;
prdPstgYn?: string;
siteId?: string;
bbsTypeCd?: string;
}
export interface Post {
bbsId: number;
rowNo: number;
bbsId: number;
pstNo: number;
pstTtl: string;
pstCn: string;
inqNocs: number;
rfrncUrl: string;
popupYn: string;
@@ -73,13 +75,13 @@ export type PostFormData = z.infer<typeof postFormSchema>;
export type PostUpdateFormData = z.infer<typeof postUpdateFormSchema>;
export interface PostService {
getPostList(bbsId: string, params: PostParams): Promise<PostList>;
getPostInfo(bbsId: string, pstNo: string): Promise<PostInfo>;
getPostList(bbsId: number, params: PostParams): Promise<PostList>;
getPostInfo(bbsId: number, pstNo: number): Promise<PostInfo>;
createPost(data: PostFormData): Promise<Post>;
updatePost(
bbsId: string,
pstNo: string,
bbsId: number,
pstNo: number,
data: PostUpdateFormData,
): Promise<Post>;
deletePost(bbsId: string, pstNo: string, pstPswd?: string): Promise<void>;
deletePost(bbsId: number, pstNo: number, pstPswd?: string): Promise<void>;
}
@@ -20,12 +20,12 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
const { data: postInfo } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo],
queryFn: () => PostApi.getPostInfo(bbsId, pstNo),
queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)),
});
const { mutateAsync: updatePost } = useMutation({
mutationFn: (data: PostUpdateFormData) =>
PostApi.updatePost(bbsId, pstNo, data),
PostApi.updatePost(parseInt(bbsId), parseInt(pstNo), data),
onSuccess: () => {
alert("수정되었습니다.", router.back);
},
@@ -20,12 +20,12 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
const { data: postInfo } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo],
queryFn: () => PostApi.getPostInfo(bbsId, pstNo),
queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)),
});
const { mutateAsync: updatePost } = useMutation({
mutationFn: (data: PostUpdateFormData) =>
PostApi.updatePost(bbsId, pstNo, data),
PostApi.updatePost(parseInt(bbsId), parseInt(pstNo), data),
onSuccess: () => {
alert("수정되었습니다.", router.back);
},
@@ -20,12 +20,12 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
const { data: postInfo } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo],
queryFn: () => PostApi.getPostInfo(bbsId, pstNo),
queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)),
});
const { mutateAsync: updatePost } = useMutation({
mutationFn: (data: PostUpdateFormData) =>
PostApi.updatePost(bbsId, pstNo, data),
PostApi.updatePost(parseInt(bbsId), parseInt(pstNo), data),
onSuccess: () => {
alert("수정되었습니다.", router.back);
},
@@ -10,6 +10,7 @@ export const ENDPOINT = {
MOVIE_THUMBNAIL: "/attach-service/api/v1/movie/thumbnail",
ATTACHMENTS: "/attach-service/api/v1/atfl/attachments",
DOWNLOAD: "/attach-service/api/v1/atflcd/download",
STREAM_VIDEO: "/attach-service/api/v1/stream/video",
},
USER_SERVICE: {
LOGIN: "/user-service/login",
@@ -7,18 +7,18 @@ export const usePrompt = () => {
const prompt = (message: string) => {
setMessage(message);
return new Promise<string>((resolve) => {
return new Promise<{ resolved: boolean; value?: string }>((resolve) => {
setResolve(() => resolve);
});
};
const onSubmit = (value: string) => {
resolve?.(value);
resolve?.({ resolved: true, value });
setMessage("");
};
const onCancel = () => {
resolve?.("");
resolve?.({ resolved: false });
setMessage("");
};
@@ -2,9 +2,15 @@ import { createContext, useState } from "react";
interface PromptContext {
message?: string;
resolve?: (value: string) => void;
resolve?: ({
resolved,
value,
}: {
resolved: boolean;
value?: string;
}) => void;
setMessage: (message: string) => void;
setResolve: (resolve: (value: string) => void) => void;
setResolve: (resolve: () => void) => void;
}
export const PromptContext = createContext<PromptContext>({
@@ -16,7 +22,10 @@ export const PromptContext = createContext<PromptContext>({
export const PromptProvider = ({ children }: { children: React.ReactNode }) => {
const [message, setMessage] = useState<string>();
const [resolve, setResolve] = useState<(value: string) => void>();
const [resolve, setResolve] =
useState<
({ resolved, value }: { resolved: boolean; value?: string }) => void
>();
return (
<PromptContext.Provider
@@ -56,7 +56,7 @@ function GridViewDataTable<T>({
key={row.id}
onClick={() => onRowClick?.(row.original)}
className={cn(
"rounded-md border border-border bg-background p-4 shadow-md",
"overflow-hidden rounded-md border border-border bg-background p-4 shadow-md",
onRowClick && "cursor-pointer hover:bg-accent",
)}
>
@@ -1,11 +1,19 @@
import dayjs from "dayjs";
import Image from "next/image";
import type { ColumnDef } from "@tanstack/react-table";
import { Badge, Checkbox, Skeleton } from "@common/components";
import { Badge, Checkbox, Skeleton, cn } from "@common/components";
import { AttachmentApi } from "@/entities/attachment";
import type { Post } from "@/entities/post";
import { useQuery } from "@tanstack/react-query";
import { ENDPOINT } from "@/shared/config";
import { AttachmentApi } from "@/entities/attachment";
import type { BbsType } from "@/shared/store/menu-store";
declare module "@tanstack/react-table" {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface TableMeta<TData> {
bbsTypeCd?: BbsType;
}
}
export const createColumns = (
thumbRefs: React.MutableRefObject<Map<number, string>>,
@@ -22,68 +30,120 @@ export const createColumns = (
className="h-4 w-4 rounded border-gray-300"
/>
),
cell: ({ row }) => {
const { pstTtl, inqNocs } = row.original;
return (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2">
<Checkbox
checked={row.getIsSelected()}
onClick={(e) => e.stopPropagation()}
onCheckedChange={(checked) => row.toggleSelected(!!checked)}
className="h-4 w-4 rounded border-gray-300"
cell: ({ row, table }) => {
const { atflCd, pstNo, pstTtl, inqNocs, rgtrNm, regDt } = row.original;
const bbsTypeCd = table.options.meta?.bbsTypeCd;
if (bbsTypeCd === "BBSIMAGE") {
return (
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<Checkbox
checked={row.getIsSelected()}
onClick={(e) => e.stopPropagation()}
onCheckedChange={(checked) => row.toggleSelected(!!checked)}
className="h-4 w-4 rounded border-gray-300"
/>
{inqNocs >= 100 && <Badge color="red">HOT</Badge>}
{dayjs().diff(dayjs(row.original.regDt), "hour") < 24 && (
<Badge color="blue">NEW</Badge>
)}
<span className="text-[1.6rem] font-bold text-label">
{pstTtl}
</span>
</div>
<ThumbImage
atflCd={atflCd}
pstNo={pstNo}
thumbRefs={thumbRefs}
bbsTypeCd={bbsTypeCd}
/>
{inqNocs >= 100 && <Badge color="red">HOT</Badge>}
{dayjs().diff(dayjs(row.original.regDt), "hour") < 24 && (
<Badge color="blue">NEW</Badge>
)}
<div className="flex flex-col">
<span className="text-[1.4rem] font-bold text-label">
{rgtrNm}
</span>
<span className="text-[1.2rem] text-label">
{dayjs(regDt).format("YYYY-MM-DD")}
</span>
</div>
</div>
<span className="text-3xl font-bold leading-none text-label">
{pstTtl}
</span>
</div>
);
);
}
if (bbsTypeCd === "BBSMOVIE") {
return (
<div className="-m-4">
<ThumbImage
atflCd={atflCd}
pstNo={pstNo}
thumbRefs={thumbRefs}
bbsTypeCd={bbsTypeCd}
classes={{
image: "object-cover",
}}
/>
<div className="p-4">
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<Checkbox
checked={row.getIsSelected()}
onClick={(e) => e.stopPropagation()}
onCheckedChange={(checked) => row.toggleSelected(!!checked)}
className="h-4 w-4 rounded border-gray-300"
/>
{inqNocs >= 100 && <Badge color="red">HOT</Badge>}
{dayjs().diff(dayjs(regDt), "hour") < 24 && (
<Badge color="blue">NEW</Badge>
)}
<span className="text-[1.6rem] font-bold text-label">
{pstTtl}
</span>
</div>
<div className="flex flex-col">
<span className="text-[1.4rem] font-bold text-label">
{rgtrNm}
</span>
<span className="text-[1.2rem] text-label">
{dayjs(regDt).format("YYYY-MM-DD")}
</span>
</div>
</div>
</div>
</div>
);
}
},
},
{
id: "atflCd",
cell: ({ row }) => {
const { atflCd, pstNo } = row.original;
return <ThumbImage atflCd={atflCd} pstNo={pstNo} thumbRefs={thumbRefs} />;
},
},
{
id: "rgtrNm",
cell: ({ row }) => {
return (
<span className="text-[1.4rem] font-bold text-label">
{row.original.rgtrNm}
</span>
);
},
},
{
id: "regDt",
cell: ({ row }) => (
<span className="text-[1.2rem] text-label">
{dayjs(row.original.regDt).format("YYYY-MM-DD")}
</span>
),
},
];
type ThumbImageProps = {
atflCd: string;
pstNo: number;
thumbRefs: React.MutableRefObject<Map<number, string>>;
bbsTypeCd?: string;
classes?: {
image?: string;
};
};
const ThumbImage = ({ atflCd, pstNo, thumbRefs }: ThumbImageProps) => {
const ThumbImage = ({
atflCd,
pstNo,
thumbRefs,
bbsTypeCd,
classes,
}: ThumbImageProps) => {
const prevImage = thumbRefs.current.get(pstNo);
const { data: imageUrl, isLoading } = useQuery({
queryKey: [ENDPOINT.ATTACH_SERVICE.IMAGES, atflCd],
queryFn: () => AttachmentApi.getImageUrlByAtflCd(atflCd),
queryFn: () => {
if (bbsTypeCd === "BBSIMAGE") {
return AttachmentApi.getImageUrlByAtflCd(atflCd);
} else if (bbsTypeCd === "BBSMOVIE") {
return AttachmentApi.getMovieThumbnailUrlByAtflCd(atflCd);
}
},
select: (url) => {
if (url) thumbRefs.current.set(pstNo, url);
return url;
@@ -93,17 +153,17 @@ const ThumbImage = ({ atflCd, pstNo, thumbRefs }: ThumbImageProps) => {
if (isLoading)
return (
<div className="my-6 w-full">
<Skeleton className="mx-auto h-[16rem] w-full" />
<div className="h-[16rem] w-full">
<Skeleton className="h-full w-full" />
</div>
);
if (imageUrl)
return (
<div className="relative my-6 flex h-[16rem] w-full items-center justify-center">
<div className="relative flex h-[16rem] w-full items-center justify-center">
<Image
src={imageUrl}
className="object-contain"
className={cn("object-contain", classes?.image)}
fill
alt="썸네일 이미지"
/>
@@ -111,7 +171,7 @@ const ThumbImage = ({ atflCd, pstNo, thumbRefs }: ThumbImageProps) => {
);
return (
<div className="my-6 flex h-[16rem] w-full items-center justify-center rounded-sm border border-border bg-background">
<div className="flex h-[16rem] w-full items-center justify-center rounded-sm border border-border bg-background">
<i className="diveicon di-image text-3xl" />
</div>
);
@@ -35,7 +35,7 @@ function PostAction({ children, handleDelete }: PostActionProps) {
};
return (
<div>
<>
{children}
<div className="flex justify-center gap-4 pb-12 pt-10">
<Button
@@ -68,7 +68,7 @@ function PostAction({ children, handleDelete }: PostActionProps) {
삭제
</Button>
</div>
</div>
</>
);
}
@@ -89,7 +89,8 @@ function PostComment({ bbsId, pstNo }: { bbsId: number; pstNo: number }) {
if (userInfo?.userUnqId) {
cmntPswd = userInfo?.userUnqId;
} else {
cmntPswd = await prompt("비밀번호를 입력해주세요.");
const { resolved, value } = await prompt("비밀번호를 입력해주세요.");
if (resolved) cmntPswd = value ?? "";
}
return cmntPswd;
};
@@ -24,11 +24,10 @@ import {
} from "@common/components";
import { PostApi, type Post, type PostParams } from "@/entities/post";
import { ENDPOINT } from "@/shared/config";
import type { BbsType } from "@/shared/store/menu-store";
import { useMenuStore, useUserStore } from "@/shared/lib";
import { GridViewDataTable } from "@/shared/ui/grid-view-data-table";
import { columns } from "../model/post-table-columns";
import { createColumns } from "../model/grid-view-table-columns";
import { columns as tableColumns } from "../model/post-table-columns";
import { createColumns as createGridViewColumns } from "../model/grid-view-table-columns";
function PostPage({ bbsId }: { bbsId: string }) {
const router = useRouter();
@@ -36,7 +35,6 @@ function PostPage({ bbsId }: { bbsId: string }) {
const { confirm } = useConfirm();
const { userInfo } = useUserStore((state) => state);
const { bbsInfo, pageMenuAuthority } = useMenuStore((state) => state);
const bbsType = bbsInfo?.bbsTypeCd as BbsType;
const [postParams, setPostParams] = useState<PostParams>({
keywordType: "title",
@@ -53,7 +51,7 @@ function PostPage({ bbsId }: { bbsId: string }) {
const { data: upndFixPostList } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, bbsInfo],
queryFn: () =>
PostApi.getPostList(bbsId, {
PostApi.getPostList(parseInt(bbsId), {
...postParams,
upndFixYn: bbsInfo?.upndFixUseYn,
}),
@@ -77,7 +75,7 @@ function PostPage({ bbsId }: { bbsId: string }) {
} = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId],
queryFn: () =>
PostApi.getPostList(bbsId, {
PostApi.getPostList(parseInt(bbsId), {
...postParams,
page: pagination.pageIndex,
size: pagination.pageSize,
@@ -85,9 +83,14 @@ function PostPage({ bbsId }: { bbsId: string }) {
enabled: false,
});
const isGridViewPage =
bbsInfo?.bbsTypeCd === "BBSIMAGE" || bbsInfo?.bbsTypeCd === "BBSMOVIE";
const table = useReactTable({
data: [...(upndFixPostList?.content ?? []), ...(postList?.content ?? [])],
columns,
data: isGridViewPage
? (postList?.content ?? [])
: [...(upndFixPostList?.content ?? []), ...(postList?.content ?? [])],
columns: isGridViewPage ? createGridViewColumns(thumbRefs) : tableColumns,
state: { pagination, rowSelection },
rowCount: postList?.totalElements ?? 0,
manualPagination: true,
@@ -95,19 +98,10 @@ function PostPage({ bbsId }: { bbsId: string }) {
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
meta: { bbsTypeCd: bbsInfo?.bbsTypeCd },
});
const gridViewTable = useReactTable({
data: postList?.content ?? [],
columns: createColumns(thumbRefs),
state: { pagination, rowSelection },
rowCount: postList?.totalElements ?? 0,
manualPagination: true,
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
});
const DataTableComponent = isGridViewPage ? GridViewDataTable : DataTable;
const onPostDelete = async () => {
const selectedPosts = table
@@ -118,7 +112,7 @@ function PostPage({ bbsId }: { bbsId: string }) {
setRowSelection({});
await Promise.all(
selectedPosts.map((pstNo) =>
PostApi.deletePost(bbsId, pstNo.toString(), userInfo?.userUnqId),
PostApi.deletePost(parseInt(bbsId), pstNo, userInfo?.userUnqId),
),
);
refetch();
@@ -211,19 +205,11 @@ function PostPage({ bbsId }: { bbsId: string }) {
</div>
<div className="card !m-[1.2rem] h-full overflow-auto !p-0">
{isFetched ? (
bbsType === "BBSIMAGE" ? (
<GridViewDataTable
table={gridViewTable}
onRowClick={handleRowClick}
viewPagination
/>
) : (
<DataTable
table={table}
onRowClick={handleRowClick}
viewPagination
/>
)
<DataTableComponent
table={table}
onRowClick={handleRowClick}
viewPagination
/>
) : (
<Skeleton className="h-[36rem] w-full" />
)}
@@ -29,7 +29,7 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
const { data: postData } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo],
queryFn: () => PostApi.getPostInfo(bbsId, pstNo),
queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)),
});
const { data: downloadResponse, isFetching } = useQuery({
@@ -47,7 +47,8 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
});
const { mutate: deletePost } = useMutation({
mutationFn: (pstPswd: string) => PostApi.deletePost(bbsId, pstNo, pstPswd),
mutationFn: (pstPswd: string) =>
PostApi.deletePost(parseInt(bbsId), parseInt(pstNo), pstPswd),
onSuccess: () => {
alert("삭제되었습니다.", router.back);
},
@@ -58,7 +59,8 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
if (userInfo?.userUnqId) {
pstPswd = userInfo?.userUnqId;
} else {
pstPswd = await prompt("비밀번호를 입력해주세요.");
const { resolved, value } = await prompt("비밀번호를 입력해주세요.");
if (resolved) pstPswd = value ?? "";
}
deletePost(pstPswd);
};
@@ -50,7 +50,7 @@ function WithPreviewFileUploader<T extends PostFormData | PostUpdateFormData>({
imageUrl = await AttachmentApi.getImageUrlByAtflId(atflId);
}
if (bbsInfo?.bbsTypeCd === "BBSMOVIE") {
imageUrl = await AttachmentApi.getMovieThumbnailUrl(atflId);
imageUrl = await AttachmentApi.getMovieThumbnailUrlByAtflId(atflId);
}
setThumbnailUrl(imageUrl);
}
+19 -12
View File
@@ -6,8 +6,9 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ConfirmProvider } from "@frontend-opensource/use-react-hooks";
import { AuthWrapper } from "@/entities/auth";
import { MenuWrapper } from "@/entities/menu";
import { StoreProvider } from "@/shared/providers";
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 { initializeNextClientConfig } from "@common/config";
@@ -19,17 +20,23 @@ const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => {
return (
<QueryClientProvider client={queryClient}>
<StoreProvider>
<ConfirmProvider>
<AuthWrapper>
<MenuWrapper>
<DndProvider backend={MultiBackend} options={getBackendOptions()}>
{children}
</DndProvider>
</MenuWrapper>
<AlertDialog />
<ConfirmDialog />
</AuthWrapper>
</ConfirmProvider>
<PromptProvider>
<ConfirmProvider>
<AuthWrapper>
<MenuWrapper>
<DndProvider
backend={MultiBackend}
options={getBackendOptions()}
>
{children}
</DndProvider>
</MenuWrapper>
<AlertDialog />
<PromptDialog />
<ConfirmDialog />
</AuthWrapper>
</ConfirmProvider>
</PromptProvider>
</StoreProvider>
</QueryClientProvider>
);
+14 -15
View File
@@ -14,19 +14,19 @@ import {
export class PostApiService implements PostService {
public async getPostList(
bbsId: string,
bbsId: number,
params: PostParams,
): Promise<PostList> {
const response = await client.get<PostList>(
`${ENDPOINT.BOARD_SERVICE.POSTS_USER}/${bbsId}`,
`${ENDPOINT.BOARD_SERVICE.POSTS}/${bbsId}`,
{ params },
);
return response.data;
}
public async getPostInfo(bbsId: string, pstNo: string): Promise<PostInfo> {
public async getPostInfo(bbsId: number, pstNo: number): Promise<PostInfo> {
const response = await client.get<PostInfo>(
`${ENDPOINT.BOARD_SERVICE.POSTS_USER}/${bbsId}/${pstNo}`,
`${ENDPOINT.BOARD_SERVICE.POSTS}/${bbsId}/${pstNo}`,
);
return response.data;
}
@@ -34,34 +34,33 @@ export class PostApiService implements PostService {
public async createPost(data: PostFormData): Promise<Post> {
const validatedData = postFormSchema.parse(data);
const response = await client.post<Post>(
`${ENDPOINT.BOARD_SERVICE.POSTS_USER}`,
`${ENDPOINT.BOARD_SERVICE.POSTS}`,
validatedData,
);
return response.data;
}
public async updatePost(
bbsId: string,
pstNo: string,
bbsId: number,
pstNo: number,
data: PostUpdateFormData,
): Promise<Post> {
const validatedData = postUpdateFormSchema.parse(data);
const response = await client.put<Post>(
`${ENDPOINT.BOARD_SERVICE.POSTS_USER}/${bbsId}/${pstNo}`,
`${ENDPOINT.BOARD_SERVICE.POSTS}/${bbsId}/${pstNo}`,
validatedData,
);
return response.data;
}
public async deletePost(
bbsId: string,
pstNo: string,
pstPswd?: string,
bbsId: number,
pstNo: number,
delRsn?: string,
): Promise<void> {
await client.delete(
`${ENDPOINT.BOARD_SERVICE.POSTS_USER}/${bbsId}/${pstNo}`,
{ params: { pstPswd } },
);
await client.delete(`${ENDPOINT.BOARD_SERVICE.POSTS}/${bbsId}/${pstNo}`, {
params: { delRsn },
});
}
}
@@ -8,13 +8,15 @@ export interface PostParams extends PageableParams {
upndFixYn?: string;
prdPstgYn?: string;
siteId?: string;
bbsTypeCd?: string;
}
export interface Post {
bbsId: number;
rowNo: number;
bbsId: number;
pstNo: number;
pstTtl: string;
pstCn: string;
inqNocs: number;
rfrncUrl: string;
popupYn: string;
@@ -73,13 +75,13 @@ export type PostFormData = z.infer<typeof postFormSchema>;
export type PostUpdateFormData = z.infer<typeof postUpdateFormSchema>;
export interface PostService {
getPostList(bbsId: string, params: PostParams): Promise<PostList>;
getPostInfo(bbsId: string, pstNo: string): Promise<PostInfo>;
getPostList(bbsId: number, params: PostParams): Promise<PostList>;
getPostInfo(bbsId: number, pstNo: number): Promise<PostInfo>;
createPost(data: PostFormData): Promise<Post>;
updatePost(
bbsId: string,
pstNo: string,
bbsId: number,
pstNo: number,
data: PostUpdateFormData,
): Promise<Post>;
deletePost(bbsId: string, pstNo: string, pstPswd?: string): Promise<void>;
deletePost(bbsId: number, pstNo: number, delRsn?: string): Promise<void>;
}
@@ -19,13 +19,13 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
const { userInfo } = useUserStore((state) => state);
const { data: postInfo } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo],
queryFn: () => PostApi.getPostInfo(bbsId, pstNo),
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsId, pstNo],
queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)),
});
const { mutateAsync: updatePost } = useMutation({
mutationFn: (data: PostUpdateFormData) =>
PostApi.updatePost(bbsId, pstNo, data),
PostApi.updatePost(parseInt(bbsId), parseInt(pstNo), data),
onSuccess: () => {
alert("수정되었습니다.", router.back);
},
@@ -19,13 +19,13 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
const { userInfo } = useUserStore((state) => state);
const { data: postInfo } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo],
queryFn: () => PostApi.getPostInfo(bbsId, pstNo),
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsId, pstNo],
queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)),
});
const { mutateAsync: updatePost } = useMutation({
mutationFn: (data: PostUpdateFormData) =>
PostApi.updatePost(bbsId, pstNo, data),
PostApi.updatePost(parseInt(bbsId), parseInt(pstNo), data),
onSuccess: () => {
alert("수정되었습니다.", router.back);
},
@@ -19,13 +19,13 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
const { userInfo } = useUserStore((state) => state);
const { data: postInfo } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo],
queryFn: () => PostApi.getPostInfo(bbsId, pstNo),
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsId, pstNo],
queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)),
});
const { mutateAsync: updatePost } = useMutation({
mutationFn: (data: PostUpdateFormData) =>
PostApi.updatePost(bbsId, pstNo, data),
PostApi.updatePost(parseInt(bbsId), parseInt(pstNo), data),
onSuccess: () => {
alert("수정되었습니다.", router.back);
},
@@ -19,13 +19,13 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
const { userInfo } = useUserStore((state) => state);
const { data: postInfo } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo],
queryFn: () => PostApi.getPostInfo(bbsId, pstNo),
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsId, pstNo],
queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)),
});
const { mutateAsync: updatePost } = useMutation({
mutationFn: (data: PostUpdateFormData) =>
PostApi.updatePost(bbsId, pstNo, data),
PostApi.updatePost(parseInt(bbsId), parseInt(pstNo), data),
onSuccess: () => {
alert("수정되었습니다.", router.back);
},
+1 -1
View File
@@ -40,7 +40,7 @@ export const ENDPOINT = {
BOARD_SERVICE: {
BBS: "/board-service/api/v1/bbs",
BBS_TEMPLATES: "/board-service/api/v1/bbsTmplt",
POSTS_USER: "/board-service/api/v1/posts/user",
POSTS: "/board-service/api/v1/posts",
POSTS_COMMENTS: "/board-service/api/v1/posts-cmnts",
},
};
+3 -3
View File
@@ -7,18 +7,18 @@ export const usePrompt = () => {
const prompt = (message: string) => {
setMessage(message);
return new Promise<string>((resolve) => {
return new Promise<{ resolved: boolean; value?: string }>((resolve) => {
setResolve(() => resolve);
});
};
const onSubmit = (value: string) => {
resolve?.(value);
resolve?.({ resolved: true, value });
setMessage("");
};
const onCancel = () => {
resolve?.("");
resolve?.({ resolved: false });
setMessage("");
};
+1
View File
@@ -1 +1,2 @@
export { StoreProvider } from "./store-provider";
export { PromptProvider } from "./prompt-provider";
@@ -2,9 +2,15 @@ import { createContext, useState } from "react";
interface PromptContext {
message?: string;
resolve?: (value: string) => void;
resolve?: ({
resolved,
value,
}: {
resolved: boolean;
value?: string;
}) => void;
setMessage: (message: string) => void;
setResolve: (resolve: (value: string) => void) => void;
setResolve: (resolve: () => void) => void;
}
export const PromptContext = createContext<PromptContext>({
@@ -16,7 +22,10 @@ export const PromptContext = createContext<PromptContext>({
export const PromptProvider = ({ children }: { children: React.ReactNode }) => {
const [message, setMessage] = useState<string>();
const [resolve, setResolve] = useState<(value: string) => void>();
const [resolve, setResolve] =
useState<
({ resolved, value }: { resolved: boolean; value?: string }) => void
>();
return (
<PromptContext.Provider
@@ -56,7 +56,7 @@ function GridViewDataTable<T>({
key={row.id}
onClick={() => onRowClick?.(row.original)}
className={cn(
"rounded-md border border-border bg-background p-4 shadow-md",
"overflow-hidden rounded-md border border-border bg-background p-4 shadow-md",
onRowClick && "cursor-pointer hover:bg-accent",
)}
>
+45
View File
@@ -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="text"
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,12 +1,19 @@
import dayjs from "dayjs";
import Image from "next/image";
import type { ColumnDef } from "@tanstack/react-table";
import { Badge, Checkbox, Skeleton } from "@common/components";
import { Badge, Checkbox, Skeleton, cn } from "@common/components";
import { AttachmentApi } from "@/entities/attachment";
import type { Post } from "@/entities/post";
import { useQuery } from "@tanstack/react-query";
import { ENDPOINT } from "@/shared/config";
import { useMenuStore } from "@/shared/lib";
import type { BbsType } from "@/shared/store/menu-store";
declare module "@tanstack/react-table" {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface TableMeta<TData> {
bbsTypeCd?: BbsType;
}
}
export const createColumns = (
thumbRefs: React.MutableRefObject<Map<number, string>>,
@@ -23,73 +30,117 @@ export const createColumns = (
className="h-4 w-4 rounded border-gray-300"
/>
),
cell: ({ row }) => {
const { pstTtl, inqNocs } = row.original;
return (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2">
<Checkbox
checked={row.getIsSelected()}
onClick={(e) => e.stopPropagation()}
onCheckedChange={(checked) => row.toggleSelected(!!checked)}
className="h-4 w-4 rounded border-gray-300"
cell: ({ row, table }) => {
const { atflCd, pstNo, pstTtl, inqNocs, rgtrNm, regDt } = row.original;
const bbsTypeCd = table.options.meta?.bbsTypeCd;
if (bbsTypeCd === "BBSIMAGE") {
return (
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<Checkbox
checked={row.getIsSelected()}
onClick={(e) => e.stopPropagation()}
onCheckedChange={(checked) => row.toggleSelected(!!checked)}
className="h-4 w-4 rounded border-gray-300"
/>
{inqNocs >= 100 && <Badge color="red">HOT</Badge>}
{dayjs().diff(dayjs(row.original.regDt), "hour") < 24 && (
<Badge color="blue">NEW</Badge>
)}
<span className="text-[1.6rem] font-bold text-label">
{pstTtl}
</span>
</div>
<ThumbImage
atflCd={atflCd}
pstNo={pstNo}
thumbRefs={thumbRefs}
bbsTypeCd={bbsTypeCd}
/>
{inqNocs >= 100 && <Badge color="red">HOT</Badge>}
{dayjs().diff(dayjs(row.original.regDt), "hour") < 24 && (
<Badge color="blue">NEW</Badge>
)}
<div className="flex flex-col">
<span className="text-[1.4rem] font-bold text-label">
{rgtrNm}
</span>
<span className="text-[1.2rem] text-label">
{dayjs(regDt).format("YYYY-MM-DD")}
</span>
</div>
</div>
<span className="text-3xl font-bold leading-none text-label">
{pstTtl}
</span>
</div>
);
);
}
if (bbsTypeCd === "BBSMOVIE") {
return (
<div className="-m-4">
<ThumbImage
atflCd={atflCd}
pstNo={pstNo}
thumbRefs={thumbRefs}
bbsTypeCd={bbsTypeCd}
classes={{
image: "object-cover",
}}
/>
<div className="p-4">
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<Checkbox
checked={row.getIsSelected()}
onClick={(e) => e.stopPropagation()}
onCheckedChange={(checked) => row.toggleSelected(!!checked)}
className="h-4 w-4 rounded border-gray-300"
/>
{inqNocs >= 100 && <Badge color="red">HOT</Badge>}
{dayjs().diff(dayjs(regDt), "hour") < 24 && (
<Badge color="blue">NEW</Badge>
)}
<span className="text-[1.6rem] font-bold text-label">
{pstTtl}
</span>
</div>
<div className="flex flex-col">
<span className="text-[1.4rem] font-bold text-label">
{rgtrNm}
</span>
<span className="text-[1.2rem] text-label">
{dayjs(regDt).format("YYYY-MM-DD")}
</span>
</div>
</div>
</div>
</div>
);
}
},
},
{
id: "atflCd",
cell: ({ row }) => {
const { atflCd, pstNo } = row.original;
return <ThumbImage atflCd={atflCd} pstNo={pstNo} thumbRefs={thumbRefs} />;
},
},
{
id: "rgtrNm",
cell: ({ row }) => {
return (
<span className="text-[1.4rem] font-bold text-label">
{row.original.rgtrNm}
</span>
);
},
},
{
id: "regDt",
cell: ({ row }) => (
<span className="text-[1.2rem] text-label">
{dayjs(row.original.regDt).format("YYYY-MM-DD")}
</span>
),
},
];
type ThumbImageProps = {
atflCd: string;
pstNo: number;
thumbRefs: React.MutableRefObject<Map<number, string>>;
bbsTypeCd?: string;
classes?: {
image?: string;
};
};
const ThumbImage = ({ atflCd, pstNo, thumbRefs }: ThumbImageProps) => {
const ThumbImage = ({
atflCd,
pstNo,
thumbRefs,
bbsTypeCd,
classes,
}: ThumbImageProps) => {
const prevImage = thumbRefs.current.get(pstNo);
const { bbsInfo } = useMenuStore((state) => state);
const { data: imageUrl, isLoading } = useQuery({
queryKey: [ENDPOINT.ATTACH_SERVICE.IMAGES, atflCd],
queryFn: () => {
if (bbsInfo?.bbsTypeCd === "BBSIMAGE") {
if (bbsTypeCd === "BBSIMAGE") {
return AttachmentApi.getImageUrlByAtflCd(atflCd);
} else if (bbsInfo?.bbsTypeCd === "BBSMOVIE") {
} else if (bbsTypeCd === "BBSMOVIE") {
return AttachmentApi.getMovieThumbnailUrlByAtflCd(atflCd);
}
},
@@ -102,17 +153,17 @@ const ThumbImage = ({ atflCd, pstNo, thumbRefs }: ThumbImageProps) => {
if (isLoading)
return (
<div className="my-6 w-full">
<Skeleton className="mx-auto h-[16rem] w-full" />
<div className="h-[16rem] w-full">
<Skeleton className="h-full w-full" />
</div>
);
if (imageUrl)
return (
<div className="relative my-6 flex h-[16rem] w-full items-center justify-center">
<div className="relative flex h-[16rem] w-full items-center justify-center">
<Image
src={imageUrl}
className="object-contain"
className={cn("object-contain", classes?.image)}
fill
alt="썸네일 이미지"
/>
@@ -120,7 +171,7 @@ const ThumbImage = ({ atflCd, pstNo, thumbRefs }: ThumbImageProps) => {
);
return (
<div className="my-6 flex h-[16rem] w-full items-center justify-center rounded-sm border border-border bg-background">
<div className="flex h-[16rem] w-full items-center justify-center rounded-sm border border-border bg-background">
<i className="diveicon di-image text-3xl" />
</div>
);
@@ -34,7 +34,7 @@ export const columns: ColumnDef<Post & { isUpndFixPost?: boolean }>[] = [
return (
<Badge
color="orange"
className="flex size-10 items-center justify-center"
className="flex size-8 items-center justify-center"
>
<i className="diveicon di-volume-up-fill" />
</Badge>
+7 -7
View File
@@ -1,24 +1,24 @@
import { useRouter, usePathname } from "next/navigation";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { Button } from "@common/components";
import { useMenuStore, useUserStore } from "@/shared/lib";
import { useMenuStore, usePrompt, useUserStore } from "@/shared/lib";
type PostActionProps = {
bbsId: string;
pstNo: string;
children: React.ReactNode;
handleDelete: () => Promise<void>;
handleDelete: (delRsn?: string) => Promise<void>;
};
function PostAction({ children, handleDelete }: PostActionProps) {
const router = useRouter();
const pathname = usePathname();
const { confirm } = useConfirm();
const { prompt } = usePrompt();
const { bbsInfo } = useMenuStore((state) => state);
const { userInfo } = useUserStore((state) => state);
const onDelete = async () => {
if (await confirm("삭제하시겠습니까?")) handleDelete();
const { resolved, value } = await prompt("삭제 사유를 입력해주세요.");
if (resolved) handleDelete(value);
};
const checkAnsUseAuthrt = () => {
@@ -35,7 +35,7 @@ function PostAction({ children, handleDelete }: PostActionProps) {
};
return (
<div>
<>
{children}
<div className="flex justify-center gap-4 pb-12 pt-10">
<Button
@@ -68,7 +68,7 @@ function PostAction({ children, handleDelete }: PostActionProps) {
삭제
</Button>
</div>
</div>
</>
);
}
+2 -1
View File
@@ -89,7 +89,8 @@ function PostComment({ bbsId, pstNo }: { bbsId: number; pstNo: number }) {
if (userInfo?.userUnqId) {
cmntPswd = userInfo?.userUnqId;
} else {
cmntPswd = await prompt("비밀번호를 입력해주세요.");
const { resolved, value } = await prompt("비밀번호를 입력해주세요.");
if (resolved) cmntPswd = value ?? "";
}
return cmntPswd;
};
+17 -17
View File
@@ -2,7 +2,6 @@
import { useEffect, useRef, useState } from "react";
import { usePathname, useRouter } from "next/navigation";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
@@ -24,7 +23,7 @@ import {
} from "@common/components";
import { PostApi, type Post, type PostParams } from "@/entities/post";
import { ENDPOINT } from "@/shared/config";
import { useMenuStore, useUserStore } from "@/shared/lib";
import { useMenuStore, usePrompt } 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";
@@ -32,8 +31,7 @@ import { createColumns as createGridViewColumns } from "../model/grid-view-table
function PostPage({ bbsId }: { bbsId: string }) {
const router = useRouter();
const pathname = usePathname();
const { confirm } = useConfirm();
const { userInfo } = useUserStore((state) => state);
const { prompt } = usePrompt();
const { bbsInfo, pageMenuAuthority } = useMenuStore((state) => state);
const thumbRefs = useRef<Map<number, string>>(new Map());
@@ -48,9 +46,9 @@ function PostPage({ bbsId }: { bbsId: string }) {
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const { data: upndFixPostList } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, bbsInfo],
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsId, bbsInfo],
queryFn: () =>
PostApi.getPostList(bbsId, {
PostApi.getPostList(parseInt(bbsId), {
...postParams,
upndFixYn: bbsInfo?.upndFixUseYn,
}),
@@ -69,9 +67,9 @@ function PostPage({ bbsId }: { bbsId: string }) {
isFetched,
refetch,
} = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId],
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsId],
queryFn: () =>
PostApi.getPostList(bbsId, {
PostApi.getPostList(parseInt(bbsId), {
...postParams,
page: pagination.pageIndex,
size: pagination.pageSize,
@@ -94,6 +92,7 @@ function PostPage({ bbsId }: { bbsId: string }) {
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
meta: { bbsTypeCd: bbsInfo?.bbsTypeCd },
});
const DataTableComponent = isGridViewPage ? GridViewDataTable : DataTable;
@@ -103,15 +102,16 @@ function PostPage({ bbsId }: { bbsId: string }) {
.getFilteredSelectedRowModel()
.rows.map((row) => row.original.pstNo);
if (await confirm("삭제하시겠습니까?")) {
setRowSelection({});
await Promise.all(
selectedPosts.map((pstNo) =>
PostApi.deletePost(bbsId, pstNo.toString(), userInfo?.userUnqId),
),
);
refetch();
}
const { resolved, value: delRsn } =
await prompt("삭제 사유를 입력해주세요.");
if (!resolved) return;
setRowSelection({});
await Promise.all(
selectedPosts.map((pstNo) =>
PostApi.deletePost(parseInt(bbsId), pstNo, delRsn),
),
);
refetch();
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
+7 -23
View File
@@ -8,28 +8,21 @@ 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,
useMenuStore,
usePrompt,
} from "@/shared/lib";
import { useAlertStore, useMenuStore } 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 { bbsInfo } = useMenuStore((state) => state);
const [filename, setFilename] = useState("");
const { data: postData } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo],
queryFn: () => PostApi.getPostInfo(bbsId, pstNo),
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsId, pstNo],
queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)),
});
const { data: downloadResponse, isFetching } = useQuery({
@@ -65,23 +58,14 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
enabled: !!videoAttachment?.atflId,
});
const { mutate: deletePost } = useMutation({
mutationFn: (pstPswd: string) => PostApi.deletePost(bbsId, pstNo, pstPswd),
const { mutateAsync: deletePost } = useMutation({
mutationFn: (delRsn?: string) =>
PostApi.deletePost(parseInt(bbsId), parseInt(pstNo), delRsn),
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 {
@@ -123,7 +107,7 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
return (
<>
<PostAction bbsId={bbsId} pstNo={pstNo} handleDelete={handleDelete}>
<PostAction bbsId={bbsId} pstNo={pstNo} handleDelete={deletePost}>
<div className="card card-border !p-8">
<h3 className="text-[2rem] font-bold text-foreground">
{postData?.pstTtl}