268 lines
7.5 KiB
TypeScript
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));
|