Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
8fa29fe
chore: eslint, prettier 설정
chan9yu Aug 4, 2025
bd1782e
chore: lefthook 설정
chan9yu Aug 4, 2025
b55968b
Merge branch 'hanghae-plus:main' into main
chan9yu Aug 6, 2025
ced4729
chore: prettier와 eslint에서 테스트 파일 및 원본 파일 제외
chan9yu Aug 5, 2025
66c9df7
fix: ESLint와 Prettier 규칙 적용
chan9yu Aug 7, 2025
994fab1
fix: ESLint와 Prettier 규칙 적용
chan9yu Aug 7, 2025
1bfc82a
feat: app 디렉토리 구성 및 이관작업, Header 컴포넌트와 Admin / Cart 페이지 분리작업
chan9yu Aug 7, 2025
e7041e7
feat: cart 엔티티 관련 로직 분리 및 구조화
chan9yu Aug 7, 2025
8af8c5f
feat: coupon 엔티티 관련 로직 분리 및 구조화
chan9yu Aug 7, 2025
4b1b495
feat: product 엔티티 관련 로직 분리 및 구조화
chan9yu Aug 7, 2025
f11179d
feat: 공용 notifications 관련 로직 분리 및 구조화
chan9yu Aug 7, 2025
51637d5
feat: useLocalStorageState 훅 구성, App.tsx 적용
chan9yu Aug 7, 2025
b777667
refactor: SVG 아이콘들을 재사용 가능한 컴포넌트로 분리
chan9yu Aug 7, 2025
95b4ba0
feat: useDebounceValue 훅 구성, App.tsx 적용
chan9yu Aug 7, 2025
43ef38e
feat: 디바운스 상태 관리 추상화를 위한 useDebounceState 훅 구성
chan9yu Aug 7, 2025
c6bdbc7
refactor: NotificationList 컴포넌트 위치 이관작업
chan9yu Aug 7, 2025
a95e154
chore: tailwind-variants 의존성 설치
chan9yu Aug 7, 2025
9ab05be
feat: 공용 SearchInput UI 컴포넌트 구성
chan9yu Aug 7, 2025
d2b1e24
feat: 공용 BadgeContainer UI 컴포넌트 구성
chan9yu Aug 7, 2025
2382d34
feat: 공용 useToggle hook 구성
chan9yu Aug 7, 2025
96afdf3
feat: AdminToggleButton 컴포넌트 추가 및 관리자 모드 로직 개선
chan9yu Aug 7, 2025
da9a011
feat: 공용 Button 컴포넌트 구성 및 기존 버튼들 교체
chan9yu Aug 7, 2025
4651a4c
refactor: Notification 컴포넌트 구조 개선
chan9yu Aug 7, 2025
efd4552
refactor: 페이지별 로직 분리로 App.tsx 복잡도 개선
chan9yu Aug 8, 2025
eda3a15
feat: CartPage 컴포넌트 분리 작업
chan9yu Aug 8, 2025
9466d7b
feat: AdminPage와 CartPage 컴포넌트 분리 및 DDD 구조 적용
chan9yu Aug 8, 2025
e3f82b5
chore: root types.ts 내용 원복
chan9yu Aug 8, 2025
3d635cf
feat: cart 엔티티 서비스 로직 재구성
chan9yu Aug 8, 2025
7b7dc8c
fix: 라벨텍스트 중복 렌더링 수정
chan9yu Aug 8, 2025
65810c9
feat: coupon 엔티티 서비스 로직 재구성
chan9yu Aug 8, 2025
004c76e
feat: product 엔티티 서비스 로직 재구성
chan9yu Aug 8, 2025
31533d7
Merge branch 'feat/basic'
chan9yu Aug 8, 2025
83bccfa
chore: advanced 시작
chan9yu Aug 8, 2025
a4101b7
chore: jotai 의존성 설치
chan9yu Aug 8, 2025
c3829a1
feat: 전역상태관리 구축 props drilling 최소화 작업
chan9yu Aug 8, 2025
848ce07
feat: NotificationType 추가
chan9yu Aug 8, 2025
979f4fc
chore: 빌드 설정
chan9yu Aug 8, 2025
e8c8fce
Merge pull request #2 from chan9yu/feat/advanced
chan9yu Aug 8, 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
18 changes: 0 additions & 18 deletions .eslintrc.cjs

This file was deleted.

1 change: 0 additions & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
- [ ] 도메인 컴포넌트에 도메인 props는 남기고 props drilling을 유발하는 불필요한 props는 잘 제거했나요?
- [ ] 전체적으로 분리와 재조립이 더 수월해진 결합도가 낮아진 코드가 되었나요?


## 과제 셀프회고

<!-- 과제에 대한 회고를 작성해주세요 -->
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

name: CI

on:
Expand Down
40 changes: 40 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Deploy to GitHub Pages

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"

- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: latest

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build
run: pnpm build:advanced
env:
NODE_ENV: production

- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
if: github.ref == 'refs/heads/main'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22
35 changes: 35 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Prettier 무시 파일들
node_modules/
dist/
build/
.vite/
coverage/
pnpm-lock.yaml
package-lock.json
yarn.lock

# 바이너리 파일들
*.png
*.jpg
*.jpeg
*.gif
*.svg
*.ico
*.woff
*.woff2
*.ttf
*.eot

# 테스트 파일들
src/advanced/__tests__/
src/basic/__tests__/
src/origin/__tests__/

# 원본 파일들
src/origin/
src/refactoring(hint)/
index.origin.html

# 기타
.env*
.DS_Store
12 changes: 12 additions & 0 deletions .prettierrc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
printWidth: 100
tabWidth: 2
useTabs: false
singleQuote: false
semi: true
bracketSpacing: true
arrowParens: always
trailingComma: none
jsxSingleQuote: false
bracketSameLine: false
plugins:
- prettier-plugin-tailwindcss
57 changes: 26 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
이번 과제는 단일책임원칙을 위반한 거대한 컴포넌트를 리팩토링 하는 것입니다. React의 컴포넌트는 단일 책임 원칙(Single Responsibility Principle, SRP)을 따르는 것이 좋습니다. 즉, 각 컴포넌트는 하나의 책임만을 가져야 합니다. 하지만 실제로는 여러 가지 기능을 가진 거대한 컴포넌트를 작성하는 경우가 많습니다.

[목표]

## 1. 취지

- React의 추구미(!)를 이해해보아요!
- 단일 책임 원칙(SRP)을 위반한 거대한 컴포넌트가 얼마나 안 좋은지 경험해보아요!
- 단일 책임이라는 개념을 이해하기 상태, 순수함수, 컴포넌트, 훅 등 다양한 계층을 이해해합니다.
- 엔티티와 UI를 구분하고 데이터, 상태, 비즈니스 로직 등의 특징이 다르다는 것을 이해해보세요.
- 이를 통해 적절한 Custom Hook과 유틸리티 함수를 분리하고, 컴포넌트 계층 구조를 정리하는 능력을 갖춥니다!


## 2. 목표

모든 소프트웨어에는 적절한 책임과 계층이 존재합니다. 하나의 계층(Component)만으로 소프트웨어를 구성하게 되면 나중에는 정리정돈이 되지 않은 코드를 만나게 됩니다. 예전에는 이러한 BestPractice에 대해서 혼돈의 시대였지만 FE가 진화를 거듭하는 과정에서 적절한 계측에 대한 합의가 이루어지고 있는 상태입니다.
Expand All @@ -22,7 +23,7 @@ React의 주요 책임 계층은 Component, hook, function 등이 있습니다.
- 엔티티를 다루는 상태와 그렇지 않은 상태 - cart, isCartFull vs isShowPopup
- 엔티티를 다루는 컴포넌트와 훅 - CartItemView, useCart(), useProduct()
- 엔티티를 다루지 않는 컴포넌트와 훅 - Button, useRoute, useEvent 등
- 엔티티를 다루는 함수와 그렇지 않은 함수 - calculateCartTotal(cart) vs capaitalize(str)
- 엔티티를 다루는 함수와 그렇지 않은 함수 - calculateCartTotal(cart) vs capaitalize(str)

이번 과제의 목표는 이러한 계층을 이해하고 분리하여 정리정돈을 하는 기준이나 방법등을 습득하는데 있습니다.

Expand All @@ -35,34 +36,34 @@ basic의 경우 상태관리를 쓰지 않고 작업을 해주세요.
#### 1) 장바구니 페이지 요구사항

- 상품 목록
- 상품명, 가격, 재고 수량 등을 표시
- 각 상품의 할인 정보 표시
- 재고가 없는 경우 품절 표시가 되며 장바구니 추가가 불가능
- 상품명, 가격, 재고 수량 등을 표시
- 각 상품의 할인 정보 표시
- 재고가 없는 경우 품절 표시가 되며 장바구니 추가가 불가능
- 장바구니
- 장바구니 내 상품 수량 조절 가능
- 각 상품의 이름, 가격, 수량과 적용된 할인율을 표시
- 적용된 할인율 표시 (예: "10% 할인 적용")
- 장바구니 내 모든 상품의 총액을 계산해야
- 장바구니 내 상품 수량 조절 가능
- 각 상품의 이름, 가격, 수량과 적용된 할인율을 표시
- 적용된 할인율 표시 (예: "10% 할인 적용")
- 장바구니 내 모든 상품의 총액을 계산해야
- 쿠폰 할인
- 할인 쿠폰을 선택하면 적용하면 최종 결제 금액에 할인정보가 반영
- 할인 쿠폰을 선택하면 적용하면 최종 결제 금액에 할인정보가 반영
- 주문요약
- 할인 전 총 금액
- 총 할인 금액
- 최종 결제 금액
- 할인 전 총 금액
- 총 할인 금액
- 최종 결제 금액

#### 2) 관리자 페이지 요구사항

- 상품 관리
- 상품 정보 (상품명, 가격, 재고, 할인율) 수정 가능
- 새로운 상품 추가 가능
- 상품 제거 가능
- 상품 정보 (상품명, 가격, 재고, 할인율) 수정 가능
- 새로운 상품 추가 가능
- 상품 제거 가능
- 할인 관리
- 상품별 할인 정보 추가/수정/삭제 가능
- 할인 조건 설정 (구매 수량에 따른 할인율)
- 상품별 할인 정보 추가/수정/삭제 가능
- 할인 조건 설정 (구매 수량에 따른 할인율)
- 쿠폰 관리
- 전체 상품에 적용 가능한 쿠폰 생성
- 쿠폰 정보 입력 (이름, 코드, 할인 유형, 할인 값)
- 할인 유형은 금액 또는 비율로 설정 가능
- 전체 상품에 적용 가능한 쿠폰 생성
- 쿠폰 정보 입력 (이름, 코드, 할인 유형, 할인 값)
- 할인 유형은 금액 또는 비율로 설정 가능

### (2) 코드 개선 요구사항

Expand All @@ -88,9 +89,8 @@ basic의 경우 상태관리를 쓰지 않고 작업을 해주세요.

### (3) 테스트 코드 통과하기



## 심화과제: Props drilling

- 이번 심화과제는 Context나 Jotai를 사용해서 Props drilling을 없애는 것입니다.
- 어떤 props는 남겨야 하는지, 어떤 props는 제거해야 하는지에 대한 기준을 세워보세요.
- Context나 Jotai를 사용하여 상태를 관리하는 방법을 익히고, 이를 통해 컴포넌트 간의 데이터 전달을 효율적으로 처리할 수 있습니다.
Expand All @@ -102,25 +102,20 @@ basic의 경우 상태관리를 쓰지 않고 작업을 해주세요.
- basic에서 열심히 컴포넌트를 분리해주었겠죠?
- 아마 그 과정에서 container - presenter 패턴으로 만들어졌기에 props drilling이 상당히 불편했을거에요.
- 그래서 심화과제에서는 props drilling을 제거하는 작업을 할거에요.
- 전역상태관리가 아직 낯설다 - jotai를 선택해주세요 (참고자료 참고)
- 나는 깊이를 공부해보고 싶아. - context를 선택해서 상태관리를 해보세요.

- 전역상태관리가 아직 낯설다 - jotai를 선택해주세요 (참고자료 참고)
- 나는 깊이를 공부해보고 싶아. - context를 선택해서 상태관리를 해보세요.

### (1) 요구사항

- 불필요한 props를 제거하고, 필요한 props만을 전달하도록 개선합니다.
- Context나 Jotai를 사용하여 상태를 관리합니다.
- 테스트 코드를 통과합니다.


### (2) 힌트

- UI 컴포넌트와 엔티티 컴포넌트는 각각 props를 다르게 받는게 좋습니다.
- UI 컴포넌트는 재사용과 독립성을 위해 상태를 최소화하고,
- UI 컴포넌트는 재사용과 독립성을 위해 상태를 최소화하고,
- 엔티티 컴포넌트는 가급적 엔티티를 중심으로 전달받는 것이 좋습니다.
- 특히 콜백의 경우,
- UI 컴포넌트는 이벤트 핸들러를 props로 받아서 처리하도록 해서 재사용성을 높이지만,
- 엔티티 컴포넌트는 props가 아닌 컴포넌트 내부에서 상태를 관리하는 것이 좋습니다.



88 changes: 88 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import js from "@eslint/js";
import tseslint from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
import prettierConfig from "eslint-config-prettier";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import simpleImportSort from "eslint-plugin-simple-import-sort";
import globals from "globals";

const ignoresConfig = {
ignores: [
"node_modules/**",
"dist/**",
"build/**",
".vite/**",
"coverage/**",
"src/**/__tests__/**",
"src/origin/",
"src/refactoring(hint)/",
"index.origin.html"
]
};

const baseConfig = {
files: ["**/*.{js,jsx,ts,tsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
sourceType: "module"
},
rules: {
...js.configs.recommended.rules
}
};

const typescriptConfig = {
files: ["**/*.{ts,tsx}"],
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: "latest",
sourceType: "module"
}
},
plugins: {
"@typescript-eslint": tseslint
},
rules: {
...tseslint.configs.recommended.rules
}
};

const reactConfig = {
files: ["**/*.{jsx,tsx}"],
plugins: {
"react-hooks": reactHooks,
"react-refresh": reactRefresh
},
rules: {
...reactHooks.configs.recommended.rules,
"react-refresh/only-export-components": [
"warn",
{
allowConstantExport: true
}
]
}
};

const importSortConfig = {
files: ["**/*.{js,jsx,ts,tsx}"],
plugins: {
"simple-import-sort": simpleImportSort
},
rules: {
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error"
}
};

export default [
ignoresConfig,
baseConfig,
typescriptConfig,
reactConfig,
importSortConfig,
prettierConfig
];
22 changes: 11 additions & 11 deletions index.advanced.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>장바구니로 학습하는 디자인패턴</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100">
<div id="root"></div>
<script type="module" src="/src/advanced/main.tsx"></script>
</body>
</html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>장바구니로 학습하는 디자인패턴</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100">
<div id="root"></div>
<script type="module" src="/src/advanced/main.tsx"></script>
</body>
</html>
22 changes: 11 additions & 11 deletions index.basic.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>장바구니로 학습하는 디자인패턴</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100">
<div id="root"></div>
<script type="module" src="/src/basic/main.tsx"></script>
</body>
</html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>장바구니로 학습하는 디자인패턴</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100">
<div id="root"></div>
<script type="module" src="/src/basic/main.tsx"></script>
</body>
</html>
11 changes: 11 additions & 0 deletions lefthook.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
pre-commit:
parallel: true
commands:
lint:
glob: "**/*.{js,ts,tsx}"
run: pnpm lint:check
fail_text: "🚨 ESLint 검사 실패! 'pnpm lint'로 수정 후 다시 커밋하세요."
format:
glob: "**/*.{js,ts,tsx,html,json,yaml,yml,md}"
run: pnpm format:check
fail_text: "🎨 코드 포맷팅이 필요합니다! 'pnpm format'로 수정 후 다시 커밋하세요."
Loading