diff --git a/src/features/profile/components/info/Info.tsx b/src/features/profile/components/info/Info.tsx
index 9681ca11..de4a2677 100644
--- a/src/features/profile/components/info/Info.tsx
+++ b/src/features/profile/components/info/Info.tsx
@@ -2,19 +2,18 @@
import { useState } from 'react';
import Avatar from '@/components/avatar/Avatar';
-import { useEditInfo } from '@/api/auth/react-query';
import { InfoEditModal } from './index';
import { EditInfoParams, ProfilePageProps } from '../../types';
import IconButton from '@/components/icon-button/IconButton';
import { IcEdit } from '../../../../../public/icons';
+import { useEditInfo } from '../../hooks/useEditInfo';
export default function Info({ user, isMyPage }: ProfilePageProps) {
const [isModalOpen, setIsModalOpen] = useState(false);
+ const { onSubmit } = useEditInfo();
- const { mutate: editInfo } = useEditInfo();
-
- const onSubmitEditInfo = (formData: EditInfoParams) => {
- editInfo(formData);
+ const onSubmitEditInfo = async (data: EditInfoParams) => {
+ await onSubmit(data);
setIsModalOpen(false);
};
@@ -42,11 +41,11 @@ export default function Info({ user, isMyPage }: ProfilePageProps) {
role="content"
>
{/* 프로필 이미지 */}
-
+
{/* 프로필 정보 */}
@@ -80,9 +79,11 @@ export default function Info({ user, isMyPage }: ProfilePageProps) {
onClose={() => setIsModalOpen(false)}
onConfirm={(formData) => onSubmitEditInfo(formData)}
infoData={{
- nickname: user?.nickname || '',
- description: user?.description || '',
- image: user?.image,
+ image: user?.image || '',
+ user: {
+ nickname: user?.nickname || '',
+ description: user?.description || '',
+ },
}}
/>
)}
diff --git a/src/features/profile/components/info/InfoEditModal.tsx b/src/features/profile/components/info/InfoEditModal.tsx
index a37b06cd..6b69aaf1 100644
--- a/src/features/profile/components/info/InfoEditModal.tsx
+++ b/src/features/profile/components/info/InfoEditModal.tsx
@@ -10,7 +10,7 @@ import { EditInfoParams } from '../../types';
interface InfoEditModalProps {
isOpen: boolean;
onClose: () => void;
- onConfirm: (updatedData: EditInfoParams) => void;
+ onConfirm: (formData: EditInfoParams) => void;
infoData: EditInfoParams;
}
@@ -31,7 +31,9 @@ function InfoEditContent({
@@ -63,7 +65,7 @@ function InfoEditContent({
type="text"
name="nickname"
aria-label="nickname"
- value={formData.nickname}
+ value={formData.user?.nickname}
onChange={handleChange}
className="w-full rounded-lg bg-gray-light-02 p-2 font-medium"
/>
@@ -74,7 +76,7 @@ function InfoEditContent({
type="text"
name="description"
aria-label="description"
- value={formData.description}
+ value={formData.user?.description}
onChange={handleChange}
className="w-full rounded-lg bg-gray-light-02 p-2 font-medium"
/>
@@ -93,21 +95,23 @@ export default function InfoEditModal({
}: InfoEditModalProps) {
const { user } = useAuthStore();
const [formData, setFormData] = useState
({
- nickname: infoData.nickname || user?.name || '',
- description: infoData.description || user?.description || '',
image: infoData.image || user?.image || '/images/profile.png',
+ user: {
+ nickname: infoData.user?.nickname || user?.name || '',
+ description: infoData.user?.description || user?.description || '',
+ },
});
const [preview, setPreview] = useState(null);
const handleFileChange = (e: React.ChangeEvent) => {
- const file = e.target.files ? e.target.files[0] : null;
+ const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
const fileResult = reader.result as string;
setPreview(fileResult);
- setFormData((prev) => ({ ...prev, image: fileResult }));
+ setFormData((prev) => ({ ...prev, image: file }));
};
reader.readAsDataURL(file);
}
@@ -115,7 +119,10 @@ export default function InfoEditModal({
const handleChange = (e: React.ChangeEvent) => {
const { name, value } = e.target;
- setFormData((prev) => ({ ...prev, [name]: value }));
+ setFormData((prev) => ({
+ ...prev,
+ user: { ...prev.user, [name]: value },
+ }));
};
const handleConfirm = () => {
diff --git a/src/features/profile/container/MyJoinedClubList.tsx b/src/features/profile/container/MyJoinedClubList.tsx
index a906eded..6651271e 100644
--- a/src/features/profile/container/MyJoinedClubList.tsx
+++ b/src/features/profile/container/MyJoinedClubList.tsx
@@ -56,7 +56,6 @@ export default function MyJoinedClubList({ order }: ClubListProps) {
const onDelete = async (clubId: number) => {
try {
const res = await leaveClub(clubId);
- console.log(res);
if (res) {
showToast({
message: '취소된 모임을 삭제하였습니다.',
@@ -134,7 +133,7 @@ export default function MyJoinedClubList({ order }: ClubListProps) {
{
+ const { mutateAsync: editInfo, isPending } = useEditInfoMutation();
+
+ const onSubmit = async (data: EditInfoParams) => {
+ const formData = toFormData(data);
+ return await editInfo(formData);
+ };
+ return { onSubmit, isLoading: isPending };
+};
diff --git a/src/features/profile/types/index.ts b/src/features/profile/types/index.ts
index e473b60e..bc35e47d 100644
--- a/src/features/profile/types/index.ts
+++ b/src/features/profile/types/index.ts
@@ -38,7 +38,9 @@ export interface ClubListProps {
}
export interface EditInfoParams {
- nickname?: string;
- description?: string;
- image?: string | null;
+ image?: string | File;
+ user?: {
+ nickname?: string;
+ description?: string;
+ };
}
diff --git a/src/features/profile/utils/infoEditFormUtils.tsx b/src/features/profile/utils/infoEditFormUtils.tsx
new file mode 100644
index 00000000..93cf606f
--- /dev/null
+++ b/src/features/profile/utils/infoEditFormUtils.tsx
@@ -0,0 +1,23 @@
+import { EditInfoParams } from '../types';
+
+export const toFormData = (data: EditInfoParams) => {
+ const formData = new FormData();
+
+ const imageFile = data.image instanceof File ? data.image : null;
+
+ if (imageFile) {
+ formData.append('image', imageFile);
+ }
+ const userData = {
+ nickname: data?.user?.nickname,
+ description: data?.user?.description,
+ };
+ formData.append(
+ 'user',
+ new Blob([JSON.stringify(userData)], {
+ type: 'application/json',
+ }),
+ );
+
+ return formData;
+};
diff --git a/src/mocks/mockDatas.ts b/src/mocks/mockDatas.ts
index e7868b83..ed2cb21e 100644
--- a/src/mocks/mockDatas.ts
+++ b/src/mocks/mockDatas.ts
@@ -253,11 +253,11 @@ export const mockReviews: Review[] = [
rating: 5,
content:
'이 책클럽은 정말 좋았습니다. 책 선정도 훌륭하고, 참여자들 간의 토론도 활발했어요.',
- image: 'https://example.com/images/review1.jpg',
+ userImage: 'https://example.com/images/review1.jpg',
createdAt: '2025-01-10T14:30:00Z',
- clubImgUrl: 'https://example.com/images/club1.jpg',
+ bookClubImageUrl: 'https://example.com/images/club1.jpg',
nickname: '진영',
- clubName: '문학 사랑 모임',
+ bookClubTitle: '문학 사랑 모임',
bookClubType: 'FREE',
},
{
@@ -267,11 +267,11 @@ export const mockReviews: Review[] = [
rating: 4,
content:
'모임 분위기는 좋았지만, 책의 주제가 조금 어려웠어요. 그래도 유익한 시간이었어요.',
- image: 'https://example.com/images/review2.jpg',
+ userImage: 'https://example.com/images/review2.jpg',
createdAt: '2025-01-12T16:45:00Z',
- clubImgUrl: 'https://example.com/images/club2.jpg',
+ bookClubImageUrl: 'https://example.com/images/club2.jpg',
nickname: '민지',
- clubName: '책과 커피 모임',
+ bookClubTitle: '책과 커피 모임',
bookClubType: 'FREE',
},
{
@@ -282,9 +282,9 @@ export const mockReviews: Review[] = [
content:
'책은 좋았지만, 온라인 모임이라 참여자들과의 소통이 부족했던 것 같아요.',
createdAt: '2025-01-14T17:00:00Z',
- clubImgUrl: 'https://example.com/images/club3.jpg',
+ bookClubImageUrl: 'https://example.com/images/club3.jpg',
nickname: '수연',
- clubName: 'SF 소설 모임',
+ bookClubTitle: 'SF 소설 모임',
bookClubType: 'FIXED',
},
{
@@ -295,9 +295,9 @@ export const mockReviews: Review[] = [
content:
'좋은 모임이었지만, 장소가 조금 좁았어요. 그 외엔 정말 유익한 시간이었습니다.',
createdAt: '2025-01-15T18:10:00Z',
- clubImgUrl: 'https://example.com/images/club4.jpg',
+ bookClubImageUrl: 'https://example.com/images/club4.jpg',
nickname: '지훈',
- clubName: '역사 탐방 모임',
+ bookClubTitle: '역사 탐방 모임',
bookClubType: 'FREE',
},
{
@@ -307,11 +307,11 @@ export const mockReviews: Review[] = [
rating: 5,
content:
'시집에 대한 다양한 해석을 나누어서 매우 흥미로운 모임이었어요. 다음 모임이 기대됩니다.',
- image: 'https://example.com/images/review5.jpg',
+ userImage: 'https://example.com/images/review5.jpg',
createdAt: '2025-01-16T12:00:00Z',
- clubImgUrl: 'https://example.com/images/club5.jpg',
+ bookClubImageUrl: 'https://example.com/images/club5.jpg',
nickname: '정민',
- clubName: '시집 독서 모임',
+ bookClubTitle: '시집 독서 모임',
bookClubType: 'FIXED',
},
{
@@ -322,9 +322,9 @@ export const mockReviews: Review[] = [
content:
'경제학 토론이 매우 유익했고, 다른 사람들의 의견을 들을 수 있어 좋았습니다.',
createdAt: '2025-01-17T13:30:00Z',
- clubImgUrl: 'https://example.com/images/club6.jpg',
+ bookClubImageUrl: 'https://example.com/images/club6.jpg',
nickname: '현수',
- clubName: '경제학 토론 모임',
+ bookClubTitle: '경제학 토론 모임',
bookClubType: 'FREE',
},
{
@@ -335,9 +335,9 @@ export const mockReviews: Review[] = [
content:
'영화와 책을 비교하는 재미는 있었으나, 일부 영화가 책의 내용을 잘 반영하지 못한 것 같아요.',
createdAt: '2025-01-18T19:00:00Z',
- clubImgUrl: 'https://example.com/images/club7.jpg',
+ bookClubImageUrl: 'https://example.com/images/club7.jpg',
nickname: '태영',
- clubName: '문학과 영화 모임',
+ bookClubTitle: '문학과 영화 모임',
bookClubType: 'FIXED',
},
{
@@ -347,11 +347,11 @@ export const mockReviews: Review[] = [
rating: 5,
content:
'여행 사진과 이야기를 나누는 모임은 정말 즐거웠어요. 다른 사람들의 경험을 듣는 것이 너무 흥미로웠습니다.',
- image: 'https://example.com/images/review8.jpg',
+ userImage: 'https://example.com/images/review8.jpg',
createdAt: '2025-01-19T20:30:00Z',
- clubImgUrl: 'https://example.com/images/club8.jpg',
+ bookClubImageUrl: 'https://example.com/images/club8.jpg',
nickname: '수아',
- clubName: '여행 사진 모임',
+ bookClubTitle: '여행 사진 모임',
bookClubType: 'FREE',
},
{
@@ -362,9 +362,9 @@ export const mockReviews: Review[] = [
content:
'디지털 기술에 대한 최신 정보를 나눌 수 있어 좋았고, 많은 토론이 이루어졌습니다.',
createdAt: '2025-01-20T21:45:00Z',
- clubImgUrl: 'https://example.com/images/club9.jpg',
+ bookClubImageUrl: 'https://example.com/images/club9.jpg',
nickname: '정호',
- clubName: '디지털 기술 토론 모임',
+ bookClubTitle: '디지털 기술 토론 모임',
bookClubType: 'FREE',
},
{
@@ -375,9 +375,9 @@ export const mockReviews: Review[] = [
content:
'고전 문학을 다시 한번 되새길 수 있는 좋은 시간이었어요. 다만, 책 선정이 조금 아쉬웠습니다.',
createdAt: '2025-01-21T22:30:00Z',
- clubImgUrl: 'https://example.com/images/club10.jpg',
+ bookClubImageUrl: 'https://example.com/images/club10.jpg',
nickname: '지은',
- clubName: '고전 문학 독서 모임',
+ bookClubTitle: '고전 문학 독서 모임',
bookClubType: 'FREE',
},
];
diff --git a/src/types/review.ts b/src/types/review.ts
index 7e116757..d1ac6daf 100644
--- a/src/types/review.ts
+++ b/src/types/review.ts
@@ -5,20 +5,20 @@ export interface Review {
bookClubId: number;
rating: number;
content: string;
- image?: string | undefined;
+ nickname: string;
+ userImage?: string | undefined;
createdAt: string;
- clubImgUrl?: string;
- nickname?: string;
- clubName: string;
+ bookClubImageUrl?: string;
+ bookClubTitle: string;
bookClubType: 'FREE' | 'FIXED';
}
export interface DetailReview
- extends Omit {
- nickname: string;
+ extends Omit {
bookClubType?: 'FREE' | 'FIXED';
- clubName?: string;
+ bookClubTitle?: string;
+ bookClubImageUrl?: string;
}
export interface ClubDetailReviewFilters {