New edit-rule page created
This commit is contained in:
@@ -32,6 +32,8 @@ import {
|
||||
clearAnonymousCreateFlowStorage,
|
||||
setTransferPendingFlag,
|
||||
} from "./utils/anonymousDraftStorage";
|
||||
import { createFlowStateFromPublishedRule } from "../../../lib/create/publishedDocumentToCreateFlowState";
|
||||
import { readLastPublishedRule } from "../../../lib/create/lastPublishedRule";
|
||||
import { deleteServerDraft } from "../../../lib/create/api";
|
||||
import messages from "../../../messages/en/index";
|
||||
import {
|
||||
@@ -133,12 +135,23 @@ function CreateFlowLayoutContent({
|
||||
const [communitySaveMagicLinkSuccess, setCommunitySaveMagicLinkSuccess] =
|
||||
useState(false);
|
||||
|
||||
const loginReturnPath =
|
||||
currentStep === "edit-rule"
|
||||
? "/create/edit-rule?syncDraft=1"
|
||||
: "/create/final-review?syncDraft=1";
|
||||
|
||||
const {
|
||||
publishBannerMessage,
|
||||
setPublishBannerMessage,
|
||||
isPublishing,
|
||||
finalize: handleFinalize,
|
||||
} = useCreateFlowFinalize({ state, router, openLogin });
|
||||
} = useCreateFlowFinalize({
|
||||
state,
|
||||
router,
|
||||
openLogin,
|
||||
updateState,
|
||||
loginReturnPath,
|
||||
});
|
||||
|
||||
const {
|
||||
isTemplateReviewRoute,
|
||||
@@ -221,6 +234,34 @@ function CreateFlowLayoutContent({
|
||||
}
|
||||
}, [currentStep]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentStep !== "edit-rule") return;
|
||||
const last = readLastPublishedRule();
|
||||
if (!last) {
|
||||
router.replace("/create/completed");
|
||||
return;
|
||||
}
|
||||
const editingId = state.editingPublishedRuleId?.trim() ?? "";
|
||||
if (editingId.length > 0 && editingId !== last.id) {
|
||||
router.replace("/create/completed");
|
||||
return;
|
||||
}
|
||||
const titleOk =
|
||||
typeof state.title === "string" && state.title.trim().length > 0;
|
||||
const sectionsClear = (state.sections?.length ?? 0) === 0;
|
||||
/** Stale template `sections` (e.g. Values-only) makes final-review rows wrong; re-hydrate until cleared. */
|
||||
if (titleOk && editingId === last.id && sectionsClear) {
|
||||
return;
|
||||
}
|
||||
updateState(createFlowStateFromPublishedRule(last));
|
||||
}, [
|
||||
currentStep,
|
||||
router,
|
||||
updateState,
|
||||
state.editingPublishedRuleId,
|
||||
state.title,
|
||||
]);
|
||||
|
||||
const handleCommunitySaveMagicLinkSubmit = useCallback(async () => {
|
||||
setCommunitySaveMagicLinkError(null);
|
||||
setCommunitySaveMagicLinkSuccess(false);
|
||||
@@ -260,7 +301,8 @@ function CreateFlowLayoutContent({
|
||||
|
||||
const isCompletedStep = currentStep === "completed";
|
||||
const isRightRailStep = currentStep === "decision-approaches";
|
||||
const isFinalReviewStep = currentStep === "final-review";
|
||||
const isFinalReviewLike =
|
||||
currentStep === "final-review" || currentStep === "edit-rule";
|
||||
const isCardLayoutStep = createFlowStepUsesCardLayout(currentStep);
|
||||
/** Two-column select / right-rail: below `lg` main scrolls; at `lg+` only the right column scrolls. */
|
||||
const isSelectSplitScrollStep =
|
||||
@@ -275,7 +317,7 @@ function CreateFlowLayoutContent({
|
||||
? "items-stretch overflow-y-auto md:overflow-hidden"
|
||||
: isSelectSplitScrollStep
|
||||
? "items-start justify-start overflow-y-auto max-lg:overflow-y-auto lg:min-h-0 lg:items-stretch lg:overflow-hidden"
|
||||
: isFinalReviewStep || isCardLayoutStep || isTemplateReviewRoute
|
||||
: isFinalReviewLike || isCardLayoutStep || isTemplateReviewRoute
|
||||
? "items-start justify-center overflow-y-auto"
|
||||
: "items-start justify-center overflow-y-auto md:items-center";
|
||||
|
||||
@@ -289,7 +331,8 @@ function CreateFlowLayoutContent({
|
||||
: "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;
|
||||
Boolean(sessionUser) &&
|
||||
(stepIdx >= SAVE_EXIT_FROM_STEP_INDEX || currentStep === "edit-rule");
|
||||
|
||||
const proportionBarProgress = getProportionBarProgressForCreateFlowStep(
|
||||
currentStep,
|
||||
@@ -408,7 +451,15 @@ function CreateFlowLayoutContent({
|
||||
saveDraftOnExit={saveDraftOnExit}
|
||||
onEdit={
|
||||
isCompletedStep
|
||||
? () => router.push("/create/final-review")
|
||||
? () => {
|
||||
const last = readLastPublishedRule();
|
||||
if (!last) return;
|
||||
updateState({
|
||||
editingPublishedRuleId: last.id,
|
||||
sections: [],
|
||||
});
|
||||
router.push("/create/edit-rule");
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onExit={(opts) => void handleExit(opts)}
|
||||
@@ -425,7 +476,7 @@ function CreateFlowLayoutContent({
|
||||
{!isCompletedStep && (
|
||||
<CreateFlowFooter
|
||||
className="shrink-0"
|
||||
progressBar={!isTemplateReviewRoute && !isFinalReviewStep}
|
||||
progressBar={!isTemplateReviewRoute && !isFinalReviewLike}
|
||||
proportionBarProgress={proportionBarProgress}
|
||||
proportionBarVariant="segmented"
|
||||
secondButton={
|
||||
@@ -555,7 +606,7 @@ function CreateFlowLayoutContent({
|
||||
>
|
||||
{footer[customRuleConfirmFooter.footerMessageKey]}
|
||||
</Button>
|
||||
) : nextStep ? (
|
||||
) : nextStep || isFinalReviewLike ? (
|
||||
<Button
|
||||
buttonType="filled"
|
||||
palette="default"
|
||||
@@ -563,14 +614,14 @@ function CreateFlowLayoutContent({
|
||||
disabled={isPublishing}
|
||||
className={CREATE_FLOW_FOOTER_BUTTON_CLASS}
|
||||
onClick={() => {
|
||||
if (currentStep === "final-review") {
|
||||
if (isFinalReviewLike) {
|
||||
void handleFinalize();
|
||||
} else {
|
||||
goToNextStep();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{currentStep === "final-review"
|
||||
{isFinalReviewLike
|
||||
? isPublishing
|
||||
? messages.create.reviewAndComplete.publish
|
||||
.finalizeButtonPublishing
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Edit published rule: community description with the same 200-char limit as
|
||||
* {@link CreateFlowScreenView} `community-context` step.
|
||||
*/
|
||||
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import Create from "../../../components/modals/Create";
|
||||
import TextInput from "../../../components/controls/TextInput";
|
||||
import ContentLockup from "../../../components/type/ContentLockup";
|
||||
import { useTranslation } from "../../../contexts/MessagesContext";
|
||||
|
||||
/** Matches `community-context` step and `createFlowSchemas` communityContext.max(200). */
|
||||
export const COMMUNITY_CONTEXT_FIELD_MAX_LENGTH = 200;
|
||||
|
||||
export interface FinalReviewCommunityContextEditModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
/** Current `communityContext` (trimmed for display; draft seeds from raw state in parent). */
|
||||
initialValue: string;
|
||||
onSave: (_value: string) => void;
|
||||
}
|
||||
|
||||
export function FinalReviewCommunityContextEditModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
initialValue,
|
||||
onSave,
|
||||
}: FinalReviewCommunityContextEditModalProps) {
|
||||
const tModal = useTranslation(
|
||||
"create.reviewAndComplete.finalReview.communityContextEditModal",
|
||||
);
|
||||
const tField = useTranslation("create.community.communityContext");
|
||||
const tSave = useTranslation(
|
||||
"create.reviewAndComplete.finalReview.chipEditModal",
|
||||
);
|
||||
|
||||
const [draft, setDraft] = useState("");
|
||||
const initialRef = useRef("");
|
||||
const seededOpenRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
seededOpenRef.current = false;
|
||||
return;
|
||||
}
|
||||
if (seededOpenRef.current) return;
|
||||
seededOpenRef.current = true;
|
||||
const seed = initialValue;
|
||||
setDraft(seed);
|
||||
initialRef.current = seed;
|
||||
}, [isOpen, initialValue]);
|
||||
|
||||
const isDirty = useMemo(
|
||||
() => draft !== initialRef.current,
|
||||
[draft],
|
||||
);
|
||||
|
||||
const characterHint = tField("characterCountTemplate")
|
||||
.replace("{current}", String(draft.length))
|
||||
.replace("{max}", String(COMMUNITY_CONTEXT_FIELD_MAX_LENGTH));
|
||||
|
||||
const handleSave = () => {
|
||||
if (!isDirty) return;
|
||||
const trimmed = draft.trimEnd();
|
||||
const capped = trimmed.slice(0, COMMUNITY_CONTEXT_FIELD_MAX_LENGTH);
|
||||
onSave(capped);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Create
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
backdropVariant="blurredYellow"
|
||||
headerContent={
|
||||
<div className="bg-[var(--color-surface-default-primary)] px-[24px] py-[12px] shrink-0">
|
||||
<ContentLockup
|
||||
title={tModal("title")}
|
||||
description={tModal("description")}
|
||||
variant="modal"
|
||||
alignment="left"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
showBackButton={false}
|
||||
showNextButton
|
||||
nextButtonText={tSave("saveButton")}
|
||||
nextButtonDisabled={!isDirty}
|
||||
onNext={handleSave}
|
||||
ariaLabel={tModal("title")}
|
||||
>
|
||||
<div className="pb-2">
|
||||
<TextInput
|
||||
className="!transition-none"
|
||||
type="text"
|
||||
placeholder={tField("placeholder")}
|
||||
value={draft}
|
||||
onChange={(e) => {
|
||||
setDraft(e.target.value);
|
||||
}}
|
||||
inputSize="medium"
|
||||
formHeader={false}
|
||||
textHint={characterHint}
|
||||
maxLength={COMMUNITY_CONTEXT_FIELD_MAX_LENGTH}
|
||||
/>
|
||||
</div>
|
||||
</Create>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,13 @@
|
||||
|
||||
import { useCallback } from "react";
|
||||
import type { CreateFlowState, CreateFlowStep } from "../types";
|
||||
import { saveDraftToServer } from "../../../../lib/create/api";
|
||||
import { buildPublishPayload } from "../../../../lib/create/buildPublishPayload";
|
||||
import {
|
||||
deleteServerDraft,
|
||||
saveDraftToServer,
|
||||
updatePublishedRule,
|
||||
} from "../../../../lib/create/api";
|
||||
import { writeLastPublishedRule } from "../../../../lib/create/lastPublishedRule";
|
||||
import messages from "../../../../messages/en/index";
|
||||
|
||||
const SYNC_ENABLED = process.env.NEXT_PUBLIC_ENABLE_BACKEND_SYNC === "true";
|
||||
@@ -44,16 +50,52 @@ export function useCreateFlowExit({
|
||||
}
|
||||
|
||||
if (saveDraft && SYNC_ENABLED) {
|
||||
const payload: CreateFlowState = {
|
||||
...state,
|
||||
...(currentStep ? { currentStep } : {}),
|
||||
};
|
||||
const result = await saveDraftToServer(payload);
|
||||
if (result.ok === true) {
|
||||
setDraftSaveBannerMessage?.(null);
|
||||
const editingId =
|
||||
typeof state.editingPublishedRuleId === "string"
|
||||
? state.editingPublishedRuleId.trim()
|
||||
: "";
|
||||
if (editingId.length > 0) {
|
||||
const payloadResult = buildPublishPayload(state);
|
||||
if (payloadResult.ok === false) {
|
||||
setDraftSaveBannerMessage?.(
|
||||
payloadResult.error === "missingCommunityName"
|
||||
? messages.create.reviewAndComplete.publish
|
||||
.missingCommunityName
|
||||
: payloadResult.error,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const { title, summary, document } = payloadResult;
|
||||
const updateResult = await updatePublishedRule(editingId, {
|
||||
title,
|
||||
summary: summary ?? null,
|
||||
document,
|
||||
});
|
||||
if (updateResult.ok === true) {
|
||||
writeLastPublishedRule({
|
||||
id: editingId,
|
||||
title,
|
||||
summary: summary ?? null,
|
||||
document,
|
||||
});
|
||||
setDraftSaveBannerMessage?.(null);
|
||||
void deleteServerDraft();
|
||||
} else {
|
||||
setDraftSaveBannerMessage?.(updateResult.error);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
setDraftSaveBannerMessage?.(result.message);
|
||||
return;
|
||||
const payload: CreateFlowState = {
|
||||
...state,
|
||||
...(currentStep ? { currentStep } : {}),
|
||||
};
|
||||
const result = await saveDraftToServer(payload);
|
||||
if (result.ok === true) {
|
||||
setDraftSaveBannerMessage?.(null);
|
||||
} else {
|
||||
setDraftSaveBannerMessage?.(result.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useCallback, useState } from "react";
|
||||
import { buildPublishPayload } from "../../../../lib/create/buildPublishPayload";
|
||||
import { publishRule } from "../../../../lib/create/api";
|
||||
import { publishRule, updatePublishedRule } from "../../../../lib/create/api";
|
||||
import { writeLastPublishedRule } from "../../../../lib/create/lastPublishedRule";
|
||||
import messages from "../../../../messages/en/index";
|
||||
import type { CreateFlowState } from "../types";
|
||||
@@ -23,12 +23,8 @@ export type UseCreateFlowFinalizeResult = {
|
||||
isPublishing: boolean;
|
||||
/**
|
||||
* Build a publish payload from the current `CreateFlowState`, post it to
|
||||
* `publishRule`, and route to `/create/completed` on success.
|
||||
*
|
||||
* Failure modes:
|
||||
* - Payload validation fails → surface the localized banner message.
|
||||
* - 401 from the API → re-open the login modal targeting `/create/final-review?syncDraft=1` so the user can retry post-auth.
|
||||
* - Any other failure → show either the trimmed server message or a generic localized fallback.
|
||||
* `publishRule` (or PATCH when editing a published rule), and route to
|
||||
* `/create/completed` on success.
|
||||
*/
|
||||
finalize: () => Promise<void>;
|
||||
};
|
||||
@@ -43,10 +39,15 @@ export function useCreateFlowFinalize({
|
||||
state,
|
||||
router,
|
||||
openLogin,
|
||||
updateState,
|
||||
loginReturnPath,
|
||||
}: {
|
||||
state: CreateFlowState;
|
||||
router: AppRouterLike;
|
||||
openLogin: OpenLogin;
|
||||
updateState: (_patch: Partial<CreateFlowState>) => void;
|
||||
/** Session gate return path (`?syncDraft=1`) — differs for `/create/edit-rule` vs `/create/final-review`. */
|
||||
loginReturnPath: string;
|
||||
}): UseCreateFlowFinalizeResult {
|
||||
const [publishBannerMessage, setPublishBannerMessage] = useState<
|
||||
string | null
|
||||
@@ -66,6 +67,46 @@ export function useCreateFlowFinalize({
|
||||
}
|
||||
const { title, summary, document: ruleDocument } = payloadResult;
|
||||
setIsPublishing(true);
|
||||
|
||||
const editingId =
|
||||
typeof state.editingPublishedRuleId === "string"
|
||||
? state.editingPublishedRuleId.trim()
|
||||
: "";
|
||||
|
||||
if (editingId.length > 0) {
|
||||
const updateResult = await updatePublishedRule(editingId, {
|
||||
title,
|
||||
summary: summary ?? null,
|
||||
document: ruleDocument,
|
||||
});
|
||||
setIsPublishing(false);
|
||||
if (updateResult.ok === true) {
|
||||
writeLastPublishedRule({
|
||||
id: editingId,
|
||||
title,
|
||||
summary: summary ?? null,
|
||||
document: ruleDocument,
|
||||
});
|
||||
updateState({ editingPublishedRuleId: undefined });
|
||||
router.push("/create/completed");
|
||||
return;
|
||||
}
|
||||
if (updateResult.status === 401) {
|
||||
openLogin({
|
||||
variant: "default",
|
||||
nextPath: loginReturnPath,
|
||||
backdropVariant: "blurredYellow",
|
||||
});
|
||||
return;
|
||||
}
|
||||
setPublishBannerMessage(
|
||||
updateResult.error.trim() !== ""
|
||||
? updateResult.error
|
||||
: messages.create.reviewAndComplete.publish.genericPublishFailed,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const publishResult = await publishRule({
|
||||
title,
|
||||
summary,
|
||||
@@ -85,7 +126,7 @@ export function useCreateFlowFinalize({
|
||||
if (publishResult.status === 401) {
|
||||
openLogin({
|
||||
variant: "default",
|
||||
nextPath: "/create/final-review?syncDraft=1",
|
||||
nextPath: loginReturnPath,
|
||||
backdropVariant: "blurredYellow",
|
||||
});
|
||||
return;
|
||||
@@ -95,7 +136,7 @@ export function useCreateFlowFinalize({
|
||||
? publishResult.error
|
||||
: messages.create.reviewAndComplete.publish.genericPublishFailed,
|
||||
);
|
||||
}, [state, router, openLogin]);
|
||||
}, [state, router, openLogin, updateState, loginReturnPath]);
|
||||
|
||||
return {
|
||||
publishBannerMessage,
|
||||
|
||||
@@ -83,6 +83,8 @@ export function CreateFlowScreenView({
|
||||
return <ConfirmStakeholdersScreen />;
|
||||
case "final-review":
|
||||
return <FinalReviewScreen />;
|
||||
case "edit-rule":
|
||||
return <FinalReviewScreen variant="editPublished" />;
|
||||
case "completed":
|
||||
return <CompletedScreen />;
|
||||
default: {
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
type FinalReviewChipEditPatch,
|
||||
type FinalReviewChipEditTarget,
|
||||
} from "../../components/FinalReviewChipEditModal";
|
||||
import { FinalReviewCommunityContextEditModal } from "../../components/FinalReviewCommunityContextEditModal";
|
||||
import {
|
||||
getAssetPath,
|
||||
vectorMarkPath,
|
||||
@@ -68,7 +69,11 @@ function readFallbackCategoryRows(
|
||||
};
|
||||
}
|
||||
|
||||
export function FinalReviewScreen() {
|
||||
export function FinalReviewScreen({
|
||||
variant = "default",
|
||||
}: {
|
||||
variant?: "default" | "editPublished";
|
||||
} = {}) {
|
||||
const { state, updateState, markCreateFlowInteraction } = useCreateFlow();
|
||||
const mdUp = useCreateFlowMdUp();
|
||||
const t = useTranslation("create.reviewAndComplete.finalReview");
|
||||
@@ -93,6 +98,8 @@ export function FinalReviewScreen() {
|
||||
useState<FinalReviewChipEditTarget | null>(null);
|
||||
const [activeReadOnlyDetail, setActiveReadOnlyDetail] =
|
||||
useState<TemplateChipDetail | null>(null);
|
||||
const [communityContextModalOpen, setCommunityContextModalOpen] =
|
||||
useState(false);
|
||||
|
||||
const handleSave = useCallback(
|
||||
(patch: FinalReviewChipEditPatch) => {
|
||||
@@ -180,14 +187,37 @@ export function FinalReviewScreen() {
|
||||
return raw.length > 0 ? raw : undefined;
|
||||
}, [state.communityContext]);
|
||||
|
||||
const rawCommunityContextForModal =
|
||||
typeof state.communityContext === "string" ? state.communityContext : "";
|
||||
|
||||
const descriptionEmptyHint =
|
||||
variant === "editPublished" ? t("communityContextEditModal.emptyHint") : undefined;
|
||||
|
||||
return (
|
||||
<CreateFlowLockupCardStepShell
|
||||
lockupTitle={t("title")}
|
||||
lockupDescription={t("description")}
|
||||
lockupTitle={
|
||||
variant === "editPublished" ? t("editPublishedTitle") : t("title")
|
||||
}
|
||||
lockupDescription={
|
||||
variant === "editPublished"
|
||||
? t("editPublishedDescription")
|
||||
: t("description")
|
||||
}
|
||||
>
|
||||
<Rule
|
||||
title={ruleCardTitle}
|
||||
description={ruleCardDescription}
|
||||
onDescriptionClick={
|
||||
variant === "editPublished"
|
||||
? () => setCommunityContextModalOpen(true)
|
||||
: undefined
|
||||
}
|
||||
descriptionEmptyHint={descriptionEmptyHint}
|
||||
descriptionEditAriaLabel={
|
||||
variant === "editPublished"
|
||||
? t("communityContextEditModal.ariaEditDescription")
|
||||
: undefined
|
||||
}
|
||||
size={mdUp ? "L" : "M"}
|
||||
expanded={true}
|
||||
backgroundColor="bg-[#c9fef9]"
|
||||
@@ -209,6 +239,17 @@ export function FinalReviewScreen() {
|
||||
onClose={() => setActiveReadOnlyDetail(null)}
|
||||
detail={activeReadOnlyDetail}
|
||||
/>
|
||||
{variant === "editPublished" ? (
|
||||
<FinalReviewCommunityContextEditModal
|
||||
isOpen={communityContextModalOpen}
|
||||
onClose={() => setCommunityContextModalOpen(false)}
|
||||
initialValue={rawCommunityContextForModal}
|
||||
onSave={(value) => {
|
||||
markCreateFlowInteraction();
|
||||
updateState({ communityContext: value, summary: value });
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</CreateFlowLockupCardStepShell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ export type CreateFlowStep =
|
||||
| "conflict-management"
|
||||
| "confirm-stakeholders"
|
||||
| "final-review"
|
||||
/** Branch-only URL: same UI as final-review; editing an already-published rule from completed. */
|
||||
| "edit-rule"
|
||||
| "completed";
|
||||
|
||||
/** String keys used by generic text-field steps for `CreateFlowState`. */
|
||||
@@ -173,6 +175,11 @@ export interface CreateFlowState {
|
||||
* `confirm-stakeholders` can re-apply `?fromFlow=1` on the template URL.
|
||||
*/
|
||||
templateReviewEntryFromCreateFlow?: boolean;
|
||||
/**
|
||||
* When set, **Finalize** and signed-in **Save & Exit** update this published
|
||||
* rule (PATCH) instead of POSTing a new rule or only saving a draft.
|
||||
*/
|
||||
editingPublishedRuleId?: string;
|
||||
currentStep?: CreateFlowStep;
|
||||
/** Section drafts; structure will tighten as steps persist real shapes. */
|
||||
sections?: Record<string, unknown>[];
|
||||
|
||||
@@ -34,6 +34,9 @@ if (PROPORTION_BY_STEP_INDEX.length !== FLOW_STEP_ORDER.length) {
|
||||
export function getProportionBarProgressForCreateFlowStep(
|
||||
step: CreateFlowStep | null | undefined,
|
||||
): ProportionBarState {
|
||||
if (step === "edit-rule") {
|
||||
return "3-2";
|
||||
}
|
||||
const idx = getStepIndex(step);
|
||||
if (idx < 0) return "1-0";
|
||||
return PROPORTION_BY_STEP_INDEX[idx] ?? "1-0";
|
||||
|
||||
@@ -129,6 +129,12 @@ export const CREATE_FLOW_SCREEN_REGISTRY: Record<
|
||||
messageNamespace: "create.reviewAndComplete.finalReview",
|
||||
centeredBodyBelowMd: false,
|
||||
},
|
||||
"edit-rule": {
|
||||
layoutKind: "review",
|
||||
figmaNodeId: "20907-212767",
|
||||
messageNamespace: "create.reviewAndComplete.finalReview",
|
||||
centeredBodyBelowMd: false,
|
||||
},
|
||||
completed: {
|
||||
layoutKind: "completed",
|
||||
figmaNodeId: "20907-213286",
|
||||
|
||||
@@ -31,9 +31,13 @@ export const FLOW_STEP_ORDER: readonly CreateFlowStep[] = [
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Valid step IDs for the create flow (for validation)
|
||||
* Valid URL segments for `/create/[screenId]` (includes branch-only `edit-rule`).
|
||||
* Linear order for navigation remains {@link FLOW_STEP_ORDER}.
|
||||
*/
|
||||
export const VALID_STEPS: readonly CreateFlowStep[] = FLOW_STEP_ORDER;
|
||||
export const VALID_STEPS: readonly CreateFlowStep[] = [
|
||||
...FLOW_STEP_ORDER,
|
||||
"edit-rule",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* First step in the flow (entry point)
|
||||
|
||||
Reference in New Issue
Block a user