Skip to content

Commit

Permalink
Fix: Modal rendering across different Iframe contexts (#7435)
Browse files Browse the repository at this point in the history
  • Loading branch information
JoshuaHungDinh authored Jul 9, 2024
1 parent a04d5a8 commit eaeecdd
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -1,25 +1,14 @@
import {__} from '@wordpress/i18n';
import {createPortal} from 'react-dom';
import {Markup} from 'interweave';
import {Button} from '@wordpress/components';
import createIframePortal from './createIframePortal';

import './styles.scss';

export default function ConsentModal({setShowModal, modalHeading, modalAcceptanceText, agreementText, acceptTerms}) {
const scrollModalIntoView = (element) => {
if (element) {
element.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'nearest'});
}
};

return createPortal(
return createIframePortal(
<div className={'givewp-fields-consent-modal'} role="dialog" aria-label={modalHeading}>
<div
className={'givewp-fields-consent-modal-content'}
ref={(element) => {
element && scrollModalIntoView(element);
}}
>
<div className={'givewp-fields-consent-modal-content'}>
<h2>{modalHeading}</h2>

<div className={'givewp-fields-consent-modal-content__agreement-text'}>
Expand All @@ -30,10 +19,12 @@ export default function ConsentModal({setShowModal, modalHeading, modalAcceptanc
<Button variant={'secondary'} onClick={() => setShowModal(false)}>
{__('Cancel', 'give')}
</Button>
<Button onClick={acceptTerms}>{modalAcceptanceText}</Button>
<Button variant={'primary'} onClick={acceptTerms}>
{modalAcceptanceText}
</Button>
</div>
</div>
</div>,
document.body
window.top.document.body
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import {createPortal, render} from 'react-dom';
import {useEffect, useRef} from 'react';

import './styles.scss';

/**
* @unreleased
* Creates a portal to the Top Level document, rendering children elements within an iframe.
*/
export default function createIframePortal(children, targetElement = window.top.document.body) {
const iframeRef = useRef<HTMLIFrameElement | null>(null);

useEffect(() => {
const iframe = iframeRef.current;
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;

if (iframe) {
// Clear existing content.
iframeDoc.head.innerHTML = '';
iframeDoc.body.innerHTML = '';

async function renderContent() {
try {
await fetchStylesheets(iframeDoc);
render(children, iframeDoc.body);
} catch (error) {
console.error('Error loading stylesheets:', error);
}
}

renderContent();
}
}, []);

return createPortal(
<iframe
ref={iframeRef}
id={'givewp-fields-consent-iframe-portal'}
style={{
position: 'fixed',
border: 'none',
top: '0',
left: '0',
minHeight: '100%',
minWidth: '100%',
// Required to be visible in the Visual Form Builder.
zIndex: '9999999999',
}}
/>,
targetElement
);
}

/**
* @unreleased
* Fetches stylesheets from the originating document and injects them into the new iframe document's head.
* This allows user provided styles to be applied to the iframe content.
* Returns a promise that resolves when all stylesheets are loaded.
*/
export async function fetchStylesheets(iframeDoc: Document) {
const styleSheets = Array.from(document.styleSheets);

// Promisify the loading of each stylesheet
const loadStylesheet = (styleSheet) => {
return new Promise<void>((resolve, reject) => {
try {
if (styleSheet.href) {
// For external stylesheets
const newLink = document.createElement('link');
newLink.rel = 'stylesheet';
newLink.href = styleSheet.href;
newLink.onload = () => resolve();
newLink.onerror = reject;
iframeDoc.head.appendChild(newLink);
} else if (styleSheet.cssRules) {
// For <style/> tags
const newStyle = document.createElement('style');
Array.from(styleSheet.cssRules).forEach((rule: {cssText: string}) => {
newStyle.appendChild(document.createTextNode(rule.cssText));
});
iframeDoc.head.appendChild(newStyle);
resolve();
}
} catch (error) {
reject(error);
}
});
};

const promises = styleSheets.map(loadStylesheet);
return await Promise.all(promises);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
}

&-modal {
position: absolute;
position: fixed;
top: 0;
left: 0;
bottom: 0;
Expand All @@ -17,9 +17,9 @@
justify-content: center;
background: transparent;
backdrop-filter: blur(2px);
z-index: 999;

&-content {
max-width: 48rem;
background: var(--givep-shades-white, #fff);
padding: 2.5rem 3.5rem;
width: calc(min(100%, 51.5rem) + 2rem);
Expand All @@ -41,8 +41,7 @@
display: flex;
gap: 1rem;

> button {
margin: 0;
button:first-child {
background: transparent;
color: var(--givewp-primary-color);
border: 1px solid var(--givewp-primary-color);
Expand All @@ -66,4 +65,3 @@
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export default function Edit({
/>
</PanelRow>

{isLinkDisplay && (
{(isLinkDisplay || isModalDisplay) && (
<PanelRow>
<TextControl
label={__('Link Text', 'give')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const settings: FieldBlock['settings'] = {
description: __('Donors can accept the terms and conditions', 'give'),
supports: {
multiple: false,
html: false
html: true,
},
attributes: {
useGlobalSettings: {
Expand Down Expand Up @@ -43,7 +43,10 @@ const settings: FieldBlock['settings'] = {
agreementText: {
type: 'string',
default: __(
'<p>Acceptance of any contribution, gift or grant is at the discretion of the GiveWP. The GiveWP will not accept any gift unless it can be used or expended consistently with the purpose and mission of the GiveWP. No irrevocable gift, whether outright or life-income in character, will be accepted if under any reasonable set of circumstances the gift would jeopardize the donor’s financial security.The GiveWP will refrain from providing advice about the tax or other treatment of gifts and will encourage donors to seek guidance from their own professional advisers to assist them in the process of making their donation.</p>',
'<p>Acceptance of any contribution, gift or grant is at the discretion of the GiveWP.</p>' +
'<p>The GiveWP will not accept any gift unless it can be used or expended consistently with the purpose and mission of the GiveWP.</p>' +
'<p>No irrevocable gift, whether outright or life-income in character, will be accepted if under any reasonable set of circumstances the gift would jeopardize the donor’s financial security.</p>' +
'<p>The GiveWP will refrain from providing advice about the tax or other treatment of gifts and will encourage donors to seek guidance from their own professional advisers to assist them in the process of making their donation.</p>',
'give'
),
},
Expand Down

0 comments on commit eaeecdd

Please sign in to comment.