Vectrum
Sign In
92 lines · 3.9 KB · TypeScript
"use client";

import { useActionState } from "react";
import { Rule, Select } from "@unlocalhosted/metalui";
import type { FormState } from "@/app/actions/types";
import { DeleteZone } from "./delete-zone";
import { BookmarkIcon, GlobeIcon, LockIcon, PlusIcon } from "./icons";
import { ChoiceCards, TextareaField, TextField } from "./settings-fields";
import { PlateBody, PlateFooter } from "./settings-plate";
import { Alert, Avatar, Box, Button } from "./ui";

type Action = (prev: FormState, formData: FormData) => Promise<FormState>;

export type ListFields = { name: string; description: string; visibility: "public" | "private" };

/** A list's name, description and visibility; new lists also choose their owner (you, or an organization). */
export function ListForm({ action, owners, owner, list }: { action: Action; owners: string[]; owner: string; list?: ListFields }) {
  const [state, formAction, pending] = useActionState(action, undefined);
  return (
    <form action={formAction}>
      <Box>
        <PlateBody>
          {!list && (
            <div className="flex flex-col gap-1.5">
              <span className="type-ui text-ink">Owner</span>
              {owners.length > 1 ? (
                <Select name="owner" aria-label="Owner" defaultValue={owner} options={owners.map((o) => ({ value: o, label: o }))} className="self-start min-w-36" />
              ) : (
                <span className="flex h-8 items-center gap-2 self-start rounded-[11px] recipe-well-field pr-3.5 pl-2 type-ui text-ink2">
                  <Avatar name={owner} size={20} /> {owner}
                  <input type="hidden" name="owner" value={owner} />
                </span>
              )}
            </div>
          )}
          <TextField
            label="Name"
            description={list ? "Renaming the list changes its address." : "Its address is made from the name, e.g. /lists/alice/tools-i-use."}
            name="name"
            required
            autoFocus={!list}
            maxLength={60}
            defaultValue={list?.name}
            placeholder="Tools I use"
          />
          <TextareaField label="Description" optional name="description" minRows={2} maxLength={300} defaultValue={list?.description} placeholder="What do these repositories have in common?" />
        </PlateBody>
        <Rule className="mx-6" />
        <PlateBody>
          <div className="flex flex-col gap-1.5">
            <span id="list-visibility" className="type-ui text-ink">
              Visibility
            </span>
            <ChoiceCards
              name="visibility"
              aria-labelledby="list-visibility"
              defaultValue={list?.visibility ?? "public"}
              choices={[
                { value: "public", icon: <GlobeIcon />, title: "Public", description: "Anyone can see the list. Private repositories in it stay hidden from those who can’t see them." },
                { value: "private", icon: <LockIcon />, title: "Private", description: "Only you can see it, or for an organization’s list, its members." },
              ]}
            />
          </div>
          {state?.error && <Alert>{state.error}</Alert>}
        </PlateBody>
        <PlateFooter>
          <Button variant="primary" icon={list ? <BookmarkIcon /> : <PlusIcon />} disabled={pending}>
            {list ? (pending ? "Saving…" : "Save List") : pending ? "Creating…" : "Create List"}
          </Button>
        </PlateFooter>
      </Box>
    </form>
  );
}

export function DeleteListZone({ action, name }: { action: Action; name: string }) {
  return (
    <DeleteZone
      action={action}
      confirmText={name}
      title="Delete this list"
      description="The repositories in it stay as they are; only the list goes."
      consequence={
        <>
          This will permanently delete the list <strong>{name}</strong>.
        </>
      }
      openLabel="Delete List"
      confirmLabel="I Understand, Delete This List"
    />
  );
}