Template flow cleaned up
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
import { clearAnonymousCreateFlowStorage } from "./anonymousDraftStorage";
|
||||
import { clearCoreValueDetailsLocalStorage } from "./coreValueDetailsLocalStorage";
|
||||
|
||||
/**
|
||||
* Wipe the anonymous in-progress create-flow draft from `localStorage` (both
|
||||
* the main `create-flow-anonymous` blob and the separate core-value details
|
||||
* key). Intended for call sites that navigate **into** the create flow from
|
||||
* outside and want a fresh slate — today that's the marketing "Popular
|
||||
* templates" click handler on the home page and the `/templates` index page
|
||||
* (when not in-flow). `CreateFlowProvider` reads `localStorage` during its
|
||||
* `useState` initializer, so clearing *before* pushing the next route means
|
||||
* the provider mounts empty and the Create Community stage starts clean.
|
||||
*
|
||||
* Note: this only touches localStorage. It does **not** delete the
|
||||
* authenticated user's server draft (`/api/drafts/me`). Server drafts are
|
||||
* loaded deliberately from the profile page, not re-hydrated into the flow
|
||||
* on every entry, so there's nothing to wipe here for signed-in users.
|
||||
*/
|
||||
export function clearCreateFlowPersistedDrafts(): void {
|
||||
clearAnonymousCreateFlowStorage();
|
||||
clearCoreValueDetailsLocalStorage();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Typography + padding overrides applied to the primary/secondary buttons
|
||||
* rendered inside `CreateFlowFooter`. The footer slot expects a compact
|
||||
* size regardless of the default `<Button size="xsmall">` output, and both
|
||||
* the Create Community / Custom Rule / Review flows and the template-review
|
||||
* footer share the same override string — keeping it here prevents drift
|
||||
* between those two call sites.
|
||||
*
|
||||
* The `!` prefixes bypass Button's own size tokens; the extra spacing vars
|
||||
* mirror the Figma compact footer button spec. When the design system
|
||||
* exposes a native size that matches, this module should collapse.
|
||||
*/
|
||||
export const CREATE_FLOW_FOOTER_BUTTON_CLASS =
|
||||
"md:!text-[14px] md:!leading-[16px] !text-[12px] !leading-[14px] " +
|
||||
"!px-[var(--spacing-measures-spacing-200,8px)] md:!px-[var(--spacing-measures-spacing-250,10px)] " +
|
||||
"!py-[var(--spacing-measures-spacing-200,8px)] md:!py-[var(--spacing-measures-spacing-250,10px)]";
|
||||
|
||||
/**
|
||||
* Template-review "Use without changes" (ghost variant) renders on a dark
|
||||
* backdrop and needs an explicit text-color override in addition to the
|
||||
* shared compact sizing. Composed from the base class so any future tweak
|
||||
* to typography/padding propagates automatically.
|
||||
*/
|
||||
export const CREATE_FLOW_FOOTER_BUTTON_ON_DARK_CLASS = `${CREATE_FLOW_FOOTER_BUTTON_CLASS} !text-white`;
|
||||
@@ -0,0 +1,69 @@
|
||||
import type { CreateFlowState, CreateFlowStep } from "../types";
|
||||
import type footerMessages from "../../../../messages/en/create/footer.json";
|
||||
|
||||
type FooterMessageKey = keyof typeof footerMessages;
|
||||
|
||||
/**
|
||||
* Binding for each Custom Rule stage step whose footer primary button
|
||||
* gates the user on "has at least one chip selected?". All five screens
|
||||
* render the same `<Button …>`; only the disable predicate and the
|
||||
* footer message differ — this table is the single source of truth for
|
||||
* both, so `CreateFlowLayoutClient` can render one JSX block for the
|
||||
* whole group.
|
||||
*
|
||||
* `selectionIds` returns the currently-selected ids array from flow
|
||||
* state for that step (empty array when nothing has been selected or
|
||||
* the field hasn't been touched). Returning a fresh array on empty is
|
||||
* fine: these are read-only length checks, not memo keys.
|
||||
*
|
||||
* Note: the Confirm Stakeholders step has its own dedicated label copy
|
||||
* and is not gated on a selection count, so it stays out of this table.
|
||||
* Template-review and Community Save also have bespoke two-button
|
||||
* layouts and are intentionally excluded.
|
||||
*/
|
||||
export type CustomRuleConfirmFooterStep = {
|
||||
step: Extract<
|
||||
CreateFlowStep,
|
||||
| "core-values"
|
||||
| "communication-methods"
|
||||
| "membership-methods"
|
||||
| "decision-approaches"
|
||||
| "conflict-management"
|
||||
>;
|
||||
footerMessageKey: FooterMessageKey;
|
||||
selectionIds: (state: CreateFlowState) => readonly string[];
|
||||
};
|
||||
|
||||
export const CUSTOM_RULE_CONFIRM_FOOTER_STEPS: readonly CustomRuleConfirmFooterStep[] =
|
||||
[
|
||||
{
|
||||
step: "core-values",
|
||||
footerMessageKey: "confirmCoreValues",
|
||||
selectionIds: (s) => s.selectedCoreValueIds ?? [],
|
||||
},
|
||||
{
|
||||
step: "communication-methods",
|
||||
footerMessageKey: "confirmCommunication",
|
||||
selectionIds: (s) => s.selectedCommunicationMethodIds ?? [],
|
||||
},
|
||||
{
|
||||
step: "membership-methods",
|
||||
footerMessageKey: "confirmMembership",
|
||||
selectionIds: (s) => s.selectedMembershipMethodIds ?? [],
|
||||
},
|
||||
{
|
||||
step: "decision-approaches",
|
||||
footerMessageKey: "confirmDecisionApproaches",
|
||||
selectionIds: (s) => s.selectedDecisionApproachIds ?? [],
|
||||
},
|
||||
{
|
||||
step: "conflict-management",
|
||||
footerMessageKey: "confirmConflictManagement",
|
||||
selectionIds: (s) => s.selectedConflictManagementIds ?? [],
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const CUSTOM_RULE_CONFIRM_FOOTER_STEP_BY_STEP: ReadonlyMap<
|
||||
CreateFlowStep,
|
||||
CustomRuleConfirmFooterStep
|
||||
> = new Map(CUSTOM_RULE_CONFIRM_FOOTER_STEPS.map((e) => [e.step, e]));
|
||||
@@ -1,27 +0,0 @@
|
||||
import type { CreateFlowState } from "../types";
|
||||
|
||||
const IGNORED_KEYS = new Set<string>(["currentStep"]);
|
||||
|
||||
function valueIndicatesUserInput(value: unknown): boolean {
|
||||
if (value === undefined || value === null) return false;
|
||||
if (typeof value === "string") return value.trim().length > 0;
|
||||
if (typeof value === "boolean") return value;
|
||||
if (typeof value === "number") return Number.isFinite(value);
|
||||
if (Array.isArray(value)) return value.length > 0;
|
||||
if (typeof value === "object") {
|
||||
return Object.keys(value as object).length > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* True once the user has entered meaningful create-flow data (not only navigation metadata).
|
||||
* Used to show "Save & Exit" vs a plain "Exit" that confirms data loss.
|
||||
*/
|
||||
export function hasCreateFlowUserInput(state: CreateFlowState): boolean {
|
||||
for (const key of Object.keys(state)) {
|
||||
if (IGNORED_KEYS.has(key)) continue;
|
||||
if (valueIndicatesUserInput(state[key])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user