diff --git a/public/locales/en/sideNav.json b/public/locales/en/sideNav.json
index bdb6e8a..4057675 100644
--- a/public/locales/en/sideNav.json
+++ b/public/locales/en/sideNav.json
@@ -2,13 +2,12 @@
"side": {
"account": "Account",
"personalInfo": "Personal information",
- "phoneNumber": "Phone number",
"contactInfo": "Contact info",
"email": "Email",
"security": "Security",
"password": "Password",
- "recentSessions": "Recent Sessions",
- "activeDevices": "Active devices",
- "settings": "Settings"
+ "recentSessions": "Recent sessions",
+ "activeSessions": "Active sessions",
+ "setting": "Settings"
}
}
diff --git a/src/components/Loading.tsx b/src/components/Loading.tsx
index 6790bd5..d0e8b82 100644
--- a/src/components/Loading.tsx
+++ b/src/components/Loading.tsx
@@ -6,7 +6,6 @@ export const Loading = () => {
sx={{
alignItems: 'center',
justifyContent: 'center',
- backgroundColor: 'background.default',
position: 'fixed',
inset: '0',
zIndex: (t) => t.zIndex.tooltip + 1,
diff --git a/src/components/NavigateWithToast.tsx b/src/components/NavigateWithToast.tsx
index 51a133f..d62e277 100644
--- a/src/components/NavigateWithToast.tsx
+++ b/src/components/NavigateWithToast.tsx
@@ -1,5 +1,5 @@
import { useToast, type ToastOptions } from '@rkheftan/harmony-ui';
-import React, { useEffect } from 'react';
+import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Navigate } from 'react-router-dom';
diff --git a/src/config/i18n.ts b/src/config/i18n.ts
index 1723690..97fd431 100644
--- a/src/config/i18n.ts
+++ b/src/config/i18n.ts
@@ -9,9 +9,9 @@ i18n
.use(initReactI18next) // Passes i18n down to react-i18next
.init({
supportedLngs: ['en', 'fa'], // Supported languages
- fallbackLng: 'en',
+ fallbackLng: 'fa',
detection: {
- order: ['localStorage', 'cookie', 'navigator'],
+ order: ['localStorage', 'cookie'],
caches: ['localStorage', 'cookie'],
lookupLocalStorage: 'language', // The key to use in localStorage
},
diff --git a/src/features/authentication/routes/UserCompletionPage.tsx b/src/features/authentication/routes/UserCompletionPage.tsx
index 57e43e5..66bfb79 100644
--- a/src/features/authentication/routes/UserCompletionPage.tsx
+++ b/src/features/authentication/routes/UserCompletionPage.tsx
@@ -188,13 +188,13 @@ export function UserCompletionPage() {
firstName,
lastName,
gender: sex || 0,
- nationalId,
+ nationalCode: nationalId,
savePassword: showPasswordSection,
password: showPasswordSection ? password : undefined,
saveEmail: showEmail,
email: showEmail ? email : undefined,
birthDate,
- country,
+ countryCode: country,
});
if (res) {
@@ -351,6 +351,7 @@ export function UserCompletionPage() {
if (Object.keys(touched).length > 0) {
validateForm();
}
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [
firstName,
lastName,
diff --git a/src/features/authentication/types/completionFormApiTypes.ts b/src/features/authentication/types/completionFormApiTypes.ts
index 7dc8821..db01f60 100644
--- a/src/features/authentication/types/completionFormApiTypes.ts
+++ b/src/features/authentication/types/completionFormApiTypes.ts
@@ -18,9 +18,9 @@ export interface CompleteUserInfoPayload {
lastName: string;
// TODO: fix this
gender: 0 | 1 | 2;
- nationalId: string;
+ nationalCode: string;
birthDate: Date | null;
- country: string;
+ countryCode: string;
savePassword?: boolean;
password?: string;
saveEmail?: boolean;
diff --git a/src/features/profile/components/security/PasswordDialog.tsx b/src/features/profile/components/security/PasswordDialog.tsx
index 2aefd9b..5920786 100644
--- a/src/features/profile/components/security/PasswordDialog.tsx
+++ b/src/features/profile/components/security/PasswordDialog.tsx
@@ -9,7 +9,6 @@ import {
Button,
Link,
CircularProgress,
- Typography,
InputAdornment,
} from '@mui/material';
import { CloseCircle, Eye, EyeSlash } from 'iconsax-react';
@@ -18,6 +17,7 @@ import { type PasswordDialogProps } from '../../types/settingsType';
import { PasswordValidationItem } from './PasswordValidation';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
export function PasswordDialog({
open,
@@ -34,13 +34,13 @@ export function PasswordDialog({
loading,
handleSubmit,
changePassword,
- apiError,
- t,
hasMinLength,
hasNumber,
hasUpperAndLower,
hasSpecialChar,
}: PasswordDialogProps) {
+ const { t } = useTranslation('setting');
+
const [showCurrent, setShowCurrent] = useState(false);
const [showNew, setShowNew] = useState(false);
const [showConfirm, setShowConfirm] = useState(false);
@@ -77,12 +77,6 @@ export function PasswordDialog({
px: { xs: 2, sm: 3 },
}}
>
- {apiError && (
-
- {apiError}
-
- )}
-
{changePassword && (
<>
{
- if (profileData?.success) {
- setChangePasswordUI(profileData.hasPassword || false);
- }
- }, [profileData]);
+ const loadProfile = async () => {
+ const profileData = await refetchProfile();
+
+ if (!profileData) return;
+
+ if (profileData.success) {
+ setChangePasswordUI(profileData.hasPassword || false);
+ } else {
+ showToast({
+ message: profileData.message || t('message.serverError'),
+ severity: 'error',
+ });
+ }
+ };
+
+ loadProfile();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
useEffect(() => {
if (password) {
@@ -79,30 +78,6 @@ export function PasswordSecurity() {
}
}, [password, validPassword]);
- useEffect(() => {
- if (addData?.success || changeData?.success) {
- showToast({
- message: changePasswordUI
- ? t('securityForm.passwordChanged')
- : t('securityForm.passwordAdded'),
- severity: 'success',
- });
- if (!changePasswordUI) {
- setChangePasswordUI(true);
- }
- setOpen(false);
- setPassword('');
- setConfirmPassword('');
- setCurrentPassword('');
- } else if (addData || changeData) {
- showToast({
- message:
- addData?.message || changeData?.message || t('securityForm.error'),
- severity: 'error',
- });
- }
- }, [addData, changeData, changePasswordUI, showToast, t]);
-
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
@@ -115,26 +90,48 @@ export function PasswordSecurity() {
return;
}
if (changePasswordUI) {
- await executeChangePassword({
+ const changeData = await executeChangePassword({
oldPassword: currentPassword,
newPassword: password,
});
+
+ if (!changeData) return;
+
+ if (!changeData.success) {
+ showToast({
+ message: changeData?.message || t('securityForm.error'),
+ severity: 'error',
+ });
+ return;
+ }
} else {
- await executeAddPassword({ newPassword: password });
+ const addData = await executeAddPassword({ newPassword: password });
+
+ if (!addData) return;
+
+ if (!addData.success) {
+ showToast({
+ message: addData?.message || t('securityForm.error'),
+ severity: 'error',
+ });
+ return;
+ }
}
+ showToast({
+ message: changePasswordUI
+ ? t('securityForm.passwordChanged')
+ : t('securityForm.passwordAdded'),
+ severity: 'success',
+ });
+ if (!changePasswordUI) {
+ setChangePasswordUI(true);
+ }
+ setOpen(false);
+ setPassword('');
+ setConfirmPassword('');
+ setCurrentPassword('');
};
- const getErrorMessage = (error: unknown): string | null => {
- if (!error) return null;
- if (error instanceof Error) return error.message;
- return String(error);
- };
-
- const apiError = useMemo(
- () => addError || changeError,
- [addError, changeError],
- );
-
return (
}
>
- {isFetching ? (
+ {isLoadingProfile ? (
- ) : fetchError ? (
-
-
- {getErrorMessage(fetchError)}
-
-
) : (
{changePasswordUI ? (
@@ -208,8 +199,6 @@ export function PasswordSecurity() {
loading={isAdding || isChanging}
handleSubmit={handlePasswordSubmit}
changePassword={changePasswordUI}
- apiError={getErrorMessage(apiError)}
- t={t}
hasMinLength={hasMinLength}
hasNumber={hasNumber}
hasUpperAndLower={hasUpperAndLower}
diff --git a/src/features/profile/components/security/RecentLogins.tsx b/src/features/profile/components/security/RecentLogins.tsx
index 3feefca..7ce6ade 100644
--- a/src/features/profile/components/security/RecentLogins.tsx
+++ b/src/features/profile/components/security/RecentLogins.tsx
@@ -9,11 +9,10 @@ import {
import { useTranslation } from 'react-i18next';
import { CardContainer } from '@/components/CardContainer';
import { PageWrapper } from '../PageWrapper';
-import { fetchProfile } from '../../api/settingsApi';
import type { LoginLog } from '../../types/settingsApiType';
-import { useApi } from '@/hooks/useApi';
import { formatDate } from '@/utils/formatSessionDate';
import { useToast } from '@rkheftan/harmony-ui';
+import { useProfile } from '../../hooks/useProfile';
export function RecentLogins() {
const { t, i18n } = useTranslation('setting');
@@ -22,31 +21,43 @@ export function RecentLogins() {
const isXsup = useMediaQuery(theme.breakpoints.up('xs'));
const showToast = useToast();
- const {
- data: profileData,
- loading: isLoading,
- error: fetchError,
- } = useApi(fetchProfile, { immediate: true });
+ const { isLoadingProfile, refetchProfile } = useProfile();
+
+ // useEffect(() => {
+ // if (profileData?.success && Array.isArray(profileData.loginLogs)) {
+ // setLogs(profileData.loginLogs);
+ // }
+ // }, [profileData]);
+
+ // useEffect(() => {
+ // if (fetchError) {
+ // showToast({
+ // message: getErrorMessage(fetchError) || t('active.errorFetch'),
+ // severity: 'error',
+ // });
+ // }
+ // }, [fetchError, t, showToast]);
useEffect(() => {
- if (profileData?.success && Array.isArray(profileData.loginLogs)) {
- setLogs(profileData.loginLogs);
- }
- }, [profileData]);
+ const loadProfile = async () => {
+ const profileData = await refetchProfile();
- useEffect(() => {
- if (fetchError) {
- showToast({
- message: getErrorMessage(fetchError) || t('active.errorFetch'),
- severity: 'error',
- });
- }
- }, [fetchError, t, showToast]);
- const getErrorMessage = (error: unknown): string | null => {
- if (!error) return null;
- if (error instanceof Error) return error.message;
- return String(error);
- };
+ if (!profileData) return;
+
+ if (profileData.success) {
+ if (!Array.isArray(profileData.loginLogs)) return;
+ setLogs(profileData.loginLogs);
+ } else {
+ showToast({
+ message: profileData.message || t('active.errorFetch'),
+ severity: 'error',
+ });
+ }
+ };
+
+ loadProfile();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
return (
@@ -54,7 +65,7 @@ export function RecentLogins() {
title={t('securityForm.recentLogins')}
subtitle={t('securityForm.description')}
>
- {isLoading ? (
+ {isLoadingProfile ? (
- ) : fetchError ? (
-
-
- {getErrorMessage(fetchError) || t('active.errorFetch')}
-
-
) : (
{logs.map((log, index) => (
diff --git a/src/features/profile/components/userInformation/PersonalInformation.tsx b/src/features/profile/components/userInformation/PersonalInformation.tsx
index eaf6078..d5de493 100644
--- a/src/features/profile/components/userInformation/PersonalInformation.tsx
+++ b/src/features/profile/components/userInformation/PersonalInformation.tsx
@@ -1,5 +1,5 @@
import { useState, useEffect, useRef } from 'react';
-import { Box, Button, Typography, CircularProgress } from '@mui/material';
+import { Box, Button, CircularProgress } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { CardContainer } from '@/components/CardContainer';
import { ProfileImage } from './personalInformation/ProfileImage';
@@ -8,10 +8,13 @@ import { InfoRowEdit } from './personalInformation/InfoRowEdit';
import { PageWrapper } from '../PageWrapper';
import { useApi } from '@/hooks/useApi';
import { Gender, type InfoRowData } from '../../types/settingsType';
-import { fetchProfile, saveProfile } from '../../api/settingsApi';
+import { saveProfile } from '../../api/settingsApi';
import { useToast } from '@rkheftan/harmony-ui';
+import { useProfile } from '../../hooks/useProfile';
export function PersonalInformation() {
+ const imageBaseUrl = import.meta.env.IMAGE_BASE_URL;
+
const { t } = useTranslation('setting');
const [isEditing, setIsEditing] = useState(false);
const [uploadedImageUrl, setUploadedImageUrl] = useState(null);
@@ -27,52 +30,46 @@ export function PersonalInformation() {
const showToast = useToast();
const infoRowEditRef = useRef<{ validateFields: () => boolean }>(null);
- const {
- data: profileData,
- loading: isLoadingProfile,
- error: fetchProfileError,
- } = useApi(fetchProfile, { immediate: true });
- const {
- data: saveData,
- loading: isSavingProfile,
- error: saveProfileError,
- execute: executeSaveProfile,
- } = useApi(saveProfile);
+ const { isLoadingProfile, refetchProfile } = useProfile();
+
+ const { loading: isSavingProfile, execute: executeSaveProfile } =
+ useApi(saveProfile);
useEffect(() => {
- if (profileData?.success) {
- const fetchedData = {
- firstName: profileData.firstName ?? '',
- lastName: profileData.lastName ?? '',
- nationalCode: profileData.nationalCode ?? '',
- gender: Object.values(Gender).includes(profileData.gender as Gender)
- ? (profileData.gender as Gender)
- : Gender.None,
- country: profileData.countryCode ?? '',
- };
- setData(fetchedData);
- setOriginalData(fetchedData);
- const imageBaseUrl = import.meta.env.IMAGE_BASE_URL;
- setUploadedImageUrl(
- profileData.profileImageUrl
- ? `${imageBaseUrl}${profileData.profileImageUrl}`
- : null,
- );
- setUploadedImageFile(null);
- }
- }, [profileData]);
+ const loadProfile = async () => {
+ const profileData = await refetchProfile();
- useEffect(() => {
- if (saveData?.success) {
- showToast({
- message: t('settingForm.successSaveProfile'),
- severity: 'success',
- });
- setIsEditing(false);
- setOriginalData(data);
- setUploadedImageFile(null);
- }
- }, [saveData, data, showToast, t]);
+ if (!profileData) return;
+
+ if (profileData.success) {
+ const fetchedData = {
+ firstName: profileData.firstName ?? '',
+ lastName: profileData.lastName ?? '',
+ nationalCode: profileData.nationalCode ?? '',
+ gender: Object.values(Gender).includes(profileData.gender as Gender)
+ ? (profileData.gender as Gender)
+ : Gender.None,
+ country: profileData.countryCode ?? '',
+ };
+ setData(fetchedData);
+ setOriginalData(fetchedData);
+ setUploadedImageUrl(
+ profileData.profileImageUrl
+ ? `${imageBaseUrl}${profileData.profileImageUrl}`
+ : null,
+ );
+ setUploadedImageFile(null);
+ } else {
+ showToast({
+ message: profileData.message || t('message.serverError'),
+ severity: 'error',
+ });
+ }
+ };
+
+ loadProfile();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
const initials = `${data?.firstName?.trim()[0] || ''}${data?.lastName?.trim()[0] || ''}`;
@@ -84,36 +81,35 @@ export function PersonalInformation() {
setUploadedImageFile(null);
};
- const handleSaveClick = () => {
+ const handleSaveClick = async () => {
if (!data) return;
const isValid = infoRowEditRef.current?.validateFields?.();
if (!isValid) return;
- executeSaveProfile({ data, imageUrl: uploadedImageFile });
- };
- const getErrorMessage = (error: unknown): string | null => {
- if (!error) return null;
- if (error instanceof Error) return error.message;
- return String(error);
- };
+ const saveData = await executeSaveProfile({
+ data,
+ imageUrl: uploadedImageFile,
+ });
- useEffect(() => {
- if (saveProfileError)
+ if (!saveData) return;
+
+ if (saveData?.success) {
showToast({
- message:
- getErrorMessage(saveProfileError) || t('settingForm.errorSave'),
+ message: t('settingForm.successSaveProfile'),
+ severity: 'success',
+ });
+ setIsEditing(false);
+ setOriginalData(data);
+ setUploadedImageFile(null);
+ // Force a refetch to update the cache with the new data
+ refetchProfile({ force: true });
+ } else {
+ showToast({
+ message: saveData.message || t('message.serverError'),
severity: 'error',
});
- }, [saveProfileError, showToast, t]);
-
- useEffect(() => {
- if (fetchProfileError)
- showToast({
- message:
- getErrorMessage(fetchProfileError) || t('settingForm.errorFetch'),
- severity: 'error',
- });
- }, [fetchProfileError, showToast, t]);
+ }
+ };
return (
@@ -163,14 +159,6 @@ export function PersonalInformation() {
{t('settingForm.editButton')}
)}
- {getErrorMessage(saveProfileError) && (
-
- {getErrorMessage(saveProfileError)}
-
- )}
}
>
@@ -186,13 +174,6 @@ export function PersonalInformation() {
>
- ) : fetchProfileError ? (
-
-
- {getErrorMessage(fetchProfileError) ||
- t('settingForm.errorFetch')}
-
-
) : (
(
'default',
);
const [isVerified, setIsVerified] = useState(false);
const [phones, setPhones] = useState([]);
const [countryCode, setCountryCode] = useState('+98');
- const [formError, setFormError] = useState();
- const [touched, setTouched] = useState(false);
+ const [phoneNumberError, setPhoneNumberError] = useState();
+ const [verificationCodeError, setVerificationCodeError] = useState();
+ const [phoneNumberTouched, setPhoneNumberTouched] = useState(false);
+ const [verificationCodeTouched, setVerificationCodeTouched] =
+ useState(false);
- const textFieldRef = useRef(null);
- const inputRef = useRef(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 (
@@ -240,7 +215,7 @@ export function PhoneNumber() {
/>
}
>
- {isLoading ? (
+ {isLoadingProfile ? (
- ) : fetchError ? (
-
-
- {getErrorMessage(fetchError) ||
- t('settingForm.errorFetchPhoneNumber')}
-
-
) : isEditing ? (
}
- inputRef={inputRef as React.RefObject}
phones={phones}
- showToast={showToast}
- setShowToast={setShowToast}
- t={t}
/>
) : (
([]);
const [formError, setFormError] = useState(null);
- const showToast = useToast();
+ const toast = useToast();
- const {
- data: profileData,
- loading: isFetching,
- error: fetchError,
- } = useApi(fetchProfile, { immediate: true });
+ const { isLoadingProfile, refetchProfile } = useProfile();
- const {
- data: sendCodeData,
- loading: isSendingCode,
- error: sendCodeError,
- execute: executeSendCode,
- } = useApi(sendEmailCode);
+ const { loading: isSendingCode, execute: executeSendCode } =
+ useApi(sendEmailCode);
- const {
- data: confirmData,
- loading: isConfirming,
- error: confirmError,
- execute: executeConfirmCode,
- } = useApi(confirmEmailCode);
+ const { loading: isConfirming, execute: executeConfirmCode } =
+ useApi(confirmEmailCode);
- const {
- data: changeEmailData,
- loading: isChangingEmail,
- error: changeEmailError,
- execute: executeChangeEmail,
- } = useApi(changeEmail);
+ const { loading: isChangingEmail, execute: executeChangeEmail } =
+ useApi(changeEmail);
const fullScreen = useMediaQuery((theme: Theme) =>
theme?.breakpoints.down('sm'),
);
- const downMd = useMediaQuery((theme: Theme) => theme.breakpoints.down('md'));
+ const downMd = useMediaQuery((theme: Theme) => theme?.breakpoints.down('md'));
const computedMaxWidth = (fullScreen ? 'xs' : downMd ? 'sm' : 'md') as
| 'xs'
| 'sm'
@@ -70,76 +54,34 @@ export function SocialMedia() {
| 'xl';
useEffect(() => {
- if (profileData?.success && profileData.email) {
- const { email } = profileData;
- setEmailList([
- {
- email,
- provider: email.includes('@gmail.') ? 'google' : 'email',
- time: '',
- },
- ]);
- }
- }, [profileData]);
+ const loadProfile = async () => {
+ const profileData = await refetchProfile();
- useEffect(() => {
- if (sendCodeData?.success) {
- showToast({
- message: t('settingForm.verificationCodeSent'),
- severity: 'success',
- });
- setDialogStep('enterCode');
- }
- }, [sendCodeData, t, showToast]);
+ if (!profileData) return;
- useEffect(() => {
- if (confirmData?.success && confirmData.confirm) {
- showToast({
- message: t('settingForm.verificationSuccessful'),
- severity: 'success',
- });
- executeChangeEmail({ email: emailInput });
- }
- }, [confirmData, emailInput, executeChangeEmail, t, showToast]);
+ if (profileData?.success) {
+ const { email } = profileData;
- useEffect(() => {
- if (changeEmailData?.success) {
- setEmailList((prev) => [
- ...prev,
- {
- email: emailInput,
- provider: emailInput.includes('@gmail.') ? 'google' : 'email',
- time: t('settingForm.justNow'),
- },
- ]);
- showToast({
- message: t('settingForm.emailChangedSuccessfully'),
- severity: 'success',
- });
- resetDialog();
- }
- }, [changeEmailData, emailInput, t, showToast]);
+ if (!email) return;
- useEffect(() => {
- if (sendCodeError) {
- showToast({
- message: getErrorMessage(sendCodeError) || t('settingForm.error'),
- severity: 'error',
- });
- }
- if (confirmError) {
- showToast({
- message: getErrorMessage(confirmError) || t('settingForm.error'),
- severity: 'error',
- });
- }
- if (changeEmailError) {
- showToast({
- message: getErrorMessage(changeEmailError) || t('settingForm.error'),
- severity: 'error',
- });
- }
- }, [sendCodeError, confirmError, changeEmailError, t, showToast]);
+ setEmailList([
+ {
+ email,
+ provider: email.includes('@gmail.') ? 'google' : 'email',
+ time: '',
+ },
+ ]);
+ } else {
+ toast({
+ message: profileData.message,
+ severity: 'error',
+ });
+ }
+ };
+
+ loadProfile();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
const resetDialog = () => {
setOpenDialog(false);
@@ -149,34 +91,74 @@ export function SocialMedia() {
setDialogStep('enterEmail');
};
- const handleSendCode = () => {
+ const handleSendCode = async () => {
if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(emailInput)) {
setFormError(t('settingForm.emailIsInvalid'));
return;
}
setFormError(null);
- executeSendCode({ email: emailInput });
+ const sendCodeData = await executeSendCode({ email: emailInput });
+
+ if (!sendCodeData) return;
+
+ if (sendCodeData.success) {
+ toast({
+ message: sendCodeData.message || t('settingForm.verificationCodeSent'),
+ severity: 'success',
+ });
+ setDialogStep('enterCode');
+ }
};
- const handleConfirmAndChangeEmail = () => {
+ const handleConfirmAndChangeEmail = async () => {
if (verificationCode.length < 4) {
setFormError(t('settingForm.verificationCodeRequired'));
return;
}
setFormError(null);
- executeConfirmCode({ email: emailInput, verifyCode: verificationCode });
- };
+ const confirmData = await executeConfirmCode({
+ email: emailInput,
+ verifyCode: verificationCode,
+ });
- const getErrorMessage = (error: unknown): string | null => {
- if (!error) return null;
- if (error instanceof Error) return error.message;
- return String(error);
- };
+ if (!confirmData) return;
- const apiError = useMemo(
- () => getErrorMessage(sendCodeError || confirmError || changeEmailError),
- [sendCodeError, confirmError, changeEmailError],
- );
+ if (!confirmData.success || !confirmData.confirm) {
+ toast({
+ message: confirmData.message || t('settingForm.error'),
+ severity: 'error',
+ });
+
+ return;
+ }
+
+ toast({
+ message: confirmData.message || t('settingForm.verificationSuccessful'),
+ severity: 'success',
+ });
+
+ const changeEmailData = await executeChangeEmail({ email: emailInput });
+
+ if (!changeEmailData) return;
+
+ if (changeEmailData.success) {
+ setEmailList((prev) => [
+ ...prev,
+ {
+ email: emailInput,
+ provider: emailInput.includes('@gmail.') ? 'google' : 'email',
+ time: t('settingForm.justNow'),
+ },
+ ]);
+ toast({
+ message: t('settingForm.emailChangedSuccessfully'),
+ severity: 'success',
+ });
+ resetDialog();
+
+ refetchProfile({ force: true });
+ }
+ };
return (
@@ -187,7 +169,7 @@ export function SocialMedia() {
setOpenDialog(true)} />
}
>
- {isFetching ? (
+ {isLoadingProfile ? (
- ) : fetchError ? (
-
-
- {getErrorMessage(fetchError)}
-
-
) : (
)}
@@ -216,7 +192,7 @@ export function SocialMedia() {
setEmailInput={setEmailInput}
verificationCode={verificationCode}
setVerificationCode={setVerificationCode}
- apiError={formError || apiError}
+ apiError={formError}
isLoading={isSendingCode || isConfirming || isChangingEmail}
dialogStep={dialogStep}
onSendCode={handleSendCode}
diff --git a/src/features/profile/components/userInformation/personalInformation/InfoRowEdit.tsx b/src/features/profile/components/userInformation/personalInformation/InfoRowEdit.tsx
index 5bf9d27..c359332 100644
--- a/src/features/profile/components/userInformation/personalInformation/InfoRowEdit.tsx
+++ b/src/features/profile/components/userInformation/personalInformation/InfoRowEdit.tsx
@@ -110,11 +110,11 @@ export const InfoRowEdit = forwardRef(
error={touched.gender && data.gender === Gender.None}
>
- {t('settingForm.genderPlaceholder', { ns: 'setting' })}
+ {t('settingForm.gender', { ns: 'setting' })}