From 6703fe68d9270d17ef95693891109806648c800b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 6 May 2026 14:46:43 +0000
Subject: [PATCH 1/3] Initial plan
From d995680c2b27fde11a9a79530bcd30e820cadfa8 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 6 May 2026 14:50:34 +0000
Subject: [PATCH 2/3] Add full-stack wishlist feature with backend API,
WishlistContext, heart buttons, and Wishlist page
Agent-Logs-Url: https://github.com/thomasiverson/GitHubCopilot_Customized/sessions/29968f7b-c4e2-4b44-bf7b-a0351299f5eb
Co-authored-by: thomasiverson <12767513+thomasiverson@users.noreply.github.com>
---
api/src/index.ts | 2 +
api/src/models/wishlist.ts | 27 ++++
api/src/routes/wishlist.test.ts | 89 +++++++++++
api/src/routes/wishlist.ts | 141 ++++++++++++++++++
frontend/src/App.tsx | 7 +-
frontend/src/api/config.ts | 3 +-
frontend/src/components/Navigation.tsx | 12 ++
.../components/entity/product/Products.tsx | 21 +++
frontend/src/components/wishlist/Wishlist.tsx | 112 ++++++++++++++
frontend/src/context/AuthContext.tsx | 6 +-
frontend/src/context/WishlistContext.tsx | 80 ++++++++++
11 files changed, 497 insertions(+), 3 deletions(-)
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
create mode 100644 frontend/src/components/wishlist/Wishlist.tsx
create mode 100644 frontend/src/context/WishlistContext.tsx
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..6d1c61f
--- /dev/null
+++ b/api/src/models/wishlist.ts
@@ -0,0 +1,27 @@
+/**
+ * @swagger
+ * components:
+ * schemas:
+ * WishlistItem:
+ * type: object
+ * required:
+ * - userId
+ * - productId
+ * - addedAt
+ * properties:
+ * userId:
+ * type: string
+ * description: The user's email address
+ * productId:
+ * type: integer
+ * description: The unique identifier of the product
+ * addedAt:
+ * type: string
+ * format: date-time
+ * description: ISO date string when the item was added
+ */
+export interface WishlistItem {
+ userId: string;
+ productId: number;
+ addedAt: string;
+}
diff --git a/api/src/routes/wishlist.test.ts b/api/src/routes/wishlist.test.ts
new file mode 100644
index 0000000..71a4a62
--- /dev/null
+++ b/api/src/routes/wishlist.test.ts
@@ -0,0 +1,89 @@
+import { describe, it, expect, beforeEach } from 'vitest';
+import request from 'supertest';
+import express from 'express';
+import wishlistRouter, { resetWishlist } from './wishlist';
+
+let app: express.Express;
+
+describe('Wishlist API', () => {
+ beforeEach(() => {
+ app = express();
+ app.use(express.json());
+ app.use('/api/wishlist', wishlistRouter);
+ resetWishlist();
+ });
+
+ it('should return 400 when userId is missing on GET', async () => {
+ const response = await request(app).get('/api/wishlist');
+ expect(response.status).toBe(400);
+ });
+
+ it('should return empty array for a user with no wishlist items', async () => {
+ const response = await request(app).get('/api/wishlist?userId=test@example.com');
+ expect(response.status).toBe(200);
+ expect(response.body).toEqual([]);
+ });
+
+ it('should add a product to the wishlist', async () => {
+ const response = await request(app)
+ .post('/api/wishlist')
+ .send({ userId: 'test@example.com', productId: 1 });
+ expect(response.status).toBe(201);
+ expect(response.body.userId).toBe('test@example.com');
+ expect(response.body.productId).toBe(1);
+ expect(response.body.addedAt).toBeDefined();
+ });
+
+ it('should return 409 when adding a duplicate wishlist item', async () => {
+ await request(app)
+ .post('/api/wishlist')
+ .send({ userId: 'test@example.com', productId: 1 });
+ const response = await request(app)
+ .post('/api/wishlist')
+ .send({ userId: 'test@example.com', productId: 1 });
+ expect(response.status).toBe(409);
+ });
+
+ it('should retrieve wishlist items for a user', async () => {
+ await request(app)
+ .post('/api/wishlist')
+ .send({ userId: 'test@example.com', productId: 1 });
+ await request(app)
+ .post('/api/wishlist')
+ .send({ userId: 'test@example.com', productId: 2 });
+
+ const response = await request(app).get('/api/wishlist?userId=test@example.com');
+ expect(response.status).toBe(200);
+ expect(response.body.length).toBe(2);
+ });
+
+ it('should only return items for the specified user', async () => {
+ await request(app)
+ .post('/api/wishlist')
+ .send({ userId: 'user1@example.com', productId: 1 });
+ await request(app)
+ .post('/api/wishlist')
+ .send({ userId: 'user2@example.com', productId: 2 });
+
+ const response = await request(app).get('/api/wishlist?userId=user1@example.com');
+ expect(response.status).toBe(200);
+ expect(response.body.length).toBe(1);
+ expect(response.body[0].userId).toBe('user1@example.com');
+ });
+
+ it('should delete a wishlist item', async () => {
+ await request(app)
+ .post('/api/wishlist')
+ .send({ userId: 'test@example.com', productId: 1 });
+
+ const response = await request(app)
+ .delete('/api/wishlist/1?userId=test@example.com');
+ expect(response.status).toBe(204);
+ });
+
+ it('should return 404 when deleting a non-existing wishlist item', async () => {
+ const response = await request(app)
+ .delete('/api/wishlist/999?userId=test@example.com');
+ expect(response.status).toBe(404);
+ });
+});
diff --git a/api/src/routes/wishlist.ts b/api/src/routes/wishlist.ts
new file mode 100644
index 0000000..53859f6
--- /dev/null
+++ b/api/src/routes/wishlist.ts
@@ -0,0 +1,141 @@
+/**
+ * @swagger
+ * tags:
+ * name: Wishlist
+ * description: API endpoints for managing user wishlists
+ */
+
+/**
+ * @swagger
+ * /api/wishlist:
+ * get:
+ * summary: Get wishlist items for a user
+ * tags: [Wishlist]
+ * parameters:
+ * - in: query
+ * name: userId
+ * required: true
+ * schema:
+ * type: string
+ * description: The user's email address
+ * responses:
+ * 200:
+ * description: List of wishlist items for the user
+ * content:
+ * application/json:
+ * schema:
+ * type: array
+ * items:
+ * $ref: '#/components/schemas/WishlistItem'
+ * 400:
+ * description: Missing userId query parameter
+ * post:
+ * summary: Add a product to the wishlist
+ * tags: [Wishlist]
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * required:
+ * - userId
+ * - productId
+ * properties:
+ * userId:
+ * type: string
+ * description: The user's email address
+ * productId:
+ * type: integer
+ * description: The product ID to add
+ * responses:
+ * 201:
+ * description: Wishlist item added successfully
+ * content:
+ * application/json:
+ * schema:
+ * $ref: '#/components/schemas/WishlistItem'
+ * 409:
+ * description: Item already exists in wishlist
+ *
+ * /api/wishlist/{productId}:
+ * delete:
+ * summary: Remove a product from the wishlist
+ * tags: [Wishlist]
+ * parameters:
+ * - in: path
+ * name: productId
+ * required: true
+ * schema:
+ * type: integer
+ * description: The product ID to remove
+ * - in: query
+ * name: userId
+ * required: true
+ * schema:
+ * type: string
+ * description: The user's email address
+ * responses:
+ * 204:
+ * description: Wishlist item removed successfully
+ * 404:
+ * description: Wishlist item not found
+ */
+
+import express from 'express';
+import { WishlistItem } from '../models/wishlist';
+
+const router = express.Router();
+
+let wishlistItems: WishlistItem[] = [];
+
+export const resetWishlist = () => {
+ wishlistItems = [];
+};
+
+// Get wishlist items for a user
+router.get('/', (req, res) => {
+ const { userId } = req.query;
+ if (!userId) {
+ res.status(400).send('Missing userId query parameter');
+ } else {
+ const items = wishlistItems.filter(item => item.userId === userId);
+ res.json(items);
+ }
+});
+
+// Add a product to the wishlist
+router.post('/', (req, res) => {
+ const { userId, productId } = req.body;
+ const existing = wishlistItems.find(
+ item => item.userId === userId && item.productId === productId
+ );
+ if (existing) {
+ res.status(409).send('Item already exists in wishlist');
+ } else {
+ const newItem: WishlistItem = {
+ userId,
+ productId,
+ addedAt: new Date().toISOString(),
+ };
+ wishlistItems.push(newItem);
+ res.status(201).json(newItem);
+ }
+});
+
+// Remove a product from the wishlist
+router.delete('/:productId', (req, res) => {
+ const { userId } = req.query;
+ const productId = parseInt(req.params.productId);
+ const index = wishlistItems.findIndex(
+ item => item.userId === userId && item.productId === 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/frontend/src/App.tsx b/frontend/src/App.tsx
index d0b02da..99a2a14 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -9,6 +9,8 @@ import { AuthProvider } from './context/AuthContext';
import { ThemeProvider } from './context/ThemeContext';
import AdminProducts from './components/admin/AdminProducts';
import { useTheme } from './context/ThemeContext';
+import { WishlistProvider } from './context/WishlistContext';
+import Wishlist from './components/wishlist/Wishlist';
// Wrapper component to apply theme classes
function ThemedApp() {
@@ -24,6 +26,7 @@ function ThemedApp() {