From 2ac42bfe732477d7668e79f2f344e3d4e21d1220 Mon Sep 17 00:00:00 2001 From: rchlfryn Date: Fri, 12 Jun 2026 12:20:32 -0700 Subject: [PATCH 1/6] Refactor GenericEmbed to extract reusable embed code to avoid duplication --- src/blocks/GenericEmbed/Component.tsx | 118 ++------------------- src/blocks/GenericEmbed/config.ts | 6 +- src/components/EmbedFrame/index.tsx | 114 ++++++++++++++++++++ src/components/EmbedFrame/policies.ts | 26 +++++ src/endpoints/seed/blocks/generic-embed.ts | 6 -- src/payload-types.ts | 2 +- 6 files changed, 157 insertions(+), 115 deletions(-) create mode 100644 src/components/EmbedFrame/index.tsx create mode 100644 src/components/EmbedFrame/policies.ts diff --git a/src/blocks/GenericEmbed/Component.tsx b/src/blocks/GenericEmbed/Component.tsx index 6bcc5468b..6aee6dc85 100644 --- a/src/blocks/GenericEmbed/Component.tsx +++ b/src/blocks/GenericEmbed/Component.tsx @@ -1,10 +1,5 @@ -'use client' - -import getTextColorFromBgColor from '@/utilities/getTextColorFromBgColor' -import { cn } from '@/utilities/ui' -import { IframeResizer } from '@open-iframe-resizer/react' -import DOMPurify from 'dompurify' -import { useEffect, useState } from 'react' +import { EmbedFrame } from '@/components/EmbedFrame' +import { BASE_ADD_ATTR } from '@/components/EmbedFrame/policies' import type { GenericEmbedBlock as GenericEmbedBlockProps } from 'src/payload-types' type Props = GenericEmbedBlockProps & { @@ -12,104 +7,13 @@ type Props = GenericEmbedBlockProps & { className?: string } -type IframeContent = { type: 'srcDoc'; value: string } | { type: 'src'; value: string } - -export const GenericEmbedBlockComponent = ({ - id, - html, - backgroundColor, - alignContent = 'left', - className, - isLayoutBlock = true, -}: Props) => { - const [iframeContent, setIframeContent] = useState(null) - - const bgColorClass = `bg-${backgroundColor}` - const textColor = getTextColorFromBgColor(backgroundColor) - - useEffect(() => { - if (typeof window === 'undefined' || !html) return - - // Normalize problematic quotes that are parsed incorrectly by DOMParser and DOMPurify - const normalizedHTML = html.replaceAll('\u201C', '"').replaceAll('\u201D', '"') - - const sanitized = DOMPurify.sanitize(normalizedHTML, { - ADD_TAGS: ['iframe', 'script', 'style', 'dbox-widget'], - ADD_ATTR: [ - 'allow', - 'allowfullscreen', - 'allowpaymentrequest', - 'async', - 'campaign', - 'enable-auto-scroll', - 'frameborder', - 'height', - 'id', - 'name', - 'sandbox', - 'scrolling', - 'src', - 'style', - 'title', - 'type', - 'width', - ], - FORCE_BODY: true, - }) - - const styleOverrides = ` - - ` - - const fullHtml = `${sanitized}${styleOverrides}` - - // Use a blob URL for embeds with ', + backgroundColor: 'transparent', + alignContent: 'center', + }, +] diff --git a/src/endpoints/seed/pages/all-blocks-page.ts b/src/endpoints/seed/pages/all-blocks-page.ts index 0f0a28ab5..a987dbcc7 100644 --- a/src/endpoints/seed/pages/all-blocks-page.ts +++ b/src/endpoints/seed/pages/all-blocks-page.ts @@ -5,6 +5,7 @@ import { contentColumns } from '../blocks/content-columns' import { contentWithCallout } from '../blocks/content-with-callout' import { eventListBlock } from '../blocks/event-list' import { formBlock } from '../blocks/form' +import { formEmbed } from '../blocks/form-embed' import { genericEmbed } from '../blocks/generic-embed' import { headerBlock } from '../blocks/header' import { imageLinkGrid } from '../blocks/image-link-grid' @@ -68,6 +69,8 @@ export const allBlocksPage = ({ sectionLabel('Generic Embed'), ...genericEmbed, sectionLabel('NAC Media Block'), + sectionLabel('Form Embed'), + ...formEmbed, ...nacMediaBlocks, sectionLabel('Blog List (Dynamic)'), { diff --git a/src/fields/EventQuery/config.ts b/src/fields/EventQuery/config.ts index a19ebd96d..34127566b 100644 --- a/src/fields/EventQuery/config.ts +++ b/src/fields/EventQuery/config.ts @@ -1,6 +1,7 @@ import type { Field, FilterOptionsProps } from 'payload' import { ButtonBlock } from '@/blocks/Button/config' +import { FormEmbedBlock } from '@/blocks/FormEmbed/config' import { GenericEmbedBlock } from '@/blocks/GenericEmbed/config' import { MediaBlock } from '@/blocks/Media/config' import { eventTypesData } from '@/constants/eventTypes' @@ -24,7 +25,7 @@ export const defaultStylingFields = (additionalFilters?: Field[]): Field[] => [ return [ ...rootFeatures, BlocksFeature({ - blocks: [ButtonBlock, MediaBlock, GenericEmbedBlock], + blocks: [ButtonBlock, MediaBlock, GenericEmbedBlock, FormEmbedBlock], }), HorizontalRuleFeature(), InlineToolbarFeature(), diff --git a/src/fields/defaultLexical.ts b/src/fields/defaultLexical.ts index 7c9cb58ab..c62eac519 100644 --- a/src/fields/defaultLexical.ts +++ b/src/fields/defaultLexical.ts @@ -1,4 +1,5 @@ import { BlogListBlock } from '@/blocks/BlogList/config' +import { FormEmbedBlock } from '@/blocks/FormEmbed/config' import { GenericEmbedBlock } from '@/blocks/GenericEmbed/config' import { SingleBlogPostBlock } from '@/blocks/SingleBlogPost/config' import { DEFAULT_INLINE_BLOCKS } from '@/constants/defaultInlineBlocks' @@ -67,7 +68,7 @@ export const defaultLexical: Config['editor'] = lexicalEditor({ }, }), BlocksFeature({ - blocks: [GenericEmbedBlock, BlogListBlock, SingleBlogPostBlock], + blocks: [GenericEmbedBlock, FormEmbedBlock, BlogListBlock, SingleBlogPostBlock], inlineBlocks: DEFAULT_INLINE_BLOCKS, }), FixedToolbarFeature(), diff --git a/src/payload-types.ts b/src/payload-types.ts index da91c456e..cd307d822 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -285,6 +285,7 @@ export interface HomePage { | EventListBlock | EventTableBlock | FormBlock + | FormEmbedBlock | GenericEmbedBlock | HeaderBlock | ImageLinkGridBlock @@ -391,6 +392,7 @@ export interface Page { | EventListBlock | EventTableBlock | FormBlock + | FormEmbedBlock | GenericEmbedBlock | HeaderBlock | ImageLinkGridBlock @@ -1218,6 +1220,21 @@ export interface Form { updatedAt: string; createdAt: string; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "FormEmbedBlock". + */ +export interface FormEmbedBlock { + /** + * For donation and form widgets that ship their own scripts (DonorBox, Classy, Eventbrite, etc.). Paste the provider embed code, including any ', + backgroundColor: 'transparent', + alignContent: 'center', + }), + // Media block headingNode('Media Block', 'h3'), blockNode({