67 lines
2.1 KiB
TypeScript
67 lines
2.1 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useEffectEvent, useState } from "react";
|
|
import { toast } from "sonner";
|
|
|
|
export type AutosaveState = "saved" | "unsaved" | "saving" | "incomplete" | "error";
|
|
|
|
interface SaveResult {
|
|
status: string;
|
|
message?: string;
|
|
version?: number;
|
|
}
|
|
|
|
export function useAutosave<T>({
|
|
value,
|
|
valid,
|
|
initialVersion,
|
|
persist,
|
|
delay = 900,
|
|
}: {
|
|
value: T;
|
|
valid: boolean;
|
|
initialVersion: number;
|
|
persist: (value: T, expectedVersion: number) => Promise<SaveResult>;
|
|
delay?: number;
|
|
}) {
|
|
const fingerprint = JSON.stringify(value);
|
|
const [savedFingerprint, setSavedFingerprint] = useState(fingerprint);
|
|
const [errorFingerprint, setErrorFingerprint] = useState<string | null>(null);
|
|
const [saving, setSaving] = useState(false);
|
|
const [version, setVersion] = useState(initialVersion);
|
|
|
|
const save = useEffectEvent(async (snapshot: T, snapshotFingerprint: string) => {
|
|
setSaving(true);
|
|
setErrorFingerprint(null);
|
|
try {
|
|
const result = await persist(snapshot, version);
|
|
if (result.status === "error") {
|
|
setErrorFingerprint(snapshotFingerprint);
|
|
toast.error(result.message ?? "บันทึกอัตโนมัติไม่สำเร็จ");
|
|
return;
|
|
}
|
|
if (result.version) setVersion(result.version);
|
|
setSavedFingerprint(snapshotFingerprint);
|
|
} catch (cause) {
|
|
setErrorFingerprint(snapshotFingerprint);
|
|
toast.error(cause instanceof Error ? cause.message : "บันทึกอัตโนมัติไม่สำเร็จ");
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (!valid || saving || fingerprint === savedFingerprint || fingerprint === errorFingerprint) return;
|
|
const timer = window.setTimeout(() => {
|
|
void save(JSON.parse(fingerprint) as T, fingerprint);
|
|
}, delay);
|
|
return () => window.clearTimeout(timer);
|
|
}, [delay, errorFingerprint, fingerprint, savedFingerprint, saving, valid]);
|
|
|
|
if (!valid) return "incomplete";
|
|
if (saving) return "saving";
|
|
if (fingerprint === errorFingerprint) return "error";
|
|
if (fingerprint !== savedFingerprint) return "unsaved";
|
|
return "saved";
|
|
}
|