268 lines
7.5 KiB
TypeScript

import type { ChangeEvent } from 'react';
import type { FC } from '../../../lib/teact/teact';
import React, {
memo, useEffect, useMemo, useRef, useState,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { ApiBotInfo, ApiUser } from '../../../api/types';
import { ApiMediaFormat } from '../../../api/types';
import { ManagementProgress } from '../../../types';
import {
getChatAvatarHash, getMainUsername, getUserFirstOrLastName,
} from '../../../global/helpers';
import {
selectBot,
selectTabState,
selectUserFullInfo,
} from '../../../global/selectors';
import { selectCurrentLimit } from '../../../global/selectors/limits';
import renderText from '../../common/helpers/renderText';
import useFlag from '../../../hooks/useFlag';
import useHistoryBack from '../../../hooks/useHistoryBack';
import useLastCallback from '../../../hooks/useLastCallback';
import useMedia from '../../../hooks/useMedia';
import useOldLang from '../../../hooks/useOldLang';
import Icon from '../../common/icons/Icon';
import AvatarEditable from '../../ui/AvatarEditable';
import FloatingActionButton from '../../ui/FloatingActionButton';
import InputText from '../../ui/InputText';
import ListItem from '../../ui/ListItem';
import SelectAvatar from '../../ui/SelectAvatar';
import Spinner from '../../ui/Spinner';
import TextArea from '../../ui/TextArea';
import './Management.scss';
type OwnProps = {
userId: string;
onClose: NoneToVoidFunction;
isActive: boolean;
};
type StateProps = {
userId?: string;
user?: ApiUser;
chatBot?: ApiBotInfo;
currentBio?: string;
progress?: ManagementProgress;
isMuted?: boolean;
maxBioLength: number;
};
const ERROR_NAME_MISSING = 'Please provide name';
const ManageBot: FC<OwnProps & StateProps> = ({
userId,
user,
progress,
onClose,
currentBio,
isActive,
maxBioLength,
}) => {
const {
setBotInfo,
uploadProfilePhoto,
uploadContactProfilePhoto,
startBotFatherConversation,
} = getActions();
const [isFieldTouched, markFieldTouched, unmarkProfileTouched] = useFlag(false);
const [isAvatarTouched, markAvatarTouched, unmarkAvatarTouched] = useFlag(false);
const [error, setError] = useState<string | undefined>();
const lang = useOldLang();
const username = useMemo(() => (user ? getMainUsername(user) : undefined), [user]);
useHistoryBack({
isActive,
onBack: onClose,
});
const currentName = user ? getUserFirstOrLastName(user) : '';
const [photo, setPhoto] = useState<File | undefined>();
const [name, setName] = useState(currentName || '');
const [bio, setBio] = useState(currentBio || '');
const currentAvatarHash = user && getChatAvatarHash(user);
const currentAvatarBlobUrl = useMedia(currentAvatarHash, false, ApiMediaFormat.BlobUrl);
useEffect(() => {
unmarkProfileTouched();
unmarkAvatarTouched();
}, [userId]);
useEffect(() => {
setName(currentName || '');
setBio(currentBio || '');
}, [currentName, currentBio, user]);
useEffect(() => {
setPhoto(undefined);
}, [currentAvatarBlobUrl]);
useEffect(() => {
if (progress === ManagementProgress.Complete) {
unmarkProfileTouched();
unmarkAvatarTouched();
setError(undefined);
}
}, [progress]);
const handleNameChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
markFieldTouched();
if (error === ERROR_NAME_MISSING) {
setError(undefined);
}
});
const handleBioChange = useLastCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
setBio(e.target.value);
markFieldTouched();
});
const handlePhotoChange = useLastCallback((newPhoto: File) => {
setPhoto(newPhoto);
markAvatarTouched();
});
const handleProfileSave = useLastCallback(() => {
const trimmedName = name.trim();
const trimmedBio = bio.trim();
if (!trimmedName.length) {
setError(ERROR_NAME_MISSING);
return;
}
setBotInfo({
...(isFieldTouched && {
bot: user,
name: trimmedName,
description: trimmedBio,
}),
});
if (photo) {
uploadProfilePhoto({
file: photo,
...(isAvatarTouched && { bot: user }),
});
}
});
const handleChangeEditIntro = useLastCallback(() => {
startBotFatherConversation({ param: `${username}-intro` });
});
const handleChangeEditCommands = useLastCallback(() => {
startBotFatherConversation({ param: `${username}-commands` });
});
const handleChangeSettings = useLastCallback(() => {
startBotFatherConversation({ param: `${username}` });
});
// eslint-disable-next-line no-null/no-null
const inputRef = useRef<HTMLInputElement>(null);
const isSuggestRef = useRef(false);
const handleSelectAvatar = useLastCallback((file: File) => {
markAvatarTouched();
uploadContactProfilePhoto({ userId, file, isSuggest: isSuggestRef.current });
});
if (!user) {
return undefined;
}
const isLoading = progress === ManagementProgress.InProgress;
return (
<div className="Management">
<div className="custom-scroll">
<div className="section">
<AvatarEditable
currentAvatarBlobUrl={currentAvatarBlobUrl}
onChange={handlePhotoChange}
title={lang('ChatSetPhotoOrVideo')}
disabled={isLoading}
/>
<InputText
id="user-name"
label={lang('PaymentCheckoutName')}
onChange={handleNameChange}
value={name}
error={error === ERROR_NAME_MISSING ? error : undefined}
teactExperimentControlled
/>
<TextArea
value={bio}
onChange={handleBioChange}
label={lang('DescriptionPlaceholder')}
disabled={isLoading}
maxLength={maxBioLength}
maxLengthIndicator={maxBioLength ? (maxBioLength - bio.length).toString() : undefined}
/>
</div>
<div className="section">
<div className="dialog-buttons">
<ListItem icon="bot-commands-filled" ripple onClick={handleChangeEditIntro}>
<span>{lang('BotEditIntro')}</span>
</ListItem>
<ListItem icon="bot-command" ripple onClick={handleChangeEditCommands}>
<span>{lang('BotEditCommands')}</span>
</ListItem>
<ListItem icon="bots" ripple onClick={handleChangeSettings}>
<span>{lang('BotChangeSettings')}</span>
</ListItem>
<div className="section-info section-info_push">
{renderText(lang('BotManageInfo'), ['links'])}
</div>
</div>
</div>
</div>
<FloatingActionButton
isShown={isFieldTouched || isAvatarTouched}
onClick={handleProfileSave}
disabled={isLoading}
ariaLabel={lang('Save')}
>
{isLoading ? (
<Spinner color="white" />
) : (
<Icon name="check" />
)}
</FloatingActionButton>
<SelectAvatar
onChange={handleSelectAvatar}
inputRef={inputRef}
/>
</div>
);
};
export default memo(withGlobal<OwnProps>(
(global, { userId }): StateProps => {
const user = selectBot(global, userId);
const userFullInfo = selectUserFullInfo(global, userId);
const { progress } = selectTabState(global).management;
const maxBioLength = selectCurrentLimit(global, 'aboutLength');
return {
userId,
user,
progress,
currentBio: userFullInfo?.bio,
maxBioLength,
};
},
)(ManageBot));