~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
67 lines · 2.3 KB Raw History
import { useState } from 'react';
import { Button } from '#/components/atoms/button.jsx';

/**
 * One group of repository settings: what the group is called, what it means,
 * the fields, and the button that writes them. Each group saves on its own,
 * because one request covers one setting and leaves the rest alone.
 *
 * The panel holds the write. `value` is what the fields would send, so the
 * panel can tell what it last wrote from an edit since: it says the write
 * landed until the fields move again, and the button is dead while there is
 * nothing new to send.
 * @param {{title: string, note?: import('react').ReactNode, action?: string, value: unknown, onSave: () => Promise<void>, children: import('react').ReactNode}} props
 */
export function SettingsPanel({
  title,
  note,
  action = 'Save',
  value,
  onSave,
  children,
}) {
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState('');
  const [written, setWritten] = useState('');
  const pending = JSON.stringify(value);
  const saved = written !== '' && written === pending;

  const submit = async (/** @type {{preventDefault: () => void}} */ event) => {
    event.preventDefault();
    setBusy(true);
    setError('');
    try {
      await onSave();
      setWritten(pending);
    } catch (err) {
      setError(err instanceof Error ? err.message : String(err));
    }
    setBusy(false);
  };

  return (
    <form
      onSubmit={submit}
      className="overflow-hidden rounded-lg border border-border bg-card"
    >
      <div className="border-border border-b px-4 py-3">
        <h3 className="font-semibold text-[14px] leading-tight">{title}</h3>
        {note && (
          <p className="mt-1 text-[12.5px] text-muted-foreground">{note}</p>
        )}
      </div>
      <div className="flex flex-col gap-4 px-4 py-4">{children}</div>
      <div className="flex flex-wrap items-center gap-3 border-border border-t bg-muted px-4 py-3">
        <Button type="submit" size="sm" disabled={busy || saved}>
          {busy ? 'Saving…' : action}
        </Button>
        {saved && (
          <span className="text-[12.5px] text-muted-foreground">Saved.</span>
        )}
        {error && (
          <span className="text-[12.5px] text-destructive">{error}</span>
        )}
      </div>
    </form>
  );
}