Intelligence

Artifacts

Browse the repository, read documents, and manage the governance folders. Source, runtime, and infrastructure are read-only.

Repository
.gitignoreDockerfilenext-env.d.tsnext.config.mjspackage-lock.jsonpackage.jsonpostcss.config.mjsREADME.mdtailwind.config.tstsconfig.jsontsconfig.tsbuildinfo
README.md
CONSTITUTION_COMPLIANCE_AUDIT_V1.mdREADME.md
repositories/aaf-holdings/hq01/components/missions/mission-dispatch.tsx
4.1 KB
"use client";

import { useState } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { Send, Loader2, AlertTriangle, CheckCircle2, ArrowRight } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";

/**
 * Dispatch panel on a mission. The instruction is dispatched to the mission's
 * executive owner through the Executive Router, bound to this mission. Dispatch
 * is only possible when the mission has an owner.
 */
export function MissionDispatch({
  missionId,
  owner,
  executable,
  status,
}: {
  missionId: string;
  owner: string | null;
  executable: boolean;
  status: string;
}) {
  const router = useRouter();
  const [instruction, setInstruction] = useState("");
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [launched, setLaunched] = useState<string | null>(null);

  async function dispatch() {
    if (!instruction.trim() || !owner) return;
    setBusy(true);
    setError(null);
    setLaunched(null);
    try {
      const res = await fetch("/api/router/dispatch", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          missionId,
          executiveId: owner,
          instruction,
        }),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(data.error ?? "Dispatch failed.");
      setLaunched(data.session.id as string);
      setInstruction("");
      router.refresh();
    } catch (err) {
      setError(err instanceof Error ? err.message : "Dispatch failed.");
    } finally {
      setBusy(false);
    }
  }

  return (
    <Card className="p-5">
      <div className="mb-3 text-[11px] font-semibold uppercase tracking-[0.1em] text-muted-foreground">
        Dispatch
      </div>
      {!executable ? (
        <p className="flex items-center gap-2 text-[13px] text-amber-700">
          <AlertTriangle className="h-4 w-4 shrink-0" />
          Mission is not executable in state <span className="font-medium">{status}</span>.
          Reach Approved, Planning, or Active to dispatch.
        </p>
      ) : !owner ? (
        <p className="text-[13px] text-muted-foreground">
          Assign an executive owner before dispatching.
        </p>
      ) : (
        <>
          <p className="mb-2 text-[12px] text-muted-foreground">
            Routes to the mission owner (<span className="font-mono">{owner}</span>)
            through the Executive Router and launches a briefed session bound to
            this mission.
          </p>
          <textarea
            value={instruction}
            onChange={(e) => setInstruction(e.target.value)}
            rows={3}
            placeholder="What should the executive do for this mission?"
            className="flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
          />
          {error && (
            <div className="mt-2 flex items-center gap-2 text-[12px] text-destructive">
              <AlertTriangle className="h-3.5 w-3.5" /> {error}
            </div>
          )}
          {launched && (
            <div className="mt-2 flex items-center gap-2 text-[12px] text-emerald-700">
              <CheckCircle2 className="h-3.5 w-3.5" /> Dispatched.
              <Link
                href={`/sessions/${launched}`}
                className="inline-flex items-center gap-0.5 font-medium underline underline-offset-2"
              >
                Watch session <ArrowRight className="h-3 w-3" />
              </Link>
            </div>
          )}
          <div className="mt-3 flex justify-end">
            <Button type="button" size="sm" onClick={dispatch} disabled={busy || !instruction.trim()}>
              {busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Send className="h-3.5 w-3.5" />}
              Dispatch
            </Button>
          </div>
        </>
      )}
    </Card>
  );
}

root · /srv/aaf