Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions AI_LOG.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
# AI Usage Log

Please be honest and detailed about your AI usage. Using AI is perfectly fine and expected, but we want to understand your workflow!
# AI Usage Log

## Tools Used
*(e.g., GitHub Copilot, ChatGPT, Claude, Cursor, etc.)*

-
-
- **Antigravity (Google DeepMind)** — AI coding assistant integrated into VS Code (pair programming mode)

## How did you use AI for this assessment?

**Task 1: Pricing Logic Bug**
- *Did you use AI to find the bug? If so, what prompt did you use?*
- *Did you use AI to write the fix?*
- Used AI to read and trace through `lib/pricing/calculator.ts` and identify the exact faulty condition on lines 38–40.
- AI explained the bug: when a Pro user already qualifies for free shipping via the tier threshold, the code still adds `STANDARD_SHIPPING_RATE` to `discountCents` if a stackable coupon is also present — producing an inflated discount.
- AI proposed the fix (tracking `proThresholdFreeShipping` as a boolean and gating the discount credit on it), which I reviewed and approved.
- AI also wrote the unit tests in `tests/pricing.test.ts` covering all 4 key combinations. The first run revealed a wrong `totalCents` assertion in 2 tests — AI corrected the math and re-ran successfully (6/6 pass).

**Task 2: Dashboard Performance**
- *How did you use AI here? Did you use it to identify the N+1 issue, or just to write the optimized query?*
- AI identified the N+1 pattern in `lib/orders/queries.ts` immediately from reading the code: one `OrderItem.findMany` per order inside a `Promise.all` loop.
- AI wrote the replacement query using Prisma's nested `include`, which eliminates the per-order round-trips.
- I reviewed the diff to confirm nothing else was changed.

**Task 3: Duplicate Order Feature**
- *Did you use AI to write the API route, the client component, or both?*
- *Did AI make any mistakes you had to fix manually?*
- AI designed the API contract: `POST /api/orders/[id]/duplicate` returns `{ items: { product, quantity }[], skipped: { ... }[] }` — including quantities from the original order (not just product IDs) and a separate skipped list with reasons.
- AI created `app/api/orders/[id]/duplicate/route.ts` with ownership check directly in the Prisma `where` clause (no extra round-trip), stock validation requiring the full original quantity, and the `skipped` response payload.
- AI created `components/dashboard/DuplicateOrderButton.tsx` as a `'use client'` component with loading/success/error states and no auto-redirect (user decides whether to go to cart).
- AI modified `OrderCard.tsx` to import and render the button in the existing footer placeholder.
- TypeScript (`tsc --noEmit`) passed with zero errors after all changes.

## General Comments
*(Any other thoughts on how AI helped or hindered you during this assessment?)*

The AI was used in a genuine pair-programming style — it read the code, explained its findings, proposed changes, and I reviewed and approved each step before it proceeded. The AI caught the test assertion error on its own (the `totalCents` formula) and self-corrected after seeing the failing test output. No fixes were needed manually beyond approving the proposed corrections.

212 changes: 182 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,198 @@
# OrbitStack

Welcome to OrbitStack! This is a modern, Next.js-based e-commerce platform built with Tailwind CSS, Prisma, and Zustand.
OrbitStack is a modern Next.js e-commerce platform built with **Next.js, TypeScript, Tailwind CSS, Prisma, and Zustand**.

This repository contains the completed implementation for the **Mufifa Initial Qualification — Repository Debugging Challenge**.

## Implemented Tasks

### 1. Pricing Logic Bug

Fixed an issue where a Pro user with a free-shipping discount code could have the standard shipping rate incorrectly added to `discountCents` even when the Pro free-shipping threshold had already made shipping free.

#### Fix
- Detect whether the Pro free-shipping threshold already applies.
- Only credit shipping savings to the discount when the discount code is responsible for making shipping free.
- Added unit tests covering the pricing combinations.

#### Tests

```bash
npx vitest run tests/pricing.test.ts
```

Result:

```text
6/6 tests passing
```

---

### 2. Dashboard Performance

The dashboard previously used an **N+1 query pattern** when loading order history.

The application first fetched all orders and then executed a separate `OrderItem.findMany()` query for every order.

#### Fix

Replaced the per-order database queries with Prisma nested relation loading:

```text
Orders
└── Order Items
└── Products
```

This eliminates the application-level N+1 query pattern and significantly reduces database round-trips for users with large order histories.

---

### 3. Duplicate Order

Added a **Duplicate Order** feature to the dashboard.

Users can duplicate a previous order and add its available products directly to their cart.

#### Features

* `POST /api/orders/[id]/duplicate` API endpoint
* Authentication validation
* Order ownership validation
* Product availability validation
* Stock validation against the original requested quantity
* Handles unavailable products without adding them to the cart
* Preserves the original order quantities
* Zustand cart integration
* Loading, success, and error states
* Duplicate Order button added to dashboard order cards

### Flow

```text
Past Order
Duplicate Order
API validation
Ownership + Product + Stock checks
Available items returned
Zustand Cart Store
Items added to cart
```

---

## Project Structure

```text
app/
├── api/
│ └── orders/
│ └── [id]/
│ └── duplicate/
│ └── route.ts
└── dashboard/

components/
└── dashboard/
├── DuplicateOrderButton.tsx
└── OrderCard.tsx

lib/
├── orders/
│ └── queries.ts
└── pricing/
└── calculator.ts

store/
└── cart.ts

tests/
└── pricing.test.ts

AI_LOG.md
```

## Getting Started

1. **Install Dependencies**
```bash
npm install
```
### 1. Install dependencies

```bash
npm install
```

### 2. Configure environment variables

Copy the example environment file:

2. **Set up Environment Variables**
The project connects to a shared testing database. Simply copy the example environment file:
```bash
cp .env.example .env
```
```bash
cp .env.example .env
```

3. **Run the Development Server**
```bash
npm run dev
```
Configure the required database/environment values as needed.

## The Challenge
### 3. Run the development server

There are three tasks to complete. Please review the codebase and implement the fixes and features described below.
```bash
npm run dev
```

### Task 1: Pricing Logic Bug
A user reported an issue where applying a free shipping discount code to a Pro account results in a negative discount calculation (instead of ) for the shipping cost.
- You need to locate the pricing computation logic and fix the bug so that shipping is calculated correctly when multiple shipping discounts/free tiers stack.
Open:

### Task 2: Dashboard Performance
The /dashboard page is loading extremely slowly for users who have a large order history.
- Identify the performance bottleneck when loading the order history.
- Optimize the data fetching so the dashboard loads quickly regardless of how many orders the user has.
```text
http://localhost:3000
```

### Task 3: "Duplicate Order" Feature
Add a new "Duplicate Order" button to the past orders displayed on the dashboard.
- Clicking the button should add the same items from the past order directly into the user's cart (provided they are still in stock).
- You will need to build the API endpoint and the client-side logic to update the cart store.
## Validation

Good luck!
The implementation was validated with:

### Pricing tests

```bash
npx vitest run tests/pricing.test.ts
```

```text
6/6 tests passing
```

### TypeScript

```bash
npx tsc --noEmit
```

```text
0 TypeScript errors
```

## Files Changed

| File | Change |
| ----------------------------------------------- | ----------------------------------------------- |
| `lib/pricing/calculator.ts` | Fixed Pro/free-shipping pricing calculation |
| `lib/orders/queries.ts` | Removed N+1 order-history query pattern |
| `app/api/orders/[id]/duplicate/route.ts` | Added Duplicate Order API |
| `components/dashboard/DuplicateOrderButton.tsx` | Added client-side duplicate order functionality |
| `components/dashboard/OrderCard.tsx` | Added Duplicate Order button |
| `tests/pricing.test.ts` | Added pricing regression tests |
| `AI_LOG.md` | Documented AI-assisted development |

## AI Usage

AI assistance was used during development for debugging, code analysis, implementation planning, and reviewing the changes.

Detailed AI usage is documented in [`AI_LOG.md`](./AI_LOG.md).

## Submission

When you are finished, please submit a pull request with your changes.
**IMPORTANT:** You must fill out the AI_LOG.md file included in this repository. Please describe which AI tools you used and how you used them to complete the tasks.
This repository contains the completed implementation for the OrbitStack Repository Debugging Challenge.

A Pull Request has been submitted against the original challenge repository.
106 changes: 106 additions & 0 deletions app/api/orders/[id]/duplicate/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/db/client';
import { getSession } from '@/lib/auth/session';
import type { ApiResponse, Product } from '@/types';

type DuplicateItem = {
product: Product;
quantity: number;
};

type DuplicateOrderResponse = {
items: DuplicateItem[];
skipped: { productId: string; name: string; reason: string }[];
};

export async function POST(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await getSession();
if (!session) {
return NextResponse.json<ApiResponse<null>>(
{ data: null, error: 'Unauthorized' },
{ status: 401 }
);
}

const { id: orderId } = await params;

// Ownership check happens at the DB query level — no second round-trip needed.
const order = await prisma.order.findUnique({
where: {
id: orderId,
userId: session.id, // only the owner can duplicate
},
include: {
items: {
include: { product: true },
},
},
});

if (!order) {
return NextResponse.json<ApiResponse<null>>(
{ data: null, error: 'Order not found' },
{ status: 404 }
);
}

const available: DuplicateItem[] = [];
const skipped: DuplicateOrderResponse['skipped'] = [];

for (const item of order.items) {
const { product } = item;

if (!product.active) {
skipped.push({
productId: product.id,
name: product.name,
reason: 'Product is no longer available',
});
continue;
}

// Require the full original quantity to be in stock.
// e.g. original: 3, current stock: 2 → skip entirely (don't add 2).
if (product.stock < item.quantity) {
skipped.push({
productId: product.id,
name: product.name,
reason:
product.stock === 0
? 'Out of stock'
: `Only ${product.stock} in stock (original order had ${item.quantity})`,
});
continue;
}

available.push({
product: {
id: product.id,
name: product.name,
description: product.description,
priceCents: product.priceCents,
stock: product.stock,
category: product.category,
imageUrl: product.imageUrl ?? null,
active: product.active,
},
quantity: item.quantity,
});
}

return NextResponse.json<ApiResponse<DuplicateOrderResponse>>({
data: { items: available, skipped },
error: null,
});
} catch (err) {
console.error('[POST /api/orders/[id]/duplicate]', err);
return NextResponse.json<ApiResponse<null>>(
{ data: null, error: 'Failed to duplicate order' },
{ status: 500 }
);
}
}
Loading