- Add database models for subscription tiers, vendor subscriptions, add-ons, billing history, and webhook events - Implement BillingService for subscription operations - Implement StripeService for Stripe API operations - Implement StripeWebhookHandler for webhook event processing - Add vendor billing API endpoints for subscription management - Create vendor billing page with Alpine.js frontend - Add limit enforcement for products and team members - Add billing exceptions for proper error handling - Create comprehensive unit tests (40 tests passing) - Add subscription billing documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
242 lines
13 KiB
HTML
242 lines
13 KiB
HTML
{# app/templates/vendor/partials/sidebar.html #}
|
|
{#
|
|
Vendor sidebar - loads vendor data client-side via JavaScript
|
|
Follows same pattern as admin sidebar
|
|
#}
|
|
|
|
<!-- 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">
|
|
<!-- Vendor Branding -->
|
|
<a class="ml-6 text-lg font-bold text-gray-800 dark:text-gray-200 flex items-center"
|
|
:href="`/vendor/${vendorCode}/dashboard`">
|
|
<span class="text-2xl mr-2">🏪</span>
|
|
<span x-text="vendor?.name || 'Vendor Portal'"></span>
|
|
</a>
|
|
|
|
<!-- Main Navigation -->
|
|
<ul class="mt-6">
|
|
<li class="relative px-6 py-3">
|
|
<span x-show="currentPage === 'dashboard'"
|
|
class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
|
|
aria-hidden="true"></span>
|
|
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
|
:class="currentPage === 'dashboard' ? 'text-gray-800 dark:text-gray-100' : ''"
|
|
:href="`/vendor/${vendorCode}/dashboard`">
|
|
<span x-html="$icon('home', 'w-5 h-5')"></span>
|
|
<span class="ml-4">Dashboard</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<!-- Products & Inventory Section -->
|
|
<div class="px-6 my-6">
|
|
<div class="flex items-center">
|
|
<span x-html="$icon('cube', 'w-5 h-5 text-gray-400')"></span>
|
|
<span class="ml-2 text-xs font-semibold text-gray-500 uppercase dark:text-gray-400">
|
|
Products
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<ul>
|
|
<li class="relative px-6 py-3">
|
|
<span x-show="currentPage === 'products'"
|
|
class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
|
|
aria-hidden="true"></span>
|
|
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
|
:class="currentPage === 'products' ? 'text-gray-800 dark:text-gray-100' : ''"
|
|
:href="`/vendor/${vendorCode}/products`">
|
|
<span x-html="$icon('shopping-bag', 'w-5 h-5')"></span>
|
|
<span class="ml-4">All Products</span>
|
|
</a>
|
|
</li>
|
|
<li class="relative px-6 py-3">
|
|
<span x-show="currentPage === 'marketplace'"
|
|
class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
|
|
aria-hidden="true"></span>
|
|
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
|
:class="currentPage === 'marketplace' ? 'text-gray-800 dark:text-gray-100' : ''"
|
|
:href="`/vendor/${vendorCode}/marketplace`">
|
|
<span x-html="$icon('download', 'w-5 h-5')"></span>
|
|
<span class="ml-4">Marketplace Import</span>
|
|
</a>
|
|
</li>
|
|
<li class="relative px-6 py-3">
|
|
<span x-show="currentPage === 'inventory'"
|
|
class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
|
|
aria-hidden="true"></span>
|
|
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
|
:class="currentPage === 'inventory' ? 'text-gray-800 dark:text-gray-100' : ''"
|
|
:href="`/vendor/${vendorCode}/inventory`">
|
|
<span x-html="$icon('clipboard-list', 'w-5 h-5')"></span>
|
|
<span class="ml-4">Inventory</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<!-- Sales Section -->
|
|
<div class="px-6 my-6">
|
|
<div class="flex items-center">
|
|
<span x-html="$icon('chart-bar', 'w-5 h-5 text-gray-400')"></span>
|
|
<span class="ml-2 text-xs font-semibold text-gray-500 uppercase dark:text-gray-400">
|
|
Sales
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<ul>
|
|
<li class="relative px-6 py-3">
|
|
<span x-show="currentPage === 'orders'"
|
|
class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
|
|
aria-hidden="true"></span>
|
|
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
|
:class="currentPage === 'orders' ? 'text-gray-800 dark:text-gray-100' : ''"
|
|
:href="`/vendor/${vendorCode}/orders`">
|
|
<span x-html="$icon('shopping-cart', 'w-5 h-5')"></span>
|
|
<span class="ml-4">Orders</span>
|
|
</a>
|
|
</li>
|
|
<li class="relative px-6 py-3">
|
|
<span x-show="currentPage === 'letzshop'"
|
|
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 === 'letzshop' ? 'text-gray-800 dark:text-gray-100' : ''"
|
|
:href="`/vendor/${vendorCode}/letzshop`">
|
|
<span x-html="$icon('external-link', 'w-5 h-5')"></span>
|
|
<span class="ml-4">Letzshop Orders</span>
|
|
</a>
|
|
</li>
|
|
<li class="relative px-6 py-3">
|
|
<span x-show="currentPage === 'customers'"
|
|
class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
|
|
aria-hidden="true"></span>
|
|
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
|
:class="currentPage === 'customers' ? 'text-gray-800 dark:text-gray-100' : ''"
|
|
:href="`/vendor/${vendorCode}/customers`">
|
|
<span x-html="$icon('users', 'w-5 h-5')"></span>
|
|
<span class="ml-4">Customers</span>
|
|
</a>
|
|
</li>
|
|
<li class="relative px-6 py-3">
|
|
<span x-show="currentPage === 'messages'"
|
|
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 === 'messages' ? 'text-gray-800 dark:text-gray-100' : ''"
|
|
:href="`/vendor/${vendorCode}/messages`">
|
|
<span x-html="$icon('chat-bubble-left-right', 'w-5 h-5')"></span>
|
|
<span class="ml-4">Messages</span>
|
|
</a>
|
|
</li>
|
|
<li class="relative px-6 py-3">
|
|
<span x-show="currentPage === 'invoices'"
|
|
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 === 'invoices' ? 'text-gray-800 dark:text-gray-100' : ''"
|
|
:href="`/vendor/${vendorCode}/invoices`">
|
|
<span x-html="$icon('document-text', 'w-5 h-5')"></span>
|
|
<span class="ml-4">Invoices</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<!-- Settings Section -->
|
|
<div class="px-6 my-6">
|
|
<div class="flex items-center">
|
|
<span x-html="$icon('cog', 'w-5 h-5 text-gray-400')"></span>
|
|
<span class="ml-2 text-xs font-semibold text-gray-500 uppercase dark:text-gray-400">
|
|
Settings
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<ul>
|
|
<li class="relative px-6 py-3">
|
|
<span x-show="currentPage === 'team'"
|
|
class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
|
|
aria-hidden="true"></span>
|
|
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
|
:class="currentPage === 'team' ? 'text-gray-800 dark:text-gray-100' : ''"
|
|
:href="`/vendor/${vendorCode}/team`">
|
|
<span x-html="$icon('user-group', 'w-5 h-5')"></span>
|
|
<span class="ml-4">Team</span>
|
|
</a>
|
|
</li>
|
|
<li class="relative px-6 py-3">
|
|
<span x-show="currentPage === 'profile'"
|
|
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 === 'profile' ? 'text-gray-800 dark:text-gray-100' : ''"
|
|
:href="`/vendor/${vendorCode}/profile`">
|
|
<span x-html="$icon('user', 'w-5 h-5')"></span>
|
|
<span class="ml-4">Profile</span>
|
|
</a>
|
|
</li>
|
|
<li class="relative px-6 py-3">
|
|
<span x-show="currentPage === 'settings'"
|
|
class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
|
|
aria-hidden="true"></span>
|
|
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
|
:class="currentPage === 'settings' ? 'text-gray-800 dark:text-gray-100' : ''"
|
|
:href="`/vendor/${vendorCode}/settings`">
|
|
<span x-html="$icon('adjustments', 'w-5 h-5')"></span>
|
|
<span class="ml-4">Settings</span>
|
|
</a>
|
|
</li>
|
|
<li class="relative px-6 py-3">
|
|
<span x-show="currentPage === 'billing'"
|
|
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 === 'billing' ? 'text-gray-800 dark:text-gray-100' : ''"
|
|
:href="`/vendor/${vendorCode}/billing`">
|
|
<span x-html="$icon('credit-card', 'w-5 h-5')"></span>
|
|
<span class="ml-4">Billing</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="px-6 my-6">
|
|
<button class="flex items-center justify-between w-full px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg active:bg-purple-600 hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple"
|
|
@click="$dispatch('open-add-product-modal')">
|
|
Add Product
|
|
<span class="ml-2" aria-hidden="true">+</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Mobile sidebar -->
|
|
<div x-show="isSideMenuOpen"
|
|
x-transition:enter="transition ease-in-out duration-150"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition ease-in-out duration-150"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
class="fixed inset-0 z-10 flex items-end bg-black bg-opacity-50 sm:items-center sm:justify-center"></div>
|
|
|
|
<aside class="fixed inset-y-0 z-20 flex-shrink-0 w-64 mt-16 overflow-y-auto bg-white dark:bg-gray-800 md:hidden"
|
|
x-show="isSideMenuOpen"
|
|
x-transition:enter="transition ease-in-out duration-150"
|
|
x-transition:enter-start="opacity-0 transform -translate-x-20"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition ease-in-out duration-150"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0 transform -translate-x-20"
|
|
@click.away="closeSideMenu"
|
|
@keydown.escape="closeSideMenu">
|
|
<!-- Mobile navigation - same structure as desktop -->
|
|
<div class="py-4 text-gray-500 dark:text-gray-400">
|
|
<a class="ml-6 text-lg font-bold text-gray-800 dark:text-gray-200 flex items-center"
|
|
:href="`/vendor/${vendorCode}/dashboard`">
|
|
<span class="text-2xl mr-2">🏪</span>
|
|
<span x-text="vendor?.name || 'Vendor Portal'"></span>
|
|
</a>
|
|
|
|
<!-- Same menu structure as desktop (omitted for brevity) -->
|
|
<!-- Copy the entire menu structure from desktop sidebar above -->
|
|
</div>
|
|
</aside> |