Skip to content
Merged
11 changes: 8 additions & 3 deletions src/app/(with-header)/myprofile/_components/MyProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import Image from 'next/image';
import { ChangeEvent, useRef, useState } from 'react';
import { toast } from 'react-toastify';
import photoIcon from '@/assets/icons/photo.svg';
import ProfileImg from '@/components/ProfileImg';
import Button from '@/components/Button';
Expand Down Expand Up @@ -45,7 +46,11 @@ export function MyProfile({ profileData, upLoadImgFile, upLoadUserData }: MuProf

const onClickUploadButton = async () => {
if (nickNameValue === '') {
alert('닉네임을 입력해 주세요.');
toast.error('닉네임을 입력해 주세요.');
return;
}
if (nickNameValue.length > 30) {
toast.error('닉네임은 최대 30글자입니다.');
return;
}
if (!fileInput && nickNameValue === preNickName) {
Expand Down Expand Up @@ -93,7 +98,7 @@ export function MyProfile({ profileData, upLoadImgFile, upLoadUserData }: MuProf
</label>
)}
<div className='flex flex-col gap-[16px] tablet:gap-[8px] mobile:gap-[4px]'>
<h1 className='flex justify-center text-2xl font-bold text-gray-800 tablet:justify-start mobile:text-xl'>{preNickName}</h1>
<h1 className='overflow-hidden text-ellipsis whitespace-nowrap text-center text-2xl font-bold text-gray-800 pc:w-[200px] tablet:justify-start mobile:text-xl'>{preNickName}</h1>
</div>
</div>
<div className='flex w-full flex-col justify-center gap-[8px] tablet:flex-row tablet:gap-[24px] mobile:flex-col mobile:gap-[6px]'>
Expand Down Expand Up @@ -122,7 +127,7 @@ export function MyProfile({ profileData, upLoadImgFile, upLoadUserData }: MuProf
text='적용하기'
onClick={onClickUploadButton}
disabled={nickNameValue === ''}
className={`rounded-[12px] px-[20px] py-[8px] text-lg tablet:px-[30px] tablet:py-[11px] mobile:px-[20px] mobile:py-[9px] mobile:text-md ${nickNameValue === '' ? 'bg-gray-400' : ''}`}
className={`rounded-[12px] px-[20px] py-[8px] text-lg disabled:bg-gray-400 tablet:px-[30px] tablet:py-[11px] mobile:px-[20px] mobile:py-[9px] mobile:text-md`}
/>
)}
</div>
Expand Down
80 changes: 22 additions & 58 deletions src/app/(with-header)/myprofile/_components/MyProfileContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
'use client';

import { useEffect, useState } from 'react';
import { fetchWithAuth } from '@/lib/auth';
import { MyProfile, MyProfileData } from './MyProfile';
import LoadingSpinner from '@/components/LoadingSpinner';
import { toast } from 'react-toastify';
import { MyProfile, MyProfileData } from '@/app/(with-header)/myprofile/_components/MyProfile';
import Refresh from '@/components/Refresh';
import { fetchUploadUser, fetchUser } from '@/lib/fetchUser';
import { fetchImage } from '@/lib/fetchImage';
import MyProfileSkeleton from './skeleton/MyProfileSkeleton';

export default function MyProfileContainer() {
const [profileData, setProfileData] = useState<MyProfileData>();
Expand All @@ -13,20 +15,13 @@ export default function MyProfileContainer() {

const getUserData = async () => {
setError('');
setIsLoading(true);
try {
setIsLoading(true);
const response = await fetchWithAuth(`${process.env.NEXT_PUBLIC_BASE_URL}/users/me`);

if (!response?.ok || response === null) {
setError('유저 데이터를 불러오는데 실패했습니다.');
return;
}

const data: MyProfileData = await response.json();
const data = await fetchUser();
setProfileData(data);
} catch (error) {
if (error instanceof Error) {
setError(error.message);
} catch (e) {
if (e instanceof Error) {
setError(e.message);
} else {
setError('알 수 없는 오류가 발생했습니다.');
}
Expand All @@ -36,56 +31,25 @@ export default function MyProfileContainer() {
};

const upLoadImgFile = async (formData: FormData): Promise<string | undefined> => {
setError('');
try {
const response = await fetchWithAuth(`${process.env.NEXT_PUBLIC_BASE_URL}/images/upload`, {
method: 'POST',
body: formData,
});

if (!response?.ok || response === null) {
setError('이미지를 업로드하는데 실패했습니다.');
return;
}

const data = await response.json();
return data.url as string;
} catch (error) {
if (error instanceof Error) {
setError(error.message);
} else {
setError('알 수 없는 오류가 발생했습니다.');
const data = await fetchImage(formData);
return data;
} catch (e) {
if (e) {
toast.error('이미지 업로드에 실패했습니다.');
}
}
};

const upLoadUserData = async (image: string, nickname: string): Promise<string | undefined> => {
setError('');
try {
const response = await fetchWithAuth(`${process.env.NEXT_PUBLIC_BASE_URL}/users/me`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ image: image, nickname: nickname }),
});

if (response?.status === 400) {
alert('동일한 닉네임이 있습니다.');
}

if (!response?.ok || response === null) {
setError('데이터를 업데이트 하는데 실패했습니다.');
return;
}

const result = await response.json();
const result = await fetchUploadUser(image, nickname);
if (!result) return;
toast.success('프로필 수정에 성공했습니다.');
return result;
} catch (error) {
if (error instanceof Error) {
setError(error.message);
} else {
setError('알 수 없는 오류가 발생했습니다.');
} catch (e) {
if (e) {
toast.error('프로필 수정에 실패했습니다.');
}
}
};
Expand All @@ -94,7 +58,7 @@ export default function MyProfileContainer() {
getUserData();
}, []);

if (isLoading) return <LoadingSpinner className='h-[530px] w-[280px] rounded-[16px] border border-gray-300 shadow-drop tablet:h-[247px] tablet:w-full mobile:h-[241px]' />;
if (isLoading) return <MyProfileSkeleton />;

if (!profileData || error)
return (
Expand Down
26 changes: 15 additions & 11 deletions src/app/(with-header)/myprofile/_components/MyReviewItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ import elapsedTime from '@/utils/formatDate';
import like from '@/assets/icons/star_hover.svg';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import MyReviewKebabDropDown from './MyReviewKebabDropDown';
import MyReviewKebabDropDown from '@/app/(with-header)/myprofile/_components/MyReviewKebabDropDown';
import { MyReview } from '@/types/review-data';
import { EditReviewData } from './MyReviewListContainer';
import { EditReviewData } from '@/app/(with-header)/myprofile/_components/MyReviewListContainer';
import { forwardRef } from 'react';

export function MyReviewItem({
reviewInitialData,
editMyReview,
deleteMyReview,
}: {
interface MyReviewItemProps {
reviewInitialData: MyReview;
editMyReview: (id: number, editReviewData: EditReviewData, updatedAt: string) => void;
deleteMyReview: (id: number) => void;
}) {
setDataCount: React.Dispatch<React.SetStateAction<number>>;
}

const MyReviewItem = forwardRef<HTMLDivElement, MyReviewItemProps>(({ reviewInitialData, editMyReview, deleteMyReview, setDataCount }, ref) => {
const router = useRouter();
const reviewElapsedTime = elapsedTime(reviewInitialData.updatedAt);

Expand All @@ -29,14 +29,14 @@ export function MyReviewItem({
};

return (
<div className='flex w-[800px] cursor-pointer rounded-[16px] border border-gray-300 px-10 py-7 hover:shadow-lg tablet:w-full mobile:w-full' onClick={onClickReviewItem}>
<div ref={ref} className='flex w-[800px] cursor-pointer rounded-[16px] border border-gray-300 px-10 py-7 hover:shadow-lg tablet:w-full mobile:w-full' onClick={onClickReviewItem}>
<div className='flex flex-grow flex-col gap-[20px]'>
<div className='relative flex gap-[15px]'>
<div className='flex h-[42px] w-[80px] items-center justify-center gap-[4px] rounded-[12px] bg-purple-10 tablet:h-[38px] mobile:h-[32px] mobile:w-[60px]'>
<Image className='w-[20px] mobile:w-[16px]' src={like} alt='별점 아이콘' />
<span className='text-2lg font-bold text-purple-100 mobile:text-md'>{reviewInitialData.rating.toFixed(1)}</span>
</div>
<MyReviewKebabDropDown id={reviewInitialData.id} reviewInitialData={reviewInitialData} editMyReview={editMyReview} deleteMyReview={deleteMyReview} />
<MyReviewKebabDropDown id={reviewInitialData.id} reviewInitialData={reviewInitialData} editMyReview={editMyReview} deleteMyReview={deleteMyReview} setDataCount={setDataCount} />
<span className='flex items-center justify-center text-lg text-gray-500 mobile:text-md'>{reviewElapsedTime}</span>
</div>
<div className='flex flex-col gap-[10px]'>
Expand All @@ -46,4 +46,8 @@ export function MyReviewItem({
</div>
</div>
);
}
});

MyReviewItem.displayName = 'MyReviewItem';

export default MyReviewItem;
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,28 @@

import { useState } from 'react';
import Image from 'next/image';
import { toast } from 'react-toastify';
import Dropdown from '@/components/Dropdown';
import kebab from '@/assets/icons/menu.svg';
import Modal from '@/components/modal/Modal';
import DeleteWineForm from '@/components/modal/DeleteWineModal';
import { fetchWithAuth } from '@/lib/auth';
import PatchReviewForm from '@/components/modal/PatchReviewForm';
import { MyReview } from '@/types/review-data';
import { EditReviewData } from './MyReviewListContainer';
import { fetchDeleteReview } from '@/lib/fetchMyReivew';
import { EditReviewData } from '@/app/(with-header)/myprofile/_components/MyReviewListContainer';

export default function MyReviewKebabDropDown({
reviewInitialData,
id,
editMyReview,
deleteMyReview,
setDataCount,
}: {
reviewInitialData: MyReview;
id: number;
editMyReview: (id: number, editReviewData: EditReviewData, updatedAt: string) => void;
deleteMyReview: (id: number) => void;
setDataCount: React.Dispatch<React.SetStateAction<number>>;
}) {
const [isEditModalOpen, setIsEditModalOpen] = useState<boolean>(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<boolean>(false);
Expand Down Expand Up @@ -48,23 +51,20 @@ export default function MyReviewKebabDropDown({

const handleDeleteWine = async () => {
try {
const response = await fetchWithAuth(`${process.env.NEXT_PUBLIC_BASE_URL}/reviews/${id}`, {
method: 'DELETE',
});

if (!response?.ok || response === null) {
throw new Error('리뷰 삭제에 실패했습니다');
}

const body = await response.json();
const body = await fetchDeleteReview(id);
if (body) {
if (deleteMyReview) {
deleteMyReview(id);
toast.success('리뷰 삭제에 성공했습니다.');
setDataCount((pre) => pre - 1);
}
closeDeleteModal();
}
} catch (error) {
console.error('리뷰 삭제 에러:', error);
} catch (e) {
if (e) {
toast.error('리뷰 삭제에 실패했습니다.');
closeDeleteModal();
}
}
};

Expand Down
Loading