Skip to content
Open
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
8380539
feat: 커스텀훅 - useCoupon 추가
SeongYoonMin Apr 21, 2025
216ffde
✨ feat: 커스텀훅 useCart 추가
SeongYoonMin Apr 21, 2025
e1de15f
✨ feat: useEffect를 통한 최초 렌더링시 product 갱신
SeongYoonMin Apr 21, 2025
7941338
✨ feat: addToCart 코드 추가
SeongYoonMin Apr 21, 2025
84b0b19
feat: Provider를 이용하여 App Page 정리
SeongYoonMin Apr 22, 2025
1e6acb9
fix: Provider를 이용하여 값 받아오게 수정
SeongYoonMin Apr 22, 2025
004ef14
feat: ProductList Components 분리
SeongYoonMin Apr 22, 2025
d4b8544
feat: 상수관리용 유틸파일 추가
SeongYoonMin Apr 22, 2025
b66ae67
feat: 유틸함수 분리
SeongYoonMin Apr 22, 2025
0e8f2f2
fix: pages로 페이지 tsx파일 구분
SeongYoonMin Apr 22, 2025
5e6a4e6
fix: 디렉터리 구조 변경으로 인한 수정
SeongYoonMin Apr 22, 2025
d9a2f0d
fix: 디렉터리 구조 변경으로 인한 수정
SeongYoonMin Apr 22, 2025
316f551
fix: Props로 addToCart전달
SeongYoonMin Apr 22, 2025
9739b1e
feat: 함수 추가
SeongYoonMin Apr 22, 2025
654d52a
feat: CartProvider
SeongYoonMin Apr 23, 2025
f204c26
remove: 불필요한 컴포넌트, 훅 제거
SeongYoonMin Apr 23, 2025
3bce199
fix: 배럴파일 수정
SeongYoonMin Apr 23, 2025
11b0182
feat: 역할분할을 위한 컴포넌트 분리
SeongYoonMin Apr 23, 2025
29cef09
fix: CartProvider 도입 및 컴포넌트 분리
SeongYoonMin Apr 23, 2025
39e4749
fix: 컴포넌트 구조 변경
SeongYoonMin Apr 24, 2025
31252f0
fix: Model과 Hooks 분리.
SeongYoonMin Apr 24, 2025
49566c1
fix: 컴포넌트 구조 변경
SeongYoonMin Apr 24, 2025
1d27e84
feat: Admin-Coupon 분리
SeongYoonMin Apr 24, 2025
f809779
feat: Admin-Product 분리
SeongYoonMin Apr 24, 2025
f227650
fix: Product, Coupon 분리하여 관리
SeongYoonMin Apr 24, 2025
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
4 changes: 2 additions & 2 deletions src/advanced/__tests__/advanced.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useState } from "react";
import { describe, expect, test } from 'vitest';
import { act, fireEvent, render, screen, within } from '@testing-library/react';
import { CartPage } from '../../refactoring/components/CartPage';
import { AdminPage } from "../../refactoring/components/AdminPage";
import { CartPage } from '../../refactoring/pages/CartPage';
import { AdminPage } from "../../refactoring/pages/AdminPage";
import { Coupon, Product } from '../../types';

const mockProducts: Product[] = [
Expand Down Expand Up @@ -233,11 +233,11 @@

describe('자유롭게 작성해보세요.', () => {
test('새로운 유틸 함수를 만든 후에 테스트 코드를 작성해서 실행해보세요', () => {
expect(true).toBe(false);

Check failure on line 236 in src/advanced/__tests__/advanced.test.tsx

View workflow job for this annotation

GitHub Actions / advacned

src/advanced/__tests__/advanced.test.tsx > advanced > > 자유롭게 작성해보세요. > 새로운 유틸 함수를 만든 후에 테스트 코드를 작성해서 실행해보세요

AssertionError: expected true to be false // Object.is equality - Expected + Received - false + true ❯ src/advanced/__tests__/advanced.test.tsx:236:20
})

test('새로운 hook 함수르 만든 후에 테스트 코드를 작성해서 실행해보세요', () => {
expect(true).toBe(false);

Check failure on line 240 in src/advanced/__tests__/advanced.test.tsx

View workflow job for this annotation

GitHub Actions / advacned

src/advanced/__tests__/advanced.test.tsx > advanced > > 자유롭게 작성해보세요. > 새로운 hook 함수르 만든 후에 테스트 코드를 작성해서 실행해보세요

AssertionError: expected true to be false // Object.is equality - Expected + Received - false + true ❯ src/advanced/__tests__/advanced.test.tsx:240:20
})
})
})
Expand Down
14 changes: 4 additions & 10 deletions src/basic/__tests__/basic.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
screen,
within,
} from "@testing-library/react";
import { CartPage } from "../../refactoring/components/CartPage";
import { AdminPage } from "../../refactoring/components/AdminPage";
import { CartPage } from "../../refactoring/pages/CartPage";
import { AdminPage } from "../../refactoring/pages/AdminPage";
import { CartItem, Coupon, Product } from "../../types";
import { useCart, useCoupons, useProducts } from "../../refactoring/hooks";
import * as cartUtils from "../../refactoring/models/cart";
Expand Down Expand Up @@ -71,20 +71,14 @@
};

return (
<AdminPage
products={products}
coupons={coupons}
onProductUpdate={handleProductUpdate}
onProductAdd={handleProductAdd}
onCouponAdd={handleCouponAdd}
/>
<AdminPage/>
);
};

describe("basic > ", () => {
describe("시나리오 테스트 > ", () => {
test("장바구니 페이지 테스트 > ", async () => {
render(<CartPage products={mockProducts} coupons={mockCoupons} />);
render(<CartPage />);
const product1 = screen.getByTestId("product-p1");
const product2 = screen.getByTestId("product-p2");
const product3 = screen.getByTestId("product-p3");
Expand Down Expand Up @@ -276,12 +270,12 @@
];

test("특정 제품으로 초기화할 수 있다.", () => {
const { result } = renderHook(() => useProducts(initialProducts));

Check failure on line 273 in src/basic/__tests__/basic.test.tsx

View workflow job for this annotation

GitHub Actions / basic

src/basic/__tests__/basic.test.tsx > basic > > useProducts > > 특정 제품으로 초기화할 수 있다.

TypeError: useProducts is not a function ❯ src/basic/__tests__/basic.test.tsx:273:43 ❯ TestComponent node_modules/@testing-library/react/dist/pure.js:331:27 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20
expect(result.current.products).toEqual(initialProducts);
});

test("제품을 업데이트할 수 있다.", () => {
const { result } = renderHook(() => useProducts(initialProducts));

Check failure on line 278 in src/basic/__tests__/basic.test.tsx

View workflow job for this annotation

GitHub Actions / basic

src/basic/__tests__/basic.test.tsx > basic > > useProducts > > 제품을 업데이트할 수 있다.

TypeError: useProducts is not a function ❯ src/basic/__tests__/basic.test.tsx:278:43 ❯ TestComponent node_modules/@testing-library/react/dist/pure.js:331:27 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20
const updatedProduct = { ...initialProducts[0], name: "Updated Product" };

act(() => {
Expand All @@ -298,7 +292,7 @@
});

test("새로운 제품을 추가할 수 있다.", () => {
const { result } = renderHook(() => useProducts(initialProducts));

Check failure on line 295 in src/basic/__tests__/basic.test.tsx

View workflow job for this annotation

GitHub Actions / basic

src/basic/__tests__/basic.test.tsx > basic > > useProducts > > 새로운 제품을 추가할 수 있다.

TypeError: useProducts is not a function ❯ src/basic/__tests__/basic.test.tsx:295:43 ❯ TestComponent node_modules/@testing-library/react/dist/pure.js:331:27 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20
const newProduct: Product = {
id: "2",
name: "New Product",
Expand Down Expand Up @@ -360,7 +354,7 @@

test("수량에 따라 올바른 할인을 적용해야 합니다.", () => {
const item: CartItem = { product: testProduct, quantity: 5 };
expect(cartUtils.calculateItemTotal(item)).toBe(400); // 500 * 0.8

Check failure on line 357 in src/basic/__tests__/basic.test.tsx

View workflow job for this annotation

GitHub Actions / basic

src/basic/__tests__/basic.test.tsx > basic > > cartUtils > calculateItemTotal > 수량에 따라 올바른 할인을 적용해야 합니다.

AssertionError: expected 500 to be 400 // Object.is equality - Expected + Received - 400 + 500 ❯ src/basic/__tests__/basic.test.tsx:357:52
});
});

Expand All @@ -385,7 +379,7 @@
test("쿠폰 없이 총액을 올바르게 계산해야 합니다.", () => {
const result = cartUtils.calculateCartTotal(cart, null);
expect(result.totalBeforeDiscount).toBe(400);
expect(result.totalAfterDiscount).toBe(380);

Check failure on line 382 in src/basic/__tests__/basic.test.tsx

View workflow job for this annotation

GitHub Actions / basic

src/basic/__tests__/basic.test.tsx > basic > > cartUtils > calculateCartTotal > 쿠폰 없이 총액을 올바르게 계산해야 합니다.

AssertionError: expected 580 to be 380 // Object.is equality - Expected + Received - 380 + 580 ❯ src/basic/__tests__/basic.test.tsx:382:43
expect(result.totalDiscount).toBe(20);
});

Expand All @@ -397,7 +391,7 @@
discountValue: 50,
};
const result = cartUtils.calculateCartTotal(cart, coupon);
expect(result.totalAfterDiscount).toBe(330);

Check failure on line 394 in src/basic/__tests__/basic.test.tsx

View workflow job for this annotation

GitHub Actions / basic

src/basic/__tests__/basic.test.tsx > basic > > cartUtils > calculateCartTotal > 금액쿠폰을 올바르게 적용해야 합니다.

AssertionError: expected 530 to be 330 // Object.is equality - Expected + Received - 330 + 530 ❯ src/basic/__tests__/basic.test.tsx:394:43
expect(result.totalDiscount).toBe(70);
});

Expand All @@ -409,7 +403,7 @@
discountValue: 10,
};
const result = cartUtils.calculateCartTotal(cart, coupon);
expect(result.totalAfterDiscount).toBe(342);

Check failure on line 406 in src/basic/__tests__/basic.test.tsx

View workflow job for this annotation

GitHub Actions / basic

src/basic/__tests__/basic.test.tsx > basic > > cartUtils > calculateCartTotal > 퍼센트 쿠폰을 올바르게 적용해야 합니다

AssertionError: expected 522 to be 342 // Object.is equality - Expected + Received - 342 + 522 ❯ src/basic/__tests__/basic.test.tsx:406:43
expect(result.totalDiscount).toBe(58);
});
});
Expand Down
73 changes: 15 additions & 58 deletions src/refactoring/App.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,10 @@
import { useState } from 'react';
import { CartPage } from './components/CartPage.tsx';
import { AdminPage } from './components/AdminPage.tsx';
import { Coupon, Product } from '../types.ts';
import { useCoupons, useProducts } from "./hooks";

const initialProducts: Product[] = [
{
id: 'p1',
name: '상품1',
price: 10000,
stock: 20,
discounts: [{ quantity: 10, rate: 0.1 }, { quantity: 20, rate: 0.2 }]
},
{
id: 'p2',
name: '상품2',
price: 20000,
stock: 20,
discounts: [{ quantity: 10, rate: 0.15 }]
},
{
id: 'p3',
name: '상품3',
price: 30000,
stock: 20,
discounts: [{ quantity: 10, rate: 0.2 }]
}
];

const initialCoupons: Coupon[] = [
{
name: '5000원 할인 쿠폰',
code: 'AMOUNT5000',
discountType: 'amount',
discountValue: 5000
},
{
name: '10% 할인 쿠폰',
code: 'PERCENT10',
discountType: 'percentage',
discountValue: 10
}
];
import { useState } from "react";
import { CartPage } from "./pages/CartPage.tsx";
import { AdminPage } from "./pages/AdminPage.tsx";
import { ProductProvider } from "./Providers/ProductProvider.tsx";
import { CouponProvider } from "./Providers/CouponProvider.tsx";

const App = () => {
const { products, updateProduct, addProduct } = useProducts(initialProducts);
const { coupons, addCoupon } = useCoupons(initialCoupons);
const [isAdmin, setIsAdmin] = useState(false);

return (
Expand All @@ -57,22 +16,20 @@ const App = () => {
onClick={() => setIsAdmin(!isAdmin)}
className="bg-white text-blue-600 px-4 py-2 rounded hover:bg-blue-100"
>
{isAdmin ? '장바구니 페이지로' : '관리자 페이지로'}
{isAdmin ? "장바구니 페이지로" : "관리자 페이지로"}
</button>
</div>
</nav>
<main className="container mx-auto mt-6">
{isAdmin ? (
<AdminPage
products={products}
coupons={coupons}
onProductUpdate={updateProduct}
onProductAdd={addProduct}
onCouponAdd={addCoupon}
/>
) : (
<CartPage products={products} coupons={coupons}/>
)}
<ProductProvider>
<CouponProvider>
{isAdmin ? (
<AdminPage />
) : (
<CartPage />
)}
</CouponProvider>
</ProductProvider>
</main>
</div>
);
Expand Down
24 changes: 24 additions & 0 deletions src/refactoring/Providers/CartContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useContext } from "react";
import React from "react";
import { useCart } from "../hooks/useCart.ts";

const CartContext = React.createContext<ReturnType<typeof useCart> | undefined>(
undefined,
);

export const useCartContext = () => {
const context = useContext(CartContext);
if (!context)
throw new Error("useCartContext must be used within CartProvider");
return context;
};

export const CartProvider: React.FC<{
children: React.ReactNode;
}> = ({ children }) => {
const cartHelper = useCart();

return (
<CartContext.Provider value={cartHelper}>{children}</CartContext.Provider>
);
};
25 changes: 25 additions & 0 deletions src/refactoring/Providers/CouponManageProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useContext } from "react";
import React from "react";
import { useCouponManage } from "../hooks/useCouponManage.ts";


const CouponManageContext = React.createContext<ReturnType<typeof useCouponManage> | undefined>(
undefined,
);

export const useCouponManageContext = () => {
const context = useContext(CouponManageContext);
if (!context)
throw new Error("useCouponManageContext must be used within CouponManageProvider");
return context;
};

export const CouponManageProvider: React.FC<{
children: React.ReactNode;
}> = ({ children }) => {
const cartHelper = useCouponManage();

return (
<CouponManageContext.Provider value={cartHelper}>{children}</CouponManageContext.Provider>
);
};
36 changes: 36 additions & 0 deletions src/refactoring/Providers/CouponProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Coupon } from "../../types.ts";
import { useContext, useState } from "react";
import React from "react";
import { INITIAL_COUPONS } from "../utils/constants.ts";

interface ICouponContextType {
coupons: Coupon[];
addCoupon: (newCoupon: Coupon) => void;
}

const CouponContext = React.createContext<ICouponContextType | undefined>(
undefined,
);

export const useCouponContext = () => {
const context = useContext(CouponContext);
if (!context)
throw new Error("useCouponContext must be used within CouponProvider");
return context;
};

export const CouponProvider: React.FC<{
children: React.ReactNode;
}> = ({ children }) => {
const [coupons, setCoupons] = useState<Coupon[]>(INITIAL_COUPONS);

const addCoupon = (newCoupon: Coupon) => {
setCoupons((prevCoupons) => [...prevCoupons, newCoupon]);
};

return (
<CouponContext.Provider value={{ coupons, addCoupon }}>
{children}
</CouponContext.Provider>
);
};
28 changes: 28 additions & 0 deletions src/refactoring/Providers/ProductManagementProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useContext } from "react";
import React from "react";
import { useProductManage } from "../hooks/useProductManage.ts";

const ProductManagementContext = React.createContext<
ReturnType<typeof useProductManage> | undefined
>(undefined);

export const useProductManagementContext = () => {
const context = useContext(ProductManagementContext);
if (!context)
throw new Error(
"useProductManagementContext must be used within ProductProvider",
);
return context;
};

export const ProductManagementProvider: React.FC<{
children: React.ReactNode;
}> = ({ children }) => {
const cartHelper = useProductManage();

return (
<ProductManagementContext.Provider value={cartHelper}>
{children}
</ProductManagementContext.Provider>
);
};
46 changes: 46 additions & 0 deletions src/refactoring/Providers/ProductProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Product } from "../../types.ts";
import { useContext, useState } from "react";
import React from "react";
import { INITIAL_PRODUCTS } from "../utils/constants.ts";

interface IProductContextType {
products: Product[];
updateProduct: (updateProduct: Product) => void;
addProduct: (newProduct: Product) => void;
}

const ProductContext = React.createContext<IProductContextType | undefined>(
undefined,
);

export const useProductContext = () => {
const context = useContext(ProductContext);
if (!context)
throw new Error("useProductContext must be used within ProductProvider");

Check failure on line 19 in src/refactoring/Providers/ProductProvider.tsx

View workflow job for this annotation

GitHub Actions / basic

src/basic/__tests__/basic.test.tsx > basic > > 시나리오 테스트 > > 관리자 페이지 테스트 >

Error: useProductContext must be used within ProductProvider ❯ Module.useProductContext src/refactoring/Providers/ProductProvider.tsx:19:11 ❯ Module.useProductManage src/refactoring/hooks/useProductManage.ts:6:51 ❯ ProductManagementProvider src/refactoring/Providers/ProductManagementProvider.tsx:21:22 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7

Check failure on line 19 in src/refactoring/Providers/ProductProvider.tsx

View workflow job for this annotation

GitHub Actions / basic

src/basic/__tests__/basic.test.tsx > basic > > 시나리오 테스트 > > 장바구니 페이지 테스트 >

Error: useProductContext must be used within ProductProvider ❯ Module.useProductContext src/refactoring/Providers/ProductProvider.tsx:19:11 ❯ CartPage src/refactoring/pages/CartPage.tsx:9:24 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20

Check failure on line 19 in src/refactoring/Providers/ProductProvider.tsx

View workflow job for this annotation

GitHub Actions / advacned

src/advanced/__tests__/advanced.test.tsx > advanced > > 시나리오 테스트 > > 관리자 페이지 테스트 >

Error: useProductContext must be used within ProductProvider ❯ Module.useProductContext src/refactoring/Providers/ProductProvider.tsx:19:11 ❯ Module.useProductManage src/refactoring/hooks/useProductManage.ts:6:51 ❯ ProductManagementProvider src/refactoring/Providers/ProductManagementProvider.tsx:21:22 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7

Check failure on line 19 in src/refactoring/Providers/ProductProvider.tsx

View workflow job for this annotation

GitHub Actions / advacned

src/advanced/__tests__/advanced.test.tsx > advanced > > 시나리오 테스트 > > 장바구니 페이지 테스트 >

Error: useProductContext must be used within ProductProvider ❯ Module.useProductContext src/refactoring/Providers/ProductProvider.tsx:19:11 ❯ CartPage src/refactoring/pages/CartPage.tsx:9:24 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ mountIndeterminateComponent node_modules/react-dom/cjs/react-dom.development.js:20103:13 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21626:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20
return context;
};

export const ProductProvider: React.FC<{
children: React.ReactNode;
}> = ({ children }) => {
const [products, setProducts] = useState<Product[]>(INITIAL_PRODUCTS);
const updateProduct = (updateProduct: Product) => {
setProducts((prevProducts) =>
prevProducts.map((product) =>
// 중복에 대한 처리
product.id === updateProduct.id
? { ...product, ...updateProduct }
: product,
),
);
};
const addProduct = (newProduct: Product) => {
setProducts((prevProducts) => [...prevProducts, newProduct]);
};

return (
<ProductContext.Provider value={{ products, updateProduct, addProduct }}>
{children}
</ProductContext.Provider>
);
};
87 changes: 87 additions & 0 deletions src/refactoring/components/Admin/CouponManagement.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { useCouponManageContext } from "../../Providers/CouponManageProvider";
import { useCouponContext } from "../../Providers/CouponProvider";

const CouponManagement = () => {
const { coupons } = useCouponContext();
const { newCoupon, setNewCoupon, handleAddCoupon } = useCouponManageContext();
return (
<div>
<h2 className="text-2xl font-semibold mb-4">쿠폰 관리</h2>
<div className="bg-white p-4 rounded shadow">
<div className="space-y-2 mb-4">
<input
type="text"
placeholder="쿠폰 이름"
value={newCoupon.name}
onChange={(e) =>
setNewCoupon({ ...newCoupon, name: e.target.value })
}
className="w-full p-2 border rounded"
/>
<input
type="text"
placeholder="쿠폰 코드"
value={newCoupon.code}
onChange={(e) =>
setNewCoupon({ ...newCoupon, code: e.target.value })
}
className="w-full p-2 border rounded"
/>
<div className="flex gap-2">
<select
value={newCoupon.discountType}
onChange={(e) =>
setNewCoupon({
...newCoupon,
discountType: e.target.value as "amount" | "percentage",
})
}
className="w-full p-2 border rounded"
>
<option value="amount">금액(원)</option>
<option value="percentage">할인율(%)</option>
</select>
<input
type="number"
placeholder="할인 값"
value={newCoupon.discountValue}
onChange={(e) =>
setNewCoupon({
...newCoupon,
discountValue: parseInt(e.target.value),
})
}
className="w-full p-2 border rounded"
/>
</div>
<button
onClick={handleAddCoupon}
className="w-full bg-green-500 text-white p-2 rounded hover:bg-green-600"
>
쿠폰 추가
</button>
</div>
<div>
<h3 className="text-lg font-semibold mb-2">현재 쿠폰 목록</h3>
<div className="space-y-2">
{coupons.map((coupon, index) => (
<div
key={index}
data-testid={`coupon-${index + 1}`}
className="bg-gray-100 p-2 rounded"
>
{coupon.name} ({coupon.code}):
{coupon.discountType === "amount"
? `${coupon.discountValue}원`
: `${coupon.discountValue}%`}{" "}
할인
</div>
))}
</div>
</div>
</div>
</div>
);
};

export default CouponManagement;
Loading
Loading