Signed in create rule clear
This commit is contained in:
@@ -27,8 +27,10 @@ const SYNC_ENABLED = process.env.NEXT_PUBLIC_ENABLE_BACKEND_SYNC === "true";
|
||||
* server draft on top would clobber unsaved keystrokes with a stale snapshot.
|
||||
*
|
||||
* Server draft becomes authoritative only when localStorage is empty — i.e.
|
||||
* fresh device, after explicit Save & Exit (which clears localStorage), or
|
||||
* after Exit-from-completed clears local state.
|
||||
* fresh device, after explicit Save & Exit (which clears localStorage),
|
||||
* after Exit-from-completed clears local state, or after
|
||||
* {@link prepareFreshCreateFlowEntry} (Create rule / new template entry) clears
|
||||
* local + deletes the server draft when sync is on.
|
||||
*
|
||||
* Skips when `?syncDraft=1` or transfer-pending — {@link PostLoginDraftTransfer}
|
||||
* owns that path.
|
||||
|
||||
@@ -4,17 +4,13 @@ import { clearCoreValueDetailsLocalStorage } from "./coreValueDetailsLocalStorag
|
||||
/**
|
||||
* Wipe the anonymous in-progress create-flow draft from `localStorage` (both
|
||||
* the main `create-flow-anonymous` blob and the separate core-value details
|
||||
* key). Intended for call sites that navigate **into** the create flow from
|
||||
* outside and want a fresh slate — today that's the marketing "Popular
|
||||
* templates" click handler on the home page and the `/templates` index page
|
||||
* (when not in-flow). `CreateFlowProvider` reads `localStorage` during its
|
||||
* `useState` initializer, so clearing *before* pushing the next route means
|
||||
* the provider mounts empty and the Create Community stage starts clean.
|
||||
* key). Clearing *before* `router.push` means `CreateFlowProvider` can read
|
||||
* empty storage on mount.
|
||||
*
|
||||
* Note: this only touches localStorage. It does **not** delete the
|
||||
* authenticated user's server draft (`/api/drafts/me`). Server drafts are
|
||||
* loaded deliberately from the profile page, not re-hydrated into the flow
|
||||
* on every entry, so there's nothing to wipe here for signed-in users.
|
||||
* For marketing/profile “new rule” entry that should also remove the signed-in
|
||||
* server draft when backend sync is on, use {@link prepareFreshCreateFlowEntry}.
|
||||
*
|
||||
* This helper only touches `localStorage`; it does **not** `DELETE /api/drafts/me`.
|
||||
*/
|
||||
export function clearCreateFlowPersistedDrafts(): void {
|
||||
clearAnonymousCreateFlowStorage();
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { deleteServerDraft } from "../../../../lib/create/api";
|
||||
import { clearAnonymousCreateFlowStorage } from "./anonymousDraftStorage";
|
||||
import { clearCoreValueDetailsLocalStorage } from "./coreValueDetailsLocalStorage";
|
||||
|
||||
const SYNC_ENABLED =
|
||||
process.env.NEXT_PUBLIC_ENABLE_BACKEND_SYNC === "true";
|
||||
|
||||
/**
|
||||
* Call **before** navigating into `/create` from marketing or profile “new rule”
|
||||
* entry points so signed-in + sync matches an anonymous fresh start: wipe
|
||||
* `localStorage` draft keys and, when sync is on, `DELETE /api/drafts/me`.
|
||||
* Anonymous `DELETE` is harmless (401). Await ensures the server draft is gone
|
||||
* before mount so {@link SignedInDraftHydration} does not rehydrate stale work.
|
||||
*
|
||||
* Do **not** use for “Continue draft” — that path should load the server draft.
|
||||
*/
|
||||
export async function prepareFreshCreateFlowEntry(): Promise<void> {
|
||||
clearAnonymousCreateFlowStorage();
|
||||
clearCoreValueDetailsLocalStorage();
|
||||
if (SYNC_ENABLED) {
|
||||
await deleteServerDraft();
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,8 @@ import {
|
||||
} from "../create/utils/flowSteps";
|
||||
import type { CreateFlowStep } from "../create/types";
|
||||
import { clearAnonymousCreateFlowStorage } from "../create/utils/anonymousDraftStorage";
|
||||
import { clearCoreValueDetailsLocalStorage } from "../create/utils/coreValueDetailsLocalStorage";
|
||||
import { prepareFreshCreateFlowEntry } from "../create/utils/prepareFreshCreateFlowEntry";
|
||||
import { useMediaQuery } from "../../hooks/useMediaQuery";
|
||||
import {
|
||||
ProfilePageSignedOutView,
|
||||
@@ -245,9 +247,18 @@ export default function ProfilePageClient() {
|
||||
const handleContinueDraft = useCallback(() => {
|
||||
if (draft == null || !draft.hasDraft) return;
|
||||
const step = resolveContinueStepState(draft.state);
|
||||
clearAnonymousCreateFlowStorage();
|
||||
clearCoreValueDetailsLocalStorage();
|
||||
router.push(`/create/${step}`);
|
||||
}, [draft, router]);
|
||||
|
||||
const handleStartNewCustomRule = useCallback(() => {
|
||||
void (async () => {
|
||||
await prepareFreshCreateFlowEntry();
|
||||
router.push("/create");
|
||||
})();
|
||||
}, [router]);
|
||||
|
||||
const handleRequestDeleteDraft = useCallback(() => {
|
||||
setActionError(null);
|
||||
setDraftDeleteOpen(true);
|
||||
@@ -360,6 +371,7 @@ export default function ProfilePageClient() {
|
||||
}}
|
||||
onCloseDeleteAccount={() => setAccountDeleteOpen(false)}
|
||||
onConfirmDeleteAccount={handleConfirmDeleteAccount}
|
||||
onStartNewCustomRule={handleStartNewCustomRule}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,6 +72,8 @@ export type ProfilePageViewProps = {
|
||||
onDismissProfileSuccess: () => void;
|
||||
onDismissActionError: () => void;
|
||||
onDismissRulesError: () => void;
|
||||
/** Clears local + server draft (when sync) then routes to `/create` — same fresh start as marketing “Create rule”. */
|
||||
onStartNewCustomRule: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -199,6 +201,7 @@ export function ProfilePageView({
|
||||
onDismissProfileSuccess,
|
||||
onDismissActionError,
|
||||
onDismissRulesError,
|
||||
onStartNewCustomRule,
|
||||
}: ProfilePageViewProps) {
|
||||
const t = useTranslation("pages.profile");
|
||||
const tLogin = useTranslation("pages.login");
|
||||
@@ -213,7 +216,7 @@ export function ProfilePageView({
|
||||
id: "create-custom",
|
||||
title: t("optionCreateCustom"),
|
||||
description: "",
|
||||
href: "/create",
|
||||
onClick: onStartNewCustomRule,
|
||||
leadingIcon: "edit",
|
||||
showDescription: false,
|
||||
},
|
||||
@@ -251,7 +254,7 @@ export function ProfilePageView({
|
||||
showDescription: false,
|
||||
},
|
||||
];
|
||||
}, [t, onSignOut, onOpenDeleteAccount, onOpenEmailChange]);
|
||||
}, [t, onSignOut, onOpenDeleteAccount, onOpenEmailChange, onStartNewCustomRule]);
|
||||
|
||||
const ruleCardShellClass =
|
||||
"w-full !max-w-full cursor-default !gap-3 !rounded-[12px] shadow-[0_0_48px_rgba(0,0,0,0.1)] lg:!rounded-[24px] lg:shadow-[0_0_24px_rgba(0,0,0,0.1)]";
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useRouter, useSearchParams } from "next/navigation";
|
||||
import HeaderLockup from "../../components/type/HeaderLockup";
|
||||
import { GovernanceTemplateGrid } from "../../components/sections/GovernanceTemplateGrid";
|
||||
import type { TemplateGridCardEntry } from "../../../lib/templates/templateGridPresentation";
|
||||
import { clearCreateFlowPersistedDrafts } from "../../(app)/create/utils/clearCreateFlowPersistedDrafts";
|
||||
import { prepareFreshCreateFlowEntry } from "../../(app)/create/utils/prepareFreshCreateFlowEntry";
|
||||
import { buildTemplateReviewHref } from "../../(app)/create/utils/flowSteps";
|
||||
import { useTranslation } from "../../contexts/MessagesContext";
|
||||
|
||||
@@ -83,11 +83,13 @@ function TemplatesGrid({
|
||||
entries={entries}
|
||||
onTemplateClick={(slug) => {
|
||||
if (!fromFlow) {
|
||||
// Direct entry to `/templates`: treat template click as a fresh
|
||||
// create-flow start and wipe any stale anonymous draft before
|
||||
// navigating. In-flow entry (`?fromFlow=1`) skips the clear so
|
||||
// the user's community stage survives the detour through here.
|
||||
clearCreateFlowPersistedDrafts();
|
||||
void (async () => {
|
||||
await prepareFreshCreateFlowEntry();
|
||||
router.push(
|
||||
buildTemplateReviewHref(slug, { fromCreateWizard: fromFlow }),
|
||||
);
|
||||
})();
|
||||
return;
|
||||
}
|
||||
router.push(
|
||||
buildTemplateReviewHref(slug, { fromCreateWizard: fromFlow }),
|
||||
|
||||
@@ -9,8 +9,7 @@ import Button from "../../buttons/Button";
|
||||
import AvatarContainer from "../../asset/AvatarContainer";
|
||||
import Avatar from "../../asset/Avatar";
|
||||
import { getAssetPath, ASSETS } from "../../../../lib/assetUtils";
|
||||
import { clearAnonymousCreateFlowStorage } from "../../../(app)/create/utils/anonymousDraftStorage";
|
||||
import { clearCoreValueDetailsLocalStorage } from "../../../(app)/create/utils/coreValueDetailsLocalStorage";
|
||||
import { prepareFreshCreateFlowEntry } from "../../../(app)/create/utils/prepareFreshCreateFlowEntry";
|
||||
import { TopView } from "./Top.view";
|
||||
import type { TopProps, NavSize } from "./Top.types";
|
||||
|
||||
@@ -45,16 +44,16 @@ const TopContainer = memo<TopProps>(
|
||||
|
||||
/**
|
||||
* `Top` is hidden on `/create` routes by ConditionalNavigationClient, so
|
||||
* this button is always clicked from outside the wizard — there is no
|
||||
* mounted CreateFlowProvider to reset. Wiping the anonymous draft keys
|
||||
* here guarantees a fresh start; the provider that mounts on `/create`
|
||||
* will read empty storage. Server drafts (signed-in Save & Exit) are
|
||||
* left alone — they're intentional persistence the user opted into.
|
||||
* this button is always clicked from outside the wizard. Clears anonymous
|
||||
* `localStorage` and, when backend sync is on, deletes the server draft
|
||||
* so signed-in users get the same fresh start as guests (see
|
||||
* {@link prepareFreshCreateFlowEntry}).
|
||||
*/
|
||||
const handleCreateRuleClick = useCallback(() => {
|
||||
clearAnonymousCreateFlowStorage();
|
||||
clearCoreValueDetailsLocalStorage();
|
||||
router.push("/create");
|
||||
void (async () => {
|
||||
await prepareFreshCreateFlowEntry();
|
||||
router.push("/create");
|
||||
})();
|
||||
}, [router]);
|
||||
|
||||
// Schema markup for site navigation
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { memo, useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { logger } from "../../../../lib/logger";
|
||||
import { clearCreateFlowPersistedDrafts } from "../../../(app)/create/utils/clearCreateFlowPersistedDrafts";
|
||||
import { prepareFreshCreateFlowEntry } from "../../../(app)/create/utils/prepareFreshCreateFlowEntry";
|
||||
import {
|
||||
fetchTemplates,
|
||||
isTemplatesFetchAborted,
|
||||
@@ -90,12 +90,12 @@ const RuleStackContainer = memo<RuleStackProps>(
|
||||
}
|
||||
}
|
||||
logger.debug(`${slug} template clicked`);
|
||||
// Marketing entry is always a *fresh* create-flow start: wipe any
|
||||
// in-progress anonymous draft so a stale community name/structure from
|
||||
// an earlier abandoned session can't short-circuit the `state.title`
|
||||
// check in `handleCustomizeTemplate` / `handleUseTemplateWithoutChanges`.
|
||||
clearCreateFlowPersistedDrafts();
|
||||
router.push(`/create/review-template/${encodeURIComponent(slug)}`);
|
||||
// Marketing home “Popular templates”: same fresh start as Top “Create rule”
|
||||
// (local + server draft when sync) so stale state cannot break template apply.
|
||||
void (async () => {
|
||||
await prepareFreshCreateFlowEntry();
|
||||
router.push(`/create/review-template/${encodeURIComponent(slug)}`);
|
||||
})();
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user