Skip to content

Commit 07f6734

Browse files
authored
Feat: goolgle login 페이지 추가 및 연결 (#204)
1 parent b0956c4 commit 07f6734

File tree

12 files changed

+336
-71
lines changed

12 files changed

+336
-71
lines changed
Lines changed: 11 additions & 0 deletions
Loading

apps/client/src/assets/chippi_extension_popup.svg

Lines changed: 9 additions & 0 deletions
Loading
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import apiRequest from '@shared/apis/setting/axiosInstance';
2+
import LoadingChippi from '@shared/components/loadingChippi/LoadingChippi';
3+
import { useEffect } from 'react';
4+
import { useNavigate, useSearchParams } from 'react-router-dom';
5+
6+
const GoogleCallback = () => {
7+
const navigate = useNavigate();
8+
const [searchParams] = useSearchParams();
9+
10+
useEffect(() => {
11+
const code = searchParams.get('code');
12+
13+
if (!code) {
14+
alert('로그인 실패. 다시 시도해주세요.');
15+
navigate('/onboarding?step=SOCIAL_LOGIN');
16+
return;
17+
}
18+
19+
loginWithCode(code);
20+
}, []);
21+
22+
const handleUserLogin = (
23+
isUser: boolean,
24+
accessToken: string | undefined
25+
) => {
26+
if (isUser) {
27+
if (accessToken) {
28+
localStorage.setItem('token', accessToken);
29+
}
30+
navigate('/');
31+
} else {
32+
navigate('/onboarding?step=ALARM');
33+
}
34+
};
35+
36+
const loginWithCode = async (code: string) => {
37+
try {
38+
const res = await apiRequest.post('/api/v2/auth/google', { code });
39+
const { isUser, userId, email, accessToken } = res.data.data;
40+
41+
localStorage.setItem('email', email);
42+
localStorage.setItem('userId', userId);
43+
44+
handleUserLogin(isUser, accessToken);
45+
} catch (error) {
46+
console.error('로그인 오류:', error);
47+
navigate('/onboarding?step=SOCIAL_LOGIN');
48+
}
49+
};
50+
51+
return (
52+
<div className="flex h-screen flex-col items-center justify-center">
53+
<LoadingChippi className="mb-6" />
54+
<p className="text-font-black-2 head3 mt-[1.6rem]">
55+
잠시만 기다려주세요…
56+
</p>
57+
<p className="body1-m text-font-gray-3 text-center">
58+
치삐가 로그인 중입니다
59+
</p>
60+
</div>
61+
);
62+
};
63+
64+
export default GoogleCallback;

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

Lines changed: 92 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
import { Progress, Button } from '@pinback/design-system/ui';
22
import { useState, useEffect, lazy, Suspense } from 'react';
33
import { motion, AnimatePresence } from 'framer-motion';
4+
import SocialLoginStep from './step/SocialLoginStep';
45
const StoryStep = lazy(() => import('./step/StoryStep'));
56
const AlarmStep = lazy(() => import('./step/AlarmStep'));
67
const MacStep = lazy(() => import('./step/MacStep'));
78
const FinalStep = lazy(() => import('./step/FinalStep'));
8-
99
import { cva } from 'class-variance-authority';
1010
import { usePostSignUp } from '@shared/apis/queries';
11-
const stepProgress = [{ progress: 30 }, { progress: 60 }, { progress: 100 }];
12-
import { AlarmsType } from '@constants/alarms';
13-
import { normalizeTime } from '@pages/onBoarding/utils/formatRemindTime';
11+
import { useNavigate, useLocation } from 'react-router-dom';
1412
import { firebaseConfig } from '../../../../firebase-config';
1513
import { initializeApp } from 'firebase/app';
1614
import { getMessaging, getToken } from 'firebase/messaging';
1715
import { registerServiceWorker } from '@pages/onBoarding/utils/registerServiceWorker';
18-
import { useLocation } from 'react-router-dom';
16+
import { AlarmsType } from '@constants/alarms';
17+
import { normalizeTime } from '@pages/onBoarding/utils/formatRemindTime';
18+
const stepProgress = [{ progress: 33 }, { progress: 66 }, { progress: 100 }];
19+
import {
20+
Step,
21+
stepOrder,
22+
StepType,
23+
storySteps,
24+
} from '@pages/onBoarding/constants/onboardingSteps';
25+
1926
const variants = {
2027
slideIn: (direction: number) => ({
2128
x: direction > 0 ? 200 : -200,
@@ -27,6 +34,7 @@ const variants = {
2734
opacity: 0,
2835
}),
2936
};
37+
3038
const CardStyle = cva(
3139
'bg-white-bg flex h-[54.8rem] w-[63.2rem] flex-col items-center justify-between rounded-[2.4rem] pt-[3.2rem]',
3240
{
@@ -39,36 +47,41 @@ const CardStyle = cva(
3947
defaultVariants: { overflow: false },
4048
}
4149
);
50+
4251
const MainCard = () => {
43-
const [step, setStep] = useState(0);
52+
const navigate = useNavigate();
53+
const location = useLocation();
54+
const { mutate: postSignData } = usePostSignUp();
55+
56+
const [step, setStep] = useState<StepType>(Step.STORY_0);
4457
const [direction, setDirection] = useState(0);
4558
const [alarmSelected, setAlarmSelected] = useState<1 | 2 | 3>(1);
4659
const [isMac, setIsMac] = useState(false);
47-
// api 구간
48-
const { mutate: postSignData } = usePostSignUp();
49-
50-
// 익스텐션에서부터 이메일 받아오는 구간!
5160
const [userEmail, setUserEmail] = useState('');
52-
const location = useLocation();
61+
const [remindTime, setRemindTime] = useState('09:00');
62+
const [fcmToken, setFcmToken] = useState<string | null>(null);
5363

5464
useEffect(() => {
5565
const params = new URLSearchParams(location.search);
56-
const emailParam = params.get('email');
57-
if (emailParam) {
58-
setUserEmail(emailParam);
59-
localStorage.setItem('email', emailParam);
66+
const storedEmail = localStorage.getItem('email');
67+
if (storedEmail) {
68+
setUserEmail(storedEmail);
69+
}
70+
71+
const stepParam = params.get('step') as StepType;
72+
if (stepParam && Object.values(Step).includes(stepParam)) {
73+
setStep(stepParam);
6074
}
6175
}, [location.search]);
6276

63-
// FCM 구간
64-
const [fcmToken, setFcmToken] = useState<string | null>(null);
6577
const app = initializeApp(firebaseConfig);
6678
const messaging = getMessaging(app);
6779

6880
const requestFCMToken = async (): Promise<string | null> => {
6981
try {
7082
const permission = await Notification.requestPermission();
7183
registerServiceWorker();
84+
7285
if (permission !== 'granted') {
7386
alert('알림 권한 허용이 필요합니다!');
7487
return null;
@@ -107,80 +120,92 @@ const MainCard = () => {
107120
}
108121
})();
109122
}, []);
123+
110124
const renderStep = () => {
111125
switch (step) {
112-
case 0:
113-
case 1:
114-
case 2:
115-
return <StoryStep step={step as 0 | 1 | 2} />;
116-
case 3:
126+
case Step.STORY_0:
127+
case Step.STORY_1:
128+
case Step.STORY_2:
129+
return (
130+
<StoryStep step={Number(step.replace('STORY_', '')) as 0 | 1 | 2} />
131+
);
132+
case Step.SOCIAL_LOGIN:
133+
return <SocialLoginStep />;
134+
case Step.ALARM:
117135
return (
118136
<AlarmStep selected={alarmSelected} setSelected={setAlarmSelected} />
119137
);
120-
case 4:
121-
if (isMac) return <MacStep />;
138+
case Step.MAC:
139+
return <MacStep />;
140+
case Step.FINAL:
122141
return <FinalStep />;
123-
case 5:
124-
if (isMac) return <FinalStep />;
125-
return null;
126142
default:
127143
return <FinalStep />;
128144
}
129145
};
130146

131-
const [remindTime, setRemindTime] = useState('09:00');
132147
const nextStep = async () => {
133-
if (step === 3) {
134-
if (alarmSelected == 1) {
135-
setRemindTime('09:00');
136-
} else if (alarmSelected == 2) {
137-
setRemindTime('20:00');
138-
} else {
148+
const idx = stepOrder.indexOf(step);
149+
const next = stepOrder[idx + 1];
150+
const isAlarmStep = step === Step.ALARM;
151+
const isFinalStep = step === Step.FINAL;
152+
const isMacStep = next === Step.MAC;
153+
const shouldSkipMacStep = isMacStep && !isMac;
154+
155+
if (isAlarmStep) {
156+
if (alarmSelected === 1) setRemindTime('09:00');
157+
else if (alarmSelected === 2) setRemindTime('20:00');
158+
else {
139159
const raw = AlarmsType[alarmSelected - 1].time;
140160
setRemindTime(normalizeTime(raw));
141161
}
162+
}
142163

164+
if (shouldSkipMacStep) {
143165
setDirection(1);
144-
setStep((prev) => prev + 1);
166+
setStep(Step.FINAL);
167+
navigate(`/onboarding?step=${Step.FINAL}`);
145168
return;
146169
}
147-
if ((isMac && step < 5) || (!isMac && step < 4)) {
148-
setDirection(1);
149-
setStep((prev) => prev + 1);
150-
} else if ((isMac && step === 5) || (!isMac && step == 4)) {
170+
171+
if (isFinalStep) {
151172
postSignData(
173+
{ email: userEmail, remindDefault: remindTime, fcmToken },
152174
{
153-
email: userEmail,
154-
remindDefault: remindTime,
155-
fcmToken: fcmToken,
156-
},
157-
{
158-
onSuccess: () => {
159-
window.location.href = '/';
160-
},
175+
onSuccess: () => (window.location.href = '/'),
161176
onError: () => {
162177
const savedEmail = localStorage.getItem('email');
163-
if (savedEmail) {
164-
window.location.href = '/';
165-
}
178+
if (savedEmail) window.location.href = '/';
166179
},
167180
}
168181
);
182+
return;
169183
}
184+
185+
setDirection(1);
186+
setStep(next);
187+
navigate(`/onboarding?step=${next}`);
170188
};
171189

172190
const prevStep = () => {
173-
if (step > 0) {
191+
const idx = stepOrder.indexOf(step);
192+
if (idx > 0) {
193+
const previous = stepOrder[idx - 1];
174194
setDirection(-1);
175-
setStep((prev) => prev - 1);
195+
setStep(previous);
196+
navigate(`/onboarding?step=${previous}`);
176197
}
177198
};
178199

179200
return (
180-
<div className={CardStyle({ overflow: step === 3 && alarmSelected === 3 })}>
181-
{step < 3 && (
201+
<div
202+
className={CardStyle({
203+
overflow: step === Step.ALARM && alarmSelected === 3,
204+
})}
205+
>
206+
{storySteps.includes(step) && (
182207
<Progress
183-
value={stepProgress[step].progress}
208+
value={stepProgress[storySteps.indexOf(step)].progress}
184209
variant="profile"
185210
className="w-[15.6rem]"
186211
/>
@@ -204,26 +229,27 @@ const MainCard = () => {
204229
</div>
205230

206231
<div className="mb-[4.8rem] mt-[1.2rem] flex w-full justify-between px-[3.2rem]">
207-
{step < 4 && step > 0 && (
232+
{!([Step.STORY_0, Step.SOCIAL_LOGIN] as StepType[]).includes(step) && (
208233
<Button
209234
variant="secondary"
210235
size="medium"
211-
isDisabled={step === 0}
212236
className="w-[4.8rem]"
213237
onClick={prevStep}
214238
>
215239
뒤로
216240
</Button>
217241
)}
218-
<Button
219-
variant="primary"
220-
size="medium"
221-
isDisabled={step === 6}
222-
className="ml-auto w-[4.8rem]"
223-
onClick={nextStep}
224-
>
225-
다음
226-
</Button>
242+
243+
{step !== Step.SOCIAL_LOGIN && (
244+
<Button
245+
variant="primary"
246+
size="medium"
247+
className="ml-auto w-[4.8rem]"
248+
onClick={nextStep}
249+
>
250+
다음
251+
</Button>
252+
)}
227253
</div>
228254
</div>
229255
);
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import Chippi from '@assets/chippi_extension_popup.svg';
2+
import GoogleLogo from '/assets/onBoarding/icons/googleLogo.svg';
3+
4+
const SocialLoginStep = () => {
5+
const handleGoogleLogin = () => {
6+
const clientId = import.meta.env.VITE_GOOGLE_CLIENT_ID;
7+
const redirectUri = import.meta.env.VITE_GOOGLE_REDIRECT_URI;
8+
9+
if (!clientId || !redirectUri) {
10+
alert('Google OAuth 설정이 누락되었습니다.');
11+
return;
12+
}
13+
14+
const googleAuthUrl =
15+
`https://accounts.google.com/o/oauth2/v2/auth?` +
16+
`client_id=${clientId}` +
17+
`&redirect_uri=${redirectUri}` +
18+
`&response_type=code` +
19+
`&scope=email profile`;
20+
21+
window.location.href = googleAuthUrl;
22+
};
23+
24+
return (
25+
<div className="flex flex-col items-center justify-center pt-6">
26+
<img
27+
src={Chippi}
28+
alt="치삐 이미지"
29+
className="h-[19.4rem] w-[19.4rem] object-contain"
30+
/>
31+
32+
<h1 className="head2 text-font-black-1 mb-[0.8rem] text-center">
33+
치삐를 만나려면 로그인이 필요해요!
34+
</h1>
35+
36+
<p className="body2-m text-font-gray-3 mb-[3.5rem] text-center">
37+
로그인하고 북마크한 정보를 리마인드를 받아보세요.
38+
</p>
39+
40+
<button
41+
onClick={handleGoogleLogin}
42+
className="sub2-sb flex h-[5.2rem] w-[22.7rem] items-center justify-between gap-3 rounded-full border border-gray-100 bg-white px-[2rem]"
43+
>
44+
<img
45+
src={GoogleLogo}
46+
alt="구글 로고"
47+
className="h-[2.435rem] w-[2.435rem]"
48+
/>
49+
구글 계정으로 로그인
50+
</button>
51+
</div>
52+
);
53+
};
54+
55+
export default SocialLoginStep;

0 commit comments

Comments
 (0)