diff --git a/.env.example b/.env.example index c612a4b7c..9a9a20f1d 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ TWITTER_CREATOR="@medusajs" TWITTER_SITE="https://medusajs.com/" -SITE_NAME="Next.js Commerce by Medusa" +SITE_NAME="Medusa Store" NEXT_PUBLIC_MEDUSA_BACKEND_API="http://localhost:9000" NEXT_PUBLIC_VERCEL_URL="http://localhost:3000" MEDUSA_API_KEY="" \ No newline at end of file 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/app/[page]/layout.tsx b/app/[page]/layout.tsx index e6d2b7dce..453253dca 100644 --- a/app/[page]/layout.tsx +++ b/app/[page]/layout.tsx @@ -4,7 +4,7 @@ import { Suspense } from 'react'; export default function Layout({ children }: { children: React.ReactNode }) { return ( -
+
{children}
diff --git a/app/[page]/page.tsx b/app/[page]/page.tsx index aa29e26ab..1165adb0a 100644 --- a/app/[page]/page.tsx +++ b/app/[page]/page.tsx @@ -23,13 +23,6 @@ export async function generateMetadata({ title: page.title, description: '', 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/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/error.tsx b/app/error.tsx index 3e2b40b1a..e0a7416a3 100644 --- a/app/error.tsx +++ b/app/error.tsx @@ -2,9 +2,18 @@ export default function Error({ reset }: { reset: () => void }) { return ( -
-

Something went wrong.

- +
+

Oh no!

+

+ There was an issue with our storefront. This could be a temporary issue, please try your + action again. +

+
); } 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 dca2c624f..7256fb488 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -4,10 +4,14 @@ import { ReactNode, Suspense } 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'; export const metadata = { + metadataBase: new URL(baseUrl), title: { - default: SITE_NAME, + default: SITE_NAME!, template: `%s | ${SITE_NAME}` }, robots: { @@ -33,7 +37,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 14ab8fb01..465600f39 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -10,13 +10,6 @@ export const revalidate = parseInt(process.env.REVALIDATE_WINDOW ?? `${60 * 60 * export const metadata = { description: 'High-performance ecommerce store built with Next.js, Vercel, and Medusa.', 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 6573882bb..18d87656e 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 ProductGridItems from 'components/layout/product-grid-items'; 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 } from 'lib/medusa'; import { Image } from 'lib/medusa/types'; +import Link from 'next/link'; export const runtime = 'edge'; @@ -25,17 +23,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.title, description: product.description, robots: { - index: hide, - follow: hide, + index: indexable, + follow: indexable, googleBot: { - index: hide, - follow: hide + index: indexable, + follow: indexable } }, openGraph: url @@ -75,48 +73,36 @@ export default async function ProductPage({ params }: { params: { handle: string }; return ( -
+ <>