✨ Feat 상품 등록 관련 entity 레이어 구현 (API, Type, Constants)#196
Conversation
📝 WalkthroughWalkthroughseller 앱의 상품 생성 기능 구조 개편으로 배럴 재내보내기 파일 추가, 신규 타입 정의, API 및 Query 함수 작성, 그리고 기존 상수 값의 업데이트 포함. Changes
핵심 이슈1️⃣ storageGuide 필드 완전 제거 → 하위호환성 위험
2️⃣ discountType 값 변경('won'/'percentage' → 'AMOUNT'/'RATE')
3️⃣ production-time value 형식 단순화(범위 → 시작시간)
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 20
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/seller/src/entity/products/create/create-form/create-indivisual-form.type.ts (1)
1-1: 🛠️ Refactor suggestion | 🟠 Major파일명/경로 오타:
indivisual→individual로 수정 필요파일
create-indivisual-form.type.ts의 철자 오류가 3개 import 경로에 확산됨(index.ts, create-form/index.ts, create-form.types.ts).
검색성 저하·새 팀원 혼동, 추후 대량 변경 시 PR 비대화 초래.
신설 entity이므로 지금create-individual-form.type.ts로 정정 및 3개 import 경로 수정하는 것이 최소 비용.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: ad31b5c8-b3e7-451a-9bbc-31c404b98eca
📒 Files selected for processing (17)
apps/seller/src/entity/products/create/create-delivery/index.tsapps/seller/src/entity/products/create/create-disclosure/index.tsapps/seller/src/entity/products/create/create-disclosure/product-disclosure.constant.tsapps/seller/src/entity/products/create/create-form/create-form.types.tsapps/seller/src/entity/products/create/create-form/create-indivisual-form.type.tsapps/seller/src/entity/products/create/create-form/create.api.tsapps/seller/src/entity/products/create/create-form/create.query.tsapps/seller/src/entity/products/create/create-form/create.type.tsapps/seller/src/entity/products/create/create-form/index.tsapps/seller/src/entity/products/create/create-header/index.tsapps/seller/src/entity/products/create/create-info/index.tsapps/seller/src/entity/products/create/create-info/product-discount-type.constants.tsapps/seller/src/entity/products/create/create-info/production-time.constants.tsapps/seller/src/entity/products/create/create-options/index.tsapps/seller/src/entity/products/create/create-store/create-store-header.type.tsapps/seller/src/entity/products/create/create-store/index.tsapps/seller/src/entity/products/index.ts
| { label: '7. 포장 단위 별 내용물 용량(중량) 수량', key: 'packagingContents' }, | ||
| { label: '8. 포장 단위별 수량', key: 'packagingQuantityUnit' }, | ||
| { | ||
| label: '10. 원재료명 (농수산물의 원산지 표시 등에 관한 법률)', | ||
| label: '9. 원재료명 (농수산물의 원산지 표시 등에 관한 법률)', | ||
| key: 'rawMaterialName', | ||
| }, | ||
| { label: '11. 영양성분', key: 'nutritionInfo' }, | ||
| { label: '12. 유전자 변형 식품에 해당하는 경우의 표시', key: 'transgenic' }, | ||
| { label: '13. 소비자 안전을 위한 주의사항', key: 'customerWarning' }, | ||
| { label: '14. 수입 식품의 경우', key: 'importFood' }, | ||
| { label: '10. 영양성분', key: 'nutritionInfo' }, | ||
| { label: '11. 유전자 변형 식품에 해당하는 경우의 표시', key: 'transgenic' }, | ||
| { label: '12. 소비자 안전을 위한 주의사항', key: 'customerWarning' }, | ||
| { label: '13. 수입 식품의 경우', key: 'importFood' }, |
There was a problem hiding this comment.
storageGuide 제거 — 기존 저장 데이터/응답 호환성 점검
스키마가 DISCLOSURE_FIELDS 기반으로 동적 생성되어 신규 폼은 정상 동작하지만, 서버 응답이나 임시 저장(draft)에 productInfoNotice.storageGuide가 포함될 경우 zod에 의해 unknown 키로 떨어지거나(strict 시 실패) 사용자에게 노출되지 않습니다. 백엔드 스펙과 일치 여부, 그리고 기존 상품의 마이그레이션/표기 처리(예: 8/9번 라벨 재번호로 인한 사용자 혼선) 확인 부탁드립니다.
| export type CreateFormType = ProductFormInput & | ||
| DeliveryFormInput & { | ||
| options: ProductOptionFormInput[] // Feature의 스키마 대신 Entity의 순수 타입을 사용 | ||
| } & ProductDisclosureFormInput & | ||
| ThumbnailFormInput |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
파일명 일관성 점검
같은 디렉터리 내에 create.type.ts, create-indivisual-form.type.ts(단수)와 create-form.types.ts(복수)가 혼재합니다. 배럴에서 export *로 합쳐지므로 동작엔 문제 없지만 컨벤션 통일을 권장합니다(.type.ts로 통일).
| export type ThumbnailFormInput = { | ||
| mainImage: File | null | ||
| extraImages: File[] | ||
| } |
There was a problem hiding this comment.
ThumbnailFormInput.mainImage 타입 점검
원인: File | null로 선언. 폼 초기값/리셋 시 undefined도 흔함.
영향: react-hook-form defaultValues에서 undefined를 쓰면 타입 불일치, 또는 매번 null로 초기화 강제.
대안: 사용 패턴 확인 후 mainImage?: File | null 또는 File | null 중 하나로 통일하고 폼 기본값과 일치시켜 주세요(요약본은 mainImage?: File로 기술됨 — 실제 코드와 차이가 있어 검증 필요).
|
|
||
| export type ProductOptionFormInput = { | ||
| mainCategory: string | ||
| mainCategory: 'bread' | 'snack' | '' |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Optional: mainCategory에 빈 문자열 허용
빈 문자열을 union에 포함하면 “미선택” 상태가 도메인 값처럼 취급되어 zod refine 등에서 별도 가드가 필요해집니다. 'bread' | 'snack' | null(또는 옵셔널)로 표현하면 의도가 더 명확합니다. 현재 폼 RHF 사용 컨벤션과 충돌하지 않는지 확인 후 결정하세요.
| @@ -0,0 +1,32 @@ | |||
| export type ProductFileType = Record<string, boolean> | |||
There was a problem hiding this comment.
ProductFileType 오타로 보임 → ProductFieldType
필드(productFields/setProductFields)에서 사용되는데 이름이 "File"이라 가독성을 해칩니다. 외부 노출 전(이번 PR 내) 정정해 두시는 게 좋습니다.
✏️ 제안
-export type ProductFileType = Record<string, boolean>
+export type ProductFieldType = Record<string, boolean>
...
- productFields: ProductFileType
- setProductFields: React.Dispatch<React.SetStateAction<ProductFileType>>
+ productFields: ProductFieldType
+ setProductFields: React.Dispatch<React.SetStateAction<ProductFieldType>>| setProductFields: React.Dispatch<React.SetStateAction<ProductFileType>> | ||
| setCurrentStep: React.Dispatch<React.SetStateAction<number>> | ||
| setHeaderHeight: React.Dispatch<React.SetStateAction<number>> | ||
| scrollToStep: (index: number) => void | ||
|
|
||
| isScrollingToStep: React.MutableRefObject<boolean> | ||
|
|
||
| nutritionDataList: NutritionData[] | ||
| setNutritionData: (index: number, data: NutritionData) => void | ||
| activeTags: ActiveTags | ||
|
|
||
| productPrice: number | null | ||
| setProductPrice: React.Dispatch<React.SetStateAction<number | null>> |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
React.* 글로벌 네임스페이스 의존 — 명시적 import 권장
import type 없이 React.Dispatch/SetStateAction/MutableRefObject 등을 사용하면 tsconfig의 jsx 설정이나 글로벌 타입 노출에 의존하게 됩니다. 명시 import가 더 안전하고 IDE 친화적입니다.
♻️ 제안
+import type { Dispatch, SetStateAction, RefObject } from 'react'
+
export type ProductFileType = Record<string, boolean>
...
- setProductFields: React.Dispatch<React.SetStateAction<ProductFileType>>
- setCurrentStep: React.Dispatch<React.SetStateAction<number>>
- setHeaderHeight: React.Dispatch<React.SetStateAction<number>>
+ setProductFields: Dispatch<SetStateAction<ProductFileType>>
+ setCurrentStep: Dispatch<SetStateAction<number>>
+ setHeaderHeight: Dispatch<SetStateAction<number>>
...
- isScrollingToStep: React.MutableRefObject<boolean>
+ isScrollingToStep: RefObject<boolean>
...
- setProductPrice: React.Dispatch<React.SetStateAction<number | null>>
+ setProductPrice: Dispatch<SetStateAction<number | null>>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| setProductFields: React.Dispatch<React.SetStateAction<ProductFileType>> | |
| setCurrentStep: React.Dispatch<React.SetStateAction<number>> | |
| setHeaderHeight: React.Dispatch<React.SetStateAction<number>> | |
| scrollToStep: (index: number) => void | |
| isScrollingToStep: React.MutableRefObject<boolean> | |
| nutritionDataList: NutritionData[] | |
| setNutritionData: (index: number, data: NutritionData) => void | |
| activeTags: ActiveTags | |
| productPrice: number | null | |
| setProductPrice: React.Dispatch<React.SetStateAction<number | null>> | |
| import type { Dispatch, SetStateAction, MutableRefObject } from 'react' | |
| export type ProductFileType = Record<string, boolean> | |
| setProductFields: Dispatch<SetStateAction<ProductFileType>> | |
| setCurrentStep: Dispatch<SetStateAction<number>> | |
| setHeaderHeight: Dispatch<SetStateAction<number>> | |
| scrollToStep: (index: number) => void | |
| isScrollingToStep: MutableRefObject<boolean> | |
| nutritionDataList: NutritionData[] | |
| setNutritionData: (index: number, data: NutritionData) => void | |
| activeTags: ActiveTags | |
| productPrice: number | null | |
| setProductPrice: Dispatch<SetStateAction<number | null>> |
| setHeaderHeight: React.Dispatch<React.SetStateAction<number>> | ||
| scrollToStep: (index: number) => void | ||
|
|
||
| isScrollingToStep: React.MutableRefObject<boolean> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 전역 React 네임스페이스 사용 가능 여부 및 동일 패턴 점검
rg -nP --type=ts -C2 'React\.MutableRefObject\b'
fd -t f 'tsconfig*.json' | xargs -I{} sh -c 'echo "==> {}"; cat "{}"' | rg -nC2 '"jsx"|"types"'Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 858
🏁 Script executed:
cat package.json | grep -A5 '"react"'Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 70
🏁 Script executed:
rg -n 'isScrollingToStep' --type=ts --type=tsx -B2 -A2Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 116
🏁 Script executed:
rg -n 'useRef.*isScrollingToStep|isScrollingToStep.*useRef' --type=ts --type=tsx -B3 -A3Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 116
🏁 Script executed:
rg -n 'const.*useRef|useRef' apps/seller/src/entity/products/create/create-store/ --type=ts --type=tsx -B1 -A1Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 116
🏁 Script executed:
fd 'package.json' --type fRepository: eco-dessert-platform/dessert-front-dashboard
Length of output: 277
🏁 Script executed:
rg -n 'isScrollingToStep' --type ts -B2 -A2Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 635
🏁 Script executed:
fd -t f '\.(ts|js)$' apps/seller/src/entity/products/create/create-store/ | head -20Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 229
🏁 Script executed:
cat apps/seller/package.json | grep -A10 -B2 '"react"'Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 491
🏁 Script executed:
cat apps/seller/src/entity/products/create/create-store/index.tsRepository: eco-dessert-platform/dessert-front-dashboard
Length of output: 193
🏁 Script executed:
rg -n 'CreateStoreHeaderContextValue|useRef' apps/seller/src/entity/products/create/create-store/ --type ts -B2 -A2Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 70
🏁 Script executed:
rg -n 'CreateFormHeaderType|CreateStoreHeaderContextValue' apps/seller/src/ --type ts -B1 -A3Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 888
🏁 Script executed:
rg -n 'useContext.*CreateStoreHeader|CreateStoreHeader.*useContext' apps/seller/src/ --type ts -B2 -A2Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 70
🏁 Script executed:
fd -t f 'create-store' apps/seller/src/ | xargs ls -laRepository: eco-dessert-platform/dessert-front-dashboard
Length of output: 216
🏁 Script executed:
rg -n 'CreateFormHeaderType' apps/seller/src/ --type ts -lRepository: eco-dessert-platform/dessert-front-dashboard
Length of output: 229
🏁 Script executed:
rg -n 'useRef.*boolean\|scrollingToStep\|isScrolling' apps/seller/src/ --type ts -B3 -A1Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 70
🏁 Script executed:
cat -n apps/seller/src/entity/products/create/create-store/create-store-header.type.tsRepository: eco-dessert-platform/dessert-front-dashboard
Length of output: 1209
🏁 Script executed:
rg -n 'CreateContext\|useRef' apps/seller/src/entity/products/create/ --type ts -B2 -A3Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 70
🏁 Script executed:
fd -t f 'create' apps/seller/src/entity/products/ -name '*.ts' | head -20Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 319
🌐 Web query:
React 19 MutableRefObject deprecated removed
💡 Result:
In React 19, MutableRefObject is deprecated in favor of a unified RefObject type. All refs returned by useRef are now mutable by default (current: T, not readonly current: T | null), useRef requires an initial argument (useRef(null) or useRef(undefined) for convenience), and refs are no longer sometimes immutable. This simplifies typing and resolves previous confusion where refs initialized with null couldn't be mutated. It is not removed—it's deprecated, so still available during migration. Codemods like refobject-defaults and useRef-required-initial handle updates automatically. Official React 19 docs confirm: "MutableRef is now deprecated in favor of a single RefObject type which useRef will always return." All refs mutable by default.
Citations:
- 1: https://react.dev/blog/2024/04/25/react-19-upgrade-guide
- 2: [react] Types for React 19 DefinitelyTyped/DefinitelyTyped#69022
- 3: [react] Make all refs mutable by default DefinitelyTyped/DefinitelyTyped#64896
- 4: https://www.linkedin.com/posts/divyamdotfoo_til-in-react-19-the-mutablerefobject-type-activity-7225176868801404929-JJjB
- 5: Change the type of refs for react 19 juliencrn/usehooks-ts#680
🏁 Script executed:
rg -rn 'CreateFormHeaderType' apps/seller/src/entity/products/create/ --type tsRepository: eco-dessert-platform/dessert-front-dashboard
Length of output: 255
🏁 Script executed:
find apps/seller/src/entity/products/create -type f -name '*.ts' | head -30Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 1869
🏁 Script executed:
rg -n 'isScrollingToStep' apps/seller/src/ --type ts -B5 -A5Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 1425
React 19에서 deprecated된 React.MutableRefObject 타입 제거
React 19부터 모든 ref가 mutable이 되어 MutableRefObject는 더 이상 사용되지 않습니다. React.RefObject<boolean>으로 변경하세요.
♻️ 제안
- isScrollingToStep: React.MutableRefObject<boolean>
+ isScrollingToStep: React.RefObject<boolean>| export type { | ||
| NutritionData, | ||
| ActiveTags, | ||
| CreateFormHeaderType, | ||
| } from './create-store-header.type' |
There was a problem hiding this comment.
ProductFileType 재내보내기 누락
CreateFormHeaderType.productFields/setProductFields의 타입이라 소비자가 setter를 prop으로 전달하거나 컴포넌트 외부에서 동일 형태를 선언할 때 직접 참조가 필요합니다. PR의 "Public API 누락" 검토 항목과 직결되는 부분입니다.
♻️ 제안
export type {
+ ProductFileType,
NutritionData,
ActiveTags,
CreateFormHeaderType,
} from './create-store-header.type'| export * from './create/create-form/create-form.types' | ||
| export * from './create/create-form/create-indivisual-form.type' | ||
| export * from './create/create-form/create.api' | ||
| export * from './create/create-form/create.query' | ||
| export * from './create/create-form/create.type' |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Optional: export * 광역 재내보내기
api/query/type을 모두 export *로 내보내면 동일 식별자 충돌 시 컴파일 에러로 발견이 늦습니다. 가능하면 index.ts에서 named re-export를 사용해 공개 표면을 명시하세요(이미 create-form/index.ts에는 잘 정리됨 → 상위 배럴도 동일 패턴 권장).
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -nP "create-form/product-form\.type" --type=ts -C2Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 815
🏁 Script executed:
find apps/seller/src/entity/products -type f -name "*.type.ts" | head -20Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 445
🏁 Script executed:
cat -n apps/seller/src/entity/products/index.tsRepository: eco-dessert-platform/dessert-front-dashboard
Length of output: 1516
🏁 Script executed:
rg "ProductOptionFormInput" apps/seller/src --type=ts -lRepository: eco-dessert-platform/dessert-front-dashboard
Length of output: 473
🏁 Script executed:
rg "product-form\.type" apps/seller/src --type=tsRepository: eco-dessert-platform/dessert-front-dashboard
Length of output: 278
🏁 Script executed:
cat -n apps/seller/src/entity/products/create/create-form/create-indivisual-form.type.ts | head -30Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 1047
🏁 Script executed:
cat -n apps/seller/src/entity/products/create/create-form/create-form.types.ts | head -30Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 550
🏁 Script executed:
rg "ProductOptionFormInput" apps/seller/src/entity/products --type=ts -B2 -A2Repository: eco-dessert-platform/dessert-front-dashboard
Length of output: 2108
product-form.type 경로 참조로 인한 빌드 실패 발생
use-product-options.form.hook.ts가 존재하지 않는 @/entity/products/create/create-form/product-form.type에서 ProductOptionFormInput을 임포트하고 있습니다. 해당 타입은 create-indivisual-form.type에 정의되어 있으므로, 다음 중 하나로 수정하세요:
@/entity/products/create/create-form/create-indivisual-form.type로 경로 변경- 또는
@/entity/products배럴에서 직접 임포트
이슈 번호
#173
작업 내용 및 테스트 방법
이 PR은 그중 첫 번째 단계인 Entity 레이어 작업 건입니다.
entity/create 하위 폴더 구조 변경 계획
options-tag.type.ts 파일의 경우, 결과적으로
features의 create-form-options 폴더와 create-header에서 공유하게 되어 entity에 남겨두는 것이 좋을 것 같다는 생각이 듭니다.
지적해주신 대로 파일을
entity → features로 이동할 경우, 현재 entity PR에 수천 줄의 features 하위 import 경로 수정 내역이 포함되어 PR의 본질이 오염될 우려가 있어 미리 구조를 공유드립니다.이에 동의하신다면, 현재 브랜치를 base로
refactor/products-create-entity-structure브랜치를 생성하여[구조 변경 + import 경로 수정]을 먼저 반영하고 merge 하려 합니다. 이후에 깨끗해진 구조 위에서 다시 기능 단위의 feature 브랜치를 생성하는 것이 가장 합리적이라 판단되는데 의견 궁금합니다.To reviewers