122 lines
3.8 KiB
TypeScript
122 lines
3.8 KiB
TypeScript
import type { FC } from '../../lib/teact/teact';
|
|
import React, {
|
|
memo, useCallback, useEffect, useMemo, useState,
|
|
} from '../../lib/teact/teact';
|
|
import { getActions } from '../../global';
|
|
|
|
import { TME_LINK_PREFIX } from '../../config';
|
|
import {
|
|
isUsernameValid, MAX_USERNAME_LENGTH, MIN_UPDATE_USERNAME_LENGTH, USERNAME_REGEX,
|
|
} from '../../util/entities/username';
|
|
import { debounce } from '../../util/schedulers';
|
|
|
|
import useOldLang from '../../hooks/useOldLang';
|
|
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
|
|
|
import InputText from '../ui/InputText';
|
|
|
|
type OwnProps = {
|
|
currentUsername?: string;
|
|
asLink?: boolean;
|
|
isLoading?: boolean;
|
|
isUsernameAvailable?: boolean;
|
|
checkedUsername?: string;
|
|
onChange: (value: string) => void;
|
|
};
|
|
|
|
const LINK_PREFIX_REGEX = /https:\/\/t\.me\/?/i;
|
|
|
|
const runDebouncedForCheckUsername = debounce((cb) => cb(), 250, false);
|
|
|
|
const UsernameInput: FC<OwnProps> = ({
|
|
currentUsername,
|
|
asLink,
|
|
isLoading,
|
|
isUsernameAvailable,
|
|
checkedUsername,
|
|
onChange,
|
|
}) => {
|
|
const { checkUsername, checkPublicLink } = getActions();
|
|
const [username, setUsername] = useState(currentUsername || '');
|
|
|
|
const lang = useOldLang();
|
|
const langPrefix = asLink ? 'SetUrl' : 'Username';
|
|
const label = asLink ? lang('SetUrlPlaceholder') : lang('Username');
|
|
|
|
const previousIsUsernameAvailable = usePreviousDeprecated(isUsernameAvailable);
|
|
const renderingIsUsernameAvailable = currentUsername !== username
|
|
? (isUsernameAvailable ?? previousIsUsernameAvailable) : undefined;
|
|
const isChecking = username && currentUsername !== username && checkedUsername !== username;
|
|
|
|
const [usernameSuccess, usernameError] = useMemo(() => {
|
|
if (!username.length) {
|
|
return [];
|
|
}
|
|
|
|
if (username.length < MIN_UPDATE_USERNAME_LENGTH) {
|
|
return [undefined, lang(`${langPrefix}InvalidShort`)];
|
|
}
|
|
if (username.length > MAX_USERNAME_LENGTH) {
|
|
return [undefined, lang(`${langPrefix}InvalidLong`)];
|
|
}
|
|
if (!USERNAME_REGEX.test(username)) {
|
|
return [undefined, lang(`${langPrefix}Invalid`)];
|
|
}
|
|
|
|
if (renderingIsUsernameAvailable === undefined || isChecking) {
|
|
return [];
|
|
}
|
|
|
|
// Variable `isUsernameAvailable` is initialized with `undefined`, so a strict false check is required
|
|
return [
|
|
renderingIsUsernameAvailable ? lang(`${langPrefix}Available`, label) : undefined,
|
|
renderingIsUsernameAvailable === false ? lang(`${langPrefix}InUse`) : undefined,
|
|
];
|
|
}, [username, renderingIsUsernameAvailable, isChecking, lang, langPrefix, label]);
|
|
|
|
useEffect(() => {
|
|
setUsername(currentUsername || '');
|
|
}, [asLink, currentUsername]);
|
|
|
|
const handleUsernameChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const value = e.target.value.trim();
|
|
// Prevent prefix editing
|
|
if (asLink && !value.match(LINK_PREFIX_REGEX)) {
|
|
if (!value.length) {
|
|
setUsername('');
|
|
onChange?.('');
|
|
}
|
|
return;
|
|
}
|
|
const newUsername = value.replace(LINK_PREFIX_REGEX, '');
|
|
|
|
setUsername(newUsername);
|
|
|
|
const isValid = newUsername === '' ? true : isUsernameValid(newUsername, true);
|
|
if (!isValid) return;
|
|
|
|
onChange?.(newUsername);
|
|
|
|
runDebouncedForCheckUsername(() => {
|
|
if (newUsername !== currentUsername) {
|
|
const check = asLink ? checkPublicLink : checkUsername;
|
|
check({ username: newUsername });
|
|
}
|
|
});
|
|
}, [asLink, checkPublicLink, checkUsername, currentUsername, onChange]);
|
|
|
|
return (
|
|
<InputText
|
|
value={`${asLink ? TME_LINK_PREFIX : ''}${username}`}
|
|
onChange={handleUsernameChange}
|
|
label={isChecking ? lang('Checking') : label}
|
|
error={usernameError}
|
|
success={usernameSuccess}
|
|
readOnly={isLoading}
|
|
teactExperimentControlled
|
|
/>
|
|
);
|
|
};
|
|
|
|
export default memo(UsernameInput);
|