import { ChangeEvent } from 'react'; import React, { FC, useState, useCallback, memo, useEffect, useMemo, } from '../../../lib/teact/teact'; import { withGlobal } from '../../../lib/teact/teactn'; import { ApiMediaFormat } from '../../../api/types'; import { GlobalActions } from '../../../global/types'; import { ProfileEditProgress } from '../../../types'; import { throttle } from '../../../util/schedulers'; import { pick } from '../../../util/iteratees'; import { selectUser } from '../../../modules/selectors'; import { getChatAvatarHash } from '../../../modules/helpers'; import useMedia from '../../../hooks/useMedia'; import useLang from '../../../hooks/useLang'; import AvatarEditable from '../../ui/AvatarEditable'; import FloatingActionButton from '../../ui/FloatingActionButton'; import Spinner from '../../ui/Spinner'; import InputText from '../../ui/InputText'; import renderText from '../../common/helpers/renderText'; import UsernameInput from '../../common/UsernameInput'; type StateProps = { currentAvatarHash?: string; currentFirstName?: string; currentLastName?: string; currentBio?: string; currentUsername?: string; progress?: ProfileEditProgress; isUsernameAvailable?: boolean; }; type DispatchProps = Pick; const runThrottled = throttle((cb) => cb(), 60000, true); const MAX_BIO_LENGTH = 70; const ERROR_FIRST_NAME_MISSING = 'Please provide your first name'; const ERROR_BIO_TOO_LONG = 'Bio can\' be longer than 70 characters'; const SettingsEditProfile: FC = ({ currentAvatarHash, currentFirstName, currentLastName, currentBio, currentUsername, progress, isUsernameAvailable, loadCurrentUser, updateProfile, checkUsername, }) => { 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 [username, setUsername] = useState(currentUsername || ''); const currentAvatarBlobUrl = useMedia(currentAvatarHash, false, ApiMediaFormat.BlobUrl); const isLoading = progress === ProfileEditProgress.InProgress; const isUsernameError = username === false; const isSaveButtonShown = useMemo(() => { if (isUsernameError) { return false; } return Boolean(photo) || isProfileFieldsTouched || isUsernameAvailable === true; }, [photo, isProfileFieldsTouched, isUsernameError, isUsernameAvailable]); // 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(() => { setUsername(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) => { setUsername(value); setIsUsernameTouched(true); }, []); const handleProfileSave = useCallback(() => { const trimmedFirstName = firstName.trim(); const trimmedLastName = lastName.trim(); const trimmedBio = bio.trim(); if (!trimmedFirstName.length) { setError(ERROR_FIRST_NAME_MISSING); return; } if (trimmedBio.length > MAX_BIO_LENGTH) { setError(ERROR_BIO_TOO_LONG); return; } updateProfile({ photo, ...(isProfileFieldsTouched && { firstName: trimmedFirstName, lastName: trimmedLastName, bio: trimmedBio, }), ...(isUsernameTouched && { username, }), }); }, [ photo, firstName, lastName, bio, isProfileFieldsTouched, username, isUsernameTouched, updateProfile, ]); const lang = useLang(); return (

{renderText(lang('lng_settings_about_bio'), ['br', 'simple_markdown'])}

{lang('Username')}

{renderText(lang('UsernameHelp'), ['br', 'simple_markdown'])}

{username && (

{lang('lng_username_link')}
https://t.me/{username}

)}
{isLoading ? ( ) : ( )}
); }; export default memo(withGlobal( (global): StateProps => { const { currentUserId } = global; const { progress, isUsernameAvailable } = global.profileEdit || {}; const currentUser = currentUserId ? selectUser(global, currentUserId) : undefined; if (!currentUser) { return { progress, isUsernameAvailable, }; } const { firstName: currentFirstName, lastName: currentLastName, username: currentUsername, fullInfo, } = currentUser; const { bio: currentBio } = fullInfo || {}; const currentAvatarHash = getChatAvatarHash(currentUser); return { currentAvatarHash, currentFirstName, currentLastName, currentBio, currentUsername, progress, isUsernameAvailable, }; }, (setGlobal, actions): DispatchProps => pick(actions, [ 'loadCurrentUser', 'updateProfile', 'checkUsername', ]), )(SettingsEditProfile));