diff --git a/.env.example b/.env.example index 46d641d54..bb67db43f 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ +COMPANY_NAME="shopware AG" TWITTER_CREATOR="@shopware" TWITTER_SITE="https://www.shopware.com/en/solutions/shopware-composable-frontends/" SITE_NAME="Next.js Commerce with Shopware Composable Frontends" diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml deleted file mode 100644 index a52b961a2..000000000 --- a/.github/workflows/e2e.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: e2e -on: - schedule: - # Runs "at 09:00 and 15:00, Monday through Friday" (see https://crontab.guru) - - cron: '0 9,15 * * 1-5' -jobs: - e2e: - runs-on: ubuntu-latest - steps: - - name: Cancel running workflows - uses: styfle/cancel-workflow-action@0.11.0 - with: - access_token: ${{ github.token }} - - name: Checkout repo - uses: actions/checkout@v3 - - name: Set node version - uses: actions/setup-node@v3 - with: - node-version-file: '.nvmrc' - - name: Set pnpm version - uses: pnpm/action-setup@v2 - with: - run_install: false - version: 7 - - name: Cache node_modules - id: node-modules-cache - uses: actions/cache@v3 - with: - path: '**/node_modules' - key: node-modules-cache-${{ hashFiles('**/pnpm-lock.yaml') }} - - name: Install dependencies - if: steps.node-modules-cache.outputs.cache-hit != 'true' - run: pnpm install - - name: Get playwright version - run: echo "PLAYWRIGHT_VERSION=$(node -e "console.log(require('./node_modules/@playwright/test/package.json').version)")" >> $GITHUB_ENV - - name: Cache playwright - uses: actions/cache@v3 - id: playwright-cache - with: - path: '~/.cache/ms-playwright' - key: playwright-cache-${{ env.PLAYWRIGHT_VERSION }} - - name: Install playwright browsers - if: steps.playwright-cache.outputs.cache-hit != 'true' - run: npx playwright install --with-deps - - name: Install playwright browser dependencies - if: steps.playwright-cache.outputs.cache-hit == 'true' - run: npx playwright install-deps - - name: Run tests - run: pnpm test:e2e diff --git a/.nvmrc b/.nvmrc index b6a7d89c6..3c032078a 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -16 +18 diff --git a/README.md b/README.md index aefe269f4..21266c857 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,9 @@ A Next.js 13 and App Router-ready ecommerce template featuring: - Styling with Tailwind CSS - Automatic light/dark mode based on system settings -> 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) +

+ +> 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). ## Prerequisites diff --git a/app/(cms)/[...cms]/page.tsx b/app/(cms)/[...cms]/page.tsx index 0a89ab4f8..8f915ba70 100644 --- a/app/(cms)/[...cms]/page.tsx +++ b/app/(cms)/[...cms]/page.tsx @@ -17,13 +17,6 @@ export async function generateMetadata({ params }: { params: { cms: string } }): 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' diff --git a/app/(cms)/layout.tsx b/app/(cms)/layout.tsx index e6d2b7dce..453253dca 100644 --- a/app/(cms)/layout.tsx +++ b/app/(cms)/layout.tsx @@ -4,7 +4,7 @@ import { Suspense } from 'react'; export default function Layout({ children }: { children: React.ReactNode }) { return ( -
+
{children}
diff --git a/app/api/revalidate/route.ts b/app/api/revalidate/route.ts index 94ddfff9b..47af2a4a4 100644 --- a/app/api/revalidate/route.ts +++ b/app/api/revalidate/route.ts @@ -1,37 +1,8 @@ -import { TAGS } from 'lib/constants'; -import { revalidateTag } from 'next/cache'; -import { headers } from 'next/headers'; +import { revalidate } from 'lib/shopify'; import { NextRequest, NextResponse } from 'next/server'; export const runtime = 'edge'; -// We always need to respond with a 200 status code to Shopify, -// otherwise it will continue to retry the request. -export async function POST(req: NextRequest): Promise { - 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() }); +export async function POST(req: NextRequest): Promise { + return revalidate(req); } diff --git a/app/favicon.ico b/app/favicon.ico index c4826c947..dc7d8431e 100644 Binary files a/app/favicon.ico and b/app/favicon.ico differ diff --git a/app/globals.css b/app/globals.css index c0eca5423..0a6d36768 100644 --- a/app/globals.css +++ b/app/globals.css @@ -2,8 +2,20 @@ @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/app/layout.tsx b/app/layout.tsx index b93239710..49d2d62f0 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -33,7 +33,7 @@ const inter = Inter({ export default async function RootLayout({ children }: { children: ReactNode }) { return ( - +
{children}
diff --git a/app/page.tsx b/app/page.tsx index 5f357726a..cf07827bf 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -8,13 +8,6 @@ export const runtime = 'edge'; export const metadata = { description: 'High-performance ecommerce store built with Next.js, Vercel, and Shopware.', openGraph: { - images: [ - { - url: `/api/og?title=${encodeURIComponent(process.env.SITE_NAME || '')}`, - width: 1200, - height: 630 - } - ], type: 'website' } }; diff --git a/app/product/[...handle]/page.tsx b/app/product/[...handle]/page.tsx index 4c54e2dfd..c476022c9 100644 --- a/app/product/[...handle]/page.tsx +++ b/app/product/[...handle]/page.tsx @@ -2,16 +2,14 @@ import type { Metadata } from 'next'; import { notFound } from 'next/navigation'; import { Suspense } from 'react'; -import { AddToCart } from 'components/cart/add-to-cart'; -import Grid from 'components/grid'; +import { GridTileImage } from 'components/grid/tile'; import Footer from 'components/layout/footer'; -import { VariantSelector } from 'components/product/variant-selector'; -import ProductGridItems from 'components/layout/product-grid-items'; import { Gallery } from 'components/product/gallery'; -import Prose from 'components/prose'; +import { ProductDescription } from 'components/product/product-description'; import { HIDDEN_PRODUCT_TAG } from 'lib/constants'; import { getProduct, getProductRecommendations } from 'lib/shopware'; import { Image } from 'lib/shopware/types'; +import Link from 'next/link'; export const runtime = 'edge'; @@ -26,17 +24,17 @@ export async function generateMetadata({ if (!product) return notFound(); const { url, width, height, altText: alt } = product.featuredImage || {}; - const hide = !product.tags.includes(HIDDEN_PRODUCT_TAG); + const indexable = !product.tags.includes(HIDDEN_PRODUCT_TAG); return { title: product.seo.title || product.title, description: product.seo.description || product.description, robots: { - index: hide, - follow: hide, + index: indexable, + follow: indexable, googleBot: { - index: hide, - follow: hide + index: indexable, + follow: indexable } }, openGraph: url @@ -77,47 +75,36 @@ export default async function ProductPage({ params }: { params: { handle: string }; return ( -
+ <>