Skip to content

Commit 5225fbb

Browse files
Merge pull request #261 from Pinback-Team/feat/#258/use-funnel
Feat(client): useFunnel 구현
2 parents 1c67694 + cb808fa commit 5225fbb

File tree

3 files changed

+239
-141
lines changed

3 files changed

+239
-141
lines changed

apps/client/src/pages/onBoarding/components/funnel/MainCard.tsx

Lines changed: 13 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Progress, Button, sendGAEvent } from '@pinback/design-system/ui';
2-
import { useState, useEffect, lazy, Suspense } from 'react';
1+
import { Progress, Button } from '@pinback/design-system/ui';
2+
import { lazy, Suspense } from 'react';
33
import { motion, AnimatePresence } from 'framer-motion';
44
import SocialLoginStep from './step/SocialLoginStep';
55
const StoryStep = lazy(() => import('./step/StoryStep'));
@@ -8,21 +8,13 @@ const AlarmStep = lazy(() => import('./step/AlarmStep'));
88
const MacStep = lazy(() => import('./step/MacStep'));
99
const FinalStep = lazy(() => import('./step/FinalStep'));
1010
import { cva } from 'class-variance-authority';
11-
import { usePostSignUp } from '@shared/apis/queries';
12-
import { useNavigate, useLocation } from 'react-router-dom';
13-
import { firebaseConfig } from '../../../../firebase-config';
14-
import { initializeApp } from 'firebase/app';
15-
import { getMessaging, getToken } from 'firebase/messaging';
16-
import { registerServiceWorker } from '@pages/onBoarding/utils/registerServiceWorker';
17-
import { AlarmsType } from '@constants/alarms';
18-
import { normalizeTime } from '@pages/onBoarding/utils/formatRemindTime';
1911
const stepProgress = [{ progress: 33 }, { progress: 66 }, { progress: 100 }];
2012
import {
2113
Step,
22-
stepOrder,
2314
StepType,
2415
storySteps,
2516
} from '@pages/onBoarding/constants/onboardingSteps';
17+
import { useOnboardingFunnel } from '@pages/onBoarding/hooks/useOnboardingFunnel';
2618

2719
const variants = {
2820
slideIn: (direction: number) => ({
@@ -50,78 +42,16 @@ const CardStyle = cva(
5042
);
5143

5244
const MainCard = () => {
53-
const navigate = useNavigate();
54-
const location = useLocation();
55-
const { mutate: postSignData } = usePostSignUp();
56-
57-
const [step, setStep] = useState<StepType>(Step.STORY_0);
58-
const [direction, setDirection] = useState(0);
59-
const [alarmSelected, setAlarmSelected] = useState<1 | 2 | 3>(1);
60-
const [isMac, setIsMac] = useState(false);
61-
const [userEmail, setUserEmail] = useState('');
62-
const [remindTime, setRemindTime] = useState('09:00');
63-
const [fcmToken, setFcmToken] = useState<string | null>(null);
64-
const [jobShareAgree, setJobShareAgree] = useState(true);
65-
66-
useEffect(() => {
67-
const params = new URLSearchParams(location.search);
68-
const storedEmail = localStorage.getItem('email');
69-
if (storedEmail) {
70-
setUserEmail(storedEmail);
71-
}
72-
73-
const stepParam = params.get('step') as StepType;
74-
if (stepParam && Object.values(Step).includes(stepParam)) {
75-
setStep(stepParam);
76-
}
77-
}, [location.search]);
78-
79-
const app = initializeApp(firebaseConfig);
80-
const messaging = getMessaging(app);
81-
82-
const requestFCMToken = async (): Promise<string | null> => {
83-
try {
84-
const permission = await Notification.requestPermission();
85-
registerServiceWorker();
86-
87-
if (permission !== 'granted') {
88-
alert('알림 권한 허용이 필요합니다!');
89-
return null;
90-
}
91-
92-
const forFcmtoken = await getToken(messaging, {
93-
vapidKey: import.meta.env.VITE_FIREBASE_VAPID_KEY,
94-
});
95-
96-
if (forFcmtoken) {
97-
return forFcmtoken;
98-
} else {
99-
alert('토큰 생성 실패. 다시 시도해주세요.');
100-
return null;
101-
}
102-
} catch (error) {
103-
console.error('FCM 토큰 받는 도중 오류:', error);
104-
alert('알림 설정 중 오류가 발생했습니다. 다시 시도해주세요.');
105-
return null;
106-
}
107-
};
108-
109-
useEffect(() => {
110-
const ua = navigator.userAgent.toLowerCase();
111-
if (ua.includes('mac os') || ua.includes('iphone') || ua.includes('ipad')) {
112-
setIsMac(true);
113-
}
114-
115-
(async () => {
116-
const token = await requestFCMToken();
117-
if (token) {
118-
setFcmToken(token);
119-
localStorage.setItem('FcmToken', token);
120-
} else {
121-
alert('푸시 알람 설정 에러');
122-
}
123-
})();
124-
}, []);
45+
const {
46+
step,
47+
direction,
48+
alarmSelected,
49+
jobShareAgree,
50+
setAlarmSelected,
51+
setJobShareAgree,
52+
nextStep,
53+
prevStep,
54+
} = useOnboardingFunnel();
12555

12656
const renderStep = () => {
12757
switch (step) {
@@ -153,64 +83,6 @@ const MainCard = () => {
15383
}
15484
};
15585

156-
const nextStep = async () => {
157-
const idx = stepOrder.indexOf(step);
158-
const next = stepOrder[idx + 1];
159-
const isAlarmStep = step === Step.ALARM;
160-
const isFinalStep = step === Step.FINAL;
161-
const isMacStep = next === Step.MAC;
162-
const shouldSkipMacStep = isMacStep && !isMac;
163-
164-
if (isAlarmStep) {
165-
if (alarmSelected === 1) setRemindTime('09:00');
166-
else if (alarmSelected === 2) setRemindTime('20:00');
167-
else {
168-
const raw = AlarmsType[alarmSelected - 1].time;
169-
setRemindTime(normalizeTime(raw));
170-
}
171-
}
172-
173-
if (shouldSkipMacStep) {
174-
setDirection(1);
175-
setStep(Step.FINAL);
176-
navigate(`/onboarding?step=${Step.FINAL}`);
177-
return;
178-
}
179-
180-
if (isFinalStep) {
181-
postSignData(
182-
{ email: userEmail, remindDefault: remindTime, fcmToken },
183-
{
184-
onSuccess: () => (window.location.href = '/'),
185-
onError: () => {
186-
const savedEmail = localStorage.getItem('email');
187-
if (savedEmail) window.location.href = '/';
188-
},
189-
}
190-
);
191-
return;
192-
}
193-
194-
setDirection(1);
195-
setStep(next);
196-
navigate(`/onboarding?step=${next}`);
197-
sendGAEvent(
198-
`onboard-step-${idx + 1}`,
199-
`onboard-step-${idx + 1}`,
200-
`onboard-step-${idx + 1}`
201-
);
202-
};
203-
204-
const prevStep = () => {
205-
const idx = stepOrder.indexOf(step);
206-
if (idx > 0) {
207-
const previous = stepOrder[idx - 1];
208-
setDirection(-1);
209-
setStep(previous);
210-
navigate(`/onboarding?step=${previous}`);
211-
}
212-
};
213-
21486
return (
21587
<div
21688
className={CardStyle({
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { sendGAEvent } from '@pinback/design-system/ui';
2+
import { usePostSignUp } from '@shared/apis/queries';
3+
import { useFunnel } from '@shared/hooks/useFunnel';
4+
import { useCallback, useEffect, useState } from 'react';
5+
import { AlarmsType } from '@constants/alarms';
6+
import { normalizeTime } from '@pages/onBoarding/utils/formatRemindTime';
7+
import { registerServiceWorker } from '@pages/onBoarding/utils/registerServiceWorker';
8+
import { Step, stepOrder, StepType } from '@pages/onBoarding/constants/onboardingSteps';
9+
import { firebaseConfig } from '../../../firebase-config';
10+
import { getApp, getApps, initializeApp } from 'firebase/app';
11+
import { getMessaging, getToken } from 'firebase/messaging';
12+
13+
type AlarmSelection = 1 | 2 | 3;
14+
15+
export function useOnboardingFunnel() {
16+
const { mutate: postSignData } = usePostSignUp();
17+
const { currentStep: step, currentIndex, setStep, goNext, goPrev } =
18+
useFunnel<StepType>({
19+
steps: stepOrder,
20+
initialStep: Step.STORY_0,
21+
});
22+
23+
const [direction, setDirection] = useState(0);
24+
const [alarmSelected, setAlarmSelected] = useState<AlarmSelection>(1);
25+
const [isMac, setIsMac] = useState(false);
26+
const [userEmail, setUserEmail] = useState('');
27+
const [remindTime, setRemindTime] = useState('09:00');
28+
const [fcmToken, setFcmToken] = useState<string | null>(null);
29+
const [jobShareAgree, setJobShareAgree] = useState(true);
30+
31+
useEffect(() => {
32+
const storedEmail = localStorage.getItem('email');
33+
if (storedEmail) {
34+
setUserEmail(storedEmail);
35+
}
36+
}, []);
37+
38+
const requestFCMToken = useCallback(async (): Promise<string | null> => {
39+
try {
40+
const app = getApps().length > 0 ? getApp() : initializeApp(firebaseConfig);
41+
const messaging = getMessaging(app);
42+
const permission = await Notification.requestPermission();
43+
registerServiceWorker();
44+
45+
if (permission !== 'granted') {
46+
alert('알림 권한 허용이 필요합니다!');
47+
return null;
48+
}
49+
50+
const forFcmtoken = await getToken(messaging, {
51+
vapidKey: import.meta.env.VITE_FIREBASE_VAPID_KEY,
52+
});
53+
54+
if (forFcmtoken) {
55+
return forFcmtoken;
56+
}
57+
58+
alert('토큰 생성 실패. 다시 시도해주세요.');
59+
return null;
60+
} catch {
61+
alert('알림 설정 중 오류가 발생했습니다. 다시 시도해주세요.');
62+
return null;
63+
}
64+
}, []);
65+
66+
useEffect(() => {
67+
const ua = navigator.userAgent.toLowerCase();
68+
if (ua.includes('mac os') || ua.includes('iphone') || ua.includes('ipad')) {
69+
setIsMac(true);
70+
}
71+
72+
(async () => {
73+
const token = await requestFCMToken();
74+
if (token) {
75+
setFcmToken(token);
76+
localStorage.setItem('FcmToken', token);
77+
} else {
78+
alert('푸시 알람 설정 에러');
79+
}
80+
})();
81+
}, [requestFCMToken]);
82+
83+
const nextStep = useCallback(async () => {
84+
const next = stepOrder[currentIndex + 1];
85+
const isAlarmStep = step === Step.ALARM;
86+
const isFinalStep = step === Step.FINAL;
87+
const isMacStep = next === Step.MAC;
88+
const shouldSkipMacStep = isMacStep && !isMac;
89+
90+
if (isAlarmStep) {
91+
if (alarmSelected === 1) setRemindTime('09:00');
92+
else if (alarmSelected === 2) setRemindTime('20:00');
93+
else {
94+
const raw = AlarmsType[alarmSelected - 1].time;
95+
setRemindTime(normalizeTime(raw));
96+
}
97+
}
98+
99+
if (shouldSkipMacStep) {
100+
setDirection(1);
101+
setStep(Step.FINAL);
102+
return;
103+
}
104+
105+
if (isFinalStep) {
106+
postSignData(
107+
{ email: userEmail, remindDefault: remindTime, fcmToken },
108+
{
109+
onSuccess: () => (window.location.href = '/'),
110+
onError: () => {
111+
const savedEmail = localStorage.getItem('email');
112+
if (savedEmail) window.location.href = '/';
113+
},
114+
}
115+
);
116+
return;
117+
}
118+
119+
setDirection(1);
120+
goNext();
121+
sendGAEvent(
122+
`onboard-step-${currentIndex + 1}`,
123+
`onboard-step-${currentIndex + 1}`,
124+
`onboard-step-${currentIndex + 1}`
125+
);
126+
}, [
127+
alarmSelected,
128+
currentIndex,
129+
fcmToken,
130+
goNext,
131+
isMac,
132+
postSignData,
133+
remindTime,
134+
setStep,
135+
step,
136+
userEmail,
137+
]);
138+
139+
const prevStep = useCallback(() => {
140+
if (currentIndex > 0) {
141+
setDirection(-1);
142+
goPrev();
143+
}
144+
}, [currentIndex, goPrev]);
145+
146+
return {
147+
step,
148+
currentIndex,
149+
direction,
150+
alarmSelected,
151+
jobShareAgree,
152+
setAlarmSelected,
153+
setJobShareAgree,
154+
nextStep,
155+
prevStep,
156+
};
157+
}

0 commit comments

Comments
 (0)