diff --git a/package-lock.json b/package-lock.json index e441e1d..e60d105 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6297,21 +6297,6 @@ "dev": true, "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": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/public/locales/en/setting.json b/public/locales/en/setting.json index 4c50a53..ac12fa7 100644 --- a/public/locales/en/setting.json +++ b/public/locales/en/setting.json @@ -35,8 +35,7 @@ "profilePicture": "User account image", "allowedFormat": "Allowed formats: PNG, JPEG, GIF (maximum 10 MB)", "uploadPicture": "Upload image", - "phoneNumberText": "Your new contact number will replace your previous contact number.", - "verb": ".", + "phoneNumberText": "Your new contact number will replace your previous contact number (<1>{{phoneNumber}})", "notDetermined": "Not determined", "successfulChangePhone": "Phone number changed successfully", "phoneNumberIsInvalid": "Phone number is invalid", @@ -72,7 +71,6 @@ "errorChangePhone": "Failed to change phone number", "verificationCodeSent": "Verification code sent" }, - "active": { "activeDevices": "Active devices", "activeDevicesCaption": "Watch and manage all your active devices", @@ -88,7 +86,6 @@ "successDelete": "Deleted successfully", "deleteFailed": "Deletion failed" }, - "settings": { "title": "Base settings", "description": "Change your base settings", @@ -111,7 +108,6 @@ "saveFailed": "Save failed", "invalidSelection": "Invalid selection" }, - "securityForm": { "password": "Password", "determinePassword": "Log in to your Harmony account more easily by setting a strong password.", @@ -138,4 +134,4 @@ "passwordAdded": "Password added", "error": "Password change failed" } -} +} \ No newline at end of file diff --git a/public/locales/fa/setting.json b/public/locales/fa/setting.json index e469895..1d405bf 100644 --- a/public/locales/fa/setting.json +++ b/public/locales/fa/setting.json @@ -36,8 +36,7 @@ "profilePicture": "تصویر حساب کاربری", "allowedFormat": "فرمت‌های مجاز: PNG، JPEG، GIF (حداکثر ۱۰ مگابایت)", "uploadPicture": "بارگذاری تصویر", - "phoneNumberText": "شماره تماس جدید شما جایگزین شماره تماس قبلی", - "verb": "خواهد شد", + "phoneNumberText": "شماره تماس جدید شما جایگزین شماره قبلی (<1>{{phoneNumber}}) خواهد شد", "notDetermined": "تعیین نشده", "successfulChangePhone": "شماره تماس با موفقیت تغییر کرد", "phoneNumberIsInvalid": "شماره وارد شده نامعتبر میباشد", @@ -73,7 +72,6 @@ "errorChangePhone": "تغییر تلفن همراه با خطا مواجه شد", "verificationCodeSent": "کد تایید ارسال شد" }, - "active": { "activeDevices": "نشست های فعال", "activeDevicesCaption": "مشاهده و مدیریت تمام نشست های فعال شما", @@ -89,7 +87,6 @@ "successDelete": "با موفقیت حذف شد", "deleteFailed": "حذف با مشکل مواجه شد" }, - "settings": { "title": "تنظیمات پایه", "description": "تنظیمات پایه‌ای حساب خود را تغییر دهید", @@ -112,7 +109,6 @@ "saveFailed": "خطا در ذخیره", "invalidSelection": "انتخاب نامعتبر است" }, - "securityForm": { "password": "رمز عبور", "determinePassword": "با تعیین یک رمز عبور قوی راحت تر به اکانت هارمونی خود وارد شوید", @@ -139,4 +135,4 @@ "passwordAdded": "رمز عبور اضافه شد", "error": "تعویض رمز عبور با مشکل مواجه شد" } -} +} \ No newline at end of file diff --git a/src/components/common/LTRTypography.tsx b/src/components/common/LTRTypography.tsx index 9a6f5c4..3162f4d 100644 --- a/src/components/common/LTRTypography.tsx +++ b/src/components/common/LTRTypography.tsx @@ -3,4 +3,5 @@ import { styled, Typography } from '@mui/material'; export const LTRTypography = styled(Typography)` /* @noflip */ direction: ltr; + unicode-bidi: isolate; `; diff --git a/src/features/authentication/components/AuthenticationSteps/LoginRegiserForm.tsx b/src/features/authentication/components/AuthenticationSteps/LoginRegiserForm.tsx index 11901f2..05037af 100644 --- a/src/features/authentication/components/AuthenticationSteps/LoginRegiserForm.tsx +++ b/src/features/authentication/components/AuthenticationSteps/LoginRegiserForm.tsx @@ -1,5 +1,5 @@ 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 { isNumeric } from '@/utils/regexes/isNumeric'; import type { AuthFactory, AuthType } from '../../types/authTypes'; @@ -14,6 +14,7 @@ import { useToast } from '@rkheftan/harmony-ui'; import { useApi } from '@/hooks/useApi'; import type { GenerateTokenResponse } from '../../api/identityAPI'; import { GoogleAuthenticationV2 } from './GoogleAuthenticationV2'; +import { replacePersianWithRealNumbers } from '@/utils/replacePersianWithRealNumbers'; export interface LoginRegisterFormProps { loginRegisterValue: string; @@ -47,11 +48,22 @@ export function LoginRegisterForm({ const [error, setError] = useState(); const [touched, setTouched] = useState(false); const inputError: boolean = touched && !!error; + const [menuAnchorEl, setMenuAnchorEl] = useState(null); const toast = useToast(); const { loading: userStatusLoading, execute: execUserStatus } = useApi( getUserStatusByPhoneNumberOrEmail, ); + useEffect(() => { + if (textFieldRef.current) { + const inputBaseElement = + textFieldRef.current.querySelector('.MuiInputBase-root'); + if (inputBaseElement) { + setMenuAnchorEl(inputBaseElement as HTMLElement); + } + } + }, []); + const handleInputChange = (event: React.ChangeEvent) => { let newValue = event.target.value; newValue = replacePersianWithRealNumbers(newValue); @@ -160,7 +172,7 @@ export function LoginRegisterForm({ value={countryCode} onChange={setCountryCode} show={showAdornment} - menuAnchor={textFieldRef.current} + menuAnchor={menuAnchorEl} onCloseFocusRef={inputRef} /> ), diff --git a/src/features/authentication/components/CountryCodeSelector.tsx b/src/features/authentication/components/CountryCodeSelector.tsx index fd42dac..fc3c3b4 100644 --- a/src/features/authentication/components/CountryCodeSelector.tsx +++ b/src/features/authentication/components/CountryCodeSelector.tsx @@ -16,7 +16,7 @@ import { useTranslation } from 'react-i18next'; import { countries, type Country } from '../../../data/countries'; import type { CountryCode } from '@/types/commonTypes'; import { Icon } from '@rkheftan/harmony-ui'; -import { LTRBox } from '@/components/common/LTRBox'; +import { LTRTypography } from '@/components/common/LTRTypography'; interface CountryCodeSelectorProps { show: boolean; value: CountryCode; @@ -133,7 +133,6 @@ export function CountryCodeSelector({ style={{ height: '1.5rem', width: '1.5rem', - // TODO: Check alignment for better styling definition marginTop: '-2px', marginRight: '4px', }} @@ -183,7 +182,6 @@ export function CountryCodeSelector({ /> - {/* Can improve preformance with using virtual scrolling */} {filteredCountries.length === 0 ? ( @@ -211,48 +209,13 @@ export function CountryCodeSelector({ /> - - - {country.phone} - - + + {country.phone} + )) )} - - {/* virtual scrolling */} - {/* ( - - {t('messages.noResultFound')} - - ), - }} - initialTopMostItemIndex={countries.indexOf(selectedCountry)} - itemContent={(_, country) => ( - handleSelect(country)} - > - - - - - - {country.phone} - - - )} - /> */} diff --git a/src/features/profile/components/CountryCodeSelector.tsx b/src/features/profile/components/CountryCodeSelector.tsx deleted file mode 100644 index fd948db..0000000 --- a/src/features/profile/components/CountryCodeSelector.tsx +++ /dev/null @@ -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; -} - -/** - * 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); - const [searchTerm, setSearchTerm] = useState(''); - const open = Boolean(anchorEl); - const searchInputRef = useRef(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 ( - - - 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 */} - - - - {value} - - - - - - - setSearchTerm(e.target.value)} - /> - - - {/* Can improve preformance with using virtual scrolling */} - - {filteredCountries.length === 0 ? ( - - - {t('messages.noResualtFound')} - - - ) : ( - filteredCountries.map((country) => ( - handleSelect(country)} - > - - - - - - {country.phone} - - - )) - )} - - - {/* virtual scrolling */} - {/* ( - - {t('messages.noResultFound')} - - ), - }} - initialTopMostItemIndex={countries.indexOf(selectedCountry)} - itemContent={(_, country) => ( - handleSelect(country)} - > - - - - - - {country.phone} - - - )} - /> */} - - - - ); -} diff --git a/src/features/profile/components/userInformation/PhoneNumber.tsx b/src/features/profile/components/userInformation/PhoneNumber.tsx index 56c591b..c065c1b 100644 --- a/src/features/profile/components/userInformation/PhoneNumber.tsx +++ b/src/features/profile/components/userInformation/PhoneNumber.tsx @@ -17,6 +17,7 @@ import { import { type Phone } from '../../types/settingsType'; import { useToast } from '@rkheftan/harmony-ui'; import { useProfile } from '../../hooks/useProfile'; +import type { CountryCode } from '@/types/commonTypes'; export function PhoneNumber() { const { t, i18n } = useTranslation('setting'); @@ -29,7 +30,7 @@ export function PhoneNumber() { ); const [isVerified, setIsVerified] = useState(false); const [phones, setPhones] = useState([]); - const [countryCode, setCountryCode] = useState('+98'); + const [countryCode, setCountryCode] = useState('+98'); const [phoneNumberError, setPhoneNumberError] = useState(); const [verificationCodeError, setVerificationCodeError] = useState(); const [phoneNumberTouched, setPhoneNumberTouched] = useState(false); diff --git a/src/features/profile/components/userInformation/phoneNumber/PhoneEditForm.tsx b/src/features/profile/components/userInformation/phoneNumber/PhoneEditForm.tsx index e12d3d6..70f570d 100644 --- a/src/features/profile/components/userInformation/phoneNumber/PhoneEditForm.tsx +++ b/src/features/profile/components/userInformation/phoneNumber/PhoneEditForm.tsx @@ -9,11 +9,12 @@ import { } from '@mui/material'; import { Edit2, TickCircle } from 'iconsax-react'; import { CountDownTimer } from '@/components/CountDownTimer'; -import { CountryCodeSelector } from '../../CountryCodeSelector'; import { Icon } from '@rkheftan/harmony-ui'; import { type PhoneEditFormProps } from '@/features/profile/types/settingsType'; -import { useRef } from 'react'; -import { useTranslation } from 'react-i18next'; +import { useRef, useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { CountryCodeSelector } from '@/features/authentication/components/CountryCodeSelector'; +import { LTRTypography } from '@/components/common/LTRTypography'; export default function PhoneEditForm({ phoneNumber, @@ -35,6 +36,7 @@ export default function PhoneEditForm({ verificationCodeError, }: PhoneEditFormProps) { const { t } = useTranslation('setting'); + const [codeSent, setCodeSent] = useState(false); const textFieldRef = useRef(null); const inputRef = useRef(null); @@ -44,8 +46,16 @@ export default function PhoneEditForm({ {t('settingForm.editPhoneNumber')} - {t('settingForm.phoneNumberText')}({phones.map((p) => p.withCode)}) - {t('settingForm.verb')} + p.withCode).join(', '), + }} + components={{ + 1: , + }} + /> @@ -117,7 +128,10 @@ export default function PhoneEditForm({