refactor: migrate vendor APIs to token-based context and consolidate architecture

## Vendor-in-Token Architecture (Complete Migration)
- Migrate all vendor API endpoints from require_vendor_context() to token_vendor_id
- Update permission dependencies to extract vendor from JWT token
- Add vendor exceptions: VendorAccessDeniedException, VendorOwnerOnlyException,
  InsufficientVendorPermissionsException
- Shop endpoints retain require_vendor_context() for URL-based detection
- Add AUTH-004 architecture rule enforcing vendor context patterns
- Fix marketplace router missing /marketplace prefix

## Exception Pattern Fixes (API-003/API-004)
- Services raise domain exceptions, endpoints let them bubble up
- Add code_quality and content_page exception modules
- Move business logic from endpoints to services (admin, auth, content_page)
- Fix exception handling in admin, shop, and vendor endpoints

## Tailwind CSS Consolidation
- Consolidate CSS to per-area files (admin, vendor, shop, platform)
- Remove shared/cdn-fallback.html and shared/css/tailwind.min.css
- Update all templates to use area-specific Tailwind output files
- Remove Node.js config (package.json, postcss.config.js, tailwind.config.js)

## Documentation & Cleanup
- Update vendor-in-token-architecture.md with completed migration status
- Update architecture-rules.md with new rules
- Move migration docs to docs/development/migration/
- Remove duplicate/obsolete documentation files
- Merge pytest.ini settings into pyproject.toml

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-04 22:24:45 +01:00
parent 76f8a59954
commit 8a367077e1
85 changed files with 21787 additions and 134978 deletions

View File

@@ -1,144 +1,86 @@
# UI Components Library Implementation Guide
# UI Components Library
**Version:** 2.0
**Last Updated:** December 2024
**Live Reference:** `/admin/components`
---
## Overview
This guide covers the implementation of:
1. **Components reference page** - A library showcasing all your UI components
2. **Updated vendor edit page** - Using proper form components
3. **Updated vendor detail page** - Using proper card components
4. **Sidebar navigation** - Adding "Components" menu item
## Files Created
The admin panel uses a consistent set of UI components built with Tailwind CSS and Alpine.js. All components support dark mode and are fully accessible.
### 1. Components Library Page
**File:** `app/templates/admin/components.html`
- Complete reference for all UI components
- Quick navigation to sections (Forms, Buttons, Cards, etc.)
- Copy-paste ready examples
- Shows validation states, disabled states, helper text, etc.
**Live Component Library:** Visit `/admin/components` in the admin panel to see all components with copy-paste ready code.
### 2. Updated Vendor Edit Page
**File:** `app/templates/admin/vendor-edit-updated.html`
- Uses proper form components from your library
- Improved visual hierarchy with card sections
- Better validation state displays (red borders for errors)
- Quick actions section at the top
- Status badges showing current state
- Clean, consistent styling throughout
---
### 3. Vendor Detail Page
**File:** `app/templates/admin/vendor-detail.html`
- NEW file (didn't exist before)
- Uses card components to display vendor information
- Status cards showing verification, active status, dates
- Information organized in clear sections
- All vendor data displayed in readable format
- Delete action button
## Page Layout Structure
### 4. JavaScript for Detail Page
**File:** `static/admin/js/vendor-detail.js`
- Loads vendor data
- Handles delete action with double confirmation
- Logging for debugging
- Error handling
All admin list pages follow a consistent structure:
## Implementation Steps
### Step 1: Add Components Menu to Sidebar
Update your `app/templates/admin/sidebar.html` (or wherever your sidebar is defined):
```html
<!-- Add this menu item after "Settings" or wherever appropriate -->
<li class="relative px-6 py-3">
<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="{ 'text-gray-800 dark:text-gray-100': currentPage === 'components' }"
href="/admin/components"
>
<span x-html="$icon('collection', 'w-5 h-5')"></span>
<span class="ml-4">Components</span>
</a>
</li>
```
┌─────────────────────────────────────────────────────┐
│ Page Header (Title + Action Button) │
├─────────────────────────────────────────────────────┤
│ Stats Cards (4 columns on desktop) │
├─────────────────────────────────────────────────────┤
│ Search & Filters Bar │
├─────────────────────────────────────────────────────┤
│ Data Table │
├── Table Header │
├── Table Rows │
└── Pagination │
└─────────────────────────────────────────────────────┘
```
### Step 2: Add Components Page Route
---
Update your `app/api/v1/admin/pages.py`:
## Form Components
```python
@router.get("/components", response_class=HTMLResponse, include_in_schema=False)
async def admin_components_page(
request: Request,
current_user: User = Depends(get_current_admin_from_cookie_or_header),
db: Session = Depends(get_db)
):
"""
Render UI components reference page.
Shows all available UI components for easy reference.
"""
return templates.TemplateResponse(
"admin/components.html",
{
"request": request,
"user": current_user,
}
)
```
### Basic Input
### Step 3: Replace Vendor Edit Template
1. Backup your current: `app/templates/admin/vendor-edit.html`
2. Replace it with: `vendor-edit-updated.html`
3. Keep your existing `vendor-edit.js` (no changes needed)
### Step 4: Add Vendor Detail Template & JavaScript
1. Copy `vendor-detail.html` to `app/templates/admin/vendor-detail.html`
2. Copy `vendor-detail.js` to `static/admin/js/vendor-detail.js`
## Component Usage Guide
### Form Components
#### Basic Input
```html
<label class="block mb-4 text-sm">
<span class="text-gray-700 dark:text-gray-400">
Label Text <span class="text-red-600">*</span>
</span>
<input
type="text"
x-model="formData.fieldName"
required
class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
/>
</label>
```
#### Input with Validation Error
```html
<label class="block mb-4 text-sm">
<span class="text-gray-700 dark:text-gray-400">Field Label</span>
<input
type="text"
class="block w-full mt-1 text-sm dark:text-gray-300 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
:class="{ 'border-red-600 focus:border-red-400 focus:shadow-outline-red': errors.fieldName }"
x-model="formData.field"
class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
/>
<span x-show="errors.fieldName" class="text-xs text-red-600 dark:text-red-400 mt-1" x-text="errors.fieldName"></span>
</label>
```
#### Disabled Input
### Required Field with Validation
```html
<label class="block mb-4 text-sm">
<span class="text-gray-700 dark:text-gray-400">
Email Address <span class="text-red-600">*</span>
</span>
<input
type="email"
x-model="formData.email"
required
:class="{ 'border-red-600 focus:border-red-400 focus:shadow-outline-red': errors.email }"
class="block w-full mt-1 text-sm dark:text-gray-300 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-input"
/>
<span x-show="errors.email" class="text-xs text-red-600 dark:text-red-400 mt-1" x-text="errors.email"></span>
</label>
```
### Disabled/Read-Only Input
```html
<input
type="text"
x-model="data.field"
disabled
value="Read-only value"
class="block w-full mt-1 text-sm bg-gray-100 border-gray-300 rounded-md dark:bg-gray-700 dark:text-gray-400 dark:border-gray-600 cursor-not-allowed"
/>
```
#### Textarea
### Textarea
```html
<textarea
x-model="formData.description"
@@ -147,26 +89,90 @@ async def admin_components_page(
></textarea>
```
### Card Components
### Select
```html
<select
x-model="formData.option"
class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-select"
>
<option value="">Select an option</option>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
</select>
```
---
## Button Components
### Primary Button
```html
<button class="px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple">
Primary Button
</button>
```
### Button with Icon
```html
<button class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none">
<span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
Add Item
</button>
```
### Secondary Button
```html
<button class="px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition-colors duration-150 bg-white border border-gray-300 rounded-lg hover:border-gray-400 focus:outline-none dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800">
Cancel
</button>
```
### Danger Button
```html
<button class="px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-lg hover:bg-red-700">
Delete
</button>
```
---
## Card Components
### Stats Card
#### Stats Card
```html
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="p-3 mr-4 text-orange-500 bg-orange-100 rounded-full dark:text-orange-100 dark:bg-orange-500">
<div class="p-3 mr-4 text-purple-500 bg-purple-100 rounded-full dark:text-purple-100 dark:bg-purple-500">
<span x-html="$icon('user-group', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
Total Users
</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200">
1,234
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.total">
0
</p>
</div>
</div>
```
#### Info Card
### Stats Card Colors
| Color | Use Case | Classes |
|-------|----------|---------|
| Blue | Total counts | `text-blue-500 bg-blue-100` |
| Green | Active/Success | `text-green-500 bg-green-100` |
| Red | Inactive/Errors | `text-red-500 bg-red-100` |
| Orange | Warnings/Admin | `text-orange-500 bg-orange-100` |
| Purple | Primary metrics | `text-purple-500 bg-purple-100` |
### Info Card
```html
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
@@ -181,125 +187,281 @@ async def admin_components_page(
</div>
```
### Button Components
---
## Status Badges
### Success Badge
#### Primary Button
```html
<button class="px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple">
Button Text
</button>
<span class="inline-flex items-center px-3 py-1 text-xs font-semibold leading-tight text-green-700 bg-green-100 rounded-full dark:bg-green-700 dark:text-green-100">
<span x-html="$icon('check-circle', 'w-3 h-3 mr-1')"></span>
Active
</span>
```
#### Button with Icon
### Warning Badge
```html
<button class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none">
<span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
Add Item
</button>
<span class="inline-flex items-center px-3 py-1 text-xs font-semibold leading-tight text-orange-700 bg-orange-100 rounded-full dark:bg-orange-700 dark:text-orange-100">
<span x-html="$icon('clock', 'w-3 h-3 mr-1')"></span>
Pending
</span>
```
#### Secondary Button
### Danger Badge
```html
<button class="px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition-colors duration-150 bg-white border border-gray-300 rounded-lg hover:border-gray-400 focus:outline-none dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800">
Cancel
</button>
<span class="inline-flex items-center px-3 py-1 text-xs font-semibold leading-tight text-red-700 bg-red-100 rounded-full dark:bg-red-700 dark:text-red-100">
<span x-html="$icon('x-circle', 'w-3 h-3 mr-1')"></span>
Inactive
</span>
```
## Features of Updated Pages
### Dynamic Badge
### Vendor Edit Page Improvements
```html
<span class="px-2 py-1 font-semibold leading-tight rounded-full text-xs"
:class="item.is_active
? 'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100'
: 'text-red-700 bg-red-100 dark:bg-red-700 dark:text-red-100'"
x-text="item.is_active ? 'Active' : 'Inactive'">
</span>
```
1. **Quick Actions Section**
- Verify/Unverify button
- Activate/Deactivate button
- Status badges showing current state
2. **Better Form Organization**
- Clear sections with headers
- Two-column layout on desktop
- Helper text for all fields
- Proper validation states
3. **Visual Consistency**
- Uses standard form components
- Consistent spacing and sizing
- Dark mode support
4. **User Experience**
- Disabled states for read-only fields
- Clear indication of required fields
- Loading states
- Error messages inline with fields
---
### Vendor Detail Page Features
## Data Tables
1. **Status Overview**
- 4 stats cards at top showing key metrics
- Visual status indicators (colors, icons)
2. **Information Organization**
- Basic info card
- Contact info card
- Business details section
- Owner information section
- Marketplace URLs (if available)
3. **Actions**
- Edit button (goes to edit page)
- Delete button (with double confirmation)
- Back to list button
### Basic Table Structure
## Quick Reference: Where to Find Components
```html
<div class="w-full overflow-hidden rounded-lg shadow-xs">
<div class="w-full overflow-x-auto">
<table class="w-full whitespace-no-wrap">
<thead>
<tr class="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b dark:border-gray-700 bg-gray-50 dark:text-gray-400 dark:bg-gray-800">
<th class="px-4 py-3">Name</th>
<th class="px-4 py-3">Status</th>
<th class="px-4 py-3">Actions</th>
</tr>
</thead>
<tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
<template x-for="item in items" :key="item.id">
<tr class="text-gray-700 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="px-4 py-3" x-text="item.name"></td>
<td class="px-4 py-3"><!-- Status badge --></td>
<td class="px-4 py-3"><!-- Action buttons --></td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
```
When you need a component, visit `/admin/components` and you'll find:
### Action Buttons
- **Forms Section**: All input types, validation states, helper text
- **Buttons Section**: All button styles and states
- **Cards Section**: Stats cards, info cards
- **Tables Section**: (from your tables.html)
- **Modals Section**: (from your modals.html)
- **Charts Section**: (from your charts.html)
```html
<div class="flex items-center space-x-2 text-sm">
<!-- View -->
<a :href="'/admin/resource/' + item.id"
class="flex items-center justify-center p-2 text-blue-600 rounded-lg hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-gray-700"
title="View">
<span x-html="$icon('eye', 'w-5 h-5')"></span>
</a>
## Testing Checklist
<!-- Edit -->
<a :href="'/admin/resource/' + item.id + '/edit'"
class="flex items-center justify-center p-2 text-purple-600 rounded-lg hover:bg-purple-50 dark:text-purple-400 dark:hover:bg-gray-700"
title="Edit">
<span x-html="$icon('edit', 'w-5 h-5')"></span>
</a>
- [ ] `/admin/components` page loads and displays all components
- [ ] Components menu item appears in sidebar
- [ ] `/admin/vendors/{vendor_code}/edit` displays correctly
- [ ] Form validation shows errors in red
- [ ] Quick actions (verify/activate) work
- [ ] `/admin/vendors/{vendor_code}` displays all vendor data
- [ ] Status cards show correct information
- [ ] Edit button navigates to edit page
- [ ] Delete button shows double confirmation
- [ ] All pages work in dark mode
- [ ] All pages are responsive on mobile
<!-- Delete -->
<button @click="deleteItem(item)"
class="flex items-center justify-center p-2 text-red-600 rounded-lg hover:bg-red-50 dark:text-red-400 dark:hover:bg-gray-700"
title="Delete">
<span x-html="$icon('delete', 'w-5 h-5')"></span>
</button>
</div>
```
## Color Scheme Reference
---
Your component library uses these color schemes:
## Loading & Error States
- **Primary**: Purple (`bg-purple-600`, `text-purple-600`)
- **Success**: Green (`bg-green-600`, `text-green-600`)
- **Warning**: Orange (`bg-orange-600`, `text-orange-600`)
- **Danger**: Red (`bg-red-600`, `text-red-600`)
- **Info**: Blue (`bg-blue-600`, `text-blue-600`)
### Loading State
## Next Steps
```html
<div x-show="loading" class="text-center py-12">
<span x-html="$icon('spinner', 'inline w-8 h-8 text-purple-600')"></span>
<p class="mt-2 text-gray-600 dark:text-gray-400">Loading...</p>
</div>
```
1. Implement the components page route
2. Add menu item to sidebar
3. Replace vendor-edit.html
4. Add vendor-detail.html and .js
5. Test all pages
6. Apply same patterns to other admin pages (users, imports, etc.)
### Error Alert
## Tips
```html
<div x-show="error && !loading" class="mb-6 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg flex items-start">
<span x-html="$icon('exclamation', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
<div>
<p class="font-semibold">Error loading data</p>
<p class="text-sm" x-text="error"></p>
</div>
</div>
```
- Always reference `/admin/components` when building new pages
- Copy component HTML directly from the components page
- Maintain consistent spacing and styling
- Use Alpine.js x-model for form bindings
- Use your icon system with `x-html="$icon('icon-name', 'w-5 h-5')"`
- Test in both light and dark modes
### Empty State
Enjoy your new component library! 🎨
```html
<div class="flex flex-col items-center py-12">
<span x-html="$icon('inbox', 'w-12 h-12 text-gray-400 mb-4')"></span>
<p class="text-lg font-medium text-gray-600 dark:text-gray-400">No items found</p>
<p class="text-sm text-gray-500" x-text="filters.search ? 'Try adjusting your search' : 'Create your first item'"></p>
</div>
```
---
## Modal Components
### Confirmation Modal
```html
<div
x-show="showModal"
x-cloak
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"
@click.self="showModal = false">
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl p-6 m-4 max-w-md w-full">
<div class="flex items-start justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200">
Confirm Action
</h3>
<button @click="showModal = false" class="text-gray-400 hover:text-gray-600">
<span x-html="$icon('x', 'w-5 h-5')"></span>
</button>
</div>
<p class="mb-6 text-sm text-gray-600 dark:text-gray-400">
Are you sure you want to perform this action?
</p>
<div class="flex justify-end gap-3">
<button @click="showModal = false"
class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600">
Cancel
</button>
<button @click="confirmAction()"
class="px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700">
Confirm
</button>
</div>
</div>
</div>
```
---
## Toast Notifications
Use the global Utils helper:
```javascript
Utils.showToast('Operation successful!', 'success');
Utils.showToast('Something went wrong', 'error');
Utils.showToast('Please check your input', 'warning');
Utils.showToast('Here is some information', 'info');
```
---
## Grid Layouts
### 2 Columns (Desktop)
```html
<div class="grid gap-6 md:grid-cols-2">
<div>Column 1</div>
<div>Column 2</div>
</div>
```
### 4 Columns (Stats Cards)
```html
<div class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
<!-- Stats cards here -->
</div>
```
---
## Color Reference
| Type | Primary | Success | Warning | Danger | Info |
|------|---------|---------|---------|--------|------|
| Background | `bg-purple-600` | `bg-green-600` | `bg-orange-600` | `bg-red-600` | `bg-blue-600` |
| Text | `text-purple-600` | `text-green-600` | `text-orange-600` | `text-red-600` | `text-blue-600` |
| Light BG | `bg-purple-100` | `bg-green-100` | `bg-orange-100` | `bg-red-100` | `bg-blue-100` |
---
## Common Icons
| Icon | Use Case |
|------|----------|
| `user-group` | Users/Teams |
| `badge-check` | Verified |
| `check-circle` | Success |
| `x-circle` | Error/Inactive |
| `clock` | Pending |
| `calendar` | Dates |
| `edit` | Edit |
| `delete` | Delete |
| `plus` | Add |
| `arrow-left` | Back |
| `exclamation` | Warning |
| `spinner` | Loading |
---
## JavaScript Patterns
### List Page Structure
```javascript
function adminResourceList() {
return {
...data(), // Inherit base layout
currentPage: 'resource-name',
// State
items: [],
loading: false,
error: null,
filters: { search: '', status: '' },
stats: {},
pagination: { page: 1, per_page: 10, total: 0 },
async init() {
await this.loadItems();
await this.loadStats();
},
async loadItems() { /* ... */ },
debouncedSearch() { /* ... */ },
async deleteItem(item) { /* ... */ }
};
}
```
---
## Related Documentation
- [Tailwind CSS](../tailwind-css.md)
- [Tailwind CSS Official Docs](https://tailwindcss.com/docs)
- [Alpine.js Official Docs](https://alpinejs.dev/)