chore: change styles and add Api for user profile

This commit is contained in:
Koosha Lahouti
2025-08-12 20:20:28 +03:30
parent ed57858c2e
commit 782ef2a2de
35 changed files with 2785 additions and 1843 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useRef } from 'react';
import { useState, useRef, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import parsePhoneNumberFromString from 'libphonenumber-js';
import { PageWrapper } from '../PageWrapper';
@@ -6,9 +6,32 @@ 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';
interface Phone {
phone: string;
time: string;
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 } = useTranslation('profileSetting');
const { t } = useTranslation('setting');
const [isEditing, setIsEditing] = useState(false);
const [phoneNumber, setPhoneNumber] = useState('');
const [verificationCode, setVerificationCode] = useState('');
@@ -18,15 +41,60 @@ export function PhoneNumber() {
);
const [isVerifying, setIsVerifying] = useState(false);
const [isVerified, setIsVerified] = useState(false);
const [phones, setPhone] = useState([
{ phone: '09123456789', time: '۱ ماه پیش', withCode: '+989123456789' },
]);
const [phones, setPhone] = useState<Phone[]>([]);
const [countryCode, setCountryCode] = useState('+98');
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);
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 (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);
}
};
fetchPhoneNumber();
}, [token, t]);
const isPhoneValid = (code: string, phone: string) => {
const phoneNum = parsePhoneNumberFromString(code + phone);
@@ -37,6 +105,7 @@ export function PhoneNumber() {
setTouched(true);
if (!phoneNumber) {
setError(t('settingForm.thisFieldIsRequired'));
return;
}
if (!isPhoneValid(countryCode, phoneNumber)) {
setError(t('settingForm.phoneNumberIsInvalid'));
@@ -54,34 +123,100 @@ export function PhoneNumber() {
setIsVerified(false);
setButtonState('default');
setShowToast(false);
setError(undefined);
setTouched(false);
}
return enteringEditMode;
});
};
const handleSendCode = () => {
const handleSendCode = async () => {
if (!phoneNumber) return;
setButtonState('counting');
setIsVerified(false);
if (!isPhoneValid(countryCode, phoneNumber)) {
setError(t('settingForm.phoneNumberIsInvalid'));
return;
}
setError(undefined);
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'));
}
};
const handleVerifyCode = () => {
const handleVerifyCode = async () => {
if (!verificationCode) {
setError(t('settingForm.verificationCodeRequired'));
return;
}
setIsVerifying(true);
setTimeout(() => {
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);
setIsVerified(true);
setShowToast(true);
const newPhone = '+98' + phoneNumber.slice(1);
setPhone([
{ phone: phoneNumber, time: 'چند ثانیه پیش', withCode: newPhone },
]);
}, 1500);
}
};
const handleVerifyClick = () => {
if (!verificationCode || isVerifying) return;
handleVerifyCode();
setTimeout(() => setShowToast(true), 1600);
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'));
}
};
return (
@@ -98,7 +233,23 @@ export function PhoneNumber() {
/>
}
>
{isEditing ? (
{isLoading ? (
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
p: 4,
minHeight: '150px',
}}
>
<CircularProgress />
</Box>
) : fetchError ? (
<Box sx={{ textAlign: 'center', p: 4 }}>
<Typography color="error">{fetchError}</Typography>
</Box>
) : isEditing ? (
<PhoneEditForm
phoneNumber={phoneNumber}
setPhoneNumber={setPhoneNumber}
@@ -111,7 +262,7 @@ export function PhoneNumber() {
buttonState={buttonState}
setButtonState={setButtonState}
handleSendCode={handleSendCode}
handleVerifyClick={handleVerifyClick}
handleVerifyClick={handleVerifyCode}
error={error}
inputError={inputError}
handleBlur={handleBlur}