From bb25cd2ad6796058e558d8e364d06677c06e1e6a Mon Sep 17 00:00:00 2001 From: Abdellah Hariti Date: Fri, 8 Nov 2024 00:04:22 +0100 Subject: [PATCH 01/10] 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 (
- +
+ +
From c3f0b01161a0b5691c404ae99d07f3ce48bb419e Mon Sep 17 00:00:00 2001 From: Abdellah Hariti Date: Fri, 8 Nov 2024 00:14:24 +0100 Subject: [PATCH 02/10] add support for expiration date --- src/components/banner/index.tsx | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/components/banner/index.tsx b/src/components/banner/index.tsx index d466c6a663960..5a800d44a0576 100644 --- a/src/components/banner/index.tsx +++ b/src/components/banner/index.tsx @@ -5,23 +5,22 @@ import {useEffect, useState} from 'react'; import styles from './banner.module.scss'; type BannerType = { + /** This is an array of strings or RegExps to feed into new RegExp() */ appearsOn: (string | RegExp)[]; + /** String that is the label for the call to action button */ linkText: string; + /** String that is the destination url of the call to action button */ linkURL: string; + /** String for the text of the banner */ text: string; + /** Optional ISO Date string that will hide the banner after this date without the need for a rebuild */ + expiresOn?: 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 Object Properties: -// -// - 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: // // Examples: @@ -68,6 +67,14 @@ const BANNERS: BannerType[] = [ linkURL: 'https://sentry.io/thought-leadership', linkText: 'Get webinarly', }, + // example with an expiration date + { + appearsOn: ['^/platforms/javascript/guides/aws-lambda/'], + text: "This banner should appear on the AWS Lambda guide, but won't because it's expired", + linkURL: 'https://sentry.io/thought-leadership', + linkText: 'Get webinarly', + expiresOn: '2024-01-01T00:00:00Z', + }, // generic javascript example { // we can constrain it to the javascript platform page only @@ -120,8 +127,12 @@ export function Banner() { ); }); - // Bail if no banner matches this page - if (!matchingBanner) { + // Bail if no banner matches this page or if the banner has expired + if ( + !matchingBanner || + (matchingBanner.expiresOn && + new Date() > new Date(matchingBanner.expiresOn ?? null)) + ) { return; } From 5278364e2a9db33414ea25f35e012923fc0531eb Mon Sep 17 00:00:00 2001 From: Abdellah Hariti Date: Fri, 8 Nov 2024 10:31:58 +0100 Subject: [PATCH 03/10] fix banner link text wrapping --- src/components/banner/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/banner/index.tsx b/src/components/banner/index.tsx index 5a800d44a0576..fd024b75cdc97 100644 --- a/src/components/banner/index.tsx +++ b/src/components/banner/index.tsx @@ -153,7 +153,9 @@ export function Banner() {
- ) : null; + ); } From 4968d56b06bb07ff822dbf26fc49fba6be047831 Mon Sep 17 00:00:00 2001 From: Abdellah Hariti Date: Fri, 8 Nov 2024 11:32:48 +0100 Subject: [PATCH 06/10] improve doc comments --- src/components/banner/index.tsx | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/components/banner/index.tsx b/src/components/banner/index.tsx index 8e96a8a6be05f..3516145a89149 100644 --- a/src/components/banner/index.tsx +++ b/src/components/banner/index.tsx @@ -7,11 +7,11 @@ import styles from './banner.module.scss'; type BannerType = { /** This is an array of strings or RegExps to feed into new RegExp() */ appearsOn: (string | RegExp)[]; - /** String that is the label for the call to action button */ + /** The label for the call to action button */ linkText: string; - /** String that is the destination url of the call to action button */ + /** The destination url of the call to action button */ linkURL: string; - /** String for the text of the banner */ + /** The main text of the banner */ text: string; /** Optional ISO Date string that will hide the banner after this date without the need for a rebuild */ expiresOn?: string; @@ -24,14 +24,9 @@ type BannerType = { // Example: // // 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'; +// appearsOn = []; // This is disabled +// appearsOn = ['^/$']; // This is enabled on the home page +// appearsOn = ['^/welcome/']; // This is enabled on the "/welcome" page // const BANNERS = [ // // This one will take precedence over the last banner in the array From 72b7b62890c0b9381cea489c30ff2166fe373ed2 Mon Sep 17 00:00:00 2001 From: Abdellah Hariti Date: Fri, 8 Nov 2024 12:04:40 +0100 Subject: [PATCH 07/10] add contributing page --- docs/contributing/pages/banners.mdx | 72 +++++++++++++++++++++++++++++ src/components/banner/index.tsx | 9 ++++ 2 files changed, 81 insertions(+) create mode 100644 docs/contributing/pages/banners.mdx diff --git a/docs/contributing/pages/banners.mdx b/docs/contributing/pages/banners.mdx new file mode 100644 index 0000000000000..7acb753b5a4e2 --- /dev/null +++ b/docs/contributing/pages/banners.mdx @@ -0,0 +1,72 @@ +--- +title: Banners +noindex: true +sidebar_order: 80 +--- + +You can add arbitrary banners to the top of a page by adding adding an entry to the `BANNERS` array on +the `banner/index.tsx` file. The `BANNERS` array is an array of objects with the following properties: + +```typescript {filename:banner/index.tsx} +type BannerType = { + /** This is an array of strings or RegExps to feed into new RegExp() */ + appearsOn: (string | RegExp)[]; + /** The label for the call to action button */ + linkText: string; + /** The destination url of the call to action button */ + linkURL: string; + /** The main text of the banner */ + text: string; + /** Optional ISO Date string that will hide the banner after this date without the need for a rebuild */ + expiresOn?: string; +}; +``` + + You can add as many banners as you like. If you need to disable all banners, simply delete them from the array. + + Each banner is evaluated in order, and the first one that matches will be shown. + +Examples: + +```typescript {filename:banner/index.tsx} +// ... +// appearsOn = []; // This is disabled +// appearsOn = ['^/$']; // This is enabled on the home page +// appearsOn = ['^/welcome/']; // This is enabled on the "/welcome" page +// ... + +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', + }, +]; + +``` + +Optionally, you can add an `expiresOn` property to a banner to hide it after a certain date without requiring a rebuild or manual removeal. +the ISO Date string should be in the format `YYYY-MM-DDTHH:MM:SSZ` to be parsed correctly and account for timezones. + +```typescript {filename:banner/index.tsx} +const BANNERS = [ + { + appearsOn: ['^/$'], + text: 'This home page banner will disappear after 2024-12-06', + linkURL: 'https://sentry.io/party', + linkText: 'RSVP', + expiresOn: '2024-12-06T00:00:00Z', + }, +]; +``` diff --git a/src/components/banner/index.tsx b/src/components/banner/index.tsx index 3516145a89149..2eef5a6e73148 100644 --- a/src/components/banner/index.tsx +++ b/src/components/banner/index.tsx @@ -79,6 +79,15 @@ const BANNERS: BannerType[] = [ linkURL: 'https://sentry.io/thought-leadership', linkText: 'Get webinarly', }, + + /// ⚠️ KEEP THIS LAST BANNER ACTIVE FOR DOCUMENTATION + // check it out on `/contributing/pages/banners/` + { + appearsOn: ['^/contributing/pages/banners/'], + text: 'Edit this banner on `/src/components/banner/index.tsx`', + linkURL: 'https://docs.sentry.io/contributing/pages/banners/', + linkText: 'CTA', + }, ]; const LOCALSTORAGE_NAMESPACE = 'banner-manifest'; From 351ea3cb6cf53e6c79c98785bfcc05445cda8d22 Mon Sep 17 00:00:00 2001 From: Abdellah Hariti Date: Fri, 8 Nov 2024 12:06:35 +0100 Subject: [PATCH 08/10] be more specific --- src/components/banner/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/banner/index.tsx b/src/components/banner/index.tsx index 2eef5a6e73148..314d5dbf71d73 100644 --- a/src/components/banner/index.tsx +++ b/src/components/banner/index.tsx @@ -18,11 +18,9 @@ type BannerType = { }; // 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 +// you need to disable all banners, simply delete them from the array. Each banner // is evaluated in order, and the first one that matches will be shown. // -// Example: -// // Examples: // appearsOn = []; // This is disabled // appearsOn = ['^/$']; // This is enabled on the home page From 54ef3ed0408c0616de799a64872c4008a1fb321b Mon Sep 17 00:00:00 2001 From: Abdellah Hariti Date: Fri, 8 Nov 2024 12:54:07 +0100 Subject: [PATCH 09/10] fix banner color in dark mode --- src/components/banner/banner.module.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/banner/banner.module.scss b/src/components/banner/banner.module.scss index ace76b52ffbf1..cec0bf8687cf6 100644 --- a/src/components/banner/banner.module.scss +++ b/src/components/banner/banner.module.scss @@ -1,5 +1,6 @@ .promo-banner { font-size: 15px; + color: #21201c; background: var(--accent-yellow); padding: 0.5rem 1rem; display: flex; From 9af7b91b9562f0119c893c101264f37fa9d9962b Mon Sep 17 00:00:00 2001 From: Abdellah Hariti Date: Fri, 8 Nov 2024 13:37:57 +0100 Subject: [PATCH 10/10] remove demo banners --- src/components/banner/index.tsx | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/src/components/banner/index.tsx b/src/components/banner/index.tsx index 314d5dbf71d73..178d259592efc 100644 --- a/src/components/banner/index.tsx +++ b/src/components/banner/index.tsx @@ -46,38 +46,6 @@ type BannerType = { // ]; 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', - }, - // example with an expiration date - { - appearsOn: ['^/platforms/javascript/guides/aws-lambda/'], - text: "This banner should appear on the AWS Lambda guide, but won't because it's expired", - linkURL: 'https://sentry.io/thought-leadership', - linkText: 'Get webinarly', - expiresOn: '2024-01-01T00:00:00Z', - }, - // 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', - }, - /// ⚠️ KEEP THIS LAST BANNER ACTIVE FOR DOCUMENTATION // check it out on `/contributing/pages/banners/` {