From f6198488af753f971d67410c7550852a5b9b5eee Mon Sep 17 00:00:00 2001 From: Jeong Ha Seung <88266129+HA-SEUNG-JEONG@users.noreply.github.com> Date: Sat, 23 May 2026 09:04:00 +0900 Subject: [PATCH 01/10] =?UTF-8?q?[onboarding]=20hotfix=20:=20[onBoarding]?= =?UTF-8?q?=20feat=20:=20feat(store):=20=EC=98=A8=EB=B3=B4=EB=94=A9=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=AC=20Zustand=20=EC=8A=A4=ED=86=A0=EC=96=B4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- src/stores/use-onboarding-store.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/stores/use-onboarding-store.ts diff --git a/src/stores/use-onboarding-store.ts b/src/stores/use-onboarding-store.ts new file mode 100644 index 000000000..d158dcea6 --- /dev/null +++ b/src/stores/use-onboarding-store.ts @@ -0,0 +1,13 @@ +import { create } from 'zustand'; + +interface OnboardingState { + isOpen: boolean; + open: () => void; + close: () => void; +} + +export const useOnboardingStore = create((set) => ({ + isOpen: false, + open: () => set({ isOpen: true }), + close: () => set({ isOpen: false }), +})); From ed1115404ad9b304a4ac6d742ab8f52426aadf66 Mon Sep 17 00:00:00 2001 From: Jeong Ha Seung <88266129+HA-SEUNG-JEONG@users.noreply.github.com> Date: Sat, 23 May 2026 09:04:16 +0900 Subject: [PATCH 02/10] =?UTF-8?q?[onboarding]=20hotfix=20:=20[onBoarding]?= =?UTF-8?q?=20feat=20:=20fix(onboarding):=20=EB=AA=A8=EB=8B=AC=204?= =?UTF-8?q?=EB=8B=A8=EA=B3=84=20=ED=81=AC=EB=A6=AC=ED=8B=B0=EC=BB=AC=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=206=EA=B1=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - step-4: uploadImage 실패 시 close() 누락 → finally로 보장 - step-4: signUp 응답 accessToken 갱신 처리 추가 - step-4: isSubmitting 상태로 업로드 중 backdrop 닫힘 방지 - step-2: jobs 로딩 전 etcMode 오초기화 → useEffect+useRef로 수정 - step-1: 반복 이미지 선택 시 blob URL 메모리 누수 수정 Co-Authored-By: Claude Sonnet 4.6 --- .../onboarding-modal/onboarding-modal.tsx | 162 +++++++++++ .../steps/step-1-nickname.tsx | 265 ++++++++++++++++++ .../onboarding-modal/steps/step-2-job.tsx | 178 ++++++++++++ .../onboarding-modal/steps/step-3-goals.tsx | 106 +++++++ .../steps/step-4-completion.tsx | 120 ++++++++ 5 files changed, 831 insertions(+) create mode 100644 src/components/auth/modals/onboarding-modal/onboarding-modal.tsx create mode 100644 src/components/auth/modals/onboarding-modal/steps/step-1-nickname.tsx create mode 100644 src/components/auth/modals/onboarding-modal/steps/step-2-job.tsx create mode 100644 src/components/auth/modals/onboarding-modal/steps/step-3-goals.tsx create mode 100644 src/components/auth/modals/onboarding-modal/steps/step-4-completion.tsx diff --git a/src/components/auth/modals/onboarding-modal/onboarding-modal.tsx b/src/components/auth/modals/onboarding-modal/onboarding-modal.tsx new file mode 100644 index 000000000..6d5f597b5 --- /dev/null +++ b/src/components/auth/modals/onboarding-modal/onboarding-modal.tsx @@ -0,0 +1,162 @@ +'use client'; + +import { X } from 'lucide-react'; +import { useEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { cn } from '@/components/common/ui/(shadcn)/lib/utils'; +import { useOnboardingStore } from '@/stores/use-onboarding-store'; +import { Step1Nickname } from './steps/step-1-nickname'; +import { Step2Job } from './steps/step-2-job'; +import { Step3Goals } from './steps/step-3-goals'; +import { Step4Completion } from './steps/step-4-completion'; + +type Step = 1 | 2 | 3 | 4; + +interface OnboardingData { + nickname: string; + profileImageUrl?: string; + profileImageFile?: File; + career?: string; + job?: string; + goals: string[]; + goalEtcText?: string; + termsAgreed: boolean; + privacyAgreed: boolean; + marketingAgreed: boolean; +} + +export function OnboardingModal() { + const { isOpen, close } = useOnboardingStore(); + const [mounted, setMounted] = useState(false); + const [currentStep, setCurrentStep] = useState(1); + const [isSubmitting, setIsSubmitting] = useState(false); + const [data, setData] = useState({ + nickname: '', + goals: [], + termsAgreed: false, + privacyAgreed: false, + marketingAgreed: false, + }); + + useEffect(() => { + setMounted(true); + return () => setMounted(false); + }, []); + + useEffect(() => { + if (isOpen) { + setCurrentStep(1); + setData({ + nickname: '', + goals: [], + termsAgreed: false, + privacyAgreed: false, + marketingAgreed: false, + }); + } + }, [isOpen]); + + const updateData = (field: keyof OnboardingData, value: unknown) => { + setData((prev) => ({ ...prev, [field]: value })); + }; + + const handleNext = () => { + if (currentStep < 4) setCurrentStep((prev) => (prev + 1) as Step); + }; + + const handleBack = () => { + if (currentStep > 1) setCurrentStep((prev) => (prev - 1) as Step); + }; + + if (!mounted || !isOpen) return null; + + const renderStep = () => { + switch (currentStep) { + case 1: + return ( + + ); + case 2: + return ( + + ); + case 3: + return ( + + ); + case 4: + return ( + + ); + } + }; + + return createPortal( +
+ {/* Backdrop */} +