# Project export: Aava

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: OpenAI Build Week
- Tagline: Snap a photo of your product on a table, AI turns it into a pro photoshoot, reels, and captions, posts everywhere, and updates your online store. A marketing team in every shopkeeper's pocket!
- Devpost: https://devpost.com/software/maanvi
- GitHub: https://github.com/princelevin/AAVA
- Video: https://www.youtube.com/embed/6c4_kkUMub0?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Codex Sites (56 commits)

## Devpost submission (written by the team)

### Inspiration

Growing up around my family's saree business, I saw how much work goes into selling just one product online. Before a customer ever sees a listing, someone has to photograph the product, edit images, write descriptions, research competitors, decide on pricing, create social media content, and publish across multiple platforms. For a small business owner, that can easily take hours for every single product. We wanted to change that. Aava is our attempt to give small businesses the same merchandising and marketing capabilities that large brands have—using AI. While we built it around fashion, the same idea works for jewelry, handmade products, furniture, home décor, food products, or any business trying to build an online presence.

### What it does

Aava is an AI merchandising assistant that helps businesses create complete product listings from just a few photos and a natural conversation. Users upload images, describe their product using voice or text, and Aava understands the product using multimodal reasoning. It asks follow-up questions whenever something is unclear instead of making assumptions. It then generates product attributes, descriptions, pricing suggestions, SEO tags, hashtags, and marketing content. After publishing, it continues helping with merchandising insights and recommendations to improve future listings. Tasks that typically take hours can now be completed in minutes, allowing business owners to spend more time creating products and serving customers.

### How we built it

OpenAI's models were at the center of everything we built. We used GPT-5.5/5.6 because the project required much more than image recognition or text generation. The model combines vision, reasoning, and conversation to understand products, ask intelligent follow-up questions, generate structured product information, and create natural marketing content—all within a single workflow. ChatGPT also became an important part of our development process. It helped us rapidly prototype ideas, refine prompts, debug issues, improve UX, and iterate much faster than we could have otherwise. Features that would normally take days to explore were often built and refined within hours. For demonstrations and creative assets, we also used OpenAI tools including Sora, allowing us to quickly visualize ideas and improve the overall user experience.

### Challenges we ran into

One of our biggest challenges was building an end-to-end AI product within the limited time and budget of a hackathon. We invested in OpenAI API credits and used ChatGPT Pro so we could build a working product instead of relying on mockups. Every API call mattered, so we spent a lot of time optimizing prompts and workflows to get the most value from each request. Another challenge was making the AI trustworthy. Fashion products often contain details that aren't obvious from images alone. Rather than hallucinating, we designed Aava to ask follow-up questions whenever confidence was low. If we continue developing Aava, we'd love to expand it with larger datasets, richer market intelligence, and even stronger multimodal capabilities.

### Accomplishments we're proud of

We're proud that Aava feels like a real business assistant rather than just another AI chatbot. Instead of generating a caption, it understands products, reasons through missing information, creates complete listings, and continues helping after products are published. We're also proud that although our demo focuses on clothing, the same workflow can support almost any product-based business.

### What we learned

The biggest lesson from this hackathon was that great AI products aren't built by simply calling a model—they're built by designing thoughtful workflows around the model. We learned that GPT-5.5/5.6 is most powerful when it can combine vision, reasoning, and conversation into a single experience. We also learned how much faster we could build by using ChatGPT as a development partner throughout the entire process.

### What's next

This is just the beginning. We want Aava to become a complete AI commerce assistant that can publish directly to platforms like Shopify, Etsy, Instagram, and WhatsApp Business, generate professional product photography, understand market trends in real time, and provide analytics that help businesses grow. There are hundreds of millions of small and medium-sized businesses worldwide, many of which don't have access to dedicated marketing or merchandising teams. Our goal is to give them those capabilities through AI, helping them build a stronger online presence while saving time, reducing costs, and making it easier to compete in the digital marketplace.

## README (from the GitHub repository)

# Aava — Autonomous Commerce Operator

Aava helps independent Indian fashion sellers turn phone photos and a spoken or typed product note into a polished, publishable listing. It combines a mobile-first merchant workspace, AI product understanding, pricing guidance, studio-style imagery, review controls, a public storefront, growth analytics, and short-form video preparation.

The interface supports English and Telugu capture, light and dark themes, and tenant-scoped merchant accounts through Supabase Auth.

## What works

- Email/password and Google authentication with one isolated boutique per Supabase user
- Mobile capture for up to six JPG, PNG, or WebP product photos
- Secure direct-to-Supabase uploads, avoiding the hosting platform's request-body limit
- English or Telugu voice transcription; the editable transcript is the source used for analysis
- Vision analysis for fabric, colours, garment type, work, occasions, style, audience, and listing facts
- Budget, mid-market, and premium pricing options with reasoning
- Automatic or merchant-selected visual direction: vintage, modern, elegant, minimal, festive, or editorial
- Three vertical studio images: catalog, lifestyle, and campaign
- Image fidelity checks against the merchant's originals before approval
- Listing title, short and long copy, highlights, Instagram captions, and hashtags
- Per-asset approve/reject review, SKU assignment, and publishing
- Public storefront with natural-language product search
- Product visits, enquiries, orders, revenue, and AI-assisted growth recommendations
- Approved-product 8-second 9:16 Reel generation, preview, MP4 download, caption, hashtags, and manual sharing
- Product deletion, responsive layouts, preserved image proportions, and light/dark themes

All AI features call the configured OpenAI API. Product, task, approval, analytics, and asset metadata are stored in Supabase; original and generated media are stored in Supabase Storage.

## Technology

- Next.js 16, React 19, TypeScript 5.9, and Tailwind CSS 4
- Supabase Auth, Postgres, Row Level Security, and Storage
- OpenAI Node SDK with `gpt-4.1`, `gpt-image-2`, `whisper-1`, and `sora-2`
- Vinext, Vite, Wrangler, and Cloudflare Workers-compatible hosting
- Zod for structured AI and request validation

## OpenAI models used

Aava uses OpenAI in two distinct ways:

1. **Development-time engineering:** the application was designed, implemented, debugged, and reviewed with OpenAI Codex powered by the **GPT-5 family**. This includes the agentic coding workflow used to build the Next.js application, API routes, Supabase integration, responsive interface, tests, and deployment setup. Codex is a development tool and is not called when a merchant uses the deployed application.
2. **Runtime product features:** the deployed backend makes real OpenAI API calls using the models below. These are the models that affect API usage and cost during an Aava product workflow.

| Model | How Aava uses it |
| --- | --- |
| `gpt-4.1` | Understands merchant photos and English/Telugu context; extracts product facts; generates pricing options, listing copy, natural-language storefront search results, fidelity checks, and growth recommendations |
| `gpt-image-2` | Recreates the merchant's garment as catalog, lifestyle, and campaign photography while using the original photos as product-identity references |
| `whisper-1` | Transcribes English or Telugu voice notes without translating Telugu into English; the merchant can edit the transcript before analysis |
| `sora-2` | Generates the approved product's 8-second, 9:16 Reel with a restrained premium visual direction and soft instrumental audio |

The runtime currently defaults to `gpt-4.1`, not GPT-5 or GPT-5.6. The GPT-5 family was used through Codex to build the project; it should not be described as a production API dependency unless `OPENAI_TEXT_MODEL` or `OPENAI_VISION_MODEL` is deliberately changed and the new model is tested against Aava's structured-output schemas.

## Product flow

1. The merchant signs in and captures or uploads product photos.
2. Aava transcribes English or Telugu speech, then lets the merchant correct the transcript.
3. Photos upload directly to the `product-images` bucket and the intake API creates a tenant-owned draft product.
4. A background task runs GPT vision analysis and saves the product truth record.
5. The merchant confirms cost, positioning, and an optional visual style.
6. Aava generates pricing, three studio images, fidelity results, and listing copy.
7. The merchant approves or rejects each review item; approval assigns a SKU and publishes the product.
8. Published products appear in the storefront and can be used to create a Sora Reel.

## Prerequisites

- Node.js 22.13 or newer
- pnpm
- An OpenAI API project with billing enabled and access to the configured models
- A Supabase project
- For Google login, Google OAuth credentials configured in Supabase Auth

## Local setup

```bash
git clone https://github.com/princelevin/AAVA.git
cd AAVA
pnpm install
cp .env.example .env.local
pnpm dev
```

Open `http://localhost:3000`.

### Environment variables

| Variable | Required | Visibility | Purpose |
| --- | --- | --- | --- |
| `OPENAI_API_KEY` | Yes | Server only | OpenAI API authentication |
| `OPENAI_TEXT_MODEL` | No | Server only | Text/structured model; defaults to `gpt-4.1` |
| `OPENAI_VISION_MODEL` | No | Server only | Product and fidelity vision model; defaults to `gpt-4.1` |
| `OPENAI_IMAGE_MODEL` | No | Server only | Image model; defaults to `gpt-image-2` |
| `OPENAI_VIDEO_MODEL` | No | Server only | Video model; defaults to `sora-2` |
| `OPENAI_TRANSCRIPTION_MODEL` | No | Server only | Audio model; defaults to `whisper-1` |
| `NEXT_PUBLIC_SUPABASE_URL` | Yes | Browser-safe | Supabase project URL |
| `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY` | Recommended | Browser-safe | Current Supabase publishable key |
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Alternative | Browser-safe | Legacy anon-key fallback |
| `SUPABASE_ANON_KEY` | Alternative | Server only | Server-side legacy anon-key fallback |
| `SUPABASE_SERVICE_ROLE_KEY` | Yes | Server only | Privileged database and storage operations |
| `SUPABASE_SECRET_KEY` | Alternative | Server only | Modern secret-key alternative to the service-role key |
| `NEXT_PUBLIC_APP_URL` | Yes | Browser-safe | App origin, such as `http://localhost:3000` |

Use either the recommended publishable key or one anon-key fallback. Use either `SUPABASE_SERVICE_ROLE_KEY` or `SUPABASE_SECRET_KEY`. Never expose either server secret through a `NEXT_PUBLIC_` variable.

## Supabase setup

### 1. Create storage buckets

Create these buckets in Supabase Storage:

| Bucket | Access |
| --- | --- |
| `product-images` | Public |
| `generated-assets` | Public |
| `audio` | Private |

### 2. Apply the database migrations

Run the SQL files in filename order. The first three establish the working multi-merchant app:

1. `202607210000_initial_schema.sql` — products, images, approvals, tasks, indexes, grants, and initial policies
2. `202607210001_add_commerce_analytics.sql` — visits, enquiries, and orders
3. `202607210002_add_auth_and_tenancy.sql` — merchant profiles, auth trigger, tenant ownership, RLS, and storage policies

The migrations are idempotent and can be pasted into the Supabase SQL editor. The final tenant-aware migration replaces the intentionally permissive bootstrap policies.

### 3. Configure authentication

In Supabase Authentication:

- Enable email/password.
- Optionally enable Google.
- Set the local site URL to `http://localhost:3000`.
- Add `http://localhost:3000/auth/callback` and the deployed `/auth/callback` URL to the redirect allow list.

New users receive a `merchant_profiles` row automatically. Their Supabase user UUID is stored as `products.business_id`, so dashboard data stays separated by boutique.

### 4. Optional Maanvi demo store

After the first three migrations:

1. Start the app and sign up with 

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 61 recognized source files, 749 KB.
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (68 of 68)

```
.env.example
.gitignore
.openai/hosting.json
app/(merchant)/dashboard/page.tsx
app/(merchant)/growth/page.tsx
app/(merchant)/layout.tsx
app/(merchant)/products/[productId]/page.tsx
app/(merchant)/products/new/page.tsx
app/api/ai/analyze/route.ts
app/api/ai/generate-image/route.ts
app/api/ai/transcribe/route.ts
app/api/dashboard/route.ts
app/api/growth/route.ts
app/api/products/[productId]/analysis/route.ts
app/api/products/[productId]/generate-assets/route.ts
app/api/products/[productId]/orders/route.ts
app/api/products/[productId]/pricing-options/route.ts
app/api/products/[productId]/review/route.ts
app/api/products/[productId]/route.ts
app/api/products/[productId]/video/route.ts
app/api/products/[productId]/workflow/route.ts
app/api/products/intake/route.ts
app/api/products/intake/uploads/route.ts
app/api/products/route.ts
app/api/storefront/products/[productId]/enquiry/route.ts
app/api/storefront/products/[productId]/visit/route.ts
app/api/storefront/products/route.ts
app/auth/callback/route.ts
app/auth/sign-in/page.tsx
app/auth/sign-up/page.tsx
app/globals.css
app/layout.tsx
app/page.tsx
app/shop/page.tsx
components/auth-form.tsx
components/merchant-dashboard.tsx
components/product-media.tsx
components/sign-out-button.tsx
components/theme-toggle.tsx
eslint.config.mjs
lib/ai/product.ts
lib/audio.ts
lib/auth.ts
lib/client/capture-draft.ts
lib/client/product-photo-upload.ts
lib/demo.ts
lib/language.ts
lib/openai.ts
lib/openai/aava.ts
lib/products/analyse-product-job.ts
lib/supabase/aava.ts
lib/supabase/admin.ts
lib/supabase/browser.ts
lib/supabase/server.ts
package.json
pnpm-workspace.yaml
postcss.config.mjs
proxy.ts
README.md
supabase/migrations/202607210000_initial_schema.sql
supabase/migrations/202607210001_add_commerce_analytics.sql
supabase/migrations/202607210002_add_auth_and_tenancy.sql
supabase/migrations/202607210003_seed_maanvi_demo_catalog.sql
supabase/migrations/202607210004_seed_maanvi_demo_analytics.sql
tests/rendered-html.test.mjs
tsconfig.json
vite.config.ts
worker/index.ts
```

### Dependencies

- package.json: @cloudflare/vite-plugin@1.37.1, @cloudflare/workers-types@^5.20260719.1, @supabase/ssr@^0.12.3, @supabase/supabase-js@^2.110.7, @tailwindcss/postcss@4.2.1, @types/node@22.19.19, @types/react@19.2.14, @types/react-dom@19.2.3, @vitejs/plugin-react@6.0.2, @vitejs/plugin-rsc@0.5.26, eslint@9.39.4, eslint-config-next@16.2.6, jpeg-js@^0.4.4, next@16.2.6, openai@^6.48.0, react@19.2.6, react-dom@19.2.6, react-server-dom-webpack@19.2.6, tailwindcss@4.2.1, typescript@5.9.3, vinext@0.0.50, vite@8.0.13, wrangler@4.92.0, zod@^4.4.3

### Recent commits (newest first)

- Document Aava OpenAI model usage
- Prepare Aava repository release
- Normalize Reel reference frames
- Refine reels with premium fabric reveal
- Fix storefront search and merchant navigation
- Clarify approvals and refine product reels
- Make light theme the default
- Polish themed form controls across app
- Add tenant-safe product deletion
- Standardize Aava catalog studio and light forms
- Restore saved generation choices on retry
- Make visual asset generation retry-safe
- Upload product photos directly to storage
- Fit product photos within hosted upload boundary
- Retry interrupted product uploads
- Discard voice audio after transcription
- Polish authentication fields in light mode
- Optimize multi-photo product uploads
- Use Sora 2 for product reels
- Add persistent light and dark themes

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "aava-commerce-operator",
  "version": "0.1.0",
  "private": true,
  "engines": {
    "node": ">=22.13.0"
  },
  "scripts": {
    "dev": "WRANGLER_LOG_PATH=.wrangler/wrangler.log vinext dev",
    "build": "WRANGLER_LOG_PATH=.wrangler/wrangler.log vinext build",
    "start": "WRANGLER_LOG_PATH=.wrangler/wrangler.log vinext start",
    "test": "pnpm run build && node --test tests/rendered-html.test.mjs",
    "lint": "eslint . --ignore-pattern dist --ignore-pattern .next",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "@supabase/ssr": "^0.12.3",
    "@supabase/supabase-js": "^2.110.7",
    "jpeg-js": "^0.4.4",
    "next": "16.2.6",
    "openai": "^6.48.0",
    "react": "19.2.6",
    "react-dom": "19.2.6",
    "zod": "^4.4.3"
  },
  "devDependencies": {
    "@cloudflare/vite-plugin": "1.37.1",
    "@cloudflare/workers-types": "^5.20260719.1",
    "@tailwindcss/postcss": "4.2.1",
    "@types/node": "22.19.19",
    "@types/react": "19.2.14",
    "@types/react-dom": "19.2.3",
    "@vitejs/plugin-react": "6.0.2",
    "@vitejs/plugin-rsc": "0.5.26",
    "eslint": "9.39.4",
    "eslint-config-next": "16.2.6",
    "react-server-dom-webpack": "19.2.6",
    "tailwindcss": "4.2.1",
    "typescript": "5.9.3",
    "vinext": "0.0.50",
    "vite": "8.0.13",
    "wrangler": "4.92.0"
  },
  "type": "module"
}

```

### worker/index.ts

```typescript
/** Cloudflare Worker entry point for Aava. */
import { handleImageOptimization, DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES } from "vinext/server/image-optimization";
import handler from "vinext/server/app-router-entry";

interface Env {
  ASSETS: Fetcher;
  IMAGES: {
    input(stream: ReadableStream): {
      transform(options: Record<string, unknown>): {
        output(options: { format: string; quality: number }): Promise<{ response(): Response }>;
      };
    };
  };
}

interface ExecutionContext {
  waitUntil(promise: Promise<unknown>): void;
  passThroughOnException(): void;
}

const worker = {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === "/_vinext/image") {
      const allowedWidths = [...DEFAULT_DEVICE_SIZES, ...DEFAULT_IMAGE_SIZES];
      return handleImageOptimization(request, {
        fetchAsset: (path) => env.ASSETS.fetch(new Request(new URL(path, request.url))),
        transformImage: async (body, { width, format, quality }) => {
          const result = await env.IMAGES.input(body).transform(width > 0 ? { width } : {}).output({ format, quality });
          return result.response();
        },
      }, allowedWidths);
    }

    return handler.fetch(request, env, ctx);
  },
};

export default worker;

```

### app/layout.tsx

```typescript
import type { Metadata, Viewport } from "next";
import { headers } from "next/headers";
import { ThemeToggle } from "@/components/theme-toggle";
import "./globals.css";

const themeBootstrap = `
  (function () {
    var theme = "light";
    try {
      var saved = window.localStorage.getItem("aava-theme");
      if (saved === "light" || saved === "dark") theme = saved;
    } catch (_) {}
    document.documentElement.dataset.theme = theme;
    document.documentElement.style.colorScheme = theme;
  })();
`;

export const viewport: Viewport = {
  width: "device-width",
  initialScale: 1,
  viewportFit: "cover",
  interactiveWidget: "resizes-content",
  themeColor: "#f7f2e8",
};

export async function generateMetadata(): Promise<Metadata> {
  const requestHeaders = await headers();
  const host = requestHeaders.get("x-forwarded-host") ?? requestHeaders.get("host") ?? "localhost:3000";
  const protocol = requestHeaders.get("x-forwarded-proto") ?? (host.includes("localhost") ? "http" : "https");
  const metadataBase = new URL(`${protocol}://${host}`);
  const title = "Aava — Your autonomous commerce saathi";
  const description = "Turn product photos and voice notes into beautiful listings, smart prices, social posts and a storefront.";

  return {
    metadataBase,
    title,
    description,
    icons: { icon: "/favicon.svg", shortcut: "/favicon.svg" },
    openGraph: {
      title,
      description,
      type: "website",
      images: [{ url: "/og-dark.png", width: 1731, height: 909, alt: "Aava — Show it. Say it. Sell it." }],
    },
    twitter: { card: "summary_large_image", title, description, images: ["/og-dark.png"] },
  };
}

export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
  return <html lang="en" data-theme="light" suppressHydrationWarning><head><script dangerouslySetInnerHTML={{ __html: themeBootstrap }} /></head><body>{children}<ThemeToggle /></body></html>;
}

```

### app/page.tsx

```typescript
import Link from "next/link";

export default function MarketingHomePage() {
  return <main className="marketing-home">
    <header className="marketing-nav">
      <Link href="/" className="marketing-logo"><span>आ</span><strong>aava</strong></Link>
      <nav aria-label="Marketing navigation"><a href="#how">How it works</a><a href="#features">What Aava does</a><a href="#story">For boutiques</a></nav>
      <div><Link href="/auth/sign-in">Sign in</Link><Link href="/auth/sign-up">Get started</Link></div>
    </header>

    <section className="marketing-hero">
      <div className="marketing-hero-copy"><p><span />THE AI COMMERCE SAATHI</p><h1>Your eye for fashion.<br /><em>Aava handles the rest.</em></h1><span>Turn everyday product photos and natural voice notes into polished listings, smart prices, campaign imagery, and a storefront made for your boutique.</span><div><Link href="/auth/sign-up">Start your boutique <b>→</b></Link><Link href="/auth/sign-in">Sign in to Aava</Link></div><small>Built for independent Indian fashion sellers · English + తెలుగు</small></div>
      <div className="marketing-operator" aria-label="Aava product workflow preview">
        <div className="operator-orbit"><i /><i /><i /><span>आ</span></div>
        <div className="operator-card operator-input"><small>01 · YOU SHARE</small><strong>Photos + your voice</strong><p>“ఇది కాంచీపురం పట్టు చీర…”</p></div>
        <div className="operator-card operator-output"><small>02 · AAVA BUILDS</small><strong>Ready to sell</strong><div><span>Product profile</span><span>Smart price</span><span>Studio imagery</span><span>Listing copy</span></div></div>
        <p>From camera roll to catalog</p>
      </div>
    </section>

    <section className="marketing-proof"><span>FOR SAREES</span><i /><span>KURTIS</span><i /><span>LEHENGAS</span><i /><span>HANDLOOM</span><i /><span>EVERYDAY WEAR</span></section>

    <section className="marketing-how" id="how"><div className="marketing-section-title"><p>ONE SIMPLE FLOW</p><h2>Show it. Say it.<br /><em>Sell it beautifully.</em></h2><span>No catalog team, copywriter, or studio required. Aava works beside you from first photo to published product.</span></div><div className="marketing-steps"><article><b>01</b><span>⌾</span><h3>Capture the piece</h3><p>Use your phone camera. Portrait, square, bright, blurry—Aava starts with what you have.</p></article><article><b>02</b><span>●</span><h3>Tell its story</h3><p>Speak naturally in English or Telugu. Mention fabric, cost, occasion, or whatever you know.</p></article><article><b>03</b><span>✦</span><h3>Review Aava&apos;s work</h3><p>Approve the product profile, pricing, professional images, copy, and ready-to-share content.</p></article></div></section>

    <section className="marketing-features" id="features"><header><p>AN AUTONOMOUS COMMERCE OPERATOR</p><h2>One assistant. An entire<br />catalog operation.</h2></header><div><article className="feature-wide"><span>VISION + VOICE</span><h3>Aava understands the garment—not just the photograph.</h3><p>Fabric, colours, weave, work, occasion, style, and customer fit become a structured product truth record.</p><div className="truth-tags"><b>Silk</b><b>Kanjeevaram</b><b>Wedding</b><b>Gold zari</b><b>Premium</b></div></article><article><span>PRICING</span><h3>Price with context.</h3><p>Budget, balanced, and premium options with margins and transparent reasoning.</p><strong>₹2,490 <small>recommended</small></strong></article><article><span>CREATIVE STUDIO</span><h3>Professional presentation.</h3><p>Catalog, lifestyle, campaign, and Reel assets—checked against the original product.</p><div className="creative-frames"><i /><i /><i /></div></article><article><span>YOUR STOREFRONT</span><h3>A boutique customers can discover.</h3><p>Published products, natural-language search, and real visits and enquiries for every store.</p><Link href="/auth/sign-up">Create yours →</Link></article></div></section>

    <section className="marketing-story" id="story"><p>MADE FOR THE WAY YOU ALREADY SELL</p><blockquote>“Aava does not ask a boutique owner to become a marketer, photographer, or software expert. It turns the knowledge she already has into commerce.”</blockquote><div><span>Private merchant workspace</span><span>Every boutique separated</span><span>You approve before publishing</span></div></section>

    <section className="marketing-cta"><div className="cta-mark">आ</div><p>YOUR NEXT COLLECTION STARTS HERE</p><h2>Let your products look as<br /><em>special as they are.</em></h2><span>Create your boutique workspace in a few minutes.</span><div><Link href="/auth/sign-up">Get started with Aava →</Link><Link href="/auth/sign-in">I already have an account</Link></div></section>
    <footer className="marketing-footer"><Link href="/" className="marketing-logo"><span>आ</span><strong>aava</strong></Link><p>Autonomous commerce for independent Indian fashion.</p><div><Link href="/auth/sign-in">Sign in</Link><Link href="/auth/sign-up">Get started</Link></div></footer>
  </main>;
}

```

### app/(merchant)/layout.tsx

```typescript
import { redirect } from "next/navigation";
import { getAuthenticatedUser } from "@/lib/auth";

export const dynamic = "force-dynamic";

export default async function MerchantLayout({ children }: { children: React.ReactNode }) {
  const user = await getAuthenticatedUser();
  if (!user) redirect("/auth/sign-in");
  return children;
}

```

### lib/supabase/server.ts

```typescript
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";

export async function createServerSupabaseClient() {
  const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
  const key = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY
    ?? process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
    ?? process.env.SUPABASE_ANON_KEY;
  if (!url || !key) throw new Error("Supabase authentication is not configured.");
  const cookieStore = await cookies();
  return createServerClient(url, key, {
    cookies: {
      getAll: () => cookieStore.getAll(),
      setAll: (values) => {
        try {
          values.forEach(({ name, value, options }) => cookieStore.set(name, value, options));
        } catch {
          // Server components cannot always write cookies. Route handlers can.
        }
      },
    },
  });
}

```

### app/shop/page.tsx

```typescript
"use client";

import Link from "next/link";
import { FormEvent, useEffect, useMemo, useState } from "react";
import { useSearchParams } from "next/navigation";
import { ProductMedia } from "@/components/product-media";
import { createBrowserSupabaseClient } from "@/lib/supabase/browser";

type Storefront = {
  businessName: string;
  displayName?: string | null;
  city?: string | null;
  instagramHandle?: string | null;
  storefrontSlug?: string | null;
};

type Product = {
  id: string;
  title: string;
  shortDescription?: string | null;
  description?: string | null;
  category?: string | null;
  fabric?: string | null;
  color?: string | null;
  price: number;
  currency: string;
  images: Array<{ id: string; type: string; altText?: string | null; url: string }>;
  seller?: Storefront | null;
  searchScore?: number;
  searchReason?: string;
};

type StorefrontResponse = {
  storefront: Storefront | null;
  products: Product[];
  count: number;
  totalMatched?: number;
  search: {
    query: string;
    interpretation: string;
    filters: { price_min?: number | null; price_max?: number | null; occasions?: string[]; categories?: string[]; colors?: string[] };
    unmatchedConstraints?: string[];
  } | null;
  error?: string;
};

const suggestions = [
  "Wedding guest under ₹3,000",
  "Elegant cotton for summer",
  "A festive saree in deep colours",
];

function money(value: number, currency: string) {
  return new Intl.NumberFormat("en-IN", { style: "currency", currency, maximumFractionDigits: 0 }).format(value);
}

function titleCase(value?: string | null) {
  if (!value) return "Indian wear";
  return value.replaceAll("_", " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
}

function Brand({ storefront }: { storefront: Storefront | null }) {
  if (storefront) {
    return <Link className="shop-logo merchant" href="/shop"><span>{storefront.businessName.slice(0, 1)}</span><div><strong>{storefront.businessName}</strong><small>{storefront.displayName ? `by ${storefront.displayName}` : storefront.city ? `Made in ${storefront.city}` : "Independent Indian fashion"}</small></div></Link>;
  }
  return <Link className="shop-logo merchant" href="/shop"><span>आ</span><div><strong>Aava Boutique</strong><small>Independent Indian fashion</small></div></Link>;
}

export default function ShopPage() {
  const searchParams = useSearchParams();
  const storefrontSlug = searchParams.get("storefront") ?? "";
  const initialQuery = searchParams.get("q") ?? "";
  const [query, setQuery] = useState(initialQuery);
  const [activeQuery, setActiveQuery] = useState(initialQuery);
  const [data, setData] = useState<StorefrontResponse | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");
  const [sort, setSort] = useState<"recommended" | "low" | "high">("recommended");
  const [reloadKey, setReloadKey] = useState(0);
  const [selectedProduct, setSelectedProduct] = useState<Product | null>(null);
  const [isMerchant, setIsMerchant] = useState(false);

  useEffect(() => {
    let active = true;
    try {
      const supabase = createBrowserSupabaseClient();
      void supabase.auth.getUser().then(({ data: { user } }) => {
        if (active) setIsMerchant(Boolean(user));
      });
      const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
        if (active) setIsMerchant(Boolean(session?.user));
      });
      return () => {
        active = false;
        subscription.unsubscribe();
      };
    } catch {
      return () => { active = false; };
    }
  }, []);

  useEffect(() => {
    let active = true;
    const controller = new AbortController();
    async function loadProducts() {
      setLoading(true);
      setError("");
      try {
        const params = new URLSearchParams({ limit: "18" });
        if (activeQuery) params.set("q", activeQuery);
        if (storefrontSlug) params.set("storefront", storefrontSlug);
        const response = await fetch(`/api/storefront/products?${params.toString()}`, { signal: controller.signal });
        const result = await response.json() as StorefrontResponse;
        if (!response.ok) throw new Error(result.error ?? "This collection could not be loaded.");
        if (active) setData(result);
      } catch (caught) {
        if (active && !(caught instanceof DOMException && caught.name === "AbortError")) {
          setError(caught instanceof Error ? caught.message : "This collection could not be loaded.");
        }
      } finally {
        if (active) setLoading(false);
      }
    }
    void loadProducts();
    return () => { active = false; controller.abort(); };
  }, [activeQuery, storefrontSlug, reloadKey]);

  const products = useMemo(() => {
    const rows = [...(data?.products ?? [])];
    if (sort === "low") return rows.sort((a, b) => Number(a.price) - Number(b.price));
    if (sort === "high") return rows.sort((a, b) => Number(b.price) - Number(a.price));
    return rows;
  }, [data?.products, sort]);

  function search(event?: FormEvent) {
    event?.preventDefault();
    const trimmed = query.trim();
    if (trimmed && trimmed.length < 2) return;
    setActiveQuery(trimmed);
    const params = new URLSearchParams();
    if (trimmed) params.set("q", trimmed);
    if (storefrontSlug) params.set("storefront", storefrontSlug);
    window.history.replaceState(null, "", `/shop${params.size ? `?${params.toString()}` : ""}`);
  }

  function applySuggestion(value: string) {
    setQuery(value);
    setActiveQuery(value);
    const params = new URLSearchParams({ q: value });
    if (storefrontSlug) params.set("storefront", storefrontSlug);
    window.history.replaceState(null, "", `/shop?${params.toString()}`);
  }

  function clearSearch() {
    setQuery("");
    setActiveQuery("");
    const suffix = storefrontSlug ? `?storefront=${encodeURIComponent(storefrontSlug)}` : "";
    window.history.replaceState(null, "", `/shop${suffix}`);
  }

  function focusStorefrontSe
[truncated — 9527 more characters]
```

### app/auth/sign-in/page.tsx

```typescript
import { AuthForm } from "@/components/auth-form";
export default function SignInPage() { return <AuthForm mode="sign-in" />; }

```

### app/auth/sign-up/page.tsx

```typescript
import { AuthForm } from "@/components/auth-form";
export default function SignUpPage() { return <AuthForm mode="sign-up" />; }

```

### app/(merchant)/dashboard/page.tsx

```typescript
import MerchantDashboard from "@/components/merchant-dashboard";

export default function DashboardPage() {
  return <MerchantDashboard />;
}

```

[48 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]