Skip to content

Commit

Permalink
Update to new design. (vercel#1103)
Browse files Browse the repository at this point in the history
  • Loading branch information
leerob authored Jul 25, 2023
1 parent d918fcc commit 59fc2bc
Show file tree
Hide file tree
Showing 61 changed files with 1,000 additions and 1,245 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
COMPANY_NAME="Vercel Inc."
TWITTER_CREATOR="@vercel"
TWITTER_SITE="https://nextjs.org/commerce"
SITE_NAME="Next.js Commerce"
Expand Down
49 changes: 0 additions & 49 deletions .github/workflows/e2e.yml

This file was deleted.

30 changes: 15 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![Deploy with Vercel](https://vercel.com/button)](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=SHOPIFY_REVALIDATION_SECRET,SHOPIFY_STOREFRONT_ACCESS_TOKEN,SHOPIFY_STORE_DOMAIN,SITE_NAME,TWITTER_CREATOR,TWITTER_SITE)
[![Deploy with Vercel](https://vercel.com/button)](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

Expand Down Expand Up @@ -64,7 +64,7 @@ Next.js Commerce requires a [paid Shopify plan](https://www.shopify.com/pricing)
### Add Shopify domain to an environment variable

Create a `SHOPIFY_STORE_DOMAIN` environment variable and use your Shopify domain as the the value (ie. `SHOPIFY_STORE_SUBDOMAIN.myshopify.com`).
Create a `SHOPIFY_STORE_DOMAIN` environment variable and use your Shopify domain as the the value (ie. `[your-shopify-store-subdomain].myshopify.com`).

> Note: Do not include the `https://`.
Expand All @@ -74,14 +74,14 @@ Next.js Commerce utilizes [Shopify's Storefront API](https://shopify.dev/docs/ap

In order to use the Shopify's Storefront API, you need to install the [Headless app](https://apps.shopify.com/headless) in your Shopify store.

Once installed, you'll need to create a `SHOPIFY_STOREFRONT_ACCESS_TOKEN` environment variable and use the public access token as the value
Once installed, you'll need to create a `SHOPIFY_STOREFRONT_ACCESS_TOKEN` environment variable and use the public access token as the value.

> Note: Shopify does offer a Node.js Storefront API SDK. We use the Storefront API via GraphQL directly instead of the Node.js SDK so we have more control over fetching and caching.
<details>
<summary>Expand to view detailed walkthrough</summary>

1. Navigate to `https://SHOPIFY_STORE_SUBDOMAIN.myshopify.com/admin/settings/apps`.
1. Navigate to `https://[your-shopify-store-subdomain].myshopify.com/admin/settings/apps`.
1. Click the green `Shopify App Store` button.
![Shopify App Store](https://user-images.githubusercontent.com/446260/233220545-cb4c1461-ebc5-424e-a421-bf0d32044027.jpg)
1. Search for `Headless` and click on the `Headless` app.
Expand All @@ -94,7 +94,7 @@ Once installed, you'll need to create a `SHOPIFY_STOREFRONT_ACCESS_TOKEN` enviro
![Create storefront](https://user-images.githubusercontent.com/446260/233220556-1eee15c4-a45d-446e-9f73-2e7c9f56b29c.jpg)
1. Copy and paste the public access token and assign it to a `SHOPIFY_STOREFRONT_ACCESS_TOKEN` environment variable.
![Pubic access token](https://user-images.githubusercontent.com/446260/233220558-5db04ff9-b894-40fe-bfba-0e92f26b8e1f.jpg)
1. If you ever need to reference the public access token again, you can navigate to `https://SHOPIFY_STORE_SUBDOMAIN.myshopify.com/admin/headless_storefronts`.
1. If you ever need to reference the public access token again, you can navigate to `https://[your-shopify-store-subdomain].myshopify.com/admin/headless_storefronts`.
</details>

### Install a headless theme
Expand All @@ -110,7 +110,7 @@ Follow the installation instructions and configure the theme with your headless

1. Download [Shopify Headless Theme](https://github.com/instantcommerce/shopify-headless-theme).
![Download Shoify Headless Theme](https://user-images.githubusercontent.com/446260/233220560-9f3f5ab0-ffb4-4305-b4ee-2c9d33eea90f.jpg)
1. Navigate to `https://SHOPIFY_STORE_SUBDOMAIN.myshopify.com/admin/themes`.
1. Navigate to `https://[your-shopify-store-subdomain].myshopify.com/admin/themes`.
1. Click `Add theme`, then `Upload zip file`.
![Upload zip file](https://user-images.githubusercontent.com/446260/233220561-7a53809e-0d95-45eb-b52f-3a52e3663a9c.jpg)
1. Select the downloaded zip file from above, and click the green `Upload file` button.
Expand Down Expand Up @@ -142,24 +142,24 @@ You can use Shopify's admin to customize these pages to match your brand and des

#### Checkout, order status, and order history

1. Navigate to `https://SHOPIFY_STORE_SUBDOMAIN.myshopify.com/admin/settings/checkout`.
1. Navigate to `https://[your-shopify-store-subdomain].myshopify.com/admin/settings/checkout`.
1. Click the green `Customize` button.
![Customize](https://user-images.githubusercontent.com/446260/233220530-9beda4b4-5008-440a-b923-9d196b722539.jpg)
1. Click `Branding` (ie. the paintbrush icon) and customize your brand. Please note, there are three steps / pages to the checkout flow. Use the dropdown to change pages and adjust branding as needed on each page. Click `Save` when you are done.
![Branding](https://user-images.githubusercontent.com/446260/233220534-e884d9fd-1a39-4f4d-9d09-163dde47c2e8.jpg)
1. Navigate to `https://SHOPIFY_STORE_SUBDOMAIN.myshopify.com/admin/settings/branding`.
1. Navigate to `https://[your-shopify-store-subdomain].myshopify.com/admin/settings/branding`.
1. Customize settings to match your brand.
![Branding](https://user-images.githubusercontent.com/446260/233220536-452b8802-9a1e-40f0-9a12-52b3dace84a5.jpg)

#### Emails

1. Navigate to `https://SHOPIFY_STORE_SUBDOMAIN.myshopify.com/admin/settings/email_settings`.
1. Navigate to `https://[your-shopify-store-subdomain].myshopify.com/admin/settings/email_settings`.
1. Customize settings to match your brand.
![Branding](https://user-images.githubusercontent.com/446260/233220538-13c83a9e-55f8-41e6-9b34-a39ee0848a8a.jpg)

#### Favicon

1. Navigate to `https://SHOPIFY_STORE_SUBDOMAIN.myshopify.com/admin/themes`.
1. Navigate to `https://[your-shopify-store-subdomain].myshopify.com/admin/themes`.
1. Click the green `Customize` button.
![Customize theme](https://user-images.githubusercontent.com/446260/233220539-4869a6cd-f59f-4de6-8091-95ed81d2302d.jpg)
1. Click `Theme settings` (ie. the paintbrush icon), expand the `FAVICON` section, upload favicon, then click the `Save` button.
Expand Down Expand Up @@ -190,7 +190,7 @@ Next.js is pre-configured to listen for the following Shopify webhook events and

#### Configure Shopify webhooks

1. Navigate to `https://SHOPIFY_STORE_SUBDOMAIN.myshopify.com/admin/settings/notifications`.
1. Navigate to `https://[your-shopify-store-subdomain].myshopify.com/admin/settings/notifications`.
1. Add webhooks for all six event topics listed above. You can add more sets for other preview urls, environments, or local development. Append `?secret=[SECRET]` to each url, where `[SECRET]` is the secret you created above.
![Shopify store webhooks](https://github.com/vercel/commerce/assets/446260/3d713fd7-b642-46e2-b2ce-f2b695ff6d2b)
![Shopify store add webhook](https://github.com/vercel/commerce/assets/446260/f0240a22-be07-42bc-bf6c-b97873868677)
Expand All @@ -216,7 +216,7 @@ Next.js Commerce is fully powered by Shopify in a truly headless and data driven

#### Products

`https://SHOPIFY_STORE_SUBDOMAIN.myshopify.com/admin/products`
`https://[your-shopify-store-subdomain].myshopify.com/admin/products`

Only `Active` products are shown. `Draft` products will not be shown until they are marked as `Active`.

Expand All @@ -228,7 +228,7 @@ Products that are active and "out of stock" are still shown on the site, but the

#### Collections

`https://SHOPIFY_STORE_SUBDOMAIN.myshopify.com/admin/collections`
`https://[your-shopify-store-subdomain].myshopify.com/admin/collections`

Create whatever collections you want and configure them however you want. All available collections will show on the search page as filters on the left, with one exception...

Expand All @@ -245,7 +245,7 @@ Create the following collections:

#### Pages

`https://SHOPIFY_STORE_SUBDOMAIN.myshopify.com/admin/pages`
`https://[your-shopify-store-subdomain].myshopify.com/admin/pages`

Next.js Commerce contains a dynamic `[page]` route. It will use the value to look for a corresponding page in Shopify. If a page is found, it will display its rich content using Tailwind's prose. If a page is not found, a 404 page is displayed.

Expand All @@ -255,7 +255,7 @@ Next.js Commerce contains a dynamic `[page]` route. It will use the value to loo

#### Navigation menus

`https://SHOPIFY_STORE_SUBDOMAIN.myshopify.com/admin/menus`
`https://[your-shopify-store-subdomain].myshopify.com/admin/menus`

Next.js Commerce's header and footer navigation is pre-configured to be controlled by Shopify navigation menus. This means you have full control over what links go here. They can be to collections, pages, external links, and more.

Expand Down
2 changes: 1 addition & 1 deletion app/[page]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Suspense } from 'react';
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<Suspense>
<div className="w-full bg-white dark:bg-black">
<div className="w-full">
<div className="mx-8 max-w-2xl py-20 sm:mx-auto">
<Suspense>{children}</Suspense>
</div>
Expand Down
7 changes: 0 additions & 7 deletions app/[page]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,6 @@ export async function generateMetadata({
title: page.seo?.title || page.title,
description: page.seo?.description || page.bodySummary,
openGraph: {
images: [
{
url: `/api/og?title=${encodeURIComponent(page.title)}`,
width: 1200,
height: 630
}
],
publishedTime: page.createdAt,
modifiedTime: page.updatedAt,
type: 'article'
Expand Down
6 changes: 6 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@
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;
}
2 changes: 1 addition & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const inter = Inter({
export default async function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en" className={inter.variable}>
<body className="bg-white text-black selection:bg-teal-300 dark:bg-black dark:text-white dark:selection:bg-fuchsia-600 dark:selection:text-white">
<body className="bg-neutral-50 text-black selection:bg-teal-300 dark:bg-neutral-900 dark:text-white dark:selection:bg-pink-500 dark:selection:text-white">
<Navbar />
<Suspense>
<main>{children}</main>
Expand Down
7 changes: 0 additions & 7 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,6 @@ export const runtime = 'edge';
export const metadata = {
description: 'High-performance ecommerce store built with Next.js, Vercel, and Shopify.',
openGraph: {
images: [
{
url: `/api/og?title=${encodeURIComponent(process.env.SITE_NAME || '')}`,
width: 1200,
height: 630
}
],
type: 'website'
}
};
Expand Down
85 changes: 48 additions & 37 deletions app/product/[handle]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@ import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { Suspense } from 'react';

import Grid from 'components/grid';
import { GridTileImage } from 'components/grid/tile';
import Footer from 'components/layout/footer';
import ProductGridItems from 'components/layout/product-grid-items';
import { AddToCart } from 'components/cart/add-to-cart';
import { Gallery } from 'components/product/gallery';
import { VariantSelector } from 'components/product/variant-selector';
import Prose from 'components/prose';
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';

export const runtime = 'edge';

Expand Down Expand Up @@ -76,43 +74,36 @@ export default async function ProductPage({ params }: { params: { handle: string
};

return (
<div>
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(productJsonLd)
}}
/>
<div className="lg:grid lg:grid-cols-6">
<div className="lg:col-span-4">
<Gallery
title={product.title}
amount={product.priceRange.maxVariantPrice.amount}
currencyCode={product.priceRange.maxVariantPrice.currencyCode}
images={product.images.map((image: Image) => ({
src: image.url,
altText: image.altText
}))}
/>
<div className="mx-auto max-w-screen-2xl px-4">
<div className="rounded-lg border border-neutral-200 bg-white p-8 px-4 dark:border-neutral-800 dark:bg-black md:p-12 lg:grid lg:grid-cols-6">
<div className="lg:col-span-4">
<Gallery
images={product.images.map((image: Image) => ({
src: image.url,
altText: image.altText
}))}
/>
</div>

<div className="py-6 pr-8 md:pr-12 lg:col-span-2">
<ProductDescription product={product} />
</div>
</div>

<div className="p-6 lg:col-span-2">
<VariantSelector options={product.options} variants={product.variants} />

{product.descriptionHtml ? (
<Prose className="mb-6 text-sm leading-tight" html={product.descriptionHtml} />
) : null}

<AddToCart variants={product.variants} availableForSale={product.availableForSale} />
</div>
</div>
<Suspense>
<RelatedProducts id={product.id} />
<Suspense>
<Footer />
<RelatedProducts id={product.id} />
</Suspense>
</div>
<Suspense>
<Footer />
</Suspense>
</div>
</>
);
}

Expand All @@ -122,11 +113,31 @@ async function RelatedProducts({ id }: { id: string }) {
if (!relatedProducts.length) return null;

return (
<div className="px-4 py-8">
<div className="mb-4 text-3xl font-bold">Related Products</div>
<Grid className="grid-cols-2 lg:grid-cols-5">
<ProductGridItems products={relatedProducts} />
</Grid>
<div className="py-8">
<h2 className="mb-4 text-2xl font-bold">Related Products</h2>
<div className="flex w-full gap-4 overflow-x-auto pt-1">
{relatedProducts.map((product, i) => {
return (
<Link
key={i}
className="w-full flex-none min-[475px]:w-1/2 sm:w-1/3 md:w-1/4 lg:w-1/5"
href={`/product/${product.handle}`}
>
<GridTileImage
alt={product.title}
label={{
title: product.title,
amount: product.priceRange.maxVariantPrice.amount,
currencyCode: product.priceRange.maxVariantPrice.currencyCode
}}
src={product.featuredImage?.url}
width={600}
height={600}
/>
</Link>
);
})}
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion app/search/[collection]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default async function CategoryPage({
{products.length === 0 ? (
<p className="py-3 text-lg">{`No products found in this collection`}</p>
) : (
<Grid className="grid-cols-2 lg:grid-cols-3">
<Grid className="grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
<ProductGridItems products={products} />
</Grid>
)}
Expand Down
6 changes: 3 additions & 3 deletions app/search/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { Suspense } from 'react';
export default function SearchLayout({ children }: { children: React.ReactNode }) {
return (
<Suspense>
<div className="mx-auto flex max-w-7xl flex-col bg-white py-6 text-black dark:bg-black dark:text-white md:flex-row">
<div className="order-first flex-none md:w-1/6">
<div className="mx-auto flex max-w-screen-2xl flex-col gap-8 px-4 pb-4 text-black dark:text-white md:flex-row">
<div className="order-first w-full flex-none md:max-w-[125px]">
<Collections />
</div>
<div className="order-last min-h-screen w-full md:order-none">{children}</div>
<div className="order-none md:order-last md:w-1/6 md:flex-none">
<div className="order-none flex-none md:order-last md:w-[125px]">
<FilterList list={sorting} title="Sort by" />
</div>
</div>
Expand Down
4 changes: 3 additions & 1 deletion app/search/loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ export default function Loading() {
{Array(12)
.fill(0)
.map((_, index) => {
return <Grid.Item key={index} className="animate-pulse bg-gray-100 dark:bg-gray-900" />;
return (
<Grid.Item key={index} className="animate-pulse bg-neutral-100 dark:bg-neutral-900" />
);
})}
</Grid>
);
Expand Down
Loading

0 comments on commit 59fc2bc

Please sign in to comment.