diff --git a/frontend/src/app/create/create-page-client.test.tsx b/frontend/src/app/create/create-page-client.test.tsx
index 45eeb0f..0e0b6d8 100644
--- a/frontend/src/app/create/create-page-client.test.tsx
+++ b/frontend/src/app/create/create-page-client.test.tsx
@@ -2,6 +2,8 @@ import React from "react";
import { act, fireEvent, render, screen } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { LanguageProvider } from "@/i18n/provider";
+
import CreatePolicyPageClient from "./create-page-client";
vi.mock("@/components/wallet-provider", () => ({
@@ -61,9 +63,17 @@ vi.mock("@/components/oracle-source-selector", () => ({
) : (
Loading oracle providers...
- ),
+ ),
}));
+function renderCreatePolicyPageClient() {
+ return render(
+
+
+ ,
+ );
+}
+
describe("CreatePolicyPageClient", () => {
beforeEach(() => {
vi.useFakeTimers();
@@ -77,14 +87,14 @@ describe("CreatePolicyPageClient", () => {
});
it("moves from policy type selection into configure step", () => {
- render();
+ renderCreatePolicyPageClient();
fireEvent.click(screen.getByRole("radio", { name: /weather protection/i }));
expect(screen.getByRole("heading", { name: /configure your policy/i })).toBeInTheDocument();
});
it("applies form validation for invalid coverage values", () => {
- render();
+ renderCreatePolicyPageClient();
fireEvent.click(screen.getByRole("radio", { name: /weather protection/i }));
const coverageInput = screen.getByLabelText(/coverage amount/i);
@@ -110,7 +120,7 @@ describe("CreatePolicyPageClient", () => {
updatedAt: Date.now(),
}),
);
- render();
+ renderCreatePolicyPageClient();
expect(screen.getByRole("heading", { name: /review your policy/i })).toBeInTheDocument();
@@ -144,7 +154,7 @@ describe("CreatePolicyPageClient", () => {
updatedAt: Date.now(),
}),
);
- render();
+ renderCreatePolicyPageClient();
expect(screen.getByRole("heading", { name: /review your policy/i })).toBeInTheDocument();
@@ -155,4 +165,40 @@ describe("CreatePolicyPageClient", () => {
const triggerInput = screen.getByPlaceholderText("Trigger mock input") as HTMLInputElement;
expect(triggerInput.value).toBe("temperature > 25 AND rainfall > 50");
});
+
+ it("lets users undo an explicit draft discard", () => {
+ const savedDraft = {
+ policyType: "weather",
+ coverageAmount: "5000",
+ premium: "120",
+ triggerCondition: "rainfall > 50",
+ duration: "90",
+ oracleProvider: "weatherlink-prime",
+ };
+ localStorage.setItem(
+ "stellarinsure-policy-draft",
+ JSON.stringify({ data: savedDraft, updatedAt: 1000 }),
+ );
+
+ renderCreatePolicyPageClient();
+
+ expect(screen.getByRole("heading", { name: /review your policy/i })).toBeInTheDocument();
+
+ fireEvent.click(screen.getByRole("button", { name: /discard/i }));
+
+ expect(screen.getByText(/draft discarded/i)).toBeInTheDocument();
+ expect(screen.getByRole("heading", { name: /choose a policy type/i })).toBeInTheDocument();
+ expect(localStorage.getItem("stellarinsure-policy-draft")).toBeNull();
+
+ fireEvent.click(screen.getByRole("button", { name: /undo discard/i }));
+
+ expect(screen.getByRole("heading", { name: /review your policy/i })).toBeInTheDocument();
+ expect(screen.getByText(/rainfall > 50/i)).toBeInTheDocument();
+
+ act(() => {
+ vi.advanceTimersByTime(600);
+ });
+
+ expect(JSON.parse(localStorage.getItem("stellarinsure-policy-draft") ?? "{}").data).toEqual(savedDraft);
+ });
});
diff --git a/frontend/src/app/create/create-page-client.tsx b/frontend/src/app/create/create-page-client.tsx
index b140a7a..f18a945 100644
--- a/frontend/src/app/create/create-page-client.tsx
+++ b/frontend/src/app/create/create-page-client.tsx
@@ -56,7 +56,11 @@ const INITIAL_DRAFT: PolicyDraft = {
oracleProvider: "",
};
-
+function getDraftStep(draft: PolicyDraft): CreateStep {
+ if (draft.policyType && draft.coverageAmount && draft.triggerCondition) return 2;
+ if (draft.policyType) return 1;
+ return 0;
+}
function parseConditionString(condition: string): ConditionRule[] | undefined {
if (!condition || condition.trim() === "") return undefined;
@@ -340,11 +344,7 @@ export default function CreatePolicyPageClient() {
hasClearedBackup,
isSaved,
} = usePolicyDraftAutosave("stellarinsure-policy-draft", INITIAL_DRAFT);
- const [step, setStep] = useState(() => {
- if (draft.policyType && draft.coverageAmount && draft.triggerCondition) return 2;
- if (draft.policyType) return 1;
- return 0;
- });
+ const [step, setStep] = useState(() => getDraftStep(draft));
const [txSteps, setTxSteps] = useState(DEFAULT_TX_STEPS);
const [coverageTouched, setCoverageTouched] = useState(false);
const [premiumTouched, setPremiumTouched] = useState(false);
@@ -369,6 +369,13 @@ export default function CreatePolicyPageClient() {
setReceipt(null);
}
+ function handleDiscardDraft() {
+ clearDraft();
+ setStep(0);
+ setReceipt(null);
+ setValidationErrors([]);
+ }
+
useEffect(() => {
const selectedPolicyType = draft.policyType;
@@ -576,13 +583,7 @@ export default function CreatePolicyPageClient() {
if (!restored) {
return;
}
- if (restored.policyType && restored.coverageAmount && restored.triggerCondition) {
- setStep(2);
- } else if (restored.policyType) {
- setStep(1);
- } else {
- setStep(0);
- }
+ setStep(getDraftStep(restored));
}}
>
Undo discard
@@ -800,10 +801,7 @@ export default function CreatePolicyPageClient() {
diff --git a/frontend/src/hooks/use-autosave.ts b/frontend/src/hooks/use-autosave.ts
index 03639f9..0674a50 100644
--- a/frontend/src/hooks/use-autosave.ts
+++ b/frontend/src/hooks/use-autosave.ts
@@ -53,6 +53,7 @@ export function useAutosave(key: string, initial: T): [T, (next: T) => void,
export function usePolicyDraftAutosave(key: string, initial: T): AutosaveControls {
const backupKey = `${key}-backup`;
const editedAtRef = useRef(0);
+ const clearedAtRef = useRef(null);
const [state, setStateInternal] = useState(() => {
const stored = readStoredDraft(key);
@@ -139,6 +140,7 @@ export function usePolicyDraftAutosave(key: string, initial: T): AutosaveCont
}, [key]);
const clear = useCallback(() => {
+ const clearedAt = Date.now();
try {
const current = readStoredDraft(key);
if (current) {
@@ -149,7 +151,8 @@ export function usePolicyDraftAutosave(key: string, initial: T): AutosaveCont
// Ignore backup failures.
}
- editedAtRef.current = Date.now();
+ clearedAtRef.current = clearedAt;
+ editedAtRef.current = clearedAt;
setStateInternal(initial);
setIsSaved(true);
@@ -174,11 +177,16 @@ export function usePolicyDraftAutosave(key: string, initial: T): AutosaveCont
const backup = JSON.parse(backupRaw) as StoredDraft;
const restoredAt = backup.updatedAt ?? Date.now();
- if (restoredAt <= editedAtRef.current) {
+ if (clearedAtRef.current === null && restoredAt <= editedAtRef.current) {
+ return null;
+ }
+
+ if (clearedAtRef.current !== null && editedAtRef.current > clearedAtRef.current) {
return null;
}
editedAtRef.current = restoredAt;
+ clearedAtRef.current = null;
setStateInternal(backup.data);
writeStoredDraft(key, backup.data, restoredAt);
localStorage.removeItem(backupKey);