From bb25cd2ad6796058e558d8e364d06677c06e1e6a Mon Sep 17 00:00:00 2001 From: Abdellah Hariti Date: Fri, 8 Nov 2024 00:04:22 +0100 Subject: [PATCH] feat: add regex patterns and support for multiple banners --- src/components/banner/banner.module.scss | 18 +-- src/components/banner/index.tsx | 172 +++++++++++++++-------- src/components/docPage/index.tsx | 4 + src/components/home.tsx | 4 +- 4 files changed, 123 insertions(+), 75 deletions(-) diff --git a/src/components/banner/banner.module.scss b/src/components/banner/banner.module.scss index b5e5ed1d39914..f0889ddb64d7e 100644 --- a/src/components/banner/banner.module.scss +++ b/src/components/banner/banner.module.scss @@ -6,21 +6,11 @@ position: relative; width: 100%; z-index: 2; - margin-top: var(--header-height); animation: slide-down 0.08s ease-out; a { color: inherit; } - - &.banner-module { - border-radius: 5px; - margin-bottom: 1rem; - } - - &+ :global(.hero) { - margin-top: -60px; - } } @keyframes slide-down { @@ -40,13 +30,7 @@ align-items: center; text-align: left; - >img { - max-height: 3rem; - margin-right: 0.5rem; - flex-shrink: 0; - } - - >span a { + > span a { text-decoration: underline; margin-left: 0.5rem; } diff --git a/src/components/banner/index.tsx b/src/components/banner/index.tsx index fa492e6f11efb..d466c6a663960 100644 --- a/src/components/banner/index.tsx +++ b/src/components/banner/index.tsx @@ -1,30 +1,87 @@ 'use client'; import {useEffect, useState} from 'react'; -import Image from 'next/image'; import styles from './banner.module.scss'; +type BannerType = { + appearsOn: (string | RegExp)[]; + linkText: string; + linkURL: string; + text: string; +}; + +// BANNERS is an array of banner objects. You can add as many as you like. If +// you need to disable all banners, set BANNERS to an empty array. Each banner +// is evaluated in order, and the first one that matches will be shown. // -// BANNER CONFIGURATION -// This is a lazy way of doing things but will work until -// we put a more robust solution in place. +// Banner Object Properties: // -const SHOW_BANNER = false; -const BANNER_TEXT = - 'Behind the Code: A Conversation With Backend Experts featuring CEOs of Laravel, Prisma, and Supabase.'; -const BANNER_LINK_URL = - 'https://sentry.io/resources/behind-the-code-a-discussion-with-backend-experts/'; -const BANNER_LINK_TEXT = 'RSVP'; -const OPTIONAL_BANNER_IMAGE = null; - +// - appearsOn: An array of RegExps or strings to feed into new RegExp() +// - text: String for the text of the banner +// - linkURL: String that is the destination url of the call to action button +// - linkText: String that is the label for the call to action button +// +// Example: // -// BANNER CODE -// Don't edit unless you need to change how the banner works. +// Examples: +// const SHOW_BANNER_ON = []; // This is disabled +// const SHOW_BANNER_ON = ['^/$']; // This is enabled on the home page +// const SHOW_BANNER_ON = ['^/welcome/']; // This is enabled on the "/welcome" page +// const BANNER_TEXT = +// 'your message here'; +// const BANNER_LINK_URL = +// 'link here'; +// const BANNER_LINK_TEXT = 'your cta here'; +// const BANNERS = [ // +// This one will take precedence over the last banner in the array +// (which matches all /platforms pages), because it matches first. +// { +// appearsOn: ['^/platforms/javascript/guides/astro/'], +// text: 'This banner appears on the Astro guide', +// linkURL: 'https://sentry.io/thought-leadership', +// linkText: 'Get webinarly', +// }, +// +// // This one will match the /welcome page and all /for pages +// { +// appearsOn: ['^/$', '^/platforms/'], +// text: 'This banner appears on the home page and all /platforms pages', +// linkURL: 'https://sentry.io/thought-leadership', +// linkText: 'Get webinarly', +// }, +// ]; + +const BANNERS: BannerType[] = [ + { + // Match the homepage + appearsOn: ['^/$'], + text: 'This is a banner for the homepage', + linkURL: 'https://sentry.io/', + linkText: 'RSVP', + }, + // javascript -> Astro example + { + appearsOn: ['^/platforms/javascript/guides/astro/'], + text: 'This banner appears on the Astro guide', + linkURL: 'https://sentry.io/thought-leadership', + linkText: 'Get webinarly', + }, + // generic javascript example + { + // we can constrain it to the javascript platform page only + // by adding a more specific regex ie '^/platforms/javascript/$' + appearsOn: ['^/platforms/javascript/'], + text: 'This banner appears on the JavaScript platform page and all subpages', + linkURL: 'https://sentry.io/thought-leadership', + linkText: 'Get webinarly', + }, +]; const LOCALSTORAGE_NAMESPACE = 'banner-manifest'; +// https://stackoverflow.com/questions/6122571/simple-non-secure-hash-function-for-javascript const fastHash = (input: string) => { let hash = 0; if (input.length === 0) { @@ -52,53 +109,54 @@ const readOrResetLocalStorage = () => { } }; -export function Banner({isModule = false}) { - const [isVisible, setIsVisible] = useState(false); - const hash = fastHash(`${BANNER_TEXT}:${BANNER_LINK_URL}`).toString(); - - const enablebanner = () => { - setIsVisible(true); - }; +export function Banner() { + type BannerWithHash = BannerType & {hash: string}; + const [banner, setBanner] = useState(null); useEffect(() => { - const manifest = readOrResetLocalStorage(); - if (!manifest) { - enablebanner(); + const matchingBanner = BANNERS.find(b => { + return b.appearsOn.some(matcher => + new RegExp(matcher).test(window.location.pathname) + ); + }); + + // Bail if no banner matches this page + if (!matchingBanner) { return; } - if (manifest.indexOf(hash) === -1) { - enablebanner(); + const manifest = readOrResetLocalStorage(); + const hash = fastHash(matchingBanner.text + matchingBanner.linkURL).toString(); + + // Bail if this banner has already been seen + if (manifest && manifest.indexOf(hash) >= 0) { + return; } - }); - - return SHOW_BANNER - ? isVisible && ( -
-
- {OPTIONAL_BANNER_IMAGE ? : ''} - - {BANNER_TEXT} - {BANNER_LINK_TEXT} - -
- -
- ) - : null; + + // Enable the banner + setBanner({...matchingBanner, hash}); + }, []); + + return banner ? ( +
+
+ + {banner.text} + {banner.linkText} + +
+ +
+ ) : null; } diff --git a/src/components/docPage/index.tsx b/src/components/docPage/index.tsx index 99414018fa069..29878eeb7ea2e 100644 --- a/src/components/docPage/index.tsx +++ b/src/components/docPage/index.tsx @@ -9,6 +9,7 @@ import {getUnversionedPath} from 'sentry-docs/versioning'; import './type.scss'; +import {Banner} from '../banner'; import {Breadcrumbs} from '../breadcrumbs'; import {CodeContextProvider} from '../codeContext'; import {GitHubCTA} from '../githubCTA'; @@ -75,6 +76,9 @@ export function DocPage({ fullWidth ? 'max-w-none w-full' : 'w-[75ch] xl:max-w-[calc(100%-250px)]', ].join(' ')} > +
+ +
{leafNode && }
diff --git a/src/components/home.tsx b/src/components/home.tsx index bb5dec09b10cb..cf408cabd6608 100644 --- a/src/components/home.tsx +++ b/src/components/home.tsx @@ -33,7 +33,9 @@ export async function Home() { return (
- +
+ +