Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
1aca2c1
:wrench: [Chore]: tanstack Query 및 관련 의존성 추가
zero0205 Feb 13, 2025
1a69f8b
:wrench: [Chore]: Tanstack Query ESLint 설정 추가
zero0205 Feb 14, 2025
b09504d
:wrench: [Chore]: Tanstack Query 초기 설정
zero0205 Feb 16, 2025
1c1d9ce
:recycle: [Refactor]: Attendance 컴포넌트 데이터 페칭 로직 useQuery로 변경
zero0205 Feb 17, 2025
6f48680
:recycle: [Refactor]: Attendance 관련 데이터 페칭 로직 entities로 분리
zero0205 Feb 17, 2025
414a7b5
:recycle: [Refactor]: Profile 컴포넌트 유저 데이터 페칭 로직 useQuery로 변경
zero0205 Feb 18, 2025
1b8baed
:recycle: [Refactor]: profile과 profileImage 데이터 페칭 로직 entities로 분리
zero0205 Feb 18, 2025
f68820e
:recycle: [Refactor]: bookmark를 features 레이어로 분리
zero0205 Feb 18, 2025
3f2ffbc
:sparkles: [Feat]: axios 응답 에러 핸들링 추가
zero0205 Feb 18, 2025
5f69e73
:recycle: [Refactor]: bookmark 조회, 생성, 삭제 로직 useQuery와 useMutation으로 변경
zero0205 Feb 18, 2025
efd9de4
:recycle: [Refactor]: 라이브 시청페이지에서 라이브 정보 페칭 로직 useQuery로 변경
zero0205 Feb 27, 2025
ef225ed
:recycle: [Refactor]: 녹화 목록 페칭 로직 useQuery로 변경
zero0205 Feb 27, 2025
e2b40c9
:recycle: [Refactor]: 방송 목록 페칭 로직을 useInfiniteQuery로 변경
zero0205 Feb 27, 2025
5fcb837
:recycle: [Refactor]: liveList를 모두 livePreviewList로 이름 변경
zero0205 Mar 2, 2025
a480e91
:recycle: [Refactor]: 유저 정보 수정 로직 useMutation으로 변경
zero0205 Mar 2, 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
15 changes: 12 additions & 3 deletions apps/client/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

"parserOptions": {
"project": ["./tsconfig.json"],
"tsconfigRootDir": "./apps/client",
"ecmaVersion": 12,
"sourceType": "module",
"ecmaFeatures": {
Expand All @@ -15,16 +16,20 @@
"es2021": true
},

"extends": ["airbnb", "airbnb/hooks", "plugin:@typescript-eslint/recommended", "prettier"],
"extends": [
"airbnb",
"airbnb/hooks",
"plugin:@typescript-eslint/recommended",
"plugin:@tanstack/query/recommended",
"prettier"
],

"settings": {
"react": {
"version": "detect"
}
},

"plugins": ["prettier"],

"rules": {
// React 관련 규칙
"react/react-in-jsx-scope": "off",
Expand Down Expand Up @@ -59,6 +64,10 @@
// 접근성 관련 규칙
"jsx-a11y/media-has-caption": "off",

// tanstack query 관련 규칙
"@tanstack/query/exhaustive-deps": "error",
"@tanstack/query/stable-query-client": "error",

// 기타 규칙
"no-param-reassign": [
"warn",
Expand Down
3 changes: 3 additions & 0 deletions apps/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-toast": "^1.2.2",
"@tanstack/react-query": "^5.66.0",
"@tanstack/react-query-devtools": "^5.66.0",
"axios": "^1.7.7",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
Expand All @@ -31,6 +33,7 @@
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@tanstack/eslint-plugin-query": "^5.66.1",
"@types/node": "^20.3.1",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
Expand Down
15 changes: 15 additions & 0 deletions apps/client/src/app/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { RouterProvider } from 'react-router-dom';
import { router } from './routes';

const queryClient = new QueryClient();

export function App() {
return (
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
<ReactQueryDevtools initialIsOpen={false} buttonPosition="bottom-left" position="right" />
</QueryClientProvider>
);
}
2 changes: 2 additions & 0 deletions apps/client/src/app/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { App } from './App';
export { Providers } from './providers';
10 changes: 10 additions & 0 deletions apps/client/src/entities/attendance/api/attendanceApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { axiosInstance } from '@/shared/api';
import { AttendanceData, AttendanceResponse } from '../model/types';

export const fetchAttendance = async (): Promise<AttendanceData[]> => {
const { data } = await axiosInstance.get<AttendanceResponse>('/v1/members/attendance');
if (!data.success) {
throw new Error(data.message || '출석부 조회에 실패했습니다.');
}
return data.data.attendances;
};
3 changes: 3 additions & 0 deletions apps/client/src/entities/attendance/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type { AttendanceData, AttendanceResponse } from './model/types';
export { useAttendanceList } from './model/queries';
export { fetchAttendance } from './api/attendanceApi';
21 changes: 21 additions & 0 deletions apps/client/src/entities/attendance/model/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useQuery } from '@tanstack/react-query';
import { fetchAttendance } from '@/entities/attendance/api/attendanceApi';
import { AttendanceData } from './types';

export const useAttendanceList = () => {
const {
data: attendanceList,
error,
isLoading,
} = useQuery<AttendanceData[], Error>({
queryKey: ['attendance'],
queryFn: fetchAttendance,
staleTime: 1000 * 60,
});

return {
attendanceList,
error,
isLoading,
};
};
17 changes: 17 additions & 0 deletions apps/client/src/entities/attendance/model/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export type AttendanceData = {
attendanceId: number;
date: string;
startTime: string;
endTime: string;
isAttendance: boolean;
};

export type AttendanceResponse = {
success: boolean;
status: string;
message: string;
data: {
memberId: number;
attendances: AttendanceData[];
};
};
14 changes: 14 additions & 0 deletions apps/client/src/entities/record/api/recordApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { axiosInstance } from '@/shared/api';

export const getRecordList = async (attendanceId: string | undefined) => {
if (!attendanceId) {
throw new Error('attendanceId가 없습니다.');
}

const response = await axiosInstance.get(`/v1/records/${attendanceId}`);

if (!response.data.success) {
throw new Error(response.data.message);
}
return response.data.data;
};
2 changes: 2 additions & 0 deletions apps/client/src/entities/record/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export type { RecordData } from './model/types';
export { useRecordList } from './model/useRecordList';
6 changes: 6 additions & 0 deletions apps/client/src/entities/record/model/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type RecordData = {
recordId: number;
title: string;
video: string;
date: string;
};
9 changes: 9 additions & 0 deletions apps/client/src/entities/record/model/useRecordList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useQuery } from '@tanstack/react-query';
import { getRecordList } from '../api/recordApi';

export const useRecordList = (attendanceId: string | undefined) => useQuery({
queryKey: ['record-list', attendanceId],
queryFn: () => getRecordList(attendanceId),
staleTime: 1000 * 60 * 60,
enabled: !!attendanceId,
});
28 changes: 28 additions & 0 deletions apps/client/src/entities/user/api/userApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { UserData, MutationUserData } from '@/entities/user';
import { axiosInstance } from '@/shared/api';

export const getUserInfo = async (): Promise<UserData> => {
const response = await axiosInstance.get('/v1/members/info');
if (!response.data.success) {
throw new Error(response.data.message);
}
return response.data.data;
};

export const getUserProfileImage = async (): Promise<string> => {
const response = await axiosInstance.get('/v1/members/profile-image');
if (!response.data.success) {
throw new Error(response.data.message);
}

return response.data.data.profileImage;
};

export const patchUserInfo = async (formData: MutationUserData) => {
const response = await axiosInstance.patch('/v1/members/info', formData);
if (!response.data.success) {
throw new Error(response.data.message);
}

return response.data;
};
3 changes: 3 additions & 0 deletions apps/client/src/entities/user/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type { UserData, MutationUserData } from './model/types';
export { getUserInfo, getUserProfileImage, patchUserInfo } from './api/userApi';
export { useUserData, useProfileImage, useUserDataMutation } from './model/queries';
55 changes: 55 additions & 0 deletions apps/client/src/entities/user/model/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { getUserInfo, getUserProfileImage, patchUserInfo } from '@/entities/user';
import { userKeys } from './queryFactory';
import { useToast } from '@/shared/lib';
import { MutationUserData } from './types';

export const useUserData = () => {
const {
data: userData,
isLoading,
error,
} = useQuery({ queryKey: userKeys.profile(), queryFn: getUserInfo, staleTime: 1000 * 60 * 10 });

return { userData, isLoading, error };
};

export const useProfileImage = (isLoggedIn: boolean) => {
const { data, isLoading, error } = useQuery({
queryKey: userKeys.profileImage(),
queryFn: getUserProfileImage,
enabled: isLoggedIn,
staleTime: 1000 * 60 * 10,
});

return { profileImgUrl: data, isLoading, error };
};

export const useUserDataMutation = (onSuccessCallback: () => void) => {
const queryClient = useQueryClient();
const { toast } = useToast();

return useMutation({
mutationFn: (formData: MutationUserData) => patchUserInfo(formData),
onSuccess: async data => {
if (data.success) {
toast({ title: '프로필 업데이트 성공', variant: 'default' });
await queryClient.invalidateQueries({ queryKey: userKeys.profile(), refetchType: 'active' });
onSuccessCallback();
} else {
toast({
title: '프로필 업데이트 실패',
description: data.message || '알 수 없는 오류가 발생했습니다',
variant: 'destructive',
});
}
},
onError: error => {
toast({
title: '프로필 업데이트 실패',
description: error instanceof Error ? error.message : '네트워크 오류가 발생했습니다',
variant: 'destructive',
});
},
});
};
5 changes: 5 additions & 0 deletions apps/client/src/entities/user/model/queryFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const userKeys = {
all: ['user'] as const,
profile: () => [...userKeys.all, 'profile'] as const,
profileImage: () => [...userKeys.all, 'profile-image'] as const,
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,12 @@ export type UserData = {
contacts: Contacts;
profileImage: string;
};

export type MutationUserData = {
contacts: {
email: string;
github: string;
blog: string;
linkedin: string;
};
} & Pick<UserData, 'camperId' | 'name' | 'field'>;
10 changes: 10 additions & 0 deletions apps/client/src/features/bookmark/api/bookmarkApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { axiosInstance } from '@/shared/api';
import { BookmarkData } from '@/features/bookmark';

export const getBookmarks = () => axiosInstance.get('/v1/bookmarks').then(res => res.data.data.bookmarks);

export const addBookmark = (newBookmark: BookmarkData) =>
axiosInstance.post('/v1/bookmarks', newBookmark).then(res => res.data.data);

export const deleteBookmark = (bookmarkId: number) =>
axiosInstance.delete(`/v1/bookmarks/${bookmarkId}`).then(res => res.data);
4 changes: 4 additions & 0 deletions apps/client/src/features/bookmark/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { Bookmark } from './ui/Bookmark';
export type { BookmarkData } from './model/types';
export { useBookmarkMutation } from './model/queries';
export { getBookmarks, addBookmark, deleteBookmark } from './api/bookmarkApi';
44 changes: 44 additions & 0 deletions apps/client/src/features/bookmark/model/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// src/features/bookmark/model/useBookmarkMutation.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useToast } from '@/shared/lib';
import { addBookmark, deleteBookmark } from '@/features/bookmark/api/bookmarkApi';
import { BookmarkData } from './types';

export const useBookmarkMutation = ({ onAddSuccess }: { onAddSuccess?: () => void }) => {
const queryClient = useQueryClient();
const { toast } = useToast();

const { mutate: mutateAdd } = useMutation<BookmarkData, Error, BookmarkData>({
mutationFn: addBookmark,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['bookmarks'] });
onAddSuccess?.();
},
onError: (error: Error) => {
toast({
variant: 'destructive',
title: '북마크 생성 실패',
description: error.message,
});
},
});

const { mutate: mutateDelete } = useMutation({
mutationFn: deleteBookmark,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['bookmarks'] });
},
onError: (error: Error) => {
toast({
variant: 'destructive',
title: '북마크 삭제 실패',
description: error.message,
});
},
});

return {
mutateAdd,
mutateDelete,
};
};
Loading