Implement shopping cart functionality with UI components and API integration
- Added CartItem, CartSummary, CartEmpty, CartSidebar, and CartSheet components for managing cart display and interactions. - Integrated useCart and useCartUI composables for cart state management and UI control. - Implemented API endpoints for cart operations, including fetching, adding, updating, and removing items. - Enhanced user experience with loading states and notifications using vue-sonner for cart actions. - Configured session management for guest and authenticated users, ensuring cart persistence across sessions. This commit completes the shopping cart feature, enabling users to add items, view their cart, and proceed to checkout. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
65
server/api/cart/items/[id].delete.ts
Normal file
65
server/api/cart/items/[id].delete.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* DELETE /api/cart/items/:id
|
||||
*
|
||||
* Remove an item from the shopping cart
|
||||
*
|
||||
* Validation:
|
||||
* - Cart item must exist
|
||||
* - Cart item must belong to current user/session
|
||||
*
|
||||
* Response:
|
||||
* - 204 No Content on success
|
||||
* - 404 Not Found if item doesn't exist or doesn't belong to user
|
||||
*/
|
||||
|
||||
import { z } from 'zod'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { cartItems } from '../../../database/schema'
|
||||
|
||||
// Path params validation
|
||||
const pathParamsSchema = z.object({
|
||||
id: z.string().uuid('Invalid cart item ID'),
|
||||
})
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
// Validate path params
|
||||
const params = await getValidatedRouterParams(event, pathParamsSchema.parse)
|
||||
const cartItemId = params.id
|
||||
|
||||
// Verify cart item belongs to current user/session
|
||||
const hasPermission = await verifyCartItemOwnership(event, cartItemId)
|
||||
|
||||
if (!hasPermission) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Cart item not found',
|
||||
})
|
||||
}
|
||||
|
||||
const db = await useDatabase()
|
||||
|
||||
// Fetch cart item to get cart ID for timestamp update
|
||||
const cartItem = await db.query.cartItems.findFirst({
|
||||
where: eq(cartItems.id, cartItemId),
|
||||
with: {
|
||||
cart: true,
|
||||
},
|
||||
})
|
||||
|
||||
if (!cartItem) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Cart item not found',
|
||||
})
|
||||
}
|
||||
|
||||
// Delete cart item
|
||||
await db.delete(cartItems).where(eq(cartItems.id, cartItemId))
|
||||
|
||||
// Update cart timestamp
|
||||
await touchCart(cartItem.cart.id)
|
||||
|
||||
// Return 204 No Content
|
||||
setResponseStatus(event, 204)
|
||||
return null
|
||||
})
|
||||
96
server/api/cart/items/[id].patch.ts
Normal file
96
server/api/cart/items/[id].patch.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* PATCH /api/cart/items/:id
|
||||
*
|
||||
* Update the quantity of a cart item
|
||||
*
|
||||
* Request Body:
|
||||
* {
|
||||
* quantity: number (positive integer)
|
||||
* }
|
||||
*
|
||||
* Validation:
|
||||
* - Cart item must exist
|
||||
* - Cart item must belong to current user/session
|
||||
* - Quantity must be >= 1
|
||||
* - Quantity must not exceed available stock
|
||||
*
|
||||
* Response:
|
||||
* {
|
||||
* success: true,
|
||||
* message: string,
|
||||
* cart: CartSummary
|
||||
* }
|
||||
*/
|
||||
|
||||
import { z } from 'zod'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { cartItems } from '../../../database/schema'
|
||||
|
||||
// Request validation schema
|
||||
const updateQuantitySchema = z.object({
|
||||
quantity: z.number().int().min(1, 'Quantity must be at least 1'),
|
||||
})
|
||||
|
||||
// Path params validation
|
||||
const pathParamsSchema = z.object({
|
||||
id: z.string().uuid('Invalid cart item ID'),
|
||||
})
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
// Validate path params
|
||||
const params = await getValidatedRouterParams(event, pathParamsSchema.parse)
|
||||
const cartItemId = params.id
|
||||
|
||||
// Validate request body
|
||||
const body = await readBody(event)
|
||||
const { quantity } = await updateQuantitySchema.parseAsync(body)
|
||||
|
||||
// Verify cart item belongs to current user/session
|
||||
const hasPermission = await verifyCartItemOwnership(event, cartItemId)
|
||||
|
||||
if (!hasPermission) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Cart item not found',
|
||||
})
|
||||
}
|
||||
|
||||
const db = await useDatabase()
|
||||
|
||||
// Fetch cart item with product details
|
||||
const cartItem = await db.query.cartItems.findFirst({
|
||||
where: eq(cartItems.id, cartItemId),
|
||||
with: {
|
||||
product: true,
|
||||
cart: true,
|
||||
},
|
||||
})
|
||||
|
||||
if (!cartItem) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Cart item not found',
|
||||
})
|
||||
}
|
||||
|
||||
// Validate quantity against stock
|
||||
validateQuantityUpdate(quantity, cartItem.product.stockQuantity)
|
||||
|
||||
// Update quantity
|
||||
await db
|
||||
.update(cartItems)
|
||||
.set({ quantity })
|
||||
.where(eq(cartItems.id, cartItemId))
|
||||
|
||||
// Update cart timestamp
|
||||
await touchCart(cartItem.cart.id)
|
||||
|
||||
// Return updated cart
|
||||
const cartSummary = await getCartWithItems(cartItem.cart.id)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Quantity updated successfully',
|
||||
cart: cartSummary,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user