diff --git a/docs/dev_notes.md b/docs/dev_notes.md index 90fb6beec..869350c03 100644 --- a/docs/dev_notes.md +++ b/docs/dev_notes.md @@ -16,14 +16,15 @@ ## Highlights - [ ] pre-configured ban/warn reasons with new perm to lock admins to only use them? - - [ ] apply new ban scheme to the web player modal - - [ ] apply new ban scheme to the NUI + - [x] apply new ban scheme to the web player modal + - [x] apply new ban scheme to the NUI - [ ] checklist: - [x] light mode - [x] multiline - [x] mobile - - [ ] better random id (no random id? stable-hash?) - [ ] dialog input sanitization + - [ ] better random id (no random id? stable-hash?) + - [ ] settings enforce unique id - [ ] NEW PAGE: Dashboard - [ ] new performance chart - [ ] number callouts from legacy players page diff --git a/nui/src/components/PlayerModal/Tabs/DialogBanView.tsx b/nui/src/components/PlayerModal/Tabs/DialogBanView.tsx index 27d3c5690..612f90a0d 100644 --- a/nui/src/components/PlayerModal/Tabs/DialogBanView.tsx +++ b/nui/src/components/PlayerModal/Tabs/DialogBanView.tsx @@ -1,6 +1,6 @@ -import React, { useState } from "react"; +import React, { useMemo, useState } from "react"; import { - Alert, + Autocomplete, Box, Button, DialogContent, @@ -21,6 +21,36 @@ import { usePermissionsValue } from "../../../state/permissions.state"; import { DialogLoadError } from "./DialogLoadError"; import { GenericApiErrorResp, GenericApiResp } from "@shared/genericApiTypes"; import { useSetPlayerModalVisibility } from "@nui/src/state/playerModal.state"; +import type { BanTemplatesDataType, BanDurationType } from "@shared/otherTypes"; + +//Helpers - yoinked from the web ui code +const maxReasonSize = 128; +const defaultDurations = ['permanent', '2 hours', '8 hours', '1 day', '2 days', '1 week', '2 weeks']; +const banDurationToString = (duration: BanDurationType) => { + if (duration === 'permanent') return 'permanent'; + if (typeof duration === 'string') return duration; + const pluralizedString = duration.value === 1 ? duration.unit.slice(0, -1) : duration.unit; + return `${duration.value} ${pluralizedString}`; +} +const banDurationToShortString = (duration: BanDurationType) => { + if (typeof duration === 'string') { + return duration === 'permanent' ? 'PERM' : duration; + } + + let suffix: string; + if (duration.unit === 'hours') { + suffix = 'h'; + } else if (duration.unit === 'days') { + suffix = 'd'; + } else if (duration.unit === 'weeks') { + suffix = 'w'; + } else if (duration.unit === 'months') { + suffix = 'mo'; + } else { + suffix = duration.unit; + } + return `${duration.value}${suffix}`; +} const DialogBanView: React.FC = () => { const assocPlayer = useAssociatedPlayerValue(); @@ -51,8 +81,9 @@ const DialogBanView: React.FC = () => { return; } - const actualDuration = - duration === "custom" ? `${customDurLength} ${customDuration}` : duration; + const actualDuration = duration === "custom" + ? `${customDurLength} ${customDuration}` + : duration; fetchWebPipe( `/player/ban?mutex=current&netid=${assocPlayer.id}`, @@ -72,8 +103,7 @@ const DialogBanView: React.FC = () => { }); } else { enqueueSnackbar( - (result as GenericApiErrorResp).error ?? - t("nui_menu.misc.unknown_error"), + (result as GenericApiErrorResp).error ?? t("nui_menu.misc.unknown_error"), { variant: "error" } ); } @@ -137,23 +167,68 @@ const DialogBanView: React.FC = () => { }, ]; + //Handling ban templates + const banTemplates: BanTemplatesDataType[] = (playerDetails as any)?.banTemplates ?? []; + const processedTemplates = useMemo(() => { + return banTemplates.map((template, index) => ({ + id: template.id, + label: template.reason, + })); + }, [banTemplates]); + + const handleTemplateChange = (event: React.SyntheticEvent, value: any, reason: string, details?: any) => { + //reason = One of "createOption", "selectOption", "removeOption", "blur" or "clear". + if (reason !== 'selectOption' || value === null) return; + const template = banTemplates.find(template => template.id === value.id); + if (!template) return; + + const processedDuration = banDurationToString(template.duration); + if (defaultDurations.includes(processedDuration)) { + setDuration(processedDuration); + } else if (typeof template.duration === 'object') { + setDuration('custom'); + setCustomDurLength(template.duration.value.toString()); + setCustomDuration(template.duration.unit); + } + } + + const handleReasonInputChange = (event: React.SyntheticEvent, value: string, reason: string) => { + setReason(value); + } + return ( {t("nui_menu.player_modal.ban.title")}
- setReason(e.currentTarget.value)} + onChange={handleTemplateChange} + onInputChange={handleReasonInputChange} + options={processedTemplates} + renderOption={(props, option, state) => { + const duration = banTemplates.find((t) => t.id === option.id)?.duration ?? '????'; + const reason = option.label.length > maxReasonSize + ? option.label.slice(0, maxReasonSize - 3) + '...' + : option.label; + return
  • + {banDurationToShortString(duration as any)} {reason} +
  • + }} + renderInput={(params) => } /> { ))} {duration === "custom" && ( - - - setCustomDurLength(e.target.value)} - /> - - - setCustomDuration(e.target.value)} - > - {customBanLength.map((option) => ( - - {option.label} - - ))} - - + + setCustomDurLength(e.target.value)} + /> + setCustomDuration(e.target.value)} + > + {customBanLength.map((option) => ( + + {option.label} + + ))} + )}