Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
cb127ac
✨[Feat] 모임 목록 쿼리값 직접 사용 및 신청 가능 필터링 구현 #290
wynter24 Jan 4, 2025
23590af
🐛[Fix] 찜하기 낙관적 업데이트하기 #290
wynter24 Jan 5, 2025
26ff912
♻️[Refactor] 기본 필터 데이터 공통으로 분리 #290
wynter24 Jan 5, 2025
a7c2ec7
♻️[Refactor] 찜 관련 낙관전 업데이트 로직 공통으로 분리 #290
wynter24 Jan 5, 2025
74998db
✨[Feat] 찜하기, 삭제하기에 낙관적 업데이트 적용 #290
wynter24 Jan 5, 2025
fe86d10
💬[Comment] 불필요한 주석 제거 #290
wynter24 Jan 5, 2025
9909924
💄[Design] 리뷰 평점이 NaN일 경우 0으로 표시 #290
wynter24 Jan 5, 2025
41261c7
✨[Feat] 주최자 모임 취소하기 후 모임 목록 캐시 무효화 #290
wynter24 Jan 5, 2025
6320b55
🐛[Fix] 사용되지 않는 데이터 제거 #290
wynter24 Jan 5, 2025
e022adb
Merge pull request #293 from codeit-team3/290-feature-api-수정-및-추가-사항-적용
cloud0406 Jan 5, 2025
30226b3
🐛 [Bug] 찜하기 동작 에러 (#294)
cloud0406 Jan 5, 2025
f063546
✨[Feat] 프로필 이미지 수정하기 #287 (#295)
sunnwave Jan 5, 2025
23fb3ee
메시지 입력 input 하단에 고정
haegu97 Jan 5, 2025
0c18142
최종 발표 전 리펙토링
haegu97 Jan 5, 2025
1c56600
최종 발표 전 리펙토링2
haegu97 Jan 5, 2025
2083659
console.log 삭제
haegu97 Jan 5, 2025
4efde5b
로딩컴포넌트로 교체
haegu97 Jan 5, 2025
42ac92f
빌드에러 수정
haegu97 Jan 5, 2025
818bf8a
빌드에러 수정
haegu97 Jan 5, 2025
84c1327
Merge pull request #297 from codeit-team3/296-refactor-최종발표-전-리펙토링
haegu97 Jan 5, 2025
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
40 changes: 30 additions & 10 deletions src/api/book-club/react-query/customHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '../index';
import { WriteReviewParams } from '../types';
import { AxiosError } from 'axios';
import { likeOnError, likeOnMutate } from './likeOptimisticUpdate';

export function useBookClubCreateMutation() {
const queryClient = useQueryClient();
Expand Down Expand Up @@ -89,6 +90,9 @@ export function useCancelBookClub() {
return useMutation({
mutationFn: (id: number) => bookClubMainAPI.cancel(id),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: bookClubs.list().queryKey,
});
queryClient.invalidateQueries({
queryKey: bookClubs.my()._ctx.created().queryKey,
});
Expand All @@ -104,14 +108,22 @@ export function useLikeBookClub() {

return useMutation<void, AxiosError<{ message: string }>, number>({
mutationFn: (id: number) => bookClubLikeAPI.like(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({
queryKey: bookClubs.list().queryKey,
});

onMutate: async (id) => {
return likeOnMutate(queryClient, id, true);
},
//TODO: 로직 확인 후 변경 필요
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: bookClubs.detail(id).queryKey,
queryKey: bookClubs._def,
});
},

onError: (_error, id, context) => {
if (context) {
likeOnError(queryClient, id, context);
}
},
});
}

Expand All @@ -120,13 +132,21 @@ export function useUnLikeBookClub() {

return useMutation<void, AxiosError<{ message: string }>, number>({
mutationFn: (id: number) => bookClubLikeAPI.unlike(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({
queryKey: bookClubs.list().queryKey,
});

onMutate: async (id) => {
return likeOnMutate(queryClient, id, false);
},
//TODO: 로직 확인 후 변경 필요
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: bookClubs.detail(id).queryKey,
queryKey: bookClubs._def,
});
},

onError: (_error, id, context) => {
if (context) {
likeOnError(queryClient, id, context);
}
},
});
}
65 changes: 65 additions & 0 deletions src/api/book-club/react-query/likeOptimisticUpdate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { QueryClient } from '@tanstack/react-query';
import { BookClub } from '@/types/bookclubs';
import { bookClubs } from './queries';
import { DEFAULT_FILTERS } from '@/lib/constants/filters';

export const likeOnMutate = async (
queryClient: QueryClient,
id: number,
isLiked: boolean,
) => {
const listQueryKey = bookClubs.list(DEFAULT_FILTERS).queryKey;
const detailQueryKey = bookClubs.detail(id).queryKey;

const previousBookClubs = queryClient.getQueryData<{
bookClubs: BookClub[];
}>(listQueryKey);

const previousDetail = queryClient.getQueryData<BookClub>(detailQueryKey);

// 목록 캐시 업데이트
if (previousBookClubs) {
queryClient.setQueryData(listQueryKey, {
...previousBookClubs,
bookClubs: previousBookClubs.bookClubs.map((club) =>
club.id === id ? { ...club, isLiked } : club,
),
});
}

// 상세 캐시 업데이트
if (previousDetail) {
queryClient.setQueryData(detailQueryKey, {
...previousDetail,
isLiked,
});
}

//TODO: 로직 확인 후 변경 필요
queryClient.invalidateQueries({
queryKey: bookClubs._def,
});

return { previousBookClubs, previousDetail };
};

export const likeOnError = (
queryClient: QueryClient,
id: number,
context: {
previousBookClubs?: { bookClubs: BookClub[] };
previousDetail?: BookClub;
},
) => {
const listQueryKey = bookClubs.list(DEFAULT_FILTERS).queryKey;
const detailQueryKey = bookClubs.detail(id).queryKey;

// 이전 상태 복구
if (context.previousBookClubs) {
queryClient.setQueryData(listQueryKey, context.previousBookClubs);
}

if (context.previousDetail) {
queryClient.setQueryData(detailQueryKey, context.previousDetail);
}
};
14 changes: 5 additions & 9 deletions src/app/chat/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function ChatRoomPage() {
const [isConnected, setIsConnected] = useState(false);

const { data } = useQuery(
bookClubs.my()._ctx.joined({ order: 'DESC', page: 1, size: 10 }),
bookClubs.my()._ctx.joined({ order: 'DESC', page: 1, size: 100 }),
);

const bookClubDetail = data?.bookClubs?.find(
Expand All @@ -64,17 +64,13 @@ function ChatRoomPage() {

await new Promise((resolve, reject) => {
const checkConnection = setInterval(() => {
console.log(`소켓 연결 시도 ${attempts + 1}회`);

if (client?.connected) {
console.log('소켓 연결 성공!');
clearInterval(checkConnection);
resolve(true);
}

attempts++;
if (attempts >= maxAttempts) {
console.log('소켓 연결 최대 시도 횟수 초과');
clearInterval(checkConnection);
reject(new Error('소켓 연결 타임아웃'));
}
Expand Down Expand Up @@ -181,7 +177,7 @@ function ChatRoomPage() {
};

return (
<div className="flex w-full flex-1 flex-col gap-5 pb-10">
<div className="relative flex w-full flex-1 flex-col gap-5 pb-10">
<header className="flex w-full min-w-[336px] items-end bg-gray-light-02 px-[20px] py-[30px] sm:justify-between md:px-[24px] lg:px-[102px]">
<div className="flex w-full flex-col gap-5">
<div className="flex items-center justify-between">
Expand All @@ -192,12 +188,12 @@ function ChatRoomPage() {
className="bg-gray-light-02"
/>
<h3>채팅</h3>
<ParticipantCounter current={10} />
<ParticipantCounter current={bookClubDetail?.memberCount} />
</div>
<div>
<IconButton
icon={<HamburgerMenuIcon width={16} height={12} />}
onClick={() => console.log('메뉴 열기 버튼 클릭')}
onClick={() => {}}
className="bg-gray-light-02"
/>
</div>
Expand Down Expand Up @@ -226,7 +222,7 @@ function ChatRoomPage() {
onProfileClick={() => {}}
/>
</div>
<div className="fixed bottom-8 left-0 right-0 flex w-full items-center justify-between gap-3 bg-white px-4 sm:px-[24px] lg:px-[102px]">
<div className="sticky bottom-0 flex w-full items-center justify-between gap-3 bg-white">
<form className="w-full" onSubmit={handleSubmit}>
<MessageInput value={message} onChange={handleMessageChange} />
</form>
Expand Down
21 changes: 3 additions & 18 deletions src/components/common-layout/FilterBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,14 @@ import {
SearchSection,
FilterSection,
} from '@/components/common-layout';
import { BookClub, BookClubParams } from '@/types/bookclubs';
import { Dispatch, SetStateAction } from 'react';
import { BookClubParams } from '@/types/bookclubs';

interface FilterBarProps {
filters: BookClubParams;
handleFilterChange: (newFilter: Partial<BookClubParams>) => void;
bookClubs: BookClub[];
initialBookClubs: BookClub[];
setBookClubs: Dispatch<SetStateAction<BookClub[]>>;
}

function FilterBar({
filters,
handleFilterChange,
bookClubs,
initialBookClubs,
setBookClubs,
}: FilterBarProps) {
function FilterBar({ filters, handleFilterChange }: FilterBarProps) {
return (
<section className="flex w-full flex-col gap-y-3 px-[20px] pt-[20px] md:px-[24px] lg:px-[102px]">
<CategoryTabs filters={filters} onFilterChange={handleFilterChange} />
Expand All @@ -30,12 +20,7 @@ function FilterBar({
handleFilterChange({ searchKeyword: value })
}
/>
<FilterSection
bookClubs={bookClubs}
initialBookClubs={initialBookClubs}
setBookClubs={setBookClubs}
onFilterChange={handleFilterChange}
/>
<FilterSection onFilterChange={handleFilterChange} />
</section>
);
}
Expand Down
30 changes: 4 additions & 26 deletions src/components/common-layout/FilterSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,23 @@

import DropDown from '@/components/drop-down/DropDown';
import FilterCheckbox from '@/components/filter-checkbox/FilterCheckbox';
import { ChangeEvent, Dispatch, SetStateAction, useState } from 'react';
import { ChangeEvent, useState } from 'react';
import SortingButton from '@/components/sorting-button/SortingButton';
import { BookClub, BookClubParams } from '../../types/bookclubs';
import { BookClubParams } from '../../types/bookclubs';
import { getMeetingType, getMemberLimit } from '@/lib/utils/filterUtils';
import { clubStatus } from '@/lib/utils/clubUtils';

interface CategoryTabsProps {
bookClubs: BookClub[];
initialBookClubs: BookClub[];
setBookClubs: Dispatch<SetStateAction<BookClub[]>>;
onFilterChange: (newFilters: Partial<BookClubParams>) => void;
}

function FilterSection({
bookClubs,
initialBookClubs,
setBookClubs,
onFilterChange,
}: CategoryTabsProps) {
function FilterSection({ onFilterChange }: CategoryTabsProps) {
const [showAvailableOnly, setShowAvailableOnly] = useState(false); // 신청가능

const toggleAvailableOnly = (e: ChangeEvent<HTMLInputElement>) => {
const isChecked = e.target.checked;
setShowAvailableOnly(isChecked);

const filteredBookClubs = isChecked
? bookClubs.filter(
(club) =>
club.memberCount < club.memberLimit &&
clubStatus(
club.memberCount,
club.memberLimit,
club.endDate,
new Date(),
) !== 'closed',
)
: initialBookClubs;

setBookClubs(filteredBookClubs);
onFilterChange({ isAvailable: isChecked });
};

const updateMemberLimitFilter = (selectedValue: string | undefined) => {
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;
2 changes: 1 addition & 1 deletion src/constants/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export const NAV_ITEMS = [
{ id: 'bookco', href: '/bookclub', label: 'bookco' },
{ id: 'bookclub', href: '/bookclub', label: '책 모임' },
{ id: 'exchange', href: '/exchange', label: '책 교환' },
{ id: 'wish', href: '/wish', label: '찜 목록' },
// { id: 'wish', href: '/wish', label: '찜 목록' },
{ id: 'chat', href: '/chat', label: '채팅' },
] as const;

Expand Down
2 changes: 1 addition & 1 deletion src/features/auth/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const login = async (data: LoginFormData) => {
await getUserInfo();

const token = getCookie('auth_token');
console.log('token', token);

if (token) {
initializeSocket(token);
}
Expand Down
Loading
Loading