mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 23:16:21 +00:00
🆙 Init V3
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText, ReportState } from '../../api';
|
||||
import { Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../common';
|
||||
import { useHelp } from '../../hooks';
|
||||
import { DescribeReportView } from './views/DescribeReportView';
|
||||
import { HelpIndexView } from './views/HelpIndexView';
|
||||
import { ReportSummaryView } from './views/ReportSummaryView';
|
||||
import { SanctionSatusView } from './views/SanctionStatusView';
|
||||
import { SelectReportedChatsView } from './views/SelectReportedChatsView';
|
||||
import { SelectReportedUserView } from './views/SelectReportedUserView';
|
||||
import { SelectTopicView } from './views/SelectTopicView';
|
||||
import { NameChangeView } from './views/name-change/NameChangeView';
|
||||
|
||||
export const HelpView: FC<{}> = props =>
|
||||
{
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
const { activeReport = null, setActiveReport = null, report = null } = useHelp();
|
||||
|
||||
const onClose = () =>
|
||||
{
|
||||
setActiveReport(null);
|
||||
setIsVisible(false);
|
||||
};
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const linkTracker: ILinkEventTracker = {
|
||||
linkReceived: (url: string) =>
|
||||
{
|
||||
const parts = url.split('/');
|
||||
|
||||
if(parts.length < 2) return;
|
||||
|
||||
switch(parts[1])
|
||||
{
|
||||
case 'show':
|
||||
setIsVisible(true);
|
||||
return;
|
||||
case 'hide':
|
||||
setIsVisible(false);
|
||||
return;
|
||||
case 'toggle':
|
||||
setIsVisible(prevValue => !prevValue);
|
||||
return;
|
||||
case 'tour':
|
||||
// todo: launch tour
|
||||
return;
|
||||
case 'report':
|
||||
if((parts.length >= 5) && (parts[2] === 'room'))
|
||||
{
|
||||
const roomId = parseInt(parts[3]);
|
||||
const unknown = unescape(parts.splice(4).join('/'));
|
||||
//this.reportRoom(roomId, unknown, "");
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
eventUrlPrefix: 'help/'
|
||||
};
|
||||
|
||||
AddLinkEventTracker(linkTracker);
|
||||
|
||||
return () => RemoveLinkEventTracker(linkTracker);
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!activeReport) return;
|
||||
|
||||
setIsVisible(true);
|
||||
}, [ activeReport ]);
|
||||
|
||||
const CurrentStepView = () =>
|
||||
{
|
||||
if(activeReport)
|
||||
{
|
||||
switch(activeReport.currentStep)
|
||||
{
|
||||
case ReportState.SELECT_USER:
|
||||
return <SelectReportedUserView />;
|
||||
case ReportState.SELECT_CHATS:
|
||||
return <SelectReportedChatsView />;
|
||||
case ReportState.SELECT_TOPICS:
|
||||
return <SelectTopicView />;
|
||||
case ReportState.INPUT_REPORT_MESSAGE:
|
||||
return <DescribeReportView />;
|
||||
case ReportState.REPORT_SUMMARY:
|
||||
return <ReportSummaryView />;
|
||||
}
|
||||
}
|
||||
|
||||
return <HelpIndexView />;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{ isVisible &&
|
||||
<NitroCardView className="nitro-help" theme="primary-slim">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('help.button.cfh') } onCloseClick={ onClose } />
|
||||
<NitroCardContentView className="text-black">
|
||||
<Grid>
|
||||
<Column center overflow="hidden" size={ 5 }>
|
||||
<div className="index-image" />
|
||||
</Column>
|
||||
<Column justifyContent="between" overflow="hidden" size={ 7 }>
|
||||
<CurrentStepView />
|
||||
</Column>
|
||||
</Grid>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView> }
|
||||
<SanctionSatusView />
|
||||
<NameChangeView />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import { FC, useState } from 'react';
|
||||
import { LocalizeText, ReportState, ReportType } from '../../../api';
|
||||
import { Button, Flex, Text } from '../../../common';
|
||||
import { useHelp } from '../../../hooks';
|
||||
|
||||
export const DescribeReportView: FC<{}> = props =>
|
||||
{
|
||||
const [ message, setMessage ] = useState('');
|
||||
const { activeReport = null, setActiveReport = null } = useHelp();
|
||||
|
||||
const submitMessage = () =>
|
||||
{
|
||||
if(message.length < 15) return;
|
||||
|
||||
setActiveReport(prevValue =>
|
||||
{
|
||||
const currentStep = ReportState.REPORT_SUMMARY;
|
||||
|
||||
return { ...prevValue, message, currentStep };
|
||||
});
|
||||
};
|
||||
|
||||
const back = () =>
|
||||
{
|
||||
setActiveReport(prevValue =>
|
||||
{
|
||||
return { ...prevValue, currentStep: (prevValue.currentStep - 1) };
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text fontSize={ 4 }>{ LocalizeText('help.emergency.chat_report.subtitle') }</Text>
|
||||
<Text>{ LocalizeText('help.cfh.input.text') }</Text>
|
||||
</div>
|
||||
<textarea className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem] h-full" value={ message } onChange={ event => setMessage(event.target.value) } />
|
||||
<Flex gap={ 2 } justifyContent="between">
|
||||
<Button disabled={ !(activeReport.reportType === ReportType.BULLY || activeReport.reportType === ReportType.EMERGENCY) } variant="secondary" onClick={ back }>
|
||||
{ LocalizeText('generic.back') }
|
||||
</Button>
|
||||
<Button disabled={ (message.length < 15) } onClick={ submitMessage }>
|
||||
{ LocalizeText('help.emergency.main.submit.button') }
|
||||
</Button>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import { GetCfhStatusMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { DispatchUiEvent, GetConfigurationValue, LocalizeText, ReportState, ReportType, SendMessageComposer } from '../../../api';
|
||||
import { Button, Text } from '../../../common';
|
||||
import { GuideToolEvent } from '../../../events';
|
||||
import { useHelp } from '../../../hooks';
|
||||
|
||||
export const HelpIndexView: FC<{}> = props =>
|
||||
{
|
||||
const { setActiveReport = null } = useHelp();
|
||||
|
||||
const onReportClick = () =>
|
||||
{
|
||||
setActiveReport(prevValue =>
|
||||
{
|
||||
const currentStep = ReportState.SELECT_USER;
|
||||
const reportType = ReportType.BULLY;
|
||||
|
||||
return { ...prevValue, currentStep, reportType };
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col justify-center alignp-items-enter !flex-grow gap-1">
|
||||
<Text fontSize={ 3 }>{ LocalizeText('help.main.frame.title') }</Text>
|
||||
<Text>{ LocalizeText('help.main.self.description') }</Text>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Button onClick={ onReportClick }>{ LocalizeText('help.main.bully.subtitle') }</Button>
|
||||
<Button disabled={ !GetConfigurationValue('guides.enabled') } onClick={ () => DispatchUiEvent(new GuideToolEvent(GuideToolEvent.CREATE_HELP_REQUEST)) }>{ LocalizeText('help.main.help.title') }</Button>
|
||||
<Button disabled={ true }>{ LocalizeText('help.main.self.tips.title') }</Button>
|
||||
</div>
|
||||
<Button textColor="black" variant="link" onClick={ () => SendMessageComposer(new GetCfhStatusMessageComposer(false)) }>{ LocalizeText('help.main.my.sanction.status') }</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
import { CallForHelpFromForumMessageMessageComposer, CallForHelpFromForumThreadMessageComposer, CallForHelpFromIMMessageComposer, CallForHelpFromPhotoMessageComposer, CallForHelpMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { LocalizeText, ReportType, SendMessageComposer } from '../../../api';
|
||||
import { Button, Text } from '../../../common';
|
||||
import { useHelp } from '../../../hooks';
|
||||
|
||||
export const ReportSummaryView: FC<{}> = props =>
|
||||
{
|
||||
const { activeReport = null, setActiveReport = null } = useHelp();
|
||||
|
||||
const submitReport = () =>
|
||||
{
|
||||
const chats: (string | number )[] = [];
|
||||
|
||||
switch(activeReport.reportType)
|
||||
{
|
||||
case ReportType.BULLY:
|
||||
case ReportType.EMERGENCY:
|
||||
case ReportType.ROOM: {
|
||||
const reportedRoomId = ((activeReport.roomId <= 0) ? activeReport.reportedChats[0].roomId : activeReport.roomId);
|
||||
|
||||
activeReport.reportedChats.forEach(entry => chats.push(entry.webId, entry.message));
|
||||
|
||||
SendMessageComposer(new CallForHelpMessageComposer(activeReport.message, activeReport.cfhTopic, activeReport.reportedUserId, reportedRoomId, chats));
|
||||
break;
|
||||
}
|
||||
case ReportType.IM:
|
||||
activeReport.reportedChats.forEach(entry => chats.push(entry.webId, entry.message));
|
||||
|
||||
SendMessageComposer(new CallForHelpFromIMMessageComposer(activeReport.message, activeReport.cfhTopic, activeReport.reportedUserId, chats));
|
||||
break;
|
||||
case ReportType.THREAD:
|
||||
SendMessageComposer(new CallForHelpFromForumThreadMessageComposer(activeReport.groupId, activeReport.threadId, activeReport.cfhTopic, activeReport.message));
|
||||
break;
|
||||
case ReportType.MESSAGE:
|
||||
SendMessageComposer(new CallForHelpFromForumMessageMessageComposer(activeReport.groupId, activeReport.threadId, activeReport.messageId, activeReport.cfhTopic, activeReport.message));
|
||||
break;
|
||||
case ReportType.PHOTO:
|
||||
SendMessageComposer(new CallForHelpFromPhotoMessageComposer(activeReport.extraData, activeReport.roomId, activeReport.reportedUserId, activeReport.cfhTopic, activeReport.roomObjectId));
|
||||
break;
|
||||
}
|
||||
|
||||
setActiveReport(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text fontSize={ 4 }>{ LocalizeText('help.cfh.button.send') }</Text>
|
||||
<Text>{ LocalizeText('help.main.summary') }</Text>
|
||||
</div>
|
||||
<Button variant="success" onClick={ submitReport }>
|
||||
{ LocalizeText('guide.help.request.emergency.submit.button') }
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
import { FC } from 'react';
|
||||
import { LocalizeText } from '../../../api';
|
||||
import { Button, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../common';
|
||||
import { useHelp } from '../../../hooks';
|
||||
|
||||
export const SanctionSatusView: FC<{}> = props =>
|
||||
{
|
||||
const { sanctionInfo = null, setSanctionInfo = null } = useHelp();
|
||||
|
||||
const sanctionLocalization = (param: string, sanctionName: string, length?: number) =>
|
||||
{
|
||||
let localizationName = `help.sanction.${ param }`;
|
||||
|
||||
switch(sanctionName)
|
||||
{
|
||||
case 'ALERT':
|
||||
localizationName = (localizationName + '.alert');
|
||||
break;
|
||||
case 'MUTE':
|
||||
localizationName = (localizationName + '.mute');
|
||||
break;
|
||||
case 'BAN_PERMANENT':
|
||||
localizationName = (localizationName + '.permban');
|
||||
break;
|
||||
default:
|
||||
localizationName = (localizationName + '.ban');
|
||||
if(length > 24)
|
||||
{
|
||||
localizationName = (localizationName + '.days');
|
||||
return LocalizeText(localizationName, [ 'days' ], [ (length / 24).toString() ]);
|
||||
}
|
||||
}
|
||||
|
||||
return LocalizeText(localizationName, [ 'hours' ], [ length.toString() ]);
|
||||
};
|
||||
|
||||
if(!sanctionInfo) return null;
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-help" theme="primary-slim">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('help.sanction.info.title') } onCloseClick={ () => setSanctionInfo(null) } />
|
||||
<NitroCardContentView className="text-black">
|
||||
<Grid>
|
||||
<Column center overflow="hidden" size={ 5 }>
|
||||
<div className="index-image" />
|
||||
</Column>
|
||||
<Column justifyContent="between" overflow="hidden" size={ 7 }>
|
||||
{ (sanctionInfo.sanctionReason === 'cfh.reason.EMPTY')
|
||||
? <div className="col-span-12 font-bold ">{ LocalizeText('help.sanction.current.none') }</div>
|
||||
: <>
|
||||
{ ((sanctionInfo.probationHoursLeft > 0) || (sanctionInfo.isSanctionActive)) &&
|
||||
<div className="col-span-12 font-bold ">{ LocalizeText('help.sanction.probation.reminder') }</div>
|
||||
}
|
||||
<div className={ `col-span-12 font-bold ${ sanctionInfo.isSanctionNew ? 'text-danger' : '' }` }>
|
||||
{ LocalizeText('help.sanction.last.sanction') } { sanctionLocalization('current', sanctionInfo.sanctionName, sanctionInfo.sanctionLengthHours) }
|
||||
</div>
|
||||
<div className="col-span-12">{ LocalizeText('generic.start.time') } { sanctionInfo.sanctionCreationTime }</div>
|
||||
<div className="col-span-12">{ LocalizeText('generic.reason') } { sanctionInfo.sanctionReason }</div>
|
||||
<div className="col-span-12">{ LocalizeText('help.sanction.probation.days.left') } { Math.trunc((sanctionInfo.probationHoursLeft / 24)) + 1 }</div>
|
||||
</>
|
||||
}
|
||||
{ ((sanctionInfo.hasCustomMute) && (!(sanctionInfo.isSanctionActive))) &&
|
||||
<div className="col-span-12 font-bold ">{ LocalizeText('help.sanction.custom.mute') }</div>
|
||||
}
|
||||
{ (sanctionInfo.tradeLockExpiryTime && sanctionInfo.tradeLockExpiryTime.length > 0) &&
|
||||
<div className="col-span-12 font-bold ">{ LocalizeText('trade.locked.until') } { sanctionInfo.tradeLockExpiryTime }</div>
|
||||
}
|
||||
<div className="col-span-12">{ sanctionLocalization('next', sanctionInfo.nextSanctionName, sanctionInfo.nextSanctionLengthHours) }</div>
|
||||
<Button variant="success" onClick={ event => setSanctionInfo(null) }>{ LocalizeText('habbo.way.ok.button') }</Button>
|
||||
</Column>
|
||||
</Grid>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
import { RoomObjectType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useMemo, useState } from 'react';
|
||||
import { ChatEntryType, IChatEntry, LocalizeText, ReportState, ReportType } from '../../../api';
|
||||
import { AutoGrid, Button, Column, Flex, LayoutGridItem, Text } from '../../../common';
|
||||
import { useChatHistory, useHelp } from '../../../hooks';
|
||||
|
||||
export const SelectReportedChatsView: FC<{}> = props =>
|
||||
{
|
||||
const [ selectedChats, setSelectedChats ] = useState<IChatEntry[]>([]);
|
||||
const { activeReport = null, setActiveReport = null } = useHelp();
|
||||
const { chatHistory = [], messengerHistory = [] } = useChatHistory();
|
||||
|
||||
const userChats = useMemo(() =>
|
||||
{
|
||||
switch(activeReport.reportType)
|
||||
{
|
||||
case ReportType.BULLY:
|
||||
case ReportType.EMERGENCY:
|
||||
return chatHistory.filter(chat => (chat.type === ChatEntryType.TYPE_CHAT) && (chat.webId === activeReport.reportedUserId) && (chat.entityType === RoomObjectType.USER));
|
||||
case ReportType.IM:
|
||||
return messengerHistory.filter(chat => (chat.webId === activeReport.reportedUserId) && (chat.type === ChatEntryType.TYPE_IM));
|
||||
}
|
||||
|
||||
return [];
|
||||
}, [ activeReport, chatHistory, messengerHistory ]);
|
||||
|
||||
const selectChat = (chatEntry: IChatEntry) =>
|
||||
{
|
||||
setSelectedChats(prevValue =>
|
||||
{
|
||||
const newValue = [ ...prevValue ];
|
||||
const index = newValue.indexOf(chatEntry);
|
||||
|
||||
if(index >= 0) newValue.splice(index, 1);
|
||||
else newValue.push(chatEntry);
|
||||
|
||||
return newValue;
|
||||
});
|
||||
};
|
||||
|
||||
const submitChats = () =>
|
||||
{
|
||||
if(!selectedChats || (selectedChats.length <= 0)) return;
|
||||
|
||||
setActiveReport(prevValue =>
|
||||
{
|
||||
return { ...prevValue, reportedChats: selectedChats, currentStep: ReportState.SELECT_TOPICS };
|
||||
});
|
||||
};
|
||||
|
||||
const back = () =>
|
||||
{
|
||||
setActiveReport(prevValue =>
|
||||
{
|
||||
return { ...prevValue, currentStep: (prevValue.currentStep - 1) };
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text fontSize={ 4 }>{ LocalizeText('help.emergency.chat_report.subtitle') }</Text>
|
||||
<Text>{ LocalizeText('help.emergency.chat_report.description') }</Text>
|
||||
</div>
|
||||
<Column gap={ 1 } overflow="hidden">
|
||||
{ !userChats || !userChats.length &&
|
||||
<Text>{ LocalizeText('help.cfh.error.no_user_data') }</Text> }
|
||||
{ (userChats.length > 0) &&
|
||||
<AutoGrid columnCount={ 1 } columnMinHeight={ 25 } gap={ 1 } overflow="auto">
|
||||
{ userChats.map((chat, index) =>
|
||||
{
|
||||
return (
|
||||
<LayoutGridItem key={ chat.id } itemActive={ (selectedChats.indexOf(chat) >= 0) } onClick={ event => selectChat(chat) }>
|
||||
<Text>{ chat.message }</Text>
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}) }
|
||||
</AutoGrid> }
|
||||
</Column>
|
||||
<Flex gap={ 2 } justifyContent="between">
|
||||
<Button disabled={ (activeReport.reportType === ReportType.IM) } variant="secondary" onClick={ back }>
|
||||
{ LocalizeText('generic.back') }
|
||||
</Button>
|
||||
<Button disabled={ (selectedChats.length <= 0) } onClick={ submitChats }>
|
||||
{ LocalizeText('help.emergency.main.submit.button') }
|
||||
</Button>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
import { GetSessionDataManager, RoomObjectType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useMemo, useState } from 'react';
|
||||
import { ChatEntryType, IReportedUser, LocalizeText, ReportState } from '../../../api';
|
||||
import { AutoGrid, Button, Column, Flex, LayoutGridItem, Text } from '../../../common';
|
||||
import { useChatHistory, useHelp } from '../../../hooks';
|
||||
|
||||
export const SelectReportedUserView: FC<{}> = props =>
|
||||
{
|
||||
const [ selectedUserId, setSelectedUserId ] = useState(-1);
|
||||
const { chatHistory = [] } = useChatHistory();
|
||||
const { activeReport = null, setActiveReport = null } = useHelp();
|
||||
|
||||
const availableUsers = useMemo(() =>
|
||||
{
|
||||
const users: Map<number, IReportedUser> = new Map();
|
||||
|
||||
chatHistory.forEach(chat =>
|
||||
{
|
||||
if((chat.type === ChatEntryType.TYPE_CHAT) && (chat.entityType === RoomObjectType.USER) && (chat.webId !== GetSessionDataManager().userId) && !users.has(chat.webId)) users.set(chat.webId, { id: chat.webId, username: chat.name });
|
||||
});
|
||||
|
||||
return Array.from(users.values());
|
||||
}, [ chatHistory ]);
|
||||
|
||||
const submitUser = (userId: number) =>
|
||||
{
|
||||
if(userId <= 0) return;
|
||||
|
||||
setActiveReport(prevValue =>
|
||||
{
|
||||
return { ...prevValue, reportedUserId: userId, currentStep: ReportState.SELECT_CHATS };
|
||||
});
|
||||
};
|
||||
|
||||
const selectUser = (userId: number) =>
|
||||
{
|
||||
setSelectedUserId(prevValue =>
|
||||
{
|
||||
if(userId === prevValue) return -1;
|
||||
|
||||
return userId;
|
||||
});
|
||||
};
|
||||
|
||||
const back = () =>
|
||||
{
|
||||
setActiveReport(prevValue =>
|
||||
{
|
||||
return { ...prevValue, currentStep: (prevValue.currentStep - 1) };
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text fontSize={ 4 }>{ LocalizeText('help.emergency.main.step.two.title') }</Text>
|
||||
{ (availableUsers.length > 0) &&
|
||||
<Text>{ LocalizeText('report.user.pick.user') }</Text> }
|
||||
</div>
|
||||
<Column gap={ 1 } overflow="hidden">
|
||||
{ !!!availableUsers.length &&
|
||||
<Text>{ LocalizeText('report.user.error.nolist') }</Text> }
|
||||
{ (availableUsers.length > 0) &&
|
||||
<AutoGrid columnCount={ 1 } columnMinHeight={ 25 } gap={ 1 }>
|
||||
{ availableUsers.map((user, index) =>
|
||||
{
|
||||
return (
|
||||
<LayoutGridItem key={ user.id } itemActive={ (selectedUserId === user.id) } onClick={ event => selectUser(user.id) }>
|
||||
<span dangerouslySetInnerHTML={ { __html: (user.username) } } />
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}) }
|
||||
</AutoGrid> }
|
||||
</Column>
|
||||
<Flex gap={ 2 } justifyContent="between">
|
||||
<Button variant="secondary" onClick={ back }>
|
||||
{ LocalizeText('generic.back') }
|
||||
</Button>
|
||||
<Button disabled={ (selectedUserId <= 0) } onClick={ () => submitUser(selectedUserId) }>
|
||||
{ LocalizeText('help.emergency.main.submit.button') }
|
||||
</Button>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
import { FC, useState } from 'react';
|
||||
import { LocalizeText, ReportState } from '../../../api';
|
||||
import { Button, Column, Flex, Text } from '../../../common';
|
||||
import { useHelp, useModTools } from '../../../hooks';
|
||||
|
||||
export const SelectTopicView: FC<{}> = props =>
|
||||
{
|
||||
const [ selectedCategory, setSelectedCategory ] = useState(-1);
|
||||
const [ selectedTopic, setSelectedTopic ] = useState(-1);
|
||||
const { setActiveReport = null } = useHelp();
|
||||
const { cfhCategories = [] } = useModTools();
|
||||
|
||||
const submitTopic = () =>
|
||||
{
|
||||
if((selectedCategory < 0) || (selectedTopic < 0)) return;
|
||||
|
||||
setActiveReport(prevValue =>
|
||||
{
|
||||
return { ...prevValue, cfhCategory: selectedCategory, cfhTopic: cfhCategories[selectedCategory].topics[selectedTopic].id, currentStep: ReportState.INPUT_REPORT_MESSAGE };
|
||||
});
|
||||
};
|
||||
|
||||
const back = () =>
|
||||
{
|
||||
setActiveReport(prevValue =>
|
||||
{
|
||||
return { ...prevValue, currentStep: (prevValue.currentStep - 1) };
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text fontSize={ 4 }>{ LocalizeText('help.emergency.chat_report.subtitle') }</Text>
|
||||
<Text>{ LocalizeText('help.cfh.pick.topic') }</Text>
|
||||
</div>
|
||||
<Column gap={ 1 } overflow="auto">
|
||||
{ (selectedCategory < 0) &&
|
||||
cfhCategories.map((category, index) => <Button key={ index } variant="danger" onClick={ event => setSelectedCategory(index) }>{ LocalizeText(`help.cfh.reason.${ category.name }`) }</Button>) }
|
||||
{ (selectedCategory >= 0) &&
|
||||
cfhCategories[selectedCategory].topics.map((topic, index) => <Button key={ index } active={ (selectedTopic === index) } variant="danger" onClick={ event => setSelectedTopic(index) }>{ LocalizeText(`help.cfh.topic.${ topic.id }`) }</Button>) }
|
||||
</Column>
|
||||
<Flex gap={ 2 } justifyContent="between">
|
||||
<Button variant="secondary" onClick={ back }>
|
||||
{ LocalizeText('generic.back') }
|
||||
</Button>
|
||||
<Button disabled={ (selectedTopic < 0) } onClick={ submitTopic }>
|
||||
{ LocalizeText('help.emergency.main.submit.button') }
|
||||
</Button>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import { ChangeUserNameMessageComposer, GetSessionDataManager, UserNameChangeMessageEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useState } from 'react';
|
||||
import { LocalizeText, SendMessageComposer } from '../../../../api';
|
||||
import { useMessageEvent } from '../../../../hooks';
|
||||
import { NameChangeLayoutViewProps } from './NameChangeView.types';
|
||||
|
||||
export const NameChangeConfirmationView: FC<NameChangeLayoutViewProps> = props =>
|
||||
{
|
||||
const { username = '', onAction = null } = props;
|
||||
const [ isConfirming, setIsConfirming ] = useState<boolean>(false);
|
||||
|
||||
const confirm = () =>
|
||||
{
|
||||
if(isConfirming) return;
|
||||
|
||||
setIsConfirming(true);
|
||||
SendMessageComposer(new ChangeUserNameMessageComposer(username));
|
||||
};
|
||||
|
||||
useMessageEvent<UserNameChangeMessageEvent>(UserNameChangeMessageEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
if(!parser) return;
|
||||
|
||||
if(parser.webId !== GetSessionDataManager().userId) return;
|
||||
|
||||
onAction('close');
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 h-full">
|
||||
<div className="bg-muted rounded p-2 text-center">{ LocalizeText('tutorial.name_change.info.confirm') }</div>
|
||||
<div className="flex flex-col items-center gap-1 h-full">
|
||||
<div>{ LocalizeText('tutorial.name_change.confirm') }</div>
|
||||
<div className="font-bold ">{ username }</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button className="btn btn-success w-full" disabled={ isConfirming } onClick={ confirm }>{ LocalizeText('generic.ok') }</button>
|
||||
<button className="btn btn-primary w-full" onClick={ () => onAction('close') }>{ LocalizeText('cancel') }</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import { GetSessionDataManager } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { LocalizeText } from '../../../../api';
|
||||
import { NameChangeLayoutViewProps } from './NameChangeView.types';
|
||||
|
||||
export const NameChangeInitView: FC<NameChangeLayoutViewProps> = props =>
|
||||
{
|
||||
const { onAction = null } = props;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 h-full">
|
||||
<div className="bg-muted rounded p-2 text-center">{ LocalizeText('tutorial.name_change.info.main') }</div>
|
||||
<div className="font-bold flex items-center justify-center size-full">{ LocalizeText('tutorial.name_change.current', [ 'name' ], [ GetSessionDataManager().userName ]) }</div>
|
||||
<div className="flex gap-2">
|
||||
<button className="btn btn-success w-full" onClick={ () => onAction('start') }>{ LocalizeText('tutorial.name_change.change') }</button>
|
||||
<button className="btn btn-primary w-full" onClick={ () => onAction('confirmation', GetSessionDataManager().userName) }>{ LocalizeText('tutorial.name_change.keep') }</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
import { CheckUserNameMessageComposer, CheckUserNameResultMessageEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useState } from 'react';
|
||||
import { LocalizeText, SendMessageComposer } from '../../../../api';
|
||||
import { useMessageEvent } from '../../../../hooks';
|
||||
import { NitroInput } from '../../../../layout';
|
||||
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<NameChangeLayoutViewProps> = props =>
|
||||
{
|
||||
const { onAction = null } = props;
|
||||
const [ newUsername, setNewUsername ] = useState<string>('');
|
||||
const [ canProceed, setCanProceed ] = useState<boolean>(false);
|
||||
const [ isChecking, setIsChecking ] = useState<boolean>(false);
|
||||
const [ errorCode, setErrorCode ] = useState<string>(null);
|
||||
const [ suggestions, setSuggestions ] = useState<string[]>([]);
|
||||
|
||||
const check = () =>
|
||||
{
|
||||
if(newUsername === '') return;
|
||||
|
||||
setCanProceed(false);
|
||||
setSuggestions([]);
|
||||
setErrorCode(null);
|
||||
setIsChecking(true);
|
||||
|
||||
SendMessageComposer(new CheckUserNameMessageComposer(newUsername));
|
||||
};
|
||||
|
||||
const handleUsernameChange = (username: string) =>
|
||||
{
|
||||
setCanProceed(false);
|
||||
setSuggestions([]);
|
||||
setErrorCode(null);
|
||||
setNewUsername(username);
|
||||
};
|
||||
|
||||
useMessageEvent<CheckUserNameResultMessageEvent>(CheckUserNameResultMessageEvent, event =>
|
||||
{
|
||||
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');
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full gap-3">
|
||||
<div>{ LocalizeText('tutorial.name_change.info.select') }</div>
|
||||
<div className="flex gap-2">
|
||||
<NitroInput type="text" value={ newUsername } onChange={ event => handleUsernameChange(event.target.value) } />
|
||||
<button className="btn btn-primary" disabled={ newUsername === '' || isChecking } onClick={ check }>{ LocalizeText('tutorial.name_change.check') }</button>
|
||||
</div>
|
||||
{ !errorCode && !canProceed &&
|
||||
<div className="p-2 text-center rounded bg-muted">{ LocalizeText('help.tutorial.name.info') }</div> }
|
||||
{ errorCode &&
|
||||
<div className="p-2 text-center text-white rounded bg-danger">{ LocalizeText(`help.tutorial.name.${ errorCode }`, [ 'name' ], [ newUsername ]) }</div> }
|
||||
{ canProceed &&
|
||||
<div className="p-2 text-center text-white rounded bg-success">{ LocalizeText('help.tutorial.name.available', [ 'name' ], [ newUsername ]) }</div> }
|
||||
{ suggestions &&
|
||||
<div className="flex flex-col gap-2">
|
||||
{ suggestions.map((suggestion, index) => <div key={ index } className="p-1 rounded cursor-pointer col bg-muted" onClick={ () => handleUsernameChange(suggestion) }>{ suggestion }</div>) }
|
||||
</div> }
|
||||
<div className="flex gap-2">
|
||||
<button className="w-full btn btn-success" disabled={ !canProceed } onClick={ () => onAction('confirmation', newUsername) }>{ LocalizeText('tutorial.name_change.pick') }</button>
|
||||
<button className="w-full btn btn-primary" onClick={ () => onAction('close') }>{ LocalizeText('cancel') }</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
import { FC, useMemo, useState } from 'react';
|
||||
import { LocalizeText } from '../../../../api';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common';
|
||||
import { HelpNameChangeEvent } from '../../../../events';
|
||||
import { useUiEvent } from '../../../../hooks';
|
||||
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<boolean>(false);
|
||||
const [ layout, setLayout ] = useState<string>(INIT);
|
||||
const [ newUsername, setNewUsername ] = useState<string>('');
|
||||
|
||||
const onAction = (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 ]);
|
||||
|
||||
useUiEvent<HelpNameChangeEvent>(HelpNameChangeEvent.INIT, event =>
|
||||
{
|
||||
setLayout(INIT);
|
||||
setIsVisible(true);
|
||||
});
|
||||
|
||||
if(!isVisible) return null;
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-change-username" theme="primary-slim">
|
||||
<NitroCardHeaderView headerText={ LocalizeText(titleKey) } onCloseClick={ () => onAction('close') } />
|
||||
<NitroCardContentView className="text-black">
|
||||
{ layout === INIT && <NameChangeInitView onAction={ onAction } /> }
|
||||
{ layout === INPUT && <NameChangeInputView onAction={ onAction } /> }
|
||||
{ layout === CONFIRMATION && <NameChangeConfirmationView username={ newUsername } onAction={ onAction } /> }
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface NameChangeLayoutViewProps
|
||||
{
|
||||
username?: string;
|
||||
onAction: (action: string, value?: string) => void;
|
||||
}
|
||||
Reference in New Issue
Block a user