Add new landing hero and assets (#57450)
This commit is contained in:
BIN
assets/images/banner-images/hero-1.png
Normal file
BIN
assets/images/banner-images/hero-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 188 KiB |
BIN
assets/images/banner-images/hero-2.png
Normal file
BIN
assets/images/banner-images/hero-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 212 KiB |
BIN
assets/images/banner-images/hero-3.png
Normal file
BIN
assets/images/banner-images/hero-3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 167 KiB |
BIN
assets/images/banner-images/hero-4.png
Normal file
BIN
assets/images/banner-images/hero-4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 196 KiB |
BIN
assets/images/banner-images/hero-5.png
Normal file
BIN
assets/images/banner-images/hero-5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 207 KiB |
BIN
assets/images/banner-images/hero-6.png
Normal file
BIN
assets/images/banner-images/hero-6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 198 KiB |
@@ -202,6 +202,10 @@ export const schema = {
|
|||||||
product_video_transcript: {
|
product_video_transcript: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
|
// Hero image for landing pages
|
||||||
|
heroImage: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
interactive: {
|
interactive: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export type ProductLandingContextT = {
|
|||||||
introLinks: Record<string, string> | null
|
introLinks: Record<string, string> | null
|
||||||
productVideo: string
|
productVideo: string
|
||||||
productVideoTranscript: string
|
productVideoTranscript: string
|
||||||
|
heroImage?: string
|
||||||
featuredLinks: Record<string, Array<FeaturedLink>>
|
featuredLinks: Record<string, Array<FeaturedLink>>
|
||||||
productUserExamples: Array<{ username: string; description: string }>
|
productUserExamples: Array<{ username: string; description: string }>
|
||||||
productCommunityExamples: Array<{ repo: string; description: string }>
|
productCommunityExamples: Array<{ repo: string; description: string }>
|
||||||
@@ -113,6 +114,7 @@ export const getProductLandingContextFromRequest = async (
|
|||||||
...pick(page, ['introPlainText', 'beta_product', 'intro']),
|
...pick(page, ['introPlainText', 'beta_product', 'intro']),
|
||||||
productVideo,
|
productVideo,
|
||||||
productVideoTranscript: page.product_video_transcript || null,
|
productVideoTranscript: page.product_video_transcript || null,
|
||||||
|
heroImage: page.heroImage || null,
|
||||||
hasGuidesPage,
|
hasGuidesPage,
|
||||||
product: {
|
product: {
|
||||||
href: productTree.href,
|
href: productTree.href,
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
import { DefaultLayout } from '@/frame/components/DefaultLayout'
|
import { DefaultLayout } from '@/frame/components/DefaultLayout'
|
||||||
import { useBespokeContext } from '@/landings/context/BespokeContext'
|
import { useLandingContext } from '@/landings/context/LandingContext'
|
||||||
import { LandingHero } from '@/landings/components/shared/LandingHero'
|
import { LandingHero } from '@/landings/components/shared/LandingHero'
|
||||||
import { ArticleGrid } from '@/landings/components/shared/LandingArticleGridWithFilter'
|
import { ArticleGrid } from '@/landings/components/shared/LandingArticleGridWithFilter'
|
||||||
|
|
||||||
import type { ArticleCardItems } from '@/landings/types'
|
import type { ArticleCardItems } from '@/landings/types'
|
||||||
|
|
||||||
export const BespokeLanding = () => {
|
export const BespokeLanding = () => {
|
||||||
const { title, intro, tocItems } = useBespokeContext()
|
const { title, intro, heroImage, introLinks, tocItems } = useLandingContext()
|
||||||
|
|
||||||
const flatArticles: ArticleCardItems = useMemo(
|
const flatArticles: ArticleCardItems = useMemo(
|
||||||
() => tocItems.flatMap((item) => item.childTocItems || []),
|
() => tocItems.flatMap((item) => item.childTocItems || []),
|
||||||
@@ -18,7 +18,7 @@ export const BespokeLanding = () => {
|
|||||||
return (
|
return (
|
||||||
<DefaultLayout>
|
<DefaultLayout>
|
||||||
<div data-search="article-body">
|
<div data-search="article-body">
|
||||||
<LandingHero title={title} intro={intro} />
|
<LandingHero title={title} intro={intro} heroImage={heroImage} introLinks={introLinks} />
|
||||||
|
|
||||||
<div data-search="hide">
|
<div data-search="hide">
|
||||||
<ArticleGrid flatArticles={flatArticles} />
|
<ArticleGrid flatArticles={flatArticles} />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
import { DefaultLayout } from '@/frame/components/DefaultLayout'
|
import { DefaultLayout } from '@/frame/components/DefaultLayout'
|
||||||
import { useDiscoveryContext } from '@/landings/context/DiscoveryContext'
|
import { useLandingContext } from '@/landings/context/LandingContext'
|
||||||
import { LandingHero } from '@/landings/components/shared/LandingHero'
|
import { LandingHero } from '@/landings/components/shared/LandingHero'
|
||||||
import { ArticleGrid } from '@/landings/components/shared/LandingArticleGridWithFilter'
|
import { ArticleGrid } from '@/landings/components/shared/LandingArticleGridWithFilter'
|
||||||
import { LandingCarousel } from '@/landings/components/shared/LandingCarousel'
|
import { LandingCarousel } from '@/landings/components/shared/LandingCarousel'
|
||||||
@@ -9,7 +9,7 @@ import { LandingCarousel } from '@/landings/components/shared/LandingCarousel'
|
|||||||
import type { ArticleCardItems } from '@/landings/types'
|
import type { ArticleCardItems } from '@/landings/types'
|
||||||
|
|
||||||
export const DiscoveryLanding = () => {
|
export const DiscoveryLanding = () => {
|
||||||
const { title, intro, tocItems, recommended } = useDiscoveryContext()
|
const { title, intro, heroImage, introLinks, tocItems, recommended } = useLandingContext()
|
||||||
|
|
||||||
const flatArticles: ArticleCardItems = useMemo(
|
const flatArticles: ArticleCardItems = useMemo(
|
||||||
() => tocItems.flatMap((item) => item.childTocItems || []),
|
() => tocItems.flatMap((item) => item.childTocItems || []),
|
||||||
@@ -19,7 +19,7 @@ export const DiscoveryLanding = () => {
|
|||||||
return (
|
return (
|
||||||
<DefaultLayout>
|
<DefaultLayout>
|
||||||
<div>
|
<div>
|
||||||
<LandingHero title={title} intro={intro} />
|
<LandingHero title={title} intro={intro} heroImage={heroImage} introLinks={introLinks} />
|
||||||
<LandingCarousel flatArticles={flatArticles} recommended={recommended} />
|
<LandingCarousel flatArticles={flatArticles} recommended={recommended} />
|
||||||
<ArticleGrid flatArticles={flatArticles} />
|
<ArticleGrid flatArticles={flatArticles} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { DefaultLayout } from '@/frame/components/DefaultLayout'
|
import { DefaultLayout } from '@/frame/components/DefaultLayout'
|
||||||
import { useJourneyContext } from '@/landings/context/JourneyContext'
|
import { useLandingContext } from '@/landings/context/LandingContext'
|
||||||
import { LandingHero } from '@/landings/components/shared/LandingHero'
|
import { LandingHero } from '@/landings/components/shared/LandingHero'
|
||||||
|
|
||||||
export const JourneyLanding = () => {
|
export const JourneyLanding = () => {
|
||||||
const { title, intro } = useJourneyContext()
|
const { title, intro, heroImage, introLinks } = useLandingContext()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DefaultLayout>
|
<DefaultLayout>
|
||||||
<div>
|
<div>
|
||||||
<LandingHero title={title} intro={intro} />
|
<LandingHero title={title} intro={intro} heroImage={heroImage} introLinks={introLinks} />
|
||||||
|
|
||||||
<div>TODO</div>
|
<div>TODO</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
191
src/landings/components/shared/LandingHero.module.scss
Normal file
191
src/landings/components/shared/LandingHero.module.scss
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
.landingHero {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4rem 0;
|
||||||
|
background-color: var(--bgColor-muted, var(--color-canvas-subtle, #f6f8fa));
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 28rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroContent {
|
||||||
|
position: relative;
|
||||||
|
width: 50rem;
|
||||||
|
max-width: 50rem;
|
||||||
|
padding: 0 7rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroText {
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroHeading {
|
||||||
|
font-size: 4rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
color: var(--fgColor-default, var(--color-fg-default, #1f2328));
|
||||||
|
max-width: 48rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroDescription {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--fgColor-muted, var(--color-fg-muted, #656d76));
|
||||||
|
margin: 0 0 2rem 0;
|
||||||
|
max-width: 36rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroActions {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroAction {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
min-height: 2.75rem;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroPrimaryAction {
|
||||||
|
background-color: var(
|
||||||
|
--bgColor-success-emphasis,
|
||||||
|
var(--color-btn-primary-bg, #1f883d)
|
||||||
|
);
|
||||||
|
color: var(--fgColor-onEmphasis, var(--color-btn-primary-text, #ffffff));
|
||||||
|
border-color: var(
|
||||||
|
--borderColor-success-emphasis,
|
||||||
|
var(--color-btn-primary-border, #1f883d)
|
||||||
|
);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(
|
||||||
|
--bgColor-success-emphasis,
|
||||||
|
var(--color-btn-primary-hover-bg, #1a7f37)
|
||||||
|
);
|
||||||
|
border-color: var(
|
||||||
|
--borderColor-success-emphasis,
|
||||||
|
var(--color-btn-primary-hover-border, #1a7f37)
|
||||||
|
);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: 2px solid
|
||||||
|
var(
|
||||||
|
--borderColor-success-emphasis,
|
||||||
|
var(--color-btn-primary-focus, #1f883d)
|
||||||
|
);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroSecondaryAction {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--fgColor-default, var(--color-fg-default, #1f2328));
|
||||||
|
border-color: var(
|
||||||
|
--borderColor-default,
|
||||||
|
var(--color-border-default, #d1d9e0)
|
||||||
|
);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--bgColor-muted, var(--color-canvas-subtle, #f3f4f6));
|
||||||
|
border-color: var(
|
||||||
|
--borderColor-default,
|
||||||
|
var(--color-border-default, #d1d9e0)
|
||||||
|
);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: 2px solid
|
||||||
|
var(--borderColor-accent-emphasis, var(--color-accent-emphasis, #0969da));
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 865px) {
|
||||||
|
.landingHero {
|
||||||
|
height: 24rem;
|
||||||
|
background-image: none !important;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: var(--bgColor-muted, var(--color-canvas-subtle, #f6f8fa));
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroContent {
|
||||||
|
width: 100%;
|
||||||
|
order: 2;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroHeading {
|
||||||
|
font-size: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroDescription {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroActions {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroAction {
|
||||||
|
width: auto;
|
||||||
|
min-width: 12rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroText {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.landingHero {
|
||||||
|
height: 32rem;
|
||||||
|
background-image: none !important;
|
||||||
|
background-color: var(--bgColor-muted, var(--color-canvas-subtle, #f6f8fa));
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem 0 1rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroContent {
|
||||||
|
width: 100%;
|
||||||
|
order: 2;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroText {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroActions {
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,61 @@
|
|||||||
import { Lead } from '@/frame/components/ui/Lead/Lead'
|
import styles from './LandingHero.module.scss'
|
||||||
|
import { useTranslation } from '@/languages/components/useTranslation'
|
||||||
|
|
||||||
type LandingHeroProps = {
|
type LandingHeroProps = {
|
||||||
title: string
|
title: string
|
||||||
intro?: string
|
intro?: string
|
||||||
|
heroImage?: string
|
||||||
|
introLinks?: Record<string, string> | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LandingHero = ({ title, intro }: LandingHeroProps) => {
|
export const LandingHero = ({ title, intro, heroImage, introLinks }: LandingHeroProps) => {
|
||||||
|
const { t } = useTranslation(['product_landing'])
|
||||||
|
|
||||||
|
const linkEntries = introLinks ? Object.entries(introLinks) : []
|
||||||
|
const primaryAction = linkEntries[0]
|
||||||
|
const secondaryAction = linkEntries[1]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header>
|
<div
|
||||||
<div>
|
className={styles.landingHero}
|
||||||
<h1>TODO: Landing hero placeholder</h1>
|
style={
|
||||||
<h2>{title}</h2>
|
heroImage
|
||||||
{intro && <Lead>{intro}</Lead>}
|
? {
|
||||||
|
backgroundImage: `url("${heroImage}")`,
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className={styles.heroContent}>
|
||||||
|
<div className={styles.heroText}>
|
||||||
|
<h1 className={styles.heroHeading}>{title}</h1>
|
||||||
|
{intro && (
|
||||||
|
<div className={styles.heroDescription}>
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: intro }} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{(primaryAction || secondaryAction) && (
|
||||||
|
<div className={styles.heroActions}>
|
||||||
|
{primaryAction && (
|
||||||
|
<a
|
||||||
|
href={primaryAction[1]}
|
||||||
|
className={`${styles.heroAction} ${styles.heroPrimaryAction}`}
|
||||||
|
>
|
||||||
|
{t(primaryAction[0])}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{secondaryAction && (
|
||||||
|
<a
|
||||||
|
href={secondaryAction[1]}
|
||||||
|
className={`${styles.heroAction} ${styles.heroSecondaryAction}`}
|
||||||
|
>
|
||||||
|
{t(secondaryAction[0])}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
import { createContext, useContext } from 'react'
|
|
||||||
import { FeaturedLink, getFeaturedLinksFromReq } from '@/landings/components/ProductLandingContext'
|
|
||||||
import { mapRawTocItemToTocItem } from '@/landings/types'
|
|
||||||
import type { TocItem } from '@/landings/types'
|
|
||||||
import type { LearningTrack } from '@/types'
|
|
||||||
|
|
||||||
export type BespokeContextT = {
|
|
||||||
title: string
|
|
||||||
intro: string
|
|
||||||
productCallout: string
|
|
||||||
permissions: string
|
|
||||||
tocItems: Array<TocItem>
|
|
||||||
variant?: 'compact' | 'expanded'
|
|
||||||
featuredLinks: Record<string, Array<FeaturedLink>>
|
|
||||||
renderedPage: string
|
|
||||||
currentLearningTrack?: LearningTrack
|
|
||||||
currentLayout: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BespokeContext = createContext<BespokeContextT | null>(null)
|
|
||||||
|
|
||||||
export const useBespokeContext = (): BespokeContextT => {
|
|
||||||
const context = useContext(BespokeContext)
|
|
||||||
|
|
||||||
if (!context) {
|
|
||||||
throw new Error('"useBespokeContext" may only be used inside "BespokeContext.Provider"')
|
|
||||||
}
|
|
||||||
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getBespokeContextFromRequest = async (req: any): Promise<BespokeContextT> => {
|
|
||||||
const page = req.context.page
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: page.title,
|
|
||||||
productCallout: page.product || '',
|
|
||||||
permissions: page.permissions || '',
|
|
||||||
intro: page.intro,
|
|
||||||
tocItems: (req.context.genericTocFlat || req.context.genericTocNested || []).map(
|
|
||||||
mapRawTocItemToTocItem,
|
|
||||||
),
|
|
||||||
variant: req.context.genericTocFlat ? 'expanded' : 'compact',
|
|
||||||
featuredLinks: getFeaturedLinksFromReq(req),
|
|
||||||
renderedPage: req.context.renderedPage,
|
|
||||||
currentLearningTrack: req.context.currentLearningTrack,
|
|
||||||
currentLayout: req.context.currentLayoutName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import { createContext, useContext } from 'react'
|
|
||||||
import { FeaturedLink, getFeaturedLinksFromReq } from '@/landings/components/ProductLandingContext'
|
|
||||||
import { mapRawTocItemToTocItem } from '@/landings/types'
|
|
||||||
import type { TocItem } from '@/landings/types'
|
|
||||||
import type { LearningTrack } from '@/types'
|
|
||||||
|
|
||||||
export type DiscoveryContextT = {
|
|
||||||
title: string
|
|
||||||
intro: string
|
|
||||||
productCallout: string
|
|
||||||
permissions: string
|
|
||||||
tocItems: Array<TocItem>
|
|
||||||
variant?: 'compact' | 'expanded'
|
|
||||||
featuredLinks: Record<string, Array<FeaturedLink>>
|
|
||||||
renderedPage: string
|
|
||||||
currentLearningTrack?: LearningTrack
|
|
||||||
currentLayout: string
|
|
||||||
recommended?: string[] // Array of article paths
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DiscoveryContext = createContext<DiscoveryContextT | null>(null)
|
|
||||||
|
|
||||||
export const useDiscoveryContext = (): DiscoveryContextT => {
|
|
||||||
const context = useContext(DiscoveryContext)
|
|
||||||
|
|
||||||
if (!context) {
|
|
||||||
throw new Error('"useDiscoveryContext" may only be used inside "DiscoveryContext.Provider"')
|
|
||||||
}
|
|
||||||
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getDiscoveryContextFromRequest = async (req: any): Promise<DiscoveryContextT> => {
|
|
||||||
const page = req.context.page
|
|
||||||
|
|
||||||
// Support legacy `spotlight` property as `recommended` for pages like Copilot Cookbook
|
|
||||||
// However, `spotlight` will have lower priority than the `recommended` property
|
|
||||||
let recommended: string[] = []
|
|
||||||
if (page.recommended && page.recommended.length > 0) {
|
|
||||||
recommended = page.recommended
|
|
||||||
} else if (page.spotlight && page.spotlight.length > 0) {
|
|
||||||
// Remove the `image` property from spotlight items, since we don't use those for the carousel
|
|
||||||
recommended = page.spotlight.map((item: any) => item.article)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: page.title,
|
|
||||||
productCallout: page.product || '',
|
|
||||||
permissions: page.permissions || '',
|
|
||||||
intro: page.intro,
|
|
||||||
tocItems: (req.context.genericTocFlat || req.context.genericTocNested || []).map(
|
|
||||||
mapRawTocItemToTocItem,
|
|
||||||
),
|
|
||||||
variant: req.context.genericTocFlat ? 'expanded' : 'compact',
|
|
||||||
featuredLinks: getFeaturedLinksFromReq(req),
|
|
||||||
renderedPage: req.context.renderedPage,
|
|
||||||
currentLearningTrack: req.context.currentLearningTrack,
|
|
||||||
currentLayout: req.context.currentLayoutName,
|
|
||||||
recommended,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import { createContext, useContext } from 'react'
|
|
||||||
import { FeaturedLink, getFeaturedLinksFromReq } from '@/landings/components/ProductLandingContext'
|
|
||||||
import { mapRawTocItemToTocItem } from '@/landings/types'
|
|
||||||
import type { TocItem } from '@/landings/types'
|
|
||||||
import type { LearningTrack } from '@/types'
|
|
||||||
|
|
||||||
export type JourneyContextT = {
|
|
||||||
title: string
|
|
||||||
intro: string
|
|
||||||
productCallout: string
|
|
||||||
permissions: string
|
|
||||||
tocItems: Array<TocItem>
|
|
||||||
variant?: 'compact' | 'expanded'
|
|
||||||
featuredLinks: Record<string, Array<FeaturedLink>>
|
|
||||||
renderedPage: string
|
|
||||||
currentLearningTrack?: LearningTrack
|
|
||||||
currentLayout: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const JourneyContext = createContext<JourneyContextT | null>(null)
|
|
||||||
|
|
||||||
export const useJourneyContext = (): JourneyContextT => {
|
|
||||||
const context = useContext(JourneyContext)
|
|
||||||
|
|
||||||
if (!context) {
|
|
||||||
throw new Error('"useJourneyContext" may only be used inside "JourneyContext.Provider"')
|
|
||||||
}
|
|
||||||
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getJourneyContextFromRequest = async (req: any): Promise<JourneyContextT> => {
|
|
||||||
const page = req.context.page
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: page.title,
|
|
||||||
productCallout: page.product || '',
|
|
||||||
permissions: page.permissions || '',
|
|
||||||
intro: page.intro,
|
|
||||||
tocItems: (req.context.genericTocFlat || req.context.genericTocNested || []).map(
|
|
||||||
mapRawTocItemToTocItem,
|
|
||||||
),
|
|
||||||
variant: req.context.genericTocFlat ? 'expanded' : 'compact',
|
|
||||||
featuredLinks: getFeaturedLinksFromReq(req),
|
|
||||||
renderedPage: req.context.renderedPage,
|
|
||||||
currentLearningTrack: req.context.currentLearningTrack,
|
|
||||||
currentLayout: req.context.currentLayoutName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
76
src/landings/context/LandingContext.tsx
Normal file
76
src/landings/context/LandingContext.tsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { createContext, useContext } from 'react'
|
||||||
|
import { FeaturedLink, getFeaturedLinksFromReq } from '@/landings/components/ProductLandingContext'
|
||||||
|
import { mapRawTocItemToTocItem } from '@/landings/types'
|
||||||
|
import type { TocItem } from '@/landings/types'
|
||||||
|
import type { LearningTrack } from '@/types'
|
||||||
|
|
||||||
|
export type LandingType = 'bespoke' | 'discovery' | 'journey'
|
||||||
|
|
||||||
|
export type LandingContextT = {
|
||||||
|
landingType: LandingType
|
||||||
|
title: string
|
||||||
|
intro: string
|
||||||
|
productCallout: string
|
||||||
|
permissions: string
|
||||||
|
tocItems: Array<TocItem>
|
||||||
|
variant?: 'compact' | 'expanded'
|
||||||
|
featuredLinks: Record<string, Array<FeaturedLink>>
|
||||||
|
renderedPage: string
|
||||||
|
currentLearningTrack?: LearningTrack
|
||||||
|
currentLayout: string
|
||||||
|
heroImage?: string
|
||||||
|
// For discovery landing pages
|
||||||
|
recommended?: string[] // Array of article paths
|
||||||
|
// For discovery landing pages
|
||||||
|
introLinks?: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LandingContext = createContext<LandingContextT | null>(null)
|
||||||
|
|
||||||
|
export const useLandingContext = (): LandingContextT => {
|
||||||
|
const context = useContext(LandingContext)
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('"useLandingContext" may only be used inside "LandingContext.Provider"')
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getLandingContextFromRequest = async (
|
||||||
|
req: any,
|
||||||
|
landingType: LandingType,
|
||||||
|
): Promise<LandingContextT> => {
|
||||||
|
const page = req.context.page
|
||||||
|
|
||||||
|
let recommended: string[] = []
|
||||||
|
if (landingType === 'discovery') {
|
||||||
|
// Support legacy `spotlight` property as `recommended` for pages like Copilot Cookbook
|
||||||
|
// However, `spotlight` will have lower priority than the `recommended` property
|
||||||
|
if (page.recommended && page.recommended.length > 0) {
|
||||||
|
recommended = page.recommended
|
||||||
|
} else if (page.spotlight && page.spotlight.length > 0) {
|
||||||
|
// Remove the `image` property from spotlight items, since we don't use those for the carousel
|
||||||
|
recommended = page.spotlight.map((item: any) => item.article)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
landingType,
|
||||||
|
title: page.title,
|
||||||
|
productCallout: page.product || '',
|
||||||
|
permissions: page.permissions || '',
|
||||||
|
intro: page.intro,
|
||||||
|
tocItems: (req.context.genericTocFlat || req.context.genericTocNested || []).map(
|
||||||
|
mapRawTocItemToTocItem,
|
||||||
|
),
|
||||||
|
variant: req.context.genericTocFlat ? 'expanded' : 'compact',
|
||||||
|
featuredLinks: getFeaturedLinksFromReq(req),
|
||||||
|
renderedPage: req.context.renderedPage,
|
||||||
|
currentLearningTrack: req.context.currentLearningTrack,
|
||||||
|
currentLayout: req.context.currentLayoutName,
|
||||||
|
heroImage: page.heroImage || '/assets/images/banner-images/hero-1.png',
|
||||||
|
introLinks: page.introLinks || null,
|
||||||
|
recommended,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,22 +49,12 @@ import {
|
|||||||
} from '@/frame/components/context/CategoryLandingContext'
|
} from '@/frame/components/context/CategoryLandingContext'
|
||||||
import { BespokeLanding } from '@/landings/components/bespoke/BespokeLanding'
|
import { BespokeLanding } from '@/landings/components/bespoke/BespokeLanding'
|
||||||
import {
|
import {
|
||||||
BespokeContext,
|
LandingContext,
|
||||||
getBespokeContextFromRequest,
|
getLandingContextFromRequest,
|
||||||
BespokeContextT,
|
LandingContextT,
|
||||||
} from '@/landings/context/BespokeContext'
|
} from '@/landings/context/LandingContext'
|
||||||
import { DiscoveryLanding } from '@/landings/components/discovery/DiscoveryLanding'
|
import { DiscoveryLanding } from '@/landings/components/discovery/DiscoveryLanding'
|
||||||
import {
|
|
||||||
DiscoveryContext,
|
|
||||||
DiscoveryContextT,
|
|
||||||
getDiscoveryContextFromRequest,
|
|
||||||
} from '@/landings/context/DiscoveryContext'
|
|
||||||
import { JourneyLanding } from '@/landings/components/journey/JourneyLanding'
|
import { JourneyLanding } from '@/landings/components/journey/JourneyLanding'
|
||||||
import {
|
|
||||||
getJourneyContextFromRequest,
|
|
||||||
JourneyContext,
|
|
||||||
JourneyContextT,
|
|
||||||
} from '@/landings/context/JourneyContext'
|
|
||||||
|
|
||||||
function initiateArticleScripts() {
|
function initiateArticleScripts() {
|
||||||
copyCode()
|
copyCode()
|
||||||
@@ -79,9 +69,9 @@ type Props = {
|
|||||||
tocLandingContext?: TocLandingContextT
|
tocLandingContext?: TocLandingContextT
|
||||||
articleContext?: ArticleContextT
|
articleContext?: ArticleContextT
|
||||||
categoryLandingContext?: CategoryLandingContextT
|
categoryLandingContext?: CategoryLandingContextT
|
||||||
bespokeContext?: BespokeContextT
|
bespokeContext?: LandingContextT
|
||||||
discoveryContext?: DiscoveryContextT
|
discoveryContext?: LandingContextT
|
||||||
journeyContext?: JourneyContextT
|
journeyContext?: LandingContextT
|
||||||
}
|
}
|
||||||
const GlobalPage = ({
|
const GlobalPage = ({
|
||||||
mainContext,
|
mainContext,
|
||||||
@@ -108,21 +98,21 @@ const GlobalPage = ({
|
|||||||
let content
|
let content
|
||||||
if (bespokeContext) {
|
if (bespokeContext) {
|
||||||
content = (
|
content = (
|
||||||
<BespokeContext.Provider value={bespokeContext}>
|
<LandingContext.Provider value={bespokeContext}>
|
||||||
<BespokeLanding />
|
<BespokeLanding />
|
||||||
</BespokeContext.Provider>
|
</LandingContext.Provider>
|
||||||
)
|
)
|
||||||
} else if (discoveryContext) {
|
} else if (discoveryContext) {
|
||||||
content = (
|
content = (
|
||||||
<DiscoveryContext.Provider value={discoveryContext}>
|
<LandingContext.Provider value={discoveryContext}>
|
||||||
<DiscoveryLanding />
|
<DiscoveryLanding />
|
||||||
</DiscoveryContext.Provider>
|
</LandingContext.Provider>
|
||||||
)
|
)
|
||||||
} else if (journeyContext) {
|
} else if (journeyContext) {
|
||||||
content = (
|
content = (
|
||||||
<JourneyContext.Provider value={journeyContext}>
|
<LandingContext.Provider value={journeyContext}>
|
||||||
<JourneyLanding />
|
<JourneyLanding />
|
||||||
</JourneyContext.Provider>
|
</LandingContext.Provider>
|
||||||
)
|
)
|
||||||
} else if (productLandingContext) {
|
} else if (productLandingContext) {
|
||||||
content = (
|
content = (
|
||||||
@@ -184,23 +174,23 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
|
|||||||
// This looks a little funky, but it's so we only send one context's data to the client
|
// This looks a little funky, but it's so we only send one context's data to the client
|
||||||
// TODO: TEMP: This is a temporary solution to turn off/on new landing pages while we develop them
|
// TODO: TEMP: This is a temporary solution to turn off/on new landing pages while we develop them
|
||||||
if (currentLayoutName === 'bespoke-landing' || req.query?.feature === 'bespoke-landing') {
|
if (currentLayoutName === 'bespoke-landing' || req.query?.feature === 'bespoke-landing') {
|
||||||
props.bespokeContext = await getBespokeContextFromRequest(req)
|
props.bespokeContext = await getLandingContextFromRequest(req, 'bespoke')
|
||||||
additionalUINamespaces.push('bespoke_landing')
|
additionalUINamespaces.push('bespoke_landing', 'product_landing')
|
||||||
} else if (currentLayoutName === 'journey-landing' || req.query?.feature === 'journey-landing') {
|
} else if (currentLayoutName === 'journey-landing' || req.query?.feature === 'journey-landing') {
|
||||||
props.journeyContext = await getJourneyContextFromRequest(req)
|
props.journeyContext = await getLandingContextFromRequest(req, 'journey')
|
||||||
additionalUINamespaces.push('journey_landing')
|
additionalUINamespaces.push('journey_landing', 'product_landing')
|
||||||
} else if (
|
} else if (
|
||||||
currentLayoutName === 'discovery-landing' ||
|
currentLayoutName === 'discovery-landing' ||
|
||||||
req?.query?.feature === 'discovery-landing'
|
req?.query?.feature === 'discovery-landing'
|
||||||
) {
|
) {
|
||||||
props.discoveryContext = await getDiscoveryContextFromRequest(req)
|
props.discoveryContext = await getLandingContextFromRequest(req, 'discovery')
|
||||||
additionalUINamespaces.push('discovery_landing')
|
additionalUINamespaces.push('discovery_landing', 'product_landing')
|
||||||
} else if (currentLayoutName === 'product-landing') {
|
} else if (currentLayoutName === 'product-landing') {
|
||||||
props.productLandingContext = await getProductLandingContextFromRequest(req)
|
props.productLandingContext = await getProductLandingContextFromRequest(req)
|
||||||
additionalUINamespaces.push('product_landing')
|
additionalUINamespaces.push('product_landing')
|
||||||
} else if (currentLayoutName === 'product-guides') {
|
} else if (currentLayoutName === 'product-guides') {
|
||||||
props.productGuidesContext = getProductGuidesContextFromRequest(req)
|
props.productGuidesContext = getProductGuidesContextFromRequest(req)
|
||||||
additionalUINamespaces.push('product_guides')
|
additionalUINamespaces.push('product_guides', 'product_landing')
|
||||||
} else if (relativePath?.endsWith('index.md')) {
|
} else if (relativePath?.endsWith('index.md')) {
|
||||||
if (currentLayoutName === 'category-landing') {
|
if (currentLayoutName === 'category-landing') {
|
||||||
props.categoryLandingContext = getCategoryLandingContextFromRequest(req)
|
props.categoryLandingContext = getCategoryLandingContextFromRequest(req)
|
||||||
|
|||||||
Reference in New Issue
Block a user