Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add example/nextjs-app-router #334

Merged
merged 9 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm test
./node_modules/.bin/lint-staged
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "nextjs-root-layout",
"name": "nextjs-app-router",
"version": "0.1.0",
"private": true,
"scripts": {
Expand All @@ -9,15 +9,16 @@
"lint": "next lint"
},
"dependencies": {
"@stripe/stripe-js": "^1.14.0",
"autoprefixer": "10.4.14",
"eslint": "8.40.0",
"eslint-config-next": "13.4.1",
"next": "13.4.1",
"postcss": "8.4.23",
"react": "18.2.0",
"react-dom": "18.2.0",
"stripe": "^13.10.0",
"tailwindcss": "3.3.2",
"use-shopping-cart": "workspace:^3.1.5"
"use-shopping-cart": "3.2.0-alpha.0"
}
}

Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { useShoppingCart } from "use-shopping-cart";
import { formatCurrencyString } from "use-shopping-cart";
import Image from "next/image";
'use client'
import { useShoppingCart, formatCurrencyString } from 'use-shopping-cart'
import Image from 'next/image'

export default function CartItem({ item }) {
const { name, emoji, quantity, price } = item;
const { removeItem } = useShoppingCart();
const { name, emoji, quantity, price } = item
const { removeItem } = useShoppingCart()

const removeItemFromCart = () => {
removeItem(item.id);
};
removeItem(item.id)
}

return (
<div className="flex items-center gap-4 mb-3">
Expand All @@ -17,7 +17,7 @@ export default function CartItem({ item }) {
{name} <span className="text-xs">({quantity})</span>
</div>
<div className="ml-auto">
{formatCurrencyString({ value: price, currency: "GBP" })}
{formatCurrencyString({ value: price, currency: 'GBP' })}
</div>
<button
onClick={() => removeItemFromCart()}
Expand All @@ -26,5 +26,5 @@ export default function CartItem({ item }) {
<Image alt="delete icon" src="./trash.svg" width={20} height={20} />
</button>
</div>
);
)
}
61 changes: 61 additions & 0 deletions examples/nextjs-app-router/src/app/components/CheckoutButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useState } from 'react'
import { useShoppingCart } from 'use-shopping-cart'

export default function CheckoutButton() {
const [status, setStatus] = useState('idle')
const { redirectToCheckout, cartCount, totalPrice, cartDetails } =
useShoppingCart()

async function handleClick(event) {
event.preventDefault()
if (cartCount > 0) {
setStatus('loading')
try {
const res = await fetch('/session', {
method: 'POST',
body: JSON.stringify(cartDetails)
})
const data = await res.json()
const result = await redirectToCheckout(data.sessionId)
if (result?.error) {
console.error(result)
setStatus('redirect-error')
}
} catch (error) {
console.error(error)
setStatus('redirect-error')
}
} else {
setStatus('no-items')
}
}

return (
<article className="mt-3 flex flex-col">
<div className="text-red-700 text-xs mb-3 h-5 text-center">
{totalPrice && totalPrice < 30
? 'You must have at least £0.30 in your basket'
: cartCount && cartCount > 20
? 'You cannot have more than 20 items'
: status === 'redirect-error'
? 'Unable to redirect to Stripe checkout page'
: status === 'no-items'
? 'Please add some items to your cart'
: null}
</div>
<button
onClick={handleClick}
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"
disabled={
(totalPrice && totalPrice < 30) ||
(cartCount && cartCount > 20) ||
status == 'no-items'
? true
: false
}
>
{status !== 'loading' ? 'Proceed to checkout' : 'Loading...'}
</button>
</article>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import ShoppingCart from './ShoppingCart'
export default function NavBar() {
const { handleCartClick, cartCount } = useShoppingCart()
return (
<nav className="py-5 px-12 flex justify-between">
<Link href="/">
<p className="bg-white text-3xl font-bold underline underline-offset-4 decoration-wavy decoration-2 decoration-emerald-500">
fresh
</p>
<nav className="py-5 bg-white px-12 flex text-black justify-between">
<Link
href="/"
className="bg-white text-3xl font-bold underline underline-offset-4 decoration-wavy decoration-2 decoration-emerald-500"
>
fresh
</Link>
<button className="relative" onClick={() => handleCartClick()}>
<Image
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
'use client'

import { useState } from 'react'
import { formatCurrencyString } from 'use-shopping-cart'
import { useShoppingCart } from 'use-shopping-cart'
import { formatCurrencyString, useShoppingCart } from 'use-shopping-cart'

export default function Product({ product }) {
const { addItem } = useShoppingCart()
Expand All @@ -25,7 +24,7 @@ export default function Product({ product }) {
}

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

export default function ShoppingCart() {
const { shouldDisplayCart, cartCount, cartDetails } = useShoppingCart();
const { shouldDisplayCart, cartCount, cartDetails } = useShoppingCart()
return (
<div
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 ${
shouldDisplayCart ? "opacity-100" : "opacity-0"
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 ${
shouldDisplayCart ? 'opacity-100' : 'opacity-0'
}`}
>
{cartCount && cartCount > 0 ? (
Expand All @@ -21,5 +21,5 @@ export default function ShoppingCart() {
<div className="p-5">You have no items in your cart</div>
)}
</div>
);
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ function CartProvider({ children }) {
return (
<USCProvider
mode="checkout-session"
stripe={'test'}
stripe={process.env.NEXT_PUBLIC_STRIPE_KEY}
currency={'USD'}
successUrl={'https://example.com/success'}
cancelUrl={'https://example.com/cancel'}
allowedCountries={['US', 'GB', 'CA']}
billingAddressCollection={true}
>
Expand All @@ -20,4 +18,3 @@ function CartProvider({ children }) {
}

export default CartProvider

Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import './globals.css'
import { Inter } from 'next/font/google'

// import { CartProvider } from 'use-shopping-cart'
import CartProvider from './components/providers'
import Layout from './components/Layout'
import NavBar from './components/NavBar'

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

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

export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>
<CartProvider>
<Layout>{children}</Layout>
<NavBar />
{children}
</CartProvider>
</body>
</html>
)
}

16 changes: 16 additions & 0 deletions examples/nextjs-app-router/src/app/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Product from './components/Product'
import { products } from './data/products'

export default function Home() {
return (
<main className="bg-[#f8f7f5] min-h-[calc(100vh-76px)] px-10 py-8">
<div className="container md:mx-auto md:max-w-[850px]">
<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]">
{products.map((product) => (
<Product product={product} key={product.id} />
))}
</div>
</div>
</main>
)
}
19 changes: 19 additions & 0 deletions examples/nextjs-app-router/src/app/session/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { stripe } from '../../lib/stripe'
import { headers } from 'next/headers'
import { products } from '../data/products'
import { validateCartItems } from 'use-shopping-cart/utilities'

export async function POST(request) {
const inventory = products
const cartProducts = await request.json()
const line_items = validateCartItems(inventory, cartProducts)
console.log('line_items', line_items)
const checkoutSession = await stripe.checkout.sessions.create({
mode: 'payment',
submit_type: 'pay',
line_items,
success_url: `${headers().get('origin')}/success`,
cancel_url: `${headers().get('origin')}/`
})
return Response.json({ sessionId: checkoutSession.id })
}
7 changes: 7 additions & 0 deletions examples/nextjs-app-router/src/app/success/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function SuccessPage() {
return (
<p className="text-black text-center">
Hi, Your order has been successfully placed
</p>
)
}
9 changes: 9 additions & 0 deletions examples/nextjs-app-router/src/lib/stripe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Stripe from 'stripe'
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
// https://github.com/stripe/stripe-node#configuration
apiVersion: '2023-08-16',
appInfo: {
name: 'projectname',
url: 'http://localhost:3000/'
}
})
55 changes: 0 additions & 55 deletions examples/nextjs-root-layout/src/app/components/CheckoutButton.js

This file was deleted.

24 changes: 0 additions & 24 deletions examples/nextjs-root-layout/src/app/components/Layout.js

This file was deleted.

13 changes: 0 additions & 13 deletions examples/nextjs-root-layout/src/app/page.js

This file was deleted.

Loading