fix: dashboard api calls, multi profile fetch
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useRef, useEffect, useMemo } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import parsePhoneNumberFromString from 'libphonenumber-js';
|
||||
import { PageWrapper } from '../PageWrapper';
|
||||
@@ -6,17 +6,17 @@ import { CardContainer } from '@/components/CardContainer';
|
||||
import PhoneDisplay from './phoneNumber/PhoneDisplay';
|
||||
import PhoneEditForm from './phoneNumber/PhoneEditForm';
|
||||
import PhoneActionButtons from './phoneNumber/PhoneActionButtons';
|
||||
import { CircularProgress, Box, Typography } from '@mui/material';
|
||||
import { CircularProgress, Box } from '@mui/material';
|
||||
import { toLocaleDigits } from '@/utils/persianDigit';
|
||||
import { useApi } from '@/hooks/useApi';
|
||||
import {
|
||||
fetchProfile,
|
||||
sendVerificationCode,
|
||||
confirmPhoneNumberCode,
|
||||
changePhoneNumber,
|
||||
} from '../../api/settingsApi';
|
||||
import { type Phone } from '../../types/settingsType';
|
||||
import { useToast } from '@rkheftan/harmony-ui';
|
||||
import { useProfile } from '../../hooks/useProfile';
|
||||
|
||||
export function PhoneNumber() {
|
||||
const { t, i18n } = useTranslation('setting');
|
||||
@@ -24,167 +24,87 @@ export function PhoneNumber() {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [phoneNumber, setPhoneNumber] = useState('');
|
||||
const [verificationCode, setVerificationCode] = useState('');
|
||||
const [showToast, setShowToast] = useState(false);
|
||||
const [buttonState, setButtonState] = useState<'default' | 'counting'>(
|
||||
'default',
|
||||
);
|
||||
const [isVerified, setIsVerified] = useState(false);
|
||||
const [phones, setPhones] = useState<Phone[]>([]);
|
||||
const [countryCode, setCountryCode] = useState('+98');
|
||||
const [formError, setFormError] = useState<string>();
|
||||
const [touched, setTouched] = useState<boolean>(false);
|
||||
const [phoneNumberError, setPhoneNumberError] = useState<string>();
|
||||
const [verificationCodeError, setVerificationCodeError] = useState<string>();
|
||||
const [phoneNumberTouched, setPhoneNumberTouched] = useState<boolean>(false);
|
||||
const [verificationCodeTouched, setVerificationCodeTouched] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const textFieldRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const { isLoadingProfile, refetchProfile } = useProfile();
|
||||
|
||||
const {
|
||||
data: profileData,
|
||||
loading: isLoading,
|
||||
error: fetchError,
|
||||
} = useApi(fetchProfile, { immediate: true });
|
||||
const { loading: isSendingCode, execute: executeSendCode } =
|
||||
useApi(sendVerificationCode);
|
||||
|
||||
const {
|
||||
data: sendCodeData,
|
||||
loading: isSendingCode,
|
||||
error: sendCodeError,
|
||||
execute: executeSendCode,
|
||||
} = useApi(sendVerificationCode);
|
||||
|
||||
const {
|
||||
data: confirmData,
|
||||
loading: isVerifying,
|
||||
error: confirmError,
|
||||
execute: executeConfirmCode,
|
||||
} = useApi(confirmPhoneNumberCode);
|
||||
|
||||
const {
|
||||
data: changePhoneData,
|
||||
loading: isChangingPhone,
|
||||
error: changePhoneError,
|
||||
execute: executeChangePhone,
|
||||
} = useApi(changePhoneNumber);
|
||||
|
||||
useEffect(() => {
|
||||
if (profileData?.success && profileData.phoneNumber) {
|
||||
setPhones([
|
||||
{
|
||||
phone: profileData.phoneNumber,
|
||||
time: '',
|
||||
withCode: profileData.phoneNumber,
|
||||
},
|
||||
]);
|
||||
}
|
||||
}, [profileData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (fetchError) {
|
||||
toast({
|
||||
message:
|
||||
getErrorMessage(fetchError) || t('settingForm.errorFetchPhoneNumber'),
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
}, [fetchError, toast, t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (sendCodeData?.success) {
|
||||
setButtonState('counting');
|
||||
setIsVerified(false);
|
||||
toast({
|
||||
message: t('settingForm.codeSentSuccessfully'),
|
||||
severity: 'success',
|
||||
});
|
||||
}
|
||||
}, [sendCodeData, t, toast]);
|
||||
|
||||
useEffect(() => {
|
||||
if (sendCodeError) {
|
||||
toast({
|
||||
message:
|
||||
getErrorMessage(sendCodeError) || t('settingForm.errorSendCode'),
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
}, [sendCodeError, toast, t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (confirmData?.success && confirmData.confirm) {
|
||||
setIsVerified(true);
|
||||
setShowToast(true);
|
||||
const fullPhoneNumber = countryCode + phoneNumber.replace(/^0/, '');
|
||||
executeChangePhone({ phoneNumber: fullPhoneNumber });
|
||||
toast({
|
||||
message: t('settingForm.phoneVerified'),
|
||||
severity: 'success',
|
||||
});
|
||||
}
|
||||
}, [confirmData, countryCode, phoneNumber, executeChangePhone, t, toast]);
|
||||
|
||||
useEffect(() => {
|
||||
if (confirmError) {
|
||||
toast({
|
||||
message:
|
||||
getErrorMessage(confirmError) || t('settingForm.errorConfirmCode'),
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
}, [confirmError, toast, t]);
|
||||
|
||||
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]);
|
||||
|
||||
useEffect(() => {
|
||||
if (changePhoneError) {
|
||||
toast({
|
||||
message:
|
||||
getErrorMessage(changePhoneError) ||
|
||||
t('settingForm.errorChangePhone'),
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
}, [changePhoneError, toast, t]);
|
||||
|
||||
const apiError = useMemo(
|
||||
() => sendCodeError || confirmError || changePhoneError,
|
||||
[sendCodeError, confirmError, changePhoneError],
|
||||
const { loading: isVerifying, execute: executeConfirmCode } = useApi(
|
||||
confirmPhoneNumberCode,
|
||||
);
|
||||
|
||||
const getErrorMessage = (error: unknown): string | undefined => {
|
||||
if (!error) return undefined;
|
||||
if (error instanceof Error) return error.message;
|
||||
return String(error);
|
||||
};
|
||||
const { loading: isChangingPhone, execute: executeChangePhone } =
|
||||
useApi(changePhoneNumber);
|
||||
|
||||
useEffect(() => {
|
||||
const loadProfile = async () => {
|
||||
const profileData = await refetchProfile();
|
||||
|
||||
if (!profileData) return;
|
||||
|
||||
if (profileData?.success) {
|
||||
setPhones([
|
||||
{
|
||||
phone: profileData.phoneNumber,
|
||||
time: '',
|
||||
withCode: profileData.phoneNumber,
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
toast({
|
||||
message: profileData.message,
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
};
|
||||
loadProfile();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!phoneNumber) {
|
||||
setPhoneNumberError(t('settingForm.thisFieldIsRequired'));
|
||||
return;
|
||||
}
|
||||
if (!isPhoneValid(countryCode, phoneNumber)) {
|
||||
setPhoneNumberError(t('settingForm.phoneNumberIsInvalid'));
|
||||
} else {
|
||||
setPhoneNumberError(undefined);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [countryCode, phoneNumber, phoneNumberTouched]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!verificationCode) {
|
||||
setVerificationCodeError(t('settingForm.verificationCodeRequired'));
|
||||
return;
|
||||
}
|
||||
setVerificationCodeError(undefined);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [verificationCode, verificationCodeTouched]);
|
||||
|
||||
const isPhoneValid = (code: string, phone: string) => {
|
||||
const phoneNum = parsePhoneNumberFromString(code + phone);
|
||||
return phoneNum?.isValid();
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
setTouched(true);
|
||||
if (!phoneNumber) {
|
||||
toast({
|
||||
message: t('settingForm.phoneNumberIsInvalid'),
|
||||
severity: 'error',
|
||||
});
|
||||
setFormError(t('settingForm.thisFieldIsRequired'));
|
||||
return;
|
||||
}
|
||||
if (!isPhoneValid(countryCode, phoneNumber)) {
|
||||
setFormError(t('settingForm.phoneNumberIsInvalid'));
|
||||
const handleBlur = (key: string) => {
|
||||
if (key === 'phoneNumber') {
|
||||
setPhoneNumberTouched(true);
|
||||
} else {
|
||||
setFormError(undefined);
|
||||
setVerificationCodeTouched(true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -195,36 +115,91 @@ export function PhoneNumber() {
|
||||
setVerificationCode('');
|
||||
setIsVerified(false);
|
||||
setButtonState('default');
|
||||
setShowToast(false);
|
||||
setFormError(undefined);
|
||||
setTouched(false);
|
||||
setPhoneNumberError(undefined);
|
||||
setPhoneNumberTouched(false);
|
||||
setVerificationCodeError(undefined);
|
||||
setVerificationCodeTouched(false);
|
||||
}
|
||||
return !prev;
|
||||
});
|
||||
};
|
||||
|
||||
const handleSendCode = async () => {
|
||||
handleBlur();
|
||||
if (formError || !isPhoneValid(countryCode, phoneNumber)) return;
|
||||
return executeSendCode({
|
||||
setPhoneNumberTouched(true);
|
||||
if (phoneNumberError) return;
|
||||
|
||||
const result = await executeSendCode({
|
||||
phoneNumber: countryCode + phoneNumber.replace(/^0/, ''),
|
||||
});
|
||||
|
||||
if (!result) return;
|
||||
|
||||
if (result?.success) {
|
||||
setButtonState('counting');
|
||||
setIsVerified(false);
|
||||
toast({
|
||||
message: result.message || t('settingForm.codeSentSuccessfully'),
|
||||
severity: 'success',
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
message: result.message || t('message.serverError'),
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleVerifyCode = () => {
|
||||
if (!verificationCode) {
|
||||
setFormError(t('settingForm.verificationCodeRequired'));
|
||||
return;
|
||||
}
|
||||
setFormError(undefined);
|
||||
executeConfirmCode({
|
||||
phoneNumber: countryCode + phoneNumber.replace(/^0/, ''),
|
||||
const handleVerifyCode = async () => {
|
||||
setVerificationCodeTouched(true);
|
||||
|
||||
if (verificationCodeError) return;
|
||||
|
||||
const fullPhoneNumber = countryCode + phoneNumber.replace(/^0/, '');
|
||||
|
||||
const confirmResult = await executeConfirmCode({
|
||||
phoneNumber: fullPhoneNumber,
|
||||
verifyCode: verificationCode,
|
||||
});
|
||||
};
|
||||
|
||||
const combinedError = formError || getErrorMessage(apiError);
|
||||
const inputError: boolean = touched && !!combinedError;
|
||||
if (!confirmResult) return;
|
||||
|
||||
if (!confirmResult.success || !confirmResult.confirm) {
|
||||
toast({
|
||||
message: confirmResult?.message || t('message.serverError'),
|
||||
severity: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsVerified(true);
|
||||
toast({
|
||||
message: t('settingForm.phoneVerified'),
|
||||
severity: 'success',
|
||||
});
|
||||
|
||||
const changeResult = await executeChangePhone({
|
||||
phoneNumber: fullPhoneNumber,
|
||||
});
|
||||
|
||||
if (!changeResult) return;
|
||||
|
||||
if (changeResult?.success) {
|
||||
setPhones([
|
||||
{
|
||||
phone: phoneNumber,
|
||||
time: t('settingForm.justNow'),
|
||||
withCode: fullPhoneNumber,
|
||||
},
|
||||
]);
|
||||
setIsEditing(false);
|
||||
refetchProfile({ force: true });
|
||||
} else {
|
||||
toast({
|
||||
message: changeResult?.message || t('message.serverError'),
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
@@ -240,7 +215,7 @@ export function PhoneNumber() {
|
||||
/>
|
||||
}
|
||||
>
|
||||
{isLoading ? (
|
||||
{isLoadingProfile ? (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@@ -252,13 +227,6 @@ export function PhoneNumber() {
|
||||
>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : fetchError ? (
|
||||
<Box sx={{ textAlign: 'center', p: 4 }}>
|
||||
<Typography color="error">
|
||||
{getErrorMessage(fetchError) ||
|
||||
t('settingForm.errorFetchPhoneNumber')}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : isEditing ? (
|
||||
<PhoneEditForm
|
||||
phoneNumber={phoneNumber}
|
||||
@@ -269,19 +237,17 @@ export function PhoneNumber() {
|
||||
setVerificationCode={setVerificationCode}
|
||||
isVerified={isVerified}
|
||||
isVerifying={isVerifying || isSendingCode || isChangingPhone}
|
||||
isSendingCode={isSendingCode}
|
||||
buttonState={buttonState}
|
||||
setButtonState={setButtonState}
|
||||
handleSendCode={handleSendCode}
|
||||
handleVerifyClick={handleVerifyCode}
|
||||
error={combinedError}
|
||||
inputError={inputError}
|
||||
phoneNumberError={phoneNumberTouched ? phoneNumberError : undefined}
|
||||
verificationCodeError={
|
||||
verificationCodeTouched ? verificationCodeError : undefined
|
||||
}
|
||||
handleBlur={handleBlur}
|
||||
textFieldRef={textFieldRef as React.RefObject<HTMLDivElement>}
|
||||
inputRef={inputRef as React.RefObject<HTMLInputElement>}
|
||||
phones={phones}
|
||||
showToast={showToast}
|
||||
setShowToast={setShowToast}
|
||||
t={t}
|
||||
/>
|
||||
) : (
|
||||
<PhoneDisplay
|
||||
|
||||
Reference in New Issue
Block a user