🆙 Init V3

This commit is contained in:
DuckieTM
2026-01-31 09:10:52 +01:00
commit 7feb10ab15
1733 changed files with 53405 additions and 0 deletions
+356
View File
@@ -0,0 +1,356 @@
import { AddLinkEventTracker, GetSessionDataManager, GuideOnDutyStatusMessageEvent, GuideSessionAttachedMessageEvent, GuideSessionDetachedMessageEvent, GuideSessionEndedMessageEvent, GuideSessionErrorMessageEvent, GuideSessionInvitedToGuideRoomMessageEvent, GuideSessionMessageMessageEvent, GuideSessionOnDutyUpdateMessageComposer, GuideSessionPartnerIsTypingMessageEvent, GuideSessionStartedMessageEvent, ILinkEventTracker, PerkAllowancesMessageEvent, PerkEnum, RemoveLinkEventTracker } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react';
import { GetConfigurationValue, GuideSessionState, GuideToolMessage, GuideToolMessageGroup, LocalizeText, SendMessageComposer } from '../../api';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../common';
import { GuideToolEvent } from '../../events';
import { useMessageEvent, useNotification, useUiEvent } from '../../hooks';
import { GuideToolAcceptView } from './views/GuideToolAcceptView';
import { GuideToolMenuView } from './views/GuideToolMenuView';
import { GuideToolOngoingView } from './views/GuideToolOngoingView';
import { GuideToolUserCreateRequestView } from './views/GuideToolUserCreateRequestView';
import { GuideToolUserFeedbackView } from './views/GuideToolUserFeedbackView';
import { GuideToolUserNoHelpersView } from './views/GuideToolUserNoHelpersView';
import { GuideToolUserPendingView } from './views/GuideToolUserPendingView';
import { GuideToolUserSomethingWrogView } from './views/GuideToolUserSomethingWrogView';
import { GuideToolUserThanksView } from './views/GuideToolUserThanksView';
export const GuideToolView: FC<{}> = props =>
{
const [ isVisible, setIsVisible ] = useState<boolean>(false);
const [ headerText, setHeaderText ] = useState<string>(LocalizeText('guide.help.guide.tool.title'));
const [ noCloseButton, setNoCloseButton ] = useState<boolean>(false);
const [ sessionState, setSessionState ] = useState<string>(GuideSessionState.GUIDE_TOOL_MENU);
const [ isOnDuty, setIsOnDuty ] = useState<boolean>(false);
const [ isHandlingBullyReports, setIsHandlingBullyReports ] = useState<boolean>(false);
const [ isHandlingGuideRequests, setIsHandlingGuideRequests ] = useState<boolean>(false);
const [ isHandlingHelpRequests, setIsHandlingHelpRequests ] = useState<boolean>(false);
const [ helpersOnDuty, setHelpersOnDuty ] = useState<number>(0);
const [ guidesOnDuty, setGuidesOnDuty ] = useState<number>(0);
const [ guardiansOnDuty, setGuardiansOnDuty ] = useState<number>(0);
const [ userRequest, setUserRequest ] = useState<string>('');
const [ helpRequestDescription, setHelpRequestDescription ] = useState<string>(null);
const [ helpRequestAverageTime, setHelpRequestAverageTime ] = useState<number>(0);
const [ ongoingUserId, setOngoingUserId ] = useState<number>(0);
const [ ongoingUsername, setOngoingUsername ] = useState<string>(null);
const [ ongoingFigure, setOngoingFigure ] = useState<string>(null);
const [ ongoingIsTyping, setOngoingIsTyping ] = useState<boolean>(false);
const [ ongoingMessageGroups, setOngoingMessageGroups ] = useState<GuideToolMessageGroup[]>([]);
const { simpleAlert = null } = useNotification();
const updateSessionState = useCallback((newState: string, replacement?: string) =>
{
switch(newState)
{
case GuideSessionState.GUIDE_TOOL_MENU:
setHeaderText(LocalizeText('guide.help.guide.tool.title'));
setNoCloseButton(false);
break;
case GuideSessionState.GUIDE_ACCEPT:
setHeaderText(LocalizeText('guide.help.request.guide.accept.title'));
setNoCloseButton(true);
break;
case GuideSessionState.GUIDE_ONGOING:
setHeaderText(LocalizeText('guide.help.request.guide.ongoing.title', [ 'name' ], [ replacement ]));
setNoCloseButton(true);
break;
case GuideSessionState.USER_CREATE:
setHeaderText(LocalizeText('guide.help.request.user.create.title'));
setNoCloseButton(false);
break;
case GuideSessionState.USER_PENDING:
setHeaderText(LocalizeText('guide.help.request.user.pending.title'));
setNoCloseButton(true);
break;
case GuideSessionState.USER_ONGOING:
setHeaderText(LocalizeText('guide.help.request.user.ongoing.title', [ 'name' ], [ replacement ]));
setNoCloseButton(true);
break;
case GuideSessionState.USER_FEEDBACK:
setHeaderText(LocalizeText('guide.help.request.user.feedback.title'));
setNoCloseButton(true);
break;
case GuideSessionState.USER_THANKS:
setHeaderText(LocalizeText('guide.help.request.user.thanks.title'));
setNoCloseButton(false);
break;
case GuideSessionState.USER_NO_HELPERS:
setHeaderText(LocalizeText('guide.help.request.no_tour_guides.heading'));
setNoCloseButton(false);
break;
case GuideSessionState.USER_SOMETHING_WRONG:
setHeaderText(LocalizeText('guide.help.request.user.guide.disconnected.error.heading'));
setNoCloseButton(false);
break;
}
setSessionState(newState);
setIsVisible(true);
}, []);
const onGuideToolEvent = useCallback((event: GuideToolEvent) =>
{
switch(event.type)
{
case GuideToolEvent.SHOW_GUIDE_TOOL:
setIsVisible(true);
return;
case GuideToolEvent.HIDE_GUIDE_TOOL:
setIsVisible(false);
return;
case GuideToolEvent.TOGGLE_GUIDE_TOOL:
setIsVisible(value => !value);
return;
case GuideToolEvent.CREATE_HELP_REQUEST:
updateSessionState(GuideSessionState.USER_CREATE);
return;
}
}, [ updateSessionState ]);
useUiEvent(GuideToolEvent.SHOW_GUIDE_TOOL, onGuideToolEvent);
useUiEvent(GuideToolEvent.HIDE_GUIDE_TOOL, onGuideToolEvent);
useUiEvent(GuideToolEvent.TOGGLE_GUIDE_TOOL, onGuideToolEvent);
useUiEvent(GuideToolEvent.CREATE_HELP_REQUEST, onGuideToolEvent);
useMessageEvent<PerkAllowancesMessageEvent>(PerkAllowancesMessageEvent, event =>
{
const parser = event.getParser();
if(!parser.isAllowed(PerkEnum.USE_GUIDE_TOOL) && isOnDuty)
{
setIsOnDuty(false);
SendMessageComposer(new GuideSessionOnDutyUpdateMessageComposer(false, false, false, false));
}
});
useMessageEvent<GuideOnDutyStatusMessageEvent>(GuideOnDutyStatusMessageEvent, event =>
{
const parser = event.getParser();
setIsOnDuty(parser.onDuty);
setGuidesOnDuty(parser.guidesOnDuty);
setHelpersOnDuty(parser.helpersOnDuty);
setGuardiansOnDuty(parser.guardiansOnDuty);
});
useMessageEvent<GuideSessionAttachedMessageEvent>(GuideSessionAttachedMessageEvent, event =>
{
const parser = event.getParser();
setHelpRequestDescription(parser.helpRequestDescription);
setHelpRequestAverageTime(parser.roleSpecificWaitTime);
if(parser.asGuide && isOnDuty) updateSessionState(GuideSessionState.GUIDE_ACCEPT);
if(!parser.asGuide) updateSessionState(GuideSessionState.USER_PENDING);
});
useMessageEvent<GuideSessionStartedMessageEvent>(GuideSessionStartedMessageEvent, event =>
{
const parser = event.getParser();
if(isOnDuty)
{
setOngoingUserId(parser.requesterUserId);
setOngoingUsername(parser.requesterName);
setOngoingFigure(parser.requesterFigure);
updateSessionState(GuideSessionState.GUIDE_ONGOING, parser.requesterName);
}
else
{
setOngoingUserId(parser.guideUserId);
setOngoingUsername(parser.guideName);
setOngoingFigure(parser.guideFigure);
updateSessionState(GuideSessionState.USER_ONGOING, parser.guideName);
}
});
useMessageEvent<GuideSessionPartnerIsTypingMessageEvent>(GuideSessionPartnerIsTypingMessageEvent, event =>
{
const parser = event.getParser();
setOngoingIsTyping(parser.isTyping);
});
useMessageEvent<GuideSessionMessageMessageEvent>(GuideSessionMessageMessageEvent, event =>
{
const parser = event.getParser();
const messageGroups = [ ...ongoingMessageGroups ];
let lastGroup = messageGroups[messageGroups.length - 1];
if(!lastGroup || lastGroup.userId !== parser.senderId)
{
lastGroup = new GuideToolMessageGroup(parser.senderId);
messageGroups.push(lastGroup);
}
lastGroup.addChat(new GuideToolMessage(parser.chatMessage));
setOngoingMessageGroups(messageGroups);
});
useMessageEvent<GuideSessionInvitedToGuideRoomMessageEvent>(GuideSessionInvitedToGuideRoomMessageEvent, event =>
{
const parser = event.getParser();
if(parser.roomId !== 0)
{
const messageGroups = [ ...ongoingMessageGroups ];
let lastGroup = messageGroups[messageGroups.length - 1];
const guideId = (isOnDuty ? GetSessionDataManager().userId : ongoingUserId);
if(!lastGroup || lastGroup.userId !== guideId)
{
lastGroup = new GuideToolMessageGroup(guideId);
messageGroups.push(lastGroup);
}
lastGroup.addChat(new GuideToolMessage(parser.roomName, parser.roomId));
setOngoingMessageGroups(messageGroups);
}
});
useMessageEvent<GuideSessionEndedMessageEvent>(GuideSessionEndedMessageEvent, event =>
{
if(isOnDuty)
{
setOngoingUserId(0);
setOngoingUsername(null);
setOngoingFigure(null);
setOngoingIsTyping(false);
setOngoingMessageGroups([]);
updateSessionState(GuideSessionState.GUIDE_TOOL_MENU);
}
else
{
updateSessionState(GuideSessionState.USER_FEEDBACK);
}
});
useMessageEvent<GuideSessionErrorMessageEvent>(GuideSessionErrorMessageEvent, event =>
{
const parser = event.getParser();
// SOMETHING_WRONG_REQUEST = 0, NO_HELPERS_AVAILABLE = 1, NO_GUARDIANS_AVAILABLE = 2
switch(parser['errorCode'])
{
case 0:
updateSessionState(GuideSessionState.USER_SOMETHING_WRONG);
break;
case 1:
case 2:
updateSessionState(GuideSessionState.USER_NO_HELPERS);
break;
}
});
useMessageEvent<GuideSessionDetachedMessageEvent>(GuideSessionDetachedMessageEvent, event =>
{
setOngoingUserId(0);
setOngoingUsername(null);
setOngoingFigure(null);
setOngoingIsTyping(false);
setOngoingMessageGroups([]);
if(isOnDuty)
{
updateSessionState(GuideSessionState.GUIDE_TOOL_MENU);
}
else
{
updateSessionState(GuideSessionState.USER_THANKS);
}
});
useEffect(() =>
{
const linkTracker: ILinkEventTracker = {
linkReceived: (url: string) =>
{
const parts = url.split('/');
if(parts.length < 2) return;
switch(parts[1])
{
case 'tour':
//Create Tour Request
return;
}
},
eventUrlPrefix: 'help/'
};
AddLinkEventTracker(linkTracker);
return () => RemoveLinkEventTracker(linkTracker);
}, []);
const processAction = useCallback((action: string) =>
{
switch(action)
{
case 'close':
setIsVisible(false);
setUserRequest('');
setSessionState(GuideSessionState.GUIDE_TOOL_MENU);
return;
case 'toggle_duty':
if(!isHandlingBullyReports && !isHandlingGuideRequests && !isHandlingHelpRequests)
{
simpleAlert(LocalizeText('guide.help.guide.tool.noqueueselected.message'), null, null, null, LocalizeText('guide.help.guide.tool.noqueueselected.caption'), null);
return;
}
setIsOnDuty(v =>
{
SendMessageComposer(new GuideSessionOnDutyUpdateMessageComposer(!v, v ? false : isHandlingGuideRequests, v ? false : isHandlingHelpRequests, v ? false : isHandlingBullyReports));
return !v;
});
return;
case 'forum_link':
const url: string = GetConfigurationValue<string>('group.homepage.url', '').replace('%groupid%', GetConfigurationValue<string>('guide.help.alpha.groupid', '0'));
window.open(url);
return;
}
}, [ isHandlingBullyReports, isHandlingGuideRequests, isHandlingHelpRequests, simpleAlert ]);
if(!isVisible) return null;
return (
<NitroCardView className="nitro-guide-tool" theme="primary-slim">
<NitroCardHeaderView headerText={ headerText } noCloseButton={ noCloseButton } onCloseClick={ event => processAction('close') } />
<NitroCardContentView className="text-black">
{ (sessionState === GuideSessionState.GUIDE_TOOL_MENU) &&
<GuideToolMenuView guardiansOnDuty={ guardiansOnDuty } guidesOnDuty={ guidesOnDuty } helpersOnDuty={ helpersOnDuty } isHandlingBullyReports={ isHandlingBullyReports } isHandlingGuideRequests={ isHandlingGuideRequests } isHandlingHelpRequests={ isHandlingHelpRequests } isOnDuty={ isOnDuty } processAction={ processAction } setIsHandlingBullyReports={ setIsHandlingBullyReports } setIsHandlingGuideRequests={ setIsHandlingGuideRequests } setIsHandlingHelpRequests={ setIsHandlingHelpRequests } /> }
{ (sessionState === GuideSessionState.GUIDE_ACCEPT) &&
<GuideToolAcceptView helpRequestAverageTime={ helpRequestAverageTime } helpRequestDescription={ helpRequestDescription } /> }
{ [ GuideSessionState.GUIDE_ONGOING, GuideSessionState.USER_ONGOING ].includes(sessionState) &&
<GuideToolOngoingView isGuide={ isOnDuty } isTyping={ ongoingIsTyping } messageGroups={ ongoingMessageGroups } userFigure={ ongoingFigure } userId={ ongoingUserId } userName={ ongoingUsername } /> }
{ (sessionState === GuideSessionState.USER_CREATE) &&
<GuideToolUserCreateRequestView setUserRequest={ setUserRequest } userRequest={ userRequest } /> }
{ (sessionState === GuideSessionState.USER_PENDING) &&
<GuideToolUserPendingView helpRequestAverageTime={ helpRequestAverageTime } helpRequestDescription={ helpRequestDescription } /> }
{ (sessionState === GuideSessionState.USER_FEEDBACK) &&
<GuideToolUserFeedbackView userName={ ongoingUsername } /> }
{ (sessionState === GuideSessionState.USER_THANKS) &&
<GuideToolUserThanksView /> }
{ (sessionState === GuideSessionState.USER_NO_HELPERS) &&
<GuideToolUserNoHelpersView /> }
{ (sessionState === GuideSessionState.USER_SOMETHING_WRONG) &&
<GuideToolUserSomethingWrogView /> }
</NitroCardContentView>
</NitroCardView>
);
};
@@ -0,0 +1,35 @@
import { GuideSessionGuideDecidesMessageComposer } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { LocalizeText, SendMessageComposer } from '../../../api';
import { Button, Text } from '../../../common';
interface GuideToolAcceptViewProps
{
helpRequestDescription: string;
helpRequestAverageTime: number;
}
export const GuideToolAcceptView: FC<GuideToolAcceptViewProps> = props =>
{
const { helpRequestDescription = null, helpRequestAverageTime = 0 } = props;
const answerRequest = (response: boolean) => SendMessageComposer(new GuideSessionGuideDecidesMessageComposer(response));
return (
<div className="flex flex-col">
<div className="flex flex-col gap-0 bg-muted p-2 rounded">
<Text bold>{ LocalizeText('guide.help.request.guide.accept.request.title') }</Text>
<Text variant="muted">{ LocalizeText('guide.help.request.type.1') }</Text>
<Text textBreak wrap>{ helpRequestDescription }</Text>
</div>
<div className="flex flex-col gap-1">
<Button variant="success" onClick={ event => answerRequest(true) }>
{ LocalizeText('guide.help.request.guide.accept.accept.button') }
</Button>
<Button variant="danger" onClick={ event => answerRequest(false) }>
{ LocalizeText('guide.help.request.guide.accept.skip.link') }
</Button>
</div>
</div>
);
};
@@ -0,0 +1,76 @@
import { FC } from 'react';
import { LocalizeText } from '../../../api';
import { Button, Column, Flex, Text } from '../../../common';
interface GuideToolMenuViewProps
{
isOnDuty: boolean;
isHandlingGuideRequests: boolean;
setIsHandlingGuideRequests: (value: boolean) => void;
isHandlingHelpRequests: boolean;
setIsHandlingHelpRequests: (value: boolean) => void;
isHandlingBullyReports: boolean;
setIsHandlingBullyReports: (value: boolean) => void;
guidesOnDuty: number;
helpersOnDuty: number;
guardiansOnDuty: number;
processAction: (action: string) => void;
}
export const GuideToolMenuView: FC<GuideToolMenuViewProps> = props =>
{
const {
isOnDuty = false,
isHandlingGuideRequests = false,
setIsHandlingGuideRequests = null,
isHandlingHelpRequests = false,
setIsHandlingHelpRequests = null,
isHandlingBullyReports = false,
setIsHandlingBullyReports = null,
guidesOnDuty = 0,
helpersOnDuty = 0,
guardiansOnDuty = 0,
processAction = null
} = props;
return (
<div className="flex flex-col">
<Flex alignItems="center" className="bg-muted p-2 rounded" gap={ 2 }>
<div className={ 'duty-switch' + (isOnDuty ? '' : ' off') } onClick={ event => processAction('toggle_duty') } />
<Column gap={ 0 }>
<Text bold>{ LocalizeText('guide.help.guide.tool.yourstatus') }</Text>
<Text>{ LocalizeText(`guide.help.guide.tool.duty.${ (isOnDuty ? 'on' : 'off') }`) }</Text>
</Column>
</Flex>
<div className="flex flex-col gap-1">
<Text bold>{ LocalizeText('guide.help.guide.tool.tickettypeselection.caption') }</Text>
<div className="flex items-center gap-1">
<input checked={ isHandlingGuideRequests } className="form-check-input" disabled={ isOnDuty } type="checkbox" onChange={ event => setIsHandlingGuideRequests(event.target.checked) } />
<Text>{ LocalizeText('guide.help.guide.tool.tickettypeselection.guiderequests') }</Text>
</div>
<div className="flex items-center gap-1">
<input checked={ isHandlingHelpRequests } className="form-check-input" disabled={ isOnDuty } type="checkbox" onChange={ event => setIsHandlingHelpRequests(event.target.checked) } />
<Text>{ LocalizeText('guide.help.guide.tool.tickettypeselection.onlyhelprequests') }</Text>
</div>
<div className="flex items-center gap-1">
<input checked={ isHandlingBullyReports } className="form-check-input" disabled={ isOnDuty } type="checkbox" onChange={ event => setIsHandlingBullyReports(event.target.checked) } />
<Text>{ LocalizeText('guide.help.guide.tool.tickettypeselection.bullyreports') }</Text>
</div>
</div>
<hr className="bg-dark m-0" />
<div className="flex justify-enter items-center gap-2">
<div className="info-icon" />
<div className="flex flex-col gap-1">
<div dangerouslySetInnerHTML={ { __html: LocalizeText('guide.help.guide.tool.guidesonduty', [ 'amount' ], [ guidesOnDuty.toString() ]) } } />
<div dangerouslySetInnerHTML={ { __html: LocalizeText('guide.help.guide.tool.helpersonduty', [ 'amount' ], [ helpersOnDuty.toString() ]) } } />
<div dangerouslySetInnerHTML={ { __html: LocalizeText('guide.help.guide.tool.guardiansonduty', [ 'amount' ], [ guardiansOnDuty.toString() ]) } } />
</div>
</div>
<hr className="bg-dark m-0" />
<Flex gap={ 2 } justifyContent="between">
<Button disabled onClick={ event => processAction('forum_link') }>{ LocalizeText('guide.help.guide.tool.forum.link') }</Button>
<Button disabled>{ LocalizeText('guide.help.guide.tool.skill.link') }</Button>
</Flex>
</div>
);
};
@@ -0,0 +1,131 @@
import { GetSessionDataManager, GuideSessionGetRequesterRoomMessageComposer, GuideSessionInviteRequesterMessageComposer, GuideSessionMessageMessageComposer, GuideSessionRequesterRoomMessageEvent, GuideSessionResolvedMessageComposer } from '@nitrots/nitro-renderer';
import { FC, KeyboardEvent, useCallback, useEffect, useRef, useState } from 'react';
import { GuideToolMessageGroup, LocalizeText, SendMessageComposer, TryVisitRoom } from '../../../api';
import { Button, Column, Flex, LayoutAvatarImageView, Text } from '../../../common';
import { useMessageEvent } from '../../../hooks';
import { NitroInput, classNames } from '../../../layout';
interface GuideToolOngoingViewProps
{
isGuide: boolean;
userId: number;
userName: string;
userFigure: string;
isTyping: boolean;
messageGroups: GuideToolMessageGroup[];
}
export const GuideToolOngoingView: FC<GuideToolOngoingViewProps> = props =>
{
const scrollDiv = useRef<HTMLDivElement>(null);
const { isGuide = false, userId = 0, userName = null, userFigure = null, isTyping = false, messageGroups = [] } = props;
const [ messageText, setMessageText ] = useState<string>('');
useEffect(() =>
{
scrollDiv.current?.scrollIntoView({ block: 'end', behavior: 'smooth' });
}, [ messageGroups ]);
const visit = useCallback(() =>
{
SendMessageComposer(new GuideSessionGetRequesterRoomMessageComposer());
}, []);
const invite = useCallback(() =>
{
SendMessageComposer(new GuideSessionInviteRequesterMessageComposer());
}, []);
const resolve = useCallback(() =>
{
SendMessageComposer(new GuideSessionResolvedMessageComposer());
}, []);
useMessageEvent<GuideSessionRequesterRoomMessageEvent>(GuideSessionRequesterRoomMessageEvent, event =>
{
const parser = event.getParser();
TryVisitRoom(parser.requesterRoomId);
});
const sendMessage = useCallback(() =>
{
if(!messageText || !messageText.length) return;
SendMessageComposer(new GuideSessionMessageMessageComposer(messageText));
setMessageText('');
}, [ messageText ]);
const onKeyDown = useCallback((event: KeyboardEvent<HTMLInputElement>) =>
{
if(event.key !== 'Enter') return;
sendMessage();
}, [ sendMessage ]);
const isOwnChat = useCallback((userId: number) =>
{
return userId === GetSessionDataManager().userId;
}, []);
return (
<Column fullHeight>
<Flex alignItems="center" className="p-2 rounded bg-muted" gap={ 1 } justifyContent="between">
{ isGuide &&
<div className="relative inline-flex align-middle">
<Button onClick={ visit }>{ LocalizeText('guide.help.request.guide.ongoing.visit.button') }</Button>
<Button onClick={ invite }>{ LocalizeText('guide.help.request.guide.ongoing.invite.button') }</Button>
</div> }
{ !isGuide &&
<Column gap={ 0 }>
<Text bold>{ userName }</Text>
<Text>{ LocalizeText('guide.help.request.user.ongoing.guide.desc') }</Text>
</Column> }
<Button disabled variant="danger">{ LocalizeText('guide.help.common.report.link') }</Button>
</Flex>
<Column className="p-2 rounded bg-muted chat-messages" gap={ 1 } overflow="hidden">
<Column overflow="auto">
{ messageGroups.map((group, index) =>
{
return (
<Flex key={ index } fullWidth gap={ 2 } justifyContent={ isOwnChat(group.userId) ? 'end' : 'start' }>
<div className="flex-shrink-0 message-avatar">
{ (!isOwnChat(group.userId)) &&
<LayoutAvatarImageView direction={ 2 } figure={ userFigure } /> }
</div>
<div className={ 'bg-light text-black border-radius mb-2 rounded py-1 px-2 messages-group-' + (isOwnChat(group.userId) ? 'right' : 'left') }>
<Text bold>
{ (isOwnChat(group.userId)) && GetSessionDataManager().userName }
{ (!isOwnChat(group.userId)) && userName }
</Text>
{ group.messages.map((chat, index) => <div key={ index } className={ classNames(chat.roomId ? 'text-break text-underline' : 'text-break', 'chat.roomId' && 'cursor-pointer') } onClick={ () => chat.roomId ? TryVisitRoom(chat.roomId) : null }>{ chat.message }</div>) }
</div>
{ (isOwnChat(group.userId)) &&
<div className="flex-shrink-0 message-avatar">
<LayoutAvatarImageView direction={ 4 } figure={ GetSessionDataManager().figure } />
</div> }
</Flex>
);
}) }
<div ref={ scrollDiv } />
</Column>
</Column>
<div className="flex flex-col gap-1">
<div className="flex gap-1">
<NitroInput placeholder={ LocalizeText('guide.help.request.guide.ongoing.input.empty', [ 'name' ], [ userName ]) } type="text" value={ messageText } onChange={ event => setMessageText(event.target.value) } onKeyDown={ onKeyDown } />
<Button variant="success" onClick={ sendMessage }>
{ LocalizeText('widgets.chatinput.say') }
</Button>
</div>
{ isTyping &&
<Text variant="muted">{ LocalizeText('guide.help.common.typing') }</Text> }
</div>
<Button fullWidth variant="success" onClick={ resolve }>
{ LocalizeText('guide.help.request.' + (isGuide ? 'guide' : 'user') + '.ongoing.close.link') }
</Button>
</Column>
);
};
@@ -0,0 +1,32 @@
import { GuideSessionCreateMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useState } from 'react';
import { LocalizeText, SendMessageComposer } from '../../../api';
import { Button, Text } from '../../../common';
interface GuideToolUserCreateRequestViewProps
{
userRequest: string;
setUserRequest: (value: string) => void;
}
const MIN_REQUEST_LENGTH: number = 15;
export const GuideToolUserCreateRequestView: FC<GuideToolUserCreateRequestViewProps> = props =>
{
const { userRequest = '', setUserRequest = null } = props;
const [ isPending, setIsPending ] = useState<boolean>(false);
const sendRequest = () =>
{
setIsPending(true);
SendMessageComposer(new GuideSessionCreateMessageComposer(1, userRequest));
};
return (
<div className="flex flex-col">
<Text>{ LocalizeText('guide.help.request.user.create.help') }</Text>
<textarea className="request-message" maxLength={ 140 } placeholder={ LocalizeText('guide.help.request.user.create.input.help') } value={ userRequest } onChange={ event => setUserRequest(event.target.value) }></textarea>
<Button fullWidth disabled={ (userRequest.length < MIN_REQUEST_LENGTH) || isPending } variant="success" onClick={ sendRequest }>{ LocalizeText('guide.help.request.user.create.input.button') }</Button>
</div>
);
};
@@ -0,0 +1,43 @@
import { GuideSessionFeedbackMessageComposer } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { LocalizeText, SendMessageComposer } from '../../../api';
import { Button, Column, Flex, Text } from '../../../common';
interface GuideToolUserFeedbackViewProps
{
userName: string;
}
export const GuideToolUserFeedbackView: FC<GuideToolUserFeedbackViewProps> = props =>
{
const { userName = null } = props;
const giveFeedback = (recommend: boolean) => SendMessageComposer(new GuideSessionFeedbackMessageComposer(recommend));
return (
<div className="flex flex-col">
<Flex className="bg-muted p-2 rounded" gap={ 1 } justifyContent="between">
<Column gap={ 0 }>
<Text bold>{ userName }</Text>
<Text>{ LocalizeText('guide.help.request.user.feedback.guide.desc') }</Text>
</Column>
<Button disabled variant="danger">{ LocalizeText('guide.help.common.report.link') }</Button>
</Flex>
<div className="flex flex-col gap-1">
<Text bold>{ LocalizeText('guide.help.request.user.feedback.closed.title') }</Text>
<Text>{ LocalizeText('guide.help.request.user.feedback.closed.desc') }</Text>
</div>
{ userName && (userName.length > 0) &&
<>
<hr className="bg-dark m-0 mt-auto" />
<div className="flex flex-col">
<Text bold center>{ LocalizeText('guide.help.request.user.feedback.question') }</Text>
<div className="flex gap-1">
<Button fullWidth variant="success" onClick={ event => giveFeedback(true) }>{ LocalizeText('guide.help.request.user.feedback.positive.button') }</Button>
<Button fullWidth variant="danger" onClick={ event => giveFeedback(false) }>{ LocalizeText('guide.help.request.user.feedback.negative.button') }</Button>
</div>
</div>
</> }
</div>
);
};
@@ -0,0 +1,13 @@
import { FC } from 'react';
import { LocalizeText } from '../../../api';
import { Text } from '../../../common';
export const GuideToolUserNoHelpersView: FC<{}> = props =>
{
return (
<div className="flex flex-col gap-1">
<Text bold>{ LocalizeText('guide.help.request.no_tour_guides.title') }</Text>
<Text>{ LocalizeText('guide.help.request.no_tour_guides.message') }</Text>
</div>
);
};
@@ -0,0 +1,33 @@
import { GuideSessionRequesterCancelsMessageComposer } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { LocalizeText, SendMessageComposer } from '../../../api';
import { Button, Column, Text } from '../../../common';
interface GuideToolUserPendingViewProps
{
helpRequestDescription: string;
helpRequestAverageTime: number;
}
export const GuideToolUserPendingView: FC<GuideToolUserPendingViewProps> = props =>
{
const { helpRequestDescription = null, helpRequestAverageTime = 0 } = props;
const cancelRequest = () => SendMessageComposer(new GuideSessionRequesterCancelsMessageComposer());
return (
<div className="flex flex-col">
<Column className="bg-muted rounded p-2" gap={ 0 }>
<Text bold>{ LocalizeText('guide.help.request.guide.accept.request.title') }</Text>
<Text variant="muted">{ LocalizeText('guide.help.request.type.1') }</Text>
<Text textBreak wrap>{ helpRequestDescription }</Text>
</Column>
<div className="flex flex-col gap-1">
<Text bold>{ LocalizeText('guide.help.request.user.pending.info.title') }</Text>
<Text>{ LocalizeText('guide.help.request.user.pending.info.message') }</Text>
<Text>{ LocalizeText('guide.help.request.user.pending.info.waiting', [ 'waitingtime' ], [ helpRequestAverageTime.toString() ]) }</Text>
</div>
<Button variant="danger" onClick={ cancelRequest }>{ LocalizeText('guide.help.request.user.pending.cancel.button') }</Button>
</div>
);
};
@@ -0,0 +1,12 @@
import { FC } from 'react';
import { LocalizeText } from '../../../api';
import { Text } from '../../../common';
export const GuideToolUserSomethingWrogView: FC<{}> = props =>
{
return (
<div className="flex flex-col gap-1">
<Text>{ LocalizeText('guide.help.request.user.guide.disconnected.error.desc') }</Text>
</div>
);
};
@@ -0,0 +1,13 @@
import { FC } from 'react';
import { LocalizeText } from '../../../api';
import { Text } from '../../../common';
export const GuideToolUserThanksView: FC<{}> = props =>
{
return (
<div className="flex flex-col gap-1">
<Text bold>{ LocalizeText('guide.help.request.user.thanks.info.title') }</Text>
<Text>{ LocalizeText('guide.help.request.user.thanks.info.desc') }</Text>
</div>
);
};