1
0
Fork 0
polaris-task-force/src/components/frontend/intelligence/SideOpControls.tsx
Z8MB1E 227885daf7 feat(missions): let side op creators mark their op as Ready
Adds a 'Mark as Ready' control on the mission detail page (owner or missions:update gated) plus the markSideOpReady server action. Status-gated to Concept/Planning/Scheduled; emits a mission:ready event. Stops the daily ready-reminders and lets missionEmbeds post the roll-call.
2026-09-07 05:07:48 -04:00

121 lines
3.7 KiB
TypeScript

"use client";
import { useState, useTransition } from "react";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { CheckCircleIcon, PencilIcon, XCircleIcon } from "lucide-react";
import { Item, ItemContent, ItemHeader, ItemTitle } from "@/components/ui/item";
import { Button } from "@/components/ui/button";
import {
cancelFridaySideOp,
markSideOpReady,
} from "@/app/(frontend)/intelligence/missions/actions";
import {
CreateSideOpDialog,
type SideOpDialogInitial,
type SideOpDialogOption,
} from "./CreateSideOpDialog";
interface SideOpControlsProps {
initial: SideOpDialogInitial;
status: string;
maps: SideOpDialogOption[];
campaigns: SideOpDialogOption[];
modLists: SideOpDialogOption[];
}
export function SideOpControls({
initial,
status,
maps,
campaigns,
modLists,
}: SideOpControlsProps) {
const router = useRouter();
const [editOpen, setEditOpen] = useState(false);
const [confirmingCancel, setConfirmingCancel] = useState(false);
const [isPending, startTransition] = useTransition();
const isTerminal = status === "Completed" || status === "Cancelled";
const canMarkReady = ["Concept", "Planning", "Scheduled"].includes(status);
function handleMarkReady() {
startTransition(async () => {
const result = await markSideOpReady(initial.missionId);
if (!result.success) {
toast.error(result.error ?? "Failed to mark the side operation as Ready.");
return;
}
toast.success("Side operation marked as Ready. Roll-call can now be posted.");
router.refresh();
});
}
function handleCancel() {
if (!confirmingCancel) {
setConfirmingCancel(true);
return;
}
startTransition(async () => {
const result = await cancelFridaySideOp(initial.missionId);
if (!result.success) {
toast.error(result.error ?? "Failed to cancel the side operation.");
setConfirmingCancel(false);
return;
}
toast.success("Side operation cancelled. Discord roll-call will be updated.");
setConfirmingCancel(false);
router.refresh();
});
}
if (isTerminal) {
return null;
}
return (
<Item variant="outline">
<ItemHeader>
<ItemTitle>Side Operation Controls</ItemTitle>
</ItemHeader>
<ItemContent>
<div className="flex flex-wrap gap-2">
{canMarkReady && (
<Button size="sm" variant="default" onClick={handleMarkReady} disabled={isPending}>
<CheckCircleIcon className="size-3.5" />
Mark as Ready
</Button>
)}
<Button size="sm" variant="outline" onClick={() => setEditOpen(true)} disabled={isPending}>
<PencilIcon className="size-3.5" />
Edit
</Button>
<Button
size="sm"
variant={confirmingCancel ? "destructive" : "outline"}
onClick={handleCancel}
disabled={isPending}
>
<XCircleIcon className="size-3.5" />
{confirmingCancel ? "Confirm cancel?" : "Cancel mission"}
</Button>
</div>
<p className="text-xs text-muted-foreground">
Marking the operation Ready locks in the details so the Discord roll-call can be posted
and the mission hooks delivered. Editing covers details, campaign, and narrative.
Cancelling frees your scheduling slot and updates the Discord embed.
</p>
</ItemContent>
<CreateSideOpDialog
open={editOpen}
onOpenChange={setEditOpen}
maps={maps}
campaigns={campaigns}
modLists={modLists}
initial={initial}
onSaved={() => router.refresh()}
/>
</Item>
);
}