chore: move api to another component

This commit is contained in:
Koosha Lahouti
2025-08-14 09:06:53 +03:30
parent d42913bced
commit 5a72b597c8
9 changed files with 390 additions and 372 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useRef, useEffect } from 'react';
import { useState, useRef, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import parsePhoneNumberFromString from 'libphonenumber-js';
import { PageWrapper } from '../PageWrapper';
@@ -6,9 +6,15 @@ import { CardContainer } from '@/components/CardContainer';
import PhoneDisplay from './phoneNumber/PhoneDisplay';
import PhoneEditForm from './phoneNumber/PhoneEditForm';
import PhoneActionButtons from './phoneNumber/PhoneActionButtons';
import apiClient from '@/lib/apiClient';
import { CircularProgress, Box, Typography } from '@mui/material';
import { toLocaleDigits } from '@/utils/persianDigit';
import { useManualApi } from '@/hooks/useApi';
import {
fetchProfile,
sendVerificationCode,
confirmPhoneNumberCode,
changePhoneNumber,
} from '../../api/settingsApi';
interface Phone {
phone: string;
@@ -16,21 +22,6 @@ interface Phone {
withCode: string;
}
interface GetProfileApiResponse {
success: boolean;
message?: string;
phoneNumber?: string;
}
interface ApiResponse {
success: boolean;
message?: string;
}
interface ConfirmApiResponse extends ApiResponse {
confirm?: boolean;
}
export function PhoneNumber() {
const { t, i18n } = useTranslation('setting');
const [isEditing, setIsEditing] = useState(false);
@@ -40,185 +31,158 @@ export function PhoneNumber() {
const [buttonState, setButtonState] = useState<'default' | 'counting'>(
'default',
);
const [isVerifying, setIsVerifying] = useState(false);
const [isVerified, setIsVerified] = useState(false);
const [phones, setPhone] = useState<Phone[]>([]);
const [phones, setPhones] = useState<Phone[]>([]);
const [countryCode, setCountryCode] = useState('+98');
const [formError, setFormError] = useState<string>();
const [touched, setTouched] = useState<boolean>(false);
const textFieldRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const [error, setError] = useState<string>();
const [touched, setTouched] = useState<boolean>(false);
const inputError: boolean = touched && !!error;
const token = localStorage.getItem('authToken');
const [isLoading, setIsLoading] = useState(true);
const [fetchError, setFetchError] = useState<string | null>(null);
const {
data: profileData,
loading: isLoading,
error: fetchError,
execute: executeFetchProfile,
} = useManualApi(fetchProfile);
const {
data: sendCodeData,
loading: isSendingCode,
error: sendCodeError,
execute: executeSendCode,
} = useManualApi(sendVerificationCode);
const {
data: confirmData,
loading: isVerifying,
error: confirmError,
execute: executeConfirmCode,
} = useManualApi(confirmPhoneNumberCode);
const {
data: changePhoneData,
loading: isChangingPhone,
error: changePhoneError,
execute: executeChangePhone,
} = useManualApi(changePhoneNumber);
useEffect(() => {
const fetchPhoneNumber = async () => {
setIsLoading(true);
setFetchError(null);
if (!token) {
setIsLoading(false);
setFetchError(t('settingForm.notLoggedIn'));
return;
}
try {
const res = await apiClient.post<GetProfileApiResponse>(
'/Profile/GetProfile',
{},
{ headers: { Authorization: `Bearer ${token}` } },
);
if (!isEditing) {
executeFetchProfile();
}
}, [isEditing]);
if (res.data.success && res.data.phoneNumber) {
setPhone([
{
phone: res.data.phoneNumber,
time: '',
withCode: res.data.phoneNumber,
},
]);
} else if (!res.data.success) {
throw new Error(
res.data.message || t('settingForm.failFetchPhoneNumber'),
);
}
} catch (err: unknown) {
let message = t('settingForm.errorFetchPhoneNumber');
if (err instanceof Error) {
message = err.message;
}
setFetchError(message);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
if (profileData?.success && profileData.phoneNumber) {
setPhones([
{
phone: profileData.phoneNumber,
time: '',
withCode: profileData.phoneNumber,
},
]);
}
}, [profileData]);
fetchPhoneNumber();
}, [token, t]);
useEffect(() => {
if (sendCodeData?.success) {
setButtonState('counting');
setIsVerified(false);
}
}, [sendCodeData]);
useEffect(() => {
if (confirmData?.success && confirmData.confirm) {
setIsVerified(true);
setShowToast(true);
const fullPhoneNumber = countryCode + phoneNumber.replace(/^0/, '');
executeChangePhone({ phoneNumber: fullPhoneNumber });
}
}, [confirmData, countryCode, phoneNumber, executeChangePhone]);
useEffect(() => {
if (changePhoneData?.success) {
const fullPhoneNumber = countryCode + phoneNumber.replace(/^0/, '');
setPhones([
{
phone: phoneNumber,
time: t('settingForm.justNow'),
withCode: fullPhoneNumber,
},
]);
setIsEditing(false);
}
}, [changePhoneData, countryCode, phoneNumber, t]);
const apiError = useMemo(
() => sendCodeError || confirmError || changePhoneError,
[sendCodeError, confirmError, changePhoneError],
);
const getErrorMessage = (error: unknown): string | undefined => {
if (!error) return undefined;
if (error instanceof Error) return error.message;
return String(error);
};
const isPhoneValid = (code: string, phone: string) => {
const phoneNum = parsePhoneNumberFromString(code + phone);
return phoneNum && phoneNum.isValid();
return phoneNum?.isValid();
};
const handleBlur = () => {
setTouched(true);
if (!phoneNumber) {
setError(t('settingForm.thisFieldIsRequired'));
setFormError(t('settingForm.thisFieldIsRequired'));
return;
}
if (!isPhoneValid(countryCode, phoneNumber)) {
setError(t('settingForm.phoneNumberIsInvalid'));
setFormError(t('settingForm.phoneNumberIsInvalid'));
} else {
setError(undefined);
setFormError(undefined);
}
};
const toggleEdit = () => {
setIsEditing((prev) => {
const enteringEditMode = !prev;
if (enteringEditMode) {
if (!prev) {
setPhoneNumber('');
setVerificationCode('');
setIsVerified(false);
setButtonState('default');
setShowToast(false);
setError(undefined);
setFormError(undefined);
setTouched(false);
}
return enteringEditMode;
return !prev;
});
};
const handleSendCode = async () => {
if (!phoneNumber) return;
if (!isPhoneValid(countryCode, phoneNumber)) {
setError(t('settingForm.phoneNumberIsInvalid'));
return;
}
setError(undefined);
const handleSendCode = () => {
handleBlur();
if (formError || !isPhoneValid(countryCode, phoneNumber)) return;
try {
const res = await apiClient.post<ApiResponse>(
'/Profile/SendVerfiyPhoneNumberCode',
{
phoneNumber: countryCode + phoneNumber.replace(/^0/, ''),
},
{ headers: { Authorization: `Bearer ${token}` } },
);
if (res.data.success) {
setButtonState('counting');
setIsVerified(false);
} else {
setError(res.data.message || t('settingForm.sendCodeFailed'));
}
} catch (error: unknown) {
setError(t('settingForm.sendCodeFailed'));
}
executeSendCode({
phoneNumber: countryCode + phoneNumber.replace(/^0/, ''),
});
};
const handleVerifyCode = async () => {
const handleVerifyCode = () => {
if (!verificationCode) {
setError(t('settingForm.verificationCodeRequired'));
setFormError(t('settingForm.verificationCodeRequired'));
return;
}
setIsVerifying(true);
setError(undefined);
try {
const res = await apiClient.post<ConfirmApiResponse>(
'/Profile/ConfirmPhoneNumberChangeCode',
{
phoneNumber: countryCode + phoneNumber.replace(/^0/, ''),
verifyCode: verificationCode,
},
{ headers: { Authorization: `Bearer ${token}` } },
);
if (res.data.success && res.data.confirm) {
setIsVerified(true);
setShowToast(true);
await handleChangePhoneNumber();
} else {
setError(res.data.message || t('settingForm.verifyCodeFailed'));
setIsVerified(false);
}
} catch (error: unknown) {
setError(t('settingForm.verifyCodeFailed'));
setIsVerified(false);
} finally {
setIsVerifying(false);
}
setFormError(undefined);
executeConfirmCode({
phoneNumber: countryCode + phoneNumber.replace(/^0/, ''),
verifyCode: verificationCode,
});
};
const handleChangePhoneNumber = async () => {
try {
const fullPhoneNumber = countryCode + phoneNumber.replace(/^0/, '');
const res = await apiClient.post<ApiResponse>(
'/Profile/ChangePhoneNumber',
{
phoneNumber: fullPhoneNumber,
},
{ headers: { Authorization: `Bearer ${token}` } },
);
if (res.data.success) {
setPhone([
{
phone: phoneNumber,
time: t('settingForm.justNow'),
withCode: fullPhoneNumber,
},
]);
setIsEditing(false);
} else {
setError(res.data.message || t('settingForm.changePhoneFailed'));
}
} catch (error: unknown) {
setError(t('settingForm.changePhoneFailed'));
}
};
const combinedError = formError || getErrorMessage(apiError);
const inputError: boolean = touched && !!combinedError;
return (
<PageWrapper>
@@ -248,7 +212,10 @@ export function PhoneNumber() {
</Box>
) : fetchError ? (
<Box sx={{ textAlign: 'center', p: 4 }}>
<Typography color="error">{fetchError}</Typography>
<Typography color="error">
{getErrorMessage(fetchError) ||
t('settingForm.errorFetchPhoneNumber')}
</Typography>
</Box>
) : isEditing ? (
<PhoneEditForm
@@ -259,12 +226,12 @@ export function PhoneNumber() {
verificationCode={verificationCode}
setVerificationCode={setVerificationCode}
isVerified={isVerified}
isVerifying={isVerifying}
isVerifying={isVerifying || isSendingCode || isChangingPhone}
buttonState={buttonState}
setButtonState={setButtonState}
handleSendCode={handleSendCode}
handleVerifyClick={handleVerifyCode}
error={error}
error={combinedError}
inputError={inputError}
handleBlur={handleBlur}
textFieldRef={textFieldRef as React.RefObject<HTMLDivElement>}