From fee70dcd0c2ec09b5c1411f42dd6c16b387a15ae Mon Sep 17 00:00:00 2001 From: Mauricio Date: Fri, 10 Jan 2025 11:00:49 +0100 Subject: [PATCH] Release 0.9.0 (#55) * fix: make section props required on overview page (#45) * Feature/more friendly white label section on failure (#46) * feat (white label component): more friendly render on failue * refactor: cs * fix (frontend): mode * fix (strapi): build * Feature/42 video embed (#47) * fix: Add check for client side rendering * feat: Create ExternalScript component * chore: Remove "use client" * feat: Add support for wide html script with example * docs: Add info link * Feature/strapi video component (#49) * fix (strapi): add video section with title to partner and product page * feat (video section): connect strapi component with frontend * feat (strapi): video with text section component * feat (video with text section): strapi & frontend * Feature/46 desktop-navigation with example (#50) * feat: Create Desktop navigation with example in theme * chore: Add "use client" in nav-context.tsx * Bugfix/42 cta stretches (#51) * fix: "use client" is necessary in context provider consumers * fix: Wrap NavProvider around Hero, NavBar * fix: Prevent stretching of button in SectionCardWide * fix: Center grid items in SectionGroup (+ add attrs passing) * Bugfix/43 cards blocks align index (#52) fix: Reverse every second index (first should have cta at right) * Bugfix/45 kpi cards should be white and text black (#53) * fix: Add light scheme with bg-white to CardIcon * fix: Add dividers * hotfix (nextjs): build * Bugfix/52 bundle fix (#54) * fix: Change nesting for full-width display of "meet our partners" section * feat: Add support for className in Title.tsx * fix: Improve alignment consistency * chore: Fix linting errors * chore: changelog --------- Co-authored-by: Nathan Alder --- CHANGELOG.md | 12 +++ nextjs/package.json | 2 +- .../(partners-products)/partners-products.tsx | 64 +++++++++++++--- .../[locale]/(solutions-group)/solutions.tsx | 21 ++--- .../solutions/[slug]/page.tsx | 27 ++++--- nextjs/src/app/[locale]/theme/page.tsx | 59 +++++++++++--- nextjs/src/app/[locale]/theme/texts.ts | 65 ++++++++++++++++ nextjs/src/app/homepage.tsx | 65 ++++++++-------- nextjs/src/components/cards/card-icon.tsx | 22 ++++-- nextjs/src/components/external-script.tsx | 14 ++++ nextjs/src/components/hero.tsx | 27 ++++++- nextjs/src/components/nav-bar/nav-context.tsx | 29 +++++++ .../components/nav-bar/nav-desktop-items.tsx | 76 +++++++++++++++++++ .../components/{ => nav-bar}/nav-desktop.tsx | 41 +++++++--- .../components/{ => nav-bar}/nav-mobile.tsx | 17 +++-- nextjs/src/components/nav-bar/nav.ts | 5 ++ .../components/sections/section-calendly.tsx | 9 ++- .../components/sections/section-card-wide.tsx | 33 +++++--- .../src/components/sections/section-group.tsx | 11 ++- .../sections/section-whitepaper.tsx | 8 +- nextjs/src/components/title.tsx | 19 ++++- nextjs/src/components/topbar-actions.tsx | 2 - nextjs/src/components/topbar.tsx | 15 ++-- strapi/package.json | 2 +- .../page-partner-and-product/schema.json | 7 +- .../populate-partner-and-products.ts | 6 ++ .../relations/section-solutions-relation.json | 3 +- .../components/sections/video-section.json | 26 +++++++ .../sections/video-with-text-section.json | 28 +++++++ strapi/types/generated/components.d.ts | 37 ++++++++- strapi/types/generated/contentTypes.d.ts | 3 + 31 files changed, 619 insertions(+), 136 deletions(-) create mode 100644 nextjs/src/components/external-script.tsx create mode 100644 nextjs/src/components/nav-bar/nav-context.tsx create mode 100644 nextjs/src/components/nav-bar/nav-desktop-items.tsx rename nextjs/src/components/{ => nav-bar}/nav-desktop.tsx (55%) rename nextjs/src/components/{ => nav-bar}/nav-mobile.tsx (81%) create mode 100644 nextjs/src/components/nav-bar/nav.ts create mode 100644 strapi/src/components/sections/video-section.json create mode 100644 strapi/src/components/sections/video-with-text-section.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e9b822..c5147dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,18 @@ straightforward as possible. - [PROJECTNAME-YYYY](http://tickets.projectname.com/browse/PROJECTNAME-YYYY) PATCH Ticket title goes here. +## [0.9.0] - 2025-01-10 + +- Bugfix bundle fix (#54) +- Bugfix kpi cards should be white and text black (#53) +- Bugfix cards blocks align index (#52) +- Bugfix cta stretches (#51) +- Feature desktop-navigation with example (#50) +- Feature strapi video component (#49) +- Feature video embed (#47) +- Feature/more friendly white label section on failure (#46) +- fix: make section props required on overview page (#45) + ## [0.8.0] - 2025-01-06 - feat: partners and journeys collections (#43) diff --git a/nextjs/package.json b/nextjs/package.json index a3741ff..3b294d5 100644 --- a/nextjs/package.json +++ b/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "nextjs", - "version": "0.8.0", + "version": "0.9.0", "private": true, "scripts": { "dev": "next dev", diff --git a/nextjs/src/app/[locale]/(partners-products)/partners-products.tsx b/nextjs/src/app/[locale]/(partners-products)/partners-products.tsx index a210819..195257b 100644 --- a/nextjs/src/app/[locale]/(partners-products)/partners-products.tsx +++ b/nextjs/src/app/[locale]/(partners-products)/partners-products.tsx @@ -14,6 +14,10 @@ import ButtonGroup from "@/components/button-group" import NavBar from "@/components/nav-bar/nav-bar" import { LinkedLocale } from "@/components/nav-bar/linked-locales-provider" import { notFound } from "next/navigation" +import ExternalScript from "@/components/external-script" +import SectionCardWide from "@/components/sections/section-card-wide" +import { youtubeSection } from "@/app/[locale]/theme/texts" +import { NavProvider } from "@/components/nav-bar/nav-context" export default async function PartnersProducts({ activeLocale, @@ -46,13 +50,15 @@ export default async function PartnersProducts({ return ( <> - - {hero && ( - - - <Text markdown={hero.body} /> - </Hero> - )} + <NavProvider> + <NavBar items={locales} /> + {hero && ( + <Hero color={hero.color.color} imageUrl={hero.backround_image.url}> + <Title markdown={hero.title} /> + <Text markdown={hero.body} /> + </Hero> + )} + </NavProvider> {intro && ( <Intro> <Text markdown={intro.body} className="grid gap-8" /> @@ -114,14 +120,14 @@ function dynamicSection(section: any, index: number) { <SectionWhitepaper title={section.white_paper.title} cta={{ - text: "Download whitepaper", - href: section.white_paper.download_file.url, + text: "Download white paper", + href: section.white_paper.download_file?.url ?? "", variant: "cta", size: "large", }} image={{ - src: section.white_paper.cover_image.url, - alt: section.white_paper.cover_image.alternativeText ?? "", + src: section.white_paper?.cover_image?.url, + alt: section.white_paper?.cover_image?.alternativeText ?? "", }} text={section.white_paper.description} /> @@ -175,6 +181,42 @@ function dynamicSection(section: any, index: number) { </SectionGroup> </Container> ) + case "sections.video-section": + return ( + <Container + key={`section_video-section_${index}`} + background={section.props.background} + padding={section.props.padding} + > + <SectionGroup title={section.section_title}> + <ExternalScript + html={section.embed_html} + className="w-full h-auto" + /> + </SectionGroup> + </Container> + ) + case "sections.video-with-text-section": + return ( + <Container + key={`section_video-section_${index}`} + background={section.props.background} + padding={section.props.padding} + > + <SectionGroup title={section.section_title}> + <SectionCardWide + childrenWide={ + <ExternalScript + html={section.embed_html} + className="w-full h-auto" + /> + } + > + <Text markdown={section.body} /> + </SectionCardWide> + </SectionGroup> + </Container> + ) default: return <p key={`section_${index}`}>Unknown section</p> } diff --git a/nextjs/src/app/[locale]/(solutions-group)/solutions.tsx b/nextjs/src/app/[locale]/(solutions-group)/solutions.tsx index ce962e0..d35febb 100644 --- a/nextjs/src/app/[locale]/(solutions-group)/solutions.tsx +++ b/nextjs/src/app/[locale]/(solutions-group)/solutions.tsx @@ -14,6 +14,7 @@ import SectionCardWide from "@/components/sections/section-card-wide" import ButtonGroup from "@/components/button-group" import LinkButton from "@/components/link-button" import CardService from "@/components/cards/card-service" +import { NavProvider } from "@/components/nav-bar/nav-context" const SUB_PAGE: any = { en: "solutions", @@ -56,13 +57,15 @@ export default async function Solutions({ return ( <> - <NavBar items={locales} /> - {hero && ( - <Hero color={hero.color.color} imageUrl={hero.backround_image.url}> - <Title markdown={hero.title} /> - <Text markdown={hero.body} /> - </Hero> - )} + <NavProvider> + <NavBar items={locales} /> + {hero && ( + <Hero color={hero.color.color} imageUrl={hero.backround_image.url}> + <Title markdown={hero.title} /> + <Text markdown={hero.body} /> + </Hero> + )} + </NavProvider> {intro && ( <Intro> <Title markdown={intro.intro_title} align="center" /> @@ -75,7 +78,7 @@ export default async function Solutions({ padding={kpi_sections.section_props.padding} > <SectionGroup title={kpi_sections.title}> - <CardGroup> + <CardGroup hasDividers> {kpi_sections.cards.map((item: any, i: number) => { return ( <CardIcon @@ -102,7 +105,7 @@ export default async function Solutions({ <SectionCardWide ctas={[]} image={{ src: item.card_image.url, alt: "" }} - reverse={false} + reverse={i % 2 === 1} key={`soutions_section_solutions_${i}`} > <Title level={3} boldness={"semibold"}> diff --git a/nextjs/src/app/[locale]/(solutions-group)/solutions/[slug]/page.tsx b/nextjs/src/app/[locale]/(solutions-group)/solutions/[slug]/page.tsx index 2e441c5..a816037 100644 --- a/nextjs/src/app/[locale]/(solutions-group)/solutions/[slug]/page.tsx +++ b/nextjs/src/app/[locale]/(solutions-group)/solutions/[slug]/page.tsx @@ -15,6 +15,7 @@ import CardSlider from "@/components/cards/card-slider" import CardSliderElement from "@/components/cards/card-slider-element" import CardArticle from "@/components/cards/card-article" import NavBar from "@/components/nav-bar/nav-bar" +import { NavProvider } from "@/components/nav-bar/nav-context" const SUB_PAGE = { en: "solutions", @@ -49,13 +50,16 @@ export default async function SolutionsDetailPage({ return ( <> - <NavBar items={locales} /> - {hero && ( - <Hero color={hero.color.color} imageUrl={hero.backround_image.url}> - <Title markdown={hero.title} /> - <Text markdown={hero.body} /> - </Hero> - )} + <NavProvider> + <NavBar items={locales} /> + {hero && ( + <Hero color={hero.color.color} imageUrl={hero.backround_image.url}> + <Title markdown={hero.title} /> + <Text markdown={hero.body} /> + </Hero> + )} + </NavProvider> + {intro && ( <Intro> <Title markdown={intro.intro_title} /> @@ -64,7 +68,7 @@ export default async function SolutionsDetailPage({ )} {project_cards && ( <Container background="stone" padding="both-padding"> - <SectionGroup title={project_cards.title}> + <SectionGroup title={project_cards.title} data-testid="project-cards"> <Text markdown={project_cards.description} className="text-center" @@ -74,7 +78,7 @@ export default async function SolutionsDetailPage({ <SectionCardWide ctas={[]} image={{ src: item.image.url, alt: "" }} - reverse={false} + reverse={i % 2 === 1} key={`project_cards${i}`} > <Title level={3} boldness={"semibold"}> @@ -98,8 +102,8 @@ export default async function SolutionsDetailPage({ background={kpis.section_props.background} padding={kpis.section_props.padding} > - <SectionGroup title={kpis.title}> - <CardGroup> + <SectionGroup title={kpis.title} data-testid="kpis" align={"center"}> + <CardGroup hasDividers> {kpis.cards.map((item: any, i: number) => { return ( <CardIcon @@ -129,6 +133,7 @@ export default async function SolutionsDetailPage({ <SectionGroup title={start_your_journey.section_group_with_external_link.title} align={"center"} + data-testid="start-your-journey" > <ButtonGroup align={"center"} diff --git a/nextjs/src/app/[locale]/theme/page.tsx b/nextjs/src/app/[locale]/theme/page.tsx index 1da487b..ffef134 100644 --- a/nextjs/src/app/[locale]/theme/page.tsx +++ b/nextjs/src/app/[locale]/theme/page.tsx @@ -5,6 +5,7 @@ import Link from "@/components/link-button" import Title from "@/components/title" import Text from "@/components/text" import { + navItems, hero, intro, solutions, @@ -21,6 +22,7 @@ import { whitepaperSection, twoColumnMarkdownSection, calendlySection, + youtubeSection, } from "./texts" import Container from "@/components/container" import CardSlider from "@/components/cards/card-slider" @@ -42,6 +44,9 @@ import GetStartedForm from "@/components/form/get-started-form" import { type Locale } from "@/hooks/useLocale" import SectionWhitepaper from "@/components/sections/section-whitepaper" import SectionCalendly from "@/components/sections/section-calendly" +import ExternalScript from "@/components/external-script" +import Topbar from "@/components/topbar" +import { NavProvider } from "@/components/nav-bar/nav-context" export default function Theme({ params: { locale }, @@ -50,16 +55,20 @@ export default function Theme({ }) { return ( <main className="bg-white"> - <Hero - color="white" - imageUrl="https://images.unsplash.com/photo-1682687220198-88e9bdea9931?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" - > - <Title markdown={hero.title} /> - <Text markdown={hero.text} /> - <Link href="https://www.adfinis.com" size="large"> - Learn how - </Link> - </Hero> + <NavProvider> + <Topbar navItems={navItems} /> + <Hero + color="white" + imageUrl="https://images.unsplash.com/photo-1682687220198-88e9bdea9931?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" + > + <Title markdown={hero.title} /> + <Text markdown={hero.text} /> + <Link href="https://www.adfinis.com" size="large"> + Learn how + </Link> + </Hero> + </NavProvider> + <Intro> <Title markdown={intro.title} align="center" /> <Text markdown={intro.text} className="grid gap-8" /> @@ -94,7 +103,7 @@ export default function Theme({ {mediaSection.media.map((item, i) => { return ( <SectionCardWide - reverse={i % 2 === 0} + reverse={i % 2 === 1} image={item.image} key={i} ctas={item.ctas} @@ -165,7 +174,7 @@ export default function Theme({ {media2Section.media.slice(0, 2).map((item, i) => { return ( <SectionCardWide - reverse={i % 2 === 0} + reverse={i % 2 === 1} image={item.image} key={i} ctas={item.ctas} @@ -304,6 +313,32 @@ export default function Theme({ <SectionCalendly url={calendlySection.url} /> </SectionGroup> </Container> + + <Container id="youtube" background="neutral" padding="both-padding"> + <SectionGroup title="Youtube"> + <ExternalScript + html={youtubeSection.html} + className="w-full h-auto" + /> + </SectionGroup> + </Container> + <Container id="youtube" background="neutral" padding="both-padding"> + <SectionGroup title="Youtube in a SectionCardWide"> + <SectionCardWide + childrenWide={ + <ExternalScript + html={youtubeSection.html} + className="w-full h-auto" + /> + } + > + <Title level={3} boldness={"semibold"}> + {youtubeSection.title} + + + + + ) } diff --git a/nextjs/src/app/[locale]/theme/texts.ts b/nextjs/src/app/[locale]/theme/texts.ts index 62aa333..07cda67 100644 --- a/nextjs/src/app/[locale]/theme/texts.ts +++ b/nextjs/src/app/[locale]/theme/texts.ts @@ -1,6 +1,62 @@ import { type Card } from "@/components/cards/card" +import { NavItem } from "@/components/nav-bar/nav" import { CTA } from "@/lib/cta" +export const navItems: NavItem[] = [ + { + title: "Solutions", + items: [ + { + title: "HashiCorp", + items: [ + { + title: "Vault", + url: "/solutions/vault", + }, + { + title: "Terraform", + url: "/solutions/terraform", + }, + { + title: "Consul", + url: "/solutions/consul", + }, + ], + }, + { + title: "Red Hat", + items: [ + { + title: "OpenShift", + url: "/solutions/openshift", + }, + { + title: "Enterprise Linux & SAP Workloads", + url: "/solutions/enterprise-linux-sap-workloads", + }, + { + title: "Ansible Automation Platform", + url: "/solutions/ansible-automation-platform", + }, + { + title: "Red Hat Satellite", + url: "/solutions/red-hat-satellite", + }, + ], + }, + ], + }, + { + title: "Partners & Products", + items: [ + { + title: "Github", + url: "/partners/github", + }, + ], + }, +] + export const hero = { title: `## Potential. **Unlocked.**`, text: `Open Source is our key to innovation and sustainable digitalization! @@ -541,6 +597,15 @@ export const calendlySection = { url: "https://calendly.com/embed-demo-sales/discovery-call", } +/** + * @see https://embedresponsively.com/ + */ +export const youtubeSection = { + html: `
`, + title: `Watch our latest video on YouTube`, + text: `Adfinis is a Swiss IT service provider specializing in open-source software solutions. With a strong commitment to innovation and sustainability, Adfinis offers consulting, development, and managed services to help organizations optimize their IT environments. Our team of experts is dedicated to delivering cutting-edge technology services that enhance operational efficiency and drive digital transformation. Discover how Adfinis can unlock your organization's potential by watching our latest video on YouTube.`, +} + export const footer = { columns: [ { diff --git a/nextjs/src/app/homepage.tsx b/nextjs/src/app/homepage.tsx index 14a77ff..eafd591 100644 --- a/nextjs/src/app/homepage.tsx +++ b/nextjs/src/app/homepage.tsx @@ -18,6 +18,7 @@ import CardGroup from "@/components/cards/card-group" import CardIcon from "@/components/cards/card-icon" import CardArticle from "@/components/cards/card-article" import CardCounter from "@/components/cards/card-counter" +import { NavProvider } from "@/components/nav-bar/nav-context" const mapCta = (cta: any) => ({ // TODO Decide if we want to change CTA type or rename label to text in strapi @@ -60,20 +61,22 @@ export default async function Homepage({ return ( <> - - {hero && ( - - - <Text markdown={hero.description} /> - <LinkButton - href={hero.cta.href} - size={hero.cta.size} - variant={hero.cta.variant} - > - {hero.cta.label} - </LinkButton> - </Hero> - )} + <NavProvider> + <NavBar items={locales} /> + {hero && ( + <Hero color="white" imageUrl={hero.image}> + <Title markdown={hero.title} /> + <Text markdown={hero.description} /> + <LinkButton + href={hero.cta.href} + size={hero.cta.size} + variant={hero.cta.variant} + > + {hero.cta.label} + </LinkButton> + </Hero> + )} + </NavProvider> {intro && ( <Intro> <Title markdown={intro.intro_title} align="center" /> @@ -107,7 +110,7 @@ export default async function Homepage({ {our_projects.projects.map((item: any, i: number) => { return ( <SectionCardWide - reverse={i % 2 === 0} + reverse={i % 2 === 1} image={ // TODO support alt and rename image to src in strap { src: item.image.url, alt: "" } @@ -122,21 +125,21 @@ export default async function Homepage({ </SectionCardWide> ) })} - {meet_our_partners && ( - <Container background="white" padding="both-padding"> - <SectionGroup - title={meet_our_partners.title} - text={meet_our_partners.intro} - align="center" - > - <ButtonGroup - ctas={meet_our_partners.ctas.map(mapCta)} - align={"center"} - /> - <Hallmarks hallmarksId={our_partners.documentId} /> - </SectionGroup> - </Container> - )} + </SectionGroup> + </Container> + )} + {meet_our_partners && ( + <Container background="white" padding="both-padding"> + <SectionGroup + title={meet_our_partners.title} + text={meet_our_partners.intro} + align="center" + > + <ButtonGroup + ctas={meet_our_partners.ctas.map(mapCta)} + align={"center"} + /> + <Hallmarks hallmarksId={our_partners.documentId} /> </SectionGroup> </Container> )} @@ -233,7 +236,7 @@ export default async function Homepage({ {who_are_we.projects.map((item: any, i: number) => { return ( <SectionCardWide - reverse={i % 2 === 0} + reverse={i % 2 === 1} image={{ src: item.image.formats.large.url, alt: "" }} key={i} ctas={[item.cta].map(mapCta)} diff --git a/nextjs/src/components/cards/card-icon.tsx b/nextjs/src/components/cards/card-icon.tsx index 72772ab..9164016 100644 --- a/nextjs/src/components/cards/card-icon.tsx +++ b/nextjs/src/components/cards/card-icon.tsx @@ -21,26 +21,38 @@ const CardIcon: React.FC<CardIconProps> = ({ cta, }) => { return ( - <div className="rounded-xl px-6 pt-6 pb-8 shadow-2 grid gap-6 divide-vertical-6 justify-items-center"> + <div + data-component="CardIcon" + className="bg-white rounded-xl px-6 pt-6 pb-8 shadow-2 grid gap-6 divide-vertical-6 justify-items-center" + data-scheme="light" + > {imageUrl && ( <Image alt={title} src={imageUrl} width={300} height={300} - className="max-h-20" + className="h-20 mb-6 mx-auto" /> )} - <Title level={3} boldness={"semibold"} align={"center"}> + <Title + level={3} + boldness={"semibold"} + align={"center"} + className="mb-6 lg:mb-1 lg:min-h-[2lh]" + > {title} - + {cta && ( {cta.text} diff --git a/nextjs/src/components/external-script.tsx b/nextjs/src/components/external-script.tsx new file mode 100644 index 0000000..fa6257f --- /dev/null +++ b/nextjs/src/components/external-script.tsx @@ -0,0 +1,14 @@ +import React from "react" + +interface ExternalScriptProps { + html: string + className?: string +} + +const ExternalScript: React.FC = ({ html, className }) => { + return ( +
+ ) +} + +export default ExternalScript diff --git a/nextjs/src/components/hero.tsx b/nextjs/src/components/hero.tsx index 50ae82a..5842b65 100644 --- a/nextjs/src/components/hero.tsx +++ b/nextjs/src/components/hero.tsx @@ -1,9 +1,11 @@ +"use client" import Image from "next/image" import Triangle from "./triangle" import clsx from "clsx" import React from "react" import { colors } from "@/lib/colors" +import { useNavContext } from "./nav-bar/nav-context" type HeroProps = { color: keyof typeof colors @@ -12,6 +14,7 @@ type HeroProps = { } const Hero: React.FC = ({ imageUrl, children, color }) => { + const { navActive } = useNavContext() return (
= ({ imageUrl, children, color }) => { alt="Hero Image" width={1920} height={1080} - className="absolute inset-0 object-cover object-center z-0 h-full w-full" + className={clsx([ + "absolute inset-0 object-cover object-center z-0 h-full w-full", + "transition-all duration-75", + { + "blur-sm": navActive, + }, + ])} + /> +
-
diff --git a/nextjs/src/components/nav-bar/nav-context.tsx b/nextjs/src/components/nav-bar/nav-context.tsx new file mode 100644 index 0000000..314361f --- /dev/null +++ b/nextjs/src/components/nav-bar/nav-context.tsx @@ -0,0 +1,29 @@ +"use client" +import React, { createContext, useState, useContext, ReactNode } from "react" + +type NavState = { + navActive: boolean + setNavActive: React.Dispatch> +} + +const NavContext = createContext(undefined) + +/** + * @description NavProvider is a context provider that has a single shared state about whether any desktop nav item is active or not. + */ +export const NavProvider = ({ children }: { children: ReactNode }) => { + const [navActive, setNavActive] = useState(false) + return ( + + {children} + + ) +} + +export const useNavContext = () => { + const context = useContext(NavContext) + if (!context) { + throw new Error("useAppContext must be used within a NavProvider") + } + return context +} diff --git a/nextjs/src/components/nav-bar/nav-desktop-items.tsx b/nextjs/src/components/nav-bar/nav-desktop-items.tsx new file mode 100644 index 0000000..b5f691a --- /dev/null +++ b/nextjs/src/components/nav-bar/nav-desktop-items.tsx @@ -0,0 +1,76 @@ +import React, { useState } from "react" +import { Transition } from "@headlessui/react" +import { useNavContext } from "./nav-context" + +import type { NavItem } from "./nav" +import Link from "next/link" + +type NavDesktopItemsProps = { + navItem: NavItem +} +const NavDesktopItems: React.FC = ({ navItem }) => { + const [isShowing, setIsShowing] = useState(false) + const { navActive, setNavActive } = useNavContext() + + const showDesktopItems = () => { + setIsShowing(true) + setNavActive(true) + } + + const hideDesktopItems = () => { + setIsShowing(false) + setNavActive(false) + } + + return ( +
+
+
+ {navItem.title} +
+
+ + +
+
+ {navItem.items?.map((item, index) => ( +
+

+ {item.title} +

+ {item.items?.map( + (subItem, subIndex) => + subItem.url && ( + + {subItem.title} + + ), + )} +
+ ))} +
+
+
+
+ ) +} + +export default NavDesktopItems diff --git a/nextjs/src/components/nav-desktop.tsx b/nextjs/src/components/nav-bar/nav-desktop.tsx similarity index 55% rename from nextjs/src/components/nav-desktop.tsx rename to nextjs/src/components/nav-bar/nav-desktop.tsx index 5a9e500..6e96420 100644 --- a/nextjs/src/components/nav-desktop.tsx +++ b/nextjs/src/components/nav-bar/nav-desktop.tsx @@ -1,11 +1,18 @@ -import React, { useEffect } from "react" +import React, { useEffect, useState } from "react" import clsx from "clsx" import useDetectScroll, { Axis } from "@smakss/react-scroll-direction" -import Logo from "./logo" -import TopbarActions from "./topbar-actions" +import Logo from "../logo" +import TopbarActions from "../topbar-actions" +import { NavItem } from "./nav" +import Link from "next/link" +import NavDesktopItems from "./nav-desktop-items" -const NavDesktop: React.FC = () => { - const [menuExpanded, setMenuExpanded] = React.useState(true) +type NavDesktopProps = { + navItems: NavItem[] +} + +const NavDesktop: React.FC = ({ navItems }) => { + const [menuExpanded, setMenuExpanded] = useState(true) const { scrollDir, scrollPosition } = useDetectScroll({ thr: 20, axis: Axis.Y, @@ -30,19 +37,21 @@ const NavDesktop: React.FC = () => { /** * @description only collapse the menu when the user has scrolled down */ - const onMouseMenuLeave = () => { + const handleMouseMenuLeave = () => { if (scrollPosition.top > 200) { setMenuExpanded(false) } } + const handleMouseEnter = () => { + setMenuExpanded(true) + } + return ( -
setMenuExpanded(true)} - onMouseLeave={() => onMouseMenuLeave()} - > +
{
{menuExpanded && ( - + )}
) diff --git a/nextjs/src/components/nav-mobile.tsx b/nextjs/src/components/nav-bar/nav-mobile.tsx similarity index 81% rename from nextjs/src/components/nav-mobile.tsx rename to nextjs/src/components/nav-bar/nav-mobile.tsx index 61fd420..d10f4bc 100644 --- a/nextjs/src/components/nav-mobile.tsx +++ b/nextjs/src/components/nav-bar/nav-mobile.tsx @@ -3,14 +3,19 @@ import clsx from "clsx" import { Transition } from "@headlessui/react" import { useClickAway } from "@uidotdev/usehooks" -import IconHamburgerMenu from "./icons/icon-hamburger-menu" +import IconHamburgerMenu from "../icons/icon-hamburger-menu" import Link from "next/link" -import ButtonLink from "./link-button" -import IconChevronRight from "./icons/icon-chevron-right" -import Logo from "./logo" -import TopbarActions from "./topbar-actions" +import ButtonLink from "../link-button" +import IconChevronRight from "../icons/icon-chevron-right" +import Logo from "../logo" +import TopbarActions from "../topbar-actions" +import type { NavItem } from "./nav" -const NavMobile: React.FC = () => { +type NavMobileProps = { + navItems: NavItem[] +} + +const NavMobile: React.FC = ({ navItems }) => { const [isOpen, setIsOpen] = useState(false) const ref = useClickAway(() => { setIsOpen(false) diff --git a/nextjs/src/components/nav-bar/nav.ts b/nextjs/src/components/nav-bar/nav.ts new file mode 100644 index 0000000..2ba8ee2 --- /dev/null +++ b/nextjs/src/components/nav-bar/nav.ts @@ -0,0 +1,5 @@ +export type NavItem = { + title: string + url?: string + items?: NavItem[] +} diff --git a/nextjs/src/components/sections/section-calendly.tsx b/nextjs/src/components/sections/section-calendly.tsx index 5b78fa5..f3b3cf2 100644 --- a/nextjs/src/components/sections/section-calendly.tsx +++ b/nextjs/src/components/sections/section-calendly.tsx @@ -17,15 +17,16 @@ const SectionCalendly: React.FC = ({ }, }) - const [width, setWidth] = useState(window.innerWidth) + const browserWindow: any = (typeof window !== "undefined" && window) || {} + const [width, setWidth] = useState(browserWindow?.innerWidth || 0) // execute on client-side only useEffect(() => { const handleResize = () => { - setWidth(window.innerWidth) + setWidth(window?.innerWidth) } - window.addEventListener("resize", handleResize) + window?.addEventListener("resize", handleResize) return () => { - window.removeEventListener("resize", handleResize) + window?.removeEventListener("resize", handleResize) } }, []) diff --git a/nextjs/src/components/sections/section-card-wide.tsx b/nextjs/src/components/sections/section-card-wide.tsx index 5b56d6d..a9df2e8 100644 --- a/nextjs/src/components/sections/section-card-wide.tsx +++ b/nextjs/src/components/sections/section-card-wide.tsx @@ -7,20 +7,28 @@ import clsx from "clsx" import { colors } from "@/lib/colors" type SectionMediaWideProps = { - reverse: boolean + reverse?: boolean children: React.ReactNode - image: { + /** + * @info if an image is passed, the component will use the image instead of childrenWide + */ + image?: { src: string alt: string } color?: keyof typeof colors - ctas: CTA[] + ctas?: CTA[] + /** + * @info childrenWide is an alternative to image. It can be any component + */ + childrenWide?: React.ReactNode } const SectionCardWide: React.FC = ({ reverse, image, color, children, + childrenWide, ctas, }) => { return ( @@ -31,17 +39,20 @@ const SectionCardWide: React.FC = ({ ])} > - + {(image && ( + + )) || + childrenWide} -
+
{children} - {ctas.length > 0 && ( + {ctas && ctas.length > 0 && (
diff --git a/nextjs/src/components/sections/section-group.tsx b/nextjs/src/components/sections/section-group.tsx index 4711b18..f882815 100644 --- a/nextjs/src/components/sections/section-group.tsx +++ b/nextjs/src/components/sections/section-group.tsx @@ -9,7 +9,7 @@ const sectionGroupStyles = cva(["grid gap-12 lg:gap-16"], { variants: { align: { start: "", - center: "justify-start lg:justify-center", + center: "justify-start lg:justify-center justify-items-center", }, hasDividers: { true: "section-group-dividers", @@ -44,9 +44,14 @@ const SectionGroup: React.FC = ({ align, hasDividers, columns, + ...attrs }) => { return ( -
+
{title && } {text && ( <div @@ -60,7 +65,7 @@ const SectionGroup: React.FC<SectionGroupProps> = ({ <Text markdown={text} className="text-justify lg:text-center" /> </div> )} - <div className={sectionGroupStyles({ hasDividers, columns })}> + <div className={sectionGroupStyles({ hasDividers, columns, align })}> {children} </div> </div> diff --git a/nextjs/src/components/sections/section-whitepaper.tsx b/nextjs/src/components/sections/section-whitepaper.tsx index 8686100..6f44f30 100644 --- a/nextjs/src/components/sections/section-whitepaper.tsx +++ b/nextjs/src/components/sections/section-whitepaper.tsx @@ -39,9 +39,11 @@ const SectionWhitepaper: React.FC<SectionWhitepaperProps> = ({ {title} - - {cta.text} - + {cta.href && ( + + {cta.text} + + )}
) diff --git a/nextjs/src/components/title.tsx b/nextjs/src/components/title.tsx index 7ad9a43..dbfcdd2 100644 --- a/nextjs/src/components/title.tsx +++ b/nextjs/src/components/title.tsx @@ -1,4 +1,5 @@ import { cva, VariantProps } from "class-variance-authority" +import clsx from "clsx" import React from "react" import Markdown from "react-markdown" import remarkGfm from "remark-gfm" @@ -34,10 +35,18 @@ type ChildrenORString = | { children: React.ReactNode markdown?: never + /** + * @warning only use className for positioning utilities. + */ + className?: string } | { markdown: string children?: never + /** + * @warning only use className for positioning utilities. + */ + className?: string } type TitleProps = VariantProps & ChildrenORString @@ -53,6 +62,7 @@ const Title: React.FC = ({ children, markdown, boldness, + className, }) => { if (markdown) { const headingLevel = markdown.startsWith("###") @@ -64,7 +74,10 @@ const Title: React.FC = ({ return ( {markdown} @@ -72,7 +85,9 @@ const Title: React.FC = ({ } const Tag = `h${level || 2}` as keyof JSX.IntrinsicElements return ( - {children} + + {children} + ) } diff --git a/nextjs/src/components/topbar-actions.tsx b/nextjs/src/components/topbar-actions.tsx index 9952694..33ecb01 100644 --- a/nextjs/src/components/topbar-actions.tsx +++ b/nextjs/src/components/topbar-actions.tsx @@ -3,8 +3,6 @@ import LocaleSwitcher from "./locale-switcher" import Search from "./search" const TopbarActions: React.FC = () => { - // Add your component logic here - return (
diff --git a/nextjs/src/components/topbar.tsx b/nextjs/src/components/topbar.tsx index f5b1975..cba91e1 100644 --- a/nextjs/src/components/topbar.tsx +++ b/nextjs/src/components/topbar.tsx @@ -1,9 +1,14 @@ "use client" import React, { useEffect } from "react" -import NavMobile from "./nav-mobile" -import NavDesktop from "./nav-desktop" +import NavMobile from "./nav-bar/nav-mobile" +import NavDesktop from "./nav-bar/nav-desktop" +import { NavItem } from "./nav-bar/nav" -const Topbar: React.FC = () => { +type TopbarProps = { + navItems: NavItem[] +} + +const Topbar: React.FC = ({ navItems }) => { return (
{ id="navbar" >
- - + +
diff --git a/strapi/package.json b/strapi/package.json index c3fd6d4..e59b4da 100644 --- a/strapi/package.json +++ b/strapi/package.json @@ -1,6 +1,6 @@ { "name": "strapi", - "version": "0.8.0", + "version": "0.9.0", "private": true, "description": "A Strapi application", "scripts": { diff --git a/strapi/src/api/page-partner-and-product/content-types/page-partner-and-product/schema.json b/strapi/src/api/page-partner-and-product/content-types/page-partner-and-product/schema.json index ef8e84b..74c4996 100644 --- a/strapi/src/api/page-partner-and-product/content-types/page-partner-and-product/schema.json +++ b/strapi/src/api/page-partner-and-product/content-types/page-partner-and-product/schema.json @@ -4,7 +4,8 @@ "info": { "singularName": "page-partner-and-product", "pluralName": "page-partner-and-products", - "displayName": "Page Partner & Product" + "displayName": "Page Partner & Product", + "description": "" }, "options": { "draftAndPublish": true @@ -73,7 +74,9 @@ "sections.heading-with-link-container", "relations.white-paper-section", "relations.quotes-relation", - "relations.calendly-section" + "relations.calendly-section", + "sections.video-section", + "sections.video-with-text-section" ] } } diff --git a/strapi/src/api/page-partner-and-product/middlewares/populate-partner-and-products.ts b/strapi/src/api/page-partner-and-product/middlewares/populate-partner-and-products.ts index 2218424..92565c7 100644 --- a/strapi/src/api/page-partner-and-product/middlewares/populate-partner-and-products.ts +++ b/strapi/src/api/page-partner-and-product/middlewares/populate-partner-and-products.ts @@ -43,6 +43,12 @@ const populate = { }, }, }, + 'sections.video-section': { + populate: '*', + }, + 'sections.video-with-text-section': { + populate: '*', + }, }, } }; diff --git a/strapi/src/components/relations/section-solutions-relation.json b/strapi/src/components/relations/section-solutions-relation.json index f9d56b3..0a67c02 100644 --- a/strapi/src/components/relations/section-solutions-relation.json +++ b/strapi/src/components/relations/section-solutions-relation.json @@ -13,7 +13,8 @@ "section_props": { "type": "component", "repeatable": false, - "component": "sections.section-props" + "component": "sections.section-props", + "required": true }, "solutions": { "type": "component", diff --git a/strapi/src/components/sections/video-section.json b/strapi/src/components/sections/video-section.json new file mode 100644 index 0000000..18eaaaa --- /dev/null +++ b/strapi/src/components/sections/video-section.json @@ -0,0 +1,26 @@ +{ + "collectionName": "components_sections_video_sections", + "info": { + "displayName": "Video Section", + "icon": "play", + "description": "" + }, + "options": {}, + "attributes": { + "section_title": { + "type": "string", + "required": true + }, + "embed_html": { + "type": "text", + "required": true, + "default": "use this tool to embed video https://embedresponsively.com/" + }, + "props": { + "type": "component", + "repeatable": false, + "component": "sections.section-props", + "required": true + } + } +} diff --git a/strapi/src/components/sections/video-with-text-section.json b/strapi/src/components/sections/video-with-text-section.json new file mode 100644 index 0000000..30476af --- /dev/null +++ b/strapi/src/components/sections/video-with-text-section.json @@ -0,0 +1,28 @@ +{ + "collectionName": "components_sections_video_with_text_sections", + "info": { + "displayName": "Video With Text Section", + "icon": "play" + }, + "options": {}, + "attributes": { + "props": { + "type": "component", + "repeatable": false, + "component": "sections.section-props", + "required": true + }, + "section_title": { + "type": "string", + "required": true + }, + "embed_html": { + "type": "text", + "required": true + }, + "body": { + "type": "richtext", + "required": true + } + } +} diff --git a/strapi/types/generated/components.d.ts b/strapi/types/generated/components.d.ts index b3c22c3..9918bdd 100644 --- a/strapi/types/generated/components.d.ts +++ b/strapi/types/generated/components.d.ts @@ -348,7 +348,8 @@ export interface RelationsSectionSolutionsRelation displayName: 'Section Solutions Relation'; }; attributes: { - section_props: Schema.Attribute.Component<'sections.section-props', false>; + section_props: Schema.Attribute.Component<'sections.section-props', false> & + Schema.Attribute.Required; solutions: Schema.Attribute.Component< 'relations.solutions-relation-with-description', true @@ -733,6 +734,38 @@ export interface SectionsTwoColumnSection extends Struct.ComponentSchema { }; } +export interface SectionsVideoSection extends Struct.ComponentSchema { + collectionName: 'components_sections_video_sections'; + info: { + description: ''; + displayName: 'Video Section'; + icon: 'play'; + }; + attributes: { + embed_html: Schema.Attribute.Text & + Schema.Attribute.Required & + Schema.Attribute.DefaultTo<'use this tool to embed video https://embedresponsively.com/'>; + props: Schema.Attribute.Component<'sections.section-props', false> & + Schema.Attribute.Required; + section_title: Schema.Attribute.String & Schema.Attribute.Required; + }; +} + +export interface SectionsVideoWithTextSection extends Struct.ComponentSchema { + collectionName: 'components_sections_video_with_text_sections'; + info: { + displayName: 'Video With Text Section'; + icon: 'play'; + }; + attributes: { + body: Schema.Attribute.RichText & Schema.Attribute.Required; + embed_html: Schema.Attribute.Text & Schema.Attribute.Required; + props: Schema.Attribute.Component<'sections.section-props', false> & + Schema.Attribute.Required; + section_title: Schema.Attribute.String & Schema.Attribute.Required; + }; +} + declare module '@strapi/strapi' { export module Public { export interface ComponentSchemas { @@ -777,6 +810,8 @@ declare module '@strapi/strapi' { 'sections.section-with-richt-heading-intro-and-cta': SectionsSectionWithRichtHeadingIntroAndCta; 'sections.text-section-with-cta': SectionsTextSectionWithCta; 'sections.two-column-section': SectionsTwoColumnSection; + 'sections.video-section': SectionsVideoSection; + 'sections.video-with-text-section': SectionsVideoWithTextSection; } } } diff --git a/strapi/types/generated/contentTypes.d.ts b/strapi/types/generated/contentTypes.d.ts index 047bef8..af101dc 100644 --- a/strapi/types/generated/contentTypes.d.ts +++ b/strapi/types/generated/contentTypes.d.ts @@ -969,6 +969,7 @@ export interface ApiPagePartnerAndProductPagePartnerAndProduct extends Struct.CollectionTypeSchema { collectionName: 'page_partner_and_products'; info: { + description: ''; displayName: 'Page Partner & Product'; pluralName: 'page-partner-and-products'; singularName: 'page-partner-and-product'; @@ -1021,6 +1022,8 @@ export interface ApiPagePartnerAndProductPagePartnerAndProduct 'relations.white-paper-section', 'relations.quotes-relation', 'relations.calendly-section', + 'sections.video-section', + 'sections.video-with-text-section', ] > & Schema.Attribute.SetPluginOptions<{