import type footerMessages from "../../../../messages/en/create/footer.json"; import { buildFinalReviewCategoryRowsDetailed, type FinalReviewCategoryNames, } from "../../../../lib/create/buildFinalReviewCategories"; import type { TemplateFacetGroupKey } from "../../../../lib/create/templateReviewMapping"; import type { CreateFlowState, CreateFlowStep } from "../types"; type FooterMessages = typeof footerMessages; /** * Per-step label override for the default "next-step" primary footer * button (the catch-all branch in `CreateFlowLayoutClient`'s footer that * fires `goToNextStep` for steps without a bespoke footer). Steps absent * from this map fall back to `footer.next`. * * `final-review` uses Continue (gated by {@link isFinalReviewContinueBlocked}). * `edit-rule` is handled separately by the caller because its label also * depends on the in-flight publish flag (`publishButtonPublishing` vs * `publishCommunityRule`). */ const DEFAULT_FOOTER_LABEL_BY_STEP: ReadonlyMap< CreateFlowStep, keyof FooterMessages > = new Map([ ["confirm-stakeholders", "confirmStakeholders"], ["community-context", "confirmDescription"], ["community-structure", "confirmDetails"], ["community-size", "confirmSize"], ]); function hasNonEmptyId(ids: readonly string[] | undefined): boolean { return (ids ?? []).some((id) => id.length > 0); } export function isCommunityNameStepBlocked( step: CreateFlowStep | null | undefined, title: string | undefined, ): boolean { if (step !== "community-name") return false; return typeof title !== "string" || title.trim().length === 0; } export function isCommunityStructureStepBlocked( step: CreateFlowStep | null | undefined, selectedOrganizationTypeIds: readonly string[] | undefined, selectedMaturityIds: readonly string[] | undefined, ): boolean { if (step !== "community-structure") return false; const mainKind = (selectedOrganizationTypeIds ?? []).find((id) => id.length > 0); return !mainKind || !hasNonEmptyId(selectedMaturityIds); } export function isCommunitySizeStepBlocked( step: CreateFlowStep | null | undefined, selectedCommunitySizeIds: readonly string[] | undefined, ): boolean { if (step !== "community-size") return false; return !hasNonEmptyId(selectedCommunitySizeIds); } const FINAL_REVIEW_BLOCK_NAMES: FinalReviewCategoryNames = { values: "values", communication: "communication", membership: "membership", decisions: "decisions", conflict: "conflict", }; const FINAL_REVIEW_REQUIRED_GROUPS: readonly TemplateFacetGroupKey[] = [ "communication", "membership", "decisionApproaches", "conflictManagement", ] as const; /** * Continue on final review waits for a community name and at least one card * on each of the four questions. Skipped values do not block. */ export function isFinalReviewContinueBlocked(state: CreateFlowState): boolean { const title = typeof state.title === "string" ? state.title.trim() : ""; if (title.length === 0) return true; const rows = buildFinalReviewCategoryRowsDetailed( state, FINAL_REVIEW_BLOCK_NAMES, ); return FINAL_REVIEW_REQUIRED_GROUPS.some((groupKey) => { const row = rows.find((candidate) => candidate.groupKey === groupKey); return !row || row.entries.length === 0; }); } /** * Resolve the localized label for the default "next-step" footer button. * Returns the per-step override when one is registered, otherwise * `footer.next`. Caller still owns the `final-review` / `edit-rule` special case. */ export function getDefaultFooterLabel( step: CreateFlowStep | null | undefined, footer: FooterMessages, ): string { if (step == null) return footer.next; const key = DEFAULT_FOOTER_LABEL_BY_STEP.get(step); return key != null ? footer[key] : footer.next; }