From 64c46e62f0c2c7d3d6d6b8bb28cf91c6514a7df8 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 16 Feb 2026 04:46:52 +0000
Subject: [PATCH 1/4] Initial plan
From 01aff0e8b3af953e34a3d2e388e7cb460d9b9111 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 16 Feb 2026 04:50:03 +0000
Subject: [PATCH 2/4] feat: Add backend wishlist API with model, routes, and
tests
Co-authored-by: thomasiverson <12767513+thomasiverson@users.noreply.github.com>
---
api/src/index.ts | 2 +
api/src/models/wishlist.ts | 32 +++++++
api/src/routes/wishlist.test.ts | 94 ++++++++++++++++++++
api/src/routes/wishlist.ts | 151 ++++++++++++++++++++++++++++++++
api/src/seedData.ts | 35 ++++++++
5 files changed, 314 insertions(+)
create mode 100644 api/src/models/wishlist.ts
create mode 100644 api/src/routes/wishlist.test.ts
create mode 100644 api/src/routes/wishlist.ts
diff --git a/api/src/index.ts b/api/src/index.ts
index b991a16..0fa96c2 100644
--- a/api/src/index.ts
+++ b/api/src/index.ts
@@ -10,6 +10,7 @@ import orderRoutes from './routes/order';
import branchRoutes from './routes/branch';
import headquartersRoutes from './routes/headquarters';
import supplierRoutes from './routes/supplier';
+import wishlistRoutes from './routes/wishlist';
const app = express();
const port = process.env.PORT || 3000;
@@ -74,6 +75,7 @@ app.use('/api/orders', orderRoutes);
app.use('/api/branches', branchRoutes);
app.use('/api/headquarters', headquartersRoutes);
app.use('/api/suppliers', supplierRoutes);
+app.use('/api/wishlist', wishlistRoutes);
app.get('/', (req, res) => {
res.send('Hello, world!');
diff --git a/api/src/models/wishlist.ts b/api/src/models/wishlist.ts
new file mode 100644
index 0000000..08406d5
--- /dev/null
+++ b/api/src/models/wishlist.ts
@@ -0,0 +1,32 @@
+/**
+ * @swagger
+ * components:
+ * schemas:
+ * WishlistItem:
+ * type: object
+ * required:
+ * - wishlistItemId
+ * - email
+ * - productId
+ * - addedAt
+ * properties:
+ * wishlistItemId:
+ * type: integer
+ * description: The unique identifier for the wishlist item
+ * email:
+ * type: string
+ * description: Email address of the user
+ * productId:
+ * type: integer
+ * description: The ID of the product in the wishlist
+ * addedAt:
+ * type: string
+ * format: date-time
+ * description: Timestamp when the item was added to the wishlist
+ */
+export interface WishlistItem {
+ wishlistItemId: number;
+ email: string;
+ productId: number;
+ addedAt: Date;
+}
diff --git a/api/src/routes/wishlist.test.ts b/api/src/routes/wishlist.test.ts
new file mode 100644
index 0000000..3cb1cad
--- /dev/null
+++ b/api/src/routes/wishlist.test.ts
@@ -0,0 +1,94 @@
+import { describe, it, expect, beforeEach } from 'vitest';
+import request from 'supertest';
+import express from 'express';
+import wishlistRouter, { resetWishlist } from './wishlist';
+import { wishlistItems as seedWishlistItems } from '../seedData';
+
+let app: express.Express;
+
+describe('Wishlist API', () => {
+ beforeEach(() => {
+ app = express();
+ app.use(express.json());
+ app.use('/wishlist', wishlistRouter);
+ resetWishlist();
+ });
+
+ it('should get all wishlist items for a user', async () => {
+ const response = await request(app).get('/wishlist/user@example.com');
+ expect(response.status).toBe(200);
+ expect(response.body.length).toBe(3);
+ expect(response.body.every((item: any) => item.email === 'user@example.com')).toBe(true);
+ });
+
+ it('should get empty array for user with no wishlist items', async () => {
+ const response = await request(app).get('/wishlist/newuser@example.com');
+ expect(response.status).toBe(200);
+ expect(response.body.length).toBe(0);
+ });
+
+ it('should add a new product to wishlist', async () => {
+ const newItem = {
+ email: 'newuser@example.com',
+ productId: 4
+ };
+ const response = await request(app).post('/wishlist').send(newItem);
+ expect(response.status).toBe(201);
+ expect(response.body.email).toBe(newItem.email);
+ expect(response.body.productId).toBe(newItem.productId);
+ expect(response.body.wishlistItemId).toBeDefined();
+ expect(response.body.addedAt).toBeDefined();
+ });
+
+ it('should prevent duplicate wishlist items', async () => {
+ const duplicateItem = {
+ email: 'user@example.com',
+ productId: 1
+ };
+ const response = await request(app).post('/wishlist').send(duplicateItem);
+ expect(response.status).toBe(400);
+ expect(response.text).toBe('Product already in wishlist');
+ });
+
+ it('should return 404 when adding non-existent product', async () => {
+ const invalidItem = {
+ email: 'user@example.com',
+ productId: 9999
+ };
+ const response = await request(app).post('/wishlist').send(invalidItem);
+ expect(response.status).toBe(404);
+ expect(response.text).toBe('Product not found');
+ });
+
+ it('should remove a product from wishlist', async () => {
+ const response = await request(app).delete('/wishlist/user@example.com/1');
+ expect(response.status).toBe(204);
+
+ // Verify it's removed
+ const getResponse = await request(app).get('/wishlist/user@example.com');
+ expect(getResponse.body.some((item: any) => item.productId === 1)).toBe(false);
+ });
+
+ it('should return 404 when removing non-existent wishlist item', async () => {
+ const response = await request(app).delete('/wishlist/user@example.com/9999');
+ expect(response.status).toBe(404);
+ expect(response.text).toBe('Wishlist item not found');
+ });
+
+ it('should filter wishlist items by email correctly', async () => {
+ const user1Response = await request(app).get('/wishlist/user@example.com');
+ const user2Response = await request(app).get('/wishlist/admin@github.com');
+
+ expect(user1Response.body.length).toBe(3);
+ expect(user2Response.body.length).toBe(2);
+
+ expect(user1Response.body.every((item: any) => item.email === 'user@example.com')).toBe(true);
+ expect(user2Response.body.every((item: any) => item.email === 'admin@github.com')).toBe(true);
+ });
+
+ it('should reset wishlist to seed data', () => {
+ resetWishlist();
+ // This is implicitly tested by the beforeEach, but we can verify manually
+ expect(true).toBe(true);
+ });
+});
diff --git a/api/src/routes/wishlist.ts b/api/src/routes/wishlist.ts
new file mode 100644
index 0000000..da8b00d
--- /dev/null
+++ b/api/src/routes/wishlist.ts
@@ -0,0 +1,151 @@
+/**
+ * @swagger
+ * tags:
+ * name: Wishlist
+ * description: API endpoints for managing user wishlists
+ */
+
+/**
+ * @swagger
+ * /api/wishlist/{email}:
+ * get:
+ * summary: Get all wishlist items for a user
+ * tags: [Wishlist]
+ * parameters:
+ * - in: path
+ * name: email
+ * required: true
+ * schema:
+ * type: string
+ * description: User email address
+ * responses:
+ * 200:
+ * description: List of wishlist items for the user
+ * content:
+ * application/json:
+ * schema:
+ * type: array
+ * items:
+ * $ref: '#/components/schemas/WishlistItem'
+ *
+ * /api/wishlist:
+ * post:
+ * summary: Add a product to the wishlist
+ * tags: [Wishlist]
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * email:
+ * type: string
+ * description: User email address
+ * productId:
+ * type: integer
+ * description: Product ID to add to wishlist
+ * responses:
+ * 201:
+ * description: Product added to wishlist successfully
+ * content:
+ * application/json:
+ * schema:
+ * $ref: '#/components/schemas/WishlistItem'
+ * 400:
+ * description: Product already in wishlist or product not found
+ * 404:
+ * description: Product not found
+ *
+ * /api/wishlist/{email}/{productId}:
+ * delete:
+ * summary: Remove a product from the wishlist
+ * tags: [Wishlist]
+ * parameters:
+ * - in: path
+ * name: email
+ * required: true
+ * schema:
+ * type: string
+ * description: User email address
+ * - in: path
+ * name: productId
+ * required: true
+ * schema:
+ * type: integer
+ * description: Product ID
+ * responses:
+ * 204:
+ * description: Product removed from wishlist successfully
+ * 404:
+ * description: Wishlist item not found
+ */
+
+import express from 'express';
+import { WishlistItem } from '../models/wishlist';
+import { wishlistItems as seedWishlistItems } from '../seedData';
+import { products as seedProducts } from '../seedData';
+
+const router = express.Router();
+
+let wishlistItems: WishlistItem[] = [...seedWishlistItems];
+
+// Add reset function for testing
+export const resetWishlist = () => {
+ wishlistItems = [...seedWishlistItems];
+};
+
+// Get all wishlist items for a user
+router.get('/:email', (req, res) => {
+ const userWishlist = wishlistItems.filter(item => item.email === req.params.email);
+ res.json(userWishlist);
+});
+
+// Add a product to the wishlist
+router.post('/', (req, res) => {
+ const { email, productId } = req.body;
+
+ // Validate that product exists
+ const productExists = seedProducts.some(p => p.productId === productId);
+ if (!productExists) {
+ res.status(404).send('Product not found');
+ return;
+ }
+
+ // Check if already in wishlist (prevent duplicates)
+ const existingItem = wishlistItems.find(
+ item => item.email === email && item.productId === productId
+ );
+ if (existingItem) {
+ res.status(400).send('Product already in wishlist');
+ return;
+ }
+
+ // Create new wishlist item
+ const newWishlistItem: WishlistItem = {
+ wishlistItemId: Math.max(...wishlistItems.map(item => item.wishlistItemId), 0) + 1,
+ email,
+ productId,
+ addedAt: new Date()
+ };
+
+ wishlistItems.push(newWishlistItem);
+ res.status(201).json(newWishlistItem);
+});
+
+// Remove a product from the wishlist
+router.delete('/:email/:productId', (req, res) => {
+ const { email, productId } = req.params;
+ const index = wishlistItems.findIndex(
+ item => item.email === email && item.productId === parseInt(productId)
+ );
+
+ if (index !== -1) {
+ wishlistItems.splice(index, 1);
+ res.status(204).send();
+ } else {
+ res.status(404).send('Wishlist item not found');
+ }
+});
+
+export default router;
diff --git a/api/src/seedData.ts b/api/src/seedData.ts
index ba0bebe..0f42315 100644
--- a/api/src/seedData.ts
+++ b/api/src/seedData.ts
@@ -6,6 +6,7 @@ import { Order } from './models/order';
import { OrderDetail } from './models/orderDetail';
import { Delivery } from './models/delivery';
import { OrderDetailDelivery } from './models/orderDetailDelivery';
+import { WishlistItem } from './models/wishlist';
// Suppliers
export const suppliers: Supplier[] = [
@@ -291,4 +292,38 @@ export const orderDetailDeliveries: OrderDetailDelivery[] = [
quantity: 20,
notes: "Delivery"
}
+];
+
+// Wishlist Items
+export const wishlistItems: WishlistItem[] = [
+ {
+ wishlistItemId: 1,
+ email: "user@example.com",
+ productId: 1,
+ addedAt: new Date("2024-01-15T10:30:00Z")
+ },
+ {
+ wishlistItemId: 2,
+ email: "user@example.com",
+ productId: 3,
+ addedAt: new Date("2024-01-16T14:20:00Z")
+ },
+ {
+ wishlistItemId: 3,
+ email: "user@example.com",
+ productId: 7,
+ addedAt: new Date("2024-01-20T09:15:00Z")
+ },
+ {
+ wishlistItemId: 4,
+ email: "admin@github.com",
+ productId: 2,
+ addedAt: new Date("2024-01-18T11:45:00Z")
+ },
+ {
+ wishlistItemId: 5,
+ email: "admin@github.com",
+ productId: 5,
+ addedAt: new Date("2024-01-19T16:30:00Z")
+ }
];
\ No newline at end of file
From 7b5f50311c344b48e3c45eb0f8d4ae63bb6912eb Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 16 Feb 2026 04:52:07 +0000
Subject: [PATCH 3/4] feat: Add frontend wishlist functionality with UI
components
Co-authored-by: thomasiverson <12767513+thomasiverson@users.noreply.github.com>
---
frontend/src/App.tsx | 7 +-
frontend/src/api/config.ts | 3 +-
frontend/src/components/Navigation.tsx | 12 ++
frontend/src/components/Wishlist.tsx | 149 ++++++++++++++++++
.../components/entity/product/Products.tsx | 34 ++++
frontend/src/context/AuthContext.tsx | 6 +-
frontend/src/context/WishlistContext.tsx | 111 +++++++++++++
7 files changed, 319 insertions(+), 3 deletions(-)
create mode 100644 frontend/src/components/Wishlist.tsx
create mode 100644 frontend/src/context/WishlistContext.tsx
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index d0b02da..c4f0896 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -7,7 +7,9 @@ import Products from './components/entity/product/Products';
import Login from './components/Login';
import { AuthProvider } from './context/AuthContext';
import { ThemeProvider } from './context/ThemeContext';
+import { WishlistProvider } from './context/WishlistContext';
import AdminProducts from './components/admin/AdminProducts';
+import Wishlist from './components/Wishlist';
import { useTheme } from './context/ThemeContext';
// Wrapper component to apply theme classes
@@ -24,6 +26,7 @@ function ThemedApp() {