SPB Git forge

spb/fetcha

Public
11commits 1branches 0releases
1.5 MBsize
maindefault branch
16 days agolast push
TypeScript 97.5% SQL 1.4% Python 0.8%
4.6 KB · 107 lines tsx
Raw Blame History
1"use client";2import * as React from "react";3import { useRouter } from "next/navigation";4import { CheckCircle2, Plus, Undo2 } from "lucide-react";5import { createIncident, resolveIncident } from "@/actions/admin";6import { Button } from "@/components/ui/button";7import { Input, NativeSelect, Textarea } from "@/components/ui/input";8import { Field, Hint, Label } from "@/components/ui/label";9import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";10import { ActionButton } from "./action-button";1112const COMPONENTS = ["api", "dashboard", "residential", "datacenter", "isp", "mobile", "sessions", "billing", "website"];1314export function ResolveIncidentButton({ id, resolved }: { id: string; resolved: boolean }) {15  return resolved ? (16    <ActionButton variant="ghost" size="xs" action={() => resolveIncident(id, false)}>17      <Undo2 className="size-3" /> Reopen18    </ActionButton>19  ) : (20    <ActionButton variant="outline" size="xs" action={() => resolveIncident(id, true)}>21      <CheckCircle2 className="size-3" /> Resolve22    </ActionButton>23  );24}2526export function NewIncidentDialog() {27  const router = useRouter();28  const [open, setOpen] = React.useState(false);29  const [pending, start] = React.useTransition();30  const [error, setError] = React.useState<string | null>(null);31  const [form, setForm] = React.useState({ component: "api", severity: "minor", title: "", body: "" });32  const set = (k: keyof typeof form) => (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => setForm((f) => ({ ...f, [k]: e.target.value }));33  return (34    <>35      <Button variant="primary" size="sm" onClick={() => setOpen(true)}>36        <Plus className="size-3.5" /> Open incident37      </Button>38      <Dialog open={open} onOpenChange={setOpen}>39        <DialogContent size="md">40          <DialogHeader>41            <DialogTitle>Open a status incident</DialogTitle>42            <DialogDescription>Published immediately on the public /status page. Write for customers: describe impact and workaround, never upstream provider names.</DialogDescription>43          </DialogHeader>44          <form45            className="grid gap-3"46            onSubmit={(e) => {47              e.preventDefault();48              start(async () => {49                setError(null);50                const r = await createIncident(form as Parameters<typeof createIncident>[0]);51                if (!r.ok) return setError(r.error);52                setOpen(false);53                setForm({ component: "api", severity: "minor", title: "", body: "" });54                router.refresh();55              });56            }}57          >58            <div className="grid gap-3 sm:grid-cols-2">59              <Field>60                <Label htmlFor="inc-comp">Component</Label>61                <NativeSelect id="inc-comp" value={form.component} onChange={set("component")}>62                  {COMPONENTS.map((c) => (63                    <option key={c} value={c}>64                      {c}65                    </option>66                  ))}67                </NativeSelect>68              </Field>69              <Field>70                <Label htmlFor="inc-sev">Severity</Label>71                <NativeSelect id="inc-sev" value={form.severity} onChange={set("severity")}>72                  <option value="minor">minor</option>73                  <option value="major">major</option>74                  <option value="critical">critical</option>75                  <option value="maintenance">maintenance</option>76                </NativeSelect>77              </Field>78            </div>79            <Field>80              <Label htmlFor="inc-title">Title</Label>81              <Input id="inc-title" value={form.title} onChange={set("title")} placeholder="Elevated error rates on residential network" required />82            </Field>83            <Field>84              <Label htmlFor="inc-body">Update</Label>85              <Textarea id="inc-body" value={form.body} onChange={set("body")} placeholder="We are investigating elevated TARGET_BLOCKED rates for some regions…" />86              <Hint>Public. Uses network classes only.</Hint>87            </Field>88            {error ? (89              <p role="alert" className="text-[13px] text-danger">90                {error}91              </p>92            ) : null}93            <DialogFooter>94              <Button type="button" variant="outline" onClick={() => setOpen(false)}>95                Cancel96              </Button>97              <Button type="submit" variant="primary" loading={pending}>98                Publish99              </Button>100            </DialogFooter>101          </form>102        </DialogContent>103      </Dialog>104    </>105  );106}107