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%
3.8 KB · 105 lines tsx
Raw Blame History
1"use client";2import * as React from "react";3import { useRouter } from "next/navigation";4import { Archive, Check } from "lucide-react";5import { archiveProject, selectProject } from "@/actions/projects";6import { Button, type ButtonProps } from "@/components/ui/button";7import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";8import { Alert } from "@/components/ui/alert";9import { Input } from "@/components/ui/input";10import { Field, Label } from "@/components/ui/label";1112/** Makes the project the current one (cookie) and refreshes the dashboard. */13export function SelectProjectButton({ projectId, size = "xs", variant = "outline", children }: { projectId: string } & Pick<ButtonProps, "size" | "variant" | "children">) {14  const router = useRouter();15  const [pending, startTransition] = React.useTransition();16  return (17    <Button18      size={size}19      variant={variant}20      loading={pending}21      onClick={() =>22        startTransition(async () => {23          await selectProject(projectId);24          router.refresh();25        })26      }27    >28      {children ?? (29        <>30          <Check /> Select31        </>32      )}33    </Button>34  );35}3637export function ArchiveProjectButton({ projectId, name, disabled, disabledReason }: { projectId: string; name: string; disabled?: boolean; disabledReason?: string }) {38  const router = useRouter();39  const [open, setOpen] = React.useState(false);40  const [confirmName, setConfirmName] = React.useState("");41  const [error, setError] = React.useState<string | null>(null);42  const [pending, startTransition] = React.useTransition();4344  return (45    <>46      <div className="flex flex-col items-start gap-1.5">47        <Button variant="danger" size="sm" disabled={disabled} onClick={() => setOpen(true)}>48          <Archive /> Archive project49        </Button>50        {disabled && disabledReason ? <p className="text-[12px] text-fg-subtle">{disabledReason}</p> : null}51      </div>52      <Dialog53        open={open}54        onOpenChange={(o) => {55          if (!pending) {56            setOpen(o);57            setError(null);58            setConfirmName("");59          }60        }}61      >62        <DialogContent size="sm">63          <DialogHeader>64            <DialogTitle>Archive {name}?</DialogTitle>65            <DialogDescription>66              Archived projects disappear from the dashboard and their API keys stop authenticating. Request history is kept for your plan&apos;s retention window. Contact support to restore an archived project.67            </DialogDescription>68          </DialogHeader>69          {error ? <Alert variant="danger">{error}</Alert> : null}70          <Field>71            <Label htmlFor="archive-confirm">72              Type <span className="font-mono">{name}</span> to confirm73            </Label>74            <Input id="archive-confirm" value={confirmName} onChange={(e) => setConfirmName(e.target.value)} autoComplete="off" autoFocus />75          </Field>76          <DialogFooter>77            <Button variant="outline" onClick={() => setOpen(false)} disabled={pending}>78              Cancel79            </Button>80            <Button81              variant="danger"82              disabled={confirmName.trim() !== name}83              loading={pending}84              onClick={() =>85                startTransition(async () => {86                  const res = await archiveProject(projectId);87                  if (!res.ok) {88                    setError(res.error);89                    return;90                  }91                  setOpen(false);92                  router.push("/dashboard/projects");93                  router.refresh();94                })95              }96            >97              Archive project98            </Button>99          </DialogFooter>100        </DialogContent>101      </Dialog>102    </>103  );104}105