import { memo, useCallback, useEffect, useState, } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import type { ApiUser } from '../../../api/types'; import type { GlobalState, TabState } from '../../../global/types'; import { getUserFullName } from '../../../global/helpers'; import { selectAnimatedEmoji, selectUser } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; import { formatPhoneNumber } from '../../../util/phoneNumber'; import { REM } from '../../common/helpers/mediaDimensions'; import renderText from '../../common/helpers/renderText'; import { useShallowSelector } from '../../../hooks/data/useSelector'; import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev'; import useLang from '../../../hooks/useLang'; import useLastCallback from '../../../hooks/useLastCallback'; import Avatar from '../../common/Avatar'; import CustomEmoji from '../../common/CustomEmoji'; import SafeLink from '../../common/SafeLink'; import Button from '../../ui/Button'; import ListItem from '../../ui/ListItem'; import Modal from '../../ui/Modal'; import Switcher from '../../ui/Switcher'; import styles from './UrlAuthModal.module.scss'; export type OwnProps = { modal?: TabState['urlAuth']; }; type StateProps = { bot?: ApiUser; currentUser?: ApiUser; }; type AcceptParams = { wasPhoneShared?: boolean; matchCode?: string; }; type DialogState = 'closed' | 'match-confirm' | 'phone'; const MATCH_CODE_EMOJI_SIZE = 2 * REM; const UrlAuthModal = ({ modal, bot, currentUser, }: OwnProps & StateProps) => { const { acceptBotUrlAuth, acceptLinkUrlAuth, checkUrlAuthMatchCode, declineUrlAuth, } = getActions(); const lang = useLang(); const renderingModal = useCurrentOrPrev(modal, false); const currentBot = useCurrentOrPrev(bot, false); const modalRequest = modal?.request; const renderingRequest = renderingModal?.request; const confirmedMatchCode = renderingModal?.matchCode; const botName = getUserFullName(currentBot) || currentBot?.firstName; const isMatchCodePrecheckPending = Boolean( modalRequest?.matchCodesFirst && modalRequest.matchCodes?.length && !modal?.matchCode, ); const isOpen = Boolean(modal?.url && modal?.request) && !isMatchCodePrecheckPending; const [isWriteAccessChecked, setIsWriteAccessChecked] = useState( () => Boolean(modalRequest?.shouldRequestWriteAccess), ); const [selectedMatchCode, setSelectedMatchCode] = useState(); const [dialogState, setDialogState] = useState('closed'); const effectiveMatchCode = confirmedMatchCode || selectedMatchCode; const isMatchDialogOpen = isMatchCodePrecheckPending || dialogState === 'match-confirm'; const isPhoneDialogOpen = dialogState === 'phone'; const matchCodeEmojisSelector = useCallback((global: GlobalState) => { return renderingRequest?.matchCodes?.map((matchCode) => selectAnimatedEmoji(global, matchCode)); }, [renderingRequest?.matchCodes]); const matchCodeEmojis = useShallowSelector(matchCodeEmojisSelector); useEffect(() => { if (!modalRequest) { setSelectedMatchCode(undefined); setDialogState('closed'); return; } setIsWriteAccessChecked(Boolean(modalRequest.shouldRequestWriteAccess)); setSelectedMatchCode(undefined); setDialogState('closed'); }, [modalRequest]); const handleDismiss = useLastCallback(() => { setDialogState('closed'); declineUrlAuth(); }); const submitAuth = useLastCallback((params: AcceptParams = {}) => { const acceptAction = renderingModal?.button ? acceptBotUrlAuth : acceptLinkUrlAuth; acceptAction({ isWriteAllowed: renderingRequest?.shouldRequestWriteAccess ? isWriteAccessChecked : undefined, wasPhoneShared: params.wasPhoneShared, matchCode: params.matchCode ?? effectiveMatchCode, }); }); const handleConfirm = useLastCallback(() => { if (!renderingRequest) { return; } if (renderingRequest.matchCodes?.length && !effectiveMatchCode) { setDialogState('match-confirm'); return; } if (renderingRequest.shouldRequestPhoneNumber) { setDialogState('phone'); return; } submitAuth(); }); const handleMatchDialogClose = useLastCallback(() => { setDialogState('closed'); }); const handleMatchCodeSelect = useLastCallback((matchCode: string) => { if (isMatchCodePrecheckPending) { checkUrlAuthMatchCode({ matchCode }); return; } setSelectedMatchCode(matchCode); setDialogState('closed'); if (renderingRequest?.shouldRequestPhoneNumber) { setDialogState('phone'); return; } submitAuth({ matchCode }); }); const handlePhoneDialogClose = useLastCallback(() => { setDialogState('closed'); }); const handlePhoneDecision = useLastCallback((wasPhoneShared: boolean) => { setDialogState('closed'); submitAuth({ wasPhoneShared }); }); const handleTriggerWriteAccess = useLastCallback(() => { setIsWriteAccessChecked(!isWriteAccessChecked); }); if (!renderingRequest) { return undefined; } const shouldRenderSessionInfo = Boolean(renderingRequest.platform || renderingRequest.browser || renderingRequest.ip || renderingRequest.region, ); const requestDomain = renderingRequest.domain; const requestDisplayName = renderingRequest.isApp ? renderingRequest.verifiedAppName || lang('BotAuthUnverifiedApp') : requestDomain; const titleTarget = renderingRequest.isApp ? requestDisplayName : ; const formattedPhoneNumber = currentUser?.phoneNumber ? `+${formatPhoneNumber(currentUser.phoneNumber)}` : undefined; const titleText = lang('BotAuthTitle', { url: titleTarget, }, { withNodes: true, }); const descriptionText = lang(renderingRequest.isApp ? 'BotAuthAppSubtitle' : 'BotAuthSiteSubtitle', undefined, { withNodes: true, withMarkdown: true, }); function renderPhoneDialogText() { return ( <>

{lang('BotAuthPhoneNumberText', { domain: requestDisplayName, phone: formattedPhoneNumber || lang('Phone'), }, { withNodes: true, withMarkdown: true, })}

{lang('BotAuthPhoneNumberQuestion')}

); } return ( <>

{titleText}

{descriptionText} {shouldRenderSessionInfo && ( <>
{lang('BotAuthDevice')} {[renderingRequest.platform, renderingRequest.browser].filter(Boolean).join(' ยท ')} {lang('SessionPreviewIp')} {renderingRequest.ip} {lang('SessionPreviewLocation')} {renderingRequest.region}
{lang('BotAuthInfo')} )} {renderingRequest.shouldRequestWriteAccess && ( <> )} > {lang('BotAuthAllowMessages')} {botName && ( {lang( 'BotAuthAllowMessagesInfo', { bot: botName }, { withNodes: true, withMarkdown: true }, )} )} )}
{renderingRequest.matchCodes?.map((matchCode, index) => { const animatedMatchCodeEmoji = matchCodeEmojis?.[index]; return ( ); })}
{lang('BotAuthTitle', { url: titleTarget, }, { withNodes: true, })}
{renderPhoneDialogText()}
); }; export default memo(withGlobal( (global, { modal }): Complete => { const currentUser = selectUser(global, global.currentUserId!); const bot = modal?.request?.botId ? selectUser(global, modal.request.botId) : undefined; return { bot, currentUser, }; }, )(UrlAuthModal));