Use ProductDownloader component, remove duplicate code (#768)

* Use ProductDownloader component, remove duplicate code

* hard-code red-d1 for now

* bump ProductDownloader
This commit is contained in:
Zack Tanner authored and GitHub committed 2020-11-05 16:30:54 -08:00
1 parent 8813952439
commit b483dd9df0
13 files changed
+93 -906

No files matched your search

@@ -1,136 +0,0 @@
import Tabs from '@hashicorp/react-tabs'
import {
prettyOs,
prettyArch,
trackDownload,
} from 'components/downloader/utils/downloader'
import styles from './style.module.css'
export default function DownloadTabs({
defaultTabIdx,
tabData,
downloads,
version,
merchandisingSlot,
brand,
logo,
tutorialLink,
}) {
return (
<Tabs
key={defaultTabIdx}
centered
fullWidthBorder
theme={brand}
className={styles.tabs}
defaultTabIdx={defaultTabIdx}
items={tabData.map(({ os, packageManagers }) => ({
heading: prettyOs(os),
tabChildren: function TabChildren() {
return (
<div className={styles.cards}>
<Cards
key={os}
os={os}
downloads={downloads}
packageManagers={packageManagers}
version={version}
theme={brand}
logo={logo}
tutorialLink={tutorialLink}
/>
{merchandisingSlot}
</div>
)
},
}))}
/>
)
}
function Cards({
os,
downloads,
packageManagers,
version,
theme,
logo,
tutorialLink,
}) {
const arches = downloads[os]
const hasPackageManager = Boolean(packageManagers)
const hasMultiplePackageManagers = Array.isArray(packageManagers)
function handleArchClick(arch) {
return () => trackDownload('boundary', version, os, arch)
}
return (
<>
<div
className={
hasMultiplePackageManagers
? styles.downloadCardsSingle
: styles.downloadCards
}
>
{hasPackageManager && (
<div className={styles.packageManagers}>
<span className={styles.cardTitle}>Package Manager</span>
{Array.isArray(packageManagers) ? (
<Tabs
theme={theme}
items={packageManagers.map(({ label, commands }) => ({
heading: label,
tabChildren: function TabChildren() {
return (
<div className={styles.install}>
{commands.map((command) => (
<pre key={command}>{command}</pre>
))}
</div>
)
},
}))}
/>
) : (
<div className={styles.install}>
{packageManagers.commands.map((command) => (
<pre key={command}>{command}</pre>
))}
</div>
)}
{tutorialLink && (
<div>
<a href={tutorialLink.href}>{tutorialLink.label}</a>
</div>
)}
</div>
)}
<div className={hasPackageManager ? styles.card : styles.soloCard}>
<span className={styles.cardTitle}>Binary Download</span>
<div className={styles.logoDownloadWrapper}>
<div className={styles.logoWrapper}>
{logo}
<span className={styles.version}>{version}</span>
</div>
{Object.entries(arches).map(([arch, url]) => (
<a
href={url}
key={arch}
className={styles.downloadLink}
onClick={handleArchClick(arch)}
>
{prettyArch(arch)}
</a>
))}
</div>
<div className={styles.fastly}>
Bandwidth courtesy of
<img src={require('../logos/fastly.svg')} alt="Fastly" />
</div>
</div>
</div>
</>
)
}
@@ -1,156 +0,0 @@
.cards {
max-width: 880px;
margin: 0 auto;
}
.logoWrapper {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
}
.logoDownloadWrapper {
width: 100%;
}
.version {
margin-left: 8px;
}
.downloadCards {
justify-content: center;
margin: 54px 0 24px;
& a {
color: var(--gray-2);
}
@media (--medium-up) {
margin: 64px 0;
display: grid;
grid-template-columns: 1fr 1fr;
grid-column-gap: 48px;
justify-items: center;
}
& .downloadLink {
color: var(--brand);
margin-right: 16px;
}
}
.downloadCardsSingle {
composes: downloadCards;
& .card {
max-width: unset;
}
@media (--medium-up) {
grid-template-columns: 1fr;
& .card:first-child {
margin-bottom: 48px;
}
}
}
.card {
width: 100%;
min-height: 278px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
justify-content: space-between;
padding: 40px 32px;
color: var(--gray-2);
border: 1px solid var(--gray-6);
&:first-child {
border-bottom: none;
@media (--medium-up) {
border-bottom: 1px solid var(--gray-6);
}
}
}
.packageManagers {
composes: card;
& a {
color: var(--brand);
}
& :global {
& .g-tabs {
width: 100%;
@media (--medium-up) {
width: initial;
}
}
}
}
.soloCard {
composes: card;
grid-column: span 2;
&:first-child {
border-bottom: 1px solid var(--gray-6);
}
}
.install {
color: var(--white);
background-color: var(--black);
padding: 16px 24px;
border-radius: 3px;
text-align: left;
margin: 32px 0;
& pre {
margin: 0;
padding: 0;
background-color: inherit;
white-space: normal;
&::before {
color: #e93471;
content: '$';
margin-right: 8px;
}
@media (--medium-up) {
white-space: pre;
}
}
}
.cardTitle {
font-family: var(--font-display);
font-size: 12px;
line-height: 18px;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--gray-4);
font-weight: bold;
}
.fastly {
display: flex;
align-items: center;
flex-direction: column;
& svg path {
fill: var(--black);
}
& img {
margin-top: 12px;
width: 48px;
}
}
@@ -1,58 +0,0 @@
import { useState, useRef, useEffect } from 'react'
import Button from '@hashicorp/react-button'
import styles from './style.module.css'
export default function Dropdown({ options, onChange, title, brand }) {
const [open, setOpen] = useState(false)
const buttonRef = useRef()
useEffect(() => {
function handleClickOutside(event) {
if (buttonRef.current.contains(event.target)) return
setOpen(false)
}
document.addEventListener('mousedown', handleClickOutside)
return () => {
document.removeEventListener('mousedown', handleClickOutside)
}
}, [])
return (
<div className={styles.root} ref={buttonRef}>
<Button
className="trigger"
theme={{ brand }}
onClick={() => setOpen((open) => !open)}
aria-expanded={open}
aria-controls="menu-list"
aria-haspopup="true"
title={title}
icon={{
position: 'right',
svg:
'<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5 7.5L10 12.5L15 7.5" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
}}
/>
{open && (
<ul className={styles.menu} id="menu-list" role="menu">
{options.map((option) => (
<li
className={styles.option}
tabIndex={0}
role="menuitem"
key={option.value}
onClick={() => {
onChange(option.value)
setOpen(false)
}}
>
{option.label}
</li>
))}
</ul>
)}
</div>
)
}
@@ -1,28 +0,0 @@
.root {
display: inline-block;
position: relative;
}
.menu {
position: absolute;
list-style: none;
max-height: 250px;
overflow-y: auto;
top: 35px;
width: 100%;
padding: 0;
background-color: var(--white);
box-shadow: 0 2px 1px -1px rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14),
0 1px 3px 0 rgba(0, 0, 0, 0.12);
}
.option {
padding: 8px 16px;
cursor: pointer;
background-color: var(--brand);
color: var(--white);
&:hover {
background-color: var(--brand-d1);
}
}
@@ -1,7 +0,0 @@
<svg width="156" height="61" viewBox="0 0 156 61" xmlns="http://www.w3.org/2000/svg">
<path
d="M106.167 6.237v40.95h12.226V40.93h-4.04V0h-8.187v6.237zM0 40.93h4.156V21.174H0V15.74l4.156-.687v-5.5C4.156 2.891 5.597 0 14.042 0c1.824 0 3.986.271 5.879.614l-1.122 6.697c-1.283-.204-1.918-.24-2.728-.24-2.976 0-3.728.298-3.728 3.222v4.76h6.177v6.12h-6.177V40.93h4.113v6.255L0 47.187V40.93zm102.002-1.977c-1.28.272-2.4.239-3.211.259-3.368.083-3.077-1.03-3.077-4.226V21.174h6.41v-6.121h-6.41V0h-8.187v36.694c0 7.204 1.767 10.492 9.469 10.492 1.824 0 4.33-.472 6.223-.88l-1.217-7.353zm-24.695-17.79v-1.096c-2.477-.455-4.937-.461-6.27-.461-3.811 0-4.276 2.032-4.276 3.134 0 1.56.53 2.402 4.657 3.31 6.033 1.363 12.093 2.784 12.093 10.31 0 7.139-3.652 10.826-11.338 10.826-5.143 0-10.134-1.11-13.941-2.082v-6.151h6.2l-.004 1.092c2.668.519 5.465.467 6.926.467 4.064 0 4.721-2.198 4.721-3.367 0-1.622-1.165-2.4-4.975-3.178-7.177-1.234-12.873-3.699-12.873-11.033 0-6.943 4.616-9.666 12.302-9.666 5.207 0 9.166.812 12.976 1.785v6.11h-6.198zm-41.475 17.77v-.967h-.98v.965c-4.395-.257-7.91-3.81-8.139-8.237h.975v-.987h-.97c.26-4.397 3.761-7.915 8.133-8.171v.97h.981v-.973c4.31.232 7.782 3.633 8.157 7.928v.283h-.983v.987h.983v.27c-.373 4.298-3.846 7.701-8.157 7.933zm16.347.02l-.006-25.632h-8.186v2.404a16.673 16.673 0 00-5.567-2.119h.046v-2.844h1V8.671H31.22v2.091h1v2.845h.056c-7.83 1.45-13.764 8.348-13.764 16.646 0 9.35 7.534 16.93 16.828 16.93a16.674 16.674 0 008.672-2.419l1.474 2.423h8.648v-8.234h-1.957.001zm86.935-23.9H156v6.115h-4.037L141.607 46.8c-2.967 7.2-7.843 13.977-15.266 13.977-1.826 0-4.257-.203-5.942-.61l.74-7.476c1.082.203 2.499.337 3.242.337 3.443 0 7.326-2.146 8.541-5.883L122.43 21.168h-4.036v-6.115h16.895v6.115h-4.036l5.945 14.713 5.944-14.713h-4.027v-6.115zM39.639 26.569l-.623-.627-3.164 2.774a1.494 1.494 0 00-.51-.09c-.853 0-1.544.716-1.544 1.598 0 .883.69 1.598 1.545 1.598.853 0 1.546-.715 1.546-1.598 0-.168-.025-.33-.071-.481l2.82-3.174z"
fill="#000"
fill-rule="nonzero"
/>
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

@@ -1,165 +0,0 @@
import { useState, Fragment, useEffect } from 'react'
import Dropdown from 'components/downloader/dropdown'
import {
prettyArch,
prettyOs,
trackDownload,
sortPlatforms,
getVersionLabel,
sortAndFilterReleases,
} from 'components/downloader/utils/downloader'
import styles from './style.module.css'
export default function ReleaseInformation({
productId,
productName,
latestVersion,
packageManagers,
containers,
tutorials,
changelog,
brand,
}) {
const [selectedVersionId, setSelectedVersionId] = useState(latestVersion)
const [releases, setReleases] = useState([])
const { version, ...selectedVersion } =
releases.find((release) => release.version === selectedVersionId) || {}
useEffect(() => {
// lazy load all release versions to populate dropdown & releases section
fetch(`https://releases.hashicorp.com/${productId}/index.json`)
.then((res) => res.json())
.then((data) => {
const latestReleases = sortAndFilterReleases(Object.keys(data.versions))
const releases = latestReleases.map((releaseVersion) => ({
...sortPlatforms(data.versions[releaseVersion]),
version: releaseVersion,
}))
setReleases(releases)
})
}, [])
return (
<div className={styles.root}>
<div className="g-container">
<h2>Release Information</h2>
<div className={styles.grid}>
{releases.length > 0 && (
<>
<div className={styles.releases}>Releases:</div>
<div>
<Dropdown
title={`${productName} ${getVersionLabel(
version,
latestVersion
)}`}
brand={brand}
options={releases.map((releaseData) => ({
label: `${productName} ${getVersionLabel(
releaseData.version,
latestVersion
)}`,
value: releaseData.version,
}))}
onChange={(release) => setSelectedVersionId(release)}
/>
<a
href={
changelog ||
`https://github.com/hashicorp/${productId}/blob/v${version}/CHANGELOG.md`
}
className={styles.changelog}
>
Changelog
</a>
</div>
</>
)}
<div className={styles.latestDownloads}>Latest Downloads:</div>
<div>
Package downloads for {productName} {version}
<div className={styles.downloads}>
{Object.entries(selectedVersion).map(([os, release]) => (
<Fragment key={os}>
<div className={styles.os}>{prettyOs(os)}</div>
<div>
{Object.entries(release).map(([arch, file]) => (
<a
href={file}
key={arch}
onClick={() =>
trackDownload('boundary', version, os, arch)
}
>
{prettyArch(arch)}
</a>
))}
</div>
</Fragment>
))}
</div>
<p>
You can find the{' '}
<a
href={`https://releases.hashicorp.com/${productId}/${version}/${productId}_${version}_SHA256SUMS`}
>
SHA256 checksums for {productName} {version}
</a>{' '}
online and you can{' '}
<a
href={`https://releases.hashicorp.com/${productId}/${version}/${productId}_${version}_SHA256SUMS.sig`}
>
verify the checksums signature file
</a>{' '}
which has been signed using{' '}
<a href="https://hashicorp.com/security">
HashiCorp&apos;s GPG key.
</a>
</p>
</div>
{packageManagers?.length > 0 && (
<>
<div className={styles.heading}>Package Managers</div>
<div className={styles.links}>
{packageManagers.map((packageManager) => (
<div key={packageManager.label}>
Install with{' '}
<a href={packageManager.url}>{packageManager.label}</a>
</div>
))}
</div>
</>
)}
{containers?.length > 0 && (
<>
<div className={styles.heading}>Containers</div>
<div className={styles.links}>
{containers.map((container) => (
<div key={container.label}>
Run with <a href={container.url}>{container.label}</a>
</div>
))}
</div>
</>
)}
{containers?.length > 0 && (
<>
<div className={styles.heading}>Tutorials</div>
<div className={styles.links}>
{tutorials.map((tutorial) => (
<div key={tutorial.label}>
<a href={tutorial.url}>{tutorial.label}</a>
</div>
))}
</div>
</>
)}
</div>
</div>
</div>
)
}
@@ -1,101 +0,0 @@
.root {
background-color: var(--gray-7);
padding: 42px 0;
margin-top: 42px;
color: var(--gray-2);
& p {
color: var(--gray-2);
}
& a {
color: var(--gray-2);
}
& :global(.g-container) {
max-width: 825px;
}
& h2 {
text-align: center;
color: var(--black);
margin-bottom: 54px;
@media (--medium-up) {
margin-bottom: 64px;
}
}
}
.heading {
margin: 24px 0;
font-weight: bold;
color: var(--black);
@media (--medium-up) {
text-align: right;
margin: 0;
}
}
.changelog {
display: block;
margin: 24px 0 0;
color: var(--black);
@media (--medium-up) {
display: inline-block;
margin: 0 0 0 24px;
}
}
.grid {
margin: 48px auto;
@media (--medium-up) {
display: grid;
grid-column-gap: 32px;
grid-row-gap: 48px;
grid-template-columns: auto 1fr;
}
}
.latestDownloads {
composes: heading;
margin-top: 40px;
@media (--medium-up) {
margin-top: 0;
}
}
.downloads {
display: grid;
width: 100%;
grid-template-columns: auto 1fr;
margin: 17px 0 40px;
grid-column-gap: 28px;
grid-row-gap: 16px;
& a {
margin: 0 6px;
}
& .os {
font-weight: bold;
color: var(--black);
}
@media (--medium-up) {
margin: 20px 0 64px;
}
}
.releases {
align-self: center;
composes: heading;
}
.links > div:not(:first-child) {
margin: 15px 0;
}
@@ -1,139 +0,0 @@
import semverRSort from 'semver/functions/rsort'
import semverPrerelease from 'semver/functions/prerelease'
import semverValid from 'semver/functions/valid'
export function getVersionLabel(version, latestVersion) {
if (version === latestVersion) {
return `${version} (latest)`
}
return version
}
export function sortAndFilterReleases(releases) {
const validReleases = releases.filter(semverValid)
// descending sort on releases, while filtering out pre-releases
return semverRSort(validReleases).filter(
(version) => !semverPrerelease(version)
)
}
/** TODO: Below utils directly from Product-Downloader component.
* Should either be exported, or migrated back to web-components */
export function prettyArch(arch) {
switch (arch) {
case 'all':
return 'Universal (32 and 64-bit)'
case 'i686':
case 'i386':
case '686':
case '386':
return '32-bit'
case 'x86_64':
case '86_64':
case 'amd64':
return '64-bit'
default:
if (/-/.test(arch)) {
const parts = arch.split(/-(.+)/)
return `${prettyArch(parts[0])} (${parts[1]})`
} else {
const parts = arch.split('_')
if (parts.length > 0) {
return (
parts[parts.length - 1].charAt(0).toUpperCase() +
parts[parts.length - 1].slice(1)
)
}
}
}
}
export function detectOs(platform) {
for (let key in platformMap) {
if (platform.indexOf(key) !== -1) {
return platformMap[key]
}
}
return null
}
export function prettyOs(os) {
switch (os) {
case 'darwin':
return 'Mac OS X'
case 'freebsd':
return 'FreeBSD'
case 'openbsd':
return 'OpenBSD'
case 'netbsd':
return 'NetBSD'
case 'archlinux':
return 'Arch Linux'
case 'linux':
return 'Linux'
case 'windows':
return 'Windows'
default:
return os.charAt(0).toUpperCase() + os.slice(1)
}
}
const platformMap = {
Mac: 'darwin',
Win: 'windows',
Linux: 'linux',
}
export function sortPlatforms(releaseData) {
// first we pull the platforms out of the release data object and format it the way we want
const platforms = releaseData.builds.reduce((acc, build) => {
if (!acc[build.os]) acc[build.os] = {}
acc[build.os][build.arch] = build.url
return acc
}, {})
const platformKeys = Object.keys(platforms)
// create array of sorted values to base the order on
const sortedValues = Object.keys(platformMap)
.map((e) => platformMap[e])
// join the lists together to make sure
// all items are accounted for when sorting
.concat(platformKeys)
// filter our any duplicates and unneeded items
.filter((elem, pos, arr) => {
return arr.indexOf(elem) == pos && platformKeys.indexOf(elem) > -1
})
return (
platformKeys
// sort items based on platformMap order
.sort((a, b) => {
return sortedValues.indexOf(a) - sortedValues.indexOf(b)
})
// create new sorted object to return
.reduce((result, key) => {
result[key] = platforms[key]
return result
}, {})
)
}
export function trackDownload(product, version, _os, _arch) {
const os = prettyOs(_os)
const arch = prettyArch(_arch)
if (window.analytics) {
window.analytics.track('Download', {
category: 'Button',
label: `${product} | v${version} | ${os} | ${arch}`,
version,
os,
architecture: arch,
product,
})
}
}
+6 -4
View File
@@ -1728,11 +1728,13 @@
}
},
"@hashicorp/react-product-downloader": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@hashicorp/react-product-downloader/-/react-product-downloader-4.1.5.tgz",
"integrity": "sha512-ytmQi0ZfGmhSboRujDtXJGIhJE13ZSd0MsEpyAEoiWwZqv190qSXnYgl3HcT7iM4nENJEmPcHA4MnvCcKQaODA==",
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@hashicorp/react-product-downloader/-/react-product-downloader-6.1.1.tgz",
"integrity": "sha512-jvWQiMmrJkkAAhNa13fL9ogaMDD4WhLR319ozlKySVT7g8a03vwum0GG8FyIFf0LyKXBQJzSsuXpSX4vC6eKzQ==",
"requires": {
"@hashicorp/react-button": "^2.2.8"
"@hashicorp/react-button": "^2.2.8",
"@hashicorp/react-tabs": "^1.0.0",
"semver": "^7.3.2"
},
"dependencies": {
"@hashicorp/react-button": {
+1 -1
View File
@@ -16,7 +16,7 @@
"@hashicorp/react-head": "1.1.4",
"@hashicorp/react-hero": "^3.1.9",
"@hashicorp/react-image": "3.0.1",
"@hashicorp/react-product-downloader": "4.1.5",
"@hashicorp/react-product-downloader": "^6.1.1",
"@hashicorp/react-product-features-list": "^2.1.2",
"@hashicorp/react-section-header": "3.0.0",
"@hashicorp/react-subnav": "5.0.0",
+85 -55
View File
@@ -1,84 +1,114 @@
import { useMemo, useState, useEffect } from 'react'
import VERSION from 'data/version.js'
import Head from 'next/head'
import HashiHead from '@hashicorp/react-head'
import { productName, productSlug } from 'data/metadata'
import { packageManagersByOs, getStartedLinks } from 'data/downloads'
import ReleaseInformation from 'components/downloader/release-information'
import { sortPlatforms, detectOs } from 'components/downloader/utils/downloader'
import DownloadCards from 'components/downloader/cards'
import ProductDownloader from '@hashicorp/react-product-downloader'
import styles from './style.module.css'
export default function DownloadsPage({ releaseData, previousVersions }) {
const sortedDownloads = useMemo(() => sortPlatforms(releaseData), [
releaseData,
])
const osKeys = Object.keys(sortedDownloads)
const [osIndex, setSelectedOsIndex] = useState()
const tabData = Object.keys(sortedDownloads).map((osKey) => ({
os: osKey,
packageManagers: packageManagersByOs[osKey] || null,
}))
useEffect(() => {
// if we're on the client side, detect the default platform only on initial render
const index = osKeys.indexOf(detectOs(window.navigator.platform))
setSelectedOsIndex(index)
}, [])
export default function DownloadsPage({ releases }) {
return (
<div className={styles.root}>
<h1>Download {productName}</h1>
<HashiHead is={Head} title={`Downloads | ${productName} by HashiCorp`} />
<DownloadCards
brand="red"
defaultTabIdx={osIndex}
tabData={tabData}
downloads={sortedDownloads}
version={VERSION}
<ProductDownloader
releases={releases}
packageManagers={[
{
label: 'Homebrew',
commands: [
'brew tap hashicorp/tap',
'brew install hashicorp/tap/boundary',
],
os: 'darwin',
},
{
label: 'Ubuntu/Debian',
commands: [
'curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -',
'sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"',
'sudo apt-get update && sudo apt-get install boundary',
],
os: 'linux',
},
{
label: 'CentOS/RHEL',
commands: [
'sudo yum install -y yum-utils',
'sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo',
'sudo yum -y install boundary',
],
os: 'linux',
},
{
label: 'Fedora',
commands: [
'sudo dnf install -y dnf-plugins-core',
'sudo dnf config-manager --add-repo https://rpm.releases.hashicorp.com/fedora/hashicorp.repo',
'sudo dnf -y install boundary',
],
os: 'linux',
},
{
label: 'Amazon Linux',
commands: [
'sudo yum install -y yum-utils',
'sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo',
'sudo yum -y install boundary',
],
os: 'linux',
},
]}
productName={productName}
productId={productSlug}
latestVersion={VERSION}
getStartedLinks={[
{
label: 'Install Boundary',
href:
'https://learn.hashicorp.com/tutorials/boundary/getting-started-install',
},
{
label: 'Introduction to Boundary',
href:
'https://learn.hashicorp.com/tutorials/boundary/getting-started-intro',
},
{
label: 'Start a Development Environment',
href:
'https://learn.hashicorp.com/tutorials/boundary/getting-started-dev',
},
]}
logo={
<img
className={styles.logo}
alt="Boundary"
src={require('./img/boundary-logo.svg')}
/>
}
brand="red"
tutorialLink={{
label: 'View Tutorial on HashiCorp Learn',
href:
'https://learn.hashicorp.com/tutorials/boundary/getting-started-install',
}}
/>
<div className="g-container">
<div className={styles.gettingStarted}>
<h2>Getting Started</h2>
<div className={styles.links}>
{getStartedLinks.map((link) => (
<a href={link.href} key={link.href}>
{link.label}
</a>
))}
</div>
</div>
</div>
<ReleaseInformation
brand="red"
productId="boundary"
productName={productName}
releases={previousVersions}
latestVersion={releaseData.version}
/>
</div>
)
}
export async function getStaticProps() {
return fetch(`https://releases.hashicorp.com/boundary/${VERSION}/index.json`)
.then((r) => r.json())
.then((releaseData) => ({ props: { releaseData } }))
return fetch(`https://releases.hashicorp.com/boundary/index.json`, {
headers: {
'Cache-Control': 'no-cache',
},
})
.then((res) => res.json())
.then((result) => {
return {
props: {
releases: result,
},
}
})
.catch(() => {
throw new Error(
`--------------------------------------------------------
+1 -55
View File
@@ -1,61 +1,7 @@
.root {
margin-top: 72px;
& a {
text-decoration: underline;
}
& h1 {
text-align: center;
}
--brand: var(--red);
--brand-d1: #c03e43;
--red-d1: #c03e43;
}
.logo {
max-width: 140px;
}
.hosting {
border: 1px solid var(--gray-6);
padding: 16px;
text-align: center;
color: var(--gray-2);
}
.gettingStarted {
max-width: 680px;
margin: 40px auto 64px;
text-align: center;
& p {
color: var(--gray-3);
}
& a {
color: var(--black);
}
& h2 {
text-align: center;
margin: 16px 0;
}
@media (--medium-up) {
margin: 88px auto 128px;
}
}
.links {
margin-top: 32px;
& a {
text-align: center;
display: block;
margin: 8px 0;
}
}
.signUpButton {
margin-left: 26px;
}
-1
View File
@@ -11,7 +11,6 @@
@import '~@hashicorp/react-consent-manager/dist/style.css';
@import '~@hashicorp/react-toggle/dist/style.css';
@import '~@hashicorp/react-section-header/style.css';
@import '~@hashicorp/react-product-downloader/dist/style.css';
@import '~@hashicorp/react-vertical-text-block-list/style.css';
@import '~@hashicorp/react-docs-sidenav/style.css';
@import '~@hashicorp/react-content/style.css';