Skip to content

[feat]종합데이터 지도 모드 및 기능별 모듈(안전·편의·접근성 등)#6

Merged
HSCHEOL merged 7 commits into
mainfrom
dev
Feb 5, 2026
Merged

[feat]종합데이터 지도 모드 및 기능별 모듈(안전·편의·접근성 등)#6
HSCHEOL merged 7 commits into
mainfrom
dev

Conversation

@HSCHEOL

@HSCHEOL HSCHEOL commented Feb 5, 2026

Copy link
Copy Markdown
Contributor

🚀 주요 구현 내용
전체적인 서비스 경험을 높이기 위해 지도를 기반으로 다양한 주거 데이터를 시각화하는 '종합데이터 모드'의 모든 기능을 구현했습니다.

1. 지도 인터페이스 & 레이아웃 최적화

app/map 구조를 리팩토링하여 풀스크린 카카오맵 기반의 UI 오버레이 시스템 구축
Zustand(useMapModeStore)를 통한 모드 전환 및 상태 관리 로직 구현
피그마 디자인을 준수한 상단 헤더(MapHeader) 및 반응형 레이아웃 적용

2. 고도화된 기능별 데이터 모듈

안전 정보(SafetyModule): 기본 경로와 큰길 우선 경로 모드 지원, 경로별 강조(Glow) 효과 및 아이콘 동적 전환

자주 가는 곳(FavoritePlacesModule): 카카오 우편번호 서비스 연동을 통한 주소 등록/변경 기능 구현, localStorage를 통한 데이터 영속성 처리

시간 슬라이더(TimeSliderModule): 실시간 시간 조절 기능, 낮/밤(06:00~18:00 기준) 아이콘 자동 전환 및 부드러운 트랜지션 효과 적용

공통 모듈: 접근성(AccessibilityModule), 편의(ConvenienceModule), 소음(NoiseModule), 환경(EnvironmentModule) 등 각 카테고리별 디자인 반영 및 온/오프 상태 처리

3. 사용자 경험(UX) 개선

Glassmorphism 기반의 프리미엄 UI 디자인 적용
모듈 리스크 스크롤 영역 최적화 (no-scrollbar 적용)
실제 외부 API(카카오 우편번호) 연동을 통한 실질적인 등록 흐름 제공

Summary by CodeRabbit

  • 새로운 기능

    • 종합 데이터 지도 모드 및 전용 맵 페이지 추가
    • 접근성/편의성/환경/소음/안전/즐겨찾기/시간대 슬라이더 패널 추가
    • 즐겨찾기(주소) 등록·변경 기능 추가
  • 개선 사항

    • 온보딩 흐름 저장 및 복원(선호정보 지속화)으로 UX 개선
    • 큐레이션 화면 마스코트 중심 디자인·레이아웃 개편
    • 상단 레이아웃·네비게이션 동작 및 버튼 배치 최적화

@HSCHEOL HSCHEOL self-assigned this Feb 5, 2026
@HSCHEOL HSCHEOL added the feature Something isn't working label Feb 5, 2026
@vercel

vercel Bot commented Feb 5, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
frontend Ready Ready Preview, Comment Feb 5, 2026 2:41pm

@coderabbitai

coderabbitai Bot commented Feb 5, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

온보딩과 지도 모드 상태를 전역 Zustand 스토어로 도입하고, 일곱 개의 새 UI 모듈을 추가했으며(Accessibility, Convenience, Environment, Noise, Safety, FavoritePlaces, TimeSlider) 온보딩 흐름과 지도 페이지 레이아웃을 리팩터링하고 쿠키 토큰 접근 및 axios 요청 인터셉터를 추가했습니다.

Changes

Cohort / File(s) Summary
Curation Component
app/components/curation/Curation.tsx
마스코트 중심의 회전 배경 구조로 재구성, 내부 원 추가, 타이틀/설명 레이어 분리, 시작 버튼을 하단 영역으로 이동, 마스코트 자산 크기 변경(160x160).
Layout & Map Header
app/components/layout/LayoutClient.tsx, app/layout.tsx, app/components/map/MapHeader.tsx
LayoutClient 추가로 지도 모드에 따른 Navbar 조건부 렌더링 적용. 루트 레이아웃이 LayoutClient로 래핑됨. MapHeader 컴포넌트(맵 모드 닫기 버튼) 추가.
Onboarding store & pages
app/store/onboardingStore.ts, app/onboarding/page.tsx
Zustand 기반 온보딩 스토어(영속화) 추가. 온보딩 페이지가 스토어 사용으로 로직 변경, 온보드 API 뮤테이션/로딩/완료 사이클 통합 및 Curation 표시 흐름 추가.
Onboarding steps
app/components/onboarding/step1/HousingTypeStep.tsx, app/components/onboarding/step2/RegionSelectionStep.tsx, app/components/onboarding/step3/AtmosphereSelectionStep.tsx, app/components/onboarding/step3/AtmosphereCard.tsx
각 스텝들이 로컬 상태 대신 useOnboardingStore 사용으로 전환. onNext 시그니처들이 인자 제거(파라미터리스)로 변경. AtmosphereCard는 Next/Image로 교체.
Map mode store & pages
app/store/mapModeStore.ts, app/map/page.tsx, app/map/layout.tsx
mapModeStore 추가(지도 모드 토글). 기존 map/layout.tsx 삭제 및 MapPage 구현으로 지도/리스트 조건부 렌더링과 모듈 패널 배치 변경.
New UI Modules
app/components/module/...
app/components/module/AccessibilityModule.tsx, .../ConvenienceModule.tsx, .../EnvironmentModule.tsx, .../NoiseModule.tsx, .../SafetyModule.tsx, .../FavoritePlacesModule.tsx, .../TimeSliderModule.tsx
7개의 클라이언트 사이드 모듈 추가 — 각 모듈은 확장/축소 토글, 점수 표시, 상호작용 UI; FavoritePlaces는 Daum 우편번호 연동 및 localStorage 사용.
List & Map Section
app/components/list/list_section/CurationSection.tsx
"종합 데이터 지도로 보기" 버튼에 mapMode 전환(onClick setMapMode(true))과 커서 스타일 추가.
Auth & API client
app/login/actions.ts, shared/api/axios-instance.ts
액세스 토큰 쿠키를 httpOnly:false로 변경하여 클라이언트 접근 허용. axios 인스턴스에 document.cookie에서 토큰을 읽어 Authorization 헤더를 주입하는 요청 인터셉터 추가.

Sequence Diagram

sequenceDiagram
    participant User as 사용자
    participant OnboardingPage as 온보딩 페이지
    participant OnboardingStore as Onboarding 스토어
    participant API as 온보드 API
    participant UserStore as User 스토어

    User->>OnboardingPage: 유형/지역/분위기 선택
    OnboardingPage->>OnboardingStore: setPreferredType/Districts/Conditions
    OnboardingStore->>OnboardingStore: 상태 업데이트 (localStorage에 영속)
    User->>OnboardingPage: 완료 클릭 (온보드 제출)
    OnboardingPage->>OnboardingPage: isCurationLoading = true (Curation 표시)
    OnboardingPage->>API: POST /onboard (preferences)
    API-->>OnboardingPage: 성공 응답
    OnboardingPage->>UserStore: 사용자 데이터 갱신
    OnboardingPage->>OnboardingStore: reset()
    OnboardingStore->>OnboardingStore: 선호값 초기화
    OnboardingPage->>User: 다음 화면으로 이동
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

Poem

🐰 깡총! 데이터가 모여 춤추네,
스토어에 선호는 저장되고,
모듈들은 한껏 펼쳐지네,
지도 모드로 깡충—변경 한 번에,
당근 한 조각, 축하 춤! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 종합데이터 지도 모드 구현 및 다양한 기능별 모듈 추가라는 주요 변경 사항을 명확하게 요약하고 있습니다.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

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.

@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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/login/actions.ts (1)

36-42: ⚠️ Potential issue | 🟠 Major

보안 위험: httpOnly: false로 설정 시 XSS 공격에 토큰이 노출됩니다.

httpOnly: false로 설정하면 클라이언트 JavaScript에서 document.cookie를 통해 토큰에 접근할 수 있지만, XSS 공격 발생 시 공격자도 토큰을 탈취할 수 있습니다.

대안으로 다음 방법들을 고려해 주세요:

  1. 서버 사이드 프록시 API 라우트: 인증이 필요한 요청을 서버 사이드에서 처리하여 토큰을 클라이언트에 노출하지 않음
  2. BFF (Backend for Frontend) 패턴: Next.js API Routes를 통해 토큰을 서버에서만 관리
  3. 현재 방식 유지 시: 강력한 CSP(Content Security Policy) 헤더 설정 및 XSS 방어 강화 필수
🤖 Fix all issues with AI agents
In `@app/components/curation/Curation.tsx`:
- Around line 52-53: The second absolute-positioned indicator uses an invalid
Tailwind class "right-[-18]" which will be ignored; update that class to include
a unit (e.g., "right-[-18px]") or replace with a valid spacing token (e.g.,
"right-4") in the same JSX element (the div with className containing "absolute
bottom-1/2 right-[-18] ...") so the element positions correctly.
- Around line 153-157: The Link remains clickable during loading because only
opacity-0 is applied; update the Link in the motion.div so when isLoading is
true it both prevents navigation and is removed from keyboard focus: add an
onClick handler like onClick={(e) => isLoading && e.preventDefault()}, add
accessibility attributes aria-disabled={isLoading} and tabIndex={isLoading ? -1
: 0}, and include the Tailwind class pointer-events-none when isLoading to block
mouse interactions; keep existing className logic but append pointer-events-none
and ensure the onClick, aria-disabled, and tabIndex are applied to the Link
component (the element using buttonLink, isLoading, motion.div, and Link).

In `@app/components/module/FavoritePlacesModule.tsx`:
- Around line 13-17: 윈도우 전역 선언에서 Window.daum에 any를 쓰지 말고 Daum Postcode API 구조를
반영한 타입 인터페이스(DaumPostcodeData)를 새로 정의하여 사용하세요: 전역 선언의 Window.daum 타입을 any에서 해당
인터페이스로 변경하고, 컴포넌트 내부에서 postcode 콜백/핸들러의 매개변수 타입(현재 data: any로 선언된 부분)을 data:
DaumPostcodeData로 바꾸어 타입 안전성을 확보하세요; 참조할 심볼: Window.daum, DaumPostcodeData, 그리고
파일 내의 data 매개변수(현재 any로 선언된 위치).

In `@app/onboarding/page.tsx`:
- Around line 88-98: The UI sets setIsCompleted(true) before the onboardMutate
call causing a flash if the mutation fails; change the flow to only set
setIsCurationLoading(true) before calling onboardMutate and move the
setIsCompleted(true) into the mutation's success handler (onSuccess) while
ensuring onError still resets loading and completion
(setIsCurationLoading(false) and setIsCompleted(false) as needed); update any
conditional logic that checks completion (the condition referenced around line
159) to rely on the state set by onSuccess instead of being set pre-flight.
🧹 Nitpick comments (16)
shared/api/axios-instance.ts (1)

21-29: 인터셉터에 에러 핸들러 추가를 권장합니다.

요청 인터셉터에서 예외가 발생할 경우를 대비해 에러 핸들러를 추가하면 디버깅이 용이해집니다.

🔧 에러 핸들러 추가 제안
 AXIOS_INSTANCE.interceptors.request.use(
   (config: InternalAxiosRequestConfig) => {
     const token = getCookie("accessToken");
     if (token) {
       config.headers.Authorization = `Bearer ${token}`;
     }
     return config;
   },
+  (error) => {
+    return Promise.reject(error);
+  },
 );
app/components/map/MapHeader.tsx (1)

27-32: 접근성 개선: 닫기 버튼에 aria-label 추가를 권장합니다.

스크린 리더 사용자를 위해 버튼의 목적을 명시하는 것이 좋습니다.

♿ 접근성 개선 제안
       <button
         onClick={() => setMapMode(false)}
+        aria-label="종합데이터 모드 닫기"
         className="bg-white border-[1.5px] border-[`#F5F5F5`] flex items-center justify-center rounded-xl shadow-[0px_3px_5px_0px_rgba(0,0,0,0.1)] shrink-0 w-15 h-15 pointer-events-auto hover:bg-gray-50 transition-colors"
       >
         <X className="w-6 h-6 text-black" />
       </button>
app/components/module/TimeSliderModule.tsx (1)

5-10: cn 유틸리티 함수 중복 정의

cn 함수가 app/lib/utils.ts에 이미 정의되어 있습니다. 로컬 정의 대신 기존 유틸리티를 import하여 코드 중복을 제거하세요.

♻️ 제안된 수정
-import { clsx, type ClassValue } from "clsx";
-import { twMerge } from "tailwind-merge";
-
-function cn(...inputs: ClassValue[]) {
-  return twMerge(clsx(inputs));
-}
+import { cn } from "@/app/lib/utils";
app/components/module/AccessibilityModule.tsx (1)

5-10: cn 유틸리티 함수 중복 정의

app/lib/utils.ts에 이미 정의된 cn 함수를 import하여 사용하세요. 이 PR의 모든 모듈 파일에서 동일한 중복이 발생하고 있습니다.

♻️ 제안된 수정
-import { clsx, type ClassValue } from "clsx";
-import { twMerge } from "tailwind-merge";
-
-function cn(...inputs: ClassValue[]) {
-  return twMerge(clsx(inputs));
-}
+import { cn } from "@/app/lib/utils";
app/components/module/NoiseModule.tsx (1)

5-10: cn 유틸리티 함수 중복 정의

app/lib/utils.tscn 함수를 import하여 사용하세요.

♻️ 제안된 수정
-import { clsx, type ClassValue } from "clsx";
-import { twMerge } from "tailwind-merge";
-
-function cn(...inputs: ClassValue[]) {
-  return twMerge(clsx(inputs));
-}
+import { cn } from "@/app/lib/utils";
app/components/module/FavoritePlacesModule.tsx (2)

5-11: cn 유틸리티 함수 중복 정의

app/lib/utils.tscn 함수를 import하여 사용하세요.

♻️ 제안된 수정
-import { clsx, type ClassValue } from "clsx";
-import { twMerge } from "tailwind-merge";
-
-function cn(...inputs: ClassValue[]) {
-  return twMerge(clsx(inputs));
-}
+import { cn } from "@/app/lib/utils";

20-31: useState 지연 초기화 고려

ESLint가 useEffect 내 setState 호출에 대해 경고하고 있습니다. localStorage 초기화는 유효한 패턴이지만, 지연 초기화를 사용하면 더 깔끔합니다.

♻️ 지연 초기화 패턴 제안
-  const [address, setAddress] = useState<string | null>(null);
-  const [placeName, setPlaceName] = useState<string | null>(null);
-
-  // Load address from localStorage on mount
-  useEffect(() => {
-    const savedAddress = localStorage.getItem("favorite-address");
-    const savedPlaceName = localStorage.getItem("favorite-place-name");
-    if (savedAddress) {
-      setAddress(savedAddress);
-      setPlaceName(savedPlaceName || "등록된 장소");
-    }
-  }, []);
+  const [address, setAddress] = useState<string | null>(() => {
+    if (typeof window === "undefined") return null;
+    return localStorage.getItem("favorite-address");
+  });
+  const [placeName, setPlaceName] = useState<string | null>(() => {
+    if (typeof window === "undefined") return null;
+    const savedAddress = localStorage.getItem("favorite-address");
+    if (!savedAddress) return null;
+    return localStorage.getItem("favorite-place-name") || "등록된 장소";
+  });
app/components/module/EnvironmentModule.tsx (1)

5-10: cn 유틸리티 함수 중복 정의

app/lib/utils.tscn 함수를 import하여 사용하세요.

♻️ 제안된 수정
-import { clsx, type ClassValue } from "clsx";
-import { twMerge } from "tailwind-merge";
-
-function cn(...inputs: ClassValue[]) {
-  return twMerge(clsx(inputs));
-}
+import { cn } from "@/app/lib/utils";
app/components/module/ConvenienceModule.tsx (2)

6-11: cn 유틸리티 함수 중복 정의

app/lib/utils.tscn 함수를 import하여 사용하세요.

♻️ 제안된 수정
-import { clsx, type ClassValue } from "clsx";
-import { twMerge } from "tailwind-merge";
-
-function cn(...inputs: ClassValue[]) {
-  return twMerge(clsx(inputs));
-}
+import { cn } from "@/app/lib/utils";

127-211: 시설 목록 반복 코드 리팩토링 고려

세 개의 시설 항목(편의점, 마트, 병원)이 거의 동일한 구조를 가지고 있습니다. 다른 모듈들처럼 배열과 map을 사용하면 코드를 간결하게 만들 수 있습니다.

♻️ 제안된 리팩토링
const facilities = [
  { key: "store" as const, name: "편의점", icon: "/icons/module/convenience/24shop.svg" },
  { key: "market" as const, name: "대형마트, 백화점, 시장", icon: "/icons/module/convenience/market.svg" },
  { key: "hospital" as const, name: "병원", icon: "/icons/module/convenience/hospital.svg" },
];

// JSX에서:
{facilities.map((facility) => (
  <div key={facility.key} className="flex items-center justify-between">
    <div className="flex items-center gap-1">
      <Image src={facility.icon} width={18} height={18} alt={facility.name} />
      <span className="font-semibold text-[14px] text-black tracking-[-0.15px]">
        {facility.name}
      </span>
    </div>
    <button
      onClick={() => toggleItem(facility.key)}
      className={cn(
        "w-5 h-5 border rounded-[2px] flex items-center justify-center transition-colors",
        checkedItems[facility.key]
          ? "bg-[`#AB9FD5`] border-[`#AB9FD5`]"
          : "bg-white border-[`#AB9FD5`]",
      )}
    >
      {checkedItems[facility.key] && (
        <Check className="w-3.5 h-3.5 text-white stroke-[3px]" />
      )}
    </button>
  </div>
))}
app/components/module/SafetyModule.tsx (2)

5-10: 공통 cn 유틸 재사용 권장
cn을 로컬에서 재정의하고 있어 공통 유틸과 중복됩니다. 공통 유틸을 쓰면 일관성과 유지보수성이 좋아집니다.

♻️ 변경 제안
-import { clsx, type ClassValue } from "clsx";
-import { twMerge } from "tailwind-merge";
-
-function cn(...inputs: ClassValue[]) {
-  return twMerge(clsx(inputs));
-}
+import { cn } from "@/app/lib/utils";

60-71: 헤더 토글에 접근성 라벨 추가
텍스트 없는 버튼이라 스크린리더에서 의미 전달이 어렵습니다. aria-label/aria-pressedtype="button"을 추가하는 편이 좋습니다.

♿ 변경 제안
-        <button
+        <button
+          type="button"
+          aria-pressed={isActive}
+          aria-label={isActive ? "안전 정보 모듈 접기" : "안전 정보 모듈 펼치기"}
           onClick={() => setIsActive(!isActive)}
           className={cn(
             "w-[36px] h-[21px] rounded-[11px] border-[0.5px] flex items-center p-[3px] transition-all duration-200",
             isActive
               ? "bg-[`#F48787`] border-[`#F48787`] justify-end"
               : "bg-[`#F8FAFB`] border-[`#8298A8`] justify-start",
           )}
         >
app/onboarding/page.tsx (4)

57-64: @ts-expect-error 사용 대신 적절한 타입 정의를 권장합니다.

@ts-expect-error를 사용하면 타입 안전성이 저하됩니다. 에러 응답 타입을 명시적으로 정의하거나, 생성된 API 타입에서 에러 타입을 활용하는 것이 좋습니다.

♻️ 제안된 수정
      onError: (error) => {
        console.error("Onboarding Mutation Error:", error);
-       // `@ts-expect-error`: error response mapping
-       const errorData = error.response?.data as {
-         error?: { message?: string };
-       };
+       const errorData = (error as { response?: { data?: { error?: { message?: string } } } })
+         ?.response?.data;
        alert(errorData?.error?.message || "서버 통신 중 오류가 발생했습니다.");
        setIsCompleted(false);
        setIsCurationLoading(false);
      },

45-48: 매직 넘버 3000ms에 대한 설명 또는 상수화가 필요합니다.

setTimeout의 3000ms 지연 시간이 왜 필요한지 명확하지 않습니다. 이 값이 특정 UX 요구사항에 기반한 것이라면 상수로 추출하고 주석을 추가하는 것이 좋습니다.

♻️ 제안된 수정
+const CURATION_DISPLAY_DURATION_MS = 3000; // 큐레이션 완료 화면 표시 시간
+
// ... in component
-         setTimeout(() => {
-           setIsCurationLoading(false);
-           resetOnboarding();
-         }, 3000);
+         setTimeout(() => {
+           setIsCurationLoading(false);
+           resetOnboarding();
+         }, CURATION_DISPLAY_DURATION_MS);

82-86: 프로덕션 코드에 console.log가 남아있습니다.

디버깅용 console.log는 프로덕션 빌드 전에 제거하거나, 조건부 로깅 유틸리티를 사용하는 것이 좋습니다.


126-126: Next.js에서 window.location.href 대신 router.push를 사용하세요.

window.location.href를 사용하면 전체 페이지가 새로고침되어 클라이언트 사이드 상태가 손실되고 성능이 저하됩니다. Next.js의 useRouter를 사용하면 클라이언트 사이드 네비게이션이 가능합니다.

♻️ 제안된 수정
+"use client";
+
+import { useRouter } from "next/navigation";
 import React from "react";
 // ... other imports

 export default function OnboardingPage() {
+  const router = useRouter();
   // ... existing code

   // In JSX:
   <HousingTypeStep
     onNext={handleHousingTypeSelect}
-    onBack={() => (window.location.href = "/")}
+    onBack={() => router.push("/")}
   />

Comment on lines +52 to +53
<div className="absolute top-1/2 left-0 -translate-x-1/2 -translate-y-1/2 w-4 h-4 bg-white rounded-full drop-shadow-[0_2px_24.4px_rgba(48,203,159,0.20)]" />
<div className="absolute bottom-1/2 right-[-18] -translate-x-1/2 -translate-y-1/2 w-4 h-4 bg-white rounded-full drop-shadow-[0_2px_24.4px_rgba(48,203,159,0.20)]" />

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 | 🟡 Minor

right-[-18]는 Tailwind 유효 값이 아닙니다
임의값은 단위를 포함해야 합니다. 현재는 클래스가 무시되어 위치가 어긋날 수 있습니다.

🛠️ 변경 제안
-            <div className="absolute bottom-1/2 right-[-18] -translate-x-1/2 -translate-y-1/2 w-4 h-4 bg-white rounded-full drop-shadow-[0_2px_24.4px_rgba(48,203,159,0.20)]" />
+            <div className="absolute bottom-1/2 right-[-18px] -translate-x-1/2 -translate-y-1/2 w-4 h-4 bg-white rounded-full drop-shadow-[0_2px_24.4px_rgba(48,203,159,0.20)]" />
📝 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
<div className="absolute top-1/2 left-0 -translate-x-1/2 -translate-y-1/2 w-4 h-4 bg-white rounded-full drop-shadow-[0_2px_24.4px_rgba(48,203,159,0.20)]" />
<div className="absolute bottom-1/2 right-[-18] -translate-x-1/2 -translate-y-1/2 w-4 h-4 bg-white rounded-full drop-shadow-[0_2px_24.4px_rgba(48,203,159,0.20)]" />
<div className="absolute top-1/2 left-0 -translate-x-1/2 -translate-y-1/2 w-4 h-4 bg-white rounded-full drop-shadow-[0_2px_24.4px_rgba(48,203,159,0.20)]" />
<div className="absolute bottom-1/2 right-[-18px] -translate-x-1/2 -translate-y-1/2 w-4 h-4 bg-white rounded-full drop-shadow-[0_2px_24.4px_rgba(48,203,159,0.20)]" />
🤖 Prompt for AI Agents
In `@app/components/curation/Curation.tsx` around lines 52 - 53, The second
absolute-positioned indicator uses an invalid Tailwind class "right-[-18]" which
will be ignored; update that class to include a unit (e.g., "right-[-18px]") or
replace with a valid spacing token (e.g., "right-4") in the same JSX element
(the div with className containing "absolute bottom-1/2 right-[-18] ...") so the
element positions correctly.

Comment on lines +153 to +157
<motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
<Link
href={buttonLink || "/"}
className={`inline-flex items-center justify-center px-15 py-4 rounded-lg bg-navy text-white text-[20px] font-bold shadow-lg hover:bg-navy/90 transition-all duration-300 mb-20 ${isLoading ? "cursor-not-allowed opacity-0" : ""}`}
>

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 | 🟡 Minor

로딩 중 링크가 여전히 클릭됩니다
opacity-0만 적용하면 링크는 계속 활성 상태입니다. 로딩 중엔 포커스/클릭을 차단하는 처리가 필요합니다.

🛠️ 변경 제안
-              <Link
+              <Link
                 href={buttonLink || "/"}
-                className={`inline-flex items-center justify-center px-15 py-4 rounded-lg bg-navy text-white text-[20px] font-bold shadow-lg hover:bg-navy/90 transition-all duration-300 mb-20 ${isLoading ? "cursor-not-allowed opacity-0" : ""}`}
+                aria-disabled={isLoading}
+                tabIndex={isLoading ? -1 : 0}
+                className={`inline-flex items-center justify-center px-15 py-4 rounded-lg bg-navy text-white text-[20px] font-bold shadow-lg hover:bg-navy/90 transition-all duration-300 mb-20 ${isLoading ? "cursor-not-allowed opacity-0 pointer-events-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
<motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
<Link
href={buttonLink || "/"}
className={`inline-flex items-center justify-center px-15 py-4 rounded-lg bg-navy text-white text-[20px] font-bold shadow-lg hover:bg-navy/90 transition-all duration-300 mb-20 ${isLoading ? "cursor-not-allowed opacity-0" : ""}`}
>
<motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
<Link
href={buttonLink || "/"}
aria-disabled={isLoading}
tabIndex={isLoading ? -1 : 0}
className={`inline-flex items-center justify-center px-15 py-4 rounded-lg bg-navy text-white text-[20px] font-bold shadow-lg hover:bg-navy/90 transition-all duration-300 mb-20 ${isLoading ? "cursor-not-allowed opacity-0 pointer-events-none" : ""}`}
>
🤖 Prompt for AI Agents
In `@app/components/curation/Curation.tsx` around lines 153 - 157, The Link
remains clickable during loading because only opacity-0 is applied; update the
Link in the motion.div so when isLoading is true it both prevents navigation and
is removed from keyboard focus: add an onClick handler like onClick={(e) =>
isLoading && e.preventDefault()}, add accessibility attributes
aria-disabled={isLoading} and tabIndex={isLoading ? -1 : 0}, and include the
Tailwind class pointer-events-none when isLoading to block mouse interactions;
keep existing className logic but append pointer-events-none and ensure the
onClick, aria-disabled, and tabIndex are applied to the Link component (the
element using buttonLink, isLoading, motion.div, and Link).

Comment on lines +13 to +17
declare global {
interface Window {
daum: any;
}
}

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

any 타입 사용으로 파이프라인 실패

Window.daumany 타입을 사용하여 빌드가 실패합니다. Daum Postcode API에 대한 적절한 타입을 정의하세요.

🔧 제안된 수정
+interface DaumPostcodeData {
+  address: string;
+  addressType: string;
+  bname: string;
+  buildingName: string;
+}
+
+interface DaumPostcode {
+  new (options: { oncomplete: (data: DaumPostcodeData) => void }): { open: () => void };
+}
+
 declare global {
   interface Window {
-    daum: any;
+    daum?: {
+      Postcode: DaumPostcode;
+    };
   }
 }

그리고 Line 36의 data: anydata: DaumPostcodeData로 변경하세요.

🧰 Tools
🪛 ESLint

[error] 15-15: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

🪛 GitHub Actions: Frontend CI

[error] 15-15: Unexpected any. Specify a different type. (no-explicit-any)

🪛 GitHub Check: build-and-lint

[failure] 15-15:
Unexpected any. Specify a different type

🤖 Prompt for AI Agents
In `@app/components/module/FavoritePlacesModule.tsx` around lines 13 - 17, 윈도우 전역
선언에서 Window.daum에 any를 쓰지 말고 Daum Postcode API 구조를 반영한 타입
인터페이스(DaumPostcodeData)를 새로 정의하여 사용하세요: 전역 선언의 Window.daum 타입을 any에서 해당 인터페이스로
변경하고, 컴포넌트 내부에서 postcode 콜백/핸들러의 매개변수 타입(현재 data: any로 선언된 부분)을 data:
DaumPostcodeData로 바꾸어 타입 안전성을 확보하세요; 참조할 심볼: Window.daum, DaumPostcodeData, 그리고
파일 내의 data 매개변수(현재 any로 선언된 위치).

Comment thread app/onboarding/page.tsx
Comment on lines +88 to +98
setIsCurationLoading(true);
// isCompleted를 true로 설정하여 Curation 컴포넌트가 보이게 함
setIsCompleted(true);
};

if (currentStep === 3) {
setTimeout(() => {
setIsLoading(false);
}, 5000);
}
onboardMutate({
data: {
preferredType: preferredType as OnboardRequestDTOPreferredType,
preferredDistricts,
preferredConditions: preferredConditions.map(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.

⚠️ Potential issue | 🟡 Minor

뮤테이션 완료 전 isCompleted를 설정하면 실패 시 UI 깜빡임이 발생합니다.

onboardMutate 호출 전에 isCompletedtrue로 설정하고 있습니다. 뮤테이션이 실패하면 onError에서 isCompletedfalse로 되돌리지만, 사용자는 Curation 화면이 잠깐 표시되었다가 사라지는 것을 경험하게 됩니다.

로딩 상태만 먼저 설정하고, isCompleted는 성공 콜백에서만 설정하는 것이 좋습니다.

🐛 제안된 수정
  const handleAtmosphereSelect = () => {
    console.log("Onboarding Submit Started", {
      preferredType,
      preferredDistricts,
      preferredConditions,
    });

    setIsCurationLoading(true);
-   // isCompleted를 true로 설정하여 Curation 컴포넌트가 보이게 함
-   setIsCompleted(true);

    onboardMutate({
      data: {
        preferredType: preferredType as OnboardRequestDTOPreferredType,
        preferredDistricts,
        preferredConditions: preferredConditions.map(String),
      },
    });
  };

그리고 Lines 159의 조건을 수정:

-         ) : isCurationLoading ? (
+         ) : isCurationLoading || isCompleted ? (
+           isCurationLoading ? (
            <Curation
              content="큐레이션 중"
              description="선택하신 조건에 맞는 최고의 매물들을 찾아드릴게요"
              isLoading={true}
            />
+           ) : (
+            <Curation
+              content="큐레이션을 위한 단계가 모두 끝났어요!"
+              ...
+            />
+           )
          )
🤖 Prompt for AI Agents
In `@app/onboarding/page.tsx` around lines 88 - 98, The UI sets
setIsCompleted(true) before the onboardMutate call causing a flash if the
mutation fails; change the flow to only set setIsCurationLoading(true) before
calling onboardMutate and move the setIsCompleted(true) into the mutation's
success handler (onSuccess) while ensuring onError still resets loading and
completion (setIsCurationLoading(false) and setIsCompleted(false) as needed);
update any conditional logic that checks completion (the condition referenced
around line 159) to rely on the state set by onSuccess instead of being set
pre-flight.

@HSCHEOL
HSCHEOL merged commit e7e7f15 into main Feb 5, 2026
2 of 5 checks passed

@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 `@app/onboarding/page.tsx`:
- Around line 91-97: The call to onboardMutate is casting preferredType to the
wrong enum; replace the cast of preferredType from
OnboardRequestDTOPreferredType to OnboardRequestDTOPropertyType so the
data.field propertyType matches the API DTO type (update the expression where
propertyType is set in the onboardMutate data object to use
OnboardRequestDTOPropertyType instead of OnboardRequestDTOPreferredType).
🧹 Nitpick comments (1)
app/onboarding/page.tsx (1)

125-126: window.location.href 대신 Next.js 라우터 사용을 권장합니다.

window.location.href = "/"는 전체 페이지를 새로고침합니다. Next.js의 클라이언트 사이드 네비게이션을 활용하려면 useRouter 훅의 router.push("/")를 사용하는 것이 좋습니다.

♻️ 제안된 수정
+"use client";
+
+import { useRouter } from "next/navigation";
 import React from "react";
 // ... other imports

 export default function OnboardingPage() {
+  const router = useRouter();
   // ... other code

   return (
     // ...
     <HousingTypeStep
       onNext={handleHousingTypeSelect}
-      onBack={() => (window.location.href = "/")}
+      onBack={() => router.push("/")}
     />
     // ...
   );
 }

Comment thread app/onboarding/page.tsx
Comment on lines +91 to +97
onboardMutate({
data: {
propertyType: preferredType as OnboardRequestDTOPreferredType,
preferredDistricts,
preferredConditions: preferredConditions.map(Number),
},
});

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:

rg -n "OnboardRequestDTO" --type ts -A 10

Repository: D-ingle/frontend

Length of output: 10323


OnboardRequestDTOPropertyType으로 타입을 수정하세요.

필드명 매핑(preferredTypepropertyType)은 의도된 것이 맞습니다. 다만 타입 캐스팅에 오류가 있습니다. 현재 OnboardRequestDTOPreferredType으로 캐스팅하고 있지만, API DTO의 propertyType 필드는 OnboardRequestDTOPropertyType 타입을 기대합니다. 두 enum의 값은 동일하지만 타입이 다르므로 올바른 타입으로 수정이 필요합니다.

수정 코드
propertyType: preferredType as OnboardRequestDTOPropertyType,
🤖 Prompt for AI Agents
In `@app/onboarding/page.tsx` around lines 91 - 97, The call to onboardMutate is
casting preferredType to the wrong enum; replace the cast of preferredType from
OnboardRequestDTOPreferredType to OnboardRequestDTOPropertyType so the
data.field propertyType matches the API DTO type (update the expression where
propertyType is set in the onboardMutate data object to use
OnboardRequestDTOPropertyType instead of OnboardRequestDTOPreferredType).

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

Labels

feature Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant