Skip to content

feat: Full-stack StickerStore application (Spring Boot + React/Vite)#212

Open
devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1775639661-sticker-shop
Open

feat: Full-stack StickerStore application (Spring Boot + React/Vite)#212
devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1775639661-sticker-shop

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot commented Apr 8, 2026

Summary

Adds a complete full-stack sticker shop e-commerce application from scratch, organized as a monorepo:

Backend (backend/) — Spring Boot 3.2 + Java 17:

  • 6 JPA entities (User, Product, Cart, CartItem, Order, OrderItem) with H2 in-memory DB
  • JWT authentication (HS256, 24h expiry) via Spring Security (stateless sessions)
  • REST API: auth (register/login), products (CRUD, search, filter by category), cart (add/update/remove items), orders (checkout from cart with shipping info, order history)
  • Data seeder: 15 sample sticker products across 5 categories, 2 users (admin/admin123, user/user123)
  • Global exception handler (400/401/404/500)

Frontend (frontend/) — React 18 + Vite + TypeScript:

  • Tailwind CSS with purple/indigo + yellow/orange color scheme
  • Auth & Cart React contexts with localStorage persistence and axios JWT interceptor
  • 8 pages: Home (hero + product grid + category filter + search), Login, Register, ProductDetail, Cart, Checkout, Orders, OrderDetail
  • Responsive design (1→2→3→4 column grid), hover effects, toast notifications
  • Protected routes redirect unauthenticated users to login

Verified locally: Backend compiles, frontend passes TypeScript check and Vite production build. Full end-to-end testing completed (login → browse → add to cart → checkout → view orders).

Updates since last revision

Addressed Devin Review feedback in c58ce02:

  1. 401 interceptor no longer breaks login error toasts (frontend/src/api.ts): The axios response interceptor now skips the redirect-to-login behavior for /auth/ endpoints, so failed login attempts correctly show error toasts instead of triggering a full page reload.
  2. Shipping info is now persisted on checkout: Added shippingName, shippingAddress, shippingCity, shippingZip fields to the Order entity. OrderController.checkout() now accepts @RequestBody CheckoutRequest and OrderService.checkout() persists the shipping data with the order.

Review & Testing Checklist for Human

  • Run both services and test the full checkout flow end-to-end: Start backend (cd backend && ./mvnw spring-boot:run), start frontend (cd frontend && npm run dev), then: login with user/user123 → browse products → add to cart → adjust quantities → checkout with shipping form → verify order appears in order history. Confirm shipping info is saved (check H2 console at /h2-console if needed, since OrderResponse does not currently return shipping fields back to the UI).
  • Test login error handling: Enter wrong credentials on login page and verify the error toast appears without a page reload (validates the 401 interceptor fix).
  • Review security config (SecurityConfig.java): CORS is locked to localhost:5173 only, CSRF is disabled (standard for stateless JWT). The JWT secret is hardcoded in application.yml — fine for demo/dev, but flag if this will be deployed.
  • Cart context sends productId: 0 on update (CartContext.tsx line ~68): The updateQuantity call sends { productId: 0, quantity } as a dummy value. The backend ignores productId on PUT (it resolves by itemId from the path), so it works, but it's a code smell worth cleaning up.
  • CheckoutPage calls navigate() during render when cart is empty — this can cause React warnings. Consider wrapping in a useEffect.

Notes

  • OrderResponse does not include shipping fields, so shipping data is persisted but not surfaced back to the UI. This is a known gap — add shipping fields to OrderResponse and OrderDetailPage if needed.
  • tsconfig.json and tsconfig.app.json are identical — standard Vite scaffolding would have tsconfig.json reference tsconfig.app.json via "references". No functional impact but slightly non-standard.
  • The admin role is seeded but there are no admin-specific endpoints or UI (no product CRUD for admins). This is expected per the spec.
  • Database is H2 in-memory — all data resets on backend restart. The DataInitializer re-seeds on every startup.

Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/1d5815112b2a41f4b94bb716edb0cf92


Open with Devin

Backend (Spring Boot 3 + Java 17):
- JPA entities: User, Product, Cart, CartItem, Order, OrderItem
- Spring Security with JWT authentication (HS256)
- REST controllers for auth, products, cart, and orders
- H2 in-memory database with data seeder (15 products, 2 users)
- Global exception handling

Frontend (React 18 + Vite + TypeScript):
- Tailwind CSS styling with purple/indigo theme
- Auth and Cart contexts with localStorage persistence
- Pages: Home, Login, Register, ProductDetail, Cart, Checkout, Orders
- Protected routes, responsive design, toast notifications
- Axios interceptor for JWT token management
@devin-ai-integration
Copy link
Copy Markdown
Contributor 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

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor 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 new potential issues.

View 9 additional findings in Devin Review.

Open in Devin Review

Comment on lines +57 to +62
if (product.getStockQuantity() < cartItem.getQuantity()) {
throw new BadRequestException("Insufficient stock for: " + product.getName());
}

product.setStockQuantity(product.getStockQuantity() - cartItem.getQuantity());
productRepository.save(product);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🔴 Checkout stock decrement has no concurrency protection, allowing overselling

In OrderService.checkout(), the stock check and decrement (product.getStockQuantity() < cartItem.getQuantity() followed by product.setStockQuantity(...)) is not protected by any database-level locking. Two concurrent checkout transactions can both read the same stock value, both pass the check, and both save decremented values, resulting in stock going negative. For example: stock=10, Thread A wants 7, Thread B wants 5. Both read stock=10, both pass the check, A saves 3, B saves 5 — final stock is 5 instead of the correct -2 (which should have been rejected). The @Transactional annotation alone with the default READ_COMMITTED isolation level does not prevent this lost-update race condition. A pessimistic lock (@Lock(PESSIMISTIC_WRITE)) on the Product query or optimistic locking (@Version on the Product entity) is needed.

Prompt for agents
The checkout method in OrderService.java has a race condition on stock quantity. Between the stock check at line 57 and the decrement at line 61, another concurrent transaction can read the same stock value. The @Transactional annotation with default READ_COMMITTED isolation does not prevent this lost-update anomaly.

To fix this, add optimistic locking to the Product entity by adding a @Version field:

  @Version
  private Long version;

This will cause JPA to automatically check the version during updates and throw an OptimisticLockException if another transaction modified the same product concurrently. The checkout method should catch this exception and return an appropriate error to the user.

Alternatively, use pessimistic locking by adding a custom query in ProductRepository with @Lock(LockModeType.PESSIMISTIC_WRITE) to lock the product rows during checkout, or use a native SQL UPDATE with a WHERE clause that includes the expected stock quantity.
Open in Devin Review

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

Comment on lines +39 to +41
if (cartItems.length === 0) {
navigate('/cart');
return null;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟡 CheckoutPage calls navigate() during render, violating React rules

At CheckoutPage.tsx:39-41, navigate('/cart') is called directly in the render body when cartItems.length === 0. Calling navigate() during render is a side effect that violates React's rendering rules — side effects should be performed in useEffect or event handlers, not during render. This can produce React warnings in development and cause unpredictable behavior with React Strict Mode's double-rendering. In practice, if the component ever renders with an empty cart before the intended navigation takes effect (e.g., due to a re-render timing issue), this navigate('/cart') could compete with a legitimate navigate('/orders/${id}') from the checkout handler.

Prompt for agents
In CheckoutPage.tsx, the navigate('/cart') call at lines 39-41 happens during the render phase, which is a React anti-pattern. Side effects like navigation must happen inside useEffect or event handlers, not during render.

Replace the imperative navigate call with either:
1. A useEffect that watches cartItems and navigates when empty:
   useEffect(() => { if (cartItems.length === 0 && !loading) navigate('/cart'); }, [cartItems, loading, navigate]);

2. Or use React Router's <Navigate> component which properly handles navigation via useEffect internally:
   if (cartItems.length === 0) return <Navigate to="/cart" replace />;

Note: you may also want to add a loading state check to avoid redirecting before the cart data has loaded.
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