Template recommendation implemented

This commit is contained in:
adilallo
2026-04-29 19:24:50 -06:00
parent c4c74ecdb4
commit a4f0c4bf27
20 changed files with 899 additions and 82 deletions
+13 -2
View File
@@ -15,7 +15,16 @@ import { useCreateFlowFinalize } from "./hooks/useCreateFlowFinalize";
import { useTemplateReviewActions } from "./hooks/useTemplateReviewActions";
import CreateFlowFooter from "../../components/navigation/CreateFlowFooter";
import CreateFlowTopNav from "../../components/navigation/CreateFlowTopNav";
import { getNextStep, getStepIndex, parseReviewReturnSearchParam, CREATE_FLOW_REVIEW_RETURN_QUERY_KEY } from "./utils/flowSteps";
import {
getNextStep,
getStepIndex,
parseReviewReturnSearchParam,
CREATE_FLOW_REVIEW_RETURN_QUERY_KEY,
TEMPLATES_FACET_RECOMMEND_QUERY,
TEMPLATES_FACET_RECOMMEND_VALUE,
TEMPLATE_REVIEW_FROM_CREATE_FLOW_QUERY,
TEMPLATE_REVIEW_FROM_CREATE_FLOW_VALUE,
} from "./utils/flowSteps";
import { getProportionBarProgressForCreateFlowStep } from "./utils/createFlowProportionProgress";
import {
createFlowStepUsesCenteredTextLayout,
@@ -600,7 +609,9 @@ function CreateFlowLayoutContent({
// detour. Direct entries to `/templates` (no marker) and
// home "Popular templates" clicks always start fresh by
// wiping anonymous draft storage at click time.
router.push("/templates?fromFlow=1");
router.push(
`/templates?${TEMPLATE_REVIEW_FROM_CREATE_FLOW_QUERY}=${TEMPLATE_REVIEW_FROM_CREATE_FLOW_VALUE}&${TEMPLATES_FACET_RECOMMEND_QUERY}=${TEMPLATES_FACET_RECOMMEND_VALUE}`,
);
}}
>
{footer.createFromTemplate}
@@ -1,10 +1,7 @@
"use client";
import { useEffect, useMemo, useRef, useState } from "react";
import facetGroups from "../../../../data/create/customRule/_facetGroups.json";
import {
type CreateFlowState,
} from "../types";
import { buildFacetQueryString } from "../../../../lib/create/buildFacetQueryString";
import { useCreateFlow } from "../context/CreateFlowContext";
/**
@@ -16,60 +13,6 @@ export type RecommendationSection =
| "decisionApproaches"
| "conflictManagement";
const FACET_GROUPS = ["size", "orgType", "scale", "maturity"] as const;
type FacetGroupId = (typeof FACET_GROUPS)[number];
/** Reverse map chipId → canonical facet value id, per group. */
const CHIP_TO_VALUE_BY_GROUP: Record<FacetGroupId, Record<string, string>> = (() => {
const out: Record<FacetGroupId, Record<string, string>> = {
size: {},
orgType: {},
scale: {},
maturity: {},
};
for (const group of FACET_GROUPS) {
const block = (facetGroups as Record<string, unknown>)[group];
if (block && typeof block === "object" && "values" in block) {
const values = (block as { values: Record<string, { chipId: string }> })
.values;
for (const [valueId, entry] of Object.entries(values)) {
out[group][entry.chipId] = valueId;
}
}
}
return out;
})();
/** Chip-id state accessors per group. */
const STATE_KEY_BY_GROUP: Record<FacetGroupId, keyof CreateFlowState> = {
size: "selectedCommunitySizeIds",
orgType: "selectedOrganizationTypeIds",
scale: "selectedScaleIds",
maturity: "selectedMaturityIds",
};
function readChipIds(
state: CreateFlowState,
group: FacetGroupId,
): string[] {
const value = state[STATE_KEY_BY_GROUP[group]];
return Array.isArray(value) ? (value as string[]) : [];
}
function buildFacetQuery(state: CreateFlowState): string {
const params = new URLSearchParams();
for (const group of FACET_GROUPS) {
const valuesById = CHIP_TO_VALUE_BY_GROUP[group];
for (const chipId of readChipIds(state, group)) {
const valueId = valuesById[chipId];
if (valueId) {
params.append(`facet.${group}`, valueId);
}
}
}
return params.toString();
}
export type FacetRecommendationsResult = {
/** `true` once the network call completes (or short-circuits with no facets). */
isReady: boolean;
@@ -99,7 +42,10 @@ export function useFacetRecommendations(
section: RecommendationSection,
): FacetRecommendationsResult {
const { state } = useCreateFlow();
const queryString = useMemo(() => buildFacetQuery(state), [state]);
const queryString = useMemo(
() => buildFacetQueryString(state),
[state],
);
const hasAnyFacets = queryString.length > 0;
const [result, setResult] = useState<FacetRecommendationsResult>({
+8
View File
@@ -153,6 +153,14 @@ export function parseCreateFlowScreenFromPathname(
export const TEMPLATE_REVIEW_FROM_CREATE_FLOW_QUERY = "fromFlow" as const;
export const TEMPLATE_REVIEW_FROM_CREATE_FLOW_VALUE = "1" as const;
/**
* Only set from `/create/review` “Create from template” with `fromFlow=1`.
* Enables facet-ranked `GET /api/templates` + “RECOMMENDED” on the grid; omit
* on profile and marketing so stale localStorage facets never show badges.
*/
export const TEMPLATES_FACET_RECOMMEND_QUERY = "recommendTemplates" as const;
export const TEMPLATES_FACET_RECOMMEND_VALUE = "1" as const;
/** `/create/{step}?reviewReturn=…` — set when opening a custom-rule step from final-review or edit-rule via + */
export const CREATE_FLOW_REVIEW_RETURN_QUERY_KEY = "reviewReturn" as const;