Route kebab Customize through the prefilled policy wizard so existing methods and values can be renamed, reordered, and kept in that field order on the card after Finalize.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -16,13 +16,23 @@ import type {
|
||||
import { CreateFlowHeaderLockup } from "../../components/CreateFlowHeaderLockup";
|
||||
import { CreateFlowTwoColumnSelectShell } from "../../components/CreateFlowTwoColumnSelectShell";
|
||||
import { CoreValueEditFields } from "../../components/methodEditFields";
|
||||
import CustomMethodCardModalBody from "../../components/CustomMethodCardModalBody";
|
||||
import CustomMethodCardWizard from "../../components/CustomMethodCardWizard";
|
||||
import { usesWizardFieldBlocksModalBody } from "../../../../../lib/create/usesWizardFieldBlocksModalBody";
|
||||
import { buildCustomRuleModalKebabMenu } from "../../components/customRuleModalKebabMenu";
|
||||
import {
|
||||
duplicateCoreValueChipInDraft,
|
||||
MAX_SELECTED_CORE_VALUES,
|
||||
removeCoreValueChipFromDraft,
|
||||
} from "../../../../../lib/create/coreValueChipFacet";
|
||||
import { omitIdFromStringRecord } from "../../../../../lib/create/duplicateMethodCardModalDraft";
|
||||
import {
|
||||
buildMethodCardWizardInitialValues,
|
||||
coreValueDetailsFromWizardFieldBlocks,
|
||||
overlayFacetPrefillValues,
|
||||
type MethodCardWizardInitialValues,
|
||||
} from "../../../../../lib/create/methodCardWizardPrefill";
|
||||
import type { CustomMethodCardFieldBlock } from "../../../../../lib/create/customMethodCardFieldBlocks";
|
||||
import { uploadCreateFlowFile } from "../../../../../lib/create/uploadToServer";
|
||||
|
||||
const MAX_CORE_VALUES = MAX_SELECTED_CORE_VALUES;
|
||||
|
||||
@@ -113,6 +123,15 @@ export function CoreValuesSelectScreen() {
|
||||
);
|
||||
const [modalSession, setModalSession] = useState<ModalSession | null>(null);
|
||||
const [draft, setDraft] = useState<CoreValueDetailEntry>(EMPTY_DETAIL);
|
||||
const [addCustomWizardOpen, setAddCustomWizardOpen] = useState(false);
|
||||
const [wizardCustomizeChipId, setWizardCustomizeChipId] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [wizardInitialValues, setWizardInitialValues] =
|
||||
useState<MethodCardWizardInitialValues | null>(null);
|
||||
const [draftFieldBlocks, setDraftFieldBlocks] = useState<
|
||||
CustomMethodCardFieldBlock[] | null
|
||||
>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setCoreValueOptions(
|
||||
@@ -165,6 +184,7 @@ export function CoreValuesSelectScreen() {
|
||||
return {
|
||||
meaning: saved?.meaning ?? preset.meaning,
|
||||
signals: saved?.signals ?? preset.signals,
|
||||
...(saved?.supportText ? { supportText: saved.supportText } : {}),
|
||||
};
|
||||
},
|
||||
[state.coreValueDetailsByChipId, getPresetTexts],
|
||||
@@ -179,12 +199,22 @@ export function CoreValuesSelectScreen() {
|
||||
) => {
|
||||
const initial = seedDetail ?? getInitialTexts(chipId, valueLabel);
|
||||
initialDraftRef.current = { ...initial };
|
||||
const persisted = state.customMethodCardFieldBlocksById?.[chipId];
|
||||
setDraftFieldBlocks(
|
||||
Array.isArray(persisted) && persisted.length > 0
|
||||
? structuredClone(persisted)
|
||||
: null,
|
||||
);
|
||||
setDraft(initial);
|
||||
setActiveModalChipId(chipId);
|
||||
setModalSession(session);
|
||||
markCreateFlowInteraction();
|
||||
},
|
||||
[getInitialTexts, markCreateFlowInteraction],
|
||||
[
|
||||
getInitialTexts,
|
||||
markCreateFlowInteraction,
|
||||
state.customMethodCardFieldBlocksById,
|
||||
],
|
||||
);
|
||||
|
||||
const handleDraftChange = useCallback(
|
||||
@@ -200,19 +230,19 @@ export function CoreValuesSelectScreen() {
|
||||
initialDraftRef.current = null;
|
||||
setActiveModalChipId(null);
|
||||
setModalSession(null);
|
||||
setDraftFieldBlocks(null);
|
||||
}, []);
|
||||
|
||||
const confirmLeaveWithoutSaving = useCallback(async () => {
|
||||
const isPendingAdd =
|
||||
modalSession === "pending" || modalSession === "customPending";
|
||||
const initial = initialDraftRef.current;
|
||||
const editingDirty =
|
||||
modalSession === "editing" &&
|
||||
const fieldsDirty =
|
||||
initial != null &&
|
||||
(draft.meaning !== initial.meaning || draft.signals !== initial.signals);
|
||||
if (!isPendingAdd && !editingDirty) {
|
||||
if (!fieldsDirty) {
|
||||
return true;
|
||||
}
|
||||
const isPendingAdd =
|
||||
modalSession === "pending" || modalSession === "customPending";
|
||||
return requestConfirm({
|
||||
title: cv.detailModal.discardTitle,
|
||||
description: isPendingAdd
|
||||
@@ -294,11 +324,7 @@ export function CoreValuesSelectScreen() {
|
||||
markCreateFlowInteraction();
|
||||
replaceState((prev) => ({
|
||||
...prev,
|
||||
selectedCoreValueIds: selectedIdsFromOptions(nextFiltered),
|
||||
coreValuesChipsSnapshot:
|
||||
chipOptionsToSnapshotRows(nextFiltered),
|
||||
coreValueDetailsByChipId:
|
||||
omitIdFromStringRecord(prev.coreValueDetailsByChipId, activeModalChipId),
|
||||
...removeCoreValueChipFromDraft(prev, activeModalChipId),
|
||||
}));
|
||||
setCoreValueOptions(nextFiltered);
|
||||
}
|
||||
@@ -355,48 +381,185 @@ export function CoreValuesSelectScreen() {
|
||||
if (!activeModalChipId || !modalSession) return;
|
||||
markCreateFlowInteraction();
|
||||
pendingEphemeralCoreDuplicateRef.current = null;
|
||||
const existingBlocks =
|
||||
draftFieldBlocks && draftFieldBlocks.length > 0
|
||||
? draftFieldBlocks
|
||||
: state.customMethodCardFieldBlocksById?.[activeModalChipId];
|
||||
updateState({
|
||||
coreValueDetailsByChipId: {
|
||||
...(state.coreValueDetailsByChipId ?? {}),
|
||||
[activeModalChipId]: draft,
|
||||
},
|
||||
...(existingBlocks && existingBlocks.length > 0
|
||||
? {
|
||||
customMethodCardFieldBlocksById: {
|
||||
...(state.customMethodCardFieldBlocksById ?? {}),
|
||||
[activeModalChipId]: overlayFacetPrefillValues(existingBlocks, {
|
||||
group: "coreValues",
|
||||
draft,
|
||||
headings: {
|
||||
meaning: cv.detailModal.meaningLabel,
|
||||
signals: cv.detailModal.signalsLabel,
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
finalizeModalDismiss();
|
||||
}, [
|
||||
activeModalChipId,
|
||||
cv.detailModal.meaningLabel,
|
||||
cv.detailModal.signalsLabel,
|
||||
draft,
|
||||
draftFieldBlocks,
|
||||
finalizeModalDismiss,
|
||||
markCreateFlowInteraction,
|
||||
modalSession,
|
||||
state.coreValueDetailsByChipId,
|
||||
state.customMethodCardFieldBlocksById,
|
||||
updateState,
|
||||
]);
|
||||
|
||||
const modalChipLabel =
|
||||
coreValueOptions.find((o) => o.id === activeModalChipId)?.label ?? "";
|
||||
|
||||
const modalUsesWizardFieldBlocksBody = Boolean(
|
||||
activeModalChipId &&
|
||||
usesWizardFieldBlocksModalBody({
|
||||
methodId: activeModalChipId,
|
||||
meta: {},
|
||||
fieldBlocksById: state.customMethodCardFieldBlocksById,
|
||||
modalEditUnlocked: true,
|
||||
draftFieldBlocks,
|
||||
}),
|
||||
);
|
||||
|
||||
const showFooterPrimary = Boolean(modalSession);
|
||||
|
||||
const handleCustomize = useCallback(() => {
|
||||
if (!activeModalChipId) return;
|
||||
markCreateFlowInteraction();
|
||||
setWizardInitialValues(
|
||||
buildMethodCardWizardInitialValues({
|
||||
cardId: activeModalChipId,
|
||||
fallbackTitle: modalChipLabel,
|
||||
fallbackDescription:
|
||||
draft.supportText?.trim() || cv.detailModal.subtitle,
|
||||
meta: {},
|
||||
persistedBlocks: state.customMethodCardFieldBlocksById,
|
||||
draftFieldBlocks,
|
||||
facetPrefill: {
|
||||
group: "coreValues",
|
||||
draft,
|
||||
headings: {
|
||||
meaning: cv.detailModal.meaningLabel,
|
||||
signals: cv.detailModal.signalsLabel,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
setWizardCustomizeChipId(activeModalChipId);
|
||||
setAddCustomWizardOpen(true);
|
||||
}, [
|
||||
activeModalChipId,
|
||||
cv.detailModal.meaningLabel,
|
||||
cv.detailModal.signalsLabel,
|
||||
cv.detailModal.subtitle,
|
||||
draft,
|
||||
draftFieldBlocks,
|
||||
markCreateFlowInteraction,
|
||||
modalChipLabel,
|
||||
state.customMethodCardFieldBlocksById,
|
||||
]);
|
||||
|
||||
const handleCloseAddWizard = useCallback(() => {
|
||||
setAddCustomWizardOpen(false);
|
||||
setWizardCustomizeChipId(null);
|
||||
setWizardInitialValues(null);
|
||||
}, []);
|
||||
|
||||
const handleFinalizeCustomCard = useCallback(
|
||||
({
|
||||
title,
|
||||
description,
|
||||
fieldBlocks,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
fieldBlocks: CustomMethodCardFieldBlock[];
|
||||
}) => {
|
||||
const chipId = wizardCustomizeChipId ?? activeModalChipId;
|
||||
if (!chipId) return;
|
||||
markCreateFlowInteraction();
|
||||
pendingEphemeralCoreDuplicateRef.current = null;
|
||||
const trimmedTitle = title.trim();
|
||||
const trimmedDescription = description.trim();
|
||||
const nextDetails = {
|
||||
...coreValueDetailsFromWizardFieldBlocks(fieldBlocks, draft),
|
||||
...(trimmedDescription.length > 0
|
||||
? { supportText: trimmedDescription }
|
||||
: {}),
|
||||
};
|
||||
replaceState((prev) => {
|
||||
const snap = [...(prev.coreValuesChipsSnapshot ?? [])];
|
||||
const i = snap.findIndex((r) => r.id === chipId);
|
||||
if (i >= 0 && trimmedTitle.length > 0) {
|
||||
snap[i] = { ...snap[i], label: trimmedTitle };
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
coreValuesChipsSnapshot: snap,
|
||||
coreValueDetailsByChipId: {
|
||||
...(prev.coreValueDetailsByChipId ?? {}),
|
||||
[chipId]: nextDetails,
|
||||
},
|
||||
customMethodCardFieldBlocksById: {
|
||||
...(prev.customMethodCardFieldBlocksById ?? {}),
|
||||
[chipId]: structuredClone(fieldBlocks),
|
||||
},
|
||||
};
|
||||
});
|
||||
setDraft(nextDetails);
|
||||
setDraftFieldBlocks(structuredClone(fieldBlocks));
|
||||
setAddCustomWizardOpen(false);
|
||||
setWizardCustomizeChipId(null);
|
||||
setWizardInitialValues(null);
|
||||
},
|
||||
[
|
||||
activeModalChipId,
|
||||
draft,
|
||||
markCreateFlowInteraction,
|
||||
replaceState,
|
||||
wizardCustomizeChipId,
|
||||
],
|
||||
);
|
||||
|
||||
const kebabMenuItems = useMemo(() => {
|
||||
if (!modalSession || !activeModalChipId) return [];
|
||||
const selectedCount = coreValueOptions.filter(
|
||||
(o) => o.state === "selected",
|
||||
).length;
|
||||
return buildCustomRuleModalKebabMenu(modalKebabMenu, {
|
||||
showCustomize: true,
|
||||
onCustomize: handleCustomize,
|
||||
onDuplicate:
|
||||
modalSession !== "editing" || selectedCount >= MAX_CORE_VALUES
|
||||
(state.editingPublishedRuleId?.trim() ?? "") !== "" ||
|
||||
selectedCount >= MAX_CORE_VALUES
|
||||
? undefined
|
||||
: handleDuplicateCoreChip,
|
||||
showRemove: true,
|
||||
showRemove: modalSession === "editing",
|
||||
onRemove: handleRemoveFromKebab,
|
||||
});
|
||||
}, [
|
||||
activeModalChipId,
|
||||
coreValueOptions,
|
||||
handleCustomize,
|
||||
handleDuplicateCoreChip,
|
||||
handleRemoveFromKebab,
|
||||
modalKebabMenu,
|
||||
modalSession,
|
||||
state.editingPublishedRuleId,
|
||||
]);
|
||||
const handleChipClick = (chipId: string) => {
|
||||
const target = coreValueOptions.find((o) => o.id === chipId);
|
||||
@@ -520,14 +683,16 @@ export function CoreValuesSelectScreen() {
|
||||
|
||||
{detailModal && (
|
||||
<Create
|
||||
isOpen={activeModalChipId !== null}
|
||||
isOpen={activeModalChipId !== null && !addCustomWizardOpen}
|
||||
onClose={handleModalDismiss}
|
||||
backdropVariant="blurredYellow"
|
||||
headerContent={
|
||||
<div className="bg-[var(--color-surface-default-primary)] px-[24px] py-[12px] shrink-0">
|
||||
<ContentLockup
|
||||
title={modalChipLabel}
|
||||
description={detailModal.subtitle}
|
||||
description={
|
||||
draft.supportText?.trim() || detailModal.subtitle
|
||||
}
|
||||
variant="modal"
|
||||
alignment="left"
|
||||
/>
|
||||
@@ -548,13 +713,43 @@ export function CoreValuesSelectScreen() {
|
||||
}
|
||||
ariaLabel={modalChipLabel || "Core value details"}
|
||||
>
|
||||
<CoreValueEditFields
|
||||
value={draft}
|
||||
onChange={handleDraftChange}
|
||||
/>
|
||||
{modalUsesWizardFieldBlocksBody && activeModalChipId ? (
|
||||
<CustomMethodCardModalBody
|
||||
cardId={activeModalChipId}
|
||||
blocksById={state.customMethodCardFieldBlocksById}
|
||||
blocksOverride={
|
||||
draftFieldBlocks !== null ? draftFieldBlocks : undefined
|
||||
}
|
||||
showPolicyContentLockupWhenNoBlocks={false}
|
||||
onFieldBlocksChange={
|
||||
draftFieldBlocks === null
|
||||
? undefined
|
||||
: (next) => {
|
||||
setDraftFieldBlocks(next);
|
||||
setDraft(
|
||||
coreValueDetailsFromWizardFieldBlocks(next, draft),
|
||||
);
|
||||
}
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<CoreValueEditFields
|
||||
value={draft}
|
||||
onChange={handleDraftChange}
|
||||
/>
|
||||
)}
|
||||
</Create>
|
||||
)}
|
||||
</CreateFlowTwoColumnSelectShell>
|
||||
<CustomMethodCardWizard
|
||||
isOpen={addCustomWizardOpen}
|
||||
onClose={handleCloseAddWizard}
|
||||
initialValues={wizardInitialValues}
|
||||
onFinalize={handleFinalizeCustomCard}
|
||||
onPersistCustomUploadFile={(file) =>
|
||||
uploadCreateFlowFile(file, "customMethodAttachment")
|
||||
}
|
||||
/>
|
||||
{confirmDialog}
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user