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 22 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
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: CI

on:
push:
branches:
- step1
pull_request:
branches:
- hojeong26
# paths: // path는 특정 폴더에 변경사항이 생겼을 때 작동하도록 설정할 수 있음
# - "services/**"

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout Repository
uses: actions/checkout@v2

- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '18'

- name: Build
run: npm run build

- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.TOKEN }}
publish_dir: ./build
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,13 @@
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

11,332 changes: 5,399 additions & 5,933 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@
"^axios$": "axios/dist/node/axios.cjs"
}
},
"homepage": "https://hojeong26.github/react-deploy",

Choose a reason for hiding this comment

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

gh-pages로 배포하는 것이지요? 깃헙페이지 기본 도메인은 {name}.github.io이 맞습니다. io 다시 넣어주세요~!

Copy link
Author

Choose a reason for hiding this comment

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

github.io저장소를 만들고 파일들을 github.io저장소로 가져와야 되나요?

Choose a reason for hiding this comment

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

gh-pages 배포방식은, 빌드된 프로젝트 결과물 자체를 gh-page 브랜치에 올리고 이를 깃허브가 무료로 제공하는 저장소에 배포되도록 하는 것입니다. 그리고 github actions를 통해 이 과정 자체를 자동화하는 것이고요. 이를 CI/CD라고 합니다.

즉, 두 개념은 다릅니다. gh-pages배포방식과 함께 github actions로 배포를 자동화하는 것을 함께 찾아보시고 적용해보세요.

Choose a reason for hiding this comment

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

기본적으로 Repository가 gh-pages를 바라보게 되어있는지 등도 체크해야 합니다. 아래 문서를 참고해보세요.

https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site

그리고 이후에 이 배포과정을 자동화해주는 github actions를 설정해보세요

Copy link
Author

Choose a reason for hiding this comment

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

배포 성공한 것 같은데 npm run start를 했을 땐 오류가 안났는데 배포된 웹페이지로 들어가면 에러가 납니다..

Copy link
Author

Choose a reason for hiding this comment

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

http://192.168.56.1:3000/react-deploy
멘토님 혹시 여기 들어가 지나요??

Choose a reason for hiding this comment

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

남겨주신 링크는 본인의 로컬 ip주소인 것으로 보입니다. 그 주소에 접속하려면 외부에 공개해야해서 저는 진입할 수 없습니다. 아래 깃헙페이지에 잘 배포된 것 확인했습니다. 엔트리를 react-deploy로 잡았기 때문에 이를 꼭 붙여줘야해요

https://hojeong26.github.io/react-deploy

"scripts": {
"start:mock": "cross-env REACT_APP_RUN_MSW=true npm run start",
"start": "craco start",
"build": "craco build",
"test": "craco test",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
"build-storybook": "storybook build",
"predeploy": "npm run build",
"deploy": "gh-pages -d build"
},
"browserslist": {
"production": [
Expand All @@ -37,6 +40,7 @@
"axios": "^1.6.7",
"craco-alias": "^3.0.1",
"framer-motion": "^11.0.6",
"gh-pages": "^6.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.12",
Expand All @@ -45,6 +49,7 @@
"react-router-dom": "^6.22.1"
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@craco/craco": "^7.1.0",
"@emotion/eslint-plugin": "^11.11.0",
"@storybook/addon-essentials": "^7.6.17",
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
23 changes: 12 additions & 11 deletions src/api/hooks/useGetProductDetail.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { useSuspenseQuery } from '@tanstack/react-query';

import { useQuery } from '@tanstack/react-query';
import type { ProductData } from '@/types';

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

export type ProductDetailRequestParams = {
Expand All @@ -10,21 +8,24 @@ export type ProductDetailRequestParams = {

type Props = ProductDetailRequestParams;

export type GoodsDetailResponseData = ProductData;
export type GoodsDetailResponseData = {
products: ProductData;
};

export const getProductDetailPath = (productId: string) => `${BASE_URL}/api/products/${productId}`;

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

return response.data;
return response.data.products;
};

export const useGetProductDetail = ({ productId }: Props) => {
return useSuspenseQuery({
queryKey: [getProductDetailPath(productId)],
queryFn: () => getProductDetail({ productId }),
return useQuery({
queryKey: ['productDetail', productId],
queryFn: async () => {
return getProductDetail({ productId });
},
});
};
4 changes: 3 additions & 1 deletion src/api/hooks/useGetProductOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import type { ProductDetailRequestParams } from './useGetProductDetail';

type Props = ProductDetailRequestParams;

export type ProductOptionsResponseData = ProductOptionsData[];
export type ProductOptionsResponseData = {
options: ProductOptionsData[];
};

export const getProductOptionsPath = (productId: string) =>
`${BASE_URL}/api/products/${productId}/options`;
Expand Down
57 changes: 28 additions & 29 deletions src/api/hooks/useGetProducts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,64 +11,63 @@ import { fetchInstance } from './../instance/index';

type RequestParams = {
categoryId: string;
pageToken?: string;
maxResults?: number;
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;
size: number;
last: boolean;
products: ProductData[];
};

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

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

return `${BASE_URL}/api/products?${params.toString()}`;
};

export const getProducts = async (params: RequestParams): Promise<ProductsResponseData> => {
const response = await fetchInstance.get<ProductsResponseRawData>(getProductsPath(params));
const url = getProductsPath(params);
const response = await fetchInstance.get<ProductsResponseRawData>(url);
const data = response.data;

console.log('Fetched Data:', data);

return {
products: data.content,
nextPageToken: data.last === false ? (data.number + 1).toString() : undefined,
pageInfo: {
totalResults: data.totalElements,
resultsPerPage: data.size,
products: data.products.filter((product) => product.id && product.name && product.price),
next_page_token: undefined,
page_info: {
total_results: data.products.length,
results_per_page: data.products.length,
},
};
};

type Params = Pick<RequestParams, 'maxResults' | 'categoryId'> & { initPageToken?: string };
type Params = Pick<RequestParams, 'max_results' | 'categoryId'> & { init_page_token?: string };
export const useGetProducts = ({
categoryId,
maxResults = 20,
initPageToken,
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', categoryId, max_results, init_page_token],
queryFn: async ({ pageParam = init_page_token }) => {
return getProducts({ categoryId, 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
8 changes: 5 additions & 3 deletions src/api/wish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { fetchInstance } from './instance';

const BASE_PATH = '/api/wishes';

export const addWish = async (productId: number) => {
export const addWish = async (product_id: number) => {
try {
const response = await fetchInstance.post(`${BASE_PATH}`, { productId });
const response = await fetchInstance.post(`${BASE_PATH}`, { product_id });
console.log('addWish response:', response); // 추가된 로그
return response.status === 201;
} catch (error) {
console.error('Error:', error);
Expand All @@ -15,7 +16,8 @@ export const addWish = async (productId: number) => {
export const getWishList = async () => {
try {
const response = await fetchInstance.get(`${BASE_PATH}`);
return response.data;
console.log('Fetched Wish List:', response.data); // 데이터 확인용 로그 추가
return response.data.wishlist;
} catch (error) {
console.error('Error:', error);
throw new Error('관심 상품 목록 조회 중 오류가 발생했습니다.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,6 @@ const Title = styled.h1`
}
`;

export const getCurrentCategory = (categoryId: string, categoryList: CategoryData[]) => {
return categoryList.find((category) => category.id.toString() === categoryId);
export const getCurrentCategory = (category_id: string, categoryList: CategoryData[]) => {
return categoryList.find((category) => category.id.toString() === category_id);
};
21 changes: 15 additions & 6 deletions src/components/features/Category/CategoryProductsSection/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import styled from '@emotion/styled';
import { Link } from 'react-router-dom';

import { useGetProducts } from '@/api/hooks/useGetProducts';
import { DefaultGoodsItems } from '@/components/common/GoodsItem/Default';
import { Container } from '@/components/common/layouts/Container';
Expand All @@ -20,12 +19,22 @@ export const CategoryProductsSection = ({ categoryId }: Props) => {
categoryId,
});

// console.log('Fetched Data:', data);
// console.log('isError:', isError);
// console.log('isLoading:', isLoading);

if (isLoading) return <LoadingView />;
if (isError) return <TextView>에러가 발생했습니다.</TextView>;
if (!data) return <></>;
if (data.pages[0].products.length <= 0) return <TextView>상품이 없어요.</TextView>;
if (!data || !data.pages || !Array.isArray(data.pages))
return <TextView>상품이 없어요.</TextView>;

const flattenGoodsList = data.pages
.flatMap((page) => page?.products ?? [])
.filter((product) => product && product.id && product.name && product.price);

console.log('Flatten Goods List:', flattenGoodsList);

const flattenGoodsList = data.pages.map((page) => page?.products ?? []).flat();
if (flattenGoodsList.length === 0) return <TextView>유효한 상품이 없어요.</TextView>;

return (
<Wrapper>
Expand All @@ -37,11 +46,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
4 changes: 3 additions & 1 deletion src/components/features/Goods/Detail/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ type Props = ProductDetailRequestParams;
export const GoodsDetailHeader = ({ productId }: Props) => {
const { data: detail } = useGetProductDetail({ productId });

if (!detail) return <div>No product details found.</div>;

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,8 @@ export const CountOptionItem = ({
const wishhandler = async () => {
setIsChecked((prev) => !prev);
try {
const success = await addWish(productId);
const success = await addWish(product_id);
console.log('addWish success:', success);
if (success) {
alert('관심 등록 완료');
} else {
Expand Down
Loading
Loading