Load rule templates from API
This commit is contained in:
@@ -1,8 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { memo, useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { logger } from "../../../../lib/logger";
|
||||
import {
|
||||
fetchTemplates,
|
||||
isTemplatesFetchAborted,
|
||||
} from "../../../../lib/create/fetchTemplates";
|
||||
import { GOVERNANCE_TEMPLATE_HOME_SLUGS } from "../../../../lib/templates/governanceTemplateCatalog";
|
||||
import { gridEntriesForSlugOrderWithCatalogFallback } from "../../../../lib/templates/templateGridPresentation";
|
||||
import type { TemplateGridCardEntry } from "../../../../lib/templates/templateGridPresentation";
|
||||
import { RuleStackView } from "./RuleStack.view";
|
||||
import type { RuleStackProps } from "./RuleStack.types";
|
||||
|
||||
@@ -19,8 +26,53 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const RuleStackContainer = memo<RuleStackProps>(({ className = "" }) => {
|
||||
const RuleStackContainer = memo<RuleStackProps>(
|
||||
({ className = "", initialGridEntries }) => {
|
||||
const router = useRouter();
|
||||
const [gridEntries, setGridEntries] = useState<TemplateGridCardEntry[] | null>(
|
||||
() => initialGridEntries ?? null,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialGridEntries !== undefined) {
|
||||
return;
|
||||
}
|
||||
const ac = new AbortController();
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
try {
|
||||
const result = await fetchTemplates({ signal: ac.signal });
|
||||
if (cancelled) return;
|
||||
if ("error" in result) {
|
||||
setGridEntries(
|
||||
gridEntriesForSlugOrderWithCatalogFallback(
|
||||
[],
|
||||
GOVERNANCE_TEMPLATE_HOME_SLUGS,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
setGridEntries(
|
||||
gridEntriesForSlugOrderWithCatalogFallback(
|
||||
result,
|
||||
GOVERNANCE_TEMPLATE_HOME_SLUGS,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
if (cancelled || isTemplatesFetchAborted(e)) return;
|
||||
setGridEntries(
|
||||
gridEntriesForSlugOrderWithCatalogFallback(
|
||||
[],
|
||||
GOVERNANCE_TEMPLATE_HOME_SLUGS,
|
||||
),
|
||||
);
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
ac.abort();
|
||||
};
|
||||
}, [initialGridEntries]);
|
||||
|
||||
const handleTemplateClick = (slug: string) => {
|
||||
// Basic analytics tracking
|
||||
@@ -44,9 +96,11 @@ const RuleStackContainer = memo<RuleStackProps>(({ className = "" }) => {
|
||||
<RuleStackView
|
||||
className={className}
|
||||
onTemplateClick={handleTemplateClick}
|
||||
gridEntries={gridEntries}
|
||||
/>
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
RuleStackContainer.displayName = "RuleStack";
|
||||
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import type { TemplateGridCardEntry } from "../../../../lib/templates/templateGridPresentation";
|
||||
|
||||
export interface RuleStackProps {
|
||||
className?: string;
|
||||
/**
|
||||
* When set (e.g. from a Server Component), first paint uses this data and
|
||||
* the client skips the `/api/templates` request.
|
||||
*/
|
||||
initialGridEntries?: TemplateGridCardEntry[];
|
||||
}
|
||||
|
||||
export interface RuleStackViewProps {
|
||||
className: string;
|
||||
onTemplateClick: (_slug: string) => void;
|
||||
/** `null` while loading curated templates from the API. */
|
||||
gridEntries: TemplateGridCardEntry[] | null;
|
||||
}
|
||||
|
||||
@@ -4,14 +4,13 @@ import { useTranslation } from "../../../contexts/MessagesContext";
|
||||
import SectionHeader from "../SectionHeader";
|
||||
import Button from "../../buttons/Button";
|
||||
import { GovernanceTemplateGrid } from "../GovernanceTemplateGrid";
|
||||
import { getGovernanceTemplatesForHome } from "../../../../lib/templates/governanceTemplateCatalog";
|
||||
import { GovernanceTemplateGridSkeleton } from "../GovernanceTemplateGrid/GovernanceTemplateGridSkeleton";
|
||||
import type { RuleStackViewProps } from "./RuleStack.types";
|
||||
|
||||
const homeFeaturedTemplates = getGovernanceTemplatesForHome();
|
||||
|
||||
export function RuleStackView({
|
||||
className,
|
||||
onTemplateClick,
|
||||
gridEntries,
|
||||
}: RuleStackViewProps) {
|
||||
const t = useTranslation("pages.home.ruleStack");
|
||||
const buttonText = t("button.seeAllTemplates");
|
||||
@@ -37,10 +36,14 @@ export function RuleStackView({
|
||||
variant="multi-line"
|
||||
/>
|
||||
|
||||
<GovernanceTemplateGrid
|
||||
entries={homeFeaturedTemplates}
|
||||
onTemplateClick={onTemplateClick}
|
||||
/>
|
||||
{gridEntries === null ? (
|
||||
<GovernanceTemplateGridSkeleton count={4} />
|
||||
) : (
|
||||
<GovernanceTemplateGrid
|
||||
entries={gridEntries}
|
||||
onTemplateClick={onTemplateClick}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div
|
||||
className="
|
||||
|
||||
Reference in New Issue
Block a user