Skip to content

feat(web,cms): add quiz-button section component#443

Merged
Kneesal merged 16 commits intoJesusFilm:mainfrom
Kneesal:feat/442-quiz-button
Mar 13, 2026
Merged

feat(web,cms): add quiz-button section component#443
Kneesal merged 16 commits intoJesusFilm:mainfrom
Kneesal:feat/442-quiz-button

Conversation

@Kneesal
Copy link
Copy Markdown
Member

@Kneesal Kneesal commented Mar 12, 2026

Summary

  • Add quiz-button CMS component schema with buttonText and iframeSrc attributes, registered in the section content dynamic zone
  • Create QuizButton web component with animated mesh gradient button that opens a fullscreen modal with embedded iframe
  • Wire GraphQL fragment, section renderer, and codegen types
  • Add idempotent seed data that places the quiz button as the last block in the first section on CMS bootstrap

Contracts Changed

  • apps/cms/schema.graphqlComponentSectionsQuizButton type added to SectionContentDynamicZone and GenericMorph unions

Regeneration Required

  • Yes — packages/graphql/src/graphql-env.d.ts regenerated via gql-tada generate output

Validation

  • CMS starts with quiz-button schema registered
  • Quiz button appears as last block in first section after seed
  • Button renders with gradient animation matching reference design
  • Clicking button opens modal with iframe

Resolves #442

Made with Cursor

Summary by CodeRabbit

  • New Features
    • Introduced a new Quiz Button component that displays an interactive button with customizable text. When clicked, the button opens a modal window containing an embedded quiz iframe, enabling users to complete quizzes seamlessly without leaving the page
    • Added mesh gradient animation utility classes to enhance visual styling and create dynamic animation effects

Kneesal added 3 commits March 13, 2026 11:50
Add quiz-button component with buttonText and iframeSrc attributes.
Register it in the section content dynamic zone and update the
GraphQL schema with the new type and union memberships.

Made-with: Cursor
Create the QuizButton component with animated gradient button that
opens a fullscreen modal with an embedded iframe. Wire the GraphQL
fragment, register in section renderer, and add mesh gradient keyframe
animation to globals.css.

Made-with: Cursor
Seed the quiz button as the last block in the first section of the
first experience on CMS bootstrap, with idempotency check.

Made-with: Cursor
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 12, 2026

Warning

Rate limit exceeded

@Kneesal has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 15 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1617cf2a-3214-4a9f-8967-43c297b77042

📥 Commits

Reviewing files that changed from the base of the PR and between f0cb5d6 and f43edb1.

📒 Files selected for processing (6)
  • apps/cms/scripts/seed-easter.cjs
  • apps/cms/src/components/sections/quiz-button.json
  • apps/web/src/components/sections/QuizButton.tsx
  • apps/web/src/components/sections/Section.tsx
  • apps/web/src/components/ui/dialog.tsx
  • apps/web/src/lib/fragments/section.ts

Walkthrough

This PR introduces a new quiz-button section component across the CMS and web platforms. It includes CMS schema definitions, a web React component with modal functionality, GraphQL type definitions, mesh-gradient styling, and integration into the section renderer and content query system.

Changes

Cohort / File(s) Summary
CMS Schema & Type Definitions
apps/cms/schema.graphql, packages/graphql/src/graphql-env.d.ts
Added ComponentSectionsQuizButton type with buttonText and iframeSrc fields, filter inputs, and included in GenericMorph and SectionContentDynamicZone unions.
CMS Component Configuration
apps/cms/src/components/sections/quiz-button.json, apps/cms/src/components/sections/section.json
Created new quiz-button component definition with buttonText and iframeSrc attributes; added quiz-button to section's content components array.
CMS GraphQL Configuration
apps/cms/config/plugins.ts
Added depthLimit property (100) to GraphQL plugin config to handle fragment spreads in dynamic-zone unions.
Web React Component
apps/web/src/components/sections/QuizButton.tsx
Implemented QuizButton component with QuizModal child component, managing modal state, iframe rendering, and keyboard close functionality.
Web Styling
apps/web/src/app/globals.css
Added animate-mesh-gradient and animate-mesh-gradient-fast utility classes with mesh-gradient keyframes for animated background positioning.
GraphQL Fragments
apps/web/src/lib/fragments/quiz-button-section.ts, apps/web/src/lib/fragments/index.ts, apps/web/src/lib/fragments/section.ts
Created quiz-button-section fragment with buttonText and iframeSrc fields; exported from fragments index; integrated into section fragment with inline spread.
Component Integration
apps/web/src/components/sections/Section.tsx, apps/web/src/lib/content.ts
Added ComponentSectionsQuizButton case to SectionContentRenderer; included quizButtonSectionFragment in GetWatchExperience query blocks.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(web,cms): add quiz-button section component' directly and accurately summarizes the main changes: adding a new quiz-button section component across the web and CMS applications.
Linked Issues check ✅ Passed The pull request successfully implements all acceptance criteria from issue #442: CMS schema with required attributes, GraphQL wiring, web component with modal/iframe, mesh gradient animation, section renderer support, and seed data placement.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #442 requirements. The addition of depthLimit in plugin config addresses a technical necessity for the GraphQL fragment composition to work correctly.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Kneesal added 3 commits March 13, 2026 11:59
Remove standalone seed file and bootstrap hook. Add quiz button
block to the first section in both seed-easter.cjs and
seed-easter.mjs instead.

Made-with: Cursor
Keep quiz-button seed only in the web seed script.

Made-with: Cursor
Keep quiz-button seed only in apps/cms/scripts/seed-easter.cjs.

Made-with: Cursor
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/cms/src/components/sections/quiz-button.json`:
- Around line 14-16: The iframeSrc schema currently allows any string; constrain
it at the CMS boundary by adding a pattern (and optionally format) validation to
the iframeSrc property in quiz-button.json so editors can only save approved
quiz origins—for example update the "iframeSrc" field (type "string", required
true) to include a "pattern" regex that matches your allowed origin(s) (e.g.,
the production quiz host and any subpaths) and/or "format": "uri" to enforce
valid URLs; ensure the pattern covers the exact hostname(s) and protocol you
trust so arbitrary remote embeds are rejected.

In `@apps/web/src/components/sections/QuizButton.tsx`:
- Around line 64-85: The modal is not keyboard accessible: remove the backdrop's
role="button" and tabIndex to stop it being focusable, ensure the dialog moves
focus into itself on open and returns focus to the triggering element on close
(capture the trigger via a ref), add a keydown handler on the dialog wrapper to
close on Escape (call onClose), and implement focus trapping while open (or
replace this implementation with the shared Dialog primitive used elsewhere).
Update the elements referenced in QuizButton.tsx (the backdrop div with
onClick/onKeyDown, the outer div with aria-modal, and the close button/onClose
handler) to enforce initial focus, focus return, Escape-to-close, and focus
trap.
- Around line 100-104: The iframe in the QuizButton component renders
third-party content via iframeSrc from quiz-button-section; tighten its
privileges by adding a restrictive sandbox attribute (e.g., empty sandbox or
minimal tokens only if needed such as "allow-scripts" if the embed requires
scripts) and set a strict referrerPolicy like "no-referrer" (or
"strict-origin-when-cross-origin") to avoid leaking referrer info; update the
<iframe> in QuizButton.tsx to include sandbox and referrerPolicy attributes
while keeping the existing title and className, and document any minimal sandbox
tokens chosen so future changes know why they were allowed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 33145d74-ebde-4c88-b9eb-60e0a3e97f56

📥 Commits

Reviewing files that changed from the base of the PR and between 9acaffa and 45e14ea.

📒 Files selected for processing (12)
  • apps/cms/schema.graphql
  • apps/cms/scripts/seed-easter.cjs
  • apps/cms/src/components/sections/quiz-button.json
  • apps/cms/src/components/sections/section.json
  • apps/web/src/app/globals.css
  • apps/web/src/components/sections/QuizButton.tsx
  • apps/web/src/components/sections/Section.tsx
  • apps/web/src/lib/fragments/index.ts
  • apps/web/src/lib/fragments/quiz-button-section.ts
  • apps/web/src/lib/fragments/section.ts
  • packages/graphql/src/graphql-env.d.ts
  • scripts/seed-easter.mjs

Comment thread apps/cms/src/components/sections/quiz-button.json Outdated
Comment thread apps/web/src/components/sections/QuizButton.tsx Outdated
Comment thread apps/web/src/components/sections/QuizButton.tsx Outdated
Kneesal added 5 commits March 13, 2026 12:19
Add null guard in determineDepth to prevent TypeError when
traversing fragment spreads that resolve to undefined nodes
in dynamic zone union queries.

Made-with: Cursor
graphql-depth-limit@1.1.0 crashes on fragment spreads within
dynamic-zone unions. Disable depth limiting in the Strapi GraphQL
plugin config until upstream is fixed.

Made-with: Cursor
Add quizButtonSectionFragment to the GET_WATCH_EXPERIENCE query's
fragment array so the definition is included in the document sent
to Strapi. Set depthLimit to 100 to work around graphql-depth-limit
crash on dynamic-zone fragment traversal.

Made-with: Cursor
The separate QuizButtonSection fragment wasn't being composed into the
final GraphQL document by gql.tada. Inline the fields directly in the
section fragment, matching the pattern used by containerFragment.

Made-with: Cursor
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/cms/config/plugins.ts`:
- Around line 56-59: Replace the workaround that sets depthLimit: 100 (which
neutralizes DoS protection) by upgrading `@strapi/plugin-graphql` to a version
that uses a maintained depth limiter; if the upgraded plugin still depends on
graphql-depth-limit, replace that dependency with `@graphile/depth-limit` and
update the GraphQL plugin configuration to use the new limiter; finally lower
the depthLimit value in the plugin config (the depthLimit setting) to a
reasonable value such as 10–15 and run tests/fragment/union queries to verify
fragment spreads no longer crash the traversal.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ac2be4a6-a488-4cc3-8129-3efe0e466af0

📥 Commits

Reviewing files that changed from the base of the PR and between 45e14ea and f0cb5d6.

📒 Files selected for processing (2)
  • apps/cms/config/plugins.ts
  • apps/web/src/lib/content.ts

Comment thread apps/cms/config/plugins.ts
Kneesal added 4 commits March 13, 2026 12:35
Restore bg-blend-multiply, hover:bg-orange-500, cursor-pointer on
inner div, plain transition on arrow span, and remove extra classes
that weren't in the original.

Made-with: Cursor
The modal was constrained to the section because a parent element's
CSS transform created a new containing block for fixed positioning.
Using createPortal to render at document.body bypasses this. Also adds
Escape key handling and body scroll lock while open.

Made-with: Cursor
The previous custom modal used position:fixed inside a section with
backdrop-blur, which creates a containing block and traps the modal
within the section. Switch to shadcn Dialog (renders via Portal at
document root) with fullscreen styles matching the original component.

Made-with: Cursor
Adds the quiz-button block as the last item in the first section's
content array so it renders at the bottom of the section.

Made-with: Cursor
- Constrain iframeSrc to approved nextstep.is origin via regex
- Sandbox iframe with allow-forms/scripts/same-origin
- Add strict-origin-when-cross-origin referrer policy

Made-with: Cursor
@Kneesal
Copy link
Copy Markdown
Member Author

Kneesal commented Mar 12, 2026

Review feedback addressed (f43edb1)

Fixed:

  • iframeSrc CMS validation: Added regex constraint (^https://[\w.-]+\.nextstep\.is/.*$) to quiz-button.json so editors can only save approved quiz origins
  • Iframe sandboxing: Added sandbox="allow-forms allow-scripts allow-same-origin" and referrerPolicy="strict-origin-when-cross-origin" to the iframe in QuizButton.tsx
  • Keyboard-accessible modal: Already resolved in prior commits (d076f351b97552) by switching from a custom div-based modal to shadcn Dialog (Portal-based, with focus trapping, Escape-to-close, and focus return)

Not changed:

@Kneesal Kneesal merged commit 73b31ff into JesusFilm:main Mar 13, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(web,cms): add quiz-button section component

1 participant