Skip to content

Commit cf78ceb

Browse files
committed
refact: 전화번호 인증 훅으로 분리
1 parent 7f6068b commit cf78ceb

File tree

5 files changed

+120
-106
lines changed

5 files changed

+120
-106
lines changed

src/entities/user/ui/ProfileInfo.tsx

Lines changed: 18 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import ProfileCircle from '../../../../design-system/ui/Profile';
77
import TertiaryButton from '../../../../design-system/ui/buttons/TertiaryButton';
88
import DefaultTextField from '../../../../design-system/ui/textFields/DefaultTextField';
99
import useAuthStore from '../../../app/provider/authStore';
10-
import { useSendCertificationCode, useUserInfo, useUserUpdate, useVerifyCertificationCode } from '../../../features/join/hooks/useUserHook';
10+
import { useUserInfo, useUserUpdate} from '../../../features/join/hooks/useUserHook';
1111
import { formatProfilName } from '../../../shared/lib/formatProfileName';
1212
import Button from '../../../../design-system/ui/Button';
13+
import { usePhoneVerification } from '../../../shared/utils/phoneVerification';
1314

1415
const ProfileInfo = () => {
1516
const isLoggedIn = useAuthStore(state => state.isLoggedIn);
@@ -18,9 +19,17 @@ const ProfileInfo = () => {
1819
const { mutate: updateUser } = useUserUpdate();
1920
const [isEditing, setIsEditing] = useState(false);
2021

21-
const { mutate: sendCode } = useSendCertificationCode();
22-
const { mutate: verifyCode } = useVerifyCertificationCode();
23-
const [isVerified, setIsVerified] = useState(false);
22+
const {
23+
isVerified,
24+
isVerifyVisible,
25+
verificationCode,
26+
setVerificationCode,
27+
timer,
28+
formatTime,
29+
requestVerificationCode,
30+
submitVerificationCode,
31+
resetVerification,
32+
} = usePhoneVerification();
2433

2534
const {
2635
register,
@@ -66,7 +75,6 @@ const ProfileInfo = () => {
6675
setName(name);
6776
refetch();
6877
setIsEditing(false);
69-
setIsVerified(false);
7078
alert('정보가 성공적으로 업데이트 되었습니다.')
7179
},
7280
onError: () => {
@@ -75,48 +83,7 @@ const ProfileInfo = () => {
7583
});
7684
};
7785

78-
// 전화번호 인증
79-
const [isVerifyVisible, setIsVerifyVisible] = useState(true);
80-
const [verificationCode, setVerificationCode] = useState('');
81-
const [timer, setTimer] = useState(0);
82-
//인증번호 발급
83-
const handlePhoneVerifyClick = () => {
84-
if (!phoneValue) {
85-
alert('연락처를 입력해주세요.');
86-
return;
87-
}
88-
// //인증api호출
89-
sendCode({ phoneNumber: phoneValue }, {
90-
onSuccess: () => {
91-
setIsVerifyVisible(true);
92-
setTimer(180);
93-
}
94-
});
95-
};
96-
// 인증번호 확인
97-
const handleVerifySubmit = () => {
98-
if (!verificationCode || verificationCode.length !== 6) {
99-
alert('6자리 인증번호를 입력해주세요.');
100-
return;
101-
}
102-
verifyCode({ phoneNumber: phoneValue, certificationCode: verificationCode }, {
103-
onSuccess: () => {
104-
setIsVerifyVisible(false);
105-
setIsVerified(true);
106-
}
107-
});
108-
};
109-
useEffect(() => {
110-
if (isVerifyVisible && timer > 0) {
111-
const interval = setInterval(() => setTimer((prev) => prev - 1), 1000);
112-
return () => clearInterval(interval);
113-
}
114-
}, [timer, isVerifyVisible]);
115-
const formatTime = (seconds: number) => {
116-
const m = String(Math.floor(seconds / 60)).padStart(2, '0');
117-
const s = String(seconds % 60).padStart(2, '0');
118-
return `${m}:${s}`;
119-
};
86+
12087

12188
if (isLoading) {
12289
return <div>로딩 중...</div>;
@@ -175,7 +142,7 @@ const ProfileInfo = () => {
175142
<Button
176143
type="button"
177144
label="인증하기"
178-
onClick={handlePhoneVerifyClick}
145+
onClick={() => requestVerificationCode(phoneValue)}
179146
className="h-9 sm:h-8 rounded-md w-24"
180147
/>
181148
</div>
@@ -192,7 +159,7 @@ const ProfileInfo = () => {
192159
<Button
193160
type="button"
194161
label="확인"
195-
onClick={handleVerifySubmit}
162+
onClick={() => submitVerificationCode(phoneValue)}
196163
className="h-9 px-3 rounded-md"
197164
/>
198165
</div>
@@ -208,8 +175,7 @@ const ProfileInfo = () => {
208175
setIsEditing(false);
209176
setValue('name', data?.name || '');
210177
setValue('phone', data?.phoneNumber || '');
211-
setIsVerified(false);
212-
setIsVerifyVisible(false);
178+
resetVerification();
213179
setVerificationCode('');
214180
}}
215181
/>
@@ -227,7 +193,7 @@ const ProfileInfo = () => {
227193
setIsEditing(false);
228194
setValue('name', data?.name || '');
229195
setValue('phone', data?.phoneNumber || '');
230-
setIsVerified(false);
196+
resetVerification();
231197
setVerificationCode('');
232198
}}
233199
/>

src/features/join/api/user.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const sendCertificationCode = async (data: { phoneNumber: string }) => {
3131
return response.data;
3232
};
3333
//인증번호 검증
34-
export const verifyCertificationCode = async (data: {phoneNumber: string, certificationCode: string}): Promise<void> => {
34+
export const verifyCertificationCode = async (data: {phoneNumber: string, certificationCode: string}) => {
3535
const response = await axiosClient.post('/sms/verify', data, {
3636
headers: { isPublicApi: true },
3737
});

src/pages/join/InfoInputPage.tsx

Lines changed: 15 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,30 @@
1-
import { useEffect, useState } from 'react';
1+
import { useEffect } from 'react';
22
import { useNavigate } from 'react-router-dom';
33
import { useForm, SubmitHandler } from 'react-hook-form';
44
import Header from '../../../design-system/ui/Header';
55
import Button from '../../../design-system/ui/Button';
66
import UnderlineTextField from '../../../design-system/ui/textFields/UnderlineTextField';
77
import { FormData, zodValidation } from '../../shared/lib/formValidation';
8-
import { useAgreeTerms, useSendCertificationCode, useUserInfo, useUserUpdate, useVerifyCertificationCode } from '../../features/join/hooks/useUserHook';
8+
import { useAgreeTerms, useUserInfo, useUserUpdate } from '../../features/join/hooks/useUserHook';
99
import useAuthStore from '../../app/provider/authStore';
1010
import { formatPhoneNumber } from '../../shared/utils/phoneFormatter';
1111
import { useAgreementStore } from '../../features/join/model/agreementStore';
12+
import { usePhoneVerification } from '../../shared/utils/phoneVerification';
1213

1314
const InfoInputPage = () => {
1415
const { data, isLoading } = useUserInfo();
1516
const { login, setName } = useAuthStore();
1617
const { mutate: updateUser } = useUserUpdate();
17-
const { mutate: sendCode } = useSendCertificationCode();
18-
const { mutate: verifyCode } = useVerifyCertificationCode();
19-
const [isVerified, setIsVerified] = useState(false);
18+
const {
19+
isVerified,
20+
isVerifyVisible,
21+
verificationCode,
22+
setVerificationCode,
23+
timer,
24+
formatTime,
25+
requestVerificationCode,
26+
submitVerificationCode,
27+
} = usePhoneVerification();
2028

2129
const {
2230
register,
@@ -45,51 +53,6 @@ const InfoInputPage = () => {
4553
const formatted = formatPhoneNumber(e.target.value);
4654
setValue('phone', formatted, { shouldValidate: true });
4755
};
48-
// 전화번호 인증
49-
const [isVerifyVisible, setIsVerifyVisible] = useState(true);
50-
const [verificationCode, setVerificationCode] = useState('');
51-
const [timer, setTimer] = useState(0);
52-
//인증번호 발급
53-
const handlePhoneVerifyClick = () => {
54-
if (!phoneValue) {
55-
alert('연락처를 입력해주세요.');
56-
return;
57-
}
58-
//인증api호출
59-
sendCode({ phoneNumber: phoneValue }, {
60-
onSuccess: () => {
61-
setIsVerifyVisible(true);
62-
setTimer(180);
63-
}
64-
});
65-
};
66-
// 인증번호 확인
67-
const handleVerifySubmit = () => {
68-
if (!verificationCode || verificationCode.length !== 6) {
69-
alert('6자리 인증번호를 입력해주세요.');
70-
return;
71-
}
72-
verifyCode({ phoneNumber: phoneValue, certificationCode: verificationCode }, {
73-
onSuccess: () => {
74-
setIsVerifyVisible(false);
75-
setIsVerified(true);
76-
}
77-
});
78-
};
79-
80-
useEffect(() => {
81-
if (isVerifyVisible && timer > 0) {
82-
const interval = setInterval(() => {
83-
setTimer(prev => prev - 1);
84-
}, 1000);
85-
return () => clearInterval(interval);
86-
}
87-
}, [isVerifyVisible, timer]);
88-
const formatTime = (seconds: number) => {
89-
const m = String(Math.floor(seconds / 60)).padStart(2, '0');
90-
const s = String(seconds % 60).padStart(2, '0');
91-
return `${m}:${s}`;
92-
};
9356

9457
const onSubmit: SubmitHandler<FormData> = formData => {
9558
const agreementStates = getAgreementStates();
@@ -170,7 +133,7 @@ const InfoInputPage = () => {
170133
<Button
171134
type='button'
172135
label="인증하기"
173-
onClick={handlePhoneVerifyClick}
136+
onClick={() => requestVerificationCode(phoneValue)}
174137
className="h-11 md:h-11 sm:h-8 px-4 rounded-md"
175138
/>
176139
</div>
@@ -191,7 +154,7 @@ const InfoInputPage = () => {
191154
<Button
192155
type='button'
193156
label="인증확인"
194-
onClick={handleVerifySubmit}
157+
onClick={() => submitVerificationCode(phoneValue)}
195158
className="h-11 md:h-11 sm:h-8 px-4 rounded-md"
196159
/>
197160
</div>

src/shared/types/api/http-client.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ axiosClient.interceptors.response.use(
5656
return response;
5757
},
5858
async (error: AxiosError<ApiErrorResponse>) => {
59-
console.log(error)
6059
const errorInfo = {
6160
status: error.response?.status || 'NETWORK_ERROR',
6261
message: error.response?.data?.message || error.message,
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { useState, useEffect } from 'react';
2+
import { useSendCertificationCode, useVerifyCertificationCode } from '../../features/join/hooks/useUserHook';
3+
4+
export const usePhoneVerification = () => {
5+
const { mutate: sendCode } = useSendCertificationCode();
6+
const { mutate: verifyCode } = useVerifyCertificationCode();
7+
8+
const [isVerified, setIsVerified] = useState(false);
9+
const [isVerifyVisible, setIsVerifyVisible] = useState(false);
10+
const [verificationCode, setVerificationCode] = useState('');
11+
const [timer, setTimer] = useState(0);
12+
const resetVerification = () => {
13+
setIsVerified(false);
14+
setIsVerifyVisible(false);
15+
setVerificationCode('');
16+
setTimer(0);
17+
};
18+
// 인증번호 발송
19+
const requestVerificationCode = (phone: string) => {
20+
if (!phone) {
21+
alert('연락처를 입력해주세요.');
22+
return;
23+
}
24+
25+
sendCode(
26+
{ phoneNumber: phone },
27+
{
28+
onSuccess: () => {
29+
setIsVerifyVisible(true);
30+
setTimer(180);
31+
},
32+
}
33+
);
34+
};
35+
36+
// 인증번호 확인
37+
const submitVerificationCode = (phone: string) => {
38+
if (!verificationCode || verificationCode.length !== 6) {
39+
alert('6자리 인증번호를 입력해주세요.');
40+
return;
41+
}
42+
43+
verifyCode(
44+
{ phoneNumber: phone, certificationCode: verificationCode },
45+
{
46+
onSuccess: () => {
47+
setIsVerifyVisible(false);
48+
setIsVerified(true);
49+
},
50+
}
51+
);
52+
};
53+
54+
// 타이머 로직
55+
useEffect(() => {
56+
if (isVerifyVisible && timer > 0) {
57+
const interval = setInterval(() => {
58+
setTimer(prev => prev - 1);
59+
}, 1000);
60+
return () => clearInterval(interval);
61+
} else if (isVerifyVisible && timer === 0) {
62+
alert('인증 시간이 만료되었습니다. 다시 인증해주세요.');
63+
setIsVerifyVisible(false);
64+
}
65+
}, [isVerifyVisible, timer]);
66+
67+
const formatTime = (seconds: number) => {
68+
const m = String(Math.floor(seconds / 60)).padStart(2, '0');
69+
const s = String(seconds % 60).padStart(2, '0');
70+
return `${m}:${s}`;
71+
};
72+
73+
return {
74+
isVerified,
75+
isVerifyVisible,
76+
verificationCode,
77+
setVerificationCode,
78+
timer,
79+
formatTime,
80+
requestVerificationCode,
81+
submitVerificationCode,
82+
setIsVerified,
83+
setIsVerifyVisible,
84+
resetVerification
85+
};
86+
};

0 commit comments

Comments
 (0)