Skip to content

Commit

Permalink
Refactor:이미지업로드 실패시 작성된 게시물을 삭제(롤백), Mutation으로 분리 (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
jobkaeHenry authored Nov 12, 2023
1 parent 8c87ef8 commit b7e6500
Show file tree
Hide file tree
Showing 12 changed files with 293 additions and 90 deletions.
121 changes: 66 additions & 55 deletions client/src/app/(protectedRoute)/new-post/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,78 +13,37 @@ import {
Tooltip,
Typography,
} from "@mui/material";

import GoBackIcon from "@/assets/icons/GoBackIcon.svg";
import InputSearchIcon from "@/assets/icons/InputSearchIcon.svg";
import AlcholeSearchIcon from "@/assets/icons/AlcholeSearchIcon.svg";
import { useRouter } from "next/navigation";
import { ChangeEvent, useEffect, useMemo, useState } from "react";
import axios from "@/libs/axios";
import { ChangeEvent, useEffect, useState } from "react";
import HOME from "@/const/clientPath";
import CameraIcon from "@/assets/icons/CameraIcon.svg";
import PinIcon from "@/assets/icons/PinIcon.svg";
import getTokenFromLocalStorage from "@/utils/getTokenFromLocalStorage";
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";

export default function NewpostPage() {
const { setLoading } = useGlobalLoadingStore();
const router = useRouter();
const invalidatePreviousPost = useInvalidatePostList();

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

const changeHadler = ({
target,
}: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setFormValue((prev) => ({ ...prev, [target.name]: target.value }));
};
const { setLoading } = useGlobalLoadingStore();
const token = getTokenFromLocalStorage();

const router = useRouter();

const submitHandler = () => {
let userId;
let pk;
setLoading(true);
axios
.get("/user/me", { headers: { Authorization: token } })
.then((res) => {
userId = res.data.id;
})
.then(() => {
axios
.post(
"/posts",
{ ...formValue },
{ headers: { Authorization: token } }
)
.then(({ data }) => {
pk = data.postNo;
const formData = new FormData();
if (file) {
formData.append("image", file);
axios.post(`/attach/resources/POST/${pk}`, formData, {
headers: {
Authorization: token,
"Content-Type": "multipart/form-data",
},
transformRequest: [
function () {
return formData;
},
],
});
}
setLoading(false);
router.push(HOME);
});
});
};
const [userTypedTag, setUserTypedTag] = useState<string>("");
const [file, setFile] = useState<File>();
const [fileUrl, setFileUrl] = useState<string | ArrayBuffer | null>();

const [isSuccess, SetIsSuccess] = useState(false);
useEffect(() => {
if (!file) {
return;
Expand All @@ -94,6 +53,42 @@ export default function NewpostPage() {
reader.onloadend = () => setFileUrl(reader.result);
}, [file]);

const changeHadler = ({
target,
}: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setFormValue((prev) => ({ ...prev, [target.name]: target.value }));
};

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

const submitHandler = async () => {
setLoading(true);
let postNo;
try {
const { postNo: res } = await newPostHandler(formValue);
postNo = res;
if (file) {
try {
await attachFileHandler({
file,
url: { pk: postNo, type: "POST" },
});
} catch {
deletePostHandler(postNo);
return;
}
}
invalidatePreviousPost();
SetIsSuccess(true);
router.push(HOME);
} catch {
return;
} finally {
setLoading(false);
}
};
return (
<Paper>
{/* 최상단 앱바 */}
Expand All @@ -105,7 +100,12 @@ export default function NewpostPage() {
<Typography variant="subtitle2" fontWeight={"bold"}>
포스팅
</Typography>
<Button onClick={submitHandler} variant="text" sx={{ minWidth: 40 }}>
<Button
disabled={isSuccess}
onClick={submitHandler}
variant="text"
sx={{ minWidth: 40 }}
>
공유
</Button>
</Toolbar>
Expand Down Expand Up @@ -154,15 +154,22 @@ export default function NewpostPage() {
</Typography>
<Box sx={{ display: "flex", gap: 1 }}>
{formValue.tagList.map((tag) => {
return <Typography variant="label">#{tag}</Typography>;
return (
<Typography variant="label" key={tag}>
#{tag}
</Typography>
);
})}
</Box>
<Box
component="form"
onSubmit={(e) => {
e.preventDefault();
setFormValue((prev) => {
if (!userTypedTag) return prev;
if (!userTypedTag || prev.tagList.includes(userTypedTag)) {
setUserTypedTag("");
return prev;
}
return { ...prev, tagList: [...prev.tagList, userTypedTag] };
});
setUserTypedTag("");
Expand All @@ -177,6 +184,7 @@ export default function NewpostPage() {
/>
<Button type="submit">태그 추가</Button>
</Box>
{/* 파일 미리보기 */}
{fileUrl && (
<Box
sx={{
Expand All @@ -192,7 +200,9 @@ export default function NewpostPage() {
}}
/>
)}
{/* 버튼 그룹 */}
<Box sx={{ display: "flex", gap: 2 }}>
{/* 사진 */}
<Tooltip title="사진 첨부">
<ButtonBase
component={"label"}
Expand Down Expand Up @@ -222,6 +232,7 @@ export default function NewpostPage() {
/>
</ButtonBase>
</Tooltip>
{/* 위치 */}
<Tooltip title="위치 추가">
<ButtonBase
component={"label"}
Expand Down
37 changes: 9 additions & 28 deletions client/src/app/error.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,10 @@
'use client'

import { useEffect } from 'react'

export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
"use client";

import ErrorPage from "@/components/ErrorPage";

export default function Error(props: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Log the error to an error reporting service
console.error(error)
}, [error])

return (
<div>
<h2>Something went wrong!</h2>
<button
onClick={
() => reset()
}
>
Try again
</button>
</div>
)
}
return <ErrorPage {...props} />;
}
2 changes: 1 addition & 1 deletion client/src/components/DevelopingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const DevelopingPage = () => {
height: "calc(100vh - 56px)",
}}
>
<Image src={DevelopingNoticeImgae} alt="개발중 알림" />
<Image priority src={DevelopingNoticeImgae} alt="개발중 알림" />
</Paper>
);
};
Expand Down
36 changes: 36 additions & 0 deletions client/src/components/ErrorPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Button, Paper } from "@mui/material";

import hasErrorPage from "@/assets/images/hasError.png";
import Image from "next/image";
import { useEffect } from "react";
import errorHandler from "@/utils/errorHandler";

const ErrorPage = ({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) => {
useEffect(() => {
errorHandler(JSON.stringify(error));
}, [error]);

return (
<Paper
sx={{
display: "flex",
justifyContent: "center",
flexDirection: "column",
alignItems: "center",
height: "calc(100vh - 56px)",
gap:2
}}
>
<Image priority src={hasErrorPage} alt="에러임을 알림" />
<Button onClick={reset}>다시 시도</Button>
</Paper>
);
};

export default ErrorPage;
27 changes: 23 additions & 4 deletions client/src/const/serverPath.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,37 @@
/**
* 로그인 API Path
*/
export const LOGIN_API_PATH = '/user/login' as const
export const LOGIN_API_PATH = "/user/login" as const;

/**
* 회원가입 API Path
*/
export const SIGNUP_API_PATH = '/user/signup' as const
export const SIGNUP_API_PATH = "/user/signup" as const;
/**
* 내 정보를 받아오는 Path
*/
export const MY_INFO = '/user/me' as const
export const MY_INFO = "/user/me" as const;

/**
* 쿠키를 심어주는 로그인 BFF
*/
export const LOGIN_BFF = '/api/auth/login' as const
export const LOGIN_BFF = "/api/auth/login" as const;

/**
* 게시물리스트를 받아오거나, 작성하는 Path
*/
export const POST_LIST = "/posts" as const;
/**
* ID(pk) 를 입력받아 해당 포스트를 지우는 URL
*/
export const REMOVE_POST = (pk:number)=>`${POST_LIST}/${pk}` as const

/**
*
* @param type : 리소스의 타입 POST|PROFILE|ALCOHOL
* @param resourcePk 등록하고자하는 게시글의 PK
* @returns
*/
export type ATTACH_FILE_ResourceType = "POST" | "PROFILE" | "ALCOHOL";
export const ATTACH_FILE = (type: ATTACH_FILE_ResourceType, resourcePk: number) =>
`/attach/resources/${type}/${resourcePk}` as const;
4 changes: 3 additions & 1 deletion client/src/libs/axios.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import getTokenFromLocalStorage from "@/utils/getTokenFromLocalStorage";
import axios from "axios";

axios.defaults.xsrfCookieName = "csrftoken";
Expand All @@ -7,10 +8,11 @@ axios.defaults.xsrfHeaderName = "x-CSRFToken";
* 쿠키를 싣고가는 요청
*/
export const axiosPrivate = axios.create({
baseURL: process.env.NEXT_PUBLIC_BASE_URL,
headers: {
"Content-Type": "application/json",
Authorization: getTokenFromLocalStorage(),
},
withCredentials: true,
});

/**
Expand Down
46 changes: 46 additions & 0 deletions client/src/queries/attach/useNewAttachMutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useMutation } from "@tanstack/react-query";
import { axiosPrivate } from "@/libs/axios";
import { ATTACH_FILE, ATTACH_FILE_ResourceType } from "@/const/serverPath";

export const useNewAttachMutation = () => {
return useMutation({
mutationFn: async (param: { file: File; url: NewAttatchRequestUrl }) =>
await postImageFn(param.file, param.url),
});
};

interface NewAttatchRequestUrl {
type: ATTACH_FILE_ResourceType;
pk: number;
}
/**
* [Post] 파일을 업로드하는 axios요청
* @param file 파일
* @param param1 {type: "POST" | "PROFILE" | "ALCOHOL", pk : 게시글 PK}
* @returns 에셋 PK
*/
export const postImageFn = async (
file: File,
{ type, pk }: NewAttatchRequestUrl
) => {
const formData = new FormData();
formData.append("image", file);

const { data } = await axiosPrivate.post<{ attachNo: number }>(
ATTACH_FILE(type, pk),
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
transformRequest: [
function () {
return formData;
},
],
}
);
return data;
};

export default useNewAttachMutation;
Loading

0 comments on commit b7e6500

Please sign in to comment.