update to agility/next

This commit is contained in:
Joel Varty 2021-06-09 23:17:19 -04:00
parent 17f458b45b
commit e836a5950f
26 changed files with 26612 additions and 6639 deletions

View File

@ -1,20 +1,20 @@
import pageTemplates from "components/agility-pageTemplates"
const AgilityPage = (props:any) => {
const AgilityPage = ({ header, agilityProps, error, revalidate }: {header:any, agilityProps: any, error?: any, revalidate?: any}) => {
if (!props || !props.pageTemplateName) {
if (!agilityProps) {
console.error(`Page object or template was not found.`)
return null
}
let AgilityPageTemplate = pageTemplates(props.pageTemplateName)
let AgilityPageTemplate = pageTemplates(agilityProps.pageTemplateName)
if (! AgilityPageTemplate) {
console.error(`${props.pageTemplateName} not found.`)
console.error(`${agilityProps.pageTemplateName} not found.`)
return null
}
return (
<AgilityPageTemplate {...props} />
<AgilityPageTemplate {...agilityProps} />
)

View File

@ -1,5 +1,5 @@
import React, { Component } from 'react';
import moduleComponents from "components/agility-modules"
import {getModule} from "components/agility-modules"
function ContentZone({ name, page, dynamicPageItem }) {
@ -11,7 +11,7 @@ import moduleComponents from "components/agility-modules"
const modulesToRender = modules.map(m => {
const AgilityModule = moduleComponents(m.moduleName)
const AgilityModule = getModule(m.moduleName)
if (AgilityModule) {
return <AgilityModule key={m.item.contentID} page={page} dynamicPageItem={dynamicPageItem} {...m.item} />

View File

@ -2,22 +2,23 @@ import React, { FC } from 'react'
import { ProductCard } from '@components/product'
import { Grid, Marquee, Hero } from '@components/ui'
import { ModuleWithInit } from '@agility/nextjs'
interface Fields {
interface ICustomData {
bestSelling: any
}
interface Props {
fields: Fields,
customData: any
interface IModule {
}
const BestsellingProducts:FC<Props> = ({fields, customData}) => {
const BestsellingProducts: ModuleWithInit<IModule, ICustomData> = ({ customData }) => {
const bestSelling = customData.bestSelling
return (
<Marquee variant="secondary">
{bestSelling.slice(0, 12).map(({ node }:any) => (
{bestSelling.slice(0, 12).map(({ node }: any) => (
<ProductCard
key={node.path}
product={node}
@ -31,5 +32,6 @@ const BestsellingProducts:FC<Props> = ({fields, customData}) => {
)
}
export default BestsellingProducts

View File

@ -1,16 +1,22 @@
import { FC } from "react"
import { Grid, Marquee, Hero } from '@components/ui'
import { ProductCard } from '@components/product'
import { ModuleWithInit } from "@agility/nextjs"
interface Fields {
interface ICustomData {
featured: any
}
interface Props {
fields: Fields,
customData: any
interface IModule {
}
const FeaturedProducts:FC<Props> = ({fields, customData}) => {
const FeaturedProducts: ModuleWithInit<IModule, ICustomData> = ({ customData }) => {
if (! customData) {
return <div>No featured products returned.</div>
}
const featured:any = customData.featured
return (

View File

@ -1,6 +1,7 @@
import React, { FC } from 'react'
import { Hero } from '@components/ui'
import * as AgilityTypes from "@agility/types"
import { Module } from '@agility/nextjs'
interface Fields {
@ -9,11 +10,7 @@ interface Fields {
cTA?:AgilityTypes.URLField
}
interface Props {
fields: Fields
}
const HeroModule:FC<Props> = ({fields}) => {
const HeroModule:Module<Fields> = ({ module: {fields }}) => {
return (
<Hero

View File

@ -1,20 +1,27 @@
import React, { FC } from 'react'
import HomeAllProductsGrid from '@components/common/HomeAllProductsGrid'
import { ModuleWithInit } from '@agility/nextjs'
interface Fields {
interface ICustomData {
categories: any
newestProducts: any
brands: any
}
interface Props {
fields: Fields,
customData: any
interface IModule {
}
const HomeAllProductsGridModule:FC<Props> = ({fields, customData}) => {
const HomeAllProductsGridModule: ModuleWithInit<IModule, ICustomData> = ({ customData }) => {
const categories = customData.categories
const newestProducts = customData.newestProducts
const brands = customData.brands
if (!categories) return <div>No data</div>
return (
<HomeAllProductsGrid
categories={categories}

View File

@ -2,17 +2,13 @@ import React, { FC } from 'react'
import { Container, Text } from '@components/ui'
import { Bag } from '@components/icons'
import { Module } from '@agility/nextjs'
interface Fields {
}
interface Props {
fields: Fields,
customData: any
}
const Orders: FC<Props> = ({ fields, customData }) => {
const Orders: Module<Fields> = ({ }) => {
return (
<Container>
<Text variant="pageHeading">My Orders</Text>

View File

@ -3,19 +3,16 @@ import { Hero } from '@components/ui'
import * as AgilityTypes from "@agility/types"
import { GetProductResult } from '@framework/api/operations/get-product'
import { ProductView } from '@components/product'
import { Module } from '@agility/nextjs'
interface Fields {
}
interface Props {
fields: Fields,
dynamicPageItem:any
}
const HeroModule:FC<Props> = ({fields, dynamicPageItem}) => {
const HeroModule:Module<Fields> = ({ dynamicPageItem }) => {
const product:any = dynamicPageItem
return (
<ProductView product={dynamicPageItem} />
<ProductView product={product} />
)
}

View File

@ -15,6 +15,8 @@ import {
getDesignerPath,
useSearchMeta,
} from '@lib/search'
import { ModuleWithInit } from '@agility/nextjs'
const SORT = Object.entries({
'latest-desc': 'Latest arrivals',
@ -23,15 +25,17 @@ const SORT = Object.entries({
'price-desc': 'Price: High to low',
})
interface Fields {
interface ICustomData {
categories: any
brands: any
}
interface Props {
fields: Fields,
customData: any
interface IModule {
}
const ProductSearch: FC<Props> = ({ fields, customData }) => {
const ProductSearch: ModuleWithInit<IModule, ICustomData> = ({ customData }) => {
const categories:[any] = customData.categories
const brands:[any] = customData.brands
@ -39,6 +43,7 @@ const ProductSearch: FC<Props> = ({ fields, customData }) => {
const [activeFilter, setActiveFilter] = useState('')
const [toggleFilter, setToggleFilter] = useState(false)
const router = useRouter()
const { asPath } = router
const { q, sort } = router.query
@ -447,4 +452,6 @@ const ProductSearch: FC<Props> = ({ fields, customData }) => {
)
}
export default ProductSearch

View File

@ -2,6 +2,7 @@ import React, { FC } from 'react'
import useCustomer from '@framework/use-customer'
import { Container, Text } from '@components/ui'
import { Module } from '@agility/nextjs'
interface Fields {
heading: string,
@ -10,11 +11,8 @@ interface Fields {
notLoggedInMessage: string
}
interface Props {
fields: Fields
}
const ProfileModule:FC<Props> = ({fields}) => {
const ProfileModule:Module<Fields> = ({ module: {fields}}) => {
const { data } = useCustomer()
return (

View File

@ -1,15 +1,14 @@
import React, {FC} from 'react';
import { Text, Container } from '@components/ui'
import { Module } from '@agility/nextjs';
interface Fields {
textblob:string,
}
interface Props {
fields: Fields
}
const RichTextArea:Module<Fields> = ({ module: {fields} }) => {
const RichTextArea:FC<Props> = ({fields}) => {
return (
<Container>

View File

@ -5,6 +5,7 @@ import useWishlist from '@framework/wishlist/use-wishlist'
import { Heart } from '@components/icons'
import { Text, Container } from '@components/ui'
import { WishlistCard } from '@components/wishlist'
import { Module } from '@agility/nextjs'
interface Fields {
@ -13,12 +14,7 @@ interface Fields {
addItemsMessage?: string
}
interface Props {
fields: Fields,
customData: any
}
const Wishlist: FC<Props> = ({ fields, customData }) => {
const Wishlist: Module<Fields> = ({ module: { fields } }) => {
const { data, isEmpty } = useWishlist({ includeProducts: true })
return (

View File

@ -33,10 +33,8 @@ const allModules = [
* Find the component for a module by name.
* @param moduleName
*/
const getModule = (moduleName: string): any | null => {
export 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

View File

@ -1,11 +1,13 @@
import React, { Component } from 'react';
import ContentZone from 'components/agility-global/ContentZone'
import { ContentZone } from '@agility/nextjs'
import { getModule } from "components/agility-modules"
const MainTemplate = (props:any) => {
return (
<div className="one-column-template">
<ContentZone name='MainContentZone' {...props} />
<ContentZone name='MainContentZone' {...props} getModule={getModule} />
</div>
);

View File

@ -5,7 +5,7 @@ import rangeMap from '@lib/range-map'
const nonNullable = (v: any) => v
const BestsellingProductsInit = async function ({ item, agility, languageCode, channelName, pageInSitemap, dynamicPageItem }: any) {
const getCustomInitialProps = async function ({ }):Promise<{bestSelling:any}> {
//TODO: pass the locale and preview mode as props...
@ -52,4 +52,4 @@ const BestsellingProductsInit = async function ({ item, agility, languageCode, c
}
export default BestsellingProductsInit
export default {getCustomInitialProps}

View File

@ -7,7 +7,7 @@ import rangeMap from '@lib/range-map'
const nonNullable = (v: any) => v
const FeaturedProductsInit = async function ({ item, agility, languageCode, channelName, pageInSitemap, dynamicPageItem }: any) {
const getCustomInitialProps = async function ({ item, agility, languageCode, channelName, pageInSitemap, dynamicPageItem }: any) {
//TODO: pass the locale and preview mode as props...
@ -31,4 +31,4 @@ const FeaturedProductsInit = async function ({ item, agility, languageCode, chan
}
export default FeaturedProductsInit
export default { getCustomInitialProps }

View File

@ -8,7 +8,7 @@ import rangeMap from '@lib/range-map'
const nonNullable = (v: any) => v
const HomeAllProductsGridData = async function ({ item, agility, languageCode, channelName, pageInSitemap, dynamicPageItem }: any) {
const getCustomInitialProps = async function ({ item, agility, languageCode, channelName, pageInSitemap, dynamicPageItem }: any) {
//TODO: pass the locale and preview mode as props...
const locale = "en-US"
@ -35,4 +35,4 @@ const HomeAllProductsGridData = async function ({ item, agility, languageCode, c
}
export default HomeAllProductsGridData
export default { getCustomInitialProps }

View File

@ -1,7 +1,8 @@
import { getConfig } from '@framework/api'
import getSiteInfo from '@framework/api/operations/get-site-info'
const ProductSearchData = async function ({ item, agility, languageCode, channelName, pageInSitemap, dynamicPageItem }: any) {
const getCustomInitialProps = async ({ agility, channelName, languageCode }:any) => {
//TODO: pass the locale and preview mode as props...
const locale = "en-US"
@ -18,4 +19,6 @@ const ProductSearchData = async function ({ item, agility, languageCode, channel
}
export default ProductSearchData
export default {
getCustomInitialProps
}

View File

@ -14,10 +14,8 @@ const allModules:any =[
* Find the data method for a module by module reference name.
* @param moduleName
*/
const getInitMethod = (moduleName:string):any => {
export const getModuleData = (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

View File

@ -1,9 +1,7 @@
const bundleAnalyzer = require('@next/bundle-analyzer')({
enabled: !!process.env.BUNDLE_ANALYZE,
})
module.exports = bundleAnalyzer({
module.exports = {
webpack5: true,
images: {
domains: ['cdn11.bigcommerce.com', 'cdn.aglty.io'],
},
@ -11,6 +9,16 @@ module.exports = bundleAnalyzer({
locales: ['en-US', 'es'],
defaultLocale: 'en-US',
},
webpack: (config, { isServer }) => {
if (!isServer) {
config.node = {
net: 'empty',
dns: 'empty'
};
}
return config;
},
rewrites() {
return [
{
@ -39,4 +47,4 @@ module.exports = bundleAnalyzer({
},
]
},
})
}

20581
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,18 +2,19 @@
"name": "nextjs-commerce",
"version": "1.0.0",
"scripts": {
"predev": "node framework/agility/agility.sync.js sync",
"predev": "agility-next sync",
"dev": "next dev",
"prebuild": "node framework/agility/agility.sync.js prebuild",
"prebuild": "agility-next prebuild",
"build": "next build",
"postbuild": "node framework/agility/agility.sync.js postbuild",
"export": "next export",
"postbuild": "agility-next postbuild",
"start": "next start",
"cms-pull": "agility-next sync",
"cms-clean": "agility-next clean",
"analyze": "BUNDLE_ANALYZE=both yarn build",
"find:unused": "next-unused",
"generate": "graphql-codegen",
"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"
"generate:definitions": "node framework/bigcommerce/scripts/generate-definitions.js"
},
"prettier": {
"semi": false,
@ -49,7 +50,8 @@
]
},
"dependencies": {
"@agility/content-sync": "^0.1.23",
"@agility/content-sync": "^1.0.3",
"@agility/nextjs": "^0.1.8",
"@reach/portal": "^0.11.2",
"@tailwindcss/ui": "^0.6.2",
"@vercel/fetch": "^6.1.0",
@ -64,12 +66,12 @@
"lodash.debounce": "^4.0.8",
"lodash.random": "^3.2.0",
"lodash.throttle": "^4.1.1",
"next": "^10.0.5-canary.11",
"next": "^10.2.3",
"next-seo": "^4.11.0",
"next-themes": "^0.0.4",
"postcss-nesting": "^7.0.1",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-merge-refs": "^1.1.0",
"react-ticker": "^1.2.2",
"swr": "^0.4.0",

View File

@ -6,23 +6,23 @@ import type {
} from 'next'
import { Layout } from '@components/common'
import { missingLocaleInPages } from '@lib/usage-warns'
import { getAgilityPageProps, getAgilityPaths } from "@agility/nextjs/node"
import { handlePreview } from "@agility/nextjs"
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 { getModuleData } from "framework/module-data"
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[] }>) {
export async function getStaticProps({ preview, params, locale, locales, defaultLocale }: GetStaticPropsContext<{ slug: string[] }>) {
try {
let productCode: string | null = null
//check if this page is a product...
@ -32,10 +32,19 @@ export async function getStaticProps({ preview, params, locale }: GetStaticProps
params.slug[1] = "product-details"
}
const page = await getAgilityPageProps({ preview, params, locale });
//add any global components (header, footer) that need agility data here
const globalComponents = {
// "header": GlobalHeader,
// "footer": GlobalFooter
}
const agilityProps = await getAgilityPageProps({ preview, params, locale, getModule: getModuleData, defaultLocale, globalComponents });
let rebuildFrequency = 10
let productDetail:any = null
if (productCode) {
const config = getConfig({ locale })
const { product } = await getProduct({
@ -45,30 +54,43 @@ export async function getStaticProps({ preview, params, locale }: GetStaticProps
})
if (product !== null) {
page.dynamicPageItem = product
//moderate hack: use the Product as the dynamic page item for product detail pages
agilityProps.dynamicPageItem = product
rebuildFrequency = 60 * 60 //once per hour for products
} else {
throw new Error(`Product not found`)
}
}
const pages = await getAgilityPaths(preview)
if (!page) {
if (!agilityProps) {
// 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 },
props: { ...defaultPageProps, agilityProps },
revalidate: rebuildFrequency
}
} catch (err) {
var e = new Error();
const st = e.stack;
console.log("Error getting page props", params, err)
return {
props: {
error: `Params: ${params}, Error: ${err}, Stack: ${st}`,
revalidate: 60000
}
}
}
}
export async function getStaticPaths({ locales }: GetStaticPathsContext) {
export async function getStaticPaths({ defaultLocale, locales }: GetStaticPathsContext) {
//get the paths configured in agility
let agilityPaths = await getAgilityPaths(false)
let agilityPaths = await getAgilityPaths({ preview: false, defaultLocale, locales })
//remove product/product-details from the agility paths (special details page...)
agilityPaths = agilityPaths.filter(p => p !== "/product/product-details")
@ -85,10 +107,10 @@ export async function getStaticPaths({ locales }: GetStaticPathsContext) {
}
}
export default function Pages({ page }: InferGetStaticPropsType<typeof getStaticProps>) {
export default function Pages(props: InferGetStaticPropsType<typeof getStaticProps>) {
return (
<AgilityPage {...page} />
<AgilityPage {...props} />
)
}

View File

@ -0,0 +1,39 @@
// import { NextApiRequest, NextApiResponse } from "next"
// import getProducts from "../../shopify-api/get-products"
// const searchProducts = async (req:NextApiRequest, res:NextApiResponse) => {
// //cors stuff
// res.setHeader('Access-Control-Allow-Credentials', "true")
// res.setHeader('Access-Control-Allow-Origin', '*')
// res.setHeader('Access-Control-Allow-Methods', 'GET,OPTIONS,PATCH,DELETE,POST,PUT')
// res.setHeader(
// 'Access-Control-Allow-Headers',
// 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version'
// )
// if (req.method === 'OPTIONS') {
// res.status(200).end()
// return
// }
// try {
// const filter = req.query.filter ?? ""
// const products = await getProducts({filter})
// res.statusCode = 200
// res.json(products)
// } catch (e) {
// res.statusCode = 500
// res.json({ message: "An error occurred ", error: e })
// }
// }
// export default searchProducts

View File

@ -0,0 +1,188 @@
//
// API Item Picker
//
var baseAPIUrl = "http://localhost:3000"
var ChooseProductCustomField = function () {
/// <summary>The type definition of this Agility Custom Field Type.</summary>
var self = this;
/// <field name="Label" type="String">The display name of the Custom Field Type</field>
self.Label = "Choose Product";
/// <field name="ReferenceName" type="String">The internal reference name of the Custom Field Type. Must not contain any special characters.</field>
self.ReferenceName = "ChooseProduct";
/// <field name="Render" type="Function">This function runs every time the field is rendered</field>
self.Render = function (options) {
/// <summary>
/// The Render handler for this field. Create any elements and bindings that you might need, pull down resources.
/// This method will be called everytime to the field value changes.
/// </summary>
/// <param name="options" type="ContentManager.Global.CustomInputFieldParams">The options used to render this field.</param>
//get our base element
var $pnl = $(".product-picker-field", options.$elem);
if ($pnl.size() == 0) {
var htmlContent = `
<style>
.prod-img {
display:inline-block;
height: 40px;
border-radius: 10px;
width: 40px;
background-size: cover;
background-repeat: no-repeat;
}
.prod-img-small {
margin-top: 2px;
display:inline-block;
height: 20px;
border-radius: 5px;
width: 20px;
background-size: cover;
background-repeat: no-repeat;
}
.prod-text {
display:inline-block;
margin-left: 5px;
line-height: 20px;
}
</style>
<input class='product-picker-field' type='hidden' style='width:100%' data-bind='value:selectedValue,select2:select2'>`;
//pull down the html template and load it into the element
options.$elem.append(htmlContent)
$pnl = $(".product-picker-field", options.$elem);
//bind our viewmodel to this
var viewModel = function () {
/// <summary>The KO ViewModel that will be binded to your HTML template.</summary>
/// <param name="options" type="Object">
/// <field name="$elem" type="jQueryElem">The .field-row jQuery Dom Element.</field>
/// <field name="contentItem" type="ContentItem Object">The entire Content Item object including Values and their KO Observable properties of all other fields on the form.</field>
/// <field name="fieldBinding" type="KO Observable">The value binding of thie Custom Field Type. Get and set this field's value by using this property.</field>
/// <field name="fieldSetting" type="Object">Object representing the field's settings such as 'Hidden', 'Label', and 'Description'</field>
/// <field name="readonly" type="boolean">Represents if this field should be readonly or not.</field>
/// </param>
var self = this;
self.ajaxRequest = null;
self.selectedValue = options.fieldBinding.extend({ throttle: 500 });
self.formatResult = function (item) {
return $(`<div class='prod-img' style="background-image:url('${item.node.featuredImage.transformedSrc}')"/></div><div class='prod-text'>${item.node.title}</div>`);
//return item.node.title;
};
self.formatSelection = function (item) {
return $(`<div class='prod-img-small' style="background-image:url('${item.node.featuredImage.transformedSrc}')"/></div><div class='prod-text'>${item.node.title}</div>`);
};
self.ajaxRequest = null;
self.select2 = {
label: 'Product',
readOnly: false,
value: options.fieldBinding,
multiple: false,
maximumSelectionSize: 1,
minimumInputLength: 0,
placeholder: 'Find product...',
formatResult: self.formatResult,
formatSelection: self.formatSelection,
templateResult: self.templateResult,
matcher: function (term, text) {
return true;
},
id: function (obj) {
//set content of the Agility CMS Content Item
//options.contentItem.Values.ExternalID(obj.ID)
//options.contentItem.Values.MyField1(obj.Value1)
//options.contentItem.Values.MyField2(obj.Value2)
//etc...
//save the whole thing as JSON
return JSON.stringify(obj.node)
},
ajax: { // instead of writing the function to execute the request we use Select2's convenient helper
url: `${baseAPIUrl}/api/search-products`,
dataType: 'json',
type: "get",
quietMillis: 250,
originalValue: ko.unwrap(options.fieldBinding),
term: "",
data: function (term, page, params) {
return {
filter: term, // search term
};
},
results: function (data, page) {
return {
results: data
};
},
current: function (data) {
},
cache: true
},
initSelection: function (element, callback) {
//use the hidden "product name" field
var json = ko.unwrap(options.fieldBinding);
console.log({ json })
if (json && json.length > 0) {
var node = JSON.parse(json)
console.log({ node })
callback({ node })
}
// console.log(val)
// var label = ko.unwrap(options.contentItem.Values.ProductName);
// if (val && label) {
// var data = {
// node: {
// id: val,
// title: label
// }
// };
// callback(data);
// }
},
allowClear: false,
dropdownCssClass: "bigdrop"
};
}
ko.applyBindings(viewModel, $pnl.get(0));
}
}
}
ContentManager.Global.CustomInputFormFields.push(new ChooseProductCustomField());

11991
yarn.lock

File diff suppressed because it is too large Load Diff