New edit-rule page created

This commit is contained in:
adilallo
2026-04-29 16:05:37 -06:00
parent ac1157a172
commit 3a9727bceb
25 changed files with 875 additions and 52 deletions
+52 -10
View File
@@ -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,