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

모임상세, 모임 참여 API 연결 로직 구현 #79

Merged
merged 14 commits into from
Jul 22, 2024
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
16 changes: 16 additions & 0 deletions frontend/src/apis/apiconfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const defaultHeaders = {
Copy link
Contributor

Choose a reason for hiding this comment

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

defaultHeader에 대해서 같이 조금 더 생각을 해봐야 할 것 같아요

저는 json이 아닌 다른 요청들도 필요할 수 있다고 생각이 들어요.(제가 잘 몰라서 그럼)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

넵 같은 의견입니다. 다만 현재는 header의 형태가 하나여서 이렇게 네이밍한것일 뿐 언제나 수정 가능합니다. 좋은 의견이네요

'Content-Type': 'application/json',
};

export const defaultOptions = {
headers: defaultHeaders,
};

export const checkStatus = async (response: Response) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

공통 API 에러처리 분리

const statusHead = Math.floor(response.status / 100);
if (statusHead === 4 || statusHead === 5) {
const json = await response.json();
throw new Error(json.message);
}
return response;
};
Comment on lines +9 to +16
Copy link
Contributor

Choose a reason for hiding this comment

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

checkStatus 함수 분리, 네이밍 아주 좋네요~
근데 config라는 파일에 있는 게 어색한 것 같습니다 :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

네이밍도 한번 고민해보아야 겠네요

Copy link
Contributor Author

Choose a reason for hiding this comment

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

다만 header라는 것이 api통신을 위한 환경설정이라는 의미로 붙인 것이긴 합니다.

31 changes: 18 additions & 13 deletions frontend/src/apis/gets.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import ENDPOINTS from '@_apis/endPoints';
import { GetMoim } from '@_apis/responseTypes';
import { GetMoim, GetMoims } from '@_apis/responseTypes';
import { MoimInfo } from '@_types/index';
import { checkStatus, defaultOptions } from './apiconfig';

const defaultGetOptions = {
method: 'GET',
...defaultOptions,
};
export const getMoims = async (): Promise<MoimInfo[]> => {
const url = ENDPOINTS.moims;

const options = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
};
const response = await fetch(url, defaultGetOptions);

checkStatus(response);
const json = (await response.json()) as GetMoims;
return json.data.moims;
};

export const getMoim = async (moimId: number): Promise<MoimInfo> => {
const url = `${ENDPOINTS.moim}/${moimId}`;

const response = await fetch(url, options);
const response = await fetch(url, defaultGetOptions);

const statusHead = Math.floor(response.status / 100);
if (statusHead === 4 || statusHead === 5) {
throw new Error('모임을 받아오지 못했습니다.');
}
checkStatus(response);

const json = (await response.json()) as GetMoim;
return json.data.moims;
return json.data;
};
33 changes: 23 additions & 10 deletions frontend/src/apis/posts.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
import ENDPOINTS from '@_apis/endPoints';
import { MoimInfo } from '@_types/index';
import { MoimInputInfo } from '@_types/index';
import { PostMoim } from '@_apis/responseTypes';
import { checkStatus, defaultOptions } from './apiconfig';

export const postMoim = async (moim: MoimInfo): Promise<number> => {
const defaultPostOptions = {
method: 'POST',
...defaultOptions,
};
export const postMoim = async (moim: MoimInputInfo): Promise<number> => {
const url = ENDPOINTS.moim;

const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
...defaultPostOptions,
body: JSON.stringify(moim),
};

const response = await fetch(url, options);

const statusHead = Math.floor(response.status / 100);
if (statusHead === 4 || statusHead === 5)
throw new Error('모임을 업데이트하지 못했습니다.');
checkStatus(response);

const json = (await response.json()) as PostMoim;
return json.id;
return json.data;
};

export const postJoinMoim = async (moimId: number) => {
const url = `${ENDPOINTS.moims}/join`;

const options = {
...defaultPostOptions,
body: JSON.stringify({ moimId }),
};

const response = await fetch(url, options);

await checkStatus(response);
};
8 changes: 6 additions & 2 deletions frontend/src/apis/responseTypes.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { MoimInfo } from '../types';

export interface GetMoim {
export interface GetMoims {
data: { moims: MoimInfo[] };
}

export interface GetMoim {
data: MoimInfo;
}

export interface PostMoim {
id: number;
data: number;
}
14 changes: 9 additions & 5 deletions frontend/src/components/Card/MoimCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@ import {
} from '../../utils/formatters';
Copy link
Contributor

Choose a reason for hiding this comment

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

이렇게 작업 공간이 겹치는 파일이 있을 때(ex:api가 component를 건드는 경우) 어떻게 하면 좋을지 생각해봐야 할 것 같아요

Copy link
Contributor Author

Choose a reason for hiding this comment

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

아마 merge하는 사람이 충돌을 해결해야 할 것 같은데 추후에 다시 의견을 나누어 보죠


import { MoimInfo } from '../../types';
import { HTMLProps } from 'react';

interface MoimCardProps {
interface MoimCardProps extends HTMLProps<HTMLDivElement> {
moimInfo: MoimInfo;
}

export default function MoimCard(props: MoimCardProps) {
const {
moimInfo: { title, date, time, place, maxPeople },
moimInfo: { title, date, time, place, maxPeople, currentPeople },
...args
} = props;

return (
<div css={S.cardBox}>
<div css={S.cardBox} {...args}>
<h2 css={S.cardTitle}>{title}</h2>
<div css={S.subjectBox}>
<span css={S.subjectTag}>날짜 및 시간</span>
Expand All @@ -30,8 +32,10 @@ export default function MoimCard(props: MoimCardProps) {
<span css={S.subjectInfo}>{place}</span>
</div>
<div css={S.subjectBox}>
<span css={S.subjectTag}>최대인원수</span>
<span css={S.subjectInfo}>{maxPeople}명</span>
<span css={S.subjectTag}>인원수</span>
<span css={S.subjectInfo}>
최대{maxPeople}명 / 현재 {currentPeople}명
</span>
</div>
</div>
);
Expand Down
14 changes: 12 additions & 2 deletions frontend/src/components/MoimCardList/MoimCardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,28 @@ import * as S from '@_components/MoimCardList/MoimCardList.style';

import MoimCard from '@_components/Card/MoimCard';
import { MoimInfo } from '@_types/index';
import { useNavigate } from 'react-router-dom';

interface MoimCardListProps {
moimInfos: MoimInfo[];
}

export default function MoimCardList(props: MoimCardListProps) {
const { moimInfos } = props;
const navigate = useNavigate();
const handleMoimCard = (moimId: number) => {
navigate(`/moim/${moimId}`);
};

return (
<div css={S.cardListSection}>
{moimInfos.map((moimInfo, index) => {
{moimInfos.map((moimInfo) => {
return (
<MoimCard key={`${moimInfo.title}${index}`} moimInfo={moimInfo} />
<MoimCard
key={moimInfo.moimId}
moimInfo={moimInfo}
onClick={() => handleMoimCard(moimInfo.moimId)}
/>
);
})}
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/constants/queryKeys.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
const QUERY_KEYS = { moims: 'moims' };
const QUERY_KEYS = { moims: 'moims', moim: 'moim' };

export default QUERY_KEYS;
1 change: 1 addition & 0 deletions frontend/src/constants/routes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const ROUTES = {
main: '/',
addMoim: '/add-moim',
detail: '/moim/:moimId',
};

export default ROUTES;
7 changes: 4 additions & 3 deletions frontend/src/hooks/mutaions/useAddMoim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import QUERY_KEYS from '@_constants/queryKeys';
import { postMoim } from '@_apis/posts';

export default function useAddMoim(onSuccess: () => void) {
export default function useAddMoim(onSuccess: (moimId: number) => void) {
const queryClient = useQueryClient();

return useMutation({
mutationFn: postMoim,
onSuccess: () => {
onSuccess: (moimId: number) => {
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.moims] });
onSuccess();

onSuccess(moimId);
},
});
}
18 changes: 18 additions & 0 deletions frontend/src/hooks/mutaions/useJoinMoim.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';

import QUERY_KEYS from '@_constants/queryKeys';
import { postJoinMoim } from '@_apis/posts';

export default function useJoinMoim(onSuccess: () => void) {
const queryClient = useQueryClient();

return useMutation({
mutationFn: postJoinMoim,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: [QUERY_KEYS.moim],
});
onSuccess();
},
Comment on lines +11 to +16
Copy link
Contributor

Choose a reason for hiding this comment

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

참여 신청이 성공했을 때, QUERY_KEYS.moim을 refetching 해야하는 이유가 있을까요??

Copy link
Contributor Author

Choose a reason for hiding this comment

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

페이지에서 현재 참여 인원수를 반영하기 위해 refetching 하고 있습니다.

});
}
12 changes: 12 additions & 0 deletions frontend/src/hooks/queries/useMoim.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import QUERY_KEYS from '@_constants/queryKeys';
import { getMoim } from '@_apis/gets';
import { useQuery } from '@tanstack/react-query';

export default function useMoim(moimId: number) {
const { data: moim, isLoading } = useQuery({
queryKey: [QUERY_KEYS.moim, moimId],
queryFn: () => getMoim(moimId),
});
Comment on lines +6 to +9
Copy link
Contributor

Choose a reason for hiding this comment

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

queryKey를 두 개를 두면 nesting이 되는 걸까요?? 꿀팁 ㄱㅅㄱㅅ~


return { moim, isLoading };
}
4 changes: 2 additions & 2 deletions frontend/src/pages/MoimCreationPage/MoimCreatePage.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import {
validateTitle,
} from './MoimCreatePage.util';

import { MoimInfo } from '../../types';
import { MoimInputInfo } from '../../types';

const useMoimInfoInput = () => {
const [inputData, setInputData] = useState<MoimInfo>({
const [inputData, setInputData] = useState<MoimInputInfo>({
title: '',
date: '',
time: '',
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/pages/MoimCreationPage/MoimCreationPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import Button from '@_components/Button/Button';
import FormLayout from '@_layouts/FormLayout/FormLayout';
import LabeledInput from '@_components/Input/MoimInput';
import MOIM_INPUT_INFOS from './MoimCreationPage.constant';
import ROUTES from '@_constants/routes';
import useAddMoim from '@_hooks/mutaions/useAddMoim';
import useMoimInfoInput from './MoimCreatePage.hook';
import { useNavigate } from 'react-router-dom';
import { useState } from 'react';

export default function MoimCreationPage() {
const navigate = useNavigate();
const { mutate } = useAddMoim(() => navigate(ROUTES.main));
const { mutate } = useAddMoim((moimId: number) => {
navigate(`/${moimId}`);
});
const [isSubmitted, setIsSubmitted] = useState(false);

const { inputData, handleChange, isValidMoimInfoInput } = useMoimInfoInput();
Expand All @@ -26,7 +27,7 @@ export default function MoimCreationPage() {

return (
<FormLayout>
<FormLayout.Header onBackArrowClick={() => navigate(ROUTES.main)}>
<FormLayout.Header onBackArrowClick={() => navigate(-1)}>
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

모임등록하기
</FormLayout.Header>

Expand Down
36 changes: 36 additions & 0 deletions frontend/src/pages/TempDetail/TempDetailPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import useJoinMoim from '@_hooks/mutaions/useJoinMoim';
import useMoim from '@_hooks/queries/useMoim';
import { useParams } from 'react-router-dom';

export default function TempDetailPage() {
const params = useParams();
const moimId = Number(params.moimId);

const { moim, isLoading } = useMoim(moimId);
const { mutate } = useJoinMoim(() => {
alert('참여했습니다.');
});

if (isLoading) {
return <div>Loading...</div>;
}
if (!moim) {
return <div>No data found</div>;
}

// moim 데이터를 출력합니다.
return (
<div>
<h1>{moim.title}</h1>
<p>{moim.description}</p>
<p>Place: {moim.place}</p>
<p>Date: {moim.date}</p>
<p>Time: {moim.time}</p>
<p>Max People: {moim.maxPeople}</p>
<p>currentPeople: {moim.currentPeople}</p>
<p>Author: {moim.authorNickname}</p>

<button onClick={() => mutate(moimId)}>참여하기</button>
</div>
);
}
6 changes: 5 additions & 1 deletion frontend/src/queryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ const createQueryClient = () => {
queries: {
networkMode: 'always',
},
mutations: {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

queryClient 에러 처리 추가

onError: (error) => alert(error.message),
networkMode: 'always',
},
},
queryCache: new QueryCache({
onError: (error) => {
alert(error);
alert(error instanceof Error ? error.message : 'An error occurred');
},
}),
});
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import MainPage from '@_pages/MainPage/MainPage';
import MoimCreationPage from '@_pages/MoimCreationPage/MoimCreationPage';
import ROUTES from '@_constants/routes';
import { createBrowserRouter } from 'react-router-dom';
import TempDetailPage from '@_pages/TempDetail/TempDetailPage';

const router = createBrowserRouter([
{
Expand All @@ -12,6 +13,10 @@ const router = createBrowserRouter([
path: ROUTES.addMoim,
element: <MoimCreationPage />,
},
{
Copy link
Contributor

Choose a reason for hiding this comment

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

이 부분에 대한 커밋이 안올라온 것 같아요ㅜㅜ

다시 올려주시면 좋을 것 같아요.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

올렸습니다. 구현 자체랑 상관없어서 올리지 않았는데 올리는 것이 이해하는데 더 좋았겠네요

path: ROUTES.detail,
element: <TempDetailPage />,
},
]);

export default router;
4 changes: 4 additions & 0 deletions frontend/src/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
export interface MoimInfo {
moimId: number;
title: string;
date: string;
time: string;
place: string;
maxPeople: number;
currentPeople: number;
authorNickname: string;
description?: string;
}

export type MoimInputInfo = Omit<MoimInfo, 'moimId' | 'currentPeople'>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Omit 굿굿~~

Copy link
Contributor

Choose a reason for hiding this comment

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

👍