mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 07:26:19 +00:00
🆙 Init V3
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user