-
Notifications
You must be signed in to change notification settings - Fork 1
Feat: 온보딩 UI #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Feat: 온보딩 UI #10
Conversation
둘러보기새로운 온보딩 플로우 기능이 추가되었습니다. 다단계 온보딩 페이지, 프로필 설정 컴포넌트, 이미지 업로드 기능, 진행 상태 표시기를 포함한 여러 클라이언트 컴포넌트가 구현되었으며, 약관 동의 로직이 상수로 추출되고 모달 컴포넌트가 개선되었습니다. 변경사항
시퀀스 다이어그램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
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으로 이동
추정 코드 리뷰 난이도🎯 4 (복잡함) | ⏱️ ~45분 관련 가능성 있는 PR
시 🐰
🚥 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
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.
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타입 추출 패턴이 적절합니다. 다만,OnboardingBackground의type: "class"변형은 현재 사용되지 않고 있습니다. 향후 확장을 위한 것이라면 괜찮지만, 사용 계획이 없다면 YAGNI 원칙에 따라 제거를 고려해볼 수 있습니다.src/components/onboarding/UploadAlert.tsx (3)
28-34:onClose()호출이 중복될 수 있습니다.
handleFileChange에서onSelectAlbum(file)호출 후onClose()를 호출하고 있지만,OnboardingProfile.tsx의onSelectAlbum콜백에서도 이미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은 배경 클릭 핸들러가 없습니다. 컴포넌트 간 일관성을 위해 배경 클릭 동작을 통일하거나,closeOnBackdropprop을 추가하여 제어하는 것을 고려해보세요.
53-65: 로딩 상태와 메시지 렌더링 로직이 명확합니다.
isLoading && loadingImageSrc조건으로 로딩 이미지를 표시하고, 그렇지 않으면message를 표시하는 로직이 잘 구성되어 있습니다. 다만isLoading이true이지만loadingImageSrc가 없는 경우 아무것도 표시되지 않으므로, 이 케이스에 대한 폴백(예: 기본 스피너)을 고려해볼 수 있습니다.
54-58:<img>태그 대신 Next.js의Image컴포넌트 사용을 고려해보세요.Next.js 프로젝트에서는 이미지 최적화를 위해
next/image의Image컴포넌트를 사용하는 것이 권장됩니다. 단, GIF 파일의 경우unoptimizedprop이 필요할 수 있습니다.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (6)
public/images/onboarding-step-1.pngis excluded by!**/*.pngpublic/images/onboarding-step-2.pngis excluded by!**/*.pngpublic/images/onboarding-step-3.pngis excluded by!**/*.pngpublic/loading.gifis excluded by!**/*.gifsrc/assets/onboarding_profile.svgis excluded by!**/*.svgsrc/assets/photo.svgis excluded by!**/*.svg
📒 Files selected for processing (16)
src/app/login/page.tsxsrc/app/onboarding/page.tsxsrc/app/onboarding/profile/page.tsxsrc/app/page.tsxsrc/components/common/AlertModal.tsxsrc/components/common/NameInput.tsxsrc/components/onboarding/OnboardingProfile.tsxsrc/components/onboarding/OnboardingStep.tsxsrc/components/onboarding/ProfileImagePicker.tsxsrc/components/onboarding/ProgressDots.tsxsrc/components/onboarding/UploadAlert.tsxsrc/components/password/PasswordForm.tsxsrc/config/onboardingSteps.tssrc/hooks/useFunnel.tsxsrc/hooks/useProfileImageUpload.tssrc/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:StepProps의name속성이Step컴포넌트에서 사용되지 않습니다.
StepProps인터페이스에name이 정의되어 있지만,Step컴포넌트는children만 사용합니다. 이는 의도된 설계로 보이며(Funnel에서props.name으로 필터링), 타입 정의가 정확합니다.
lemoncurdyogurt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사소한 디자인 오류라던가, 퍼널로직 수정에 대해서 리뷰적어두었습니다!
dim관련해서 이전에 말했던 것이라서 이번에는 수정 꼭해주세요...!
…into feat/6-onboarding
There was a problem hiding this 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: 타입 안전성 개선을 고려하세요.
OnboardingFunnelContext가Record<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.ts의OnboardingFunnelContext와 유사한 구조를 가지고 있습니다. 두 타입이 동일한 목적을 가진다면 하나로 통합하여 중복을 제거하는 것을 고려하세요.
11-13: OnboardingBackground의 "class" 타입이 현재 사용되지 않습니다.
OnboardingBackground에서type: "class"옵션을 정의했지만, 현재 모든 스텝이type: "image"만 사용하고 있습니다. 향후 확장을 위한 것이라면 문제없지만, 사용하지 않는다면 제거를 고려하세요.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (4)
public/images/onboarding-step-1.svgis excluded by!**/*.svgpublic/images/onboarding-step-2.svgis excluded by!**/*.svgpublic/images/onboarding-step-3.svgis excluded by!**/*.svgyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (25)
package.jsonsrc/app/page.tsxsrc/app/signup/agree/page.tsxsrc/app/signup/password/page.tsxsrc/components/common/LoadingModal.tsxsrc/components/onboarding/OnboardingProfile.tsxsrc/components/onboarding/OnboardingStep.tsxsrc/components/onboarding/ProfileImagePicker.tsxsrc/components/onboarding/ProgressDots.tsxsrc/components/onboarding/UploadButton.tsxsrc/components/password/PasswordForm.tsxsrc/components/signup/ProfileForm.tsxsrc/constants/agreement.tssrc/constants/alert.tssrc/constants/onboardingSteps.tssrc/hooks/useOnboardingFunnel.tssrc/hooks/usePasswordValidation.tssrc/hooks/useProfileImageUpload.tssrc/pages/_app.tsxsrc/pages/onboarding/index.tsxsrc/pages/onboarding/profile.tsxsrc/styles/font.tssrc/styles/globals.csssrc/types/agreement.type.tssrc/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,onCloseprops를 모두 지원합니다. 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!로딩 상태와 메시지 렌더링 조건 분기가 적절합니다.
loaderCSS 클래스가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함수에는 다음과 같은 문제가 있습니다:
- 메모리 누수:
URL.createObjectURL()로 생성된 Object URL이 해제되지 않습니다- 파일 검증 부재: 파일 타입과 크기 제한이 없습니다
- 에러 처리 누락: 업로드 실패 시 처리가 없습니다
- 시뮬레이션 딜레이: 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.
There was a problem hiding this 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
📒 Files selected for processing (2)
src/app/page.tsxsrc/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)
There was a problem hiding this 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로 명시하세요.
loadingImageSrcprop이 주석 처리되어 있습니다. 향후 사용 계획이 있다면 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_STEPS가as 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
📒 Files selected for processing (10)
src/app/onboarding/page.tsxsrc/app/onboarding/profile/page.tsxsrc/app/page.tsxsrc/components/common/LoadingModal.tsxsrc/components/onboarding/OnboardingProfile.tsxsrc/components/onboarding/OnboardingStep.tsxsrc/components/onboarding/ProgressDots.tsxsrc/components/onboarding/UploadButton.tsxsrc/hooks/useProfileImageUpload.tssrc/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함수를 통해 사용자가 업로드를 취소할 수 있습니다.
🔥 작업 내용
🤔 추후 작업 사항
📸 작업 내역 스크린샷
🔗 이슈
Summary by CodeRabbit
릴리즈 노트
New Features
UI/UX Improvements
Chores
✏️ Tip: You can customize this high-level summary in your review settings.