diff --git a/package-lock.json b/package-lock.json index 5dc7a94..eb709eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "react-country-flag": "^3.1.0", "react-dom": "^19.1.0", "react-i18next": "^15.6.0", + "react-virtuoso": "^4.13.0", "stylis": "^4.3.6", "stylis-plugin-rtl": "^2.1.1" }, @@ -3802,6 +3803,16 @@ "react-dom": ">=16.6.0" } }, + "node_modules/react-virtuoso": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.13.0.tgz", + "integrity": "sha512-XHv2Fglpx80yFPdjZkV9d1baACKghg/ucpDFEXwaix7z0AfVQj+mF6lM+YQR6UC/TwzXG2rJKydRMb3+7iV3PA==", + "license": "MIT", + "peerDependencies": { + "react": ">=16 || >=17 || >= 18 || >= 19", + "react-dom": ">=16 || >=17 || >= 18 || >=19" + } + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", diff --git a/package.json b/package.json index 9f6c517..22e1fc7 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "react-country-flag": "^3.1.0", "react-dom": "^19.1.0", "react-i18next": "^15.6.0", + "react-virtuoso": "^4.13.0", "stylis": "^4.3.6", "stylis-plugin-rtl": "^2.1.1" }, diff --git a/public/locales/fa/common.json b/public/locales/fa/common.json index 3f4cd0d..9c5b085 100644 --- a/public/locales/fa/common.json +++ b/public/locales/fa/common.json @@ -1,3 +1,8 @@ { - "helloWorld": "سلام دنیا" + "labels": { + "search": "جست و جو" + }, + "messages": { + "noResualtFound": "نتیجه ای یافت نشد." + } } diff --git a/src/components/components/DigitsInput.tsx b/src/components/components/DigitsInput.tsx new file mode 100644 index 0000000..de4f054 --- /dev/null +++ b/src/components/components/DigitsInput.tsx @@ -0,0 +1,106 @@ +import React, { + useRef, + useEffect, + type SetStateAction, + type Dispatch, + useState, + type KeyboardEvent, +} from 'react'; +import { TextField, Stack } from '@mui/material'; + +interface DigitInputProps { + onChange: Dispatch>; +} + +const DigitInput: React.FC = ({ onChange }) => { + const [code, setCode] = useState(['', '', '', '']); + const inputRefs = useRef>([]); + + useEffect(() => { + inputRefs.current[0]?.focus(); + }, []); + + const handleChange = (value: string, index: number) => { + if (!/^\d$/.test(value) && value !== '') return; + + const newCode = [...code]; + newCode[index] = value; + setCode(newCode); + onChange(newCode); + + if (value && index < 4 - 1) { + inputRefs.current[index + 1]?.focus(); + } + }; + + const handleBackspace = ( + event: KeyboardEvent, + index: number, + ) => { + event.preventDefault(); + if (index >= 0) { + handleChange('', index); + inputRefs.current[index - 1]?.focus(); + } + }; + + const handlePaste = (event: React.ClipboardEvent) => { + event.preventDefault(); + const pastedData = event.clipboardData.getData('text').replace(/\D/g, ''); // Remove non-digit characters + const newCode = [...code]; + + pastedData.split('').forEach((digit, i) => { + if (i < code.length) { + newCode[i] = digit; + } + }); + + setCode(newCode); + onChange(newCode); + + // Focus the next empty input after the last pasted character + const lastIndex = Math.min(pastedData.length, code.length) - 1; + if (lastIndex >= 0 && inputRefs.current[lastIndex]) { + inputRefs.current[lastIndex]?.focus(); + } + }; + + return ( + + {code.map((digit, index) => ( + (inputRefs.current[index] = el)} + value={digit} + onChange={(e) => handleChange(e.target.value, index)} + onKeyDown={(e) => e.key === 'Backspace' && handleBackspace(e, index)} + onPaste={(e) => handlePaste(e)} + slotProps={{ + htmlInput: { + maxLength: 1, + sx: { + height: '72px', + }, + style: { + textAlign: 'center', + fontSize: '48px', + }, + }, + }} + variant="standard" + size="medium" + sx={{ + width: '83px', + }} + /> + ))} + + ); +}; + +export default DigitInput; diff --git a/src/features/authentication/components/CountryCodeSelector.tsx b/src/features/authentication/components/CountryCodeSelector.tsx index 9532d82..ea4d89d 100644 --- a/src/features/authentication/components/CountryCodeSelector.tsx +++ b/src/features/authentication/components/CountryCodeSelector.tsx @@ -1,5 +1,7 @@ import { Box, + InputAdornment, + ListItem, ListItemIcon, ListItemText, Menu, @@ -7,11 +9,12 @@ import { TextField, Typography, } from '@mui/material'; -import { useEffect, useMemo, useRef, useState, type RefObject } from 'react'; +import { useMemo, useRef, useState, type RefObject } from 'react'; import { countries, type Country } from '../data/countries'; import { ArrowDown2 } from 'iconsax-reactjs'; import ReactCountryFlag from 'react-country-flag'; - +import { useTranslation } from 'react-i18next'; +import { Virtuoso } from 'react-virtuoso'; interface CountryCodeSelectorProps { show: boolean; value: string; @@ -33,9 +36,13 @@ export function CountryCodeSelector({ }: CountryCodeSelectorProps) { const [anchorEl, setAnchorEl] = useState(null); const [searchTerm, setSearchTerm] = useState(''); - const searchInputRef = useRef(null); 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); @@ -61,10 +68,6 @@ export function CountryCodeSelector({ searchInputRef.current?.focus(); }; - useEffect(() => { - // console.log(open); - }, [open]); - const filteredCountries = useMemo( () => countries.filter( @@ -76,98 +79,175 @@ export function CountryCodeSelector({ ); 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 - display: 'flex', - alignItems: 'center', - gap: 0.25, - pl: show ? 0.25 : 0, - - '&:hover': { - cursor: 'pointer', - }, + mx: 0, }} > - {/* This inner Box prevents the content from being squeezed during the transition */} - + + theme.transitions.create(['width', 'opacity'], { + duration: theme.transitions.duration.standard, + }), - - {value} - + {/* This inner Box prevents the content from being squeezed during the transition */} + - + {value} + + + + + - - setSearchTerm(e.target.value)} - /> - - - {filteredCountries.map((country) => ( - handleSelect(country)} + transition: { + onEntered: handleMenuEntered, + }, + }} + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'left', + }} + transformOrigin={{ + vertical: 'top', + horizontal: 'left', + }} + > + - - - - - {country.phone} - - ))} - - + 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/authentication/components/LoginForm.tsx b/src/features/authentication/components/LoginForm.tsx index 0fe6d22..0584f23 100644 --- a/src/features/authentication/components/LoginForm.tsx +++ b/src/features/authentication/components/LoginForm.tsx @@ -1,25 +1,18 @@ -import { - Box, - Button, - InputAdornment, - Stack, - TextField, - Typography, -} from '@mui/material'; +import { Box, Button, Stack, TextField, Typography } from '@mui/material'; import { useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { CountryCodeSelector } from './CountryCodeSelector'; import { Google } from 'iconsax-reactjs'; - -const isNumeric = (value: string) => /^\d*$/.test(value); +import { isNumeric } from '@/utils/regexes/isNumeric'; export function LoginForm() { - const { t } = useTranslation('authentication'); + const { t, i18n } = useTranslation('authentication'); const [value, setValue] = useState(''); const [countryCode, setCountryCode] = useState('+41'); const [inputType, setInputType] = useState<'phone' | 'email'>('phone'); const textFieldRef = useRef(null); const inputRef = useRef(null); + const dir = i18n.dir(); const handleInputChange = (event: React.ChangeEvent) => { const newValue = event.target.value; @@ -33,6 +26,11 @@ export function LoginForm() { } }; + const handleBlur = () => { + // setTouched(true); + // setError(validate(value, inputType)); + }; + const showAdornment = inputType === 'phone' && value.length > 0; return ( @@ -50,25 +48,29 @@ export function LoginForm() { label={t('loginForm.emailOrPhoneLabel')} value={value} onChange={handleInputChange} + onBlur={handleBlur} + // error={touched && !!error} + autoFocus slotProps={{ - htmlInput: { dir: 'ltr', sx: { lineHeight: 1.5 } }, + htmlInput: { dir: 'auto', sx: { lineHeight: 1.5, paddingX: 0 } }, input: { - // in en mode it placed in wrong end - endAdornment: ( - - - + startAdornment: dir === 'ltr' && ( + + ), + endAdornment: dir === 'rtl' && ( + ), }, }} diff --git a/src/features/authentication/components/SmsOtpForm.tsx b/src/features/authentication/components/SmsOtpForm.tsx new file mode 100644 index 0000000..ce03b0a --- /dev/null +++ b/src/features/authentication/components/SmsOtpForm.tsx @@ -0,0 +1,44 @@ +import { useTranslation } from 'react-i18next'; +import { Box, Button, Typography } from '@mui/material'; +import { Edit2 } from 'iconsax-reactjs'; +import DigitInput from '@/components/components/DigitsInput'; + +interface SmsOtpProps { + value: string; + type: 'phone' | 'email'; +} + +export function SmsOtpForm({ value, type }: SmsOtpProps) { + const { t } = useTranslation('authentication'); + + return ( + + + اعتبارسنجی + + + + + کد تایید ۴ رقمی به شماره موبایل شما ارسال شد. لطفا آن را وارد کنید. + + console.log(value)} /> + + + ); +} diff --git a/src/features/authentication/data/countries.ts b/src/features/authentication/data/countries.ts index b26e16c..2b05f9e 100644 --- a/src/features/authentication/data/countries.ts +++ b/src/features/authentication/data/countries.ts @@ -4,11 +4,268 @@ export interface Country { phone: string; } +export interface Country { + code: string; + label: string; + phone: string; +} + export const countries: readonly Country[] = [ - { code: 'CH', label: 'Switzerland', phone: '+41' }, - { code: 'SA', label: 'Saudi Arabia', phone: '+966' }, - { code: 'QA', label: 'Qatar', phone: '+974' }, - { code: 'KW', label: 'Kuwait', phone: '+965' }, - { code: 'BH', label: 'Bahrain', phone: '+973' }, - { code: 'AE', label: 'United Arab Emirates', phone: '+971' }, + { code: 'AF', label: 'country.afghanistan', phone: '+93' }, + { code: 'AX', label: 'country.aland_islands', phone: '+358' }, + { code: 'AL', label: 'country.albania', phone: '+355' }, + { code: 'DZ', label: 'country.algeria', phone: '+213' }, + { code: 'AS', label: 'country.american_samoa', phone: '+1684' }, + { code: 'AD', label: 'country.andorra', phone: '+376' }, + { code: 'AO', label: 'country.angola', phone: '+244' }, + { code: 'AI', label: 'country.anguilla', phone: '+1264' }, + { code: 'AQ', label: 'country.antarctica', phone: '+672' }, + { code: 'AG', label: 'country.antigua_and_barbuda', phone: '+1268' }, + { code: 'AR', label: 'country.argentina', phone: '+54' }, + { code: 'AM', label: 'country.armenia', phone: '+374' }, + { code: 'AW', label: 'country.aruba', phone: '+297' }, + { code: 'AU', label: 'country.australia', phone: '+61' }, + { code: 'AT', label: 'country.austria', phone: '+43' }, + { code: 'AZ', label: 'country.azerbaijan', phone: '+994' }, + { code: 'BS', label: 'country.bahamas', phone: '+1242' }, + { code: 'BH', label: 'country.bahrain', phone: '+973' }, + { code: 'BD', label: 'country.bangladesh', phone: '+880' }, + { code: 'BB', label: 'country.barbados', phone: '+1246' }, + { code: 'BY', label: 'country.belarus', phone: '+375' }, + { code: 'BE', label: 'country.belgium', phone: '+32' }, + { code: 'BZ', label: 'country.belize', phone: '+501' }, + { code: 'BJ', label: 'country.benin', phone: '+229' }, + { code: 'BM', label: 'country.bermuda', phone: '+1441' }, + { code: 'BT', label: 'country.bhutan', phone: '+975' }, + { code: 'BO', label: 'country.bolivia', phone: '+591' }, + { code: 'BA', label: 'country.bosnia_and_herzegovina', phone: '+387' }, + { code: 'BW', label: 'country.botswana', phone: '+267' }, + { code: 'BR', label: 'country.brazil', phone: '+55' }, + { + code: 'IO', + label: 'country.british_indian_ocean_territory', + phone: '+246', + }, + { code: 'VG', label: 'country.british_virgin_islands', phone: '+1284' }, + { code: 'BN', label: 'country.brunei', phone: '+673' }, + { code: 'BG', label: 'country.bulgaria', phone: '+359' }, + { code: 'BF', label: 'country.burkina_faso', phone: '+226' }, + { code: 'BI', label: 'country.burundi', phone: '+257' }, + { code: 'KH', label: 'country.cambodia', phone: '+855' }, + { code: 'CM', label: 'country.cameroon', phone: '+237' }, + { code: 'CA', label: 'country.canada', phone: '+1' }, + { code: 'CV', label: 'country.cape_verde', phone: '+238' }, + { code: 'KY', label: 'country.cayman_islands', phone: '+1345' }, + { code: 'CF', label: 'country.central_african_republic', phone: '+236' }, + { code: 'TD', label: 'country.chad', phone: '+235' }, + { code: 'CL', label: 'country.chile', phone: '+56' }, + { code: 'CN', label: 'country.china', phone: '+86' }, + { code: 'CX', label: 'country.christmas_island', phone: '+61' }, + { code: 'CC', label: 'country.cocos_keeling_islands', phone: '+61' }, + { code: 'CO', label: 'country.colombia', phone: '+57' }, + { code: 'KM', label: 'country.comoros', phone: '+269' }, + { code: 'CG', label: 'country.congo_brazzaville', phone: '+242' }, + { code: 'CD', label: 'country.congo_kinshasa', phone: '+243' }, + { code: 'CK', label: 'country.cook_islands', phone: '+682' }, + { code: 'CR', label: 'country.costa_rica', phone: '+506' }, + { code: 'CI', label: 'country.cote_divoire', phone: '+225' }, + { code: 'HR', label: 'country.croatia', phone: '+385' }, + { code: 'CU', label: 'country.cuba', phone: '+53' }, + { code: 'CW', label: 'country.curacao', phone: '+599' }, + { code: 'CY', label: 'country.cyprus', phone: '+357' }, + { code: 'CZ', label: 'country.czech_republic', phone: '+420' }, + { code: 'DK', label: 'country.denmark', phone: '+45' }, + { code: 'DJ', label: 'country.djibouti', phone: '+253' }, + { code: 'DM', label: 'country.dominica', phone: '+1767' }, + { code: 'DO', label: 'country.dominican_republic', phone: '+1' }, + { code: 'EC', label: 'country.ecuador', phone: '+593' }, + { code: 'EG', label: 'country.egypt', phone: '+20' }, + { code: 'SV', label: 'country.el_salvador', phone: '+503' }, + { code: 'GQ', label: 'country.equatorial_guinea', phone: '+240' }, + { code: 'ER', label: 'country.eritrea', phone: '+291' }, + { code: 'EE', label: 'country.estonia', phone: '+372' }, + { code: 'SZ', label: 'country.eswatini', phone: '+268' }, + { code: 'ET', label: 'country.ethiopia', phone: '+251' }, + { code: 'FK', label: 'country.falkland_islands', phone: '+500' }, + { code: 'FO', label: 'country.faroe_islands', phone: '+298' }, + { code: 'FJ', label: 'country.fiji', phone: '+679' }, + { code: 'FI', label: 'country.finland', phone: '+358' }, + { code: 'FR', label: 'country.france', phone: '+33' }, + { code: 'GF', label: 'country.french_guiana', phone: '+594' }, + { code: 'PF', label: 'country.french_polynesia', phone: '+689' }, + { code: 'GA', label: 'country.gabon', phone: '+241' }, + { code: 'GM', label: 'country.gambia', phone: '+220' }, + { code: 'GE', label: 'country.georgia', phone: '+995' }, + { code: 'DE', label: 'country.germany', phone: '+49' }, + { code: 'GH', label: 'country.ghana', phone: '+233' }, + { code: 'GI', label: 'country.gibraltar', phone: '+350' }, + { code: 'GR', label: 'country.greece', phone: '+30' }, + { code: 'GL', label: 'country.greenland', phone: '+299' }, + { code: 'GD', label: 'country.grenada', phone: '+1473' }, + { code: 'GP', label: 'country.guadeloupe', phone: '+590' }, + { code: 'GU', label: 'country.guam', phone: '+1671' }, + { code: 'GT', label: 'country.guatemala', phone: '+502' }, + { code: 'GG', label: 'country.guernsey', phone: '+44' }, + { code: 'GN', label: 'country.guinea', phone: '+224' }, + { code: 'GW', label: 'country.guinea_bissau', phone: '+245' }, + { code: 'GY', label: 'country.guyana', phone: '+592' }, + { code: 'HT', label: 'country.haiti', phone: '+509' }, + { code: 'HN', label: 'country.honduras', phone: '+504' }, + { code: 'HK', label: 'country.hong_kong', phone: '+852' }, + { code: 'HU', label: 'country.hungary', phone: '+36' }, + { code: 'IS', label: 'country.iceland', phone: '+354' }, + { code: 'IN', label: 'country.india', phone: '+91' }, + { code: 'ID', label: 'country.indonesia', phone: '+62' }, + { code: 'IR', label: 'country.iran', phone: '+98' }, + { code: 'IQ', label: 'country.iraq', phone: '+964' }, + { code: 'IE', label: 'country.ireland', phone: '+353' }, + { code: 'IM', label: 'country.isle_of_man', phone: '+44' }, + { code: 'IL', label: 'country.israel', phone: '+972' }, + { code: 'IT', label: 'country.italy', phone: '+39' }, + { code: 'JM', label: 'country.jamaica', phone: '+1876' }, + { code: 'JP', label: 'country.japan', phone: '+81' }, + { code: 'JE', label: 'country.jersey', phone: '+44' }, + { code: 'JO', label: 'country.jordan', phone: '+962' }, + { code: 'KZ', label: 'country.kazakhstan', phone: '+7' }, + { code: 'KE', label: 'country.kenya', phone: '+254' }, + { code: 'KI', label: 'country.kiribati', phone: '+686' }, + { code: 'XK', label: 'country.kosovo', phone: '+383' }, + { code: 'KW', label: 'country.kuwait', phone: '+965' }, + { code: 'KG', label: 'country.kyrgyzstan', phone: '+996' }, + { code: 'LA', label: 'country.laos', phone: '+856' }, + { code: 'LV', label: 'country.latvia', phone: '+371' }, + { code: 'LB', label: 'country.lebanon', phone: '+961' }, + { code: 'LS', label: 'country.lesotho', phone: '+266' }, + { code: 'LR', label: 'country.liberia', phone: '+231' }, + { code: 'LY', label: 'country.libya', phone: '+218' }, + { code: 'LI', label: 'country.liechtenstein', phone: '+423' }, + { code: 'LT', label: 'country.lithuania', phone: '+370' }, + { code: 'LU', label: 'country.luxembourg', phone: '+352' }, + { code: 'MO', label: 'country.macau', phone: '+853' }, + { code: 'MG', label: 'country.madagascar', phone: '+261' }, + { code: 'MW', label: 'country.malawi', phone: '+265' }, + { code: 'MY', label: 'country.malaysia', phone: '+60' }, + { code: 'MV', label: 'country.maldives', phone: '+960' }, + { code: 'ML', label: 'country.mali', phone: '+223' }, + { code: 'MT', label: 'country.malta', phone: '+356' }, + { code: 'MH', label: 'country.marshall_islands', phone: '+692' }, + { code: 'MQ', label: 'country.martinique', phone: '+596' }, + { code: 'MR', label: 'country.mauritania', phone: '+222' }, + { code: 'MU', label: 'country.mauritius', phone: '+230' }, + { code: 'YT', label: 'country.mayotte', phone: '+262' }, + { code: 'MX', label: 'country.mexico', phone: '+52' }, + { code: 'FM', label: 'country.micronesia', phone: '+691' }, + { code: 'MD', label: 'country.moldova', phone: '+373' }, + { code: 'MC', label: 'country.monaco', phone: '+377' }, + { code: 'MN', label: 'country.mongolia', phone: '+976' }, + { code: 'ME', label: 'country.montenegro', phone: '+382' }, + { code: 'MS', label: 'country.montserrat', phone: '+1664' }, + { code: 'MA', label: 'country.morocco', phone: '+212' }, + { code: 'MZ', label: 'country.mozambique', phone: '+258' }, + { code: 'MM', label: 'country.myanmar', phone: '+95' }, + { code: 'NA', label: 'country.namibia', phone: '+264' }, + { code: 'NR', label: 'country.nauru', phone: '+674' }, + { code: 'NP', label: 'country.nepal', phone: '+977' }, + { code: 'NL', label: 'country.netherlands', phone: '+31' }, + { code: 'NC', label: 'country.new_caledonia', phone: '+687' }, + { code: 'NZ', label: 'country.new_zealand', phone: '+64' }, + { code: 'NI', label: 'country.nicaragua', phone: '+505' }, + { code: 'NE', label: 'country.niger', phone: '+227' }, + { code: 'NG', label: 'country.nigeria', phone: '+234' }, + { code: 'NU', label: 'country.niue', phone: '+683' }, + { code: 'NF', label: 'country.norfolk_island', phone: '+672' }, + { code: 'KP', label: 'country.north_korea', phone: '+850' }, + { code: 'MK', label: 'country.north_macedonia', phone: '+389' }, + { code: 'MP', label: 'country.northern_mariana_islands', phone: '+1670' }, + { code: 'NO', label: 'country.norway', phone: '+47' }, + { code: 'OM', label: 'country.oman', phone: '+968' }, + { code: 'PK', label: 'country.pakistan', phone: '+92' }, + { code: 'PW', label: 'country.palau', phone: '+680' }, + { code: 'PS', label: 'country.palestine', phone: '+970' }, + { code: 'PA', label: 'country.panama', phone: '+507' }, + { code: 'PG', label: 'country.papua_new_guinea', phone: '+675' }, + { code: 'PY', label: 'country.paraguay', phone: '+595' }, + { code: 'PE', label: 'country.peru', phone: '+51' }, + { code: 'PH', label: 'country.philippines', phone: '+63' }, + { code: 'PN', label: 'country.pitcairn_islands', phone: '+64' }, + { code: 'PL', label: 'country.poland', phone: '+48' }, + { code: 'PT', label: 'country.portugal', phone: '+351' }, + { code: 'PR', label: 'country.puerto_rico', phone: '+1' }, + { code: 'QA', label: 'country.qatar', phone: '+974' }, + { code: 'RE', label: 'country.reunion', phone: '+262' }, + { code: 'RO', label: 'country.romania', phone: '+40' }, + { code: 'RU', label: 'country.russia', phone: '+7' }, + { code: 'RW', label: 'country.rwanda', phone: '+250' }, + { code: 'BL', label: 'country.saint_barthelemy', phone: '+590' }, + { code: 'SH', label: 'country.saint_helena', phone: '+290' }, + { code: 'KN', label: 'country.saint_kitts_and_nevis', phone: '+1869' }, + { code: 'LC', label: 'country.saint_lucia', phone: '+1758' }, + { code: 'MF', label: 'country.saint_martin', phone: '+590' }, + { code: 'PM', label: 'country.saint_pierre_and_miquelon', phone: '+508' }, + { + code: 'VC', + label: 'country.saint_vincent_and_the_grenadines', + phone: '+1784', + }, + { code: 'WS', label: 'country.samoa', phone: '+685' }, + { code: 'SM', label: 'country.san_marino', phone: '+378' }, + { code: 'ST', label: 'country.sao_tome_and_principe', phone: '+239' }, + { code: 'SA', label: 'country.saudi_arabia', phone: '+966' }, + { code: 'SN', label: 'country.senegal', phone: '+221' }, + { code: 'RS', label: 'country.serbia', phone: '+381' }, + { code: 'SC', label: 'country.seychelles', phone: '+248' }, + { code: 'SL', label: 'country.sierra_leone', phone: '+232' }, + { code: 'SG', label: 'country.singapore', phone: '+65' }, + { code: 'SX', label: 'country.sint_maarten', phone: '+1721' }, + { code: 'SK', label: 'country.slovakia', phone: '+421' }, + { code: 'SI', label: 'country.slovenia', phone: '+386' }, + { code: 'SB', label: 'country.solomon_islands', phone: '+677' }, + { code: 'SO', label: 'country.somalia', phone: '+252' }, + { code: 'ZA', label: 'country.south_africa', phone: '+27' }, + { + code: 'GS', + label: 'country.south_georgia_and_south_sandwich_islands', + phone: '+500', + }, + { code: 'KR', label: 'country.south_korea', phone: '+82' }, + { code: 'SS', label: 'country.south_sudan', phone: '+211' }, + { code: 'ES', label: 'country.spain', phone: '+34' }, + { code: 'LK', label: 'country.sri_lanka', phone: '+94' }, + { code: 'SD', label: 'country.sudan', phone: '+249' }, + { code: 'SR', label: 'country.suriname', phone: '+597' }, + { code: 'SJ', label: 'country.svalbard_and_jan_mayen', phone: '+47' }, + { code: 'SE', label: 'country.sweden', phone: '+46' }, + { code: 'CH', label: 'country.switzerland', phone: '+41' }, + { code: 'SY', label: 'country.syria', phone: '+963' }, + { code: 'TW', label: 'country.taiwan', phone: '+886' }, + { code: 'TJ', label: 'country.tajikistan', phone: '+992' }, + { code: 'TZ', label: 'country.tanzania', phone: '+255' }, + { code: 'TH', label: 'country.thailand', phone: '+66' }, + { code: 'TL', label: 'country.timor_leste', phone: '+670' }, + { code: 'TG', label: 'country.togo', phone: '+228' }, + { code: 'TK', label: 'country.tokelau', phone: '+690' }, + { code: 'TO', label: 'country.tonga', phone: '+676' }, + { code: 'TT', label: 'country.trinidad_and_tobago', phone: '+1868' }, + { code: 'TN', label: 'country.tunisia', phone: '+216' }, + { code: 'TR', label: 'country.turkey', phone: '+90' }, + { code: 'TM', label: 'country.turkmenistan', phone: '+993' }, + { code: 'TC', label: 'country.turks_and_caicos_islands', phone: '+1649' }, + { code: 'TV', label: 'country.tuvalu', phone: '+688' }, + { code: 'VI', label: 'country.us_virgin_islands', phone: '+1340' }, + { code: 'UG', label: 'country.uganda', phone: '+256' }, + { code: 'UA', label: 'country.ukraine', phone: '+380' }, + { code: 'AE', label: 'country.united_arab_emirates', phone: '+971' }, + { code: 'GB', label: 'country.united_kingdom', phone: '+44' }, + { code: 'US', label: 'country.united_states', phone: '+1' }, + { code: 'UY', label: 'country.uruguay', phone: '+598' }, + { code: 'UZ', label: 'country.uzbekistan', phone: '+998' }, + { code: 'VU', label: 'country.vanuatu', phone: '+678' }, + { code: 'VA', label: 'country.vatican_city', phone: '+39' }, + { code: 'VE', label: 'country.venezuela', phone: '+58' }, + { code: 'VN', label: 'country.vietnam', phone: '+84' }, + { code: 'WF', label: 'country.wallis_and_futuna', phone: '+681' }, + { code: 'EH', label: 'country.western_sahara', phone: '+212' }, + { code: 'YE', label: 'country.yemen', phone: '+967' }, + { code: 'ZM', label: 'country.zambia', phone: '+260' }, + { code: 'ZW', label: 'country.zimbabwe', phone: '+263' }, ]; diff --git a/src/features/authentication/routes/LoginPage.tsx b/src/features/authentication/routes/LoginPage.tsx index d136967..ada147d 100644 --- a/src/features/authentication/routes/LoginPage.tsx +++ b/src/features/authentication/routes/LoginPage.tsx @@ -1,9 +1,15 @@ import { FlexBox } from '@/components/components/common/FlexBox'; import Logo from '@/components/Logo'; import { Paper } from '@mui/material'; -import { LoginForm } from '../components/LoginForm'; +import { SmsOtpForm } from '../components/SmsOtpForm'; +import { useState } from 'react'; export function LoginPage() { + const [phoneOrEmail, setPhoneOrEmail] = useState( + 'sajadmirjalili82@gmail.com', + ); + const [type, setType] = useState<'phone' | 'email'>('phone'); + return ( - + {/* */} + ); diff --git a/src/providers/CustomThemeProvider.tsx b/src/providers/CustomThemeProvider.tsx index 2759d8f..ee1201e 100644 --- a/src/providers/CustomThemeProvider.tsx +++ b/src/providers/CustomThemeProvider.tsx @@ -19,7 +19,7 @@ export const CustomThemeProvider: React.FC<{ children: React.ReactNode }> = ({ palette: lightPalette, }, dark: { - palette: darkPalette, + palette: darkPalette, }, }, cssVariables: { diff --git a/src/providers/RtlProvider.tsx b/src/providers/RtlProvider.tsx index 7bbf52a..eb37994 100644 --- a/src/providers/RtlProvider.tsx +++ b/src/providers/RtlProvider.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { CacheProvider } from '@emotion/react'; import createCache from '@emotion/cache'; @@ -9,17 +9,14 @@ export const RtlProvider: React.FC<{ children: React.ReactNode }> = ({ children, }) => { const { i18n } = useTranslation(); - const [cache, setCache] = useState(createCache({ key: 'css' })); - useEffect(() => { - const newDir = i18n.dir(i18n.language); - - const newCache = createCache({ - key: 'css', - stylisPlugins: newDir === 'rtl' ? [rtlPlugin] : [], + const cacheRtl = useMemo(() => { + const isRtl = i18n.dir(i18n.language) === 'rtl'; + return createCache({ + key: isRtl ? 'muirtl' : 'muiltr', + stylisPlugins: isRtl ? [rtlPlugin] : [], }); - setCache(newCache); - }, [i18n, i18n.language]); + }, [i18n]); - return {children}; + return {children}; }; diff --git a/src/utils/regexes/isNumeric.ts b/src/utils/regexes/isNumeric.ts new file mode 100644 index 0000000..10e385d --- /dev/null +++ b/src/utils/regexes/isNumeric.ts @@ -0,0 +1 @@ +export const isNumeric = (value: string) => /^\d*$/.test(value);