Skip to content

Commit acda0dc

Browse files
committed
Update bookstore demo session/cart handling
1 parent b7cd2de commit acda0dc

File tree

8 files changed

+99
-60
lines changed

8 files changed

+99
-60
lines changed

demos/bookstore/app/cart.tsx

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ import type { User } from './models/users.ts'
1111
import { getCurrentUser } from './utils/context.ts'
1212
import { render } from './utils/render.ts'
1313
import { RestfulForm } from './components/restful-form.tsx'
14+
import { ensureCart } from './middleware/cart.ts'
1415

1516
export default {
1617
use: [loadAuth],
1718
handlers: {
1819
index({ session }) {
19-
let cart = getCart(session.get('userId'))
20-
let total = getCartTotal(cart)
20+
let cartId = session.get('cartId')
21+
let cart = cartId ? getCart(cartId) : null
22+
let total = cart ? getCartTotal(cart) : 0
2123

2224
let user: User | null = null
2325
try {
@@ -31,7 +33,7 @@ export default {
3133
<h1>Shopping Cart</h1>
3234

3335
<div class="card">
34-
{cart.items.length > 0 ? (
36+
{cart && cart.items.length > 0 ? (
3537
<>
3638
<table>
3739
<thead>
@@ -135,52 +137,55 @@ export default {
135137
},
136138

137139
api: {
138-
async add({ session, formData }) {
139-
// Simulate network latency
140-
await new Promise((resolve) => setTimeout(resolve, 1000))
140+
use: [ensureCart],
141+
handlers: {
142+
async add({ session, formData }) {
143+
// Simulate network latency
144+
await new Promise((resolve) => setTimeout(resolve, 1000))
141145

142-
let bookId = formData.get('bookId')?.toString() ?? ''
146+
let bookId = formData.get('bookId')?.toString() ?? ''
143147

144-
let book = getBookById(bookId)
145-
if (!book) {
146-
return new Response('Book not found', { status: 404 })
147-
}
148+
let book = getBookById(bookId)
149+
if (!book) {
150+
return new Response('Book not found', { status: 404 })
151+
}
148152

149-
addToCart(session.get('userId'), book.id, book.slug, book.title, book.price, 1)
153+
addToCart(session.get('cartId')!, book.id, book.slug, book.title, book.price, 1)
150154

151-
if (formData.get('redirect') === 'none') {
152-
return new Response(null, { status: 204 })
153-
}
155+
if (formData.get('redirect') === 'none') {
156+
return new Response(null, { status: 204 })
157+
}
154158

155-
return redirect(routes.cart.index.href())
156-
},
159+
return redirect(routes.cart.index.href())
160+
},
157161

158-
async update({ session, formData }) {
159-
let bookId = formData.get('bookId')?.toString() ?? ''
160-
let quantity = parseInt(formData.get('quantity')?.toString() ?? '1', 10)
162+
async update({ session, formData }) {
163+
let bookId = formData.get('bookId')?.toString() ?? ''
164+
let quantity = parseInt(formData.get('quantity')?.toString() ?? '1', 10)
161165

162-
updateCartItem(session.get('userId'), bookId, quantity)
166+
updateCartItem(session.get('cartId')!, bookId, quantity)
163167

164-
if (formData.get('redirect') === 'none') {
165-
return new Response(null, { status: 204 })
166-
}
168+
if (formData.get('redirect') === 'none') {
169+
return new Response(null, { status: 204 })
170+
}
167171

168-
return redirect(routes.cart.index.href())
169-
},
172+
return redirect(routes.cart.index.href())
173+
},
170174

171-
async remove({ session, formData }) {
172-
// Simulate network latency
173-
await new Promise((resolve) => setTimeout(resolve, 1000))
175+
async remove({ session, formData }) {
176+
// Simulate network latency
177+
await new Promise((resolve) => setTimeout(resolve, 1000))
174178

175-
let bookId = formData.get('bookId')?.toString() ?? ''
179+
let bookId = formData.get('bookId')?.toString() ?? ''
176180

177-
removeFromCart(session.get('userId'), bookId)
181+
removeFromCart(session.get('cartId')!, bookId)
178182

179-
if (formData.get('redirect') === 'none') {
180-
return new Response(null, { status: 204 })
181-
}
183+
if (formData.get('redirect') === 'none') {
184+
return new Response(null, { status: 204 })
185+
}
182186

183-
return redirect(routes.cart.index.href())
187+
return redirect(routes.cart.index.href())
188+
},
184189
},
185190
},
186191
},

demos/bookstore/app/checkout.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@ import { getCart, clearCart, getCartTotal } from './models/cart.ts'
77
import { createOrder, getOrderById } from './models/orders.ts'
88
import { Layout } from './layout.tsx'
99
import { render } from './utils/render.ts'
10-
import { getCurrentUser, getStorage } from './utils/context.ts'
10+
import { getCurrentUser } from './utils/context.ts'
1111

1212
export default {
1313
use: [requireAuth],
1414
handlers: {
1515
index({ session }) {
16-
let cart = getCart(session.get('userId'))
17-
let total = getCartTotal(cart)
16+
let cartId = session.get('cartId')
17+
let cart = cartId ? getCart(cartId) : null
18+
let total = cart ? getCartTotal(cart) : 0
1819

19-
if (cart.items.length === 0) {
20+
if (!cart || cart.items.length === 0) {
2021
return render(
2122
<Layout>
2223
<div class="card">
@@ -109,9 +110,10 @@ export default {
109110

110111
async action({ session, formData }) {
111112
let user = getCurrentUser()
112-
let cart = getCart(session.get('userId'))
113+
let cartId = session.get('cartId')
114+
let cart = cartId ? getCart(cartId) : null
113115

114-
if (cart.items.length === 0) {
116+
if (!cartId || !cart || cart.items.length === 0) {
115117
return redirect(routes.cart.index.href())
116118
}
117119

@@ -133,7 +135,7 @@ export default {
133135
shippingAddress,
134136
)
135137

136-
clearCart(session.get('userId'))
138+
clearCart(cartId)
137139

138140
return redirect(routes.checkout.confirmation.href({ orderId: order.id }))
139141
},

demos/bookstore/app/fragments.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ export default {
2121
return render(<div>Book not found</div>, { status: 404 })
2222
}
2323

24-
let cart = getCart(session.get('userId'))
25-
let inCart = cart.items.some((item) => item.slug === params.slug)
24+
let cartId = session.get('cartId')
25+
let cart = cartId ? getCart(cartId) : null
26+
let inCart = cart?.items.some((item) => item.slug === params.slug) === true
2627

2728
return render(<BookCard book={book} inCart={inCart} />)
2829
},
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { Middleware } from '@remix-run/fetch-router'
2+
import { createCartIfNotExists } from '../models/cart.ts'
3+
4+
/**
5+
* Middleware that ensures the user session has an associated cart
6+
* To be used on any route that mutates the cart
7+
*/
8+
export const ensureCart: Middleware = async ({ session }) => {
9+
createCartIfNotExists(session)
10+
}

demos/bookstore/app/models/cart.ts

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { Session } from '@remix-run/session'
2+
13
export interface CartItem {
24
bookId: string
35
slug: string
@@ -10,27 +12,44 @@ export interface Cart {
1012
items: CartItem[]
1113
}
1214

13-
// Store carts by user ID
15+
// Store carts by cartId, cartId will be stored in the session
16+
let nextCartId = 1
1417
const carts = new Map<string, Cart>()
1518

16-
export function getCart(userId: string): Cart {
17-
let cart = carts.get(userId)
19+
export function createCartIfNotExists(session: Session): Cart {
20+
let cartId = session.get('cartId')
21+
if (cartId) {
22+
let cart = carts.get(cartId)
23+
if (cart) {
24+
return cart
25+
}
26+
} else {
27+
cartId = String(nextCartId++)
28+
session.set('cartId', cartId)
29+
}
30+
31+
let cart = { items: [] }
32+
carts.set(cartId, cart)
33+
return cart
34+
}
35+
36+
export function getCart(cartId: string): Cart {
37+
let cart = carts.get(cartId)
1838
if (!cart) {
19-
cart = { items: [] }
20-
carts.set(userId, cart)
39+
throw new Error('Cart not found')
2140
}
2241
return cart
2342
}
2443

2544
export function addToCart(
26-
userId: string,
45+
cartId: string,
2746
bookId: string,
2847
slug: string,
2948
title: string,
3049
price: number,
3150
quantity: number = 1,
3251
): Cart {
33-
let cart = getCart(userId)
52+
let cart = getCart(cartId)
3453

3554
let existingItem = cart.items.find((item) => item.bookId === bookId)
3655
if (existingItem) {
@@ -42,8 +61,8 @@ export function addToCart(
4261
return cart
4362
}
4463

45-
export function updateCartItem(userId: string, bookId: string, quantity: number): Cart | undefined {
46-
let cart = getCart(userId)
64+
export function updateCartItem(cartId: string, bookId: string, quantity: number): Cart | undefined {
65+
let cart = getCart(cartId)
4766
let item = cart.items.find((item) => item.bookId === bookId)
4867

4968
if (!item) return undefined
@@ -57,14 +76,14 @@ export function updateCartItem(userId: string, bookId: string, quantity: number)
5776
return cart
5877
}
5978

60-
export function removeFromCart(userId: string, bookId: string): Cart {
61-
let cart = getCart(userId)
79+
export function removeFromCart(cartId: string, bookId: string): Cart {
80+
let cart = getCart(cartId)
6281
cart.items = cart.items.filter((item) => item.bookId !== bookId)
6382
return cart
6483
}
6584

66-
export function clearCart(userId: string): void {
67-
carts.set(userId, { items: [] })
85+
export function clearCart(cartId: string): void {
86+
carts.set(cartId, { items: [] })
6887
}
6988

7089
export function getCartTotal(cart: Cart): number {

demos/bookstore/app/utils/frame.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ export async function resolveFrame(frameSrc: string) {
2020
throw new Error(`Book not found: ${slug}`)
2121
}
2222

23-
let cart = getCart(getSession().get('userId'))
24-
let inCart = cart.items.some((item) => item.slug === slug)
23+
let cartId = getSession().get('cartId')
24+
let cart = cartId ? getCart(cartId) : null
25+
let inCart = cart?.items.some((item) => item.slug === slug) === true
2526

2627
return <BookCard book={book} inCart={inCart} />
2728
}

demos/bookstore/app/utils/session.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Session } from '@remix-run/session'
33

44
declare module '@remix-run/session' {
55
interface SessionData {
6+
cartId?: string
67
userId?: string
78
}
89
}

packages/fetch-router/src/lib/router.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export class Router {
8888
this.#uploadHandler = options?.uploadHandler
8989
this.#methodOverride = options?.methodOverride ?? true
9090

91-
if (!options || options.sessionStorage == null || options.sessionStorage === true) {
91+
if (options?.sessionStorage == null || options?.sessionStorage === true) {
9292
// Unless they opt-out, we default to an `HttpOnly` cookie-based session that
9393
// will only be "activated" in a `Set-Cookie` response if they mutate the
9494
// session

0 commit comments

Comments
 (0)