diff --git a/components/Breadcrumbs.tsx b/components/Breadcrumbs.tsx index aff29d267e..2992f79856 100644 --- a/components/Breadcrumbs.tsx +++ b/components/Breadcrumbs.tsx @@ -26,6 +26,20 @@ export const Breadcrumbs = () => { {breadcrumb.title} + ) : pathWithLocale.includes('/guides') ? ( + + + {breadcrumb.title} + + ) : ( +} + +export type ArticleGuide = { + href: string + title: string + intro: string + type: string + topics: Array +} + +export type ProductSubLandingContextT = { + title: string + intro: string + featuredTrack?: FeaturedTrack + learningTracks?: Array + includeGuides?: Array + allTopics?: Array +} + +export const ProductSubLandingContext = createContext(null) + +export const useProductSubLandingContext = (): ProductSubLandingContextT => { + const context = useContext(ProductSubLandingContext) + + if (!context) { + throw new Error( + '"useProductSubLandingContext" may only be used inside "ProductSubLandingContext.Provider"' + ) + } + + return context +} + +export const getProductSubLandingContextFromRequest = (req: any): ProductSubLandingContextT => { + const page = req.context.page + + return { + ...pick(page, ['intro', 'allTopics']), + title: req.context.productMap[req.context.currentProduct].name, + featuredTrack: { + ...pick(page.featuredTrack, ['title', 'description', 'trackName', 'guides']), + guides: (page.featuredTrack?.guides || []).map((guide: any) => { + return pick(guide, ['title', 'intro', 'href', 'page.type']) + }), + }, + learningTracks: (page.learningTracks || []).map((track: any) => ({ + ...pick(track, ['title', 'description', 'trackName', 'guides']), + guides: (track.guides || []).map((guide: any) => { + return pick(guide, ['title', 'intro', 'href', 'page.type']) + }), + })), + includeGuides: (page.includeGuides || []).map((guide: any) => { + return pick(guide, ['href', 'title', 'intro', 'type', 'topics']) + }), + } +} diff --git a/components/landing/CodeExampleCard.tsx b/components/landing/CodeExampleCard.tsx index f04e882524..42c2dcc63c 100644 --- a/components/landing/CodeExampleCard.tsx +++ b/components/landing/CodeExampleCard.tsx @@ -29,7 +29,11 @@ export const CodeExampleCard = ({ example }: Props) => {
- + {example.href}
diff --git a/components/landing/LandingSection.tsx b/components/landing/LandingSection.tsx index 0b6bd019bf..a1956fc14c 100644 --- a/components/landing/LandingSection.tsx +++ b/components/landing/LandingSection.tsx @@ -1,19 +1,29 @@ import cx from 'classnames' +import { useTranslation } from 'components/hooks/useTranslation' type Props = { title?: React.ReactNode sectionLink?: string children?: React.ReactNode className?: string + description?: string } -export const LandingSection = ({ title, children, className, sectionLink }: Props) => { +export const LandingSection = ({ title, children, className, sectionLink, description }: Props) => { + const { t } = useTranslation('product_sublanding') + return (
{title && ( -

+

{sectionLink ? {title} : title}

)} + {description && ( +
+ )} {children}
) diff --git a/components/sublanding/ArticleCard.tsx b/components/sublanding/ArticleCard.tsx new file mode 100644 index 0000000000..90721a2066 --- /dev/null +++ b/components/sublanding/ArticleCard.tsx @@ -0,0 +1,34 @@ +import { ArticleGuide } from 'components/context/ProductSubLandingContext' + +type Props = { + card: ArticleGuide + type: string + display?: string +} + +export const ArticleCard = ({ card, type, display }: Props) => { + return ( + + ) +} diff --git a/components/sublanding/ArticleCards.tsx b/components/sublanding/ArticleCards.tsx new file mode 100644 index 0000000000..feafe245f1 --- /dev/null +++ b/components/sublanding/ArticleCards.tsx @@ -0,0 +1,67 @@ +import { useProductSubLandingContext } from 'components/context/ProductSubLandingContext' +import { useTranslation } from 'components/hooks/useTranslation' +import { ArticleCard } from './ArticleCard' + +const MAX_ARTICLES = 9 +export const ArticleCards = () => { + const { t } = useTranslation('product_sublanding') + const guideTypes: Record = t('guide_types') + const { allTopics, includeGuides } = useProductSubLandingContext() + + return ( +
+
+
+ + +
+
+ + +
+
+
+ {(includeGuides || []).map((card, index) => { + return index + 1 > MAX_ARTICLES ? ( + + ) : ( + + ) + })} +
+ {includeGuides && includeGuides.length > MAX_ARTICLES && ( + + )} +
+

{t('no_result')}

+
+
+ ) +} diff --git a/components/sublanding/LearningTrack.tsx b/components/sublanding/LearningTrack.tsx new file mode 100644 index 0000000000..c1c59f59a4 --- /dev/null +++ b/components/sublanding/LearningTrack.tsx @@ -0,0 +1,80 @@ +import { useTranslation } from 'components/hooks/useTranslation' +import { ArrowRightIcon } from '@primer/octicons-react' +import { useState } from 'react' +import { FeaturedTrack } from 'components/context/ProductSubLandingContext' + +type Props = { + track: FeaturedTrack +} + +const MAX_VISIBLE_GUIDES = 4 +export const LearningTrack = ({ track }: Props) => { + const [visibleGuides, setVisibleGuides] = useState(track.guides?.slice(0, 4)) + const showAll = () => { + setVisibleGuides(track.guides) + } + const { t } = useTranslation('product_sublanding') + + return ( +
+
+
+
+
+
{track.title}
+

+ {track.description} +

+
+
+ + {t('start')} + + + + +
+ {visibleGuides?.map((guide) => ( +
+ +
+ {track.guides && ( + + {track.guides?.indexOf(guide) + 1} + + )} +
+
{guide.title}
+
+ {t('guide_types')[guide.page.type]} +
+
+ {track.guides && track.guides?.indexOf(guide) + 1 === MAX_VISIBLE_GUIDES ? ( + + ) : ( +
+ )} +
+ ))} +
+
+ ) +} diff --git a/components/sublanding/LearningTracks.tsx b/components/sublanding/LearningTracks.tsx new file mode 100644 index 0000000000..dce7f0af00 --- /dev/null +++ b/components/sublanding/LearningTracks.tsx @@ -0,0 +1,16 @@ +import { useProductSubLandingContext } from 'components/context/ProductSubLandingContext' +import { LearningTrack } from 'components/sublanding/LearningTrack' + +export const LearningTracks = () => { + const { learningTracks } = useProductSubLandingContext() + + return ( +
+
+ {(learningTracks || []).map((track) => { + return + })} +
+
+ ) +} diff --git a/components/sublanding/ProductSubLanding.tsx b/components/sublanding/ProductSubLanding.tsx new file mode 100644 index 0000000000..ef461f4a3d --- /dev/null +++ b/components/sublanding/ProductSubLanding.tsx @@ -0,0 +1,40 @@ +import { DefaultLayout } from 'components/DefaultLayout' +import { useProductSubLandingContext } from 'components/context/ProductSubLandingContext' +import React from 'react' +import { LandingSection } from 'components/landing/LandingSection' +import { SubLandingHero } from 'components/sublanding/SubLandingHero' +import { LearningTracks } from 'components/sublanding/LearningTracks' +import { ArticleCards } from 'components/sublanding/ArticleCards' + +export const ProductSubLanding = () => { + const { title, learningTracks, includeGuides } = useProductSubLandingContext() + + return ( + + + + + + {learningTracks && learningTracks.length > 0 && ( + + + + )} + + {includeGuides && ( + + + + )} + + ) +} diff --git a/components/sublanding/SubLandingHero.tsx b/components/sublanding/SubLandingHero.tsx new file mode 100644 index 0000000000..ff7e0f034e --- /dev/null +++ b/components/sublanding/SubLandingHero.tsx @@ -0,0 +1,88 @@ +import { Breadcrumbs } from '../Breadcrumbs' +import { useProductSubLandingContext } from 'components/context/ProductSubLandingContext' +import { ArrowRightIcon, StarFillIcon } from '@primer/octicons-react' +import { useTranslation } from 'components/hooks/useTranslation' + +export const SubLandingHero = () => { + const { title, intro, featuredTrack } = useProductSubLandingContext() + const { t } = useTranslation('product_sublanding') + + const guideItems = featuredTrack?.guides?.map((guide) => ( +
  • + +
    +
    + {featuredTrack.guides && ( + + {featuredTrack.guides?.indexOf(guide) + 1} + + )} +
    +
    + {t('guide_types')[guide.page.type]} +
    +
    +

    {guide.title}

    +
    + {guide.intro} +
    +
    +
  • + )) + + return ( +
    +
    +
    + +

    {title} guides

    +
    +
    +
    + {featuredTrack && ( +
    +
      +
    • +
      +
      + +
      +

      {featuredTrack.title}

      +
      + {featuredTrack.description} +
      + {featuredTrack.guides && ( + + + + + {t(`start_path`)} + + )} +
      +
    • + {guideItems} +
    +
    +
    +
    + )} +
    + ) +} diff --git a/pages/[versionId]/[productId]/index.tsx b/pages/[versionId]/[productId]/index.tsx index 67b8550864..8a1a9e853e 100644 --- a/pages/[versionId]/[productId]/index.tsx +++ b/pages/[versionId]/[productId]/index.tsx @@ -11,6 +11,11 @@ import { ProductLandingContextT, ProductLandingContext, } from 'components/context/ProductLandingContext' +import { + getProductSubLandingContextFromRequest, + ProductSubLandingContextT, + ProductSubLandingContext, +} from 'components/context/ProductSubLandingContext' import { getArticleContextFromRequest, @@ -20,6 +25,7 @@ import { import { ArticlePage } from 'components/article/ArticlePage' import { ProductLanding } from 'components/landing/ProductLanding' +import { ProductSubLanding } from 'components/sublanding/ProductSubLanding' import { TocLanding } from 'components/landing/TocLanding' import { getTocLandingContextFromRequest, @@ -30,12 +36,14 @@ import { type Props = { mainContext: MainContextT productLandingContext: ProductLandingContextT + productSubLandingContext: ProductSubLandingContextT tocLandingContext: TocLandingContextT articleContext: ArticleContextT } const GlobalPage = ({ mainContext, productLandingContext, + productSubLandingContext, tocLandingContext, articleContext, }: Props) => { @@ -49,7 +57,11 @@ const GlobalPage = ({ ) } else if (currentLayoutName === 'product-sublanding') { - content =

    todo: product sub-landing

    + content = ( + + + + ) } else if (relativePath?.endsWith('index.md')) { content = ( @@ -76,6 +88,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => props: { mainContext: getMainContextFromRequest(req), productLandingContext: getProductLandingContextFromRequest(req), + productSubLandingContext: getProductSubLandingContextFromRequest(req), tocLandingContext: getTocLandingContextFromRequest(req), articleContext: getArticleContextFromRequest(req), },