Better error handling.

This commit is contained in:
Lee Robinson 2023-08-04 18:30:20 -05:00
parent a11b6ad83b
commit 6f24cf7ebf
8 changed files with 92 additions and 89 deletions

View File

@ -2,9 +2,10 @@
export default function Error({ reset }: { reset: () => void }) { export default function Error({ reset }: { reset: () => void }) {
return ( return (
<div> <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 max-w-xl mx-auto my-4">
<h2>Something went wrong.</h2> <h2 className="text-xl font-bold">Oh no!</h2>
<button onClick={() => reset()}>Try again</button> <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="w-full mt-4 flex mx-auto items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white hover:opacity-90" onClick={() => reset()}>Try Again</button>
</div> </div>
); );
} }

View File

@ -4,10 +4,14 @@ import { ReactNode, Suspense } from 'react';
import './globals.css'; import './globals.css';
const { TWITTER_CREATOR, TWITTER_SITE, SITE_NAME } = process.env; 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 = { export const metadata = {
metadataBase: new URL(baseUrl),
title: { title: {
default: SITE_NAME, default: SITE_NAME!,
template: `%s | ${SITE_NAME}` template: `%s | ${SITE_NAME}`
}, },
robots: { robots: {

View File

@ -3,7 +3,7 @@
import { addToCart, createCart, getCart, removeFromCart, updateCart } from 'lib/shopify'; import { addToCart, createCart, getCart, removeFromCart, updateCart } from 'lib/shopify';
import { cookies } from 'next/headers'; import { cookies } from 'next/headers';
export const addItem = async (variantId: string | undefined): Promise<Error | undefined> => { export const addItem = async (variantId: string | undefined): Promise<String | undefined> => {
let cartId = cookies().get('cartId')?.value; let cartId = cookies().get('cartId')?.value;
let cart; let cart;
@ -18,25 +18,26 @@ export const addItem = async (variantId: string | undefined): Promise<Error | un
} }
if (!variantId) { if (!variantId) {
return new Error('Missing variantId'); return 'Missing product variant ID'
} }
try { try {
await addToCart(cartId, [{ merchandiseId: variantId, quantity: 1 }]); await addToCart(cartId, [{ merchandiseId: variantId, quantity: 1 }]);
} catch (e) { } catch (e) {
return new Error('Error adding item', { cause: e }); return 'Error adding item to cart'
} }
}; };
export const removeItem = async (lineId: string): Promise<Error | undefined> => { export const removeItem = async (lineId: string): Promise<String | undefined> => {
const cartId = cookies().get('cartId')?.value; const cartId = cookies().get('cartId')?.value;
if (!cartId) { if (!cartId) {
return new Error('Missing cartId'); return 'Missing cart ID';
} }
try { try {
await removeFromCart(cartId, [lineId]); await removeFromCart(cartId, [lineId]);
} catch (e) { } catch (e) {
return new Error('Error removing item', { cause: e }); return 'Error removing item from cart'
} }
}; };
@ -48,11 +49,11 @@ export const updateItemQuantity = async ({
lineId: string; lineId: string;
variantId: string; variantId: string;
quantity: number; quantity: number;
}): Promise<Error | undefined> => { }): Promise<String | undefined> => {
const cartId = cookies().get('cartId')?.value; const cartId = cookies().get('cartId')?.value;
if (!cartId) { if (!cartId) {
return new Error('Missing cartId'); return 'Missing cart ID';
} }
try { try {
await updateCart(cartId, [ await updateCart(cartId, [
@ -63,6 +64,6 @@ export const updateItemQuantity = async ({
} }
]); ]);
} catch (e) { } catch (e) {
return new Error('Error updating item quantity', { cause: e }); return 'Error updating item quantity'
} }
}; };

View File

@ -26,43 +26,43 @@ export function AddToCart({
); );
const selectedVariantId = variant?.id || defaultVariantId; const selectedVariantId = variant?.id || defaultVariantId;
const title = !availableForSale const title = !availableForSale
? 'Out of stock' ? 'Out of stock'
: !selectedVariantId : !selectedVariantId
? 'Please select options' ? 'Please select options'
: undefined; : undefined;
return ( return (
<button <button
aria-label="Add item to cart" aria-label="Add item to cart"
disabled={isPending || !availableForSale || !selectedVariantId} disabled={isPending || !availableForSale || !selectedVariantId}
title={title} title={title}
onClick={() => { onClick={() => {
// Safeguard in case someone messes with `disabled` in devtools. // Safeguard in case someone messes with `disabled` in devtools.
if (!availableForSale || !selectedVariantId) return; if (!availableForSale || !selectedVariantId) return;
startTransition(async () => { startTransition(async () => {
const error = await addItem(selectedVariantId); const error = await addItem(selectedVariantId);
if (error) { if (error) {
alert(error); // Trigger the error boundary in the root error.js
return; throw new Error(error.toString());
}
router.refresh();
});
}}
className={clsx(
'relative flex w-full items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white hover:opacity-90',
{
'cursor-not-allowed opacity-60 hover:opacity-60': !availableForSale || !selectedVariantId,
'cursor-not-allowed': isPending
} }
)}
router.refresh(); >
}); <div className="absolute left-0 ml-4">
}} {!isPending ? <PlusIcon className="h-5" /> : <LoadingDots className="mb-3 bg-white" />}
className={clsx( </div>
'relative flex w-full items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white hover:opacity-90', <span>{availableForSale ? 'Add To Cart' : 'Out Of Stock'}</span>
{ </button>
'cursor-not-allowed opacity-60 hover:opacity-60': !availableForSale || !selectedVariantId,
'cursor-not-allowed': isPending
}
)}
>
<div className="absolute left-0 ml-4">
{!isPending ? <PlusIcon className="h-5" /> : <LoadingDots className="mb-3 bg-white" />}
</div>
<span>{availableForSale ? 'Add To Cart' : 'Out Of Stock'}</span>
</button>
); );
} }

View File

@ -19,8 +19,8 @@ export default function DeleteItemButton({ item }: { item: CartItem }) {
const error = await removeItem(item.id); const error = await removeItem(item.id);
if (error) { if (error) {
alert(error); // Trigger the error boundary in the root error.js
return; throw new Error(error.toString());
} }
router.refresh(); router.refresh();

View File

@ -32,8 +32,8 @@ export default function EditItemQuantityButton({
}); });
if (error) { if (error) {
alert(error); // Trigger the error boundary in the root error.js
return; throw new Error(error.toString());
} }
router.refresh(); router.refresh();

View File

@ -25,7 +25,7 @@
"@headlessui/react": "^1.7.15", "@headlessui/react": "^1.7.15",
"@heroicons/react": "^2.0.18", "@heroicons/react": "^2.0.18",
"clsx": "^2.0.0", "clsx": "^2.0.0",
"next": "13.4.12", "next": "13.4.13-canary.15",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0" "react-dom": "18.2.0"
}, },

71
pnpm-lock.yaml generated
View File

@ -11,8 +11,8 @@ dependencies:
specifier: ^2.0.0 specifier: ^2.0.0
version: 2.0.0 version: 2.0.0
next: next:
specifier: 13.4.12 specifier: 13.4.13-canary.15
version: 13.4.12(react-dom@18.2.0)(react@18.2.0) version: 13.4.13-canary.15(react-dom@18.2.0)(react@18.2.0)
react: react:
specifier: 18.2.0 specifier: 18.2.0
version: 18.2.0 version: 18.2.0
@ -224,8 +224,8 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.14 '@jridgewell/sourcemap-codec': 1.4.14
dev: true dev: true
/@next/env@13.4.12: /@next/env@13.4.13-canary.15:
resolution: {integrity: sha512-RmHanbV21saP/6OEPBJ7yJMuys68cIf8OBBWd7+uj40LdpmswVAwe1uzeuFyUsd6SfeITWT3XnQfn6wULeKwDQ==} resolution: {integrity: sha512-AljMmO5a2uB0ZTDcBVhcfkE7WtdQDfnPg2zz/e6jKjVMRFPSvxaoRoSGUwONIhk9CAPbX9px7bZYom2wbhrTkw==}
dev: false dev: false
/@next/eslint-plugin-next@13.4.12: /@next/eslint-plugin-next@13.4.12:
@ -234,8 +234,8 @@ packages:
glob: 7.1.7 glob: 7.1.7
dev: true dev: true
/@next/swc-darwin-arm64@13.4.12: /@next/swc-darwin-arm64@13.4.13-canary.15:
resolution: {integrity: sha512-deUrbCXTMZ6ZhbOoloqecnUeNpUOupi8SE2tx4jPfNS9uyUR9zK4iXBvH65opVcA/9F5I/p8vDXSYbUlbmBjZg==} resolution: {integrity: sha512-ymE/tPjf5DXIqWxEefkqGX094ZDpKw/0sKb7xmzF0m8Kolac1eqA6ZnCsb1TKXYVQyrGUx/Z0xmxCK4cm2dEdw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
@ -243,8 +243,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-darwin-x64@13.4.12: /@next/swc-darwin-x64@13.4.13-canary.15:
resolution: {integrity: sha512-WRvH7RxgRHlC1yb5oG0ZLx8F7uci9AivM5/HGGv9ZyG2Als8Ij64GC3d+mQ5sJhWjusyU6T6V1WKTUoTmOB0zQ==} resolution: {integrity: sha512-B9fCPRjE1t5r1bmivq5fqHvU8mLNX7hkS2zj9arVrZEC7HdOugbSOpmQb5+yr5ZmNKMItQbPDJIATY+ZAiUtww==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
@ -252,8 +252,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-arm64-gnu@13.4.12: /@next/swc-linux-arm64-gnu@13.4.13-canary.15:
resolution: {integrity: sha512-YEKracAWuxp54tKiAvvq73PUs9lok57cc8meYRibTWe/VdPB2vLgkTVWFcw31YDuRXdEhdX0fWS6Q+ESBhnEig==} resolution: {integrity: sha512-K30IPFxZPtZLs1gqir95oNdCNgmu0awbC7MMLqOu9+wmW+LYjA6M3ltRe2Duy9nZ7JQob1oRl/s7MMbtCuzVAA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@ -261,8 +261,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-arm64-musl@13.4.12: /@next/swc-linux-arm64-musl@13.4.13-canary.15:
resolution: {integrity: sha512-LhJR7/RAjdHJ2Isl2pgc/JaoxNk0KtBgkVpiDJPVExVWA1c6gzY57+3zWuxuyWzTG+fhLZo2Y80pLXgIJv7g3g==} resolution: {integrity: sha512-ClJvWIhvCLXM3iSMet9bqKxyxifN7DGo8+wiV8gwIU+OMWHGNgGtmZ3xXae3R91w8DOLrsREyBN4uGLlgpwRXg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@ -270,8 +270,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-x64-gnu@13.4.12: /@next/swc-linux-x64-gnu@13.4.13-canary.15:
resolution: {integrity: sha512-1DWLL/B9nBNiQRng+1aqs3OaZcxC16Nf+mOnpcrZZSdyKHek3WQh6j/fkbukObgNGwmCoVevLUa/p3UFTTqgqg==} resolution: {integrity: sha512-/B0xaPcdx2HWDC9Bxks3dLIUyu9Falmd7ENRanYizfdihgM+kV2zIQe/5h5zaESKMEltLt2ELPOPCaFU5gOnYA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@ -279,8 +279,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-x64-musl@13.4.12: /@next/swc-linux-x64-musl@13.4.13-canary.15:
resolution: {integrity: sha512-kEAJmgYFhp0VL+eRWmUkVxLVunn7oL9Mdue/FS8yzRBVj7Z0AnIrHpTIeIUl1bbdQq1VaoOztnKicAjfkLTRCQ==} resolution: {integrity: sha512-YZZlKne+5iwsPe9yN8QP5sfyDN7ybpWTuYukfv6sKL68STuAVqqp4QX2g7a3Fw+LMJiDwyCFJaUDgO9KSLEqDw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@ -288,8 +288,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-arm64-msvc@13.4.12: /@next/swc-win32-arm64-msvc@13.4.13-canary.15:
resolution: {integrity: sha512-GMLuL/loR6yIIRTnPRY6UGbLL9MBdw2anxkOnANxvLvsml4F0HNIgvnU3Ej4BjbqMTNjD4hcPFdlEow4XHPdZA==} resolution: {integrity: sha512-nOi9w+E+ajqJuQhcB260AMJERJPYS1K5pL+5Rymyt9VWCZEJZiHTRuaf8y/H7sObZcQKwRVa7C/EWyZjj658XA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
@ -297,8 +297,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-ia32-msvc@13.4.12: /@next/swc-win32-ia32-msvc@13.4.13-canary.15:
resolution: {integrity: sha512-PhgNqN2Vnkm7XaMdRmmX0ZSwZXQAtamBVSa9A/V1dfKQCV1rjIZeiy/dbBnVYGdj63ANfsOR/30XpxP71W0eww==} resolution: {integrity: sha512-EqV5Bt7TmdFWa00KkoEeb5K4uRFrV1BAiwqylsk+d+2U1N2UIad/dTOyTzobjDcZ9uii1EaCVMiTuMfcsGIahw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
@ -306,8 +306,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-x64-msvc@13.4.12: /@next/swc-win32-x64-msvc@13.4.13-canary.15:
resolution: {integrity: sha512-Z+56e/Ljt0bUs+T+jPjhFyxYBcdY2RIq9ELFU+qAMQMteHo7ymbV7CKmlcX59RI9C4YzN8PgMgLyAoi916b5HA==} resolution: {integrity: sha512-hZcZ0vS1eevRTr1JUyDfTYXF36DBEowNZWyzNW8ZrlGMZyQloE9yf/jHoLtutxr2jV6GoHWGGqVSw4exOyjjKw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
@ -2241,25 +2241,22 @@ packages:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
dev: true dev: true
/next@13.4.12(react-dom@18.2.0)(react@18.2.0): /next@13.4.13-canary.15(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-eHfnru9x6NRmTMcjQp6Nz0J4XH9OubmzOa7CkWL+AUrUxpibub3vWwttjduu9No16dug1kq04hiUUpo7J3m3Xw==} resolution: {integrity: sha512-dSOzenhqdjH6fNbSKYZ4PkqmKLOviFSVUd75Csz+zZPoTWmAKR+9waUAttOyRnUgYd/qutt8KXGH+DiU0nmhVA==}
engines: {node: '>=16.8.0'} engines: {node: '>=16.8.0'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
'@opentelemetry/api': ^1.1.0 '@opentelemetry/api': ^1.1.0
fibers: '>= 3.1.0'
react: ^18.2.0 react: ^18.2.0
react-dom: ^18.2.0 react-dom: ^18.2.0
sass: ^1.3.0 sass: ^1.3.0
peerDependenciesMeta: peerDependenciesMeta:
'@opentelemetry/api': '@opentelemetry/api':
optional: true optional: true
fibers:
optional: true
sass: sass:
optional: true optional: true
dependencies: dependencies:
'@next/env': 13.4.12 '@next/env': 13.4.13-canary.15
'@swc/helpers': 0.5.1 '@swc/helpers': 0.5.1
busboy: 1.6.0 busboy: 1.6.0
caniuse-lite: 1.0.30001517 caniuse-lite: 1.0.30001517
@ -2270,15 +2267,15 @@ packages:
watchpack: 2.4.0 watchpack: 2.4.0
zod: 3.21.4 zod: 3.21.4
optionalDependencies: optionalDependencies:
'@next/swc-darwin-arm64': 13.4.12 '@next/swc-darwin-arm64': 13.4.13-canary.15
'@next/swc-darwin-x64': 13.4.12 '@next/swc-darwin-x64': 13.4.13-canary.15
'@next/swc-linux-arm64-gnu': 13.4.12 '@next/swc-linux-arm64-gnu': 13.4.13-canary.15
'@next/swc-linux-arm64-musl': 13.4.12 '@next/swc-linux-arm64-musl': 13.4.13-canary.15
'@next/swc-linux-x64-gnu': 13.4.12 '@next/swc-linux-x64-gnu': 13.4.13-canary.15
'@next/swc-linux-x64-musl': 13.4.12 '@next/swc-linux-x64-musl': 13.4.13-canary.15
'@next/swc-win32-arm64-msvc': 13.4.12 '@next/swc-win32-arm64-msvc': 13.4.13-canary.15
'@next/swc-win32-ia32-msvc': 13.4.12 '@next/swc-win32-ia32-msvc': 13.4.13-canary.15
'@next/swc-win32-x64-msvc': 13.4.12 '@next/swc-win32-x64-msvc': 13.4.13-canary.15
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
- babel-plugin-macros - babel-plugin-macros