'use client'; import { useCallback, useEffect, useState, type ReactNode } from 'react'; import { AdminError, adminFetch } from '@/lib/admin-fetch'; /** Fetch an admin resource with loading/error state and a `reload()` handle. */ export function useAdmin(path: string | null, params?: Record) { const [result, setResult] = useState<{ key: string; data: T | null; err: string | null }>({ key: '', data: null, err: null }); const [nonce, setNonce] = useState(0); const key = `${path}?${JSON.stringify(params ?? {})}#${nonce}`; useEffect(() => { if (!path) return; let alive = true; adminFetch(path, { params: JSON.parse(key.slice(key.indexOf('?') + 1, key.lastIndexOf('#'))) as Record }) .then((d) => alive && setResult({ key, data: d, err: null })) .catch((e: unknown) => alive && setResult((r) => ({ key, data: r.data, err: e instanceof AdminError ? `${e.status} ${JSON.stringify(e.body ?? '')}` : String(e) }))); return () => { alive = false; }; }, [path, key]); const reload = useCallback(() => setNonce((n) => n + 1), []); const loading = result.key !== key; return { data: result.data, err: loading ? null : result.err, loading, reload }; } export function AdminPage({ title, desc, right, children }: { title: string; desc?: string; right?: ReactNode; children: ReactNode }) { return (

{title}

{desc &&

{desc}

}
{right}
{children}
); } export function Panel({ title, right, children, className = '' }: { title?: string; right?: ReactNode; children: ReactNode; className?: string }) { return (
{(title || right) && (
{title &&

{title}

} {right &&
{right}
}
)} {children}
); } export function ErrorNote({ err }: { err: string | null }) { if (!err) return null; return

Admin API error: {err}

; } export function Ok({ ok, label }: { ok: boolean; label?: string }) { return ( ); } export const inputCls = 'h-8 rounded-[4px] border border-line bg-panel px-2 text-[12.5px] text-ink placeholder:text-ink-3'; export const btnCls = 'h-8 rounded-[4px] border border-line px-3 text-[12px] text-ink hover:border-line-2 disabled:opacity-40'; export const btnPrimary = 'h-8 rounded-[4px] bg-accent px-3 text-[12px] font-medium text-bg hover:opacity-90 disabled:opacity-40'; export const btnDanger = 'h-8 rounded-[4px] border border-bad/60 px-3 text-[12px] text-bad hover:bg-bad/10 disabled:opacity-40'; export function Field({ label, children }: { label: string; children: ReactNode }) { return ( ); } export function Toast({ msg }: { msg: string | null }) { if (!msg) return null; return (

{msg}

); }