apps/portal/src/components/PopupModal.js
import React from 'react';
import Frame from './Frame';
import {hasMode} from '../utils/check-mode';
import AppContext from '../AppContext';
import {getFrameStyles} from './Frame.styles';
import Pages, {getActivePage} from '../pages';
import PopupNotification from './common/PopupNotification';
import PoweredBy from './common/PoweredBy';
import {getSiteProducts, isInviteOnlySite, isCookiesDisabled, hasFreeProductPrice} from '../utils/helpers';
const StylesWrapper = () => {
return {
modalContainer: {
zIndex: '3999999',
position: 'fixed',
left: '0',
top: '0',
width: '100%',
height: '100%',
overflow: 'hidden'
},
frame: {
common: {
margin: 'auto',
position: 'relative',
padding: '0',
outline: '0',
width: '100%',
opacity: '1',
overflow: 'hidden',
height: '100%'
}
},
page: {
links: {
width: '600px'
}
}
};
};
function CookieDisabledBanner({message}) {
const cookieDisabled = isCookiesDisabled();
if (cookieDisabled) {
return (
<div className='gh-portal-cookiebanner'>{message}</div>
);
}
return null;
}
class PopupContent extends React.Component {
static contextType = AppContext;
componentDidMount() {
// Handle Esc to close popup
if (this.node && !hasMode(['preview']) && !this.props.isMobile) {
this.node.focus();
this.keyUphandler = (event) => {
if (event.key === 'Escape') {
this.dismissPopup(event);
}
};
this.node.ownerDocument.removeEventListener('keyup', this.keyUphandler);
this.node.ownerDocument.addEventListener('keyup', this.keyUphandler);
}
this.sendContainerHeightChangeEvent();
}
dismissPopup(event) {
const eventTargetTag = (event.target && event.target.tagName);
// If focused on input field, only allow close if no value entered
const allowClose = eventTargetTag !== 'INPUT' || (eventTargetTag === 'INPUT' && !event?.target?.value);
if (allowClose) {
this.context.onAction('closePopup');
}
}
sendContainerHeightChangeEvent() {
if (this.node && hasMode(['preview'])) {
if (this.node?.clientHeight !== this.lastContainerHeight) {
this.lastContainerHeight = this.node?.clientHeight;
window.document.body.style.overflow = 'hidden';
window.document.body.style['scrollbar-width'] = 'none';
window.parent.postMessage({
type: 'portal-preview-updated',
payload: {
height: this.lastContainerHeight
}
}, '*');
}
}
}
componentDidUpdate() {
this.sendContainerHeightChangeEvent();
}
componentWillUnmount() {
if (this.node) {
this.node.ownerDocument.removeEventListener('keyup', this.keyUphandler);
}
}
handlePopupClose(e) {
if (hasMode(['preview'])) {
return;
}
if (e.target === e.currentTarget) {
this.context.onAction('closePopup');
}
}
renderActivePage() {
const {page} = this.context;
getActivePage({page});
const PageComponent = Pages[page];
return (
<PageComponent />
);
}
renderPopupNotification() {
const {popupNotification} = this.context;
if (!popupNotification || !popupNotification.type) {
return null;
}
return (
<PopupNotification />
);
}
sendPortalPreviewReadyEvent() {
if (window.self !== window.parent) {
window.parent.postMessage({
type: 'portal-preview-ready',
payload: {}
}, '*');
}
}
render() {
const {page, pageQuery, site, customSiteUrl} = this.context;
const products = getSiteProducts({site});
const noOfProducts = products.length;
getActivePage({page});
const Styles = StylesWrapper({page});
const pageStyle = {
...Styles.page[page]
};
let popupWidthStyle = '';
let popupSize = 'regular';
let cookieBannerText = '';
let pageClass = page;
switch (page) {
case 'signup':
cookieBannerText = 'Cookies must be enabled in your browser to sign up.';
break;
case 'signin':
cookieBannerText = 'Cookies must be enabled in your browser to sign in.';
break;
case 'accountHome':
pageClass = 'account-home';
break;
case 'accountProfile':
pageClass = 'account-profile';
break;
case 'accountPlan':
pageClass = 'account-plan';
break;
default:
cookieBannerText = 'Cookies must be enabled in your browser.';
pageClass = page;
break;
}
if (noOfProducts > 1 && !isInviteOnlySite({site, pageQuery})) {
if (page === 'signup') {
pageClass += ' full-size';
popupSize = 'full';
}
}
const freeProduct = hasFreeProductPrice({site});
if ((freeProduct && noOfProducts > 2) || (!freeProduct && noOfProducts > 1)) {
if (page === 'accountPlan') {
pageClass += ' full-size';
popupSize = 'full';
}
}
if (page === 'emailSuppressionFAQ' || page === 'emailReceivingFAQ') {
pageClass += ' large-size';
}
let className = 'gh-portal-popup-container';
if (hasMode(['preview'])) {
pageClass += ' preview';
}
if (hasMode(['preview'], {customSiteUrl}) && !site.disableBackground) {
className += ' preview';
}
if (hasMode(['dev'])) {
className += ' dev';
}
const containerClassName = `${className} ${popupWidthStyle} ${pageClass}`;
this.sendPortalPreviewReadyEvent();
return (
<>
<div className={'gh-portal-popup-wrapper ' + pageClass} onClick={e => this.handlePopupClose(e)}>
<div className={containerClassName} style={pageStyle} ref={node => (this.node = node)} tabIndex={-1}>
<CookieDisabledBanner message={cookieBannerText} />
{this.renderPopupNotification()}
{this.renderActivePage()}
{(popupSize === 'full' ?
<div className={'gh-portal-powered inside ' + (hasMode(['preview']) ? 'hidden ' : '') + pageClass}>
<PoweredBy />
</div>
: '')}
</div>
</div>
<div className={'gh-portal-powered outside ' + (hasMode(['preview']) ? 'hidden ' : '') + pageClass}>
<PoweredBy />
</div>
</>
);
}
}
export default class PopupModal extends React.Component {
static contextType = AppContext;
constructor(props) {
super(props);
this.state = {
height: null
};
}
renderCurrentPage(page) {
const PageComponent = Pages[page];
return (
<PageComponent />
);
}
onHeightChange(height) {
this.setState({height});
}
handlePopupClose(e) {
e.preventDefault();
if (e.target === e.currentTarget) {
this.context.onAction('closePopup');
}
}
renderFrameStyles() {
const {site} = this.context;
const FrameStyle = getFrameStyles({site});
const styles = `
:root {
--brandcolor: ${this.context.brandColor}
}
` + FrameStyle;
return (
<>
<style dangerouslySetInnerHTML={{__html: styles}} />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
</>
);
}
renderFrameContainer() {
const {member, site, customSiteUrl} = this.context;
const Styles = StylesWrapper({member});
const isMobile = window.innerWidth < 480;
const frameStyle = {
...Styles.frame.common
};
let className = 'gh-portal-popup-background';
if (hasMode(['preview'])) {
Styles.modalContainer.zIndex = '3999997';
}
if (hasMode(['preview'], {customSiteUrl}) && !site.disableBackground) {
className += ' preview';
}
if (hasMode(['dev'])) {
className += ' dev';
}
return (
<div style={Styles.modalContainer}>
<Frame style={frameStyle} title="portal-popup" head={this.renderFrameStyles()} dataTestId='portal-popup-frame'>
<div className={className} onClick = {e => this.handlePopupClose(e)}></div>
<PopupContent isMobile={isMobile} />
</Frame>
</div>
);
}
render() {
const {showPopup} = this.context;
if (showPopup) {
return this.renderFrameContainer();
}
return null;
}
}