Skip to content

Commit

Permalink
feat: 프로필 수정 (#86)
Browse files Browse the repository at this point in the history
  • Loading branch information
yeeZinu committed Jun 21, 2024
1 parent 5158b30 commit 5960ea8
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ export default function FollowModal({ userId, isModalState, setIsModalState, fol
hasNextPage: hasNextFolloweees,
} = useFollowees(userId);

console.log("followeeData", followeeData);

useEffect(() => {
if (inView && hasNextFolloweees) {
fetchNextFollowees();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,85 @@
width: 100%;
}

.content {
display: flex;
flex-direction: column;
align-items: start;
justify-content: flex-start;
position: relative;
width: 620px;
height: auto;
padding: 40px;

@include respond-to(tablet) {
width: 590px;
}

@include respond-to(mobile) {
width: 335px;
}
}

.profile {
width: 100%;
}

.form {
display: flex;
flex-direction: column;
width: 100%;
gap: 20px;

@include respond-to(tablet) {
gap: 15px;
}

@include respond-to(mobile) {
gap: 10px;
}
}

.title {
@include text-xl;

margin-bottom: 40px;

@include respond-to(tablet) {
@include text-lg;
}

@include respond-to(mobile) {
@include text-lg;
}
}

.imageUploader {
width: 160px;
height: 160px;

@include respond-to(mobile) {
width: 140px;
height: 140px;
}
}

.submitButton {
margin-top: 20px;

&.error {
color: $white-100;
background-color: $red-100;
}
}

.closeButton {
position: absolute;
top: 20px;
right: 20px;
cursor: pointer;

@include respond-to(mobile) {
width: 24px;
height: 24px;
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,160 @@
import { signOut } from "next-auth/react";
import { useQueryClient } from "@tanstack/react-query";
import Image from "next/image";
import { signOut, useSession } from "next-auth/react";
import React, { useState } from "react";
import { SubmitHandler, useForm } from "react-hook-form";
import { useUpdateProfile } from "@/app/(userpage)/user/[userId]/hooks/useUpdateProfile";
import Button from "@/components/Button/Button";
import Input from "@/components/Input/Input";
import TextArea from "@/components/Input/TextArea";
import Modal from "@/components/Modal/Modal";
import useUploadImageMutation from "@/components/Upload/hooks/useUploadImageMutation";
import UploadImage from "@/components/Upload/UploadImage";
import { ErrorResponse } from "@/types/global";
import cn from "@/utils/classNames";
import { CLOSE_ICON } from "@/utils/constant";
import styles from "./MyProfileButton.module.scss";

type UserFormValue = {
description: string;
nickname: string;
image: File;
};

export default function MyProfileButton() {
const queryClient = useQueryClient();
const { data: session } = useSession();
const accessToken = session?.accessToken ?? "";

const [isModal, setIsModal] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false);
const [isError, setIsError] = useState(false);

const handleClose = () => setIsModal(false);
const handleOpen = () => setIsModal(true);

const {
register,
setValue,
setError,
handleSubmit,
formState: { isValid, errors },
} = useForm<UserFormValue>({ mode: "onBlur" });

const { updateProfileMutation } = useUpdateProfile(accessToken);
const uploadImageMutation = useUploadImageMutation();

const onSubmit: SubmitHandler<UserFormValue> = async (data) => {
if (data.image) {
const url = await uploadImageMutation.mutateAsync(data.image);

if (url) {
const profileData = {
description: data.description,
nickname: data.nickname,
image: url,
};

await updateProfileMutation.mutateAsync(profileData, {
onSuccess: () => {
setIsSubmitted(true);
queryClient.invalidateQueries({ queryKey: ["userData", String(session?.user.id)] });
setTimeout(() => {
handleClose();
setIsSubmitted(false);
}, 1000);
},
onError: (error: Error) => {
const response: ErrorResponse = JSON.parse(error.message);
const errorMessage = response.details?.name?.message;
if (errorMessage) {
setError("nickname", { message: `${errorMessage}` });
} else {
setIsError(true);
setTimeout(() => {
setIsError(false);
}, 2000);
}
},
});
}
}
};

const getButtonText = () => {
if (updateProfileMutation.isPending) {
return "Loading...";
}
if (isSubmitted) {
return "완료!";
}
if (isError) {
return "실패: 다시 시도해 주세요.";
}
return "저장하기";
};

return (
<div className={cn(styles.container)}>
{isModal && (
<Modal onClose={handleClose}>
<div className={styles.profileEdit}>모달 테스트</div>
<div className={styles.content}>
<h2 className={styles.title}>프로필 편집</h2>
<form
onSubmit={handleSubmit(onSubmit)}
className={styles.form}
>
<UploadImage
name='image'
setValue={setValue}
register={register}
className={cn(styles.imageUploader)}
/>
<Input
name='nickname'
register={register}
rules={{
required: "닉네임은 필수 입력입니다.",
maxLength: { value: 10, message: "닉네임은 최대 10자까지 가능합니다." },
}}
errors={errors}
type='text'
placeholder='닉네임을 입력해주세요.'
maxLength={10}
className={cn(styles.profileInput)}
/>
<TextArea
name='description'
rows={5}
register={register}
rules={{
required: "자기 소개글은 필수 입력입니다.",
minLength: { value: 1, message: "최소 1자 이상 적어주세요." },
maxLength: 300,
}}
errors={errors}
maxLength={300}
className={styles.textarea}
/>

<Button
type='submit'
styleType='primary'
disabled={!isValid || uploadImageMutation.isPending || isSubmitted || isError}
className={cn(styles.submitButton, isError && styles.error)}
>
{getButtonText()}
</Button>
</form>
<Image
className={cn(styles.closeButton)}
src={CLOSE_ICON}
width={40}
height={40}
alt='닫기'
onClick={handleClose}
/>
</div>
</Modal>
)}
<Button
Expand Down
26 changes: 26 additions & 0 deletions src/app/(userpage)/user/[userId]/hooks/useUpdateProfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useMutation } from "@tanstack/react-query";
import { UpdateProfileRequest } from "@/types/global";

export const useUpdateProfile = (token: string) => {
const updateProfileMutation = useMutation({
mutationFn: async (data: UpdateProfileRequest) => {
const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/users/me`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(data),
});

if (!res.ok) {
const error = await res.json();
throw new Error(JSON.stringify(error));
}

return res.json();
},
});

return { updateProfileMutation };
};
6 changes: 6 additions & 0 deletions src/types/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,9 @@ export type Follow = {
nickname: string;
id: number;
};

export type UpdateProfileRequest = {
description: string;
nickname: string;
image: string;
};

0 comments on commit 5960ea8

Please sign in to comment.