fix: remove duplicate component instance
This commit is contained in:
15
package-lock.json
generated
15
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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": "تعویض رمز عبور با مشکل مواجه شد"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user