import type { ChangeEvent } from 'react'; import monkeyPath from '../../assets/monkey.svg'; import type { FC } from '../../lib/teact/teact'; import React, { memo, useCallback, useEffect, useLayoutEffect, useRef, useState, } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import type { GlobalState } from '../../global/types'; import type { LangCode } from '../../types'; import type { ApiCountryCode } from '../../api/types'; import { IS_SAFARI, IS_TOUCH_ENV } from '../../util/windowEnvironment'; import { preloadImage } from '../../util/files'; import preloadFonts from '../../util/fonts'; import { pick } from '../../util/iteratees'; import { formatPhoneNumber, getCountryCodesByIso, getCountryFromPhoneNumber } from '../../util/phoneNumber'; import { setLanguage } from '../../util/langProvider'; import useLang from '../../hooks/useLang'; import useFlag from '../../hooks/useFlag'; import useLangString from '../../hooks/useLangString'; import { getSuggestedLanguage } from './helpers/getSuggestedLanguage'; import Button from '../ui/Button'; import Checkbox from '../ui/Checkbox'; import InputText from '../ui/InputText'; import Loading from '../ui/Loading'; import CountryCodeInput from './CountryCodeInput'; type StateProps = Pick & { language?: LangCode; phoneCodeList: ApiCountryCode[]; }; const MIN_NUMBER_LENGTH = 7; let isPreloadInitiated = false; const AuthPhoneNumber: FC = ({ connectionState, authState, authPhoneNumber, authIsLoading, authIsLoadingQrCode, authError, authRememberMe, authNearestCountry, phoneCodeList, language, }) => { const { setAuthPhoneNumber, setAuthRememberMe, loadNearestCountry, loadCountryList, clearAuthError, goToAuthQrCode, setSettingOption, } = getActions(); const lang = useLang(); // eslint-disable-next-line no-null/no-null const inputRef = useRef(null); const suggestedLanguage = getSuggestedLanguage(); const continueText = useLangString(suggestedLanguage, 'ContinueOnThisLanguage'); const [country, setCountry] = useState(); const [phoneNumber, setPhoneNumber] = useState(); const [isTouched, setIsTouched] = useState(false); const [lastSelection, setLastSelection] = useState<[number, number] | undefined>(); const [isLoading, markIsLoading, unmarkIsLoading] = useFlag(); const fullNumber = country ? `+${country.countryCode} ${phoneNumber || ''}` : phoneNumber; const canSubmit = fullNumber && fullNumber.replace(/[^\d]+/g, '').length >= MIN_NUMBER_LENGTH; useEffect(() => { if (!IS_TOUCH_ENV) { inputRef.current!.focus(); } }, [country]); useEffect(() => { if (connectionState === 'connectionStateReady' && !authNearestCountry) { loadNearestCountry(); } }, [connectionState, authNearestCountry, loadNearestCountry]); useEffect(() => { if (connectionState === 'connectionStateReady') { loadCountryList({ langCode: language }); } }, [connectionState, language, loadCountryList]); useEffect(() => { if (authNearestCountry && phoneCodeList && !country && !isTouched) { setCountry(getCountryCodesByIso(phoneCodeList, authNearestCountry)[0]); } }, [country, authNearestCountry, isTouched, phoneCodeList]); const parseFullNumber = useCallback((newFullNumber: string) => { if (!newFullNumber.length) { setPhoneNumber(''); } const suggestedCountry = phoneCodeList && getCountryFromPhoneNumber(phoneCodeList, newFullNumber); // Any phone numbers should be allowed, in some cases ignoring formatting const selectedCountry = !country || (suggestedCountry && suggestedCountry.iso2 !== country.iso2) || (!suggestedCountry && newFullNumber.length) ? suggestedCountry : country; if (!country || !selectedCountry || (selectedCountry && selectedCountry.iso2 !== country.iso2)) { setCountry(selectedCountry); } setPhoneNumber(formatPhoneNumber(newFullNumber, selectedCountry)); }, [phoneCodeList, country]); const handleLangChange = useCallback(() => { markIsLoading(); void setLanguage(suggestedLanguage, () => { unmarkIsLoading(); setSettingOption({ language: suggestedLanguage }); }); }, [markIsLoading, setSettingOption, suggestedLanguage, unmarkIsLoading]); useEffect(() => { if (phoneNumber === undefined && authPhoneNumber) { parseFullNumber(authPhoneNumber); } }, [authPhoneNumber, phoneNumber, parseFullNumber]); useLayoutEffect(() => { if (inputRef.current && lastSelection) { inputRef.current.setSelectionRange(...lastSelection); } }, [lastSelection]); const isJustPastedRef = useRef(false); const handlePaste = useCallback(() => { isJustPastedRef.current = true; requestAnimationFrame(() => { isJustPastedRef.current = false; }); }, []); const handleCountryChange = useCallback((value: ApiCountryCode) => { setCountry(value); setPhoneNumber(''); }, []); const handlePhoneNumberChange = useCallback((e: ChangeEvent) => { if (authError) { clearAuthError(); } // This is for further screens. We delay it until user input to speed up the initial loading. if (!isPreloadInitiated) { isPreloadInitiated = true; preloadFonts(); void preloadImage(monkeyPath); } const { value, selectionStart, selectionEnd } = e.target; setLastSelection( selectionStart && selectionEnd && selectionEnd < value.length ? [selectionStart, selectionEnd] : undefined, ); setIsTouched(true); const shouldFixSafariAutoComplete = ( IS_SAFARI && country && fullNumber !== undefined && value.length - fullNumber.length > 1 && !isJustPastedRef.current ); parseFullNumber(shouldFixSafariAutoComplete ? `${country!.countryCode} ${value}` : value); }, [authError, clearAuthError, country, fullNumber, parseFullNumber]); const handleKeepSessionChange = useCallback((e: ChangeEvent) => { setAuthRememberMe(e.target.checked); }, [setAuthRememberMe]); function handleSubmit(event: React.FormEvent) { event.preventDefault(); if (authIsLoading) { return; } if (canSubmit) { setAuthPhoneNumber({ phoneNumber: fullNumber }); } } const handleGoToAuthQrCode = useCallback(() => { goToAuthQrCode(); }, [goToAuthQrCode]); const isAuthReady = authState === 'authorizationStateWaitPhoneNumber'; return (
); }; export default memo(withGlobal( (global): StateProps => { const { settings: { byKey: { language } }, countryList: { phoneCodes: phoneCodeList }, } = global; return { ...pick(global, [ 'connectionState', 'authState', 'authPhoneNumber', 'authIsLoading', 'authIsLoadingQrCode', 'authError', 'authRememberMe', 'authNearestCountry', ]), language, phoneCodeList, }; }, )(AuthPhoneNumber));