From e0552b255637bbfaad54fe6b6c54480b13a59195 Mon Sep 17 00:00:00 2001 From: Joe Heffernan Date: Wed, 22 Jul 2026 15:15:27 -0700 Subject: [PATCH 1/2] fix ideas resource label in cms registry --- src/cms/cms.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cms/cms.js b/src/cms/cms.js index aedded2..c56b394 100644 --- a/src/cms/cms.js +++ b/src/cms/cms.js @@ -20,7 +20,7 @@ CMS.registerWidget("url-image", UrlImageControl, UrlImagePreview); CMS.registerPreviewTemplate("index", IndexPagePreview); CMS.registerPreviewTemplate("about", AboutPagePreview); -CMS.registerPreviewTemplate("idea", IdeaPostPreview); +CMS.registerPreviewTemplate("ideas", IdeaPostPreview); // Decap exposes a number of lifecycle stages we can hook into and register. CMS.registerEventListener({ From 72037f1ed3b6aac6801dda4a5d2b1bd13b848a46 Mon Sep 17 00:00:00 2001 From: Joe Heffernan Date: Wed, 22 Jul 2026 15:27:58 -0700 Subject: [PATCH 2/2] update IdeaPostPreview component to TS, add data normalization, guard for preview rendering in the idea post template --- src/cms/preview-templates/IdeaPostPreview.js | 26 ------ src/cms/preview-templates/IdeaPostPreview.tsx | 88 +++++++++++++++++++ src/templates/idea-post.tsx | 82 ++++++++++------- 3 files changed, 138 insertions(+), 58 deletions(-) delete mode 100644 src/cms/preview-templates/IdeaPostPreview.js create mode 100644 src/cms/preview-templates/IdeaPostPreview.tsx diff --git a/src/cms/preview-templates/IdeaPostPreview.js b/src/cms/preview-templates/IdeaPostPreview.js deleted file mode 100644 index d3d3517..0000000 --- a/src/cms/preview-templates/IdeaPostPreview.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from "react"; - -import PropTypes from "prop-types"; - -import { IdeaPostTemplate } from "../../templates/idea-post"; - -const IdeaPostPreview = ({ entry, widgetFor }) => { - const tags = entry.getIn(["data", "tags"]); - return ( - - ); -}; - -IdeaPostPreview.propTypes = { - entry: PropTypes.shape({ - getIn: PropTypes.func, - }), - widgetFor: PropTypes.func, -}; - -export default IdeaPostPreview; diff --git a/src/cms/preview-templates/IdeaPostPreview.tsx b/src/cms/preview-templates/IdeaPostPreview.tsx new file mode 100644 index 0000000..a0d4269 --- /dev/null +++ b/src/cms/preview-templates/IdeaPostPreview.tsx @@ -0,0 +1,88 @@ +import React from "react"; + +import { + IdeaPostTemplate, + IdeaPostTemplateProps, +} from "../../templates/idea-post"; +import { ImmutableLike, fromImmutable } from "../utils/immutable"; + +interface PreviewProps { + entry?: ImmutableLike; + value?: unknown; +} + +/** + * Normalize CMS form data into the shape IdeaPostTemplate expects. + * Decap gives us raw widget values which differ from resolved Gatsby data: + * - relation widgets return value_field strings, not resolved objects + * - single select widgets return a string, not an array + */ +function normalizeCmsData( + raw: Record, +): Partial { + const v = raw as Partial; + + // program: single select string → array + const program = v.program; + const normalizedProgram = program + ? Array.isArray(program) + ? program + : [String(program)] + : undefined; + + // authors: relation gives ["name1", "name2"] → [{ name, contactId }] + const authors = v.authors + ? (v.authors as unknown as string[]).map((a) => + typeof a === "string" ? { name: a, contactId: "" } : a, + ) + : undefined; + + // date: datetime widget returns a Date/object, template expects a string + const rawDate = raw.date; + const date = rawDate + ? typeof rawDate === "string" + ? rawDate + : new Date(rawDate as string | number).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "2-digit", + }) + : undefined; + + return { + ...v, + authors, + date, + isPreview: true, + program: normalizedProgram, + }; +} + +const IdeaPostPreview: React.FC = ({ entry, value }) => { + const raw = value ?? (entry?.get("data") as ImmutableLike | undefined); + const v = fromImmutable>(raw) ?? {}; + return ( + <> +
+ Previews are approximate/under development — content and styling + may differ from production, and not all functionality will be + available. +
+ + + ); +}; + +export default IdeaPostPreview; diff --git a/src/templates/idea-post.tsx b/src/templates/idea-post.tsx index 1f40dc0..442a4b9 100644 --- a/src/templates/idea-post.tsx +++ b/src/templates/idea-post.tsx @@ -41,18 +41,20 @@ const { tagRowLabel, } = require("../style/idea-post.module.css"); -export const IdeaPostTemplate: React.FC< - IdeaPostNode & { - onExpandDescription?: ( - content: string, - label: string, - sectionKey: string, - ) => void; - } -> = ({ +export type IdeaPostTemplateProps = IdeaPostNode & { + isPreview?: boolean; + onExpandDescription?: ( + content: string, + label: string, + sectionKey: string, + ) => void; +}; + +export const IdeaPostTemplate: React.FC = ({ authors, date, introduction, + isPreview, nextSteps, onExpandDescription, preliminaryFindings, @@ -121,27 +123,34 @@ export const IdeaPostTemplate: React.FC< - - setContactModalOpen(false)} - /> + {!isPreview && ( + setContactModalOpen(false)} + /> + )} {/* Tag row */} {tags && tags.length > 0 && (
Topics - {tags.map((t) => ( - - ))} + {tags.map((t) => + isPreview ? ( + + {t} + + ) : ( + + ), + )}
)} @@ -198,16 +207,25 @@ export const IdeaPostTemplate: React.FC< )} - {resources && ( - - )} + {resources && + (isPreview ? ( +
    + {(resources as unknown as string[]).map( + (slug) => ( +
  • {slug}
  • + ), + )} +
+ ) : ( + + ))} - {hasRelatedIdeas && ( + {hasRelatedIdeas && !isPreview && (