diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 3ad69b9d53..ec7f62f6cc 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -177,6 +177,9 @@ jobs: core.setFailed('e2e tests did not succeed') preview: name: Preview + environment: + name: ${{ (github.ref_name == 'main') && 'staging' || format('preview-{0}', github.ref_name) }} + url: ${{ (github.ref_name == 'main') && 'https://staging.web3.storage/' || steps.cloudflare_url.outputs.stdout }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 diff --git a/package-lock.json b/package-lock.json index 2b07b21f47..d4b5bdfc97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23830,7 +23830,7 @@ }, "packages/api": { "name": "@web3-storage/api", - "version": "7.18.1", + "version": "7.20.0", "license": "(Apache-2.0 OR MIT)", "dependencies": { "@aws-sdk/client-s3": "^3.53.1", @@ -26650,7 +26650,7 @@ }, "packages/website": { "name": "@web3-storage/website", - "version": "2.36.1", + "version": "2.36.3", "dependencies": { "@docsearch/react": "^3.0.0", "@fortawesome/free-brands-svg-icons": "^6.1.2", diff --git a/packages/website/components/messagebanner/messagebanner.scss b/packages/website/components/messagebanner/messagebanner.scss index 487642bbe1..43341872c0 100644 --- a/packages/website/components/messagebanner/messagebanner.scss +++ b/packages/website/components/messagebanner/messagebanner.scss @@ -75,6 +75,10 @@ } } +.message-banner-underline-links *:link { + text-decoration: underline; +} + .message-banner-close-button { position: absolute; display: flex; diff --git a/packages/website/components/page-banner/page-banner-portal.js b/packages/website/components/page-banner/page-banner-portal.js new file mode 100644 index 0000000000..d94e6aa1de --- /dev/null +++ b/packages/website/components/page-banner/page-banner-portal.js @@ -0,0 +1,59 @@ +/** + * @fileoverview component that contains a 'page banner', i.e. a banner across the whole + * web page that shows a prominent message to the end-user. + */ + +import clsx from 'clsx'; +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; + +export const defaultPortalElementId = 'page-banner-portal'; +export const defaultContentsClassName = 'page-banner-contents'; +export const defaultPortalQuerySelector = `#${defaultPortalElementId} .${defaultContentsClassName}`; + +/** + * wrap children in stiles like a .message-banner (see ../messagebanner/messagebanner.scss) + */ +export function MessageBannerStyled({ children }) { + return ( +
+
+
+ {children} +
+
+
+ ); +} + +/** + * component for an element across the top of page that can host banner messages on a per-page basis. + * Other components will use ReactDOM.createPortal() to render into this even though it isn't a child of + * that component (since it needs to be atop the page). + */ +export const PageBannerPortal = ({ id, contentsRef, contentsClassName = defaultContentsClassName }) => { + return ( +
+
+
+ ); +}; + +/** + * React Context used for passing around the HTMLElement for the PageBannerPortal + * so that the PageBanner component can pass it to React.createPortal. + */ +export const PageBannerPortalContainerContext = React.createContext(undefined); + +/** + * render children into a PageBannerPortal at the top of the page + */ +export const PageBanner = ({ children }) => { + const container = React.useContext(PageBannerPortalContainerContext); + const bannerChild = ( +
+ {children} +
+ ); + return <>{container && children && ReactDOM.createPortal(bannerChild, container)}; +}; diff --git a/packages/website/components/w3up-launch.js b/packages/website/components/w3up-launch.js new file mode 100644 index 0000000000..d07d21c8b3 --- /dev/null +++ b/packages/website/components/w3up-launch.js @@ -0,0 +1,59 @@ +import * as React from 'react'; + +const sunsetStartEnv = process.env.NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START ?? '2023-01-09T00:00:00Z'; +const sunsetStartDate = new Date(Date.parse(sunsetStartEnv)); + +/** + * If this isn't set, no announcements will appear + */ +const sunsetAnnouncementStartEnv = process.env.NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_ANNOUNCEMENT_START; + +/** + * after this datetime, show announcements that web3.storage is sunset + * and end-users should switch to w3up/console.web3.storage + */ +const sunsetAnnouncementStartDate = sunsetAnnouncementStartEnv + ? new Date(Date.parse(sunsetAnnouncementStartEnv)) + : undefined; + +/** + * Return whether sunset announcements related to w3up-launch should be shown. + * An announcement date must be explicitly configured via env var, and now must be after that date. + * @param {Date} at - time at which to return whether to show the announcement + * @param {Date|undefined} [announcementStartDate] - when to begin showing announcements. + * If not provided, always return false. + */ +export const shouldShowSunsetAnnouncement = (at = new Date(), announcementStartDate = sunsetAnnouncementStartDate) => { + return announcementStartDate && at > announcementStartDate; +}; + +export const w3upLaunchConfig = { + type: 'W3upLaunchConfig', + stages: { + sunsetAnnouncement: { + start: sunsetAnnouncementStartDate, + }, + sunset: { + start: sunsetStartDate, + }, + }, + shouldShowSunsetAnnouncement: shouldShowSunsetAnnouncement(), +}; + +/** + * copy for banner message across top of some web3.storage pages when w3up ships + */ +export const W3upMigrationRecommendationCopy = () => { + const createNewAccountHref = 'https://console.web3.storage/?intent=create-account'; + const learnWhatsNewHref = 'https://console.web3.storage/?intent=learn-new-web3storage-experience'; + const sunsetDateFormatter = new Intl.DateTimeFormat(undefined, { dateStyle: 'long' }); + return ( + <> + This web3.storage product will sunset on {sunsetDateFormatter.format(sunsetStartDate)}. We recommend migrating + your usage of web3.storage to the new web3.storage. +
+ Click here to create a new account and  + here to read about what’s awesome about the new web3.storage experience. + + ); +}; diff --git a/packages/website/modules/docs-theme/index.js b/packages/website/modules/docs-theme/index.js index 9de049f56c..3e3ca087e4 100644 --- a/packages/website/modules/docs-theme/index.js +++ b/packages/website/modules/docs-theme/index.js @@ -1,6 +1,5 @@ import React, { useEffect } from 'react'; import { MDXProvider } from '@mdx-js/react'; - import Head from 'next/head'; import { useRouter } from 'next/router'; import hljs from 'highlight.js/lib/core'; @@ -9,10 +8,13 @@ import powershell from 'highlight.js/lib/languages/powershell'; import bash from 'highlight.js/lib/languages/bash'; import go from 'highlight.js/lib/languages/go'; import json from 'highlight.js/lib/languages/json'; + import Sidebar from './sidebar/sidebar'; import Feedback from './feedback/feedback'; import Toc from './toc/toc'; import DocsPagination from './docspagination/docspagination'; +import { W3upMigrationRecommendationCopy, shouldShowSunsetAnnouncement } from '../../components/w3up-launch.js'; +import * as PageBannerPortal from '../../components/page-banner/page-banner-portal.js'; hljs.registerLanguage('javascript', javascript); hljs.registerLanguage('powershell', powershell); @@ -27,7 +29,7 @@ export default function Docs(props) { useEffect(() => { const pres = document.querySelectorAll('pre'); - pres.forEach((pre) => { + pres.forEach(pre => { const code = pre.firstElementChild?.innerHTML; if (code) { const button = document.createElement('button'); @@ -39,7 +41,7 @@ export default function Docs(props) { button.classList.add('code-copy-button'); document.body.appendChild(textarea); - button.addEventListener('click', (event) => { + button.addEventListener('click', event => { if (!navigator.clipboard) { textarea.focus({ preventScroll: true }); textarea.select(); @@ -63,7 +65,6 @@ export default function Docs(props) { pre.appendChild(button); } - }); hljs.highlightAll(); @@ -91,6 +92,11 @@ export default function Docs(props) { return function Layout({ children }) { return ( <> + {shouldShowSunsetAnnouncement() && ( + + + + )} {sharedHead}
diff --git a/packages/website/pages/_app.js b/packages/website/pages/_app.js index 80da76b4d1..1fae4f0d74 100644 --- a/packages/website/pages/_app.js +++ b/packages/website/pages/_app.js @@ -1,12 +1,17 @@ import '../styles/global.scss'; import { useRouter } from 'next/router'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import clsx from 'clsx'; import Script from 'next/script'; import Metadata from 'components/general/metadata'; import RestrictedRoute from 'components/general/restrictedRoute'; import AppProviders from 'components/general/appProviders'; +import { + PageBannerPortal, + defaultPortalElementId, + PageBannerPortalContainerContext, +} from '../components/page-banner/page-banner-portal.js'; import MessageBanner from '../components/messagebanner/messagebanner.js'; import Navigation from '../components/navigation/navigation.js'; import Footer from '../components/footer/footer.js'; @@ -19,6 +24,7 @@ const App = ({ Component, pageProps }) => { const productRoutes = ['/login', '/account', '/account/payment', '/tokens', '/callback']; const productApp = productRoutes.includes(pathname); const pageClass = pathname.includes('docs') ? 'docs-site' : productApp ? 'product-app' : 'marketing-site'; + const [pageBannerPortalEl, setPageBannerPortalEl] = useState(); useEffect(() => { document.querySelector('body')?.classList.add(pageClass); @@ -41,13 +47,28 @@ const App = ({ Component, pageProps }) => { referrerPolicy="no-referrer-when-downgrade" /> -
- {productApp &&
} - - - -