This commit is contained in:
Joel Varty
2021-06-21 15:55:31 -04:00
parent 7cc26439f4
commit 206d221f34
29 changed files with 827 additions and 449 deletions

View File

@@ -1,21 +1,49 @@
import pageTemplates from "components/agility-pageTemplates"
import Head from 'next/head'
const AgilityPage = ({ agilityProps, error, revalidate }: { agilityProps: any, error?: any, revalidate?: any}) => {
const AgilityPage = ({ agilityProps, error, revalidate }: { agilityProps: any, error?: any, revalidate?: any }) => {
if (!agilityProps) {
console.error(`Page object or template was not found.`)
return null
}
let AgilityPageTemplate = pageTemplates(agilityProps.pageTemplateName)
if (! AgilityPageTemplate) {
console.error(`${agilityProps.pageTemplateName} not found.`)
return null
let pageTitle = "Commerce Storefront"
if (agilityProps.globalData?.sitedata) {
pageTitle = agilityProps.globalData?.sitedata.name
}
return (
<AgilityPageTemplate {...agilityProps} />
)
if (agilityProps.notFound === true) {
return (
<>
<Head>
<title>Page Not Found - {pageTitle}</title>
</Head>
<div className="m-8 text-center" >Page not found.</div>
</>
)
}
if (agilityProps.pageTemplateName) {
let AgilityPageTemplate = pageTemplates(agilityProps.pageTemplateName)
if (!AgilityPageTemplate) {
console.error(`${agilityProps.pageTemplateName} not found.`)
return null
}
return (
<>
<Head>
<title>{agilityProps.sitemapNode?.title} - {pageTitle}</title>
</Head>
<AgilityPageTemplate {...agilityProps} />
</>
)
} else {
return null
}
}

View File

@@ -1,75 +0,0 @@
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

View File

@@ -1,73 +0,0 @@
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

View File

@@ -0,0 +1,99 @@
import React from "react";
import Head from "next/head";
import { renderHTML } from "@agility/nextjs";
import { AgilityImage } from "@agility/nextjs";
import truncate from "truncate-html";
import Link from "next/link";
import Image from "next/image"
const PostDetails = ({ dynamicPageItem }: any) => {
// post fields
const post = dynamicPageItem.fields;
const productJSON = post.product
const product = JSON.parse(productJSON)
// format date
const dateStr = new Date(post.date).toLocaleDateString();
const description = truncate(post.content, {
length: 160,
decodeEntities: true,
stripTags: true,
reserveLastWord: true,
});
let imageSrc = post.image?.url || null;
// post image alt
let imageAlt = post.image?.label || null;
let ogImageSrc = `${imageSrc}?w=1600&h=900`
let imageHeight = 900
let imageWidth = 1600
return (
<>
<Head>
<meta property="twitter:image" content={ogImageSrc} />
<meta property="twitter:card" content="summary_large_image" />
<meta name="og:title" content={post.title} />
<meta property="og:image" content={ogImageSrc} />
<meta property="og:image:width" content={`${imageWidth}`} />
<meta property="og:image:height" content={`${imageHeight}`} />
<meta name="description" content={description} />
<meta name="og:description" content={description} />
<meta name="twitter:description" content={description} />
<meta name="twitter:title" content={post.title} />
</Head>
<div className="relative px-8">
<div className="max-w-screen-xl mx-auto">
<div className="h-64 md:h-96 aspect-w-16 aspect-h-9 relative">
<AgilityImage
src={imageSrc}
alt={imageAlt}
className="object-cover object-center rounded-lg"
layout="fill"
/>
<Link href={`/product${product.slug}`}>
<a className="absolute" style={{bottom: "-80px", right: "-20px"}}>
<Image src={product.imageUrl} alt={product.name} width={300} height={300} layout="fixed" />
</a>
</Link>
</div>
<div className="max-w-2xl mx-auto mt-4">
<Link href={`/product${product.slug}`}><a className="uppercase text-primary-500 text-xs font-bold tracking-widest leading-loose">{product.name}</a></Link>
<div className="border-b-2 border-primary-500 w-8"></div>
<div className="mt-4 uppercase text-gray-600 italic font-semibold text-xs">
{dateStr}
</div>
<h1 className="font-display text-4xl font-bold my-6 text-secondary-500">
{post.title}
</h1>
<div
className="prose max-w-full mb-20"
dangerouslySetInnerHTML={renderHTML(post.content)}
/>
</div>
</div>
</div>
</>
);
};
PostDetails.getCustomInitialProps = async () => {
return {
cloud_name: process.env.CLOUDINARY_CLOUD_NAME
}
}
export default PostDetails;

View File

@@ -0,0 +1,87 @@
import React from "react";
import Link from "next/link";
import Image from "next/image"
import { ModuleWithInit, AgilityImage } from '@agility/nextjs'
import products from "pages/api/catalog/products";
interface ICustomData {
posts: []
}
interface IModule {
}
const PostsListing: ModuleWithInit<IModule, ICustomData> = ({ customData, module, languageCode, isDevelopmentMode, isPreview }) => {
// get posts
const { posts } = customData;
// set up href for internal links
let href = "/pages/[...slug]";
// if there are no posts, display message on frontend
if (posts.length <= 0) {
return (
<div className="mt-44 px-6 flex flex-col items-center justify-center">
<h1 className="text-3xl text-center font-bold">No posts available.</h1>
<div className="my-10">
<Link href={href} as="/home">
<a className="px-4 py-3 my-3 border border-transparent text-base leading-6 font-medium rounded-md text-white bg-primary-600 hover:bg-primary-500 focus:outline-none focus:border-primary-700 focus:shadow-outline-primary transition duration-300">
Return Home
</a>
</Link>
</div>
</div>
);
}
return (
<div className="relative px-8 mb-12">
<div className="max-w-screen-xl mx-auto">
<div className="sm:grid sm:gap-8 sm:grid-cols-2 lg:grid-cols-3">
{posts.map((post: any, index) => (
<Link href={post.url} key={index}>
<a>
<div className="flex-col group mb-8 md:mb-0">
<div className="relative h-64">
<AgilityImage
src={post.imageSrc}
alt={post.imageAlt}
className="object-cover object-center rounded-t-lg"
layout="fill"
/>
<div className="absolute right-0" style={{bottom: "-60px"}}>
<Image src={post.productImageSrc} alt={post.productName} width={200} height={200} layout="fixed" />
</div>
</div>
<div className="bg-gray-100 p-8 border-2 border-t-0 rounded-b-lg">
<div className="uppercase text-primary-500 text-xs font-bold tracking-widest leading-loose">
{post.productName}
</div>
<div className="border-b-2 border-primary-500 w-8"></div>
<div className="mt-4 uppercase text-gray-600 italic font-semibold text-xs">
{post.date}
</div>
<h2 className="text-secondary-500 mt-1 font-black text-2xl group-hover:text-primary-500 transition duration-300">
{post.title}
</h2>
</div>
</div>
</a>
</Link>
))}
</div>
</div>
</div>
);
};
export default PostsListing;

View File

@@ -1,39 +0,0 @@
import { FC } from "react"
import { Grid, Marquee, Hero } from '@components/ui'
import { ProductCard } from '@components/product'
import { ModuleWithInit } from "@agility/nextjs"
interface ICustomData {
products: any
}
interface IModule {
}
const FeaturedProducts: ModuleWithInit<IModule, ICustomData> = ({ customData }) => {
if (! customData) {
return <div>No featured products returned.</div>
}
const products:any = customData.products
return (
<Grid variant="filled">
{products.slice(0, 3).map((product: any, i: number) => (
<ProductCard
key={product.id}
product={product}
imgProps={{
width: i === 0 ? 1080 : 540,
height: i === 0 ? 1080 : 540,
}}
/>
))}
</Grid>
)
}
export default FeaturedProducts

View File

@@ -1,42 +0,0 @@
import React, { FC } from 'react'
import HomeAllProductsGrid from '@components/common/HomeAllProductsGrid'
import { ModuleWithInit } from '@agility/nextjs'
import { ProductCard } from '@components/product'
import { Grid, Marquee, Hero } from '@components/ui'
interface ICustomData {
products: any
}
interface IModule {
}
const HomeAllProductsGridModule: ModuleWithInit<IModule, ICustomData> = ({ customData }) => {
const products = customData.products
return (
<Grid layout="B" variant="filled">
{products.slice(0, 3).map((product: any, i: number) => (
<ProductCard
key={product.id}
product={product}
imgProps={{
width: i === 0 ? 1080 : 540,
height: i === 0 ? 1080 : 540,
}}
/>
))}
</Grid>
)
}
export default HomeAllProductsGridModule

View File

@@ -1,8 +1,44 @@
const ProductListing = () => {
return (
<section>ProductListing</section>
)
import React, { FC } from 'react'
import { ModuleWithInit } from '@agility/nextjs'
import { ProductCard } from '@components/product'
import { Grid, Marquee, Hero } from '@components/ui'
interface ICustomData {
products: any
}
export default ProductListing
interface IModule {
numItems: string,
layout?: 'A' | 'B' | 'C' | 'D' | 'normal'
variant?: 'default' | 'filled'
}
const ProductListingModule: ModuleWithInit<IModule, ICustomData> = ( { customData, module, languageCode, isDevelopmentMode, isPreview }) => {
const products = customData.products
return (
<Grid layout={module.fields.layout} variant={module.fields.variant}>
{products.map((product: any, i: number) => (
<ProductCard
key={product.id}
product={product}
imgProps={{
width: i === 0 ? 1080 : 540,
height: i === 0 ? 1080 : 540,
}}
/>
))}
</Grid>
)
}
export default ProductListingModule

View File

@@ -12,7 +12,7 @@ interface IModule {
}
const BestsellingProducts: ModuleWithInit<IModule, ICustomData> = ({ customData }) => {
const ProductMarqueeModule: ModuleWithInit<IModule, ICustomData> = ({ customData }) => {
const products = customData.products
@@ -26,5 +26,5 @@ const BestsellingProducts: ModuleWithInit<IModule, ICustomData> = ({ customData
}
export default BestsellingProducts
export default ProductMarqueeModule

View File

@@ -8,8 +8,6 @@ interface Fields {
const RichTextArea:Module<Fields> = ({ module: {fields} }) => {
return (
<Container>
<Text className="prose prose-sm sm:prose lg:prose-lg xl:prose-xl" html={fields.textblob} />

View File

@@ -1,21 +1,22 @@
import RichTextArea from "./RichTextArea"
import BestsellingProducts from "./BestsellingProducts"
import ProductMarquee from "./ProductMarquee"
import ProductDetails from "./ProductDetails"
import FeaturedProducts from "./FeaturedProducts"
import ProductListing from "./ProductListing"
import ProductSearch from "./ProductSearch"
import Hero from "./Hero"
import HomeAllProductsGrid from "./HomeAllProductsGrid"
import HomeAllProductsGrid from "./ProductListing"
import Cart from "./Cart"
import Orders from "./Orders"
import Profile from "./Profile"
import Wishlist from "./Wishlist"
import BlogPostListing from "./BlogPostListing"
import BlogPostDetails from "./BlogPostDetails"
const allModules = [
{ name: "RichTextArea", module: RichTextArea },
{ name: "BestsellingProducts", module: BestsellingProducts },
{ name: "FeaturedProducts", module: FeaturedProducts },
{ name: "ProductMarquee", module: ProductMarquee },
{ name: "ProductListing", module: ProductListing },
{ name: "ProductSearch", module: ProductSearch },
{ name: "Hero", module: Hero },
@@ -23,8 +24,11 @@ const allModules = [
{ name: "HomeAllProductsGrid", module: HomeAllProductsGrid },
{ name: "Cart", module: Cart },
{ name: "Orders", module: Orders },
{ name: "Profile", module: Profile},
{ name: "Wishlist", module: Wishlist}
{ name: "Profile", module: Profile },
{ name: "Wishlist", module: Wishlist },
{ name: "BlogPostListing", module: BlogPostListing },
{ name: "BlogPostDetails", module: BlogPostDetails }
]
/**

View File

@@ -10,122 +10,118 @@ import { I18nWidget } from '@components/common'
import s from './Footer.module.css'
interface Props {
className?: string
children?: any
pages?: Page[]
className?: string
children?: any
pages?: Page[],
agilityProps: any
}
const links = [
{
name: 'Home',
url: '/',
},
]
const links:[] = []
const Footer: FC<Props> = ({ className, pages }) => {
const { sitePages } = usePages(pages)
const rootClassName = cn(s.root, className)
const Footer: FC<Props> = ({ className, pages, agilityProps }) => {
const { sitePages } = usePages(pages)
const rootClassName = cn(s.root, className)
return (
<footer className={rootClassName}>
<Container>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 border-b border-accent-2 py-12 text-primary bg-primary transition-colors duration-150">
<div className="col-span-1 lg:col-span-2">
<Link href="/">
<a className="flex flex-initial items-center font-bold md:mr-24">
<span className="rounded-full border border-accent-6 mr-2">
<Logo />
</span>
<span>ACME</span>
</a>
</Link>
</div>
<div className="col-span-1 lg:col-span-8">
<div className="grid md:grid-rows-4 md:grid-cols-3 md:grid-flow-col">
{[...links, ...sitePages].map((page) => (
<span key={page.url} className="py-3 md:py-0 md:pb-4">
<Link href={page.url!}>
<a className="text-accent-9 hover:text-accent-6 transition ease-in-out duration-150">
{page.name}
</a>
</Link>
</span>
))}
</div>
</div>
<div className="col-span-1 lg:col-span-2 flex items-start lg:justify-end text-primary">
<div className="flex space-x-6 items-center h-10">
<a
className={s.link}
aria-label="Github Repository"
href="https://github.com/joelvarty/nextsj-commerce-agilitycms"
>
<Github />
</a>
<I18nWidget />
</div>
</div>
</div>
<div className="pt-6 pb-10 flex flex-col md:flex-row justify-between items-center space-y-4 text-accent-6 text-sm">
<div>
<span>&copy; 2021 ACME, Inc. All rights reserved.</span>
</div>
<div className="flex items-center text-primary text-sm">
<span className="text-primary">Created by</span>
<a
rel="noopener"
href="https://vercel.com"
aria-label="Vercel.com Link"
target="_blank"
className="text-primary"
>
<Vercel
className="inline-block h-6 ml-3 text-primary"
alt="Vercel.com Logo"
/>
</a>
const siteData = agilityProps.globalData["sitedata"]
<span className="text-primary ml-5">CMS Integration by</span>
<a
rel="noopener"
href="https://agilitycms.com"
aria-label="AgilityCMS.com Link"
target="_blank"
className="text-primary"
>
<Agility
className="inline-block h-6 ml-3 text-primary"
alt="AgilityCMS.com Logo"
/>
</a>
</div>
</div>
</Container>
</footer>
)
return (
<footer className={rootClassName}>
<Container>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 border-b border-accent-2 py-12 text-primary bg-primary transition-colors duration-150">
<div className="col-span-1 lg:col-span-2">
<Link href="/">
<a className="flex flex-initial items-center font-bold md:mr-24 ">
<img src={siteData.logo.url} height="32" width="32" className="rounded-full border border-accent-6 mr-2" />
<span>{siteData.name}</span>
</a>
</Link>
</div>
<div className="col-span-1 lg:col-span-8">
<div className="grid md:grid-rows-4 md:grid-cols-3 md:grid-flow-col">
{[...links, ...sitePages, ...siteData.links].map((page) => (
<span key={page.url} className="py-3 md:py-0 md:pb-4">
<Link href={page.url!}>
<a className="text-accent-9 hover:text-accent-6 transition ease-in-out duration-150">
{page.name}
</a>
</Link>
</span>
))}
</div>
</div>
<div className="col-span-1 lg:col-span-2 flex items-start lg:justify-end text-primary">
<div className="flex space-x-6 items-center h-10">
<a
className={s.link}
aria-label="Github Repository"
href="https://github.com/joelvarty/nextsj-commerce-agilitycms"
>
<Github />
</a>
<I18nWidget />
</div>
</div>
</div>
<div className="pt-6 pb-10 flex flex-col md:flex-row justify-between items-center space-y-4 text-accent-6 text-sm">
<div>
<span>&copy; 2021 ACME, Inc. All rights reserved.</span>
</div>
<div className="flex items-center text-primary text-sm">
<span className="text-primary">Created by</span>
<a
rel="noopener"
href="https://vercel.com"
aria-label="Vercel.com Link"
target="_blank"
className="text-primary"
>
<Vercel
className="inline-block h-6 ml-3 text-primary"
alt="Vercel.com Logo"
/>
</a>
<span className="text-primary ml-5">CMS Integration by</span>
<a
rel="noopener"
href="https://agilitycms.com"
aria-label="AgilityCMS.com Link"
target="_blank"
className="text-primary"
>
<Agility
className="inline-block h-6 ml-3 text-primary"
alt="AgilityCMS.com Logo"
/>
</a>
</div>
</div>
</Container>
</footer>
)
}
function usePages(pages?: Page[]) {
const { locale } = useRouter()
const sitePages: Page[] = []
const { locale } = useRouter()
const sitePages: Page[] = []
if (pages) {
pages.forEach((page) => {
const slug = page.url && getSlug(page.url)
if (!slug) return
if (locale && !slug.startsWith(`${locale}/`)) return
sitePages.push(page)
})
}
if (pages) {
pages.forEach((page) => {
const slug = page.url && getSlug(page.url)
if (!slug) return
if (locale && !slug.startsWith(`${locale}/`)) return
sitePages.push(page)
})
}
return {
sitePages: sitePages.sort(bySortOrder),
}
return {
sitePages: sitePages.sort(bySortOrder),
}
}
// Sort pages by the sort order assigned in the BC dashboard
function bySortOrder(a: Page, b: Page) {
return (a.sort_order ?? 0) - (b.sort_order ?? 0)
return (a.sort_order ?? 0) - (b.sort_order ?? 0)
}
export default Footer

View File

@@ -45,7 +45,8 @@ const FeatureBar = dynamic(
interface Props {
pageProps: {
pages?: Page[]
categories: Category[]
categories: Category[],
agilityProps: any
}
}
@@ -90,10 +91,14 @@ const SidebarUI: FC = () => {
) : null
}
const Layout: FC<Props> = ({
children,
pageProps: { categories = [], ...pageProps },
}) => {
const Layout: FC<Props> = (props) => {
const {
children,
pageProps: { agilityProps, categories = [], ...pageProps },
} = props
const { acceptedCookies, onAcceptCookies } = useAcceptCookies()
const { locale = 'en-US' } = useRouter()
const navBarlinks = categories.slice(0, 2).map((c) => ({
@@ -104,9 +109,9 @@ const Layout: FC<Props> = ({
return (
<CommerceProvider locale={locale}>
<div className={cn(s.root)}>
<Navbar links={navBarlinks} />
<Navbar links={navBarlinks} agilityProps={agilityProps}/>
<main className="fit">{children}</main>
<Footer pages={pageProps.pages} />
<Footer pages={pageProps.pages} agilityProps={agilityProps} />
<ModalUI />
<SidebarUI />
<FeatureBar

View File

@@ -6,46 +6,52 @@ import { Logo, Container } from '@components/ui'
import { Searchbar, UserNav } from '@components/common'
interface Link {
href: string
label: string
href: string
label: string
}
interface NavbarProps {
links?: Link[]
links?: Link[],
agilityProps: any
}
const Navbar: FC<NavbarProps> = ({ links }) => (
<NavbarRoot>
<Container>
<div className={s.nav}>
<div className="flex items-center flex-1">
<Link href="/">
<a className={s.logo} aria-label="Logo">
<Logo />
</a>
</Link>
<nav className={s.navMenu}>
<Link href="/search">
<a className={s.link}>All</a>
</Link>
{links?.map((l) => (
<Link href={l.href} key={l.href}>
<a className={s.link}>{l.label}</a>
</Link>
))}
</nav>
</div>
<div className="justify-center flex-1 hidden lg:flex">
<Searchbar />
</div>
<div className="flex items-center justify-end flex-1 space-x-8">
<UserNav />
</div>
</div>
<div className="flex pb-4 lg:px-6 lg:hidden">
<Searchbar id="mobile-search" />
</div>
</Container>
</NavbarRoot>
)
const Navbar: FC<NavbarProps> = ({ links, agilityProps }) => {
const siteData = agilityProps.globalData["sitedata"]
return (
<NavbarRoot>
<Container>
<div className={s.nav}>
<div className="flex items-center flex-1">
<Link href="/">
<a className={s.logo} aria-label="Logo">
<img src={siteData.logo.url} alt={siteData.logo.label} height="32" width="32" className="rounded-full border border-accent-6" />
</a>
</Link>
<nav className={s.navMenu}>
<Link href="/search">
<a className={s.link}>All</a>
</Link>
{links?.map((l) => (
<Link href={l.href} key={l.href}>
<a className={s.link}>{l.label}</a>
</Link>
))}
</nav>
</div>
<div className="justify-center flex-1 hidden lg:flex">
<Searchbar />
</div>
<div className="flex items-center justify-end flex-1 space-x-8">
<UserNav />
</div>
</div>
<div className="flex pb-4 lg:px-6 lg:hidden">
<Searchbar id="mobile-search" />
</div>
</Container>
</NavbarRoot>
)
}
export default Navbar