Skip to content
Draft
Show file tree
Hide file tree
Changes from 56 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
a9f1052
[seller] fix(173):sticky header 아코디언 arrow 커스텀
yeaseula Apr 20, 2026
1c3160f
[seller] feat(173): 썸네일 폼 구현 및 context 연결
yeaseula Apr 20, 2026
f60d1a3
[seller] feat(173): 썸네일 컴포넌트 css 적용
yeaseula Apr 21, 2026
d075e86
[seller] feat(173): 이미지 삭제 로직 추가, 모달 추가
yeaseula Apr 21, 2026
901f296
[seller] refactor(173): 이미지 삭제 및 모달관련 hook으로 분리
yeaseula Apr 21, 2026
c0ee6cf
[seller] fix(173):썸네일 컴포넌트 순환참조 경고 수정
yeaseula Apr 21, 2026
0824477
[seller] fix(173):(임시)fixedLayout 컴포넌트 css 변경
yeaseula Apr 21, 2026
2d3522f
[seller] fix(173): Chip 변경된 디자인 시안 반영
yeaseula Apr 21, 2026
c5f3778
[seller] feat(173): 스티키 헤더 단계별 스크롤 기능 추가
yeaseula Apr 21, 2026
6bfd150
[seller] feat(173): 스티키 헤더 단계별 진행도 기능 오류 수정
yeaseula Apr 21, 2026
146ecb2
[seller] feat(173): 스티키 헤더 성분 관련 태그기능 추가
yeaseula Apr 22, 2026
76e8017
[seller] refactor(173): 영양성분, 카테고리 entity를 활용해 provider 코드 수정
yeaseula Apr 22, 2026
a3fc380
[seller] refactor(173): calculation 관련 hook을 Public API 규칙으로 수정
yeaseula Apr 22, 2026
729821a
[seller] feat(173): 상품 가격 자동 입력 기능
yeaseula Apr 22, 2026
61d93db
[seller] feat(173): 최종 상품 가격 컴포넌트 분리 및 적용
yeaseula Apr 22, 2026
ab9ffb0
[seller] feat(173): footer 컴포넌트 분리
yeaseula Apr 22, 2026
5d4d924
[seller] feat(173): 상품 옵션 정보 폼에서 복사/추가/삭제 기능 구현
yeaseula Apr 24, 2026
ff54771
[seller] feat(173): 미리보기 화면 퍼블리싱
yeaseula Apr 24, 2026
7524b49
[seller] fix(173): 누락된 디자인 복구
yeaseula Apr 24, 2026
80df1f2
[seller] fix(173): 체크 된 필수 성분, 카테고리 모두 반영되도록 수정
yeaseula Apr 24, 2026
5bbd414
[seller] fix(173): 체크 된 tag 개별 반영으로 수정
yeaseula Apr 24, 2026
3e41f20
[seller] fix(173): 영양 성분 타입 수정 및 초기값 null로 수정
yeaseula Apr 24, 2026
dfa7b19
[seller] refactor(173): 배송일 entity 활용
yeaseula Apr 24, 2026
f2489b8
[seller] fix(173): 에디터 페이지->모달로 변경 및 스타일 수정
yeaseula Apr 24, 2026
30c9f76
[seller] refactor(173): 주석 삭제
yeaseula Apr 24, 2026
bc31c81
[seller] fix(173): 안쓰는 페이지 삭제
yeaseula Apr 24, 2026
efca2ee
[seller] fix(173): main.tsx import 수정
yeaseula Apr 24, 2026
091e904
[seller] feat(173): 미리보기 뱃지와 icon 이미지 매핑
yeaseula Apr 25, 2026
4abdbe6
[seller] feat(173): 미리보기에 상세설명 입력값 노출기능 추가
yeaseula Apr 25, 2026
cd5fcc1
[seller] fix(173): 클래스명 미인식 오류 수정
yeaseula Apr 25, 2026
2ab8270
[seller] refactor(173): Context API -> zustand store 구조로 마이그레이션 기본 설계 완료
yeaseula Apr 25, 2026
10e58a2
[seller] refactor(173): store와 관계된 type을 entity로 이동, 주석 및 import 정리
yeaseula Apr 25, 2026
61645f6
[seller] refactor(173): 모든 form에 store 적용
yeaseula Apr 25, 2026
0911886
[seller] refactor(173): entity폴더의 Public API 구조 변경 및 적용
yeaseula Apr 25, 2026
c8c06f8
[seller] fix(173): Public API 구조 변경에 따른 누락 에러 수정
yeaseula Apr 25, 2026
ddc10fd
[seller] fix(173): 입력값 없을 시 노출 문구 수정
yeaseula Apr 25, 2026
b1453dc
[seller] fix(173): 상품 옵션이 2개이상부터 묶음상품 뱃지, 맛별 가격 상이 텍스트 노출되도록 수정
yeaseula Apr 25, 2026
59ca13a
[seller] refactor(173): preview 컴포넌트 로직 분리
yeaseula Apr 25, 2026
6629f99
[seller] fix(173): 잘못된 import 경로 수정
yeaseula Apr 25, 2026
a8f5ed6
[seller] fix(173): API 구조에 맞게 enum구조 수정
yeaseula Apr 25, 2026
9fbcf38
[seller] fix(173): 잘못된 import 경로 수정
yeaseula Apr 25, 2026
9321bd1
[seller] feat(173): API 연결 코드 작성
yeaseula Apr 26, 2026
8cf0346
[seller] feat(173): 미리보기, 저장하기 버튼 form 입력시에만 활성화되도록 수정
yeaseula Apr 26, 2026
4a3de1c
[seller] fix(173): submit함수에 toast추가 및 폼 유효성 검사와 관련한 경고 포함
yeaseula Apr 26, 2026
4968568
[seller] refactor(173): 사용하지 않는 함수 제거
yeaseula Apr 26, 2026
c279306
[seller] fix(173): productionTime 요청 형식 변경
yeaseula Apr 26, 2026
a4b293a
[seller] fix(173): 썸네일 등록 시 권장 사이즈,형식,용량초과 시 경고 혹은 에러 문구 추가 수정
yeaseula Apr 26, 2026
6bd7989
[seller] feat(173): 임시저장 기능 persist로 구현
yeaseula Apr 26, 2026
c82faa7
[seller] fix(173): 임시저장 모달이 최초 컴포넌트 마운트시에만 생성되도록
yeaseula Apr 26, 2026
1774202
[seller] fix(173): sticky header의 z-index값 수정 - select보다 항상 앞에 오도록
yeaseula Apr 26, 2026
a5668ae
[seller] fix(173): 퀼 에디터 이미지 첨부 엑박 오류 수정
yeaseula Apr 26, 2026
eba3473
[seller] refactor(173): 디테일, 미리보기 헤더를 공통 컴포넌트로 변경
yeaseula Apr 26, 2026
8dc16a9
[seller] refactor(173): Public API 수정
yeaseula Apr 26, 2026
5fadb8e
[seller] refactor(173): console 디버깅 코드 삭제
yeaseula Apr 26, 2026
3dc8753
[seller] fix(173): 잘못된 import로 인한 lint 오류 수정
yeaseula Apr 26, 2026
45fc7db
Merge branch 'develop' into feat/products-create-page
yeaseula Apr 26, 2026
18a3684
[seller] fix(173): 할인율 enum값 변경
yeaseula Apr 26, 2026
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
8 changes: 8 additions & 0 deletions apps/seller/src/assets/icons/preview/noimage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { DeliveryCompany } from './product-delivery-company'
export { DeliveryTerms } from './product-delivery-terms'
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { DISCLOSURE_FIELDS, RADIO_OPTIONS } from './product-disclosure.constant'
export type { ProductInfoNoticeKey } from './product-disclosure.constant'
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ export const DISCLOSURE_FIELDS = [
{ label: '4. 소재지', key: 'originLocation' },
{ label: '5. 제조년월일', key: 'manufactureDate' },
{ label: '6. 소비기한 또는 품질 유지기한', key: 'expirationDate' },
{ label: '7. 보관방법', key: 'storageGuide' },
{ label: '8. 포장 단위 별 내용물 용량(중량) 수량', key: 'packagingContents' },
{ label: '9. 포장 단위별 수량', key: 'packagingQuantityUnit' },
// { label: '7. 보관방법', key: 'storageGuide' },
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

주석 처리된 코드 제거 권장

원인: storageGuide 항목이 주석으로 남아 의도가 불명확합니다.
영향: 향후 유지보수 시 혼란, dead code 누적.
대안: 영구 제외라면 주석 라인 삭제, 일시적이라면 이유를 짧게 명시하세요.

{ 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' },
] as const

export const RADIO_OPTIONS = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {
DeliveryFormInput,
ProductDisclosureFormInput,
ProductFormInput,
ProductOptionFormInput,
ThumbnailFormInput,
} from './create-indivisual-form.type'

export type CreateFormType = ProductFormInput &
DeliveryFormInput & {
options: ProductOptionFormInput[] // Feature의 스키마 대신 Entity의 순수 타입을 사용
} & ProductDisclosureFormInput &
ThumbnailFormInput
Comment on lines +9 to +13
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

교집합 타입 시 키 충돌 주의 (Optional)

여러 폼 타입을 &로 합치면 동일 키가 서로 다른 타입을 가질 경우 해당 키가 never로 추론됩니다. 향후 폼 인풋 타입 확장 시 키 네임스페이스(예: product*, delivery*) 규칙을 유지해 주세요.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export type ProductFormInput = {
productionTime: string
price: number | null
discountAmount: number | null
discountType: 'won' | 'percentage'
discountType: 'AMOUNT' | 'PERCENT'
}

export type DeliveryFormInput = {
Expand All @@ -16,8 +16,13 @@ export type DeliveryFormInput = {
deliveryMinFee: number | null
}

export type ThumbnailFormInput = {
mainImage: File | null
extraImages: File[]
}

export type ProductOptionFormInput = {
mainCategory: string
mainCategory: 'bread' | 'snack' | ''
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Optional: mainCategory 유니온에 '' 포함은 도메인 누수

미선택 상태를 ''로 표현하면 매퍼/요청 타입까지 빈 문자열 분기를 요구하게 됩니다. 'bread'|'snack'로 좁히고 미선택은 undefined(또는 별도 nullable)로 모델링하는 편이 타입 안전성과 의도 표현이 더 명확합니다 (File 4 스키마와 함께 정렬 필요).

subCategory: string
optionName: string
ingredientCategories: ('glutenFree' | 'vegan')[]
Expand Down
21 changes: 21 additions & 0 deletions apps/seller/src/entity/products/create/create-form/create.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { client } from '@/shared/utils/axios'

import { ApiResponse, StoreInfo } from './create.type'

// storeId 조회
export const getMyStore = async () => {
const response = await client.get<ApiResponse<{ store: StoreInfo }>>(
'/api/v1/seller/stores',
)
return response.data.result.store
}

// 상품 등록
export const createProduct = async (formData: FormData) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

반환 타입 any 회피 권장

응답 스키마(ApiResponse<…>)를 제네릭으로 지정해 호출부의 타입 안전성을 확보해 주세요. 추후 result 구조 변경 시 컴파일러가 회귀를 잡아줍니다.

const response = await client.post('/api/v1/seller/boards', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
return response.data
}
Comment on lines +14 to +21
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Does axios automatically set multipart/form-data boundary when posting FormData, and what happens if Content-Type header is manually set without boundary?

💡 Result:

Yes, axios automatically sets the Content-Type: multipart/form-data header with the correct boundary when posting a FormData object, by detecting the FormData payload and either letting the browser/network adapter handle it or using the FormData instance's headers (like getHeaders in Node.js with form-data polyfill). This has been the standard behavior since early versions, with improvements in v0.27+ for better Node.js support via form-data dependency. If the Content-Type header is manually set to multipart/form-data without a boundary parameter (e.g., { headers: { 'Content-Type': 'multipart/form-data' } }), it can cause problems: - In browser XMLHttpRequest adapter (default): Axios historically avoided overriding the header to let the browser set the boundary; manual setting without boundary may lead to missing boundary, causing servers to reject with "Multipart: Boundary not found". - In Node.js http adapter: Uses FormData's getHeaders which includes boundary, but manual override without boundary breaks parsing. - In fetch adapter (recent fix in 2026 commit 62f6281): Axios now detects and removes manual Content-Type: multipart/form-data without boundary when data is FormData, allowing native fetch to set the correct header with boundary. Recommendation: Do not manually set Content-Type when using FormData—let axios auto-detect and set it correctly. Manual setting is only needed for the automatic object-to-FormData serialization feature (v0.27+), but even then, omit the boundary.

Citations:


FormData 전송 시 수동으로 Content-Type 헤더 설정하지 말 것

axios가 FormData를 감지하여 boundary를 포함한 헤더를 자동 설정하므로, 수동으로 'Content-Type': 'multipart/form-data'를 지정하면 boundary가 누락되어 서버가 요청을 파싱하지 못합니다. 수동 설정 시 브라우저와 Node.js 환경 모두에서 "Multipart: Boundary not found" 오류가 발생할 수 있습니다.

제안 수정
 export const createProduct = async (formData: FormData) => {
-  const response = await client.post('/api/v1/seller/boards', formData, {
-    headers: {
-      'Content-Type': 'multipart/form-data',
-    },
-  })
+  const response = await client.post('/api/v1/seller/boards', formData)
   return response.data
 }
📝 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.

Suggested change
export const createProduct = async (formData: FormData) => {
const response = await client.post('/api/v1/seller/boards', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
return response.data
}
export const createProduct = async (formData: FormData) => {
const response = await client.post('/api/v1/seller/boards', formData)
return response.data
}

16 changes: 16 additions & 0 deletions apps/seller/src/entity/products/create/create-form/create.query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { queryOptions } from '@tanstack/react-query'

import { getMyStore } from './create.api'

export const productKeys = {
all: ['products'] as const,
myStore: () => [...productKeys.all, 'myStore'] as const,
}

export const productQueries = {
myStore: () =>
queryOptions({
queryKey: productKeys.myStore(),
queryFn: getMyStore,
}),
}
Comment on lines +1 to +16
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

productKeysproductQueries를 별도 객체로 분리하는 방식보다, queryOptions를 활용해 queryKeyqueryFn을 하나의 객체에서 관리하는 Query Factory 패턴을 권장합니다.

export const productQueries = {
  all: () => ['products'] as const,
  myStore: () =>
    queryOptions({
      queryKey: [...productQueries.all(), 'myStore'],
      queryFn: getMyStore,
    }),
}

queryKeyqueryFn은 본질적으로 밀접하게 연결되어 있어, 분리하면 키 변경 시 두 곳을 동시에 수정해야 하는 유지보수 부담이 생깁니다. 통합하면 관련 로직이 한 곳에 응집되고, useQueryqueryClient.prefetchQuery 등에서 동일한 옵션을 자동으로 공유할 수 있습니다.

참고: QueryKey Factory에서 Query Factory로의 전환

66 changes: 66 additions & 0 deletions apps/seller/src/entity/products/create/create-form/create.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// API 요청 타입
export interface CreateProductRequest {
storeId: number
title: string
isFresh: boolean
productionStartAt: string
price: number
discountType: 'AMOUNT' | 'PERCENT'
discountValue: number
deliveryCondition: string
deliveryCompany: string
deliveryFee: number
freeShippingConditions: number
content: string
products: ProductOptionRequest[]
productInfoNoticeRequest: Record<string, string>
Comment on lines +6 to +16
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP '\b(productionStartAt|productionStartTime|boardDetailRequest\.content)\b' -C2

Repository: eco-dessert-platform/dessert-front-dashboard

Length of output: 1599


🏁 Script executed:

# 백엔드 API 스키마나 DTO 정의 찾기
find . -type f \( -name "*.ts" -o -name "*.java" -o -name "*.dto.ts" \) | head -20

Repository: eco-dessert-platform/dessert-front-dashboard

Length of output: 1062


🏁 Script executed:

# 매퍼 전체 컨텍스트 확인 - multipartData.append 패턴 분석
rg -A1 -B1 "multipartData\.append" apps/seller/src/features/products/create/create-form/create-form-mapper.ts | head -50

Repository: eco-dessert-platform/dessert-front-dashboard

Length of output: 1426


🏁 Script executed:

# 백엔드 타입 정의가 있는지 확인
find . -path "*/backend/*" -o -path "*/api/*" | grep -E "(dto|entity|type|schema)" | head -20

Repository: eco-dessert-platform/dessert-front-dashboard

Length of output: 70


🏁 Script executed:

# create.type.ts 전체 파일 확인하여 필드명 정확히 파악
cat apps/seller/src/entity/products/create/create-form/create.type.ts

Repository: eco-dessert-platform/dessert-front-dashboard

Length of output: 1394


타입 정의가 실제 API 필드명과 불일치 - 매퍼와 타입 계약이 어긋남

create.type.tsproductionStartAtcontent로 정의했지만, create-form-mapper.ts는 백엔드 API에 productionStartTime(L127)과 boardDetailRequest.content(L143)로 전송합니다. 타입 파일이 실제 API 계약을 반영하지 않으면 개발자가 잘못된 필드명을 사용하게 되어 API 요청이 매핑되지 않을 위험이 있습니다. 백엔드 DTO 정의를 확인 후 타입 파일을 백엔드 API에 맞춰 수정하세요.

}

export interface ProductOptionRequest {
title: string
category: string
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Optional 정리

  • L21 category: string'BREAD' | 'BAGEL' | 'CAKE' | 'JAM' | 'COOKIE' | 'GRANOLA' | 'ETC' 리터럴 유니온이 mapper의 CATEGORY_MAP 값과 정합성을 가집니다.
  • L16 productInfoNoticeRequest의 키도 ProductInfoNoticeKey로 좁히면 안전합니다.
  • L52-59 StoreInfoemail/phoneNumber는 백엔드 응답이 nullable일 수 있으니 확인 필요합니다.

plusPriceWithBoardPrice: number
stock: number
dietaryTags: {
glutenFreeTag: boolean
highProteinTag: boolean
sugarFreeTag: boolean
veganTag: boolean
ketogenicTag: boolean
}
availability: {
monday: boolean
tuesday: boolean
wednesday: boolean
thursday: boolean
friday: boolean
saturday: boolean
sunday: boolean
}
nutritionInfo: {
totalWeight: number
servingSize: number
carbohydrates: number
sugars: number
protein: number
fat: number
calories: number
} | null
Comment on lines +40 to +48
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

nutritionInfosodium 누락

ProductOptionFormInputsodium 필드를 가지지만 API 타입의 nutritionInfo에는 없습니다. 결과적으로 사용자가 입력한 나트륨 값이 제출에서 누락됩니다. 백엔드 스펙과 맞춰 sodium: number를 추가하거나(추가 시 mapper에도 반영), 폼에서 sodium을 제거해야 합니다.

}

// API 응답 타입
export interface StoreInfo {
storeId: number
name: string
introduce: string
profile: string
phoneNumber: string
email: string
}

export interface ApiResponse<T> {
success: boolean
code: number
message: string
result: T
}
10 changes: 10 additions & 0 deletions apps/seller/src/entity/products/create/create-form/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export * from './create-form.types'
export * from './create-indivisual-form.type'
export { getMyStore, createProduct } from './create.api'
export { productQueries, productKeys } from './create.query'
export type {
CreateProductRequest,
ProductOptionRequest,
StoreInfo,
ApiResponse,
} from './create.type'
3 changes: 3 additions & 0 deletions apps/seller/src/entity/products/create/create-header/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { CategoryOptions } from './category-options.constants'
export { EssentialOptions } from './essential-options.constants'
export type { OptionTags } from './options-tag.type'
2 changes: 2 additions & 0 deletions apps/seller/src/entity/products/create/create-info/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { ProductDiscountType } from './product-discount-type.constants'
export { productionTimes } from './production-time.constants'
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
export const ProductDiscountType = [
{
label: '원',
value: 'won',
value: 'AMOUNT',
},
{
label: '%',
value: 'percentage',
value: 'PERCENT',
},
]
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
// export const productionTimes = [
// {
// label: '03:00~04:00',
// value: '03:00~04:00',
// },
// {
// label: '04:00~05:00',
// value: '04:00~05:00',
// },
// {
// label: '05:00~06:00',
// value: '05:00~06:00',
// },
// {
// label: '06:00~07:00',
// value: '06:00~07:00',
// },
// {
// label: '07:00~08:00',
// value: '07:00~08:00',
// },
// ]
export const productionTimes = [
{
label: '03:00~04:00',
value: '03:00~04:00',
},
{
label: '04:00~05:00',
value: '04:00~05:00',
},
{
label: '05:00~06:00',
value: '05:00~06:00',
},
{
label: '06:00~07:00',
value: '06:00~07:00',
},
{
label: '07:00~08:00',
value: '07:00~08:00',
},
{ label: '03:00~04:00', value: '03:00' },
{ label: '04:00~05:00', value: '04:00' },
{ label: '05:00~06:00', value: '05:00' },
{ label: '06:00~07:00', value: '06:00' },
{ label: '07:00~08:00', value: '07:00' },
]
Comment on lines +1 to 29
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

주석 처리된 이전 코드 제거 권장 + value 의미 확인

원인: 22줄의 주석 블록은 dead code이고, value가 범위(03:00~04:00)에서 시작 시각(03:00)으로 바뀌면서 의미가 달라졌습니다. 영향: 서버/스키마가 기존 start~end 포맷을 기대한다면 제출 시 검증 실패 또는 잘못된 값 저장 가능. 대안: 주석 코드는 git history로 충분하니 삭제하고, API 계약과 표시(label) ↔ 저장(value) 매핑을 한 번 더 확인해 주세요.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export { NUTRITION_FIELDS } from './product-nutritions.constant'
export type { NutritionFieldKey } from './product-nutritions.constant'
export {
MAIN_CATEGORY_OPTIONS,
SUB_CATEGORY_MAP,
} from './product-options.constant'
export { SHIPPING_DAYS } from './product-shipping-days.constant'
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export type ProductFileType = Record<string, boolean>

export interface NutritionData {
sugar: number | null
protein: number | null
fat: number | null
ingredientCategories: string[]
}

export interface ActiveTags {
[key: string]: boolean
}

export interface CreateFormHeaderType {
productFields: ProductFileType
currentStep: number
headerHeight: number

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>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Is React.MutableRefObject deprecated in React 19 and should React.RefObject be used instead?

💡 Result:

No, React.MutableRefObject is not deprecated in React 19. Instead, in the @types/react package for React 19, MutableRefObject is deprecated in favor of a unified RefObject type. All refs returned from useRef are now mutable by default (RefObject where current: T, non-nullable unless you pass null/undefined), and useRef requires an initial argument. Official React docs do not mention deprecation of these types, as they focus on runtime behavior where refs have always been mutable. Use RefObject for type safety with TypeScript in React 19.

Citations:


🏁 Script executed:

cat -n apps/seller/src/entity/products/create/create-store/create-store-header.type.ts | head -40

Repository: eco-dessert-platform/dessert-front-dashboard

Length of output: 1209


🏁 Script executed:

rg "isScrollingToStep" apps/seller/src/entity/products/create --type ts --type tsx -B 2 -A 2

Repository: eco-dessert-platform/dessert-front-dashboard

Length of output: 116


🏁 Script executed:

rg "isScrollingToStep" apps/seller/src/entity/products/create -B 2 -A 2

Repository: eco-dessert-platform/dessert-front-dashboard

Length of output: 620


🏁 Script executed:

rg "isScrollingToStep" apps/seller/src --B 3 -A 3

Repository: eco-dessert-platform/dessert-front-dashboard

Length of output: 494


🏁 Script executed:

rg "isScrollingToStep" apps/seller/src -B 3 -A 3

Repository: eco-dessert-platform/dessert-front-dashboard

Length of output: 3534


React 19에서 MutableRefObjectRefObject<boolean> 변경 필요

React 19의 @types/react에서 MutableRefObject는 deprecated되었습니다. 모든 ref가 기본적으로 mutable이므로 React.RefObject<boolean>으로 변경하세요. 현재 사용 패턴에서 current는 항상 boolean 값이므로 RefObject이 적절합니다.


nutritionDataList: NutritionData[]
setNutritionData: (index: number, data: NutritionData) => void
activeTags: ActiveTags

productPrice: number | null
setProductPrice: React.Dispatch<React.SetStateAction<number | null>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './create-store-header.type'
23 changes: 0 additions & 23 deletions apps/seller/src/entity/products/index.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { useEffect, useState } from 'react'

export function useNumberInput(
value: number | null,
value: number | null | undefined, // undefined 추가
onChange: (value: number | null) => void,
options?: { allowNegative?: boolean },
) {
const { allowNegative = false } = options ?? {}

// 💡 undefined 체크 추가하여 에러 방지
const [displayValue, setDisplayValue] = useState(
value !== null ? value.toLocaleString('ko-KR') : '',
value !== null && value !== undefined ? value.toLocaleString('ko-KR') : '',
)

useEffect(() => {
setDisplayValue(value !== null ? value.toLocaleString('ko-KR') : '')
// 💡 undefined 체크 추가하여 에러 방지
setDisplayValue(
value !== null && value !== undefined
? value.toLocaleString('ko-KR')
: '',
)
}, [value])
Comment on lines 11 to 22
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

LGTM (Optional 1건)

undefined 가드 추가로 toLocaleString 런타임 오류가 방지됩니다. Optional: value !== null && value !== undefinedvalue != null로 단축 가능합니다(ESLint eqeqeq에서 null 비교는 일반적으로 허용).


const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand All @@ -22,7 +28,6 @@ export function useNumberInput(
? raw.replace(/[^0-9-]/g, '').replace(/(?!^)-/g, '')
: raw.replace(/[^0-9]/g, '')

// '-' 만 입력된 중간 상태는 표시만 유지, onChange는 호출 안 함
if (cleaned === '-') {
setDisplayValue('-')
return
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { useFloatInput } from './create-form-float-input.hook'
export { useNumberInput } from './create-form-number-input.hook'
Loading
Loading