diff --git a/apps/cms/config/plugins.ts b/apps/cms/config/plugins.ts index 7ecff970..2924b25e 100644 --- a/apps/cms/config/plugins.ts +++ b/apps/cms/config/plugins.ts @@ -53,6 +53,10 @@ const config = ({ config: { endpoint: "/graphql", shadowCRUD: true, + // graphql-depth-limit@1.1.0 crashes on fragment spreads within + // dynamic-zone unions (reads .kind on undefined nodes). Set high + // to avoid the crash path in the library's recursive traversal. + depthLimit: 100, landingPage: env("NODE_ENV") !== "production", generateArtifacts: true, artifacts: { diff --git a/apps/cms/schema.graphql b/apps/cms/schema.graphql index f50643c3..940ee6c7 100644 --- a/apps/cms/schema.graphql +++ b/apps/cms/schema.graphql @@ -392,6 +392,26 @@ input ComponentSectionsPromoBannerInput { widthPercent: Int } +type ComponentSectionsQuizButton { + buttonText: String! + id: ID! + iframeSrc: String! +} + +input ComponentSectionsQuizButtonFiltersInput { + and: [ComponentSectionsQuizButtonFiltersInput] + buttonText: StringFilterInput + iframeSrc: StringFilterInput + not: ComponentSectionsQuizButtonFiltersInput + or: [ComponentSectionsQuizButtonFiltersInput] +} + +input ComponentSectionsQuizButtonInput { + buttonText: String + id: ID + iframeSrc: String +} + type ComponentSectionsRelatedQuestionItem { answer: String! id: ID! @@ -787,7 +807,7 @@ input FloatFilterInput { startsWith: Float } -union GenericMorph = ComponentSectionsBibleQuoteItem | ComponentSectionsBibleQuotesCarousel | ComponentSectionsCard | ComponentSectionsContainer | ComponentSectionsContainerSlot | ComponentSectionsCta | ComponentSectionsEasterDates | ComponentSectionsInfoBlock | ComponentSectionsInfoBlocks | ComponentSectionsMediaCollection | ComponentSectionsMediaCollectionItem | ComponentSectionsPromoBanner | ComponentSectionsRelatedQuestionItem | ComponentSectionsRelatedQuestions | ComponentSectionsSection | ComponentSectionsText | ComponentSectionsVideo | ComponentSectionsVideoHero | Experience | I18NLocale | ReviewWorkflowsWorkflow | ReviewWorkflowsWorkflowStage | UploadFile | UsersPermissionsPermission | UsersPermissionsRole | UsersPermissionsUser | Video +union GenericMorph = ComponentSectionsBibleQuoteItem | ComponentSectionsBibleQuotesCarousel | ComponentSectionsCard | ComponentSectionsContainer | ComponentSectionsContainerSlot | ComponentSectionsCta | ComponentSectionsEasterDates | ComponentSectionsInfoBlock | ComponentSectionsInfoBlocks | ComponentSectionsMediaCollection | ComponentSectionsMediaCollectionItem | ComponentSectionsPromoBanner | ComponentSectionsQuizButton | ComponentSectionsRelatedQuestionItem | ComponentSectionsRelatedQuestions | ComponentSectionsSection | ComponentSectionsText | ComponentSectionsVideo | ComponentSectionsVideoHero | Experience | I18NLocale | ReviewWorkflowsWorkflow | ReviewWorkflowsWorkflowStage | UploadFile | UsersPermissionsPermission | UsersPermissionsRole | UsersPermissionsUser | Video type I18NLocale { code: String @@ -1190,7 +1210,7 @@ type ReviewWorkflowsWorkflowStageRelationResponseCollection { nodes: [ReviewWorkflowsWorkflowStage!]! } -union SectionContentDynamicZone = ComponentSectionsBibleQuotesCarousel | ComponentSectionsCard | ComponentSectionsContainer | ComponentSectionsCta | ComponentSectionsInfoBlocks | ComponentSectionsMediaCollection | ComponentSectionsPromoBanner | ComponentSectionsRelatedQuestions | ComponentSectionsText | ComponentSectionsVideo | Error +union SectionContentDynamicZone = ComponentSectionsBibleQuotesCarousel | ComponentSectionsCard | ComponentSectionsContainer | ComponentSectionsCta | ComponentSectionsInfoBlocks | ComponentSectionsMediaCollection | ComponentSectionsPromoBanner | ComponentSectionsQuizButton | ComponentSectionsRelatedQuestions | ComponentSectionsText | ComponentSectionsVideo | Error scalar SectionContentDynamicZoneInput diff --git a/apps/cms/scripts/seed-easter.cjs b/apps/cms/scripts/seed-easter.cjs index 3985c7d4..73129393 100644 --- a/apps/cms/scripts/seed-easter.cjs +++ b/apps/cms/scripts/seed-easter.cjs @@ -349,6 +349,12 @@ async function main() { ], } + const quizButtonBlock = { + __component: "sections.quiz-button", + buttonText: "What's your next step of faith?", + iframeSrc: "https://your.nextstep.is/embed/easter2025?expand=false", + } + const sectionBlock = { __component: "sections.section", sectionKey: "easter-meaning", @@ -358,6 +364,7 @@ async function main() { easterExplainedBlock, textAndQuestionsContainer, bibleQuotesBlock, + quizButtonBlock, ], } diff --git a/apps/cms/src/components/sections/quiz-button.json b/apps/cms/src/components/sections/quiz-button.json new file mode 100644 index 00000000..e51f1e1c --- /dev/null +++ b/apps/cms/src/components/sections/quiz-button.json @@ -0,0 +1,20 @@ +{ + "collectionName": "components_sections_quiz_buttons", + "info": { + "displayName": "Quiz Button", + "icon": "question-circle", + "description": "Interactive quiz button that opens a modal with an embedded quiz iframe" + }, + "options": {}, + "attributes": { + "buttonText": { + "type": "string", + "required": true + }, + "iframeSrc": { + "type": "string", + "required": true, + "regex": "^https://[\\w.-]+\\.nextstep\\.is/.*$" + } + } +} diff --git a/apps/cms/src/components/sections/section.json b/apps/cms/src/components/sections/section.json index a464afd9..49aa8cec 100644 --- a/apps/cms/src/components/sections/section.json +++ b/apps/cms/src/components/sections/section.json @@ -43,7 +43,8 @@ "sections.related-questions", "sections.bible-quotes-carousel", "sections.card", - "sections.video" + "sections.video", + "sections.quiz-button" ], "required": false } diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 36a1b614..bec9cc21 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -133,3 +133,24 @@ @apply font-sans; } } + +@layer utilities { + .animate-mesh-gradient { + animation: mesh-gradient 6s ease infinite; + } + .animate-mesh-gradient-fast { + animation: mesh-gradient 2s ease infinite; + } +} + +@keyframes mesh-gradient { + 0% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } +} diff --git a/apps/web/src/components/sections/QuizButton.tsx b/apps/web/src/components/sections/QuizButton.tsx new file mode 100644 index 00000000..cacadea7 --- /dev/null +++ b/apps/web/src/components/sections/QuizButton.tsx @@ -0,0 +1,78 @@ +"use client" + +import { type ReactElement, useState } from "react" +import { Loader2, XIcon } from "lucide-react" +import { Dialog, DialogClose, DialogContent } from "@/components/ui/dialog" + +type QuizButtonData = { + id: string + buttonText: string + iframeSrc: string +} + +type QuizButtonProps = { + data: QuizButtonData +} + +export function QuizButton({ data }: QuizButtonProps): ReactElement { + const { buttonText, iframeSrc } = data + const [open, setOpen] = useState(false) + + return ( + <> +
+ +
+ + + + + + Close + +
+
+ +
+
+