# Vendor Frontend Migration Plan - Jinja2 Templates

## Overview

This document outlines the complete migration strategy for the vendor frontend from legacy standalone HTML to a modern Jinja2 template-based architecture, following the exact same patterns established in the admin area migration.

---

## Table of Contents

1. [Current State Analysis](#current-state-analysis)
2. [Target Architecture](#target-architecture)
3. [Migration Strategy](#migration-strategy)
4. [Directory Structure](#directory-structure)
5. [Template Hierarchy](#template-hierarchy)
6. [Component Breakdown](#component-breakdown)
7. [Static Assets Organization](#static-assets-organization)
8. [Migration Steps](#migration-steps)
9. [Testing Plan](#testing-plan)
10. [Rollback Strategy](#rollback-strategy)

---

## 1. Current State Analysis

### Existing Vendor Routes (pages.py)
```
PUBLIC ROUTES:
- GET /vendor/{vendor_code}/              → Redirect to login
- GET /vendor/{vendor_code}/login         → Login page

AUTHENTICATED ROUTES:
- GET /vendor/{vendor_code}/dashboard     → Dashboard
- GET /vendor/{vendor_code}/products      → Product management
- GET /vendor/{vendor_code}/orders        → Order management
- GET /vendor/{vendor_code}/customers     → Customer management
- GET /vendor/{vendor_code}/inventory     → Inventory management
- GET /vendor/{vendor_code}/marketplace   → Marketplace imports
- GET /vendor/{vendor_code}/team          → Team management
- GET /vendor/{vendor_code}/settings      → Settings
```

### Current Issues
1. **Standalone HTML files**: Each page is completely independent
2. **Code duplication**: Repeated header, sidebar, scripts across pages
3. **Inconsistent styling**: No shared base template
4. **Hard to maintain**: Changes require updating multiple files
5. **No server-side rendering**: All data fetching happens client-side
6. **Mixed concerns**: Templates contain business logic

---

## 2. Target Architecture

### Jinja2 Template System

Following the exact same architecture as the admin area:

```
app/templates/vendor/
├── base.html                    # Base template with common structure
├── login.html                   # Public login page
├── admin/                       # Authenticated vendor admin pages
│   ├── dashboard.html
│   ├── products.html
│   ├── orders.html
│   ├── customers.html
│   ├── inventory.html
│   ├── marketplace.html
│   ├── team.html
│   └── settings.html
└── partials/                    # Reusable components
    ├── header.html              # Top navigation bar
    ├── sidebar.html             # Main navigation sidebar
    ├── vendor_info.html         # Vendor information card
    └── notifications.html       # Toast notifications
```

### Key Principles

1. **Template Inheritance**: All pages extend base.html
2. **Server-Side Rendering**: Initial page load with data
3. **Progressive Enhancement**: Alpine.js adds interactivity
4. **Component Reusability**: Shared partials across pages
5. **Consistent Styling**: Tailwind CSS with shared configuration
6. **Clean Separation**: Templates for structure, JS for behavior

---

## 3. Migration Strategy

### Phase 1: Foundation (Week 1)
- Create base template structure
- Set up static assets organization
- Create shared partials (header, sidebar)
- Migrate login page

### Phase 2: Core Pages (Week 2)
- Migrate dashboard
- Migrate marketplace import
- Migrate products listing

### Phase 3: Management Pages (Week 3)
- Migrate orders
- Migrate customers
- Migrate inventory

### Phase 4: Settings & Team (Week 4)
- Migrate team management
- Migrate settings
- Final testing and cleanup

---

## 4. Directory Structure

### Complete File Organization

```
app/
├── templates/
│   └── vendor/
│       ├── base.html
│       ├── login.html
│       ├── admin/
│       │   ├── dashboard.html
│       │   ├── products.html
│       │   ├── orders.html
│       │   ├── customers.html
│       │   ├── inventory.html
│       │   ├── marketplace.html
│       │   ├── team.html
│       │   └── settings.html
│       └── partials/
│           ├── header.html
│           ├── sidebar.html
│           ├── vendor_info.html
│           └── notifications.html
│
└── static/
    ├── shared/
    │   ├── js/
    │   │   ├── log-config.js       # Logging configuration
    │   │   ├── icons.js            # Icon registry
    │   │   ├── utils.js            # Utility functions
    │   │   └── api-client.js       # API wrapper
    │   └── css/
    │       └── base.css            # Shared base styles
    │
    └── vendor/
        ├── css/
        │   ├── tailwind.output.css  # Generated Tailwind
        │   └── vendor.css           # Vendor-specific styles
        ├── js/
        │   ├── init-alpine.js       # Alpine.js initialization
        │   ├── login.js             # Login page logic
        │   ├── dashboard.js         # Dashboard logic
        │   ├── products.js          # Products page logic
        │   ├── orders.js            # Orders page logic
        │   ├── customers.js         # Customers page logic
        │   ├── inventory.js         # Inventory page logic
        │   ├── marketplace.js       # Marketplace page logic
        │   ├── team.js              # Team page logic
        │   └── settings.js          # Settings page logic
        └── img/
            ├── login-office.jpeg
            └── login-office-dark.jpeg
```

---

## 5. Template Hierarchy

### Base Template Structure

```jinja2
{# app/templates/vendor/base.html #}
<!DOCTYPE html>
<html :class="{ 'theme-dark': dark }" x-data="{% block alpine_data %}data(){% endblock %}" lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>{% block title %}Vendor Panel{% endblock %} - {{ vendor.name }}</title>

    <!-- Fonts -->
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />

    <!-- Tailwind CSS -->
    <link rel="stylesheet" href="{{ url_for('static', path='vendor/css/tailwind.output.css') }}" />

    <!-- Alpine Cloak -->
    <style>
        [x-cloak] { display: none !important; }
    </style>

    {% block extra_head %}{% endblock %}
</head>
<body x-cloak>
    <div class="flex h-screen bg-gray-50 dark:bg-gray-900" :class="{ 'overflow-hidden': isSideMenuOpen }">
        <!-- Sidebar -->
        {% include 'vendor/partials/sidebar.html' %}

        <div class="flex flex-col flex-1 w-full">
            <!-- Header -->
            {% include 'vendor/partials/header.html' %}

            <!-- Main Content -->
            <main class="h-full overflow-y-auto">
                <div class="container px-6 mx-auto grid">
                    {% block content %}{% endblock %}
                </div>
            </main>
        </div>
    </div>

    <!-- Core Scripts - ORDER MATTERS! -->
    <!-- 1. FIRST: Log Configuration -->
    <script src="{{ url_for('static', path='shared/js/log-config.js') }}"></script>

    <!-- 2. SECOND: Icons (before Alpine.js) -->
    <script src="{{ url_for('static', path='shared/js/icons.js') }}"></script>

    <!-- 3. THIRD: Alpine.js Base Data -->
    <script src="{{ url_for('static', path='vendor/js/init-alpine.js') }}"></script>

    <!-- 4. FOURTH: Utils (standalone utilities) -->
    <script src="{{ url_for('static', path='shared/js/utils.js') }}"></script>

    <!-- 5. FIFTH: API Client (depends on Utils) -->
    <script src="{{ url_for('static', path='shared/js/api-client.js') }}"></script>

    <!-- 6. SIXTH: Alpine.js v3 (with defer) -->
    <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.0/dist/cdn.min.js"></script>

    <!-- 7. LAST: Page-specific scripts -->
    {% block extra_scripts %}{% endblock %}
</body>
</html>
```

### Login Template Structure

```jinja2
{# app/templates/vendor/login.html #}
<!DOCTYPE html>
<html :class="{ 'theme-dark': dark }" x-data="vendorLogin()" lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vendor Login - Multi-Tenant Platform</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
    <link rel="stylesheet" href="{{ url_for('static', path='vendor/css/tailwind.output.css') }}" />
    <style>
        [x-cloak] { display: none !important; }
    </style>
</head>
<body>
    <div class="flex items-center min-h-screen p-6 bg-gray-50 dark:bg-gray-900" x-cloak>
        <!-- Login Form Content -->
    </div>

    <!-- Scripts -->
    <script src="{{ url_for('static', path='shared/js/log-config.js') }}"></script>
    <script src="{{ url_for('static', path='shared/js/icons.js') }}"></script>
    <script src="{{ url_for('static', path='shared/js/utils.js') }}"></script>
    <script src="{{ url_for('static', path='shared/js/api-client.js') }}"></script>
    <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.0/dist/cdn.min.js"></script>
    <script src="{{ url_for('static', path='vendor/js/login.js') }}"></script>
</body>
</html>
```

### Dashboard Template Structure

```jinja2
{# app/templates/vendor/admin/dashboard.html #}
{% extends "vendor/base.html" %}

{% block title %}Dashboard{% endblock %}

{% block alpine_data %}vendorDashboard(){% endblock %}

{% block content %}
<!-- Page Header -->
<div class="flex items-center justify-between my-6">
    <h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
        Dashboard
    </h2>
    <button @click="refresh()" :disabled="loading" class="...">
        Refresh
    </button>
</div>

<!-- Vendor Info Card -->
{% include 'vendor/partials/vendor_info.html' %}

<!-- Stats Grid -->
<div class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
    <!-- Stats cards -->
</div>

<!-- Recent Activity -->
<div class="w-full overflow-hidden rounded-lg shadow-xs">
    <!-- Activity content -->
</div>
{% endblock %}

{% block extra_scripts %}
<script src="{{ url_for('static', path='vendor/js/dashboard.js') }}"></script>
{% endblock %}
```

---

## 6. Component Breakdown

### 6.1 Header Component (partials/header.html)

```jinja2
{# app/templates/vendor/partials/header.html #}
<header class="z-10 py-4 bg-white shadow-md dark:bg-gray-800">
    <div class="container flex items-center justify-between h-full px-6 mx-auto text-purple-600 dark:text-purple-300">
        <!-- Mobile hamburger -->
        <button class="p-1 mr-5 -ml-1 rounded-md md:hidden focus:outline-none focus:shadow-outline-purple"
                @click="toggleSideMenu"
                aria-label="Menu">
            <span x-html="$icon('menu', 'w-6 h-6')"></span>
        </button>

        <!-- Search input -->
        <div class="flex justify-center flex-1 lg:mr-32">
            <div class="relative w-full max-w-xl mr-6 focus-within:text-purple-500">
                <div class="absolute inset-y-0 flex items-center pl-2">
                    <span x-html="$icon('search', 'w-4 h-4')"></span>
                </div>
                <input class="w-full pl-8 pr-2 text-sm text-gray-700 placeholder-gray-600 bg-gray-100 border-0 rounded-md dark:placeholder-gray-500 dark:focus:shadow-outline-gray dark:focus:placeholder-gray-600 dark:bg-gray-700 dark:text-gray-200 focus:placeholder-gray-500 focus:bg-white focus:border-purple-300 focus:outline-none focus:shadow-outline-purple form-input"
                       type="text"
                       placeholder="Search products, orders..."
                       aria-label="Search" />
            </div>
        </div>

        <ul class="flex items-center flex-shrink-0 space-x-6">
            <!-- Theme toggler -->
            <li class="flex">
                <button class="rounded-md focus:outline-none focus:shadow-outline-purple"
                        @click="toggleTheme"
                        aria-label="Toggle color mode">
                    <template x-if="!dark">
                        <span x-html="$icon('moon', 'w-5 h-5')"></span>
                    </template>
                    <template x-if="dark">
                        <span x-html="$icon('sun', 'w-5 h-5')"></span>
                    </template>
                </button>
            </li>

            <!-- Notifications menu -->
            <li class="relative">
                <button class="relative align-middle rounded-md focus:outline-none focus:shadow-outline-purple"
                        @click="toggleNotificationsMenu"
                        @keydown.escape="closeNotificationsMenu"
                        aria-label="Notifications"
                        aria-haspopup="true">
                    <span x-html="$icon('bell', 'w-5 h-5')"></span>
                    <!-- Notification badge -->
                    <span aria-hidden="true"
                          class="absolute top-0 right-0 inline-block w-3 h-3 transform translate-x-1 -translate-y-1 bg-red-600 border-2 border-white rounded-full dark:border-gray-800"></span>
                </button>

                <!-- Notifications dropdown -->
                <template x-if="isNotificationsMenuOpen">
                    <ul x-transition:leave="transition ease-in duration-150"
                        x-transition:leave-start="opacity-100"
                        x-transition:leave-end="opacity-0"
                        @click.away="closeNotificationsMenu"
                        @keydown.escape="closeNotificationsMenu"
                        class="absolute right-0 w-56 p-2 mt-2 space-y-2 text-gray-600 bg-white border border-gray-100 rounded-md shadow-md dark:text-gray-300 dark:border-gray-700 dark:bg-gray-700">
                        <li class="flex">
                            <a class="inline-flex items-center justify-between w-full px-2 py-1 text-sm font-semibold transition-colors duration-150 rounded-md hover:bg-gray-100 hover:text-gray-800 dark:hover:bg-gray-800 dark:hover:text-gray-200"
                               href="#">
                                <span>New order received</span>
                            </a>
                        </li>
                    </ul>
                </template>
            </li>

            <!-- Profile menu -->
            <li class="relative">
                <button class="align-middle rounded-full focus:shadow-outline-purple focus:outline-none"
                        @click="toggleProfileMenu"
                        @keydown.escape="closeProfileMenu"
                        aria-label="Account"
                        aria-haspopup="true">
                    <div class="w-8 h-8 rounded-full bg-purple-600 flex items-center justify-center text-white font-semibold">
                        <span x-text="currentUser.username?.charAt(0).toUpperCase() || '?'"></span>
                    </div>
                </button>

                <!-- Profile dropdown -->
                <template x-if="isProfileMenuOpen">
                    <ul x-transition:leave="transition ease-in duration-150"
                        x-transition:leave-start="opacity-100"
                        x-transition:leave-end="opacity-0"
                        @click.away="closeProfileMenu"
                        @keydown.escape="closeProfileMenu"
                        class="absolute right-0 w-56 p-2 mt-2 space-y-2 text-gray-600 bg-white border border-gray-100 rounded-md shadow-md dark:border-gray-700 dark:text-gray-300 dark:bg-gray-700"
                        aria-label="submenu">
                        <li class="flex">
                            <a class="inline-flex items-center w-full px-2 py-1 text-sm font-semibold transition-colors duration-150 rounded-md hover:bg-gray-100 hover:text-gray-800 dark:hover:bg-gray-800 dark:hover:text-gray-200"
                               href="/vendor/{{ vendor_code }}/settings">
                                <span x-html="$icon('cog', 'w-4 h-4 mr-3')"></span>
                                <span>Settings</span>
                            </a>
                        </li>
                        <li class="flex">
                            <a class="inline-flex items-center w-full px-2 py-1 text-sm font-semibold transition-colors duration-150 rounded-md hover:bg-gray-100 hover:text-gray-800 dark:hover:bg-gray-800 dark:hover:text-gray-200"
                               @click.prevent="handleLogout"
                               href="#">
                                <span x-html="$icon('logout', 'w-4 h-4 mr-3')"></span>
                                <span>Log out</span>
                            </a>
                        </li>
                    </ul>
                </template>
            </li>
        </ul>
    </div>
</header>
```

### 6.2 Sidebar Component (partials/sidebar.html)

```jinja2
{# app/templates/vendor/partials/sidebar.html #}
<!-- Desktop sidebar -->
<aside class="z-20 hidden w-64 overflow-y-auto bg-white dark:bg-gray-800 md:block flex-shrink-0">
    <div class="py-4 text-gray-500 dark:text-gray-400">
        <a class="ml-6 text-lg font-bold text-gray-800 dark:text-gray-200"
           href="/vendor/{{ vendor_code }}/dashboard">
            <span class="text-2xl">🏪</span> {{ vendor.name if vendor else 'Vendor Portal' }}
        </a>

        <!-- Main Navigation -->
        <ul class="mt-6">
            <li class="relative px-6 py-3">
                <span x-show="currentPage === 'dashboard'"
                      class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
                      aria-hidden="true"></span>
                <a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
                   :class="currentPage === 'dashboard' ? 'text-gray-800 dark:text-gray-100' : ''"
                   href="/vendor/{{ vendor_code }}/dashboard">
                    <span x-html="$icon('home', 'w-5 h-5')"></span>
                    <span class="ml-4">Dashboard</span>
                </a>
            </li>
        </ul>

        <!-- Products & Inventory -->
        <div class="px-6 my-6">
            <div class="flex items-center">
                <span x-html="$icon('cube', 'w-5 h-5 text-gray-400')"></span>
                <span class="ml-2 text-xs font-semibold text-gray-500 uppercase dark:text-gray-400">
                    Products
                </span>
            </div>
        </div>
        <ul>
            <li class="relative px-6 py-3">
                <span x-show="currentPage === 'products'"
                      class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
                      aria-hidden="true"></span>
                <a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
                   :class="currentPage === 'products' ? 'text-gray-800 dark:text-gray-100' : ''"
                   href="/vendor/{{ vendor_code }}/products">
                    <span x-html="$icon('shopping-bag', 'w-5 h-5')"></span>
                    <span class="ml-4">All Products</span>
                </a>
            </li>
            <li class="relative px-6 py-3">
                <span x-show="currentPage === 'marketplace'"
                      class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
                      aria-hidden="true"></span>
                <a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
                   :class="currentPage === 'marketplace' ? 'text-gray-800 dark:text-gray-100' : ''"
                   href="/vendor/{{ vendor_code }}/marketplace">
                    <span x-html="$icon('download', 'w-5 h-5')"></span>
                    <span class="ml-4">Marketplace Import</span>
                </a>
            </li>
            <li class="relative px-6 py-3">
                <span x-show="currentPage === 'inventory'"
                      class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
                      aria-hidden="true"></span>
                <a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
                   :class="currentPage === 'inventory' ? 'text-gray-800 dark:text-gray-100' : ''"
                   href="/vendor/{{ vendor_code }}/inventory">
                    <span x-html="$icon('clipboard-list', 'w-5 h-5')"></span>
                    <span class="ml-4">Inventory</span>
                </a>
            </li>
        </ul>

        <!-- Sales -->
        <div class="px-6 my-6">
            <div class="flex items-center">
                <span x-html="$icon('chart-bar', 'w-5 h-5 text-gray-400')"></span>
                <span class="ml-2 text-xs font-semibold text-gray-500 uppercase dark:text-gray-400">
                    Sales
                </span>
            </div>
        </div>
        <ul>
            <li class="relative px-6 py-3">
                <span x-show="currentPage === 'orders'"
                      class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
                      aria-hidden="true"></span>
                <a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
                   :class="currentPage === 'orders' ? 'text-gray-800 dark:text-gray-100' : ''"
                   href="/vendor/{{ vendor_code }}/orders">
                    <span x-html="$icon('shopping-cart', 'w-5 h-5')"></span>
                    <span class="ml-4">Orders</span>
                </a>
            </li>
            <li class="relative px-6 py-3">
                <span x-show="currentPage === 'customers'"
                      class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
                      aria-hidden="true"></span>
                <a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
                   :class="currentPage === 'customers' ? 'text-gray-800 dark:text-gray-100' : ''"
                   href="/vendor/{{ vendor_code }}/customers">
                    <span x-html="$icon('users', 'w-5 h-5')"></span>
                    <span class="ml-4">Customers</span>
                </a>
            </li>
        </ul>

        <!-- Settings -->
        <div class="px-6 my-6">
            <div class="flex items-center">
                <span x-html="$icon('cog', 'w-5 h-5 text-gray-400')"></span>
                <span class="ml-2 text-xs font-semibold text-gray-500 uppercase dark:text-gray-400">
                    Settings
                </span>
            </div>
        </div>
        <ul>
            <li class="relative px-6 py-3">
                <span x-show="currentPage === 'team'"
                      class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
                      aria-hidden="true"></span>
                <a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
                   :class="currentPage === 'team' ? 'text-gray-800 dark:text-gray-100' : ''"
                   href="/vendor/{{ vendor_code }}/team">
                    <span x-html="$icon('user-group', 'w-5 h-5')"></span>
                    <span class="ml-4">Team</span>
                </a>
            </li>
            <li class="relative px-6 py-3">
                <span x-show="currentPage === 'settings'"
                      class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
                      aria-hidden="true"></span>
                <a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
                   :class="currentPage === 'settings' ? 'text-gray-800 dark:text-gray-100' : ''"
                   href="/vendor/{{ vendor_code }}/settings">
                    <span x-html="$icon('adjustments', 'w-5 h-5')"></span>
                    <span class="ml-4">Settings</span>
                </a>
            </li>
        </ul>

        <!-- Quick Actions -->
        <div class="px-6 my-6">
            <button class="flex items-center justify-between w-full px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg active:bg-purple-600 hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple"
                    @click="$dispatch('open-add-product-modal')">
                Add Product
                <span class="ml-2" aria-hidden="true">+</span>
            </button>
        </div>
    </div>
</aside>

<!-- Mobile sidebar -->
<div x-show="isSideMenuOpen"
     x-transition:enter="transition ease-in-out duration-150"
     x-transition:enter-start="opacity-0"
     x-transition:enter-end="opacity-100"
     x-transition:leave="transition ease-in-out duration-150"
     x-transition:leave-start="opacity-100"
     x-transition:leave-end="opacity-0"
     class="fixed inset-0 z-10 flex items-end bg-black bg-opacity-50 sm:items-center sm:justify-center"></div>

<aside class="fixed inset-y-0 z-20 flex-shrink-0 w-64 mt-16 overflow-y-auto bg-white dark:bg-gray-800 md:hidden"
       x-show="isSideMenuOpen"
       x-transition:enter="transition ease-in-out duration-150"
       x-transition:enter-start="opacity-0 transform -translate-x-20"
       x-transition:enter-end="opacity-100"
       x-transition:leave="transition ease-in-out duration-150"
       x-transition:leave-start="opacity-100"
       x-transition:leave-end="opacity-0 transform -translate-x-20"
       @click.away="closeSideMenu"
       @keydown.escape="closeSideMenu">
    <!-- Same content as desktop sidebar -->
    <div class="py-4 text-gray-500 dark:text-gray-400">
        <!-- Mobile navigation content (same as desktop) -->
    </div>
</aside>
```

### 6.3 Vendor Info Component (partials/vendor_info.html)

```jinja2
{# app/templates/vendor/partials/vendor_info.html #}
<div class="p-4 mb-8 bg-white rounded-lg shadow-md dark:bg-gray-800">
    <div class="flex items-center justify-between">
        <div class="flex items-center">
            <div class="w-12 h-12 rounded-full bg-purple-100 dark:bg-purple-600 flex items-center justify-center mr-4">
                <span class="text-xl font-bold text-purple-600 dark:text-purple-100">
                    {{ vendor.name[0].upper() if vendor and vendor.name else '?' }}
                </span>
            </div>
            <div>
                <h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200">
                    {{ vendor.name if vendor else 'Loading...' }}
                </h3>
                <p class="text-sm text-gray-600 dark:text-gray-400">
                    <span class="font-medium">{{ vendor.vendor_code if vendor else '' }}</span>
                    {% if vendor and vendor.subdomain %}
                    • {{ vendor.subdomain }}.platform.com
                    {% endif %}
                </p>
            </div>
        </div>
        <div class="flex items-center space-x-2">
            {% if vendor %}
            <span class="px-2 py-1 text-xs font-semibold leading-tight rounded-full {{ 'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100' if vendor.is_verified else 'text-orange-700 bg-orange-100 dark:text-white dark:bg-orange-600' }}">
                {{ 'Verified' if vendor.is_verified else 'Pending Verification' }}
            </span>
            <span class="px-2 py-1 text-xs font-semibold leading-tight rounded-full {{ 'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100' if vendor.is_active else 'text-red-700 bg-red-100 dark:bg-red-700 dark:text-red-100' }}">
                {{ 'Active' if vendor.is_active else 'Inactive' }}
            </span>
            {% endif %}
        </div>
    </div>
</div>
```

---

## 7. Static Assets Organization

### 7.1 JavaScript Files

#### init-alpine.js
```javascript
// app/static/vendor/js/init-alpine.js
/**
 * Alpine.js initialization for vendor pages
 * Provides common data and methods for all vendor pages
 */

function data() {
    return {
        dark: false,
        isSideMenuOpen: false,
        isNotificationsMenuOpen: false,
        isProfileMenuOpen: false,
        currentPage: '',
        currentUser: {},
        vendor: null,
        vendorCode: null,

        init() {
            // Set current page from URL
            const path = window.location.pathname;
            const segments = path.split('/').filter(Boolean);
            this.currentPage = segments[segments.length - 1] || 'dashboard';

            // Get vendor code from URL
            if (segments[0] === 'vendor' && segments[1]) {
                this.vendorCode = segments[1];
            }

            // Load user from localStorage
            const user = localStorage.getItem('currentUser');
            if (user) {
                this.currentUser = JSON.parse(user);
            }

            // Load theme preference
            const theme = localStorage.getItem('theme');
            if (theme === 'dark') {
                this.dark = true;
            }

            // Load vendor info
            this.loadVendorInfo();
        },

        async loadVendorInfo() {
            if (!this.vendorCode) return;

            try {
                const response = await apiClient.get(`/api/v1/vendors/${this.vendorCode}`);
                this.vendor = response;
                logDebug('Vendor info loaded', this.vendor);
            } catch (error) {
                logError('Failed to load vendor info', error);
            }
        },

        toggleSideMenu() {
            this.isSideMenuOpen = !this.isSideMenuOpen;
        },

        closeSideMenu() {
            this.isSideMenuOpen = false;
        },

        toggleNotificationsMenu() {
            this.isNotificationsMenuOpen = !this.isNotificationsMenuOpen;
            if (this.isNotificationsMenuOpen) {
                this.isProfileMenuOpen = false;
            }
        },

        closeNotificationsMenu() {
            this.isNotificationsMenuOpen = false;
        },

        toggleProfileMenu() {
            this.isProfileMenuOpen = !this.isProfileMenuOpen;
            if (this.isProfileMenuOpen) {
                this.isNotificationsMenuOpen = false;
            }
        },

        closeProfileMenu() {
            this.isProfileMenuOpen = false;
        },

        toggleTheme() {
            this.dark = !this.dark;
            localStorage.setItem('theme', this.dark ? 'dark' : 'light');
        },

        async handleLogout() {
            try {
                // Call logout endpoint
                await apiClient.post('/api/v1/vendor/auth/logout');
            } catch (error) {
                logError('Logout error', error);
            } finally {
                // Clear local storage
                localStorage.removeItem('accessToken');
                localStorage.removeItem('currentUser');

                // Redirect to login
                window.location.href = `/vendor/${this.vendorCode}/login`;
            }
        }
    };
}
```

#### login.js
```javascript
// app/static/vendor/js/login.js
/**
 * Vendor login page logic
 */

function vendorLogin() {
    return {
        credentials: {
            username: '',
            password: ''
        },
        vendor: null,
        vendorCode: null,
        loading: false,
        checked: false,
        error: '',
        success: '',
        errors: {},

        async init() {
            // Get vendor code from URL path
            const pathSegments = window.location.pathname.split('/').filter(Boolean);
            if (pathSegments[0] === 'vendor' && pathSegments[1]) {
                this.vendorCode = pathSegments[1];
                await this.loadVendor();
            } else {
                // Try to get from query params (backward compatibility)
                const urlParams = new URLSearchParams(window.location.search);
                this.vendorCode = urlParams.get('vendor') || localStorage.getItem('vendorCode');
                if (this.vendorCode) {
                    await this.loadVendor();
                }
            }
            this.checked = true;
        },

        async loadVendor() {
            this.loading = true;
            try {
                const response = await apiClient.get(`/api/v1/vendors/${this.vendorCode}`);
                this.vendor = response;
                logInfo('Vendor loaded', this.vendor);
            } catch (error) {
                logError('Failed to load vendor', error);
                this.error = 'Failed to load vendor information';
            } finally {
                this.loading = false;
            }
        },

        async handleLogin() {
            this.clearErrors();
            this.loading = true;

            try {
                // Validate inputs
                if (!this.credentials.username) {
                    this.errors.username = 'Username is required';
                }
                if (!this.credentials.password) {
                    this.errors.password = 'Password is required';
                }

                if (Object.keys(this.errors).length > 0) {
                    this.loading = false;
                    return;
                }

                // Call login endpoint
                const response = await apiClient.post('/api/v1/vendor/auth/login', {
                    username: this.credentials.username,
                    password: this.credentials.password,
                    vendor_code: this.vendorCode
                });

                logInfo('Login successful', response);

                // Store tokens and user info
                localStorage.setItem('accessToken', response.access_token);
                localStorage.setItem('currentUser', JSON.stringify(response.user));
                localStorage.setItem('vendorCode', this.vendorCode);

                // Show success message
                this.success = 'Login successful! Redirecting...';

                // Redirect to dashboard
                setTimeout(() => {
                    window.location.href = `/vendor/${this.vendorCode}/dashboard`;
                }, 1000);

            } catch (error) {
                logError('Login failed', error);

                if (error.status === 401) {
                    this.error = 'Invalid username or password';
                } else if (error.status === 403) {
                    this.error = 'Your account does not have access to this vendor';
                } else {
                    this.error = error.message || 'Login failed. Please try again.';
                }
            } finally {
                this.loading = false;
            }
        },

        clearErrors() {
            this.error = '';
            this.errors = {};
        }
    };
}
```

#### dashboard.js
```javascript
// app/static/vendor/js/dashboard.js
/**
 * Vendor dashboard page logic
 */

function vendorDashboard() {
    return {
        loading: false,
        error: '',
        stats: {
            products_count: 0,
            orders_count: 0,
            customers_count: 0,
            revenue: 0
        },
        recentOrders: [],
        recentProducts: [],

        async init() {
            await this.loadDashboardData();
        },

        async loadDashboardData() {
            this.loading = true;
            this.error = '';

            try {
                // Load stats
                const statsResponse = await apiClient.get(
                    `/api/v1/vendors/${this.vendorCode}/stats`
                );
                this.stats = statsResponse;

                // Load recent orders
                const ordersResponse = await apiClient.get(
                    `/api/v1/vendors/${this.vendorCode}/orders?limit=5&sort=created_at:desc`
                );
                this.recentOrders = ordersResponse.items || [];

                // Load recent products
                const productsResponse = await apiClient.get(
                    `/api/v1/vendors/${this.vendorCode}/products?limit=5&sort=created_at:desc`
                );
                this.recentProducts = productsResponse.items || [];

                logInfo('Dashboard data loaded', {
                    stats: this.stats,
                    orders: this.recentOrders.length,
                    products: this.recentProducts.length
                });

            } catch (error) {
                logError('Failed to load dashboard data', error);
                this.error = 'Failed to load dashboard data. Please try refreshing the page.';
            } finally {
                this.loading = false;
            }
        },

        async refresh() {
            await this.loadDashboardData();
        },

        formatCurrency(amount) {
            return new Intl.NumberFormat('en-US', {
                style: 'currency',
                currency: 'EUR'
            }).format(amount || 0);
        },

        formatDate(dateString) {
            if (!dateString) return '';
            const date = new Date(dateString);
            return date.toLocaleDateString('en-US', {
                year: 'numeric',
                month: 'short',
                day: 'numeric'
            });
        }
    };
}
```

### 7.2 CSS Organization

#### tailwind.config.js
```javascript
// tailwind.config.js for vendor area
module.exports = {
    content: [
        './app/templates/vendor/**/*.html',
        './app/static/vendor/js/**/*.js'
    ],
    darkMode: 'class',
    theme: {
        extend: {
            colors: {
                purple: {
                    50: '#f5f3ff',
                    100: '#ede9fe',
                    200: '#ddd6fe',
                    300: '#c4b5fd',
                    400: '#a78bfa',
                    500: '#8b5cf6',
                    600: '#7c3aed',
                    700: '#6d28d9',
                    800: '#5b21b6',
                    900: '#4c1d95',
                }
            }
        }
    },
    plugins: [
        require('@tailwindcss/forms')
    ]
};
```

---

## 8. Migration Steps

### Phase 1: Foundation Setup (Week 1)

#### Step 1.1: Create Base Template Structure
```bash
# Create directory structure
mkdir -p app/templates/vendor/admin
mkdir -p app/templates/vendor/partials
mkdir -p app/static/vendor/js
mkdir -p app/static/vendor/css
mkdir -p app/static/vendor/img

# Copy base template from admin as starting point
cp app/templates/admin/base.html app/templates/vendor/base.html
```

#### Step 1.2: Create Vendor-Specific Partials
```bash
# Create partials
touch app/templates/vendor/partials/header.html
touch app/templates/vendor/partials/sidebar.html
touch app/templates/vendor/partials/vendor_info.html
touch app/templates/vendor/partials/notifications.html
```

**Tasks:**
- [ ] Create base.html with vendor-specific styling
- [ ] Create header.html with vendor context
- [ ] Create sidebar.html with vendor navigation
- [ ] Create vendor_info.html component
- [ ] Set up Tailwind CSS for vendor area
- [ ] Create init-alpine.js with vendor data

#### Step 1.3: Migrate Login Page
```bash
# Create login template
touch app/templates/vendor/login.html
```

**Tasks:**
- [ ] Convert login.html to Jinja2 template
- [ ] Create login.js with vendor authentication logic
- [ ] Test login flow with vendor context
- [ ] Verify redirect to dashboard works
- [ ] Test error handling

**Update pages.py route:**
```python
@router.get("/vendor/{vendor_code}/login", response_class=HTMLResponse)
async def vendor_login_page(
    request: Request,
    vendor_code: str = Path(...),
    db: Session = Depends(get_db)
):
    """Render vendor login page with vendor info."""
    # Load vendor from database
    vendor = db.query(Vendor).filter(Vendor.vendor_code == vendor_code).first()
    
    return templates.TemplateResponse(
        "vendor/login.html",
        {
            "request": request,
            "vendor_code": vendor_code,
            "vendor": vendor
        }
    )
```

### Phase 2: Core Pages (Week 2)

#### Step 2.1: Migrate Dashboard
```bash
# Create dashboard template
touch app/templates/vendor/admin/dashboard.html
touch app/static/vendor/js/dashboard.js
```

**Tasks:**
- [ ] Create dashboard.html extending base.html
- [ ] Create dashboard.js with data loading logic
- [ ] Implement stats cards
- [ ] Add recent activity section
- [ ] Test with real vendor data
- [ ] Verify navigation works

**Update pages.py route:**
```python
@router.get("/vendor/{vendor_code}/dashboard", response_class=HTMLResponse)
async def vendor_dashboard_page(
    request: Request,
    vendor_code: str = Path(...),
    current_user: User = Depends(get_current_vendor_user),
    db: Session = Depends(get_db)
):
    """Render vendor dashboard with initial data."""
    vendor = db.query(Vendor).filter(Vendor.vendor_code == vendor_code).first()
    
    # Load initial stats
    stats = {
        'products_count': db.query(Product).filter(Product.vendor_id == vendor.id).count(),
        'orders_count': db.query(Order).filter(Order.vendor_id == vendor.id).count(),
        'customers_count': db.query(Customer).filter(Customer.vendor_id == vendor.id).count(),
        'revenue': 0  # Calculate from orders
    }
    
    return templates.TemplateResponse(
        "vendor/admin/dashboard.html",
        {
            "request": request,
            "vendor_code": vendor_code,
            "vendor": vendor,
            "current_user": current_user,
            "stats": stats
        }
    )
```

#### Step 2.2: Migrate Marketplace Import
```bash
# Create marketplace template
touch app/templates/vendor/admin/marketplace.html
touch app/static/vendor/js/marketplace.js
```

**Tasks:**
- [ ] Create marketplace.html template
- [ ] Create marketplace.js with import logic
- [ ] Implement import job listing
- [ ] Add product selection interface
- [ ] Test import workflow
- [ ] Verify product publishing works

#### Step 2.3: Migrate Products
```bash
# Create products template
touch app/templates/vendor/admin/products.html
touch app/static/vendor/js/products.js
```

**Tasks:**
- [ ] Create products.html template
- [ ] Create products.js with CRUD logic
- [ ] Implement product listing table
- [ ] Add search and filtering
- [ ] Create product detail modal
- [ ] Test product management

### Phase 3: Management Pages (Week 3)

#### Step 3.1: Migrate Orders
```bash
# Create orders template
touch app/templates/vendor/admin/orders.html
touch app/static/vendor/js/orders.js
```

**Tasks:**
- [ ] Create orders.html template
- [ ] Create orders.js with order management logic
- [ ] Implement order listing with status filters
- [ ] Add order detail view
- [ ] Test order status updates

#### Step 3.2: Migrate Customers
```bash
# Create customers template
touch app/templates/vendor/admin/customers.html
touch app/static/vendor/js/customers.js
```

**Tasks:**
- [ ] Create customers.html template
- [ ] Create customers.js with customer management
- [ ] Implement customer listing
- [ ] Add customer detail view
- [ ] Test customer search

#### Step 3.3: Migrate Inventory
```bash
# Create inventory template
touch app/templates/vendor/admin/inventory.html
touch app/static/vendor/js/inventory.js
```

**Tasks:**
- [ ] Create inventory.html template
- [ ] Create inventory.js with stock management
- [ ] Implement inventory table
- [ ] Add stock adjustment interface
- [ ] Test inventory updates

### Phase 4: Settings & Team (Week 4)

#### Step 4.1: Migrate Team Management
```bash
# Create team template
touch app/templates/vendor/admin/team.html
touch app/static/vendor/js/team.js
```

**Tasks:**
- [ ] Create team.html template
- [ ] Create team.js with team management logic
- [ ] Implement team member listing
- [ ] Add role assignment interface
- [ ] Test team invitations

#### Step 4.2: Migrate Settings
```bash
# Create settings template
touch app/templates/vendor/settings.html
touch app/static/vendor/js/settings.js
```

**Tasks:**
- [ ] Create settings.html template
- [ ] Create settings.js with settings management
- [ ] Implement settings tabs (General, Payment, Integrations)
- [ ] Add form validation
- [ ] Test settings updates

#### Step 4.3: Final Testing & Cleanup
**Tasks:**
- [ ] Cross-browser testing (Chrome, Firefox, Safari)
- [ ] Mobile responsiveness testing
- [ ] Dark mode testing
- [ ] Performance optimization
- [ ] Security audit
- [ ] Code cleanup and documentation
- [ ] Remove legacy HTML files
- [ ] Update documentation

---

## 9. Testing Plan

### 9.1 Unit Testing

**Template Rendering Tests:**
```python
# tests/test_vendor_templates.py
def test_vendor_login_page_renders(client, test_vendor):
    """Test that vendor login page renders correctly."""
    response = client.get(f"/vendor/{test_vendor.vendor_code}/login")
    assert response.status_code == 200
    assert test_vendor.name in response.text
    assert test_vendor.vendor_code in response.text

def test_vendor_dashboard_requires_auth(client, test_vendor):
    """Test that dashboard requires authentication."""
    response = client.get(f"/vendor/{test_vendor.vendor_code}/dashboard")
    assert response.status_code == 302  # Redirect to login

def test_vendor_dashboard_with_auth(client, test_vendor, auth_token):
    """Test that authenticated user can access dashboard."""
    response = client.get(
        f"/vendor/{test_vendor.vendor_code}/dashboard",
        headers={"Authorization": f"Bearer {auth_token}"}
    )
    assert response.status_code == 200
    assert test_vendor.name in response.text
```

### 9.2 Integration Testing

**Full Workflow Tests:**
```python
# tests/test_vendor_workflows.py
def test_vendor_login_to_dashboard_flow(client, test_vendor, test_user):
    """Test complete login to dashboard flow."""
    # 1. Access login page
    response = client.get(f"/vendor/{test_vendor.vendor_code}/login")
    assert response.status_code == 200
    
    # 2. Submit login credentials
    login_response = client.post(
        "/api/v1/vendor/auth/login",
        json={
            "username": test_user.username,
            "password": "password123",
            "vendor_code": test_vendor.vendor_code
        }
    )
    assert login_response.status_code == 200
    token = login_response.json()["access_token"]
    
    # 3. Access dashboard
    dashboard_response = client.get(
        f"/vendor/{test_vendor.vendor_code}/dashboard",
        headers={"Authorization": f"Bearer {token}"}
    )
    assert dashboard_response.status_code == 200
    assert test_vendor.name in dashboard_response.text
```

### 9.3 Frontend Testing

**Manual Testing Checklist:**

- [ ] **Login Page**
  - [ ] Vendor name and code display correctly
  - [ ] Login form validation works
  - [ ] Error messages display properly
  - [ ] Successful login redirects to dashboard
  - [ ] "Vendor not found" state displays correctly

- [ ] **Dashboard**
  - [ ] Stats cards load with correct data
  - [ ] Navigation works correctly
  - [ ] Dark mode toggle works
  - [ ] Responsive design on mobile
  - [ ] Profile menu functions properly

- [ ] **Sidebar Navigation**
  - [ ] All links work correctly
  - [ ] Active page highlighting works
  - [ ] Mobile menu opens/closes properly
  - [ ] Vendor branding displays correctly

- [ ] **Header**
  - [ ] Search functionality works
  - [ ] Notifications menu opens
  - [ ] Profile menu opens
  - [ ] Logout works correctly
  - [ ] Theme toggle works

- [ ] **Products Page**
  - [ ] Product listing loads
  - [ ] Search and filtering work
  - [ ] Pagination works
  - [ ] Add/Edit/Delete operations work
  - [ ] Product images display correctly

- [ ] **Marketplace Page**
  - [ ] Import job creation works
  - [ ] Product browsing works
  - [ ] Product selection works
  - [ ] Publishing to catalog works

- [ ] **Orders Page**
  - [ ] Order listing loads
  - [ ] Status filtering works
  - [ ] Order details display
  - [ ] Status updates work

- [ ] **Customers Page**
  - [ ] Customer listing loads
  - [ ] Customer search works
  - [ ] Customer details display

- [ ] **Inventory Page**
  - [ ] Inventory listing loads
  - [ ] Stock adjustments work
  - [ ] Low stock alerts display

- [ ] **Team Page**
  - [ ] Team member listing loads
  - [ ] Invitations work
  - [ ] Role assignments work

- [ ] **Settings Page**
  - [ ] Settings tabs work
  - [ ] Form validation works
  - [ ] Updates save correctly

### 9.4 Performance Testing

**Metrics to Monitor:**
- [ ] Page load time < 2 seconds
- [ ] Time to interactive < 3 seconds
- [ ] API response time < 500ms
- [ ] Bundle size < 200KB (compressed)
- [ ] Lighthouse score > 90

---

## 10. Rollback Strategy

### 10.1 Version Control

**Git Branching Strategy:**
```bash
# Create feature branch for migration
git checkout -b feature/vendor-jinja2-migration

# Create checkpoints for each phase
git tag -a vendor-migration-phase1 -m "Phase 1: Foundation complete"
git tag -a vendor-migration-phase2 -m "Phase 2: Core pages complete"
git tag -a vendor-migration-phase3 -m "Phase 3: Management pages complete"
git tag -a vendor-migration-phase4 -m "Phase 4: Settings & team complete"
```

### 10.2 Feature Flags

**Environment Variable Toggle:**
```python
# app/config.py
USE_LEGACY_VENDOR_TEMPLATES = os.getenv("USE_LEGACY_VENDOR_TEMPLATES", "false").lower() == "true"
```

**Route Handling:**
```python
# app/api/v1/vendor/pages.py
from app.config import USE_LEGACY_VENDOR_TEMPLATES

@router.get("/vendor/{vendor_code}/dashboard")
async def vendor_dashboard_page(request: Request, vendor_code: str):
    if USE_LEGACY_VENDOR_TEMPLATES:
        # Return legacy HTML
        return FileResponse("app/static/legacy/vendor/dashboard.html")
    else:
        # Return Jinja2 template
        return templates.TemplateResponse("vendor/admin/dashboard.html", {...})
```

### 10.3 Rollback Procedures

**Quick Rollback:**
```bash
# Set environment variable to use legacy templates
export USE_LEGACY_VENDOR_TEMPLATES=true

# Restart application
systemctl restart gunicorn
```

**Full Rollback:**
```bash
# Revert to previous tag
git checkout vendor-migration-phase1

# Rebuild static assets
npm run build:vendor

# Restart application
systemctl restart gunicorn
```

---

## Summary

This migration plan provides a complete roadmap for converting the vendor frontend from legacy standalone HTML to a modern Jinja2 template-based architecture. The plan follows the exact same patterns and structure as the admin area migration, ensuring consistency across the platform.

**Key Benefits:**
1. **Reduced Code Duplication**: Shared base templates and partials
2. **Improved Maintainability**: Centralized styling and behavior
3. **Better Performance**: Server-side rendering with progressive enhancement
4. **Consistent UX**: Uniform design across all vendor pages
5. **Easier Testing**: Better separation of concerns

**Timeline:**
- **Week 1**: Foundation (base templates, partials, login)
- **Week 2**: Core pages (dashboard, marketplace, products)
- **Week 3**: Management pages (orders, customers, inventory)
- **Week 4**: Settings & team, testing, cleanup

**Success Criteria:**
- All vendor pages migrated to Jinja2 templates
- No legacy HTML files remaining
- All tests passing
- Performance metrics met
- Documentation updated
