Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/api/auth/authClientAPI.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { EditInfoParams } from '@/features/profile/types';
import apiClient from '@/lib/utils/apiClient';

export const authClientAPI = {
//회원정보 확인

//회원정보 수정
editInfo: async ({ nickname, image, description }: EditInfoParams) => {
await apiClient.post('auths/user', {
nickname,
image,
description,
editInfo: async (formData: FormData) => {
const response = await apiClient.post('auths/user', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
return response.data;
},

//회원가입
Expand Down
5 changes: 2 additions & 3 deletions src/api/auth/react-query/customHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import { useMutation } from '@tanstack/react-query';
import { showToast } from '@/components/toast/toast';
import { authClientAPI } from '../authClientAPI';
import { getUserInfo } from '@/features/auth/api/auth';
import { EditInfoParams } from '@/features/profile/types';

//프로필 수정하기
export function useEditInfo() {
export function useEditInfoMutation() {
return useMutation({
mutationFn: (data: EditInfoParams) => authClientAPI.editInfo(data),
mutationFn: (formData: FormData) => authClientAPI.editInfo(formData),
onSuccess: () => {
getUserInfo();
showToast({ message: '프로필 수정이 완료되었습니다.', type: 'success' });
Expand Down
11 changes: 7 additions & 4 deletions src/api/book-club/bookClubMainAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,20 @@ export const bookClubMainAPI = {

//유저가 참가한 북클럽 조회
userJoined: async (userId: number, params?: MyProfileParams) => {
const response = await apiClient.get(`/book-clubs/user/${userId}/joined`, {
const response = await apiClient.get(`/book-clubs/users/${userId}/joined`, {
params,
});
return response.data;
},

//유저가 만든 북클럽 조회
userCreated: async (userId: number, params?: MyProfileParams) => {
const response = await apiClient.get(`/book-clubs/user/${userId}/created`, {
params,
});
const response = await apiClient.get(
`/book-clubs/users/${userId}/created`,
{
params,
},
);
return response.data;
},

Expand Down
2 changes: 2 additions & 0 deletions src/components/header/HeaderBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { usePathname, useRouter } from 'next/navigation';
import { useAuthStore } from '@/store/authStore';
import DropDown from '../drop-down/DropDown';
import { logout } from '@/features/auth/api/auth';
import { showToast } from '../toast/toast';

function HeaderBar() {
const pathname = usePathname();
Expand All @@ -17,6 +18,7 @@ function HeaderBar() {
if (value === 'LOGOUT') {
try {
await logout();
showToast({ message: '로그아웃 되었습니다 ', type: 'success' });
router.replace('/bookclub');
} catch (error) {
console.error('로그아웃 실패:', error);
Expand Down
1 change: 1 addition & 0 deletions src/constants/avatar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const AVATAR_SIZE = {
lg: 'h-[56px] w-[56px]',
xl: 'h-[74px] w-[71px]',
max: 'h-[74px] w-[74px]',
profile: 'h-[80px] w-[80px]',
} as const;

export type AvatarSize = keyof typeof AVATAR_SIZE;
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import ChatBubble from '@/features/chat-room/components/chat-bubble/ChatBubble';
import { ChatMessageType } from '../../../types/chatBubbleList';
import PopUp from '@/components/pop-up/PopUp';
import { useState } from 'react';
import { useRouter } from 'next/navigation';

interface ChatMessageProps {
message: ChatMessageType;
Expand All @@ -16,24 +19,41 @@ function ChatMessage({
isConsecutive,
hostId,
time,
onProfileClick,
// onProfileClick,
}: ChatMessageProps) {
const { userId, content, userNickname } = message;
const [isPopUpOpen, setIsPopUpOpen] = useState(false);
const router = useRouter();

return isMyMessage ? (
<ChatBubble variant="ME" props={{ content, time }} />
) : (
<ChatBubble
variant="OPPONENT"
props={{
content,
time,
name: userNickname,
isHost: userId === hostId,
onProfileClick: () => onProfileClick?.(userId),
isConsecutive,
}}
/>
return (
<>
{isMyMessage ? (
<ChatBubble variant="ME" props={{ content, time }} />
) : (
<ChatBubble
variant="OPPONENT"
props={{
content,
time,
name: userNickname,
isHost: userId === hostId,
onProfileClick: () => setIsPopUpOpen(true),
isConsecutive,
}}
/>
)}
<PopUp
isOpen={isPopUpOpen}
isLarge={true}
isTwoButton={true}
label={`${userNickname}님의 프로필 페이지로 이동하시겠어요?`}
handlePopUpClose={() => setIsPopUpOpen(false)}
handlePopUpConfirm={() => {
setIsPopUpOpen(false);
router.push(`/profile/${userId}`);
}}
/>
</>
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/features/club-details/components/ReviewList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function ReviewList({
ratingCount={review.rating}
comment={review.content}
userProfile={{
profileImage: review.image || '/images/profile.png',
profileImage: review.userImage || '/images/profile.png',
userName: review.nickname,
createdAt: formatDateForUI(review.createdAt, 'DATE_ONLY'),
}}
Expand Down
20 changes: 10 additions & 10 deletions src/features/club-details/mocks/DetailReviewDatas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const mockReviews: DetailReview[] = [
rating: 5,
content: '정말 멋진 독서 모임이었어요! 많은 것을 배웠습니다.',
nickname: '책사랑꾼',
image: undefined,
userImage: undefined,
createdAt: '2025-01-01T10:00:00Z',
},
{
Expand All @@ -18,7 +18,7 @@ export const mockReviews: DetailReview[] = [
rating: 3,
content: '그럭저럭 괜찮았어요.',
nickname: '평범한독자',
image: 'https://example.com/images/user2.jpg',
userImage: 'https://example.com/images/user2.jpg',
createdAt: '2025-01-02T15:30:00Z',
},
{
Expand All @@ -28,7 +28,7 @@ export const mockReviews: DetailReview[] = [
rating: 1,
content: '시간 낭비였던 것 같아요.',
nickname: '비판적인독자',
image: undefined,
userImage: undefined,
createdAt: '2025-01-01T09:00:00Z',
},
{
Expand All @@ -38,7 +38,7 @@ export const mockReviews: DetailReview[] = [
rating: 4,
content: '좋은 경험이었지만 약간 서둘러서 진행된 느낌이었습니다.',
nickname: '빠른독자',
image: 'https://example.com/images/user4.jpg',
userImage: 'https://example.com/images/user4.jpg',
createdAt: '2025-01-03T12:00:00Z',
},
{
Expand All @@ -48,7 +48,7 @@ export const mockReviews: DetailReview[] = [
rating: 5,
content: '지금까지 참가한 독서 모임 중 최고였습니다!',
nickname: '열정독자',
image: undefined,
userImage: undefined,
createdAt: '2025-01-02T08:00:00Z',
},
{
Expand All @@ -58,7 +58,7 @@ export const mockReviews: DetailReview[] = [
rating: 2,
content: '조직적으로 더 나아질 필요가 있습니다.',
nickname: '정리된혼돈',
image: 'https://example.com/images/user6.jpg',
userImage: 'https://example.com/images/user6.jpg',
createdAt: '2025-01-04T14:45:00Z',
},
{
Expand All @@ -68,7 +68,7 @@ export const mockReviews: DetailReview[] = [
rating: 3,
content: '평범했어요.',
nickname: '평범한조',
image: undefined,
userImage: undefined,
createdAt: '2025-01-03T18:20:00Z',
},
{
Expand All @@ -78,7 +78,7 @@ export const mockReviews: DetailReview[] = [
rating: 4,
content: '재미있고 흥미로운 시간이었습니다!',
nickname: '행복한독자',
image: 'https://example.com/images/user8.jpg',
userImage: 'https://example.com/images/user8.jpg',
createdAt: '2025-01-05T09:15:00Z',
},
{
Expand All @@ -88,7 +88,7 @@ export const mockReviews: DetailReview[] = [
rating: 1,
content: '추천하고 싶지 않아요.',
nickname: '실망한독자',
image: undefined,
userImage: undefined,
createdAt: '2025-01-04T11:00:00Z',
},
{
Expand All @@ -98,7 +98,7 @@ export const mockReviews: DetailReview[] = [
rating: 5,
content: '정말 즐거운 시간이었습니다!',
nickname: '기쁜독자',
image: 'https://example.com/images/user10.jpg',
userImage: 'https://example.com/images/user10.jpg',
createdAt: '2025-01-06T10:10:00Z',
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ export default function ProfileWrittenReview({
>
<div className="flex cursor-pointer items-center gap-x-6 sm:flex-col sm:items-start sm:gap-y-6 md:flex-row">
<WrittenReview.ClubImage
src={review.clubImgUrl || defaultClubImage}
src={review.bookClubImageUrl || defaultClubImage}
alt="review_club_image"
/>
<div className="relative flex min-h-[180px] w-[336px] flex-1 flex-col gap-y-1.5 text-sm font-medium text-gray-darker md:w-full">
<WrittenReview.Rating ratingCount={review.rating} />
<div className="flex flex-col gap-y-2">
<WrittenReview.ClubInfo
clubName={review.clubName}
clubName={review.bookClubTitle}
bookClubType={review.bookClubType}
/>
<WrittenReview.Comment text={review.content} />
Expand Down
21 changes: 11 additions & 10 deletions src/features/profile/components/info/Info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};

Expand Down Expand Up @@ -42,11 +41,11 @@ export default function Info({ user, isMyPage }: ProfilePageProps) {
role="content"
>
{/* 프로필 이미지 */}
<div className="mr-6 h-[80px] w-[80px] rounded-full border-[3px] border-gray-normal-01">
<div className="mr-6 h-[80px] w-[80px] items-center justify-center rounded-full border-[3px] border-gray-normal-01">
<Avatar
src={user?.image || '/images/profile.png'}
alt="profile_page_profile_image"
size="max"
size="profile"
/>
</div>
{/* 프로필 정보 */}
Expand Down Expand Up @@ -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 || '',
},
}}
/>
)}
Expand Down
25 changes: 16 additions & 9 deletions src/features/profile/components/info/InfoEditModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { EditInfoParams } from '../../types';
interface InfoEditModalProps {
isOpen: boolean;
onClose: () => void;
onConfirm: (updatedData: EditInfoParams) => void;
onConfirm: (formData: EditInfoParams) => void;
infoData: EditInfoParams;
}

Expand All @@ -31,7 +31,9 @@ function InfoEditContent({
<div className="relative">
<div className="rounded-full border-2 border-gray-normal-01">
<Avatar
src={preview || formData.image || '/images/profile.png'}
src={
preview || formData.image?.toString() || '/images/profile.png'
}
alt="프로필 이미지"
size="xl"
/>
Expand Down Expand Up @@ -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"
/>
Expand All @@ -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"
/>
Expand All @@ -93,29 +95,34 @@ export default function InfoEditModal({
}: InfoEditModalProps) {
const { user } = useAuthStore();
const [formData, setFormData] = useState<EditInfoParams>({
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<string | null>(null);

const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
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);
}
};

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
setFormData((prev) => ({
...prev,
user: { ...prev.user, [name]: value },
}));
};

const handleConfirm = () => {
Expand Down
Loading
Loading