Skip to content

Commit

Permalink
게시글-수정기능-추가 (#87)
Browse files Browse the repository at this point in the history
* Minor : 파일 확장자 변경

* Refactor : 게시글 최대길이가 넘어갈 경우 입력되지 않도록함

* Minor : 파일 미리보기 AspectRatio 적용

* Refactor : IntialValue 를 받도록 변경

* New : 게시글 수정하기 기능 추가

* FIX : 테스트 엣지케이스 추가
  • Loading branch information
jobkaeHenry authored Dec 14, 2023
1 parent de5b272 commit 413e354
Show file tree
Hide file tree
Showing 16 changed files with 476 additions and 120 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>
</>
);
}
2 changes: 1 addition & 1 deletion client/src/components/PreviewImageByURL.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ const PreviewImageByURL = (
sx={{
backgroundImage: `url(${fileUrl})`,
width: "100%",
height: 142,
borderRadius: 4,
border: "1px solid",
borderColor: "gray.secondary",
backgroundRepeat: "no-repeat",
backgroundSize: "cover",
backgroundPosition: "center",
aspectRatio: 2.36,
...sx,
}}
ref={ref}
Expand Down
26 changes: 21 additions & 5 deletions client/src/components/newpost/NewPostTextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,20 @@ import { sanitize } from "isomorphic-dompurify";

interface NewPostTextEditorInterface {
onContentChange: (props: { content: string; tagList: string[] }) => void;
maxLength?:number
initialValue?: string;
maxLength?: number;
}

const NewPostTextEditor = ({ onContentChange,maxLength=200 }: NewPostTextEditorInterface) => {
const NewPostTextEditor = ({
onContentChange,
maxLength = 200,
initialValue,
}: NewPostTextEditorInterface) => {
const [_mentioningValue, setMentioningValue] = useState("");

const [tagList, setTagList] = useState<string[]>([]);

const [content, setContent] = useState("");
const [content, setContent] = useState(initialValue ?? "");
const [textLength, setTextLength] = useState(0);

useEffect(() => {
Expand Down Expand Up @@ -60,6 +65,7 @@ const NewPostTextEditor = ({ onContentChange,maxLength=200 }: NewPostTextEditorI
modules={modules}
placeholder="입력해주세요"
onChange={(content, _d, _s, editor) => {
const textLength = editor.getLength() - 1;
const parsedTags = editor
.getContents()
.filter((op) => op.insert?.mention?.value)
Expand All @@ -68,8 +74,18 @@ const NewPostTextEditor = ({ onContentChange,maxLength=200 }: NewPostTextEditorI
[]
);
setTagList(parsedTags);
setContent(content);
setTextLength(editor.getLength() - 1);
setTextLength((prev) => {
if (textLength < maxLength) {
return textLength;
}
return prev;
});
setContent((prev) => {
if (textLength < maxLength) {
return content;
}
return prev;
});
}}
value={content}
/>
Expand Down
Loading

0 comments on commit 413e354

Please sign in to comment.