     1→# 03 - Admin Panel UI/UX Specification
     2→
     3→This document defines every view, component, and interaction for the admin panel. The admin is entirely server-rendered using Laravel 12 + Livewire v4 + Flux UI Free components + Tailwind CSS v4. There is no SPA or client-side router.
     4→
     5→**Flux UI Free components available:** avatar, badge, brand, breadcrumbs, button, callout, checkbox, dropdown, field, heading, icon, input, modal, navbar, profile, radio, select, separator, switch, text, textarea, tooltip
     6→
     7→Anything not covered by Flux UI Free uses plain Tailwind CSS v4 markup with Livewire directives.
     8→
     9→---
    10→
    11→## 1. Admin Layout Shell
    12→
    13→The admin layout is a persistent shell that wraps every admin page. It consists of a left sidebar, a top bar, and a main content area.
    14→
    15→### 1.1 Route Prefix
    16→
    17→All admin routes live under `/admin` and are protected by authentication middleware plus store-scoping middleware. The store is resolved from the session or a store-selector in the top bar.
    18→
    19→### 1.2 Sidebar Navigation
    20→
    21→**Livewire component:** `App\Livewire\Admin\Layout\Sidebar`
    22→
    23→**Blade view:** `resources/views/livewire/admin/layout/sidebar.blade.php`
    24→
    25→#### Component Requirements
    26→
    27→| Property | Type | Purpose |
    28→|----------|------|---------|
    29→| `collapsed` | `bool` | Whether the sidebar is collapsed (mobile state) |
    30→| `currentRoute` | `string` | The current route name for active highlighting |
    31→
    32→| Action | Behavior |
    33→|--------|----------|
    34→| `toggle()` | Toggles the `collapsed` state |
    35→
    36→#### Wireframe
    37→
    38→```
    39→+-------------------------------+
    40→|  [BRAND LOGO]  Platform Name  |
    41→|-------------------------------|
    42→|  [separator]                  |
    43→|                               |
    44→|  [chart-bar]    Dashboard     |
    45→|                               |
    46→|  PRODUCTS                     |
    47→|  [cube]         Products      |
    48→|  [rect-stack]   Collections   |
    49→|  [archive-box]  Inventory     |
    50→|                               |
    51→|  [separator]                  |
    52→|  ORDERS                       |
    53→|  [shopping-bag] Orders        |
    54→|                               |
    55→|  [separator]                  |
    56→|  CUSTOMERS                    |
    57→|  [users]        Customers     |
    58→|                               |
    59→|  [separator]                  |
    60→|  DISCOUNTS                    |
    61→|  [tag]          Discounts     |
    62→|                               |
    63→|  [separator]                  |
    64→|  CONTENT                      |
    65→|  [doc-text]     Pages         |
    66→|  [bars-3]       Navigation    |
    67→|  [paint-brush]  Themes        |
    68→|                               |
    69→|  [separator]                  |
    70→|  [chart-pie]    Analytics     |
    71→|  [cog-6-tooth]  Settings      |
    72→|  [squares-2x2]  Apps          |
    73→|  [code-bracket] Developers    |
    74→+-------------------------------+
    75→```
    76→
    77→#### Navigation Structure
    78→
    79→```
    80→Dashboard                        -> admin.dashboard
    81→Products (group label)
    82→  Products                       -> admin.products.index
    83→  Collections                    -> admin.collections.index
    84→  Inventory                      -> admin.inventory.index
    85→Orders
    86→  Orders                         -> admin.orders.index
    87→Customers
    88→  Customers                      -> admin.customers.index
    89→Discounts
    90→  Discounts                      -> admin.discounts.index
    91→Content (group label)
    92→  Pages                          -> admin.pages.index
    93→  Navigation                     -> admin.navigation.index
    94→  Themes                         -> admin.themes.index
    95→Analytics                        -> admin.analytics.index
    96→Settings                         -> admin.settings.index
    97→Apps                             -> admin.apps.index
    98→Developers                       -> admin.developers.index
    99→```
   100→
   101→Each nav item displays an icon (via Flux icon component) and a text label. The active item receives a highlighted background and bold text. All navigation links use Livewire SPA-style page transitions. Groups are visually separated with Flux separator components.
   102→
   103→#### Flux Component Mapping
   104→
   105→| UI Element | Flux Component |
   106→|-----------|----------------|
   107→| Platform logo and name | `brand` |
   108→| Navigation icons | `icon` |
   109→| Group dividers | `separator` |
   110→
   111→#### Responsive Behavior
   112→
   113→- **Desktop (lg and above):** Sidebar is always visible, fixed left, 256px wide. Main content has a left margin of 256px.
   114→- **Mobile (below lg):** Sidebar is hidden off-screen. A hamburger button (icon: bars-3) appears in the top bar. When user clicks it, the sidebar slides in as an overlay with a semi-transparent backdrop. Clicking the backdrop or a nav item closes it.
   115→
   116→### 1.3 Top Bar
   117→
   118→**Livewire component:** `App\Livewire\Admin\Layout\TopBar`
   119→
   120→**Blade view:** `resources/views/livewire/admin/layout/top-bar.blade.php`
   121→
   122→#### Component Requirements
   123→
   124→| Property | Type | Purpose |
   125→|----------|------|---------|
   126→| `currentStoreName` | `string` | Name of the active store |
   127→| `stores` | `Collection` | List of stores the user has access to |
   128→| `unreadNotificationCount` | `int` | Count of unread notifications |
   129→
   130→| Action | Behavior |
   131→|--------|----------|
   132→| `switchStore(string $storeId)` | Switches active store in session, redirects to dashboard |
   133→
   134→#### Wireframe
   135→
   136→```
   137→+--------------------------------------------------------------------+
   138→| [hamburger*]  [Store Name v]                  [bell(3)]  [avatar v] |
   139→+--------------------------------------------------------------------+
   140→  * hamburger visible on mobile only
   141→
   142→  Store Name dropdown:
   143→    - Store A
   144→    - Store B
   145→    - Store C
   146→
   147→  Avatar dropdown:
   148→    - Settings (links to /admin/settings)
   149→    - [separator]
   150→    - Log out (POST /logout with CSRF)
   151→```
   152→
   153→#### Interaction Flows
   154→
   155→- When user clicks the store name, a dropdown appears listing all stores. Clicking a store calls `switchStore()` which updates the session and redirects to the dashboard.
   156→- Notification bell shows an unread count badge (positioned absolutely over the icon) when `unreadNotificationCount > 0`.
   157→- Profile dropdown is triggered by a Flux profile component showing the user's avatar and name.
   158→
   159→#### Flux Component Mapping
   160→
   161→| UI Element | Flux Component |
   162→|-----------|----------------|
   163→| Store selector | `dropdown` |
   164→| Hamburger (mobile) | `button` (icon: bars-3) |
   165→| Notification bell | `button` (icon: bell, variant: ghost) |
   166→| Notification count | `badge` |
   167→| User profile trigger | `profile` |
   168→| Profile menu | `dropdown` |
   169→| Menu divider | `separator` |
   170→
   171→### 1.4 Main Content Area
   172→
   173→The main content area sits to the right of the sidebar and below the top bar. Every page renders within this area.
   174→
   175→**Breadcrumbs:** Every page includes a Flux breadcrumbs component at the top of the content area. The first item is always "Home" linking to `/admin`. Subsequent items reflect the page hierarchy. Example for product edit: Home > Products > "Blue T-Shirt".
   176→
   177→**Layout wrapper:** The content area has consistent padding (larger on desktop, smaller on mobile) and a max-width container centered horizontally.
   178→
   179→### 1.5 Toast Notifications
   180→
   181→A global toast notification system lives in the layout shell. It uses Alpine.js to listen for Livewire dispatched events.
   182→
   183→| Property | Value |
   184→|----------|-------|
   185→| Event name | `toast` |
   186→| Payload | `{ type: 'success' / 'error' / 'info', message: string }` |
   187→| Position | Top-right corner |
   188→| Auto-dismiss | 5 seconds |
   189→| Stacking | Supports multiple toasts |
   190→| Styling | Rounded card, shadow, colored left border (green=success, red=error, blue=info) |
   191→
   192→Components dispatch toasts after successful actions (e.g., "Product saved successfully.") or on errors ("Something went wrong. Please try again.").
   193→
   194→### 1.6 Dark Mode
   195→
   196→The admin supports dark mode via Tailwind's `dark:` variant. The mode follows the user's system preference by default. A toggle can be added in the profile dropdown later. All components and custom markup must include dark mode variants.
   197→
   198→Dark mode preference is persisted in `localStorage` under the key `theme` with values `light` or `dark`. On page load, the stored preference is applied before first paint to avoid flash of incorrect theme. If no preference is stored, the system preference (`prefers-color-scheme`) is used as default.
   199→
   200→---
   201→
   202→## 2. Dashboard
   203→
   204→**Route:** `GET /admin`
   205→
   206→**Livewire component:** `App\Livewire\Admin\Dashboard`
   207→
   208→**Blade view:** `resources/views/livewire/admin/dashboard.blade.php`
   209→
   210→### Component Requirements
   211→
   212→| Property | Type | Purpose |
   213→|----------|------|---------|
   214→| `dateRange` | `string` | Active date range preset: `today`, `last_7_days`, `last_30_days`, `custom` |
   215→| `customStartDate` | `?string` | Custom range start (Y-m-d) |
   216→| `customEndDate` | `?string` | Custom range end (Y-m-d) |
   217→| `totalSales` | `int` | Total sales amount in minor units |
   218→| `ordersCount` | `int` | Number of orders in range |
   219→| `averageOrderValue` | `int` | Average order value in minor units |
   220→| `visitorsCount` | `int` | Unique visitors in range |
   221→| `salesChange` | `float` | Percentage change vs previous period |
   222→| `ordersChange` | `float` | Percentage change vs previous period |
   223→| `aovChange` | `float` | Percentage change vs previous period |
   224→| `visitorsChange` | `float` | Percentage change vs previous period |
   225→| `ordersChartData` | `array` | Array of `{date, count}` for last 30 days |
   226→| `topProducts` | `array` | Top 5 products: `{title, units_sold, revenue}` |
   227→| `funnelData` | `array` | Funnel steps: `{visits, add_to_cart, checkout_started, checkout_completed}` |
   228→
   229→| Action | Behavior |
   230→|--------|----------|
   231→| `updatedDateRange()` | Re-fetches all KPI data for the selected range |
   232→| `loadKpis()` | Queries aggregated order/analytics data |
   233→| `loadChart()` | Queries daily order counts for chart |
   234→| `loadTopProducts()` | Queries top 5 products by revenue |
   235→| `loadFunnel()` | Queries conversion funnel data |
   236→
   237→| Computed Property | Purpose |
   238→|-------------------|---------|
   239→| `formattedTotalSales` | Formatted currency string |
   240→| `formattedAov` | Formatted currency string |
   241→
   242→### Wireframe
   243→
   244→```
   245→+------------------------------------------------------------------+
   246→| Dashboard                           [Today v] (date range picker) |
   247→+------------------------------------------------------------------+
   248→|                                                                    |
   249→|  +-------------+  +-------------+  +-------------+  +----------+  |
   250→|  | Total Sales |  | Orders      |  | Avg Order   |  | Visitors |  |
   251→|  | $12,345     |  | 156         |  | $79.13      |  | 2,340    |  |
   252→|  | [+12.3%] ^  |  | [+8.1%] ^  |  | [-2.1%] v  |  | [+5%] ^  |  |
   253→|  +-------------+  +-------------+  +-------------+  +----------+  |
   254→|                                                                    |
   255→|  +--------------------------------------------------------------+  |
   256→|  | Orders over time                                              |  |
   257→|  |  [line/bar chart - daily order counts for selected range]     |  |
   258→|  +--------------------------------------------------------------+  |
   259→|                                                                    |
   260→|  +------------------------------+  +----------------------------+  |
   261→|  | Top products                 |  | Conversion funnel          |  |
   262→|  | Product Title | Sold | Rev   |  | Visits       [========] N |  |
   263→|  | Blue Shirt    | 45   | $900  |  | Add to Cart  [======]   N |  |
   264→|  | Red Hat       | 32   | $640  |  | Checkout     [====]     N |  |
   265→|  | Sneakers      | 28   | $2800 |  | Completed    [==]       N |  |
   266→|  | ...           |      |       |  |                            |  |
   267→|  +------------------------------+  +----------------------------+  |
   268→+--------------------------------------------------------------------+
   269→```
   270→
   271→### Layout Description
   272→
   273→**Page heading:** "Dashboard" displayed as a Flux heading (size xl).
   274→
   275→**Date range filter:** Positioned top-right. A Flux dropdown triggered by a button showing the current range label and a calendar icon. Options: Today, Last 7 days, Last 30 days, Custom range (reveals two date input fields when selected).
   276→
   277→### KPI Tiles Row
   278→
   279→A responsive 4-column grid (1 column on mobile, 2 on sm, 4 on lg).
   280→
   281→Each tile is a card containing:
   282→- A muted-color text label (e.g., "Total Sales")
   283→- A large heading with the formatted number value
   284→- A row with a Flux badge showing percentage change (green if positive, red if negative) and a directional arrow icon
   285→
   286→Each tile shows a loading state (reduced opacity) while data refreshes.
   287→
   288→### Orders Chart
   289→
   290→A card below the KPI tiles containing:
   291→- A Flux heading (size lg): "Orders over time"
   292→- A Chart.js canvas (line or bar chart) showing daily order counts
   293→- Chart.js initialized via Alpine.js; data passed from Livewire
   294→- A spinner overlay shown during chart data loading
   295→
   296→### Top Products Table
   297→
   298→A card containing:
   299→- A Flux heading (size lg): "Top products"
   300→- A plain table with columns: Product Title, Units Sold, Revenue
   301→- Maximum 5 rows
   302→- Empty state text: "No sales data for this period."
   303→
   304→### Conversion Funnel
   305→
   306→A card containing:
   307→- A Flux heading (size lg): "Conversion funnel"
   308→- Horizontal bar visualization using Tailwind utility widths
   309→- Each step: label on the left, proportional-width bar on the right, count displayed
   310→- Steps: Visits, Add to Cart, Checkout Started, Checkout Completed
   311→- Bars use decreasing widths and progressively darker colors
   312→
   313→### Flux Component Mapping
   314→
   315→| UI Element | Flux Component |
   316→|-----------|----------------|
   317→| Page title | `heading` (size xl) |
   318→| Section titles | `heading` (size lg) |
   319→| Date range selector | `dropdown`, `button`, `icon` |
   320→| Date inputs (custom range) | `input` (type date) |
   321→| KPI labels | `text` |
   322→| KPI values | `heading` (size xl) |
   323→| Trend badges | `badge` |
   324→| Trend arrows | `icon` |
   325→
   326→### View States
   327→
   328→| State | Behavior |
   329→|-------|----------|
   330→| Loading | KPI tiles show reduced opacity; chart shows spinner overlay |
   331→| Empty | "No sales data for this period." in top products; funnel bars at zero width |
   332→| Populated | All tiles, chart, table, and funnel render with data |
   333→| Error | Toast notification with error message |
   334→
   335→---
   336→
   337→## 3. Products List
   338→
   339→**Route:** `GET /admin/products`
   340→
   341→**Livewire component:** `App\Livewire\Admin\Products\Index`
   342→
   343→**Blade view:** `resources/views/livewire/admin/products/index.blade.php`
   344→
   345→### Component Requirements
   346→
   347→| Property | Type | Purpose |
   348→|----------|------|---------|
   349→| `search` | `string` | Search query, live-debounced at 300ms |
   350→| `statusFilter` | `string` | Status filter: `all`, `draft`, `active`, `archived` |
   351→| `typeFilter` | `string` | Product type filter |
   352→| `selectedIds` | `array<int>` | Array of selected product IDs for bulk actions |
   353→| `selectAll` | `bool` | Whether all visible rows are selected |
   354→| `sortField` | `string` | Column to sort by (default: `updated_at`) |
   355→| `sortDirection` | `string` | `asc` or `desc` |
   356→
   357→| Action | Behavior |
   358→|--------|----------|
   359→| `updatedSearch()` | Resets pagination |
   360→| `updatedStatusFilter()` | Resets pagination |
   361→| `updatedTypeFilter()` | Resets pagination |
   362→| `bulkArchive()` | Archives all selected products |
   363→| `bulkDelete()` | Soft-deletes selected products (with confirmation) |
   364→| `bulkSetActive()` | Sets selected products to active status |
   365→| `sortBy(string $field)` | Toggles sort direction or changes sort field |
   366→| `toggleSelectAll()` | Selects or deselects all visible product IDs |
   367→| `confirmBulkDelete()` | Opens delete confirmation modal |
   368→
   369→| Computed Property | Purpose |
   370→|-------------------|---------|
   371→| `products` | Paginated query result with search, filters, sorting applied. Eager loads `variants`, `media` (limit 1). Uses `withCount('variants')`. |
   372→| `productTypes` | Distinct product types for the type filter dropdown |
   373→
   374→### Wireframe
   375→
   376→```
   377→+------------------------------------------------------------------+
   378→| Products                                    [+ Add product]       |
   379→+------------------------------------------------------------------+
   380→| [search icon] Search products...   [Status: All v] [Type: All v] |
   381→+------------------------------------------------------------------+
   382→| (shown when items selected)                                       |
   383→| "3 products selected"  [Set Active]  [Archive]  [Delete]         |
   384→+------------------------------------------------------------------+
   385→| [x] | IMG | Title ^     | Status  | Inventory | Type | Vendor | Updated ^  |
   386→|-----|-----|-------------|---------|-----------|------|--------|-----------|
   387→| [ ] | img | Blue Shirt  | Active  | 45        | Tops | Nike   | 2h ago    |
   388→| [ ] | img | Red Hat     | Draft   | 12        | Hats | Adidas | 1d ago    |
   389→| [ ] | img | Sneakers    | Active  | 0         | Shoes| Nike   | 3d ago    |
   390→| ...                                                              |
   391→+------------------------------------------------------------------+
   392→| < 1 2 3 ... 10 >  (pagination)                                  |
   393→+------------------------------------------------------------------+
   394→```
   395→
   396→### Table Column Definitions
   397→
   398→| Column | Sortable | Content |
   399→|--------|----------|---------|
   400→| Select | No | Checkbox for bulk selection |
   401→| Image | No | 40x40 rounded thumbnail of first media item, or placeholder icon |
   402→| Title | Yes | Product title as a link to the edit page (uses Livewire navigate) |
   403→| Status | No | Flux badge: draft=zinc/gray, active=green, archived=red |
   404→| Inventory | Yes | Sum of `inventory_items.quantity_on_hand` across all variants |
   405→| Type | No | Product type text or "-" |
   406→| Vendor | No | Vendor name text or "-" |
   407→| Updated | Yes (default) | Relative timestamp (e.g., "2h ago") |
   408→
   409→### Bulk Action Bar
   410→
   411→Conditionally shown when `count($selectedIds) > 0`. Contains:
   412→- Text showing "X products selected"
   413→- "Set Active" button (ghost variant)
   414→- "Archive" button (ghost variant)
   415→- "Delete" button (danger variant) - opens confirmation modal
   416→
   417→### Empty State
   418→
   419→When no products exist (not filtered, genuinely empty):
   420→- Centered container with a large muted placeholder icon (cube)
   421→- Heading: "Add your first product"
   422→- Text: "Start building your catalog by adding products."
   423→- Primary button: "Add product" linking to the create page
   424→
   425→### Delete Confirmation Modal
   426→
   427→| Property | Value |
   428→|----------|-------|
   429→| Modal name | `confirm-bulk-delete` |
   430→| Max width | md |
   431→| Heading | "Delete products?" |
   432→| Body text | "This will archive X product(s). Products with orders cannot be permanently deleted." |
   433→| Cancel button | Ghost variant, closes modal |
   434→| Confirm button | Danger variant, calls `bulkDelete` |
   435→
   436→### Flux Component Mapping
   437→
   438→| UI Element | Flux Component |
   439→|-----------|----------------|
   440→| Page title | `heading` |
   441→| Add product button | `button` (variant: primary) |
   442→| Search field | `input` (icon: magnifying-glass) |
   443→| Status filter | `select` |
   444→| Type filter | `select` |
   445→| Bulk action buttons | `button` (ghost/danger variants) |
   446→| Selected count text | `text` |
   447→| Row checkbox | `checkbox` |
   448→| Status badges | `badge` |
   449→| Cell text | `text` |
   450→| Delete confirmation | `modal` |
   451→| Pagination | Laravel default Tailwind pagination |
   452→
   453→### View States
   454→
   455→| State | Behavior |
   456→|-------|----------|
   457→| Loading | Table body shows reduced opacity |
   458→| Empty (no products) | Empty state with illustration and CTA |
   459→| Empty (filtered) | Table with "No products match your filters" message |
   460→| Populated | Table with data rows and pagination |
   461→
   462→---
   463→
   464→## 4. Product Create/Edit
   465→
   466→**Route:** `GET /admin/products/create` and `GET /admin/products/{product}/edit`
   467→
   468→**Livewire component:** `App\Livewire\Admin\Products\Form`
   469→
   470→**Blade view:** `resources/views/livewire/admin/products/form.blade.php`
   471→
   472→### Component Requirements
   473→
   474→| Property | Type | Purpose |
   475→|----------|------|---------|
   476→| `product` | `?Product` | The product being edited, null for create |
   477→| `title` | `string` | Product title |
   478→| `descriptionHtml` | `string` | Product description (HTML) |
   479→| `status` | `string` | `draft`, `active`, `archived` |
   480→| `vendor` | `string` | Vendor name |
   481→| `productType` | `string` | Product type string |
   482→| `tags` | `string` | Comma-separated tags |
   483→| `handle` | `string` | URL handle/slug |
   484→| `publishedAt` | `?string` | Publish date (Y-m-d H:i) |
   485→| `collectionIds` | `array<int>` | Selected collection IDs |
   486→| `options` | `array` | Options array: `[{name: string, values: string[]}]` |
   487→| `variants` | `array` | Variants array: `[{sku, price, compareAtPrice, quantity, requiresShipping, optionValues}]` |
   488→| `media` | `array` | Existing media items: `[{id, url, alt_text, position}]` |
   489→| `newMedia` | `array` | Temporary uploaded files |
   490→
   491→| Action | Behavior |
   492→|--------|----------|
   493→| `mount(?Product $product)` | Loads product data into properties or sets defaults for create |
   494→| `save()` | Validates and saves/creates the product, variants, options, and media |
   495→| `addOption()` | Adds a new empty option `{name: '', values: []}` |
   496→| `removeOption(int $index)` | Removes an option and regenerates variants |
   497→| `addOptionValue(int $optionIndex)` | Adds a value to an option |
   498→| `removeOptionValue(int $optionIndex, int $valueIndex)` | Removes a value |
   499→| `generateVariants()` | Generates variant combinations from current options |
   500→| `uploadMedia()` | Handles file upload via Livewire temporary upload |
   501→| `removeMedia(int $mediaId)` | Removes a media item |
   502→| `reorderMedia(array $order)` | Updates media positions |
   503→| `updateMediaAlt(int $mediaId, string $alt)` | Updates alt text for a media item |
   504→| `deleteProduct()` | Archives the product (with confirmation modal) |
   505→
   506→| Computed Property | Purpose |
   507→|-------------------|---------|
   508→| `availableCollections` | All collections for the current store |
   509→| `isEditing` | `bool` - whether editing or creating |
   510→
   511→### Validation Rules
   512→
   513→| Field | Rules |
   514→|-------|-------|
   515→| `title` | Required, string, max 255 |
   516→| `descriptionHtml` | Nullable, string, max 65535 |
   517→| `status` | Required, one of: draft, active, archived |
   518→| `vendor` | Nullable, string, max 255 |
   519→| `productType` | Nullable, string, max 255 |
   520→| `tags` | Nullable, string |
   521→| `handle` | Required, string, max 255, unique (excluding current product) |
   522→| `variants.*.sku` | Nullable, string, max 255 |
   523→| `variants.*.price` | Required, integer, min 0 |
   524→| `variants.*.compareAtPrice` | Nullable, integer, min 0 |
   525→| `variants.*.quantity` | Required, integer, min 0 |
   526→
   527→### Two-Column Layout Wireframe
   528→
   529→```
   530→+------------------------------------------+-------------------+
   531→|          LEFT COLUMN (2/3)               | RIGHT COLUMN (1/3)|
   532→+------------------------------------------+-------------------+
   533→|                                          |                   |
   534→|  TITLE                                   |  STATUS CARD      |
   535→|  +------------------------------------+  |  Status: [Draft v]|
   536→|  | [Short Sleeve T-Shirt           ]  |  |                   |
   537→|  +------------------------------------+  |  PUBLISHING CARD  |
   538→|                                          |  Published at:    |
   539→|  DESCRIPTION                             |  [datetime input]  |
   540→|  +------------------------------------+  |                   |
   541→|  | Describe your product...           |  |  ORGANIZATION     |
   542→|  |                                    |  |  Vendor:          |
   543→|  |                                    |  |  [Nike          ] |
   544→|  |                                    |  |  Product type:    |
   545→|  +------------------------------------+  |  [T-Shirts      ] |
   546→|                                          |  Tags:            |
   547→|  MEDIA                                   |  [summer, cotton ] |
   548→|  +------------------------------------+  |  (comma-separated)|
   549→|  | +--+ +--+ +--+ +--+               |  |                   |
   550→|  | |  | |  | |  | |  |  (thumbnails) |  |  COLLECTIONS      |
   551→|  | +--+ +--+ +--+ +--+               |  |  [x] Summer Sale  |
   552→|  |                                    |  |  [ ] Winter       |
   553→|  | [--- Drag & drop or click ---]    |  |  [x] New Arrivals |
   554→|  +------------------------------------+  |                   |
   555→|                                          |                   |
   556→|  VARIANTS                                |                   |
   557→|  Option name: [Size    ]                 |                   |
   558→|  Values:      [S, M, L, XL         ]    |                   |
   559→|  [trash]                                 |                   |
   560→|                                          |                   |
   561→|  [+ Add another option]                  |                   |
   562→|                                          |                   |
   563→|  Variant | SKU    | Price | Compare | Qty | Ship |          |
   564→|  S / Red  | [   ] | [   ] | [     ] | [ ] | [x]  |          |
   565→|  M / Red  | [   ] | [   ] | [     ] | [ ] | [x]  |          |
   566→|  L / Red  | [   ] | [   ] | [     ] | [ ] | [x]  |          |
   567→|                                          |                   |
   568→|  SEO (collapsible)                       |                   |
   569→|  > Search engine listing                 |                   |
   570→|    URL handle: [short-sleeve-t-shirt]    |                   |
   571→+------------------------------------------+-------------------+
   572→
   573→+==================================================================+
   574→| STICKY SAVE BAR (fixed bottom)                                    |
   575→|                                      [Discard]  [Save]           |
   576→+==================================================================+
   577→```
   578→
   579→### Left Column Sections
   580→
   581→#### Title Input
   582→- Flux field with label "Title"
   583→- Flux input, placeholder "Short Sleeve T-Shirt"
   584→- Flux error display for validation
   585→
   586→#### Description
   587→- Flux field with label "Description"
   588→- Flux textarea, 8 rows, placeholder "Describe your product..."
   589→- Future enhancement: WYSIWYG toolbar via Alpine.js; baseline is plain textarea
   590→
   591→#### Media Section
   592→A card with heading "Media" (size md).
   593→
   594→**Upload area:** A dashed-border drop zone with upload icon and text "Drag and drop images or click to upload". A hidden file input (multiple, accept images) triggered by clicking the zone.
   595→
   596→**Upload progress:** A progress bar shown during file upload.
   597→
   598→**Media grid:** A 4-column grid (responsive: 2 on small, 3 on sm, 4 on lg) of thumbnails:
   599→- Each item: square aspect ratio, object-cover, rounded
   600→- On hover overlay shows: delete button (X icon, top-right), alt text edit button (pencil icon, bottom-left)
   601→- Drag-to-reorder supported via Livewire sortable or Alpine.js Sortable
   602→
   603→#### Variants Section
   604→A card with heading "Variants" (size md).
   605→
   606→**Options builder:** For each option, a row containing:
   607→- "Option name" input field (placeholder: "Size")
   608→- "Values" input field (placeholder: "S, M, L, XL" - comma-separated)
   609→- Trash button to remove the option
   610→
   611→Below options: "Add another option" ghost button with plus icon.
   612→
   613→When options change, `generateVariants()` is called automatically via lifecycle hook.
   614→
   615→**Variants table:** Shown when variants exist. Scrollable table with inline-editable fields:
   616→
   617→| Column | Input Type | Description |
   618→|--------|-----------|-------------|
   619→| Variant | Read-only text | Combination name, e.g. "S / Red" |
   620→| SKU | Text input (small) | Stock keeping unit |
   621→| Price | Number input (small) | Price in major units |
   622→| Compare at price | Number input (small) | Original price for sale display |
   623→| Quantity | Number input (small) | On-hand inventory |
   624→| Requires shipping | Checkbox | Whether variant needs shipping |
   625→
   626→#### SEO Section
   627→A collapsible section (collapsed by default, expands with animation):
   628→- Toggle button: chevron icon + "Search engine listing" text
   629→- When expanded: URL handle input field with placeholder "short-sleeve-t-shirt" and validation error display
   630→
   631→### Right Column Sections
   632→
   633→#### Status Card
   634→- Flux field with label "Status"
   635→- Flux select with options: Draft, Active, Archived
   636→
   637→#### Publishing Card
   638→- Flux field with label "Published at"
   639→- Datetime-local input
   640→
   641→#### Product Organization Card
   642→- Vendor: Flux input, placeholder "Nike"
   643→- Product type: Flux input, placeholder "T-Shirts"
   644→- Tags: Flux input, placeholder "summer, cotton, sale" with description "Separate tags with commas"
   645→
   646→#### Collections Card
   647→- Flux field with label "Collections"
   648→- For each available collection: a row with checkbox and collection title text
   649→- Checkbox values bound to `collectionIds`
   650→
   651→### Sticky Save Bar
   652→
   653→Fixed at the bottom of the viewport. Appears when changes are detected.
   654→- Left-aligned to account for sidebar width on desktop
   655→- "Discard" button (ghost variant) - links back to products index
   656→- "Save" button (primary variant) - calls `save()`, shows "Saving..." text during loading, disabled while loading
   657→
   658→### Delete Confirmation Modal (Edit Only)
   659→
   660→| Property | Value |
   661→|----------|-------|
   662→| Modal name | `confirm-delete-product` |
   663→| Max width | md |
   664→| Heading | "Delete this product?" |
   665→| Body text | "This product will be archived. Products with existing orders cannot be permanently removed." |
   666→| Cancel button | Ghost variant, closes modal |
   667→| Confirm button | Danger variant, calls `deleteProduct` |
   668→
   669→### Flux Component Mapping
   670→
   671→| UI Element | Flux Component |
   672→|-----------|----------------|
   673→| Page title | `heading` |
   674→| Section titles | `heading` (size md) |
   675→| All form labels | `field`, label via Flux |
   676→| Text inputs | `input` |
   677→| Textarea | `textarea` |
   678→| Status/type selects | `select` |
   679→| Checkboxes | `checkbox` |
   680→| Validation errors | `field` error display |
   681→| Tags description | `field` description |
   682→| Save/discard buttons | `button` |
   683→| Delete confirmation | `modal` |
   684→| Section dividers | `separator` |
   685→| Variant icons | `icon` |
   686→| Status text | `text` |
   687→
   688→### View States
   689→
   690→| State | Behavior |
   691→|-------|----------|
   692→| Loading (save) | Save button disabled, text changes to "Saving..." |
   693→| Loading (upload) | Progress bar shown in media section |
   694→| Create mode | Empty form, no delete button, heading says "Add product" |
   695→| Edit mode | Pre-filled form, delete button visible, heading shows product title |
   696→| Validation error | Inline error messages below each invalid field |
   697→
   698→---
   699→
   700→## 5. Collections List and Edit
   701→
   702→### 5.1 Collections List
   703→
   704→**Route:** `GET /admin/collections`
   705→
   706→**Livewire component:** `App\Livewire\Admin\Collections\Index`
   707→
   708→**Blade view:** `resources/views/livewire/admin/collections/index.blade.php`
   709→
   710→#### Component Requirements
   711→
   712→| Property | Type | Purpose |
   713→|----------|------|---------|
   714→| `search` | `string` | Search query |
   715→| `statusFilter` | `string` | `all`, `active`, `archived` |
   716→
   717→| Action | Behavior |
   718→|--------|----------|
   719→| `updatedSearch()` | Resets pagination |
   720→| `deleteCollection(int $id)` | Deletes a collection after confirmation |
   721→
   722→| Computed Property | Purpose |
   723→|-------------------|---------|
   724→| `collections` | Paginated, filtered, searched. Includes `withCount('products')`. |
   725→
   726→#### Layout
   727→
   728→Same pattern as Products list:
   729→- Heading "Collections" with "Add collection" primary button
   730→- Search input
   731→- Status filter dropdown
   732→
   733→#### Table Columns
   734→
   735→| Column | Content |
   736→|--------|---------|
   737→| Title | Link to edit page |
   738→| Products count | Number of products in collection |
   739→| Status | Flux badge |
   740→| Updated date | Relative timestamp |
   741→
   742→**Empty state:** "Create your first collection" with CTA button.
   743→
   744→### 5.2 Collection Edit/Create
   745→
   746→**Route:** `GET /admin/collections/create` and `GET /admin/collections/{collection}/edit`
   747→
   748→**Livewire component:** `App\Livewire\Admin\Collections\Form`
   749→
   750→**Blade view:** `resources/views/livewire/admin/collections/form.blade.php`
   751→
   752→#### Component Requirements
   753→
   754→| Property | Type | Purpose |
   755→|----------|------|---------|
   756→| `collection` | `?Collection` | The collection being edited |
   757→| `title` | `string` | Collection title |
   758→| `handle` | `string` | URL slug |
   759→| `descriptionHtml` | `string` | Collection description |
   760→| `status` | `string` | `active` or `archived` |
   761→| `productSearch` | `string` | Search input for product assignment |
   762→| `assignedProductIds` | `array<int>` | Ordered list of product IDs in this collection |
   763→
   764→| Action | Behavior |
   765→|--------|----------|
   766→| `save()` | Validates and saves the collection with product assignments |
   767→| `addProduct(int $productId)` | Adds a product to the collection |
   768→| `removeProduct(int $productId)` | Removes a product from the collection |
   769→| `reorderProducts(array $order)` | Updates product positions |
   770→
   771→| Computed Property | Purpose |
   772→|-------------------|---------|
   773→| `searchResults` | Products matching `productSearch` that are not already assigned |
   774→| `assignedProducts` | Ordered collection of assigned Product models |
   775→
   776→#### Layout Wireframe
   777→
   778→```
   779→+------------------------------------------+-------------------+
   780→|          LEFT COLUMN (2/3)               | RIGHT COLUMN (1/3)|
   781→+------------------------------------------+-------------------+
   782→|  Title: [Summer Collection         ]     | Status: [Active v] |
   783→|  Handle: [summer-collection        ]     |                   |
   784→|  Description:                            |                   |
   785→|  [textarea...                      ]     |                   |
   786→|                                          |                   |
   787→|  PRODUCTS                                |                   |
   788→|  Search: [Search products...       ]     |                   |
   789→|  +----------------------------------+   |                   |
   790→|  | Blue Shirt           [Add]       |   |                   |
   791→|  | Red Hat              [Add]       |   |                   |
   792→|  +----------------------------------+   |                   |
   793→|                                          |                   |
   794→|  Assigned products:                      |                   |
   795→|  [drag] [img] Sneakers         [remove]  |                   |
   796→|  [drag] [img] Blue Shirt       [remove]  |                   |
   797→+------------------------------------------+-------------------+
   798→
   799→+==================================================================+
   800→| STICKY SAVE BAR                          [Discard]  [Save]       |
   801→+==================================================================+
   802→```
   803→
   804→**Left column:**
   805→- Title input
   806→- Handle input
   807→- Description textarea
   808→- Products assignment section:
   809→  - Search input with live debounce (300ms)
   810→  - Search results dropdown (conditionally shown): list of matching products with "Add" button
   811→  - Assigned products list: each row shows product title, thumbnail, and a remove button. Rows are sortable via drag-to-reorder.
   812→
   813→**Right column:**
   814→- Status: Flux select with Active / Archived
   815→- Sticky save bar (same pattern as product form)
   816→
   817→---
   818→
   819→## 6. Inventory Management
   820→
   821→**Route:** `GET /admin/inventory`
   822→
   823→**Livewire component:** `App\Livewire\Admin\Inventory\Index`
   824→
   825→**Blade view:** `resources/views/livewire/admin/inventory/index.blade.php`
   826→
   827→### Component Requirements
   828→
   829→| Property | Type | Purpose |
   830→|----------|------|---------|
   831→| `search` | `string` | Search by product title or SKU |
   832→| `stockFilter` | `string` | `all`, `in_stock`, `low_stock`, `out_of_stock` |
   833→
   834→| Computed Property | Purpose |
   835→|-------------------|---------|
   836→| `inventoryItems` | Paginated inventory items with variant and product eager loaded, filtered by search and stock level |
   837→
   838→### Wireframe
   839→
   840→```
   841→+------------------------------------------------------------------+
   842→| Inventory                                                         |
   843→+------------------------------------------------------------------+
   844→| [search] Search by product or SKU   [Stock: All v]               |
   845→+------------------------------------------------------------------+
   846→| Product | Variant | SKU     | On Hand | Reserved | Policy | Act  |
   847→|---------|---------|---------|---------|----------|--------|------|
   848→| Shirt   | S/Blue  | SH-001  | [45]    | 3        | deny   | ...  |
   849→| Shirt   | M/Blue  | SH-002  | [0]     | 0        | deny   | ...  |
   850→| Hat     | Red     | HT-001  | [12]    | 1        | cont   | ...  |
   851→+------------------------------------------------------------------+
   852→```
   853→
   854→### Layout
   855→
   856→- Heading "Inventory"
   857→- Search input and stock filter dropdown
   858→- Table columns: Product, Variant, SKU, On Hand (inline-editable number input), Reserved, Policy (shown as Flux badge: "deny" or "continue"), Actions
   859→
   860→---
   861→
   862→## 7. Orders List
   863→
   864→**Route:** `GET /admin/orders`
   865→
   866→**Livewire component:** `App\Livewire\Admin\Orders\Index`
   867→
   868→**Blade view:** `resources/views/livewire/admin/orders/index.blade.php`
   869→
   870→### Component Requirements
   871→
   872→| Property | Type | Purpose |
   873→|----------|------|---------|
   874→| `search` | `string` | Search by order number or customer email |
   875→| `statusFilter` | `string` | `all`, `pending`, `paid`, `fulfilled`, `cancelled`, `refunded` |
   876→| `sortField` | `string` | Sort column (default: `placed_at`) |
   877→| `sortDirection` | `string` | `asc` or `desc` (default: `desc`) |
   878→
   879→| Action | Behavior |
   880→|--------|----------|
   881→| `updatedSearch()` | Resets pagination |
   882→| `updatedStatusFilter()` | Resets pagination |
   883→| `sortBy(string $field)` | Updates sort |
   884→
   885→| Computed Property | Purpose |
   886→|-------------------|---------|
   887→| `orders` | Paginated query with search, status filter, sorting. Eager loads `customer`. |
   888→
   889→### Wireframe
   890→
   891→```
   892→+------------------------------------------------------------------+
   893→| Orders                                                            |
   894→+------------------------------------------------------------------+
   895→| [search] Search by order # or email...                           |
   896→+------------------------------------------------------------------+
   897→| [All] [Pending] [Paid] [Fulfilled] [Cancelled] [Refunded]       |
   898→+------------------------------------------------------------------+
   899→| Order #  | Date           | Customer  | Payment   | Fulfill | Total  |
   900→|----------|----------------|-----------|-----------|---------|--------|
   901→| #1001    | Jan 15, 2:30 PM| John Doe  | Paid      | Unful.  | $131.36|
   902→| #1000    | Jan 14, 9:15 AM| Guest     | Pending   | Unful.  | $45.00 |
   903→+------------------------------------------------------------------+
   904→| < 1 2 3 >                                                        |
   905→+------------------------------------------------------------------+
   906→```
   907→
   908→### Filter Tabs
   909→
   910→A horizontal row of tab-style buttons (plain Tailwind, not Flux component). Options: All, Pending, Paid, Fulfilled, Cancelled, Refunded. The active tab gets a bottom border and bold text. Inactive tabs use muted text color.
   911→
   912→### Table Column Definitions
   913→
   914→| Column | Content |
   915→|--------|---------|
   916→| Order number | Link to order detail (e.g., "#1001"), uses Livewire navigate |
   917→| Date | `placed_at` formatted as "M j, Y g:i A" |
   918→| Customer | Customer name or "Guest" |
   919→| Financial status | Flux badge: pending=zinc, paid=green, refunded=yellow, cancelled=red |
   920→| Fulfillment status | Flux badge: unfulfilled=zinc, partial=yellow, fulfilled=green |
   921→| Total | Formatted currency amount |
   922→
   923→Pagination below the table.
   924→
   925→### Flux Component Mapping
   926→
   927→| UI Element | Flux Component |
   928→|-----------|----------------|
   929→| Page title | `heading` |
   930→| Search field | `input` |
   931→| Status badges | `badge` |
   932→| Cell text | `text` |
   933→
   934→---
   935→
   936→## 8. Order Detail
   937→
   938→**Route:** `GET /admin/orders/{order}`
   939→
   940→**Livewire component:** `App\Livewire\Admin\Orders\Show`
   941→
   942→**Blade view:** `resources/views/livewire/admin/orders/show.blade.php`
   943→
   944→### Component Requirements
   945→
   946→| Property | Type | Purpose |
   947→|----------|------|---------|
   948→| `order` | `Order` | The order model (eager loaded with lines, payments, fulfillments, customer) |
   949→| `fulfillmentLines` | `array` | Lines/quantities selected for fulfillment |
   950→| `trackingCompany` | `string` | Tracking company for fulfillment |
   951→| `trackingNumber` | `string` | Tracking number |
   952→| `trackingUrl` | `string` | Tracking URL |
   953→| `refundAmount` | `?int` | Custom refund amount |
   954→| `refundReason` | `string` | Refund reason text |
   955→| `refundLines` | `array` | Lines selected for refund |
   956→
   957→| Action | Behavior |
   958→|--------|----------|
   959→| `mount(Order $order)` | Loads order with all relationships |
   960→| `confirmPayment()` | Confirms bank transfer payment received (transitions financial_status to `paid`, commits inventory, dispatches `OrderPaid`) |
   961→| `createFulfillment()` | Creates a fulfillment record for selected lines (enforces fulfillment guard) |
   962→| `markAsShipped(int $fulfillmentId)` | Transitions fulfillment to `shipped`, sets `shipped_at` |
   963→| `markAsDelivered(int $fulfillmentId)` | Transitions fulfillment to `delivered`, sets `delivered_at` |
   964→| `createRefund()` | Creates a refund for selected lines or custom amount |
   965→| `openFulfillmentModal()` | Opens the fulfillment modal |
   966→| `openRefundModal()` | Opens the refund modal |
   967→
   968→### Two-Column Layout Wireframe
   969→
   970→```
   971→+----------------------------------------------+--------------------+
   972→|           LEFT COLUMN (2/3)                  | RIGHT COL (1/3)    |
   973→+----------------------------------------------+--------------------+
   974→| #1001  [Paid]  [Unfulfilled]                 | CUSTOMER           |
   975→| Jan 15, 2024 2:30 PM                        | John Doe           |
   976→|                                              | john@example.com   |
   977→| [Create fulfillment]  [Refund]               | > View customer    |
   978→|                                              |--------------------|
   979→| TIMELINE                                     | SHIPPING ADDRESS   |
   980→| o  Order placed - Jan 15, 2:30 PM           | 123 Main St        |
   981→| |                                            | Springfield, IL    |
   982→| o  Payment received - Jan 15, 2:31 PM       | 62701              |
   983→| |                                            | United States      |
   984→| o  (more events...)                          |--------------------|
   985→|                                              | BILLING ADDRESS    |
   986→| ORDER LINES                                  | 123 Main St        |
   987→| IMG | Product      | Qty | Unit $ | Total   | Springfield, IL    |
   988→| img | Blue Shirt   | 2   | $60.00 | $120.00 | 62701              |
   989→| img | Red Hat      | 1   | $20.00 | $20.00  | United States      |
   990→|                                              |                    |
   991→|                    Subtotal     $140.00       |                    |
   992→|                    Discount     -$12.00       |                    |
   993→|                    Shipping      $5.00        |                    |
   994→|                    Tax          $18.36        |                    |
   995→|                    Total       $151.36        |                    |
   996→|                                              |                    |
   997→| PAYMENT DETAILS                              |                    |
   998→| Method: Credit Card  [Paid]                  |                    |
   999→| Amount: 151.36 EUR                           |                    |
  1000→| Ref: mock_xxx...                             |                    |
  1001→|                                              |                    |
  1002→| (If bank_transfer + pending:)                |                    |
  1003→| [Confirm Payment] button                     |                    |
  1004→+----------------------------------------------+--------------------+
  1005→```
  1006→
  1007→### Left Column Sections
  1008→
  1009→#### Order Heading
  1010→- Order number as a large heading
  1011→- Financial status badge (color mapped: paid=green, default=zinc)
  1012→- Fulfillment status badge (color mapped: fulfilled=green, default=zinc)
  1013→- Placed-at date as muted text
  1014→
  1015→#### Action Buttons
  1016→Shown below the heading:
  1017→- "Confirm payment" button (primary, shown only when `payment_method = bank_transfer` AND `financial_status = pending`). On click, calls `confirmPayment()` which transitions the order to `paid`, commits inventory, and dispatches `OrderPaid`.
  1018→- "Create fulfillment" button (shown when order is not fully fulfilled AND fulfillment guard passes - see below)
  1019→- "Refund" button (ghost variant, shown when order is paid or partially_refunded)
  1020→
  1021→#### Fulfillment Guard
  1022→
  1023→A Flux `callout` (variant: warning) is displayed when the order's `financial_status` is NOT `paid` and NOT `partially_refunded`:
  1024→
  1025→> **Cannot create fulfillment.** Payment must be confirmed before items can be fulfilled. Current financial status: *{financial_status}*.
  1026→
  1027→When the guard condition is active, the "Create fulfillment" button is disabled (or hidden). This prevents shipping items before payment is secured.
  1028→
  1029→#### Timeline
  1030→A vertical timeline with left border line and dot markers:
  1031→- Each event: title text (bold) and timestamp text (small, muted)
  1032→- Events include: Order placed, Payment received, Fulfillment created, Shipped, Delivered, Refunded (as applicable)
  1033→
  1034→#### Order Lines Table
  1035→
  1036→| Column | Content |
  1037→|--------|---------|
  1038→| Image | 40x40 product thumbnail |
  1039→| Product | Title and SKU |
  1040→| Quantity | Unit count |
  1041→| Unit Price | Formatted amount |
  1042→| Total | Line total formatted |
  1043→
  1044→#### Order Summary
  1045→A right-aligned summary block below the lines table:
  1046→- Subtotal, Discount (negative), Shipping, Tax, Total (bold)
  1047→- Each row is a flex container with label left and amount right
  1048→
  1049→#### Payment Details
  1050→A card showing:
  1051→- Payment method as text (Credit Card / PayPal / Bank Transfer), derived from `order.payment_method` enum
  1052→- Status as Flux badge (pending=zinc, captured=green, failed=red, refunded=yellow)
  1053→- Amount formatted as currency
  1054→- Mock reference ID (provider_payment_id)
  1055→- **"Confirm payment" button** (primary variant): Shown only when `payment_method = bank_transfer` AND `financial_status = pending`. Clicking calls `confirmPayment()`.
  1056→
  1057→#### Fulfillment Cards
  1058→
  1059→Each existing fulfillment is rendered as a card below the timeline. Each card shows:
  1060→- Fulfillment status badge (pending=zinc, shipped=blue, delivered=green)
  1061→- Tracking info (company, number, URL link)
  1062→- Line items included in the fulfillment
  1063→- **Action buttons on each fulfillment card:**
  1064→  - "Mark as shipped" button (shown when fulfillment status is `pending`). On click, calls `markAsShipped(fulfillmentId)`. Sets `shipped_at` timestamp.
  1065→  - "Mark as delivered" button (shown when fulfillment status is `shipped`). On click, calls `markAsDelivered(fulfillmentId)`. Sets `delivered_at` timestamp. When all fulfillments are delivered, the order's `fulfillment_status` transitions to `fulfilled`.
  1066→
  1067→### Right Column Sections
  1068→
  1069→#### Customer Card
  1070→- Heading "Customer" (size md)
  1071→- Separator
  1072→- Customer name (or "Guest")
  1073→- Customer email (muted text)
  1074→- "View customer" link (shown only if customer exists, links to customer detail page)
  1075→
  1076→#### Shipping Address Card
  1077→- Heading "Shipping address" (size md)
  1078→- Separator
  1079→- Full address rendered from shipping_address_json: line1, city/state/zip, country
  1080→
  1081→#### Billing Address Card
  1082→Same pattern as shipping address, using billing_address_json.
  1083→
  1084→### Fulfillment Modal
  1085→
  1086→| Property | Value |
  1087→|----------|-------|
  1088→| Modal name | `create-fulfillment` |
  1089→| Max width | lg |
  1090→| Heading | "Create fulfillment" |
  1091→
  1092→**Content:**
  1093→- For each unfulfilled order line: a row with checkbox, line title + unfulfilled quantity text, and a number input for quantity to fulfill (min 1, max unfulfilled quantity)
  1094→- Separator
  1095→- Tracking company input field (placeholder: "UPS, FedEx, DHL...")
  1096→- Tracking number input field
  1097→- Tracking URL input field (type: url)
  1098→- Cancel button (ghost) and "Create fulfillment" button (primary)
  1099→
  1100→### Refund Modal
  1101→
  1102→| Property | Value |
  1103→|----------|-------|
  1104→| Modal name | `create-refund` |
  1105→| Max width | lg |
  1106→| Heading | "Refund order" |
  1107→
  1108→**Content:**
  1109→- For each order line: a row with checkbox, line title text, and a number input for quantity to refund (min 0, max original quantity)
  1110→- Separator
  1111→- "Or enter custom amount" number input field (placeholder: "0.00")
  1112→- "Reason" textarea (3 rows, placeholder: "Reason for refund...")
  1113→- Cancel button (ghost) and "Create refund" button (danger)
  1114→
  1115→### Flux Component Mapping
  1116→
  1117→| UI Element | Flux Component |
  1118→|-----------|----------------|
  1119→| Order number heading | `heading` |
  1120→| Status badges | `badge` |
  1121→| Action buttons | `button` |
  1122→| Section headings | `heading` (size md) |
  1123→| Section dividers | `separator` |
  1124→| Text content | `text` |
  1125→| Both modals | `modal` |
  1126→| Modal form fields | `field`, `input`, `textarea` |
  1127→| Line checkboxes | `checkbox` |
  1128→| Modal buttons | `button` |
  1129→| Icons | `icon` |
  1130→| Callout messages | `callout` |
  1131→
  1132→---
  1133→
  1134→## 9. Customers List and Detail
  1135→
  1136→### 9.1 Customers List
  1137→
  1138→**Route:** `GET /admin/customers`
  1139→
  1140→**Livewire component:** `App\Livewire\Admin\Customers\Index`
  1141→
  1142→**Blade view:** `resources/views/livewire/admin/customers/index.blade.php`
  1143→
  1144→#### Component Requirements
  1145→
  1146→| Property | Type | Purpose |
  1147→|----------|------|---------|
  1148→| `search` | `string` | Search by name or email |
  1149→
  1150→| Computed Property | Purpose |
  1151→|-------------------|---------|
  1152→| `customers` | Paginated, searched customers with `withCount('orders')` and `withSum('orders', 'total_amount')` |
  1153→
  1154→#### Layout
  1155→
  1156→- Heading "Customers"
  1157→- Search input
  1158→- Table columns: Name, Email, Orders count, Total spent (formatted), Created date
  1159→- Each row links to customer detail
  1160→
  1161→### 9.2 Customer Detail
  1162→
  1163→**Route:** `GET /admin/customers/{customer}`
  1164→
  1165→**Livewire component:** `App\Livewire\Admin\Customers\Show`
  1166→
  1167→**Blade view:** `resources/views/livewire/admin/customers/show.blade.php`
  1168→
  1169→#### Component Requirements
  1170→
  1171→| Property | Type | Purpose |
  1172→|----------|------|---------|
  1173→| `customer` | `Customer` | The customer model |
  1174→| `editingAddress` | `?CustomerAddress` | Address currently being edited |
  1175→| `addressLabel` | `string` | Label for address form |
  1176→| `addressJson` | `array` | Address fields being edited |
  1177→
  1178→| Action | Behavior |
  1179→|--------|----------|
  1180→| `mount(Customer $customer)` | Loads customer with orders and addresses |
  1181→| `openAddressForm(?CustomerAddress $address)` | Opens address modal for create/edit |
  1182→| `saveAddress()` | Saves the address |
  1183→| `deleteAddress(int $addressId)` | Deletes an address |
  1184→| `setDefaultAddress(int $addressId)` | Sets address as default |
  1185→
  1186→#### Two-Column Layout Wireframe
  1187→
  1188→```
  1189→+----------------------------------------------+--------------------+
  1190→|           LEFT COLUMN (2/3)                  | RIGHT COL (1/3)    |
  1191→+----------------------------------------------+--------------------+
  1192→| CUSTOMER INFO                                | ADDRESSES          |
  1193→| Name: Jane Smith                             | Home (default)     |
  1194→| Email: jane@example.com                      | 123 Main St        |
  1195→| Created: Jan 1, 2024                         | Springfield, IL    |
  1196→| Marketing: [Opted In]                        | [Edit] [Delete]    |
  1197→|                                              |                    |
  1198→| ORDER HISTORY                                | Office             |
  1199→| Order # | Date      | Status | Total         | 456 Corp Ave       |
  1200→| #1005   | Jan 20    | Paid   | $89.00        | Chicago, IL        |
  1201→| #1001   | Jan 15    | Paid   | $131.36       | [Edit] [Delete]    |
  1202→| < 1 2 >                                     |                    |
  1203→|                                              | [+ Add address]    |
  1204→+----------------------------------------------+--------------------+
  1205→```
  1206→
  1207→**Left column:**
  1208→- Customer info card: Name, email, created date, marketing opt-in status (Flux badge)
  1209→- Order history table: columns Order #, Date, Status, Total. Each order links to order detail. Paginated.
  1210→
  1211→**Right column:**
  1212→- Addresses card: List of addresses, each showing label, full address text, default badge (if `is_default`), Edit and Delete buttons
  1213→- "Add address" button opens address modal
  1214→
  1215→#### Address Modal
  1216→
  1217→| Property | Value |
  1218→|----------|-------|
  1219→| Modal name | `address-form` |
  1220→| Max width | md |
  1221→| Heading | "Edit address" or "Add address" (dynamic) |
  1222→
  1223→**Form fields:**
  1224→- Label input (placeholder: "Home, Office...")
  1225→- Address line 1 input
  1226→- Address line 2 input
  1227→- City and State/Province inputs (side by side, 2-column grid)
  1228→- ZIP/Postal code and Country select (side by side, 2-column grid)
  1229→- Cancel button (ghost) and "Save" button (primary)
  1230→
  1231→---
  1232→
  1233→## 10. Discounts List and Form
  1234→
  1235→### 10.1 Discounts List
  1236→
  1237→**Route:** `GET /admin/discounts`
  1238→
  1239→**Livewire component:** `App\Livewire\Admin\Discounts\Index`
  1240→
  1241→**Blade view:** `resources/views/livewire/admin/discounts/index.blade.php`
  1242→
  1243→#### Component Requirements
  1244→
  1245→| Property | Type | Purpose |
  1246→|----------|------|---------|
  1247→| `search` | `string` | Search by discount code |
  1248→| `statusFilter` | `string` | `all`, `active`, `expired`, `scheduled` |
  1249→
  1250→| Computed Property | Purpose |
  1251→|-------------------|---------|
  1252→| `discounts` | Paginated, filtered discounts |
  1253→
  1254→#### Layout
  1255→
  1256→- Heading "Discounts" with "Create discount" primary button
  1257→- Search input and status filter
  1258→
  1259→#### Table Columns
  1260→
  1261→| Column | Content |
  1262→|--------|---------|
  1263→| Code | Discount code or "Automatic" label |
  1264→| Type | Flux badge: "Code" or "Automatic" |
  1265→| Value | e.g., "10%" or "$5.00" or "Free shipping" |
  1266→| Usage | "3 / 100" (used / limit) or "3 / unlimited" |
  1267→| Status | Flux badge: active=green, expired=red, scheduled=yellow |
  1268→| Dates | Start and end dates |
  1269→
  1270→### 10.2 Discount Form
  1271→
  1272→**Route:** `GET /admin/discounts/create` and `GET /admin/discounts/{discount}/edit`
  1273→
  1274→**Livewire component:** `App\Livewire\Admin\Discounts\Form`
  1275→
  1276→**Blade view:** `resources/views/livewire/admin/discounts/form.blade.php`
  1277→
  1278→#### Component Requirements
  1279→
  1280→| Property | Type | Purpose |
  1281→|----------|------|---------|
  1282→| `discount` | `?Discount` | The discount being edited |
  1283→| `type` | `string` | `code` or `automatic` |
  1284→| `code` | `string` | Discount code (if type is code) |
  1285→| `valueType` | `string` | `percent`, `fixed`, `free_shipping` |
  1286→| `valueAmount` | `?int` | Discount value (percentage or fixed amount) |
  1287→| `minimumPurchaseAmount` | `?int` | Minimum cart value for eligibility |
  1288→| `specificProductIds` | `array<int>` | Specific products the discount applies to |
  1289→| `specificCollectionIds` | `array<int>` | Specific collections the discount applies to |
  1290→| `usageLimit` | `?int` | Maximum total uses |
  1291→| `onePerCustomer` | `bool` | Limit to one use per customer |
  1292→| `startsAt` | `string` | Start datetime |
  1293→| `endsAt` | `?string` | End datetime (optional) |
  1294→| `isActive` | `bool` | Whether the discount is active |
  1295→| `productSearch` | `string` | Search for products to add |
  1296→| `collectionSearch` | `string` | Search for collections to add |
  1297→
  1298→| Action | Behavior |
  1299→|--------|----------|
  1300→| `save()` | Validates and saves the discount |
  1301→| `generateCode()` | Auto-generates a random discount code |
  1302→| `addProduct(int $productId)` | Adds product to specific products list |
  1303→| `removeProduct(int $productId)` | Removes product |
  1304→| `addCollection(int $collectionId)` | Adds collection to specific collections |
  1305→| `removeCollection(int $collectionId)` | Removes collection |
  1306→
  1307→#### Layout Wireframe (Single Column)
  1308→
  1309→```
  1310→+==================================================================+
  1311→| TYPE CARD                                                         |
  1312→|  ( ) Discount code - Customers enter a code at checkout          |
  1313→|  (o) Automatic discount - Applied automatically at checkout      |
  1314→+==================================================================+
  1315→| CODE CARD (shown when type = "code")                             |
  1316→|  Discount code: [SUMMER20             ]  [Generate]              |
  1317→+==================================================================+
  1318→| VALUE CARD                                                        |
  1319→|  ( ) Percentage                                                   |
  1320→|  (o) Fixed amount                                                 |
  1321→|  ( ) Free shipping                                                |
  1322→|                                                                   |
  1323→|  Amount: [5.00                        ] (hidden for free shipping)|
  1324→+==================================================================+
  1325→| CONDITIONS CARD                                                   |
  1326→|  Minimum purchase amount: [0.00       ]                          |
  1327→|  (Leave empty for no minimum)                                     |
  1328→|                                                                   |
  1329→|  Specific products:                                               |
  1330→|  [Search products...          ]                                   |
  1331→|  +---results dropdown---+                                         |
  1332→|  Selected: Blue Shirt [x]  Red Hat [x]                           |
  1333→|                                                                   |
  1334→|  Specific collections: (same pattern as products)                 |
  1335→+==================================================================+
  1336→| USAGE LIMITS CARD                                                 |
  1337→|  Total usage limit: [100              ] (or leave blank)         |
  1338→|  [x] Limit to one use per customer                               |
  1339→+==================================================================+
  1340→| ACTIVE DATES CARD                                                 |
  1341→|  Start date: [2024-01-15 00:00       ]                           |
  1342→|  End date:   [2024-02-15 00:00       ] (or leave blank)          |
  1343→+==================================================================+
  1344→| STATUS                                                            |
  1345→|  [switch: ON]  Active                                             |
  1346→+==================================================================+
  1347→| STICKY SAVE BAR                          [Discard]  [Save]       |
  1348→+==================================================================+
  1349→```
  1350→
  1351→#### Section Details
  1352→
  1353→**Type section:** Two radio buttons - "Discount code" (description: "Customers enter a code at checkout") and "Automatic discount" (description: "Applied automatically at checkout"). Selecting type is live-bound.
  1354→
  1355→**Code input:** Shown only when type = "code". Text input with placeholder "SUMMER20" and a "Generate" ghost button that auto-generates a random code.
  1356→
  1357→**Value section:** Three radio buttons for value type: Percentage, Fixed amount, Free shipping. When not free shipping, a number input appears for the amount (label changes based on type: "Percentage" or "Amount").
  1358→
  1359→**Conditions section:**
  1360→- Minimum purchase amount: number input with description "Leave empty for no minimum"
  1361→- Specific products: search input with live debounce. Results appear in a scrollable dropdown. Selected products shown as a list with remove (X) buttons.
  1362→- Specific collections: same pattern as products.
  1363→
  1364→**Usage limits section:**
  1365→- Total usage limit number input (placeholder: "Unlimited")
  1366→- "Limit to one use per customer" checkbox
  1367→
  1368→**Active dates section:**
  1369→- Start date: datetime-local input
  1370→- End date: datetime-local input with description "Leave empty for no end date"
  1371→
  1372→**Status:** Toggle switch with text label "Active" or "Disabled".
  1373→
  1374→Sticky save bar at the bottom (same pattern as product form).
  1375→
  1376→---
  1377→
  1378→## 11. Settings Pages
  1379→
  1380→### 11.1 General Settings
  1381→
  1382→**Route:** `GET /admin/settings`
  1383→
  1384→**Livewire component:** `App\Livewire\Admin\Settings\General`
  1385→
  1386→**Blade view:** `resources/views/livewire/admin/settings/general.blade.php`
  1387→
  1388→#### Component Requirements
  1389→
  1390→| Property | Type | Purpose |
  1391→|----------|------|---------|
  1392→| `storeName` | `string` | Store display name |
  1393→| `storeHandle` | `string` | Store handle (read-only or with warning) |
  1394→| `defaultCurrency` | `string` | Currency code (e.g., "EUR") |
  1395→| `defaultLocale` | `string` | Locale code (e.g., "en") |
  1396→| `timezone` | `string` | Timezone string |
  1397→
  1398→| Action | Behavior |
  1399→|--------|----------|
  1400→| `save()` | Validates and saves store settings |
  1401→
  1402→#### Layout
  1403→
  1404→Two-column layout per section: left side has section label and description, right side has form fields.
  1405→
  1406→**Store details section:**
  1407→- Left: Heading "Store details" + description "Basic information about your store."
  1408→- Right: Store name input, Store handle input (disabled, with description: "The store handle cannot be changed after creation.")
  1409→
  1410→Separator between sections.
  1411→
  1412→**Defaults section:**
  1413→- Left: Heading "Defaults" + description "Currency, language, and timezone settings."
  1414→- Right:
  1415→  - Default currency: Select (EUR/USD/GBP and more)
  1416→  - Default locale: Select (English/German/French and more)
  1417→  - Timezone: Select (populated from PHP timezone list)
  1418→
  1419→Save button at the bottom (primary variant, right-aligned).
  1420→
  1421→### 11.2 Domains Settings
  1422→
  1423→Domains is a tab within the Settings page (`/admin/settings`), not a separate route. The Settings page has tabs: General, Domains, Shipping, Taxes, Checkout, Notifications.
  1424→
  1425→**Livewire component:** `App\Livewire\Admin\Settings\Domains`
  1426→
  1427→**Blade view:** `resources/views/livewire/admin/settings/domains.blade.php`
  1428→
  1429→#### Component Requirements
  1430→
  1431→| Property | Type | Purpose |
  1432→|----------|------|---------|
  1433→| `domains` | `Collection` | Store domains |
  1434→| `newHostname` | `string` | Hostname for new domain |
  1435→| `newType` | `string` | Type for new domain: `storefront`, `admin`, `api` |
  1436→
  1437→| Action | Behavior |
  1438→|--------|----------|
  1439→| `addDomain()` | Creates a new domain record |
  1440→| `removeDomain(int $domainId)` | Removes a domain |
  1441→| `setPrimary(int $domainId)` | Sets domain as primary |
  1442→
  1443→#### Layout
  1444→
  1445→- Heading "Domains"
  1446→- Table of existing domains:
  1447→
  1448→| Column | Content |
  1449→|--------|---------|
  1450→| Hostname | Domain hostname |
  1451→| Type | Flux badge: storefront/admin/api |
  1452→| Primary | Flux badge (green) "Primary" if `is_primary` |
  1453→| TLS | Flux badge showing TLS mode |
  1454→| Actions | "Set Primary" button, "Delete" button |
  1455→
  1456→- "Add domain" button opens a modal
  1457→
  1458→#### Add Domain Modal
  1459→
  1460→| Property | Value |
  1461→|----------|-------|
  1462→| Modal name | `add-domain` |
  1463→| Max width | md |
  1464→| Heading | "Add domain" |
  1465→
  1466→**Form fields:**
  1467→- Hostname input (placeholder: "shop.example.com")
  1468→- Type select: Storefront, Admin, API
  1469→- Cancel button (ghost) and "Add domain" button (primary)
  1470→
  1471→### 11.3 Shipping Settings
  1472→
  1473→**Route:** `GET /admin/settings/shipping`
  1474→
  1475→**Livewire component:** `App\Livewire\Admin\Settings\Shipping`
  1476→
  1477→**Blade view:** `resources/views/livewire/admin/settings/shipping.blade.php`
  1478→
  1479→#### Component Requirements
  1480→
  1481→| Property | Type | Purpose |
  1482→|----------|------|---------|
  1483→| `zones` | `Collection` | Shipping zones with rates |
  1484→| `editingZone` | `?ShippingZone` | Zone being edited |
  1485→| `zoneName` | `string` | Name for zone form |
  1486→| `zoneCountries` | `array` | Selected countries for zone |
  1487→| `editingRate` | `?ShippingRate` | Rate being edited |
  1488→| `rateName` | `string` | Rate name |
  1489→| `rateType` | `string` | `flat`, `weight`, `price`, `carrier` |
  1490→| `rateConfig` | `array` | Rate configuration (depends on type) |
  1491→| `rateActive` | `bool` | Whether rate is active |
  1492→| `testAddress` | `array` | Address fields for test tool |
  1493→| `testResult` | `?array` | Result of test address lookup |
  1494→
  1495→| Action | Behavior |
  1496→|--------|----------|
  1497→| `openZoneModal(?ShippingZone $zone)` | Opens zone create/edit modal |
  1498→| `saveZone()` | Saves shipping zone |
  1499→| `deleteZone(int $zoneId)` | Deletes a shipping zone |
  1500→| `openRateModal(int $zoneId, ?ShippingRate $rate)` | Opens rate create/edit modal |
  1501→| `saveRate()` | Saves shipping rate |
  1502→| `deleteRate(int $rateId)` | Deletes a rate |
  1503→| `testShippingAddress()` | Tests which zone/rate matches a given address |
  1504→
  1505→#### Layout Wireframe
  1506→
  1507→```
  1508→+==================================================================+
  1509→| Shipping                                         [+ Add zone]    |
  1510→+==================================================================+
  1511→|                                                                   |
  1512→| +--------------------------------------------------------------+ |
  1513→| | ZONE: Domestic                            [Edit] [Delete]    | |
  1514→| | Countries: US, CA                                             | |
  1515→| |                                                               | |
  1516→| | Name      | Type    | Config           | Active | Actions    | |
  1517→| |-----------|---------|------------------|--------|------------|  |
  1518→| | Standard  | flat    | $5.00            | [on]   | [Ed] [Del]| |
  1519→| | Express   | flat    | $15.00           | [on]   | [Ed] [Del]| |
  1520→| | [+ Add rate]                                                  | |
  1521→| +--------------------------------------------------------------+ |
  1522→|                                                                   |
  1523→| +--------------------------------------------------------------+ |
  1524→| | ZONE: Europe                              [Edit] [Delete]    | |
  1525→| | Countries: DE, FR, GB, ...                                    | |
  1526→| | (rates table...)                                              | |
  1527→| +--------------------------------------------------------------+ |
  1528→|                                                                   |
  1529→| TEST SHIPPING ADDRESS                                             |
  1530→| Country: [v]  State: [    ]  City: [    ]  ZIP: [    ]           |
  1531→| [Test]                                                            |
  1532→| Result: Matched zone: Domestic                                    |
  1533→|         Standard - $5.00 / Express - $15.00                      |
  1534→+==================================================================+
  1535→```
  1536→
  1537→Each zone is displayed as a card with:
  1538→- Zone name heading and Edit/Delete buttons in the header
  1539→- Countries list as muted text
  1540→- Rates table inside the zone card with columns: Name, Type (as badge), Config summary, Active (toggle switch), Actions (Edit/Delete buttons)
  1541→- "Add rate" button at the bottom of each zone card
  1542→
  1543→"Add zone" button at the top of the page.
  1544→
  1545→#### Zone Modal
  1546→
  1547→| Property | Value |
  1548→|----------|-------|
  1549→| Modal name | `zone-form` |
  1550→| Max width | md |
  1551→| Heading | "Edit shipping zone" or "Add shipping zone" (dynamic) |
  1552→
  1553→**Form fields:**
  1554→- Zone name input (placeholder: "Domestic, Europe, International...")
  1555→- Countries: scrollable checklist of all available countries with checkboxes
  1556→- Cancel button (ghost) and "Save zone" button (primary)
  1557→
  1558→#### Rate Modal
  1559→
  1560→| Property | Value |
  1561→|----------|-------|
  1562→| Modal name | `rate-form` |
  1563→| Max width | md |
  1564→| Heading | "Edit shipping rate" or "Add shipping rate" (dynamic) |
  1565→
  1566→**Form fields:**
  1567→- Rate name input (placeholder: "Standard, Express...")
  1568→- Rate type select: Flat rate, Weight-based, Price-based, Carrier-calculated
  1569→- Dynamic config fields based on rate type:
  1570→  - **Flat:** Price input
  1571→  - **Weight-based:** Min weight (g) input, Max weight (g) input, Price input
  1572→  - **Price-based:** Min order amount input, Max order amount input, Price input
  1573→  - **Carrier:** Info callout: "Carrier-calculated rates require a carrier integration to be configured."
  1574→- Active toggle switch with "Active" text label
  1575→- Cancel button (ghost) and "Save rate" button (primary)
  1576→
  1577→#### Test Shipping Address Tool
  1578→
  1579→A card section at the bottom of the page:
  1580→- Heading "Test shipping address"
  1581→- Description: "Enter an address to see which shipping zone and rates match."
  1582→- 2-column grid of fields: Country (select), State/Region (input), City (input), ZIP/Postal code (input)
  1583→- "Test" button
  1584→- Result area (shown after test):
  1585→  - If matched: "Matched zone: [zone name]" and list of available rates with prices
  1586→  - If no match: Warning callout "No shipping zone matches this address."
  1587→
  1588→### 11.4 Tax Settings
  1589→
  1590→**Route:** `GET /admin/settings/taxes`
  1591→
  1592→**Livewire component:** `App\Livewire\Admin\Settings\Taxes`
  1593→
  1594→**Blade view:** `resources/views/livewire/admin/settings/taxes.blade.php`
  1595→
  1596→#### Component Requirements
  1597→
  1598→| Property | Type | Purpose |
  1599→|----------|------|---------|
  1600→| `mode` | `string` | `manual` or `provider` |
  1601→| `pricesIncludeTax` | `bool` | Whether prices include tax |
  1602→| `provider` | `string` | Provider name (e.g., `stripe_tax`) |
  1603→| `providerApiKey` | `string` | API key for tax provider |
  1604→| `manualRates` | `array` | Array of `{zone_name, rate_percentage}` |
  1605→
  1606→| Action | Behavior |
  1607→|--------|----------|
  1608→| `save()` | Saves tax settings |
  1609→| `addManualRate()` | Adds a new empty rate row |
  1610→| `removeManualRate(int $index)` | Removes a manual rate |
  1611→
  1612→#### Layout Wireframe
  1613→
  1614→```
  1615→+==================================================================+
  1616→| TAXES                                                             |
  1617→+==================================================================+
  1618→| MODE SELECTION                                                    |
  1619→|  ( ) Manual tax rates - Define tax rates per zone manually       |
  1620→|  (o) Tax provider - Use an automated tax calculation service     |
  1621→+==================================================================+
  1622→| MANUAL RATES TABLE (shown when mode = "manual")                  |
  1623→| Zone name       | Rate (%)    |                                   |
  1624→|-----------------|-------------|                                   |
  1625→| [EU           ] | [19.00    ] | [trash]                          |
  1626→| [US-CA        ] | [7.25     ] | [trash]                          |
  1627→| [+ Add rate]                                                      |
  1628→+==================================================================+
  1629→| PROVIDER CONFIG (shown when mode = "provider")                   |
  1630→|  Provider: [Stripe Tax v]                                        |
  1631→|  API key:  [*************]                                       |
  1632→+==================================================================+
  1633→| [separator]                                                       |
  1634→| TAX-INCLUSIVE TOGGLE                                              |
  1635→|  [switch: ON]  Prices include tax                                |
  1636→|  Description: When enabled, the listed price includes tax.       |
  1637→|  Tax is calculated backwards from the price.                      |
  1638→+==================================================================+
  1639→| [Save]                                                            |
  1640→+==================================================================+
  1641→```
  1642→
  1643→**Mode selection:** Two radio buttons - "Manual tax rates" (description: "Define tax rates per zone manually") and "Tax provider" (description: "Use an automated tax calculation service"). Live-bound.
  1644→
  1645→**Manual rates table:** Shown when mode = "manual". Inline-editable table with columns: Zone name (text input), Rate % (number input with step 0.01). Each row has a trash button. "Add rate" ghost button with plus icon.
  1646→
  1647→**Provider config:** Shown when mode = "provider". Provider select (Stripe Tax / None) and API key password input.
  1648→
  1649→**Tax-inclusive toggle:** Toggle switch with "Prices include tax" label and description text explaining the behavior.
  1650→
  1651→Save button at the bottom.
  1652→
  1653→---
  1654→
  1655→## 12. Themes Management
  1656→
  1657→### 12.1 Themes List
  1658→
  1659→**Route:** `GET /admin/themes`
  1660→
  1661→**Livewire component:** `App\Livewire\Admin\Themes\Index`
  1662→
  1663→**Blade view:** `resources/views/livewire/admin/themes/index.blade.php`
  1664→
  1665→#### Component Requirements
  1666→
  1667→| Property | Type | Purpose |
  1668→|----------|------|---------|
  1669→| `themes` | `Collection` | All themes for the store |
  1670→
  1671→| Action | Behavior |
  1672→|--------|----------|
  1673→| `publishTheme(int $themeId)` | Publishes a theme (sets as active) |
  1674→| `duplicateTheme(int $themeId)` | Creates a copy of the theme |
  1675→| `deleteTheme(int $themeId)` | Deletes a theme |
  1676→
  1677→#### Layout Wireframe
  1678→
  1679→```
  1680→+==================================================================+
  1681→| Themes                                                            |
  1682→+==================================================================+
  1683→|                                                                   |
  1684→| +------------------+ +------------------+ +------------------+   |
  1685→| | [preview image]  | | [preview image]  | | [preview image]  |   |
  1686→| |                  | |                  | |                  |   |
  1687→| | My Theme   v1.0  | | Dark Theme v1.2  | | Minimal    v2.0  |   |
  1688→| | [Published]      | | [Draft]          | | [Draft]          |   |
  1689→| |                  | |                  | |                  |   |
  1690→| | [Customize] [...] | | [Customize] [...] | | [Customize] [...] |   |
  1691→| +------------------+ +------------------+ +------------------+   |
  1692→|                                                                   |
  1693→+==================================================================+
  1694→```
  1695→
  1696→**Theme grid:** Responsive 3-column grid (1 on mobile, 2 on md, 3 on lg).
  1697→
  1698→Each theme card contains:
  1699→- **Thumbnail area:** Aspect-video container with theme preview image or placeholder
  1700→- **Info section:**
  1701→  - Theme name (heading, size md) and version text
  1702→  - Status badge (published=green, draft=zinc)
  1703→- **Actions:**
  1704→  - "Customize" primary button (links to theme editor)
  1705→  - More actions dropdown (triggered by ellipsis icon button):
  1706→    - "Preview" menu item
  1707→    - "Publish" menu item (hidden if already published)
  1708→    - "Duplicate" menu item
  1709→    - Separator
  1710→    - "Delete" menu item (styled red/danger)
  1711→
  1712→The published theme card has a blue ring border highlight.
  1713→
  1714→### 12.2 Theme Editor
  1715→
  1716→**Route:** `GET /admin/themes/{theme}/editor`
  1717→
  1718→**Livewire component:** `App\Livewire\Admin\Themes\Editor`
  1719→
  1720→**Blade view:** `resources/views/livewire/admin/themes/editor.blade.php`
  1721→
  1722→#### Component Requirements
  1723→
  1724→| Property | Type | Purpose |
  1725→|----------|------|---------|
  1726→| `theme` | `Theme` | The theme being edited |
  1727→| `sections` | `array` | List of theme sections from settings schema |
  1728→| `selectedSection` | `?string` | Currently selected section key |
  1729→| `sectionSettings` | `array` | Settings values for the selected section |
  1730→| `previewUrl` | `string` | URL for the live preview iframe |
  1731→
  1732→| Action | Behavior |
  1733→|--------|----------|
  1734→| `mount(Theme $theme)` | Loads theme and its sections |
  1735→| `selectSection(string $sectionKey)` | Changes the selected section and loads its settings |
  1736→| `updateSetting(string $key, mixed $value)` | Updates a setting value |
  1737→| `save()` | Saves all section settings to theme_settings |
  1738→| `publish()` | Publishes the theme |
  1739→| `refreshPreview()` | Reloads the preview iframe |
  1740→
  1741→#### Layout Wireframe (Full Width, Three-Panel)
  1742→
  1743→```
  1744→+==================================================================+
  1745→| [<- Back to themes]                     [Save]  [Save & publish] |
  1746→+==================================================================+
  1747→| SECTIONS   |  LIVE PREVIEW (iframe)       |  SETTINGS PANEL      |
  1748→| (264px)    |  (flexible width)            |  (320px)             |
  1749→|            |                              |                      |
  1750→| Header     |  +------------------------+ |  Header Settings     |
  1751→| Hero       |  |                        | |  [separator]         |
  1752→| Features*  |  |  Live storefront       | |                      |
  1753→| Products   |  |  preview rendered      | |  Logo text:          |
  1754→| Footer     |  |  in an iframe          | |  [My Store        ]  |
  1755→|            |  |                        | |                      |
  1756→|            |  |                        | |  Background color:   |
  1757→|            |  |                        | |  [color picker]      |
  1758→|            |  |                        | |                      |
  1759→|            |  |                        | |  Show search:        |
  1760→|            |  |                        | |  [x] checkbox        |
  1761→|            |  |                        | |                      |
  1762→|            |  +------------------------+ |  Layout:             |
  1763→|            |                              |  [Centered       v]  |
  1764→+==================================================================+
  1765→  * = currently selected section (highlighted)
  1766→```
  1767→
  1768→**Full-width layout** (no standard admin shell padding). Three-panel layout:
  1769→
  1770→**Top toolbar:** A horizontal bar with:
  1771→- "Back to themes" button (ghost, with left arrow icon) linking to themes index
  1772→- "Save" ghost button and "Save and publish" primary button
  1773→
  1774→**Left panel (264px):** Section list
  1775→- Heading "Sections"
  1776→- Vertical list of section buttons
  1777→- Active section gets highlighted background and bold text
  1778→- Clicking a section calls `selectSection()`
  1779→
  1780→**Center panel (flexible):** Live preview
  1781→- Background color behind the iframe
  1782→- Full-height iframe loading the storefront preview URL
  1783→- Iframe has rounded border and shadow
  1784→
  1785→**Right panel (320px):** Settings form
  1786→- Shows settings for the selected section
  1787→- Heading with section label
  1788→- Separator
  1789→- Dynamic form fields based on section field definitions:
  1790→
  1791→| Field Type | UI Element |
  1792→|-----------|------------|
  1793→| `text` | Flux input (live-debounced at 500ms) |
  1794→| `textarea` | Flux textarea (live-debounced at 500ms, 3 rows) |
  1795→| `color` | Native color picker input |
  1796→| `select` | Flux select with options from field definition |
  1797→| `checkbox` | Flux checkbox |
  1798→
  1799→- When no section is selected: centered text "Select a section to edit its settings."
  1800→
  1801→---
  1802→
  1803→## 13. Pages Management
  1804→
  1805→### 13.1 Pages List
  1806→
  1807→**Route:** `GET /admin/pages`
  1808→
  1809→**Livewire component:** `App\Livewire\Admin\Pages\Index`
  1810→
  1811→**Blade view:** `resources/views/livewire/admin/pages/index.blade.php`
  1812→
  1813→#### Component Requirements
  1814→
  1815→| Property | Type | Purpose |
  1816→|----------|------|---------|
  1817→| `search` | `string` | Search by page title |
  1818→
  1819→| Computed Property | Purpose |
  1820→|-------------------|---------|
  1821→| `pages` | Paginated pages for the current store |
  1822→
  1823→#### Layout
  1824→
  1825→- Heading "Pages" with "Add page" primary button
  1826→- Search input
  1827→- Table columns: Title (link to edit), Handle, Status (Flux badge), Updated date
  1828→- Pagination
  1829→
  1830→### 13.2 Page Form
  1831→
  1832→**Route:** `GET /admin/pages/create` and `GET /admin/pages/{page}/edit`
  1833→
  1834→**Livewire component:** `App\Livewire\Admin\Pages\Form`
  1835→
  1836→**Blade view:** `resources/views/livewire/admin/pages/form.blade.php`
  1837→
  1838→#### Component Requirements
  1839→
  1840→| Property | Type | Purpose |
  1841→|----------|------|---------|
  1842→| `page` | `?Page` | Page being edited |
  1843→| `title` | `string` | Page title |
  1844→| `handle` | `string` | URL handle |
  1845→| `bodyHtml` | `string` | Page body content (HTML) |
  1846→| `status` | `string` | `draft` or `active` |
  1847→| `publishedAt` | `?string` | Publish date |
  1848→
  1849→| Action | Behavior |
  1850→|--------|----------|
  1851→| `save()` | Validates and saves the page |
  1852→| `deletePage()` | Deletes the page |
  1853→
  1854→#### Layout (Two-Column)
  1855→
  1856→**Left column (2/3):**
  1857→- Title: Flux input
  1858→- Handle: Flux input
  1859→- Body: Flux textarea (16 rows)
  1860→
  1861→**Right column (1/3):**
  1862→- Status: Flux select with Draft/Active
  1863→- Published at: Datetime-local input
  1864→
  1865→Sticky save bar (same pattern as other forms).
  1866→
  1867→---
  1868→
  1869→## 14. Navigation Management
  1870→
  1871→**Route:** `GET /admin/navigation`
  1872→
  1873→**Livewire component:** `App\Livewire\Admin\Navigation\Index`
  1874→
  1875→**Blade view:** `resources/views/livewire/admin/navigation/index.blade.php`
  1876→
  1877→### Component Requirements
  1878→
  1879→| Property | Type | Purpose |
  1880→|----------|------|---------|
  1881→| `menus` | `Collection` | Navigation menus (Main Menu, Footer Menu) |
  1882→| `editingMenu` | `?NavigationMenu` | Menu being edited |
  1883→| `menuItems` | `array` | Nested array of menu items for the editing menu |
  1884→| `editingItem` | `?array` | Item currently being edited in the modal |
  1885→| `editingItemIndex` | `?int` | Index of item being edited |
  1886→| `itemLabel` | `string` | Item label |
  1887→| `itemType` | `string` | `link`, `page`, `collection`, `product` |
  1888→| `itemUrl` | `string` | URL if type is link |
  1889→| `itemResourceId` | `?int` | Resource ID if type is page/collection/product |
  1890→
  1891→| Action | Behavior |
  1892→|--------|----------|
  1893→| `selectMenu(int $menuId)` | Loads a menu for editing |
  1894→| `addItem()` | Opens the item form modal in create mode |
  1895→| `editItem(int $index)` | Opens the item form modal in edit mode |
  1896→| `saveItem()` | Saves the item (add or update) |
  1897→| `removeItem(int $index)` | Removes an item |
  1898→| `reorderItems(array $order)` | Updates item positions (supports nesting) |
  1899→| `saveMenu()` | Persists all menu items |
  1900→
  1901→### Layout Wireframe
  1902→
  1903→```
  1904→+==================================================================+
  1905→| Navigation                                                        |
  1906→+==================================================================+
  1907→|                                                                   |
  1908→| MENUS                                                             |
  1909→| +----------------------------+  +----------------------------+   |
  1910→| | Main Menu        [Edit]    |  | Footer Menu       [Edit]   |   |
  1911→| +----------------------------+  +----------------------------+   |
  1912→|                                                                   |
  1913→| MENU EDITOR (shown when a menu is selected)                      |
  1914→| +--------------------------------------------------------------+ |
  1915→| | Main Menu                                    [+ Add item]    | |
  1916→| |                                                               | |
  1917→| | [drag] Home               link: /            [edit] [trash]  | |
  1918→| | [drag] Products           collection: All    [edit] [trash]  | |
  1919→| | [drag] About Us           page: About        [edit] [trash]  | |
  1920→| | [drag] Contact            link: /contact     [edit] [trash]  | |
  1921→| |                                                               | |
  1922→| |                                            [Save menu]       | |
  1923→| +--------------------------------------------------------------+ |
  1924→+==================================================================+
  1925→```
  1926→
  1927→**Menu list:** Each menu displayed as a card with title and "Edit" button.
  1928→
  1929→**Menu editor (shown when a menu is selected):**
  1930→- Menu title as heading
  1931→- "Add item" button (ghost, plus icon) in the header
  1932→- Sortable list of menu items, each row containing:
  1933→  - Drag handle (bars icon)
  1934→  - Item label (bold text)
  1935→  - Item type and target (small, muted text: e.g., "link: /about" or "collection: Summer Sale")
  1936→  - Edit button (pencil icon) and Delete button (trash icon)
  1937→- "Save menu" primary button at the bottom
  1938→
  1939→#### Menu Item Form Modal
  1940→
  1941→| Property | Value |
  1942→|----------|-------|
  1943→| Modal name | `item-form` |
  1944→| Max width | md |
  1945→| Heading | "Edit menu item" or "Add menu item" (dynamic) |
  1946→
  1947→**Form fields:**
  1948→- Label input (placeholder: "About Us")
  1949→- Type select: Custom link, Page, Collection, Product (live-bound)
  1950→- Conditional fields based on type:
  1951→  - **Custom link:** URL input (placeholder: "https://...")
  1952→  - **Page/Collection/Product:** Resource select dropdown populated with available resources of that type
  1953→- Cancel button (ghost) and "Save item" button (primary)
  1954→
  1955→---
  1956→
  1957→## 15. Apps Page
  1958→
  1959→**Route:** `GET /admin/apps`
  1960→
  1961→**Livewire component:** `App\Livewire\Admin\Apps\Index`
  1962→
  1963→**Blade view:** `resources/views/livewire/admin/apps/index.blade.php`
  1964→
  1965→### Component Requirements
  1966→
  1967→| Property | Type | Purpose |
  1968→|----------|------|---------|
  1969→| `installedApps` | `Collection` | Apps installed for this store |
  1970→
  1971→| Action | Behavior |
  1972→|--------|----------|
  1973→| `uninstallApp(int $appId)` | Uninstalls an app (with confirmation) |
  1974→
  1975→### Layout Wireframe
  1976→
  1977→```
  1978→+==================================================================+
  1979→| Apps                                                              |
  1980→+==================================================================+
  1981→|                                                                   |
  1982→| +--------------------------------------------------------------+ |
  1983→| | [icon]  My Integration App                         [Active]  | |
  1984→| |         Installed 3 months ago                               | |
  1985→| +--------------------------------------------------------------+ |
  1986→| +--------------------------------------------------------------+ |
  1987→| | [icon]  Analytics Plugin                          [Inactive] | |
  1988→| |         Installed 1 week ago                                 | |
  1989→| +--------------------------------------------------------------+ |
  1990→|                                                                   |
  1991→+==================================================================+
  1992→```
  1993→
  1994→List of installed app cards, each containing:
  1995→- App icon placeholder (squares icon in a rounded container)
  1996→- App name (heading, size md)
  1997→- Install date (relative timestamp, muted text)
  1998→- Status badge (active=green, inactive=zinc)
  1999→
  2000→Each app links to a detail page showing:

Prompt:
Read ALL the specification files in /Users/fabianwesner/Herd/shop/specs/ directory. I need the full content of:
1. specs/01-DATABASE-SCHEMA.md (read in chunks if needed)
2. specs/02-API-ROUTES.md (read in chunks if needed)
3. specs/03-ADMIN-UI.md (read in chunks if needed)
4. specs/04-STOREFRONT-UI.md (read in chunks if needed)
5. specs/05-BUSINESS-LOGIC.md (read in chunks if needed)
6. specs/06-AUTH-AND-SECURITY.md (read in chunks if needed)
7. specs/07-SEEDERS-AND-TEST-DATA.md (read in chunks if needed)
8. specs/08-PLAYWRIGHT-E2E-PLAN.md (read in chunks if needed)

For each file provide a comprehensive summary including:
- All table/column definitions
- All API endpoints
- All UI components and their specifications
- All business rules
- All test specifications
- All seeder data definitions

This is critical - I need ALL the details, not just summaries. Extract every table name, column, relationship, route, component, and test case.


Prompt:
Read ALL the specification files in /Users/fabianwesner/Herd/shop/specs/ directory. I need the full content of:
1. specs/01-DATABASE-SCHEMA.md (read in chunks if needed)
2. specs/02-API-ROUTES.md (read in chunks if needed)
3. specs/03-ADMIN-UI.md (read in chunks if needed)
4. specs/04-STOREFRONT-UI.md (read in chunks if needed)
5. specs/05-BUSINESS-LOGIC.md (read in chunks if needed)
6. specs/06-AUTH-AND-SECURITY.md (read in chunks if needed)
7. specs/07-SEEDERS-AND-TEST-DATA.md (read in chunks if needed)
8. specs/08-PLAYWRIGHT-E2E-PLAN.md (read in chunks if needed)

For each file provide a comprehensive summary including:
- All table/column definitions
- All API endpoints
- All UI components and their specifications
- All business rules
- All test specifications
- All seeder data definitions

This is critical - I need ALL the details, not just summaries. Extract every table name, column, relationship, route, component, and test case.


Prompt:
Explore the current state of the Laravel project at /Users/fabianwesner/Herd/shop. I need to understand:

1. What files already exist (models, controllers, migrations, routes, views, tests, etc.)
2. Current state of composer.json dependencies
3. Current state of package.json
4. Current .env configuration
5. Current config files (auth.php, database.php, etc.)
6. Current routes/web.php, routes/api.php, routes/console.php
7. Current app directory structure
8. Current tests directory structure
9. Current database/migrations directory
10. Current resources/views directory

Give me a comprehensive overview of what already exists vs what needs to be built.