mirror of
https://github.com/vercel/commerce.git
synced 2025-05-18 23:46:58 +00:00
v1 convert to Agility
This commit is contained in:
parent
742ac5786e
commit
2dd8d59ae7
23
components/agility-global/AgilityPage.tsx
Normal file
23
components/agility-global/AgilityPage.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import pageTemplates from "components/agility-pageTemplates"
|
||||||
|
|
||||||
|
const AgilityPage = (props:any) => {
|
||||||
|
|
||||||
|
if (!props || !props.pageTemplateName) {
|
||||||
|
console.error(`Page object or template was not found.`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let AgilityPageTemplate = pageTemplates(props.pageTemplateName)
|
||||||
|
if (! AgilityPageTemplate) {
|
||||||
|
console.error(`${props.pageTemplateName} not found.`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AgilityPageTemplate {...props} />
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AgilityPage
|
35
components/agility-global/ContentZone.js
Normal file
35
components/agility-global/ContentZone.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import moduleComponents from "components/agility-modules"
|
||||||
|
|
||||||
|
|
||||||
|
function ContentZone({ name, page, dynamicPageItem }) {
|
||||||
|
function RenderModules() {
|
||||||
|
|
||||||
|
if (!page) return null
|
||||||
|
|
||||||
|
let modules = page.zones[name];
|
||||||
|
|
||||||
|
const modulesToRender = modules.map(m => {
|
||||||
|
|
||||||
|
const AgilityModule = moduleComponents(m.moduleName)
|
||||||
|
|
||||||
|
if (AgilityModule) {
|
||||||
|
return <AgilityModule key={m.item.contentID} page={page} dynamicPageItem={dynamicPageItem} {...m.item} />
|
||||||
|
} else {
|
||||||
|
console.error(`React Component for ${m.moduleName} was not found in the Agility Modules list.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
return modulesToRender;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<RenderModules />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ContentZone
|
75
components/agility-global/GlobalFooter.js
Normal file
75
components/agility-global/GlobalFooter.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import React, { Component, useState } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
import {expandLinkedList} from "@agility/utils"
|
||||||
|
|
||||||
|
const GlobalFooter = (props) => {
|
||||||
|
const { globalFooterProps } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>FOOTER</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalFooter.getCustomInitialProps = async function ({agility, languageCode, channelName}) {
|
||||||
|
|
||||||
|
const api = agility;
|
||||||
|
|
||||||
|
let contentItem = null;
|
||||||
|
|
||||||
|
//hack
|
||||||
|
return {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
//get the global footer
|
||||||
|
let contentItemList = await api.getContentList({
|
||||||
|
referenceName: "globalfooter",
|
||||||
|
languageCode: languageCode
|
||||||
|
});
|
||||||
|
|
||||||
|
if (contentItemList?.length > 0) {
|
||||||
|
contentItem = contentItemList[0];
|
||||||
|
|
||||||
|
//resolve the links...
|
||||||
|
contentItem = await expandLinkedList({ agility, contentItem, languageCode,
|
||||||
|
fieldName: "column2Links",
|
||||||
|
sortIDField: "column2SortIDs"
|
||||||
|
})
|
||||||
|
|
||||||
|
contentItem = await expandLinkedList({ agility, contentItem, languageCode,
|
||||||
|
fieldName: "column3Links",
|
||||||
|
sortIDField: "column3SortIDs"
|
||||||
|
})
|
||||||
|
|
||||||
|
contentItem = await expandLinkedList({ agility, contentItem, languageCode,
|
||||||
|
fieldName: "column4Links",
|
||||||
|
sortIDField: "column4SortIDs"
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
if (console) console.error("Could not load global footer item.", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
//return a clean object...
|
||||||
|
return {
|
||||||
|
siteName: contentItem.fields.siteName,
|
||||||
|
siteDescription: contentItem.fields.siteDescription,
|
||||||
|
column2Title: contentItem.fields.column2Title,
|
||||||
|
column3Title: contentItem.fields.column3Title,
|
||||||
|
column4Title: contentItem.fields.column4Title,
|
||||||
|
facebookURL: contentItem.fields.facebookURL,
|
||||||
|
twitterURL: contentItem.fields.twitterURL,
|
||||||
|
youTubeURL: contentItem.fields.youTubeURL,
|
||||||
|
column2Links: contentItem.fields.column2Links.map(link => link.fields.link),
|
||||||
|
column3Links: contentItem.fields.column3Links.map(link => link.fields.link),
|
||||||
|
column4Links: contentItem.fields.column4Links.map(link => link.fields.link),
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default GlobalFooter
|
73
components/agility-global/GlobalHeader.js
Normal file
73
components/agility-global/GlobalHeader.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import React, { Component, useState } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
|
||||||
|
const GlobalHeader = (props) => {
|
||||||
|
const { globalHeaderProps, sitemapNode, page } = props;
|
||||||
|
|
||||||
|
const globalHeaderItem = globalHeaderProps.contentItem;
|
||||||
|
let siteName = globalHeaderItem?.fields.siteName || "Agility Starter 2020"
|
||||||
|
let logo = globalHeaderItem?.fields.logo || nulll
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>HEADER</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalHeader.getCustomInitialProps = async function (props) {
|
||||||
|
|
||||||
|
const api = props.agility;
|
||||||
|
const languageCode = props.languageCode;
|
||||||
|
const channelName = props.channelName;
|
||||||
|
let contentItem = null;
|
||||||
|
let links = [];
|
||||||
|
|
||||||
|
//hack
|
||||||
|
return {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
//get the global header
|
||||||
|
let contentItemList = await api.getContentList({
|
||||||
|
referenceName: "globalheader",
|
||||||
|
languageCode: languageCode
|
||||||
|
});
|
||||||
|
|
||||||
|
if (contentItemList && contentItemList.length) {
|
||||||
|
contentItem = contentItemList[0];
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (console) console.error("Could not load global header item.", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
//get the nested sitemap
|
||||||
|
let sitemap = await api.getSitemapNested({
|
||||||
|
channelName: channelName,
|
||||||
|
languageCode: languageCode,
|
||||||
|
});
|
||||||
|
|
||||||
|
//grab the top level links that are visible on menu
|
||||||
|
links = sitemap
|
||||||
|
.filter(node => node.visible.menu)
|
||||||
|
.map(node => {
|
||||||
|
return {
|
||||||
|
text: node.menuText || node.title,
|
||||||
|
path: node.path
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
if (console) console.error("Could not load nested sitemap.", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
contentItem,
|
||||||
|
links
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default GlobalHeader
|
62
components/agility-global/Layout.js
Normal file
62
components/agility-global/Layout.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import PreviewBar from './PreviewBar'
|
||||||
|
import GlobalHeader from './GlobalHeader'
|
||||||
|
import GlobalFooter from './GlobalFooter'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import Head from 'next/head'
|
||||||
|
import dynamic from 'next/dynamic'
|
||||||
|
import tw from "twin.macro"
|
||||||
|
|
||||||
|
const MainElem = tw.main`p-8`;
|
||||||
|
|
||||||
|
import AnimationRevealPage from "helpers/AnimationRevealPage"
|
||||||
|
import Error from 'next/error'
|
||||||
|
|
||||||
|
function Layout(props) {
|
||||||
|
const { page, sitemapNode, dynamicPageItem, notFound } = props
|
||||||
|
|
||||||
|
// If the page is not yet generated, this will be displayed
|
||||||
|
// initially until getStaticProps() finishes running
|
||||||
|
const router = useRouter()
|
||||||
|
if (router.isFallback) {
|
||||||
|
return <div>Loading page...</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notFound === true) {
|
||||||
|
return <Error statusCode="404" />
|
||||||
|
}
|
||||||
|
|
||||||
|
const AgilityPageTemplate = dynamic(() => import('components/agility-pageTemplates/' + props.pageTemplateName));
|
||||||
|
|
||||||
|
if (dynamicPageItem?.seo?.metaDescription) {
|
||||||
|
page.seo.metaDescription = dynamicPageItem.seo.metaDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>{sitemapNode?.title} - Agility CMS Sample Blog</title>
|
||||||
|
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
|
||||||
|
<meta name="description" content={page.seo.metaDescription} />
|
||||||
|
<meta name="generator" content="Agility CMS" />
|
||||||
|
<meta name="agility_timestamp" content={new Date().toLocaleString()} />
|
||||||
|
{dynamicPageItem?.seo?.ogImage &&
|
||||||
|
<meta property="og:image" content={dynamicPageItem.seo.ogImage} />
|
||||||
|
}
|
||||||
|
<link rel="stylesheet" href="/prose.css" />
|
||||||
|
|
||||||
|
</Head>
|
||||||
|
<PreviewBar {...props} />
|
||||||
|
|
||||||
|
<MainElem>
|
||||||
|
{/* <AnimationRevealPage disabled> */}
|
||||||
|
<GlobalHeader {...props} />
|
||||||
|
<AgilityPageTemplate {...props} />
|
||||||
|
<GlobalFooter {...props} />
|
||||||
|
{/* </AnimationRevealPage> */}
|
||||||
|
</MainElem>
|
||||||
|
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layout
|
60
components/agility-global/PreviewBar.js
Normal file
60
components/agility-global/PreviewBar.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const PreviewBar = ({ isPreview, isDevelopmentMode }) => {
|
||||||
|
|
||||||
|
if (isPreview && !isDevelopmentMode) {
|
||||||
|
return (
|
||||||
|
<div className="agility-preview-bar">
|
||||||
|
<img className="agility-preview-bar__logo" src="https://media.agilitycms.com/preview-bar/2018-11/agility-logo-preview-bar-1.png" alt="Powered by Agility CMS" />
|
||||||
|
<span className="agility-preview-bar__text">You are viewing the latest changes in <strong>Preview Mode</strong>.</span>
|
||||||
|
<div>
|
||||||
|
<button className="agility-preview-bar__btn agility-preview-bar__btn-share" title="Click to generate a share-able link" onClick={getPreviewLink}>Share</button>
|
||||||
|
<button className="agility-preview-bar__btn" title="Click to exit preview" onClick={exitPreview}>Exit Preview</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
} else if(isDevelopmentMode) {
|
||||||
|
return (
|
||||||
|
<div className="agility-preview-bar">
|
||||||
|
<img className="agility-preview-bar__logo" src="https://media.agilitycms.com/preview-bar/2018-11/agility-logo-preview-bar-1.png" alt="Powered by Agility CMS" />
|
||||||
|
<span className="agility-preview-bar__text">You are viewing the latest changes in <strong>Development Mode</strong>.</span>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const exitPreview = () => {
|
||||||
|
const exit = confirm("Would you like to exit Preview Mode?");
|
||||||
|
if (exit === true) {
|
||||||
|
window.location = `/api/exitPreview?slug=${window.location.pathname}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPreviewLink = () => {
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.onload = function () {
|
||||||
|
|
||||||
|
// Process our return data
|
||||||
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
|
// What do when the request is successful
|
||||||
|
|
||||||
|
const previewKey = xhr.responseText;
|
||||||
|
const previewLink = `${window.location.pathname}?agilitypreviewkey=${escape(previewKey)}`;
|
||||||
|
|
||||||
|
prompt("To share this page in preview mode with others, copy the link below:", previewLink);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// What do when the request fails
|
||||||
|
alert('Could not generate Preview Link. This indicates a problem with the API route that generates a Preview Link.')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create and send a GET request
|
||||||
|
xhr.open('GET', '/api/generatePreviewKey');
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PreviewBar;
|
35
components/agility-modules/BestsellingProducts.tsx
Normal file
35
components/agility-modules/BestsellingProducts.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import React, { FC } from 'react'
|
||||||
|
|
||||||
|
import { ProductCard } from '@components/product'
|
||||||
|
import { Grid, Marquee, Hero } from '@components/ui'
|
||||||
|
|
||||||
|
interface Fields {
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
fields: Fields,
|
||||||
|
customData: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const BestsellingProducts:FC<Props> = ({fields, customData}) => {
|
||||||
|
|
||||||
|
const bestSelling = customData.bestSelling
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Marquee variant="secondary">
|
||||||
|
{bestSelling.slice(0, 12).map(({ node }:any) => (
|
||||||
|
<ProductCard
|
||||||
|
key={node.path}
|
||||||
|
product={node}
|
||||||
|
variant="slim"
|
||||||
|
imgWidth={320}
|
||||||
|
imgHeight={320}
|
||||||
|
imgLayout="fixed"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Marquee>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BestsellingProducts
|
||||||
|
|
31
components/agility-modules/FeaturedProducts.tsx
Normal file
31
components/agility-modules/FeaturedProducts.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { FC } from "react"
|
||||||
|
import { Grid, Marquee, Hero } from '@components/ui'
|
||||||
|
import { ProductCard } from '@components/product'
|
||||||
|
|
||||||
|
interface Fields {
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
fields: Fields,
|
||||||
|
customData: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const FeaturedProducts:FC<Props> = ({fields, customData}) => {
|
||||||
|
const featured:any = customData.featured
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid layout="B">
|
||||||
|
{featured.map(({ node }:any, i:number) => (
|
||||||
|
<ProductCard
|
||||||
|
key={node.path}
|
||||||
|
product={node}
|
||||||
|
imgWidth={i === 1 ? 1080 : 540}
|
||||||
|
imgHeight={i === 1 ? 1080 : 540}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FeaturedProducts
|
||||||
|
|
29
components/agility-modules/Hero.tsx
Normal file
29
components/agility-modules/Hero.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React, { FC } from 'react'
|
||||||
|
import { Hero } from '@components/ui'
|
||||||
|
import * as AgilityTypes from "@agility/types"
|
||||||
|
|
||||||
|
|
||||||
|
interface Fields {
|
||||||
|
title:string,
|
||||||
|
description:string
|
||||||
|
cTA?:AgilityTypes.URLField
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
fields: Fields
|
||||||
|
}
|
||||||
|
|
||||||
|
const HeroModule:FC<Props> = ({fields}) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Hero
|
||||||
|
headline={fields.title}
|
||||||
|
description={fields.description}
|
||||||
|
linkText={fields.cTA?.text}
|
||||||
|
linkUrl={fields.cTA?.href}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HeroModule
|
||||||
|
|
23
components/agility-modules/ProductDetails.tsx
Normal file
23
components/agility-modules/ProductDetails.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import React, { FC } from 'react'
|
||||||
|
import { Hero } from '@components/ui'
|
||||||
|
import * as AgilityTypes from "@agility/types"
|
||||||
|
import { GetProductResult } from '@framework/api/operations/get-product'
|
||||||
|
import { ProductView } from '@components/product'
|
||||||
|
|
||||||
|
interface Fields {
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
fields: Fields,
|
||||||
|
dynamicPageItem:any
|
||||||
|
}
|
||||||
|
|
||||||
|
const HeroModule:FC<Props> = ({fields, dynamicPageItem}) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProductView product={dynamicPageItem} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HeroModule
|
||||||
|
|
8
components/agility-modules/ProductListing.tsx
Normal file
8
components/agility-modules/ProductListing.tsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
const ProductListing = () => {
|
||||||
|
return (
|
||||||
|
<section>ProductListing</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProductListing
|
||||||
|
|
22
components/agility-modules/RichTextArea.tsx
Normal file
22
components/agility-modules/RichTextArea.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React, {FC} from 'react';
|
||||||
|
import { Text, Container } from '@components/ui'
|
||||||
|
|
||||||
|
interface Fields {
|
||||||
|
textblob:string,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
fields: Fields
|
||||||
|
}
|
||||||
|
|
||||||
|
const RichTextArea:FC<Props> = ({fields}) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Text className="prose prose-sm sm:prose lg:prose-lg xl:prose-xl" html={fields.textblob} />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RichTextArea
|
30
components/agility-modules/index.ts
Normal file
30
components/agility-modules/index.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import {FC} from "react"
|
||||||
|
import * as AgilityTypes from "@agility/types"
|
||||||
|
import RichTextArea from "./RichTextArea"
|
||||||
|
import BestsellingProducts from "./BestsellingProducts"
|
||||||
|
import ProductDetails from "./ProductDetails"
|
||||||
|
import FeaturedProducts from "./FeaturedProducts"
|
||||||
|
import ProductListing from "./ProductListing"
|
||||||
|
import Hero from "./Hero"
|
||||||
|
|
||||||
|
|
||||||
|
const allModules =[
|
||||||
|
{ name: "RichTextArea", module:RichTextArea },
|
||||||
|
{ name: "BestsellingProducts", module: BestsellingProducts },
|
||||||
|
{ name: "FeaturedProducts", module: FeaturedProducts},
|
||||||
|
{ name: "ProductListing", module: ProductListing},
|
||||||
|
{ name: "Hero", module: Hero},
|
||||||
|
{ name: "ProductDetails", module: ProductDetails }
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the component for a module.
|
||||||
|
* @param moduleName
|
||||||
|
*/
|
||||||
|
const getModule = (moduleName:string):any | null => {
|
||||||
|
const obj = allModules.find(m => m.name.toLowerCase() === moduleName.toLowerCase())
|
||||||
|
if (!obj) return null
|
||||||
|
return obj.module
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getModule
|
14
components/agility-pageTemplates/MainTemplate.tsx
Normal file
14
components/agility-pageTemplates/MainTemplate.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import ContentZone from 'components/agility-global/ContentZone'
|
||||||
|
|
||||||
|
const MainTemplate = (props:any) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="one-column-template">
|
||||||
|
<ContentZone name='MainContentZone' {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MainTemplate;
|
18
components/agility-pageTemplates/index.ts
Normal file
18
components/agility-pageTemplates/index.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import MainTemplate from "./MainTemplate"
|
||||||
|
|
||||||
|
interface TemplateObj {
|
||||||
|
name:string,
|
||||||
|
template:any
|
||||||
|
}
|
||||||
|
|
||||||
|
const allTemplates:[TemplateObj] =[
|
||||||
|
{ name: "MainTemplate", template:MainTemplate }
|
||||||
|
]
|
||||||
|
|
||||||
|
const getPageTemplate = (templateName:string):any => {
|
||||||
|
const obj = allTemplates.find(m => m.name.toLowerCase() === templateName.toLowerCase())
|
||||||
|
if (! obj) return null
|
||||||
|
return obj?.template
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getPageTemplate
|
@ -6,10 +6,13 @@ import Link from 'next/link'
|
|||||||
interface Props {
|
interface Props {
|
||||||
className?: string
|
className?: string
|
||||||
headline: string
|
headline: string
|
||||||
description: string
|
description: string,
|
||||||
|
linkText?: string,
|
||||||
|
linkUrl?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const Hero: FC<Props> = ({ headline, description }) => {
|
const Hero: FC<Props> = ({ headline, description, linkText, linkUrl }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-black">
|
<div className="bg-black">
|
||||||
<Container>
|
<Container>
|
||||||
@ -21,12 +24,14 @@ const Hero: FC<Props> = ({ headline, description }) => {
|
|||||||
<p className="mt-5 text-xl leading-7 text-accent-2 text-white">
|
<p className="mt-5 text-xl leading-7 text-accent-2 text-white">
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
<Link href="/blog">
|
{ linkText && linkUrl &&
|
||||||
|
<Link href={linkUrl}>
|
||||||
<a className="text-white pt-3 font-bold hover:underline flex flex-row cursor-pointer w-max-content">
|
<a className="text-white pt-3 font-bold hover:underline flex flex-row cursor-pointer w-max-content">
|
||||||
Read it here
|
{linkText}
|
||||||
<RightArrow width="20" heigh="20" className="ml-1" />
|
<RightArrow width="20" heigh="20" className="ml-1" />
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -3,9 +3,9 @@ import s from './LoadingDots.module.css'
|
|||||||
const LoadingDots: React.FC = () => {
|
const LoadingDots: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<span className={s.root}>
|
<span className={s.root}>
|
||||||
<span />
|
<span></span>
|
||||||
<span />
|
<span></span>
|
||||||
<span />
|
<span></span>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
51
framework/agility/agility.config.js
Normal file
51
framework/agility/agility.config.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
const agilityContentSync = require("@agility/content-sync");
|
||||||
|
const agilityFileSystem = require("@agility/content-sync/src/store-interface-filesystem");
|
||||||
|
|
||||||
|
const agilityConfig = {
|
||||||
|
guid: process.env.AGILITY_GUID, //Set your guid here
|
||||||
|
fetchAPIKey: process.env.AGILITY_API_FETCH_KEY, //Set your fetch apikey here
|
||||||
|
previewAPIKey: process.env.AGILITY_API_PREVIEW_KEY, //set your preview apikey
|
||||||
|
languageCode: "en-us", //the language for your website in Agility CMS
|
||||||
|
channelName: "website", //the name of your channel in Agility CMS
|
||||||
|
securityKey: process.env.AGILITY_SECURITY_KEY, //the website security key used to validate and generate preview keys
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSyncClient = ({ isPreview, isDevelopmentMode }) => {
|
||||||
|
let cachePath = `node_modules/@agility/content-sync/cache/${
|
||||||
|
isPreview ? "preview" : "live"
|
||||||
|
}`;
|
||||||
|
|
||||||
|
if (!isDevelopmentMode) {
|
||||||
|
//we are in prod and need to use a different directory that Vercel understands
|
||||||
|
cachePath = `/tmp/agilitycache/${isPreview ? "preview" : "live"}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Agility CMS => Content cache path is ${cachePath}`);
|
||||||
|
const apiKey = isPreview
|
||||||
|
? agilityConfig.previewAPIKey
|
||||||
|
: agilityConfig.fetchAPIKey;
|
||||||
|
|
||||||
|
if (!agilityConfig.guid) {
|
||||||
|
console.log("Agility CMS => No GUID was provided.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return agilityContentSync.getSyncClient({
|
||||||
|
guid: agilityConfig.guid,
|
||||||
|
apiKey: apiKey,
|
||||||
|
isPreview: isPreview,
|
||||||
|
languages: [agilityConfig.languageCode],
|
||||||
|
channels: [agilityConfig.channelName],
|
||||||
|
store: {
|
||||||
|
interface: agilityFileSystem,
|
||||||
|
options: {
|
||||||
|
rootPath: cachePath,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
agilityConfig,
|
||||||
|
getSyncClient,
|
||||||
|
};
|
299
framework/agility/agility.node.ts
Normal file
299
framework/agility/agility.node.ts
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
import crypto from 'crypto'
|
||||||
|
import { asyncForEach } from "./utils"
|
||||||
|
|
||||||
|
import { ModuleWithInit } from "@agility/types"
|
||||||
|
|
||||||
|
//Agility API stuff
|
||||||
|
import { agilityConfig, getSyncClient } from './agility.config'
|
||||||
|
import GlobalFooter from 'components/agility-global/GlobalFooter'
|
||||||
|
import GlobalHeader from 'components/agility-global/GlobalHeader'
|
||||||
|
|
||||||
|
import moduleInitializers from "framework/module-data"
|
||||||
|
|
||||||
|
|
||||||
|
const securityKey = agilityConfig.securityKey
|
||||||
|
const channelName = agilityConfig.channelName
|
||||||
|
const languageCode = agilityConfig.languageCode
|
||||||
|
const isDevelopmentMode = process.env.NODE_ENV === "development"
|
||||||
|
|
||||||
|
interface AgilityPageProps {
|
||||||
|
sitemapNode?: any,
|
||||||
|
page?: any,
|
||||||
|
dynamicPageItem?: any,
|
||||||
|
pageTemplateName?:string|null,
|
||||||
|
globalHeaderProps?:any,
|
||||||
|
globalFooterProps?:any,
|
||||||
|
languageCode?:string|null,
|
||||||
|
channelName?:string|null,
|
||||||
|
isPreview?:boolean,
|
||||||
|
isDevelopmentMode?:boolean,
|
||||||
|
notFound?:boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAgilityPageProps = async ({ params, preview, locale }:any):Promise<AgilityPageProps> => {
|
||||||
|
|
||||||
|
let path = '/';
|
||||||
|
if (params) {
|
||||||
|
//build path by iterating through slugs
|
||||||
|
path = '';
|
||||||
|
params.slug.map((slug: string) => {
|
||||||
|
path += '/' + slug
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: use locale to determin LANGUAGECODE (pulled from config at this point...)
|
||||||
|
|
||||||
|
//determine if we are in preview mode
|
||||||
|
const isPreview:boolean = (preview || isDevelopmentMode);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const agilitySyncClient = getSyncClient({
|
||||||
|
isPreview: isPreview,
|
||||||
|
isDevelopmentMode
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//always sync to get latest
|
||||||
|
|
||||||
|
|
||||||
|
if (! agilitySyncClient) {
|
||||||
|
console.log("Agility CMS => Sync client could not be accessed.")
|
||||||
|
return {
|
||||||
|
notFound: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDevelopmentMode) {
|
||||||
|
console.log(`Agility CMS => Syncing ${isPreview ? "Preview" : "Live"} Mode`)
|
||||||
|
await agilitySyncClient.runSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Agility CMS => Getting page props for '${path}'...`);
|
||||||
|
|
||||||
|
//get sitemap
|
||||||
|
const sitemap = await agilitySyncClient.store.getSitemap({ channelName, languageCode });
|
||||||
|
|
||||||
|
if (sitemap === null) {
|
||||||
|
console.warn("No sitemap found after sync.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let pageInSitemap = sitemap[path];
|
||||||
|
let page: any = null;
|
||||||
|
let dynamicPageItem: any = null;
|
||||||
|
|
||||||
|
if (path === '/') {
|
||||||
|
let firstPagePathInSitemap = Object.keys(sitemap)[0];
|
||||||
|
pageInSitemap = sitemap[firstPagePathInSitemap];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageInSitemap) {
|
||||||
|
//get the page
|
||||||
|
page = await agilitySyncClient.store.getPage({
|
||||||
|
pageID: pageInSitemap.pageID,
|
||||||
|
languageCode: languageCode
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//Could not find page
|
||||||
|
console.warn('page [' + path + '] not found in sitemap.');
|
||||||
|
return handlePageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!page) {
|
||||||
|
console.warn('page [' + path + '] not found in getpage method.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//if there is a dynamic page content id on this page, grab it...
|
||||||
|
if (pageInSitemap.contentID > 0) {
|
||||||
|
dynamicPageItem = await agilitySyncClient.store.getContentItem({
|
||||||
|
contentID: pageInSitemap.contentID,
|
||||||
|
languageCode: languageCode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//resolve the page template
|
||||||
|
const pageTemplateName = page.templateName.replace(/[^0-9a-zA-Z]/g, '');
|
||||||
|
|
||||||
|
//resolve the modules per content zone
|
||||||
|
await asyncForEach(Object.keys(page.zones), async (zoneName: string) => {
|
||||||
|
|
||||||
|
let modules: { moduleName: string; item: any }[] = [];
|
||||||
|
|
||||||
|
//grab the modules for this content zone
|
||||||
|
const modulesForThisContentZone = page.zones[zoneName];
|
||||||
|
|
||||||
|
//loop through the zone's modules
|
||||||
|
await asyncForEach(modulesForThisContentZone, async (moduleItem: { module: string, item: any }) => {
|
||||||
|
|
||||||
|
//find the react component to use for the module
|
||||||
|
const moduleInitializer = moduleInitializers(moduleItem.module)
|
||||||
|
|
||||||
|
if (moduleInitializer) {
|
||||||
|
//resolve any additional data for the modules
|
||||||
|
|
||||||
|
//we have some additional data in the module we'll need, execute that method now, so it can be included in SSG
|
||||||
|
if (isDevelopmentMode) {
|
||||||
|
console.log(`Agility CMS => Fetching additional data via getCustomInitialProps for ${moduleItem.module}...`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleData = await moduleInitializer({
|
||||||
|
item: moduleItem.item,
|
||||||
|
agility: agilitySyncClient.store,
|
||||||
|
languageCode,
|
||||||
|
channelName,
|
||||||
|
pageInSitemap,
|
||||||
|
dynamicPageItem
|
||||||
|
});
|
||||||
|
|
||||||
|
//if we have additional module data, then add it to the module props using 'customData'
|
||||||
|
if (moduleData != null) {
|
||||||
|
moduleItem.item.customData = moduleData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modules.push({
|
||||||
|
moduleName: moduleItem.module,
|
||||||
|
item: moduleItem.item,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
//store as dictionary
|
||||||
|
page.zones[zoneName] = modules;
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
//resolve data for other shared components
|
||||||
|
const globalHeaderProps = await GlobalHeader.getCustomInitialProps({ agility: agilitySyncClient.store, languageCode: languageCode, channelName: channelName });
|
||||||
|
const globalFooterProps = await GlobalFooter.getCustomInitialProps({ agility: agilitySyncClient.store, languageCode: languageCode, channelName: channelName });
|
||||||
|
|
||||||
|
return {
|
||||||
|
sitemapNode: pageInSitemap,
|
||||||
|
page,
|
||||||
|
dynamicPageItem,
|
||||||
|
pageTemplateName,
|
||||||
|
globalHeaderProps,
|
||||||
|
globalFooterProps,
|
||||||
|
languageCode,
|
||||||
|
channelName,
|
||||||
|
isPreview,
|
||||||
|
isDevelopmentMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAgilityPaths = async () => {
|
||||||
|
|
||||||
|
console.log(`Agility CMS => Fetching sitemap for getAgilityPaths...`);
|
||||||
|
|
||||||
|
//determine if we are in preview mode
|
||||||
|
const isPreview = isDevelopmentMode;
|
||||||
|
|
||||||
|
const agilitySyncClient = getSyncClient({
|
||||||
|
isPreview,
|
||||||
|
isDevelopmentMode
|
||||||
|
});
|
||||||
|
|
||||||
|
//always sync to get latest
|
||||||
|
|
||||||
|
if (! agilitySyncClient) {
|
||||||
|
console.log("Agility CMS => Sync client could not be accessed.")
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (!isDevelopmentMode) {
|
||||||
|
console.log(`Agility CMS => Syncing ${isPreview ? "Preview" : "Live"} Mode`)
|
||||||
|
await agilitySyncClient.runSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
const sitemapFlat = await agilitySyncClient.store.getSitemap({
|
||||||
|
channelName,
|
||||||
|
languageCode
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (!sitemapFlat) {
|
||||||
|
console.warn("Agility CMS => No Site map found. Make sure your environment variables are setup correctly.")
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//returns an array of paths as a string (i.e. ['/home', '/posts'] )
|
||||||
|
const paths = Object.keys(sitemapFlat)
|
||||||
|
.filter(path => {
|
||||||
|
const sitemapNode = sitemapFlat[path]
|
||||||
|
return !sitemapNode.redirect
|
||||||
|
&& !sitemapNode.isFolder
|
||||||
|
})
|
||||||
|
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const validatePreview = async({ agilityPreviewKey, slug }: any) => {
|
||||||
|
//Validate the preview key
|
||||||
|
if (!agilityPreviewKey) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
message: `Missing agilitypreviewkey.`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//sanitize incoming key (replace spaces with '+')
|
||||||
|
if (agilityPreviewKey.indexOf(` `) > -1) {
|
||||||
|
agilityPreviewKey = agilityPreviewKey.split(` `).join(`+`);
|
||||||
|
}
|
||||||
|
|
||||||
|
//compare the preview key being used
|
||||||
|
const correctPreviewKey = generatePreviewKey();
|
||||||
|
|
||||||
|
if (agilityPreviewKey !== correctPreviewKey) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
message: `Invalid agilitypreviewkey.`
|
||||||
|
//message: `Invalid agilitypreviewkey. Incoming key is=${agilityPreviewKey} compared to=${correctPreviewKey}...`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//return success
|
||||||
|
return {
|
||||||
|
error: false,
|
||||||
|
message: null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const generatePreviewKey =() => {
|
||||||
|
//the string we want to encode
|
||||||
|
const str = `-1_${securityKey}_Preview`;
|
||||||
|
|
||||||
|
//build our byte array
|
||||||
|
let data = [];
|
||||||
|
for (var i = 0; i < str.length; ++i) {
|
||||||
|
data.push(str.charCodeAt(i));
|
||||||
|
data.push(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//convert byte array to buffer
|
||||||
|
const strBuffer = Buffer.from(data);
|
||||||
|
|
||||||
|
//encode it!
|
||||||
|
const previewKey = crypto.createHash('sha512').update(strBuffer).digest('base64');
|
||||||
|
return previewKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePageNotFound() {
|
||||||
|
return {
|
||||||
|
notFound: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getAgilityPageProps,
|
||||||
|
getAgilityPaths,
|
||||||
|
validatePreview,
|
||||||
|
generatePreviewKey
|
||||||
|
}
|
||||||
|
|
51
framework/agility/agility.sync.js
Normal file
51
framework/agility/agility.sync.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
THIS FILE IS ONLY EXECUTED LOCALLY
|
||||||
|
WHEN DOING A LOCAL SYNC ON DEMAND
|
||||||
|
IN DEVELOPMENT MODE
|
||||||
|
*/
|
||||||
|
|
||||||
|
require("dotenv").config({
|
||||||
|
path: `.env.local`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { getSyncClient } = require('./agility.config')
|
||||||
|
|
||||||
|
|
||||||
|
const runSync = async () => {
|
||||||
|
|
||||||
|
const agilitySyncClient = getSyncClient({ isPreview: true, isDevelopmentMode: true })
|
||||||
|
if (! agilitySyncClient) {
|
||||||
|
console.log("Agility CMS => Sync client could not be accessed.")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await agilitySyncClient.runSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearSync = async () => {
|
||||||
|
|
||||||
|
const agilitySyncClient = getSyncClient({ isPreview: true, isDevelopmentMode: true })
|
||||||
|
if (! agilitySyncClient) {
|
||||||
|
console.log("Agility CMS => Sync client could not be accessed.")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await agilitySyncClient.clearSync();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (process.argv[2]) {
|
||||||
|
if (process.argv[2] === "clear") {
|
||||||
|
//clear everything
|
||||||
|
return clearSync();
|
||||||
|
} else if (process.argv[2] === "sync") {
|
||||||
|
//run the sync
|
||||||
|
return runSync()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
clearSync,
|
||||||
|
runSync
|
||||||
|
}
|
24
framework/agility/types.ts
Normal file
24
framework/agility/types.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
|
||||||
|
export interface CustomInitPropsArg {
|
||||||
|
item:any,
|
||||||
|
agility:any,
|
||||||
|
languageCode:any,
|
||||||
|
channelName:any,
|
||||||
|
pageInSitemap:any,
|
||||||
|
dynamicPageItem?:any
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CustomInitProps<T> {
|
||||||
|
(props:CustomInitPropsArg): T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModuleWithInit<TProps, TInit> extends FC<TProps> {
|
||||||
|
getCustomInitialProps:CustomInitProps<TInit>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface URLField {
|
||||||
|
href:string,
|
||||||
|
target:string,
|
||||||
|
text:string
|
||||||
|
}
|
108
framework/agility/utils.js
Normal file
108
framework/agility/utils.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
const renderHTML = (html) => {
|
||||||
|
if (!html) return { __html: "" };
|
||||||
|
return { __html: cleanHTML(html) };
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanHTML = (html) => {
|
||||||
|
if (!html) return ""
|
||||||
|
|
||||||
|
//fix '~' in links in HTML
|
||||||
|
return html.replace(/href="~\//gi, 'href="/')
|
||||||
|
}
|
||||||
|
|
||||||
|
const asyncForEach = async (array, callback) => {
|
||||||
|
for (let index = 0; index < array.length; index++) {
|
||||||
|
await callback(array[index], index, array);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const expandContentList = async ({ agility, contentItems, languageCode, depth }) => {
|
||||||
|
|
||||||
|
asyncForEach(contentItems, async (contentItem) => {
|
||||||
|
await expandContentItem({agility, contentItem, languageCode, depth})
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const expandContentItem = async ({ agility, contentItem, languageCode, depth = 1 }) => {
|
||||||
|
if (!contentItem) return null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const api = agility;
|
||||||
|
|
||||||
|
if (depth > 0) {
|
||||||
|
//make this work for the .fields or the .customFields property...
|
||||||
|
let fields = contentItem.fields;
|
||||||
|
if (!fields) fields = contentItem.customFields;
|
||||||
|
|
||||||
|
for (const fieldName in fields) {
|
||||||
|
const fieldValue = fields[fieldName];
|
||||||
|
|
||||||
|
if (fieldValue.contentid > 0) {
|
||||||
|
//single linked item
|
||||||
|
const childItem = await api.getContentItem({ contentID: fieldValue.contentid, languageCode, depth: depth - 1 });
|
||||||
|
if (childItem != null) fields[fieldName] = childItem;
|
||||||
|
} else if (fieldValue.sortids && fieldValue.sortids.split) {
|
||||||
|
//multi linked item
|
||||||
|
const sortIDAry = fieldValue.sortids.split(',');
|
||||||
|
const childItems = [];
|
||||||
|
for (const childItemID of sortIDAry) {
|
||||||
|
const childItem = await api.getContentItem({ contentID: childItemID, languageCode, depth: depth - 1 });
|
||||||
|
if (childItem != null) childItems.push(childItem);
|
||||||
|
}
|
||||||
|
fields[fieldName] = childItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contentItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expandLinkedList = async ({ agility, contentItem, languageCode, fieldName, sortIDField }) => {
|
||||||
|
if (!contentItem) return null;
|
||||||
|
|
||||||
|
let fieldObj = contentItem.fields[fieldName]
|
||||||
|
if (! fieldObj) throw Error(`The field ${fieldName} was not found on the content item.`)
|
||||||
|
|
||||||
|
const referenceName = fieldObj.referencename
|
||||||
|
if (! referenceName) throw Error(`A referencename property was not found on the ${fieldName} value.`)
|
||||||
|
|
||||||
|
//grab the content list...
|
||||||
|
let listItems = await agility.getContentList({referenceName, languageCode})
|
||||||
|
if (listItems?.length > 0) {
|
||||||
|
let sortIDs = contentItem.fields[sortIDField]
|
||||||
|
if (sortIDs?.length > 0 && sortIDs?.split) {
|
||||||
|
//if we have sort ids, assemble the values in the list based on those...
|
||||||
|
const sortIDAry = sortIDs.split(',');
|
||||||
|
const sortedItems = [];
|
||||||
|
for (const idStr of sortIDAry) {
|
||||||
|
const childContentID = parseInt(idStr)
|
||||||
|
|
||||||
|
const childItemIndex = listItems.findIndex(item => item.contentID === childContentID)
|
||||||
|
if (childItemIndex >= 0) {
|
||||||
|
sortedItems.push(listItems[childItemIndex]);
|
||||||
|
listItems.splice(childItemIndex, 1)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listItems = sortedItems.concat(listItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem.fields[fieldName] = listItems;
|
||||||
|
return contentItem;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
renderHTML,
|
||||||
|
cleanHTML,
|
||||||
|
asyncForEach,
|
||||||
|
expandContentItem,
|
||||||
|
expandContentList,
|
||||||
|
expandLinkedList
|
||||||
|
}
|
@ -35,9 +35,11 @@ async function getAllPages({
|
|||||||
>('/v3/content/pages')
|
>('/v3/content/pages')
|
||||||
const pages = (data as RecursiveRequired<typeof data>) ?? []
|
const pages = (data as RecursiveRequired<typeof data>) ?? []
|
||||||
|
|
||||||
return {
|
const retPages = {
|
||||||
pages: preview ? pages : pages.filter((p) => p.is_visible),
|
pages: preview ? pages : pages.filter((p) => p.is_visible),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return retPages
|
||||||
}
|
}
|
||||||
|
|
||||||
export default getAllPages
|
export default getAllPages
|
||||||
|
55
framework/module-data/BestsellingProductsData.ts
Normal file
55
framework/module-data/BestsellingProductsData.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { getConfig } from '@framework/api'
|
||||||
|
import getAllProducts from '@framework/api/operations/get-all-products'
|
||||||
|
|
||||||
|
import rangeMap from '@lib/range-map'
|
||||||
|
|
||||||
|
const nonNullable = (v: any) => v
|
||||||
|
|
||||||
|
const BestsellingProductsInit = async function ({ item, agility, languageCode, channelName, pageInSitemap, dynamicPageItem }: any) {
|
||||||
|
//TODO: pass the locale and preview mode as props...
|
||||||
|
|
||||||
|
|
||||||
|
const locale = "en-US"
|
||||||
|
const preview = false
|
||||||
|
|
||||||
|
const config = getConfig({ locale })
|
||||||
|
|
||||||
|
// Get Best Selling Products
|
||||||
|
const { products: bestSellingProducts } = await getAllProducts({
|
||||||
|
variables: { field: 'bestSellingProducts', first: 6 },
|
||||||
|
config,
|
||||||
|
preview,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get Best Newest Products
|
||||||
|
const { products: newestProducts } = await getAllProducts({
|
||||||
|
variables: { field: 'newestProducts', first: 12 },
|
||||||
|
config,
|
||||||
|
preview,
|
||||||
|
})
|
||||||
|
|
||||||
|
// These are the products that are going to be displayed in the landing.
|
||||||
|
// We prefer to do the computation at buildtime/servertime
|
||||||
|
const { bestSelling } = (() => {
|
||||||
|
|
||||||
|
// Create a copy of products that we can mutate
|
||||||
|
const products = [...newestProducts]
|
||||||
|
// If the lists of featured and best selling products don't have enough
|
||||||
|
// products, then fill them with products from the products list, this
|
||||||
|
// is useful for new commerce sites that don't have a lot of products
|
||||||
|
return {
|
||||||
|
bestSelling: rangeMap(
|
||||||
|
6,
|
||||||
|
(i) => bestSellingProducts[i] ?? products.shift()
|
||||||
|
).filter(nonNullable),
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
return {
|
||||||
|
bestSelling
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BestsellingProductsInit
|
34
framework/module-data/FeaturedProductsData.ts
Normal file
34
framework/module-data/FeaturedProductsData.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { getConfig } from '@framework/api'
|
||||||
|
import getAllProducts from '@framework/api/operations/get-all-products'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import rangeMap from '@lib/range-map'
|
||||||
|
|
||||||
|
const nonNullable = (v: any) => v
|
||||||
|
|
||||||
|
const FeaturedProductsInit = async function ({ item, agility, languageCode, channelName, pageInSitemap, dynamicPageItem }: any) {
|
||||||
|
//TODO: pass the locale and preview mode as props...
|
||||||
|
|
||||||
|
|
||||||
|
const locale = "en-US"
|
||||||
|
const preview = false
|
||||||
|
|
||||||
|
const config = getConfig({ locale })
|
||||||
|
|
||||||
|
// Get Featured Products
|
||||||
|
const { products: featuredProducts } = await getAllProducts({
|
||||||
|
variables: { field: 'featuredProducts', first: 6 },
|
||||||
|
config,
|
||||||
|
preview,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
featured: featuredProducts
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FeaturedProductsInit
|
22
framework/module-data/index.ts
Normal file
22
framework/module-data/index.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import {FC} from "react"
|
||||||
|
import * as AgilityTypes from "@agility/types"
|
||||||
|
|
||||||
|
import BestsellingProductsData from "./BestsellingProductsData"
|
||||||
|
import FeaturedProductsData from "./FeaturedProductsData"
|
||||||
|
|
||||||
|
const allModules:any =[
|
||||||
|
{ name: "BestsellingProducts", init: BestsellingProductsData },
|
||||||
|
{ name: "FeaturedProducts", init: FeaturedProductsData}
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the data method for a module.
|
||||||
|
* @param moduleName
|
||||||
|
*/
|
||||||
|
const getInitMethod = (moduleName:string):any => {
|
||||||
|
const obj = allModules.find((m: { name: string }) => m.name.toLowerCase() === moduleName.toLowerCase())
|
||||||
|
if (!obj) return null
|
||||||
|
return obj.init
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getInitMethod
|
5
jsconfig.json
Normal file
5
jsconfig.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "."
|
||||||
|
}
|
||||||
|
}
|
@ -3,8 +3,9 @@ const bundleAnalyzer = require('@next/bundle-analyzer')({
|
|||||||
})
|
})
|
||||||
|
|
||||||
module.exports = bundleAnalyzer({
|
module.exports = bundleAnalyzer({
|
||||||
|
|
||||||
images: {
|
images: {
|
||||||
domains: ['cdn11.bigcommerce.com'],
|
domains: ['cdn11.bigcommerce.com', 'cdn.aglty.io'],
|
||||||
},
|
},
|
||||||
i18n: {
|
i18n: {
|
||||||
locales: ['en-US', 'es'],
|
locales: ['en-US', 'es'],
|
||||||
|
@ -8,7 +8,9 @@
|
|||||||
"analyze": "BUNDLE_ANALYZE=both yarn build",
|
"analyze": "BUNDLE_ANALYZE=both yarn build",
|
||||||
"find:unused": "next-unused",
|
"find:unused": "next-unused",
|
||||||
"generate": "graphql-codegen",
|
"generate": "graphql-codegen",
|
||||||
"generate:definitions": "node framework/bigcommerce/scripts/generate-definitions.js"
|
"generate:definitions": "node framework/bigcommerce/scripts/generate-definitions.js",
|
||||||
|
"cms-pull": "NODE_ENV=development && node framework/agility/agility.sync.js sync",
|
||||||
|
"cms-clear": "node framework/agility/agility.sync.js clear"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"semi": false,
|
"semi": false,
|
||||||
@ -44,6 +46,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@agility/content-sync": "^0.1.23",
|
||||||
"@reach/portal": "^0.11.2",
|
"@reach/portal": "^0.11.2",
|
||||||
"@tailwindcss/ui": "^0.6.2",
|
"@tailwindcss/ui": "^0.6.2",
|
||||||
"@vercel/fetch": "^6.1.0",
|
"@vercel/fetch": "^6.1.0",
|
||||||
|
92
pages/[...slug].tsx
Normal file
92
pages/[...slug].tsx
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import type {
|
||||||
|
GetStaticPathsContext,
|
||||||
|
GetStaticPropsContext,
|
||||||
|
InferGetStaticPropsType,
|
||||||
|
|
||||||
|
} from 'next'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import { Layout } from '@components/common'
|
||||||
|
import { missingLocaleInPages } from '@lib/usage-warns'
|
||||||
|
|
||||||
|
import { defaultPageProps } from '@lib/defaults'
|
||||||
|
|
||||||
|
import AgilityPage from "components/agility-global/AgilityPage"
|
||||||
|
|
||||||
|
import { getConfig } from '@framework/api'
|
||||||
|
import getProduct from '@framework/api/operations/get-product'
|
||||||
|
|
||||||
|
import { getAgilityPageProps, getAgilityPaths } from "framework/agility/agility.node";
|
||||||
|
import getAllProductPaths from '@framework/api/operations/get-all-product-paths'
|
||||||
|
|
||||||
|
|
||||||
|
export async function getStaticProps({ preview, params, locale }: GetStaticPropsContext<{ slug: string[] }>) {
|
||||||
|
|
||||||
|
let productCode: string | null = null
|
||||||
|
|
||||||
|
//check if this page is a product...
|
||||||
|
if (params?.slug.length === 2
|
||||||
|
&& params?.slug[0] === "product") {
|
||||||
|
productCode = params.slug[1]
|
||||||
|
params.slug[1] = "product-details"
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = await getAgilityPageProps({ preview, params, locale });
|
||||||
|
|
||||||
|
if (productCode) {
|
||||||
|
const config = getConfig({ locale })
|
||||||
|
const { product } = await getProduct({
|
||||||
|
variables: { slug: productCode },
|
||||||
|
config,
|
||||||
|
preview,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (product !== null) {
|
||||||
|
page.dynamicPageItem = product
|
||||||
|
} else {
|
||||||
|
throw new Error(`Product not found`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pages = await getAgilityPaths()
|
||||||
|
|
||||||
|
if (!page) {
|
||||||
|
// We throw to make sure this fails at build time as this is never expected to happen
|
||||||
|
throw new Error(`Page not found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: { ...defaultPageProps, pages, page },
|
||||||
|
revalidate: 60 * 60, // Every hour
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStaticPaths({ locales }: GetStaticPathsContext) {
|
||||||
|
|
||||||
|
//get the paths configured in agility
|
||||||
|
let agilityPaths = await getAgilityPaths()
|
||||||
|
|
||||||
|
//remove product/product-details from the agility paths (special details page...)
|
||||||
|
agilityPaths = agilityPaths.filter(p => p !== "/product/product-details")
|
||||||
|
|
||||||
|
//get the product paths from the commerce api
|
||||||
|
const { products } = await getAllProductPaths()
|
||||||
|
const productPaths = products.map(p => `/product${p.node.path}`)
|
||||||
|
|
||||||
|
const paths = [...agilityPaths, ...productPaths]
|
||||||
|
|
||||||
|
return {
|
||||||
|
paths,
|
||||||
|
fallback: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Pages({ page }: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AgilityPage {...page} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Pages.Layout = Layout
|
154
pages/index.tsx
154
pages/index.tsx
@ -1,152 +1,2 @@
|
|||||||
import rangeMap from '@lib/range-map'
|
//this is just a pointer to the catch-all route and logic for all CMS driven pages (i.e. even rootpage is dynamic from the CMS)
|
||||||
import { Layout } from '@components/common'
|
export { default, getStaticProps } from './[...slug]';
|
||||||
import { ProductCard } from '@components/product'
|
|
||||||
import { Grid, Marquee, Hero } from '@components/ui'
|
|
||||||
import HomeAllProductsGrid from '@components/common/HomeAllProductsGrid'
|
|
||||||
import type { GetStaticPropsContext, InferGetStaticPropsType } from 'next'
|
|
||||||
|
|
||||||
import { getConfig } from '@framework/api'
|
|
||||||
import getAllProducts from '@framework/api/operations/get-all-products'
|
|
||||||
import getSiteInfo from '@framework/api/operations/get-site-info'
|
|
||||||
import getAllPages from '@framework/api/operations/get-all-pages'
|
|
||||||
|
|
||||||
export async function getStaticProps({
|
|
||||||
preview,
|
|
||||||
locale,
|
|
||||||
}: GetStaticPropsContext) {
|
|
||||||
const config = getConfig({ locale })
|
|
||||||
|
|
||||||
// Get Featured Products
|
|
||||||
const { products: featuredProducts } = await getAllProducts({
|
|
||||||
variables: { field: 'featuredProducts', first: 6 },
|
|
||||||
config,
|
|
||||||
preview,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Get Best Selling Products
|
|
||||||
const { products: bestSellingProducts } = await getAllProducts({
|
|
||||||
variables: { field: 'bestSellingProducts', first: 6 },
|
|
||||||
config,
|
|
||||||
preview,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Get Best Newest Products
|
|
||||||
const { products: newestProducts } = await getAllProducts({
|
|
||||||
variables: { field: 'newestProducts', first: 12 },
|
|
||||||
config,
|
|
||||||
preview,
|
|
||||||
})
|
|
||||||
|
|
||||||
const { categories, brands } = await getSiteInfo({ config, preview })
|
|
||||||
const { pages } = await getAllPages({ config, preview })
|
|
||||||
|
|
||||||
// These are the products that are going to be displayed in the landing.
|
|
||||||
// We prefer to do the computation at buildtime/servertime
|
|
||||||
const { featured, bestSelling } = (() => {
|
|
||||||
// Create a copy of products that we can mutate
|
|
||||||
const products = [...newestProducts]
|
|
||||||
// If the lists of featured and best selling products don't have enough
|
|
||||||
// products, then fill them with products from the products list, this
|
|
||||||
// is useful for new commerce sites that don't have a lot of products
|
|
||||||
return {
|
|
||||||
featured: rangeMap(6, (i) => featuredProducts[i] ?? products.shift())
|
|
||||||
.filter(nonNullable)
|
|
||||||
.sort((a, b) => a.node.prices.price.value - b.node.prices.price.value)
|
|
||||||
.reverse(),
|
|
||||||
bestSelling: rangeMap(
|
|
||||||
6,
|
|
||||||
(i) => bestSellingProducts[i] ?? products.shift()
|
|
||||||
).filter(nonNullable),
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
featured,
|
|
||||||
bestSelling,
|
|
||||||
newestProducts,
|
|
||||||
categories,
|
|
||||||
brands,
|
|
||||||
pages,
|
|
||||||
},
|
|
||||||
revalidate: 14400,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const nonNullable = (v: any) => v
|
|
||||||
|
|
||||||
export default function Home({
|
|
||||||
featured,
|
|
||||||
bestSelling,
|
|
||||||
brands,
|
|
||||||
categories,
|
|
||||||
newestProducts,
|
|
||||||
}: InferGetStaticPropsType<typeof getStaticProps>) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Grid>
|
|
||||||
{featured.slice(0, 3).map(({ node }, i) => (
|
|
||||||
<ProductCard
|
|
||||||
key={node.path}
|
|
||||||
product={node}
|
|
||||||
imgWidth={i === 0 ? 1080 : 540}
|
|
||||||
imgHeight={i === 0 ? 1080 : 540}
|
|
||||||
imgPriority
|
|
||||||
imgLoading="eager"
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
<Marquee variant="secondary">
|
|
||||||
{bestSelling.slice(3, 6).map(({ node }) => (
|
|
||||||
<ProductCard
|
|
||||||
key={node.path}
|
|
||||||
product={node}
|
|
||||||
variant="slim"
|
|
||||||
imgWidth={320}
|
|
||||||
imgHeight={320}
|
|
||||||
imgLayout="fixed"
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Marquee>
|
|
||||||
<Hero
|
|
||||||
headline="Release Details: The Yeezy BOOST 350 V2 ‘Natural'"
|
|
||||||
description="
|
|
||||||
The Yeezy BOOST 350 V2 lineup continues to grow. We recently had the
|
|
||||||
‘Carbon’ iteration, and now release details have been locked in for
|
|
||||||
this ‘Natural’ joint. Revealed by Yeezy Mafia earlier this year, the
|
|
||||||
shoe was originally called ‘Abez’, which translated to ‘Tin’ in
|
|
||||||
Hebrew. It’s now undergone a name change, and will be referred to as
|
|
||||||
‘Natural’."
|
|
||||||
/>
|
|
||||||
<Grid layout="B">
|
|
||||||
{featured.slice(3, 6).map(({ node }, i) => (
|
|
||||||
<ProductCard
|
|
||||||
key={node.path}
|
|
||||||
product={node}
|
|
||||||
imgWidth={i === 1 ? 1080 : 540}
|
|
||||||
imgHeight={i === 1 ? 1080 : 540}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
<Marquee>
|
|
||||||
{bestSelling.slice(0, 3).map(({ node }) => (
|
|
||||||
<ProductCard
|
|
||||||
key={node.path}
|
|
||||||
product={node}
|
|
||||||
variant="slim"
|
|
||||||
imgWidth={320}
|
|
||||||
imgHeight={320}
|
|
||||||
imgLayout="fixed"
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Marquee>
|
|
||||||
<HomeAllProductsGrid
|
|
||||||
categories={categories}
|
|
||||||
brands={brands}
|
|
||||||
newestProducts={newestProducts}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Home.Layout = Layout
|
|
152
pagesBAK/index.tsx
Normal file
152
pagesBAK/index.tsx
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import rangeMap from '@lib/range-map'
|
||||||
|
import { Layout } from '@components/common'
|
||||||
|
import { ProductCard } from '@components/product'
|
||||||
|
import { Grid, Marquee, Hero } from '@components/ui'
|
||||||
|
import HomeAllProductsGrid from '@components/common/HomeAllProductsGrid'
|
||||||
|
import type { GetStaticPropsContext, InferGetStaticPropsType } from 'next'
|
||||||
|
|
||||||
|
import { getConfig } from '@framework/api'
|
||||||
|
import getAllProducts from '@framework/api/operations/get-all-products'
|
||||||
|
import getSiteInfo from '@framework/api/operations/get-site-info'
|
||||||
|
import getAllPages from '@framework/api/operations/get-all-pages'
|
||||||
|
|
||||||
|
export async function getStaticProps({
|
||||||
|
preview,
|
||||||
|
locale,
|
||||||
|
}: GetStaticPropsContext) {
|
||||||
|
const config = getConfig({ locale })
|
||||||
|
|
||||||
|
// Get Featured Products
|
||||||
|
const { products: featuredProducts } = await getAllProducts({
|
||||||
|
variables: { field: 'featuredProducts', first: 6 },
|
||||||
|
config,
|
||||||
|
preview,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get Best Selling Products
|
||||||
|
const { products: bestSellingProducts } = await getAllProducts({
|
||||||
|
variables: { field: 'bestSellingProducts', first: 6 },
|
||||||
|
config,
|
||||||
|
preview,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get Best Newest Products
|
||||||
|
const { products: newestProducts } = await getAllProducts({
|
||||||
|
variables: { field: 'newestProducts', first: 12 },
|
||||||
|
config,
|
||||||
|
preview,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { categories, brands } = await getSiteInfo({ config, preview })
|
||||||
|
const { pages } = await getAllPages({ config, preview })
|
||||||
|
|
||||||
|
// These are the products that are going to be displayed in the landing.
|
||||||
|
// We prefer to do the computation at buildtime/servertime
|
||||||
|
const { featured, bestSelling } = (() => {
|
||||||
|
// Create a copy of products that we can mutate
|
||||||
|
const products = [...newestProducts]
|
||||||
|
// If the lists of featured and best selling products don't have enough
|
||||||
|
// products, then fill them with products from the products list, this
|
||||||
|
// is useful for new commerce sites that don't have a lot of products
|
||||||
|
return {
|
||||||
|
featured: rangeMap(6, (i) => featuredProducts[i] ?? products.shift())
|
||||||
|
.filter(nonNullable)
|
||||||
|
.sort((a, b) => a.node.prices.price.value - b.node.prices.price.value)
|
||||||
|
.reverse(),
|
||||||
|
bestSelling: rangeMap(
|
||||||
|
6,
|
||||||
|
(i) => bestSellingProducts[i] ?? products.shift()
|
||||||
|
).filter(nonNullable),
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
featured,
|
||||||
|
bestSelling,
|
||||||
|
newestProducts,
|
||||||
|
categories,
|
||||||
|
brands,
|
||||||
|
pages,
|
||||||
|
},
|
||||||
|
revalidate: 14400,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nonNullable = (v: any) => v
|
||||||
|
|
||||||
|
export default function Home({
|
||||||
|
featured,
|
||||||
|
bestSelling,
|
||||||
|
brands,
|
||||||
|
categories,
|
||||||
|
newestProducts,
|
||||||
|
}: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Grid>
|
||||||
|
{featured.slice(0, 3).map(({ node }, i) => (
|
||||||
|
<ProductCard
|
||||||
|
key={node.path}
|
||||||
|
product={node}
|
||||||
|
imgWidth={i === 0 ? 1080 : 540}
|
||||||
|
imgHeight={i === 0 ? 1080 : 540}
|
||||||
|
imgPriority
|
||||||
|
imgLoading="eager"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
<Marquee variant="secondary">
|
||||||
|
{bestSelling.slice(3, 6).map(({ node }) => (
|
||||||
|
<ProductCard
|
||||||
|
key={node.path}
|
||||||
|
product={node}
|
||||||
|
variant="slim"
|
||||||
|
imgWidth={320}
|
||||||
|
imgHeight={320}
|
||||||
|
imgLayout="fixed"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Marquee>
|
||||||
|
<Hero
|
||||||
|
headline="Release Details: The Yeezy BOOST 350 V2 ‘Natural'"
|
||||||
|
description="
|
||||||
|
The Yeezy BOOST 350 V2 lineup continues to grow. We recently had the
|
||||||
|
‘Carbon’ iteration, and now release details have been locked in for
|
||||||
|
this ‘Natural’ joint. Revealed by Yeezy Mafia earlier this year, the
|
||||||
|
shoe was originally called ‘Abez’, which translated to ‘Tin’ in
|
||||||
|
Hebrew. It’s now undergone a name change, and will be referred to as
|
||||||
|
‘Natural’."
|
||||||
|
/>
|
||||||
|
<Grid layout="B">
|
||||||
|
{featured.slice(3, 6).map(({ node }, i) => (
|
||||||
|
<ProductCard
|
||||||
|
key={node.path}
|
||||||
|
product={node}
|
||||||
|
imgWidth={i === 1 ? 1080 : 540}
|
||||||
|
imgHeight={i === 1 ? 1080 : 540}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
<Marquee>
|
||||||
|
{bestSelling.slice(0, 3).map(({ node }) => (
|
||||||
|
<ProductCard
|
||||||
|
key={node.path}
|
||||||
|
product={node}
|
||||||
|
variant="slim"
|
||||||
|
imgWidth={320}
|
||||||
|
imgHeight={320}
|
||||||
|
imgLayout="fixed"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Marquee>
|
||||||
|
<HomeAllProductsGrid
|
||||||
|
categories={categories}
|
||||||
|
brands={brands}
|
||||||
|
newestProducts={newestProducts}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Home.Layout = Layout
|
@ -23,9 +23,12 @@
|
|||||||
"@commerce/*": ["framework/commerce/*"],
|
"@commerce/*": ["framework/commerce/*"],
|
||||||
"@commerce": ["framework/commerce"],
|
"@commerce": ["framework/commerce"],
|
||||||
"@framework/*": ["framework/bigcommerce/*"],
|
"@framework/*": ["framework/bigcommerce/*"],
|
||||||
"@framework": ["framework/bigcommerce"]
|
"@framework": ["framework/bigcommerce"],
|
||||||
|
"@agility/*": ["framework/agility/*"],
|
||||||
|
"@agility": ["framework/agility"]
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.js", "pages/XXXpages.tsx.bak"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
65
yarn.lock
65
yarn.lock
@ -2,6 +2,23 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@agility/content-fetch@^1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@agility/content-fetch/-/content-fetch-1.0.0.tgz#a945aa1abf21c093a6688494ab5a15d1282774d9"
|
||||||
|
integrity sha512-cG8XMT4OtlxbuEk7DW4jwe2MK5WuCvu4vClajjOD4DerkkINtWGfvt92XHlcFQ9Az4RmBnOR4/LQMnH3l7AjXg==
|
||||||
|
dependencies:
|
||||||
|
axios "^0.18.1"
|
||||||
|
axios-cache-adapter "^2.4.1"
|
||||||
|
|
||||||
|
"@agility/content-sync@^0.1.23":
|
||||||
|
version "0.1.23"
|
||||||
|
resolved "https://registry.yarnpkg.com/@agility/content-sync/-/content-sync-0.1.23.tgz#68c46a0330bbe2aba772d638669c3bf49f8892e5"
|
||||||
|
integrity sha512-LDw6+nPqI0Lgtt4fzkDcdHtLnvNiuSyhMca2X/I8QuSrtX2asJGbYH3e8YQEq59C2Ocvfn9xrGJyIVj48f5Ukw==
|
||||||
|
dependencies:
|
||||||
|
"@agility/content-fetch" "^1.0.0"
|
||||||
|
dotenv "^8.2.0"
|
||||||
|
proper-lockfile "^4.1.1"
|
||||||
|
|
||||||
"@ampproject/toolbox-core@2.7.4", "@ampproject/toolbox-core@^2.7.1-alpha.0":
|
"@ampproject/toolbox-core@2.7.4", "@ampproject/toolbox-core@^2.7.1-alpha.0":
|
||||||
version "2.7.4"
|
version "2.7.4"
|
||||||
resolved "https://registry.yarnpkg.com/@ampproject/toolbox-core/-/toolbox-core-2.7.4.tgz#8355136f16301458ce942acf6c55952c9a415627"
|
resolved "https://registry.yarnpkg.com/@ampproject/toolbox-core/-/toolbox-core-2.7.4.tgz#8355136f16301458ce942acf6c55952c9a415627"
|
||||||
@ -1766,6 +1783,22 @@ aws4@^1.8.0:
|
|||||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
|
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
|
||||||
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
|
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
|
||||||
|
|
||||||
|
axios-cache-adapter@^2.4.1:
|
||||||
|
version "2.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/axios-cache-adapter/-/axios-cache-adapter-2.5.0.tgz#e68be61e8c1cd7f31e4e7cfcbfd0435404ae67ef"
|
||||||
|
integrity sha512-YcMPdMoqmSLoZx7A5YD/PdYGuX6/Y9M2tHBhaIXvXrPeGgNnbW7nb3+uArWlT53WGHLfclnu2voMmS7jGXVg6A==
|
||||||
|
dependencies:
|
||||||
|
cache-control-esm "1.0.0"
|
||||||
|
lodash "^4.17.11"
|
||||||
|
|
||||||
|
axios@^0.18.1:
|
||||||
|
version "0.18.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3"
|
||||||
|
integrity sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==
|
||||||
|
dependencies:
|
||||||
|
follow-redirects "1.5.10"
|
||||||
|
is-buffer "^2.0.2"
|
||||||
|
|
||||||
babel-plugin-dynamic-import-node@^2.3.3:
|
babel-plugin-dynamic-import-node@^2.3.3:
|
||||||
version "2.3.3"
|
version "2.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3"
|
resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3"
|
||||||
@ -2050,6 +2083,11 @@ bytes@3.1.0, bytes@^3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
|
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
|
||||||
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
|
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
|
||||||
|
|
||||||
|
cache-control-esm@1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cache-control-esm/-/cache-control-esm-1.0.0.tgz#417647ecf1837a5e74155f55d5a4ae32a84e2581"
|
||||||
|
integrity sha512-Fa3UV4+eIk4EOih8FTV6EEsVKO0W5XWtNs6FC3InTfVz+EjurjPfDXY5wZDo/lxjDxg5RjNcurLyxEJBcEUx9g==
|
||||||
|
|
||||||
cacheable-request@^6.0.0:
|
cacheable-request@^6.0.0:
|
||||||
version "6.1.0"
|
version "6.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
|
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
|
||||||
@ -2650,7 +2688,7 @@ debounce@^1.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131"
|
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131"
|
||||||
integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==
|
integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==
|
||||||
|
|
||||||
debug@3.1.0:
|
debug@3.1.0, debug@=3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||||
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
||||||
@ -3445,6 +3483,13 @@ flatten@^1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
|
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
|
||||||
integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==
|
integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==
|
||||||
|
|
||||||
|
follow-redirects@1.5.10:
|
||||||
|
version "1.5.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
|
||||||
|
integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
|
||||||
|
dependencies:
|
||||||
|
debug "=3.1.0"
|
||||||
|
|
||||||
forever-agent@~0.6.1:
|
forever-agent@~0.6.1:
|
||||||
version "0.6.1"
|
version "0.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||||
@ -3671,7 +3716,7 @@ got@^9.6.0:
|
|||||||
to-readable-stream "^1.0.0"
|
to-readable-stream "^1.0.0"
|
||||||
url-parse-lax "^3.0.0"
|
url-parse-lax "^3.0.0"
|
||||||
|
|
||||||
graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4:
|
graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4:
|
||||||
version "4.2.4"
|
version "4.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
|
||||||
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
|
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
|
||||||
@ -4042,6 +4087,11 @@ is-binary-path@~2.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
binary-extensions "^2.0.0"
|
binary-extensions "^2.0.0"
|
||||||
|
|
||||||
|
is-buffer@^2.0.2:
|
||||||
|
version "2.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191"
|
||||||
|
integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==
|
||||||
|
|
||||||
is-callable@^1.1.4, is-callable@^1.2.2:
|
is-callable@^1.1.4, is-callable@^1.2.2:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9"
|
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9"
|
||||||
@ -6085,6 +6135,15 @@ prop-types@15.7.2, prop-types@^15.6.2:
|
|||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
react-is "^16.8.1"
|
react-is "^16.8.1"
|
||||||
|
|
||||||
|
proper-lockfile@^4.1.1:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.1.tgz#284cf9db9e30a90e647afad69deb7cb06881262c"
|
||||||
|
integrity sha512-1w6rxXodisVpn7QYvLk706mzprPTAPCYAqxMvctmPN3ekuRk/kuGkGc82pangZiAt4R3lwSuUzheTTn0/Yb7Zg==
|
||||||
|
dependencies:
|
||||||
|
graceful-fs "^4.1.11"
|
||||||
|
retry "^0.12.0"
|
||||||
|
signal-exit "^3.0.2"
|
||||||
|
|
||||||
prr@~1.0.1:
|
prr@~1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
|
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
|
||||||
@ -6484,7 +6543,7 @@ restore-cursor@^3.1.0:
|
|||||||
onetime "^5.1.0"
|
onetime "^5.1.0"
|
||||||
signal-exit "^3.0.2"
|
signal-exit "^3.0.2"
|
||||||
|
|
||||||
retry@0.12.0:
|
retry@0.12.0, retry@^0.12.0:
|
||||||
version "0.12.0"
|
version "0.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
|
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
|
||||||
integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
|
integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
|
||||||
|
Loading…
x
Reference in New Issue
Block a user