Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eleventy.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ module.exports = function(eleventyConfig) {
eleventyConfig.addPassthroughCopy("src/js/ai-expert-modal.js");
eleventyConfig.addPassthroughCopy("src/js/hm-promo-banner.js");
eleventyConfig.addPassthroughCopy("src/js/nav-tracking.js");
eleventyConfig.addPassthroughCopy("src/js/signup-popup.js");

// Watch content images for the image pipeline
eleventyConfig.addWatchTarget("src/**/*.{svg,webp,png,jpeg,gif}");
Expand Down
3 changes: 3 additions & 0 deletions nuxt/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,9 @@ export default defineNuxtConfig({
// Explicit nav-click tracking. Source is src/js/nav-tracking.js;
// prod:eleventy-nuxt copies the 11ty output into nuxt/public/.
{ src: '/js/nav-tracking.js', defer: true },
// Opens sign-up in a small popup window on desktop/tablet.
// Source is src/js/signup-popup.js; copied the same way as nav-tracking.js.
{ src: '/js/signup-popup.js', defer: true },
]
}
},
Expand Down
2 changes: 2 additions & 0 deletions src/_includes/layouts/base.njk
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ eleventyComputed:
<script type="module" src="/js/flowrenderer.min.js"></script>
<!-- Explicit nav-click tracking; keep in sync with the Nuxt copy -->
<script defer src="/js/nav-tracking.js"></script>
<!-- Opens sign-up in a small popup window on desktop/tablet -->
<script defer src="/js/signup-popup.js"></script>

{%- if not DEV_MODE -%}
{% include "analytics/head.html" %}
Expand Down
115 changes: 115 additions & 0 deletions src/js/signup-popup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Opens the sign-up page in a small popup window on desktop/tablet, centered
// over the visible page content, with a dark overlay behind it.
// On mobile, leaves the link's default navigation untouched.
document.addEventListener('DOMContentLoaded', () => {
// Tracks the one popup/overlay pair that can be open at a time, so
// there's a single thing to clean up instead of separate mutable
// variables that a second click could overwrite and orphan.
let session = null

function closeSession () {
if (!session) {
return
}
if (!session.popup.closed) {
session.popup.close()
}
clearInterval(session.pollClosed)
session.overlay.remove()
session = null
}

function openPopup (href) {
if (session) {
if (!session.popup.closed) {
// Already have one open - bring it forward instead of
// opening a second popup and orphaning this one's overlay.
session.popup.focus()
return
}
closeSession()
}

const width = 420
const height = Math.min(900, window.innerHeight * 0.75)

// outerWidth/outerHeight include browser chrome (toolbars, a
// vertical tab strip, etc). Subtracting innerWidth/innerHeight
// estimates that chrome so we can center over the visible page
// content instead of the full browser window.
const chromeWidth = window.outerWidth - window.innerWidth
const chromeHeight = window.outerHeight - window.innerHeight
const viewportLeft = window.screenX + chromeWidth
const viewportTop = window.screenY + chromeHeight

const left = viewportLeft + (window.innerWidth - width) / 2
const top = viewportTop + (window.innerHeight - height) / 2

const popup = window.open(
href,
'flowfuse-signup',
`width=${width},height=${height},left=${left},top=${top},menubar=no,toolbar=no,location=no,status=no`
)

if (!popup) {
return
}

const overlay = document.createElement('div')
overlay.setAttribute('id', 'signup-popup-overlay')
overlay.style.position = 'fixed'
overlay.style.inset = '0'
overlay.style.background = 'rgba(0, 0, 0, 0.45)'
overlay.style.zIndex = '9999'
overlay.addEventListener('click', closeSession)
document.body.appendChild(overlay)

const pollClosed = setInterval(() => {
if (popup.closed) {
closeSession()
}
}, 100)

session = { popup, overlay, pollClosed }
popup.focus()
}

// Delegated on `document` (rather than binding each matching link once)
// so this also catches anchors rendered after this script ran - e.g.
// Nuxt's client-side route navigation swapping in a new CtaSignUp link
// without a full page load.
document.addEventListener('click', (event) => {
const link = event.target.closest('a[href*="/account/create"]')
if (!link) {
return
}

const isDesktopOrTablet = window.matchMedia('(min-width: 768px)').matches
if (!isDesktopOrTablet) {
return
}

event.preventDefault()

// Explicit signal for the product to key its popup-specific
// layout off, instead of `window.opener` — that's also set for
// an ordinary ctrl/cmd-click "open in new tab", which isn't
// this popup at all.
const popupUrl = new URL(link.href)
popupUrl.searchParams.set('context', 'popup')

openPopup(popupUrl.href)
})

// Deliberate trade-off, not an oversight: closing on any window focus
// (e.g. switching tabs/apps and coming back) can cancel an in-progress,
// not-yet-submitted sign-up. That's judged preferable to the
// alternative - the popup falling behind this window with no auto-close,
// which leaves the overlay covering the page with no visible popup and
// no clear reason why, reading as the site being stuck rather than a
// sign-up that's still open one window over. Nothing of substance is
// lost by closing early here: this only ever covers the initial,
// unsubmitted form step. Added once (not per click) so listeners don't
// accumulate across retries.
window.addEventListener('focus', closeSession)
})