diff --git a/src/components/FooterFull.tsx b/src/components/FooterFull.tsx index c84cafb3..807af0ef 100644 --- a/src/components/FooterFull.tsx +++ b/src/components/FooterFull.tsx @@ -1,6 +1,7 @@ import styled from 'styled-components' import { mqs } from '@src/breakpoints' +import { type FooterFragment } from '@src/generated/graphqlDirectus' import { BasicFooter, MinAltFooter } from './BasicFooter' import { FooterNav } from './FooterNav' @@ -21,11 +22,13 @@ export enum HeaderVariant { } export function FullFooter({ + footerData, className, variant = FooterVariant.withNav, }: { className?: string variant?: FooterVariant + footerData: Nullable }) { if (variant === FooterVariant.none) { return null @@ -55,7 +58,7 @@ export function FullFooter({ role="contentinfo" className="relative z-20 flex flex-col gap-y-xxxlarge pt-xxxxlarge" > - {showNav && } + {showNav && } diff --git a/src/components/FooterNav.tsx b/src/components/FooterNav.tsx index 96c7a826..ab6c86f2 100644 --- a/src/components/FooterNav.tsx +++ b/src/components/FooterNav.tsx @@ -1,115 +1,16 @@ -import { type ComponentProps, type ReactNode } from 'react' - import Link from 'next/link' import styled from 'styled-components' -import { type ReadonlyDeep } from 'type-fest' import { mqs } from '@src/breakpoints' -import { DISCORD_LINK } from '@src/consts' +import { + type FooterFragment, + type FooterLinkFragment, +} from '@src/generated/graphqlDirectus' import { FullPageWidth } from './layout/LayoutHelpers' import { NewsletterSignupForm } from './NewsletterSignupForm' -type NavItemT = { - heading: ReactNode - links: ComponentProps[] -} - -const navItems = [ - { - heading: 'Product', - links: [ - { - children: 'Login', - href: 'https://app.plural.sh/login', - target: '_blank', - }, - { - children: 'GitHub', - href: 'https://github.com/pluralsh/plural', - target: '_blank', - }, - { - children: 'Pricing', - href: '/pricing', - }, - { - children: 'Support', - href: '/contact', - }, - ], - }, - { - heading: 'Company', - links: [ - { - children: 'About', - href: '/about', - }, - { - children: 'Careers', - href: '/careers', - }, - { - children: 'In the news', - href: 'https://www.plural.sh/blog', - target: '_blank', - }, - ], - }, - { - heading: 'Resources', - links: [ - { - children: 'Docs', - href: 'https://docs.plural.sh', - target: '_blank', - }, - { - children: 'Blog', - href: 'https://www.plural.sh/blog', - target: '_blank', - }, - { - children: 'Releases', - href: 'https://github.com/pluralsh/plural/releases', - target: '_blank', - }, - // { - // children: 'Security & Trust Portal', - // href: 'https://github.com/pluralsh/plural/releases', - // target: '_blank', - // }, - ] as const, - }, - { - heading: 'Contact', - links: [ - { - children: 'Discord', - href: DISCORD_LINK, - target: '_blank', - }, - { - children: 'Twitter', - href: 'https://www.twitter.com/plural_sh', - target: '_blank', - }, - { - children: 'LinkedIn', - href: 'https://www.linkedin.com/company/pluralsh', - target: '_blank', - }, - { - children: 'Email', - href: '/contact', - target: '_blank', - }, - ], - }, -] as const satisfies ReadonlyDeep - const NavSections = styled.ul(({ theme }) => ({ ...theme.partials.reset.list, display: 'grid', @@ -149,6 +50,7 @@ const NavItems = styled.ul(({ theme }) => ({ flexDirection: 'column', gap: theme.spacing.xsmall, })) + const NavItem = styled.li(({ theme }) => ({ ...theme.partials.reset.li, margin: 0, @@ -166,29 +68,83 @@ export const FooterNavLink = styled(Link)(({ theme }) => ({ }, })) -export const FooterNav = styled(({ ...props }: ComponentProps<'div'>) => ( -
- - - {navItems.map((navItem) => ( - - {navItem.heading} - - {navItem.links.map((link) => ( - - - - ))} - +export function FooterNav({ + footerData, +}: { + footerData: Nullable +}) { + if (!footerData) return null + + return ( + + + + + + + + + + + + + ) +} + +const sanitizeFooterLinks = ( + links: Nullable< + Nullable<{ footer_links_id?: Nullable }>[] + > +) => + links + ?.filter((link): link is { footer_links_id: FooterLinkFragment } => !!link) + .map((link) => ({ + name: link.footer_links_id?.name ?? '', + url: link.footer_links_id?.url ?? '', + })) ?? [] + +function FooterColumn({ + title, + links, +}: { + title: Nullable + links: { name: string; url: string }[] +}) { + if (!title) return null + + return ( + + {title} + + {links?.map((link) => ( + + + {link?.name} + + ))} - - - - - -
-))(({ theme }) => { + + + ) +} + +const FooterNavWrapSC = styled.div(({ theme }) => { const outlineOffset = -4 return { diff --git a/src/components/PrimaryPage.tsx b/src/components/PrimaryPage.tsx index 28457b55..b9aa463d 100644 --- a/src/components/PrimaryPage.tsx +++ b/src/components/PrimaryPage.tsx @@ -51,7 +51,10 @@ export default function PrimaryPage({ /> {children} - + diff --git a/src/generated/graphqlDirectus.ts b/src/generated/graphqlDirectus.ts index e8f486c6..5732683c 100644 --- a/src/generated/graphqlDirectus.ts +++ b/src/generated/graphqlDirectus.ts @@ -12639,12 +12639,16 @@ export type OurImpactComponentFragment = { __typename?: 'our_impact', top_left_m export type QuoteCarouselComponentFragment = { __typename?: 'quote_carousel', title?: string | null, quotes?: { __typename?: 'quote_lists', slug: string, items?: Array<{ __typename?: 'quote_lists_items', item?: { __typename?: 'quotes', id: string, quote?: string | null, author_text?: string | null } | null } | null> | null } | null }; -export type GlobalDataFragment = { __typename?: 'Query', site_settings?: { __typename?: 'site_settings', og_description?: string | null, og_image?: { __typename?: 'directus_files', id: string, title?: string | null, description?: string | null, tags?: any | null, filename_disk?: string | null, filename_download: string, metadata?: any | null, type?: string | null, filesize?: any | null } | null } | null, solutions_pages: Array<{ __typename?: 'solutions_pages', id: string, slug: string, category?: string | null, dropdown_icon?: string | null, dropdown_title?: string | null }>, product_pages: Array<{ __typename?: 'product_pages', id: string, slug: string, dropdown_icon?: string | null, dropdown_title?: string | null, dropdown_description?: string | null }>, resource_pages: Array<{ __typename?: 'resource_pages', id: string, external: boolean, slug: string, url?: string | null, dropdown_title?: string | null }> }; +export type GlobalDataFragment = { __typename?: 'Query', site_settings?: { __typename?: 'site_settings', og_description?: string | null, og_image?: { __typename?: 'directus_files', id: string, title?: string | null, description?: string | null, tags?: any | null, filename_disk?: string | null, filename_download: string, metadata?: any | null, type?: string | null, filesize?: any | null } | null } | null, solutions_pages: Array<{ __typename?: 'solutions_pages', id: string, slug: string, category?: string | null, dropdown_icon?: string | null, dropdown_title?: string | null }>, product_pages: Array<{ __typename?: 'product_pages', id: string, slug: string, dropdown_icon?: string | null, dropdown_title?: string | null, dropdown_description?: string | null }>, resource_pages: Array<{ __typename?: 'resource_pages', id: string, external: boolean, slug: string, url?: string | null, dropdown_title?: string | null }>, footer?: { __typename?: 'footer', column_1_title?: string | null, column_2_title?: string | null, column_3_title?: string | null, column_4_title?: string | null, column_1_links?: Array<{ __typename?: 'footer_footer_links', footer_links_id?: { __typename?: 'footer_links', name: string, url: string } | null } | null> | null, column_2_links?: Array<{ __typename?: 'footer_footer_links_1', footer_links_id?: { __typename?: 'footer_links', name: string, url: string } | null } | null> | null, column_3_links?: Array<{ __typename?: 'footer_footer_links_2', footer_links_id?: { __typename?: 'footer_links', name: string, url: string } | null } | null> | null, column_4_links?: Array<{ __typename?: 'footer_footer_links_3', footer_links_id?: { __typename?: 'footer_links', name: string, url: string } | null } | null> | null } | null }; + +export type FooterFragment = { __typename?: 'footer', column_1_title?: string | null, column_2_title?: string | null, column_3_title?: string | null, column_4_title?: string | null, column_1_links?: Array<{ __typename?: 'footer_footer_links', footer_links_id?: { __typename?: 'footer_links', name: string, url: string } | null } | null> | null, column_2_links?: Array<{ __typename?: 'footer_footer_links_1', footer_links_id?: { __typename?: 'footer_links', name: string, url: string } | null } | null> | null, column_3_links?: Array<{ __typename?: 'footer_footer_links_2', footer_links_id?: { __typename?: 'footer_links', name: string, url: string } | null } | null> | null, column_4_links?: Array<{ __typename?: 'footer_footer_links_3', footer_links_id?: { __typename?: 'footer_links', name: string, url: string } | null } | null> | null }; + +export type FooterLinkFragment = { __typename?: 'footer_links', name: string, url: string }; export type GetGlobalDataQueryVariables = Exact<{ [key: string]: never; }>; -export type GetGlobalDataQuery = { __typename?: 'Query', site_settings?: { __typename?: 'site_settings', og_description?: string | null, og_image?: { __typename?: 'directus_files', id: string, title?: string | null, description?: string | null, tags?: any | null, filename_disk?: string | null, filename_download: string, metadata?: any | null, type?: string | null, filesize?: any | null } | null } | null, solutions_pages: Array<{ __typename?: 'solutions_pages', id: string, slug: string, category?: string | null, dropdown_icon?: string | null, dropdown_title?: string | null }>, product_pages: Array<{ __typename?: 'product_pages', id: string, slug: string, dropdown_icon?: string | null, dropdown_title?: string | null, dropdown_description?: string | null }>, resource_pages: Array<{ __typename?: 'resource_pages', id: string, external: boolean, slug: string, url?: string | null, dropdown_title?: string | null }> }; +export type GetGlobalDataQuery = { __typename?: 'Query', site_settings?: { __typename?: 'site_settings', og_description?: string | null, og_image?: { __typename?: 'directus_files', id: string, title?: string | null, description?: string | null, tags?: any | null, filename_disk?: string | null, filename_download: string, metadata?: any | null, type?: string | null, filesize?: any | null } | null } | null, solutions_pages: Array<{ __typename?: 'solutions_pages', id: string, slug: string, category?: string | null, dropdown_icon?: string | null, dropdown_title?: string | null }>, product_pages: Array<{ __typename?: 'product_pages', id: string, slug: string, dropdown_icon?: string | null, dropdown_title?: string | null, dropdown_description?: string | null }>, resource_pages: Array<{ __typename?: 'resource_pages', id: string, external: boolean, slug: string, url?: string | null, dropdown_title?: string | null }>, footer?: { __typename?: 'footer', column_1_title?: string | null, column_2_title?: string | null, column_3_title?: string | null, column_4_title?: string | null, column_1_links?: Array<{ __typename?: 'footer_footer_links', footer_links_id?: { __typename?: 'footer_links', name: string, url: string } | null } | null> | null, column_2_links?: Array<{ __typename?: 'footer_footer_links_1', footer_links_id?: { __typename?: 'footer_links', name: string, url: string } | null } | null> | null, column_3_links?: Array<{ __typename?: 'footer_footer_links_2', footer_links_id?: { __typename?: 'footer_links', name: string, url: string } | null } | null> | null, column_4_links?: Array<{ __typename?: 'footer_footer_links_3', footer_links_id?: { __typename?: 'footer_links', name: string, url: string } | null } | null> | null } | null }; export type MinJobListingFragment = { __typename?: 'job_listings', id: string, slug: string, job_title?: string | null, department?: string | null, tags?: any | null, location?: string | null, status?: string | null }; @@ -13104,6 +13108,40 @@ export const ResourcePageTinyFragmentDoc = gql` dropdown_title } `; +export const FooterLinkFragmentDoc = gql` + fragment FooterLink on footer_links { + name + url +} + `; +export const FooterFragmentDoc = gql` + fragment Footer on footer { + column_1_title + column_1_links { + footer_links_id { + ...FooterLink + } + } + column_2_title + column_2_links { + footer_links_id { + ...FooterLink + } + } + column_3_title + column_3_links { + footer_links_id { + ...FooterLink + } + } + column_4_title + column_4_links { + footer_links_id { + ...FooterLink + } + } +} + ${FooterLinkFragmentDoc}`; export const GlobalDataFragmentDoc = gql` fragment GlobalData on Query { site_settings { @@ -13118,11 +13156,15 @@ export const GlobalDataFragmentDoc = gql` resource_pages(filter: {status: {_neq: "hidden"}}) { ...ResourcePageTiny } + footer { + ...Footer + } } ${SiteSettingsFragmentDoc} ${SolutionPageTinyFragmentDoc} ${ProductPageTinyFragmentDoc} -${ResourcePageTinyFragmentDoc}`; +${ResourcePageTinyFragmentDoc} +${FooterFragmentDoc}`; export const MinJobListingFragmentDoc = gql` fragment MinJobListing on job_listings { id diff --git a/src/graph/directus/globalData.graphql b/src/graph/directus/globalData.graphql index efc8cd99..57771df1 100644 --- a/src/graph/directus/globalData.graphql +++ b/src/graph/directus/globalData.graphql @@ -11,6 +11,41 @@ fragment GlobalData on Query { resource_pages(filter: { status: { _neq: "hidden" } }) { ...ResourcePageTiny } + footer { + ...Footer + } +} + +fragment Footer on footer { + column_1_title + column_1_links { + footer_links_id { + ...FooterLink + } + } + column_2_title + column_2_links { + footer_links_id { + ...FooterLink + } + } + column_3_title + column_3_links { + footer_links_id { + ...FooterLink + } + } + column_4_title + column_4_links { + footer_links_id { + ...FooterLink + } + } +} + +fragment FooterLink on footer_links { + name + url } query GetGlobalData { diff --git a/src/utils/getGlobalProps.tsx b/src/utils/getGlobalProps.tsx index a91fbb10..ea1eec6c 100644 --- a/src/utils/getGlobalProps.tsx +++ b/src/utils/getGlobalProps.tsx @@ -41,6 +41,7 @@ async function getGlobalProps() { return { siteSettings, + footerData: data.footer, swrConfig: { fallback: swrFallback, },