From ff7a5a37282bf95e2d5e52227b8cbfdd8805555b Mon Sep 17 00:00:00 2001 From: Bhaskar Murthy Date: Sat, 24 Jul 2021 19:30:45 -0400 Subject: [PATCH] feat(recipes): generate recipe pages from airtable (#178995849) --- lib/api/airtable.ts | 51 ++++++++++++++++++++++++ next.config.js | 2 +- package.json | 2 + pages/[...pages].tsx | 86 ---------------------------------------- pages/product/[slug].tsx | 81 ------------------------------------- pages/recipes/[slug].tsx | 64 ++++++++++++++++++++++++++++++ tsconfig.json | 7 +++- yarn.lock | 41 ++++++++++++++++++- 8 files changed, 164 insertions(+), 170 deletions(-) create mode 100644 lib/api/airtable.ts delete mode 100644 pages/[...pages].tsx delete mode 100644 pages/product/[slug].tsx create mode 100644 pages/recipes/[slug].tsx diff --git a/lib/api/airtable.ts b/lib/api/airtable.ts new file mode 100644 index 000000000..a77f21100 --- /dev/null +++ b/lib/api/airtable.ts @@ -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 => + 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 => + 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, + })) + ) + } + }) + ) diff --git a/next.config.js b/next.config.js index 515b2ae7c..721fe46e4 100644 --- a/next.config.js +++ b/next.config.js @@ -14,7 +14,7 @@ const isVendure = provider === 'vendure' module.exports = withCommerceConfig({ commerce, i18n: { - locales: ['en-US', 'es'], + locales: ['en-US'], defaultLocale: 'en-US', }, rewrites() { diff --git a/package.json b/package.json index 95b46c497..a53d98f90 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pages/[...pages].tsx b/pages/[...pages].tsx deleted file mode 100644 index a8a24b3aa..000000000 --- a/pages/[...pages].tsx +++ /dev/null @@ -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) { - const router = useRouter() - - return router.isFallback ? ( -

Loading...

// TODO (BC) Add Skeleton Views - ) : ( -
- {page?.body && } -
- ) -} - -Pages.Layout = Layout diff --git a/pages/product/[slug].tsx b/pages/product/[slug].tsx deleted file mode 100644 index bb1ecbee3..000000000 --- a/pages/product/[slug].tsx +++ /dev/null @@ -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((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) { - const router = useRouter() - - return router.isFallback ? ( -

Loading...

- ) : ( - - ) -} - -Slug.Layout = Layout diff --git a/pages/recipes/[slug].tsx b/pages/recipes/[slug].tsx new file mode 100644 index 000000000..45a47b878 --- /dev/null +++ b/pages/recipes/[slug].tsx @@ -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) => ( +
+ {recipe.title} +
+) + +export default Recipe diff --git a/tsconfig.json b/tsconfig.json index c37583a0a..35e554a16 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -34,6 +34,11 @@ "./framework/shopify", "./framework/swell", "./framework/vendure", - "./framework/saleor" + "./framework/saleor", + "framework/bigcommerce", + "framework/saleor", + "framework/swell", + "framework/vendure", + "framework/local" ] } diff --git a/yarn.lock b/yarn.lock index 8d1a534a5..34546d330 100644 --- a/yarn.lock +++ b/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"