feat: Complete ShopHub Shopping App (Next.js 15)#62
feat: Complete ShopHub Shopping App (Next.js 15)#62devin-ai-integration[bot] wants to merge 1 commit into
Conversation
- Product listing with search, filters, sorting, pagination - Product detail page with ratings and stock status - JWT authentication (login/signup) - Shopping cart with quantity management - Checkout flow with mock payment - Order history - Responsive Tailwind CSS UI - SQLite database with seed data (16 products) - Zustand state management - 48 passing tests (API + component) - E2E Playwright tests - setup.sh for one-command setup - Full README documentation
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
| if (items.length === 0 && !success) { | ||
| return ( | ||
| <div className="max-w-2xl mx-auto px-4 py-16 text-center"> | ||
| <h2 className="text-2xl font-bold text-gray-900">Cart is Empty</h2> | ||
| <p className="mt-2 text-gray-500">Add items to your cart first</p> | ||
| <Link href="/" className="btn-primary inline-block mt-4"> | ||
| Browse Products | ||
| </Link> | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
🔴 Checkout page flashes "Cart is Empty" before cart data loads
When a logged-in user navigates to the checkout page, the cart items are fetched asynchronously via api.cart.list() in a useEffect (line 38-41), but there is no loading state tracking for this fetch. The Zustand cart store initializes items as [], so before the API response arrives, the render logic at line 136 evaluates items.length === 0 && !success as true and shows the "Cart is Empty" screen.
Root Cause and Impact
The authLoading guard at line 84 only covers the auth hydration phase. Once auth is loaded and the token is available, authLoading becomes false, but the cart fetch is still in-flight. During this window, the component renders with an empty items array and hits the early return at line 136-146, displaying "Cart is Empty" with a "Browse Products" link.
This means users who navigate to checkout with items in their cart will briefly see a misleading empty cart message before the real cart data loads and the checkout form appears. This is a confusing UX that could cause users to navigate away thinking their cart is empty.
The cart page (src/app/cart/page.tsx:26-30) has the same pattern but uses setLoading(true) before the fetch, though it doesn't gate the empty-cart UI on that loading state either.
Prompt for agents
In shopping-app/src/app/checkout/page.tsx, add a loading state for the cart data fetch. Add a `const [cartLoading, setCartLoading] = useState(true);` state variable. In the useEffect at lines 33-43, set `setCartLoading(true)` before the fetch and `setCartLoading(false)` after it completes (in the .then callback). Then, update the loading check at line 84 to also check `cartLoading`: `if (authLoading || cartLoading)`. This ensures the loading skeleton is shown until both auth and cart data are loaded, preventing the false "Cart is Empty" flash.
Was this helpful? React with 👍 or 👎 to provide feedback.
| for (const item of cartItems) { | ||
| if (item.quantity > item.stock) { | ||
| return NextResponse.json( | ||
| { | ||
| success: false, | ||
| error: `Insufficient stock for ${item.name}`, | ||
| }, | ||
| { status: 400 } | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
🔴 Order stock check is outside the database transaction, allowing stock to go negative
In the order creation endpoint, the stock availability check at lines 79-89 reads stock values from a query executed at lines 56-70, but the actual stock decrement happens inside a separate database transaction at lines 96-121. The stock check is not part of the transaction.
Race Condition Details
The flow is:
- Read cart items with current stock (line 56-70)
- Check
item.quantity > item.stock(line 80) — outside transaction - Begin transaction (line 96)
- Decrement stock:
stock = stock - ?(line 115) — inside transaction
If two order requests arrive concurrently (e.g., in a production Next.js deployment with multiple workers, each with their own SQLite connection), both can pass the stock check at step 2 with the same stale stock value, then both proceed to decrement stock in their respective transactions. This results in stock going negative.
For example, if a product has stock=1 and two users each have 1 in their cart:
- Request A reads stock=1, passes check
- Request B reads stock=1, passes check
- Request A's transaction decrements stock to 0
- Request B's transaction decrements stock to -1
The fix is to move the stock check inside the transaction, or re-read and verify stock within the transaction before decrementing.
Prompt for agents
In shopping-app/src/app/api/orders/route.ts, move the stock validation check inside the database transaction (the `db.transaction()` callback starting at line 96). Re-read the product stock within the transaction before decrementing. For example, inside the transaction's for loop (around line 113), before calling insertItem.run and updateStock.run, query the current stock with `db.prepare('SELECT stock FROM products WHERE id = ?').get(item.product_id)` and verify `item.quantity <= currentStock`. If insufficient, throw an error that will cause the transaction to roll back. The outer catch block at line 129 will handle the error response.
Was this helpful? React with 👍 or 👎 to provide feedback.
What does this PR do?
Adds a complete, standalone shopping application under
shopping-app/built with Next.js 15 App Router, SQLite (better-sqlite3), JWT auth, Zustand, and Tailwind CSS.This is an entirely new standalone app added to the repo — it does not modify any existing cal.com code.
Features implemented:
setup.shone-command bootstrap scriptMandatory Tasks (DO NOT REMOVE)
How should this be tested?
Demo credentials:
demo@shop.com/password123Happy path: Browse products → Login → Add to cart → Checkout with any card number → View order in Order History.
Important items for reviewer attention
Repo placement — This is a standalone Next.js app dropped into the cal.com monorepo under
shopping-app/. It has its ownpackage.json,node_modules, and lockfile, completely independent of the monorepo. Reviewer should confirm this is the intended location.Security: JWT fallback secret —
src/lib/auth.ts:7hasprocess.env.JWT_SECRET || "dev-secret-key". In production, if the env var is missing, it silently uses an insecure default.Security: Wildcard image hostname —
next.config.tsallows images from anyhttps://**domain (needed for Unsplash seed images but permissive).Seed script is destructive —
src/lib/seed.tsrunsDELETE FROMon all tables before inserting. Running it on a populated DB would wipe data.E2E tests written but not demonstrated — Unit tests (48) passed; E2E specs are written but weren't run in this session.
Checklist
Link to Devin run: https://partner-workshops.devinenterprise.com/sessions/bb0b63a8d7424d0cae52a10ed28649c8