Merge branch 'main' into sol/basic-site-working

This commit is contained in:
Sol Irvine
2023-08-08 09:15:13 +09:00
28 changed files with 294 additions and 258 deletions

View File

@@ -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<Response> {
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<NextResponse> {
return revalidate(req);
}

View File

@@ -2,9 +2,18 @@
export default function Error({ reset }: { reset: () => void }) {
return (
<div>
<h2>Something went wrong.</h2>
<button onClick={() => reset()}>Try again</button>
<div className="mx-auto my-4 flex max-w-xl flex-col rounded-lg border border-neutral-200 bg-white p-8 dark:border-neutral-800 dark:bg-black md:p-12">
<h2 className="text-xl font-bold">Oh no!</h2>
<p className="my-2">
There was an issue with our storefront. This could be a temporary issue, please try your
action again.
</p>
<button
className="mx-auto mt-4 flex w-full items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white hover:opacity-90"
onClick={() => reset()}
>
Try Again
</button>
</div>
);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 535 B

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -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: {

View File

@@ -23,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.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
@@ -82,8 +82,8 @@ export default async function ProductPage({ params }: { params: { handle: string
}}
/>
<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">
<div className="flex flex-col rounded-lg border border-neutral-200 bg-white p-8 dark:border-neutral-800 dark:bg-black md:p-12 lg:flex-row">
<div className="h-full w-full basis-full lg:basis-4/6">
<Gallery
images={product.images.map((image: Image) => ({
src: image.url,
@@ -92,7 +92,7 @@ export default async function ProductPage({ params }: { params: { handle: string
/>
</div>
<div className="py-6 pr-8 md:pr-12 lg:col-span-2">
<div className="basis-full lg:basis-2/6">
<ProductDescription product={product} />
</div>
</div>
@@ -115,14 +115,13 @@ async function RelatedProducts({ id }: { id: string }) {
return (
<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}`}
>
<ul className="flex w-full gap-4 overflow-x-auto pt-1">
{relatedProducts.map((product) => (
<li
key={product.handle}
className="aspect-square w-full flex-none min-[475px]:w-1/2 sm:w-1/3 md:w-1/4 lg:w-1/5"
>
<Link className="relative h-full w-full" href={`/product/${product.handle}`}>
<GridTileImage
alt={product.title}
label={{
@@ -131,13 +130,13 @@ async function RelatedProducts({ id }: { id: string }) {
currencyCode: product.priceRange.maxVariantPrice.currencyCode
}}
src={product.featuredImage?.url}
width={600}
height={600}
fill
sizes="(min-width: 1024px) 20vw, (min-width: 768px) 25vw, (min-width: 640px) 33vw, (min-width: 475px) 50vw, 100vw"
/>
</Link>
);
})}
</div>
</li>
))}
</ul>
</div>
);
}

View File

@@ -24,7 +24,7 @@ export default async function SearchPage({
return (
<>
{searchValue ? (
<p>
<p className="mb-4">
{products.length === 0
? 'There are no products that match '
: `Showing ${products.length} ${resultsText} for `}

View File

@@ -1,11 +1,16 @@
import { getCollections, getPages, getProducts } from 'lib/shopify';
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 default async function sitemap(): Promise<Promise<Promise<MetadataRoute.Sitemap>>> {
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const routesMap = [''].map((route) => ({
url: `${baseUrl}${route}`,
lastModified: new Date().toISOString()
@@ -32,9 +37,13 @@ export default async function sitemap(): Promise<Promise<Promise<MetadataRoute.S
}))
);
const fetchedRoutes = (
await Promise.all([collectionsPromise, productsPromise, pagesPromise])
).flat();
let fetchedRoutes: Route[] = [];
try {
fetchedRoutes = (await Promise.all([collectionsPromise, productsPromise, pagesPromise])).flat();
} catch (error) {
throw JSON.stringify(error, null, 2);
}
return [...routesMap, ...fetchedRoutes];
}