Let guests keep a published rule via Save & Exit and a post-finalize sign-in prompt.
Email is optional so they can continue without saving; skip from Save & Exit leaves the flow instead of returning to completed. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -23,8 +23,8 @@ import {
|
||||
CREATE_FLOW_MANAGE_STAKEHOLDERS_VALUE,
|
||||
CREATE_FLOW_REVIEW_RETURN_QUERY_KEY,
|
||||
getNextStep,
|
||||
getStepIndex,
|
||||
parseReviewReturnSearchParam,
|
||||
shouldOfferCreateFlowSaveAndExit,
|
||||
createFlowStepUsesSelectSplitScroll,
|
||||
TEMPLATES_FACET_RECOMMEND_QUERY,
|
||||
TEMPLATES_FACET_RECOMMEND_VALUE,
|
||||
@@ -64,6 +64,10 @@ import {
|
||||
} from "../../../lib/create/publishedDocumentToCreateFlowState";
|
||||
import { METHOD_FACET_API_SECTION_IDS } from "../../../lib/create/customRuleFacets";
|
||||
import { readLastPublishedRule } from "../../../lib/create/lastPublishedRule";
|
||||
import {
|
||||
clearPendingKeepRuleLogin,
|
||||
hasPendingKeepRuleLogin,
|
||||
} from "../../../lib/create/pendingKeepRuleLogin";
|
||||
import { runCompletedStepExit } from "./utils/runCompletedStepExit";
|
||||
import messages from "../../../messages/en/index";
|
||||
import {
|
||||
@@ -91,9 +95,6 @@ import {
|
||||
useCreateFlowDraftSaveBanner,
|
||||
} from "./context/CreateFlowDraftSaveBannerContext";
|
||||
|
||||
/** First step where Save & Exit is offered (first Create Community select per Figma). */
|
||||
const SAVE_EXIT_FROM_STEP_INDEX = getStepIndex("community-structure");
|
||||
|
||||
function CreateFlowSessionShell({ children }: { children: ReactNode }) {
|
||||
const [sessionUser, setSessionUser] = useState<
|
||||
{ id: string; email: string } | null | undefined
|
||||
@@ -111,9 +112,9 @@ function CreateFlowSessionShell({ children }: { children: ReactNode }) {
|
||||
|
||||
const sessionResolved = sessionUser !== undefined;
|
||||
// Mirror in-progress draft to localStorage for ALL visitors once we know who
|
||||
// they are. Refresh-survival is the same UX for guest and signed-in users;
|
||||
// signed-in users additionally get an explicit "Save & Exit" that PUTs to
|
||||
// the server (handled in `useCreateFlowExit`).
|
||||
// they are. Refresh-survival is the same UX for guest and signed-in users.
|
||||
// Save & Exit: guests open the save-progress login modal; signed-in users
|
||||
// PUT the server draft (`useCreateFlowExit`).
|
||||
const enableLocalDraftMirroring = sessionResolved;
|
||||
|
||||
return (
|
||||
@@ -197,6 +198,8 @@ function CreateFlowLayoutContent({
|
||||
} | null>(null);
|
||||
const [shareModalOpen, setShareModalOpen] = useState(false);
|
||||
const [leaveConfirmOpen, setLeaveConfirmOpen] = useState(false);
|
||||
const [completedHasPublishedRule, setCompletedHasPublishedRule] =
|
||||
useState(false);
|
||||
const leaveConfirmResolverRef = useRef<((proceed: boolean) => void) | null>(
|
||||
null,
|
||||
);
|
||||
@@ -267,14 +270,14 @@ function CreateFlowLayoutContent({
|
||||
title: completedCopy.guestInvitesSkippedTitle,
|
||||
description: completedCopy.guestInvitesSkippedDescription,
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
setCompletedFlowBanner({
|
||||
key: "guestClaimHint",
|
||||
status: "warning",
|
||||
title: completedCopy.guestClaimHintTitle,
|
||||
description: completedCopy.guestClaimHintDescription,
|
||||
});
|
||||
}
|
||||
setCompletedFlowBanner({
|
||||
key: "guestClaimHint",
|
||||
status: "warning",
|
||||
title: completedCopy.guestClaimHintTitle,
|
||||
description: completedCopy.guestClaimHintDescription,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -305,14 +308,26 @@ function CreateFlowLayoutContent({
|
||||
});
|
||||
|
||||
const handleExit = async (opts?: { saveDraft?: boolean }) => {
|
||||
const saveDraft = opts?.saveDraft ?? false;
|
||||
if (!sessionResolved) return;
|
||||
|
||||
// Exit from `/create/completed` is post-publish: the rule is saved, so we
|
||||
// skip the leave-confirm + login prompt and just wipe the in-flight draft.
|
||||
// For signed-in users we also DELETE the server draft so a future visit to
|
||||
// /create starts fresh instead of rehydrating yesterday's work.
|
||||
// Completed is post-publish. Guests still need Save & Exit → keep-this-rule
|
||||
// login (the document is live but unclaimed). Signed-in users just leave.
|
||||
if (currentStep === "completed") {
|
||||
if (sessionUser === null) {
|
||||
openLogin({
|
||||
variant: "keepRule",
|
||||
nextPath: CREATE_ROUTES.completed,
|
||||
backdropVariant: "blurredYellow",
|
||||
onDismiss: () => {
|
||||
runCompletedStepExit({
|
||||
clearState,
|
||||
clearAnonymousCreateFlowStorage,
|
||||
router,
|
||||
});
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
runCompletedStepExit({
|
||||
clearState,
|
||||
clearAnonymousCreateFlowStorage,
|
||||
@@ -322,7 +337,6 @@ function CreateFlowLayoutContent({
|
||||
}
|
||||
|
||||
if (sessionUser === null) {
|
||||
if (saveDraft) return;
|
||||
const returnToTemplateReview =
|
||||
templateReviewSlug != null
|
||||
? `/create/review-template/${encodeURIComponent(templateReviewSlug)}?syncDraft=1`
|
||||
@@ -428,9 +442,32 @@ function CreateFlowLayoutContent({
|
||||
useEffect(() => {
|
||||
if (currentStep !== "completed") {
|
||||
setCompletedFlowBanner(null);
|
||||
setCompletedHasPublishedRule(false);
|
||||
return;
|
||||
}
|
||||
setCompletedHasPublishedRule(Boolean(readLastPublishedRule()));
|
||||
}, [currentStep]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentStep !== "completed") return;
|
||||
if (!sessionResolved) return;
|
||||
if (sessionUser !== null) {
|
||||
clearPendingKeepRuleLogin();
|
||||
return;
|
||||
}
|
||||
if (!hasPendingKeepRuleLogin()) return;
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
if (!hasPendingKeepRuleLogin()) return;
|
||||
clearPendingKeepRuleLogin();
|
||||
openLogin({
|
||||
variant: "keepRule",
|
||||
nextPath: CREATE_ROUTES.completed,
|
||||
backdropVariant: "blurredYellow",
|
||||
});
|
||||
}, 0);
|
||||
return () => window.clearTimeout(timeoutId);
|
||||
}, [currentStep, sessionResolved, sessionUser, openLogin]);
|
||||
|
||||
const handleCommunitySaveMagicLinkSubmit = useCallback(async () => {
|
||||
setCommunitySaveMagicLinkError(null);
|
||||
setCommunitySaveMagicLinkSuccess(false);
|
||||
@@ -486,7 +523,6 @@ function CreateFlowLayoutContent({
|
||||
const isSelectSplitScrollStep = createFlowStepUsesSelectSplitScroll(
|
||||
currentStep,
|
||||
);
|
||||
const stepIdx = currentStep != null ? getStepIndex(currentStep) : -1;
|
||||
|
||||
/** Lockup+card / card-stack: `items-start` + shell `my-auto` so overflow scrolls from the top. */
|
||||
const mainContentClass = isCompletedStep
|
||||
@@ -506,9 +542,10 @@ function CreateFlowLayoutContent({
|
||||
? "max-md:flex-col max-md:items-stretch"
|
||||
: "max-md:flex-col max-md:items-center";
|
||||
const mainResponsiveLayout = `${mainMaxMdCross} ${mainMaxMdJustify} md:flex-row md:justify-center`;
|
||||
const saveDraftOnExit =
|
||||
Boolean(sessionUser) &&
|
||||
(stepIdx >= SAVE_EXIT_FROM_STEP_INDEX || currentStep === "edit-rule");
|
||||
const saveDraftOnExit = shouldOfferCreateFlowSaveAndExit(
|
||||
currentStep,
|
||||
sessionUser,
|
||||
);
|
||||
|
||||
const proportionBarProgress = getProportionBarProgressForCreateFlowStep(
|
||||
currentStep,
|
||||
@@ -674,7 +711,10 @@ function CreateFlowLayoutContent({
|
||||
<CreateFlowTopNav
|
||||
hasShare={isCompletedStep}
|
||||
hasExport={isCompletedStep}
|
||||
hasEdit={isCompletedStep && Boolean(sessionUser)}
|
||||
hasEdit={
|
||||
isCompletedStep &&
|
||||
(Boolean(sessionUser) || completedHasPublishedRule)
|
||||
}
|
||||
hasManageStakeholders={isEditRuleStep}
|
||||
saveDraftOnExit={saveDraftOnExit}
|
||||
onShare={
|
||||
|
||||
Reference in New Issue
Block a user