From 84e7c92c832d5a30307ff5f5f5733804a589c98a Mon Sep 17 00:00:00 2001 From: Joe Heffernan Date: Tue, 28 Jul 2026 11:01:20 -0700 Subject: [PATCH 1/2] render idea preview without resolving relational widget data --- src/cms/cms.js | 2 +- src/cms/preview-templates/IdeaPostPreview.js | 26 ------ src/cms/preview-templates/IdeaPostPreview.tsx | 88 +++++++++++++++++++ src/templates/idea-post.tsx | 71 +++++++++------ 4 files changed, 133 insertions(+), 54 deletions(-) delete mode 100644 src/cms/preview-templates/IdeaPostPreview.js create mode 100644 src/cms/preview-templates/IdeaPostPreview.tsx diff --git a/src/cms/cms.js b/src/cms/cms.js index aedded28..c56b3946 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({ diff --git a/src/cms/preview-templates/IdeaPostPreview.js b/src/cms/preview-templates/IdeaPostPreview.js deleted file mode 100644 index d3d35171..00000000 --- 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 00000000..a0d4269f --- /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 1f40dc04..1f29f4c8 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} + + ) : ( + + ), + )}
)} @@ -214,8 +223,16 @@ export const IdeaPostTemplate: React.FC< {relatedIdeas!.map((idea) => { if (!idea.slug && !idea.title) return null; return ( -
  • - {idea.title} +
  • + {/* Gatsby's Link needs the app runtime + the Decap preview iframe lacks */} + {isPreview ? ( + {idea.title} + ) : ( + + {idea.title} + + )}
  • ); })} From c2661dbf6e380fc936e1e4170265e92ee37c7d7d Mon Sep 17 00:00:00 2001 From: Joe Heffernan Date: Tue, 18 Aug 2026 10:18:31 -0700 Subject: [PATCH 2/2] add time zone utc to new date --- src/cms/preview-templates/IdeaPostPreview.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cms/preview-templates/IdeaPostPreview.tsx b/src/cms/preview-templates/IdeaPostPreview.tsx index a0d4269f..204a7cd1 100644 --- a/src/cms/preview-templates/IdeaPostPreview.tsx +++ b/src/cms/preview-templates/IdeaPostPreview.tsx @@ -46,6 +46,7 @@ function normalizeCmsData( year: "numeric", month: "long", day: "2-digit", + timeZone: "UTC", }) : undefined;