From 58eb15e18d9359bcc965316f253a5b1e12f9227e Mon Sep 17 00:00:00 2001 From: Duncan Crawbuck Date: Wed, 22 Oct 2025 19:58:52 -0700 Subject: [PATCH 1/4] feat: add pylon chat widget --- .env.example | 2 + src/components/GlobalScripts.tsx | 65 ++++++++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index e8a655e8..4f53d311 100644 --- a/.env.example +++ b/.env.example @@ -4,3 +4,5 @@ NEXT_PUBLIC_MESH_SDK_KEY= NEXT_PUBLIC_UNIFY_SCRIPT_SRC= NEXT_PUBLIC_UNIFY_API_KEY= NEXT_PUBLIC_RB2B_KEY= +NEXT_PUBLIC_PYLON_APP_ID= +PYLON_IDENTITY_SECRET= \ No newline at end of file diff --git a/src/components/GlobalScripts.tsx b/src/components/GlobalScripts.tsx index f4bf79ab..76157d26 100644 --- a/src/components/GlobalScripts.tsx +++ b/src/components/GlobalScripts.tsx @@ -15,9 +15,60 @@ export function GlobalScripts({ location }: GlobalScriptsProps) { const unifyScriptSrc = process.env.NEXT_PUBLIC_UNIFY_SCRIPT_SRC; const unifyApiKey = process.env.NEXT_PUBLIC_UNIFY_API_KEY; const rb2bKey = process.env.NEXT_PUBLIC_RB2B_KEY; + const pylonAppId = process.env.NEXT_PUBLIC_PYLON_APP_ID; + + // Only show Pylon in development + const isDev = process.env.NEXTJS_ENV === 'development' || process.env.NODE_ENV === 'development'; + + // Separate script for Pylon in local development only + const pylonLocalDevScript = isDev ? ` + (function() { + var isLocalhost = typeof window !== 'undefined' && + (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'); + + if (!isLocalhost) return; + + var pylonAppId = ${toJsStringLiteral(pylonAppId)}; + if (!pylonAppId) return; + + // Load Pylon widget script + (function(){ + var e=window; + var t=document; + var n=function(){n.e(arguments)}; + n.q=[]; + n.e=function(e){n.q.push(e)}; + e.Pylon=n; + var r=function(){ + var e=t.createElement("script"); + e.setAttribute("type","text/javascript"); + e.setAttribute("async","true"); + e.setAttribute("src","https://widget.usepylon.com/widget/" + pylonAppId); + var n=t.getElementsByTagName("script")[0]; + n.parentNode.insertBefore(e,n); + }; + if(t.readyState==="complete"){r()} + else if(e.addEventListener){e.addEventListener("load",r,false)} + })(); + + // Configure Pylon with test user data for local dev + window.pylon = { + chat_settings: { + app_id: pylonAppId, + email: "dev@superwall.com", + name: "Local Dev User" + } + }; + })(); + ` : ''; const scriptContent = ` (async function () { + // Skip on localhost (handled by separate local dev script) + var isLocalhost = typeof window !== 'undefined' && + (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'); + if (isLocalhost) return; + try { var response = await fetch('/api/auth/session', { credentials: 'include' }); var isLoggedIn = true; @@ -109,6 +160,7 @@ export function GlobalScripts({ location }: GlobalScriptsProps) { })(); } + // Production Pylon integration disabled - only enabled in dev } catch (_err) { // On error, assume logged in (do nothing) } @@ -116,8 +168,15 @@ export function GlobalScripts({ location }: GlobalScriptsProps) { `; return ( - + <> + {isDev && ( + + )} + + ); } From 3f8468fa45b80c3a52e58719498302ab8f18b5ed Mon Sep 17 00:00:00 2001 From: Duncan Crawbuck Date: Wed, 22 Oct 2025 20:00:13 -0700 Subject: [PATCH 2/4] fix: pylon script can assume auth is only valid in dev --- src/components/GlobalScripts.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/components/GlobalScripts.tsx b/src/components/GlobalScripts.tsx index 76157d26..858dc6cd 100644 --- a/src/components/GlobalScripts.tsx +++ b/src/components/GlobalScripts.tsx @@ -64,10 +64,6 @@ export function GlobalScripts({ location }: GlobalScriptsProps) { const scriptContent = ` (async function () { - // Skip on localhost (handled by separate local dev script) - var isLocalhost = typeof window !== 'undefined' && - (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'); - if (isLocalhost) return; try { var response = await fetch('/api/auth/session', { credentials: 'include' }); @@ -160,7 +156,6 @@ export function GlobalScripts({ location }: GlobalScriptsProps) { })(); } - // Production Pylon integration disabled - only enabled in dev } catch (_err) { // On error, assume logged in (do nothing) } From 6e913918cfeccebc9bc27b86be41dc58f50f3a28 Mon Sep 17 00:00:00 2001 From: Duncan Crawbuck Date: Wed, 29 Oct 2025 20:17:29 -0700 Subject: [PATCH 3/4] fix: script for pylon widget --- src/components/GlobalScripts.tsx | 106 ++++++++++++++----------------- 1 file changed, 49 insertions(+), 57 deletions(-) diff --git a/src/components/GlobalScripts.tsx b/src/components/GlobalScripts.tsx index 858dc6cd..2c315c8e 100644 --- a/src/components/GlobalScripts.tsx +++ b/src/components/GlobalScripts.tsx @@ -17,66 +17,65 @@ export function GlobalScripts({ location }: GlobalScriptsProps) { const rb2bKey = process.env.NEXT_PUBLIC_RB2B_KEY; const pylonAppId = process.env.NEXT_PUBLIC_PYLON_APP_ID; - // Only show Pylon in development - const isDev = process.env.NEXTJS_ENV === 'development' || process.env.NODE_ENV === 'development'; - - // Separate script for Pylon in local development only - const pylonLocalDevScript = isDev ? ` - (function() { - var isLocalhost = typeof window !== 'undefined' && - (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'); - - if (!isLocalhost) return; - - var pylonAppId = ${toJsStringLiteral(pylonAppId)}; - if (!pylonAppId) return; - - // Load Pylon widget script - (function(){ - var e=window; - var t=document; - var n=function(){n.e(arguments)}; - n.q=[]; - n.e=function(e){n.q.push(e)}; - e.Pylon=n; - var r=function(){ - var e=t.createElement("script"); - e.setAttribute("type","text/javascript"); - e.setAttribute("async","true"); - e.setAttribute("src","https://widget.usepylon.com/widget/" + pylonAppId); - var n=t.getElementsByTagName("script")[0]; - n.parentNode.insertBefore(e,n); - }; - if(t.readyState==="complete"){r()} - else if(e.addEventListener){e.addEventListener("load",r,false)} - })(); - - // Configure Pylon with test user data for local dev - window.pylon = { - chat_settings: { - app_id: pylonAppId, - email: "dev@superwall.com", - name: "Local Dev User" - } - }; - })(); - ` : ''; - const scriptContent = ` (async function () { try { var response = await fetch('/api/auth/session', { credentials: 'include' }); var isLoggedIn = true; + var sessionData = null; if (response && response.ok) { try { var session = await response.json(); isLoggedIn = !!(session && session.isLoggedIn); + sessionData = session; } catch (_e) { - // ignore JSON parse errors; default remains logged in + console.error('Failed to authenticate user' + _e); + return; } } - if (!isLoggedIn) { + else { + console.error('Failed to authenticate user'); + return; + } + + if (isLoggedIn) { + // Load Pylon widget for logged-in users + var pylonAppId = ${toJsStringLiteral(pylonAppId)}; + if (pylonAppId && sessionData.userInfo.email) { + // Load Pylon widget script + (function(){ + var e=window; + var t=document; + var n=function(){n.e(arguments)}; + n.q=[]; + n.e=function(e){n.q.push(e)}; + e.Pylon=n; + var r=function(){ + var e=t.createElement("script"); + e.setAttribute("type","text/javascript"); + e.setAttribute("async","true"); + e.setAttribute("src","https://widget.usepylon.com/widget/" + pylonAppId); + var n=t.getElementsByTagName("script")[0]; + n.parentNode.insertBefore(e,n); + }; + if(t.readyState==="complete"){r()} + else if(e.addEventListener){e.addEventListener("load",r,false)} + })(); + + // Configure Pylon with user data from session + var userEmail = sessionData.userInfo.email; + var userName = sessionData.userInfo.name; + window.pylon = { + chat_settings: { + app_id: pylonAppId, + email: sessionData.userInfo.email, + name: sessionData.userInfo.name, + email_hash: sessionData.emailHash, + } + }; + } + } else { var meshSdkKey = ${toJsStringLiteral(meshSdkKey)}; if (meshSdkKey) { // Avina @@ -163,15 +162,8 @@ export function GlobalScripts({ location }: GlobalScriptsProps) { `; return ( - <> - {isDev && ( - - )} - - + ); } From a33e6b61548cb3a299b0d0fbb50beeebe9f77c73 Mon Sep 17 00:00:00 2001 From: Duncan Crawbuck Date: Wed, 29 Oct 2025 20:43:22 -0700 Subject: [PATCH 4/4] feat: support page based on log in state --- content/docs/support.mdx | 27 +++-- src/components/LoginStatusContext.tsx | 139 ++++++++++++++++++++++++++ src/mdx-components.tsx | 7 ++ 3 files changed, 166 insertions(+), 7 deletions(-) create mode 100644 src/components/LoginStatusContext.tsx diff --git a/content/docs/support.mdx b/content/docs/support.mdx index 3d1f2646..a84e21fa 100644 --- a/content/docs/support.mdx +++ b/content/docs/support.mdx @@ -5,20 +5,33 @@ description: "Need help with anything Superwall-related? We're here to help." ## Resources -We have lots of resources that can help you find the answer to your issue, try looking there first: +We have lots of resources that can help you find the answer to your issue: +- [Ask AI](/ai) for **instant answers** to your questions - Consult our Docs (you're here right now! try searching for your issue) - Search for solutions in our [Help Center](https://support.superwall.com/) - Check our [Status Page](https://status.superwall.com/) for any on-going issues -- [Ask AI](/ai) for instant answers to your questions ## Contact Support -If additional help is required, you can contact Support by logging into your dashboard at [superwall.com](https://superwall.com/login) and using the **💬 Chat button** at the bottom-right corner of the screen. - -**Please provide any relevant details to help us resolve your issue quickly:** -- A detailed description of the issue -- User IDs if the issue is affecting specific users +If additional help is required, you can create a new Support ticket by using the **💬 Support button** in the bottom-right corner of the screen. + + + + + **Please provide all relevant details to help us resolve your issue quickly:** + - A detailed description of the issue + - User IDs if the issue is affecting specific users + - Any and all relevant screenshots, links, logs, etc. + + + + + You will need to [log into your Superwall account](https://superwall.com/login) first + + + + ## Need help faster? Try using [Ask AI](/ai) for instant answers. \ No newline at end of file diff --git a/src/components/LoginStatusContext.tsx b/src/components/LoginStatusContext.tsx new file mode 100644 index 00000000..c7dfe98c --- /dev/null +++ b/src/components/LoginStatusContext.tsx @@ -0,0 +1,139 @@ +'use client'; + +import React, { createContext, useContext, useEffect, useState } from 'react'; +import { LoaderCircle } from 'lucide-react'; + +// Context for sharing login status across components +const LoginStatusContext = createContext<{ + isLoggedIn: boolean | null; + isLoading: boolean; +}>({ + isLoggedIn: null, + isLoading: true, +}); + +// Provider component that checks login status once +export function LoginStatusProvider({ children }: { children: React.ReactNode }) { + const [isLoggedIn, setIsLoggedIn] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + async function checkAuth() { + try { + const response = await fetch('/api/auth/session', { + credentials: 'include' + }); + + if (response && response.ok) { + try { + const session = await response.json(); + setIsLoggedIn(!!(session && session.isLoggedIn)); + } catch (_e) { + console.error('Failed to parse session data', _e); + setIsLoggedIn(false); + } + } else { + setIsLoggedIn(false); + } + } catch (_err) { + // On error, assume not logged in + setIsLoggedIn(false); + } finally { + setIsLoading(false); + } + } + + checkAuth(); + }, []); + + return ( + + {children} + + ); +} + +// Hook to use login status context +function useLoginStatus() { + return useContext(LoginStatusContext); +} + +// Loading component +function LoadingState() { + return ( +

+ + Loading... +

+ ); +} + +// Helper components for MDX children syntax - these handle their own conditional rendering +export function LoggedIn({ children }: { children: React.ReactNode }) { + const { isLoggedIn, isLoading } = useLoginStatus(); + + if (isLoading || !isLoggedIn) { + return null; + } + + return <>{children}; +} +LoggedIn.displayName = 'LoggedIn'; + +export function LoggedOut({ children }: { children: React.ReactNode }) { + const { isLoggedIn, isLoading } = useLoginStatus(); + + if (isLoading || isLoggedIn) { + return null; + } + + return <>{children}; +} +LoggedOut.displayName = 'LoggedOut'; + +// General-purpose component that conditionally renders content based on auth status +interface BasedOnAuthProps { + loggedIn?: React.ReactNode; + loggedOut?: React.ReactNode; + children?: React.ReactNode; // For MDX children syntax +} + +export function BasedOnAuth({ loggedIn, loggedOut, children }: BasedOnAuthProps) { + const { isLoading } = useLoginStatus(); + + // Show loading state while checking + if (isLoading) { + return ; + } + + // If children are provided, render them (LoggedIn/LoggedOut will handle their own conditional rendering) + if (children) { + return <>{children}; + } + + // Fall back to props-based approach + const { isLoggedIn } = useLoginStatus(); + return <>{isLoggedIn ? loggedIn : loggedOut}; +} + +// Component for logged-in users (for use with children syntax) +export function WhenLoggedIn({ children }: { children: React.ReactNode }) { + const { isLoggedIn, isLoading } = useLoginStatus(); + + if (isLoading || !isLoggedIn) { + return null; + } + + return <>{children}; +} + +// Component for logged-out users (for use with children syntax) +export function WhenLoggedOut({ children }: { children: React.ReactNode }) { + const { isLoggedIn, isLoading } = useLoginStatus(); + + if (isLoading || isLoggedIn) { + return null; + } + + return <>{children}; +} diff --git a/src/mdx-components.tsx b/src/mdx-components.tsx index 25064c15..b9c19b6c 100644 --- a/src/mdx-components.tsx +++ b/src/mdx-components.tsx @@ -10,6 +10,7 @@ import { Accordion, Accordions as AccordionGroup } from 'fumadocs-ui/components/ import { ImageZoom as Frame } from 'fumadocs-ui/components/image-zoom' import { SDKContent } from './components/SDKContent' import { GithubInfo as GithubInfoComponent } from 'fumadocs-ui/components/github-info'; +import { WhenLoggedIn, WhenLoggedOut, LoginStatusProvider, BasedOnAuth, LoggedIn, LoggedOut } from './components/LoginStatusContext'; // We'll add custom components here @@ -278,5 +279,11 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents { SDKContent, GithubInfo, ParamTable, + WhenLoggedIn, + WhenLoggedOut, + LoginStatusProvider, + BasedOnAuth, + LoggedIn, + LoggedOut, } as MDXComponents }