Skip to content

Commit

Permalink
[Feat] 게더링 api 연동 (#99)
Browse files Browse the repository at this point in the history
* feat: 탠스택 쿼리 툴킷 설정

* refactor: 인덱스 적용 및 파일 삭제

* refactor: 바뀐 타입 적용

* refactor: 게더링 외 파일에서 바뀐 타입 적용

* feat: 게더링 작성 훅

* feat: 게더링 디테일 훅

* feat: 게더링 리스트 훅

* feat: 커스텀 인피니트 쿼리 희정 버전 반영

* feat: 바뀐 타입 적용

* refactor: 게더링 카드의 버튼 눌렀을 때 카드 눌리는 현상 수정

* feat: 게더링 리스트 페이지 훅 연결

* feat: 게더링 api 타입 정리

* feat: 도메인 연결

* style: 작성 버튼 스타일 수정

* style: 게더링 작성 페이지 스타일 수정

* style: 게더링 태그 인풋 스타일 수정

* refactor: 쓸모 없는 내용 삭제

* style: 게더링 작성 페이지 스타일 수정

* feat: 게더링 디테일 페이지 유저 정보 유저 스토어 연동

* feat: 게더링 리스트 페이지 사이드바 필터링 연동

* refactor: personnel 타입 수정

* reafactor: 게더링 디테일 페이지 뒤로가기 버튼 추가

* refactor: 게더링 리스트 똑같은 페이지 반복되는 오류 수정

* feat: 게더링 디테일 페이지 api 연동

* feat: 게더링 리스트 필터링 기능 구현

* feat: 포트폴리오 리스트 페이지 api 연동 및 구현

* fix: 머지 충돌에러 수정

* fix: 게더링 리스트 payload 오류 수정

* feat: 게더링 리스트

* feat: 게더링 수정 기능

* feat: 게더링 삭제 기능 훅

* feat: 게더링 모집완료 기능 훅

* feat: 게더링 수정, 삭제, 모집완료  기능 적용

* fix: 데이트피커 타입 오류 수정

* feat: 메인 포트폴리오 리스트 위젯 구현

* fix: a 태크로 변경

* refactor: 로더 추가

* fix: 배포 오류 수정

* feat: 포폴 조회수 증가 연동
  • Loading branch information
joarthvr authored Dec 8, 2024
1 parent 450c192 commit 9d07a47
Show file tree
Hide file tree
Showing 76 changed files with 2,247 additions and 691 deletions.
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

1 comment on commit 9d07a47

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚡ Lighthouse report for http://localhost:3000/

Category Score
🔴 Performance 30
🟠 Accessibility 83
🟢 Best Practices 92
🟠 SEO 85

Detailed Metrics

Metric Value
🔴 First Contentful Paint 55.3 s
🔴 Largest Contentful Paint 96.2 s
🟠 Total Blocking Time 440 ms
🟠 Cumulative Layout Shift 0.162
🔴 Speed Index 72.5 s

Please sign in to comment.