diff --git a/examples/common/apps.ts b/examples/common/apps.ts
index 939e395f..d868bb15 100644
--- a/examples/common/apps.ts
+++ b/examples/common/apps.ts
@@ -6,7 +6,6 @@ const apps = [
"playground14",
"playground15",
"vercel-blog-starter",
- "vercel-commerce",
"ssg-app",
// e2e
"app-pages-router",
diff --git a/examples/vercel-commerce/.dev.vars.example b/examples/vercel-commerce/.dev.vars.example
deleted file mode 100644
index 9ff0463d..00000000
--- a/examples/vercel-commerce/.dev.vars.example
+++ /dev/null
@@ -1,7 +0,0 @@
-COMPANY_NAME="Vercel Inc."
-TWITTER_CREATOR="@vercel"
-TWITTER_SITE="https://nextjs.org/commerce"
-SITE_NAME="Next.js Commerce"
-SHOPIFY_REVALIDATION_SECRET=""
-SHOPIFY_STOREFRONT_ACCESS_TOKEN=""
-SHOPIFY_STORE_DOMAIN="[your-shopify-store-subdomain].myshopify.com"
diff --git a/examples/vercel-commerce/.env.example b/examples/vercel-commerce/.env.example
deleted file mode 100644
index 9ff0463d..00000000
--- a/examples/vercel-commerce/.env.example
+++ /dev/null
@@ -1,7 +0,0 @@
-COMPANY_NAME="Vercel Inc."
-TWITTER_CREATOR="@vercel"
-TWITTER_SITE="https://nextjs.org/commerce"
-SITE_NAME="Next.js Commerce"
-SHOPIFY_REVALIDATION_SECRET=""
-SHOPIFY_STOREFRONT_ACCESS_TOKEN=""
-SHOPIFY_STORE_DOMAIN="[your-shopify-store-subdomain].myshopify.com"
diff --git a/examples/vercel-commerce/.gitignore b/examples/vercel-commerce/.gitignore
deleted file mode 100644
index a0c54461..00000000
--- a/examples/vercel-commerce/.gitignore
+++ /dev/null
@@ -1,43 +0,0 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-/.pnp
-.pnp.js
-
-# testing
-/coverage
-.playwright
-
-# next.js
-/.next/
-/out/
-
-# production
-/build
-
-# misc
-.DS_Store
-*.pem
-
-# debug
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-.pnpm-debug.log*
-
-# local env files
-.env*
-!.env.example
-
-# vercel
-.vercel
-
-# typescript
-*.tsbuildinfo
-next-env.d.ts
-
-# cf
-.dev.vars
-.wrangler
-.worker-next
diff --git a/examples/vercel-commerce/.vscode/launch.json b/examples/vercel-commerce/.vscode/launch.json
deleted file mode 100644
index 448434dd..00000000
--- a/examples/vercel-commerce/.vscode/launch.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "version": "0.2.0",
- "configurations": [
- {
- "name": "Next.js: debug server-side",
- "type": "node-terminal",
- "request": "launch",
- "command": "pnpm dev"
- },
- {
- "name": "Next.js: debug client-side",
- "type": "chrome",
- "request": "launch",
- "url": "http://localhost:3000"
- },
- {
- "name": "Next.js: debug full stack",
- "type": "node-terminal",
- "request": "launch",
- "command": "pnpm dev",
- "serverReadyAction": {
- "pattern": "started server on .+, url: (https?://.+)",
- "uriFormat": "%s",
- "action": "debugWithChrome"
- }
- }
- ]
-}
diff --git a/examples/vercel-commerce/.vscode/settings.json b/examples/vercel-commerce/.vscode/settings.json
deleted file mode 100644
index 8345c107..00000000
--- a/examples/vercel-commerce/.vscode/settings.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "typescript.tsdk": "node_modules/typescript/lib",
- "typescript.enablePromptUseWorkspaceTsdk": true,
- "editor.codeActionsOnSave": {
- "source.fixAll": "explicit",
- "source.organizeImports": "explicit",
- "source.sortMembers": "explicit"
- }
-}
diff --git a/examples/vercel-commerce/README.md b/examples/vercel-commerce/README.md
deleted file mode 100644
index dd2c70b0..00000000
--- a/examples/vercel-commerce/README.md
+++ /dev/null
@@ -1,72 +0,0 @@
-[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fcommerce&project-name=commerce&repo-name=commerce&demo-title=Next.js%20Commerce&demo-url=https%3A%2F%2Fdemo.vercel.store&demo-image=https%3A%2F%2Fbigcommerce-demo-asset-ksvtgfvnd.vercel.app%2Fbigcommerce.png&env=COMPANY_NAME,SHOPIFY_REVALIDATION_SECRET,SHOPIFY_STORE_DOMAIN,SHOPIFY_STOREFRONT_ACCESS_TOKEN,SITE_NAME,TWITTER_CREATOR,TWITTER_SITE)
-
-# Next.js Commerce
-
-A high-perfomance, server-rendered Next.js App Router ecommerce application.
-
-This template uses React Server Components, Server Actions, `Suspense`, `useOptimistic`, and more.
-
-
-
-> Note: Looking for Next.js Commerce v1? View the [code](https://github.com/vercel/commerce/tree/v1), [demo](https://commerce-v1.vercel.store), and [release notes](https://github.com/vercel/commerce/releases/tag/v1).
-
-## Providers
-
-Vercel will only be actively maintaining a Shopify version [as outlined in our vision and strategy for Next.js Commerce](https://github.com/vercel/commerce/pull/966).
-
-Vercel is happy to partner and work with any commerce provider to help them get a similar template up and running and listed below. Alternative providers should be able to fork this repository and swap out the `lib/shopify` file with their own implementation while leaving the rest of the template mostly unchanged.
-
-- Shopify (this repository)
-- [BigCommerce](https://github.com/bigcommerce/nextjs-commerce) ([Demo](https://next-commerce-v2.vercel.app/))
-- [Ecwid by Lightspeed](https://github.com/Ecwid/ecwid-nextjs-commerce/) ([Demo](https://ecwid-nextjs-commerce.vercel.app/))
-- [Medusa](https://github.com/medusajs/vercel-commerce) ([Demo](https://medusa-nextjs-commerce.vercel.app/))
-- [Saleor](https://github.com/saleor/nextjs-commerce) ([Demo](https://saleor-commerce.vercel.app/))
-- [Shopware](https://github.com/shopwareLabs/vercel-commerce) ([Demo](https://shopware-vercel-commerce-react.vercel.app/))
-- [Swell](https://github.com/swellstores/verswell-commerce) ([Demo](https://verswell-commerce.vercel.app/))
-- [Umbraco](https://github.com/umbraco/Umbraco.VercelCommerce.Demo) ([Demo](https://vercel-commerce-demo.umbraco.com/))
-- [Wix](https://github.com/wix/nextjs-commerce) ([Demo](https://wix-nextjs-commerce.vercel.app/))
-
-> Note: Providers, if you are looking to use similar products for your demo, you can [download these assets](https://drive.google.com/file/d/1q_bKerjrwZgHwCw0ovfUMW6He9VtepO_/view?usp=sharing).
-
-## Integrations
-
-Integrations enable upgraded or additional functionality for Next.js Commerce
-
-- [Orama](https://github.com/oramasearch/nextjs-commerce) ([Demo](https://vercel-commerce.oramasearch.com/))
-
- - Upgrades search to include typeahead with dynamic re-rendering, vector-based similarity search, and JS-based configuration.
- - Search runs entirely in the browser for smaller catalogs or on a CDN for larger.
-
-- [React Bricks](https://github.com/ReactBricks/nextjs-commerce-rb) ([Demo](https://nextjs-commerce.reactbricks.com/))
- - Edit pages, product details, and footer content visually using [React Bricks](https://www.reactbricks.com) visual headless CMS.
-
-## Running locally
-
-You will need to use the environment variables [defined in `.env.example`](.env.example) to run Next.js Commerce. It's recommended you use [Vercel Environment Variables](https://vercel.com/docs/concepts/projects/environment-variables) for this, but a `.env` file is all that is necessary.
-
-> Note: You should not commit your `.env` file or it will expose secrets that will allow others to control your Shopify store.
-
-1. Install Vercel CLI: `npm i -g vercel`
-2. Link local instance with Vercel and GitHub accounts (creates `.vercel` directory): `vercel link`
-3. Download your environment variables: `vercel env pull`
-
-```bash
-pnpm install
-pnpm dev
-```
-
-Your app should now be running on [localhost:3000](http://localhost:3000/).
-
-
- Expand if you work at Vercel and want to run locally and / or contribute
-
-1. Run `vc link`.
-1. Select the `Vercel Solutions` scope.
-1. Connect to the existing `commerce-shopify` project.
-1. Run `vc env pull` to get environment variables.
-1. Run `pnpm dev` to ensure everything is working correctly.
-
-
-## Vercel, Next.js Commerce, and Shopify Integration Guide
-
-You can use this comprehensive [integration guide](https://vercel.com/docs/integrations/ecommerce/shopify) with step-by-step instructions on how to configure Shopify as a headless CMS using Next.js Commerce as your headless Shopify storefront on Vercel.
diff --git a/examples/vercel-commerce/app/[page]/layout.tsx b/examples/vercel-commerce/app/[page]/layout.tsx
deleted file mode 100644
index 50614b5b..00000000
--- a/examples/vercel-commerce/app/[page]/layout.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import Footer from 'components/layout/footer';
-
-export default function Layout({ children }: { children: React.ReactNode }) {
- return (
- <>
-
-
- >
- );
-}
diff --git a/examples/vercel-commerce/app/[page]/opengraph-image.tsx b/examples/vercel-commerce/app/[page]/opengraph-image.tsx
deleted file mode 100644
index 2fd59281..00000000
--- a/examples/vercel-commerce/app/[page]/opengraph-image.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import OpengraphImage from 'components/opengraph-image';
-import { getPage } from 'lib/shopify';
-
-export const runtime = 'edge';
-
-export default async function Image({ params }: { params: { page: string } }) {
- const page = await getPage(params.page);
- const title = page.seo?.title || page.title;
-
- return await OpengraphImage({ title });
-}
diff --git a/examples/vercel-commerce/app/[page]/page.tsx b/examples/vercel-commerce/app/[page]/page.tsx
deleted file mode 100644
index 02aaf136..00000000
--- a/examples/vercel-commerce/app/[page]/page.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import type { Metadata } from 'next';
-
-import Prose from 'components/prose';
-import { getPage } from 'lib/shopify';
-import { notFound } from 'next/navigation';
-
-export async function generateMetadata({
- params
-}: {
- params: { page: string };
-}): Promise {
- const page = await getPage(params.page);
-
- if (!page) return notFound();
-
- return {
- title: page.seo?.title || page.title,
- description: page.seo?.description || page.bodySummary,
- openGraph: {
- publishedTime: page.createdAt,
- modifiedTime: page.updatedAt,
- type: 'article'
- }
- };
-}
-
-export default async function Page({ params }: { params: { page: string } }) {
- const page = await getPage(params.page);
-
- if (!page) return notFound();
-
- return (
- <>
- {page.title}
-
-
- {`This document was last updated on ${new Intl.DateTimeFormat(undefined, {
- year: 'numeric',
- month: 'long',
- day: 'numeric'
- }).format(new Date(page.updatedAt))}.`}
-
- >
- );
-}
diff --git a/examples/vercel-commerce/app/api/revalidate/route.ts b/examples/vercel-commerce/app/api/revalidate/route.ts
deleted file mode 100644
index 4ecc0b45..00000000
--- a/examples/vercel-commerce/app/api/revalidate/route.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { revalidate } from 'lib/shopify';
-import { NextRequest, NextResponse } from 'next/server';
-
-export async function POST(req: NextRequest): Promise {
- return revalidate(req);
-}
diff --git a/examples/vercel-commerce/app/error.tsx b/examples/vercel-commerce/app/error.tsx
deleted file mode 100644
index 80b32a42..00000000
--- a/examples/vercel-commerce/app/error.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-'use client';
-
-export default function Error({ reset }: { reset: () => void }) {
- return (
-
-
Oh no!
-
- There was an issue with our storefront. This could be a temporary issue, please try your
- action again.
-
-
reset()}
- >
- Try Again
-
-
- );
-}
diff --git a/examples/vercel-commerce/app/favicon.ico b/examples/vercel-commerce/app/favicon.ico
deleted file mode 100644
index dc7d8431..00000000
Binary files a/examples/vercel-commerce/app/favicon.ico and /dev/null differ
diff --git a/examples/vercel-commerce/app/globals.css b/examples/vercel-commerce/app/globals.css
deleted file mode 100644
index 0a6d3676..00000000
--- a/examples/vercel-commerce/app/globals.css
+++ /dev/null
@@ -1,21 +0,0 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
-@media (prefers-color-scheme: dark) {
- html {
- color-scheme: dark;
- }
-}
-
-@supports (font: -apple-system-body) and (-webkit-appearance: none) {
- img[loading='lazy'] {
- clip-path: inset(0.6px);
- }
-}
-
-a,
-input,
-button {
- @apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400 focus-visible:ring-offset-2 focus-visible:ring-offset-neutral-50 dark:focus-visible:ring-neutral-600 dark:focus-visible:ring-offset-neutral-900;
-}
diff --git a/examples/vercel-commerce/app/layout.tsx b/examples/vercel-commerce/app/layout.tsx
deleted file mode 100644
index 1c92b328..00000000
--- a/examples/vercel-commerce/app/layout.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { CartProvider } from 'components/cart/cart-context';
-import { Navbar } from 'components/layout/navbar';
-import { GeistSans } from 'geist/font/sans';
-import { getCart } from 'lib/shopify';
-import { ensureStartsWith } from 'lib/utils';
-import { cookies } from 'next/headers';
-import { ReactNode } from 'react';
-import './globals.css';
-
-const { TWITTER_CREATOR, TWITTER_SITE, SITE_NAME } = process.env;
-const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL
- ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`
- : 'http://localhost:3000';
-const twitterCreator = TWITTER_CREATOR ? ensureStartsWith(TWITTER_CREATOR, '@') : undefined;
-const twitterSite = TWITTER_SITE ? ensureStartsWith(TWITTER_SITE, 'https://') : undefined;
-
-export const metadata = {
- metadataBase: new URL(baseUrl),
- title: {
- default: SITE_NAME!,
- template: `%s | ${SITE_NAME}`
- },
- robots: {
- follow: true,
- index: true
- },
- ...(twitterCreator &&
- twitterSite && {
- twitter: {
- card: 'summary_large_image',
- creator: twitterCreator,
- site: twitterSite
- }
- })
-};
-
-export default async function RootLayout({ children }: { children: ReactNode }) {
- const cartId = cookies().get('cartId')?.value;
- // Don't await the fetch, pass the Promise to the context provider
- const cart = getCart(cartId);
-
- return (
-
-
-
-
- {children}
-
-
-
- );
-}
diff --git a/examples/vercel-commerce/app/opengraph-image.tsx b/examples/vercel-commerce/app/opengraph-image.tsx
deleted file mode 100644
index 23762cbd..00000000
--- a/examples/vercel-commerce/app/opengraph-image.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import OpengraphImage from 'components/opengraph-image';
-
-export const runtime = 'edge';
-
-export default async function Image() {
- return await OpengraphImage();
-}
diff --git a/examples/vercel-commerce/app/page.tsx b/examples/vercel-commerce/app/page.tsx
deleted file mode 100644
index 7d407ede..00000000
--- a/examples/vercel-commerce/app/page.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Carousel } from 'components/carousel';
-import { ThreeItemGrid } from 'components/grid/three-items';
-import Footer from 'components/layout/footer';
-
-export const metadata = {
- description: 'High-performance ecommerce store built with Next.js, Vercel, and Shopify.',
- openGraph: {
- type: 'website'
- }
-};
-
-export default function HomePage() {
- return (
- <>
-
-
-
- >
- );
-}
diff --git a/examples/vercel-commerce/app/product/[handle]/page.tsx b/examples/vercel-commerce/app/product/[handle]/page.tsx
deleted file mode 100644
index e2280675..00000000
--- a/examples/vercel-commerce/app/product/[handle]/page.tsx
+++ /dev/null
@@ -1,149 +0,0 @@
-import type { Metadata } from 'next';
-import { notFound } from 'next/navigation';
-
-import { GridTileImage } from 'components/grid/tile';
-import Footer from 'components/layout/footer';
-import { Gallery } from 'components/product/gallery';
-import { ProductProvider } from 'components/product/product-context';
-import { ProductDescription } from 'components/product/product-description';
-import { HIDDEN_PRODUCT_TAG } from 'lib/constants';
-import { getProduct, getProductRecommendations } from 'lib/shopify';
-import { Image } from 'lib/shopify/types';
-import Link from 'next/link';
-import { Suspense } from 'react';
-
-export async function generateMetadata({
- params
-}: {
- params: { handle: string };
-}): Promise {
- const product = await getProduct(params.handle);
-
- if (!product) return notFound();
-
- const { url, width, height, altText: alt } = product.featuredImage || {};
- const indexable = !product.tags.includes(HIDDEN_PRODUCT_TAG);
-
- return {
- title: product.seo.title || product.title,
- description: product.seo.description || product.description,
- robots: {
- index: indexable,
- follow: indexable,
- googleBot: {
- index: indexable,
- follow: indexable
- }
- },
- openGraph: url
- ? {
- images: [
- {
- url,
- width,
- height,
- alt
- }
- ]
- }
- : null
- };
-}
-
-export default async function ProductPage({ params }: { params: { handle: string } }) {
- const product = await getProduct(params.handle);
-
- if (!product) return notFound();
-
- const productJsonLd = {
- '@context': 'https://schema.org',
- '@type': 'Product',
- name: product.title,
- description: product.description,
- image: product.featuredImage.url,
- offers: {
- '@type': 'AggregateOffer',
- availability: product.availableForSale
- ? 'https://schema.org/InStock'
- : 'https://schema.org/OutOfStock',
- priceCurrency: product.priceRange.minVariantPrice.currencyCode,
- highPrice: product.priceRange.maxVariantPrice.amount,
- lowPrice: product.priceRange.minVariantPrice.amount
- }
- };
-
- return (
-
-
-
-
-
-
- }
- >
- ({
- src: image.url,
- altText: image.altText
- }))}
- />
-
-
-
-
-
-
-
-
-
- );
-}
-
-async function RelatedProducts({ id }: { id: string }) {
- const relatedProducts = await getProductRecommendations(id);
-
- if (!relatedProducts.length) return null;
-
- return (
-
-
Related Products
-
- {relatedProducts.map((product) => (
-
-
-
-
-
- ))}
-
-
- );
-}
diff --git a/examples/vercel-commerce/app/robots.ts b/examples/vercel-commerce/app/robots.ts
deleted file mode 100644
index c9849a27..00000000
--- a/examples/vercel-commerce/app/robots.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL
- ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`
- : 'http://localhost:3000';
-
-export default function robots() {
- return {
- rules: [
- {
- userAgent: '*'
- }
- ],
- sitemap: `${baseUrl}/sitemap.xml`,
- host: baseUrl
- };
-}
diff --git a/examples/vercel-commerce/app/search/[collection]/opengraph-image.tsx b/examples/vercel-commerce/app/search/[collection]/opengraph-image.tsx
deleted file mode 100644
index 9eb9c47f..00000000
--- a/examples/vercel-commerce/app/search/[collection]/opengraph-image.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import OpengraphImage from 'components/opengraph-image';
-import { getCollection } from 'lib/shopify';
-
-export const runtime = 'edge';
-
-export default async function Image({ params }: { params: { collection: string } }) {
- const collection = await getCollection(params.collection);
- const title = collection?.seo?.title || collection?.title;
-
- return await OpengraphImage({ title });
-}
diff --git a/examples/vercel-commerce/app/search/[collection]/page.tsx b/examples/vercel-commerce/app/search/[collection]/page.tsx
deleted file mode 100644
index e25542bf..00000000
--- a/examples/vercel-commerce/app/search/[collection]/page.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { getCollection, getCollectionProducts } from 'lib/shopify';
-import { Metadata } from 'next';
-import { notFound } from 'next/navigation';
-
-import Grid from 'components/grid';
-import ProductGridItems from 'components/layout/product-grid-items';
-import { defaultSort, sorting } from 'lib/constants';
-
-export async function generateMetadata({
- params
-}: {
- params: { collection: string };
-}): Promise {
- const collection = await getCollection(params.collection);
-
- if (!collection) return notFound();
-
- return {
- title: collection.seo?.title || collection.title,
- description:
- collection.seo?.description || collection.description || `${collection.title} products`
- };
-}
-
-export default async function CategoryPage({
- params,
- searchParams
-}: {
- params: { collection: string };
- searchParams?: { [key: string]: string | string[] | undefined };
-}) {
- const { sort } = searchParams as { [key: string]: string };
- const { sortKey, reverse } = sorting.find((item) => item.slug === sort) || defaultSort;
- const products = await getCollectionProducts({ collection: params.collection, sortKey, reverse });
-
- return (
-
- {products.length === 0 ? (
- {`No products found in this collection`}
- ) : (
-
-
-
- )}
-
- );
-}
diff --git a/examples/vercel-commerce/app/search/children-wrapper.tsx b/examples/vercel-commerce/app/search/children-wrapper.tsx
deleted file mode 100644
index 8d345b51..00000000
--- a/examples/vercel-commerce/app/search/children-wrapper.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-'use client';
-
-import { useSearchParams } from 'next/navigation';
-import { Fragment } from 'react';
-
-// Ensure children are re-rendered when the search query changes
-export default function ChildrenWrapper({ children }: { children: React.ReactNode }) {
- const searchParams = useSearchParams();
- return {children} ;
-}
diff --git a/examples/vercel-commerce/app/search/layout.tsx b/examples/vercel-commerce/app/search/layout.tsx
deleted file mode 100644
index 1af30ab5..00000000
--- a/examples/vercel-commerce/app/search/layout.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import Footer from 'components/layout/footer';
-import Collections from 'components/layout/search/collections';
-import FilterList from 'components/layout/search/filter';
-import { sorting } from 'lib/constants';
-import ChildrenWrapper from './children-wrapper';
-
-export default function SearchLayout({ children }: { children: React.ReactNode }) {
- return (
- <>
-
-
-
-
-
- {children}
-
-
-
-
-
-
- >
- );
-}
diff --git a/examples/vercel-commerce/app/search/loading.tsx b/examples/vercel-commerce/app/search/loading.tsx
deleted file mode 100644
index fa87c53f..00000000
--- a/examples/vercel-commerce/app/search/loading.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import Grid from 'components/grid';
-
-export default function Loading() {
- return (
- <>
-
-
- {Array(12)
- .fill(0)
- .map((_, index) => {
- return (
-
- );
- })}
-
- >
- );
-}
diff --git a/examples/vercel-commerce/app/search/page.tsx b/examples/vercel-commerce/app/search/page.tsx
deleted file mode 100644
index 60f11b18..00000000
--- a/examples/vercel-commerce/app/search/page.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import Grid from 'components/grid';
-import ProductGridItems from 'components/layout/product-grid-items';
-import { defaultSort, sorting } from 'lib/constants';
-import { getProducts } from 'lib/shopify';
-
-export const metadata = {
- title: 'Search',
- description: 'Search for products in the store.'
-};
-
-export default async function SearchPage({
- searchParams
-}: {
- searchParams?: { [key: string]: string | string[] | undefined };
-}) {
- const { sort, q: searchValue } = searchParams as { [key: string]: string };
- const { sortKey, reverse } = sorting.find((item) => item.slug === sort) || defaultSort;
-
- const products = await getProducts({ sortKey, reverse, query: searchValue });
- const resultsText = products.length > 1 ? 'results' : 'result';
-
- return (
- <>
- {searchValue ? (
-
- {products.length === 0
- ? 'There are no products that match '
- : `Showing ${products.length} ${resultsText} for `}
- "{searchValue}"
-
- ) : null}
- {products.length > 0 ? (
-
-
-
- ) : null}
- >
- );
-}
diff --git a/examples/vercel-commerce/app/sitemap.ts b/examples/vercel-commerce/app/sitemap.ts
deleted file mode 100644
index 8f31ee94..00000000
--- a/examples/vercel-commerce/app/sitemap.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { getCollections, getPages, getProducts } from 'lib/shopify';
-import { validateEnvironmentVariables } from 'lib/utils';
-import { MetadataRoute } from 'next';
-
-type Route = {
- url: string;
- lastModified: string;
-};
-
-const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL
- ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`
- : 'http://localhost:3000';
-
-export const dynamic = 'force-dynamic';
-
-export default async function sitemap(): Promise {
- validateEnvironmentVariables();
-
- const routesMap = [''].map((route) => ({
- url: `${baseUrl}${route}`,
- lastModified: new Date().toISOString()
- }));
-
- const collectionsPromise = getCollections().then((collections) =>
- collections.map((collection) => ({
- url: `${baseUrl}${collection.path}`,
- lastModified: collection.updatedAt
- }))
- );
-
- const productsPromise = getProducts({}).then((products) =>
- products.map((product) => ({
- url: `${baseUrl}/product/${product.handle}`,
- lastModified: product.updatedAt
- }))
- );
-
- const pagesPromise = getPages().then((pages) =>
- pages.map((page) => ({
- url: `${baseUrl}/${page.handle}`,
- lastModified: page.updatedAt
- }))
- );
-
- let fetchedRoutes: Route[] = [];
-
- try {
- fetchedRoutes = (await Promise.all([collectionsPromise, productsPromise, pagesPromise])).flat();
- } catch (error) {
- throw JSON.stringify(error, null, 2);
- }
-
- return [...routesMap, ...fetchedRoutes];
-}
diff --git a/examples/vercel-commerce/components/carousel.tsx b/examples/vercel-commerce/components/carousel.tsx
deleted file mode 100644
index bf7cd724..00000000
--- a/examples/vercel-commerce/components/carousel.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { getCollectionProducts } from 'lib/shopify';
-import Link from 'next/link';
-import { GridTileImage } from './grid/tile';
-
-export async function Carousel() {
- // Collections that start with `hidden-*` are hidden from the search page.
- const products = await getCollectionProducts({ collection: 'latest-stuff' });
-
- if (!products?.length) return null;
-
- // Purposefully duplicating products to make the carousel loop and not run out of products on wide screens.
- const carouselProducts = [...products, ...products, ...products];
-
- return (
-
-
- {carouselProducts.map((product, i) => (
-
-
-
-
-
- ))}
-
-
- );
-}
diff --git a/examples/vercel-commerce/components/cart/actions.ts b/examples/vercel-commerce/components/cart/actions.ts
deleted file mode 100644
index a2c59255..00000000
--- a/examples/vercel-commerce/components/cart/actions.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-'use server';
-
-import { TAGS } from 'lib/constants';
-import { addToCart, createCart, getCart, removeFromCart, updateCart } from 'lib/shopify';
-import { revalidateTag } from 'next/cache';
-import { cookies } from 'next/headers';
-import { redirect } from 'next/navigation';
-
-export async function addItem(prevState: any, selectedVariantId: string | undefined) {
- let cartId = cookies().get('cartId')?.value;
-
- if (!cartId || !selectedVariantId) {
- return 'Error adding item to cart';
- }
-
- try {
- await addToCart(cartId, [{ merchandiseId: selectedVariantId, quantity: 1 }]);
- revalidateTag(TAGS.cart);
- } catch (e) {
- return 'Error adding item to cart';
- }
-}
-
-export async function removeItem(prevState: any, merchandiseId: string) {
- let cartId = cookies().get('cartId')?.value;
-
- if (!cartId) {
- return 'Missing cart ID';
- }
-
- try {
- const cart = await getCart(cartId);
-
- if (!cart) {
- return 'Error fetching cart';
- }
-
- const lineItem = cart.lines.find((line) => line.merchandise.id === merchandiseId);
-
- if (lineItem && lineItem.id) {
- await removeFromCart(cartId, [lineItem.id]);
- revalidateTag(TAGS.cart);
- } else {
- return 'Item not found in cart';
- }
- } catch (e) {
- return 'Error removing item from cart';
- }
-}
-
-export async function updateItemQuantity(
- prevState: any,
- payload: {
- merchandiseId: string;
- quantity: number;
- }
-) {
- let cartId = cookies().get('cartId')?.value;
-
- if (!cartId) {
- return 'Missing cart ID';
- }
-
- const { merchandiseId, quantity } = payload;
-
- try {
- const cart = await getCart(cartId);
-
- if (!cart) {
- return 'Error fetching cart';
- }
-
- const lineItem = cart.lines.find((line) => line.merchandise.id === merchandiseId);
-
- if (lineItem && lineItem.id) {
- if (quantity === 0) {
- await removeFromCart(cartId, [lineItem.id]);
- } else {
- await updateCart(cartId, [
- {
- id: lineItem.id,
- merchandiseId,
- quantity
- }
- ]);
- }
- } else if (quantity > 0) {
- // If the item doesn't exist in the cart and quantity > 0, add it
- await addToCart(cartId, [{ merchandiseId, quantity }]);
- }
-
- revalidateTag(TAGS.cart);
- } catch (e) {
- console.error(e);
- return 'Error updating item quantity';
- }
-}
-
-export async function redirectToCheckout() {
- let cartId = cookies().get('cartId')?.value;
-
- if (!cartId) {
- return 'Missing cart ID';
- }
-
- let cart = await getCart(cartId);
-
- if (!cart) {
- return 'Error fetching cart';
- }
-
- redirect(cart.checkoutUrl);
-}
-
-export async function createCartAndSetCookie() {
- let cart = await createCart();
- cookies().set('cartId', cart.id!);
-}
diff --git a/examples/vercel-commerce/components/cart/add-to-cart.tsx b/examples/vercel-commerce/components/cart/add-to-cart.tsx
deleted file mode 100644
index 8bcebb06..00000000
--- a/examples/vercel-commerce/components/cart/add-to-cart.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-'use client';
-
-import { PlusIcon } from '@heroicons/react/24/outline';
-import clsx from 'clsx';
-import { addItem } from 'components/cart/actions';
-import { useProduct } from 'components/product/product-context';
-import { Product, ProductVariant } from 'lib/shopify/types';
-import { useFormState } from 'react-dom';
-import { useCart } from './cart-context';
-
-function SubmitButton({
- availableForSale,
- selectedVariantId
-}: {
- availableForSale: boolean;
- selectedVariantId: string | undefined;
-}) {
- const buttonClasses =
- 'relative flex w-full items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white';
- const disabledClasses = 'cursor-not-allowed opacity-60 hover:opacity-60';
-
- if (!availableForSale) {
- return (
-
- Out Of Stock
-
- );
- }
-
- console.log(selectedVariantId);
- if (!selectedVariantId) {
- return (
-
-
- Add To Cart
-
- );
- }
-
- return (
-
-
- Add To Cart
-
- );
-}
-
-export function AddToCart({ product }: { product: Product }) {
- const { variants, availableForSale } = product;
- const { addCartItem } = useCart();
- const { state } = useProduct();
- const [message, formAction] = useFormState(addItem, null);
-
- const variant = variants.find((variant: ProductVariant) =>
- variant.selectedOptions.every((option) => option.value === state[option.name.toLowerCase()])
- );
- const defaultVariantId = variants.length === 1 ? variants[0]?.id : undefined;
- const selectedVariantId = variant?.id || defaultVariantId;
- const actionWithVariant = formAction.bind(null, selectedVariantId);
- const finalVariant = variants.find((variant) => variant.id === selectedVariantId)!;
-
- return (
-
- );
-}
diff --git a/examples/vercel-commerce/components/cart/cart-context.tsx b/examples/vercel-commerce/components/cart/cart-context.tsx
deleted file mode 100644
index 2d8ddd42..00000000
--- a/examples/vercel-commerce/components/cart/cart-context.tsx
+++ /dev/null
@@ -1,184 +0,0 @@
-'use client';
-
-import type { Cart, CartItem, Product, ProductVariant } from 'lib/shopify/types';
-import React, { createContext, use, useContext, useMemo, useOptimistic } from 'react';
-
-type UpdateType = 'plus' | 'minus' | 'delete';
-
-type CartAction =
- | { type: 'UPDATE_ITEM'; payload: { merchandiseId: string; updateType: UpdateType } }
- | { type: 'ADD_ITEM'; payload: { variant: ProductVariant; product: Product } };
-
-type CartContextType = {
- cart: Cart | undefined;
- updateCartItem: (merchandiseId: string, updateType: UpdateType) => void;
- addCartItem: (variant: ProductVariant, product: Product) => void;
-};
-
-const CartContext = createContext(undefined);
-
-function calculateItemCost(quantity: number, price: string): string {
- return (Number(price) * quantity).toString();
-}
-
-function updateCartItem(item: CartItem, updateType: UpdateType): CartItem | null {
- if (updateType === 'delete') return null;
-
- const newQuantity = updateType === 'plus' ? item.quantity + 1 : item.quantity - 1;
- if (newQuantity === 0) return null;
-
- const singleItemAmount = Number(item.cost.totalAmount.amount) / item.quantity;
- const newTotalAmount = calculateItemCost(newQuantity, singleItemAmount.toString());
-
- return {
- ...item,
- quantity: newQuantity,
- cost: {
- ...item.cost,
- totalAmount: {
- ...item.cost.totalAmount,
- amount: newTotalAmount
- }
- }
- };
-}
-
-function createOrUpdateCartItem(
- existingItem: CartItem | undefined,
- variant: ProductVariant,
- product: Product
-): CartItem {
- const quantity = existingItem ? existingItem.quantity + 1 : 1;
- const totalAmount = calculateItemCost(quantity, variant.price.amount);
-
- return {
- id: existingItem?.id,
- quantity,
- cost: {
- totalAmount: {
- amount: totalAmount,
- currencyCode: variant.price.currencyCode
- }
- },
- merchandise: {
- id: variant.id,
- title: variant.title,
- selectedOptions: variant.selectedOptions,
- product: {
- id: product.id,
- handle: product.handle,
- title: product.title,
- featuredImage: product.featuredImage
- }
- }
- };
-}
-
-function updateCartTotals(lines: CartItem[]): Pick {
- const totalQuantity = lines.reduce((sum, item) => sum + item.quantity, 0);
- const totalAmount = lines.reduce((sum, item) => sum + Number(item.cost.totalAmount.amount), 0);
- const currencyCode = lines[0]?.cost.totalAmount.currencyCode ?? 'USD';
-
- return {
- totalQuantity,
- cost: {
- subtotalAmount: { amount: totalAmount.toString(), currencyCode },
- totalAmount: { amount: totalAmount.toString(), currencyCode },
- totalTaxAmount: { amount: '0', currencyCode }
- }
- };
-}
-
-function createEmptyCart(): Cart {
- return {
- id: undefined,
- checkoutUrl: '',
- totalQuantity: 0,
- lines: [],
- cost: {
- subtotalAmount: { amount: '0', currencyCode: 'USD' },
- totalAmount: { amount: '0', currencyCode: 'USD' },
- totalTaxAmount: { amount: '0', currencyCode: 'USD' }
- }
- };
-}
-
-function cartReducer(state: Cart | undefined, action: CartAction): Cart {
- const currentCart = state || createEmptyCart();
-
- switch (action.type) {
- case 'UPDATE_ITEM': {
- const { merchandiseId, updateType } = action.payload;
- const updatedLines = currentCart.lines
- .map((item) =>
- item.merchandise.id === merchandiseId ? updateCartItem(item, updateType) : item
- )
- .filter(Boolean) as CartItem[];
-
- if (updatedLines.length === 0) {
- return {
- ...currentCart,
- lines: [],
- totalQuantity: 0,
- cost: {
- ...currentCart.cost,
- totalAmount: { ...currentCart.cost.totalAmount, amount: '0' }
- }
- };
- }
-
- return { ...currentCart, ...updateCartTotals(updatedLines), lines: updatedLines };
- }
- case 'ADD_ITEM': {
- const { variant, product } = action.payload;
- const existingItem = currentCart.lines.find((item) => item.merchandise.id === variant.id);
- const updatedItem = createOrUpdateCartItem(existingItem, variant, product);
-
- const updatedLines = existingItem
- ? currentCart.lines.map((item) => (item.merchandise.id === variant.id ? updatedItem : item))
- : [...currentCart.lines, updatedItem];
-
- return { ...currentCart, ...updateCartTotals(updatedLines), lines: updatedLines };
- }
- default:
- return currentCart;
- }
-}
-
-export function CartProvider({
- children,
- cartPromise
-}: {
- children: React.ReactNode;
- cartPromise: Promise;
-}) {
- const initialCart = use(cartPromise);
- const [optimisticCart, updateOptimisticCart] = useOptimistic(initialCart, cartReducer);
-
- const updateCartItem = (merchandiseId: string, updateType: UpdateType) => {
- updateOptimisticCart({ type: 'UPDATE_ITEM', payload: { merchandiseId, updateType } });
- };
-
- const addCartItem = (variant: ProductVariant, product: Product) => {
- updateOptimisticCart({ type: 'ADD_ITEM', payload: { variant, product } });
- };
-
- const value = useMemo(
- () => ({
- cart: optimisticCart,
- updateCartItem,
- addCartItem
- }),
- [optimisticCart]
- );
-
- return {children} ;
-}
-
-export function useCart() {
- const context = useContext(CartContext);
- if (context === undefined) {
- throw new Error('useCart must be used within a CartProvider');
- }
- return context;
-}
diff --git a/examples/vercel-commerce/components/cart/close-cart.tsx b/examples/vercel-commerce/components/cart/close-cart.tsx
deleted file mode 100644
index 9f1036f8..00000000
--- a/examples/vercel-commerce/components/cart/close-cart.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { XMarkIcon } from '@heroicons/react/24/outline';
-import clsx from 'clsx';
-
-export default function CloseCart({ className }: { className?: string }) {
- return (
-
-
-
- );
-}
diff --git a/examples/vercel-commerce/components/cart/delete-item-button.tsx b/examples/vercel-commerce/components/cart/delete-item-button.tsx
deleted file mode 100644
index fb2aa310..00000000
--- a/examples/vercel-commerce/components/cart/delete-item-button.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-'use client';
-
-import { XMarkIcon } from '@heroicons/react/24/outline';
-import { removeItem } from 'components/cart/actions';
-import type { CartItem } from 'lib/shopify/types';
-import { useFormState } from 'react-dom';
-
-export function DeleteItemButton({
- item,
- optimisticUpdate
-}: {
- item: CartItem;
- optimisticUpdate: any;
-}) {
- const [message, formAction] = useFormState(removeItem, null);
- const merchandiseId = item.merchandise.id;
- const actionWithVariant = formAction.bind(null, merchandiseId);
-
- return (
-
- );
-}
diff --git a/examples/vercel-commerce/components/cart/edit-item-quantity-button.tsx b/examples/vercel-commerce/components/cart/edit-item-quantity-button.tsx
deleted file mode 100644
index 28ac78ba..00000000
--- a/examples/vercel-commerce/components/cart/edit-item-quantity-button.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-'use client';
-
-import { MinusIcon, PlusIcon } from '@heroicons/react/24/outline';
-import clsx from 'clsx';
-import { updateItemQuantity } from 'components/cart/actions';
-import type { CartItem } from 'lib/shopify/types';
-import { useFormState } from 'react-dom';
-
-function SubmitButton({ type }: { type: 'plus' | 'minus' }) {
- return (
-
- {type === 'plus' ? (
-
- ) : (
-
- )}
-
- );
-}
-
-export function EditItemQuantityButton({
- item,
- type,
- optimisticUpdate
-}: {
- item: CartItem;
- type: 'plus' | 'minus';
- optimisticUpdate: any;
-}) {
- const [message, formAction] = useFormState(updateItemQuantity, null);
- const payload = {
- merchandiseId: item.merchandise.id,
- quantity: type === 'plus' ? item.quantity + 1 : item.quantity - 1
- };
- const actionWithVariant = formAction.bind(null, payload);
-
- return (
-
- );
-}
diff --git a/examples/vercel-commerce/components/cart/modal.tsx b/examples/vercel-commerce/components/cart/modal.tsx
deleted file mode 100644
index ca1e3465..00000000
--- a/examples/vercel-commerce/components/cart/modal.tsx
+++ /dev/null
@@ -1,224 +0,0 @@
-'use client';
-
-import { Dialog, Transition } from '@headlessui/react';
-import { ShoppingCartIcon } from '@heroicons/react/24/outline';
-import LoadingDots from 'components/loading-dots';
-import Price from 'components/price';
-import { DEFAULT_OPTION } from 'lib/constants';
-import { createUrl } from 'lib/utils';
-import Image from 'next/image';
-import Link from 'next/link';
-import { Fragment, useEffect, useRef, useState } from 'react';
-import { useFormStatus } from 'react-dom';
-import { createCartAndSetCookie, redirectToCheckout } from './actions';
-import { useCart } from './cart-context';
-import CloseCart from './close-cart';
-import { DeleteItemButton } from './delete-item-button';
-import { EditItemQuantityButton } from './edit-item-quantity-button';
-import OpenCart from './open-cart';
-
-type MerchandiseSearchParams = {
- [key: string]: string;
-};
-
-export default function CartModal() {
- const { cart, updateCartItem } = useCart();
- const [isOpen, setIsOpen] = useState(false);
- const quantityRef = useRef(cart?.totalQuantity);
- const openCart = () => setIsOpen(true);
- const closeCart = () => setIsOpen(false);
-
- useEffect(() => {
- if (!cart) {
- createCartAndSetCookie();
- }
- }, [cart]);
-
- useEffect(() => {
- if (
- cart?.totalQuantity &&
- cart?.totalQuantity !== quantityRef.current &&
- cart?.totalQuantity > 0
- ) {
- if (!isOpen) {
- setIsOpen(true);
- }
- quantityRef.current = cart?.totalQuantity;
- }
- }, [isOpen, cart?.totalQuantity, quantityRef]);
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
- {!cart || cart.lines.length === 0 ? (
-
-
-
Your cart is empty.
-
- ) : (
-
-
- {cart.lines
- .sort((a, b) =>
- a.merchandise.product.title.localeCompare(b.merchandise.product.title)
- )
- .map((item, i) => {
- const merchandiseSearchParams = {} as MerchandiseSearchParams;
-
- item.merchandise.selectedOptions.forEach(({ name, value }) => {
- if (value !== DEFAULT_OPTION) {
- merchandiseSearchParams[name.toLowerCase()] = value;
- }
- });
-
- const merchandiseUrl = createUrl(
- `/product/${item.merchandise.product.handle}`,
- new URLSearchParams(merchandiseSearchParams)
- );
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- {item.merchandise.product.title}
-
- {item.merchandise.title !== DEFAULT_OPTION ? (
-
- {item.merchandise.title}
-
- ) : null}
-
-
-
-
-
-
-
-
- {item.quantity}
-
-
-
-
-
-
- );
- })}
-
-
-
-
-
Shipping
-
Calculated at checkout
-
-
-
-
-
- )}
-
-
-
-
- >
- );
-}
-
-function CheckoutButton() {
- const { pending } = useFormStatus();
-
- return (
-
- {pending ? : 'Proceed to Checkout'}
-
- );
-}
diff --git a/examples/vercel-commerce/components/cart/open-cart.tsx b/examples/vercel-commerce/components/cart/open-cart.tsx
deleted file mode 100644
index 902dc4f9..00000000
--- a/examples/vercel-commerce/components/cart/open-cart.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { ShoppingCartIcon } from '@heroicons/react/24/outline';
-import clsx from 'clsx';
-
-export default function OpenCart({
- className,
- quantity
-}: {
- className?: string;
- quantity?: number;
-}) {
- return (
-
-
-
- {quantity ? (
-
- {quantity}
-
- ) : null}
-
- );
-}
diff --git a/examples/vercel-commerce/components/grid/index.tsx b/examples/vercel-commerce/components/grid/index.tsx
deleted file mode 100644
index 92681555..00000000
--- a/examples/vercel-commerce/components/grid/index.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import clsx from 'clsx';
-
-function Grid(props: React.ComponentProps<'ul'>) {
- return (
-
- );
-}
-
-function GridItem(props: React.ComponentProps<'li'>) {
- return (
-
- {props.children}
-
- );
-}
-
-Grid.Item = GridItem;
-
-export default Grid;
diff --git a/examples/vercel-commerce/components/grid/three-items.tsx b/examples/vercel-commerce/components/grid/three-items.tsx
deleted file mode 100644
index c4911bad..00000000
--- a/examples/vercel-commerce/components/grid/three-items.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { GridTileImage } from 'components/grid/tile';
-import { getCollectionProducts } from 'lib/shopify';
-import type { Product } from 'lib/shopify/types';
-import Link from 'next/link';
-
-function ThreeItemGridItem({
- item,
- size,
- priority
-}: {
- item: Product;
- size: 'full' | 'half';
- priority?: boolean;
-}) {
- return (
-
-
-
-
-
- );
-}
-
-export async function ThreeItemGrid() {
- // Collections that start with `hidden-*` are hidden from the search page.
- const homepageItems = await getCollectionProducts({
- collection: 'summer-collection'
- });
-
- if (!homepageItems[0] || !homepageItems[1] || !homepageItems[2]) return null;
-
- const [firstProduct, secondProduct, thirdProduct] = homepageItems;
-
- return (
-
- );
-}
diff --git a/examples/vercel-commerce/components/grid/tile.tsx b/examples/vercel-commerce/components/grid/tile.tsx
deleted file mode 100644
index d5e5817b..00000000
--- a/examples/vercel-commerce/components/grid/tile.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import clsx from 'clsx';
-import Image from 'next/image';
-import Label from '../label';
-
-export function GridTileImage({
- isInteractive = true,
- active,
- label,
- ...props
-}: {
- isInteractive?: boolean;
- active?: boolean;
- label?: {
- title: string;
- amount: string;
- currencyCode: string;
- position?: 'bottom' | 'center';
- };
-} & React.ComponentProps) {
- return (
-
- {props.src ? (
-
- ) : null}
- {label ? (
-
- ) : null}
-
- );
-}
diff --git a/examples/vercel-commerce/components/icons/logo.tsx b/examples/vercel-commerce/components/icons/logo.tsx
deleted file mode 100644
index 46fa0246..00000000
--- a/examples/vercel-commerce/components/icons/logo.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import clsx from 'clsx';
-
-export default function LogoIcon(props: React.ComponentProps<'svg'>) {
- return (
-
-
-
-
- );
-}
diff --git a/examples/vercel-commerce/components/label.tsx b/examples/vercel-commerce/components/label.tsx
deleted file mode 100644
index 113afacb..00000000
--- a/examples/vercel-commerce/components/label.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import clsx from 'clsx';
-import Price from './price';
-
-const Label = ({
- title,
- amount,
- currencyCode,
- position = 'bottom'
-}: {
- title: string;
- amount: string;
- currencyCode: string;
- position?: 'bottom' | 'center';
-}) => {
- return (
-
- );
-};
-
-export default Label;
diff --git a/examples/vercel-commerce/components/layout/footer-menu.tsx b/examples/vercel-commerce/components/layout/footer-menu.tsx
deleted file mode 100644
index 8b6c722f..00000000
--- a/examples/vercel-commerce/components/layout/footer-menu.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-'use client';
-
-import clsx from 'clsx';
-import { Menu } from 'lib/shopify/types';
-import Link from 'next/link';
-import { usePathname } from 'next/navigation';
-import { useEffect, useState } from 'react';
-
-export function FooterMenuItem({ item }: { item: Menu }) {
- const pathname = usePathname();
- const [active, setActive] = useState(pathname === item.path);
-
- useEffect(() => {
- setActive(pathname === item.path);
- }, [pathname, item.path]);
-
- return (
-
-
- {item.title}
-
-
- );
-}
-
-export default function FooterMenu({ menu }: { menu: Menu[] }) {
- if (!menu.length) return null;
-
- return (
-
-
- {menu.map((item: Menu) => {
- return ;
- })}
-
-
- );
-}
diff --git a/examples/vercel-commerce/components/layout/footer.tsx b/examples/vercel-commerce/components/layout/footer.tsx
deleted file mode 100644
index fc9336ea..00000000
--- a/examples/vercel-commerce/components/layout/footer.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import Link from 'next/link';
-
-import FooterMenu from 'components/layout/footer-menu';
-import LogoSquare from 'components/logo-square';
-import { getMenu } from 'lib/shopify';
-import { Suspense } from 'react';
-
-const { COMPANY_NAME, SITE_NAME } = process.env;
-
-export default async function Footer() {
- const currentYear = new Date().getFullYear();
- const copyrightDate = 2023 + (currentYear > 2023 ? `-${currentYear}` : '');
- const skeleton = 'w-full h-6 animate-pulse rounded bg-neutral-200 dark:bg-neutral-700';
- const menu = await getMenu('next-js-frontend-footer-menu');
- const copyrightName = COMPANY_NAME || SITE_NAME || '';
-
- return (
-
-
-
-
-
- {SITE_NAME}
-
-
-
-
-
-
-
-
-
-
- }
- >
-
-
-
-
-
-
- );
-}
diff --git a/examples/vercel-commerce/components/layout/navbar/index.tsx b/examples/vercel-commerce/components/layout/navbar/index.tsx
deleted file mode 100644
index 6c7f3dea..00000000
--- a/examples/vercel-commerce/components/layout/navbar/index.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import CartModal from 'components/cart/modal';
-import LogoSquare from 'components/logo-square';
-import { getMenu } from 'lib/shopify';
-import { Menu } from 'lib/shopify/types';
-import Link from 'next/link';
-import { Suspense } from 'react';
-import MobileMenu from './mobile-menu';
-import Search, { SearchSkeleton } from './search';
-
-const { SITE_NAME } = process.env;
-
-export async function Navbar() {
- const menu = await getMenu('next-js-frontend-header-menu');
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- {SITE_NAME}
-
-
- {menu.length ? (
-
- {menu.map((item: Menu) => (
-
-
- {item.title}
-
-
- ))}
-
- ) : null}
-
-
- }>
-
-
-
-
-
-
-
-
- );
-}
diff --git a/examples/vercel-commerce/components/layout/navbar/mobile-menu.tsx b/examples/vercel-commerce/components/layout/navbar/mobile-menu.tsx
deleted file mode 100644
index ea59f8da..00000000
--- a/examples/vercel-commerce/components/layout/navbar/mobile-menu.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-'use client';
-
-import { Dialog, Transition } from '@headlessui/react';
-import Link from 'next/link';
-import { usePathname, useSearchParams } from 'next/navigation';
-import { Fragment, Suspense, useEffect, useState } from 'react';
-
-import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline';
-import { Menu } from 'lib/shopify/types';
-import Search, { SearchSkeleton } from './search';
-
-export default function MobileMenu({ menu }: { menu: Menu[] }) {
- const pathname = usePathname();
- const searchParams = useSearchParams();
- const [isOpen, setIsOpen] = useState(false);
- const openMobileMenu = () => setIsOpen(true);
- const closeMobileMenu = () => setIsOpen(false);
-
- useEffect(() => {
- const handleResize = () => {
- if (window.innerWidth > 768) {
- setIsOpen(false);
- }
- };
- window.addEventListener('resize', handleResize);
- return () => window.removeEventListener('resize', handleResize);
- }, [isOpen]);
-
- useEffect(() => {
- setIsOpen(false);
- }, [pathname, searchParams]);
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- }>
-
-
-
- {menu.length ? (
-
- {menu.map((item: Menu) => (
-
-
- {item.title}
-
-
- ))}
-
- ) : null}
-
-
-
-
-
- >
- );
-}
diff --git a/examples/vercel-commerce/components/layout/navbar/search.tsx b/examples/vercel-commerce/components/layout/navbar/search.tsx
deleted file mode 100644
index a590db6a..00000000
--- a/examples/vercel-commerce/components/layout/navbar/search.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-'use client';
-
-import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
-import Form from 'next/form';
-import { useSearchParams } from 'next/navigation';
-
-export default function Search() {
- const searchParams = useSearchParams();
-
- return (
-
- );
-}
-
-export function SearchSkeleton() {
- return (
-
- );
-}
diff --git a/examples/vercel-commerce/components/layout/product-grid-items.tsx b/examples/vercel-commerce/components/layout/product-grid-items.tsx
deleted file mode 100644
index 5dee9836..00000000
--- a/examples/vercel-commerce/components/layout/product-grid-items.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import Grid from 'components/grid';
-import { GridTileImage } from 'components/grid/tile';
-import { Product } from 'lib/shopify/types';
-import Link from 'next/link';
-
-export default function ProductGridItems({ products }: { products: Product[] }) {
- return (
- <>
- {products.map((product) => (
-
-
-
-
-
- ))}
- >
- );
-}
diff --git a/examples/vercel-commerce/components/layout/search/collections.tsx b/examples/vercel-commerce/components/layout/search/collections.tsx
deleted file mode 100644
index c45833a3..00000000
--- a/examples/vercel-commerce/components/layout/search/collections.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import clsx from 'clsx';
-import { Suspense } from 'react';
-
-import { getCollections } from 'lib/shopify';
-import FilterList from './filter';
-
-async function CollectionList() {
- const collections = await getCollections();
- return ;
-}
-
-const skeleton = 'mb-3 h-4 w-5/6 animate-pulse rounded';
-const activeAndTitles = 'bg-neutral-800 dark:bg-neutral-300';
-const items = 'bg-neutral-400 dark:bg-neutral-700';
-
-export default function Collections() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- }
- >
-
-
- );
-}
diff --git a/examples/vercel-commerce/components/layout/search/filter/dropdown.tsx b/examples/vercel-commerce/components/layout/search/filter/dropdown.tsx
deleted file mode 100644
index 31daa25c..00000000
--- a/examples/vercel-commerce/components/layout/search/filter/dropdown.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-'use client';
-
-import { usePathname, useSearchParams } from 'next/navigation';
-import { useEffect, useRef, useState } from 'react';
-
-import { ChevronDownIcon } from '@heroicons/react/24/outline';
-import type { ListItem } from '.';
-import { FilterItem } from './item';
-
-export default function FilterItemDropdown({ list }: { list: ListItem[] }) {
- const pathname = usePathname();
- const searchParams = useSearchParams();
- const [active, setActive] = useState('');
- const [openSelect, setOpenSelect] = useState(false);
- const ref = useRef(null);
-
- useEffect(() => {
- const handleClickOutside = (event: MouseEvent) => {
- if (ref.current && !ref.current.contains(event.target as Node)) {
- setOpenSelect(false);
- }
- };
-
- window.addEventListener('click', handleClickOutside);
- return () => window.removeEventListener('click', handleClickOutside);
- }, []);
-
- useEffect(() => {
- list.forEach((listItem: ListItem) => {
- if (
- ('path' in listItem && pathname === listItem.path) ||
- ('slug' in listItem && searchParams.get('sort') === listItem.slug)
- ) {
- setActive(listItem.title);
- }
- });
- }, [pathname, list, searchParams]);
-
- return (
-
-
{
- setOpenSelect(!openSelect);
- }}
- className="flex w-full items-center justify-between rounded border border-black/30 px-4 py-2 text-sm dark:border-white/30"
- >
-
{active}
-
-
- {openSelect && (
-
{
- setOpenSelect(false);
- }}
- className="absolute z-40 w-full rounded-b-md bg-white p-4 shadow-md dark:bg-black"
- >
- {list.map((item: ListItem, i) => (
-
- ))}
-
- )}
-
- );
-}
diff --git a/examples/vercel-commerce/components/layout/search/filter/index.tsx b/examples/vercel-commerce/components/layout/search/filter/index.tsx
deleted file mode 100644
index 11a7cd36..00000000
--- a/examples/vercel-commerce/components/layout/search/filter/index.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { SortFilterItem } from 'lib/constants';
-import { Suspense } from 'react';
-import FilterItemDropdown from './dropdown';
-import { FilterItem } from './item';
-
-export type ListItem = SortFilterItem | PathFilterItem;
-export type PathFilterItem = { title: string; path: string };
-
-function FilterItemList({ list }: { list: ListItem[] }) {
- return (
- <>
- {list.map((item: ListItem, i) => (
-
- ))}
- >
- );
-}
-
-export default function FilterList({ list, title }: { list: ListItem[]; title?: string }) {
- return (
- <>
-
- {title ? (
-
- {title}
-
- ) : null}
-
-
-
- >
- );
-}
diff --git a/examples/vercel-commerce/components/layout/search/filter/item.tsx b/examples/vercel-commerce/components/layout/search/filter/item.tsx
deleted file mode 100644
index 3fce8e8a..00000000
--- a/examples/vercel-commerce/components/layout/search/filter/item.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-'use client';
-
-import clsx from 'clsx';
-import type { SortFilterItem } from 'lib/constants';
-import { createUrl } from 'lib/utils';
-import Link from 'next/link';
-import { usePathname, useSearchParams } from 'next/navigation';
-import type { ListItem, PathFilterItem } from '.';
-
-function PathFilterItem({ item }: { item: PathFilterItem }) {
- const pathname = usePathname();
- const searchParams = useSearchParams();
- const active = pathname === item.path;
- const newParams = new URLSearchParams(searchParams.toString());
- const DynamicTag = active ? 'p' : Link;
-
- newParams.delete('q');
-
- return (
-
-
- {item.title}
-
-
- );
-}
-
-function SortFilterItem({ item }: { item: SortFilterItem }) {
- const pathname = usePathname();
- const searchParams = useSearchParams();
- const active = searchParams.get('sort') === item.slug;
- const q = searchParams.get('q');
- const href = createUrl(
- pathname,
- new URLSearchParams({
- ...(q && { q }),
- ...(item.slug && item.slug.length && { sort: item.slug })
- })
- );
- const DynamicTag = active ? 'p' : Link;
-
- return (
-
-
- {item.title}
-
-
- );
-}
-
-export function FilterItem({ item }: { item: ListItem }) {
- return 'path' in item ? : ;
-}
diff --git a/examples/vercel-commerce/components/loading-dots.tsx b/examples/vercel-commerce/components/loading-dots.tsx
deleted file mode 100644
index 10e64222..00000000
--- a/examples/vercel-commerce/components/loading-dots.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import clsx from 'clsx';
-
-const dots = 'mx-[1px] inline-block h-1 w-1 animate-blink rounded-md';
-
-const LoadingDots = ({ className }: { className: string }) => {
- return (
-
-
-
-
-
- );
-};
-
-export default LoadingDots;
diff --git a/examples/vercel-commerce/components/logo-square.tsx b/examples/vercel-commerce/components/logo-square.tsx
deleted file mode 100644
index eccf5cba..00000000
--- a/examples/vercel-commerce/components/logo-square.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import clsx from 'clsx';
-import LogoIcon from './icons/logo';
-
-export default function LogoSquare({ size }: { size?: 'sm' | undefined }) {
- return (
-
-
-
- );
-}
diff --git a/examples/vercel-commerce/components/opengraph-image.tsx b/examples/vercel-commerce/components/opengraph-image.tsx
deleted file mode 100644
index 288e0bd5..00000000
--- a/examples/vercel-commerce/components/opengraph-image.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { ImageResponse } from 'next/og';
-import LogoIcon from './icons/logo';
-
-export type Props = {
- title?: string;
-};
-
-export default async function OpengraphImage(props?: Props): Promise {
- const { title } = {
- ...{
- title: process.env.SITE_NAME
- },
- ...props
- };
-
- return new ImageResponse(
- (
-
- ),
- {
- width: 1200,
- height: 630,
- fonts: [
- {
- name: 'Inter',
- data: await fetch(new URL('../fonts/Inter-Bold.ttf', import.meta.url)).then((res) =>
- res.arrayBuffer()
- ),
- style: 'normal',
- weight: 700
- }
- ]
- }
- );
-}
diff --git a/examples/vercel-commerce/components/price.tsx b/examples/vercel-commerce/components/price.tsx
deleted file mode 100644
index e7090148..00000000
--- a/examples/vercel-commerce/components/price.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import clsx from 'clsx';
-
-const Price = ({
- amount,
- className,
- currencyCode = 'USD',
- currencyCodeClassName
-}: {
- amount: string;
- className?: string;
- currencyCode: string;
- currencyCodeClassName?: string;
-} & React.ComponentProps<'p'>) => (
-
- {`${new Intl.NumberFormat(undefined, {
- style: 'currency',
- currency: currencyCode,
- currencyDisplay: 'narrowSymbol'
- }).format(parseFloat(amount))}`}
- {`${currencyCode}`}
-
-);
-
-export default Price;
diff --git a/examples/vercel-commerce/components/product/gallery.tsx b/examples/vercel-commerce/components/product/gallery.tsx
deleted file mode 100644
index 79659096..00000000
--- a/examples/vercel-commerce/components/product/gallery.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-'use client';
-
-import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/24/outline';
-import { GridTileImage } from 'components/grid/tile';
-import { useProduct, useUpdateURL } from 'components/product/product-context';
-import Image from 'next/image';
-
-export function Gallery({ images }: { images: { src: string; altText: string }[] }) {
- const { state, updateImage } = useProduct();
- const updateURL = useUpdateURL();
- const imageIndex = state.image ? parseInt(state.image) : 0;
-
- const nextImageIndex = imageIndex + 1 < images.length ? imageIndex + 1 : 0;
- const previousImageIndex = imageIndex === 0 ? images.length - 1 : imageIndex - 1;
-
- const buttonClassName =
- 'h-full px-6 transition-all ease-in-out hover:scale-110 hover:text-black dark:hover:text-white flex items-center justify-center';
-
- return (
-
- );
-}
diff --git a/examples/vercel-commerce/components/product/product-context.tsx b/examples/vercel-commerce/components/product/product-context.tsx
deleted file mode 100644
index cc964c5a..00000000
--- a/examples/vercel-commerce/components/product/product-context.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-'use client';
-
-import { useRouter, useSearchParams } from 'next/navigation';
-import React, { createContext, useContext, useMemo, useOptimistic } from 'react';
-
-type ProductState = {
- [key: string]: string;
-} & {
- image?: string;
-};
-
-type ProductContextType = {
- state: ProductState;
- updateOption: (name: string, value: string) => ProductState;
- updateImage: (index: string) => ProductState;
-};
-
-const ProductContext = createContext(undefined);
-
-export function ProductProvider({ children }: { children: React.ReactNode }) {
- const searchParams = useSearchParams();
-
- const getInitialState = () => {
- const params: ProductState = {};
- for (const [key, value] of searchParams.entries()) {
- params[key] = value;
- }
- return params;
- };
-
- const [state, setOptimisticState] = useOptimistic(
- getInitialState(),
- (prevState: ProductState, update: ProductState) => ({
- ...prevState,
- ...update
- })
- );
-
- const updateOption = (name: string, value: string) => {
- const newState = { [name]: value };
- setOptimisticState(newState);
- return { ...state, ...newState };
- };
-
- const updateImage = (index: string) => {
- const newState = { image: index };
- setOptimisticState(newState);
- return { ...state, ...newState };
- };
-
- const value = useMemo(
- () => ({
- state,
- updateOption,
- updateImage
- }),
- [state]
- );
-
- return {children} ;
-}
-
-export function useProduct() {
- const context = useContext(ProductContext);
- if (context === undefined) {
- throw new Error('useProduct must be used within a ProductProvider');
- }
- return context;
-}
-
-export function useUpdateURL() {
- const router = useRouter();
-
- return (state: ProductState) => {
- const newParams = new URLSearchParams(window.location.search);
- Object.entries(state).forEach(([key, value]) => {
- newParams.set(key, value);
- });
- router.push(`?${newParams.toString()}`, { scroll: false });
- };
-}
diff --git a/examples/vercel-commerce/components/product/product-description.tsx b/examples/vercel-commerce/components/product/product-description.tsx
deleted file mode 100644
index 427916a8..00000000
--- a/examples/vercel-commerce/components/product/product-description.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { AddToCart } from 'components/cart/add-to-cart';
-import Price from 'components/price';
-import Prose from 'components/prose';
-import { Product } from 'lib/shopify/types';
-import { VariantSelector } from './variant-selector';
-
-export function ProductDescription({ product }: { product: Product }) {
- return (
- <>
-
-
- {product.descriptionHtml ? (
-
- ) : null}
-
- >
- );
-}
diff --git a/examples/vercel-commerce/components/product/variant-selector.tsx b/examples/vercel-commerce/components/product/variant-selector.tsx
deleted file mode 100644
index e4b1dd0c..00000000
--- a/examples/vercel-commerce/components/product/variant-selector.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-'use client';
-
-import clsx from 'clsx';
-import { useProduct, useUpdateURL } from 'components/product/product-context';
-import { ProductOption, ProductVariant } from 'lib/shopify/types';
-
-type Combination = {
- id: string;
- availableForSale: boolean;
- [key: string]: string | boolean;
-};
-
-export function VariantSelector({
- options,
- variants
-}: {
- options: ProductOption[];
- variants: ProductVariant[];
-}) {
- const { state, updateOption } = useProduct();
- const updateURL = useUpdateURL();
- const hasNoOptionsOrJustOneOption =
- !options.length || (options.length === 1 && options[0]?.values.length === 1);
-
- if (hasNoOptionsOrJustOneOption) {
- return null;
- }
-
- const combinations: Combination[] = variants.map((variant) => ({
- id: variant.id,
- availableForSale: variant.availableForSale,
- ...variant.selectedOptions.reduce(
- (accumulator, option) => ({ ...accumulator, [option.name.toLowerCase()]: option.value }),
- {}
- )
- }));
-
- return options.map((option) => (
-
- ));
-}
diff --git a/examples/vercel-commerce/components/prose.tsx b/examples/vercel-commerce/components/prose.tsx
deleted file mode 100644
index f910d229..00000000
--- a/examples/vercel-commerce/components/prose.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import clsx from 'clsx';
-import type { FunctionComponent } from 'react';
-
-interface TextProps {
- html: string;
- className?: string;
-}
-
-const Prose: FunctionComponent = ({ html, className }) => {
- return (
-
- );
-};
-
-export default Prose;
diff --git a/examples/vercel-commerce/components/welcome-toast.tsx b/examples/vercel-commerce/components/welcome-toast.tsx
deleted file mode 100644
index 4c3ffb6d..00000000
--- a/examples/vercel-commerce/components/welcome-toast.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-'use client';
-
-import { useEffect } from 'react';
-
-export function WelcomeToast() {
- useEffect(() => {}, []);
-
- return null;
-}
diff --git a/examples/vercel-commerce/fonts/Inter-Bold.ttf b/examples/vercel-commerce/fonts/Inter-Bold.ttf
deleted file mode 100644
index 8e82c70d..00000000
Binary files a/examples/vercel-commerce/fonts/Inter-Bold.ttf and /dev/null differ
diff --git a/examples/vercel-commerce/lib/constants.ts b/examples/vercel-commerce/lib/constants.ts
deleted file mode 100644
index 56bc6cd1..00000000
--- a/examples/vercel-commerce/lib/constants.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-export type SortFilterItem = {
- title: string;
- slug: string | null;
- sortKey: 'RELEVANCE' | 'BEST_SELLING' | 'CREATED_AT' | 'PRICE';
- reverse: boolean;
-};
-
-export const defaultSort: SortFilterItem = {
- title: 'Relevance',
- slug: null,
- sortKey: 'RELEVANCE',
- reverse: false
-};
-
-export const sorting: SortFilterItem[] = [
- defaultSort,
- { title: 'Trending', slug: 'trending-desc', sortKey: 'BEST_SELLING', reverse: false }, // asc
- { title: 'Latest arrivals', slug: 'latest-desc', sortKey: 'CREATED_AT', reverse: true },
- { title: 'Price: Low to high', slug: 'price-asc', sortKey: 'PRICE', reverse: false }, // asc
- { title: 'Price: High to low', slug: 'price-desc', sortKey: 'PRICE', reverse: true }
-];
-
-export const TAGS = {
- collections: 'collections',
- products: 'products',
- cart: 'cart'
-};
-
-export const HIDDEN_PRODUCT_TAG = 'nextjs-frontend-hidden';
-export const DEFAULT_OPTION = 'Default Title';
-export const SHOPIFY_GRAPHQL_API_ENDPOINT = '/api/2023-01/graphql.json';
diff --git a/examples/vercel-commerce/lib/shopify/fragments/cart.ts b/examples/vercel-commerce/lib/shopify/fragments/cart.ts
deleted file mode 100644
index fc5c838d..00000000
--- a/examples/vercel-commerce/lib/shopify/fragments/cart.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import productFragment from './product';
-
-const cartFragment = /* GraphQL */ `
- fragment cart on Cart {
- id
- checkoutUrl
- cost {
- subtotalAmount {
- amount
- currencyCode
- }
- totalAmount {
- amount
- currencyCode
- }
- totalTaxAmount {
- amount
- currencyCode
- }
- }
- lines(first: 100) {
- edges {
- node {
- id
- quantity
- cost {
- totalAmount {
- amount
- currencyCode
- }
- }
- merchandise {
- ... on ProductVariant {
- id
- title
- selectedOptions {
- name
- value
- }
- product {
- ...product
- }
- }
- }
- }
- }
- }
- totalQuantity
- }
- ${productFragment}
-`;
-
-export default cartFragment;
diff --git a/examples/vercel-commerce/lib/shopify/fragments/image.ts b/examples/vercel-commerce/lib/shopify/fragments/image.ts
deleted file mode 100644
index 5d002f17..00000000
--- a/examples/vercel-commerce/lib/shopify/fragments/image.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-const imageFragment = /* GraphQL */ `
- fragment image on Image {
- url
- altText
- width
- height
- }
-`;
-
-export default imageFragment;
diff --git a/examples/vercel-commerce/lib/shopify/fragments/product.ts b/examples/vercel-commerce/lib/shopify/fragments/product.ts
deleted file mode 100644
index be14dedc..00000000
--- a/examples/vercel-commerce/lib/shopify/fragments/product.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import imageFragment from './image';
-import seoFragment from './seo';
-
-const productFragment = /* GraphQL */ `
- fragment product on Product {
- id
- handle
- availableForSale
- title
- description
- descriptionHtml
- options {
- id
- name
- values
- }
- priceRange {
- maxVariantPrice {
- amount
- currencyCode
- }
- minVariantPrice {
- amount
- currencyCode
- }
- }
- variants(first: 250) {
- edges {
- node {
- id
- title
- availableForSale
- selectedOptions {
- name
- value
- }
- price {
- amount
- currencyCode
- }
- }
- }
- }
- featuredImage {
- ...image
- }
- images(first: 20) {
- edges {
- node {
- ...image
- }
- }
- }
- seo {
- ...seo
- }
- tags
- updatedAt
- }
- ${imageFragment}
- ${seoFragment}
-`;
-
-export default productFragment;
diff --git a/examples/vercel-commerce/lib/shopify/fragments/seo.ts b/examples/vercel-commerce/lib/shopify/fragments/seo.ts
deleted file mode 100644
index 2d4786c4..00000000
--- a/examples/vercel-commerce/lib/shopify/fragments/seo.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-const seoFragment = /* GraphQL */ `
- fragment seo on SEO {
- description
- title
- }
-`;
-
-export default seoFragment;
diff --git a/examples/vercel-commerce/lib/shopify/index.ts b/examples/vercel-commerce/lib/shopify/index.ts
deleted file mode 100644
index f3954689..00000000
--- a/examples/vercel-commerce/lib/shopify/index.ts
+++ /dev/null
@@ -1,473 +0,0 @@
-import { HIDDEN_PRODUCT_TAG, SHOPIFY_GRAPHQL_API_ENDPOINT, TAGS } from 'lib/constants';
-import { isShopifyError } from 'lib/type-guards';
-import { ensureStartsWith } from 'lib/utils';
-import { revalidateTag } from 'next/cache';
-import { headers } from 'next/headers';
-import { NextRequest, NextResponse } from 'next/server';
-import {
- addToCartMutation,
- createCartMutation,
- editCartItemsMutation,
- removeFromCartMutation
-} from './mutations/cart';
-import { getCartQuery } from './queries/cart';
-import {
- getCollectionProductsQuery,
- getCollectionQuery,
- getCollectionsQuery
-} from './queries/collection';
-import { getMenuQuery } from './queries/menu';
-import { getPageQuery, getPagesQuery } from './queries/page';
-import {
- getProductQuery,
- getProductRecommendationsQuery,
- getProductsQuery
-} from './queries/product';
-import {
- Cart,
- Collection,
- Connection,
- Image,
- Menu,
- Page,
- Product,
- ShopifyAddToCartOperation,
- ShopifyCart,
- ShopifyCartOperation,
- ShopifyCollection,
- ShopifyCollectionOperation,
- ShopifyCollectionProductsOperation,
- ShopifyCollectionsOperation,
- ShopifyCreateCartOperation,
- ShopifyMenuOperation,
- ShopifyPageOperation,
- ShopifyPagesOperation,
- ShopifyProduct,
- ShopifyProductOperation,
- ShopifyProductRecommendationsOperation,
- ShopifyProductsOperation,
- ShopifyRemoveFromCartOperation,
- ShopifyUpdateCartOperation
-} from './types';
-
-const domain = process.env.SHOPIFY_STORE_DOMAIN
- ? ensureStartsWith(process.env.SHOPIFY_STORE_DOMAIN, 'https://')
- : '';
-const endpoint = `${domain}${SHOPIFY_GRAPHQL_API_ENDPOINT}`;
-const key = process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!;
-
-type ExtractVariables = T extends { variables: object } ? T['variables'] : never;
-
-export async function shopifyFetch({
- cache = 'force-cache',
- headers,
- query,
- tags,
- variables
-}: {
- cache?: RequestCache;
- headers?: HeadersInit;
- query: string;
- tags?: string[];
- variables?: ExtractVariables;
-}): Promise<{ status: number; body: T } | never> {
- try {
- const result = await fetch(endpoint, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'X-Shopify-Storefront-Access-Token': key,
- ...headers
- },
- body: JSON.stringify({
- ...(query && { query }),
- ...(variables && { variables })
- }),
- cache,
- ...(tags && { next: { tags } })
- });
-
- const body = await result.json();
-
- if (body.errors) {
- throw body.errors[0];
- }
-
- return {
- status: result.status,
- body
- };
- } catch (e) {
- if (isShopifyError(e)) {
- throw {
- cause: e.cause?.toString() || 'unknown',
- status: e.status || 500,
- message: e.message,
- query
- };
- }
-
- throw {
- error: e,
- query
- };
- }
-}
-
-const removeEdgesAndNodes = (array: Connection): T[] => {
- return array.edges.map((edge) => edge?.node);
-};
-
-const reshapeCart = (cart: ShopifyCart): Cart => {
- if (!cart.cost?.totalTaxAmount) {
- cart.cost.totalTaxAmount = {
- amount: '0.0',
- currencyCode: 'USD'
- };
- }
-
- return {
- ...cart,
- lines: removeEdgesAndNodes(cart.lines)
- };
-};
-
-const reshapeCollection = (collection: ShopifyCollection): Collection | undefined => {
- if (!collection) {
- return undefined;
- }
-
- return {
- ...collection,
- path: `/search/${collection.handle}`
- };
-};
-
-const reshapeCollections = (collections: ShopifyCollection[]) => {
- const reshapedCollections = [];
-
- for (const collection of collections) {
- if (collection) {
- const reshapedCollection = reshapeCollection(collection);
-
- if (reshapedCollection) {
- reshapedCollections.push(reshapedCollection);
- }
- }
- }
-
- return reshapedCollections;
-};
-
-const reshapeImages = (images: Connection, productTitle: string) => {
- const flattened = removeEdgesAndNodes(images);
-
- return flattened.map((image) => {
- const filename = image.url.match(/.*\/(.*)\..*/)?.[1];
- return {
- ...image,
- altText: image.altText || `${productTitle} - ${filename}`
- };
- });
-};
-
-const reshapeProduct = (product: ShopifyProduct, filterHiddenProducts: boolean = true) => {
- if (!product || (filterHiddenProducts && product.tags.includes(HIDDEN_PRODUCT_TAG))) {
- return undefined;
- }
-
- const { images, variants, ...rest } = product;
-
- return {
- ...rest,
- images: reshapeImages(images, product.title),
- variants: removeEdgesAndNodes(variants).map((v) =>
- // We'll make at least one variant unavailable for sale to demonstrate the feature.
- v.title === 'Vintage Black / L' ? { ...v, availableForSale: false } : v
- )
- };
-};
-
-const reshapeProducts = (products: ShopifyProduct[]) => {
- const reshapedProducts = [];
-
- for (const product of products) {
- if (product) {
- const reshapedProduct = reshapeProduct(product);
-
- if (reshapedProduct) {
- reshapedProducts.push(reshapedProduct);
- }
- }
- }
-
- return reshapedProducts;
-};
-
-export async function createCart(): Promise {
- const res = await shopifyFetch({
- query: createCartMutation,
- cache: 'no-store'
- });
-
- return reshapeCart(res.body.data.cartCreate.cart);
-}
-
-export async function addToCart(
- cartId: string,
- lines: { merchandiseId: string; quantity: number }[]
-): Promise {
- const res = await shopifyFetch({
- query: addToCartMutation,
- variables: {
- cartId,
- lines
- },
- cache: 'no-store'
- });
- return reshapeCart(res.body.data.cartLinesAdd.cart);
-}
-
-export async function removeFromCart(cartId: string, lineIds: string[]): Promise {
- const res = await shopifyFetch({
- query: removeFromCartMutation,
- variables: {
- cartId,
- lineIds
- },
- cache: 'no-store'
- });
-
- return reshapeCart(res.body.data.cartLinesRemove.cart);
-}
-
-export async function updateCart(
- cartId: string,
- lines: { id: string; merchandiseId: string; quantity: number }[]
-): Promise {
- const res = await shopifyFetch({
- query: editCartItemsMutation,
- variables: {
- cartId,
- lines
- },
- cache: 'no-store'
- });
-
- return reshapeCart(res.body.data.cartLinesUpdate.cart);
-}
-
-export async function getCart(cartId: string | undefined): Promise {
- if (!cartId) {
- return undefined;
- }
-
- const res = await shopifyFetch({
- query: getCartQuery,
- variables: { cartId },
- tags: [TAGS.cart]
- });
-
- // Old carts becomes `null` when you checkout.
- if (!res.body.data.cart) {
- return undefined;
- }
-
- return reshapeCart(res.body.data.cart);
-}
-
-export async function getCollection(handle: string): Promise {
- const res = await shopifyFetch({
- query: getCollectionQuery,
- tags: [TAGS.collections],
- variables: {
- handle
- }
- });
-
- return reshapeCollection(res.body.data.collection);
-}
-
-export async function getCollectionProducts({
- collection,
- reverse,
- sortKey
-}: {
- collection: string;
- reverse?: boolean;
- sortKey?: string;
-}): Promise {
- const res = await shopifyFetch({
- query: getCollectionProductsQuery,
- tags: [TAGS.collections, TAGS.products],
- variables: {
- handle: collection,
- reverse,
- sortKey: sortKey === 'CREATED_AT' ? 'CREATED' : sortKey
- }
- });
-
- if (!res.body.data.collection) {
- console.log(`No collection found for \`${collection}\``);
- return [];
- }
-
- return reshapeProducts(removeEdgesAndNodes(res.body.data.collection.products));
-}
-
-export async function getCollections(): Promise {
- const res = await shopifyFetch({
- query: getCollectionsQuery,
- tags: [TAGS.collections]
- });
- const shopifyCollections = removeEdgesAndNodes(res.body?.data?.collections);
- const collections = [
- {
- handle: '',
- title: 'All',
- description: 'All products',
- seo: {
- title: 'All',
- description: 'All products'
- },
- path: '/search',
- updatedAt: new Date().toISOString()
- },
- // Filter out the `hidden` collections.
- // Collections that start with `hidden-*` need to be hidden on the search page.
- ...reshapeCollections(shopifyCollections).filter(
- (collection) =>
- !collection.handle.startsWith('hidden') &&
- // These collections have no products
- !['antiperistaltic-gold-socks', 'blistered-aluminum-boat', 'frontpage'].includes(
- collection.handle
- )
- )
- ];
-
- return collections;
-}
-
-export async function getMenu(handle: string): Promise {
- if (handle === 'next-js-frontend-header-menu') {
- return [
- { title: 'All', path: '/search' },
- { title: 'Latest Stuff', path: '/search/latest-stuff' },
- { title: 'Casual Things', path: '/search/casual-things' }
- ];
- } else if (handle === 'next-js-frontend-footer-menu') {
- return [{ title: 'Home', path: '/' }];
- }
-
- const res = await shopifyFetch({
- query: getMenuQuery,
- tags: [TAGS.collections],
- variables: {
- handle
- }
- });
-
- return (
- res.body?.data?.menu?.items.map((item: { title: string; url: string }) => ({
- title: item.title,
- path: item.url.replace(domain, '').replace('/collections', '/search').replace('/pages', '')
- })) || []
- );
-}
-
-export async function getPage(handle: string): Promise {
- const res = await shopifyFetch({
- query: getPageQuery,
- cache: 'no-store',
- variables: { handle }
- });
-
- return res.body.data.pageByHandle;
-}
-
-export async function getPages(): Promise {
- const res = await shopifyFetch({
- query: getPagesQuery,
- cache: 'no-store'
- });
-
- return removeEdgesAndNodes(res.body.data.pages);
-}
-
-export async function getProduct(handle: string): Promise {
- const res = await shopifyFetch({
- query: getProductQuery,
- tags: [TAGS.products],
- variables: {
- handle
- }
- });
-
- return reshapeProduct(res.body.data.product, false);
-}
-
-export async function getProductRecommendations(productId: string): Promise {
- const res = await shopifyFetch({
- query: getProductRecommendationsQuery,
- tags: [TAGS.products],
- variables: {
- productId
- }
- });
-
- return reshapeProducts(res.body.data.productRecommendations);
-}
-
-export async function getProducts({
- query,
- reverse,
- sortKey
-}: {
- query?: string;
- reverse?: boolean;
- sortKey?: string;
-}): Promise {
- const res = await shopifyFetch({
- query: getProductsQuery,
- tags: [TAGS.products],
- variables: {
- query,
- reverse,
- sortKey
- }
- });
-
- return reshapeProducts(removeEdgesAndNodes(res.body.data.products));
-}
-
-// This is called from `app/api/revalidate.ts` so providers can control revalidation logic.
-export async function revalidate(req: NextRequest): Promise {
- // We always need to respond with a 200 status code to Shopify,
- // otherwise it will continue to retry the request.
- const collectionWebhooks = ['collections/create', 'collections/delete', 'collections/update'];
- const productWebhooks = ['products/create', 'products/delete', 'products/update'];
- const topic = headers().get('x-shopify-topic') || 'unknown';
- const secret = req.nextUrl.searchParams.get('secret');
- const isCollectionUpdate = collectionWebhooks.includes(topic);
- const isProductUpdate = productWebhooks.includes(topic);
-
- if (!secret || secret !== process.env.SHOPIFY_REVALIDATION_SECRET) {
- console.error('Invalid revalidation secret.');
- return NextResponse.json({ status: 200 });
- }
-
- if (!isCollectionUpdate && !isProductUpdate) {
- // We don't need to revalidate anything for any other topics.
- return NextResponse.json({ status: 200 });
- }
-
- if (isCollectionUpdate) {
- revalidateTag(TAGS.collections);
- }
-
- if (isProductUpdate) {
- revalidateTag(TAGS.products);
- }
-
- return NextResponse.json({ status: 200, revalidated: true, now: Date.now() });
-}
diff --git a/examples/vercel-commerce/lib/shopify/mutations/cart.ts b/examples/vercel-commerce/lib/shopify/mutations/cart.ts
deleted file mode 100644
index 4cc1b5ac..00000000
--- a/examples/vercel-commerce/lib/shopify/mutations/cart.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import cartFragment from '../fragments/cart';
-
-export const addToCartMutation = /* GraphQL */ `
- mutation addToCart($cartId: ID!, $lines: [CartLineInput!]!) {
- cartLinesAdd(cartId: $cartId, lines: $lines) {
- cart {
- ...cart
- }
- }
- }
- ${cartFragment}
-`;
-
-export const createCartMutation = /* GraphQL */ `
- mutation createCart($lineItems: [CartLineInput!]) {
- cartCreate(input: { lines: $lineItems }) {
- cart {
- ...cart
- }
- }
- }
- ${cartFragment}
-`;
-
-export const editCartItemsMutation = /* GraphQL */ `
- mutation editCartItems($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
- cartLinesUpdate(cartId: $cartId, lines: $lines) {
- cart {
- ...cart
- }
- }
- }
- ${cartFragment}
-`;
-
-export const removeFromCartMutation = /* GraphQL */ `
- mutation removeFromCart($cartId: ID!, $lineIds: [ID!]!) {
- cartLinesRemove(cartId: $cartId, lineIds: $lineIds) {
- cart {
- ...cart
- }
- }
- }
- ${cartFragment}
-`;
diff --git a/examples/vercel-commerce/lib/shopify/queries/cart.ts b/examples/vercel-commerce/lib/shopify/queries/cart.ts
deleted file mode 100644
index 044e47f6..00000000
--- a/examples/vercel-commerce/lib/shopify/queries/cart.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import cartFragment from '../fragments/cart';
-
-export const getCartQuery = /* GraphQL */ `
- query getCart($cartId: ID!) {
- cart(id: $cartId) {
- ...cart
- }
- }
- ${cartFragment}
-`;
diff --git a/examples/vercel-commerce/lib/shopify/queries/collection.ts b/examples/vercel-commerce/lib/shopify/queries/collection.ts
deleted file mode 100644
index 6396ff8e..00000000
--- a/examples/vercel-commerce/lib/shopify/queries/collection.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import productFragment from '../fragments/product';
-import seoFragment from '../fragments/seo';
-
-const collectionFragment = /* GraphQL */ `
- fragment collection on Collection {
- handle
- title
- description
- seo {
- ...seo
- }
- updatedAt
- }
- ${seoFragment}
-`;
-
-export const getCollectionQuery = /* GraphQL */ `
- query getCollection($handle: String!) {
- collection(handle: $handle) {
- ...collection
- }
- }
- ${collectionFragment}
-`;
-
-export const getCollectionsQuery = /* GraphQL */ `
- query getCollections {
- collections(first: 100, sortKey: TITLE) {
- edges {
- node {
- ...collection
- }
- }
- }
- }
- ${collectionFragment}
-`;
-
-export const getCollectionProductsQuery = /* GraphQL */ `
- query getCollectionProducts(
- $handle: String!
- $sortKey: ProductCollectionSortKeys
- $reverse: Boolean
- ) {
- collection(handle: $handle) {
- products(sortKey: $sortKey, reverse: $reverse, first: 100) {
- edges {
- node {
- ...product
- }
- }
- }
- }
- }
- ${productFragment}
-`;
diff --git a/examples/vercel-commerce/lib/shopify/queries/menu.ts b/examples/vercel-commerce/lib/shopify/queries/menu.ts
deleted file mode 100644
index d05b0994..00000000
--- a/examples/vercel-commerce/lib/shopify/queries/menu.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-export const getMenuQuery = /* GraphQL */ `
- query getMenu($handle: String!) {
- menu(handle: $handle) {
- items {
- title
- url
- }
- }
- }
-`;
diff --git a/examples/vercel-commerce/lib/shopify/queries/page.ts b/examples/vercel-commerce/lib/shopify/queries/page.ts
deleted file mode 100644
index ac6f6f98..00000000
--- a/examples/vercel-commerce/lib/shopify/queries/page.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import seoFragment from '../fragments/seo';
-
-const pageFragment = /* GraphQL */ `
- fragment page on Page {
- ... on Page {
- id
- title
- handle
- body
- bodySummary
- seo {
- ...seo
- }
- createdAt
- updatedAt
- }
- }
- ${seoFragment}
-`;
-
-export const getPageQuery = /* GraphQL */ `
- query getPage($handle: String!) {
- pageByHandle(handle: $handle) {
- ...page
- }
- }
- ${pageFragment}
-`;
-
-export const getPagesQuery = /* GraphQL */ `
- query getPages {
- pages(first: 100) {
- edges {
- node {
- ...page
- }
- }
- }
- }
- ${pageFragment}
-`;
diff --git a/examples/vercel-commerce/lib/shopify/queries/product.ts b/examples/vercel-commerce/lib/shopify/queries/product.ts
deleted file mode 100644
index d3f12bd0..00000000
--- a/examples/vercel-commerce/lib/shopify/queries/product.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import productFragment from '../fragments/product';
-
-export const getProductQuery = /* GraphQL */ `
- query getProduct($handle: String!) {
- product(handle: $handle) {
- ...product
- }
- }
- ${productFragment}
-`;
-
-export const getProductsQuery = /* GraphQL */ `
- query getProducts($sortKey: ProductSortKeys, $reverse: Boolean, $query: String) {
- products(sortKey: $sortKey, reverse: $reverse, query: $query, first: 100) {
- edges {
- node {
- ...product
- }
- }
- }
- }
- ${productFragment}
-`;
-
-export const getProductRecommendationsQuery = /* GraphQL */ `
- query getProductRecommendations($productId: ID!) {
- productRecommendations(productId: $productId) {
- ...product
- }
- }
- ${productFragment}
-`;
diff --git a/examples/vercel-commerce/lib/shopify/types.ts b/examples/vercel-commerce/lib/shopify/types.ts
deleted file mode 100644
index 9b443559..00000000
--- a/examples/vercel-commerce/lib/shopify/types.ts
+++ /dev/null
@@ -1,272 +0,0 @@
-export type Maybe = T | null;
-
-export type Connection = {
- edges: Array>;
-};
-
-export type Edge = {
- node: T;
-};
-
-export type Cart = Omit & {
- lines: CartItem[];
-};
-
-export type CartProduct = {
- id: string;
- handle: string;
- title: string;
- featuredImage: Image;
-};
-
-export type CartItem = {
- id: string | undefined;
- quantity: number;
- cost: {
- totalAmount: Money;
- };
- merchandise: {
- id: string;
- title: string;
- selectedOptions: {
- name: string;
- value: string;
- }[];
- product: CartProduct;
- };
-};
-
-export type Collection = ShopifyCollection & {
- path: string;
-};
-
-export type Image = {
- url: string;
- altText: string;
- width: number;
- height: number;
-};
-
-export type Menu = {
- title: string;
- path: string;
-};
-
-export type Money = {
- amount: string;
- currencyCode: string;
-};
-
-export type Page = {
- id: string;
- title: string;
- handle: string;
- body: string;
- bodySummary: string;
- seo?: SEO;
- createdAt: string;
- updatedAt: string;
-};
-
-export type Product = Omit & {
- variants: ProductVariant[];
- images: Image[];
-};
-
-export type ProductOption = {
- id: string;
- name: string;
- values: string[];
-};
-
-export type ProductVariant = {
- id: string;
- title: string;
- availableForSale: boolean;
- selectedOptions: {
- name: string;
- value: string;
- }[];
- price: Money;
-};
-
-export type SEO = {
- title: string;
- description: string;
-};
-
-export type ShopifyCart = {
- id: string | undefined;
- checkoutUrl: string;
- cost: {
- subtotalAmount: Money;
- totalAmount: Money;
- totalTaxAmount: Money;
- };
- lines: Connection;
- totalQuantity: number;
-};
-
-export type ShopifyCollection = {
- handle: string;
- title: string;
- description: string;
- seo: SEO;
- updatedAt: string;
-};
-
-export type ShopifyProduct = {
- id: string;
- handle: string;
- availableForSale: boolean;
- title: string;
- description: string;
- descriptionHtml: string;
- options: ProductOption[];
- priceRange: {
- maxVariantPrice: Money;
- minVariantPrice: Money;
- };
- variants: Connection;
- featuredImage: Image;
- images: Connection;
- seo: SEO;
- tags: string[];
- updatedAt: string;
-};
-
-export type ShopifyCartOperation = {
- data: {
- cart: ShopifyCart;
- };
- variables: {
- cartId: string;
- };
-};
-
-export type ShopifyCreateCartOperation = {
- data: { cartCreate: { cart: ShopifyCart } };
-};
-
-export type ShopifyAddToCartOperation = {
- data: {
- cartLinesAdd: {
- cart: ShopifyCart;
- };
- };
- variables: {
- cartId: string;
- lines: {
- merchandiseId: string;
- quantity: number;
- }[];
- };
-};
-
-export type ShopifyRemoveFromCartOperation = {
- data: {
- cartLinesRemove: {
- cart: ShopifyCart;
- };
- };
- variables: {
- cartId: string;
- lineIds: string[];
- };
-};
-
-export type ShopifyUpdateCartOperation = {
- data: {
- cartLinesUpdate: {
- cart: ShopifyCart;
- };
- };
- variables: {
- cartId: string;
- lines: {
- id: string;
- merchandiseId: string;
- quantity: number;
- }[];
- };
-};
-
-export type ShopifyCollectionOperation = {
- data: {
- collection: ShopifyCollection;
- };
- variables: {
- handle: string;
- };
-};
-
-export type ShopifyCollectionProductsOperation = {
- data: {
- collection: {
- products: Connection;
- };
- };
- variables: {
- handle: string;
- reverse?: boolean;
- sortKey?: string;
- };
-};
-
-export type ShopifyCollectionsOperation = {
- data: {
- collections: Connection;
- };
-};
-
-export type ShopifyMenuOperation = {
- data: {
- menu?: {
- items: {
- title: string;
- url: string;
- }[];
- };
- };
- variables: {
- handle: string;
- };
-};
-
-export type ShopifyPageOperation = {
- data: { pageByHandle: Page };
- variables: { handle: string };
-};
-
-export type ShopifyPagesOperation = {
- data: {
- pages: Connection;
- };
-};
-
-export type ShopifyProductOperation = {
- data: { product: ShopifyProduct };
- variables: {
- handle: string;
- };
-};
-
-export type ShopifyProductRecommendationsOperation = {
- data: {
- productRecommendations: ShopifyProduct[];
- };
- variables: {
- productId: string;
- };
-};
-
-export type ShopifyProductsOperation = {
- data: {
- products: Connection;
- };
- variables: {
- query?: string;
- reverse?: boolean;
- sortKey?: string;
- };
-};
diff --git a/examples/vercel-commerce/lib/type-guards.ts b/examples/vercel-commerce/lib/type-guards.ts
deleted file mode 100644
index 6f920d1f..00000000
--- a/examples/vercel-commerce/lib/type-guards.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-export interface ShopifyErrorLike {
- status: number;
- message: Error;
- cause?: Error;
-}
-
-export const isObject = (object: unknown): object is Record => {
- return typeof object === 'object' && object !== null && !Array.isArray(object);
-};
-
-export const isShopifyError = (error: unknown): error is ShopifyErrorLike => {
- if (!isObject(error)) return false;
-
- if (error instanceof Error) return true;
-
- return findError(error);
-};
-
-function findError(error: T): boolean {
- if (Object.prototype.toString.call(error) === '[object Error]') {
- return true;
- }
-
- const prototype = Object.getPrototypeOf(error) as T | null;
-
- return prototype === null ? false : findError(prototype);
-}
diff --git a/examples/vercel-commerce/lib/utils.ts b/examples/vercel-commerce/lib/utils.ts
deleted file mode 100644
index 69b76d29..00000000
--- a/examples/vercel-commerce/lib/utils.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { ReadonlyURLSearchParams } from 'next/navigation';
-
-export const createUrl = (pathname: string, params: URLSearchParams | ReadonlyURLSearchParams) => {
- const paramsString = params.toString();
- const queryString = `${paramsString.length ? '?' : ''}${paramsString}`;
-
- return `${pathname}${queryString}`;
-};
-
-export const ensureStartsWith = (stringToCheck: string, startsWith: string) =>
- stringToCheck.startsWith(startsWith) ? stringToCheck : `${startsWith}${stringToCheck}`;
-
-export const validateEnvironmentVariables = () => {
- const requiredEnvironmentVariables = ['SHOPIFY_STORE_DOMAIN', 'SHOPIFY_STOREFRONT_ACCESS_TOKEN'];
- const missingEnvironmentVariables = [] as string[];
-
- requiredEnvironmentVariables.forEach((envVar) => {
- if (!process.env[envVar]) {
- missingEnvironmentVariables.push(envVar);
- }
- });
-
- if (missingEnvironmentVariables.length) {
- throw new Error(
- `The following environment variables are missing. Your site will not work without them. Read more: https://vercel.com/docs/integrations/shopify#configure-environment-variables\n\n${missingEnvironmentVariables.join(
- '\n'
- )}\n`
- );
- }
-
- if (
- process.env.SHOPIFY_STORE_DOMAIN?.includes('[') ||
- process.env.SHOPIFY_STORE_DOMAIN?.includes(']')
- ) {
- throw new Error(
- 'Your `SHOPIFY_STORE_DOMAIN` environment variable includes brackets (ie. `[` and / or `]`). Your site will not work with them there. Please remove them.'
- );
- }
-};
diff --git a/examples/vercel-commerce/license.md b/examples/vercel-commerce/license.md
deleted file mode 100644
index 7ee04f58..00000000
--- a/examples/vercel-commerce/license.md
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2023 Vercel, Inc.
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/examples/vercel-commerce/next.config.js b/examples/vercel-commerce/next.config.js
deleted file mode 100644
index 4dc30010..00000000
--- a/examples/vercel-commerce/next.config.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/** @type {import('next').NextConfig} */
-module.exports = {
- typescript: { ignoreBuildErrors: true },
- eslint: { ignoreDuringBuilds: true },
- images: {
- formats: ['image/avif', 'image/webp'],
- remotePatterns: [
- {
- protocol: 'https',
- hostname: 'cdn.shopify.com',
- pathname: '/s/files/**'
- }
- ]
- }
-};
diff --git a/examples/vercel-commerce/open-next.config.ts b/examples/vercel-commerce/open-next.config.ts
deleted file mode 100644
index f189176e..00000000
--- a/examples/vercel-commerce/open-next.config.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { defineCloudflareConfig } from '@opennextjs/cloudflare/config';
-
-export default defineCloudflareConfig();
diff --git a/examples/vercel-commerce/package.json b/examples/vercel-commerce/package.json
deleted file mode 100644
index 6341cb5d..00000000
--- a/examples/vercel-commerce/package.json
+++ /dev/null
@@ -1,44 +0,0 @@
-{
- "name": "vercel-commerce",
- "private": true,
- "engines": {
- "node": ">=20",
- "pnpm": ">=9"
- },
- "scripts": {
- "dev": "next dev --turbo",
- "build": "next build",
- "start": "next start",
- "prettier": "prettier --write --ignore-unknown .",
- "prettier:check": "prettier --check --ignore-unknown .",
- "test": "pnpm prettier:check",
- "tofix-build:worker": "pnpm opennextjs-cloudflare build",
- "tofix-preview:worker": "pnpm opennextjs-cloudflare preview",
- "tofix-preview": "pnpm build:worker && pnpm preview:worker"
- },
- "dependencies": {
- "@headlessui/react": "^2.1.2",
- "@heroicons/react": "^2.1.5",
- "clsx": "^2.1.1",
- "geist": "^1.3.1",
- "next": "15.0.0-canary.113",
- "react": "19.0.0-rc-3208e73e-20240730",
- "react-dom": "19.0.0-rc-3208e73e-20240730",
- "sonner": "^1.5.0"
- },
- "devDependencies": {
- "@opennextjs/cloudflare": "workspace:*",
- "@tailwindcss/container-queries": "^0.1.1",
- "@tailwindcss/typography": "^0.5.13",
- "@types/node": "catalog:",
- "@types/react": "catalog:",
- "@types/react-dom": "catalog:",
- "autoprefixer": "^10.4.19",
- "postcss": "^8.4.39",
- "prettier": "3.3.3",
- "prettier-plugin-tailwindcss": "^0.6.5",
- "tailwindcss": "^3.4.6",
- "typescript": "catalog:",
- "wrangler": "catalog:"
- }
-}
diff --git a/examples/vercel-commerce/postcss.config.js b/examples/vercel-commerce/postcss.config.js
deleted file mode 100644
index 5cbc2c7d..00000000
--- a/examples/vercel-commerce/postcss.config.js
+++ /dev/null
@@ -1,6 +0,0 @@
-module.exports = {
- plugins: {
- tailwindcss: {},
- autoprefixer: {}
- }
-};
diff --git a/examples/vercel-commerce/prettier.config.js b/examples/vercel-commerce/prettier.config.js
deleted file mode 100644
index 551ae683..00000000
--- a/examples/vercel-commerce/prettier.config.js
+++ /dev/null
@@ -1,8 +0,0 @@
-/** @type {import('prettier').Config} */
-module.exports = {
- singleQuote: true,
- arrowParens: 'always',
- trailingComma: 'none',
- printWidth: 100,
- tabWidth: 2
-};
diff --git a/examples/vercel-commerce/tailwind.config.js b/examples/vercel-commerce/tailwind.config.js
deleted file mode 100644
index eaf8db15..00000000
--- a/examples/vercel-commerce/tailwind.config.js
+++ /dev/null
@@ -1,54 +0,0 @@
-const plugin = require('tailwindcss/plugin');
-
-/** @type {import('tailwindcss').Config} */
-module.exports = {
- content: ['./app/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
- theme: {
- extend: {
- fontFamily: {
- sans: ['var(--font-geist-sans)']
- },
- keyframes: {
- fadeIn: {
- from: { opacity: 0 },
- to: { opacity: 1 }
- },
- marquee: {
- '0%': { transform: 'translateX(0%)' },
- '100%': { transform: 'translateX(-100%)' }
- },
- blink: {
- '0%': { opacity: 0.2 },
- '20%': { opacity: 1 },
- '100% ': { opacity: 0.2 }
- }
- },
- animation: {
- fadeIn: 'fadeIn .3s ease-in-out',
- carousel: 'marquee 60s linear infinite',
- blink: 'blink 1.4s both infinite'
- }
- }
- },
- future: {
- hoverOnlyWhenSupported: true
- },
- plugins: [
- require('@tailwindcss/container-queries'),
- require('@tailwindcss/typography'),
- plugin(({ matchUtilities, theme }) => {
- matchUtilities(
- {
- 'animation-delay': (value) => {
- return {
- 'animation-delay': value
- };
- }
- },
- {
- values: theme('transitionDelay')
- }
- );
- })
- ]
-};
diff --git a/examples/vercel-commerce/tsconfig.json b/examples/vercel-commerce/tsconfig.json
deleted file mode 100644
index ea2f6700..00000000
--- a/examples/vercel-commerce/tsconfig.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "compilerOptions": {
- "target": "es2015",
- "lib": ["dom", "dom.iterable", "esnext"],
- "downlevelIteration": true,
- "allowJs": true,
- "skipLibCheck": true,
- "strict": true,
- "forceConsistentCasingInFileNames": true,
- "noEmit": true,
- "esModuleInterop": true,
- "module": "esnext",
- "moduleResolution": "node",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "jsx": "preserve",
- "incremental": true,
- "baseUrl": ".",
- "noUncheckedIndexedAccess": true,
- "plugins": [
- {
- "name": "next"
- }
- ]
- },
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
- "exclude": ["node_modules", "open-next.config.ts"]
-}
diff --git a/examples/vercel-commerce/wrangler.jsonc b/examples/vercel-commerce/wrangler.jsonc
deleted file mode 100644
index cc1ed96c..00000000
--- a/examples/vercel-commerce/wrangler.jsonc
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "$schema": "node_modules/wrangler/config-schema.json",
- "main": ".open-next/worker.js",
- "name": "vercel-commerce-on-workers",
- "compatibility_date": "2024-12-30",
- "compatibility_flags": ["nodejs_compat"],
- "assets": {
- "directory": ".open-next/assets",
- "binding": "ASSETS"
- }
-}