import type { ChangeEvent } from 'react'; import type { FC } from '../../../lib/teact/teact'; import React, { memo, useCallback, useEffect, useMemo, useState, } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import type { ApiUsername } from '../../../api/types'; import { ApiMediaFormat } from '../../../api/types'; import { ProfileEditProgress } from '../../../types'; import { PURCHASE_USERNAME, TME_LINK_PREFIX, USERNAME_PURCHASE_ERROR } from '../../../config'; import { getChatAvatarHash } from '../../../global/helpers'; import { selectTabState, selectUser, selectUserFullInfo } from '../../../global/selectors'; import { selectCurrentLimit } from '../../../global/selectors/limits'; import { throttle } from '../../../util/schedulers'; import renderText from '../../common/helpers/renderText'; import useHistoryBack from '../../../hooks/useHistoryBack'; import useLang from '../../../hooks/useLang'; import useMedia from '../../../hooks/useMedia'; import usePrevious from '../../../hooks/usePrevious'; import ManageUsernames from '../../common/ManageUsernames'; import SafeLink from '../../common/SafeLink'; import UsernameInput from '../../common/UsernameInput'; import AvatarEditable from '../../ui/AvatarEditable'; import FloatingActionButton from '../../ui/FloatingActionButton'; import InputText from '../../ui/InputText'; import Spinner from '../../ui/Spinner'; import TextArea from '../../ui/TextArea'; type OwnProps = { isActive: boolean; onReset: () => void; }; type StateProps = { currentAvatarHash?: string; currentFirstName?: string; currentLastName?: string; currentBio?: string; progress?: ProfileEditProgress; checkedUsername?: string; editUsernameError?: string; isUsernameAvailable?: boolean; maxBioLength: number; usernames?: ApiUsername[]; }; const runThrottled = throttle((cb) => cb(), 60000, true); const ERROR_FIRST_NAME_MISSING = 'Please provide your first name'; const SettingsEditProfile: FC = ({ isActive, currentAvatarHash, currentFirstName, currentLastName, currentBio, progress, checkedUsername, editUsernameError, isUsernameAvailable, maxBioLength, usernames, onReset, }) => { const { loadCurrentUser, updateProfile, } = getActions(); const lang = useLang(); const firstEditableUsername = useMemo(() => usernames?.find(({ isEditable }) => isEditable), [usernames]); const currentUsername = firstEditableUsername?.username || ''; const [isUsernameTouched, setIsUsernameTouched] = useState(false); const [isProfileFieldsTouched, setIsProfileFieldsTouched] = useState(false); const [error, setError] = useState(); const [photo, setPhoto] = useState(); const [firstName, setFirstName] = useState(currentFirstName || ''); const [lastName, setLastName] = useState(currentLastName || ''); const [bio, setBio] = useState(currentBio || ''); const [editableUsername, setEditableUsername] = useState(currentUsername); const currentAvatarBlobUrl = useMedia(currentAvatarHash, false, ApiMediaFormat.BlobUrl); const isLoading = progress === ProfileEditProgress.InProgress; const isUsernameError = editableUsername === false; const previousIsUsernameAvailable = usePrevious(isUsernameAvailable); const renderingIsUsernameAvailable = isUsernameAvailable ?? previousIsUsernameAvailable; const shouldRenderUsernamesManage = usernames && usernames.length > 1; const isSaveButtonShown = useMemo(() => { if (isUsernameError) { return false; } return Boolean(photo) || isProfileFieldsTouched || (isUsernameTouched && renderingIsUsernameAvailable === true); }, [isUsernameError, photo, isProfileFieldsTouched, isUsernameTouched, renderingIsUsernameAvailable]); useHistoryBack({ isActive, onBack: onReset, }); // Due to the parent Transition, this component never gets unmounted, // that's why we use throttled API call on every update. useEffect(() => { runThrottled(() => { loadCurrentUser(); }); }, [loadCurrentUser]); useEffect(() => { setPhoto(undefined); }, [currentAvatarBlobUrl]); useEffect(() => { setFirstName(currentFirstName || ''); setLastName(currentLastName || ''); setBio(currentBio || ''); }, [currentFirstName, currentLastName, currentBio]); useEffect(() => { setEditableUsername(currentUsername || ''); }, [currentUsername]); useEffect(() => { if (progress === ProfileEditProgress.Complete) { setIsProfileFieldsTouched(false); setIsUsernameTouched(false); setError(undefined); } }, [progress]); const handlePhotoChange = useCallback((newPhoto: File) => { setPhoto(newPhoto); }, []); const handleFirstNameChange = useCallback((e: ChangeEvent) => { setFirstName(e.target.value); setIsProfileFieldsTouched(true); }, []); const handleLastNameChange = useCallback((e: ChangeEvent) => { setLastName(e.target.value); setIsProfileFieldsTouched(true); }, []); const handleBioChange = useCallback((e: ChangeEvent) => { setBio(e.target.value); setIsProfileFieldsTouched(true); }, []); const handleUsernameChange = useCallback((value: string | false) => { setEditableUsername(value); setIsUsernameTouched(currentUsername !== value); }, [currentUsername]); const handleProfileSave = useCallback(() => { const trimmedFirstName = firstName.trim(); const trimmedLastName = lastName.trim(); const trimmedBio = bio.trim(); if (!editableUsername) return; if (!trimmedFirstName.length) { setError(ERROR_FIRST_NAME_MISSING); return; } updateProfile({ photo, ...(isProfileFieldsTouched && { firstName: trimmedFirstName, lastName: trimmedLastName, bio: trimmedBio, }), ...(isUsernameTouched && { username: editableUsername, }), }); }, [ photo, firstName, lastName, bio, isProfileFieldsTouched, editableUsername, isUsernameTouched, updateProfile, ]); function renderPurchaseLink() { const purchaseInfoLink = `${TME_LINK_PREFIX}${PURCHASE_USERNAME}`; return (

{(lang('lng_username_purchase_available') as string) .replace('{link}', '%PURCHASE_LINK%') .split('%') .map((s) => { return (s === 'PURCHASE_LINK' ? : s); })}

); } return (