Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthrough온보딩과 지도 모드 상태를 전역 Zustand 스토어로 도입하고, 일곱 개의 새 UI 모듈을 추가했으며(Accessibility, Convenience, Environment, Noise, Safety, FavoritePlaces, TimeSlider) 온보딩 흐름과 지도 페이지 레이아웃을 리팩터링하고 쿠키 토큰 접근 및 axios 요청 인터셉터를 추가했습니다. Changes
Sequence DiagramsequenceDiagram
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: 다음 화면으로 이동
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 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 공격 발생 시 공격자도 토큰을 탈취할 수 있습니다.대안으로 다음 방법들을 고려해 주세요:
- 서버 사이드 프록시 API 라우트: 인증이 필요한 요청을 서버 사이드에서 처리하여 토큰을 클라이언트에 노출하지 않음
- BFF (Backend for Frontend) 패턴: Next.js API Routes를 통해 토큰을 서버에서만 관리
- 현재 방식 유지 시: 강력한 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.ts의cn함수를 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.ts의cn함수를 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.ts의cn함수를 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.ts의cn함수를 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-pressed와type="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("/")} />
| <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)]" /> |
There was a problem hiding this comment.
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.
| <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.
| <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" : ""}`} | ||
| > |
There was a problem hiding this comment.
로딩 중 링크가 여전히 클릭됩니다
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.
| <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).
| declare global { | ||
| interface Window { | ||
| daum: any; | ||
| } | ||
| } |
There was a problem hiding this comment.
any 타입 사용으로 파이프라인 실패
Window.daum에 any 타입을 사용하여 빌드가 실패합니다. 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: any를 data: 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로 선언된 위치).
| 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), | ||
| }, | ||
| }); |
There was a problem hiding this comment.
뮤테이션 완료 전 isCompleted를 설정하면 실패 시 UI 깜빡임이 발생합니다.
onboardMutate 호출 전에 isCompleted를 true로 설정하고 있습니다. 뮤테이션이 실패하면 onError에서 isCompleted를 false로 되돌리지만, 사용자는 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.
There was a problem hiding this comment.
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("/")} /> // ... ); }
| onboardMutate({ | ||
| data: { | ||
| propertyType: preferredType as OnboardRequestDTOPreferredType, | ||
| preferredDistricts, | ||
| preferredConditions: preferredConditions.map(Number), | ||
| }, | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "OnboardRequestDTO" --type ts -A 10Repository: D-ingle/frontend
Length of output: 10323
OnboardRequestDTOPropertyType으로 타입을 수정하세요.
필드명 매핑(preferredType → propertyType)은 의도된 것이 맞습니다. 다만 타입 캐스팅에 오류가 있습니다. 현재 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).
🚀 주요 구현 내용
전체적인 서비스 경험을 높이기 위해 지도를 기반으로 다양한 주거 데이터를 시각화하는 '종합데이터 모드'의 모든 기능을 구현했습니다.
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
새로운 기능
개선 사항