├── .nvmrc ├── .prettierignore ├── app ├── favicon.ico ├── opengraph-image.tsx ├── api │ └── revalidate │ │ └── route.ts ├── [page] │ ├── opengraph-image.tsx │ ├── layout.tsx │ └── page.tsx ├── search │ ├── loading.tsx │ ├── [collection] │ │ ├── opengraph-image.tsx │ │ └── page.tsx │ ├── layout.tsx │ └── page.tsx ├── robots.ts ├── globals.css ├── page.tsx ├── error.tsx ├── sitemap.ts ├── layout.tsx └── product │ └── [handle] │ └── page.tsx ├── fonts └── Inter-Bold.ttf ├── postcss.config.js ├── .github ├── dependabot.yml └── workflows │ └── test.yml ├── prettier.config.js ├── .vscode ├── settings.json └── launch.json ├── lib ├── bigcommerce │ ├── fragments │ │ ├── page.ts │ │ ├── product.ts │ │ └── cart.ts │ ├── constants.ts │ ├── queries │ │ ├── menu.ts │ │ ├── checkout.ts │ │ ├── category.ts │ │ ├── route.ts │ │ ├── cart.ts │ │ ├── page.ts │ │ └── product.ts │ ├── storefront-config.ts │ ├── mutations │ │ └── cart.ts │ ├── mappers.ts │ ├── types.ts │ └── index.ts ├── type-guards.ts ├── utils.ts └── constants.ts ├── components ├── cart │ ├── index.tsx │ ├── close-cart.tsx │ ├── open-cart.tsx │ ├── delete-item-button.tsx │ ├── edit-item-quantity-button.tsx │ ├── actions.ts │ ├── add-to-cart.tsx │ └── modal.tsx ├── grid │ ├── index.tsx │ ├── tile.tsx │ └── three-items.tsx ├── loading-dots.tsx ├── icons │ ├── logo.tsx │ └── github.tsx ├── price.tsx ├── logo-square.tsx ├── prose.tsx ├── layout │ ├── search │ │ ├── filter │ │ │ ├── index.tsx │ │ │ ├── dropdown.tsx │ │ │ └── item.tsx │ │ └── collections.tsx │ ├── product-grid-items.tsx │ ├── footer-menu.tsx │ ├── navbar │ │ ├── search.tsx │ │ ├── index.tsx │ │ └── mobile-menu.tsx │ └── footer.tsx ├── label.tsx ├── opengraph-image.tsx ├── product │ ├── product-description.tsx │ ├── gallery.tsx │ └── variant-selector.tsx └── carousel.tsx ├── .eslintrc.js ├── .gitignore ├── next.config.js ├── tsconfig.json ├── middleware.ts ├── license.md ├── tailwind.config.js ├── package.json ├── .env.example └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .vercel 2 | .next 3 | pnpm-lock.yaml 4 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcommerce/nextjs-commerce/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /fonts/Inter-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcommerce/nextjs-commerce/HEAD/fonts/Inter-Bold.ttf -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /app/opengraph-image.tsx: -------------------------------------------------------------------------------- 1 | import OpengraphImage from 'components/opengraph-image'; 2 | 3 | export const runtime = 'edge'; 4 | 5 | export default async function Image() { 6 | return await OpengraphImage(); 7 | } 8 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('prettier').Config} */ 2 | module.exports = { 3 | singleQuote: true, 4 | arrowParens: 'always', 5 | trailingComma: 'none', 6 | printWidth: 100, 7 | tabWidth: 2, 8 | plugins: ['prettier-plugin-tailwindcss'] 9 | }; 10 | -------------------------------------------------------------------------------- /app/api/revalidate/route.ts: -------------------------------------------------------------------------------- 1 | import { revalidate } from 'lib/bigcommerce'; 2 | import { NextRequest, NextResponse } from 'next/server'; 3 | 4 | export const runtime = 'edge'; 5 | 6 | export async function POST(req: NextRequest): Promise { 7 | return revalidate(req); 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll": true, 6 | "source.organizeImports": true, 7 | "source.sortMembers": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/bigcommerce/fragments/page.ts: -------------------------------------------------------------------------------- 1 | export const pageContentFragment = /* GraphQL */ ` 2 | fragment pageContent on WebPage { 3 | __typename 4 | entityId 5 | isVisibleInNavigation 6 | name 7 | seo { 8 | metaKeywords 9 | metaDescription 10 | pageTitle 11 | } 12 | } 13 | `; 14 | -------------------------------------------------------------------------------- /lib/bigcommerce/constants.ts: -------------------------------------------------------------------------------- 1 | export const BIGCOMMERCE_API_URL = process.env.BIGCOMMERCE_API_URL ?? 'https://api.bigcommerce.com'; 2 | export const BIGCOMMERCE_CANONICAL_STORE_DOMAIN = 3 | process.env.BIGCOMMERCE_CANONICAL_STORE_DOMAIN ?? 'mybigcommerce.com'; 4 | export const BIGCOMMERCE_GRAPHQL_API_ENDPOINT = `${BIGCOMMERCE_CANONICAL_STORE_DOMAIN}/graphql`; 5 | -------------------------------------------------------------------------------- /components/cart/index.tsx: -------------------------------------------------------------------------------- 1 | import { getCart } from 'lib/bigcommerce'; 2 | import { cookies } from 'next/headers'; 3 | import CartModal from './modal'; 4 | 5 | export default async function Cart() { 6 | const cartId = cookies().get('cartId')?.value; 7 | let cart; 8 | 9 | if (cartId) { 10 | cart = await getCart(cartId); 11 | } 12 | return ; 13 | } 14 | -------------------------------------------------------------------------------- /app/[page]/opengraph-image.tsx: -------------------------------------------------------------------------------- 1 | import OpengraphImage from 'components/opengraph-image'; 2 | import { getPage } from 'lib/bigcommerce'; 3 | 4 | export const runtime = 'edge'; 5 | 6 | export default async function Image({ params }: { params: { page: string } }) { 7 | const page = await getPage(params.page); 8 | const title = page.seo?.title || page.title; 9 | 10 | return await OpengraphImage({ title }); 11 | } 12 | -------------------------------------------------------------------------------- /app/search/loading.tsx: -------------------------------------------------------------------------------- 1 | import Grid from 'components/grid'; 2 | 3 | export default function Loading() { 4 | return ( 5 | 6 | {Array(12) 7 | .fill(0) 8 | .map((_, index) => { 9 | return ( 10 | 11 | ); 12 | })} 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /app/robots.ts: -------------------------------------------------------------------------------- 1 | import { MetadataRoute } from 'next'; 2 | 3 | const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL 4 | ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}` 5 | : 'http://localhost:3000'; 6 | 7 | export default function robots(): MetadataRoute.Robots { 8 | return { 9 | rules: [ 10 | { 11 | userAgent: '*' 12 | } 13 | ], 14 | sitemap: `${baseUrl}/sitemap.xml`, 15 | host: baseUrl 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/[page]/layout.tsx: -------------------------------------------------------------------------------- 1 | import Footer from 'components/layout/footer'; 2 | import { Suspense } from 'react'; 3 | 4 | export default function Layout({ children }: { children: React.ReactNode }) { 5 | return ( 6 | 7 |
8 |
9 | {children} 10 |
11 |
12 |