Align backend plan with codebase
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
fetchAuthSession,
|
||||
fetchDraftFromServer,
|
||||
saveDraftToServer,
|
||||
} from "../../../lib/create/api";
|
||||
import { useCreateFlow } from "./CreateFlowContext";
|
||||
|
||||
const SYNC_ENABLED =
|
||||
process.env.NEXT_PUBLIC_ENABLE_BACKEND_SYNC === "true";
|
||||
|
||||
const DEBOUNCE_MS = 1000;
|
||||
|
||||
/**
|
||||
* When NEXT_PUBLIC_ENABLE_BACKEND_SYNC=true, loads the signed-in user's draft
|
||||
* from the server and debounces saves. Anonymous users keep localStorage-only behavior.
|
||||
*/
|
||||
export function CreateFlowBackendSync() {
|
||||
const { state, replaceState } = useCreateFlow();
|
||||
const [hydrated, setHydrated] = useState(!SYNC_ENABLED);
|
||||
const saveTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!SYNC_ENABLED) return;
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const { user } = await fetchAuthSession();
|
||||
if (cancelled || !user) {
|
||||
setHydrated(true);
|
||||
return;
|
||||
}
|
||||
const serverDraft = await fetchDraftFromServer();
|
||||
if (cancelled) return;
|
||||
if (serverDraft && Object.keys(serverDraft).length > 0) {
|
||||
replaceState(serverDraft);
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) setHydrated(true);
|
||||
}
|
||||
})();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [replaceState]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!SYNC_ENABLED || !hydrated) return;
|
||||
|
||||
if (saveTimer.current) clearTimeout(saveTimer.current);
|
||||
|
||||
saveTimer.current = setTimeout(() => {
|
||||
saveTimer.current = null;
|
||||
void (async () => {
|
||||
const { user } = await fetchAuthSession();
|
||||
if (!user) return;
|
||||
await saveDraftToServer(state);
|
||||
})();
|
||||
}, DEBOUNCE_MS);
|
||||
|
||||
return () => {
|
||||
if (saveTimer.current) clearTimeout(saveTimer.current);
|
||||
};
|
||||
}, [state, hydrated]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -79,6 +79,11 @@ export function CreateFlowProvider({
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const replaceState = useCallback((next: CreateFlowState) => {
|
||||
setState(next);
|
||||
writeStateToStorage(STORAGE_KEY, next);
|
||||
}, []);
|
||||
|
||||
const clearState = useCallback(() => {
|
||||
setState({});
|
||||
removeFromStorage(STORAGE_KEY);
|
||||
@@ -89,6 +94,7 @@ export function CreateFlowProvider({
|
||||
state,
|
||||
currentStep,
|
||||
updateState,
|
||||
replaceState,
|
||||
clearState,
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { CreateFlowBackendSync } from "./context/CreateFlowBackendSync";
|
||||
import {
|
||||
CreateFlowProvider,
|
||||
useCreateFlow,
|
||||
@@ -107,6 +108,7 @@ export default function CreateFlowLayout({
|
||||
}) {
|
||||
return (
|
||||
<CreateFlowProvider>
|
||||
<CreateFlowBackendSync />
|
||||
<CreateFlowLayoutContent>{children}</CreateFlowLayoutContent>
|
||||
</CreateFlowProvider>
|
||||
);
|
||||
|
||||
@@ -37,6 +37,8 @@ export interface CreateFlowContextValue {
|
||||
state: CreateFlowState;
|
||||
currentStep: CreateFlowStep | null;
|
||||
updateState: (_updates: Partial<CreateFlowState>) => void;
|
||||
/** Replace entire flow state (e.g. hydrate from server draft). */
|
||||
replaceState: (_next: CreateFlowState) => void;
|
||||
/** Clear all flow state (e.g. on exit). Also clears persisted draft. */
|
||||
clearState: () => void;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user