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
24 changes: 21 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
COMPANY_NAME="Vercel Inc."
SITE_NAME="Next.js Commerce"
# Note: Shopify is the brand here, but you can use any other ecommerce provider
COMPANY_NAME="Tarazoo"
SITE_NAME="Tarazoo Commerce"
SHOPIFY_REVALIDATION_SECRET=""
SHOPIFY_STOREFRONT_ACCESS_TOKEN=""
SHOPIFY_STORE_DOMAIN="[your-shopify-store-subdomain].myshopify.com"
SHOPIFY_STORE_DOMAIN=""

# Supabase Configuration
NEXT_PUBLIC_SUPABASE_URL="https://your-project.supabase.co"
NEXT_PUBLIC_SUPABASE_ANON_KEY="your-anon-key"
SUPABASE_SERVICE_ROLE_KEY="your-service-role-key"

# Cohere (Vision) API Key
COHERE_API_KEY=""
NEXT_PUBLIC_MERCHANT_ID_DEFAULT="a1b2c3d4-e5f6-7890-abcd-ef1234567890"

# MINLP Service Configuration
MINLP_BASE_URL="https://your-minlp-service.vercel.app"

# Feature Flags
DEMO_MODE="true"
ENABLE_PWA="true"
ENABLE_EXPLAIN="true"
176 changes: 176 additions & 0 deletions README_DEMO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# Tarazoo - Unified Commerce Platform Demo

## 🚀 Quick Start (36-Hour Hackathon Build)

### Overview
Tarazoo is a unified commerce platform combining:
- **Mobile Shopper App**: Barcode scanning, cart with 13% tax, checkout
- **Merchant Dashboard**: Live orders, sales metrics, MINLP optimization
- **Tech Stack**: Next.js, Supabase, FastAPI, Vercel

## 📱 Demo Features

### Shopper Experience (Mobile-First)
- **Camera Scanner**: Tap camera icon → scan product barcodes → auto-add to cart
- **Smart Cart**: Shows subtotal + 13% tax calculation
- **Quick Checkout**: Creates orders in Supabase with status tracking
- **PWA Support**: Install as mobile app for native-like experience

### Merchant Dashboard
- **Live Orders Feed**: Real-time order updates via Supabase
- **Sales Metrics**: Total revenue, average order value, today's orders
- **MINLP Optimization**: One-click supply chain optimization
- **Explanation Engine**: 3 bullets + TL;DR for optimization results

## 🛠 Setup Instructions

### 1. Supabase Setup
```bash
# Create a new Supabase project at https://supabase.com
# Run migrations in Supabase SQL editor:
# - /supabase/migrations/001_initial_schema.sql
# - /supabase/migrations/002_seed_data.sql
```

### 2. Environment Variables
```bash
cp .env.example .env.local
# Add your Supabase credentials:
# NEXT_PUBLIC_SUPABASE_URL=your-project-url
# NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
```

### 3. Install & Run
```bash
# Install dependencies
pnpm install

# Run the Next.js app
pnpm dev

# In another terminal, run the MINLP service
cd services/minlp
pip install -r requirements.txt
python main.py
```

## 📋 Demo Script (3-4 minutes)

### Act 1: Shopper Journey
1. Open app on mobile (http://localhost:3000)
2. Tap camera icon in navbar
3. Scan barcode: `1234567890123` (Coffee)
4. Product auto-adds to cart with notification
5. Scan another: `2345678901234` (Chocolate)
6. Open cart → see subtotal + 13% tax
7. Checkout → order confirmed

### Act 2: Merchant Dashboard
1. Navigate to /dashboard
2. See new order appear instantly (realtime)
3. Review sales metrics cards
4. Click "Run MINLP Optimization"
5. View optimization results (cost, suppliers, lead time)
6. Click "Get Explanation" → see 3 bullets + TL;DR

### Act 3: Wrap Up
- "Unified UI for shoppers and merchants"
- "Realtime Supabase for instant updates"
- "FastAPI MINLP for supply chain optimization"
- "Deployed on Vercel with edge functions"

## 🧪 Test Barcodes

| Product | Barcode | Price |
|---------|---------|-------|
| Organic Coffee Beans | 1234567890123 | $24.99 |
| Premium Dark Chocolate | 2345678901234 | $8.99 |
| Artisan Sourdough Bread | 3456789012345 | $5.99 |
| Organic Almond Butter | 4567890123456 | $12.99 |
| Free Range Eggs | 5678901234567 | $7.99 |
| Greek Yogurt | 6789012345678 | $6.99 |
| Raw Honey | 7890123456789 | $14.99 |
| Extra Virgin Olive Oil | 8901234567890 | $18.99 |

## 🚢 Deployment

### Vercel Deployment
```bash
# Deploy Next.js app
vercel

# Deploy MINLP as Vercel Function
# Create /api/minlp endpoint that proxies to FastAPI
```

### Environment Variables (Production)
- Add all env vars from .env.local to Vercel dashboard
- Enable Supabase Realtime in project settings
- Set MINLP_BASE_URL to deployed function URL

## 📊 SLOs & Performance

- **Scan→Cart**: ≤ 2s P50
- **Checkout→Order**: ≤ 5s P95
- **MINLP Run**: ≤ 10s
- **Explain**: ≤ 3s

## 🎯 Key Differentiators

1. **One Codebase**: Shopper + Merchant in same Next.js app
2. **Mobile-First**: Camera scanner with torch + fallback
3. **Realtime**: Orders appear instantly via Supabase
4. **Smart MINLP**: Optimizes supply chain with explanations
5. **PWA Ready**: Installable with offline support

## 🐛 Troubleshooting

### Camera Not Working?
- Check browser permissions for camera access
- Use manual barcode input as fallback
- Ensure HTTPS in production (required for getUserMedia)

### Orders Not Appearing?
- Verify Supabase Realtime is enabled
- Check RLS policies allow INSERT/SELECT
- Confirm merchant_id matches in dashboard

### MINLP Service Issues?
- Ensure FastAPI is running on port 8000
- Check CORS is enabled in main.py
- Verify MINLP_BASE_URL in .env.local

## 📝 Architecture Notes

```
/apps/web (Next.js)
├── app/
│ ├── cart/ (with tax calculation)
│ ├── checkout/ (Supabase order creation)
│ └── dashboard/ (merchant portal)
├── components/
│ ├── camera-scanner.tsx (@zxing/library)
│ └── cart/supabase-cart-context.tsx
└── lib/
└── supabase.ts (client + operations)

/services/minlp (FastAPI)
└── main.py (optimization + explanation)

/supabase/migrations/
├── 001_initial_schema.sql
└── 002_seed_data.sql
```

## 🏆 Hackathon Impact

**Why This Wins:**
- **Visual Demo**: Barcode scanning is impressive live
- **Business Value**: Real supply chain optimization
- **Technical Depth**: Realtime, PWA, MINLP integration
- **Polish**: Unified UI, instant feedback, explanations
- **Scalable**: Ready for production with Vercel + Supabase

---

Built with ❤️ for the 36-hour hackathon challenge
32 changes: 32 additions & 0 deletions app/api/minlp/explain/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { NextRequest, NextResponse } from 'next/server';

const MINLP_BASE_URL = process.env.MINLP_BASE_URL || 'http://localhost:8000';

export async function POST(req: NextRequest) {

try {
const body = await req.json();
const { solution } = body;

// Call MINLP service for explanation
const explainResponse = await fetch(`${MINLP_BASE_URL}/explain`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ solution })
});

if (!explainResponse.ok) {
throw new Error('MINLP explain service failed');
}

const explanation = await explainResponse.json();

return NextResponse.json(explanation);
} catch (error) {
console.error('MINLP explain error:', error);
return NextResponse.json(
{ error: 'Failed to generate explanation' },
{ status: 500 }
);
}
}
55 changes: 55 additions & 0 deletions app/api/minlp/solve/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { createMinlpRun } from 'lib/supabase';
import { NextRequest, NextResponse } from 'next/server';

const MINLP_BASE_URL = process.env.MINLP_BASE_URL || 'http://localhost:8000';

export async function POST(request: NextRequest) {

try {
const body = await request.json();
const { merchantId, orders } = body;

// Prepare items from orders for MINLP
const items = orders.flatMap((order: any) =>
order.items || [{
sku: 'SKU001',
qty: 2,
price_cents: 2499
}]
);

// Call MINLP service
const minlpResponse = await fetch(`${MINLP_BASE_URL}/solve`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
merchant_id: merchantId,
order_id: orders[0]?.order_id,
items
})
});

if (!minlpResponse.ok) {
throw new Error('MINLP service failed');
}

const solution = await minlpResponse.json();

// Save to Supabase
const minlpRun = await createMinlpRun(
merchantId,
orders[0]?.order_id || null,
{ items },
solution,
undefined
);

return NextResponse.json({ success: true, solution, runId: minlpRun?.run_id });
} catch (error) {
console.error('MINLP solve error:', error);
return NextResponse.json(
{ error: 'Failed to run optimization' },
{ status: 500 }
);
}
}
24 changes: 24 additions & 0 deletions app/api/products/[handle]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { getProduct } from 'lib/shopify';
import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const handle = searchParams.get('handle');

if (!handle) {
return NextResponse.json({ error: 'Handle parameter is required' }, { status: 400 });
}

try {
const product = await getProduct(handle, { fresh: true });

if (!product) {
return NextResponse.json({ error: 'Product not found' }, { status: 404 });
}

return NextResponse.json(product);
} catch (error) {
console.error('Error fetching product:', error);
return NextResponse.json({ error: 'Failed to fetch product' }, { status: 500 });
}
}
32 changes: 32 additions & 0 deletions app/api/sync-products/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { NextRequest, NextResponse } from 'next/server';
import { syncShopifyToSupabase } from 'lib/shopify-supabase-sync';

export async function POST(request: NextRequest) {
try {
const result = await syncShopifyToSupabase();

if (result.success) {
return NextResponse.json({
success: true,
message: `Synced ${result.count} products from Shopify`,
products: result.products
});
} else {
return NextResponse.json(
{ success: false, error: result.error },
{ status: 500 }
);
}
} catch (error) {
console.error('Sync API error:', error);
return NextResponse.json(
{ success: false, error: 'Sync failed' },
{ status: 500 }
);
}
}

export async function GET(request: NextRequest) {
// Trigger sync on GET for easy testing
return POST(request);
}
Loading