The Coline design system: accessible React components for Coline Apps, themed live by the host workspace, and for Coline Sites in light and dark. Tailwind v4 stylesheet included. MIT.
| 5 | import { useColine, useToast } from "../provider"; |
| 6 |
| 7 | export interface ColineMutation<TInput, TResult> { |
| 8 | mutate: (input: TInput) => Promise<TResult | undefined>; |
| 9 | isPending: boolean; |
| 10 | error: string | null; |
| 11 | data: TResult | null; |
| 12 | reset: () => void; |
| 13 | } |
| 14 |
| 15 | /** |
| 16 | * Run a capability write with pending and error state. Errors are caught |
| 17 | * (and shown as a host toast unless `silent`), so event handlers never |
| 18 | * leak unhandled rejections. |
| 19 | */ |
| 20 | export function useColineMutation<TInput, TResult>( |
| 21 | run: (coline: ColineCapabilities, input: TInput) => Promise<TResult>, |
| 22 | options: { silent?: boolean; successMessage?: string; onSuccess?: (result: TResult) => void } = {}, |
| 23 | ): ColineMutation<TInput, TResult> { |
| 24 | const coline = useColine(); |
| 25 | const toast = useToast(); |
| 26 | const [state, setState] = React.useState<{ isPending: boolean; error: string | null; data: TResult | null }>({ |
| 27 | isPending: false, |
| 28 | error: null, |
| 29 | data: null, |
| 30 | }); |
| 31 | const runRef = React.useRef(run); |
| 32 | const optionsRef = React.useRef(options); |
| 33 | React.useEffect(() => { |
| 34 | runRef.current = run; |
| 35 | optionsRef.current = options; |
| 36 | }); |
| 37 |
| 38 | const mutate = React.useCallback( |
| 39 | async (input: TInput) => { |
| 40 | setState((current) => ({ ...current, isPending: true, error: null })); |
| 41 | try { |
| 42 | const result = await runRef.current(coline, input); |
| 43 | setState({ isPending: false, error: null, data: result }); |
| 44 | if (optionsRef.current.successMessage) toast(optionsRef.current.successMessage, "success"); |
| 45 | optionsRef.current.onSuccess?.(result); |
| 46 | return result; |
| 47 | } catch (error) { |
| 48 | const message = error instanceof Error ? error.message : String(error); |
| 49 | setState((current) => ({ ...current, isPending: false, error: message })); |
| 50 | if (!optionsRef.current.silent) toast(message, "error"); |
| 51 | return undefined; |
| 52 | } |
| 53 | }, |
| 54 | [coline, toast], |
| 55 | ); |
| 56 |
| 57 | const reset = React.useCallback(() => setState({ isPending: false, error: null, data: null }), []); |
| 58 | return { mutate, reset, ...state }; |
| 59 | } |
| 60 |