adding developer tools in admin panel, adding vendor management
This commit is contained in:
@@ -41,7 +41,7 @@ templates = Jinja2Templates(directory="app/templates")
|
||||
async def admin_root():
|
||||
"""
|
||||
Redirect /admin/ to /admin/login.
|
||||
|
||||
|
||||
Simple approach:
|
||||
- Unauthenticated users → see login form
|
||||
- Authenticated users → login page shows form (they can navigate to dashboard)
|
||||
@@ -67,9 +67,9 @@ async def admin_login_page(request: Request):
|
||||
|
||||
@router.get("/dashboard", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_dashboard_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Render admin dashboard page.
|
||||
@@ -90,9 +90,9 @@ async def admin_dashboard_page(
|
||||
|
||||
@router.get("/vendors", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_vendors_list_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Render vendors management page.
|
||||
@@ -109,9 +109,9 @@ async def admin_vendors_list_page(
|
||||
|
||||
@router.get("/vendors/create", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_vendor_create_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Render vendor creation form.
|
||||
@@ -127,10 +127,10 @@ async def admin_vendor_create_page(
|
||||
|
||||
@router.get("/vendors/{vendor_code}", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_vendor_detail_page(
|
||||
request: Request,
|
||||
vendor_code: str = Path(..., description="Vendor code"),
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
request: Request,
|
||||
vendor_code: str = Path(..., description="Vendor code"),
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Render vendor detail page.
|
||||
@@ -148,10 +148,10 @@ async def admin_vendor_detail_page(
|
||||
|
||||
@router.get("/vendors/{vendor_code}/edit", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_vendor_edit_page(
|
||||
request: Request,
|
||||
vendor_code: str = Path(..., description="Vendor code"),
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
request: Request,
|
||||
vendor_code: str = Path(..., description="Vendor code"),
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Render vendor edit form.
|
||||
@@ -172,9 +172,9 @@ async def admin_vendor_edit_page(
|
||||
|
||||
@router.get("/users", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_users_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Render users management page.
|
||||
@@ -195,9 +195,9 @@ async def admin_users_page(
|
||||
|
||||
@router.get("/imports", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_imports_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Render imports management page.
|
||||
@@ -218,9 +218,9 @@ async def admin_imports_page(
|
||||
|
||||
@router.get("/settings", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_settings_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Render admin settings page.
|
||||
@@ -233,3 +233,100 @@ async def admin_settings_page(
|
||||
"user": current_user,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# DEVELOPER TOOLS - COMPONENTS & TESTING
|
||||
# ============================================================================
|
||||
|
||||
@router.get("/components", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_components_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Render UI components library page.
|
||||
Reference for all available UI components.
|
||||
"""
|
||||
return templates.TemplateResponse(
|
||||
"admin/components.html",
|
||||
{
|
||||
"request": request,
|
||||
"user": current_user,
|
||||
}
|
||||
)
|
||||
|
||||
@router.get("/icons", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_icons_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Render icons browser page.
|
||||
Browse and search all available icons.
|
||||
"""
|
||||
return templates.TemplateResponse(
|
||||
"admin/icons.html",
|
||||
{
|
||||
"request": request,
|
||||
"user": current_user,
|
||||
}
|
||||
)
|
||||
|
||||
@router.get("/testing", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_testing_hub(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Render testing hub page.
|
||||
Central hub for all test suites and QA tools.
|
||||
"""
|
||||
return templates.TemplateResponse(
|
||||
"admin/testing-hub.html",
|
||||
{
|
||||
"request": request,
|
||||
"user": current_user,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.get("/test/auth-flow", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_test_auth_flow(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Render authentication flow testing page.
|
||||
Tests login, logout, token expiration, and protected routes.
|
||||
"""
|
||||
return templates.TemplateResponse(
|
||||
"admin/test-auth-flow.html",
|
||||
{
|
||||
"request": request,
|
||||
"user": current_user,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.get("/test/vendors-users-migration", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_test_vendors_users_migration(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Render vendors and users migration testing page.
|
||||
Tests CRUD operations, data migration, and form validation.
|
||||
"""
|
||||
return templates.TemplateResponse(
|
||||
"admin/test-vendors-users-migration.html",
|
||||
{
|
||||
"request": request,
|
||||
"user": current_user,
|
||||
}
|
||||
)
|
||||
|
||||
519
app/templates/admin/components.html
Normal file
519
app/templates/admin/components.html
Normal file
@@ -0,0 +1,519 @@
|
||||
{# app/templates/admin/components.html #}
|
||||
{% extends "admin/base.html" %}
|
||||
|
||||
{% block title %}UI Components{% endblock %}
|
||||
|
||||
{# ✅ CRITICAL: Link to Alpine.js component #}
|
||||
{% block alpine_data %}adminComponents(){% 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">
|
||||
UI Components Library
|
||||
</h2>
|
||||
<a href="/admin/dashboard" class="flex items-center 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-800 dark:border-gray-600 dark:hover:bg-gray-700">
|
||||
<span x-html="$icon('arrow-left', 'w-4 h-4 mr-2')"></span>
|
||||
Back to Dashboard
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Introduction -->
|
||||
<div class="mb-8 p-6 bg-gradient-to-r from-purple-600 to-indigo-600 rounded-lg shadow-lg">
|
||||
<div class="flex items-start">
|
||||
<span x-html="$icon('collection', 'w-10 h-10 mr-4 flex-shrink-0 text-white')"></span>
|
||||
<div>
|
||||
<h3 class="text-xl font-bold mb-2 text-white">Quick Reference Library</h3>
|
||||
<p class="text-white opacity-90">
|
||||
Copy-paste ready UI components for your admin pages. All components support dark mode and are fully accessible.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Layout: Sidebar + Content -->
|
||||
<div class="flex gap-6">
|
||||
|
||||
<!-- Sticky Navigation Sidebar -->
|
||||
<div class="w-64 flex-shrink-0">
|
||||
<div class="sticky top-6">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-4">
|
||||
<h3 class="text-sm font-semibold text-gray-600 dark:text-gray-400 uppercase mb-3">Sections</h3>
|
||||
<nav class="space-y-1">
|
||||
<template x-for="section in sections" :key="section.id">
|
||||
<a
|
||||
:href="'#' + section.id"
|
||||
@click.prevent="goToSection(section.id)"
|
||||
class="flex items-center px-3 py-2 text-sm rounded-lg transition-colors"
|
||||
:class="isSectionActive(section.id)
|
||||
? 'bg-purple-100 text-purple-700 dark:bg-purple-900 dark:text-purple-200 font-medium'
|
||||
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'"
|
||||
>
|
||||
<span x-html="$icon(section.icon, 'w-4 h-4 mr-2')"></span>
|
||||
<span x-text="section.name"></span>
|
||||
</a>
|
||||
</template>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Quick Tips -->
|
||||
<div class="mt-4 bg-blue-50 dark:bg-gray-800 border border-blue-200 dark:border-gray-700 rounded-lg p-4">
|
||||
<div class="flex items-start">
|
||||
<span x-html="$icon('light-bulb', 'w-5 h-5 text-blue-600 mr-2 flex-shrink-0')"></span>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-blue-900 dark:text-blue-200 mb-1">Pro Tip</p>
|
||||
<p class="text-xs text-blue-800 dark:text-blue-300">
|
||||
Click any code block to copy it to your clipboard!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 space-y-8">
|
||||
|
||||
<!-- FORMS SECTION -->
|
||||
<section id="forms">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||
<h2 class="text-2xl font-bold text-gray-800 dark:text-gray-200 mb-4 flex items-center">
|
||||
<span x-html="$icon('clipboard-list', 'w-6 h-6 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
Forms
|
||||
</h2>
|
||||
|
||||
<!-- Basic Input -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-3">Basic Input</h3>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 mb-3">
|
||||
<label class="block mb-4 text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">Field Label</span>
|
||||
<input
|
||||
type="text"
|
||||
x-model="exampleForm.textInput"
|
||||
placeholder="Enter text..."
|
||||
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>
|
||||
</div>
|
||||
<button @click="copyCode(`<label class="block mb-4 text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">Field Label</span>
|
||||
<input
|
||||
type="text"
|
||||
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"
|
||||
/>
|
||||
</label>`)" class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
|
||||
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
|
||||
Copy Code
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Required Field with Error -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-3">Required Field with Error</h3>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 mb-3">
|
||||
<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="exampleForm.email"
|
||||
required
|
||||
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 border-red-600 focus:border-red-400 focus:shadow-outline-red"
|
||||
/>
|
||||
<span class="text-xs text-red-600 dark:text-red-400 mt-1" x-text="exampleErrors.email"></span>
|
||||
</label>
|
||||
</div>
|
||||
<button @click="copyCode(`<label class="block mb-4 text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">
|
||||
Field Label <span class="text-red-600">*</span>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
x-model="formData.field"
|
||||
required
|
||||
:class="{ 'border-red-600 focus:border-red-400 focus:shadow-outline-red': errors.field }"
|
||||
class="block w-full mt-1 text-sm dark:text-gray-300 dark:bg-gray-700 focus:border-purple-400 focus:outline-none form-input"
|
||||
/>
|
||||
<span x-show="errors.field" class="text-xs text-red-600 dark:text-red-400 mt-1" x-text="errors.field"></span>
|
||||
</label>`)" class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
|
||||
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
|
||||
Copy Code
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Textarea -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-3">Textarea</h3>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 mb-3">
|
||||
<label class="block mb-4 text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">Description</span>
|
||||
<textarea
|
||||
x-model="exampleForm.textarea"
|
||||
rows="3"
|
||||
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-textarea"
|
||||
></textarea>
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400 mt-1">Helper text goes here</span>
|
||||
</label>
|
||||
</div>
|
||||
<button @click="copyCode(`<label class="block mb-4 text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">Description</span>
|
||||
<textarea
|
||||
x-model="formData.description"
|
||||
rows="3"
|
||||
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 form-textarea"
|
||||
></textarea>
|
||||
</label>`)" class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
|
||||
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
|
||||
Copy Code
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Select Dropdown -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-3">Select Dropdown</h3>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 mb-3">
|
||||
<label class="block mb-4 text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">Select Option</span>
|
||||
<select
|
||||
x-model="exampleForm.select"
|
||||
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="">Choose option</option>
|
||||
<option value="option1">Option 1</option>
|
||||
<option value="option2">Option 2</option>
|
||||
<option value="option3">Option 3</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<button @click="copyCode(`<label class="block mb-4 text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">Select Option</span>
|
||||
<select
|
||||
x-model="formData.select"
|
||||
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 form-select"
|
||||
>
|
||||
<option value="">Choose option</option>
|
||||
<option value="option1">Option 1</option>
|
||||
</select>
|
||||
</label>`)" class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
|
||||
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
|
||||
Copy Code
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Checkbox -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-3">Checkbox</h3>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 mb-3">
|
||||
<label class="flex items-center text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
x-model="exampleForm.checkbox"
|
||||
class="text-purple-600 form-checkbox focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray"
|
||||
/>
|
||||
<span class="ml-2 text-gray-700 dark:text-gray-400">I agree to the terms and conditions</span>
|
||||
</label>
|
||||
</div>
|
||||
<button @click="copyCode(`<label class="flex items-center text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
x-model="formData.checkbox"
|
||||
class="text-purple-600 form-checkbox focus:border-purple-400 focus:outline-none focus:shadow-outline-purple"
|
||||
/>
|
||||
<span class="ml-2 text-gray-700 dark:text-gray-400">Checkbox label</span>
|
||||
</label>`)" class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
|
||||
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
|
||||
Copy Code
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Disabled Input -->
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-3">Disabled/Read-Only Input</h3>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 mb-3">
|
||||
<label class="block mb-4 text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">Read-Only Field</span>
|
||||
<input
|
||||
type="text"
|
||||
x-model="exampleForm.disabled"
|
||||
disabled
|
||||
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"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<button @click="copyCode(`<input
|
||||
type="text"
|
||||
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"
|
||||
/>`)" class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
|
||||
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
|
||||
Copy Code
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- BUTTONS SECTION -->
|
||||
<section id="buttons">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||
<h2 class="text-2xl font-bold text-gray-800 dark:text-gray-200 mb-4 flex items-center">
|
||||
<span x-html="$icon('cursor-click', 'w-6 h-6 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
Buttons
|
||||
</h2>
|
||||
|
||||
<!-- Primary Button -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-3">Primary Button</h3>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 mb-3 flex gap-3">
|
||||
<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 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>
|
||||
With Icon
|
||||
</button>
|
||||
</div>
|
||||
<button @click="copyCode(`<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">
|
||||
Button Text
|
||||
</button>`)" class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
|
||||
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
|
||||
Copy Code
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Secondary Button -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-3">Secondary Button</h3>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 mb-3 flex gap-3">
|
||||
<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">
|
||||
Secondary Button
|
||||
</button>
|
||||
<button class="flex items-center 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 dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800">
|
||||
<span x-html="$icon('arrow-left', 'w-4 h-4 mr-2')"></span>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
<button @click="copyCode(`<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 dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800">
|
||||
Button Text
|
||||
</button>`)" class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
|
||||
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
|
||||
Copy Code
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Danger Button -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-3">Danger Button</h3>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 mb-3 flex gap-3">
|
||||
<button class="px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-red-600 border border-transparent rounded-lg hover:bg-red-700 focus:outline-none">
|
||||
Delete
|
||||
</button>
|
||||
<button class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-red-600 border border-transparent rounded-lg hover:bg-red-700 focus:outline-none">
|
||||
<span x-html="$icon('delete', 'w-4 h-4 mr-2')"></span>
|
||||
Delete Item
|
||||
</button>
|
||||
</div>
|
||||
<button @click="copyCode(`<button class="px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-red-600 border border-transparent rounded-lg hover:bg-red-700 focus:outline-none">
|
||||
Delete
|
||||
</button>`)" class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
|
||||
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
|
||||
Copy Code
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Button States -->
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-3">Button States</h3>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 mb-3 flex gap-3">
|
||||
<button disabled class="px-4 py-2 text-sm font-medium leading-5 text-white bg-purple-400 border border-transparent rounded-lg cursor-not-allowed">
|
||||
Disabled
|
||||
</button>
|
||||
<button class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white bg-purple-600 border border-transparent rounded-lg cursor-wait">
|
||||
<span x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
|
||||
Loading...
|
||||
</button>
|
||||
</div>
|
||||
<button @click="copyCode(`<button disabled class="px-4 py-2 text-sm font-medium leading-5 text-white bg-purple-400 border border-transparent rounded-lg cursor-not-allowed">
|
||||
Disabled
|
||||
</button>`)" class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
|
||||
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
|
||||
Copy Code
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CARDS SECTION -->
|
||||
<section id="cards">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||
<h2 class="text-2xl font-bold text-gray-800 dark:text-gray-200 mb-4 flex items-center">
|
||||
<span x-html="$icon('collection', 'w-6 h-6 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
Cards
|
||||
</h2>
|
||||
|
||||
<!-- Stats Card -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-3">Stats Card</h3>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 mb-3">
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="copyCode(`<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<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">Label</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200">Value</p>
|
||||
</div>
|
||||
</div>`)" class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
|
||||
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
|
||||
Copy Code
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Info Card -->
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-3">Info Card</h3>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 mb-3">
|
||||
<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">Card Title</h3>
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Field Label</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300">Field Value</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Another Label</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300">Another Value</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="copyCode(`<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">Card Title</h3>
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Label</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300">Value</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>`)" class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
|
||||
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
|
||||
Copy Code
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- BADGES SECTION -->
|
||||
<section id="badges">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||
<h2 class="text-2xl font-bold text-gray-800 dark:text-gray-200 mb-4 flex items-center">
|
||||
<span x-html="$icon('tag', 'w-6 h-6 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
Badges
|
||||
</h2>
|
||||
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 mb-3">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<!-- Success -->
|
||||
<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>
|
||||
<!-- Warning -->
|
||||
<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>
|
||||
<!-- Danger -->
|
||||
<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('exclamation', 'w-3 h-3 mr-1')"></span>
|
||||
Inactive
|
||||
</span>
|
||||
<!-- Info -->
|
||||
<span class="inline-flex items-center px-3 py-1 text-xs font-semibold leading-tight text-blue-700 bg-blue-100 rounded-full dark:bg-blue-700 dark:text-blue-100">
|
||||
<span x-html="$icon('information-circle', 'w-3 h-3 mr-1')"></span>
|
||||
Info
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="copyCode(`<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>`)" class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
|
||||
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
|
||||
Copy Code
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ALERTS SECTION -->
|
||||
<section id="alerts">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||
<h2 class="text-2xl font-bold text-gray-800 dark:text-gray-200 mb-4 flex items-center">
|
||||
<span x-html="$icon('exclamation', 'w-6 h-6 mr-2 text-purple-600 dark:text-purple-400')"></span>
|
||||
Alerts & Toasts
|
||||
</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<button @click="showToastExample('success')" class="px-4 py-2 text-sm font-medium text-white bg-green-600 rounded-lg hover:bg-green-700">
|
||||
Show Success Toast
|
||||
</button>
|
||||
<button @click="showToastExample('error')" class="px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-lg hover:bg-red-700">
|
||||
Show Error Toast
|
||||
</button>
|
||||
<button @click="showToastExample('warning')" class="px-4 py-2 text-sm font-medium text-white bg-orange-600 rounded-lg hover:bg-orange-700">
|
||||
Show Warning Toast
|
||||
</button>
|
||||
<button @click="showToastExample('info')" class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700">
|
||||
Show Info Toast
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<button @click="copyCode(`Utils.showToast('Operation successful!', 'success');`)" class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 flex items-center">
|
||||
<span x-html="$icon('duplicate', 'w-4 h-4 mr-1')"></span>
|
||||
Copy Toast Code
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- MORE SECTIONS -->
|
||||
<section id="tables">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||
<h2 class="text-2xl font-bold text-gray-800 dark:text-gray-200 mb-4">Tables</h2>
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
See your existing table components in the vendor and user list pages.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="modals">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||
<h2 class="text-2xl font-bold text-gray-800 dark:text-gray-200 mb-4">Modals</h2>
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
Modal dialogs for confirmations and forms.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
{# ✅ CRITICAL: Load JavaScript file #}
|
||||
<script src="{{ url_for('static', path='admin/js/components.js') }}"></script>
|
||||
{% endblock %}
|
||||
318
app/templates/admin/icons.html
Normal file
318
app/templates/admin/icons.html
Normal file
@@ -0,0 +1,318 @@
|
||||
{# app/templates/admin/icons.html #}
|
||||
{% extends "admin/base.html" %}
|
||||
|
||||
{% block title %}Icons Browser{% endblock %}
|
||||
|
||||
{# ✅ CRITICAL: Link to Alpine.js component #}
|
||||
{% block alpine_data %}adminIcons(){% 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">
|
||||
Icons Browser
|
||||
</h2>
|
||||
<a href="/admin/dashboard" class="flex items-center 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-800 dark:border-gray-600 dark:hover:bg-gray-700">
|
||||
<span x-html="$icon('arrow-left', 'w-4 h-4 mr-2')"></span>
|
||||
Back to Dashboard
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Introduction -->
|
||||
<div class="mb-8 p-6 bg-gradient-to-r from-indigo-600 to-purple-600 rounded-lg shadow-lg text-white">
|
||||
<div class="flex items-start">
|
||||
<span x-html="$icon('photograph', 'w-10 h-10 mr-4 flex-shrink-0')"></span>
|
||||
<div>
|
||||
<h3 class="text-xl font-bold mb-2">Icon Library</h3>
|
||||
<p class="text-indigo-100 mb-3">
|
||||
Browse all <span x-text="allIcons.length"></span> available icons. Click any icon to copy its name or usage code.
|
||||
</p>
|
||||
<div class="flex items-center gap-4 text-sm">
|
||||
<div class="flex items-center">
|
||||
<span x-html="$icon('check-circle', 'w-4 h-4 mr-1')"></span>
|
||||
<span>Heroicons</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span x-html="$icon('check-circle', 'w-4 h-4 mr-1')"></span>
|
||||
<span>Dark Mode Support</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span x-html="$icon('check-circle', 'w-4 h-4 mr-1')"></span>
|
||||
<span>Fully Accessible</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search and Filter -->
|
||||
<div class="mb-6 bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<!-- Search Box -->
|
||||
<div class="flex-1">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Search Icons
|
||||
</label>
|
||||
<div class="relative">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<span x-html="$icon('search', 'w-5 h-5 text-gray-400')"></span>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
x-model="searchQuery"
|
||||
@input="filterIcons()"
|
||||
placeholder="Type to search... (e.g., 'user', 'arrow', 'check')"
|
||||
class="w-full pl-10 pr-4 py-2 text-sm border border-gray-300 rounded-lg focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:bg-gray-700 dark:border-gray-600 dark:text-gray-300"
|
||||
/>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
Found <span x-text="filteredIcons.length"></span> icon(s)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Category Pills -->
|
||||
<div class="flex-1">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Filter by Category
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<template x-for="category in categories.slice(0, 6)" :key="category.id">
|
||||
<button
|
||||
@click="setCategory(category.id)"
|
||||
class="inline-flex items-center px-3 py-1 text-xs font-medium rounded-full transition-colors"
|
||||
:class="activeCategory === category.id
|
||||
? 'bg-purple-600 text-white'
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600'"
|
||||
>
|
||||
<span x-html="$icon(category.icon, 'w-3 h-3 mr-1')"></span>
|
||||
<span x-text="category.name"></span>
|
||||
<span class="ml-1 opacity-75" x-text="'(' + getCategoryCount(category.id) + ')'"></span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- All Categories (Expandable) -->
|
||||
<div class="mt-4 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<details class="group">
|
||||
<summary class="cursor-pointer text-sm font-medium text-purple-600 dark:text-purple-400 hover:text-purple-700 flex items-center">
|
||||
<span x-html="$icon('chevron-right', 'w-4 h-4 mr-1 group-open:rotate-90 transition-transform')"></span>
|
||||
Show All Categories
|
||||
</summary>
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
<template x-for="category in categories" :key="category.id">
|
||||
<button
|
||||
@click="setCategory(category.id)"
|
||||
class="inline-flex items-center px-3 py-1.5 text-xs font-medium rounded-lg transition-colors"
|
||||
:class="activeCategory === category.id
|
||||
? 'bg-purple-600 text-white'
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600'"
|
||||
>
|
||||
<span x-html="$icon(category.icon, 'w-4 h-4 mr-2')"></span>
|
||||
<span x-text="category.name"></span>
|
||||
<span class="ml-2 px-2 py-0.5 bg-black bg-opacity-10 rounded-full" x-text="getCategoryCount(category.id)"></span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Active Category Info -->
|
||||
<div x-show="activeCategory !== 'all'" class="mb-4 flex items-center justify-between bg-purple-50 dark:bg-purple-900 dark:bg-opacity-20 border border-purple-200 dark:border-purple-700 rounded-lg px-4 py-3">
|
||||
<div class="flex items-center">
|
||||
<span x-html="$icon(getCategoryInfo(activeCategory).icon, 'w-5 h-5 text-purple-600 dark:text-purple-400 mr-2')"></span>
|
||||
<span class="text-sm font-medium text-purple-900 dark:text-purple-200">
|
||||
Showing <span x-text="getCategoryInfo(activeCategory).name"></span>
|
||||
(<span x-text="filteredIcons.length"></span> icons)
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
@click="setCategory('all')"
|
||||
class="text-sm text-purple-600 dark:text-purple-400 hover:text-purple-700 dark:hover:text-purple-300 flex items-center"
|
||||
>
|
||||
<span x-html="$icon('close', 'w-4 h-4 mr-1')"></span>
|
||||
Clear Filter
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Icons Grid -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||
<!-- Empty State -->
|
||||
<div x-show="filteredIcons.length === 0" class="text-center py-12">
|
||||
<span x-html="$icon('exclamation', 'w-16 h-16 mx-auto text-gray-400 mb-4')"></span>
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-2">No icons found</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400 mb-4">Try adjusting your search or filter</p>
|
||||
<button
|
||||
@click="searchQuery = ''; setCategory('all')"
|
||||
class="px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700"
|
||||
>
|
||||
Clear Filters
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Icons Grid -->
|
||||
<div x-show="filteredIcons.length > 0" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-3">
|
||||
<template x-for="icon in filteredIcons" :key="icon.name">
|
||||
<div
|
||||
@click="selectIcon(icon.name)"
|
||||
class="group relative flex flex-col items-center justify-center p-4 bg-gray-50 dark:bg-gray-900 rounded-lg hover:bg-purple-50 dark:hover:bg-purple-900 dark:hover:bg-opacity-20 cursor-pointer transition-all hover:shadow-md border-2 border-transparent hover:border-purple-300 dark:hover:border-purple-700"
|
||||
:class="{ 'border-purple-500 bg-purple-50 dark:bg-purple-900 dark:bg-opacity-30': selectedIcon === icon.name }"
|
||||
>
|
||||
<!-- Icon -->
|
||||
<div class="text-gray-600 dark:text-gray-400 group-hover:text-purple-600 dark:group-hover:text-purple-400 transition-colors">
|
||||
<span x-html="$icon(icon.name, 'w-8 h-8')"></span>
|
||||
</div>
|
||||
|
||||
<!-- Icon Name -->
|
||||
<p class="mt-2 text-xs text-center text-gray-600 dark:text-gray-400 font-mono truncate w-full px-1" :title="icon.name" x-text="icon.name"></p>
|
||||
|
||||
<!-- Hover Actions -->
|
||||
<div class="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity bg-white dark:bg-gray-800 bg-opacity-90 dark:bg-opacity-90 rounded-lg">
|
||||
<div class="flex gap-1">
|
||||
<button
|
||||
@click.stop="copyIconName(icon.name)"
|
||||
class="p-2 bg-purple-600 text-white rounded hover:bg-purple-700 transition-colors"
|
||||
title="Copy name"
|
||||
>
|
||||
<span x-html="$icon('duplicate', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
<button
|
||||
@click.stop="copyIconUsage(icon.name)"
|
||||
class="p-2 bg-indigo-600 text-white rounded hover:bg-indigo-700 transition-colors"
|
||||
title="Copy usage"
|
||||
>
|
||||
<span x-html="$icon('code', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Selected Icon Details -->
|
||||
<div x-show="selectedIcon" class="mt-6 bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-4">
|
||||
Selected Icon: <span class="font-mono text-purple-600 dark:text-purple-400" x-text="selectedIcon"></span>
|
||||
</h3>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<!-- Preview -->
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">Preview</h4>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-8 flex items-center justify-center gap-6">
|
||||
<div class="text-gray-800 dark:text-gray-200">
|
||||
<span x-html="$icon(selectedIcon, 'w-12 h-12')"></span>
|
||||
</div>
|
||||
<div class="text-gray-800 dark:text-gray-200">
|
||||
<span x-html="$icon(selectedIcon, 'w-16 h-16')"></span>
|
||||
</div>
|
||||
<div class="text-gray-800 dark:text-gray-200">
|
||||
<span x-html="$icon(selectedIcon, 'w-24 h-24')"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Usage Code -->
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">Usage Code</h4>
|
||||
|
||||
<!-- Alpine.js Usage -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Alpine.js (Recommended)</label>
|
||||
<div class="relative">
|
||||
<pre class="bg-gray-900 text-gray-100 rounded-lg p-3 text-xs overflow-x-auto"><code x-text="'x-html="$icon(\'' + selectedIcon + '\', \'w-5 h-5\')"'"></code></pre>
|
||||
<button
|
||||
@click="copyIconUsage(selectedIcon)"
|
||||
class="absolute top-2 right-2 p-1.5 bg-gray-800 text-gray-300 rounded hover:bg-gray-700 transition-colors"
|
||||
title="Copy"
|
||||
>
|
||||
<span x-html="$icon('duplicate', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Icon Name -->
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Icon Name</label>
|
||||
<div class="relative">
|
||||
<pre class="bg-gray-900 text-gray-100 rounded-lg p-3 text-xs overflow-x-auto"><code x-text="selectedIcon"></code></pre>
|
||||
<button
|
||||
@click="copyIconName(selectedIcon)"
|
||||
class="absolute top-2 right-2 p-1.5 bg-gray-800 text-gray-300 rounded hover:bg-gray-700 transition-colors"
|
||||
title="Copy"
|
||||
>
|
||||
<span x-html="$icon('duplicate', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Size Examples -->
|
||||
<div class="mt-6">
|
||||
<h4 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">Common Sizes</h4>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
|
||||
<div class="flex items-center gap-8">
|
||||
<div class="text-center">
|
||||
<div class="text-gray-800 dark:text-gray-200 mb-1">
|
||||
<span x-html="$icon(selectedIcon, 'w-4 h-4')"></span>
|
||||
</div>
|
||||
<code class="text-xs text-gray-600 dark:text-gray-400">w-4 h-4</code>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-gray-800 dark:text-gray-200 mb-1">
|
||||
<span x-html="$icon(selectedIcon, 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<code class="text-xs text-gray-600 dark:text-gray-400">w-5 h-5</code>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-gray-800 dark:text-gray-200 mb-1">
|
||||
<span x-html="$icon(selectedIcon, 'w-6 h-6')"></span>
|
||||
</div>
|
||||
<code class="text-xs text-gray-600 dark:text-gray-400">w-6 h-6</code>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-gray-800 dark:text-gray-200 mb-1">
|
||||
<span x-html="$icon(selectedIcon, 'w-8 h-8')"></span>
|
||||
</div>
|
||||
<code class="text-xs text-gray-600 dark:text-gray-400">w-8 h-8</code>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-gray-800 dark:text-gray-200 mb-1">
|
||||
<span x-html="$icon(selectedIcon, 'w-12 h-12')"></span>
|
||||
</div>
|
||||
<code class="text-xs text-gray-600 dark:text-gray-400">w-12 h-12</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Usage Guide -->
|
||||
<div class="mt-6 bg-blue-50 dark:bg-gray-800 border border-blue-200 dark:border-gray-700 rounded-lg p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-3 flex items-center">
|
||||
<span x-html="$icon('book-open', 'w-5 h-5 mr-2 text-blue-600')"></span>
|
||||
How to Use Icons
|
||||
</h3>
|
||||
<div class="grid md:grid-cols-2 gap-6 text-sm">
|
||||
<div>
|
||||
<h4 class="font-semibold text-gray-700 dark:text-gray-300 mb-2">In Alpine.js Templates</h4>
|
||||
<p class="text-gray-600 dark:text-gray-400 mb-2">Use the <code class="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded">x-html</code> directive:</p>
|
||||
<pre class="bg-gray-900 text-gray-100 rounded p-2 text-xs overflow-x-auto"><code><span x-html="$icon('home', 'w-5 h-5')"></span></code></pre>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold text-gray-700 dark:text-gray-300 mb-2">Customizing Size & Color</h4>
|
||||
<p class="text-gray-600 dark:text-gray-400 mb-2">Use Tailwind classes:</p>
|
||||
<pre class="bg-gray-900 text-gray-100 rounded p-2 text-xs overflow-x-auto"><code><span x-html="$icon('check', 'w-6 h-6 text-green-500')"></span></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
{# ✅ CRITICAL: Load JavaScript file #}
|
||||
<script src="{{ url_for('static', path='admin/js/icons-page.js') }}"></script>
|
||||
{% endblock %}
|
||||
644
app/templates/admin/test-auth-flow.html
Normal file
644
app/templates/admin/test-auth-flow.html
Normal file
@@ -0,0 +1,644 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Auth Flow Testing - Admin Panel</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #666;
|
||||
margin-bottom: 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.test-section {
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #3b82f6;
|
||||
}
|
||||
|
||||
.test-section h2 {
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.test-description {
|
||||
color: #666;
|
||||
margin-bottom: 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.test-steps {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.test-steps ol {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.test-steps li {
|
||||
margin-bottom: 8px;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.expected-result {
|
||||
background: #e8f5e9;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid #4caf50;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.expected-result strong {
|
||||
color: #2e7d32;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.expected-result ul {
|
||||
margin-left: 20px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #dc2626;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: #f59e0b;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-warning:hover {
|
||||
background: #d97706;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #10b981;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background: #059669;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6b7280;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #4b5563;
|
||||
}
|
||||
|
||||
.status-panel {
|
||||
background: #1e293b;
|
||||
color: #e2e8f0;
|
||||
padding: 20px;
|
||||
border-radius: 6px;
|
||||
margin-top: 30px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.status-panel h3 {
|
||||
color: #38bdf8;
|
||||
margin-bottom: 15px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.status-value {
|
||||
color: #34d399;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-value.false {
|
||||
color: #f87171;
|
||||
}
|
||||
|
||||
.log-level-control {
|
||||
background: #fef3c7;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 30px;
|
||||
border-left: 4px solid #f59e0b;
|
||||
}
|
||||
|
||||
.log-level-control h3 {
|
||||
color: #92400e;
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.log-level-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.log-level-buttons button {
|
||||
padding: 8px 16px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.warning-box {
|
||||
background: #fef2f2;
|
||||
border: 1px solid #fecaca;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.warning-box h3 {
|
||||
color: #991b1b;
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.warning-box ul {
|
||||
margin-left: 20px;
|
||||
color: #7f1d1d;
|
||||
}
|
||||
|
||||
.warning-box li {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🧪 Auth Flow Testing</h1>
|
||||
<p class="subtitle">Comprehensive testing for the Jinja2 migration auth loop fix</p>
|
||||
|
||||
<!-- Log Level Control -->
|
||||
<div class="log-level-control">
|
||||
<h3>📊 Log Level Control</h3>
|
||||
<p style="color: #78350f; font-size: 13px; margin-bottom: 10px;">
|
||||
Change logging verbosity for login.js and api-client.js
|
||||
</p>
|
||||
<div class="log-level-buttons">
|
||||
<button onclick="setLogLevel(0)" class="btn-secondary">0 - None</button>
|
||||
<button onclick="setLogLevel(1)" class="btn-danger">1 - Errors Only</button>
|
||||
<button onclick="setLogLevel(2)" class="btn-warning">2 - Warnings</button>
|
||||
<button onclick="setLogLevel(3)" class="btn-success">3 - Info (Production)</button>
|
||||
<button onclick="setLogLevel(4)" class="btn-primary">4 - Debug (Full)</button>
|
||||
</div>
|
||||
<p style="color: #78350f; font-size: 12px; margin-top: 10px; font-style: italic;">
|
||||
Current levels: LOGIN = <span id="currentLoginLevel">4</span>, API = <span id="currentApiLevel">3</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Test 1: Clean Slate -->
|
||||
<div class="test-section">
|
||||
<h2>Test 1: Clean Slate - Fresh Login Flow</h2>
|
||||
<p class="test-description">
|
||||
Tests the complete login flow from scratch with no existing tokens.
|
||||
</p>
|
||||
|
||||
<div class="test-steps">
|
||||
<strong>Steps:</strong>
|
||||
<ol>
|
||||
<li>Click "Clear All Data" below</li>
|
||||
<li>Click "Navigate to /admin"</li>
|
||||
<li>Observe browser behavior and console logs</li>
|
||||
<li>You should land on login page</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="expected-result">
|
||||
<strong>✅ Expected Result:</strong>
|
||||
<ul>
|
||||
<li>Single redirect: /admin → /admin/login</li>
|
||||
<li>Login page loads with NO API calls to /admin/auth/me</li>
|
||||
<li>No loops, no errors in console</li>
|
||||
<li>Form is ready for input</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="clearAllData()" class="btn-danger">Clear All Data</button>
|
||||
<button onclick="navigateToAdmin()" class="btn-primary">Navigate to /admin</button>
|
||||
<button onclick="navigateToLogin()" class="btn-secondary">Go to Login</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 2: Login Success -->
|
||||
<div class="test-section">
|
||||
<h2>Test 2: Successful Login</h2>
|
||||
<p class="test-description">
|
||||
Tests that login works correctly and redirects to dashboard.
|
||||
</p>
|
||||
|
||||
<div class="test-steps">
|
||||
<strong>Steps:</strong>
|
||||
<ol>
|
||||
<li>Ensure you're on /admin/login</li>
|
||||
<li>Enter valid admin credentials</li>
|
||||
<li>Click "Login"</li>
|
||||
<li>Observe redirect and dashboard load</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="expected-result">
|
||||
<strong>✅ Expected Result:</strong>
|
||||
<ul>
|
||||
<li>Login API call succeeds (check Network tab)</li>
|
||||
<li>Token stored in localStorage</li>
|
||||
<li>Success message shows briefly</li>
|
||||
<li>Redirect to /admin/dashboard after 500ms</li>
|
||||
<li>Dashboard loads with stats and recent vendors</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="navigateToLogin()" class="btn-primary">Go to Login Page</button>
|
||||
<button onclick="checkAuthStatus()" class="btn-secondary">Check Auth Status</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 3: Dashboard Refresh -->
|
||||
<div class="test-section">
|
||||
<h2>Test 3: Dashboard Refresh (Authenticated)</h2>
|
||||
<p class="test-description">
|
||||
Tests that refreshing the dashboard works without redirect loops.
|
||||
</p>
|
||||
|
||||
<div class="test-steps">
|
||||
<strong>Steps:</strong>
|
||||
<ol>
|
||||
<li>Complete Test 2 (login successfully)</li>
|
||||
<li>On dashboard, press F5 or click "Refresh Page"</li>
|
||||
<li>Observe page reload behavior</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="expected-result">
|
||||
<strong>✅ Expected Result:</strong>
|
||||
<ul>
|
||||
<li>Dashboard reloads normally</li>
|
||||
<li>No redirects to login</li>
|
||||
<li>Stats and vendors load correctly</li>
|
||||
<li>No console errors</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="navigateToDashboard()" class="btn-primary">Go to Dashboard</button>
|
||||
<button onclick="window.location.reload()" class="btn-secondary">Refresh Page</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 4: Expired Token -->
|
||||
<div class="test-section">
|
||||
<h2>Test 4: Expired Token Handling</h2>
|
||||
<p class="test-description">
|
||||
Tests that expired tokens are handled gracefully with redirect to login.
|
||||
</p>
|
||||
|
||||
<div class="test-steps">
|
||||
<strong>Steps:</strong>
|
||||
<ol>
|
||||
<li>Click "Set Expired Token"</li>
|
||||
<li>Click "Navigate to Dashboard"</li>
|
||||
<li>Observe authentication failure and redirect</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="expected-result">
|
||||
<strong>✅ Expected Result:</strong>
|
||||
<ul>
|
||||
<li>Server detects expired token</li>
|
||||
<li>Returns 401 Unauthorized</li>
|
||||
<li>Browser redirects to /admin/login</li>
|
||||
<li>Token is cleared from localStorage</li>
|
||||
<li>No infinite loops</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="setExpiredToken()" class="btn-warning">Set Expired Token</button>
|
||||
<button onclick="navigateToDashboard()" class="btn-primary">Navigate to Dashboard</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 5: Direct Dashboard Access (No Token) -->
|
||||
<div class="test-section">
|
||||
<h2>Test 5: Direct Dashboard Access (Unauthenticated)</h2>
|
||||
<p class="test-description">
|
||||
Tests that accessing dashboard without token redirects to login.
|
||||
</p>
|
||||
|
||||
<div class="test-steps">
|
||||
<strong>Steps:</strong>
|
||||
<ol>
|
||||
<li>Click "Clear All Data"</li>
|
||||
<li>Click "Navigate to Dashboard"</li>
|
||||
<li>Observe immediate redirect to login</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="expected-result">
|
||||
<strong>✅ Expected Result:</strong>
|
||||
<ul>
|
||||
<li>Redirect from /admin/dashboard to /admin/login</li>
|
||||
<li>No API calls attempted</li>
|
||||
<li>Login page loads correctly</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="clearAllData()" class="btn-danger">Clear All Data</button>
|
||||
<button onclick="navigateToDashboard()" class="btn-primary">Navigate to Dashboard</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 6: Login Page with Valid Token -->
|
||||
<div class="test-section">
|
||||
<h2>Test 6: Login Page with Valid Token</h2>
|
||||
<p class="test-description">
|
||||
Tests what happens when user visits login page while already authenticated.
|
||||
</p>
|
||||
|
||||
<div class="test-steps">
|
||||
<strong>Steps:</strong>
|
||||
<ol>
|
||||
<li>Login successfully (Test 2)</li>
|
||||
<li>Click "Go to Login Page" below</li>
|
||||
<li>Observe behavior</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="expected-result">
|
||||
<strong>✅ Expected Result:</strong>
|
||||
<ul>
|
||||
<li>Login page loads</li>
|
||||
<li>Existing token is cleared (init() clears it)</li>
|
||||
<li>Form is displayed normally</li>
|
||||
<li>NO redirect loops</li>
|
||||
<li>NO API calls to validate token</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="setValidToken()" class="btn-success">Set Valid Token (Mock)</button>
|
||||
<button onclick="navigateToLogin()" class="btn-primary">Go to Login Page</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Panel -->
|
||||
<div class="status-panel">
|
||||
<h3>🔍 Current Auth Status</h3>
|
||||
<div id="statusDisplay">
|
||||
<div class="status-item">
|
||||
<span class="status-label">Current URL:</span>
|
||||
<span class="status-value" id="currentUrl">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Has admin_token:</span>
|
||||
<span class="status-value" id="hasToken">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Has admin_user:</span>
|
||||
<span class="status-value" id="hasUser">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Token Preview:</span>
|
||||
<span class="status-value" id="tokenPreview">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Username:</span>
|
||||
<span class="status-value" id="username">-</span>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="updateStatus()" style="margin-top: 15px; background: #38bdf8; color: #0f172a; padding: 8px 16px; border-radius: 4px; font-size: 12px; cursor: pointer; border: none;">
|
||||
🔄 Refresh Status
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Warning Box -->
|
||||
<div class="warning-box">
|
||||
<h3>⚠️ Important Notes</h3>
|
||||
<ul>
|
||||
<li>Always check browser console for detailed logs</li>
|
||||
<li>Use Network tab to see actual HTTP requests and redirects</li>
|
||||
<li>Clear browser cache if you see unexpected behavior</li>
|
||||
<li>Make sure FastAPI server is running on localhost:8000</li>
|
||||
<li>Valid admin credentials required for login tests</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Update status display
|
||||
function updateStatus() {
|
||||
const token = localStorage.getItem('admin_token');
|
||||
const userStr = localStorage.getItem('admin_user');
|
||||
let user = null;
|
||||
|
||||
try {
|
||||
user = userStr ? JSON.parse(userStr) : null;
|
||||
} catch (e) {
|
||||
console.error('Failed to parse user data:', e);
|
||||
}
|
||||
|
||||
document.getElementById('currentUrl').textContent = window.location.href;
|
||||
|
||||
const hasTokenEl = document.getElementById('hasToken');
|
||||
hasTokenEl.textContent = token ? 'Yes' : 'No';
|
||||
hasTokenEl.className = token ? 'status-value' : 'status-value false';
|
||||
|
||||
const hasUserEl = document.getElementById('hasUser');
|
||||
hasUserEl.textContent = user ? 'Yes' : 'No';
|
||||
hasUserEl.className = user ? 'status-value' : 'status-value false';
|
||||
|
||||
document.getElementById('tokenPreview').textContent = token
|
||||
? token.substring(0, 30) + '...'
|
||||
: 'No token';
|
||||
|
||||
document.getElementById('username').textContent = user?.username || 'Not logged in';
|
||||
|
||||
console.log('📊 Status Updated:', {
|
||||
hasToken: !!token,
|
||||
hasUser: !!user,
|
||||
user: user
|
||||
});
|
||||
}
|
||||
|
||||
// Test functions
|
||||
function clearAllData() {
|
||||
console.log('🗑️ Clearing all localStorage data...');
|
||||
localStorage.clear();
|
||||
console.log('✅ All data cleared');
|
||||
alert('✅ All localStorage data cleared!\n\nCheck console for details.');
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
function navigateToAdmin() {
|
||||
console.log('🚀 Navigating to /admin...');
|
||||
window.location.href = '/admin';
|
||||
}
|
||||
|
||||
function navigateToLogin() {
|
||||
console.log('🚀 Navigating to /admin/login...');
|
||||
window.location.href = '/admin/login';
|
||||
}
|
||||
|
||||
function navigateToDashboard() {
|
||||
console.log('🚀 Navigating to /admin/dashboard...');
|
||||
window.location.href = '/admin/dashboard';
|
||||
}
|
||||
|
||||
function checkAuthStatus() {
|
||||
updateStatus();
|
||||
alert('Check console and status panel for auth details.');
|
||||
}
|
||||
|
||||
function setExpiredToken() {
|
||||
const expiredToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.invalidexpiredtoken';
|
||||
console.log('⚠️ Setting expired/invalid token...');
|
||||
localStorage.setItem('admin_token', expiredToken);
|
||||
localStorage.setItem('admin_user', JSON.stringify({
|
||||
id: 1,
|
||||
username: 'test_expired',
|
||||
role: 'admin'
|
||||
}));
|
||||
console.log('✅ Expired token set');
|
||||
alert('⚠️ Expired token set!\n\nNow try navigating to dashboard.');
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
function setValidToken() {
|
||||
// This is a mock token - won't actually work with backend
|
||||
const mockToken = 'mock_valid_token_' + Date.now();
|
||||
console.log('✅ Setting mock valid token...');
|
||||
localStorage.setItem('admin_token', mockToken);
|
||||
localStorage.setItem('admin_user', JSON.stringify({
|
||||
id: 1,
|
||||
username: 'test_user',
|
||||
role: 'admin'
|
||||
}));
|
||||
console.log('✅ Mock token set (will not work with real backend)');
|
||||
alert('✅ Mock token set!\n\nNote: This is a fake token and won\'t work with the real backend.');
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
// Log level control
|
||||
function setLogLevel(level) {
|
||||
console.log(`📊 Setting log level to ${level}...`);
|
||||
|
||||
// Note: This only works if login.js and api-client.js are loaded
|
||||
// In production, you'd need to reload the page or use a more sophisticated approach
|
||||
if (typeof LOG_LEVEL !== 'undefined') {
|
||||
window.LOG_LEVEL = level;
|
||||
document.getElementById('currentLoginLevel').textContent = level;
|
||||
console.log('✅ LOGIN log level set to', level);
|
||||
} else {
|
||||
console.warn('⚠️ LOG_LEVEL not found (login.js not loaded)');
|
||||
}
|
||||
|
||||
if (typeof API_LOG_LEVEL !== 'undefined') {
|
||||
window.API_LOG_LEVEL = level;
|
||||
document.getElementById('currentApiLevel').textContent = level;
|
||||
console.log('✅ API log level set to', level);
|
||||
} else {
|
||||
console.warn('⚠️ API_LOG_LEVEL not found (api-client.js not loaded)');
|
||||
}
|
||||
|
||||
alert(`Log level set to ${level}\n\n0 = None\n1 = Errors\n2 = Warnings\n3 = Info\n4 = Debug\n\nNote: Changes apply to current page. Reload to apply to all scripts.`);
|
||||
}
|
||||
|
||||
// Initialize status on load
|
||||
updateStatus();
|
||||
|
||||
// Auto-refresh status every 2 seconds
|
||||
setInterval(updateStatus, 2000);
|
||||
|
||||
console.log('🧪 Auth Flow Testing Script Loaded');
|
||||
console.log('📊 Use the buttons above to run tests');
|
||||
console.log('🔍 Watch browser console and Network tab for details');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1060
app/templates/admin/test-vendors-users-migration.html
Normal file
1060
app/templates/admin/test-vendors-users-migration.html
Normal file
File diff suppressed because it is too large
Load Diff
205
app/templates/admin/testing-hub.html
Normal file
205
app/templates/admin/testing-hub.html
Normal file
@@ -0,0 +1,205 @@
|
||||
{# app/templates/admin/testing-hub.html #}
|
||||
{% extends "admin/base.html" %}
|
||||
|
||||
{% block title %}Testing Hub{% endblock %}
|
||||
|
||||
{# ✅ CRITICAL: Link to Alpine.js component #}
|
||||
{% block alpine_data %}adminTestingHub(){% 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">
|
||||
Testing Hub
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- Introduction Card -->
|
||||
<div class="bg-gradient-to-r from-purple-600 to-indigo-600 rounded-lg shadow-lg p-6 mb-8">
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0 text-white">
|
||||
<span x-html="$icon('beaker', 'w-12 h-12')"></span>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<h3 class="text-xl font-bold mb-2 text-white">Testing & QA Tools</h3>
|
||||
<p class="text-white opacity-90">
|
||||
Comprehensive testing tools for manual QA, feature verification, and bug reproduction.
|
||||
These pages help you test specific flows without writing code.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Stats -->
|
||||
<div class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-3 mr-4 text-blue-500 bg-blue-100 rounded-full dark:text-blue-100 dark:bg-blue-500">
|
||||
<span x-html="$icon('clipboard-list', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Test Suites</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.totalSuites"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-3 mr-4 text-green-500 bg-green-100 rounded-full dark:text-green-100 dark:bg-green-500">
|
||||
<span x-html="$icon('check-circle', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Test Cases</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.totalTests + '+'"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<span x-html="$icon('lightning-bolt', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Features Covered</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.coverage"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<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('clock', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Quick Tests</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.avgDuration"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test Suites Grid -->
|
||||
<div class="grid gap-6 mb-8 md:grid-cols-2">
|
||||
<template x-for="suite in testSuites" :key="suite.id">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden hover:shadow-xl transition-shadow duration-300">
|
||||
<div class="p-4 bg-gradient-to-r" :class="getColorClasses(suite.color).gradient">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-xl font-semibold text-white flex items-center">
|
||||
<span x-html="$icon(suite.icon, 'w-6 h-6 mr-2 text-white')"></span>
|
||||
<span x-text="suite.name"></span>
|
||||
</h3>
|
||||
<span class="px-3 py-1 bg-white bg-opacity-25 rounded-full text-xs text-white font-semibold">
|
||||
<span x-text="suite.testCount"></span> Tests
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<p class="text-gray-600 dark:text-gray-400 mb-4" x-text="suite.description"></p>
|
||||
|
||||
<div class="space-y-2 mb-6">
|
||||
<template x-for="feature in suite.features" :key="feature">
|
||||
<div class="flex items-start text-sm">
|
||||
<span x-html="$icon('check-circle', 'w-4 h-4 text-green-500 mr-2 mt-0.5 flex-shrink-0')"></span>
|
||||
<span class="text-gray-600 dark:text-gray-400" x-text="feature"></span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
@click="goToTest(suite.url)"
|
||||
class="flex-1 flex items-center justify-center px-4 py-2 text-sm font-medium text-white rounded-lg transition-colors"
|
||||
:class="getColorClasses(suite.color).button">
|
||||
<span x-html="$icon('play', 'w-4 h-4 mr-2 text-white')"></span>
|
||||
Run Tests
|
||||
</button>
|
||||
<button class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors">
|
||||
<span x-html="$icon('information-circle', 'w-5 h-5')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Best Practices -->
|
||||
<div class="px-4 py-3 mb-8 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
||||
<h2 class="mb-4 text-xl font-semibold text-gray-800 dark:text-gray-200 flex items-center">
|
||||
<span x-html="$icon('light-bulb', 'w-6 h-6 mr-2 text-yellow-500')"></span>
|
||||
Testing Best Practices
|
||||
</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-700 dark:text-gray-300 mb-2">Before Testing</h3>
|
||||
<ul class="space-y-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
<li class="flex items-start">
|
||||
<span class="text-purple-600 mr-2">•</span>
|
||||
<span>Ensure FastAPI server is running on localhost:8000</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<span class="text-purple-600 mr-2">•</span>
|
||||
<span>Open browser DevTools (F12) to see console logs</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<span class="text-purple-600 mr-2">•</span>
|
||||
<span>Check Network tab for API requests</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<span class="text-purple-600 mr-2">•</span>
|
||||
<span>Clear localStorage before starting fresh tests</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-700 dark:text-gray-300 mb-2">During Testing</h3>
|
||||
<ul class="space-y-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
<li class="flex items-start">
|
||||
<span class="text-purple-600 mr-2">•</span>
|
||||
<span>Follow test steps in order</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<span class="text-purple-600 mr-2">•</span>
|
||||
<span>Check expected results against actual behavior</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<span class="text-purple-600 mr-2">•</span>
|
||||
<span>Look for errors in console and network tabs</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<span class="text-purple-600 mr-2">•</span>
|
||||
<span>Take screenshots if you find bugs</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional Resources -->
|
||||
<div class="bg-blue-50 dark:bg-gray-800 border border-blue-200 dark:border-gray-700 rounded-lg p-6 mb-8">
|
||||
<h3 class="mb-3 text-lg font-semibold text-gray-800 dark:text-gray-200 flex items-center">
|
||||
<span x-html="$icon('book-open', 'w-5 h-5 mr-2 text-blue-600')"></span>
|
||||
Additional Resources
|
||||
</h3>
|
||||
<div class="grid md:grid-cols-3 gap-4">
|
||||
<a href="/admin/components" class="block p-4 bg-white dark:bg-gray-700 rounded-lg hover:shadow-md transition-shadow">
|
||||
<h4 class="font-semibold text-gray-700 dark:text-gray-200 mb-1">Component Library</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">View all available UI components</p>
|
||||
</a>
|
||||
<a href="/admin/icons" class="block p-4 bg-white dark:bg-gray-700 rounded-lg hover:shadow-md transition-shadow">
|
||||
<h4 class="font-semibold text-gray-700 dark:text-gray-200 mb-1">Icons Browser</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Browse all available icons</p>
|
||||
</a>
|
||||
<a href="#" class="block p-4 bg-white dark:bg-gray-700 rounded-lg hover:shadow-md transition-shadow">
|
||||
<h4 class="font-semibold text-gray-700 dark:text-gray-200 mb-1">API Documentation</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">FastAPI endpoint reference</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
{# ✅ CRITICAL: Load JavaScript file #}
|
||||
<script src="{{ url_for('static', path='admin/js/testing-hub.js') }}"></script>
|
||||
{% endblock %}
|
||||
268
app/templates/admin/vendor-detail.html
Normal file
268
app/templates/admin/vendor-detail.html
Normal file
@@ -0,0 +1,268 @@
|
||||
{# app/templates/admin/vendor-detail.html #}
|
||||
{% extends "admin/base.html" %}
|
||||
|
||||
{% block title %}Vendor Details{% endblock %}
|
||||
|
||||
{% block alpine_data %}adminVendorDetail(){% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Page Header -->
|
||||
<div class="flex items-center justify-between my-6">
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200" x-text="vendor?.name || 'Vendor Details'">
|
||||
Vendor Details
|
||||
</h2>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1" x-show="vendor">
|
||||
<span x-text="vendor?.vendor_code"></span>
|
||||
<span class="text-gray-400 mx-2">•</span>
|
||||
<span x-text="vendor?.subdomain"></span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<a
|
||||
:href="`/admin/vendors/${vendorCode}/edit`"
|
||||
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 focus:shadow-outline-purple">
|
||||
<span x-html="$icon('edit', 'w-4 h-4 mr-2')"></span>
|
||||
Edit Vendor
|
||||
</a>
|
||||
<a href="/admin/vendors"
|
||||
class="flex items-center 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 dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800 hover:border-gray-400 focus:outline-none">
|
||||
<span x-html="$icon('arrow-left', 'w-4 h-4 mr-2')"></span>
|
||||
Back
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<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 vendor details...</p>
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
<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 vendor</p>
|
||||
<p class="text-sm" x-text="error"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vendor Details -->
|
||||
<div x-show="!loading && vendor">
|
||||
<!-- Status Cards -->
|
||||
<div class="grid gap-6 mb-8 md:grid-cols-4">
|
||||
<!-- Verification Status -->
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-3 mr-4 rounded-full"
|
||||
:class="vendor?.is_verified ? 'text-green-500 bg-green-100 dark:text-green-100 dark:bg-green-500' : 'text-orange-500 bg-orange-100 dark:text-orange-100 dark:bg-orange-500'">
|
||||
<span x-html="$icon(vendor?.is_verified ? 'badge-check' : 'clock', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
Verification
|
||||
</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="vendor?.is_verified ? 'Verified' : 'Pending'">
|
||||
-
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Active Status -->
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-3 mr-4 rounded-full"
|
||||
:class="vendor?.is_active ? 'text-green-500 bg-green-100 dark:text-green-100 dark:bg-green-500' : 'text-red-500 bg-red-100 dark:text-red-100 dark:bg-red-500'">
|
||||
<span x-html="$icon(vendor?.is_active ? 'check-circle' : 'x-circle', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
Status
|
||||
</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="vendor?.is_active ? 'Active' : 'Inactive'">
|
||||
-
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Created Date -->
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-3 mr-4 text-blue-500 bg-blue-100 rounded-full dark:text-blue-100 dark:bg-blue-500">
|
||||
<span x-html="$icon('calendar', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
Created
|
||||
</p>
|
||||
<p class="text-sm font-semibold text-gray-700 dark:text-gray-200" x-text="formatDate(vendor?.created_at)">
|
||||
-
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Updated Date -->
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<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('refresh', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
Last Updated
|
||||
</p>
|
||||
<p class="text-sm font-semibold text-gray-700 dark:text-gray-200" x-text="formatDate(vendor?.updated_at)">
|
||||
-
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Info Cards -->
|
||||
<div class="grid gap-6 mb-8 md:grid-cols-2">
|
||||
<!-- Basic Information -->
|
||||
<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">
|
||||
Basic Information
|
||||
</h3>
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Vendor Code</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="vendor?.vendor_code || '-'">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Name</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="vendor?.name || '-'">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Subdomain</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="vendor?.subdomain || '-'">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Description</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="vendor?.description || 'No description provided'">-</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contact Information -->
|
||||
<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">
|
||||
Contact Information
|
||||
</h3>
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Owner Email</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="vendor?.owner_email || '-'">-</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">Owner's authentication email</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Contact Email</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="vendor?.contact_email || '-'">-</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">Public business contact</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Phone</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="vendor?.contact_phone || '-'">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Website</p>
|
||||
<a
|
||||
x-show="vendor?.website"
|
||||
:href="vendor?.website"
|
||||
target="_blank"
|
||||
class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400"
|
||||
x-text="vendor?.website">
|
||||
</a>
|
||||
<span x-show="!vendor?.website" class="text-sm text-gray-700 dark:text-gray-300">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Business Details -->
|
||||
<div class="px-4 py-3 mb-8 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">
|
||||
Business Details
|
||||
</h3>
|
||||
<div class="grid gap-6 md:grid-cols-2">
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-2">Business Address</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300 whitespace-pre-line" x-text="vendor?.business_address || 'No address provided'">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-2">Tax Number</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="vendor?.tax_number || 'Not provided'">-</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Owner Information -->
|
||||
<div class="px-4 py-3 mb-8 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">
|
||||
Owner Information
|
||||
</h3>
|
||||
<div class="grid gap-6 md:grid-cols-3">
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-2">Owner User ID</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="vendor?.owner_user_id || '-'">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-2">Owner Username</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="vendor?.owner_username || '-'">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-2">Owner Email</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="vendor?.owner_email || '-'">-</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Marketplace URLs -->
|
||||
<div class="px-4 py-3 mb-8 bg-white rounded-lg shadow-md dark:bg-gray-800" x-show="vendor?.letzshop_csv_url_fr || vendor?.letzshop_csv_url_en || vendor?.letzshop_csv_url_de">
|
||||
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
||||
Marketplace CSV URLs
|
||||
</h3>
|
||||
<div class="space-y-3">
|
||||
<div x-show="vendor?.letzshop_csv_url_fr">
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-1">French (FR)</p>
|
||||
<a
|
||||
:href="vendor?.letzshop_csv_url_fr"
|
||||
target="_blank"
|
||||
class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 break-all"
|
||||
x-text="vendor?.letzshop_csv_url_fr">
|
||||
</a>
|
||||
</div>
|
||||
<div x-show="vendor?.letzshop_csv_url_en">
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-1">English (EN)</p>
|
||||
<a
|
||||
:href="vendor?.letzshop_csv_url_en"
|
||||
target="_blank"
|
||||
class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 break-all"
|
||||
x-text="vendor?.letzshop_csv_url_en">
|
||||
</a>
|
||||
</div>
|
||||
<div x-show="vendor?.letzshop_csv_url_de">
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-1">German (DE)</p>
|
||||
<a
|
||||
:href="vendor?.letzshop_csv_url_de"
|
||||
target="_blank"
|
||||
class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400 break-all"
|
||||
x-text="vendor?.letzshop_csv_url_de">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex justify-end gap-3">
|
||||
<button
|
||||
@click="deleteVendor()"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-red-600 border border-transparent rounded-lg hover:bg-red-700 focus:outline-none focus:shadow-outline-red">
|
||||
<span x-html="$icon('delete', 'w-4 h-4 mr-2')"></span>
|
||||
Delete Vendor
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
<script src="{{ url_for('static', path='admin/js/vendor-detail.js') }}"></script>
|
||||
{% endblock %}
|
||||
@@ -13,12 +13,12 @@
|
||||
Edit Vendor
|
||||
</h2>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1" x-show="vendor">
|
||||
<span x-text="vendor?.name"></span>
|
||||
<span class="text-gray-400">•</span>
|
||||
<span x-text="vendor?.name"></span>
|
||||
<span class="text-gray-400">•</span>
|
||||
<span x-text="vendor?.vendor_code"></span>
|
||||
</p>
|
||||
</div>
|
||||
<a href="/admin/vendors"
|
||||
<a href="/admin/vendors"
|
||||
class="flex items-center 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 dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800 hover:border-gray-400 focus:outline-none">
|
||||
<span x-html="$icon('arrow-left', 'w-4 h-4 mr-2')"></span>
|
||||
Back to Vendors
|
||||
@@ -32,30 +32,61 @@
|
||||
</div>
|
||||
|
||||
<!-- Edit Form -->
|
||||
<div x-show="!loadingVendor && vendor" class="px-4 py-3 mb-8 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
||||
<!-- Quick Actions -->
|
||||
<div class="flex items-center gap-3 mb-6 pb-6 border-b dark:border-gray-700">
|
||||
<button
|
||||
@click="toggleVerification()"
|
||||
:disabled="saving"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 rounded-lg focus:outline-none focus:shadow-outline-purple disabled:opacity-50"
|
||||
:class="vendor?.is_verified ? 'bg-orange-600 hover:bg-orange-700' : 'bg-green-600 hover:bg-green-700'">
|
||||
<span x-html="$icon(vendor?.is_verified ? 'x-circle' : 'badge-check', 'w-4 h-4 mr-2')"></span>
|
||||
<span x-text="vendor?.is_verified ? 'Unverify Vendor' : 'Verify Vendor'"></span>
|
||||
</button>
|
||||
<div x-show="!loadingVendor && vendor">
|
||||
<!-- Quick Actions Card -->
|
||||
<div class="px-4 py-3 mb-6 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">
|
||||
Quick Actions
|
||||
</h3>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<button
|
||||
@click="toggleVerification()"
|
||||
:disabled="saving"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 rounded-lg focus:outline-none focus:shadow-outline-purple disabled:opacity-50"
|
||||
:class="vendor?.is_verified ? 'bg-orange-600 hover:bg-orange-700' : 'bg-green-600 hover:bg-green-700'">
|
||||
<span x-html="$icon(vendor?.is_verified ? 'x-circle' : 'badge-check', 'w-4 h-4 mr-2')"></span>
|
||||
<span x-text="vendor?.is_verified ? 'Unverify Vendor' : 'Verify Vendor'"></span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="toggleActive()"
|
||||
:disabled="saving"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 rounded-lg focus:outline-none focus:shadow-outline-purple disabled:opacity-50"
|
||||
:class="vendor?.is_active ? 'bg-red-600 hover:bg-red-700' : 'bg-green-600 hover:bg-green-700'">
|
||||
<span x-html="$icon(vendor?.is_active ? 'lock-closed' : 'lock-open', 'w-4 h-4 mr-2')"></span>
|
||||
<span x-text="vendor?.is_active ? 'Deactivate' : 'Activate'"></span>
|
||||
</button>
|
||||
<button
|
||||
@click="toggleActive()"
|
||||
:disabled="saving"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 rounded-lg focus:outline-none focus:shadow-outline-purple disabled:opacity-50"
|
||||
:class="vendor?.is_active ? 'bg-red-600 hover:bg-red-700' : 'bg-green-600 hover:bg-green-700'">
|
||||
<span x-html="$icon(vendor?.is_active ? 'lock-closed' : 'lock-open', 'w-4 h-4 mr-2')"></span>
|
||||
<span x-text="vendor?.is_active ? 'Deactivate' : 'Activate'"></span>
|
||||
</button>
|
||||
|
||||
<!-- Status Badges -->
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<span
|
||||
x-show="vendor?.is_verified"
|
||||
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('badge-check', 'w-3 h-3 mr-1')"></span>
|
||||
Verified
|
||||
</span>
|
||||
<span
|
||||
x-show="!vendor?.is_verified"
|
||||
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>
|
||||
<span
|
||||
x-show="vendor?.is_active"
|
||||
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">
|
||||
Active
|
||||
</span>
|
||||
<span
|
||||
x-show="!vendor?.is_active"
|
||||
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">
|
||||
Inactive
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form -->
|
||||
<form @submit.prevent="handleSubmit">
|
||||
<!-- Form Card -->
|
||||
<form @submit.prevent="handleSubmit" class="px-4 py-3 mb-8 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
||||
<div class="grid gap-6 mb-8 md:grid-cols-2">
|
||||
<!-- Left Column: Basic Info -->
|
||||
<div>
|
||||
@@ -64,8 +95,8 @@
|
||||
</h3>
|
||||
|
||||
<!-- Vendor Code (readonly) -->
|
||||
<label class="block mb-4">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
<label class="block mb-4 text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">
|
||||
Vendor Code
|
||||
</span>
|
||||
<input
|
||||
@@ -80,8 +111,8 @@
|
||||
</label>
|
||||
|
||||
<!-- Name -->
|
||||
<label class="block mb-4">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
<label class="block mb-4 text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">
|
||||
Vendor Name <span class="text-red-600">*</span>
|
||||
</span>
|
||||
<input
|
||||
@@ -90,15 +121,15 @@
|
||||
required
|
||||
maxlength="255"
|
||||
:disabled="saving"
|
||||
class="block w-full mt-1 text-sm border-gray-300 rounded-md dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
|
||||
:class="{ 'border-red-600': errors.name }"
|
||||
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"
|
||||
:class="{ 'border-red-600 focus:border-red-400 focus:shadow-outline-red': errors.name }"
|
||||
>
|
||||
<span x-show="errors.name" class="text-xs text-red-600 dark:text-red-400 mt-1" x-text="errors.name"></span>
|
||||
</label>
|
||||
|
||||
<!-- Subdomain -->
|
||||
<label class="block mb-4">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
<label class="block mb-4 text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">
|
||||
Subdomain <span class="text-red-600">*</span>
|
||||
</span>
|
||||
<input
|
||||
@@ -108,8 +139,8 @@
|
||||
required
|
||||
maxlength="100"
|
||||
:disabled="saving"
|
||||
class="block w-full mt-1 text-sm border-gray-300 rounded-md dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
|
||||
:class="{ 'border-red-600': errors.subdomain }"
|
||||
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"
|
||||
:class="{ 'border-red-600 focus:border-red-400 focus:shadow-outline-red': errors.subdomain }"
|
||||
>
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400 mt-1">
|
||||
Lowercase letters, numbers, and hyphens only
|
||||
@@ -118,15 +149,15 @@
|
||||
</label>
|
||||
|
||||
<!-- Description -->
|
||||
<label class="block mb-4">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
<label class="block mb-4 text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">
|
||||
Description
|
||||
</span>
|
||||
<textarea
|
||||
x-model="formData.description"
|
||||
rows="3"
|
||||
:disabled="saving"
|
||||
class="block w-full mt-1 text-sm border-gray-300 rounded-md dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-textarea"
|
||||
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-textarea"
|
||||
></textarea>
|
||||
</label>
|
||||
</div>
|
||||
@@ -138,8 +169,8 @@
|
||||
</h3>
|
||||
|
||||
<!-- Owner Email (readonly) -->
|
||||
<label class="block mb-4">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
<label class="block mb-4 text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">
|
||||
Owner Email
|
||||
</span>
|
||||
<input
|
||||
@@ -148,11 +179,14 @@
|
||||
disabled
|
||||
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"
|
||||
>
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400 mt-1">
|
||||
Owner's authentication email
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<!-- Contact Email -->
|
||||
<label class="block mb-4">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
<label class="block mb-4 text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">
|
||||
Contact Email <span class="text-red-600">*</span>
|
||||
</span>
|
||||
<input
|
||||
@@ -160,28 +194,31 @@
|
||||
x-model="formData.contact_email"
|
||||
required
|
||||
:disabled="saving"
|
||||
class="block w-full mt-1 text-sm border-gray-300 rounded-md dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
|
||||
:class="{ 'border-red-600': errors.contact_email }"
|
||||
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"
|
||||
:class="{ 'border-red-600 focus:border-red-400 focus:shadow-outline-red': errors.contact_email }"
|
||||
>
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400 mt-1">
|
||||
Public business contact email
|
||||
</span>
|
||||
<span x-show="errors.contact_email" class="text-xs text-red-600 dark:text-red-400 mt-1" x-text="errors.contact_email"></span>
|
||||
</label>
|
||||
|
||||
<!-- Phone -->
|
||||
<label class="block mb-4">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
<label class="block mb-4 text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">
|
||||
Phone
|
||||
</span>
|
||||
<input
|
||||
type="tel"
|
||||
x-model="formData.contact_phone"
|
||||
:disabled="saving"
|
||||
class="block w-full mt-1 text-sm border-gray-300 rounded-md dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
|
||||
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>
|
||||
|
||||
<!-- Website -->
|
||||
<label class="block mb-4">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
<label class="block mb-4 text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">
|
||||
Website
|
||||
</span>
|
||||
<input
|
||||
@@ -189,7 +226,7 @@
|
||||
x-model="formData.website"
|
||||
:disabled="saving"
|
||||
placeholder="https://example.com"
|
||||
class="block w-full mt-1 text-sm border-gray-300 rounded-md dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
|
||||
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>
|
||||
</div>
|
||||
@@ -200,38 +237,38 @@
|
||||
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
||||
Business Details
|
||||
</h3>
|
||||
|
||||
|
||||
<div class="grid gap-6 md:grid-cols-2">
|
||||
<!-- Business Address -->
|
||||
<label class="block">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
<label class="block text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">
|
||||
Business Address
|
||||
</span>
|
||||
<textarea
|
||||
x-model="formData.business_address"
|
||||
rows="3"
|
||||
:disabled="saving"
|
||||
class="block w-full mt-1 text-sm border-gray-300 rounded-md dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-textarea"
|
||||
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-textarea"
|
||||
></textarea>
|
||||
</label>
|
||||
|
||||
<!-- Tax Number -->
|
||||
<label class="block">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
<label class="block text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-400">
|
||||
Tax Number
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
x-model="formData.tax_number"
|
||||
:disabled="saving"
|
||||
class="block w-full mt-1 text-sm border-gray-300 rounded-md dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Save Button -->
|
||||
<div class="flex items-center justify-end gap-3">
|
||||
<div class="flex items-center justify-end gap-3 pt-6 border-t dark:border-gray-700">
|
||||
<a
|
||||
href="/admin/vendors"
|
||||
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 dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800 hover:border-gray-400 focus:outline-none">
|
||||
@@ -241,7 +278,10 @@
|
||||
type="submit"
|
||||
:disabled="saving"
|
||||
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 focus:shadow-outline-purple disabled:opacity-50 disabled:cursor-not-allowed">
|
||||
<span x-show="!saving">Save Changes</span>
|
||||
<span x-show="!saving">
|
||||
<span x-html="$icon('check', 'w-4 h-4 mr-2 inline')"></span>
|
||||
Save Changes
|
||||
</span>
|
||||
<span x-show="saving" class="flex items-center">
|
||||
<span x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
|
||||
Saving...
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!-- app/templates/partials/sidebar.html -->
|
||||
{# app/templates/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">
|
||||
@@ -6,6 +6,7 @@
|
||||
Admin Portal
|
||||
</a>
|
||||
<ul class="mt-6">
|
||||
<!-- Dashboard -->
|
||||
<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"
|
||||
@@ -16,7 +17,10 @@
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Main Navigation -->
|
||||
<ul>
|
||||
<!-- Vendors -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'vendors'" 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"
|
||||
@@ -26,27 +30,88 @@
|
||||
<span class="ml-4">Vendors</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Users -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'users'" 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 === 'users' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/users">
|
||||
<span x-html="$icon('users')"></span>
|
||||
<span class="ml-4">Users</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Import Jobs -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'imports'" 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"
|
||||
href="#">
|
||||
:class="currentPage === 'imports' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/imports">
|
||||
<span x-html="$icon('cube')"></span>
|
||||
<span class="ml-4">Import Jobs</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Developer Tools Section -->
|
||||
<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">
|
||||
Create vendor
|
||||
<span class="ml-2" aria-hidden="true">+</span>
|
||||
</button>
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
</div>
|
||||
<p class="px-6 text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
|
||||
Developer Tools
|
||||
</p>
|
||||
<ul class="mt-3">
|
||||
<!-- Components -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'components'" 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 === 'components' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/components">
|
||||
<span x-html="$icon('view-grid')"></span>
|
||||
<span class="ml-4">Components</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Icons -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'icons'" 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 === 'icons' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/icons">
|
||||
<span x-html="$icon('photograph')"></span>
|
||||
<span class="ml-4">Icons</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Testing Hub -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'testing'" 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 === 'testing' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/testing">
|
||||
<span x-html="$icon('beaker')"></span>
|
||||
<span class="ml-4">Testing Hub</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Settings Section -->
|
||||
<div class="px-6 my-6">
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
</div>
|
||||
<ul>
|
||||
<!-- Settings -->
|
||||
<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="/admin/settings">
|
||||
<span x-html="$icon('cog')"></span>
|
||||
<span class="ml-4">Settings</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -75,6 +140,7 @@
|
||||
Admin Portal
|
||||
</a>
|
||||
<ul class="mt-6">
|
||||
<!-- Dashboard -->
|
||||
<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"
|
||||
@@ -85,7 +151,10 @@
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Main Navigation -->
|
||||
<ul>
|
||||
<!-- Vendors -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'vendors'" 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"
|
||||
@@ -95,26 +164,87 @@
|
||||
<span class="ml-4">Vendors</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Users -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'users'" 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 === 'users' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/users">
|
||||
<span x-html="$icon('users')"></span>
|
||||
<span class="ml-4">Users</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Import Jobs -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'imports'" 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"
|
||||
href="#">
|
||||
:class="currentPage === 'imports' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/imports">
|
||||
<span x-html="$icon('cube')"></span>
|
||||
<span class="ml-4">Import Jobs</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Developer Tools Section -->
|
||||
<div class="px-6 my-6">
|
||||
<button class="flex items-center justify-between 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">
|
||||
Create vendor
|
||||
<span class="ml-2" aria-hidden="true">+</span>
|
||||
</button>
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
</div>
|
||||
<p class="px-6 text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
|
||||
Developer Tools
|
||||
</p>
|
||||
<ul class="mt-3">
|
||||
<!-- Components -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'components'" 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 === 'components' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/components">
|
||||
<span x-html="$icon('view-grid')"></span>
|
||||
<span class="ml-4">Components</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Icons -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'icons'" 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 === 'icons' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/icons">
|
||||
<span x-html="$icon('photograph')"></span>
|
||||
<span class="ml-4">Icons</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Testing Hub -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'testing'" 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 === 'testing' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/testing">
|
||||
<span x-html="$icon('beaker')"></span>
|
||||
<span class="ml-4">Testing Hub</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Settings Section -->
|
||||
<div class="px-6 my-6">
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
</div>
|
||||
<ul>
|
||||
<!-- Settings -->
|
||||
<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="/admin/settings">
|
||||
<span x-html="$icon('cog')"></span>
|
||||
<span class="ml-4">Settings</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
Reference in New Issue
Block a user