Skip to content

Commit 46d1e87

Browse files
Add example/nextjs-app-router (#334)
* setting up new nextjs example for SSR testing * Add example/nextjs-app-router * Remove tsconfig.json * fix husky, remove nextjs test, update to use alpha version of USC * update .husky file * Add date-fns in package.json * Refactor code as per next13 app router --------- Co-authored-by: Nick DeJesus <ndejesus1227@gmail.com>
1 parent 0df8624 commit 46d1e87

30 files changed

+1017
-493
lines changed

.husky/pre-commit

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env sh
2+
. "$(dirname -- "$0")/_/husky.sh"
3+
4+
npm test
5+
./node_modules/.bin/lint-staged

examples/nextjs-root-layout/package.json renamed to examples/nextjs-app-router/package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "nextjs-root-layout",
2+
"name": "nextjs-app-router",
33
"version": "0.1.0",
44
"private": true,
55
"scripts": {
@@ -9,15 +9,16 @@
99
"lint": "next lint"
1010
},
1111
"dependencies": {
12+
"@stripe/stripe-js": "^1.14.0",
1213
"autoprefixer": "10.4.14",
1314
"eslint": "8.40.0",
1415
"eslint-config-next": "13.4.1",
1516
"next": "13.4.1",
1617
"postcss": "8.4.23",
1718
"react": "18.2.0",
1819
"react-dom": "18.2.0",
20+
"stripe": "^13.10.0",
1921
"tailwindcss": "3.3.2",
20-
"use-shopping-cart": "workspace:^3.1.5"
22+
"use-shopping-cart": "3.2.0-alpha.0"
2123
}
2224
}
23-
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { useShoppingCart } from "use-shopping-cart";
2-
import { formatCurrencyString } from "use-shopping-cart";
3-
import Image from "next/image";
1+
'use client'
2+
import { useShoppingCart, formatCurrencyString } from 'use-shopping-cart'
3+
import Image from 'next/image'
44

55
export default function CartItem({ item }) {
6-
const { name, emoji, quantity, price } = item;
7-
const { removeItem } = useShoppingCart();
6+
const { name, emoji, quantity, price } = item
7+
const { removeItem } = useShoppingCart()
88

99
const removeItemFromCart = () => {
10-
removeItem(item.id);
11-
};
10+
removeItem(item.id)
11+
}
1212

1313
return (
1414
<div className="flex items-center gap-4 mb-3">
@@ -17,7 +17,7 @@ export default function CartItem({ item }) {
1717
{name} <span className="text-xs">({quantity})</span>
1818
</div>
1919
<div className="ml-auto">
20-
{formatCurrencyString({ value: price, currency: "GBP" })}
20+
{formatCurrencyString({ value: price, currency: 'GBP' })}
2121
</div>
2222
<button
2323
onClick={() => removeItemFromCart()}
@@ -26,5 +26,5 @@ export default function CartItem({ item }) {
2626
<Image alt="delete icon" src="./trash.svg" width={20} height={20} />
2727
</button>
2828
</div>
29-
);
29+
)
3030
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { useState } from 'react'
2+
import { useShoppingCart } from 'use-shopping-cart'
3+
4+
export default function CheckoutButton() {
5+
const [status, setStatus] = useState('idle')
6+
const { redirectToCheckout, cartCount, totalPrice, cartDetails } =
7+
useShoppingCart()
8+
9+
async function handleClick(event) {
10+
event.preventDefault()
11+
if (cartCount > 0) {
12+
setStatus('loading')
13+
try {
14+
const res = await fetch('/session', {
15+
method: 'POST',
16+
body: JSON.stringify(cartDetails)
17+
})
18+
const data = await res.json()
19+
const result = await redirectToCheckout(data.sessionId)
20+
if (result?.error) {
21+
console.error(result)
22+
setStatus('redirect-error')
23+
}
24+
} catch (error) {
25+
console.error(error)
26+
setStatus('redirect-error')
27+
}
28+
} else {
29+
setStatus('no-items')
30+
}
31+
}
32+
33+
return (
34+
<article className="mt-3 flex flex-col">
35+
<div className="text-red-700 text-xs mb-3 h-5 text-center">
36+
{totalPrice && totalPrice < 30
37+
? 'You must have at least £0.30 in your basket'
38+
: cartCount && cartCount > 20
39+
? 'You cannot have more than 20 items'
40+
: status === 'redirect-error'
41+
? 'Unable to redirect to Stripe checkout page'
42+
: status === 'no-items'
43+
? 'Please add some items to your cart'
44+
: null}
45+
</div>
46+
<button
47+
onClick={handleClick}
48+
className="bg-emerald-50 hover:bg-emerald-500 hover:text-white transition-colors duration-500 text-emerald-500 py-3 px-5 rounded-md w-100 disabled:bg-slate-300 disabled:cursor-not-allowed disabled:text-white"
49+
disabled={
50+
(totalPrice && totalPrice < 30) ||
51+
(cartCount && cartCount > 20) ||
52+
status == 'no-items'
53+
? true
54+
: false
55+
}
56+
>
57+
{status !== 'loading' ? 'Proceed to checkout' : 'Loading...'}
58+
</button>
59+
</article>
60+
)
61+
}

examples/nextjs-root-layout/src/app/components/NavBar.js renamed to examples/nextjs-app-router/src/app/components/NavBar.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ import ShoppingCart from './ShoppingCart'
88
export default function NavBar() {
99
const { handleCartClick, cartCount } = useShoppingCart()
1010
return (
11-
<nav className="py-5 px-12 flex justify-between">
12-
<Link href="/">
13-
<p className="bg-white text-3xl font-bold underline underline-offset-4 decoration-wavy decoration-2 decoration-emerald-500">
14-
fresh
15-
</p>
11+
<nav className="py-5 bg-white px-12 flex text-black justify-between">
12+
<Link
13+
href="/"
14+
className="bg-white text-3xl font-bold underline underline-offset-4 decoration-wavy decoration-2 decoration-emerald-500"
15+
>
16+
fresh
1617
</Link>
1718
<button className="relative" onClick={() => handleCartClick()}>
1819
<Image

examples/nextjs-root-layout/src/app/components/Product.js renamed to examples/nextjs-app-router/src/app/components/Product.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
'use client'
22

33
import { useState } from 'react'
4-
import { formatCurrencyString } from 'use-shopping-cart'
5-
import { useShoppingCart } from 'use-shopping-cart'
4+
import { formatCurrencyString, useShoppingCart } from 'use-shopping-cart'
65

76
export default function Product({ product }) {
87
const { addItem } = useShoppingCart()
@@ -25,7 +24,7 @@ export default function Product({ product }) {
2524
}
2625

2726
return (
28-
<article className="flex flex-col gap-3 bg-white p-8 rounded-xl shadow-md text-center mb-6">
27+
<article className="flex text-black flex-col gap-3 bg-white p-8 rounded-xl shadow-md text-center mb-6">
2928
<div className="text-8xl cursor-default">{emoji}</div>
3029
<div className="text-lg">{name}</div>
3130
<div className="text-2xl font-semibold mt-auto">
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { useShoppingCart } from "use-shopping-cart";
2-
import CartItem from "./CartItem";
3-
import CheckoutButton from "./CheckoutButton";
1+
import { useShoppingCart } from 'use-shopping-cart'
2+
import CartItem from './CartItem'
3+
import CheckoutButton from './CheckoutButton'
44

55
export default function ShoppingCart() {
6-
const { shouldDisplayCart, cartCount, cartDetails } = useShoppingCart();
6+
const { shouldDisplayCart, cartCount, cartDetails } = useShoppingCart()
77
return (
88
<div
9-
className={`bg-white flex flex-col absolute right-3 md:right-9 top-14 w-80 py-4 px-4 shadow-[0_5px_15px_0_rgba(0,0,0,.15)] rounded-md transition-opacity duration-500 ${
10-
shouldDisplayCart ? "opacity-100" : "opacity-0"
9+
className={`bg-white text-black flex flex-col absolute right-3 md:right-9 top-14 w-80 py-4 px-4 shadow-[0_5px_15px_0_rgba(0,0,0,.15)] rounded-md transition-opacity duration-500 ${
10+
shouldDisplayCart ? 'opacity-100' : 'opacity-0'
1111
}`}
1212
>
1313
{cartCount && cartCount > 0 ? (
@@ -21,5 +21,5 @@ export default function ShoppingCart() {
2121
<div className="p-5">You have no items in your cart</div>
2222
)}
2323
</div>
24-
);
24+
)
2525
}

examples/nextjs-root-layout/src/app/components/providers.js renamed to examples/nextjs-app-router/src/app/components/providers.js

+1-4
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ function CartProvider({ children }) {
77
return (
88
<USCProvider
99
mode="checkout-session"
10-
stripe={'test'}
10+
stripe={process.env.NEXT_PUBLIC_STRIPE_KEY}
1111
currency={'USD'}
12-
successUrl={'https://example.com/success'}
13-
cancelUrl={'https://example.com/cancel'}
1412
allowedCountries={['US', 'GB', 'CA']}
1513
billingAddressCollection={true}
1614
>
@@ -20,4 +18,3 @@ function CartProvider({ children }) {
2018
}
2119

2220
export default CartProvider
23-
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
import './globals.css'
22
import { Inter } from 'next/font/google'
33

4-
// import { CartProvider } from 'use-shopping-cart'
54
import CartProvider from './components/providers'
6-
import Layout from './components/Layout'
5+
import NavBar from './components/NavBar'
76

87
const inter = Inter({ subsets: ['latin'] })
98

109
export const metadata = {
11-
title: 'Create Next App',
12-
description: 'Generated by create next app'
10+
title: 'Fresh',
11+
description:
12+
'Next.js 13 app router example to show how to use use-shopping-cart'
1313
}
1414

1515
export default function RootLayout({ children }) {
1616
return (
1717
<html lang="en">
1818
<body className={inter.className}>
1919
<CartProvider>
20-
<Layout>{children}</Layout>
20+
<NavBar />
21+
{children}
2122
</CartProvider>
2223
</body>
2324
</html>
2425
)
2526
}
26-
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import Product from './components/Product'
2+
import { products } from './data/products'
3+
4+
export default function Home() {
5+
return (
6+
<main className="bg-[#f8f7f5] min-h-[calc(100vh-76px)] px-10 py-8">
7+
<div className="container md:mx-auto md:max-w-[850px]">
8+
<div className="grid sm:grid-cols-2 md:grid-cols-4 justify-center mx-auto gap-4 place-center flex-wrap w-100 md:max-w-[900px]">
9+
{products.map((product) => (
10+
<Product product={product} key={product.id} />
11+
))}
12+
</div>
13+
</div>
14+
</main>
15+
)
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { stripe } from '../../lib/stripe'
2+
import { headers } from 'next/headers'
3+
import { products } from '../data/products'
4+
import { validateCartItems } from 'use-shopping-cart/utilities'
5+
6+
export async function POST(request) {
7+
const inventory = products
8+
const cartProducts = await request.json()
9+
const line_items = validateCartItems(inventory, cartProducts)
10+
console.log('line_items', line_items)
11+
const checkoutSession = await stripe.checkout.sessions.create({
12+
mode: 'payment',
13+
submit_type: 'pay',
14+
line_items,
15+
success_url: `${headers().get('origin')}/success`,
16+
cancel_url: `${headers().get('origin')}/`
17+
})
18+
return Response.json({ sessionId: checkoutSession.id })
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function SuccessPage() {
2+
return (
3+
<p className="text-black text-center">
4+
Hi, Your order has been successfully placed
5+
</p>
6+
)
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import Stripe from 'stripe'
2+
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
3+
// https://github.com/stripe/stripe-node#configuration
4+
apiVersion: '2023-08-16',
5+
appInfo: {
6+
name: 'projectname',
7+
url: 'http://localhost:3000/'
8+
}
9+
})

examples/nextjs-root-layout/src/app/components/CheckoutButton.js

-55
This file was deleted.

examples/nextjs-root-layout/src/app/components/Layout.js

-24
This file was deleted.

examples/nextjs-root-layout/src/app/page.js

-13
This file was deleted.

0 commit comments

Comments
 (0)