fix: remove duplicate component instance

This commit is contained in:
Sajad Mirjalili
2025-09-25 17:13:44 +03:30
parent fff86c5ff1
commit d5531b2508
11 changed files with 60 additions and 335 deletions

15
package-lock.json generated
View File

@@ -6297,21 +6297,6 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/yaml": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
"integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
"dev": true,
"license": "ISC",
"optional": true,
"peer": true,
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
}
},
"node_modules/yocto-queue": { "node_modules/yocto-queue": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View File

@@ -35,8 +35,7 @@
"profilePicture": "User account image", "profilePicture": "User account image",
"allowedFormat": "Allowed formats: PNG, JPEG, GIF (maximum 10 MB)", "allowedFormat": "Allowed formats: PNG, JPEG, GIF (maximum 10 MB)",
"uploadPicture": "Upload image", "uploadPicture": "Upload image",
"phoneNumberText": "Your new contact number will replace your previous contact number.", "phoneNumberText": "Your new contact number will replace your previous contact number (<1>{{phoneNumber}}</1>)",
"verb": ".",
"notDetermined": "Not determined", "notDetermined": "Not determined",
"successfulChangePhone": "Phone number changed successfully", "successfulChangePhone": "Phone number changed successfully",
"phoneNumberIsInvalid": "Phone number is invalid", "phoneNumberIsInvalid": "Phone number is invalid",
@@ -72,7 +71,6 @@
"errorChangePhone": "Failed to change phone number", "errorChangePhone": "Failed to change phone number",
"verificationCodeSent": "Verification code sent" "verificationCodeSent": "Verification code sent"
}, },
"active": { "active": {
"activeDevices": "Active devices", "activeDevices": "Active devices",
"activeDevicesCaption": "Watch and manage all your active devices", "activeDevicesCaption": "Watch and manage all your active devices",
@@ -88,7 +86,6 @@
"successDelete": "Deleted successfully", "successDelete": "Deleted successfully",
"deleteFailed": "Deletion failed" "deleteFailed": "Deletion failed"
}, },
"settings": { "settings": {
"title": "Base settings", "title": "Base settings",
"description": "Change your base settings", "description": "Change your base settings",
@@ -111,7 +108,6 @@
"saveFailed": "Save failed", "saveFailed": "Save failed",
"invalidSelection": "Invalid selection" "invalidSelection": "Invalid selection"
}, },
"securityForm": { "securityForm": {
"password": "Password", "password": "Password",
"determinePassword": "Log in to your Harmony account more easily by setting a strong password.", "determinePassword": "Log in to your Harmony account more easily by setting a strong password.",
@@ -138,4 +134,4 @@
"passwordAdded": "Password added", "passwordAdded": "Password added",
"error": "Password change failed" "error": "Password change failed"
} }
} }

View File

@@ -36,8 +36,7 @@
"profilePicture": "تصویر حساب کاربری", "profilePicture": "تصویر حساب کاربری",
"allowedFormat": "فرمت‌های مجاز: PNG، JPEG، GIF (حداکثر ۱۰ مگابایت)", "allowedFormat": "فرمت‌های مجاز: PNG، JPEG، GIF (حداکثر ۱۰ مگابایت)",
"uploadPicture": "بارگذاری تصویر", "uploadPicture": "بارگذاری تصویر",
"phoneNumberText": "شماره تماس جدید شما جایگزین شماره تماس قبلی", "phoneNumberText": "شماره تماس جدید شما جایگزین شماره قبلی (<1>{{phoneNumber}}</1>) خواهد شد",
"verb": "خواهد شد",
"notDetermined": "تعیین نشده", "notDetermined": "تعیین نشده",
"successfulChangePhone": "شماره تماس با موفقیت تغییر کرد", "successfulChangePhone": "شماره تماس با موفقیت تغییر کرد",
"phoneNumberIsInvalid": "شماره وارد شده نامعتبر میباشد", "phoneNumberIsInvalid": "شماره وارد شده نامعتبر میباشد",
@@ -73,7 +72,6 @@
"errorChangePhone": "تغییر تلفن همراه با خطا مواجه شد", "errorChangePhone": "تغییر تلفن همراه با خطا مواجه شد",
"verificationCodeSent": "کد تایید ارسال شد" "verificationCodeSent": "کد تایید ارسال شد"
}, },
"active": { "active": {
"activeDevices": "نشست های فعال", "activeDevices": "نشست های فعال",
"activeDevicesCaption": "مشاهده و مدیریت تمام نشست های فعال شما", "activeDevicesCaption": "مشاهده و مدیریت تمام نشست های فعال شما",
@@ -89,7 +87,6 @@
"successDelete": "با موفقیت حذف شد", "successDelete": "با موفقیت حذف شد",
"deleteFailed": "حذف با مشکل مواجه شد" "deleteFailed": "حذف با مشکل مواجه شد"
}, },
"settings": { "settings": {
"title": "تنظیمات پایه", "title": "تنظیمات پایه",
"description": "تنظیمات پایه‌ای حساب خود را تغییر دهید", "description": "تنظیمات پایه‌ای حساب خود را تغییر دهید",
@@ -112,7 +109,6 @@
"saveFailed": "خطا در ذخیره", "saveFailed": "خطا در ذخیره",
"invalidSelection": "انتخاب نامعتبر است" "invalidSelection": "انتخاب نامعتبر است"
}, },
"securityForm": { "securityForm": {
"password": "رمز عبور", "password": "رمز عبور",
"determinePassword": "با تعیین یک رمز عبور قوی راحت تر به اکانت هارمونی خود وارد شوید", "determinePassword": "با تعیین یک رمز عبور قوی راحت تر به اکانت هارمونی خود وارد شوید",
@@ -139,4 +135,4 @@
"passwordAdded": "رمز عبور اضافه شد", "passwordAdded": "رمز عبور اضافه شد",
"error": "تعویض رمز عبور با مشکل مواجه شد" "error": "تعویض رمز عبور با مشکل مواجه شد"
} }
} }

View File

@@ -3,4 +3,5 @@ import { styled, Typography } from '@mui/material';
export const LTRTypography = styled(Typography)` export const LTRTypography = styled(Typography)`
/* @noflip */ /* @noflip */
direction: ltr; direction: ltr;
unicode-bidi: isolate;
`; `;

View File

@@ -1,5 +1,5 @@
import { Button, Stack, TextField, Typography, Box } from '@mui/material'; import { Button, Stack, TextField, Typography, Box } from '@mui/material';
import { useRef, useState, type Dispatch } from 'react'; import { useEffect, useRef, useState, type Dispatch } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { isNumeric } from '@/utils/regexes/isNumeric'; import { isNumeric } from '@/utils/regexes/isNumeric';
import type { AuthFactory, AuthType } from '../../types/authTypes'; import type { AuthFactory, AuthType } from '../../types/authTypes';
@@ -14,6 +14,7 @@ import { useToast } from '@rkheftan/harmony-ui';
import { useApi } from '@/hooks/useApi'; import { useApi } from '@/hooks/useApi';
import type { GenerateTokenResponse } from '../../api/identityAPI'; import type { GenerateTokenResponse } from '../../api/identityAPI';
import { GoogleAuthenticationV2 } from './GoogleAuthenticationV2'; import { GoogleAuthenticationV2 } from './GoogleAuthenticationV2';
import { replacePersianWithRealNumbers } from '@/utils/replacePersianWithRealNumbers';
export interface LoginRegisterFormProps { export interface LoginRegisterFormProps {
loginRegisterValue: string; loginRegisterValue: string;
@@ -47,11 +48,22 @@ export function LoginRegisterForm({
const [error, setError] = useState<string>(); const [error, setError] = useState<string>();
const [touched, setTouched] = useState<boolean>(false); const [touched, setTouched] = useState<boolean>(false);
const inputError: boolean = touched && !!error; const inputError: boolean = touched && !!error;
const [menuAnchorEl, setMenuAnchorEl] = useState<HTMLElement | null>(null);
const toast = useToast(); const toast = useToast();
const { loading: userStatusLoading, execute: execUserStatus } = useApi( const { loading: userStatusLoading, execute: execUserStatus } = useApi(
getUserStatusByPhoneNumberOrEmail, getUserStatusByPhoneNumberOrEmail,
); );
useEffect(() => {
if (textFieldRef.current) {
const inputBaseElement =
textFieldRef.current.querySelector('.MuiInputBase-root');
if (inputBaseElement) {
setMenuAnchorEl(inputBaseElement as HTMLElement);
}
}
}, []);
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
let newValue = event.target.value; let newValue = event.target.value;
newValue = replacePersianWithRealNumbers(newValue); newValue = replacePersianWithRealNumbers(newValue);
@@ -160,7 +172,7 @@ export function LoginRegisterForm({
value={countryCode} value={countryCode}
onChange={setCountryCode} onChange={setCountryCode}
show={showAdornment} show={showAdornment}
menuAnchor={textFieldRef.current} menuAnchor={menuAnchorEl}
onCloseFocusRef={inputRef} onCloseFocusRef={inputRef}
/> />
), ),

View File

@@ -16,7 +16,7 @@ import { useTranslation } from 'react-i18next';
import { countries, type Country } from '../../../data/countries'; import { countries, type Country } from '../../../data/countries';
import type { CountryCode } from '@/types/commonTypes'; import type { CountryCode } from '@/types/commonTypes';
import { Icon } from '@rkheftan/harmony-ui'; import { Icon } from '@rkheftan/harmony-ui';
import { LTRBox } from '@/components/common/LTRBox'; import { LTRTypography } from '@/components/common/LTRTypography';
interface CountryCodeSelectorProps { interface CountryCodeSelectorProps {
show: boolean; show: boolean;
value: CountryCode; value: CountryCode;
@@ -133,7 +133,6 @@ export function CountryCodeSelector({
style={{ style={{
height: '1.5rem', height: '1.5rem',
width: '1.5rem', width: '1.5rem',
// TODO: Check alignment for better styling definition
marginTop: '-2px', marginTop: '-2px',
marginRight: '4px', marginRight: '4px',
}} }}
@@ -183,7 +182,6 @@ export function CountryCodeSelector({
/> />
</Box> </Box>
{/* Can improve preformance with using virtual scrolling */}
<Box sx={{ width: '100%', maxHeight: '14.75rem', overflow: 'auto' }}> <Box sx={{ width: '100%', maxHeight: '14.75rem', overflow: 'auto' }}>
{filteredCountries.length === 0 ? ( {filteredCountries.length === 0 ? (
<MenuItem disabled> <MenuItem disabled>
@@ -211,48 +209,13 @@ export function CountryCodeSelector({
/> />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={t(country.label, { ns: 'country' })} /> <ListItemText primary={t(country.label, { ns: 'country' })} />
<Typography color="text.secondary"> <LTRTypography color="text.secondary">
<Typography> {country.phone}
<LTRBox>{country.phone}</LTRBox> </LTRTypography>
</Typography>
</Typography>
</MenuItem> </MenuItem>
)) ))
)} )}
</Box> </Box>
{/* virtual scrolling */}
{/* <Virtuoso
style={{ height: '14.75rem' }} // Adjust height to account for the search bar
data={filteredCountries}
components={{
EmptyPlaceholder: () => (
<MenuItem disabled>
<ListItemText>{t('messages.noResultFound')}</ListItemText>
</MenuItem>
),
}}
initialTopMostItemIndex={countries.indexOf(selectedCountry)}
itemContent={(_, country) => (
<MenuItem
key={country.code}
selected={country.phone === value}
onClick={() => handleSelect(country)}
>
<ListItemIcon>
<ReactCountryFlag
countryCode={country.code}
svg
style={{ fontSize: '1.25em', lineHeight: '1.25em' }}
/>
</ListItemIcon>
<ListItemText primary={country.label} />
<Typography variant="body2" color="text.secondary">
{country.phone}
</Typography>
</MenuItem>
)}
/> */}
</Menu> </Menu>
</Box> </Box>
</InputAdornment> </InputAdornment>

View File

@@ -1,254 +0,0 @@
import {
Box,
InputAdornment,
ListItem,
ListItemIcon,
ListItemText,
Menu,
MenuItem,
TextField,
Typography,
} from '@mui/material';
import { useMemo, useRef, useState, type RefObject } from 'react';
import { ArrowDown2 } from 'iconsax-react';
import ReactCountryFlag from 'react-country-flag';
import { useTranslation } from 'react-i18next';
import { countries, type Country } from '@/data/countries';
interface CountryCodeSelectorProps {
show: boolean;
value: string;
onChange: (newValue: string) => void;
menuAnchor: HTMLElement | null;
onCloseFocusRef: RefObject<HTMLInputElement | null>;
}
/**
* An animated country code adornment that fades and slides into view.
* Its visibility is controlled by the `show` prop.
*/
export function CountryCodeSelector({
show,
value,
onChange,
menuAnchor,
onCloseFocusRef,
}: CountryCodeSelectorProps) {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [searchTerm, setSearchTerm] = useState('');
const open = Boolean(anchorEl);
const searchInputRef = useRef<HTMLInputElement>(null);
const menuWidth = menuAnchor ? menuAnchor.clientWidth : 'auto';
const { t, i18n } = useTranslation();
const selectedCountry =
countries.find((c) => c.phone === value) || countries[0];
const handleClick = () => {
setAnchorEl(menuAnchor);
};
const handleClose = () => {
setTimeout(() => {
setAnchorEl(null);
}, 0);
setTimeout(() => {
onCloseFocusRef.current?.focus();
}, 100);
setSearchTerm(''); // Reset search on close
};
const handleSelect = (country: Country) => {
onChange(country.phone);
handleClose();
};
const handleMenuEntered = () => {
// Focus the input field after the menu has finished opening
searchInputRef.current?.focus();
};
const filteredCountries = useMemo(
() =>
countries.filter(
(country) =>
t(country.label).toLowerCase().includes(searchTerm.toLowerCase()) ||
country.label.toLowerCase().includes(searchTerm.toLowerCase()) ||
country.phone.includes(searchTerm),
),
[searchTerm, t],
);
return (
<InputAdornment
position={i18n.dir() === 'rtl' ? 'start' : 'end'}
sx={{
mx: 0,
}}
>
<Box
onClick={handleClick}
sx={{
// Animate width and opacity based on the 'show' prop
width: show ? 'auto' : 0,
opacity: show ? 1 : 0,
transition: (theme) =>
theme.transitions.create(['width', 'opacity'], {
duration: theme.transitions.duration.standard,
}),
// Prevent content from wrapping or spilling out during animation
overflow: 'hidden',
whiteSpace: 'nowrap',
// layout styles
height: '100%',
display: 'flex',
alignItems: 'center',
gap: 0.25,
pl: show ? 0.25 : 0,
'&:hover': {
cursor: 'pointer',
},
}}
>
{/* This inner Box prevents the content from being squeezed during the transition */}
<ArrowDown2 size="24" variant="Bold" />
<Typography
variant="body1"
color="text.primary"
component="div"
sx={{ direction: 'rtl' }} // TODO: need to fixed for both en and fa
>
{value}
</Typography>
<ReactCountryFlag
countryCode={selectedCountry.code}
svg
style={{
height: '1.5rem',
width: '1.5rem',
// TODO: Check alignment for better styling definition
marginTop: '-2px',
marginRight: '4px',
}}
/>
<Menu
anchorEl={anchorEl}
open={open}
onClose={handleClose}
slotProps={{
list: {
sx: {
// Set the width to match the anchor element's width
width: menuWidth,
height: '18.75rem',
},
},
transition: {
onEntered: handleMenuEntered,
},
}}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
<Box
sx={{
position: 'sticky',
top: 0,
zIndex: 1,
p: 1,
backgroundColor: 'background.paper',
}}
>
<TextField
inputRef={searchInputRef}
size="small"
fullWidth
label={t('labels.search')}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</Box>
{/* Can improve preformance with using virtual scrolling */}
<Box sx={{ width: '100%', maxHeight: '14.75rem', overflow: 'auto' }}>
{filteredCountries.length === 0 ? (
<MenuItem disabled>
<ListItem>
<ListItemText>{t('messages.noResualtFound')}</ListItemText>
</ListItem>
</MenuItem>
) : (
filteredCountries.map((country) => (
<MenuItem
key={country.code}
selected={country.phone === value}
onClick={() => handleSelect(country)}
>
<ListItemIcon>
<ReactCountryFlag
countryCode={country.code}
svg
style={{
height: '1.125rem',
width: '1.125rem',
}}
/>
</ListItemIcon>
<ListItemText primary={t(country.label)} />
<Typography color="text.secondary">
{country.phone}
</Typography>
</MenuItem>
))
)}
</Box>
{/* virtual scrolling */}
{/* <Virtuoso
style={{ height: '14.75rem' }} // Adjust height to account for the search bar
data={filteredCountries}
components={{
EmptyPlaceholder: () => (
<MenuItem disabled>
<ListItemText>{t('messages.noResultFound')}</ListItemText>
</MenuItem>
),
}}
initialTopMostItemIndex={countries.indexOf(selectedCountry)}
itemContent={(_, country) => (
<MenuItem
key={country.code}
selected={country.phone === value}
onClick={() => handleSelect(country)}
>
<ListItemIcon>
<ReactCountryFlag
countryCode={country.code}
svg
style={{ fontSize: '1.25em', lineHeight: '1.25em' }}
/>
</ListItemIcon>
<ListItemText primary={country.label} />
<Typography variant="body2" color="text.secondary">
{country.phone}
</Typography>
</MenuItem>
)}
/> */}
</Menu>
</Box>
</InputAdornment>
);
}

View File

@@ -17,6 +17,7 @@ import {
import { type Phone } from '../../types/settingsType'; import { type Phone } from '../../types/settingsType';
import { useToast } from '@rkheftan/harmony-ui'; import { useToast } from '@rkheftan/harmony-ui';
import { useProfile } from '../../hooks/useProfile'; import { useProfile } from '../../hooks/useProfile';
import type { CountryCode } from '@/types/commonTypes';
export function PhoneNumber() { export function PhoneNumber() {
const { t, i18n } = useTranslation('setting'); const { t, i18n } = useTranslation('setting');
@@ -29,7 +30,7 @@ export function PhoneNumber() {
); );
const [isVerified, setIsVerified] = useState(false); const [isVerified, setIsVerified] = useState(false);
const [phones, setPhones] = useState<Phone[]>([]); const [phones, setPhones] = useState<Phone[]>([]);
const [countryCode, setCountryCode] = useState('+98'); const [countryCode, setCountryCode] = useState<CountryCode>('+98');
const [phoneNumberError, setPhoneNumberError] = useState<string>(); const [phoneNumberError, setPhoneNumberError] = useState<string>();
const [verificationCodeError, setVerificationCodeError] = useState<string>(); const [verificationCodeError, setVerificationCodeError] = useState<string>();
const [phoneNumberTouched, setPhoneNumberTouched] = useState<boolean>(false); const [phoneNumberTouched, setPhoneNumberTouched] = useState<boolean>(false);

View File

@@ -9,11 +9,12 @@ import {
} from '@mui/material'; } from '@mui/material';
import { Edit2, TickCircle } from 'iconsax-react'; import { Edit2, TickCircle } from 'iconsax-react';
import { CountDownTimer } from '@/components/CountDownTimer'; import { CountDownTimer } from '@/components/CountDownTimer';
import { CountryCodeSelector } from '../../CountryCodeSelector';
import { Icon } from '@rkheftan/harmony-ui'; import { Icon } from '@rkheftan/harmony-ui';
import { type PhoneEditFormProps } from '@/features/profile/types/settingsType'; import { type PhoneEditFormProps } from '@/features/profile/types/settingsType';
import { useRef } from 'react'; import { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { CountryCodeSelector } from '@/features/authentication/components/CountryCodeSelector';
import { LTRTypography } from '@/components/common/LTRTypography';
export default function PhoneEditForm({ export default function PhoneEditForm({
phoneNumber, phoneNumber,
@@ -35,6 +36,7 @@ export default function PhoneEditForm({
verificationCodeError, verificationCodeError,
}: PhoneEditFormProps) { }: PhoneEditFormProps) {
const { t } = useTranslation('setting'); const { t } = useTranslation('setting');
const [codeSent, setCodeSent] = useState(false);
const textFieldRef = useRef<HTMLDivElement>(null); const textFieldRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
@@ -44,8 +46,16 @@ export default function PhoneEditForm({
<Box sx={{ mb: 4 }}> <Box sx={{ mb: 4 }}>
<Typography variant="h6">{t('settingForm.editPhoneNumber')}</Typography> <Typography variant="h6">{t('settingForm.editPhoneNumber')}</Typography>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
{t('settingForm.phoneNumberText')}({phones.map((p) => p.withCode)}) <Trans
{t('settingForm.verb')} i18nKey="settingForm.phoneNumberText"
ns="setting"
values={{
phoneNumber: phones.map((p) => p.withCode).join(', '),
}}
components={{
1: <LTRTypography as="span" />,
}}
/>
</Typography> </Typography>
</Box> </Box>
<Box <Box
@@ -53,6 +63,7 @@ export default function PhoneEditForm({
display: 'flex', display: 'flex',
gap: 2, gap: 2,
alignItems: 'center', alignItems: 'center',
pb: 2,
flexDirection: { xs: 'column', sm: 'row' }, flexDirection: { xs: 'column', sm: 'row' },
}} }}
> >
@@ -117,7 +128,10 @@ export default function PhoneEditForm({
<Button <Button
variant="text" variant="text"
loading={isSendingCode} loading={isSendingCode}
onClick={handleSendCode} onClick={() => {
setCodeSent(true);
handleSendCode();
}}
sx={{ sx={{
color: 'primary.main', color: 'primary.main',
width: { xs: '100%', sm: 208 }, width: { xs: '100%', sm: 208 },
@@ -135,14 +149,15 @@ export default function PhoneEditForm({
)} )}
</Box> </Box>
{/* buttonState === 'counting' && !isVerified && */} {/* buttonState === 'counting' && !isVerified && */}
{buttonState === 'counting' && !isVerified && ( {codeSent && !isSendingCode && !isVerified && (
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
gap: 2, gap: 2,
alignItems: 'center', alignItems: 'center',
flexDirection: { xs: 'column', sm: 'row' }, flexDirection: { xs: 'column', sm: 'row' },
mb: 1, mb: 2,
pt: 2,
}} }}
> >
<TextField <TextField

View File

@@ -1,3 +1,4 @@
import type { CountryCode } from '@/types/commonTypes';
import type { TFunction } from 'i18next'; import type { TFunction } from 'i18next';
export enum Gender { export enum Gender {
@@ -107,8 +108,8 @@ export interface PhoneDisplayProps {
export interface PhoneEditFormProps { export interface PhoneEditFormProps {
phoneNumber: string; phoneNumber: string;
setPhoneNumber: (v: string) => void; setPhoneNumber: (v: string) => void;
countryCode: string; countryCode: CountryCode;
setCountryCode: (v: string) => void; setCountryCode: (v: CountryCode) => void;
verificationCode: string; verificationCode: string;
setVerificationCode: (v: string) => void; setVerificationCode: (v: string) => void;
isVerified: boolean; isVerified: boolean;

View File

@@ -1,3 +1,4 @@
import i18n from '@/config/i18n';
import { ACCESS_TOKEN_KEY } from '@/providers/AuthProvider'; import { ACCESS_TOKEN_KEY } from '@/providers/AuthProvider';
import axios from 'axios'; import axios from 'axios';
@@ -24,6 +25,14 @@ apiClient.interceptors.request.use(
// Add the authorization token to the headers // Add the authorization token to the headers
config.headers.Authorization = `Bearer ${token}`; config.headers.Authorization = `Bearer ${token}`;
} }
const currentLang = i18n.language;
if (currentLang) {
// If language is 'fa', send 'fa-IR', otherwise send the language code as is
config.headers['Accept-Language'] =
currentLang === 'fa' ? 'fa-IR' : 'en-US';
}
return config; return config;
}, },
(error) => { (error) => {