Skip to content

feat: 새 가게 등록 UI 구현#31

Merged
dew102938 merged 11 commits into
developfrom
feat/StoreRegistration
Jan 29, 2026
Merged

feat: 새 가게 등록 UI 구현#31
dew102938 merged 11 commits into
developfrom
feat/StoreRegistration

Conversation

@dew102938

@dew102938 dew102938 commented Jan 26, 2026

Copy link
Copy Markdown
Contributor

💡 개요

새 가게 등록 UI 구현

🔢 관련 이슈 링크

💻 작업내용

  • 1단계(사업자 인증) UI 구현 및 zod+RHF 유효성 검사 적용
  • 2단계(가게 정보) UI 구현 및 zod+RHF 유효성 검사 적용
  • 3단계(메뉴 등록) UI 구현 및 zod+RHF, useFieldArray로 메뉴 목록 배열 관리
  • 페이지에서 단계별 데이터 통합, 최종 라우팅 연결

📌 변경사항PR

  • FEAT: 새로운 기능 추가
  • FIX: 버그/오류 수정
  • CHORE: 코드/내부 파일/설정 수정
  • DOCS: 문서 수정(README 등)
  • REFACTOR: 코드 리팩토링 (기능 변경 없음)
  • TEST: 테스트 코드 추가/수정
  • STYLE: 스타일 변경(포맷, 세미콜론 등)

🤔 추가 논의하고 싶은 내용

  • 특별히 없습니다

✅ 체크리스트

  • 브랜치는 잘 맞게 올렸는지
  • 관련 이슈를 맞게 연결했는지
  • 로컬에서 정상 동작을 확있했는지
  • 충돌이 없다(또는 브랜치에서 충돌 해결 후 PR 업데이트 완료)

Summary by CodeRabbit

  • New Features

    • 마이페이지용 3단계 가게 등록 워크플로 도입(사업자 인증 → 가게 정보 → 메뉴 등록)
    • 사업자 등록번호 입력·검증 단계, 실시간 유효성 반영과 단계별 진행 표시기 및 이전/다음 내비게이션
    • 가게 정보 폼(전화번호 자동 포맷팅 포함) 및 메뉴 관리(다중 항목, 이미지 업로드·미리보기·삭제, 가격/설명 입력)과 검증 스키마 추가
    • 등록 완료 확인/완료 모달(자동 닫힘) 및 취소 확인 모달 제공
  • Chores

    • "새 가게 등록" 버튼을 등록 페이지 라우트로 전환

✏️ Tip: You can customize this high-level summary in your review settings.

@dew102938 dew102938 self-assigned this Jan 26, 2026
@dew102938 dew102938 added the feat label Jan 26, 2026
@coderabbitai

coderabbitai Bot commented Jan 26, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

3단계 스토어 등록 UI와 라우팅을 추가했습니다. 단계는 사업자 인증, 매장 정보 입력, 메뉴 등록이며, 각 스텝은 react-hook-form과 Zod로 유효성 검증되고 StoreRegistrationPage가 단계 전환·데이터 병합·완료 흐름을 조율합니다.

Changes

Cohort / File(s) Summary
라우팅 및 페이지
src/App.tsx, src/pages/myPage/StoreRegistrationPage.tsx, src/pages/myPage/storePage.tsx
/mypage/store/register 라우트 추가 및 StoreRegistrationPage 연결. 기존 등록 버튼을 Link로 변경. 페이지에서 3단계 데이터를 수집, 병합 후 완료/리다이렉트 처리 및 모달 제어.
스키마 (Zod)
src/components/store-registration/StoreInfo.schema.ts, src/components/store-registration/Menu.schema.ts
매장/메뉴용 Zod 스키마 추가(타입 추론 포함). 전화번호·가격 정규식과 한국어 에러 메시지 포함.
스텝 컴포넌트
src/components/store-registration/StepBusinessAuth.tsx, src/components/store-registration/StepStoreInfo.tsx, src/components/store-registration/StepMenuRegistration.tsx
3개의 스텝 컴포넌트 추가: 사업자 인증(모의 API 호출, onComplete), 매장 정보(포맷터·실시간 onChange), 메뉴 등록(useFieldArray·onChange). 부모에 isValid/data 실시간 전달.
네비게이션 / 스텝 UI
src/components/store-registration/RegistrationStepper.tsx, src/components/store-registration/RegistrationNavigation.tsx
단계 표시 및 이전/다음(마지막은 '등록 완료') 버튼 UI 추가. disabled 상태 및 라벨 변경 로직 포함.
메뉴 입력 관련 컴포넌트
src/components/store-registration/MenuItemInput.tsx, src/components/store-registration/Menu.schema.ts
메뉴 항목 편집 컴포넌트: 이미지 업로드·미리보기(Object URL 사용), 필드 바인딩(menus.${index}.*), 항목 삭제 처리. 이미지 메모리/해제와 file input 리셋 유의 필요.
모달 컴포넌트
src/components/store-registration/ConfirmModal.tsx, src/components/store-registration/CompleteModal.tsx
확인/완료 모달 추가. CompleteModal은 auto-close 타이머(클린업 필요) 및 요약 정보 표시.
유틸리티
src/utils/phoneNumber.ts
한국형 전화번호 포맷터 추가(지역별 분기 및 길이 제한).

Sequence Diagram(s)

sequenceDiagram
    actor User as 사용자
    participant Router as 브라우저/라우터
    participant Page as StoreRegistrationPage
    participant Step1 as StepBusinessAuth
    participant Step2 as StepStoreInfo
    participant Step3 as StepMenuRegistration
    participant Schema as Zod_Schema

    User->>Router: /mypage/store/register 접근
    Router->>Page: 라우팅 렌더
    Page->>Step1: 렌더 (onComplete 전달)
    User->>Step1: 사업자번호 입력/제출
    Step1->>Schema: 사업자번호 검증
    Schema-->>Step1: 검증결과
    Step1-->>Page: onComplete({ businessNumber, isVerified })
    User->>Page: 다음 클릭
    Page->>Step2: 렌더 (defaultValues, onChange)
    User->>Step2: 매장정보 입력
    Step2->>Schema: 실시간 검증
    Step2-->>Page: onChange(isValid, data)
    User->>Page: 다음 클릭
    Page->>Step3: 렌더 (onChange)
    User->>Step3: 메뉴 추가 및 이미지 업로드
    Step3->>Schema: 메뉴 검증
    Step3-->>Page: onChange(isValid, menuData)
    User->>Page: 등록 완료 클릭
    Page->>Page: 데이터 병합 및 price 숫자 변환
    Page->>Router: 완료 후 /mypage/store 로 리다이렉트
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 새 가게 문을 여네, 한 걸음씩 차근차근
인증하고, 정보 적고, 메뉴를 올리네 🍽️
이미지 반짝, 가격은 숫자, 에러는 친절히 고쳐요
스텝을 밟아 완성된 등록, 축하의 한 바구니 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 새 가게 등록 UI 구현이라는 주요 변경사항을 명확하고 간결하게 설명하고 있습니다.
Description check ✅ Passed PR 설명이 개요, 관련 이슈, 작업내용, 변경사항, 체크리스트를 포함하여 템플릿을 잘 따르고 있습니다.
Linked Issues check ✅ Passed 3단계 UI 구현(StepBusinessAuth, StepStoreInfo, StepMenuRegistration)과 필요한 스키마, 컴포넌트, 라우팅이 모두 구현되었으며 이슈 #30의 UI 구성과 라우팅 연결 요구사항을 충족합니다.
Out of Scope Changes check ✅ Passed phoneNumber 유틸리티 추가와 storePage의 링크 변경 외에는 모두 이슈 #30 범위 내의 변경사항이며, 이 두 항목도 등록 플로우를 지원하는 필수 변경사항입니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/components/store-registration/MenuItemInput.tsx`:
- Around line 32-40: handleImageChange creates object URLs with
URL.createObjectURL but never revokes them, causing memory leaks; modify
handleImageChange to revoke any previous preview URL (the state variable set by
setPreviewUrl) before creating a new one, and add a useEffect cleanup that
revokes the current preview URL on component unmount (or when previewUrl
changes) to ensure URL.revokeObjectURL is always called; reference the
handleImageChange function, the previewUrl state, setPreviewUrl, and the
component's useEffect cleanup for where to add the revocation logic.

In `@src/pages/myPage/StoreRegistrationPage.tsx`:
- Around line 63-66: The current mapping in StoreRegistrationPage.tsx uses
Number(menu.price) which can yield NaN; change the menus mapping (where menus:
(step3Data.menus || []).map(...)) to defensively parse and validate menu.price:
parseFloat(menu.price as string) into a variable (e.g., parsedPrice), then check
Number.isFinite(parsedPrice) (or !isNaN(parsedPrice)) and either set price to
parsedPrice or to a safe fallback (null/undefined) or reject the submission by
triggering validation; ensure the final payload sent to the API contains only
valid numeric prices or the form surface shows validation errors for invalid
inputs.
🧹 Nitpick comments (11)
src/components/store-registration/StoreInfo.schema.ts (1)

14-15: Consider adding cross-field validation for business hours.

The schema validates openTime and closeTime independently but doesn't ensure that close time is after open time. This could allow invalid business hours (e.g., open at 22:00, close at 08:00 on the same day).

You may want to add a .refine() check if same-day validation is needed, or document that overnight hours are intentionally supported.

Example refinement if same-day validation is desired
 export const StoreInfoSchema = z.object({
   storeName: z.string().min(1, { message: "가게 이름을 입력하세요." }),
   category: z.string().min(1, { message: "음식 종류를 선택하세요." }),
   address: z.string().min(1, { message: "주소를 입력하세요." }),
   detailAddress: z.string().optional(),
   phone: z
     .string()
     .min(1, { message: "전화번호를 입력하세요." })
     .regex(/^\d{2,3}-\d{3,4}-\d{4}$/, {
       message: "올바른 전화번호 형식이 아닙니다.",
     }),
   openTime: z.string().min(1, { message: "시작 시간을 선택하세요." }),
   closeTime: z.string().min(1, { message: "종료 시간을 선택하세요." }),
   description: z.string().optional(),
-});
+}).refine((data) => data.openTime < data.closeTime, {
+  message: "종료 시간은 시작 시간 이후여야 합니다.",
+  path: ["closeTime"],
+});
src/components/store-registration/Menu.schema.ts (1)

13-13: Consider stricter typing for the image field.

Using z.any() bypasses type validation entirely. Since this field expects a File object from the upload input, consider using a more specific validator for better type safety and runtime validation.

Stricter image validation options
-        image: z.any().optional(),
+        image: z.union([z.instanceof(File), z.null()]).optional(),

Or if you need to support serialized data as well:

-        image: z.any().optional(),
+        image: z.custom<File | null>((val) => val === null || val instanceof File).optional(),
src/components/store-registration/MenuItemInput.tsx (1)

76-82: Consider adding client-side file validation.

The UI displays file constraints ("최대 용량: 5MB", "형식: JPG, PNG") but they're not enforced. Adding validation would provide immediate feedback before upload.

Example validation in handleImageChange
 const handleImageChange = (e: ChangeEvent<HTMLInputElement>) => {
   const file = e.target.files?.[0];
   if (file) {
+    const MAX_SIZE = 5 * 1024 * 1024; // 5MB
+    const ALLOWED_TYPES = ['image/jpeg', 'image/png'];
+    
+    if (file.size > MAX_SIZE) {
+      alert('파일 크기는 5MB 이하여야 합니다.');
+      return;
+    }
+    if (!ALLOWED_TYPES.includes(file.type)) {
+      alert('JPG 또는 PNG 형식만 지원합니다.');
+      return;
+    }
+    
     const url = URL.createObjectURL(file);
     setPreviewUrl(url);
     setValue(`menus.${index}.image`, file);
   }
 };
src/components/store-registration/StepMenuRegistration.tsx (1)

66-72: Add type="button" to prevent accidental form submission.

The "메뉴 추가" button doesn't specify type="button". If this component is rendered inside a <form> element (now or in the future), clicking it could trigger form submission since the default button type is "submit".

Proposed fix
         <button
+          type="button"
           onClick={() => append({ menuName: "", price: "", description: "" })}
           className="w-full py-3 border-2 border-dashed border-gray-300 rounded-lg text-gray-600 hover:border-gray-400 hover:text-gray-700 flex items-center justify-center gap-2 cursor-pointer"
         >
src/components/store-registration/StepStoreInfo.tsx (2)

37-40: Consider using watch() with a callback or useWatch for better performance.

Using JSON.stringify(values) in the dependency array serializes the entire form on every render to detect changes. This can be inefficient for larger forms.

Consider using useWatch with a subscription callback or restructuring to avoid the serialization:

♻️ Suggested refactor using useWatch
-import { useForm } from "react-hook-form";
+import { useForm, useWatch } from "react-hook-form";
...
-  //값이 변할 때마다 부모에게 실시간 보고
-  const values = watch();
-
-  useEffect(() => {
-    onChange(isValid, values as StoreInfoFormValues);
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [isValid, JSON.stringify(values), onChange]);
+  // Subscribe to form changes and report to parent
+  useWatch({
+    control,
+    onChange: ({ values }) => {
+      onChange(isValid, values as StoreInfoFormValues);
+    },
+  });
+
+  useEffect(() => {
+    onChange(isValid, watch() as StoreInfoFormValues);
+  }, [isValid]);

Note: You'll need to extract control from useForm.


109-114: Placeholder button noted — ensure address search is tracked for completion.

The "주소 검색" button is currently a placeholder with no functionality. Per the linked issue, API integration is still pending.

Would you like me to open an issue to track the address search integration?

src/App.tsx (1)

45-49: Route works but consider nesting under /mypage/* for consistency.

While React Router v6's scoring algorithm ensures /mypage/store/register matches before /mypage/*, having this route as a sibling creates organizational inconsistency. Also, there's a minor formatting issue (extra space before {).

♻️ Suggested formatting fix
-   {
+  {
     path: "/mypage/store/register", //가게 등록 경로
-    element:<StoreRegistrationPage />,
+    element: <StoreRegistrationPage />,
     errorElement: <NotFound />,  
-  }
+  },
src/components/store-registration/StepBusinessAuth.tsx (2)

7-12: Add custom error messages to the schema for better UX.

The schema will show generic error messages like "Invalid" when validation fails. Adding descriptive messages helps users understand what's wrong.

♻️ Suggested improvement
 const schema = z.object({
   businessNumber: z
     .string()
-    .regex(/^[0-9]+$/)
-    .length(10),
+    .regex(/^[0-9]+$/, { message: "숫자만 입력해주세요." })
+    .length(10, { message: "사업자등록번호는 10자리입니다." }),
 });

43-57: Mock API always succeeds — consider adding error handling for real integration.

The simulated API call always returns success. When integrating with the real API, ensure proper error handling is added (e.g., invalid business number, network failure).

Also, Line 49 has a console.log that should be removed before production.

🧹 Remove console.log
-    console.log("인증번호:", data.businessNumber);
src/components/store-registration/RegistrationStepper.tsx (1)

38-43: Consider using Tailwind's arbitrary value syntax instead of inline style.

For consistency with the rest of the styling, the minWidth inline style can be replaced with Tailwind's arbitrary value syntax.

♻️ Suggested change
                 <div
                   className={`hidden sm:block flex-1 h-1 mx-4 transition-colors ${
                     currentStep > step.number ? "bg-blue-500" : "bg-gray-200"
-                  }`}
-                  style={{ minWidth: "80px" }}
+                  } min-w-[80px]`}
                 />
src/pages/myPage/StoreRegistrationPage.tsx (1)

69-72: Remove console.log and replace alert() with a proper notification.

console.log should be removed for production, and alert() provides a poor user experience. Consider using a toast notification component for success feedback.

🧹 Suggested cleanup
-      console.log("최종 데이터:", finalPayload);
       //API 호출

-      alert("가게 등록이 완료되었습니다!");
+      // TODO: Replace with toast notification
+      // toast.success("가게 등록이 완료되었습니다!");

Comment thread src/components/store-registration/MenuItemInput.tsx
Comment thread src/pages/myPage/StoreRegistrationPage.tsx
Comment thread src/pages/myPage/StoreRegistrationPage.tsx
Comment thread src/components/store-registration/StepBusinessAuth.tsx
Comment thread src/components/store-registration/StepStoreInfo.tsx Outdated
Comment thread src/components/store-registration/StepStoreInfo.tsx Outdated
Comment thread src/components/store-registration/RegistrationStepper.tsx
Comment thread src/components/store-registration/RegistrationNavigation.tsx Outdated
Comment thread src/pages/myPage/StoreRegistrationPage.tsx Outdated
Comment thread src/components/store-registration/StepStoreInfo.tsx
Comment thread src/pages/myPage/StoreRegistrationPage.tsx
@dew102938 dew102938 force-pushed the feat/StoreRegistration branch from a70557c to bf701ce Compare January 28, 2026 10:54

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/components/store-registration/StepStoreInfo.tsx`:
- Around line 51-93: The form in StepStoreInfo (component StepStoreInfo.tsx)
lacks proper label/input associations and an onSubmit handler: add unique id
attributes for the storeName input and category select (e.g., id="storeName" and
id="category") and set the corresponding Label components' htmlFor to those ids
so screenreaders can associate labels with fields (references:
register("storeName"), register("category"), errors, touchedFields); also add an
onSubmit handler on the <form> that prevents default and delegates to the parent
submit callback or React Hook Form's handleSubmit to avoid accidental Enter-key
submissions and to centralize validation handling. Ensure any aria-required or
aria-invalid attributes reflect errors for accessibility.
🧹 Nitpick comments (5)
src/components/store-registration/RegistrationNavigation.tsx (1)

22-43: 접근성(a11y) 개선이 필요해요!

코딩 가이드라인에 따라 접근성 체크를 해봤는데, 몇 가지 개선점이 있어요:

  1. type="button" 누락: 폼 내부에서 사용될 경우, 버튼이 기본적으로 type="submit"으로 동작해서 의도치 않은 폼 제출이 발생할 수 있어요.

  2. 아이콘 접근성: 장식용 아이콘에 aria-hidden="true" 추가하면 스크린 리더 사용자 경험이 좋아져요.

♻️ 접근성 개선 제안
      <button
+       type="button"
        onClick={onPrev}
        disabled={currentStep === 1}
        className="flex items-center gap-2 px-6 py-4 sm:py-3 text-gray-500 border border-gray-500 rounded-lg hover:text-gray-700 hover:border-gray-700 transition-colors disabled:text-gray-400 disabled:border-gray-300 disabled:cursor-not-allowed cursor-pointer"
      >
-       <ChevronLeft className="size-5" />
+       <ChevronLeft className="size-5" aria-hidden="true" />
        이전
      </button>
      <button
+       type="button"
        onClick={onNext}
        disabled={isNextDisabled}
        className="flex items-center gap-2 px-6 py-4 sm:py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors disabled:bg-gray-300 disabled:cursor-not-allowed cursor-pointer"
      >
        {isLastStep ? (
          <>등록 완료</>
        ) : (
          <>
            다음
-           <ChevronRight className="size-5" />
+           <ChevronRight className="size-5" aria-hidden="true" />
          </>
        )}
      </button>

As per coding guidelines: "컴포넌트는 단일 책임, props 타입/네이밍 명확히, 접근성(aria) 체크."

src/components/store-registration/StepStoreInfo.tsx (3)

37-40: onChange 콜백 안정성 개선을 권장해요.

현재 JSON.stringify(values)를 의존성 배열에 사용하고 있는데, 이 패턴은 동작하지만 몇 가지 주의가 필요해요:

  1. 부모 컴포넌트에서 onChangeuseCallback으로 메모이제이션하지 않으면 매 렌더링마다 새 참조가 생성되어 불필요한 effect 실행이 발생할 수 있어요.
  2. eslint-disable 주석이 잠재적 이슈를 숨길 수 있어요.
♻️ 개선 제안

useMemo로 직렬화된 값을 캐싱하거나, 부모에서 onChange를 반드시 메모이제이션하도록 문서화하는 것을 권장해요:

+ const serializedValues = JSON.stringify(values);
+
  useEffect(() => {
    onChange(isValid, values as StoreInfoFormValues);
-   // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [isValid, JSON.stringify(values), onChange]);
+  }, [isValid, serializedValues, onChange]);

또는 부모 컴포넌트에서:

const handleStoreInfoChange = useCallback((isValid: boolean, data: StoreInfoFormValues) => {
  // ...
}, []);

145-177: 영업시간 유효성 검사 및 반응형 레이아웃 확인 필요.

두 가지 개선점이 있어요:

  1. 시간 유효성 검사: closeTimeopenTime보다 이른 시간이어도 유효성 검사를 통과해요. 스키마에서 cross-field validation을 추가하면 좋겠어요.

  2. 반응형 그리드 불일치: Line 52에서는 grid-cols-1 md:grid-cols-2를 사용했는데, 여기서는 grid-cols-2만 사용해서 모바일에서 레이아웃이 깨질 수 있어요.

♻️ 개선 제안

레이아웃 일관성:

- <div className="grid grid-cols-2 gap-4">
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">

시간 유효성 검사 (StoreInfo.schema.ts에 추가):

export const StoreInfoSchema = z.object({
  // ... 기존 필드들
}).refine(
  (data) => !data.openTime || !data.closeTime || data.openTime < data.closeTime,
  {
    message: "종료 시간은 시작 시간보다 늦어야 합니다.",
    path: ["closeTime"],
  }
);

109-114: 주소 검색 버튼 구현 예정 확인.

이슈 #30에서 API 연동이 pending 상태로 되어 있어서 현재 버튼에 기능이 없는 것으로 보여요. 추후 구현 시 누락되지 않도록 TODO 주석이나 비활성화 상태를 추가하면 좋겠어요.

💡 선택적 개선
  <button
    type="button"
+   disabled // TODO: 주소 검색 API 연동 후 활성화
    className="px-6 py-3 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 whitespace-nowrap cursor-pointer"
  >
    주소 검색
  </button>
src/components/store-registration/MenuItemInput.tsx (1)

140-145: 가격 입력은 숫자 키패드 유도가 있으면 좋아요.
inputMode="numeric"pattern을 추가하면 모바일 UX가 개선됩니다.

💡 개선 예시
 <input
   {...register(`menus.${index}.price`)}
   type="text"
+  inputMode="numeric"
+  pattern="\d*"
   placeholder="30000"
   className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
 />

Comment thread src/components/store-registration/StepStoreInfo.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/components/store-registration/RegistrationNavigation.tsx`:
- Around line 20-43: The two buttons in RegistrationNavigation should be
explicit non-submit controls and accessible: add type="button" to the previous
and next buttons (the elements using onPrev and onNext) to prevent accidental
form submission, and add descriptive aria-label attributes (e.g., "이전 단계로 이동"
for the onPrev button and "다음 단계로 이동" or "등록 완료" for the onNext button depending
on isLastStep) so screen readers clearly convey their actions; update the JSX
around the buttons that render ChevronLeft/ChevronRight and depend on
currentStep, isNextDisabled and isLastStep accordingly.

In `@src/components/store-registration/StepBusinessAuth.tsx`:
- Around line 43-52: Remove the sensitive console logging of the business
registration number in the onSubmit handler of StepBusinessAuth; specifically
delete or guard the line using console.log("인증번호:", data.businessNumber) inside
the onSubmit async function so that the businessNumber is not written to the
browser console in production (if you need logs for local debugging, wrap them
behind a debug flag or environment check such as process.env.NODE_ENV !==
'production' or a dedicated DEBUG flag).
- Around line 71-90: The label and input in StepBusinessAuth.tsx are not
explicitly connected and the input lacks ARIA error state; add an id (e.g.,
id="businessNumber") to the input that matches the label's htmlFor, and set
aria-invalid based on the form error for register("businessNumber") (e.g.,
formState.errors.businessNumber != null) and optionally aria-describedby
pointing to an error message element so screen readers can announce validation
state; ensure the onChange logic that references defaultValues.isVerified and
onComplete remains unchanged.
🧹 Nitpick comments (3)
src/components/store-registration/RegistrationStepper.tsx (1)

15-45: 접근성(aria) 보강이 필요해요.

현재 단계 표시가 시각 정보에 치우쳐 있어 스크린리더가 현재 단계를 파악하기 어렵습니다. nav/ol + aria-current="step" + 아이콘 aria-hidden/sr-only 텍스트로 보완해주세요.

♿ 제안 수정
-  return (
-    <div className="bg-white border-b border-gray-200">
+  return (
+    <nav className="bg-white border-b border-gray-200" aria-label="등록 단계">
       <div className="max-w-7xl mx-auto px-6 lg:px-8 py-8">
-        <div className="flex items-start justify-between max-w-2xl mx-auto">
+        <ol className="flex items-start justify-between max-w-2xl mx-auto">
           {steps.map((step, index) => (
-            <div key={step.number} className={`flex items-start ${index !== steps.length - 1 ? "flex-1" : ""}`}>
+            <li
+              key={step.number}
+              aria-current={currentStep === step.number ? "step" : undefined}
+              className={`flex items-start ${index !== steps.length - 1 ? "flex-1" : ""}`}
+            >
               <div className="flex flex-col items-center">
                 <div
                   className={`w-12 h-12 rounded-full flex items-center justify-center transition-colors ${
                     currentStep >= step.number
                       ? "bg-blue-500 text-white"
                       : "bg-gray-200 text-gray-600"
                   }`}
                 >
                   {currentStep > step.number ? (
-                    <Check className="size-6" />
+                    <>
+                      <Check className="size-6" aria-hidden="true" />
+                      <span className="sr-only">완료</span>
+                    </>
                   ) : (
                     <span className="font-medium">{step.number}</span>
                   )}
                 </div>
                 <span className="text-xs sm:text-sm text-gray-600 mt-2 break-keep text-center">{step.label}</span>
               </div>
               {index !== steps.length - 1 && (
                 <div
                   className={`hidden sm:block flex-1 h-1 mx-8 mt-[24px] transition-colors ${
                     currentStep > step.number ? "bg-blue-500" : "bg-gray-200"
                   }`}
                   style={{ minWidth: "80px" }}
                 />
               )}
-            </div>
+            </li>
           ))}
-        </div>
+        </ol>
       </div>
-    </div>
+    </nav>
   );

As per coding guidelines: src/components/**: 컴포넌트는 단일 책임, props 타입/네이밍 명확히, 접근성(aria) 체크.

src/pages/myPage/StoreRegistrationPage.tsx (2)

62-79: handleNext에서 유효성 가드/중복 제출 방지 추가를 권장해요.

버튼이 disabled여도 다른 경로로 호출될 수 있으니, 핸들러 자체에서 한 번 더 막아두는 편이 안전합니다.

🔒 제안 수정
-  const handleNext = () => {
+  const handleNext = () => {
+    if (isNextDisabled) return;
     if (currentStep < TOTAL_STEPS) {
       setCurrentStep((prev) => (prev + 1) as 1 | 2 | 3);
     } else {
       const finalPayload = {
         ...step1Data,
         ...step2Data,
         menus: (step3Data.menus || []).map((menu) => ({
           ...menu,
           price: Number(menu.price) || 0, //문자열 가격을 숫자로 변환, 실패 시 0
         })),
       };

122-128: 아이콘 버튼에 접근성 레이블을 추가해주세요.

닫기 버튼이 아이콘만 있어 보조기기에서 의미를 알기 어렵습니다.

♿ 제안 수정
             <button
               type="button"
               onClick={() => setIsExitModalOpen(true)}
+              aria-label="가게 등록 닫기"
               className="text-gray-500 p-2 rounded-lg hover:bg-gray-100 transition-colors cursor-pointer"
             >
-              <X className="size-6" />
+              <X className="size-6" aria-hidden="true" />
             </button>

Comment thread src/components/store-registration/RegistrationNavigation.tsx
Comment thread src/components/store-registration/StepBusinessAuth.tsx Outdated
Comment thread src/components/store-registration/StepBusinessAuth.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/components/store-registration/StepBusinessAuth.tsx`:
- Around line 4-56: The async onSubmit in StepBusinessAuth can call setIsLoading
and onComplete after the component unmounts; wrap the async work in a
try/finally and add a mounted guard (useRef flag set in useEffect) so you only
call setIsLoading(false) and onComplete(...) when mounted; update the onSubmit
function to check the mounted flag before calling setIsLoading/onComplete. Also
wire accessibility and mobile UX: add inputMode="numeric" to the business number
input registered via register("businessNumber"), set
aria-describedby="businessNumber-error" on that input, and give the error
message element id="businessNumber-error" so errors are explicitly associated
with the field.
🧹 Nitpick comments (1)
src/components/store-registration/StepBusinessAuth.tsx (1)

74-90: 숫자 입력 UX 개선을 위해 inputMode 추가를 پیشنهاد드려요.

사업자등록번호는 숫자만 입력하므로 모바일에서 숫자 키패드를 유도하면 입력 경험이 좋아집니다.

🛠️ 제안 수정
           <input
             id="businessNumber"
+            inputMode="numeric"
+            pattern="\d*"
             {...register("businessNumber", {
               onChange: (e) => {
                 if (defaultValues.isVerified) {
                   onComplete({
                     businessNumber: e.target.value,
                     isVerified: false,
                   });
                 }
               },
             })}
             type="text"

모바일 환경에서 숫자 키패드가 표시되는지 한 번 확인해 주세요.

Comment thread src/components/store-registration/StepBusinessAuth.tsx Outdated

@jjjsun jjjsun left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

수정사항 확인했습니다! 고생하셨습니다!!

@dew102938 dew102938 merged commit 8434f03 into develop Jan 29, 2026
1 check passed
@jjjsun jjjsun deleted the feat/StoreRegistration branch February 9, 2026 20:02
@jjjsun jjjsun added the ✨ Feature 새로운 기능 추가 label Apr 1, 2026
@jjjsun jjjsun added the 🎨 Design UI/UX label Apr 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🎨 Design UI/UX ✨ Feature 새로운 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 새 가게 등록 UI 구현

2 participants