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_강호정 6주차 과제 #113

Merged
merged 25 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0cbcc15
docs: step4 질문에 대한 답변 작성
hojeong26 Aug 3, 2024
c58c782
docs: 질문에 대한 답 보충
hojeong26 Aug 4, 2024
5ac9330
feat: baseURL 수정
hojeong26 Aug 5, 2024
95a609f
refactor: 카멜케이스 전부 스네이크 케이스로 변경
hojeong26 Aug 5, 2024
a177a32
docs: README.md파일 수정
hojeong26 Aug 5, 2024
918c544
feat: api명세에 따라 data구조 수정
hojeong26 Aug 5, 2024
5300672
Merge branch 'hojeong26' into step1
hojeong26 Aug 5, 2024
03d65f0
fix: 카테고리 페이지로 안넘어가는 오류 해결
hojeong26 Aug 5, 2024
febfaef
fix: 카테고리 페이지로 넘어가지 않는 오류 해결
hojeong26 Aug 5, 2024
5600465
Merge branch 'step1' of https://github.com/hojeong26/react-deploy int…
hojeong26 Aug 5, 2024
6491ec4
fix: 카테고리별 상품 목록 나오게 설정
hojeong26 Aug 5, 2024
690e92e
fix: 상품 상세페이지로 넘어가도록 수정
hojeong26 Aug 6, 2024
57ed942
fix: 결제 페이지로 넘어가지 않는 오류 수정
hojeong26 Aug 6, 2024
0fc19b3
fix: 위시 리스트 관련 api수정
hojeong26 Aug 6, 2024
13252cf
CI 추가
hojeong26 Aug 7, 2024
413aba1
ci수정
hojeong26 Aug 7, 2024
e1954aa
deploy추가
hojeong26 Aug 7, 2024
49820ef
들여쓰기 수정
hojeong26 Aug 7, 2024
9a1274a
feat: gh-pages설치
hojeong26 Aug 7, 2024
7296417
ci.yml파일 수정
hojeong26 Aug 7, 2024
102106f
ci.yml파일 수정
hojeong26 Aug 7, 2024
a7d59bb
fix: error수정
hojeong26 Aug 7, 2024
d2d4b81
github.io로 수정
hojeong26 Aug 7, 2024
ba289a1
https에서 http로 요청 에러 해결
hojeong26 Aug 7, 2024
c99bdec
https에서 http로 요청 에러 해결
hojeong26 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
44 changes: 43 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,43 @@
# react-deploy
# react-deploy

### 질문의 답변 작성

- 질문 1. SPA 페이지를 정적 배포를 하려고 할 때 Vercel을 사용하지 않고 한다면 어떻게 할 수 있을까요?

> **SPA** : 단일 페이지 애플리케이션,
> 서버에서 필요한 데이터만 비동기로 받아와서 동적으로 현재 화면에 다시 렌더링 하는 방식

> **정적 배포** : 미리 생성된 파일을 서버에 업로드하여 배포하는 방식

github page사용한다.

- 질문 2. CSRF나 XSS 공격을 막는 방법은 무엇일까요?

> **CSRF** : 사이트간 요청 위조(웹 보안 취약점)

1. Referer check : HTTP 요청 헤더 정보에서 Referrer정보 확인
2. CAPTCHA 도입
3. CSRF 토큰 사용 : 사용자 세션에 임의에 값을 저장하여 모든 요청마다 해당 값을 포함하여 전송하도록 함

> **XSS** : 웹 페이지에 악성 스크립트 삽입

1. 입력 값 제한 : 사용자의 입력값을 제한하여 스크립트를 삽입하지 못하도록 함
2. 입력 값 치환 : 위험한 문자 입력 시 필터링
3. 직접출력 금지

- 질문 3. 브라우저 렌더링 원리에대해 설명해주세요.

1. 렌더링 엔진 : 웹 페이지의 요소들을 파싱
2. 렌더 트리 : 렌더링 엔진이 파싱한 웹 페이지 요소들을 이용하여 구성한 트리 구조 (_이 구조는 브라우저가 화면에 표시할 요소들을 정의_)
3. 레이아웃 : 렌더 트리를 이용하여 브라우저의 화면에 요소들을 배치하는 과정
4. 페인팅 : 레이아웃을 이용하여 계산된 위치에 요소들을 표시하는 과정

=> 로더가 서버로부터 전달받은 리소스 스트림을 읽어오고 파서가 DOM Tree와 CSSOM Tree를 생성한다. 여기서 렌더링에 필요한 노드만 선택하여 페이지를 렌더링한다.

**[api명세](https://alive-tail-1fa.notion.site/API-778e7fee2b6c45f4bf19c06ac1e15461?pvs=4)**

**<백엔드 분이 주신 url>**

- 아래 endpoint로 접속해볼 수 있습니다!
- 52.78.81.37:8080

Choose a reason for hiding this comment

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

여기로 접속하면 에러가 뜨는데요. 프론트 배포된 것 맞을까요?

Copy link
Author

Choose a reason for hiding this comment

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

아직 에러 수정하고 있습니다..!

- ex: http://52.78.81.37:8080/api/products?sort=name,asc&category_id=1
4 changes: 2 additions & 2 deletions src/api/hooks/products.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import { getProductsPath } from './useGetProducts';
export const productsMockHandler = [
rest.get(
getProductsPath({
categoryId: '2920',
category_id: '2920',

Choose a reason for hiding this comment

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

아마 서버 인터페이스로 snake_case를 채택한 것 같은데, 프론트엔드에서 코드 case를 섞어 사용하면 차후 의도를 알아보기 힘들 수도 있고, 가독성을 떨어뜨릴 수도 있습니다.

camelCase와 snake_case를 섞어 쓰기 보다는, 카멜케이스로 작성하였으니 서버에서 받는 데이터들도 모두 api interceptor를 통해 카멜케이스로 바꿔서 사용할 수도 있습니다. 그러면 서버 필드가 어떤 case이든지 fe입장에선 카멜케이스로 통일시킬 수 있습니다.

직접 구현할 수도 있고, 이러한 유틸은 라이브러리의 도움을 받을 수도 있습니다.
ex) https://www.npmjs.com/package/change-case

}),
(_, res, ctx) => {
return res(ctx.json(PRODUCTS_MOCK_DATA));
},
),
rest.get(
getProductsPath({
categoryId: '2930',
category_id: '2930',
}),
(_, res, ctx) => {
return res(ctx.json(PRODUCTS_MOCK_DATA));
Expand Down
7 changes: 6 additions & 1 deletion src/api/hooks/useGetCategorys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import type { CategoryData } from '@/types';

import { BASE_URL, fetchInstance } from '../instance';

export type CategoryResponseData = CategoryData[];
export type CategoryResponseData = {
status: number;
categories: CategoryData[];
timestamp: string;
success: boolean;
};

export const getCategoriesPath = () => `${BASE_URL}/api/categories`;
const categoriesQueryKey = [getCategoriesPath()];
Expand Down
10 changes: 5 additions & 5 deletions src/api/hooks/useGetProductDetail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { ProductData } from '@/types';
import { BASE_URL, fetchInstance } from '../instance';

export type ProductDetailRequestParams = {
productId: string;
product_id: string;
};

type Props = ProductDetailRequestParams;
Expand All @@ -16,15 +16,15 @@ export const getProductDetailPath = (productId: string) => `${BASE_URL}/api/prod

export const getProductDetail = async (params: ProductDetailRequestParams) => {
const response = await fetchInstance.get<GoodsDetailResponseData>(
getProductDetailPath(params.productId),
getProductDetailPath(params.product_id),
);

return response.data;
};

export const useGetProductDetail = ({ productId }: Props) => {
export const useGetProductDetail = ({ product_id }: Props) => {
return useSuspenseQuery({
queryKey: [getProductDetailPath(productId)],
queryFn: () => getProductDetail({ productId }),
queryKey: [getProductDetailPath(product_id)],
queryFn: () => getProductDetail({ product_id }),
});
};
8 changes: 4 additions & 4 deletions src/api/hooks/useGetProductOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ export const getProductOptionsPath = (productId: string) =>

export const getProductOptions = async (params: ProductDetailRequestParams) => {
const response = await fetchInstance.get<ProductOptionsResponseData>(
getProductOptionsPath(params.productId),
getProductOptionsPath(params.product_id),
);
return response.data;
};

export const useGetProductOptions = ({ productId }: Props) => {
export const useGetProductOptions = ({ product_id }: Props) => {
return useSuspenseQuery({
queryKey: [getProductOptionsPath(productId)],
queryFn: () => getProductOptions({ productId }),
queryKey: [getProductOptionsPath(product_id)],
queryFn: () => getProductOptions({ product_id }),
});
};
50 changes: 25 additions & 25 deletions src/api/hooks/useGetProducts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,35 @@ import { BASE_URL } from '../instance';
import { fetchInstance } from './../instance/index';

type RequestParams = {
categoryId: string;
pageToken?: string;
maxResults?: number;
category_id: string;
page_token?: string;
max_results?: number;
};

type ProductsResponseData = {
products: ProductData[];
nextPageToken?: string;
pageInfo: {
totalResults: number;
resultsPerPage: number;
next_page_token?: string;
page_info: {
total_results: number;
results_per_page: number;
};
};

type ProductsResponseRawData = {
content: ProductData[];
number: number;
totalElements: number;
total_elements: number;
size: number;
last: boolean;
};

export const getProductsPath = ({ categoryId, pageToken, maxResults }: RequestParams) => {
export const getProductsPath = ({ category_id, page_token, max_results }: RequestParams) => {
const params = new URLSearchParams();

params.append('categoryId', categoryId);
params.append('categoryId', category_id);
params.append('sort', 'name,asc');
if (pageToken) params.append('page', pageToken);
if (maxResults) params.append('size', maxResults.toString());
if (page_token) params.append('page', page_token);
if (max_results) params.append('size', max_results.toString());

return `${BASE_URL}/api/products?${params.toString()}`;
};
Expand All @@ -49,26 +49,26 @@ export const getProducts = async (params: RequestParams): Promise<ProductsRespon

return {
products: data.content,
nextPageToken: data.last === false ? (data.number + 1).toString() : undefined,
pageInfo: {
totalResults: data.totalElements,
resultsPerPage: data.size,
next_page_token: data.last === false ? (data.number + 1).toString() : undefined,
page_info: {
total_results: data.total_elements,
results_per_page: data.size,
},
};
};

type Params = Pick<RequestParams, 'maxResults' | 'categoryId'> & { initPageToken?: string };
type Params = Pick<RequestParams, 'max_results' | 'category_id'> & { init_page_token?: string };
export const useGetProducts = ({
categoryId,
maxResults = 20,
initPageToken,
category_id,
max_results = 20,
init_page_token,
}: Params): UseInfiniteQueryResult<InfiniteData<ProductsResponseData>> => {
return useInfiniteQuery({
queryKey: ['products', categoryId, maxResults, initPageToken],
queryFn: async ({ pageParam = initPageToken }) => {
return getProducts({ categoryId, pageToken: pageParam, maxResults });
queryKey: ['products', category_id, max_results, init_page_token],
queryFn: async ({ pageParam = init_page_token }) => {
return getProducts({ category_id, page_token: pageParam, max_results });
},
initialPageParam: initPageToken,
getNextPageParam: (lastPage) => lastPage.nextPageToken,
initialPageParam: init_page_token,
getNextPageParam: (lastPage) => lastPage.next_page_token,
});
};
4 changes: 2 additions & 2 deletions src/api/instance/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ const initInstance = (config: AxiosRequestConfig): AxiosInstance => {
return instance;
};

export const BASE_URL = 'https://api.example.com';
export const BASE_URL = 'http://52.78.81.37:8080';
// TODO: 추후 서버 API 주소 변경 필요
export const fetchInstance = initInstance({
baseURL: 'https://api.example.com',
baseURL: 'http://52.78.81.37:8080',
});

export const queryClient = new QueryClient({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import { breakpoints } from '@/styles/variants';
import type { CategoryData } from '@/types';

type Props = {
categoryId: string;
category_id: string;
};

export const CategoryHeroSection = ({ categoryId }: Props) => {
const { isRender, currentTheme } = useCurrentCategory({ categoryId });
export const CategoryHeroSection = ({ category_id }: Props) => {
const { isRender, currentTheme } = useCurrentCategory({ category_id });

if (!isRender) return null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import { getDynamicPath } from '@/routes/path';
import { breakpoints } from '@/styles/variants';

type Props = {
categoryId: string;
category_id: string;
};

export const CategoryProductsSection = ({ categoryId }: Props) => {
export const CategoryProductsSection = ({ category_id }: Props) => {
const { data, isError, isLoading, hasNextPage, fetchNextPage, isFetchingNextPage } =
useGetProducts({
categoryId,
category_id,
});

if (isLoading) return <LoadingView />;
Expand All @@ -37,11 +37,11 @@ export const CategoryProductsSection = ({ categoryId }: Props) => {
}}
gap={16}
>
{flattenGoodsList.map(({ id, imageUrl, name, price }) => (
{flattenGoodsList.map(({ id, image_url, name, price }) => (
<Link key={id} to={getDynamicPath.productsDetail(id)}>
<DefaultGoodsItems
key={id}
imageSrc={imageUrl}
imageSrc={image_url}
title={name}
amount={price}
subtitle={''}
Expand Down
6 changes: 3 additions & 3 deletions src/components/features/Goods/Detail/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { breakpoints } from '@/styles/variants';

type Props = ProductDetailRequestParams;

export const GoodsDetailHeader = ({ productId }: Props) => {
const { data: detail } = useGetProductDetail({ productId });
export const GoodsDetailHeader = ({ product_id }: Props) => {
const { data: detail } = useGetProductDetail({ product_id });

return (
<Wrapper>
<GoodsImage src={detail.imageUrl} alt={detail.name} />
<GoodsImage src={detail.image_url} alt={detail.name} />
<InfoWrapper>
<Title>{detail.name}</Title>
<Price>{detail.price}원</Price>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,25 @@ import { addWish } from '@/api/wish';

type Props = {
name: string;
productId: number;
minValues?: number;
maxValues?: number;
product_id: number;
min_values?: number;
max_values?: number;
value: string;
onChange: (value: string) => void;
};

export const CountOptionItem = ({
name,
productId,
minValues = 1,
maxValues = 100,
product_id,
min_values = 1,
max_values = 100,
value,
onChange,
}: Props) => {
const { getInputProps, getIncrementButtonProps, getDecrementButtonProps } = useNumberInput({
step: 1,
min: minValues,
max: maxValues,
min: min_values,
max: max_values,
defaultValue: value,
onChange: (valueAsString) => {
onChange(valueAsString);
Expand All @@ -40,7 +40,7 @@ export const CountOptionItem = ({
const wishhandler = async () => {
setIsChecked((prev) => !prev);
try {
const success = await addWish(productId);
const success = await addWish(product_id);
if (success) {
alert('관심 등록 완료');
} else {
Expand Down
10 changes: 5 additions & 5 deletions src/components/features/Goods/Detail/OptionSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import { CountOptionItem } from './OptionItem/CountOptionItem';

type Props = ProductDetailRequestParams;

export const OptionSection = ({ productId }: Props) => {
const { data: detail } = useGetProductDetail({ productId });
const { data: options } = useGetProductOptions({ productId });
export const OptionSection = ({ product_id }: Props) => {
const { data: detail } = useGetProductDetail({ product_id });
const { data: options } = useGetProductOptions({ product_id });

const [countAsString, setCountAsString] = useState('1');
const totalPrice = useMemo(() => {
Expand All @@ -38,7 +38,7 @@ export const OptionSection = ({ productId }: Props) => {
}

orderHistorySessionStorage.set({
id: parseInt(productId),
id: parseInt(product_id),
count: parseInt(countAsString),
});

Expand All @@ -49,7 +49,7 @@ export const OptionSection = ({ productId }: Props) => {
<Wrapper>
<CountOptionItem
name={options[0].name}
productId={parseInt(productId)}
product_id={parseInt(product_id)}
value={countAsString}
onChange={setCountAsString}
/>
Expand Down
4 changes: 2 additions & 2 deletions src/components/features/Goods/Detail/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import { GoodsDetailHeader } from './Header';

type Props = ProductDetailRequestParams;

export const GoodsDetail = ({ productId }: Props) => {
export const GoodsDetail = ({ product_id }: Props) => {
return (
<Wrapper>
<GoodsDetailHeader productId={productId} />
<GoodsDetailHeader product_id={product_id} />
</Wrapper>
);
};
Expand Down
Loading