Skip to content

Commit

Permalink
refactor: applied new ban form to the player modal
Browse files Browse the repository at this point in the history
  • Loading branch information
tabarra committed May 11, 2024
1 parent c0da83b commit 6f7c5c6
Showing 1 changed file with 34 additions and 192 deletions.
226 changes: 34 additions & 192 deletions panel/src/layout/PlayerModal/PlayerBanTab.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,30 @@
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useAdminPerms } from "@/hooks/auth";
import { PlayerModalRefType, useClosePlayerModal } from "@/hooks/playerModal";
import { ClipboardPasteIcon, ExternalLinkIcon, Loader2Icon } from "lucide-react";
import { useMemo, useRef, useState } from "react";
import { Loader2Icon } from "lucide-react";
import { useRef, useState } from "react";
import { useBackendApi } from "@/hooks/fetch";
import { GenericApiOkResp } from "@shared/genericApiTypes";
import ModalCentralMessage from "@/components/ModalCentralMessage";
import { DropDownSelect, DropDownSelectContent, DropDownSelectItem, DropDownSelectTrigger } from "@/components/dropDownSelect";
import { banDurationToShortString, banDurationToString, cn } from "@/lib/utils";
import { Link, useLocation } from "wouter";
import { DynamicNewItem } from "@/components/DynamicNewBadge";
import type { BanTemplatesDataType } from "@shared/otherTypes";
import BanForm, { BanFormType } from "@/components/BanForm";
import { txToast } from "@/components/TxToaster";


const ADD_NEW_SELECT_OPTION = '!add-new';
const maxReasonSize = 150;
const defaultDurations = ['permanent', '2 hours', '8 hours', '1 day', '2 days', '1 week', '2 weeks'];

type PlayerBanTabProps = {
banTemplates: BanTemplatesDataType[];
playerRef: PlayerModalRefType;
};

export default function PlayerBanTab({ playerRef, banTemplates }: PlayerBanTabProps) {
const reasonRef = useRef<HTMLInputElement>(null);
const customMultiplierRef = useRef<HTMLInputElement>(null);
const setLocation = useLocation()[1];
const [currentDuration, setCurrentDuration] = useState('2 days');
const [customUnits, setCustomUnits] = useState('days');
const banFormRef = useRef<BanFormType>(null);
const [isSaving, setIsSaving] = useState(false);
const { hasPerm } = useAdminPerms();
const closeModal = useClosePlayerModal();
const playerBanApi = useBackendApi<GenericApiOkResp>({
method: 'POST',
path: `/player/ban`,
throwGenericErrors: true,
});

if (!hasPerm('players.ban')) {
Expand All @@ -45,202 +33,56 @@ export default function PlayerBanTab({ playerRef, banTemplates }: PlayerBanTabPr
</ModalCentralMessage>;
}

const handleSubmit = (event?: React.FormEvent<HTMLFormElement>) => {
event?.preventDefault();
const handleSave = () => {
if (!banFormRef.current) return;
const { reason, duration } = banFormRef.current.getData();

if (!reason || reason.length < 3) {
txToast.warning(`The reason must be at least 3 characters long.`);
banFormRef.current.focusReason();
return;
}

setIsSaving(true);
playerBanApi({
queryParams: playerRef,
data: {
reason: reasonRef.current?.value.trim(),
duration: currentDuration === 'custom'
? `${customMultiplierRef.current?.value} ${customUnits}`
: currentDuration,
},
data: {reason, duration},
toastLoadingMessage: 'Banning player...',
genericHandler: {
successMsg: 'Player banned.',
},
success: (data) => {
setIsSaving(false);
if ('success' in data) {
closeModal();
}
closeModal();
},
});
};

const handleTemplateSelectChange = (value: string) => {
if (value === ADD_NEW_SELECT_OPTION) {
setLocation('/settings/ban-templates');
closeModal();
} else {
const template = banTemplates.find(template => template.id === value);
if (!template) return;

const processedDuration = banDurationToString(template.duration);
if (defaultDurations.includes(processedDuration)) {
setCurrentDuration(processedDuration);
} else if (typeof template.duration === 'object') {
setCurrentDuration('custom');
customMultiplierRef.current!.value = template.duration.value.toString();
setCustomUnits(template.duration.unit);
error: (error) => {
setIsSaving(false);
}

reasonRef.current!.value = template.reason;
setTimeout(() => {
reasonRef.current!.focus();
}, 50);
}
}

//Optimization
const processedTemplates = useMemo(() => {
return banTemplates.map((template, index) => {
const duration = banDurationToShortString(template.duration);
const reason = template.reason.length > maxReasonSize
? template.reason.slice(0, maxReasonSize - 3) + '...'
: template.reason;
return (
<DropDownSelectItem
key={index}
value={template.id}
className="focus:bg-secondary focus:text-secondary-foreground"
>
<span
className="inline-block pr-1 font-mono opacity-75 min-w-[4ch]"
>{duration}</span> {reason}
</DropDownSelectItem>
);
});
}, [banTemplates]);
};

return (
<form onSubmit={handleSubmit} className="grid gap-4 p-1">
<div className="flex flex-col gap-3">
<Label htmlFor="banReason" className="">
Reason
</Label>
<div className="flex gap-1">
<Input
id="banReason"
placeholder="The reason for the ban, rule violated, etc."
className="w-full"
ref={reasonRef}
autoFocus
required
/>
<DropDownSelect onValueChange={handleTemplateSelectChange}>
<DropDownSelectTrigger className="tracking-wide">
<button
className={cn(
'relative', //FIXME: REMOVE THIS LINE - DynamicNewItem
'size-10 inline-flex justify-center items-center rounded-md shrink-0',
'ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
'border bg-black/20 shadow-sm',
'hover:bg-primary hover:text-primary-foreground hover:border-primary',
)}
>
<ClipboardPasteIcon className="size-5" />
<DynamicNewItem featName="banTemplates" durationDays={7}>
<div className="absolute rounded-full size-2 -top-1 -right-1 bg-accent" />
</DynamicNewItem>
</button>
</DropDownSelectTrigger>
<DropDownSelectContent className="tracking-wide w-[calc(100vw-1rem)] sm:max-w-screen-sm" align="end">
{!banTemplates.length ? (
<div className="text-warning-inline text-center p-4">
You do not have any template configured. <br />
<Link
href="/settings/ban-templates"
className="cursor-pointer underline hover:text-accent"
onClick={() => { closeModal(); }}
>
Add Ban Template
<ExternalLinkIcon className="inline mr-1 h-4" />
</Link>
</div>
) : null}
{processedTemplates}
{banTemplates.length ? (
<DropDownSelectItem
value={ADD_NEW_SELECT_OPTION}
className="font-bold text-warning-inline"
>
Add Ban Template
<ExternalLinkIcon className="inline mr-1 h-4" />
</DropDownSelectItem>
) : null}
</DropDownSelectContent>
</DropDownSelect>
</div>
</div>
<div className="flex flex-col gap-3">
<Label htmlFor="durationSelect">
Duration
</Label>
<div className="space-y-1">
<Select
onValueChange={setCurrentDuration}
value={currentDuration}
>
<SelectTrigger id="durationSelect" className="tracking-wide">
<SelectValue placeholder="Select Duration" />
</SelectTrigger>
<SelectContent className="tracking-wide">
<SelectItem value="custom" className="font-bold">Custom (set below)</SelectItem>
<SelectItem value="2 hours">2 HOURS</SelectItem>
<SelectItem value="8 hours">8 HOURS</SelectItem>
<SelectItem value="1 day">1 DAY</SelectItem>
<SelectItem value="2 days">2 DAYS</SelectItem>
<SelectItem value="1 week">1 WEEK</SelectItem>
<SelectItem value="2 weeks">2 WEEKS</SelectItem>
<SelectItem value="permanent" className="font-bold">Permanent</SelectItem>
</SelectContent>
</Select>
<div className="flex flex-row gap-2">
<Input
id="durationMultiplier"
type="number"
placeholder="123"
required
disabled={currentDuration !== 'custom'}
ref={customMultiplierRef}
/>
<Select
onValueChange={setCustomUnits}
value={customUnits}
>
<SelectTrigger
className="tracking-wide"
id="durationUnits"
disabled={currentDuration !== 'custom'}
>
<SelectValue />
</SelectTrigger>
<SelectContent className="tracking-wide">
<SelectItem value="hours">HOURS</SelectItem>
<SelectItem value="days">DAYS</SelectItem>
<SelectItem value="weeks">WEEKS</SelectItem>
<SelectItem value="months">MONTHS</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
<div className="grid gap-4 p-1">
<BanForm
ref={banFormRef}
banTemplates={banTemplates}
disabled={isSaving}
onNavigateAway={() => { closeModal(); }}
/>
<div className="flex place-content-end">
<Button
variant="destructive"
size="sm"
type="submit"
variant="destructive"
disabled={isSaving}
onClick={handleSave}
>
{isSaving
? <span className="flex items-center leading-relaxed">
{isSaving ? (
<span className="flex items-center leading-relaxed">
<Loader2Icon className="inline animate-spin h-4" /> Banning...
</span>
: 'Apply Ban'}
) : 'Apply Ban'}
</Button>
</div>
</form>
</div>
);
}

0 comments on commit 6f7c5c6

Please sign in to comment.