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 all 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ yarn-debug.log*
yarn-error.log*

.yaml

# Local Netlify folder
.netlify
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

9,458 changes: 4,483 additions & 4,975 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.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 Down Expand Up @@ -45,6 +48,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 Expand Up @@ -79,6 +83,7 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^12.0.0",
"eslint-plugin-storybook": "^0.8.0",
"gh-pages": "^6.1.1",
"jest": "^29.7.0",
"msw": "^1.3.3",
"prettier": "^3.2.5",
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