Skip to content

feat: Complete ShopHub Shopping App (Next.js 15)#62

Closed
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1770919123-shopping-app
Closed

feat: Complete ShopHub Shopping App (Next.js 15)#62
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1770919123-shopping-app

Conversation

@devin-ai-integration
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot commented Feb 12, 2026

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:

  • Product listing with search, category/price filters, sorting, pagination
  • Product detail page with ratings, stock status, quantity selector
  • JWT authentication (signup/login) with bcrypt password hashing
  • Shopping cart (add, update quantity, remove)
  • Checkout flow with mock payment
  • Order history with item details
  • Responsive Tailwind CSS UI with skeleton loading states
  • SQLite database with 16 seeded products
  • 48 passing unit/API tests (Vitest), 6 E2E test specs (Playwright)
  • setup.sh one-command bootstrap script

Homepage screenshot

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. N/A — standalone app with its own README.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

cd shopping-app
chmod +x setup.sh && ./setup.sh
npm run dev          # App at http://localhost:3099
npm test             # 48 unit/API tests
npm run test:e2e     # 6 E2E tests (needs `npx playwright install chromium` first)

Demo credentials: demo@shop.com / password123

Happy path: Browse products → Login → Add to cart → Checkout with any card number → View order in Order History.

Important items for reviewer attention

  1. Repo placement — This is a standalone Next.js app dropped into the cal.com monorepo under shopping-app/. It has its own package.json, node_modules, and lockfile, completely independent of the monorepo. Reviewer should confirm this is the intended location.

  2. Security: JWT fallback secretsrc/lib/auth.ts:7 has process.env.JWT_SECRET || "dev-secret-key". In production, if the env var is missing, it silently uses an insecure default.

  3. Security: Wildcard image hostnamenext.config.ts allows images from any https://** domain (needed for Unsplash seed images but permissive).

  4. Seed script is destructivesrc/lib/seed.ts runs DELETE FROM on all tables before inserting. Running it on a populated DB would wipe data.

  5. E2E tests written but not demonstrated — Unit tests (48) passed; E2E specs are written but weren't run in this session.

Checklist

  • I have read the contributing guide
  • My code follows the style guidelines of this project
  • I have commented my code, particularly in hard-to-understand areas
  • I have checked if my changes generate no new warnings

Link to Devin run: https://partner-workshops.devinenterprise.com/sessions/bb0b63a8d7424d0cae52a10ed28649c8


Open with Devin

- 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-integration
Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Copy link
Copy Markdown
Author

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 8 additional findings in Devin Review.

Open in Devin Review

Comment on lines +136 to +146
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>
);
}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +79 to +89
for (const item of cartItems) {
if (item.quantity > item.stock) {
return NextResponse.json(
{
success: false,
error: `Insufficient stock for ${item.name}`,
},
{ status: 400 }
);
}
}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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:

  1. Read cart items with current stock (line 56-70)
  2. Check item.quantity > item.stock (line 80) — outside transaction
  3. Begin transaction (line 96)
  4. 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.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants