Skip to content

Conversation

@idid10
Copy link
Contributor

@idid10 idid10 commented Jan 4, 2026

🔥 작업 내용

  • 온보딩 페이지 UI
  • 프로필 이미지 업로드

🤔 추후 작업 사항

📸 작업 내역 스크린샷

image image image

🔗 이슈

  • 링크

Summary by CodeRabbit

릴리즈 노트

  • New Features

    • 온보딩 플로우 추가: 다단계 안내 화면 및 진행 상황 표시 기능
    • 프로필 설정: 이름 입력 및 프로필 이미지 업로드 기능 추가
    • 앨범에서 사진 선택 또는 기본 이미지 적용 옵션 제공
  • UI/UX Improvements

    • 회원가입 완료 후 온보딩 페이지로 이동
    • 로딩 상태 표시 모달 추가
    • 약관 동의 화면 개선
  • Chores

    • 의존성 업데이트

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

@idid10 idid10 linked an issue Jan 4, 2026 that may be closed by this pull request
5 tasks
@coderabbitai
Copy link

coderabbitai bot commented Jan 4, 2026

둘러보기

새로운 온보딩 플로우 기능이 추가되었습니다. 다단계 온보딩 페이지, 프로필 설정 컴포넌트, 이미지 업로드 기능, 진행 상태 표시기를 포함한 여러 클라이언트 컴포넌트가 구현되었으며, 약관 동의 로직이 상수로 추출되고 모달 컴포넌트가 개선되었습니다.

변경사항

응집도 / 파일 요약
온보딩 페이지 컴포넌트
src/app/onboarding/page.tsx, src/app/onboarding/profile/page.tsx
다단계 온보딩 플로우와 프로필 설정 페이지 신규 추가. 진행 상태 추적 및 로컬스토리지 저장소 통합
온보딩 UI 컴포넌트
src/components/onboarding/OnboardingStep.tsx, src/components/onboarding/OnboardingProfile.tsx, src/components/onboarding/ProgressDots.tsx, src/components/onboarding/ProfileImagePicker.tsx, src/components/onboarding/UploadButton.tsx
온보딩 단계 렌더링, 프로필 설정 입력, 진행률 표시, 프로필 이미지 선택, 이미지 업로드 모달 컴포넌트 신규 추가
공통 컴포넌트
src/components/common/NameInput.tsx, src/components/common/LoadingModal.tsx
동적 너비 조절 텍스트 입력 필드 신규 추가, AlertModal을 LoadingModal로 확장 및 백드롭 옵션 추가
온보딩 관련 상수 및 타입
src/constants/onboardingSteps.ts, src/constants/agreement.ts, src/constants/alert.ts, src/types/agreement.type.ts
온보딩 단계 정의, 약관 목록 및 초기 상태, 알림 배경 스타일, 약관 키 타입 신규 정의
커스텀 훅
src/hooks/useProfileImageUpload.ts
프로필 이미지 상태 및 업로드 로직 관리 훅 신규 추가
약관 리팩토링
src/app/signup/agree/page.tsx
로컬 정의된 약관 상수를 중앙화된 상수 모듈로 이동
컴포넌트 마이그레이션
src/components/password/PasswordForm.tsx, src/components/signup/ProfileForm.tsx
AlertModal 사용을 LoadingModal로 변경
페이지 업데이트
src/app/page.tsx, src/app/login/page.tsx, src/app/signup/password/page.tsx
함수명 변경, 텍스트 수정, 회원가입 후 이동 경로 /onboarding으로 변경
스타일 및 유틸리티
src/styles/globals.css, src/utils/upload.ts
로더 애니메이션 스타일 추가, 기존 --color-dim-02 제거, 업로드 이미지 유틸리티 주석 처리
의존성 업데이트
package.json
@toss/use-funnel@use-funnel/next로 의존성 교체 (^1.4.2 → ^0.0.22)

시퀀스 다이어그램

sequenceDiagram
    participant User
    participant OnboardingPage
    participant OnboardingStep
    participant ProgressDots
    participant LocalStorage
    participant Router

    User->>OnboardingPage: 온보딩 접속
    OnboardingPage->>ProgressDots: 진행 상태 렌더링
    ProgressDots-->>OnboardingPage: 현재 진행률 표시
    
    OnboardingPage->>OnboardingStep: 현재 단계 렌더링
    OnboardingStep-->>User: 단계 콘텐츠 및 다음 버튼 표시
    
    User->>OnboardingStep: 다음 버튼 클릭
    OnboardingStep->>OnboardingPage: onNext 콜백 호출
    
    alt isLastStep = true
        OnboardingPage->>LocalStorage: onboardingDone 저장
        OnboardingPage->>Router: /onboarding/profile로 이동
    else isLastStep = false
        OnboardingPage->>OnboardingPage: 다음 단계로 진행
    end
Loading
sequenceDiagram
    participant User
    participant OnboardingProfile
    participant UploadButton
    participant useProfileImageUpload
    participant Router

    User->>OnboardingProfile: 프로필 설정 페이지 접속
    OnboardingProfile->>OnboardingProfile: 초기 상태 설정 (name, profileImage)
    
    User->>OnboardingProfile: 프로필 이미지 클릭
    OnboardingProfile->>UploadButton: 이미지 선택 모달 열기
    UploadButton-->>User: 앨범 선택/기본 이미지 옵션 표시
    
    User->>UploadButton: 앨범에서 사진 선택
    UploadButton->>useProfileImageUpload: uploadImage(file) 호출
    useProfileImageUpload->>useProfileImageUpload: 로딩 상태 활성화
    useProfileImageUpload->>useProfileImageUpload: 2초 대기 후 Object URL 생성
    useProfileImageUpload-->>OnboardingProfile: profileImage 상태 업데이트
    OnboardingProfile->>UploadButton: 모달 닫기
    
    User->>OnboardingProfile: 이름 입력
    OnboardingProfile->>OnboardingProfile: 이름 상태 업데이트
    
    User->>OnboardingProfile: 완료 버튼 클릭 (이름 입력 시에만 활성화)
    OnboardingProfile->>OnboardingProfile: handleCreateProfile 호출
    OnboardingProfile->>Router: /login으로 이동
Loading

추정 코드 리뷰 난이도

🎯 4 (복잡함) | ⏱️ ~45분

관련 가능성 있는 PR

  • feat:start 페이지 UI #3: 동일한 응용프로그램 코드를 수정합니다 (src/app/login/page.tsx, 비밀번호 검증 훅 등). 변경사항이 겹칠 수 있습니다.

시 🐰

온보딩 여정, 단계마다 피어나는 진행률 🌱

이미지 업로드, 이름 입력받고 ✨

프로필 완성, 로그인으로 나아가네 🐇

🚥 Pre-merge checks | ✅ 2 | ❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목 '온보딩 UI'는 변경 사항의 주요 내용과 일치합니다. 온보딩 페이지, 프로필 이미지 피커, 진행 상태 표시 등 UI 컴포넌트들이 추가되었으며, PR 설명과도 부합합니다.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

@idid10 idid10 requested a review from lemoncurdyogurt January 4, 2026 15:16
Copy link

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

Caution

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

⚠️ Outside diff range comments (1)
src/styles/globals.css (1)

99-120: 중복된 유틸리티 블록을 제거해야 합니다.

99-120번 줄의 @layer utilities 블록이 50-97번 줄의 내용과 완전히 중복됩니다. .scrollbar-hide.bg-radial-yellowgreen-mintgreen 유틸리티가 두 번 정의되어 있습니다.

중복된 블록을 제거하고 새로운 유틸리티(.bg-dim-01, .backdrop-blur-7)만 122-130번 줄의 별도 블록에 유지하는 것이 좋습니다.

🔎 수정 제안
-@layer utilities {
-  .scrollbar-hide {
-    -ms-overflow-style: none; /* IE and Edge */
-    scrollbar-width: none; /* Firefox */
-  }
-  .scrollbar-hide::-webkit-scrollbar {
-    display: none; /* Chrome, Safari, Opera*/
-  }
-  .bg-radial-yellowgreen-mintgreen {
-    background:
-      radial-gradient(
-        39.1% 31.12% at 80.25% 2.51%,
-        rgba(246, 248, 173, 0.42) 57.26%,
-        rgba(255, 255, 255, 0.42) 100%
-      ),
-      radial-gradient(
-        39.99% 49.21% at 23.64% 2.72%,
-        rgba(66, 240, 158, 0.66) 21.15%,
-        rgba(255, 255, 255, 0.87) 93.53%
-      );
-  }
-}
-
 @layer utilities {
   .bg-dim-01 {
     background-color: var(--color-dim-01);
   }

   .backdrop-blur-7 {
     backdrop-filter: blur(7px);
   }
 }
🧹 Nitpick comments (11)
src/components/onboarding/ProgressDots.tsx (1)

10-23: className 중복 코드 리팩토링 고려

조건부 className에서 h-2.5 w-2.5 rounded-full이 양쪽 분기에 중복됩니다. 가독성과 유지보수성 향상을 위해 공통 클래스를 분리하는 것을 고려해보세요.

🔎 제안하는 리팩토링
         {Array.from({ length: total }).map((_, i) => {
           const isActive = i <= current;
 
           return (
             <span
               key={i}
-              className={
-                isActive
-                  ? "bg-mint-01 h-2.5 w-2.5 rounded-full"
-                  : "bg-neutral-08 h-2.5 w-2.5 rounded-full"
-              }
+              className={`h-2.5 w-2.5 rounded-full ${
+                isActive ? "bg-mint-01" : "bg-neutral-08"
+              }`}
             />
           );
         })}
src/components/common/NameInput.tsx (1)

17-22: 매직 넘버 문서화 권장

라인 21의 36은 입력 필드의 패딩값으로 추정되지만 명확하지 않습니다. 코드 가독성을 위해 상수로 추출하거나 주석을 추가하는 것을 고려해보세요.

🔎 제안하는 개선
+const PADDING_WIDTH = 36; // px-4 (16px * 2) + border/extra spacing
+
 const NameInput = ({ value, onChange }: NameInputProps) => {
   const spanRef = useRef<HTMLSpanElement>(null);
   const [inputWidth, setInputWidth] = useState<number>(INITIAL_WIDTH);
 
   useEffect(() => {
     if (!spanRef.current) return;
 
     const spanWidth = spanRef.current.offsetWidth;
-    setInputWidth(spanWidth + 36);
+    setInputWidth(spanWidth + PADDING_WIDTH);
   }, [value]);
src/components/onboarding/OnboardingStep.tsx (2)

18-18: 모바일 뷰포트 높이 처리 개선 고려

h-screen 대신 h-dvh 사용을 고려해보세요. 모바일 브라우저의 주소 표시줄로 인한 레이아웃 이슈를 방지할 수 있습니다. 상위 온보딩 페이지(src/app/onboarding/page.tsx)에서 이미 min-h-dvh를 사용하고 있어 일관성 측면에서도 유리합니다.

🔎 제안하는 수정
-    <main className="relative flex h-screen flex-col bg-white pt-[558px]">
+    <main className="relative flex h-dvh flex-col bg-white pt-[558px]">

19-33: 배경 오프셋 매직 넘버 상수화 고려

-translate-y-[110px]가 라인 25와 30에서 중복됩니다. 상수로 추출하거나 공통 클래스로 정의하면 유지보수가 용이해집니다.

🔎 제안하는 개선
 const OnboardingStep = ({ stepName, isLast, onNext }: Props) => {
   const { title, background } = onboardingContents[stepName];
+  const backgroundOffsetClass = "-translate-y-[110px]";
 
   return (
     <main className="relative flex h-screen flex-col bg-white pt-[558px]">
       {background.type === "image" && (
         <Image
           src={background.src}
           alt=""
           fill
           priority
-          className="pointer-events-none -translate-y-[110px] object-cover object-top"
+          className={`pointer-events-none ${backgroundOffsetClass} object-cover object-top`}
         />
       )}
       {background.type === "class" && (
         <div
-          className={`pointer-events-none absolute -translate-y-[110px] ${background.className}`}
+          className={`pointer-events-none absolute ${backgroundOffsetClass} ${background.className}`}
           aria-hidden
         />
       )}
src/config/onboardingSteps.ts (1)

1-6: 타입 정의가 잘 구성되어 있습니다.

as const 단언을 사용한 튜플 정의와 OnboardingStepName 타입 추출 패턴이 적절합니다. 다만, OnboardingBackgroundtype: "class" 변형은 현재 사용되지 않고 있습니다. 향후 확장을 위한 것이라면 괜찮지만, 사용 계획이 없다면 YAGNI 원칙에 따라 제거를 고려해볼 수 있습니다.

src/components/onboarding/UploadAlert.tsx (3)

28-34: onClose() 호출이 중복될 수 있습니다.

handleFileChange에서 onSelectAlbum(file) 호출 후 onClose()를 호출하고 있지만, OnboardingProfile.tsxonSelectAlbum 콜백에서도 이미 setIsUploadOpen(false)를 호출합니다. 중복 호출은 현재 문제가 되지 않지만, 한 곳에서만 닫기 로직을 관리하는 것이 좋습니다.

또한, 동일한 파일을 다시 선택할 경우 onChange 이벤트가 발생하지 않습니다. input의 value를 초기화해야 합니다.

🔎 수정 제안
  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
-   if (!file) return;
+   if (!file) return;
+   
+   e.target.value = ""; // 동일 파일 재선택 허용

    onSelectAlbum(file);
-   onClose();
+   // onClose는 부모에서 처리
  };

56-62: 파일 크기 검증이 없습니다.

accept="image/*"로 이미지 파일만 허용하고 있지만, 파일 크기에 대한 제한이 없습니다. 대용량 이미지 업로드 시 성능 문제나 서버 오류가 발생할 수 있으므로, 클라이언트 측에서 파일 크기를 검증하는 것을 권장합니다.


36-43: 접근성 개선을 고려해보세요.

모달에 role="dialog", aria-modal="true", aria-labelledby 속성이 없습니다. 또한 모달이 열릴 때 포커스 트래핑이 구현되어 있지 않아 키보드 사용자의 접근성이 제한될 수 있습니다.

src/components/common/AlertModal.tsx (3)

38-43: 배경 클릭으로 모달이 닫히지 않습니다.

UploadAlert은 배경 클릭 시 onClose를 호출하지만, AlertModal은 배경 클릭 핸들러가 없습니다. 컴포넌트 간 일관성을 위해 배경 클릭 동작을 통일하거나, closeOnBackdrop prop을 추가하여 제어하는 것을 고려해보세요.


53-65: 로딩 상태와 메시지 렌더링 로직이 명확합니다.

isLoading && loadingImageSrc 조건으로 로딩 이미지를 표시하고, 그렇지 않으면 message를 표시하는 로직이 잘 구성되어 있습니다. 다만 isLoadingtrue이지만 loadingImageSrc가 없는 경우 아무것도 표시되지 않으므로, 이 케이스에 대한 폴백(예: 기본 스피너)을 고려해볼 수 있습니다.


54-58: <img> 태그 대신 Next.js의 Image 컴포넌트 사용을 고려해보세요.

Next.js 프로젝트에서는 이미지 최적화를 위해 next/imageImage 컴포넌트를 사용하는 것이 권장됩니다. 단, GIF 파일의 경우 unoptimized prop이 필요할 수 있습니다.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 33f859c and 74cb410.

⛔ Files ignored due to path filters (6)
  • public/images/onboarding-step-1.png is excluded by !**/*.png
  • public/images/onboarding-step-2.png is excluded by !**/*.png
  • public/images/onboarding-step-3.png is excluded by !**/*.png
  • public/loading.gif is excluded by !**/*.gif
  • src/assets/onboarding_profile.svg is excluded by !**/*.svg
  • src/assets/photo.svg is excluded by !**/*.svg
📒 Files selected for processing (16)
  • src/app/login/page.tsx
  • src/app/onboarding/page.tsx
  • src/app/onboarding/profile/page.tsx
  • src/app/page.tsx
  • src/components/common/AlertModal.tsx
  • src/components/common/NameInput.tsx
  • src/components/onboarding/OnboardingProfile.tsx
  • src/components/onboarding/OnboardingStep.tsx
  • src/components/onboarding/ProfileImagePicker.tsx
  • src/components/onboarding/ProgressDots.tsx
  • src/components/onboarding/UploadAlert.tsx
  • src/components/password/PasswordForm.tsx
  • src/config/onboardingSteps.ts
  • src/hooks/useFunnel.tsx
  • src/hooks/useProfileImageUpload.ts
  • src/styles/globals.css
💤 Files with no reviewable changes (1)
  • src/app/login/page.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
src/components/onboarding/OnboardingProfile.tsx (1)
src/hooks/useProfileImageUpload.ts (1)
  • useProfileImageUpload (5-30)
src/components/onboarding/OnboardingStep.tsx (1)
src/config/onboardingSteps.ts (2)
  • OnboardingStepName (2-2)
  • onboardingContents (8-36)
src/app/onboarding/page.tsx (3)
src/hooks/useFunnel.tsx (1)
  • useFunnel (12-36)
src/config/onboardingSteps.ts (2)
  • ONBOARDING_STEPS (1-1)
  • OnboardingStepName (2-2)
src/components/onboarding/ProgressDots.tsx (1)
  • ProgressDots (6-27)
🔇 Additional comments (9)
src/hooks/useProfileImageUpload.ts (1)

1-1: 클라이언트 컴포넌트 지시어가 적절하게 사용되었습니다.

이 훅은 React 상태를 사용하므로 "use client" 지시어가 올바르게 적용되었습니다.

src/styles/globals.css (2)

46-47: 새로운 dim 색상 변수가 적절하게 추가되었습니다.

--color-dim-00--color-dim-01 변수는 온보딩 UI의 반투명 배경 스타일링에 사용될 것으로 보이며, 기존 색상 팔레트와 일관성을 유지하고 있습니다.


122-130: 새로운 유틸리티 클래스가 잘 추가되었습니다.

.bg-dim-01.backdrop-blur-7 유틸리티는 모달과 온보딩 UI의 배경 효과를 위해 적절하게 정의되었습니다. AlertModal의 backdrop 기능과 잘 연동될 것으로 보입니다.

src/components/password/PasswordForm.tsx (1)

225-225: AlertModal에 backdrop prop이 적절하게 추가되었습니다.

새로운 backdrop 기능을 지원하기 위해 backdrop="default" prop이 추가되었습니다. 이는 AlertModal 컴포넌트의 업데이트된 API와 일치하며, 기존 모달 동작에 영향을 주지 않습니다.

src/app/onboarding/profile/page.tsx (1)

1-6: 온보딩 프로필 페이지가 올바르게 구현되었습니다.

Next.js 페이지 라우팅 규칙에 맞게 간결하게 구현되었으며, OnboardingProfileClient 컴포넌트를 적절하게 래핑하고 있습니다.

src/app/onboarding/page.tsx (1)

24-33: localStorage 키는 일관되게 사용 중

라인 28에서 사용하는 "onboardingDone" 키는 코드베이스 전체에서 유일하게 한 번만 사용됩니다. 다른 변형 형태(예: "onboarding-done", "onboarding_done")는 발견되지 않았으므로 키 일관성 문제는 없습니다.

src/config/onboardingSteps.ts (1)

8-36: LGTM!

Record<OnboardingStepName, ...> 타입을 사용하여 모든 스텝에 대한 콘텐츠가 정의되어야 함을 컴파일 타임에 보장하고 있습니다. 이미지 경로가 하드코딩되어 있으므로 해당 이미지 파일들이 /public/images/ 디렉토리에 존재하는지 확인해주세요.

src/components/onboarding/OnboardingProfile.tsx (1)

49-52: 이미지 선택 후 즉시 모달을 닫는 것이 UX에 적합한지 확인이 필요합니다.

onSelectAlbum에서 uploadImage(file)를 호출한 직후 setIsUploadOpen(false)로 모달을 닫고 있습니다. uploadImage는 비동기 함수이므로 업로드가 완료되기 전에 모달이 닫히며, 이후 별도의 로딩 모달이 표시됩니다. 현재 흐름이 의도된 것인지 확인해주세요.

src/hooks/useFunnel.tsx (1)

3-6: StepPropsname 속성이 Step 컴포넌트에서 사용되지 않습니다.

StepProps 인터페이스에 name이 정의되어 있지만, Step 컴포넌트는 children만 사용합니다. 이는 의도된 설계로 보이며(Funnel에서 props.name으로 필터링), 타입 정의가 정확합니다.

@lemoncurdyogurt lemoncurdyogurt added the feat 기능 추가 label Jan 6, 2026
Copy link
Member

@lemoncurdyogurt lemoncurdyogurt left a comment

Choose a reason for hiding this comment

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

사소한 디자인 오류라던가, 퍼널로직 수정에 대해서 리뷰적어두었습니다!
dim관련해서 이전에 말했던 것이라서 이번에는 수정 꼭해주세요...!

Copy link

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

Caution

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

⚠️ Outside diff range comments (1)
src/styles/globals.css (1)

45-107: 중복된 @layer utilities 블록 제거 필요

45-84번 줄과 86-107번 줄에 동일한 유틸리티 클래스가 중복 정의되어 있습니다. 이는 유지보수성을 저해하고 예상치 못한 스타일 충돌을 야기할 수 있습니다.

🔎 중복 제거 제안
-@layer utilities {
-  .scrollbar-hide {
-    -ms-overflow-style: none; /* IE and Edge */
-    scrollbar-width: none; /* Firefox */
-  }
-  .scrollbar-hide::-webkit-scrollbar {
-    display: none; /* Chrome, Safari, Opera*/
-  }
-  .bg-radial-yellowgreen-mintgreen {
-    background:
-      radial-gradient(
-        39.1% 31.12% at 80.25% 2.51%,
-        rgba(246, 248, 173, 0.42) 57.26%,
-        rgba(255, 255, 255, 0.42) 100%
-      ),
-      radial-gradient(
-        39.99% 49.21% at 23.64% 2.72%,
-        rgba(66, 240, 158, 0.66) 21.15%,
-        rgba(255, 255, 255, 0.87) 93.53%
-      );
-  }
-}
🤖 Fix all issues with AI Agents
In @package.json:
- Line 20: The dependency "@use-funnel/next" is a pre-1.0 replacement for the
deprecated "@toss/use-funnel"; before merging, verify migration compatibility by
running full integration and E2E tests against the app, add a CI job that
exercises funnel-related flows, pin the dependency (remove caret) or add a
shrinkwrap/lockfile policy to avoid accidental minor bumps, and document the
change and monitoring plan (including rollback steps) in the PR/README to ensure
safe production rollout.

In @src/app/page.tsx:
- Around line 1-4: RootPage currently does an unconditional redirect to "/login"
which blocks authenticated users and can cause redirect loops; update the server
component RootPage to check authentication (e.g., call getSession() or your auth
check) and perform conditional redirects: if no session redirect("/login"), if
session but onboarding incomplete redirect("/onboarding"), otherwise
redirect("/home"); reference and replace the current redirect call in RootPage
and use getSession (or your session-fetching helper) to determine the correct
branch.

In @src/components/common/LoadingModal.tsx:
- Line 13: The LoadingModal component declares and destructures a
loadingImageSrc prop but never uses it; remove this unused prop by deleting
loadingImageSrc from the props interface (e.g., LoadingModalProps) and from the
component parameter destructuring in the LoadingModal function, and remove any
related default or import usage; alternatively, if you intend to support a
custom image, implement usage by rendering an <img> or background using
loadingImageSrc inside LoadingModal instead—choose one approach and ensure no
references to loadingImageSrc remain elsewhere.

In @src/components/onboarding/UploadButton.tsx:
- Around line 28-34: handleFileChange currently reads the selected file and
calls onSelectAlbum/onClose but doesn't reset the file input, so selecting the
same file again won't trigger onChange; after processing the file (after
onSelectAlbum and before/after onClose) clear the input's value — locate the
input element referenced by handleFileChange (e.g., the HTMLInputElement from
the event) and set its value = '' (or use a ref to the input and reset
ref.current.value = '') so the input is reinitialized for subsequent same-file
selections.

In @src/hooks/useOnboardingFunnel.ts:
- Line 3: The import of useFunnel from "@use-funnel/next" in
useOnboardingFunnel.ts is incompatible with React 19 required by Next.js 15;
either (A) switch the import to "@use-funnel/browser" in useOnboardingFunnel.ts
when using the App Router to avoid the Next-specific package, (B) upgrade or
replace the dependency with a React 19‑compatible release of @use-funnel (check
npm/changelog and update package.json and lockfile), or (C) add targeted
compatibility tests for useFunnel (invoke the useFunnel hook inside the
onboarding hook and run the app/router integration tests) to validate it works
under React 19—choose the appropriate option and update the import/reference to
useFunnel (or the dependency version) accordingly.

In @src/hooks/usePasswordValidation.ts:
- Around line 7-9: The isValidPassword function was reduced to only checking
length which removes required checks for letters, digits, and special
characters; restore the stronger validation used in the commented code by
updating isValidPassword to enforce min length 8 plus at least one
lowercase/uppercase letter, one digit and one special character (use the same
regex/logic from the commented block), ensure the signup flow still calls this
function, and if a weaker check is needed for tests only gate it behind an
explicit TEST env flag or test helper so production logic remains strict.

In @src/hooks/useProfileImageUpload.ts:
- Around line 22-30: Remove the commented-out uploadImage function block in
useProfileImageUpload (the commented async uploadImage / setIsLoading /
uploadImageFile / setProfileImage / finally lines) to clean up unused commented
code; if you want to preserve the snippet for future reference, rely on Git
history or move it to a docs/snippets file, but do not keep it commented in the
hook source.
- Line 5: Remove the unused commented import in useProfileImageUpload: delete
the commented line referencing uploadImageFile so the file no longer contains a
stale "//import { uploadImageFile } from "@/utils/upload";" comment; if/when the
uploadImageFile utility is needed later, re-add it as an active import.
- Line 42: The function useProfileImageUpload contains a duplicated, unreachable
return (the second "return { profileImage, isLoading, uploadImage, resetImage }
as const;" after the earlier return); remove the redundant trailing return so
only the intended single return in useProfileImageUpload remains, ensuring no
other logic depends on the removed line.

In @src/pages/_app.tsx:
- Line 2: The import name is misspelled: change the Next.js font import from
localfont to localFont in the import declaration (the symbol currently imported
as localfont in src/pages/_app.tsx) and update any usages of the variable
(references to localfont throughout the file, e.g., where the font is used or
its className is referenced) to use localFont so the correct Next.js API name is
used consistently.
- Around line 6-11: The pretendard font is being redefined here via the
localfont(...) call (const pretendard) even though the same font is already
exported from src/styles/font.ts; remove this duplicate localfont definition and
instead import the existing exported symbol (e.g., pretendard) from your styles
module and use that imported constant in the _app component so only the single
exported font definition from styles/font.ts is used across the app.

In @src/styles/font.ts:
- Line 1: The import identifier is misspelled as localfont; update the import to
use the correct camelCase name localFont (import localFont from
"next/font/local") and rename any usages of localfont in this file (e.g., calls
like localfont({...}) or references to the variable) to localFont so TypeScript
compiles without error.

In @src/styles/globals.css:
- Around line 109-127: The .loader rule uses hardcoded colors; replace the
literal hex values (border: 5px solid #ffffff and border-bottom-color: #42f09e)
with the project theme CSS variables defined under @theme (e.g., use
var(--<appropriate-theme-bg>) for the border and
var(--<appropriate-theme-accent>) for the highlight) and move the .loader and
@keyframes rotation definitions into the @layer utilities block to keep
consistency with other utility classes; ensure the variable names match the
existing @theme names and update any references accordingly.
🧹 Nitpick comments (8)
src/hooks/usePasswordValidation.ts (1)

2-6: 주석 처리된 코드를 제거하는 것을 고려하세요.

사용하지 않는 주석 처리된 코드는 코드베이스를 복잡하게 만들고 향후 혼란을 초래할 수 있습니다. 이전 로직이 필요한 경우 Git 히스토리에서 확인할 수 있습니다.

🔎 제안된 수정 사항
-  // const isValidPassword = (pwd: string) => {
-  //   const hasLetter = /[A-Za-z]/.test(pwd);
-  //   const hasNumber = /\d/.test(pwd);
-  //   const hasSpecial = /[^A-Za-z0-9]/.test(pwd);
-  //   return pwd.length >= 8 && hasLetter && hasNumber && hasSpecial;
   const isValidPassword = (pwd: string) => {
     return pwd.length >= 8;
   };
src/components/onboarding/UploadButton.tsx (1)

39-39: Tailwind 클래스 사용을 권장합니다.

인라인 스타일 대신 Tailwind 클래스를 사용하여 일관성을 유지하는 것을 권장합니다.

🔎 수정 제안
     <div
       className="fixed inset-0 z-50 flex items-end justify-center"
-      style={{ backgroundColor: "rgba(36, 38, 40, 0.3)" }}
+      className="fixed inset-0 z-50 flex items-end justify-center bg-black/30"
       onClick={onClose}
src/hooks/useOnboardingFunnel.ts (1)

7-7: 타입 안전성 개선을 고려하세요.

OnboardingFunnelContextRecord<OnboardingStepName, object>로 정의되어 각 스텝의 컨텍스트가 제네릭한 object 타입입니다. 현재는 빈 객체 {}만 사용하므로 문제없지만, 향후 각 스텝별로 특정 데이터를 저장해야 할 경우 타입 안전성이 보장되지 않습니다.

필요 시 각 스텝별 구체적인 타입을 정의하는 것을 고려하세요.

src/pages/onboarding/index.tsx (1)

28-28: localStorage 키를 상수로 추출하는 것을 고려하세요.

"onboardingDone" 문자열이 하드코딩되어 있습니다. 다른 곳에서도 이 키를 사용할 가능성이 있다면 상수로 추출하여 재사용성과 유지보수성을 높이는 것이 좋습니다.

🔎 제안하는 리팩토링

상수 파일 또는 해당 파일 상단에 추가:

const ONBOARDING_DONE_KEY = "onboardingDone";

사용:

-        localStorage.setItem("onboardingDone", "true");
+        localStorage.setItem(ONBOARDING_DONE_KEY, "true");
src/components/onboarding/OnboardingStep.tsx (2)

21-21: 매직 넘버를 상수로 추출하는 것을 고려하세요.

w-[440px], pt-[558px], mt-[13px] 등 여러 하드코딩된 수치가 있습니다. 이들을 상수로 정의하면 유지보수성과 가독성이 향상됩니다. 또한 디자인 시스템 토큰으로 관리할 수 있다면 더욱 좋습니다.

🔎 제안하는 리팩토링
const ONBOARDING_CONTAINER_WIDTH = 440;
const ONBOARDING_CONTENT_PADDING_TOP = 558;
const ONBOARDING_MARGIN_TOP = 13;

또는 Tailwind config에 커스텀 스페이싱으로 추가하여 재사용 가능하게 만들 수 있습니다.


34-34: className 조합 시 유틸리티 함수 사용을 고려하세요.

템플릿 리터럴로 className을 직접 연결하는 것보다 clsx 또는 프로젝트에서 사용 중인 cn 유틸리티를 활용하면 조건부 클래스 처리와 가독성이 개선됩니다.

🔎 제안하는 리팩토링
-            className={`pointer-events-none absolute -translate-y-[110px] ${background.className}`}
+            className={cn("pointer-events-none absolute -translate-y-[110px]", background.className)}
src/constants/onboardingSteps.ts (2)

5-9: 타입 중복을 확인하세요.

OnboardingFunnelSteps 타입이 src/hooks/useOnboardingFunnel.tsOnboardingFunnelContext와 유사한 구조를 가지고 있습니다. 두 타입이 동일한 목적을 가진다면 하나로 통합하여 중복을 제거하는 것을 고려하세요.


11-13: OnboardingBackground의 "class" 타입이 현재 사용되지 않습니다.

OnboardingBackground에서 type: "class" 옵션을 정의했지만, 현재 모든 스텝이 type: "image"만 사용하고 있습니다. 향후 확장을 위한 것이라면 문제없지만, 사용하지 않는다면 제거를 고려하세요.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 74cb410 and a4d14e0.

⛔ Files ignored due to path filters (4)
  • public/images/onboarding-step-1.svg is excluded by !**/*.svg
  • public/images/onboarding-step-2.svg is excluded by !**/*.svg
  • public/images/onboarding-step-3.svg is excluded by !**/*.svg
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (25)
  • package.json
  • src/app/page.tsx
  • src/app/signup/agree/page.tsx
  • src/app/signup/password/page.tsx
  • src/components/common/LoadingModal.tsx
  • src/components/onboarding/OnboardingProfile.tsx
  • src/components/onboarding/OnboardingStep.tsx
  • src/components/onboarding/ProfileImagePicker.tsx
  • src/components/onboarding/ProgressDots.tsx
  • src/components/onboarding/UploadButton.tsx
  • src/components/password/PasswordForm.tsx
  • src/components/signup/ProfileForm.tsx
  • src/constants/agreement.ts
  • src/constants/alert.ts
  • src/constants/onboardingSteps.ts
  • src/hooks/useOnboardingFunnel.ts
  • src/hooks/usePasswordValidation.ts
  • src/hooks/useProfileImageUpload.ts
  • src/pages/_app.tsx
  • src/pages/onboarding/index.tsx
  • src/pages/onboarding/profile.tsx
  • src/styles/font.ts
  • src/styles/globals.css
  • src/types/agreement.type.ts
  • src/utils/upload.ts
✅ Files skipped from review due to trivial changes (1)
  • src/utils/upload.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/components/onboarding/ProfileImagePicker.tsx
  • src/components/onboarding/ProgressDots.tsx
  • src/components/onboarding/OnboardingProfile.tsx
  • src/components/password/PasswordForm.tsx
🧰 Additional context used
🧬 Code graph analysis (5)
src/constants/agreement.ts (1)
src/types/agreement.type.ts (1)
  • AgreementKey (1-7)
src/pages/_app.tsx (1)
src/styles/font.ts (1)
  • pretendard (3-8)
src/hooks/useOnboardingFunnel.ts (1)
src/constants/onboardingSteps.ts (1)
  • OnboardingStepName (3-3)
src/app/signup/agree/page.tsx (2)
src/types/agreement.type.ts (1)
  • AgreementKey (1-7)
src/constants/agreement.ts (1)
  • INITIAL_AGREEMENTS (12-19)
src/components/onboarding/OnboardingStep.tsx (1)
src/constants/onboardingSteps.ts (2)
  • OnboardingStepName (3-3)
  • ONBOARDING_CONTENTS (15-43)
🪛 Biome (2.1.2)
src/hooks/useProfileImageUpload.ts

[error] 42-42: This code will never be reached ...

... because this statement will return from the function beforehand

(lint/correctness/noUnreachable)

🔇 Additional comments (12)
src/constants/agreement.ts (1)

1-19: LGTM! 타입 안전한 agreement 상수 정의

타입 정의와 상수가 잘 분리되어 있고, 타입 안전성이 보장됩니다.

src/components/signup/ProfileForm.tsx (1)

12-12: LoadingModal 컴포넌트 인터페이스 확인 완료

LoadingModal 컴포넌트는 isOpen, title, message, onClose props를 모두 지원합니다. ProfileForm.tsx에서도 이 모든 props를 올바르게 전달하고 있으므로 호환성 문제가 없습니다.

src/types/agreement.type.ts (1)

1-7: LGTM!

타입 정의가 깔끔하고 각 약관의 필수/선택 여부가 주석으로 잘 문서화되어 있습니다.

src/constants/alert.ts (1)

1-7: LGTM!

백드롭 타입과 클래스 매핑이 잘 구조화되어 있습니다. Record 타입을 사용하여 타입 안전성을 보장하고 있습니다.

src/app/signup/agree/page.tsx (1)

12-19: LGTM!

상수와 타입을 중앙 집중화하여 코드 중복을 제거한 좋은 리팩토링입니다. INITIAL_AGREEMENTS를 초기 상태로 사용하는 것은 useState가 내부적으로 값을 복사하므로 안전합니다.

src/components/common/LoadingModal.tsx (1)

44-52: LGTM!

로딩 상태와 메시지 렌더링 조건 분기가 적절합니다. loader CSS 클래스가 globals.css(109-114행)에 정의되어 있습니다.

src/app/signup/password/page.tsx (1)

137-137: 변경사항이 적절합니다.

회원가입 완료 후 온보딩 페이지로 이동하도록 내비게이션 경로가 올바르게 업데이트되었습니다.

src/pages/onboarding/profile.tsx (1)

1-6: 구현이 적절합니다.

Next.js 페이지 패턴을 올바르게 따르고 있으며, 온보딩 프로필 컴포넌트를 깔끔하게 래핑하고 있습니다.

src/hooks/useProfileImageUpload.ts (1)

11-20: 이전 리뷰에서 지적된 여러 문제를 해결해주세요.

uploadImage 함수에는 다음과 같은 문제가 있습니다:

  1. 메모리 누수: URL.createObjectURL()로 생성된 Object URL이 해제되지 않습니다
  2. 파일 검증 부재: 파일 타입과 크기 제한이 없습니다
  3. 에러 처리 누락: 업로드 실패 시 처리가 없습니다
  4. 시뮬레이션 딜레이: 2초 지연은 실제 API 연동 전 임시 코드로 보입니다

이전 리뷰 코멘트에서 제공된 개선 방안을 참고하여 수정해주세요.

Likely an incorrect or invalid review comment.

src/pages/onboarding/index.tsx (1)

37-67: 구현이 잘 되어 있습니다.

페이지 구조와 퍼널 패턴 사용이 적절하며, 각 스텝 컴포넌트에 필요한 props를 올바르게 전달하고 있습니다.

src/components/onboarding/OnboardingStep.tsx (1)

39-47: 콘텐츠 레이아웃과 버튼 로직이 잘 구현되어 있습니다.

z-10을 사용하여 배경 위에 콘텐츠를 올바르게 배치했고, whitespace-pre-line으로 타이틀의 줄바꿈을 지원하며, isLastStep에 따라 버튼 텍스트를 적절하게 변경하고 있습니다.

src/constants/onboardingSteps.ts (1)

24-40: 모든 온보딩 이미지 파일이 정상적으로 존재합니다.

/images/onboarding-step-{1,2,3}.svg 경로는 모두 public/images/ 디렉토리에 존재하며, Next.js의 정적 자산 서빙 방식에 맞게 올바르게 구성되어 있습니다. 런타임에 404 오류가 발생하지 않습니다.

Likely an incorrect or invalid review comment.

Copy link

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

🤖 Fix all issues with AI Agents
In @src/app/page.tsx:
- Around line 1-2: HomePage is unprotected: add an authentication guard that
ties into the existing onboarding flow under src/pages/onboarding and
src/components/onboarding by checking the user's auth/onboarding state and
redirecting to the onboarding route or /login if needed; implement this either
in a middleware (middleware.ts) that protects the root route, in the root/layout
(src/app/layout.tsx) by awaiting/validating session and redirecting, or directly
inside the HomePage component (export default function HomePage) by calling the
onboarding/auth hook and issuing a redirect when unauthenticated or
onboarding-incomplete; ensure the redirect target matches the onboarding flow
and reuse the existing onboarding hooks/components for state checks so the home
page is only accessible after successful auth/onboarding.

In @src/components/onboarding/OnboardingProfile.tsx:
- Around line 70-77: The LoadingModal usage currently passes a no-op onClose={()
=> {}} causing the "취소하기" button to do nothing; either remove the confirmLabel
prop or hide/disable the cancel button when cancellation is not allowed, or
implement actual cancel/upload-abort logic and wire it into the onClose handler
(e.g., call your upload abort function or setLoading(false)); update the
LoadingModal invocation (symbols: LoadingModal, props confirmLabel and onClose)
accordingly and consider removing confirmLabel when showing an uninterruptible
loading state for better UX.
- Around line 26-29: handleCreateProfile currently navigates away without
persisting the entered profile, so capture and save the data before routing: in
handleCreateProfile (and where isNameValid is checked) gather the name and image
values and call the profile persistence method (e.g., an API function like
saveProfile or a Redux/Context action such as dispatch(setProfile(...))) and
await its result; only call router.replace("/login") after a successful save,
show/handle errors on failure, and disable the submit action while the save is
in progress to avoid double submissions.
- Around line 57-68: The onSelectAlbum handler calls uploadImage without
handling errors so the modal closes even on failure; update the onSelectAlbum to
await uploadImage (or handle its Promise), catch any upload errors, keep the
modal open on failure (do not call setIsUploadOpen(false) on error), and surface
the error to the user (e.g., show a toast or set an error state) so failures are
visible; also ensure onSelectDefault still closes via resetImage and
setIsUploadOpen(false).
- Around line 23-24: Remove the unreachable duplicate return in the
useProfileImageUpload hook: inside src/hooks/useProfileImageUpload.ts delete the
second return that repeats the object (the one returning "return { profileImage,
isLoading, uploadImage, resetImage } as const;") so only the first valid "return
{ profileImage, isLoading, uploadImage, resetImage };" remains; verify the hook
still exports the same symbols (profileImage, isLoading, uploadImage,
resetImage).
🧹 Nitpick comments (1)
src/components/onboarding/OnboardingProfile.tsx (1)

23-24: 메모리 누수 방지를 위해 Object URL 정리를 고려하세요.

useProfileImageUpload 훅이 URL.createObjectURL을 사용하여 이미지 URL을 생성하는데, 이러한 Object URL은 사용 후 URL.revokeObjectURL로 정리해야 메모리 누수를 방지할 수 있습니다. 컴포넌트가 언마운트되거나 새 이미지가 업로드될 때 이전 Object URL을 정리하는 것을 권장합니다.

useProfileImageUpload 훅에 cleanup 로직을 추가하는 것을 고려해보세요:

useEffect(() => {
  return () => {
    if (profileImage && profileImage.startsWith('blob:')) {
      URL.revokeObjectURL(profileImage);
    }
  };
}, [profileImage]);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4d14e0 and 1483d0c.

📒 Files selected for processing (2)
  • src/app/page.tsx
  • src/components/onboarding/OnboardingProfile.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/onboarding/OnboardingProfile.tsx (1)
src/hooks/useProfileImageUpload.ts (1)
  • useProfileImageUpload (7-43)

Copy link

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

🧹 Nitpick comments (2)
src/components/common/LoadingModal.tsx (1)

13-13: 주석 처리된 코드를 제거하거나 TODO로 명시하세요.

loadingImageSrc prop이 주석 처리되어 있습니다. 향후 사용 계획이 있다면 TODO 주석으로 명시하고, 그렇지 않다면 완전히 제거하는 것이 코드 가독성과 유지보수성에 더 좋습니다.

♻️ 제안: 주석 처리된 코드 제거
 interface LoadingModalProps {
   isOpen: boolean;
   title: string;
   message?: string;
   confirmLabel?: string;
   onClose: () => void;
   backdrop?: AlertBackdrop;
   isLoading?: boolean;
-  //loadingImageSrc?: string;
 }

 const LoadingModal = ({
   isOpen,
   title,
   message,
   confirmLabel = "확인",
   onClose,
   backdrop = "default",
   isLoading = false,
-  //loadingImageSrc,
 }: LoadingModalProps) => {

Also applies to: 24-24

src/app/onboarding/page.tsx (1)

19-21: 불필요한 타입 단언을 제거하세요.

ONBOARDING_STEPSas const로 선언되어 있고 currentStepIndex가 배열 범위 내에서 관리되므로, 타입 단언 없이도 TypeScript가 올바른 타입을 추론할 수 있습니다.

♻️ 제안: 타입 단언 제거
-  const currentStepName = ONBOARDING_STEPS[
-    currentStepIndex
-  ] as OnboardingStepName;
+  const currentStepName = ONBOARDING_STEPS[currentStepIndex];
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1483d0c and 6317409.

📒 Files selected for processing (10)
  • src/app/onboarding/page.tsx
  • src/app/onboarding/profile/page.tsx
  • src/app/page.tsx
  • src/components/common/LoadingModal.tsx
  • src/components/onboarding/OnboardingProfile.tsx
  • src/components/onboarding/OnboardingStep.tsx
  • src/components/onboarding/ProgressDots.tsx
  • src/components/onboarding/UploadButton.tsx
  • src/hooks/useProfileImageUpload.ts
  • src/styles/globals.css
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/app/page.tsx
  • src/styles/globals.css
  • src/components/onboarding/ProgressDots.tsx
  • src/components/onboarding/OnboardingStep.tsx
  • src/hooks/useProfileImageUpload.ts
  • src/components/onboarding/UploadButton.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
src/app/onboarding/page.tsx (2)
src/constants/onboardingSteps.ts (2)
  • ONBOARDING_STEPS (1-1)
  • OnboardingStepName (3-3)
src/components/onboarding/ProgressDots.tsx (1)
  • ProgressDots (6-23)
src/components/onboarding/OnboardingProfile.tsx (1)
src/hooks/useProfileImageUpload.ts (1)
  • useProfileImageUpload (7-53)
src/components/common/LoadingModal.tsx (1)
src/constants/alert.ts (2)
  • AlertBackdrop (1-1)
  • ALERT_BACKDROP_CLASS (3-7)
🔇 Additional comments (5)
src/app/onboarding/profile/page.tsx (1)

1-6: LGTM!

Next.js App Router 패턴을 올바르게 따르는 깔끔한 페이지 래퍼입니다.

src/app/onboarding/page.tsx (1)

24-36: LGTM!

typeof window 체크를 통한 SSR 안전 처리와 온보딩 완료 후 프로필 페이지로의 네비게이션 로직이 올바르게 구현되어 있습니다.

src/components/onboarding/OnboardingProfile.tsx (3)

26-45: 프로필 저장 로직이 올바르게 구현되었습니다.

이전 리뷰에서 지적된 데이터 손실 문제가 해결되었습니다. 저장 성공 후에만 페이지 이동이 발생하며, 에러 처리도 포함되어 있습니다. TODO 주석에 명시된 대로 실제 API 연동 시 이 부분을 교체하시면 됩니다.


76-83: 이미지 업로드 에러 처리가 올바르게 추가되었습니다.

이전 리뷰에서 지적된 에러 처리 누락 문제가 해결되었습니다. 업로드 실패 시 모달이 열린 상태로 유지되고 에러가 로깅됩니다.


90-97: 로딩 모달의 취소 기능이 올바르게 구현되었습니다.

이전 리뷰에서 지적된 빈 onClose 핸들러 문제가 해결되었습니다. cancelUpload 함수를 통해 사용자가 업로드를 취소할 수 있습니다.

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

Labels

feat 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FEAT: 온보딩 UI

3 participants