Skip to content

✨Feat: course setting-page ui 구현#60

Merged
skyblue1232 merged 9 commits intodevelopfrom
feat/#53/course-setting
Oct 29, 2025
Merged

✨Feat: course setting-page ui 구현#60
skyblue1232 merged 9 commits intodevelopfrom
feat/#53/course-setting

Conversation

@skyblue1232
Copy link
Copy Markdown
Contributor

@skyblue1232 skyblue1232 commented Oct 26, 2025

🔥 작업 내용

  • CourseSettingPage 전체 페이지 구현

여행 코스 설정 화면(CourseSettingPage) UI 완성
ControlBar, BottomNav 적용 및 레이아웃 통합
bannerMap.svg 배너 추가 및 섹션 간 간격(3.6rem / 1.9rem) 맞춤

  • 컴포넌트 분리

CourseSelectSection: 여행 목적 / 체류 시간 / 이동 방식 선택 공통화
CourseInputSection: “꼭 가고 싶은 곳 입력” 섹션 분리 + NextButton 아이콘 추가

  • 접근성(ARIA) 강화

CourseSelectSection, CourseInputSection 내 aria 속성 추가 (aria-label, aria-pressed, aria-labelledby 등)
CourseSettingPage 전체에 role="form" 및 aria-live 속성 적용
스크린 리더에서 페이지 및 그룹 구조 자연스럽게 탐색 가능하도록 개선

  • UI 개선

입력창 focus 시 기본 border 제거
버튼 영역 가로 스크롤 시 스크롤바 완전 투명 처리
버튼 개수에 따라 자동 중앙 정렬 (가로 스크롤 불필요 시)

🤔 추후 작업 사항

  • 카카오 맵 관련되서는 카카오 담당하시는 서버분 계정에서 맵 api 허용 설정 완료하면 알려주신다고 하셨습니다.
  • /map/result 페이지 실제 결과 로직 및 데이터 연동 예정
  • 선택한 옵션(purpose/stay/move/input 값) 상태 전역 관리 또는 서버 전송 로직 추가
  • 접근성 추가 점검

🔗 이슈

PR Point (To Reviewer)

  • ex) 로그인 입력 검증 로직 적절한지 확인 부탁드립니다.
  • CourseSelectSection의 가로 스크롤 + 중앙 정렬 로직이 자연스러운지 확인 부탁드립니다.
  • ARIA 적용 부분(특히 aria-pressed, role="form")이 구조적으로 적절한지 검토 부탁드립니다.

📸 피그마 스크린샷 or 기능 GIF

(작업 내역 스크린샷)

-.Clipchamp.mp4

Summary by CodeRabbit

  • New Features

    • 코스 설정 페이지 전면 개편: 목적·숙박·이동 수단 선택을 가로 스크롤형 UI로 통합하고 입력 보조 필드 및 우측 정렬 Next 버튼 추가
    • 접근성 향상: aria 속성, 스크린리더 설명, 라이브 리전 적용 및 반응형 레이아웃 개선
    • 상태 관리를 위한 신규 선택 훅과 재사용 가능한 버튼/아이콘 추가
  • Refactor

    • 페이지 구조 및 네비게이션 흐름 재구성(선택 완료 시 결과 페이지로 이동)
  • Chores

    • 선택 옵션 상수와 컴포넌트 재수출 정리 및 아이콘 세트 확장

@skyblue1232 skyblue1232 self-assigned this Oct 26, 2025
@skyblue1232 skyblue1232 added the feat 새로운 기능 추가 / 퍼블리싱 label Oct 26, 2025
@skyblue1232 skyblue1232 linked an issue Oct 26, 2025 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Oct 26, 2025

Warning

Rate limit exceeded

@skyblue1232 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 5 minutes and 50 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 10580a3 and e03c26c.

📒 Files selected for processing (1)
  • src/pages/map/index.tsx (1 hunks)

Walkthrough

목적·숙박·이동 선택용 컴포넌트와 자유 입력 섹션, 선택 옵션 상수 및 상태 훅을 추가하고 페이지를 훅 기반으로 리팩터링해 모든 선택 완료 시 결과 페이지로 네비게이션하도록 흐름을 도입했습니다.

Changes

Cohort / File(s) 변경 요약
페이지 리팩토링
\src/pages/map/index.tsx``
페이지 컴포넌트명 CourseSettingPreviewCourseSettingPage 변경, useCourseSelection 훅 적용, 로컬 상태 제거, ControlBar/BottomNav·배너·ARIA 기반 폼 구조로 레이아웃 및 네비게이션(canProceed → push("/map/result")) 도입
선택 UI 컴포넌트
\src/pages/map/components/CourseSelectSection.tsx``
가로 스크롤 selectable pills 렌더링 컴포넌트 추가(프로퍼티: title, options, selected, onSelect; aria-pressed/role 등 접근성 포함)
입력 UI 컴포넌트
\src/pages/map/components/CourseInputSection.tsx``
자유 입력 텍스트 필드와 우측 Next 버튼 컴포넌트 추가(placeholder/label/onNext/disabled props, 스크린리더용 설명 포함)
상태 훅
\src/shared/hooks/useCourseSelection.ts``
purpose, stay, move 상태와 각 setter를 노출하는 커스텀 훅 추가
상수 데이터
\src/shared/constants/course/courseOptions.ts``
purposes, stays, moves 선택 옵션 배열(id, label) 추가
아이콘 레지스트리
\src/shared/icons/iconNames.ts`, `src/shared/icons/index.ts``
아이콘명 "NextButton" 추가 및 NextButton.svg 임포트 추가
컴포넌트 재수출
\src/shared/components/index.ts``
CommonButton을 인덱스 바렐에 새로 재수출 추가 (중복 선언 존재)

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User as 사용자
    participant Page as CourseSettingPage
    participant Hook as useCourseSelection
    participant Select as CourseSelectSection
    participant Input as CourseInputSection
    participant Router as Router

    User->>Page: 페이지 로드
    Page->>Hook: 상태 획득 (purpose, stay, move)

    User->>Select: 옵션 선택 (버튼 클릭)
    Select->>Hook: setPurpose/setStay/setMove 호출

    User->>Input: 텍스트 입력 후 Next 클릭
    Input->>Page: onNext 호출

    alt 모든 선택 완료
        Page->>Router: push("/map/result")
    else 선택 미완료
        Page->>User: 완료 안내/비활성 상태 유지
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 분

  • 추가 주의 영역:
    • src/pages/map/index.tsx: 네비게이션 조건(canProceed) 및 ARIA/롤 사용 검증
    • src/pages/map/components/CourseSelectSection.tsx: 선택/토글 로직과 접근성 속성(aria-pressed, role=listitem)
    • src/shared/components/index.ts: CommonButton 중복 재수출 문제 확인
    • src/shared/icons/*: iconNames 확장 및 SVG 임포트가 아이콘 매핑에 미치는 영향

Possibly related PRs

Suggested reviewers

  • jjangminii
  • KongMezu

Poem

🐰 깡충깡충, 버튼들이 모였네,
텍스트 툭, 선택 톡, 길이 보였네.
훅으로 모은 마음 폴짝폴짝,
결과로 폴짝, 당근 한 입 찹찹 🥕

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed PR 제목 "✨Feat: course setting-page ui 구현"은 변경 사항의 핵심을 명확하게 반영하고 있습니다. 제공된 raw_summary에 따르면 모든 변경사항(CourseInputSection, CourseSelectSection, CourseSettingPage 수정, 상수 파일, 훅, 아이콘 추가)이 Course Setting Page UI 구현을 위한 것이며, 제목이 이를 정확하게 나타냅니다. 제목은 간결하고 구체적이며, 팀원들이 변경 이력을 스캔할 때 주요 변경사항을 쉽게 파악할 수 있습니다.
Out of Scope Changes Check ✅ Passed 모든 변경사항이 이슈 #53의 범위 내에 있으며, 범위를 벗어나는 변경사항은 없습니다. 새로운 컴포넌트 추가(CourseInputSection, CourseSelectSection), 기존 페이지 수정(CourseSettingPage), 공유 상수 및 훅 추가, 아이콘 및 컴포넌트 export 추가 등 모든 파일 변경이 Course Setting Page UI 구현이라는 명확한 목적에 부합합니다. PR 설명의 "추후 작업 사항"에서 향후 추가될 작업(카카오 맵 API, /map/result 페이지 로직, 전역 상태 관리 등)을 명시하여 현재 PR의 범위를 명확히 했습니다.
Description Check ✅ Passed PR 설명이 저장소의 required 템플릿 구조를 완벽하게 충족하고 있습니다. 🔥 작업 내용은 CourseSettingPage 전체 구현, 컴포넌트 분리, 접근성 강화 등을 상세히 설명하고 있으며, 🤔 추후 작업 사항에서 미완료 항목들을 명시했습니다. 🔗 이슈는 #53을 명확히 링크했고, PR Point에서 검토자를 위한 구체적인 확인 항목 3가지를 제시했으며, 📸 피그마 스크린샷/GIF 링크를 제공했습니다. 모든 필수 섹션이 충분히 채워져 있습니다.

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.

❤️ Share

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

@github-actions github-actions bot added the setting 패키지 설치, 개발 설정 label Oct 26, 2025
@github-actions
Copy link
Copy Markdown

🏷️ Labeler has automatically applied labels based on your PR title, branch name, or commit message.
Please verify that they are correct before merging.

@github-actions github-actions bot added the comment 필요한 주석 추가 및 변경 label Oct 26, 2025
@github-actions
Copy link
Copy Markdown

🏷️ Labeler has automatically applied labels based on your PR title, branch name, or commit message.
Please verify that they are correct before merging.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (7)
src/shared/constants/course/courseOptions.ts (1)

1-17: 옵션 상수에 타입 안정성 부여(as const) + i18n 확장성 고려

현재도 동작엔 문제 없지만, 리터럴 고정을 통해 교차 모듈에서 안전한 타입 추론이 가능합니다. 또한 label은 번역 키로 분리하면 i18n 확장에 유리합니다.

예시:

-export const purposes = [
+export const purposes = [
   { id: 'date', label: '데이트' },
   { id: 'family', label: '가족여행' },
   { id: 'friends', label: '우정여행' },
-];
+] as const;
 
-export const stays = [
+export const stays = [
   { id: 'one', label: '당일치기' },
   { id: 'two', label: '1박2일' },
   { id: 'three', label: '2박3일' },
-];
+] as const;
 
-export const moves = [
+export const moves = [
   { id: 'walk', label: '도보' },
   { id: 'transit', label: '대중교통' },
   { id: 'car', label: '자가차' },
-];
+] as const;
+
+export type PurposeId = typeof purposes[number]['id'];
+export type StayId = typeof stays[number]['id'];
+export type MoveId = typeof moves[number]['id'];

추가로, label을 나중에 t('course.purpose.date') 같은 키로 교체할 여지를 남겨두면 좋습니다.

src/shared/hooks/useCourseSelection.ts (1)

3-16: 옵션 ID를 유니온 타입으로 좁히기 + 파생 상태 제공 제안

string | null 대신 옵션 상수에서 유추한 유니온 타입을 쓰면 오타를 컴파일 타임에 차단할 수 있습니다. 또한 canProceed 같은 파생 boolean을 훅에서 함께 반환하면 사용처 단순화에 도움이 됩니다.

예시:

-import { useState } from 'react';
+import { useState } from 'react';
+import type { PurposeId, StayId, MoveId } from '@/shared/constants/course/courseOptions';

 export function useCourseSelection() {
-  const [purpose, setPurpose] = useState<string | null>(null);
-  const [stay, setStay] = useState<string | null>(null);
-  const [move, setMove] = useState<string | null>(null);
+  const [purpose, setPurpose] = useState<PurposeId | null>(null);
+  const [stay, setStay] = useState<StayId | null>(null);
+  const [move, setMove] = useState<MoveId | null>(null);
+  const canProceed = Boolean(purpose && stay && move);

   return {
     purpose,
     setPurpose,
     stay,
     setStay,
     move,
     setMove,
+    canProceed,
   };
 }
src/pages/map/components/CourseSelectSection.tsx (1)

28-38: 수평 스크롤 + 중앙 정렬: 현재 로직 OK, 모멘텀 스크롤/스크롤바 CSS 범위만 보완

  • 콘텐츠가 뷰포트보다 좁을 때 flex justify-center min-w-max로 버튼들이 중앙 정렬됩니다. 요구사항 충족합니다.
  • 다만 모멘텀 스크롤 속성(WebkitOverflowScrolling: 'touch')은 스크롤되는 요소(overflow-x-auto)에게 적용해야 iOS에서 유효합니다.
  • div::-webkit-scrollbar 선택자는 컴포넌트 내 모든 div에 적용되어 과도합니다. 스크롤 컨테이너에만 한정하세요.

아래처럼 class를 부여해 범위를 좁히고, 속성 위치를 옮기는 것을 권장합니다:

-      <div
-        className="w-full overflow-x-auto"
-        style={{
-          scrollbarWidth: 'none',
-          msOverflowStyle: 'none',
-        }}
-      >
+      <div
+        className="w-full overflow-x-auto no-scrollbar"
+        style={{
+          scrollbarWidth: 'none',   // Firefox
+          msOverflowStyle: 'none',  // IE/Edge Legacy
+          WebkitOverflowScrolling: 'touch', // iOS momentum
+        }}
+      >
         <div
-          className="flex justify-center gap-[0.8rem] min-w-max"
-          style={{ WebkitOverflowScrolling: 'touch' }}
+          className="flex justify-center gap-[0.8rem] min-w-max"
           role="list"
         >
@@
-        <style jsx>{`
-          div::-webkit-scrollbar {
-            display: none;
-          }
-        `}</style>
+        <style jsx>{`
+          .no-scrollbar::-webkit-scrollbar {
+            display: none;
+          }
+        `}</style>

Also applies to: 53-57

src/pages/map/components/CourseInputSection.tsx (2)

23-29: 입력 필드에 실제 <label> 연결로 접근성 강화 (aria-label 중복 제거)

현재 섹션 제목 <p> + aria-label로 이름을 부여하고 있습니다. 입력 컨트롤에는 연결된 <label htmlFor>이 가장 적합합니다. aria-label은 중복이므로 제거하세요.

-        <p
-          id={`${inputId}-label`}
-          className="text-body-lg text-gray-700"
-        >
-          {label}
-        </p>
+        <label
+          id={`${inputId}-label`}
+          htmlFor={inputId}
+          className="text-body-lg text-gray-700"
+        >
+          {label}
+        </label>
@@
-        <input
+        <input
           id={inputId}
           type="text"
           placeholder={placeholder}
-          aria-label={label}
           aria-describedby={`${inputId}-desc`}
           className="
             w-full rounded-[2rem] py-[1.2rem] px-[1.7rem]
             text-body-md placeholder-gray-400 bg-gray-100
             border-0 outline-none focus:ring-0 focus:outline-none
           "
         />

Also applies to: 30-41


47-55: Enter 키로 진행 지원(선택 사항)

텍스트 입력 후 Enter로도 onNext를 호출하면 조작성이 좋아집니다. (폼으로 감싸 submit 처리하는 방식이 최선)

-        <button
+        <button
           type="button"
           onClick={onNext}
           className="flex items-center justify-center"
           aria-label="다음 단계로 이동"
         >

또는 <form onSubmit={...}> 내부로 두고 버튼을 type="submit"으로 전환하세요. (index.tsx 변경 필요)

src/pages/map/index.tsx (2)

23-31: role="form" 대신 실제 <form> 사용 권장

폼 영역에 role="form"을 부여하기보다 실제 <form>으로 마크업하고 onSubmit={handleNext}로 연결하면 키보드/스크린리더 흐름이 개선됩니다.

예시(요약):

-    <div
+    <form
       className={cn(
         'relative px-[2.4rem] bg-white flex flex-col h-full pt-[1.3rem] pb-[12rem]'
       )}
-      role="form"
+      onSubmit={(e) => { e.preventDefault(); handleNext(); }}
       aria-labelledby="course-setting-title"
       aria-describedby="course-setting-desc"
     >
@@
-    </div>
+    </form>

참고: CourseInputSection의 진행 버튼은 type="submit"으로 바꾸면 시맨틱이 맞춰집니다. (해당 컴포넌트 변경 필요)


39-42: aria-live="polite" 적용 위치 조정

<main> 전체를 라이브 영역으로 두면 예기치 않은 대규모 읽기가 발생할 수 있습니다. 상태 변화 메시지만 노출하는 작은 전용 라이브 영역을 두는 방식을 권합니다.

-      <main
-        className="w-full pt-[3.4rem] flex flex-col overflow-auto"
-        aria-live="polite" 
-      >
+      <main className="w-full pt-[3.4rem] flex flex-col overflow-auto">
+        <p id="live-status" className="sr-only" aria-live="polite" />

선택지 미완료 시 alert 대신 위 라이브 영역 텍스트를 갱신하는 것도 접근성 측면에서 더 나은 UX입니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 07d6602 and 7a7a968.

⛔ Files ignored due to path filters (3)
  • public/assets/bannerMap.svg is excluded by !**/*.svg
  • src/shared/icons/source/NextButton.svg is excluded by !**/*.svg
  • src/shared/icons/source/Stamp.svg is excluded by !**/*.svg
📒 Files selected for processing (7)
  • src/pages/map/components/CourseInputSection.tsx (1 hunks)
  • src/pages/map/components/CourseSelectSection.tsx (1 hunks)
  • src/pages/map/index.tsx (1 hunks)
  • src/shared/constants/course/courseOptions.ts (1 hunks)
  • src/shared/hooks/useCourseSelection.ts (1 hunks)
  • src/shared/icons/iconNames.ts (1 hunks)
  • src/shared/icons/index.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/pages/map/index.tsx (6)
src/shared/hooks/useCourseSelection.ts (1)
  • useCourseSelection (3-16)
src/shared/lib/utils.ts (1)
  • cn (71-73)
src/pages/map/components/CourseSelectSection.tsx (1)
  • CourseSelectSection (10-61)
src/shared/constants/course/courseOptions.ts (3)
  • purposes (1-5)
  • stays (7-11)
  • moves (13-17)
src/pages/map/components/CourseInputSection.tsx (1)
  • CourseInputSection (9-59)
src/shared/components/tab/BottomNav.tsx (1)
  • BottomNav (21-54)
src/pages/map/components/CourseInputSection.tsx (1)
src/shared/icons/index.ts (1)
  • Icon (22-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-and-deploy
🔇 Additional comments (2)
src/shared/icons/index.ts (1)

16-16: 자동 생성 파일 변경 출처 확인 필요

이 파일은 자동 생성 파일로 명시되어 있습니다. NextButton.svg 추가가 코드젠 결과인지 확인해 주세요. 수동 수정이면 이후 빌드/젠 과정에서 덮어씌워질 수 있습니다.

src/shared/icons/iconNames.ts (1)

16-16: 아이콘 이름 추가 방식 확인

"NextButton" 추가가 아이콘 코드젠 파이프라인을 통해 반영된 것인지 확인해 주세요. 수동 변경은 재생성 시 소실 위험이 있습니다.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/pages/map/index.tsx (1)

8-8: Pages Router에서 next/router 사용한 점 확인

이전 피드백 반영됨. 런타임 이슈 소지 제거되었습니다. 굿.

🧹 Nitpick comments (8)
src/pages/map/components/CourseInputSection.tsx (4)

16-16: id 하드코딩 대신 React useId 사용

다중 인스턴스/SSR에서 id 충돌을 막기 위해 useId로 대체를 권장합니다.

-'use client';
+ 'use client';
@@
-import { Icon } from '@/shared/icons';
+import { Icon } from '@/shared/icons';
+import { useId } from 'react';
@@
-  const inputId = 'desired-place-input';
+  const inputId = useId();

Also applies to: 33-33


25-30: 시각 레이블은 <label>로 연결하고 aria-label 중복 제거

입력 필드는 연결된 <label>이 가장 확실합니다. 그룹 제목 <p> 대신 <label htmlFor> 사용 시 aria-label은 불필요합니다.

-        <p
-          id={`${inputId}-label`}
-          className="text-body-lg text-gray-700"
-        >
-          {label}
-        </p>
+        <label
+          id={`${inputId}-label`}
+          htmlFor={inputId}
+          className="text-body-lg text-gray-700"
+        >
+          {label}
+        </label>
@@
-          aria-label={label}

Also applies to: 36-36


20-24: role="group"는 선택 사항

단일 입력만 포함되면 그룹 롤은 필수 아님. 유지해도 되지만 간소화 가능. Fieldset/legend로 실제 그룹핑할 때만 부여 추천.


49-57: 다음 버튼 비활성화/접근성 상태 반영

진행 불가 시 비활성화 표시가 필요합니다. disabledaria-disabled를 지원하는 prop 추가를 권장합니다. 폼 전환 시 type="submit"로도 동작하도록 확장하세요.

 interface CourseInputSectionProps {
   placeholder?: string;
   label?: string;
   onNext?: () => void;
+  disabled?: boolean;
 }
@@
-export default function CourseInputSection({
+export default function CourseInputSection({
   placeholder = '여기에 입력해 주세요...',
   label = '꼭 가고 싶은 곳을 적어 주세요 (선택)',
   onNext,
+  disabled = false,
 }: CourseInputSectionProps) {
@@
-        <button
-          type="button"
-          onClick={onNext}
+        <button
+          type="button"
+          onClick={disabled ? undefined : onNext}
           className="flex items-center justify-center"
           aria-label="다음 단계로 이동"
+          aria-disabled={disabled || undefined}
+          disabled={disabled}
         >

폼으로 전환한다면 아래처럼 type="submit"을 사용하세요.

- type="button"
+ type="submit"
src/pages/map/index.tsx (4)

24-31: role="form" 대신 실제 <form> 사용

폼 의미론이 필요하다면 <form onSubmit={...}>로 전환하는 것이 접근성/키보드 동작(Enter 제출) 모두에 이점이 있습니다.

예시:

-    <div
-      className={cn(
-        'relative px-[2.4rem] bg-white flex flex-col h-full pt-[1.3rem] pb-[12rem]'
-      )}
-      role="form"
-      aria-labelledby="course-setting-title"
-      aria-describedby="course-setting-desc"
-    >
+    <form
+      className={cn(
+        'relative px-[2.4rem] bg-white flex flex-col h-full pt-[1.3rem] pb-[12rem]'
+      )}
+      aria-labelledby="course-setting-title"
+      aria-describedby="course-setting-desc"
+      onSubmit={(e) => {
+        e.preventDefault();
+        handleNext();
+      }}
+    >

그리고 </div></form>으로 변경.


39-42: aria-live="polite"는 불필요

메인 영역이 동적으로 갱신되지 않으면 라이브 리전은 과잉입니다. 제거를 권장합니다. 오류 안내가 필요하면 별도 상태 메시지 영역에 role="status"/aria-live를 적용하세요.

-      <main
-        className="w-full pt-[3.4rem] flex flex-col overflow-auto"
-        aria-live="polite" 
-      >
+      <main className="w-full pt-[3.4rem] flex flex-col overflow-auto">

16-21: alert 대신 인라인 오류 메시지 + 라이브 리전

alert()는 접근성/UX 모두 거칩니다. 상태 메시지를 표시하고 라이브 리전으로 공지하세요. 버튼은 비활성화하는 편이 더 직관적입니다.

간단 예시:

-  const handleNext = () => {
-    if (!canProceed) return alert('모든 항목을 선택해주세요.');
+  const handleNext = () => {
+    if (!canProceed) {
+      // TODO: 상태 메시지 setState로 표시하고 role="status" 영역에서 공지
+      return;
+    }
     router.push('/map/result');
   };

그리고 CourseInputSectiondisabled={!canProceed} 전달을 권장합니다.

-  <CourseInputSection onNext={handleNext} />
+  <CourseInputSection onNext={handleNext} disabled={!canProceed} />

65-85: 가로 스크롤 + 자동 센터링 로직 검토 결과: 개념은 적절함

외부 컨테이너 overflow-x-auto, 내부 컨테이너 min-w-max 조합으로

  • 내용 < 뷰포트: 가운데 정렬
  • 내용 > 뷰포트: 수평 스크롤
    의도를 대체로 충족합니다. 소소한 개선 제안 2가지:
  1. 더 직관적인 센터링
  • 내부를 w-max mx-auto로 고정하면 내용 넓이만큼만 잡고 자동 가운데 정렬됩니다.
  • 현재 min-w-max는 부모 폭만큼 늘어날 수 있어 일부 환경에서 간헐적 좌우 여백 차이가 생길 수 있습니다.

CourseSelectSection.tsx 예시(파일 외 보조 제안):

-        <div
-          className="flex justify-center gap-[0.8rem] min-w-max"
+        <div
+          className="flex gap-[0.8rem] w-max mx-auto"
           style={{ WebkitOverflowScrolling: 'touch' }}
           role="list"
         >
  1. 스크롤 스냅으로 UX 보완(선택)
-        <div className="w-full overflow-x-auto" ...
+        <div className="w-full overflow-x-auto snap-x snap-mandatory" ...
@@
-              <CommonButton
+              <CommonButton
                 key={id}
                 ...
+                className="snap-center"

또한 CommonButton이 네이티브 <button>을 렌더링하는지 확인해 주세요. 네이티브가 아니라면 role="button" + tabIndex=0 + Space/Enter 키 처리 필요합니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a7a968 and 9d7c130.

📒 Files selected for processing (4)
  • src/pages/map/components/CourseInputSection.tsx (1 hunks)
  • src/pages/map/components/CourseSelectSection.tsx (1 hunks)
  • src/pages/map/index.tsx (1 hunks)
  • src/shared/components/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/map/components/CourseSelectSection.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
src/pages/map/index.tsx (6)
src/shared/hooks/useCourseSelection.ts (1)
  • useCourseSelection (3-16)
src/shared/lib/utils.ts (1)
  • cn (71-73)
src/pages/map/components/CourseSelectSection.tsx (1)
  • CourseSelectSection (12-64)
src/shared/constants/course/courseOptions.ts (3)
  • purposes (1-5)
  • stays (7-11)
  • moves (13-17)
src/pages/map/components/CourseInputSection.tsx (1)
  • CourseInputSection (11-61)
src/shared/components/tab/BottomNav.tsx (1)
  • BottomNav (21-54)
src/pages/map/components/CourseInputSection.tsx (1)
src/shared/icons/index.ts (1)
  • Icon (22-22)
🔇 Additional comments (1)
src/shared/components/index.ts (1)

5-5: 배럴 익스포트 추가 LGTM

공용 버튼을 barrel로 노출한 선택, 사용처 간 결합도 낮추는 데 도움 됩니다. 추가 액션 없습니다.

Comment on lines +38 to +43
className="
w-full rounded-[2rem] py-[1.2rem] px-[1.7rem]
text-body-md placeholder-gray-400 bg-gray-100
border-0 outline-none focus:ring-0 focus:outline-none
"
/>
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.

⚠️ Potential issue | 🟠 Major

포커스 스타일 제거 → WCAG Focus Visible 위반 가능

outline-none, focus:ring-0로 키보드 포커스가 보이지 않을 수 있습니다. 명시적 포커스 표시를 추가하세요.

아래처럼 교체 권장:

-          className="
-            w-full rounded-[2rem] py-[1.2rem] px-[1.7rem]
-            text-body-md placeholder-gray-400 bg-gray-100
-            border-0 outline-none focus:ring-0 focus:outline-none
-          "
+          className="
+            w-full rounded-[2rem] py-[1.2rem] px-[1.7rem]
+            text-body-md placeholder-gray-400 bg-gray-100
+            border-0
+            focus-visible:ring-2 focus-visible:ring-mint-300
+            focus-visible:ring-offset-2 focus-visible:ring-offset-white
+            focus-visible:outline-none
+          "
📝 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
className="
w-full rounded-[2rem] py-[1.2rem] px-[1.7rem]
text-body-md placeholder-gray-400 bg-gray-100
border-0 outline-none focus:ring-0 focus:outline-none
"
/>
className="
w-full rounded-[2rem] py-[1.2rem] px-[1.7rem]
text-body-md placeholder-gray-400 bg-gray-100
border-0
focus-visible:ring-2 focus-visible:ring-mint-300
focus-visible:ring-offset-2 focus-visible:ring-offset-white
focus-visible:outline-none
"
🤖 Prompt for AI Agents
In src/pages/map/components/CourseInputSection.tsx around lines 38 to 43, the
input currently removes all visible focus styling with "outline-none" and
"focus:ring-0" which can hide keyboard focus; replace those rules with an
explicit keyboard-visible focus style (e.g., remove global outline suppression
and add focus-visible utilities such as focus-visible:ring-2 and
focus-visible:ring-[color] or focus-visible:outline to provide a high-contrast
visible ring when focused via keyboard) and keep any mouse-focused visual
behavior intact (use focus-visible instead of focus to avoid changing click
focus), ensuring the new focus style meets WCAG contrast and size requirements.

Copy link
Copy Markdown
Contributor

@KongMezu KongMezu left a comment

Choose a reason for hiding this comment

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

중복으로 들어간 내용 한번 확인해주시면 될 거 같습니다! 그 외 리뷰는 제안 정도여서 :)
코스 선택 페이지 수고하셨습니다!

Comment on lines +25 to +38
<p
id={`${inputId}-label`}
className="text-body-lg text-gray-700"
>
{label}
</p>

<input
id={inputId}
type="text"
placeholder={placeholder}
aria-label={label}
aria-describedby={`${inputId}-desc`}
className="
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.

  aria-labelledby={`${inputId}-label`}
  aria-describedby={`${inputId}-desc`}

p태그로 이미 라벨이 있어서 스크린리더에서 aria-label은 중복 읽힐 수 있을거 같아요!
aria-labelledby로 하면 좋을 거 같습니다!

label={label}
aria-pressed={selected === id}
aria-label={`${label} 선택`}
role="button"
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.

이거 이미 button 컴포넌트 불러온건데
role 선언이 중복으로 선언되어있는거 같아요!
확인 부탁드립니다!

const canProceed = Boolean(purpose && stay && move);

const handleNext = () => {
if (!canProceed) return alert('모든 항목을 선택해주세요.');
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.

여기 alret 경고문 보다는 다음으로 넘어가는 버튼 비활성화가 더 좋지 않을까 제안해봅니다!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/pages/map/index.tsx (1)

79-79: CourseInputSection에 disabled prop을 전달하여 UX 일관성을 개선하세요.

CourseInputSectiondisabled prop을 지원하지만, 현재 전달되지 않아 필수 선택이 완료되지 않았을 때도 NextButton이 활성화된 것처럼 보입니다. 이는 사용자가 버튼을 클릭했을 때 아무 동작도 하지 않아 혼란을 줄 수 있습니다.

-          <CourseInputSection onNext={handleNext} />
+          <CourseInputSection onNext={handleNext} disabled={!canProceed} />
🧹 Nitpick comments (2)
src/pages/map/index.tsx (2)

33-38: 향후 소셜 로그인 통합 시 ControlBar props 업데이트 필요.

현재 isLoggedIn, onLogin, userName이 플레이스홀더 값으로 하드코딩되어 있습니다. PR 목표에 언급된 대로 향후 카카오/구글 로그인 구현 시 실제 인증 상태와 핸들러로 교체해야 합니다.

TODO 주석을 추가하거나 이를 추적할 이슈를 생성하시겠습니까?


79-79: 입력값 상태 관리 추가를 권장합니다.

CourseInputSection의 입력 필드가 제어되지 않으며(uncontrolled), 사용자가 입력한 값이 캡처되지 않습니다. PR 목표에 언급된 대로 향후 서버 제출을 위해 입력값을 상태로 관리하는 것이 좋습니다.

useCourseSelection 훅을 확장하여 입력값 상태를 추가하세요:

src/shared/hooks/useCourseSelection.ts:

export function useCourseSelection() {
  const [purpose, setPurpose] = useState<string | null>(null);
  const [stay, setStay] = useState<string | null>(null);
  const [move, setMove] = useState<string | null>(null);
  const [desiredPlace, setDesiredPlace] = useState<string>('');

  return {
    purpose,
    setPurpose,
    stay,
    setStay,
    move,
    setMove,
    desiredPlace,
    setDesiredPlace,
  };
}

src/pages/map/index.tsx:

-  const { purpose, setPurpose, stay, setStay, move, setMove } =
+  const { purpose, setPurpose, stay, setStay, move, setMove, desiredPlace, setDesiredPlace } =
     useCourseSelection();
@@
-          <CourseInputSection onNext={handleNext} disabled={!canProceed} />
+          <CourseInputSection 
+            onNext={handleNext} 
+            disabled={!canProceed}
+            value={desiredPlace}
+            onChange={setDesiredPlace}
+          />

src/pages/map/components/CourseInputSection.tsx:

// interface에 추가
interface CourseInputSectionProps {
  // ... 기존 props
  value?: string;
  onChange?: (value: string) => void;
}

// 컴포넌트 내부에서
<input
  id={inputId}
  type="text"
  value={value}
  onChange={(e) => onChange?.(e.target.value)}
  // ... 기타 props
/>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d7c130 and 031bb34.

📒 Files selected for processing (3)
  • src/pages/map/components/CourseInputSection.tsx (1 hunks)
  • src/pages/map/components/CourseSelectSection.tsx (1 hunks)
  • src/pages/map/index.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/pages/map/components/CourseInputSection.tsx
  • src/pages/map/components/CourseSelectSection.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/pages/map/index.tsx (6)
src/shared/hooks/useCourseSelection.ts (1)
  • useCourseSelection (3-16)
src/shared/lib/utils.ts (1)
  • cn (71-73)
src/pages/map/components/CourseSelectSection.tsx (1)
  • CourseSelectSection (12-63)
src/shared/constants/course/courseOptions.ts (3)
  • purposes (1-5)
  • stays (7-11)
  • moves (13-17)
src/pages/map/components/CourseInputSection.tsx (1)
  • CourseInputSection (13-70)
src/shared/components/tab/BottomNav.tsx (1)
  • BottomNav (21-54)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-and-deploy
🔇 Additional comments (3)
src/pages/map/index.tsx (3)

11-22: 이전 리뷰 피드백이 올바르게 반영되었습니다.

  • next/router에서 useRouter를 정확히 import하고 있습니다.
  • Boolean() 캐스팅으로 의도가 명확해졌습니다.
  • alert 대신 조건부 라우팅 로직으로 개선되었습니다.

40-58: 접근성 구현이 우수합니다.

  • aria-live="polite" 적용으로 동적 콘텐츠 업데이트가 스크린 리더에 전달됩니다.
  • sr-only 클래스와 aria-labelledby/aria-describedby 연결이 적절합니다.
  • 의미론적 HTML 구조와 ARIA 역할이 잘 조합되어 있습니다.

25-84: 레이아웃 구조가 잘 구성되어 있습니다.

고정 위치의 ControlBarBottomNav, 그리고 스크롤 가능한 메인 컨텐츠 영역이 적절히 조합되어 있습니다. 섹션 간격(3.6rem / 1.9rem)도 PR 설명과 일치합니다.

다양한 화면 크기와 콘텐츠 길이에서 레이아웃이 올바르게 작동하는지 확인하세요. 특히 다음 사항을 테스트하세요:

  • 작은 화면에서 ControlBar와 BottomNav가 컨텐츠를 가리지 않는지
  • 스크롤 시 모든 컨텐츠가 접근 가능한지
  • 가로 방향 및 다양한 기기에서의 표시

Copy link
Copy Markdown
Contributor

@KongMezu KongMezu left a comment

Choose a reason for hiding this comment

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

수고하셨습니다! 추후에 페이지 연결하면서 더 리팩토링 고려해보면 좋을 거 같습니다!
approve 하겠습니다!

@github-actions github-actions bot added the api api 연결 label Oct 26, 2025
@github-actions
Copy link
Copy Markdown

🏷️ Labeler has automatically applied labels based on your PR title, branch name, or commit message.
Please verify that they are correct before merging.

@geulDa geulDa deleted a comment from vercel bot Oct 29, 2025
@vercel
Copy link
Copy Markdown

vercel bot commented Oct 29, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
af-fe Ready Ready Preview Comment Oct 29, 2025 3:02pm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api api 연결 comment 필요한 주석 추가 및 변경 feat 새로운 기능 추가 / 퍼블리싱 setting 패키지 설치, 개발 설정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] Course Setting

2 participants