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/create-mission-form.tsx
6.6 KB
"use client";

import { useState } from "react";
import { useRouter } from "next/navigation";
import { Plus, X, Loader2, Rocket } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Card } from "@/components/ui/card";
import type { Mission, MissionPriority } from "@/lib/missions/types";

export interface OwnerOption {
  id: string;
  label: string;
  active: boolean;
}

const PRIORITIES: MissionPriority[] = ["P0", "P1", "P2", "P3"];

/**
 * Create Mission form. Minimum required fields: Title, Description, Product,
 * Repository, Executive Owner, Priority. Status defaults to Planning on the
 * server. On success it opens the new mission.
 */
export function CreateMissionForm({
  owners,
  defaultRepository,
}: {
  owners: OwnerOption[];
  defaultRepository: string;
}) {
  const router = useRouter();
  const [open, setOpen] = useState(false);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const firstActive = owners.find((o) => o.active)?.id ?? owners[0]?.id ?? "";
  const [form, setForm] = useState({
    title: "",
    description: "",
    product: "",
    repository: defaultRepository,
    executive_owner: firstActive,
    priority: "P1" as MissionPriority,
    company: "",
  });

  async function submit(e: React.FormEvent) {
    e.preventDefault();
    setBusy(true);
    setError(null);
    try {
      const res = await fetch("/api/missions", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(form),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(data.error ?? "Could not create mission.");
      const mission = data.mission as Mission;
      router.push(`/mission-control/${mission.id}`);
    } catch (err) {
      setError(err instanceof Error ? err.message : "Could not create mission.");
      setBusy(false);
    }
  }

  return (
    <div className="flex flex-col items-stretch gap-3 sm:items-end">
      <Button type="button" size="sm" onClick={() => setOpen((v) => !v)}>
        {open ? <X className="h-3.5 w-3.5" /> : <Plus className="h-3.5 w-3.5" />}
        {open ? "Close" : "Create Mission"}
      </Button>

      {open && (
        <Card className="w-full max-w-xl p-5 text-left">
          <form className="space-y-4" onSubmit={submit}>
            <Field label="Title" required>
              <Input
                value={form.title}
                onChange={(e) => setForm({ ...form, title: e.target.value })}
                placeholder="AAF Real Estate Launch"
                required
              />
            </Field>
            <Field label="Description" required>
              <textarea
                value={form.description}
                onChange={(e) => setForm({ ...form, description: e.target.value })}
                rows={3}
                required
                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"
                placeholder="What change will this mission make?"
              />
            </Field>
            <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
              <Field label="Product" required>
                <Input
                  value={form.product}
                  onChange={(e) => setForm({ ...form, product: e.target.value })}
                  placeholder="REOS"
                  required
                />
              </Field>
              <Field label="Repository" required>
                <Input
                  value={form.repository}
                  onChange={(e) => setForm({ ...form, repository: e.target.value })}
                  className="font-mono text-xs"
                  required
                />
              </Field>
            </div>
            <div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
              <Field label="Executive Owner" required>
                <select
                  value={form.executive_owner}
                  onChange={(e) => setForm({ ...form, executive_owner: e.target.value })}
                  className="flex h-10 w-full rounded-md border border-input bg-background px-3 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                >
                  {owners.map((o) => (
                    <option key={o.id} value={o.id}>
                      {o.label}
                      {o.active ? "" : " (inactive)"}
                    </option>
                  ))}
                </select>
              </Field>
              <Field label="Priority" required>
                <select
                  value={form.priority}
                  onChange={(e) =>
                    setForm({ ...form, priority: e.target.value as MissionPriority })
                  }
                  className="flex h-10 w-full rounded-md border border-input bg-background px-3 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                >
                  {PRIORITIES.map((p) => (
                    <option key={p} value={p}>
                      {p}
                    </option>
                  ))}
                </select>
              </Field>
              <Field label="Company">
                <Input
                  value={form.company}
                  onChange={(e) => setForm({ ...form, company: e.target.value })}
                  placeholder="optional"
                />
              </Field>
            </div>

            {error && (
              <div className="rounded-md bg-destructive/10 px-3 py-2 text-[13px] text-destructive">
                {error}
              </div>
            )}

            <div className="flex justify-end">
              <Button type="submit" disabled={busy}>
                {busy ? (
                  <Loader2 className="h-4 w-4 animate-spin" />
                ) : (
                  <Rocket className="h-4 w-4" />
                )}
                Create Mission
              </Button>
            </div>
          </form>
        </Card>
      )}
    </div>
  );
}

function Field({
  label,
  required,
  children,
}: {
  label: string;
  required?: boolean;
  children: React.ReactNode;
}) {
  return (
    <label className="block">
      <span className="mb-1.5 block text-[11px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
        {label}
        {required && <span className="ml-0.5 text-destructive">*</span>}
      </span>
      {children}
    </label>
  );
}

root · /srv/aaf