chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
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>
);
}