mirror of
https://github.com/vercel/commerce.git
synced 2025-06-19 21:51:21 +00:00
feat(recipes): generate recipe pages from airtable (#178995849)
This commit is contained in:
parent
e4a45a7ea3
commit
ff7a5a3728
51
lib/api/airtable.ts
Normal file
51
lib/api/airtable.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import Airtable from 'airtable'
|
||||
|
||||
const API_KEY = process.env.AIRTABLE_API_KEY || ''
|
||||
const BASE_ID = process.env.AIRTABLE_BASE_ID || ''
|
||||
|
||||
const base = new Airtable({ apiKey: API_KEY }).base(BASE_ID)
|
||||
|
||||
export interface Recipe {
|
||||
id: string
|
||||
title: string
|
||||
slug: string
|
||||
}
|
||||
|
||||
export const getRecipePages = async (): Promise<string[]> =>
|
||||
new Promise((resolve, reject) =>
|
||||
base('Recipes')
|
||||
.select({
|
||||
view: 'Pages',
|
||||
filterByFormula: "{Status} = 'Live'",
|
||||
fields: ['Slug'],
|
||||
})
|
||||
.all((error, records = []) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else {
|
||||
resolve(records.map((recipe) => recipe.get('Slug') as string))
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
export const getRecipes = async (): Promise<Recipe[]> =>
|
||||
new Promise((resolve, reject) =>
|
||||
base('Recipes')
|
||||
.select({
|
||||
view: 'Pages',
|
||||
filterByFormula: "{Status} = 'Live'",
|
||||
})
|
||||
.all((error, records = []) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else {
|
||||
resolve(
|
||||
records.map((recipe) => ({
|
||||
id: recipe.getId(),
|
||||
title: recipe.get('Recipes') as string,
|
||||
slug: recipe.get('Slug') as string,
|
||||
}))
|
||||
)
|
||||
}
|
||||
})
|
||||
)
|
@ -14,7 +14,7 @@ const isVendure = provider === 'vendure'
|
||||
module.exports = withCommerceConfig({
|
||||
commerce,
|
||||
i18n: {
|
||||
locales: ['en-US', 'es'],
|
||||
locales: ['en-US'],
|
||||
defaultLocale: 'en-US',
|
||||
},
|
||||
rewrites() {
|
||||
|
@ -5,6 +5,7 @@
|
||||
"dev": "NODE_OPTIONS='--inspect' next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"analyze": "BUNDLE_ANALYZE=both yarn build",
|
||||
"prettier-fix": "prettier --write .",
|
||||
"find:unused": "npx next-unused",
|
||||
@ -21,6 +22,7 @@
|
||||
"dependencies": {
|
||||
"@react-spring/web": "^9.2.1",
|
||||
"@vercel/fetch": "^6.1.0",
|
||||
"airtable": "^0.11.1",
|
||||
"autoprefixer": "^10.2.6",
|
||||
"body-scroll-lock": "^3.1.5",
|
||||
"classnames": "^2.3.1",
|
||||
|
@ -1,86 +0,0 @@
|
||||
import type {
|
||||
GetStaticPathsContext,
|
||||
GetStaticPropsContext,
|
||||
InferGetStaticPropsType,
|
||||
} from 'next'
|
||||
import commerce from '@lib/api/commerce'
|
||||
import { Text } from '@components/ui'
|
||||
import { Layout } from '@components/common'
|
||||
import getSlug from '@lib/get-slug'
|
||||
import { missingLocaleInPages } from '@lib/usage-warns'
|
||||
import type { Page } from '@commerce/types/page'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
export async function getStaticProps({
|
||||
preview,
|
||||
params,
|
||||
locale,
|
||||
locales,
|
||||
}: GetStaticPropsContext<{ pages: string[] }>) {
|
||||
const config = { locale, locales }
|
||||
const pagesPromise = commerce.getAllPages({ config, preview })
|
||||
const siteInfoPromise = commerce.getSiteInfo({ config, preview })
|
||||
const { pages } = await pagesPromise
|
||||
const { categories } = await siteInfoPromise
|
||||
const path = params?.pages.join('/')
|
||||
const slug = locale ? `${locale}/${path}` : path
|
||||
const pageItem = pages.find((p: Page) =>
|
||||
p.url ? getSlug(p.url) === slug : false
|
||||
)
|
||||
const data =
|
||||
pageItem &&
|
||||
(await commerce.getPage({
|
||||
variables: { id: pageItem.id! },
|
||||
config,
|
||||
preview,
|
||||
}))
|
||||
|
||||
const page = data?.page
|
||||
|
||||
if (!page) {
|
||||
// We throw to make sure this fails at build time as this is never expected to happen
|
||||
throw new Error(`Page with slug '${slug}' not found`)
|
||||
}
|
||||
|
||||
return {
|
||||
props: { pages, page, categories },
|
||||
revalidate: 60 * 60, // Every hour
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticPaths({ locales }: GetStaticPathsContext) {
|
||||
const config = { locales }
|
||||
const { pages }: { pages: Page[] } = await commerce.getAllPages({ config })
|
||||
const [invalidPaths, log] = missingLocaleInPages()
|
||||
const paths = pages
|
||||
.map((page) => page.url)
|
||||
.filter((url) => {
|
||||
if (!url || !locales) return url
|
||||
// If there are locales, only include the pages that include one of the available locales
|
||||
if (locales.includes(getSlug(url).split('/')[0])) return url
|
||||
|
||||
invalidPaths.push(url)
|
||||
})
|
||||
log()
|
||||
|
||||
return {
|
||||
paths,
|
||||
fallback: 'blocking',
|
||||
}
|
||||
}
|
||||
|
||||
export default function Pages({
|
||||
page,
|
||||
}: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||
const router = useRouter()
|
||||
|
||||
return router.isFallback ? (
|
||||
<h1>Loading...</h1> // TODO (BC) Add Skeleton Views
|
||||
) : (
|
||||
<div className="max-w-2xl mx-8 sm:mx-auto py-20">
|
||||
{page?.body && <Text html={page.body} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Pages.Layout = Layout
|
@ -1,81 +0,0 @@
|
||||
import type {
|
||||
GetStaticPathsContext,
|
||||
GetStaticPropsContext,
|
||||
InferGetStaticPropsType,
|
||||
} from 'next'
|
||||
import { useRouter } from 'next/router'
|
||||
import commerce from '@lib/api/commerce'
|
||||
import { Layout } from '@components/common'
|
||||
import { ProductView } from '@components/product'
|
||||
|
||||
export async function getStaticProps({
|
||||
params,
|
||||
locale,
|
||||
locales,
|
||||
preview,
|
||||
}: GetStaticPropsContext<{ slug: string }>) {
|
||||
const config = { locale, locales }
|
||||
const pagesPromise = commerce.getAllPages({ config, preview })
|
||||
const siteInfoPromise = commerce.getSiteInfo({ config, preview })
|
||||
const productPromise = commerce.getProduct({
|
||||
variables: { slug: params!.slug },
|
||||
config,
|
||||
preview,
|
||||
})
|
||||
|
||||
const allProductsPromise = commerce.getAllProducts({
|
||||
variables: { first: 4 },
|
||||
config,
|
||||
preview,
|
||||
})
|
||||
const { pages } = await pagesPromise
|
||||
const { categories } = await siteInfoPromise
|
||||
const { product } = await productPromise
|
||||
const { products: relatedProducts } = await allProductsPromise
|
||||
|
||||
if (!product) {
|
||||
throw new Error(`Product with slug '${params!.slug}' not found`)
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
pages,
|
||||
product,
|
||||
relatedProducts,
|
||||
categories,
|
||||
},
|
||||
revalidate: 200,
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticPaths({ locales }: GetStaticPathsContext) {
|
||||
const { products } = await commerce.getAllProductPaths()
|
||||
|
||||
return {
|
||||
paths: locales
|
||||
? locales.reduce<string[]>((arr, locale) => {
|
||||
// Add a product path for every locale
|
||||
products.forEach((product: any) => {
|
||||
arr.push(`/${locale}/product${product.path}`)
|
||||
})
|
||||
return arr
|
||||
}, [])
|
||||
: products.map((product: any) => `/product${product.path}`),
|
||||
fallback: 'blocking',
|
||||
}
|
||||
}
|
||||
|
||||
export default function Slug({
|
||||
product,
|
||||
relatedProducts,
|
||||
}: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||
const router = useRouter()
|
||||
|
||||
return router.isFallback ? (
|
||||
<h1>Loading...</h1>
|
||||
) : (
|
||||
<ProductView product={product} relatedProducts={relatedProducts} />
|
||||
)
|
||||
}
|
||||
|
||||
Slug.Layout = Layout
|
64
pages/recipes/[slug].tsx
Normal file
64
pages/recipes/[slug].tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import type { GetStaticPropsContext, InferGetStaticPropsType } from 'next'
|
||||
|
||||
import { getRecipePages, getRecipes } from '@lib/api/airtable'
|
||||
import { Text } from '@components/ui'
|
||||
|
||||
interface Recipe {
|
||||
slug: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export const getStaticProps = async ({
|
||||
params,
|
||||
}: GetStaticPropsContext<{ slug: string }>) => {
|
||||
// const config = { locale, locales }
|
||||
// const pagesPromise = commerce.getAllPages({ config, preview })
|
||||
// const siteInfoPromise = commerce.getSiteInfo({ config, preview })
|
||||
// const { pages } = await pagesPromise
|
||||
// const { categories } = await siteInfoPromise
|
||||
// const path = params?.pages.join('/')
|
||||
const recipes = await getRecipes()
|
||||
const recipe = recipes.find((recipe) => recipe.slug === params?.slug)
|
||||
// const pageItem = pages.find((p: Page) =>
|
||||
// p.url ? getSlug(p.url) === slug : false
|
||||
// )
|
||||
// const data =
|
||||
// pageItem &&
|
||||
// (await commerce.getPage({
|
||||
// variables: { id: pageItem.id! },
|
||||
// config,
|
||||
// preview,
|
||||
// }))
|
||||
|
||||
// const page = data?.page
|
||||
|
||||
if (!recipe) {
|
||||
// We throw to make sure this fails at build time as this is never expected to happen
|
||||
throw new Error(`Page with slug '${params?.slug}' not found`)
|
||||
}
|
||||
|
||||
return {
|
||||
props: { recipe },
|
||||
revalidate: 60 * 60, // Every hour
|
||||
}
|
||||
}
|
||||
|
||||
export const getStaticPaths = async () => {
|
||||
const pages = await getRecipePages()
|
||||
return {
|
||||
paths: pages.map((slug) => ({
|
||||
params: {
|
||||
slug,
|
||||
},
|
||||
})),
|
||||
fallback: false,
|
||||
}
|
||||
}
|
||||
|
||||
const Recipe = ({ recipe }: InferGetStaticPropsType<typeof getStaticProps>) => (
|
||||
<div className="max-w-2xl mx-8 sm:mx-auto py-20">
|
||||
<Text>{recipe.title}</Text>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default Recipe
|
@ -34,6 +34,11 @@
|
||||
"./framework/shopify",
|
||||
"./framework/swell",
|
||||
"./framework/vendure",
|
||||
"./framework/saleor"
|
||||
"./framework/saleor",
|
||||
"framework/bigcommerce",
|
||||
"framework/saleor",
|
||||
"framework/swell",
|
||||
"framework/vendure",
|
||||
"framework/local"
|
||||
]
|
||||
}
|
||||
|
41
yarn.lock
41
yarn.lock
@ -1116,6 +1116,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67"
|
||||
integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==
|
||||
|
||||
"@types/node@>=8.0.0 <15":
|
||||
version "14.17.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.5.tgz#b59daf6a7ffa461b5648456ca59050ba8e40ed54"
|
||||
integrity sha512-bjqH2cX/O33jXT/UmReo2pM7DIJREPMnarixbQ57DOOzzFaI6D2+IcwaJQaJpv0M1E9TIhPCYVxrkcityLjlqA==
|
||||
|
||||
"@types/parse-json@^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||
@ -1140,6 +1145,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
|
||||
integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==
|
||||
|
||||
"@types/uuid@8.3.1":
|
||||
version "8.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f"
|
||||
integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==
|
||||
|
||||
"@types/websocket@1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.2.tgz#d2855c6a312b7da73ed16ba6781815bf30c6187a"
|
||||
@ -1185,13 +1195,18 @@
|
||||
async-retry "1.2.3"
|
||||
lru-cache "5.1.1"
|
||||
|
||||
abort-controller@3.0.0:
|
||||
abort-controller@3.0.0, abort-controller@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
|
||||
integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
|
||||
dependencies:
|
||||
event-target-shim "^5.0.0"
|
||||
|
||||
abortcontroller-polyfill@^1.4.0:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz#1b5b487bd6436b5b764fd52a612509702c3144b5"
|
||||
integrity sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q==
|
||||
|
||||
acorn-node@^1.6.1:
|
||||
version "1.8.2"
|
||||
resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8"
|
||||
@ -1243,6 +1258,17 @@ aggregate-error@^3.0.0:
|
||||
clean-stack "^2.0.0"
|
||||
indent-string "^4.0.0"
|
||||
|
||||
airtable@^0.11.1:
|
||||
version "0.11.1"
|
||||
resolved "https://registry.yarnpkg.com/airtable/-/airtable-0.11.1.tgz#2fda51da04f5e4be7092351ea7be3cfdcf308347"
|
||||
integrity sha512-33zBuUDhLl+FWWAFxFjS1a+vJr/b+UK//EV943nuiimChWph6YykQjYPmu/GucQ30g7mgaqq+98uPD4rfDHOgg==
|
||||
dependencies:
|
||||
"@types/node" ">=8.0.0 <15"
|
||||
abort-controller "^3.0.0"
|
||||
abortcontroller-polyfill "^1.4.0"
|
||||
lodash "^4.17.21"
|
||||
node-fetch "^2.6.1"
|
||||
|
||||
anser@1.4.9:
|
||||
version "1.4.9"
|
||||
resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.9.tgz#1f85423a5dcf8da4631a341665ff675b96845760"
|
||||
@ -6044,6 +6070,19 @@ util@^0.12.0:
|
||||
safe-buffer "^5.1.2"
|
||||
which-typed-array "^1.1.2"
|
||||
|
||||
uuid@8.3.2:
|
||||
version "8.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||
|
||||
uuidv4@^6.2.10:
|
||||
version "6.2.11"
|
||||
resolved "https://registry.yarnpkg.com/uuidv4/-/uuidv4-6.2.11.tgz#34d5a03324eb38296b87ae523a64233b5286cc27"
|
||||
integrity sha512-OTS4waH9KplrXNADKo+Q1kT9AHWr8DaC0S5F54RQzEwcUaEzBEWQQlJyDUw/u1bkRhJyqkqhLD4M4lbFbV+89g==
|
||||
dependencies:
|
||||
"@types/uuid" "8.3.1"
|
||||
uuid "8.3.2"
|
||||
|
||||
valid-url@1.0.9, valid-url@^1.0.9:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200"
|
||||
|
Loading…
x
Reference in New Issue
Block a user