Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] 게더링 api 연동 #99

Merged
merged 42 commits into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
c6a9b83
feat: 탠스택 쿼리 툴킷 설정
joarthvr Dec 3, 2024
1d86313
refactor: 인덱스 적용 및 파일 삭제
joarthvr Dec 3, 2024
bad4200
refactor: 바뀐 타입 적용
joarthvr Dec 3, 2024
7e7d85d
refactor: 게더링 외 파일에서 바뀐 타입 적용
joarthvr Dec 3, 2024
c421649
feat: 게더링 작성 훅
joarthvr Dec 3, 2024
ce7288f
feat: 게더링 디테일 훅
joarthvr Dec 3, 2024
51381aa
feat: 게더링 리스트 훅
joarthvr Dec 3, 2024
732babb
feat: 커스텀 인피니트 쿼리 희정 버전 반영
joarthvr Dec 3, 2024
0bffe44
feat: 바뀐 타입 적용
joarthvr Dec 4, 2024
dd962cb
refactor: 게더링 카드의 버튼 눌렀을 때 카드 눌리는 현상 수정
joarthvr Dec 4, 2024
a952344
feat: 게더링 리스트 페이지 훅 연결
joarthvr Dec 4, 2024
a2c754d
feat: 게더링 api 타입 정리
joarthvr Dec 4, 2024
9793a78
feat: 도메인 연결
joarthvr Dec 4, 2024
94ea861
Merge branch 'dev' of https://github.com/prgrms-web-devcourse-final-p…
joarthvr Dec 4, 2024
f552472
style: 작성 버튼 스타일 수정
joarthvr Dec 4, 2024
f2d7973
style: 게더링 작성 페이지 스타일 수정
joarthvr Dec 4, 2024
a5a56a1
style: 게더링 태그 인풋 스타일 수정
joarthvr Dec 4, 2024
2cbf3ba
refactor: 쓸모 없는 내용 삭제
joarthvr Dec 4, 2024
e0790f4
style: 게더링 작성 페이지 스타일 수정
joarthvr Dec 4, 2024
7f56a3e
feat: 게더링 디테일 페이지 유저 정보 유저 스토어 연동
joarthvr Dec 4, 2024
e3b4519
feat: 게더링 리스트 페이지 사이드바 필터링 연동
joarthvr Dec 4, 2024
ca0d6a9
refactor: personnel 타입 수정
joarthvr Dec 4, 2024
e899856
reafactor: 게더링 디테일 페이지 뒤로가기 버튼 추가
joarthvr Dec 4, 2024
d2559b3
refactor: 게더링 리스트 똑같은 페이지 반복되는 오류 수정
joarthvr Dec 5, 2024
ffe8c0f
feat: 게더링 디테일 페이지 api 연동
joarthvr Dec 5, 2024
37ba3ca
feat: 게더링 리스트 필터링 기능 구현
joarthvr Dec 5, 2024
ad39d5c
feat: 포트폴리오 리스트 페이지 api 연동 및 구현
joarthvr Dec 5, 2024
806462c
Merge branch 'dev' of https://github.com/prgrms-web-devcourse-final-p…
joarthvr Dec 6, 2024
75048a7
fix: 머지 충돌에러 수정
joarthvr Dec 6, 2024
bd12dc9
fix: 게더링 리스트 payload 오류 수정
joarthvr Dec 6, 2024
fdf7c0c
feat: 게더링 리스트
joarthvr Dec 7, 2024
3cbba3f
Merge branch 'dev' of https://github.com/prgrms-web-devcourse-final-p…
joarthvr Dec 7, 2024
d6efda5
feat: 게더링 수정 기능
joarthvr Dec 7, 2024
5e35e10
feat: 게더링 삭제 기능 훅
joarthvr Dec 7, 2024
dd95201
feat: 게더링 모집완료 기능 훅
joarthvr Dec 7, 2024
cfb5c01
feat: 게더링 수정, 삭제, 모집완료 기능 적용
joarthvr Dec 7, 2024
6c9d329
fix: 데이트피커 타입 오류 수정
joarthvr Dec 7, 2024
a3436db
feat: 메인 포트폴리오 리스트 위젯 구현
joarthvr Dec 7, 2024
4999939
fix: a 태크로 변경
joarthvr Dec 8, 2024
c9fcaf5
refactor: 로더 추가
joarthvr Dec 8, 2024
4076162
fix: 배포 오류 수정
joarthvr Dec 8, 2024
c23a50a
feat: 포폴 조회수 증가 연동
joarthvr Dec 8, 2024
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
4 changes: 4 additions & 0 deletions src/app/appRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ const AppRouter = () => {
path: '/gathering/write',
element: <WriteGatheringPage />,
},
{
path: '/gathering/edit/:gatheringId',
element: <WriteGatheringPage/>,
},
{
path: '/gathering/:gatheringId',
element: <GatheringDetailPage />,
Expand Down
2 changes: 0 additions & 2 deletions src/app/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';

import App from './App';
import { worker } from '../mocks/browser';

import './styles/globals.scss';

// if (process.env.NODE_ENV === 'development') {
Expand Down
89 changes: 49 additions & 40 deletions src/features/gathering/api/gathering.api.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,62 @@
import type { GatheringDetailResponse } from '../model/gathering.dto';
import type { GatheringPageResponse, GatheringListParams } from '../model/dto/gathering.dto';
import type {
GatheringItemDto,
GatheringSortType,
GatheringPeriod,
GatheringPosition,
} from '../model/gathering.dto';
GatheringDetailResponse,
CreateGatheringRequest,
CreateGatheringResponse,
GatheringLikeResponse,
} from '../model/dto/request.dto';

import api from '@/shared/api/baseApi';

interface GetGatheringsParams {
sort?: GatheringSortType;
period?: GatheringPeriod;
position?: GatheringPosition;
status?: '모집중' | '모집완료';
size?: number;
gatheringId?: number;
}
export const gatheringApi = {
getGatherings: async (params: GatheringListParams): Promise<GatheringPageResponse> => {
// params를 URLSearchParams로 변환
const queryString = new URLSearchParams();

interface GetGatheringsParams {
sort?: GatheringSortType;
period?: GatheringPeriod;
position?: GatheringPosition;
status?: '모집중' | '모집완료';
size?: number;
nextLikeId?: number;
}
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== 'undefined') {
if (key === 'positions' && Array.isArray(value)) {
value.forEach(pos => {
queryString.append('positions', pos);
});
} else {
queryString.append(key, value.toString());
}
}
});

interface GatheringListResponse {
data: {
content: GatheringItemDto[];
hasNext: boolean;
nextLikeId: number;
};
timeStamp: string;
}

export const getGatheringList = {
getGatherings: async (params: GetGatheringsParams): Promise<GatheringListResponse> => {
const { data } = await api.get<GatheringListResponse>('/gathering', { params });
const { data } = await api.get<GatheringPageResponse>(`/gathering?${queryString.toString()}`);
return data;
},
};
interface GatheringDetailApi {
getGatheringById: (id: string) => Promise<GatheringDetailResponse>;
}

export const gatheringDetailApi: GatheringDetailApi = {
getGatheringById: async (id: string) => {
getGatheringDetail: async (id: string): Promise<GatheringDetailResponse> => {
const { data } = await api.get<GatheringDetailResponse>(`/gathering/${id}`);
return data;
},

create: async (requestData: CreateGatheringRequest): Promise<CreateGatheringResponse> => {
const { data } = await api.post<CreateGatheringResponse>('/gathering', {
...requestData,
gatheringImages: [],
});
return data;
},
update: async (
gatheringId: string,
data: CreateGatheringRequest,
): Promise<CreateGatheringResponse> => {
const response = await api.put<CreateGatheringResponse>(`/gathering/${gatheringId}`, data);
return response.data;
},

toggleLike: async (gatheringId: string): Promise<GatheringLikeResponse> => {
const { data } = await api.post<GatheringLikeResponse>(`/gathering/${gatheringId}/like`);
return data;
},
deleteGathering: async (gatheringId: string): Promise<void> => {
await api.delete(`/gathering/${gatheringId}`);
},
completeGathering: async (gatheringId: string): Promise<void> => {
await api.patch(`/gathering/${gatheringId}`);
},
};
63 changes: 0 additions & 63 deletions src/features/gathering/api/gathering.hook.ts

This file was deleted.

3 changes: 2 additions & 1 deletion src/features/gathering/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export { GatheringMarkdownEditor } from './ui/GatheringMarkdownEditor';
export { GatheringSelect } from './ui/GatheringSelect';
export { GatheringTagInput } from './ui/GatheringTagInput';
export { GatheringTitleInput } from './ui/GatheringTitIeInput';

export * from './lib/hooks';
export * from './model/index';
7 changes: 7 additions & 0 deletions src/features/gathering/lib/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export * from './useCompleteGathering';
export * from './useCreateGathering';
export * from './useDeleteGathering';
export * from './useGatheringDetail';
export * from './useGatheringLike';
export * from './useInfiniteGatheringId';
export * from './useUpdateGathering';
24 changes: 24 additions & 0 deletions src/features/gathering/lib/hooks/useCompleteGathering.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';

import { gatheringApi } from '../../api/gathering.api';

interface UseCompleteGatheringProps {
onSuccess?: () => void;
onError?: (error: unknown) => void;
}

export const useCompleteGathering = ({ onSuccess, onError }: UseCompleteGatheringProps = {}) => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: (gatheringId: string) => gatheringApi.completeGathering(gatheringId),

onSuccess: async () => {
// 관련 쿼리 무효화
await queryClient.invalidateQueries({ queryKey: ['/gatheringDetail'] });
onSuccess?.();
},

onError,
});
};
45 changes: 45 additions & 0 deletions src/features/gathering/lib/hooks/useCreateGathering.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import type { AxiosError } from 'axios';
import { useNavigate } from 'react-router-dom';

import { gatheringApi } from '../../api/gathering.api';
import type { CreateGatheringRequest, CreateGatheringResponse } from '../../model/dto/request.dto';

export const useCreateGathering = () => {
const queryClient = useQueryClient();
const navigate = useNavigate();

return useMutation<CreateGatheringResponse, AxiosError, CreateGatheringRequest>({
mutationFn: gatheringApi.create,
onSuccess: async response => {
try {
await queryClient.invalidateQueries({ queryKey: ['gathering'] });

const gatheringId = response.data?.gatheringId;
if (gatheringId) {
navigate(`/gathering/${gatheringId}`);
} else {
console.error('No gathering ID in response');
alert('게더링이 생성되었으나 상세 페이지로 이동할 수 없습니다.');
}
} catch (error) {
console.error('게더링 생성 후 처리 중 오류 발생:', error);
alert('게더링은 생성되었으나 추가 처리 중 오류가 발생했습니다.');
}
},
onError: (error: AxiosError) => {
if (error.response?.status === 401) {
alert('로그인이 필요합니다.');
navigate('/login');
} else if (error.response?.status === 403) {
alert('권한이 부족합니다. 다시 로그인해주세요.');
navigate('/login');
} else if (error.response?.status === 400) {
alert('입력하신 정보를 다시 확인해주세요.');
} else {
console.error('게더링 생성 실패:', error);
alert('게더링 생성에 실패했습니다. 잠시 후 다시 시도해주세요.');
}
},
});
};
27 changes: 27 additions & 0 deletions src/features/gathering/lib/hooks/useDeleteGathering.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';

import { gatheringApi } from '../../api/gathering.api';

interface UseDeleteGatheringProps {
onSuccess?: () => void;
onError?: (error: unknown) => void;
}

export const useDeleteGathering = ({ onSuccess, onError }: UseDeleteGatheringProps = {}) => {
const queryClient = useQueryClient();
const navigate = useNavigate();

return useMutation({
mutationFn: (gatheringId: string) => gatheringApi.deleteGathering(gatheringId),
onSuccess: async () => {
// 캐시 무효화
await queryClient.invalidateQueries({ queryKey: ['/gatheringDetail'] });
// 성공 콜백
onSuccess?.();
// 목록 페이지로 이동
navigate('/gathering');
},
onError,
});
};
13 changes: 13 additions & 0 deletions src/features/gathering/lib/hooks/useGatheringDetail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useQuery } from '@tanstack/react-query';
import type { AxiosError } from 'axios';

import { gatheringApi } from '../../api/gathering.api';
import type { GatheringDetailResponse } from '../../model/dto/request.dto';

export const useGatheringDetail = (gatheringId: string) => {
return useQuery<GatheringDetailResponse, AxiosError>({
queryKey: ['/gatheringDetail',gatheringId],
queryFn: () => gatheringApi.getGatheringDetail(gatheringId),
enabled: !!gatheringId,
});
};
72 changes: 72 additions & 0 deletions src/features/gathering/lib/hooks/useGatheringLike.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';

import { gatheringApi } from '../../api/gathering.api';
import type { GatheringLikeResponse, GatheringDetailResponse } from '../../model/dto/request.dto';

interface UseGatheringLikeProps {
gatheringId: string;
onSuccess?: (response: GatheringLikeResponse) => void;
onError?: (error: unknown) => void;
}

interface MutationContext {
previousDetail: GatheringDetailResponse | undefined;
}

export const useGatheringLike = ({ gatheringId, onSuccess, onError }: UseGatheringLikeProps) => {
const queryClient = useQueryClient();

return useMutation<GatheringLikeResponse, Error, void, MutationContext>({
mutationFn: () => gatheringApi.toggleLike(gatheringId),

onMutate: async () => {
await queryClient.cancelQueries({
queryKey: ['/gatheringDetail', gatheringId],
});

const previousDetail = queryClient.getQueryData<GatheringDetailResponse>([
'/gatheringDetail',
gatheringId,
]);

console.log('이전 상태:', previousDetail);
return { previousDetail };
},

onSuccess: response => {
console.log('좋아요 API 응답:', response);

const currentDetail = queryClient.getQueryData<GatheringDetailResponse>([
'/gatheringDetail',
gatheringId,
]);

if (currentDetail?.data) {
const newLikeCounts = response.data
? currentDetail.data.likeCounts + 1
: Math.max(0, currentDetail.data.likeCounts - 1);

queryClient.setQueryData<GatheringDetailResponse>(['/gatheringDetail', gatheringId], {
...currentDetail,
data: {
...currentDetail.data,
likeCounts: newLikeCounts,
},
});

console.log('캐시 업데이트 완료, 새로운 좋아요 수:', newLikeCounts);
}

onSuccess?.(response);
},

onError: (error, _, context) => {
console.log('에러 발생:', error);

if (context?.previousDetail) {
queryClient.setQueryData(['/gatheringDetail', gatheringId], context.previousDetail);
}
onError?.(error);
},
});
};
Loading
Loading