diff --git a/src/events/help/HelpNameChangeEvent.ts b/src/events/help/HelpNameChangeEvent.ts new file mode 100644 index 00000000..076f5b51 --- /dev/null +++ b/src/events/help/HelpNameChangeEvent.ts @@ -0,0 +1,6 @@ +import { NitroEvent } from '@nitrots/nitro-renderer/src/core/events/NitroEvent'; + +export class HelpNameChangeEvent extends NitroEvent +{ + public static INIT: string = 'HC_NAME_CHANGE_INIT'; +} diff --git a/src/views/help/HelpView.scss b/src/views/help/HelpView.scss index b6ba7d76..3dd5ff68 100644 --- a/src/views/help/HelpView.scss +++ b/src/views/help/HelpView.scss @@ -12,3 +12,7 @@ .nitro-cfh-sanction-status { width: 400px; } + +.nitro-change-username { + width: 300px; +} diff --git a/src/views/help/HelpView.tsx b/src/views/help/HelpView.tsx index 9ebea6eb..3f1a7fab 100644 --- a/src/views/help/HelpView.tsx +++ b/src/views/help/HelpView.tsx @@ -9,6 +9,7 @@ import { IHelpReportState, initialReportState } from './context/HelpContext.type import { HelpMessageHandler } from './HelpMessageHandler'; import { DescribeReportView } from './views/DescribeReportView'; import { HelpIndexView } from './views/HelpIndexView'; +import { NameChangeView } from './views/name-change/NameChangeView'; import { SanctionSatusView } from './views/SanctionStatusView'; import { SelectReportedChatsView } from './views/SelectReportedChatsView'; import { SelectReportedUserView } from './views/SelectReportedUserView'; @@ -76,6 +77,7 @@ export const HelpView: FC<{}> = props => } + ); } diff --git a/src/views/help/views/name-change/NameChangeConfirmationView.tsx b/src/views/help/views/name-change/NameChangeConfirmationView.tsx new file mode 100644 index 00000000..6a91d4c2 --- /dev/null +++ b/src/views/help/views/name-change/NameChangeConfirmationView.tsx @@ -0,0 +1,47 @@ +import { ChangeUserNameMessageComposer, UserNameChangeMessageEvent } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useState } from 'react'; +import { GetSessionDataManager, LocalizeText } from '../../../../api'; +import { CreateMessageHook, SendMessageHook } from '../../../../hooks'; +import { NameChangeLayoutViewProps } from './NameChangeView.types'; + +export const NameChangeConfirmationView:FC = props => +{ + const { username = '', onAction = null } = props; + + const [ isConfirming, setIsConfirming ] = useState(false); + + const onUserNameChangeMessageEvent = useCallback((event: UserNameChangeMessageEvent) => + { + const parser = event.getParser(); + + if(!parser) return; + + if(parser.webId !== GetSessionDataManager().userId) return; + + onAction('close'); + }, [ onAction ]); + + CreateMessageHook(UserNameChangeMessageEvent, onUserNameChangeMessageEvent); + + const confirm = useCallback(() => + { + if(isConfirming) return; + + setIsConfirming(true); + SendMessageHook(new ChangeUserNameMessageComposer(username)); + }, [ isConfirming, username ]); + + return ( +
+
{ LocalizeText('tutorial.name_change.info.confirm') }
+
+
{ LocalizeText('tutorial.name_change.confirm') }
+
{ username }
+
+
+ + +
+
+ ); +} diff --git a/src/views/help/views/name-change/NameChangeInitView.tsx b/src/views/help/views/name-change/NameChangeInitView.tsx new file mode 100644 index 00000000..d995dc3c --- /dev/null +++ b/src/views/help/views/name-change/NameChangeInitView.tsx @@ -0,0 +1,19 @@ +import { FC } from 'react'; +import { GetSessionDataManager, LocalizeText } from '../../../../api'; +import { NameChangeLayoutViewProps } from './NameChangeView.types'; + +export const NameChangeInitView:FC = props => +{ + const { onAction = null } = props; + + return ( +
+
{ LocalizeText('tutorial.name_change.info.main') }
+
{ LocalizeText('tutorial.name_change.current', ['name'], [GetSessionDataManager().userName]) }
+
+ + +
+
+ ); +} diff --git a/src/views/help/views/name-change/NameChangeInputView.tsx b/src/views/help/views/name-change/NameChangeInputView.tsx new file mode 100644 index 00000000..3e1b3d12 --- /dev/null +++ b/src/views/help/views/name-change/NameChangeInputView.tsx @@ -0,0 +1,102 @@ +import { CheckUserNameMessageComposer, CheckUserNameResultMessageEvent } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useState } from 'react'; +import { LocalizeText } from '../../../../api'; +import { CreateMessageHook, SendMessageHook } from '../../../../hooks'; +import { NameChangeLayoutViewProps } from './NameChangeView.types'; + +const AVAILABLE: number = 0; +const TOO_SHORT: number = 2; +const TOO_LONG: number = 3; +const NOT_VALID: number = 4; +const TAKEN_WITH_SUGGESTIONS: number = 5; +const DISABLED: number = 6; + +export const NameChangeInputView:FC = props => +{ + const { onAction = null } = props; + + const [ newUsername, setNewUsername ] = useState(''); + const [ canProceed, setCanProceed ] = useState(false); + const [ isChecking, setIsChecking ] = useState(false); + const [ errorCode, setErrorCode ] = useState(null); + const [ suggestions, setSuggestions ] = useState([]); + + const onCheckUserNameResultMessageEvent = useCallback((event: CheckUserNameResultMessageEvent) => + { + setIsChecking(false); + + const parser = event.getParser(); + + if(!parser) return; + + switch(parser.resultCode) + { + case AVAILABLE: + setCanProceed(true); + break; + case TOO_SHORT: + setErrorCode('short'); + break; + case TOO_LONG: + setErrorCode('long'); + break; + case NOT_VALID: + setErrorCode('invalid'); + break; + case TAKEN_WITH_SUGGESTIONS: + setSuggestions(parser.nameSuggestions); + setErrorCode('taken'); + break; + case DISABLED: + setErrorCode('change_not_allowed'); + } + }, []); + + CreateMessageHook(CheckUserNameResultMessageEvent, onCheckUserNameResultMessageEvent); + + const check = useCallback(() => + { + if(newUsername === '') return; + + setCanProceed(false); + setSuggestions([]); + setErrorCode(null); + + setIsChecking(true); + SendMessageHook(new CheckUserNameMessageComposer(newUsername)); + }, [ newUsername ]); + + const handleUsernameChange = useCallback((username: string) => + { + setCanProceed(false); + setSuggestions([]); + setErrorCode(null); + + setNewUsername(username); + }, []); + + return ( +
+
{ LocalizeText('tutorial.name_change.info.select') }
+
+ handleUsernameChange(e.target.value) } /> + +
+ { !errorCode && !canProceed &&
{ LocalizeText('help.tutorial.name.info') }
} + { errorCode &&
{ LocalizeText(`help.tutorial.name.${errorCode}`, ['name'], [newUsername]) }
} + { canProceed &&
{ LocalizeText('help.tutorial.name.available', ['name'], [newUsername]) }
} + { suggestions &&
+ { + suggestions.map((suggestion, i) => + { + return (
handleUsernameChange(suggestion) }>{ suggestion }
); + }) + } +
} +
+ + +
+
+ ); +} diff --git a/src/views/help/views/name-change/NameChangeView.tsx b/src/views/help/views/name-change/NameChangeView.tsx new file mode 100644 index 00000000..e40613fe --- /dev/null +++ b/src/views/help/views/name-change/NameChangeView.tsx @@ -0,0 +1,68 @@ +import { FC, useCallback, useMemo, useState } from 'react'; +import { LocalizeText } from '../../../../api'; +import { HelpNameChangeEvent } from '../../../../events/help/HelpNameChangeEvent'; +import { useUiEvent } from '../../../../hooks'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout'; +import { NameChangeConfirmationView } from './NameChangeConfirmationView'; +import { NameChangeInitView } from './NameChangeInitView'; +import { NameChangeInputView } from './NameChangeInputView'; + +const INIT: string = 'INIT'; +const INPUT: string = 'INPUT'; +const CONFIRMATION: string = 'CONFIRMATION'; + +export const NameChangeView:FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ layout, setLayout ] = useState(INIT); + const [ newUsername, setNewUsername ] = useState(''); + + const onHelpNameChangeEvent = useCallback((event: HelpNameChangeEvent) => + { + setLayout(INIT); + setIsVisible(true); + }, []); + + useUiEvent(HelpNameChangeEvent.INIT, onHelpNameChangeEvent); + + const onAction = useCallback((action: string, value?: string) => + { + switch(action) + { + case 'start': + setLayout(INPUT); + break; + case 'confirmation': + setNewUsername(value); + setLayout(CONFIRMATION); + break; + case 'close': + setNewUsername(''); + setIsVisible(false); + break; + } + }, []); + + const titleKey = useMemo(() => + { + switch(layout) + { + case INIT: return 'tutorial.name_change.title.main'; + case INPUT: return 'tutorial.name_change.title.select'; + case CONFIRMATION: return 'tutorial.name_change.title.confirm'; + } + }, [layout]); + + if(!isVisible) return null; + + return ( + + onAction('close') } /> + + { layout === INIT && } + { layout === INPUT && } + { layout === CONFIRMATION && } + + + ) +} diff --git a/src/views/help/views/name-change/NameChangeView.types.ts b/src/views/help/views/name-change/NameChangeView.types.ts new file mode 100644 index 00000000..13222e1f --- /dev/null +++ b/src/views/help/views/name-change/NameChangeView.types.ts @@ -0,0 +1,5 @@ +export interface NameChangeLayoutViewProps +{ + username?: string; + onAction: (action: string, value?: string) => void; +} diff --git a/src/views/room/widgets/avatar-info/views/own-avatar/AvatarInfoWidgetOwnAvatarView.tsx b/src/views/room/widgets/avatar-info/views/own-avatar/AvatarInfoWidgetOwnAvatarView.tsx index 8d1d9c46..c83c58b5 100644 --- a/src/views/room/widgets/avatar-info/views/own-avatar/AvatarInfoWidgetOwnAvatarView.tsx +++ b/src/views/room/widgets/avatar-info/views/own-avatar/AvatarInfoWidgetOwnAvatarView.tsx @@ -2,6 +2,7 @@ import { AvatarAction, AvatarExpressionEnum, RoomControllerLevel, RoomObjectCate import { FC, useCallback, useMemo, useState } from 'react'; import { GetCanStandUp, GetCanUseExpression, GetOwnPosture, GetUserProfile, HasHabboClub, HasHabboVip, IsRidingHorse, LocalizeText, RoomWidgetAvatarExpressionMessage, RoomWidgetChangePostureMessage, RoomWidgetDanceMessage, RoomWidgetMessage, RoomWidgetUpdateDecorateModeEvent, RoomWidgetUserActionMessage } from '../../../../../../api'; import { AvatarEditorEvent } from '../../../../../../events'; +import { HelpNameChangeEvent } from '../../../../../../events/help/HelpNameChangeEvent'; import { dispatchUiEvent } from '../../../../../../hooks'; import { CurrencyIcon } from '../../../../../shared/currency-icon/CurrencyIcon'; import { useRoomContext } from '../../../../context/RoomContext'; @@ -42,6 +43,9 @@ export const AvatarInfoWidgetOwnAvatarView: FC