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

[FE] refactor: LandingPage에 리액트 쿼리 적용 및 리팩토링 #218

Merged
merged 20 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bbdb7d2
chore: LandingPage의 styles 파일 분리
ImxYJL Aug 3, 2024
bf4f592
fix: POST 요청을 하는 함수의 이름을 post~로 수정
ImxYJL Aug 3, 2024
d9fef29
feat: 그룹 데이터 생성 요청에 대한 MSW 핸들러 추가
ImxYJL Aug 3, 2024
8df4ef3
refactor: 모킹 데이터 값을 더 직관적으로 수정
ImxYJL Aug 3, 2024
fec364e
refactor: LandingPage를 ErrorSuspenseContainer가 감싸도록 수정
ImxYJL Aug 4, 2024
269655e
refactor: URL을 얻어오는 API에 react-query 적용 및 API 호출 함수 이름 수정
ImxYJL Aug 4, 2024
bc2efd8
chore: LandingPage 하위 컴포넌트들의 index 파일 추가 및 적용
ImxYJL Aug 4, 2024
117cb0e
refactor: groupAccessCode 관련 msw 핸들러 추가 및 에러 상태(없는 코드 입력, 서버 에러)에 따른 …
ImxYJL Aug 4, 2024
b118388
refactor: groupAccessCode에 알파벳 대소문자와 숫자만 올 수 있도록 수정
ImxYJL Aug 4, 2024
4ade792
refactor: LandingPage에서 ErrorSuspenseContainer를 제거하고 대신 URLGeneratorF…
ImxYJL Aug 5, 2024
cca5eb7
refactor: Input 컴포넌트의 onChange 이벤트 타입 수정
ImxYJL Aug 5, 2024
3ef4d31
refactor: Input 컴포넌트에 name 속성 추가
ImxYJL Aug 5, 2024
c484cc7
Merge branch 'develop' of https://github.com/woowacourse-teams/2024-r…
ImxYJL Aug 6, 2024
24bb091
Merge branch 'develop' of https://github.com/woowacourse-teams/2024-r…
ImxYJL Aug 6, 2024
ed8f888
refactor: 수정된 경로 반영
ImxYJL Aug 6, 2024
79c6cc6
refactor: usePostDataForUrl 쿼리에서 mutation을 리턴하도록 수정
ImxYJL Aug 6, 2024
21df3c4
refactor: URL을 성공적으로 생성한 이후 Input을 리셋하는 함수 추가
ImxYJL Aug 6, 2024
dbd335a
chore: NOTE 주석 추가
ImxYJL Aug 6, 2024
8fe6414
refactor: getIsValidGroupAccessCodeApi에서 400 외의 에러 처리를 기존의 createApiE…
ImxYJL Aug 6, 2024
552cc6f
chore: 누락됐던 -Api suffix 반영
ImxYJL Aug 7, 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
2 changes: 1 addition & 1 deletion frontend/src/apis/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const endPoint = {
gettingDataToWriteReview: (reviewRequestCode: string) =>
`${process.env.API_BASE_URL}/reviews/write?${REVIEW_WRITING_API_PARAMS.queryString.reviewRequestCode}=${reviewRequestCode}`,
gettingReviewList: `${process.env.API_BASE_URL}/reviews`,
gettingCreatedGroupData: `${process.env.API_BASE_URL}/groups`,
postingDataForURL: `${process.env.API_BASE_URL}/groups`,
};

export default endPoint;
26 changes: 23 additions & 3 deletions frontend/src/apis/group.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { API_ERROR_MESSAGE, INVALID_GROUP_ACCESS_CODE_MESSAGE } from '@/constants';

import createApiErrorMessage from './apiErrorMessageCreator';
import endPoint from './endpoints';

interface DataForURL {
export interface DataForURL {
revieweeName: string;
projectName: string;
}

export const getCreatedGroupDataApi = async (dataForURL: DataForURL) => {
const response = await fetch(endPoint.gettingCreatedGroupData, {
export const postDataForURL = async (dataForURL: DataForURL) => {
const response = await fetch(endPoint.postingDataForURL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand All @@ -22,3 +24,21 @@ export const getCreatedGroupDataApi = async (dataForURL: DataForURL) => {
const data = await response.json();
return data;
};

// NOTE: 리뷰 목록 엔드포인트(gettingReviewList)에 요청을 보내고 있지만,
// 요청 성격이 목록을 얻어오는 것이 아닌 유효한 groupAccessCode인지 확인하는 것이므로 group 파일에 작성함
// 단, 해당 엔드포인트에 대한 정상 요청 핸들러가 동작한다면 아래 에러 핸들러는 동작하지 않음
export const getIsValidGroupAccessCodeApi = async (groupAccessCode: string) => {
const response = await fetch(endPoint.gettingReviewList, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
GroupAccessCode: groupAccessCode,
},
});

if (response.status === 400) throw new Error(INVALID_GROUP_ACCESS_CODE_MESSAGE);
if (response.status === 500) throw new Error(API_ERROR_MESSAGE.serverError);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (response.status === 500) throw new Error(API_ERROR_MESSAGE.serverError);
if (response.status === 500) throw new Error(createApiErrorMessage(response.status));

위에처럼 createApiErrorMessage를 사용하지 않고 API_ERROR_MESSAGE.serverError를 바로 사용한 이유가 있을까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

이 요청에서는 특별히 400과 500 에러를 따로 처리하는 만큼 500 에러임을 코드에서 더 강조하려고 했습니다.
400은 따로 에러 메세지를 설정해주는데, 500을 처리할 때 createApiErrorMessage를 사용해서 status만 넘겨주면 뭐지? 싶을 것 같아서요!

그런데 지금 생각해보니까 특이 케이스인 400만 지금처럼 맨 처음에 따로 처리해주고 그 외의 에러들은 한꺼번에 처리해도 될 것 같다는 생각이 들었습니다.
그래서 400을 제외한 에러들은 createApiErrorMessage를 통해 한꺼번에 처리하도록 바꿨습니다~~


return response.ok;
};
12 changes: 0 additions & 12 deletions frontend/src/apis/review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,3 @@ export const getReviewListApi = async (groupAccessCode: string) => {
const data = await response.json();
return data as ReviewPreviewList;
};

export const checkGroupAccessCodeApi = async (groupAccessCode: string) => {
const response = await fetch(endPoint.gettingReviewList, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
GroupAccessCode: groupAccessCode,
},
});

return response.ok;
};
19 changes: 12 additions & 7 deletions frontend/src/components/common/Input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,24 @@ export interface InputStyleProps {
}
interface InputProps extends InputStyleProps {
value: string;
onChange: (value: string) => void;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
type: string;
id?: string;
name?: string;
placeholder?: string;
}

const Input = ({ id, value, onChange, type, placeholder, $style }: InputProps) => {
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
onChange(event.target.value);
};

const Input = ({ id, value, name, onChange, type, placeholder, $style }: InputProps) => {
return (
<S.Input id={id} value={value} type={type} onChange={handleChange} placeholder={placeholder} $style={$style} />
<S.Input
id={id}
value={value}
type={type}
name={name}
onChange={onChange}
placeholder={placeholder}
style={$style}
/>
);
};

Expand Down
2 changes: 2 additions & 0 deletions frontend/src/constants/errorMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ export const API_ERROR_MESSAGE: ApiErrorMessages = {
export const SERVER_ERROR_REGEX = /^5\d{2}$/;

export const ROUTE_ERROR_MESSAGE = '찾으시는 페이지가 없어요.';

export const INVALID_GROUP_ACCESS_CODE_MESSAGE = '올바르지 않은 확인 코드예요.';
5 changes: 5 additions & 0 deletions frontend/src/constants/queryKeys.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
// TODO: 내용이 배열이 아니므로 단수형으로 수정하기
export const REVIEW_QUERY_KEYS = {
detailedReview: 'detailedReview',
reviews: 'reviews',
};

export const GROUP_QUERY_KEY = {
dataForURL: 'dataForURL',
};
46 changes: 34 additions & 12 deletions frontend/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { RecoilRoot } from 'recoil';

import App from '@/App';

import ErrorSuspenseContainer from './components/error/ErrorSuspenseContainer/index';
import DetailedReviewPage from './pages/DetailedReviewPage';
import ErrorPage from './pages/ErrorPage';
import LandingPage from './pages/LandingPage';
Expand Down Expand Up @@ -60,15 +61,36 @@ const router = createBrowserRouter([

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);

root.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={theme}>
<Global styles={globalStyles} />
<RecoilRoot>
<RouterProvider router={router} />
</RecoilRoot>
</ThemeProvider>
</QueryClientProvider>
</React.StrictMode>,
);
async function enableMocking() {
if (process.env.MSW) {
const { worker } = await import('./mocks/browser');
return worker.start();
}
}

enableMocking().then(() => {
root.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={theme}>
<Global styles={globalStyles} />
<RecoilRoot>
<RouterProvider router={router} />
</RecoilRoot>
</ThemeProvider>
</QueryClientProvider>
</React.StrictMode>,
);
});
// root.render(
// <React.StrictMode>
// <QueryClientProvider client={queryClient}>
// <ThemeProvider theme={theme}>
// <Global styles={globalStyles} />
// <RecoilRoot>
// <RouterProvider router={router} />
// </RecoilRoot>
// </ThemeProvider>
// </QueryClientProvider>
// </React.StrictMode>,
// );
39 changes: 39 additions & 0 deletions frontend/src/mocks/handlers/group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { http, HttpResponse } from 'msw';

import endPoint from '@/apis/endpoints';

import { CREATED_GROUP_DATA, INVALID_GROUP_ACCESS_CODE } from '../mockData/group';

const postDataForUrl = () => {
return http.post(endPoint.postingDataForURL, async () => {
return HttpResponse.json(CREATED_GROUP_DATA, { status: 200 });
});
};

// const postDataForUrl = () => {
// return http.post(endPoint.postingDataForURL, async () => {
// return HttpResponse.json({ error: '서버 에러 테스트' }, { status: 500 });
// });
// };

const getIsValidGroupAccessCode = () => {
return http.get(endPoint.gettingReviewList, async () => {
return HttpResponse.json({ status: 200 });
});
};

// const getIsValidGroupAccessCode = () => {
// return http.get(endPoint.gettingReviewList, async () => {
// return HttpResponse.json(INVALID_GROUP_ACCESS_CODE, { status: 400 });
// });
// };

// const getIsValidGroupAccessCode = () => {
// return http.get(endPoint.gettingReviewList, async () => {
// return HttpResponse.json({ error: '서버 에러 테스트' }, { status: 500 });
// });
// };

const groupHandler = [postDataForUrl(), getIsValidGroupAccessCode()];

export default groupHandler;
3 changes: 2 additions & 1 deletion frontend/src/mocks/handlers/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import groupHandler from './group';
import reviewHandler from './review';

const handlers = [...reviewHandler];
const handlers = [...reviewHandler, ...groupHandler];

export default handlers;
12 changes: 12 additions & 0 deletions frontend/src/mocks/mockData/group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const CREATED_GROUP_DATA = {
reviewRequestCode: 'mocked-reviewRequestCode',
groupAccessCode: 'mocked-groupAccessCode',
};

export const INVALID_GROUP_ACCESS_CODE = {
type: 'about:blank',
title: 'Bad Request',
status: 400,
detail: '올바르지 않은 확인 코드입니다.',
instance: '/reviews',
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';

import { EssentialPropsWithChildren } from '@/types';

import * as S from '../../styles';
import * as S from './styles';

interface FormBodyProps {
direction: React.CSSProperties['flexDirection'];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import styled from '@emotion/styled';

export const FormBody = styled.div<{ direction: React.CSSProperties['flexDirection'] }>`
display: flex;
flex-direction: ${({ direction }) => direction};
gap: 1.6em;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import React from 'react';

import { EssentialPropsWithChildren } from '@/types';

import * as S from '../../styles';
import FormBody from '../FormBody';
import { FormBody } from '../index';

import * as S from './styles';

interface FormProps {
title: string;
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/pages/LandingPage/components/FormLayout/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import styled from '@emotion/styled';

export const FormLayout = styled.form`
display: flex;
flex-direction: column;

width: 40rem;
`;

export const Title = styled.h2`
font-size: ${({ theme }) => theme.fontSize.basic};

margin-bottom: 2.2rem;
`;
Original file line number Diff line number Diff line change
@@ -1,48 +1,55 @@
import { useState } from 'react';
import { useNavigate } from 'react-router';

import { checkGroupAccessCodeApi } from '@/apis/review';
import { getIsValidGroupAccessCodeApi } from '@/apis/group';
import { Input, Button } from '@/components';
import { useGroupAccessCode } from '@/hooks';
import { debounce } from '@/utils/debounce';

import * as S from '../../styles';
import FormLayout from '../FormLayout';
import { FormLayout } from '../index';

import * as S from './styles';

const DEBOUNCE_TIME = 300;

const ReviewAccessForm = () => {
const navigate = useNavigate();
const { updateGroupAccessCode } = useGroupAccessCode();

const [groupAccessCode, setGroupAccessCode] = useState('');
const [errorMessage, setErrorMessage] = useState('');

const navigate = useNavigate();
const { updateGroupAccessCode } = useGroupAccessCode();

const isValidGroupAccessCode = async () => {
const isValid = await checkGroupAccessCodeApi(groupAccessCode);
const isValid = await getIsValidGroupAccessCodeApi(groupAccessCode);
return isValid;
};

const handleGroupAccessCodeInputChange = (value: string) => {
setGroupAccessCode(value);
const isAlphanumeric = (groupAccessCode: string) => {
const alphanumericRegex = /^[A-Za-z0-9]*$/;
return alphanumericRegex.test(groupAccessCode);
};

const handleGroupAccessCodeInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setGroupAccessCode(event.target.value);
};

const handleAccessReviewButtonClick = debounce(async (event: React.MouseEvent<HTMLElement>) => {
event.preventDefault();

try {
const isValid = await isValidGroupAccessCode();

if (isValid) {
updateGroupAccessCode(groupAccessCode);
setErrorMessage('');

navigate('/user/review-preview-list');
} else {
setErrorMessage('유효하지 않은 그룹 접근 코드입니다.');
if (!isAlphanumeric(groupAccessCode)) {
setErrorMessage('알파벳 대소문자와 숫자만 입력 가능합니다.');
return;
}

await isValidGroupAccessCode();

updateGroupAccessCode(groupAccessCode);
setErrorMessage('');

navigate('/user/review-preview-list');
} catch (error) {
setErrorMessage('오류가 발생했습니다. 다시 시도해주세요.');
if (error instanceof Error) setErrorMessage(error.message);
}
}, DEBOUNCE_TIME);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import styled from '@emotion/styled';

export const ReviewAccessFormContent = styled.div`
display: flex;
flex-direction: column;

width: 100%;
`;

export const ReviewAccessFormBody = styled.div`
display: flex;
justify-content: space-between;

width: 100%;
`;

export const ErrorMessage = styled.p`
font-size: 1.3rem;

color: ${({ theme }) => theme.colors.red};

padding-left: 0.7rem;
`;
Loading