command:
/bin/zsh
-lc
sed -n '1,360p' routes/web.php

web.php

cmd: sed -n '1,360p' routes/web.php

stdout:
<?php

use App\Livewire\Admin\Auth\ForgotPassword as AdminForgotPassword;
use App\Livewire\Admin\Auth\Login as AdminLogin;
use App\Livewire\Admin\Auth\ResetPassword as AdminResetPassword;
use App\Livewire\Admin\Customers\Index as AdminCustomersIndex;
use App\Livewire\Admin\Customers\Show as AdminCustomersShow;
use App\Livewire\Admin\Dashboard as AdminDashboard;
use App\Livewire\Admin\Discounts\Form as AdminDiscountForm;
use App\Livewire\Admin\Discounts\Index as AdminDiscountsIndex;
use App\Livewire\Admin\Orders\Index as AdminOrdersIndex;
use App\Livewire\Admin\Orders\Show as AdminOrdersShow;
use App\Livewire\Admin\Products\Form as AdminProductForm;
use App\Livewire\Admin\Products\Index as AdminProductsIndex;
use App\Livewire\Admin\Section as AdminSection;
use App\Livewire\Admin\Settings\General as AdminSettingsGeneral;
use App\Livewire\Admin\Settings\Shipping as AdminSettingsShipping;
use App\Livewire\Admin\Settings\Taxes as AdminSettingsTaxes;
use App\Livewire\Storefront\Account\Addresses\Index as AccountAddresses;
use App\Livewire\Storefront\Account\Auth\ForgotPassword as CustomerForgotPassword;
use App\Livewire\Storefront\Account\Auth\Login as CustomerLogin;
use App\Livewire\Storefront\Account\Auth\Register as CustomerRegister;
use App\Livewire\Storefront\Account\Auth\ResetPassword as CustomerResetPassword;
use App\Livewire\Storefront\Account\Dashboard as AccountDashboard;
use App\Livewire\Storefront\Account\Orders\Index as AccountOrders;
use App\Livewire\Storefront\Account\Orders\Show as AccountOrderShow;
use App\Livewire\Storefront\Cart\Show as CartShow;
use App\Livewire\Storefront\Checkout\Confirmation as CheckoutConfirmation;
use App\Livewire\Storefront\Checkout\Show as CheckoutShow;
use App\Livewire\Storefront\Collections\Index as CollectionsIndex;
use App\Livewire\Storefront\Collections\Show as CollectionShow;
use App\Livewire\Storefront\Home;
use App\Livewire\Storefront\Pages\Show as PageShow;
use App\Livewire\Storefront\Products\Show as ProductShow;
use App\Livewire\Storefront\Search\Index as SearchIndex;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;

Route::view('dashboard', 'dashboard')
    ->middleware(['auth', 'verified'])
    ->name('dashboard');

require __DIR__.'/settings.php';

Route::get('/favicon.ico', fn (): \Symfony\Component\HttpFoundation\Response => response('', 204))->name('favicon');

Route::middleware('store.resolve')->group(function (): void {
    Route::livewire('/', Home::class)->name('home');
    Route::livewire('/collections', CollectionsIndex::class)->name('collections.index');
    Route::livewire('/collections/{handle}', CollectionShow::class)->name('collection.show');
    Route::livewire('/products/{handle}', ProductShow::class)->name('product.show');
    Route::livewire('/cart', CartShow::class)->name('cart.show');
    Route::livewire('/search', SearchIndex::class)->name('search');
    Route::livewire('/pages/{handle}', PageShow::class)->name('page.show');
    Route::livewire('/checkout/{checkoutId}', CheckoutShow::class)->name('checkout.show');
    Route::livewire('/checkout/{checkoutId}/confirmation', CheckoutConfirmation::class)->name('checkout.confirmation');
    Route::livewire('/account/login', CustomerLogin::class)->middleware('throttle:login')->name('account.login');
    Route::livewire('/account/register', CustomerRegister::class)->name('account.register');
    Route::livewire('/forgot-password', CustomerForgotPassword::class)->name('password.request');
    Route::livewire('/reset-password/{token}', CustomerResetPassword::class)->name('password.reset');
    Route::post('/account/logout', function (): \Illuminate\Http\RedirectResponse {
        Auth::guard('customer')->logout();
        request()->session()->invalidate();
        request()->session()->regenerateToken();

        return redirect()->route('home');
    })->name('account.logout');

    Route::middleware('auth:customer')->group(function (): void {
        Route::livewire('/account', AccountDashboard::class)->name('account.dashboard');
        Route::livewire('/account/orders', AccountOrders::class)->name('account.orders');
        Route::livewire('/account/orders/{orderNumber}', AccountOrderShow::class)->name('account.order.show');
        Route::livewire('/account/addresses', AccountAddresses::class)->name('account.addresses');
    });
});

Route::livewire('/admin/login', AdminLogin::class)->middleware('throttle:login')->name('admin.login');
Route::livewire('/admin/forgot-password', AdminForgotPassword::class)->middleware('throttle:login')->name('admin.password.request');
Route::livewire('/admin/reset-password/{token}', AdminResetPassword::class)->name('admin.password.reset');
Route::post('/admin/logout', function (): \Illuminate\Http\RedirectResponse {
    Auth::guard('web')->logout();
    request()->session()->invalidate();
    request()->session()->regenerateToken();

    return redirect()->route('admin.login');
})->name('admin.logout');

Route::prefix('admin')->middleware(['auth', 'verified', 'store.resolve', 'role.check:owner,admin,staff,support'])->group(function (): void {
    Route::livewire('/', AdminDashboard::class)->name('admin.dashboard');
    Route::livewire('/products', AdminProductsIndex::class)->name('admin.products.index');
    Route::livewire('/products/create', AdminProductForm::class)->middleware('role.check:owner,admin,staff')->name('admin.products.create');
    Route::livewire('/products/{product}/edit', AdminProductForm::class)->middleware('role.check:owner,admin,staff')->name('admin.products.edit');
    Route::livewire('/orders', AdminOrdersIndex::class)->name('admin.orders.index');
    Route::livewire('/orders/{order}', AdminOrdersShow::class)->name('admin.orders.show');
    Route::livewire('/customers', AdminCustomersIndex::class)->name('admin.customers.index');
    Route::livewire('/customers/{customer}', AdminCustomersShow::class)->name('admin.customers.show');
    Route::livewire('/discounts', AdminDiscountsIndex::class)->name('admin.discounts.index');
    Route::livewire('/discounts/create', AdminDiscountForm::class)->middleware('role.check:owner,admin,staff')->name('admin.discounts.create');
    Route::livewire('/discounts/{discount}/edit', AdminDiscountForm::class)->middleware('role.check:owner,admin,staff')->name('admin.discounts.edit');
    Route::livewire('/settings', AdminSettingsGeneral::class)->middleware('role.check:owner,admin')->name('admin.settings');
    Route::livewire('/settings/shipping', AdminSettingsShipping::class)->middleware('role.check:owner,admin')->name('admin.settings.shipping');
    Route::livewire('/settings/taxes', AdminSettingsTaxes::class)->middleware('role.check:owner,admin')->name('admin.settings.taxes');
    Route::livewire('/inventory', AdminSection::class)->name('admin.inventory');
    Route::livewire('/collections', AdminSection::class)->name('admin.collections');
    Route::livewire('/collections/create', AdminSection::class)->middleware('role.check:owner,admin,staff')->name('admin.collections.create');
    Route::livewire('/collections/{collection}/edit', AdminSection::class)->middleware('role.check:owner,admin,staff')->name('admin.collections.edit');
    Route::livewire('/themes', AdminSection::class)->name('admin.themes');
    Route::livewire('/themes/{theme}/editor', AdminSection::class)->name('admin.themes.editor');
    Route::livewire('/pages', AdminSection::class)->name('admin.pages');
    Route::livewire('/pages/create', AdminSection::class)->middleware('role.check:owner,admin')->name('admin.pages.create');
    Route::livewire('/pages/{page}/edit', AdminSection::class)->middleware('role.check:owner,admin')->name('admin.pages.edit');
    Route::livewire('/navigation', AdminSection::class)->name('admin.navigation');
    Route::livewire('/apps', AdminSection::class)->name('admin.apps');
    Route::livewire('/apps/{installation}', AdminSection::class)->name('admin.apps.show');
    Route::livewire('/developers', AdminSection::class)->name('admin.developers');
    Route::livewire('/analytics', AdminSection::class)->name('admin.analytics');
    Route::livewire('/search/settings', AdminSection::class)->name('admin.search.settings');
});


type: CommandExecution

command: rg -n "AdminSection|inventory|collections|themes|pages|navigation|apps|developers|analytics|search" specs/02-API-ROUTES.md specs/03-ADMIN-UI.md | head -300

type: search

cmd: rg -n 'AdminSection|inventory|collections|themes|pages|navigation|apps|developers|analytics|search' specs/02-API-ROUTES.md specs/03-ADMIN-UI.md

query: AdminSection|inventory|collections|themes|pages|navigation|apps|developers|analytics|search

status: completed

stdout:
specs/03-ADMIN-UI.md:83:  Collections                    -> admin.collections.index
specs/03-ADMIN-UI.md:84:  Inventory                      -> admin.inventory.index
specs/03-ADMIN-UI.md:92:  Pages                          -> admin.pages.index
specs/03-ADMIN-UI.md:93:  Navigation                     -> admin.navigation.index
specs/03-ADMIN-UI.md:94:  Themes                         -> admin.themes.index
specs/03-ADMIN-UI.md:95:Analytics                        -> admin.analytics.index
specs/03-ADMIN-UI.md:97:Apps                             -> admin.apps.index
specs/03-ADMIN-UI.md:98:Developers                       -> admin.developers.index
specs/03-ADMIN-UI.md: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.
specs/03-ADMIN-UI.md:232:| `loadKpis()` | Queries aggregated order/analytics data |
specs/03-ADMIN-UI.md:349:| `search` | `string` | Search query, live-debounced at 300ms |
specs/03-ADMIN-UI.md:371:| `products` | Paginated query result with search, filters, sorting applied. Eager loads `variants`, `media` (limit 1). Uses `withCount('variants')`. |
specs/03-ADMIN-UI.md:380:| [search icon] Search products...   [Status: All v] [Type: All v] |
specs/03-ADMIN-UI.md:404:| Inventory | Yes | Sum of `inventory_items.quantity_on_hand` across all variants |
specs/03-ADMIN-UI.md:508:| `availableCollections` | All collections for the current store |
specs/03-ADMIN-UI.md:623:| Quantity | Number input (small) | On-hand inventory |
specs/03-ADMIN-UI.md:704:**Route:** `GET /admin/collections`
specs/03-ADMIN-UI.md:708:**Blade view:** `resources/views/livewire/admin/collections/index.blade.php`
specs/03-ADMIN-UI.md:714:| `search` | `string` | Search query |
specs/03-ADMIN-UI.md:724:| `collections` | Paginated, filtered, searched. Includes `withCount('products')`. |
specs/03-ADMIN-UI.md:746:**Route:** `GET /admin/collections/create` and `GET /admin/collections/{collection}/edit`
specs/03-ADMIN-UI.md:750:**Blade view:** `resources/views/livewire/admin/collections/form.blade.php`
specs/03-ADMIN-UI.md:773:| `searchResults` | Products matching `productSearch` that are not already assigned |
specs/03-ADMIN-UI.md:821:**Route:** `GET /admin/inventory`
specs/03-ADMIN-UI.md:825:**Blade view:** `resources/views/livewire/admin/inventory/index.blade.php`
specs/03-ADMIN-UI.md:831:| `search` | `string` | Search by product title or SKU |
specs/03-ADMIN-UI.md:836:| `inventoryItems` | Paginated inventory items with variant and product eager loaded, filtered by search and stock level |
specs/03-ADMIN-UI.md:844:| [search] Search by product or SKU   [Stock: All v]               |
specs/03-ADMIN-UI.md:874:| `search` | `string` | Search by order number or customer email |
specs/03-ADMIN-UI.md:887:| `orders` | Paginated query with search, status filter, sorting. Eager loads `customer`. |
specs/03-ADMIN-UI.md:895:| [search] Search by order # or email...                           |
specs/03-ADMIN-UI.md:960:| `confirmPayment()` | Confirms bank transfer payment received (transitions financial_status to `paid`, commits inventory, dispatches `OrderPaid`) |
specs/03-ADMIN-UI.md: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`.
specs/03-ADMIN-UI.md:1148:| `search` | `string` | Search by name or email |
specs/03-ADMIN-UI.md:1152:| `customers` | Paginated, searched customers with `withCount('orders')` and `withSum('orders', 'total_amount')` |
specs/03-ADMIN-UI.md:1247:| `search` | `string` | Search by discount code |
specs/03-ADMIN-UI.md:1289:| `specificCollectionIds` | `array<int>` | Specific collections the discount applies to |
specs/03-ADMIN-UI.md:1296:| `collectionSearch` | `string` | Search for collections to add |
specs/03-ADMIN-UI.md:1304:| `addCollection(int $collectionId)` | Adds collection to specific collections |
specs/03-ADMIN-UI.md:1334:|  Specific collections: (same pattern as products)                 |
specs/03-ADMIN-UI.md:1361:- Specific products: search input with live debounce. Results appear in a scrollable dropdown. Selected products shown as a list with remove (X) buttons.
specs/03-ADMIN-UI.md:1362:- Specific collections: same pattern as products.
specs/03-ADMIN-UI.md:1659:**Route:** `GET /admin/themes`
specs/03-ADMIN-UI.md:1663:**Blade view:** `resources/views/livewire/admin/themes/index.blade.php`
specs/03-ADMIN-UI.md:1669:| `themes` | `Collection` | All themes for the store |
specs/03-ADMIN-UI.md:1716:**Route:** `GET /admin/themes/{theme}/editor`
specs/03-ADMIN-UI.md:1720:**Blade view:** `resources/views/livewire/admin/themes/editor.blade.php`
specs/03-ADMIN-UI.md:1745:| [<- Back to themes]                     [Save]  [Save & publish] |
specs/03-ADMIN-UI.md:1759:|            |  |                        | |  Show search:        |
specs/03-ADMIN-UI.md:1771:- "Back to themes" button (ghost, with left arrow icon) linking to themes index
specs/03-ADMIN-UI.md:1807:**Route:** `GET /admin/pages`
specs/03-ADMIN-UI.md:1811:**Blade view:** `resources/views/livewire/admin/pages/index.blade.php`
specs/03-ADMIN-UI.md:1817:| `search` | `string` | Search by page title |
specs/03-ADMIN-UI.md:1821:| `pages` | Paginated pages for the current store |
specs/03-ADMIN-UI.md:1832:**Route:** `GET /admin/pages/create` and `GET /admin/pages/{page}/edit`
specs/03-ADMIN-UI.md:1836:**Blade view:** `resources/views/livewire/admin/pages/form.blade.php`
specs/03-ADMIN-UI.md:1871:**Route:** `GET /admin/navigation`
specs/03-ADMIN-UI.md:1875:**Blade view:** `resources/views/livewire/admin/navigation/index.blade.php`
specs/03-ADMIN-UI.md:1959:**Route:** `GET /admin/apps`
specs/03-ADMIN-UI.md:1963:**Blade view:** `resources/views/livewire/admin/apps/index.blade.php`
specs/03-ADMIN-UI.md:2005:**Empty state:** "No apps installed" with descriptive text.
specs/03-ADMIN-UI.md:2011:**Route:** `GET /admin/developers`
specs/03-ADMIN-UI.md:2015:**Blade view:** `resources/views/livewire/admin/developers/index.blade.php`
specs/03-ADMIN-UI.md:2124:**Route:** `GET /admin/analytics`
specs/03-ADMIN-UI.md:2128:**Blade view:** `resources/views/livewire/admin/analytics/index.blade.php`
specs/03-ADMIN-UI.md:2151:| `updatedDateRange()` | Reloads analytics data |
specs/03-ADMIN-UI.md:2154:| `loadAnalytics()` | Master method to reload all analytics data |
specs/03-ADMIN-UI.md:2211:**Route:** `GET /admin/search/settings`
specs/03-ADMIN-UI.md:2215:**Blade view:** `resources/views/livewire/admin/search/settings.blade.php`
specs/03-ADMIN-UI.md:2252:| Words that are excluded from search indexing.                    |
specs/03-ADMIN-UI.md:2296:All list pages follow a consistent pattern:
specs/03-ADMIN-UI.md:2307:All form pages follow a consistent pattern:
specs/03-ADMIN-UI.md:2367:- Semantic HTML: headings hierarchy, landmark regions, lists for navigation
specs/03-ADMIN-UI.md:2402:| `App\Livewire\Admin\Layout\Sidebar` | `livewire.admin.layout.sidebar` | Layout (all pages) |
specs/03-ADMIN-UI.md:2403:| `App\Livewire\Admin\Layout\TopBar` | `livewire.admin.layout.top-bar` | Layout (all pages) |
specs/03-ADMIN-UI.md:2407:| `App\Livewire\Admin\Collections\Index` | `livewire.admin.collections.index` | `GET /admin/collections` |
specs/03-ADMIN-UI.md:2408:| `App\Livewire\Admin\Collections\Form` | `livewire.admin.collections.form` | `GET /admin/collections/create`, `GET /admin/collections/{collection}/edit` |
specs/03-ADMIN-UI.md:2409:| `App\Livewire\Admin\Inventory\Index` | `livewire.admin.inventory.index` | `GET /admin/inventory` |
specs/03-ADMIN-UI.md:2420:| `App\Livewire\Admin\Themes\Index` | `livewire.admin.themes.index` | `GET /admin/themes` |
specs/03-ADMIN-UI.md:2421:| `App\Livewire\Admin\Themes\Editor` | `livewire.admin.themes.editor` | `GET /admin/themes/{theme}/editor` |
specs/03-ADMIN-UI.md:2422:| `App\Livewire\Admin\Pages\Index` | `livewire.admin.pages.index` | `GET /admin/pages` |
specs/03-ADMIN-UI.md:2423:| `App\Livewire\Admin\Pages\Form` | `livewire.admin.pages.form` | `GET /admin/pages/create`, `GET /admin/pages/{page}/edit` |
specs/03-ADMIN-UI.md:2424:| `App\Livewire\Admin\Navigation\Index` | `livewire.admin.navigation.index` | `GET /admin/navigation` |
specs/03-ADMIN-UI.md:2425:| `App\Livewire\Admin\Apps\Index` | `livewire.admin.apps.index` | `GET /admin/apps` |
specs/03-ADMIN-UI.md:2426:| `App\Livewire\Admin\Developers\Index` | `livewire.admin.developers.index` | `GET /admin/developers` |
specs/03-ADMIN-UI.md:2427:| `App\Livewire\Admin\Analytics\Index` | `livewire.admin.analytics.index` | `GET /admin/analytics` |
specs/03-ADMIN-UI.md:2428:| `App\Livewire\Admin\Search\Settings` | `livewire.admin.search.settings` | `GET /admin/search/settings` |
specs/02-API-ROUTES.md:34:All admin pages render Livewire full-page components. Data mutations happen via Livewire actions (not separate POST endpoints).
specs/02-API-ROUTES.md:48:| GET | `/admin/products` | Product list - searchable, filterable by status, bulk actions | `Admin\Products\Index` |
specs/02-API-ROUTES.md:56:| GET | `/admin/inventory` | Inventory management page | `Admin\Inventory\Index` |
specs/02-API-ROUTES.md:62:| GET | `/admin/collections` | Collection list | `Admin\Collections\Index` |
specs/02-API-ROUTES.md:63:| GET | `/admin/collections/create` | Create collection form | `Admin\Collections\Create` |
specs/02-API-ROUTES.md:64:| GET | `/admin/collections/{collection}/edit` | Edit collection form | `Admin\Collections\Edit` |
specs/02-API-ROUTES.md:100:| GET | `/admin/themes` | Theme management - cards with actions (Customize, Preview, Publish, Duplicate, Delete) | `Admin\Themes\Index` |
specs/02-API-ROUTES.md:101:| GET | `/admin/themes/{theme}/editor` | Theme editor - left panel sections, center preview iframe, right panel settings | `Admin\Themes\Editor` |
specs/02-API-ROUTES.md:107:| GET | `/admin/pages` | Content pages list | `Admin\Pages\Index` |
specs/02-API-ROUTES.md:108:| GET | `/admin/pages/create` | Create content page | `Admin\Pages\Create` |
specs/02-API-ROUTES.md:109:| GET | `/admin/pages/{page}/edit` | Edit content page | `Admin\Pages\Edit` |
specs/02-API-ROUTES.md:115:| GET | `/admin/navigation` | Navigation menus management | `Admin\Navigation\Index` |
specs/02-API-ROUTES.md:121:| GET | `/admin/apps` | Apps directory - list installed and available apps | `Admin\Apps\Index` |
specs/02-API-ROUTES.md:122:| GET | `/admin/apps/{installation}` | Installed app detail - scopes, webhooks, usage | `Admin\Apps\Show` |
specs/02-API-ROUTES.md:128:| GET | `/admin/developers` | Developer settings - API keys/tokens, webhook management | `Admin\Developers\Index` |
specs/02-API-ROUTES.md:130:> **Note:** Webhook subscription management (CRUD) is available exclusively through the admin UI at `/admin/developers`. There are no REST API endpoints for programmatic webhook subscription management in the initial implementation.
specs/02-API-ROUTES.md:136:| GET | `/admin/analytics` | Analytics dashboard - sales, traffic, funnel, exports | `Admin\Analytics\Index` |
specs/02-API-ROUTES.md:142:| GET | `/admin/search/settings` | Search settings - synonyms, stopwords, reindex | `Admin\Search\Settings` |
specs/02-API-ROUTES.md:155:| GET | `/collections` | All collections listing | `Storefront\Collections\Index` |
specs/02-API-ROUTES.md:156:| GET | `/collections/{handle}` | Single collection page with product grid and filters | `Storefront\Collections\Show` |
specs/02-API-ROUTES.md:159:| GET | `/search` | Search results page with filter panel, sort dropdown, product grid | `Storefront\Search\Index` |
specs/02-API-ROUTES.md:160:| GET | `/pages/{handle}` | Static content page (About, FAQ, etc.) | `Storefront\Pages\Show` |
specs/02-API-ROUTES.md:203:These endpoints power the interactive storefront experience (cart drawer, checkout flow, search autocomplete, analytics).
specs/02-API-ROUTES.md:332:| `quantity` | integer | Yes | Min: 1, max: 9999. Must not exceed available inventory when policy is `deny`. |
specs/02-API-ROUTES.md:372:| `quantity` | integer | Yes | Min: 1, max: 9999. Must not exceed available inventory when policy is `deny`. |
specs/02-API-ROUTES.md:382:- `422` - Validation error (e.g., exceeds inventory)
specs/02-API-ROUTES.md:440:    API->>API: Reserve inventory
specs/02-API-ROUTES.md:445:        API->>API: Create order (paid), commit inventory
specs/02-API-ROUTES.md:449:        API->>API: Release reserved inventory
specs/02-API-ROUTES.md:453:        API->>API: Create order (pending), inventory stays reserved
specs/02-API-ROUTES.md:1019:#### GET /api/storefront/v1/search
specs/02-API-ROUTES.md:1024:- **Rate limit:** `search` (30/min per IP)
specs/02-API-ROUTES.md:1097:#### GET /api/storefront/v1/search/suggest
specs/02-API-ROUTES.md:1102:- **Rate limit:** `search` (30/min per IP)
specs/02-API-ROUTES.md:1143:#### POST /api/storefront/v1/analytics/events
specs/02-API-ROUTES.md:1145:Batch submit storefront analytics events.
specs/02-API-ROUTES.md:1148:- **Rate limit:** `analytics` (60/min per IP)
specs/02-API-ROUTES.md:1186:| `events[].type` | string | Yes | One of: `page_view`, `add_to_cart`, `remove_from_cart`, `checkout_started`, `checkout_completed`, `search`. |
specs/02-API-ROUTES.md:1212:These endpoints provide programmatic access for integrations, CLI tools, and third-party apps. The admin web UI uses Livewire actions directly and does not call these endpoints.
specs/02-API-ROUTES.md:1436:      "total_inventory": 150,
specs/02-API-ROUTES.md:1492:      "inventory": {
specs/02-API-ROUTES.md:1498:  "collections": [5]
specs/02-API-ROUTES.md:1516:| `collections` | array of integers | No | Collection IDs to add this product to. |
specs/02-API-ROUTES.md:1534:| `variants[].inventory.quantity_on_hand` | integer | No | Default: 0. Min: 0. |
specs/02-API-ROUTES.md:1535:| `variants[].inventory.policy` | string | No | One of: `deny`, `continue`. Default: `deny`. |
specs/02-API-ROUTES.md:1549:Get a single product with all variants, options, media, and collections.
specs/02-API-ROUTES.md:1606:        "inventory": {
specs/02-API-ROUTES.md:1628:    "collections": [
specs/02-API-ROUTES.md:1742:| GET | `/api/admin/v1/stores/{storeId}/collections` | `read-collections` | List collections | `api.admin` (60/min) |
specs/02-API-ROUTES.md:1743:| POST | `/api/admin/v1/stores/{storeId}/collections` | `write-collections` | Create a collection | `api.admin` (60/min) |
specs/02-API-ROUTES.md:1744:| PUT | `/api/admin/v1/stores/{storeId}/collections/{collectionId}` | `write-collections` | Update a collection | `api.admin` (60/min) |
specs/02-API-ROUTES.md:1745:| DELETE | `/api/admin/v1/stores/{storeId}/collections/{collectionId}` | `write-collections` | Delete a collection | `api.admin` (60/min) |
specs/02-API-ROUTES.md:1747:#### GET /api/admin/v1/stores/{storeId}/collections
specs/02-API-ROUTES.md:1749:List collections.
specs/02-API-ROUTES.md:1789:#### POST /api/admin/v1/stores/{storeId}/collections
specs/02-API-ROUTES.md:1825:#### PUT /api/admin/v1/stores/{storeId}/collections/{collectionId}
specs/02-API-ROUTES.md:1840:#### DELETE /api/admin/v1/stores/{storeId}/collections/{collectionId}
specs/02-API-ROUTES.md:1844:**Authentication:** Bearer token with `write-collections` ability.
specs/02-API-ROUTES.md:2229:| `rules_json.applicable_collection_ids` | array | No | Empty array means all collections. |
specs/02-API-ROUTES.md:2498:| POST | `/api/admin/v1/stores/{storeId}/themes` | `write-themes` | Upload or install a new theme | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2499:| POST | `/api/admin/v1/stores/{storeId}/themes/{themeId}/publish` | `write-themes` | Publish a theme (make active) | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2500:| PUT | `/api/admin/v1/stores/{storeId}/themes/{themeId}/settings` | `write-themes` | Update theme customization settings | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2502:#### POST /api/admin/v1/stores/{storeId}/themes
specs/02-API-ROUTES.md:2535:#### POST /api/admin/v1/stores/{storeId}/themes/{themeId}/publish
specs/02-API-ROUTES.md:2558:#### PUT /api/admin/v1/stores/{storeId}/themes/{themeId}/settings
specs/02-API-ROUTES.md:2604:| GET | `/api/admin/v1/stores/{storeId}/pages` | `read-content` | List content pages | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2605:| POST | `/api/admin/v1/stores/{storeId}/pages` | `write-content` | Create a content page | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2606:| PUT | `/api/admin/v1/stores/{storeId}/pages/{pageId}` | `write-content` | Update a content page | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2607:| DELETE | `/api/admin/v1/stores/{storeId}/pages/{pageId}` | `write-content` | Delete a content page | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2609:#### GET /api/admin/v1/stores/{storeId}/pages
specs/02-API-ROUTES.md:2611:List content pages.
specs/02-API-ROUTES.md:2648:#### POST /api/admin/v1/stores/{storeId}/pages
specs/02-API-ROUTES.md:2678:#### PUT /api/admin/v1/stores/{storeId}/pages/{pageId}
specs/02-API-ROUTES.md:2693:#### DELETE /api/admin/v1/stores/{storeId}/pages/{pageId}
specs/02-API-ROUTES.md:2719:| POST | `/api/admin/v1/stores/{storeId}/search/reindex` | `write-settings` | Trigger full search reindex (async) | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2720:| GET | `/api/admin/v1/stores/{storeId}/search/status` | `read-settings` | Get current search index status | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2722:#### POST /api/admin/v1/stores/{storeId}/search/reindex
specs/02-API-ROUTES.md:2724:Trigger a full search reindex for the store. Runs asynchronously via a queued job.
specs/02-API-ROUTES.md:2743:#### GET /api/admin/v1/stores/{storeId}/search/status
specs/02-API-ROUTES.md:2745:Get the current search index status.
specs/02-API-ROUTES.md:2770:| GET | `/api/admin/v1/stores/{storeId}/analytics/summary` | `read-analytics` | Get analytics summary for date range | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2774:#### GET /api/admin/v1/stores/{storeId}/analytics/summary
specs/02-API-ROUTES.md:2776:Get analytics summary for a date range.
specs/02-API-ROUTES.md:2907:These endpoints implement OAuth2 authorization for third-party apps, powered by Laravel Passport.
specs/02-API-ROUTES.md:2979:Third-party apps use OAuth tokens to access store data via scoped endpoints.
specs/02-API-ROUTES.md:2983:| GET | `/api/apps/v1/stores/{storeId}/products` | `read-products` | List products (same schema as admin API) |
specs/02-API-ROUTES.md:2984:| GET | `/api/apps/v1/stores/{storeId}/orders` | `read-orders` | List orders |
specs/02-API-ROUTES.md:2985:| GET | `/api/apps/v1/stores/{storeId}/customers` | `read-customers` | List customers |
specs/02-API-ROUTES.md:3084:| `search` | 30 requests | 1 minute | IP address | Search and autocomplete endpoints |
specs/02-API-ROUTES.md:3085:| `analytics` | 60 requests | 1 minute | IP address | Analytics event ingestion |
specs/02-API-ROUTES.md:3123:| `GET /api/storefront/v1/search` | `search` |
specs/02-API-ROUTES.md:3124:| `GET /api/storefront/v1/search/suggest` | `search` |
specs/02-API-ROUTES.md:3125:| `POST /api/storefront/v1/analytics/events` | `analytics` |
specs/02-API-ROUTES.md:3173:- **App API:** Versioned via URL path (`/api/apps/v1/`).


command: find app/Livewire/Admin -type f -maxdepth 4 -print | sort | xargs -I{} sh -c 'echo --- {}; sed -n "1,220p" "{}"'

path: Admin

stdout:
--- app/Livewire/Admin/Auth/ForgotPassword.php
<?php

namespace App\Livewire\Admin\Auth;

use Illuminate\Support\Facades\Password;
use Livewire\Component;

class ForgotPassword extends Component
{
    public string $email = '';

    public string $message = '';

    public function sendResetLink(): void
    {
        $this->validate(['email' => ['required', 'email']]);
        Password::broker('users')->sendResetLink(['email' => $this->email]);
        $this->message = 'If an account exists for that email, a reset link has been sent.';
    }

    public function render(): mixed
    {
        return view('livewire.admin.auth.forgot-password')->layout('layouts.auth');
    }
}
--- app/Livewire/Admin/Auth/Login.php
<?php

namespace App\Livewire\Admin\Auth;

use Illuminate\Support\Facades\Auth;
use Livewire\Component;

class Login extends Component
{
    public string $email = '';

    public string $password = '';

    public bool $remember = false;

    public function login(): void
    {
        $credentials = $this->validate(['email' => ['required', 'email'], 'password' => ['required', 'string']]);

        if (! Auth::guard('web')->attempt($credentials, $this->remember)) {
            $this->addError('email', 'These credentials do not match our records.');

            return;
        }

        session()->regenerate();
        $store = auth()->user()->stores()->first();

        if ($store !== null) {
            session()->put('current_store_id', $store->getKey());
        }

        $this->redirect(route('admin.dashboard'), navigate: true);
    }

    public function render(): mixed
    {
        return view('livewire.admin.auth.login')->layout('layouts.auth');
    }
}
--- app/Livewire/Admin/Auth/ResetPassword.php
<?php

namespace App\Livewire\Admin\Auth;

use App\Models\User;
use Illuminate\Support\Facades\Password;
use Livewire\Component;

class ResetPassword extends Component
{
    public string $token;

    public string $email = '';

    public string $password = '';

    public string $passwordConfirmation = '';

    public function mount(string $token): void
    {
        $this->token = $token;
        $this->email = request()->string('email')->toString();
    }

    public function resetPassword(): void
    {
        $data = $this->validate(['email' => ['required', 'email'], 'password' => ['required', 'min:8', 'same:passwordConfirmation']]);
        $status = Password::broker('users')->reset(['email' => $data['email'], 'password' => $data['password'], 'password_confirmation' => $this->passwordConfirmation, 'token' => $this->token], function (User $user, string $password): void {
            $user->password = $password;
            $user->save();
        });

        if ($status !== Password::PASSWORD_RESET) {
            $this->addError('email', __($status));

            return;
        }

        $this->redirect(route('admin.login'), navigate: true);
    }

    public function render(): mixed
    {
        return view('livewire.admin.auth.reset-password')->layout('layouts.auth');
    }
}
--- app/Livewire/Admin/Customers/Index.php
<?php

namespace App\Livewire\Admin\Customers;

use App\Models\Customer;
use Livewire\Component;

class Index extends Component
{
    public string $search = '';

    public function render(): mixed
    {
        $customers = Customer::query()->when($this->search !== '', fn ($query) => $query->where('email', 'like', '%'.$this->search.'%')->orWhere('first_name', 'like', '%'.$this->search.'%')->orWhere('last_name', 'like', '%'.$this->search.'%'))->withCount('orders')->latest()->paginate(15);

        return view('livewire.admin.customers.index', compact('customers'))->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Customers/Show.php
<?php

namespace App\Livewire\Admin\Customers;

use App\Models\Customer;
use Livewire\Component;

class Show extends Component
{
    public Customer $customer;

    public function mount(Customer $customer): void
    {
        $this->customer = $customer->load(['orders', 'addresses']);
    }

    public function render(): mixed
    {
        return view('livewire.admin.customers.show')->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Dashboard.php
<?php

namespace App\Livewire\Admin;

use App\Models\Order;
use App\Models\Product;
use Livewire\Component;

class Dashboard extends Component
{
    public function render(): mixed
    {
        $orders = Order::query()->with('customer')->latest('placed_at')->take(10)->get();
        $sales = (int) Order::query()->where('financial_status', 'paid')->sum('total_amount');
        $orderCount = (int) Order::query()->count();

        return view('livewire.admin.dashboard', ['orders' => $orders, 'sales' => $sales, 'orderCount' => $orderCount, 'productCount' => Product::query()->count()])->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Discounts/Form.php
<?php

namespace App\Livewire\Admin\Discounts;

use App\Models\Discount;
use Livewire\Component;

class Form extends Component
{
    public ?Discount $discount = null;

    public string $code = '';

    public string $valueType = 'percent';

    public int $valueAmount = 10;

    public string $message = '';

    public function mount(?Discount $discount = null): void
    {
        $this->discount = $discount;

        if ($discount !== null) {
            $this->code = (string) $discount->code;
            $this->valueType = $discount->value_type->value;
            $this->valueAmount = $discount->value_amount;
        }
    }

    public function save(): void
    {
        $data = $this->validate(['code' => ['required', 'string', 'max:64'], 'valueType' => ['required', 'in:percent,fixed,free_shipping'], 'valueAmount' => ['required', 'integer', 'min:0']]);
        $this->discount = Discount::updateOrCreate(['id' => $this->discount?->id], ['store_id' => app('current_store')->getKey(), 'code' => strtoupper($data['code']), 'type' => 'code', 'value_type' => $data['valueType'], 'value_amount' => $data['valueAmount'], 'status' => 'active', 'starts_at' => now(), 'rules_json' => []]);
        $this->message = 'Discount saved';
    }

    public function render(): mixed
    {
        return view('livewire.admin.discounts.form')->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Discounts/Index.php
<?php

namespace App\Livewire\Admin\Discounts;

use App\Models\Discount;
use Livewire\Component;

class Index extends Component
{
    public function render(): mixed
    {
        return view('livewire.admin.discounts.index', ['discounts' => Discount::query()->latest()->get()])->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Orders/Index.php
<?php

namespace App\Livewire\Admin\Orders;

use App\Models\Order;
use Livewire\Component;

class Index extends Component
{
    public string $status = 'all';

    public function render(): mixed
    {
        $orders = Order::query()->with('customer')->when($this->status !== 'all', fn ($query) => $query->where('status', $this->status))->latest('placed_at')->paginate(15);

        return view('livewire.admin.orders.index', compact('orders'))->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Orders/Show.php
<?php

namespace App\Livewire\Admin\Orders;

use App\Models\Order;
use App\Services\FulfillmentService;
use App\Services\OrderService;
use App\Services\RefundService;
use Livewire\Component;

class Show extends Component
{
    public Order $order;

    public string $message = '';

    public int $refundAmount = 0;

    public function mount(Order $order): void
    {
        $this->order = $order->load(['lines.variant.inventory', 'payments', 'fulfillments.lines']);
    }

    public function confirmPayment(OrderService $orders): void
    {
        $orders->confirmPayment($this->order);
        $this->message = 'Payment confirmed';
        $this->order = $this->order->refresh()->load(['lines.variant.inventory', 'payments', 'fulfillments.lines']);
    }

    public function fulfill(FulfillmentService $fulfillments): void
    {
        $lines = $this->order->lines->map(fn ($line): array => ['order_line_id' => $line->id, 'quantity' => $line->quantity])->all();
        $fulfillments->create($this->order, $lines);
        $this->message = 'Fulfillment created';
        $this->order = $this->order->refresh()->load(['lines', 'payments', 'fulfillments.lines']);
    }

    public function refund(RefundService $refunds): void
    {
        $payment = $this->order->payments->first();

        if ($payment === null) {
            $this->addError('refundAmount', 'No payment found.');

            return;
        }

        $refunds->create($this->order, $payment, $this->refundAmount ?: $payment->amount, 'Admin refund', true);
        $this->message = 'Refund processed';
        $this->order = $this->order->refresh()->load(['lines', 'payments', 'refunds']);
    }

    public function render(): mixed
    {
        return view('livewire.admin.orders.show')->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Products/Form.php
<?php

namespace App\Livewire\Admin\Products;

use App\Enums\ProductStatus;
use App\Models\Product;
use App\Services\ProductService;
use Livewire\Component;

class Form extends Component
{
    public ?Product $product = null;

    public string $title = '';

    public string $description = '';

    public string $vendor = 'Acme';

    public string $productType = 'Apparel';

    public int $priceAmount = 0;

    public string $status = 'draft';

    public string $message = '';

    public function mount(?Product $product = null): void
    {
        $this->product = $product;

        if ($product !== null) {
            $this->title = $product->title;
            $this->description = (string) $product->description;
            $this->vendor = (string) $product->vendor;
            $this->productType = (string) $product->product_type;
            $this->status = $product->status->value;
            $this->priceAmount = (int) ($product->defaultVariant()?->price_amount ?? 0);
        }
    }

    public function save(ProductService $products): void
    {
        $data = $this->validate(['title' => ['required', 'string', 'max:255'], 'description' => ['nullable', 'string'], 'vendor' => ['nullable', 'string', 'max:255'], 'productType' => ['nullable', 'string', 'max:255'], 'priceAmount' => ['required', 'integer', 'min:0'], 'status' => ['required', 'in:draft,active,archived']]);
        $payload = ['title' => $data['title'], 'description' => $data['description'], 'vendor' => $data['vendor'], 'product_type' => $data['productType'], 'status' => ProductStatus::from($data['status']), 'variants' => [['title' => 'Default', 'price_amount' => $data['priceAmount'], 'is_default' => true]]];

        if ($this->product === null) {
            $this->product = $products->create(app('current_store'), $payload);
        } else {
            $products->update($this->product, $payload);
        }

        $this->message = 'Product saved';
    }

    public function render(): mixed
    {
        return view('livewire.admin.products.form')->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Products/Index.php
<?php

namespace App\Livewire\Admin\Products;

use App\Enums\ProductStatus;
use App\Models\Product;
use App\Services\ProductService;
use Livewire\Component;

class Index extends Component
{
    public string $search = '';

    public string $status = 'active';

    public string $message = '';

    public function archive(int $productId, ProductService $products): void
    {
        $product = Product::query()->findOrFail($productId);
        $this->authorize('archive', $product);
        $products->transitionStatus($product, ProductStatus::Archived);
        $this->message = 'Product archived';
    }

    public function render(): mixed
    {
        $products = Product::query()->with(['variants.inventory'])->when($this->search !== '', fn ($query) => $query->where('title', 'like', '%'.$this->search.'%'))->when($this->status !== 'all', fn ($query) => $query->where('status', $this->status))->latest()->paginate(15);

        return view('livewire.admin.products.index', compact('products'))->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Section.php
<?php

namespace App\Livewire\Admin;

use Livewire\Component;

class Section extends Component
{
    public string $heading = 'Store settings';

    public function mount(): void
    {
        $this->heading = match (true) {
            request()->is('admin/inventory') => 'Inventory',
            request()->is('admin/collections') => 'Collections',
            request()->is('admin/themes*') => 'Themes',
            request()->is('admin/pages') => 'Pages',
            request()->is('admin/navigation') => 'Navigation',
            request()->is('admin/apps*') => 'Apps',
            request()->is('admin/developers') => 'Developers',
            request()->is('admin/analytics') => 'Analytics',
            request()->is('admin/search/settings') => 'Search settings',
            default => $this->heading,
        };
    }

    public function render(): mixed
    {
        return view('livewire.admin.section')->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Settings/General.php
<?php

namespace App\Livewire\Admin\Settings;

use App\Models\StoreSettings;
use Livewire\Component;

class General extends Component
{
    public string $storeName = '';

    public string $message = '';

    public function mount(): void
    {
        $settings = StoreSettings::first();
        $this->storeName = (string) ($settings?->general_json['store_name'] ?? app('current_store')->name);
    }

    public function save(): void
    {
        StoreSettings::updateOrCreate(['store_id' => app('current_store')->getKey()], ['general_json' => ['store_name' => $this->storeName]]);
        app('current_store')->update(['name' => $this->storeName]);
        $this->message = 'Settings saved';
    }

    public function render(): mixed
    {
        return view('livewire.admin.settings.general')->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Settings/Shipping.php
<?php

namespace App\Livewire\Admin\Settings;

use App\Models\ShippingRate;
use App\Models\ShippingZone;
use Livewire\Component;

class Shipping extends Component
{
    public string $zoneName = 'Domestic';

    public string $rateName = 'Standard Shipping';

    public int $amount = 499;

    public string $message = '';

    public function save(): void
    {
        $data = $this->validate(['zoneName' => ['required', 'string'], 'rateName' => ['required', 'string'], 'amount' => ['required', 'integer', 'min:0']]);
        $zone = ShippingZone::updateOrCreate(['store_id' => app('current_store')->getKey(), 'name' => $data['zoneName']], ['countries_json' => ['DE'], 'regions_json' => []]);
        ShippingRate::updateOrCreate(['shipping_zone_id' => $zone->getKey(), 'name' => $data['rateName']], ['type' => 'flat', 'price_amount' => $data['amount'], 'currency' => app('current_store')->default_currency, 'is_active' => true]);
        $this->message = 'Shipping rate saved';
    }

    public function render(): mixed
    {
        return view('livewire.admin.settings.shipping', ['zones' => ShippingZone::with('rates')->latest()->get()])->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Settings/Taxes.php
<?php

namespace App\Livewire\Admin\Settings;

use App\Models\TaxSettings;
use Livewire\Component;

class Taxes extends Component
{
    public int $rate = 1900;

    public string $message = '';

    public function mount(): void
    {
        $this->rate = (int) (TaxSettings::first()?->default_rate_basis_points ?? 1900);
    }

    public function save(): void
    {
        $this->validate(['rate' => ['required', 'integer', 'min:0', 'max:10000']]);
        TaxSettings::updateOrCreate(['store_id' => app('current_store')->getKey()], ['mode' => 'exclusive', 'default_rate_basis_points' => $this->rate, 'rates_json' => ['DE' => $this->rate]]);
        $this->message = 'Tax settings saved';
    }

    public function render(): mixed
    {
        return view('livewire.admin.settings.taxes')->layout('layouts.admin');
    }
}


command: find resources/views/livewire/admin -type f -maxdepth 4 -print | sort | xargs -I{} sh -c 'echo --- {}; sed -n "1,180p" "{}"'

path: admin

stdout:
--- resources/views/livewire/admin/auth/forgot-password.blade.php

Admin access

Forgot your password?
<form wire:submit="sendResetLink" class="mt-8 space-y-5">
Email<input id="admin-forgot-email" type="email" wire:model="email" class="mt-2 w-full rounded-lg border border-zinc-300 px-4 py-3">@error('email')
{{ $message }}
@enderror
<button class="w-full rounded-full bg-zinc-900 px-5 py-3 font-semibold text-white">Send reset link</button></form>@if ($message)
{{ $message }}
@endif

--- resources/views/livewire/admin/auth/login.blade.php

Acme Fashion

Sign in

Admin access
<form wire:submit="login" class="mt-8 space-y-5">
Email<input id="admin-email" type="email" wire:model="email" class="mt-2 w-full rounded-lg border border-zinc-300 px-4 py-3">@error('email')
{{ $message }}
@enderror

Password<input id="admin-password" type="password" wire:model="password" class="mt-2 w-full rounded-lg border border-zinc-300 px-4 py-3">@error('password')
{{ $message }}
@enderror
<button class="w-full rounded-full bg-zinc-900 px-5 py-3 font-semibold text-white">Sign in</button></form>Forgot password?

--- resources/views/livewire/admin/auth/reset-password.blade.php

Admin access

Reset your password
<form wire:submit="resetPassword" class="mt-8 space-y-5">
Email<input id="admin-reset-email" type="email" wire:model="email" class="mt-2 w-full rounded-lg border border-zinc-300 px-4 py-3">@error('email')
{{ $message }}
@enderror

New password<input id="admin-reset-password" type="password" wire:model="password" class="mt-2 w-full rounded-lg border border-zinc-300 px-4 py-3">

Confirm password<input id="admin-reset-confirmation" type="password" wire:model="passwordConfirmation" class="mt-2 w-full rounded-lg border border-zinc-300 px-4 py-3">
<button class="w-full rounded-full bg-zinc-900 px-5 py-3 font-semibold text-white">Reset password</button></form>

--- resources/views/livewire/admin/customers/index.blade.php

Customers
<input wire:model.live.debounce.300ms="search" placeholder="Search customers" class="mt-6 rounded-lg border border-zinc-300 bg-white px-4 py-3">

Customer	Orders	Joined

@foreach ($customers as $customer)
id }}">{{ $customer->name }}
{{ $customer->email }}
	{{ $customer->orders_count }}	{{ $customer->created_at->format('M j, Y') }}
@endforeach

{{ $customers->links() }}

--- resources/views/livewire/admin/customers/show.blade.php

← Customers
{{ $customer->name }}

{{ $customer->email }}

Order history
@forelse ($customer->orders as $order)id }}" href="{{ route('admin.orders.show', $order) }}" class="mt-4 flex justify-between border-b border-zinc-100 py-3" wire:navigate>{{ $order->order_number }}€{{ number_format($order->total_amount / 100, 2) }}@empty
No orders.
@endforelse

Addresses
@forelse ($customer->addresses as $address)
id }}" class="mt-4 rounded-lg bg-zinc-50 p-4 text-sm">{{ $address->address_json['address1'] ?? '' }}, {{ $address->address_json['city'] ?? '' }}
@empty
No addresses.
@endforelse

--- resources/views/livewire/admin/dashboard.blade.php

Overview

Dashboard

Add product

Total sales

€{{ number_format($sales / 100, 2) }}

Orders

{{ $orderCount }}

Products

{{ $productCount }}

Average order value

€{{ number_format($orderCount ? ($sales / $orderCount) / 100 : 0, 2) }}

Recent orders
View all

@forelse ($orders as $order)id }}" href="{{ route('admin.orders.show', $order) }}" class="flex items-center justify-between px-5 py-4 hover:bg-zinc-50" wire:navigate>{{ $order->order_number }}{{ $order->customer?->name ?? $order->email }}€{{ number_format($order->total_amount / 100, 2) }}@empty
No orders yet.
@endforelse

--- resources/views/livewire/admin/discounts/form.blade.php

← Discounts
Create discount
@if ($message)
{{ $message }}
@endif<form wire:submit="save" class="mt-8 space-y-5 rounded-2xl bg-white p-6 shadow-sm"><input wire:model="code" placeholder="Discount code" class="w-full rounded-lg border border-zinc-300 px-4 py-3"><select wire:model="valueType" class="w-full rounded-lg border border-zinc-300 px-4 py-3"><option value="percent">Percentage</option><option value="fixed">Fixed amount</option><option value="free_shipping">Free shipping</option></select><input type="number" wire:model="valueAmount" placeholder="Value (cents for fixed)" class="w-full rounded-lg border border-zinc-300 px-4 py-3"><button class="rounded-full bg-blue-600 px-6 py-3 font-semibold text-white">Save discount</button></form>

--- resources/views/livewire/admin/discounts/index.blade.php

Marketing

Discounts

Create discount

Code	Value	Status

@foreach ($discounts as $discount)
id }}">{{ $discount->code }}	{{ $discount->value_type->value === 'percent' ? $discount->value_amount.'%' : ($discount->value_type->value === 'fixed' ? '€'.number_format($discount->value_amount / 100, 2) : 'Free shipping') }}	{{ $discount->isAvailable() ? 'Active' : 'Expired' }}
@endforeach

--- resources/views/livewire/admin/orders/index.blade.php

Commerce

Orders

<select wire:model.live="status" class="rounded-lg border border-zinc-300 bg-white px-4 py-3"><option value="all">All statuses</option><option value="pending">Pending</option><option value="processing">Processing</option><option value="completed">Completed</option><option value="cancelled">Cancelled</option></select>

Order	Customer	Status	Payment	Total

@forelse ($orders as $order)
id }}">{{ $order->order_number }}
{{ $order->placed_at?->format('M j, Y') }}
	{{ $order->customer?->name ?? $order->email }}	{{ ucfirst($order->status->value) }}	{{ ucfirst($order->financial_status->value) }}	€{{ number_format($order->total_amount / 100, 2) }}
@empty
No orders found.
@endforelse

{{ $orders->links() }}

--- resources/views/livewire/admin/orders/show.blade.php

← Orders

{{ $order->order_number }}

{{ ucfirst($order->financial_status->value) }} · {{ ucfirst($order->fulfillment_status->value) }}

@if ($message){{ $message }}@endif

Line items

@foreach ($order->lines as $line)
id }}" class="flex justify-between gap-4 py-4">{{ $line->product_title }} · {{ $line->variant_title }} × {{ $line->quantity }}€{{ number_format($line->line_total_amount / 100, 2) }}
@endforeach

Total€{{ number_format($order->total_amount / 100, 2) }}

Fulfillments
@forelse ($order->fulfillments as $fulfillment)
id }}" class="mt-4 rounded-lg bg-zinc-50 p-4">
{{ ucfirst($fulfillment->status) }}

@empty
No fulfillments yet.
@endforelse

@if ($order->payment_method === 'bank_transfer' && $order->financial_status->value === 'pending')<button wire:click="confirmPayment" class="w-full rounded-full bg-blue-600 px-5 py-3 font-semibold text-white">Confirm payment</button>@endif@if (in_array($order->financial_status->value, ['paid', 'partially_refunded'], true))<button wire:click="fulfill" class="w-full rounded-full bg-zinc-900 px-5 py-3 font-semibold text-white">Create fulfillment</button>
Refund amount (cents)<input id="refund-amount" type="number" wire:model="refundAmount" class="mt-2 w-full rounded-lg border border-zinc-300 px-4 py-3"><button wire:click="refund" class="mt-3 w-full rounded-full border border-red-300 px-5 py-3 font-semibold text-red-600">Process refund</button>
@endif

--- resources/views/livewire/admin/products/form.blade.php

← Products
{{ $product ? 'Edit product' : 'Add product' }}
@if ($message)
{{ $message }}
@endif<form wire:submit="save" class="mt-8 space-y-6 rounded-2xl bg-white p-6 shadow-sm">
Title<input id="title" wire:model="title" class="mt-2 w-full rounded-lg border border-zinc-300 px-4 py-3">@error('title')
{{ $message }}
@enderror

Description<textarea id="description" wire:model="description" rows="4" class="mt-2 w-full rounded-lg border border-zinc-300 px-4 py-3"></textarea>

<input wire:model="vendor" placeholder="Vendor" class="rounded-lg border border-zinc-300 px-4 py-3"><input wire:model="productType" placeholder="Product type" class="rounded-lg border border-zinc-300 px-4 py-3"><input type="number" wire:model="priceAmount" placeholder="Price in cents" class="rounded-lg border border-zinc-300 px-4 py-3">
<select wire:model="status" class="rounded-lg border border-zinc-300 px-4 py-3"><option value="draft">Draft</option><option value="active">Active</option><option value="archived">Archived</option></select><button class="rounded-full bg-blue-600 px-6 py-3 font-semibold text-white">Save product</button></form>

--- resources/views/livewire/admin/products/index.blade.php

Catalog

Products

Add product
@if ($message)
{{ $message }}
@endif
<input wire:model.live.debounce.300ms="search" placeholder="Search products" class="rounded-lg border border-zinc-300 bg-white px-4 py-3"><select wire:model.live="status" class="rounded-lg border border-zinc-300 bg-white px-4 py-3"><option value="active">Active</option><option value="draft">Draft</option><option value="archived">Archived</option><option value="all">All</option></select>

Product	Status	Price	Actions

@forelse ($products as $product)
id }}">{{ $product->title }}
{{ $product->vendor }}
	{{ ucfirst($product->status->value) }}	€{{ number_format(($product->defaultVariant()?->price_amount ?? 0) / 100, 2) }}	@if ($product->status->value !== 'archived')<button wire:click="archive({{ $product->id }})" class="text-sm font-semibold text-red-600">Archive</button>@endif
@empty
No products found.
@endforelse

{{ $products->links() }}

--- resources/views/livewire/admin/section.blade.php

Admin

{{ $heading }}

Manage your store’s {{ strtolower($heading) }} from this workspace.

This section is ready for your store data and configuration.

--- resources/views/livewire/admin/settings/general.blade.php

Configuration

Store Settings
@if ($message)
{{ $message }}
@endif<form wire:submit="save" class="mt-8 rounded-2xl bg-white p-6 shadow-sm">Store name<input id="store-name" wire:model="storeName" class="mt-2 w-full rounded-lg border border-zinc-300 px-4 py-3"><button class="mt-5 rounded-full bg-blue-600 px-6 py-3 font-semibold text-white">Save settings</button></form>
ShippingTaxes

--- resources/views/livewire/admin/settings/shipping.blade.php

← Settings
Shipping settings
@if ($message)
{{ $message }}
@endif<form wire:submit="save" class="mt-8 grid gap-4 rounded-2xl bg-white p-6 shadow-sm sm:grid-cols-3"><input wire:model="zoneName" placeholder="Zone name" class="rounded-lg border border-zinc-300 px-4 py-3"><input wire:model="rateName" placeholder="Rate name" class="rounded-lg border border-zinc-300 px-4 py-3"><input type="number" wire:model="amount" placeholder="Amount in cents" class="rounded-lg border border-zinc-300 px-4 py-3"><button class="sm:col-span-3 rounded-full bg-blue-600 px-6 py-3 font-semibold text-white">Save shipping rate</button></form>
@foreach ($zones as $zone)
id }}" class="rounded-2xl bg-white p-6 shadow-sm">
{{ $zone->name }}
@foreach ($zone->rates as $rate)
id }}" class="mt-2 text-sm text-zinc-600">{{ $rate->name }} · €{{ number_format($rate->price_amount / 100, 2) }}
@endforeach
@endforeach

--- resources/views/livewire/admin/settings/taxes.blade.php

← Settings
Tax Settings
@if ($message)
{{ $message }}
@endif<form wire:submit="save" class="mt-8 rounded-2xl bg-white p-6 shadow-sm">Default tax rate (basis points)<input id="tax-rate" type="number" wire:model="rate" class="mt-2 w-full rounded-lg border border-zinc-300 px-4 py-3">
1900 basis points = 19%.
<button class="mt-5 rounded-full bg-blue-600 px-6 py-3 font-semibold text-white">Save tax settings</button></form>


text:
Script completed
Wall time 0.2 seconds
Output:

text:
Warning: truncated output (original token count: 16378)
Total output lines: 866

<?php

use App\Livewire\Admin\Auth\ForgotPassword as AdminForgotPassword;
use App\Livewire\Admin\Auth\Login as AdminLogin;
use App\Livewire\Admin\Auth\ResetPassword as AdminResetPassword;
use App\Livewire\Admin\Customers\Index as AdminCustomersIndex;
use App\Livewire\Admin\Customers\Show as AdminCustomersShow;
use App\Livewire\Admin\Dashboard as AdminDashboard;
use App\Livewire\Admin\Discounts\Form as AdminDiscountForm;
use App\Livewire\Admin\Discounts\Index as AdminDiscountsIndex;
use App\Livewire\Admin\Orders\Index as AdminOrdersIndex;
use App\Livewire\Admin\Orders\Show as AdminOrdersShow;
use App\Livewire\Admin\Products\Form as AdminProductForm;
use App\Livewire\Admin\Products\Index as AdminProductsIndex;
use App\Livewire\Admin\Section as AdminSection;
use App\Livewire\Admin\Settings\General as AdminSettingsGeneral;
use App\Livewire\Admin\Settings\Shipping as AdminSettingsShipping;
use App\Livewire\Admin\Settings\Taxes as AdminSettingsTaxes;
use App\Livewire\Storefront\Account\Addresses\Index as AccountAddresses;
use App\Livewire\Storefront\Account\Auth\ForgotPassword as CustomerForgotPassword;
use App\Livewire\Storefront\Account\Auth\Login as CustomerLogin;
use App\Livewire\Storefront\Account\Auth\Register as CustomerRegister;
use App\Livewire\Storefront\Account\Auth\ResetPassword as CustomerResetPassword;
use App\Livewire\Storefront\Account\Dashboard as AccountDashboard;
use App\Livewire\Storefront\Account\Orders\Index as AccountOrders;
use App\Livewire\Storefront\Account\Orders\Show as AccountOrderShow;
use App\Livewire\Storefront\Cart\Show as CartShow;
use App\Livewire\Storefront\Checkout\Confirmation as CheckoutConfirmation;
use App\Livewire\Storefront\Checkout\Show as CheckoutShow;
use App\Livewire\Storefront\Collections\Index as CollectionsIndex;
use App\Livewire\Storefront\Collections\Show as CollectionShow;
use App\Livewire\Storefront\Home;
use App\Livewire\Storefront\Pages\Show as PageShow;
use App\Livewire\Storefront\Products\Show as ProductShow;
use App\Livewire\Storefront\Search\Index as SearchIndex;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;

Route::view('dashboard', 'dashboard')
    ->middleware(['auth', 'verified'])
    ->name('dashboard');

require __DIR__.'/settings.php';

Route::get('/favicon.ico', fn (): \Symfony\Component\HttpFoundation\Response => response('', 204))->name('favicon');

Route::middleware('store.resolve')->group(function (): void {
    Route::livewire('/', Home::class)->name('home');
    Route::livewire('/collections', CollectionsIndex::class)->name('collections.index');
    Route::livewire('/collections/{handle}', CollectionShow::class)->name('collection.show');
    Route::livewire('/products/{handle}', ProductShow::class)->name('product.show');
    Route::livewire('/cart', CartShow::class)->name('cart.show');
    Route::livewire('/search', SearchIndex::class)->name('search');
    Route::livewire('/pages/{handle}', PageShow::class)->name('page.show');
    Route::livewire('/checkout/{checkoutId}', CheckoutShow::class)->name('checkout.show');
    Route::livewire('/checkout/{checkoutId}/confirmation', CheckoutConfirmation::class)->name('checkout.confirmation');
    Route::livewire('/account/login', CustomerLogin::class)->middleware('throttle:login')->name('account.login');
    Route::livewire('/account/register', CustomerRegister::class)->name('account.register');
    Route::livewire('/forgot-password', CustomerForgotPassword::class)->name('password.request');
    Route::livewire('/reset-password/{token}', CustomerResetPassword::class)->name('password.reset');
    Route::post('/account/logout', function (): \Illuminate\Http\RedirectResponse {
        Auth::guard('customer')->logout();
        request()->session()->invalidate();
        request()->session()->regenerateToken();

        return redirect()->route('home');
    })->name('account.logout');

    Route::middleware('auth:customer')->group(function (): void {
        Route::livewire('/account', AccountDashboard::class)->name('account.dashboard');
        Route::livewire('/account/orders', AccountOrders::class)->name('account.orders');
        Route::livewire('/account/orders/{orderNumber}', AccountOrderShow::class)->name('account.order.show');
        Route::livewire('/account/addresses', AccountAddresses::class)->name('account.addresses');
    });
});

Route::livewire('/admin/login', AdminLogin::class)->middleware('throttle:login')->name('admin.login');
Route::livewire('/admin/forgot-password', AdminForgotPassword::class)->middleware('throttle:login')->name('admin.password.request');
Route::livewire('/admin/reset-password/{token}', AdminResetPassword::class)->name('admin.password.reset');
Route::post('/admin/logout', function (): \Illuminate\Http\RedirectResponse {
    Auth::guard('web')->logout();
    request()->session()->invalidate();
    request()->session()->regenerateToken();

    return redirect()->route('admin.login');
})->name('admin.logout');

Route::prefix('admin')->middleware(['auth', 'verified', 'store.resolve', 'role.check:owner,admin,staff,support'])->group(function (): void {
    Route::livewire('/', AdminDashboard::class)->name('admin.dashboard');
    Route::livewire('/products', AdminProductsIndex::class)->name('admin.products.index');
    Route::livewire('/products/create', AdminProductForm::class)->middleware('role.check:owner,admin,staff')->name('admin.products.create');
    Route::livewire('/products/{product}/edit', AdminProductForm::class)->middleware('role.check:owner,admin,staff')->name('admin.products.edit');
    Route::livewire('/orders', AdminOrdersIndex::class)->name('admin.orders.index');
    Route::livewire('/orders/{order}', AdminOrdersShow::class)->name('admin.orders.show');
    Route::livewire('/customers', AdminCustomersIndex::class)->name('admin.customers.index');
    Route::livewire('/customers/{customer}', AdminCustomersShow::class)->name('admin.customers.show');
    Route::livewire('/discounts', AdminDiscountsIndex::class)->name('admin.discounts.index');
    Route::livewire('/discounts/create', AdminDiscountForm::class)->middleware('role.check:owner,admin,staff')->name('admin.discounts.create');
    Route::livewire('/discounts/{discount}/edit', AdminDiscountForm::class)->middleware('role.check:owner,admin,staff')->name('admin.discounts.edit');
    Route::livewire('/settings', AdminSettingsGeneral::class)->middleware('role.check:owner,admin')->name('admin.settings');
    Route::livewire('/settings/shipping', AdminSettingsShipping::class)->middleware('role.check:owner,admin')->name('admin.settings.shipping');
    Route::livewire('/settings/taxes', AdminSettingsTaxes::class)->middleware('role.check:owner,admin')->name('admin.settings.taxes');
    Route::livewire('/inventory', AdminSection::class)->name('admin.inventory');
    Route::livewire('/collections', AdminSection::class)->name('admin.collections');
    Route::livewire('/collections/create', AdminSection::class)->middleware('role.check:owner,admin,staff')->name('admin.collections.create');
    Route::livewire('/collections/{collection}/edit', AdminSection::class)->middleware('role.check:owner,admin,staff')->name('admin.collections.edit');
    Route::livewire('/themes', AdminSection::class)->name('admin.themes');
    Route::livewire('/themes/{theme}/editor', AdminSection::class)->name('admin.themes.editor');
    Route::livewire('/pages', AdminSection::class)->name('admin.pages');
    Route::livewire('/pages/create', AdminSection::class)->middleware('role.check:owner,admin')->name('admin.pages.create');
    Route::livewire('/pages/{page}/edit', AdminSection::class)->middleware('role.check:owner,admin')->name('admin.pages.edit');
    Route::livewire('/navigation', AdminSection::class)->name('admin.navigation');
    Route::livewire('/apps', AdminSection::class)->name('admin.apps');
    Route::livewire('/apps/{installation}', AdminSection::class)->name('admin.apps.show');
    Route::livewire('/developers', AdminSection::class)->name('admin.developers');
    Route::livewire('/analytics', AdminSection::class)->name('admin.analytics');
    Route::livewire('/search/settings', AdminSection::class)->name('admin.search.settings');
});

--- app/Livewire/Admin/Auth/ForgotPassword.php
<?php

namespace App\Livewire\Admin\Auth;

use Illuminate\Support\Facades\Password;
use Livewire\Component;

class ForgotPassword extends Component
{
    public string $email = '';

    public string $message = '';

    public function sendResetLink(): void
    {
        $this->validate(['email' => ['required', 'email']]);
        Password::broker('users')->sendResetLink(['email' => $this->email]);
        $this->message = 'If an account exists for that email, a reset link has been sent.';
    }

    public function render(): mixed
    {
        return view('livewire.admin.auth.forgot-password')->layout('layouts.auth');
    }
}
--- app/Livewire/Admin/Auth/Login.php
<?php

namespace App\Livewire\Admin\Auth;

use Illuminate\Support\Facades\Auth;
use Livewire\Component;

class Login extends Component
{
    public string $email = '';

    public string $password = '';

    public bool $remember = false;

    public function login(): void
    {
        $credentials = $this->validate(['email' => ['required', 'email'], 'password' => ['required', 'string']]);

        if (! Auth::guard('web')->attempt($credentials, $this->remember)) {
            $this->addError('email', 'These credentials do not match our records.');

            return;
        }

        session()->regenerate();
        $store = auth()->user()->stores()->first();

        if ($store !== null) {
            session()->put('current_store_id', $store->getKey());
        }

        $this->redirect(route('admin.dashboard'), navigate: true);
    }

    public function render(): mixed
    {
        return view('livewire.admin.auth.login')->layout('layouts.auth');
    }
}
--- app/Livewire/Admin/Auth/ResetPassword.php
<?php

namespace App\Livewire\Admin\Auth;

use App\Models\User;
use Illuminate\Support\Facades\Password;
use Livewire\Component;

class ResetPassword extends Component
{
    public string $token;

    public string $email = '';

    public string $password = '';

    public string $passwordConfirmation = '';

    public function mount(string $token): void
    {
        $this->token = $token;
        $this->email = request()->string('email')->toString();
    }

    public function resetPassword(): void
    {
        $data = $this->validate(['email' => ['required', 'email'], 'password' => ['required', 'min:8', 'same:passwordConfirmation']]);
        $status = Password::broker('users')->reset(['email' => $data['email'], 'password' => $data['password'], 'password_confirmation' => $this->passwordConfirmation, 'token' => $this->token], function (User $user, string $password): void {
            $user->password = $password;
            $user->save();
        });

        if ($status !== Password::PASSWORD_RESET) {
            $this->addError('email', __($status));

            return;
        }

        $this->redirect(route('admin.login'), navigate: true);
    }

    public function render(): mixed
    {
        return view('livewire.admin.auth.reset-password')->layout('layouts.auth');
    }
}
--- app/Livewire/Admin/Customers/Index.php
<?php

namespace App\Livewire\Admin\Customers;

use App\Models\Customer;
use Livewire\Component;

class Index extends Component
{
    public string $search = '';

    public function render(): mixed
    {
        $customers = Customer::query()->when($this->search !== '', fn ($query) => $query->where('email', 'like', '%'.$this->search.'%')->orWhere('first_name', 'like', '%'.$this->search.'%')->orWhere('last_name', 'like', '%'.$this->search.'%'))->withCount('orders')->latest()->paginate(15);

        return view('livewire.admin.customers.index', compact('customers'))->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Customers/Show.php
<?php

namespace App\Livewire\Admin\Customers;

use App\Models\Customer;
use Livewire\Component;

class Show extends Component
{
    public Customer $customer;

    public function mount(Customer $customer): void
    {
        $this->customer = $customer->load(['orders', 'addresses']);
    }

    public function render(): mixed
    {
        return view('livewire.admin.customers.show')->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Dashboard.php
<?php

namespace App\Livewire\Admin;

use App\Models\Order;
use App\Models\Product;
use Livewire\Component;

class Dashboard extends Component
{
    public function render(): mixed
    {
        $orders = Order::query()->with('customer')->latest('placed_at')->take(10)->get();
        $sales = (int) Order::query()->where('financial_status', 'paid')->sum('total_amount');
        $orderCount = (int) Order::query()->count();

        return view('livewire.admin.dashboard', ['orders' => $orders, 'sales' => $sales, 'orderCount' => $orderCount, 'productCount' => Product::query()->count()])->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Discounts/Form.php
<?php

namespace App\Livewire\Admin\Discounts;

use App\Models\Discount;
use Livewire\Component;

class Form extends Component
{
    public ?Discount $discount = null;

    public string $code = '';

    public string $valueType = 'percent';

    public int $valueAmount = 10;

    public string $message = '';

    public function mount(?Discount $discount = null): void
    {
        $this->discount = $discount;

        if ($discount !== null) {
            $this->code = (string) $discount->code;
            $this->valueType = $discount->value_type->value;
            $this->valueAmount = $discount->value_amount;
        }
    }

    public function save(): void
    {
        $data = $this->validate(['code' => ['required', 'string', 'max:64'], 'valueType' => ['required', 'in:percent,fixed,free_shipping'], 'valueAmount' => ['required', 'integer', 'min:0']]);
        $this->discount = Discount::updateOrCreate(['id' => $this->discount?->id], ['store_id' => app('current_store')->getKey(), 'code' => strtoupper($data['code']), 'type' => 'code', 'value_type' => $data['valueType'], 'value_amount' => $data['valueAmount'], 'status' => 'active', 'starts_at' => now(), 'rules_json' => []]);
        $this->message = 'Discount saved';
    }

    public function render(): mixed
    {
        return view('livewire.admin.discounts.form')->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Discounts/Index.php
<?php

namespace App\Livewire\Admin\Discounts;

use App\Models\Discount;
use Livewire\Component;

class Index extends Component
{
    public function render(): mixed
    {
        return view('livewire.admin.discounts.index', ['discounts' => Discount::query()->latest()->get()])->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Orders/Index.php
<?php

namespace App\Livewire\Admin\Orders;

use App\Models\Order;
use Livewire\Component;

class Index extends Component
{
    public string $status = 'all';

    public function render(): mixed
    {
        $orders = Order::query()->with('customer')->when($this->status !== 'all', fn ($query) => $query->where('status', $this->status))->latest('placed_at')->paginate(15);

        return view('livewire.admin.orders.index', compact('orders'))->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Orders/Show.php
<?php

namespace App\Livewire\Admin\Orders;

use App\Models\Order;
use App\Services\FulfillmentService;
use App\Services\OrderService;
use App\Services\RefundService;
use Livewire\Component;

class Show extends Component
{
    public Order $order;

    public string $message = '';

    public int $refundAmount = 0;

    public function mount(Order $order): void
    {
        $this->order = $order->load(['lines.variant.inventory', 'payments', 'fulfillments.lines']);
    }

    public function confirmPayment(OrderService $orders): void
    {
        $orders->confirmPayment($this->order);
        $this->message = 'Payment confirmed';
        $this->order = $this->order->refresh()->load(['lines.variant.inventory', 'payments', 'fulfillments.lines']);
    }

    public function fulfill(FulfillmentService $fulfillments): void
    {
        $lines = $this->order->lines->map(fn ($line): array => ['order_line_id' => $line->id, 'quantity' => $line->quantity])->all();
        $fulfillments->create($this->order, $lines);
        $this->message = 'Fulfillment created';
        $this->order = $this->order->refresh()->load(['lines', 'payments', 'fulfillments.lines']);
    }

    public function refund(RefundService $refunds): void
    {
        $payment = $this->order->payments->first();

        if ($payment === null) {
            $this->addError('refundAmount', 'No payment found.');

            return;
        }

        $refunds->create($this->order, $payment, $this->refundAmount ?: $payment->amount, 'Admin refund', true);
        $this->message = 'Refund processed';
        $this->order = $this->order->refresh()->load(['lines', 'payments', 'refunds']);
    }

    public function render(): mixed
    {
        return view('livewire.admin.orders.show')->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Products/Form.php
<?php

namespace App\Livewire\Admin\Products;

use App\Enums\ProductStatus;
use App\Models\Product;
use App\Services\ProductService;
use Livewire\Component;

class Form extends Component
{
    public ?Product $product = null;

    public string $title = '';

    public string $description = '';

    public string $vendor = 'Acme';

    public string $productType = 'Apparel';

    public int $priceAmount = 0;

    public string $status = 'draft';

    public string $message = '';

    public function mount(?Product $product = null): void
    {
        $this->product = $product;

        if ($product !== null) {
            $this->title = $product->title;
            $this->description = (string) $product->description;
            $this->vendor = (string) $product->vendor;
            $this->productType = (string) $product->product_type;
            $this->status = $product->status->value;
            $this->priceAmount = (int) ($product->defaultVariant()?->price_amount ?? 0);
        }
    }

    public function save(ProductService $products): void
    {
        $data = $this->validate(['title' => ['required', 'string', 'max:255'], 'description' => ['nullable', 'string'], 'vendor' => ['nullable', 'string', 'max:255'], 'productType' => ['nullable', 'string', 'max:255'], 'priceAmount' => ['required', 'integer', 'min:0'], 'status' => ['required', 'in:draft,active,archived']]);
        $payload = ['title' => $data['title'], 'description' => $data['description'], 'vendor' => $data['vendor'], 'product_type' => $data['productType'], 'status' => ProductStatus::from($data['status']), 'variants' => [['title' => 'Default', 'price_amount' => $data['priceAmount'], 'is_default' => true]]];

        if ($this->product === null) {
            $this->product = $products->create(app('current_store'), $payload);
        } else {
            $products->update($this->product, $payload);
        }

        $this->message = 'Product saved';
    }

    public function render(): mixed
    {
        return view('livewire.admin.products.form')->layout('layouts.admin');
    }
}
--- app/Livewire/Admin/Products/Index.php
<?php

namespace App\Livewire\Admin\Products;

use App\Enums\ProductStatus;
use App\Models\Product;
use App\Services\ProductService;
use Livewire\Component;

class Index extends Component
{
    public string $search = '';

    public string $status = 'active';

    public string $message = '';

    public function archive(int $productId, ProductService $products): void
    {
        $product = Product::query()->findOrFail($productId);
        $this->authorize('archive', $product);
        $products->transitionStatus($product, ProductStatus::Archived);
        $this->message = 'Product archived';
    }

    public function render(): mixed
    {
        $produ…6378 tokens truncated…message)<div class="mt-5 rounded-lg bg-green-50 px-4 py-3 text-sm font-semibold text-green-700">{{ $message }}</div>@endif<form wire:submit="save" class="mt-8 rounded-2xl bg-white p-6 shadow-sm"><label for="tax-rate" class="text-sm font-medium">Default tax rate (basis points)</label><input id="tax-rate" type="number" wire:model="rate" class="mt-2 w-full rounded-lg border border-zinc-300 px-4 py-3"><p class="mt-2 text-sm text-zinc-500">1900 basis points = 19%.</p><button class="mt-5 rounded-full bg-blue-600 px-6 py-3 font-semibold text-white">Save tax settings</button></form></div>

specs/03-ADMIN-UI.md:83:  Collections                    -> admin.collections.index
specs/03-ADMIN-UI.md:84:  Inventory                      -> admin.inventory.index
specs/03-ADMIN-UI.md:92:  Pages                          -> admin.pages.index
specs/03-ADMIN-UI.md:93:  Navigation                     -> admin.navigation.index
specs/03-ADMIN-UI.md:94:  Themes                         -> admin.themes.index
specs/03-ADMIN-UI.md:95:Analytics                        -> admin.analytics.index
specs/03-ADMIN-UI.md:97:Apps                             -> admin.apps.index
specs/03-ADMIN-UI.md:98:Developers                       -> admin.developers.index
specs/03-ADMIN-UI.md: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.
specs/03-ADMIN-UI.md:232:| `loadKpis()` | Queries aggregated order/analytics data |
specs/03-ADMIN-UI.md:349:| `search` | `string` | Search query, live-debounced at 300ms |
specs/03-ADMIN-UI.md:371:| `products` | Paginated query result with search, filters, sorting applied. Eager loads `variants`, `media` (limit 1). Uses `withCount('variants')`. |
specs/03-ADMIN-UI.md:380:| [search icon] Search products...   [Status: All v] [Type: All v] |
specs/03-ADMIN-UI.md:404:| Inventory | Yes | Sum of `inventory_items.quantity_on_hand` across all variants |
specs/03-ADMIN-UI.md:508:| `availableCollections` | All collections for the current store |
specs/03-ADMIN-UI.md:623:| Quantity | Number input (small) | On-hand inventory |
specs/03-ADMIN-UI.md:704:**Route:** `GET /admin/collections`
specs/03-ADMIN-UI.md:708:**Blade view:** `resources/views/livewire/admin/collections/index.blade.php`
specs/03-ADMIN-UI.md:714:| `search` | `string` | Search query |
specs/03-ADMIN-UI.md:724:| `collections` | Paginated, filtered, searched. Includes `withCount('products')`. |
specs/03-ADMIN-UI.md:746:**Route:** `GET /admin/collections/create` and `GET /admin/collections/{collection}/edit`
specs/03-ADMIN-UI.md:750:**Blade view:** `resources/views/livewire/admin/collections/form.blade.php`
specs/03-ADMIN-UI.md:773:| `searchResults` | Products matching `productSearch` that are not already assigned |
specs/03-ADMIN-UI.md:821:**Route:** `GET /admin/inventory`
specs/03-ADMIN-UI.md:825:**Blade view:** `resources/views/livewire/admin/inventory/index.blade.php`
specs/03-ADMIN-UI.md:831:| `search` | `string` | Search by product title or SKU |
specs/03-ADMIN-UI.md:836:| `inventoryItems` | Paginated inventory items with variant and product eager loaded, filtered by search and stock level |
specs/03-ADMIN-UI.md:844:| [search] Search by product or SKU   [Stock: All v]               |
specs/03-ADMIN-UI.md:874:| `search` | `string` | Search by order number or customer email |
specs/03-ADMIN-UI.md:887:| `orders` | Paginated query with search, status filter, sorting. Eager loads `customer`. |
specs/03-ADMIN-UI.md:895:| [search] Search by order # or email...                           |
specs/03-ADMIN-UI.md:960:| `confirmPayment()` | Confirms bank transfer payment received (transitions financial_status to `paid`, commits inventory, dispatches `OrderPaid`) |
specs/03-ADMIN-UI.md: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`.
specs/03-ADMIN-UI.md:1148:| `search` | `string` | Search by name or email |
specs/03-ADMIN-UI.md:1152:| `customers` | Paginated, searched customers with `withCount('orders')` and `withSum('orders', 'total_amount')` |
specs/03-ADMIN-UI.md:1247:| `search` | `string` | Search by discount code |
specs/03-ADMIN-UI.md:1289:| `specificCollectionIds` | `array<int>` | Specific collections the discount applies to |
specs/03-ADMIN-UI.md:1296:| `collectionSearch` | `string` | Search for collections to add |
specs/03-ADMIN-UI.md:1304:| `addCollection(int $collectionId)` | Adds collection to specific collections |
specs/03-ADMIN-UI.md:1334:|  Specific collections: (same pattern as products)                 |
specs/03-ADMIN-UI.md:1361:- Specific products: search input with live debounce. Results appear in a scrollable dropdown. Selected products shown as a list with remove (X) buttons.
specs/03-ADMIN-UI.md:1362:- Specific collections: same pattern as products.
specs/03-ADMIN-UI.md:1659:**Route:** `GET /admin/themes`
specs/03-ADMIN-UI.md:1663:**Blade view:** `resources/views/livewire/admin/themes/index.blade.php`
specs/03-ADMIN-UI.md:1669:| `themes` | `Collection` | All themes for the store |
specs/03-ADMIN-UI.md:1716:**Route:** `GET /admin/themes/{theme}/editor`
specs/03-ADMIN-UI.md:1720:**Blade view:** `resources/views/livewire/admin/themes/editor.blade.php`
specs/03-ADMIN-UI.md:1745:| [<- Back to themes]                     [Save]  [Save & publish] |
specs/03-ADMIN-UI.md:1759:|            |  |                        | |  Show search:        |
specs/03-ADMIN-UI.md:1771:- "Back to themes" button (ghost, with left arrow icon) linking to themes index
specs/03-ADMIN-UI.md:1807:**Route:** `GET /admin/pages`
specs/03-ADMIN-UI.md:1811:**Blade view:** `resources/views/livewire/admin/pages/index.blade.php`
specs/03-ADMIN-UI.md:1817:| `search` | `string` | Search by page title |
specs/03-ADMIN-UI.md:1821:| `pages` | Paginated pages for the current store |
specs/03-ADMIN-UI.md:1832:**Route:** `GET /admin/pages/create` and `GET /admin/pages/{page}/edit`
specs/03-ADMIN-UI.md:1836:**Blade view:** `resources/views/livewire/admin/pages/form.blade.php`
specs/03-ADMIN-UI.md:1871:**Route:** `GET /admin/navigation`
specs/03-ADMIN-UI.md:1875:**Blade view:** `resources/views/livewire/admin/navigation/index.blade.php`
specs/03-ADMIN-UI.md:1959:**Route:** `GET /admin/apps`
specs/03-ADMIN-UI.md:1963:**Blade view:** `resources/views/livewire/admin/apps/index.blade.php`
specs/03-ADMIN-UI.md:2005:**Empty state:** "No apps installed" with descriptive text.
specs/03-ADMIN-UI.md:2011:**Route:** `GET /admin/developers`
specs/03-ADMIN-UI.md:2015:**Blade view:** `resources/views/livewire/admin/developers/index.blade.php`
specs/03-ADMIN-UI.md:2124:**Route:** `GET /admin/analytics`
specs/03-ADMIN-UI.md:2128:**Blade view:** `resources/views/livewire/admin/analytics/index.blade.php`
specs/03-ADMIN-UI.md:2151:| `updatedDateRange()` | Reloads analytics data |
specs/03-ADMIN-UI.md:2154:| `loadAnalytics()` | Master method to reload all analytics data |
specs/03-ADMIN-UI.md:2211:**Route:** `GET /admin/search/settings`
specs/03-ADMIN-UI.md:2215:**Blade view:** `resources/views/livewire/admin/search/settings.blade.php`
specs/03-ADMIN-UI.md:2252:| Words that are excluded from search indexing.                    |
specs/03-ADMIN-UI.md:2296:All list pages follow a consistent pattern:
specs/03-ADMIN-UI.md:2307:All form pages follow a consistent pattern:
specs/03-ADMIN-UI.md:2367:- Semantic HTML: headings hierarchy, landmark regions, lists for navigation
specs/03-ADMIN-UI.md:2402:| `App\Livewire\Admin\Layout\Sidebar` | `livewire.admin.layout.sidebar` | Layout (all pages) |
specs/03-ADMIN-UI.md:2403:| `App\Livewire\Admin\Layout\TopBar` | `livewire.admin.layout.top-bar` | Layout (all pages) |
specs/03-ADMIN-UI.md:2407:| `App\Livewire\Admin\Collections\Index` | `livewire.admin.collections.index` | `GET /admin/collections` |
specs/03-ADMIN-UI.md:2408:| `App\Livewire\Admin\Collections\Form` | `livewire.admin.collections.form` | `GET /admin/collections/create`, `GET /admin/collections/{collection}/edit` |
specs/03-ADMIN-UI.md:2409:| `App\Livewire\Admin\Inventory\Index` | `livewire.admin.inventory.index` | `GET /admin/inventory` |
specs/03-ADMIN-UI.md:2420:| `App\Livewire\Admin\Themes\Index` | `livewire.admin.themes.index` | `GET /admin/themes` |
specs/03-ADMIN-UI.md:2421:| `App\Livewire\Admin\Themes\Editor` | `livewire.admin.themes.editor` | `GET /admin/themes/{theme}/editor` |
specs/03-ADMIN-UI.md:2422:| `App\Livewire\Admin\Pages\Index` | `livewire.admin.pages.index` | `GET /admin/pages` |
specs/03-ADMIN-UI.md:2423:| `App\Livewire\Admin\Pages\Form` | `livewire.admin.pages.form` | `GET /admin/pages/create`, `GET /admin/pages/{page}/edit` |
specs/03-ADMIN-UI.md:2424:| `App\Livewire\Admin\Navigation\Index` | `livewire.admin.navigation.index` | `GET /admin/navigation` |
specs/03-ADMIN-UI.md:2425:| `App\Livewire\Admin\Apps\Index` | `livewire.admin.apps.index` | `GET /admin/apps` |
specs/03-ADMIN-UI.md:2426:| `App\Livewire\Admin\Developers\Index` | `livewire.admin.developers.index` | `GET /admin/developers` |
specs/03-ADMIN-UI.md:2427:| `App\Livewire\Admin\Analytics\Index` | `livewire.admin.analytics.index` | `GET /admin/analytics` |
specs/03-ADMIN-UI.md:2428:| `App\Livewire\Admin\Search\Settings` | `livewire.admin.search.settings` | `GET /admin/search/settings` |
specs/02-API-ROUTES.md:34:All admin pages render Livewire full-page components. Data mutations happen via Livewire actions (not separate POST endpoints).
specs/02-API-ROUTES.md:48:| GET | `/admin/products` | Product list - searchable, filterable by status, bulk actions | `Admin\Products\Index` |
specs/02-API-ROUTES.md:56:| GET | `/admin/inventory` | Inventory management page | `Admin\Inventory\Index` |
specs/02-API-ROUTES.md:62:| GET | `/admin/collections` | Collection list | `Admin\Collections\Index` |
specs/02-API-ROUTES.md:63:| GET | `/admin/collections/create` | Create collection form | `Admin\Collections\Create` |
specs/02-API-ROUTES.md:64:| GET | `/admin/collections/{collection}/edit` | Edit collection form | `Admin\Collections\Edit` |
specs/02-API-ROUTES.md:100:| GET | `/admin/themes` | Theme management - cards with actions (Customize, Preview, Publish, Duplicate, Delete) | `Admin\Themes\Index` |
specs/02-API-ROUTES.md:101:| GET | `/admin/themes/{theme}/editor` | Theme editor - left panel sections, center preview iframe, right panel settings | `Admin\Themes\Editor` |
specs/02-API-ROUTES.md:107:| GET | `/admin/pages` | Content pages list | `Admin\Pages\Index` |
specs/02-API-ROUTES.md:108:| GET | `/admin/pages/create` | Create content page | `Admin\Pages\Create` |
specs/02-API-ROUTES.md:109:| GET | `/admin/pages/{page}/edit` | Edit content page | `Admin\Pages\Edit` |
specs/02-API-ROUTES.md:115:| GET | `/admin/navigation` | Navigation menus management | `Admin\Navigation\Index` |
specs/02-API-ROUTES.md:121:| GET | `/admin/apps` | Apps directory - list installed and available apps | `Admin\Apps\Index` |
specs/02-API-ROUTES.md:122:| GET | `/admin/apps/{installation}` | Installed app detail - scopes, webhooks, usage | `Admin\Apps\Show` |
specs/02-API-ROUTES.md:128:| GET | `/admin/developers` | Developer settings - API keys/tokens, webhook management | `Admin\Developers\Index` |
specs/02-API-ROUTES.md:130:> **Note:** Webhook subscription management (CRUD) is available exclusively through the admin UI at `/admin/developers`. There are no REST API endpoints for programmatic webhook subscription management in the initial implementation.
specs/02-API-ROUTES.md:136:| GET | `/admin/analytics` | Analytics dashboard - sales, traffic, funnel, exports | `Admin\Analytics\Index` |
specs/02-API-ROUTES.md:142:| GET | `/admin/search/settings` | Search settings - synonyms, stopwords, reindex | `Admin\Search\Settings` |
specs/02-API-ROUTES.md:155:| GET | `/collections` | All collections listing | `Storefront\Collections\Index` |
specs/02-API-ROUTES.md:156:| GET | `/collections/{handle}` | Single collection page with product grid and filters | `Storefront\Collections\Show` |
specs/02-API-ROUTES.md:159:| GET | `/search` | Search results page with filter panel, sort dropdown, product grid | `Storefront\Search\Index` |
specs/02-API-ROUTES.md:160:| GET | `/pages/{handle}` | Static content page (About, FAQ, etc.) | `Storefront\Pages\Show` |
specs/02-API-ROUTES.md:203:These endpoints power the interactive storefront experience (cart drawer, checkout flow, search autocomplete, analytics).
specs/02-API-ROUTES.md:332:| `quantity` | integer | Yes | Min: 1, max: 9999. Must not exceed available inventory when policy is `deny`. |
specs/02-API-ROUTES.md:372:| `quantity` | integer | Yes | Min: 1, max: 9999. Must not exceed available inventory when policy is `deny`. |
specs/02-API-ROUTES.md:382:- `422` - Validation error (e.g., exceeds inventory)
specs/02-API-ROUTES.md:440:    API->>API: Reserve inventory
specs/02-API-ROUTES.md:445:        API->>API: Create order (paid), commit inventory
specs/02-API-ROUTES.md:449:        API->>API: Release reserved inventory
specs/02-API-ROUTES.md:453:        API->>API: Create order (pending), inventory stays reserved
specs/02-API-ROUTES.md:1019:#### GET /api/storefront/v1/search
specs/02-API-ROUTES.md:1024:- **Rate limit:** `search` (30/min per IP)
specs/02-API-ROUTES.md:1097:#### GET /api/storefront/v1/search/suggest
specs/02-API-ROUTES.md:1102:- **Rate limit:** `search` (30/min per IP)
specs/02-API-ROUTES.md:1143:#### POST /api/storefront/v1/analytics/events
specs/02-API-ROUTES.md:1145:Batch submit storefront analytics events.
specs/02-API-ROUTES.md:1148:- **Rate limit:** `analytics` (60/min per IP)
specs/02-API-ROUTES.md:1186:| `events[].type` | string | Yes | One of: `page_view`, `add_to_cart`, `remove_from_cart`, `checkout_started`, `checkout_completed`, `search`. |
specs/02-API-ROUTES.md:1212:These endpoints provide programmatic access for integrations, CLI tools, and third-party apps. The admin web UI uses Livewire actions directly and does not call these endpoints.
specs/02-API-ROUTES.md:1436:      "total_inventory": 150,
specs/02-API-ROUTES.md:1492:      "inventory": {
specs/02-API-ROUTES.md:1498:  "collections": [5]
specs/02-API-ROUTES.md:1516:| `collections` | array of integers | No | Collection IDs to add this product to. |
specs/02-API-ROUTES.md:1534:| `variants[].inventory.quantity_on_hand` | integer | No | Default: 0. Min: 0. |
specs/02-API-ROUTES.md:1535:| `variants[].inventory.policy` | string | No | One of: `deny`, `continue`. Default: `deny`. |
specs/02-API-ROUTES.md:1549:Get a single product with all variants, options, media, and collections.
specs/02-API-ROUTES.md:1606:        "inventory": {
specs/02-API-ROUTES.md:1628:    "collections": [
specs/02-API-ROUTES.md:1742:| GET | `/api/admin/v1/stores/{storeId}/collections` | `read-collections` | List collections | `api.admin` (60/min) |
specs/02-API-ROUTES.md:1743:| POST | `/api/admin/v1/stores/{storeId}/collections` | `write-collections` | Create a collection | `api.admin` (60/min) |
specs/02-API-ROUTES.md:1744:| PUT | `/api/admin/v1/stores/{storeId}/collections/{collectionId}` | `write-collections` | Update a collection | `api.admin` (60/min) |
specs/02-API-ROUTES.md:1745:| DELETE | `/api/admin/v1/stores/{storeId}/collections/{collectionId}` | `write-collections` | Delete a collection | `api.admin` (60/min) |
specs/02-API-ROUTES.md:1747:#### GET /api/admin/v1/stores/{storeId}/collections
specs/02-API-ROUTES.md:1749:List collections.
specs/02-API-ROUTES.md:1789:#### POST /api/admin/v1/stores/{storeId}/collections
specs/02-API-ROUTES.md:1825:#### PUT /api/admin/v1/stores/{storeId}/collections/{collectionId}
specs/02-API-ROUTES.md:1840:#### DELETE /api/admin/v1/stores/{storeId}/collections/{collectionId}
specs/02-API-ROUTES.md:1844:**Authentication:** Bearer token with `write-collections` ability.
specs/02-API-ROUTES.md:2229:| `rules_json.applicable_collection_ids` | array | No | Empty array means all collections. |
specs/02-API-ROUTES.md:2498:| POST | `/api/admin/v1/stores/{storeId}/themes` | `write-themes` | Upload or install a new theme | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2499:| POST | `/api/admin/v1/stores/{storeId}/themes/{themeId}/publish` | `write-themes` | Publish a theme (make active) | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2500:| PUT | `/api/admin/v1/stores/{storeId}/themes/{themeId}/settings` | `write-themes` | Update theme customization settings | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2502:#### POST /api/admin/v1/stores/{storeId}/themes
specs/02-API-ROUTES.md:2535:#### POST /api/admin/v1/stores/{storeId}/themes/{themeId}/publish
specs/02-API-ROUTES.md:2558:#### PUT /api/admin/v1/stores/{storeId}/themes/{themeId}/settings
specs/02-API-ROUTES.md:2604:| GET | `/api/admin/v1/stores/{storeId}/pages` | `read-content` | List content pages | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2605:| POST | `/api/admin/v1/stores/{storeId}/pages` | `write-content` | Create a content page | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2606:| PUT | `/api/admin/v1/stores/{storeId}/pages/{pageId}` | `write-content` | Update a content page | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2607:| DELETE | `/api/admin/v1/stores/{storeId}/pages/{pageId}` | `write-content` | Delete a content page | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2609:#### GET /api/admin/v1/stores/{storeId}/pages
specs/02-API-ROUTES.md:2611:List content pages.
specs/02-API-ROUTES.md:2648:#### POST /api/admin/v1/stores/{storeId}/pages
specs/02-API-ROUTES.md:2678:#### PUT /api/admin/v1/stores/{storeId}/pages/{pageId}
specs/02-API-ROUTES.md:2693:#### DELETE /api/admin/v1/stores/{storeId}/pages/{pageId}
specs/02-API-ROUTES.md:2719:| POST | `/api/admin/v1/stores/{storeId}/search/reindex` | `write-settings` | Trigger full search reindex (async) | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2720:| GET | `/api/admin/v1/stores/{storeId}/search/status` | `read-settings` | Get current search index status | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2722:#### POST /api/admin/v1/stores/{storeId}/search/reindex
specs/02-API-ROUTES.md:2724:Trigger a full search reindex for the store. Runs asynchronously via a queued job.
specs/02-API-ROUTES.md:2743:#### GET /api/admin/v1/stores/{storeId}/search/status
specs/02-API-ROUTES.md:2745:Get the current search index status.
specs/02-API-ROUTES.md:2770:| GET | `/api/admin/v1/stores/{storeId}/analytics/summary` | `read-analytics` | Get analytics summary for date range | `api.admin` (60/min) |
specs/02-API-ROUTES.md:2774:#### GET /api/admin/v1/stores/{storeId}/analytics/summary
specs/02-API-ROUTES.md:2776:Get analytics summary for a date range.
specs/02-API-ROUTES.md:2907:These endpoints implement OAuth2 authorization for third-party apps, powered by Laravel Passport.
specs/02-API-ROUTES.md:2979:Third-party apps use OAuth tokens to access store data via scoped endpoints.
specs/02-API-ROUTES.md:2983:| GET | `/api/apps/v1/stores/{storeId}/products` | `read-products` | List products (same schema as admin API) |
specs/02-API-ROUTES.md:2984:| GET | `/api/apps/v1/stores/{storeId}/orders` | `read-orders` | List orders |
specs/02-API-ROUTES.md:2985:| GET | `/api/apps/v1/stores/{storeId}/customers` | `read-customers` | List customers |
specs/02-API-ROUTES.md:3084:| `search` | 30 requests | 1 minute | IP address | Search and autocomplete endpoints |
specs/02-API-ROUTES.md:3085:| `analytics` | 60 requests | 1 minute | IP address | Analytics event ingestion |
specs/02-API-ROUTES.md:3123:| `GET /api/storefront/v1/search` | `search` |
specs/02-API-ROUTES.md:3124:| `GET /api/storefront/v1/search/suggest` | `search` |
specs/02-API-ROUTES.md:3125:| `POST /api/storefront/v1/analytics/events` | `analytics` |
specs/02-API-ROUTES.md:3173:- **App API:** Versioned via URL path (`/api/apps/v1/`).


exec

status: completed

input:
const r = await tools.exec_command({cmd: "sed -n '1,220p' app/Livewire/Admin/Section.php; sed -n '1,220p' resources/views/livewire/admin/section.blade.php; sed -n '1,140p' resources/views/layouts/admin.blade.php", workdir: "/Users/wesner/Herd/shop", max_output_tokens: 8000, yield_time_ms: 10000});
text(r.output);


command: sed -n '1,220p' app/Livewire/Admin/Section.php; sed -n '1,220p' resources/views/livewire/admin/section.blade.php; sed -n '1,140p' resources/views/layouts/admin.blade.php

Section.php

type: read

admin.blade.php

stdout:
<?php

namespace App\Livewire\Admin;

use Livewire\Component;

class Section extends Component
{
    public string $heading = 'Store settings';

    public function mount(): void
    {
        $this->heading = match (true) {
            request()->is('admin/inventory') => 'Inventory',
            request()->is('admin/collections') => 'Collections',
            request()->is('admin/themes*') => 'Themes',
            request()->is('admin/pages') => 'Pages',
            request()->is('admin/navigation') => 'Navigation',
            request()->is('admin/apps*') => 'Apps',
            request()->is('admin/developers') => 'Developers',
            request()->is('admin/analytics') => 'Analytics',
            request()->is('admin/search/settings') => 'Search settings',
            default => $this->heading,
        };
    }

    public function render(): mixed
    {
        return view('livewire.admin.section')->layout('layouts.admin');
    }
}

Admin

{{ $heading }}

Manage your store’s {{ strtolower($heading) }} from this workspace.

This section is ready for your store data and configuration.

<!doctype html>

    <meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>{{ $title ?? 'Admin · '.($currentStore?->name ?? 'Shop') }}</title>@vite(['resources/css/app.css', 'resources/js/app.js'])@livewireStyles

{{ $currentStore?->name ?? 'Shop' }}DashboardProductsOrdersCustomersDiscountsSettingsAnalyticsThemes<form class="mt-10" method="post" action="{{ route('admin.logout') }}">@csrf<button class="text-sm text-zinc-500 hover:text-red-600">Sign out</button></form>

Store Admin{{ auth()->user()?->email }}
{{ $slot }}

@livewireScripts