Skip to content

Commit

Permalink
Merge pull request #376 from oduck-team/refactor/375
Browse files Browse the repository at this point in the history
refactor: 프로필 페이지 코드 리팩토링
  • Loading branch information
presentKey authored Apr 30, 2024
2 parents 5c07cab + f46eb0f commit 41da3d7
Show file tree
Hide file tree
Showing 56 changed files with 1,167 additions and 902 deletions.
2 changes: 1 addition & 1 deletion src/components/Backdrop/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const variants: Variants = {
export interface BackdropProps {
isVisible?: boolean;
className?: string;
onClick: () => void;
onClick: (() => void) | ((e: React.MouseEvent) => void);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/components/Modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export interface ModalProps {
size?: Size;
showBackdrop?: boolean;
overflowY?: OverflowY;
onClose: () => void;
onClose: (() => void) | ((e: React.MouseEvent) => void);
}

/** @desc < AnimatePresence > 컴포넌트로 감싸서 사용해주세요. */
Expand Down
40 changes: 40 additions & 0 deletions src/features/users/hooks/useBookmarkDelete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { AxiosError } from "axios";

import useToast from "@/components/Toast/useToast";
import useToggleBookmark from "@/features/bookmarks/hooks/useToggleBookmark";
import useDebounce from "@/hooks/useDebounce";
import { useCommonToastError } from "@/libs/error";

export default function useBookmarkDelete(animeId: number, title: string) {
const { toastAuthError, toastDefaultError } = useCommonToastError();
const toast = useToast();
const deleteBookmark = useToggleBookmark(animeId);

const handleDeleteButtonClick = useDebounce(() => {
deleteBookmark.mutate(undefined, {
onSuccess: () => {
toast.success({
message: `${title} 탈덕 했어요.`,
buttonText: "탈덕 취소하기",
onClickButton: () => deleteBookmark.mutate(),
duration: 4,
});
},
onError: (error) => {
if (error instanceof AxiosError && error.response?.status) {
const status = error.response.status;
switch (status) {
case 401:
toastAuthError();
break;
default:
toastDefaultError();
break;
}
}
},
});
}, 200);

return { handleDeleteButtonClick };
}
16 changes: 16 additions & 0 deletions src/features/users/hooks/useCroppedImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useMemo, useState } from "react";

export default function useCroppedImage(): [
File | null,
React.Dispatch<React.SetStateAction<File | null>>,
string | null,
] {
const [croppedImage, setCroppedImage] = useState<File | null>(null);

const previewImage = useMemo(
() => croppedImage && URL.createObjectURL(croppedImage),
[croppedImage],
);

return [croppedImage, setCroppedImage, previewImage];
}
121 changes: 30 additions & 91 deletions src/features/users/hooks/useEditForm.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { AxiosError } from "axios";
import { useEffect, useMemo, useState } from "react";
import { useEffect, useState } from "react";

import useToast from "@/components/Toast/useToast";
import useAuth from "@/features/auth/hooks/useAuth";
import { useApi } from "@/hooks/useApi";
import { fileToWebPFile } from "@/libs/compressor";
import { useCommonToastError } from "@/libs/error";

import useMutationUpdateProfile from "./useMutationUpdateProfile";

export interface ProfileEditFormData {
name: string;
Expand All @@ -15,17 +13,15 @@ export interface ProfileEditFormData {
thumbnail?: string;
}

interface UpdateProfileMutateVariables {
backgroundImagePath: string | undefined;
thumbnailImagePath: string | undefined;
}
export default function useEditForm(
name: string,
description: string,
croppedArtImage: File | null,
croppedThumbnailImage: File | null,
) {
const { fileApi } = useApi();
const { user } = useAuth();

export default function useEditForm(name: string, description: string) {
const { profile, fileApi } = useApi();
const { fetchUser, user } = useAuth();
const [croppedArtImage, setCroppedArtImage] = useState<File | null>(null); // crop 배경 이미지
const [croppedThumbnailImage, setCroppedThumbnailImage] =
useState<File | null>(null); // crop 썸네일 이미지
const [form, setForm] = useState<ProfileEditFormData>({
name: name.length <= 10 ? name : "",
description: description,
Expand All @@ -34,27 +30,19 @@ export default function useEditForm(name: string, description: string) {
const [isFormChange, setIsFormChange] = useState(false);
const [isLoading, setIsLoading] = useState(false); // submit loading 상태

const updateProfile = useMutation(
({
backgroundImagePath,
thumbnailImagePath,
}: UpdateProfileMutateVariables) =>
profile.updateProfile(form, backgroundImagePath, thumbnailImagePath),
);
const queryClient = useQueryClient();

const toast = useToast();
const { toastAuthError, toastDefaultError } = useCommonToastError();
const updateProfile = useMutationUpdateProfile(form);

const previewArt = useMemo(
() => croppedArtImage && URL.createObjectURL(croppedArtImage),
[croppedArtImage],
);
const getImagePath = async (croppedImage: File | null, filename: string) => {
if (croppedImage && user) {
const webPFile = await fileToWebPFile(croppedImage);

const previewThumbNail = useMemo(
() => croppedThumbnailImage && URL.createObjectURL(croppedThumbnailImage),
[croppedThumbnailImage],
);
return await fileApi.uploadImage({
path: `user/${user.memberId}`,
filename: filename,
file: webPFile,
});
}
};

const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
Expand All @@ -77,7 +65,6 @@ export default function useEditForm(name: string, description: string) {

const handleFormSumbit = async (e: React.FormEvent) => {
e.preventDefault();

if (!isFormChange || !user) return;
setIsLoading(true);

Expand All @@ -88,65 +75,21 @@ export default function useEditForm(name: string, description: string) {
"한글, 영문, 숫자만 입력 가능합니다. 한글 또는 영문은 반드시 포함하여 2자~10자 닉네임을 설정해주세요.",
});
setIsLoading(false);

return;
}

let backgroundImagePath, thumbnailImagePath;
if (croppedArtImage) {
const webPFile = await fileToWebPFile(croppedArtImage);

backgroundImagePath = await fileApi.uploadImage({
path: `user/${user.memberId}`,
filename: "backgroundImage",
file: webPFile,
});
}

if (croppedThumbnailImage) {
const webPFile = await fileToWebPFile(croppedThumbnailImage);

thumbnailImagePath = await fileApi.uploadImage({
path: `user/${user.memberId}`,
filename: "thumbnailImage",
file: webPFile,
});
}
const backgroundImagePath = await getImagePath(
croppedArtImage,
"backgroundImage",
);
const thumbnailImagePath = await getImagePath(
croppedThumbnailImage,
"thumbnailImage",
);

updateProfile.mutate(
{ backgroundImagePath, thumbnailImagePath },
{
onSuccess: async () => {
await queryClient.invalidateQueries(["profile", user?.name]);
await fetchUser();
queryClient.removeQueries(["profile", "edit", user?.name]);

const path =
process.env.NODE_ENV === "production"
? "https://oduck.io/profile"
: "http://localhost:5173/profile";

window.location.href = path;
},
onError: (error) => {
if (error instanceof AxiosError && error.response?.status) {
const status = error.response.status;
switch (status) {
case 401: // 인증 오류
toastAuthError();
break;
case 400: // 정규식 검사 오류
toast.error({ message: "사용할 수 없는 닉네임입니다." });
break;
case 409: // 닉네임 중복 오류
toast.error({ message: "이미 사용중인 닉네임입니다." });
break;
default:
toastDefaultError();
break;
}
}
},
onSettled: () => setIsLoading(false),
},
);
Expand All @@ -164,12 +107,8 @@ export default function useEditForm(name: string, description: string) {
status,
isFormChange,
isLoading,
previewArt,
previewThumbNail,
handleInputChange,
handleFormSumbit,
setCroppedArtImage,
setCroppedThumbnailImage,
};
}

Expand Down
14 changes: 14 additions & 0 deletions src/features/users/hooks/useFetchEditProfil.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useQuery } from "@tanstack/react-query";

import useAuth from "@/features/auth/hooks/useAuth";
import { useApi } from "@/hooks/useApi";

export default function useFetchEditProfil() {
const { user } = useAuth();
const { profile } = useApi();
const query = useQuery(["profile", "edit", user?.name], () =>
profile.getProfile(user?.name),
);

return query;
}
28 changes: 28 additions & 0 deletions src/features/users/hooks/useFetchInfiniteBookmark.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useInfiniteQuery } from "@tanstack/react-query";

import { useApi } from "@/hooks/useApi";

import { SelectedSort } from "./useSortBar";
import { MENU } from "./useTabMenu";

export default function useFetchInfiniteBookmark(
memberId: number,
selectedSort: SelectedSort,
selectedMenu: MENU,
) {
const { profile } = useApi();
const query = useInfiniteQuery(
["profile", memberId, "bookmark", selectedSort.id, selectedSort.isDESC],
({ pageParam }) => profile.getBookmark(memberId, pageParam, selectedSort),
{
getNextPageParam: (lastPage) => lastPage.cursor || undefined,
select: (data) => ({
pages: data.pages.flatMap((page) => page.items),
pageParams: data.pageParams,
}),
enabled: selectedMenu === "입덕애니",
},
);

return query;
}
28 changes: 28 additions & 0 deletions src/features/users/hooks/useFetchInfiniteReview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useInfiniteQuery } from "@tanstack/react-query";

import { useApi } from "@/hooks/useApi";

import { SelectedSort } from "./useSortBar";
import { MENU } from "./useTabMenu";

export default function useFetchInfiniteReview(
memberId: number,
selectedSort: SelectedSort,
selectedMenu: MENU,
) {
const { profile } = useApi();
const query = useInfiniteQuery(
["profile", memberId, "review", selectedSort.id, selectedSort.isDESC],
({ pageParam }) => profile.getReview(memberId, pageParam, selectedSort),
{
getNextPageParam: (lastPage) => lastPage.cursor || undefined,
select: (data) => ({
pages: data.pages.flatMap((page) => page.items),
pageParams: data.pageParams,
}),
enabled: selectedMenu === "한줄리뷰",
},
);

return query;
}
18 changes: 18 additions & 0 deletions src/features/users/hooks/useFetchProfile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useQuery } from "@tanstack/react-query";
import { useParams } from "react-router-dom";

import useAuth from "@/features/auth/hooks/useAuth";
import { useApi } from "@/hooks/useApi";

export default function useFetchProfile() {
const { user } = useAuth();
const params = useParams();
const { profile } = useApi();
const query = useQuery(
["profile", params.name || user?.name],
() => profile.getProfile(params.name || user?.name),
{ enabled: !!params.name || !!user?.name },
);

return query;
}
23 changes: 23 additions & 0 deletions src/features/users/hooks/useFetchTabListCount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useQuery } from "@tanstack/react-query";

import { useApi } from "@/hooks/useApi";

import { MENU } from "./useTabMenu";

export default function useFetchTabListCount(
memberId: number,
selectedMenu: MENU,
) {
const { profile } = useApi();
const query = useQuery({
queryKey: [
"profile",
memberId,
"count",
selectedMenu === "입덕애니" ? "bookmark" : "review",
],
queryFn: () => profile.getTabListCount(memberId, selectedMenu),
});

return query;
}
Loading

0 comments on commit 41da3d7

Please sign in to comment.