// Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React, { useEffect, useState, useCallback, useMemo } from 'react'; import classNames from 'classnames'; import type { LocalizerType } from '../types/Util'; import type { UsernameReservationType } from '../types/Username'; import { missingCaseError } from '../util/missingCaseError'; import { getNickname, getDiscriminator } from '../types/Username'; import { UsernameReservationState, UsernameReservationError, } from '../state/ducks/usernameEnums'; import { ConfirmationDialog } from './ConfirmationDialog'; import { Input } from './Input'; import { Spinner } from './Spinner'; import { Modal } from './Modal'; import { Button, ButtonVariant } from './Button'; export type PropsDataType = Readonly<{ i18n: LocalizerType; currentUsername?: string; reservation?: UsernameReservationType; error?: UsernameReservationError; state: UsernameReservationState; minNickname: number; maxNickname: number; }>; export type ActionPropsDataType = Readonly<{ setUsernameReservationError( error: UsernameReservationError | undefined ): void; reserveUsername(nickname: string | undefined): void; confirmUsername(): void; }>; export type ExternalPropsDataType = Readonly<{ onClose(): void; }>; export type PropsType = PropsDataType & ActionPropsDataType & ExternalPropsDataType; export function EditUsernameModalBody({ i18n, currentUsername, reserveUsername, confirmUsername, minNickname, maxNickname, reservation, setUsernameReservationError, error, state, onClose, }: PropsType): JSX.Element { const currentNickname = useMemo(() => { if (!currentUsername) { return undefined; } return getNickname(currentUsername); }, [currentUsername]); const isReserving = state === UsernameReservationState.Reserving; const isConfirming = state === UsernameReservationState.Confirming; const canSave = !isReserving && !isConfirming && reservation !== undefined; const [hasEverChanged, setHasEverChanged] = useState(false); const [nickname, setNickname] = useState(currentNickname); const [isLearnMoreVisible, setIsLearnMoreVisible] = useState(false); useEffect(() => { if (state === UsernameReservationState.Closed) { onClose(); } }, [state, onClose]); const discriminator = useMemo(() => { if (reservation !== undefined) { // New discriminator return getDiscriminator(reservation.username); } // User never changed the nickname - return discriminator from the current // username. if (!hasEverChanged && currentUsername) { return getDiscriminator(currentUsername); } // No reservation, different nickname - no discriminator return undefined; }, [reservation, hasEverChanged, currentUsername]); const errorString = useMemo(() => { if (!error) { return undefined; } if (error === UsernameReservationError.NotEnoughCharacters) { return i18n('icu:ProfileEditor--username--check-character-min', { min: minNickname, }); } if (error === UsernameReservationError.TooManyCharacters) { return i18n('icu:ProfileEditor--username--check-character-max', { max: maxNickname, }); } if (error === UsernameReservationError.CheckStartingCharacter) { return i18n('icu:ProfileEditor--username--check-starting-character'); } if (error === UsernameReservationError.CheckCharacters) { return i18n('icu:ProfileEditor--username--check-characters'); } if (error === UsernameReservationError.UsernameNotAvailable) { return i18n('icu:ProfileEditor--username--unavailable'); } // Displayed through confirmation modal below if ( error === UsernameReservationError.General || error === UsernameReservationError.ConflictOrGone ) { return; } throw missingCaseError(error); }, [error, i18n, minNickname, maxNickname]); useEffect(() => { // Initial effect run if (!hasEverChanged) { return; } reserveUsername(nickname); }, [hasEverChanged, nickname, reserveUsername]); const onChange = useCallback((newNickname: string) => { setHasEverChanged(true); setNickname(newNickname); }, []); const onSave = useCallback(() => { confirmUsername(); }, [confirmUsername]); const onCancel = useCallback(() => { onClose(); }, [onClose]); const onLearnMore = useCallback((e: React.MouseEvent) => { e.preventDefault(); setIsLearnMoreVisible(true); }, []); let title = i18n('icu:ProfileEditor--username--title'); if (nickname && discriminator) { title = `${nickname}${discriminator}`; } const learnMoreTitle = ( <> {i18n('icu:EditUsernameModalBody__learn-more__title')} > ); return ( <>