Skip to content

Commit

Permalink
New : 게시글 수정하기 기능 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
jobkaeHenry committed Dec 14, 2023
1 parent 9680cfa commit 1006b77
Show file tree
Hide file tree
Showing 11 changed files with 431 additions and 107 deletions.
24 changes: 24 additions & 0 deletions client/src/app/(protectedRoute)/edit-post/[pid]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import CustomAppbar from "@/components/layout/CustomAppbar";
import { ReactNode, Suspense } from "react";
import CustomContainer from "@/components/layout/CustomContainer";

type Props = {
children: ReactNode;
};

const layout = ({ children }: Props) => {
return (
<Suspense
fallback={
<>
<CustomAppbar />
<CustomContainer />
</>
}
>
{children}
</Suspense>
);
};

export default layout;
70 changes: 70 additions & 0 deletions client/src/app/(protectedRoute)/edit-post/[pid]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"use client";

import { useRouter } from "next/navigation";
import { useState } from "react";
import HOME from "@/const/clientPath";
import { useGlobalLoadingStore } from "@/store/useGlobalLoadingStore";
import { useInvalidatePostList } from "@/queries/post/useGetPostListInfiniteQuery";
import { NewPostRequestInterface } from "@/types/newPost/NewPostInterface";

import CustomAppbar from "@/components/layout/CustomAppbar";
import CustomContainer from "@/components/layout/CustomContainer";
import useSubmitEditPostMutation from "@/queries/newPost/useSubmitEditPostMutation";
import PostEditor from "@/components/newpost/PostEditor";
import useGetPostDetailQuery, { useInvalidatePostDetail } from "@/queries/post/useGetPostDetailQuery";

export default function EditPostPage({ params }: { params: { pid: string } }) {
const { setLoading } = useGlobalLoadingStore();

const router = useRouter();
const invalidatePreviousPost = useInvalidatePostList();
const invalidatePostDetail = useInvalidatePostDetail()

const { data: initialData } = useGetPostDetailQuery(params.pid);
const { alcoholName, alcoholNo, alcoholType, postContent, postAttachUrls,postNo } =
initialData;

const [formData, setFormData] = useState<NewPostRequestInterface>();
const [file, setFile] = useState<File>();

const { mutateAsync: submitHandler, isSuccess } = useSubmitEditPostMutation({
onMutate: () => setLoading(true),
onSuccess: () => {
invalidatePreviousPost();
invalidatePostDetail(String(postNo))
router.push(HOME);
},
onSettled: () => setLoading(false),
});

return (
<>
{/* 최상단 앱바 */}
<CustomAppbar
title="포스팅"
appendButton="공유"
disableAppend={isSuccess}
onClickAppend={() =>
formData &&
submitHandler({
postNo: String(initialData.postNo),
formData,
file,
prevFileNo: initialData.postAttachUrls?.[0]?.attachNo,
})
}
/>
<CustomContainer>
<PostEditor
onFormChange={(formData) => setFormData(formData)}
onFileChange={(file) => setFile(file)}
initialAlcohol={
alcoholName ? { alcoholName, alcoholNo, alcoholType } : undefined
}
initialContent={postContent}
initialImage={(postAttachUrls ?? [])[0]?.attachUrl}
/>
</CustomContainer>
</>
);
}
117 changes: 18 additions & 99 deletions client/src/app/(protectedRoute)/new-post/page.tsx
Original file line number Diff line number Diff line change
@@ -1,128 +1,47 @@
"use client";

import { Box, Container, Paper, Tooltip } from "@mui/material";

import { useRouter } from "next/navigation";
import { useCallback, useState } from "react";
import { useState } from "react";
import HOME from "@/const/clientPath";
import PictureIcon from "@/assets/icons/PictureIcon.svg";
import PinIcon from "@/assets/icons/PinIcon.svg";
import { useGlobalLoadingStore } from "@/store/useGlobalLoadingStore";
import useNewPostMutation from "@/queries/newPost/useNewPostMutation";
import useNewAttachMutation from "@/queries/attach/useNewAttachMutation";
import { useInvalidatePostList } from "@/queries/post/useGetPostListInfiniteQuery";
import { useDeletePostMutation } from "@/queries/post/useDeletePostMutation";
import {
NewPostRequestInterface,
NewPostRequestAlCohol,
} from "@/types/newPost/NewPostInterface";
import SearchAlcoholInput from "@/components/newpost/SearchAlcoholInput";
import { NewPostRequestInterface } from "@/types/newPost/NewPostInterface";

import CustomAppbar from "@/components/layout/CustomAppbar";
import SquareIconButton from "@/components/SquareIconButton";
import PreviewImageByURL from "@/components/PreviewImageByURL";
import NewPostTextEditor from "@/components/newpost/NewPostTextEditor";
import useRenderAsDataUrl from "@/hooks/useRenderAsDataUrl";
import SingleImageInput from "@/components/SingleImageInput";
import { POST_IMAGE_SIZE } from "@/const/imageSize";
import CustomContainer from "@/components/layout/CustomContainer";
import useSubmitPostMutation from "@/queries/newPost/useSubmitPostMutation";
import PostEditor from "@/components/newpost/PostEditor";

export default function NewpostPage() {
const { setLoading } = useGlobalLoadingStore();

const router = useRouter();
const invalidatePreviousPost = useInvalidatePostList();

const [formValue, setFormValue] = useState<NewPostRequestInterface>({
postContent: "",
postType: "BASIC",
positionInfo: "",
tagList: [] as string[],
});

const [alcoholNo, setAlcoholNo] =
useState<NewPostRequestAlCohol["alcoholNo"]>();

const [formValue, setFormValue] = useState<NewPostRequestInterface>();
const [file, setFile] = useState<File>();
const fileUrl = useRenderAsDataUrl(file);

const [isSuccess, SetIsSuccess] = useState(false);

const { mutateAsync: newPostHandler } = useNewPostMutation();
const { mutateAsync: attachFileHandler } = useNewAttachMutation();
const { mutateAsync: deletePostHandler } = useDeletePostMutation();

const submitHandler = useCallback(
async (formValue: NewPostRequestInterface, file?: File) => {
setLoading(true);
let postNo;
try {
const { postNo: res } = await newPostHandler(formValue);
postNo = res;
if (file) {
try {
await attachFileHandler({
file,
url: { pk: postNo, type: "POST" },
size: POST_IMAGE_SIZE,
});
} catch {
deletePostHandler(postNo);
return;
}
}
invalidatePreviousPost();
SetIsSuccess(true);
router.push(HOME);
} catch {
return;
} finally {
setLoading(false);
}
const { mutateAsync: submitHandler, isSuccess } = useSubmitPostMutation({
onMutate: () => setLoading(true),
onSuccess: () => {
invalidatePreviousPost();
router.push(HOME);
},
[router]
);
onSettled: () => setLoading(false),
});

return (
<Paper>
<>
{/* 최상단 앱바 */}
<CustomAppbar
title="포스팅"
appendButton="공유"
disableAppend={isSuccess}
onClickAppend={() => submitHandler({ ...formValue, alcoholNo }, file)}
onClickAppend={() => formValue && submitHandler({ formValue, file })}
/>

<CustomContainer>
{/* 검색창 */}
<SearchAlcoholInput setAlcoholNo={setAlcoholNo} />
{/* 내용 */}
<NewPostTextEditor
onContentChange={({ content, tagList }) =>
setFormValue((prev) => ({
...prev,
postContent: content,
tagList,
}))
}
/>
{/* 파일 미리보기 */}
{fileUrl && <PreviewImageByURL fileUrl={fileUrl} />}
{/* 버튼 그룹 */}
<Box sx={{ display: "flex", gap: 2 }}>
{/* 사진 */}
<Tooltip title="사진 첨부">
<SquareIconButton
component={"label"}
iconComponent={<PictureIcon />}
>
<SingleImageInput onChange={(file) => setFile(file)} />
</SquareIconButton>
</Tooltip>
{/* 위치 */}
<Tooltip title="위치 추가">
<SquareIconButton iconComponent={<PinIcon />} />
</Tooltip>
</Box>
<PostEditor onFormChange={setFormValue} onFileChange={setFile} />
</CustomContainer>
</Paper>
</>
);
}
100 changes: 100 additions & 0 deletions client/src/components/newpost/PostEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { Box, Tooltip } from "@mui/material";
import SquareIconButton from "@/components/SquareIconButton";
import PreviewImageByURL from "@/components/PreviewImageByURL";
import NewPostTextEditor from "@/components/newpost/NewPostTextEditor";
import SearchAlcoholInput from "@/components/newpost/SearchAlcoholInput";
import PictureIcon from "@/assets/icons/PictureIcon.svg";
import PinIcon from "@/assets/icons/PinIcon.svg";
import SingleImageInput from "@/components/SingleImageInput";
import { useEffect, useState } from "react";
import useRenderAsDataUrl from "@/hooks/useRenderAsDataUrl";
import { NewPostRequestInterface } from "@/types/newPost/NewPostInterface";
import { AlcoholDetailInterface } from "@/types/alcohol/AlcoholInterface";

interface PostEditorProps {
initialContent?: string;
initialAlcohol?: Pick<
AlcoholDetailInterface,
"alcoholName" | "alcoholNo" | "alcoholType"
>;
initialImage?: string;
/**
* formvalue와 file 을 인자로 넘겨주는 커스텀이벤트핸들러
* @param props {formvalue,file}
* @returns
*/
onFormChange: (formValue: NewPostRequestInterface) => void;
onFileChange: (file?: File) => void;
}

const PostEditor = ({
initialContent,
initialAlcohol,
initialImage,
onFormChange,
onFileChange,
}: PostEditorProps) => {
const [formValue, setFormValue] = useState<NewPostRequestInterface>({
postType: "BASIC",
positionInfo: "",
tagList: [] as string[],
});

useEffect(() => {
onFormChange(formValue);
}, [formValue]);

const [file, setFile] = useState<File>();

useEffect(() => {
onFileChange(file);
}, [file]);

const fileUrl = useRenderAsDataUrl(file) ?? initialImage;

return (
<>
{/* 검색창 */}
<SearchAlcoholInput
initialValue={initialAlcohol}
setAlcoholNo={(alcoholNo) =>
setFormValue((prev) => ({ ...prev, alcoholNo }))
}
/>
{/* 내용 */}
<NewPostTextEditor
onContentChange={({ content, tagList }) =>
setFormValue((prev) => ({
...prev,
postContent: content,
tagList,
}))
}
initialValue={initialContent}
/>
{/* 파일 미리보기 */}
{(fileUrl) && (
<PreviewImageByURL fileUrl={fileUrl} />
)}
{/* 버튼 그룹 */}
<Box sx={{ display: "flex", gap: 2 }}>
{/* 사진 */}
<Tooltip title="사진 첨부">
<SquareIconButton component={"label"} iconComponent={<PictureIcon />}>
<SingleImageInput
onChange={(file) => {
setFile(file);
}}
/>
</SquareIconButton>
</Tooltip>
{/* 위치 */}
<Tooltip title="위치 추가">
<SquareIconButton iconComponent={<PinIcon />} />
</Tooltip>
</Box>
</>
);
};

export default PostEditor;
11 changes: 6 additions & 5 deletions client/src/components/post/PostCardOptionDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import React, { useState } from "react";
import { MoreVertOutlined } from "@mui/icons-material";
import { ButtonBase, Menu, MenuItem } from "@mui/material";
import { useDeletePostMutation } from "@/queries/post/useDeletePostMutation";
import useDeleteAttachMutation from "@/queries/attach/useDeleteAttachMutation";
import { useRouter } from "next/navigation";
import HOME from "@/const/clientPath";
import HOME, { EDIT_POST } from "@/const/clientPath";
import DeleteEditDropdown from "../DeleteEditDropdown";

type PostCardOptionDropdownProps = {
Expand All @@ -17,6 +14,7 @@ const PostCardOptionDropdown = ({
filePk,
}: PostCardOptionDropdownProps) => {
const router = useRouter();

const { mutateAsync: deletePost } = useDeletePostMutation();
const { mutateAsync: deleteFile } = useDeleteAttachMutation();

Expand All @@ -27,8 +25,11 @@ const PostCardOptionDropdown = ({
router.push(HOME);
}
};
const editHandler = () => {
router.push(EDIT_POST(String(postId)));
};

return <DeleteEditDropdown onDelete={deleteHandler} />;
return <DeleteEditDropdown onDelete={deleteHandler} onEdit={editHandler}/>;
};

export default PostCardOptionDropdown;
Loading

0 comments on commit 1006b77

Please sign in to comment.