From 0cbcc1516d426219da45259f09341e81d1fa9cbb Mon Sep 17 00:00:00 2001 From: hojeong26 Date: Sat, 3 Aug 2024 22:38:11 +0900 Subject: [PATCH 01/23] =?UTF-8?q?docs:=20step4=20=EC=A7=88=EB=AC=B8?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EB=8B=B5=EB=B3=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3eaeec280..7f938a86b 100644 --- a/README.md +++ b/README.md @@ -1 +1,32 @@ -# react-deploy \ No newline at end of file +# 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. 페인팅 : 레이아웃을 이용하여 계산된 위치에 요소들을 표시하는 과정 From c58c7823634bfd9d1da0e662b734a6be0daf12c9 Mon Sep 17 00:00:00 2001 From: hojeong26 Date: Sun, 4 Aug 2024 10:42:50 +0900 Subject: [PATCH 02/23] =?UTF-8?q?docs:=20=EC=A7=88=EB=AC=B8=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EB=8B=B5=20=EB=B3=B4=EC=B6=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 7f938a86b..ef3be7375 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,10 @@ 3. 직접출력 금지 - 질문 3. 브라우저 렌더링 원리에대해 설명해주세요. + 1. 렌더링 엔진 : 웹 페이지의 요소들을 파싱 2. 렌더 트리 : 렌더링 엔진이 파싱한 웹 페이지 요소들을 이용하여 구성한 트리 구조 (_이 구조는 브라우저가 화면에 표시할 요소들을 정의_) 3. 레이아웃 : 렌더 트리를 이용하여 브라우저의 화면에 요소들을 배치하는 과정 4. 페인팅 : 레이아웃을 이용하여 계산된 위치에 요소들을 표시하는 과정 + + => 로더가 서버로부터 전달받은 리소스 스트림을 읽어오고 파서가 DOM Tree와 CSSOM Tree를 생성한다. 여기서 렌더링에 필요한 노드만 선택하여 페이지를 렌더링한다. From 5ac93306f8fa46ab29c8335aecbd8d29264bcc41 Mon Sep 17 00:00:00 2001 From: hojeong26 Date: Mon, 5 Aug 2024 12:21:25 +0900 Subject: [PATCH 03/23] =?UTF-8?q?feat:=20baseURL=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/instance/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/instance/index.ts b/src/api/instance/index.ts index a60533d3f..3b5443767 100644 --- a/src/api/instance/index.ts +++ b/src/api/instance/index.ts @@ -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({ From 95a609fa3098f7cc3e3328fb8d9308bdb8d0315d Mon Sep 17 00:00:00 2001 From: hojeong26 Date: Mon, 5 Aug 2024 13:31:52 +0900 Subject: [PATCH 04/23] =?UTF-8?q?refactor:=20=EC=B9=B4=EB=A9=9C=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=A0=84=EB=B6=80=20=EC=8A=A4=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=ED=81=AC=20=EC=BC=80=EC=9D=B4=EC=8A=A4=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/hooks/products.mock.ts | 4 +- src/api/hooks/useGetProductDetail.ts | 10 ++-- src/api/hooks/useGetProductOptions.ts | 8 +-- src/api/hooks/useGetProducts.ts | 50 +++++++++---------- .../Category/CategoryHeroSection/index.tsx | 6 +-- .../CategoryProductsSection/index.tsx | 10 ++-- .../features/Goods/Detail/Header.tsx | 6 +-- .../Detail/OptionItem/CountOptionItem.tsx | 18 +++---- .../features/Goods/Detail/OptionSection.tsx | 10 ++-- .../features/Goods/Detail/index.tsx | 4 +- .../features/Home/CategorySection/index.tsx | 2 +- .../OrderForm/Fields/CashReceiptFields.tsx | 6 +-- .../OrderForm/Fields/MessageCardFields.tsx | 2 +- .../Order/OrderForm/GoodsInfo/index.tsx | 4 +- .../Order/OrderForm/OrderInfo/index.tsx | 2 +- .../features/Order/OrderForm/index.tsx | 20 ++++---- src/hooks/useCurrentCategory.ts | 6 +-- src/pages/Category/index.tsx | 8 +-- src/pages/Goods/Detail/index.tsx | 6 +-- src/test/GoodsDetailHeader.test.tsx | 6 +-- src/types/index.ts | 32 ++++++------ 21 files changed, 110 insertions(+), 110 deletions(-) diff --git a/src/api/hooks/products.mock.ts b/src/api/hooks/products.mock.ts index b0f259c08..c007b2d62 100644 --- a/src/api/hooks/products.mock.ts +++ b/src/api/hooks/products.mock.ts @@ -7,7 +7,7 @@ import { getProductsPath } from './useGetProducts'; export const productsMockHandler = [ rest.get( getProductsPath({ - categoryId: '2920', + category_id: '2920', }), (_, res, ctx) => { return res(ctx.json(PRODUCTS_MOCK_DATA)); @@ -15,7 +15,7 @@ export const productsMockHandler = [ ), rest.get( getProductsPath({ - categoryId: '2930', + category_id: '2930', }), (_, res, ctx) => { return res(ctx.json(PRODUCTS_MOCK_DATA)); diff --git a/src/api/hooks/useGetProductDetail.ts b/src/api/hooks/useGetProductDetail.ts index 539de0196..8c8dc60b3 100644 --- a/src/api/hooks/useGetProductDetail.ts +++ b/src/api/hooks/useGetProductDetail.ts @@ -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; @@ -16,15 +16,15 @@ export const getProductDetailPath = (productId: string) => `${BASE_URL}/api/prod export const getProductDetail = async (params: ProductDetailRequestParams) => { const response = await fetchInstance.get( - 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 }), }); }; diff --git a/src/api/hooks/useGetProductOptions.ts b/src/api/hooks/useGetProductOptions.ts index a3bdc538f..865b17a6a 100644 --- a/src/api/hooks/useGetProductOptions.ts +++ b/src/api/hooks/useGetProductOptions.ts @@ -14,14 +14,14 @@ export const getProductOptionsPath = (productId: string) => export const getProductOptions = async (params: ProductDetailRequestParams) => { const response = await fetchInstance.get( - 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 }), }); }; diff --git a/src/api/hooks/useGetProducts.ts b/src/api/hooks/useGetProducts.ts index 432f90d93..4403566f2 100644 --- a/src/api/hooks/useGetProducts.ts +++ b/src/api/hooks/useGetProducts.ts @@ -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()}`; }; @@ -49,26 +49,26 @@ export const getProducts = async (params: RequestParams): Promise & { initPageToken?: string }; +type Params = Pick & { init_page_token?: string }; export const useGetProducts = ({ - categoryId, - maxResults = 20, - initPageToken, + category_id, + max_results = 20, + init_page_token, }: Params): UseInfiniteQueryResult> => { 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, }); }; diff --git a/src/components/features/Category/CategoryHeroSection/index.tsx b/src/components/features/Category/CategoryHeroSection/index.tsx index 2af9cb2f9..fa27e3129 100644 --- a/src/components/features/Category/CategoryHeroSection/index.tsx +++ b/src/components/features/Category/CategoryHeroSection/index.tsx @@ -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; diff --git a/src/components/features/Category/CategoryProductsSection/index.tsx b/src/components/features/Category/CategoryProductsSection/index.tsx index d2dae2c3b..2332a3b06 100644 --- a/src/components/features/Category/CategoryProductsSection/index.tsx +++ b/src/components/features/Category/CategoryProductsSection/index.tsx @@ -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 ; @@ -37,11 +37,11 @@ export const CategoryProductsSection = ({ categoryId }: Props) => { }} gap={16} > - {flattenGoodsList.map(({ id, imageUrl, name, price }) => ( + {flattenGoodsList.map(({ id, image_url, name, price }) => ( { - const { data: detail } = useGetProductDetail({ productId }); +export const GoodsDetailHeader = ({ product_id }: Props) => { + const { data: detail } = useGetProductDetail({ product_id }); return ( - + {detail.name} {detail.price}원 diff --git a/src/components/features/Goods/Detail/OptionItem/CountOptionItem.tsx b/src/components/features/Goods/Detail/OptionItem/CountOptionItem.tsx index 5a12ecc4b..466228f2a 100644 --- a/src/components/features/Goods/Detail/OptionItem/CountOptionItem.tsx +++ b/src/components/features/Goods/Detail/OptionItem/CountOptionItem.tsx @@ -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); @@ -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 { diff --git a/src/components/features/Goods/Detail/OptionSection.tsx b/src/components/features/Goods/Detail/OptionSection.tsx index 4e8cb1b82..8a9e457d6 100644 --- a/src/components/features/Goods/Detail/OptionSection.tsx +++ b/src/components/features/Goods/Detail/OptionSection.tsx @@ -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(() => { @@ -38,7 +38,7 @@ export const OptionSection = ({ productId }: Props) => { } orderHistorySessionStorage.set({ - id: parseInt(productId), + id: parseInt(product_id), count: parseInt(countAsString), }); @@ -49,7 +49,7 @@ export const OptionSection = ({ productId }: Props) => { diff --git a/src/components/features/Goods/Detail/index.tsx b/src/components/features/Goods/Detail/index.tsx index 26d7a7b8d..625897573 100644 --- a/src/components/features/Goods/Detail/index.tsx +++ b/src/components/features/Goods/Detail/index.tsx @@ -7,10 +7,10 @@ import { GoodsDetailHeader } from './Header'; type Props = ProductDetailRequestParams; -export const GoodsDetail = ({ productId }: Props) => { +export const GoodsDetail = ({ product_id }: Props) => { return ( - + ); }; diff --git a/src/components/features/Home/CategorySection/index.tsx b/src/components/features/Home/CategorySection/index.tsx index 2b46ef48a..36629e7bc 100644 --- a/src/components/features/Home/CategorySection/index.tsx +++ b/src/components/features/Home/CategorySection/index.tsx @@ -26,7 +26,7 @@ export const CategorySection = () => { > {data.map((category) => ( - + ))} diff --git a/src/components/features/Order/OrderForm/Fields/CashReceiptFields.tsx b/src/components/features/Order/OrderForm/Fields/CashReceiptFields.tsx index 8d483566d..fff2fded8 100644 --- a/src/components/features/Order/OrderForm/Fields/CashReceiptFields.tsx +++ b/src/components/features/Order/OrderForm/Fields/CashReceiptFields.tsx @@ -14,7 +14,7 @@ export const CashReceiptFields = () => { ( 현금영수증 신청 @@ -25,7 +25,7 @@ export const CashReceiptFields = () => { ( + ); }; diff --git a/src/components/features/Order/OrderForm/Fields/MessageCardFields.tsx b/src/components/features/Order/OrderForm/Fields/MessageCardFields.tsx index 41497e56d..a46dbf1cb 100644 --- a/src/components/features/Order/OrderForm/Fields/MessageCardFields.tsx +++ b/src/components/features/Order/OrderForm/Fields/MessageCardFields.tsx @@ -10,7 +10,7 @@ export const MessageCardFields = () => {