60 lines · 2.5 KB
import { useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import { adminSetSignupCap } from "~/fns/admin";
import type { getAdminOverview } from "~/fns/admin";
import { useAction } from "~/hooks/use-action";
import { WAITLIST_INVITE_HOURS } from "~/lib/waitlist";
import { Button } from "~/ui/Button";
import { ErrorBanner } from "~/ui/Callout";
import { compactInputClass } from "~/ui/Field";
import { Panel } from "~/ui/Panel";
import { WAITLIST_KEY, WaitlistTable } from "./WaitlistTable";

type Waitlist = Awaited<ReturnType<typeof getAdminOverview>>["waitlist"];

/** Caps how many user accounts may exist; raising it invites people off the waitlist, oldest first. */
export function SignupCapControls({ waitlist }: { waitlist: Waitlist }) {
  const [cap, setCap] = useState(waitlist.cap === null ? "" : String(waitlist.cap));
  const save = useAction(adminSetSignupCap);
  const queryClient = useQueryClient();
  const parsed = cap.trim() === "" ? null : Number(cap);
  const valid = parsed === null || (Number.isInteger(parsed) && parsed >= 1);
  const changed = parsed !== waitlist.cap;
  return (
    <Panel title="Sign-up cap">
      <div className="space-y-3 p-4 text-sm">
        <p className="text-kumo-subtle">
          {waitlist.users} users
          {waitlist.cap === null ? ", no cap" : ` of ${waitlist.cap}`} · {waitlist.waiting} waiting · {waitlist.invited}{" "}
          invited (holding a seat for {WAITLIST_INVITE_HOURS} hours)
        </p>
        <ErrorBanner message={save.error} />
        <form
          className="flex flex-wrap items-center gap-2"
          onSubmit={async (event) => {
            event.preventDefault();
            if (!valid || !changed) return;
            if (await save.run({ data: { cap: parsed } })) {
              await queryClient.invalidateQueries({ queryKey: WAITLIST_KEY });
            }
          }}