StarButton

tcmts.hub.pages.StarButton · #bfsntb3o · term · runs in the browser

uses useState

A library's stars, and the visitor's: starring writes a codes.tcm.star record to their own repository (one per library, across its versions), and taking it back deletes it; the hub checks it there at once.

import { restar } from "#codebase/tcmts/hub";
import { hubSession } from "#codebase/tcmts/hub/session";
import { STAR_COLLECTION, starKey, starRecord } from "#codebase/tcmts/records";
import { useState } from "react";

export function StarButton({
  author,
  pkg,
  count,
  starred,
}: {
  author: string;
  pkg: string;
  count: number;
  starred: boolean;
}) {
  const [shown, setShown] = useState({ count, starred });
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | undefined>();
  const press = async () => {
    setError(undefined);
    const s = await hubSession();
    if (!s) return setError("Sign in to star it.");
    setBusy(true);
    try {
      const rkey = starKey(author, pkg);
      const res = await s.fetch(
        `/xrpc/com.atproto.repo.${shown.starred ? "deleteRecord" : "putRecord"}`,
        {
          method: "POST",
          headers: { "content-type": "application/json" },
          body: JSON.stringify({
            repo: s.did,
            collection: STAR_COLLECTION,
            rkey,
            ...(shown.starred
              ? {}
              : {
                  record: starRecord(author, pkg),
                }),
          }),
        },
      );
      if (!res.ok) throw new Error(`your server said ${res.status}`);
      setShown(await restar(s.did, author, pkg));
    } catch (e) {
      setError(`That didn't work: ${(e as Error).message}`);
    } finally {
      setBusy(false);
    }
  };
  return (
    <span className="star">
      <button
        type="button"
        aria-pressed={shown.starred}
        disabled={busy}
        onClick={() => void press()}
      >
        {shown.starred ? "★ Starred" : "☆ Star"}
      </button>{" "}
      <span className="muted">{shown.count}</span>
      {error && (
        <span className="error" role="alert">
          {" "}
          {error}
        </span>
      )}
    </span>
  );
}

uses

used by