Output:
Chunk ID: 967d2a
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 880
Output:
resources/views/livewire/admin/discounts/form.blade.php:8:        <flux:button :href="route('admin.discounts.index')" wire:navigate variant="filled" icon="arrow-left">
resources/views/livewire/admin/discounts/form.blade.php:35:                        <flux:button type="button" wire:click="generateCode" variant="filled">Generate</flux:button>
resources/views/livewire/admin/customers/show.blade.php:8:        <flux:button :href="route('admin.customers.index')" wire:navigate variant="filled" icon="arrow-left">
resources/views/livewire/admin/customers/show.blade.php:126:                            <flux:button wire:click="openAddressForm({{ $address->getKey() }})" size="sm" variant="filled">Edit</flux:button>
resources/views/livewire/admin/customers/show.blade.php:130:                            <flux:button wire:click="deleteAddress({{ $address->getKey() }})" wire:confirm="Delete this address?" size="sm" variant="danger">Delete</flux:button>
resources/views/livewire/admin/customers/show.blade.php:178:                    <flux:button variant="filled">Cancel</flux:button>
resources/views/livewire/admin/products/form.blade.php:14:            <flux:button wire:click="deleteProduct" variant="danger" icon="archive-box">Archive</flux:button>
resources/views/livewire/admin/products/index.blade.php:38:                <flux:button wire:click="bulkDelete" variant="danger">Delete</flux:button>
resources/views/livewire/admin/settings/index.blade.php:10:            <flux:button :href="route('admin.settings.shipping')" wire:navigate variant="filled">Shipping</flux:button>
resources/views/livewire/admin/settings/index.blade.php:11:            <flux:button :href="route('admin.settings.taxes')" wire:navigate variant="filled">Taxes</flux:button>
resources/views/livewire/admin/settings/index.blade.php:134:                                        <flux:button type="button" size="sm" variant="filled" wire:click="setPrimary({{ $domain->getKey() }})">Set primary</flux:button>
resources/views/livewire/admin/settings/index.blade.php:136:                                    <flux:button type="button" size="sm" variant="danger" wire:click="removeDomain({{ $domain->getKey() }})">Delete</flux:button>
resources/views/livewire/admin/orders/show.blade.php:23:                    <flux:button variant="danger" icon="receipt-refund">
resources/views/livewire/admin/orders/show.blade.php:31:                    <flux:button variant="filled" icon="truck">
resources/views/livewire/admin/orders/show.blade.php:138:                                        <flux:button wire:click="markFulfillmentShipped({{ $fulfillment->getKey() }})" size="sm" variant="filled">Mark shipped</flux:button>
resources/views/livewire/admin/orders/show.blade.php:141:                                        <flux:button wire:click="markFulfillmentDelivered({{ $fulfillment->getKey() }})" size="sm" variant="filled">Mark delivered</flux:button>
resources/views/livewire/admin/orders/show.blade.php:168:                    <flux:button :href="route('admin.customers.show', $order->customer)" wire:navigate variant="filled" size="sm" class="mt-4">
resources/views/livewire/admin/orders/show.blade.php:232:                    <flux:button variant="filled">Cancel</flux:button>
resources/views/livewire/admin/orders/show.blade.php:234:                <flux:button type="submit" variant="danger">Process refund</flux:button>
resources/views/livewire/admin/orders/show.blade.php:272:                    <flux:button variant="filled">Cancel</flux:button>


Input:
*** Begin Patch
*** Delete File: /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/settings/shipping.blade.php
*** End Patch


Success. Updated the following files:
D /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/settings/shipping.blade.php

{{-- No surplus words or unnecessary actions. - Marcus Aurelius --}}


Output: {"output":"Success. Updated the following files:\nD /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/settings/shipping.blade.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Add File: /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/settings/shipping.blade.php
+

+

+

+            <flux:heading size="xl">Shipping</flux:heading>
+            <flux:text class="mt-1">Zones, rates, and address matching.</flux:text>
+

+
+

+            <flux:button :href="route('admin.settings.index')" wire:navigate variant="filled">General</flux:button>
+            <flux:button :href="route('admin.settings.shipping')" wire:navigate variant="primary">Shipping</flux:button>
+            <flux:button :href="route('admin.settings.taxes')" wire:navigate variant="filled">Taxes</flux:button>
+

+

+
+    @if (session('status'))
+        <flux:callout color="green" icon="check-circle">{{ session('status') }}</flux:callout>
+    @endif
+
+

+

+            @foreach ($zones as $zone)
+
getKey() }}" class="rounded-lg border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-900">
+

+

+                            <flux:heading size="lg">{{ $zone->name }}</flux:heading>
+                            <flux:text class="mt-1">{{ implode(', ', $zone->countries_json ?? []) }}</flux:text>
+

+
+

+                            <flux:button type="button" wire:click="editZone({{ $zone->getKey() }})" size="sm" variant="filled">Edit</flux:button>
+                            <flux:button type="button" wire:click="deleteZone({{ $zone->getKey() }})" wire:confirm="Delete this shipping zone?" size="sm" variant="danger">Delete</flux:button>
+

+

+
+

+

+

+

+                                    Name
+                                    Type
+                                    Config
+                                    Active
+                                    Actions
+

+

+

+                                @forelse ($zone->rates as $rate)
+
getKey() }}">
+                                        {{ $rate->name }}
+                                        <flux:badge>{{ Str::headline($rate->type->value) }}</flux:badge>
+                                        {{ $this->rateSummary($rate) }}
+
+                                            <flux:switch wire:click="toggleRateActive({{ $rate->getKey() }})" :checked="$rate->is_active" aria-label="Toggle {{ $rate->name }}" />
+
+
+

+                                                <flux:button type="button" wire:click="editRate({{ $rate->getKey() }})" size="sm" variant="filled">Edit</flux:button>
+                                                <flux:button type="button" wire:click="deleteRate({{ $rate->getKey() }})" wire:confirm="Delete this shipping rate?" size="sm" variant="danger">Delete</flux:button>
+

+
+

+                                @empty
+

+                                        No rates configured.
+

+                                @endforelse
+

+

+

+
+

+                        <flux:button type="button" wire:click="addRate({{ $zone->getKey() }})" variant="filled" icon="plus">Add rate</flux:button>
+

+

+            @endforeach
+

+
+

+            <form wire:submit="saveZone" class="rounded-lg border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-900">
+                <flux:heading size="lg">{{ $editingZoneId ? 'Edit zone' : 'Add zone' }}</flux:heading>
+
+

+                    <flux:input wire:model="zoneName" label="Zone name" placeholder="Germany" />
+                    <flux:error name="zoneName" />
+
+                    <flux:input wire:model="zoneCountries" label="Countries" placeholder="DE, AT, CH" />
+                    <flux:error name="zoneCountries" />
+
+

+                        <flux:button type="button" wire:click="$set('editingZoneId', null)" variant="ghost">Cancel</flux:button>
+                        <flux:button type="submit" variant="primary">Save zone</flux:button>
+

+

+            </form>
+
+            @if ($rateZoneId)
+                <form wire:submit="saveRate" class="rounded-lg border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-900">
+                    <flux:heading size="lg">{{ $editingRateId ? 'Edit rate' : 'Add rate' }}</flux:heading>
+
+

+                        <flux:input wire:model="rateName" label="Rate name" placeholder="Standard" />
+                        <flux:select wire:model.live="rateType" label="Rate type">
+                            <flux:select.option value="flat">Flat</flux:select.option>
+                            <flux:select.option value="weight">Weight</flux:select.option>
+                            <flux:select.option value="price">Price</flux:select.option>
+                            <flux:select.option value="carrier">Carrier</flux:select.option>
+                        </flux:select>
+                        <flux:input wire:model="rateAmount" type="number" step="0.01" min="0" label="Amount" />
+
+                        @if ($rateType === 'weight')
+

+                                <flux:input wire:model="minimumWeight" type="number" min="0" label="Min weight (g)" />
+                                <flux:input wire:model="maximumWeight" type="number" min="1" label="Max weight (g)" />
+

+                        @endif
+
+                        @if ($rateType === 'price')
+

+                                <flux:input wire:model="minimumOrderAmount" type="number" step="0.01" min="0" label="Min order" />
+                                <flux:input wire:model="maximumOrderAmount" type="number" step="0.01" min="0" label="Max order" />
+

+                        @endif
+
+                        <flux:switch wire:model="rateActive" label="{{ $rateActive ? 'Active' : 'Inactive' }}" align="left" />
+
+

+                            <flux:button type="button" wire:click="$set('rateZoneId', null)" variant="ghost">Cancel</flux:button>
+                            <flux:button type="submit" variant="primary">Save rate</flux:button>
+

+

+                </form>
+            @endif
+
+

+                <flux:heading size="lg">Test address</flux:heading>
+
+

+                    <flux:input wire:model="testCountry" label="Country" placeholder="DE" />
+                    <flux:input wire:model="testRegion" label="Region" placeholder="DE-BE" />
+

+
+

+                    <flux:button type="button" wire:click="testShippingAddress" variant="filled">Test</flux:button>
+

+
+                @if ($testResult)
+

+                        @if ($testResult['zone'])
+
Matched zone: {{ $testResult['zone'] }}

+

+                                @foreach ($testResult['rates'] as $rate)
+
{{ $rate }}

+                                @endforeach
+

+                        @else
+
No shipping zone matches this address.

+                        @endif
+

+                @endif
+

+

+

+

*** End Patch


Success. Updated the following files:
A /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/settings/shipping.blade.php

            <flux:heading size="xl">Shipping</flux:heading>
            <flux:text class="mt-1">Zones, rates, and address matching.</flux:text>

            <flux:button :href="route('admin.settings.index')" wire:navigate variant="filled">General</flux:button>
            <flux:button :href="route('admin.settings.shipping')" wire:navigate variant="primary">Shipping</flux:button>
            <flux:button :href="route('admin.settings.taxes')" wire:navigate variant="filled">Taxes</flux:button>

    @if (session('status'))
        <flux:callout color="green" icon="check-circle">{{ session('status') }}</flux:callout>
    @endif

            @foreach ($zones as $zone)

getKey() }}" class="rounded-lg border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-900">

                            <flux:heading size="lg">{{ $zone->name }}</flux:heading>
                            <flux:text class="mt-1">{{ implode(', ', $zone->countries_json ?? []) }}</flux:text>

                            <flux:button type="button" wire:click="editZone({{ $zone->getKey() }})" size="sm" variant="filled">Edit</flux:button>
                            <flux:button type="button" wire:click="deleteZone({{ $zone->getKey() }})" wire:confirm="Delete this shipping zone?" size="sm" variant="danger">Delete</flux:button>

                                    Name
                                    Type
                                    Config
                                    Active
                                    Actions

                                @forelse ($zone->rates as $rate)

getKey() }}">
                                        {{ $rate->name }}
                                        <flux:badge>{{ Str::headline($rate->type->value) }}</flux:badge>
                                        {{ $this->rateSummary($rate) }}

                                            <flux:switch wire:click="toggleRateActive({{ $rate->getKey() }})" :checked="$rate->is_active" aria-label="Toggle {{ $rate->name }}" />

                                                <flux:button type="button" wire:click="editRate({{ $rate->getKey() }})" size="sm" variant="filled">Edit</flux:button>
                                                <flux:button type="button" wire:click="deleteRate({{ $rate->getKey() }})" wire:confirm="Delete this shipping rate?" size="sm" variant="danger">Delete</flux:button>

                                @empty

                                        No rates configured.

                                @endforelse

                        <flux:button type="button" wire:click="addRate({{ $zone->getKey() }})" variant="filled" icon="plus">Add rate</flux:button>

            @endforeach

            <form wire:submit="saveZone" class="rounded-lg border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-900">
                <flux:heading size="lg">{{ $editingZoneId ? 'Edit zone' : 'Add zone' }}</flux:heading>

                    <flux:input wire:model="zoneName" label="Zone name" placeholder="Germany" />
                    <flux:error name="zoneName" />

                    <flux:input wire:model="zoneCountries" label="Countries" placeholder="DE, AT, CH" />
                    <flux:error name="zoneCountries" />

                        <flux:button type="button" wire:click="$set('editingZoneId', null)" variant="ghost">Cancel</flux:button>
                        <flux:button type="submit" variant="primary">Save zone</flux:button>

            </form>

            @if ($rateZoneId)
                <form wire:submit="saveRate" class="rounded-lg border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-900">
                    <flux:heading size="lg">{{ $editingRateId ? 'Edit rate' : 'Add rate' }}</flux:heading>

                        <flux:input wire:model="rateName" label="Rate name" placeholder="Standard" />
                        <flux:select wire:model.live="rateType" label="Rate type">
                            <flux:select.option value="flat">Flat</flux:select.option>
                            <flux:select.option value="weight">Weight</flux:select.option>
                            <flux:select.option value="price">Price</flux:select.option>
                            <flux:select.option value="carrier">Carrier</flux:select.option>
                        </flux:select>
                        <flux:input wire:model="rateAmount" type="number" step="0.01" min="0" label="Amount" />

                        @if ($rateType === 'weight')

                                <flux:input wire:model="minimumWeight" type="number" min="0" label="Min weight (g)" />
                                <flux:input wire:model="maximumWeight" type="number" min="1" label="Max weight (g)" />

                        @endif

                        @if ($rateType === 'price')

                                <flux:input wire:model="minimumOrderAmount" type="number" step="0.01" min="0" label="Min order" />
                                <flux:input wire:model="maximumOrderAmount" type="number" step="0.01" min="0" label="Max order" />

                        @endif

                        <flux:switch wire:model="rateActive" label="{{ $rateActive ? 'Active' : 'Inactive' }}" align="left" />

                            <flux:button type="button" wire:click="$set('rateZoneId', null)" variant="ghost">Cancel</flux:button>
                            <flux:button type="submit" variant="primary">Save rate</flux:button>

                </form>
            @endif

                <flux:heading size="lg">Test address</flux:heading>

                    <flux:input wire:model="testCountry" label="Country" placeholder="DE" />
                    <flux:input wire:model="testRegion" label="Region" placeholder="DE-BE" />

                    <flux:button type="button" wire:click="testShippingAddress" variant="filled">Test</flux:button>

                @if ($testResult)

                        @if ($testResult['zone'])

Matched zone: {{ $testResult['zone'] }}

                                @foreach ($testResult['rates'] as $rate)

{{ $rate }}

                                @endforeach

                        @else

No shipping zone matches this address.

                        @endif

                @endif


Output: {"output":"Success. Updated the following files:\nA /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/settings/shipping.blade.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Delete File: /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/settings/taxes.blade.php
*** End Patch


Success. Updated the following files:
D /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/settings/taxes.blade.php

{{-- Simplicity is the ultimate sophistication. - Leonardo da Vinci --}}


Output: {"output":"Success. Updated the following files:\nD /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/settings/taxes.blade.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Add File: /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/settings/taxes.blade.php
+

+

+

+            <flux:heading size="xl">Taxes</flux:heading>
+            <flux:text class="mt-1">Manual tax rates and provider mode.</flux:text>
+

+
+

+            <flux:button :href="route('admin.settings.index')" wire:navigate variant="filled">General</flux:button>
+            <flux:button :href="route('admin.settings.shipping')" wire:navigate variant="filled">Shipping</flux:button>
+            <flux:button :href="route('admin.settings.taxes')" wire:navigate variant="primary">Taxes</flux:button>
+

+

+
+    @if (session('status'))
+        <flux:callout color="green" icon="check-circle">{{ session('status') }}</flux:callout>
+    @endif
+
+    <form wire:submit="save" class="space-y-6">
+

+            <flux:radio.group wire:model.live="mode" label="Mode" class="grid gap-3 sm:grid-cols-2">
+                <flux:radio value="manual" label="Manual tax rates" description="Define tax rates by country." />
+                <flux:radio value="provider" label="Tax provider" description="Use an automated tax provider." />
+            </flux:radio.group>
+            <flux:error name="mode" />
+

+
+        @if ($mode === 'manual')
+

+

+                    <flux:heading size="lg">Manual rates</flux:heading>
+                    <flux:button type="button" wire:click="addManualRate" variant="filled" icon="plus">Add rate</flux:button>
+

+
+

+                    @foreach ($manualRates as $index => $rate)
+

+                            <flux:input wire:model="manualRates.{{ $index }}.country" label="Country" maxlength="2" />
+                            <flux:input wire:model="manualRates.{{ $index }}.name" label="Name" />
+                            <flux:input wire:model="manualRates.{{ $index }}.rate_percentage" label="Rate %" type="number" step="0.01" min="0" max="100" />
+                            <flux:button type="button" wire:click="removeManualRate({{ $index }})" variant="danger" icon="trash" aria-label="Remove tax rate" />
+

+                    @endforeach
+

+

+        @else
+

+                <flux:heading size="lg">Provider</flux:heading>
+
+

+                    <flux:select wire:model="provider" label="Provider">
+                        <flux:select.option value="none">None</flux:select.option>
+                        <flux:select.option value="stripe_tax">Stripe Tax</flux:select.option>
+                    </flux:select>
+                    <flux:input wire:model="providerApiKey" type="password" label="API key" />
+

+

+        @endif
+
+

+            <flux:switch wire:model="pricesIncludeTax" label="Prices include tax" align="left" />
+

+
+

+            <flux:button type="submit" variant="primary" wire:loading.attr="disabled">
+                Save taxes
+                Saving...
+            </flux:button>
+

+    </form>
+

*** End Patch


Success. Updated the following files:
A /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/settings/taxes.blade.php

            <flux:heading size="xl">Taxes</flux:heading>
            <flux:text class="mt-1">Manual tax rates and provider mode.</flux:text>

            <flux:button :href="route('admin.settings.index')" wire:navigate variant="filled">General</flux:button>
            <flux:button :href="route('admin.settings.shipping')" wire:navigate variant="filled">Shipping</flux:button>
            <flux:button :href="route('admin.settings.taxes')" wire:navigate variant="primary">Taxes</flux:button>

    @if (session('status'))
        <flux:callout color="green" icon="check-circle">{{ session('status') }}</flux:callout>
    @endif

    <form wire:submit="save" class="space-y-6">

            <flux:radio.group wire:model.live="mode" label="Mode" class="grid gap-3 sm:grid-cols-2">
                <flux:radio value="manual" label="Manual tax rates" description="Define tax rates by country." />
                <flux:radio value="provider" label="Tax provider" description="Use an automated tax provider." />
            </flux:radio.group>
            <flux:error name="mode" />

        @if ($mode === 'manual')

                    <flux:heading size="lg">Manual rates</flux:heading>
                    <flux:button type="button" wire:click="addManualRate" variant="filled" icon="plus">Add rate</flux:button>

                    @foreach ($manualRates as $index => $rate)

                            <flux:input wire:model="manualRates.{{ $index }}.country" label="Country" maxlength="2" />
                            <flux:input wire:model="manualRates.{{ $index }}.name" label="Name" />
                            <flux:input wire:model="manualRates.{{ $index }}.rate_percentage" label="Rate %" type="number" step="0.01" min="0" max="100" />
                            <flux:button type="button" wire:click="removeManualRate({{ $index }})" variant="danger" icon="trash" aria-label="Remove tax rate" />

                    @endforeach

        @else

                <flux:heading size="lg">Provider</flux:heading>

                    <flux:select wire:model="provider" label="Provider">
                        <flux:select.option value="none">None</flux:select.option>
                        <flux:select.option value="stripe_tax">Stripe Tax</flux:select.option>
                    </flux:select>
                    <flux:input wire:model="providerApiKey" type="password" label="API key" />

        @endif

            <flux:switch wire:model="pricesIncludeTax" label="Prices include tax" align="left" />

            <flux:button type="submit" variant="primary" wire:loading.attr="disabled">
                Save taxes
                Saving...
            </flux:button>

    </form>


Output: {"output":"Success. Updated the following files:\nA /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/settings/taxes.blade.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Delete File: /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/pages/index.blade.php
*** End Patch


Success. Updated the following files:
D /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/pages/index.blade.php

{{-- Smile, breathe, and go slowly. - Thich Nhat Hanh --}}


Output: {"output":"Success. Updated the following files:\nD /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/pages/index.blade.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Add File: /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/pages/index.blade.php
+

+

+

+            <flux:heading size="xl">Pages</flux:heading>
+            <flux:text class="mt-1">Static storefront content pages.</flux:text>
+

+
+        @can('create', App\Models\Page::class)
+            <flux:button :href="route('admin.pages.create')" wire:navigate variant="primary" icon="plus">
+                Add page
+            </flux:button>
+        @endcan
+

+
+    <flux:input wire:model.live.debounce.300ms="search" icon="magnifying-glass" placeholder="Search pages..." aria-label="Search pages" />
+
+

+

+

+

+

+                        Title
+                        Handle
+                        Status
+                        Updated
+

+

+

+                    @forelse ($pages as $page)
+
getKey() }}">
+
+
+                                    {{ $page->title }}
+
+
+                            /{{ $page->handle }}
+
+                                <flux:badge :color="$this->statusColor($page->status)">{{ Str::headline($page->status->value) }}</flux:badge>
+
+                            {{ $page->updated_at?->format('M j, Y') }}
+

+                    @empty
+

+                            No pages found.
+

+                    @endforelse
+

+

+

+

+
+    {{ $pages->links() }}
+

*** End Patch


Success. Updated the following files:
A /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/pages/index.blade.php

            <flux:heading size="xl">Pages</flux:heading>
            <flux:text class="mt-1">Static storefront content pages.</flux:text>

        @can('create', App\Models\Page::class)
            <flux:button :href="route('admin.pages.create')" wire:navigate variant="primary" icon="plus">
                Add page
            </flux:button>
        @endcan

    <flux:input wire:model.live.debounce.300ms="search" icon="magnifying-glass" placeholder="Search pages..." aria-label="Search pages" />

                        Title
                        Handle
                        Status
                        Updated

                    @forelse ($pages as $page)

getKey() }}">

                                    {{ $page->title }}

                            /{{ $page->handle }}

                                <flux:badge :color="$this->statusColor($page->status)">{{ Str::headline($page->status->value) }}</flux:badge>

                            {{ $page->updated_at?->format('M j, Y') }}

                    @empty

                            No pages found.

                    @endforelse

    {{ $pages->links() }}


Output: {"output":"Success. Updated the following files:\nA /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/pages/index.blade.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Delete File: /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/pages/form.blade.php
*** End Patch


Success. Updated the following files:
D /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/pages/form.blade.php

{{-- Order your soul. Reduce your wants. - Augustine --}}


Output: {"output":"Success. Updated the following files:\nD /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/pages/form.blade.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Add File: /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/pages/form.blade.php
+

+

+

+            <flux:heading size="xl">{{ $isEditing ? 'Edit page' : 'Create page' }}</flux:heading>
+            <flux:text class="mt-1">Title, handle, publication state, and HTML body.</flux:text>
+

+
+

+            @if ($isEditing)
+                <flux:button type="button" wire:click="deletePage" wire:confirm="Delete this page?" variant="danger" icon="trash">Delete</flux:button>
+            @endif
+            <flux:button :href="route('admin.pages.index')" wire:navigate variant="filled" icon="arrow-left">Pages</flux:button>
+

+

+
+    @if (session('status'))
+        <flux:callout color="green" icon="check-circle">{{ session('status') }}</flux:callout>
+    @endif
+
+    <form wire:submit="save" class="grid gap-6 lg:grid-cols-[minmax(0,1fr)_320px]">
+

+

+

+                    <flux:input wire:model.live.debounce.300ms="title" label="Title" />
+                    <flux:error name="title" />
+
+                    <flux:input wire:model="handle" label="Handle" />
+                    <flux:error name="handle" />
+
+                    <flux:textarea wire:model="bodyHtml" label="Body HTML" rows="16" />
+                    <flux:error name="bodyHtml" />
+

+

+

+
+

+

+                <flux:heading size="lg">Publishing</flux:heading>
+
+

+                    <flux:select wire:model="status" label="Status">
+                        <flux:select.option value="draft">Draft</flux:select.option>
+                        <flux:select.option value="published">Published</flux:select.option>
+                        <flux:select.option value="archived">Archived</flux:select.option>
+                    </flux:select>
+                    <flux:error name="status" />
+
+                    <flux:input wire:model="publishedAt" type="datetime-local" label="Published at" />
+                    <flux:error name="publishedAt" />
+

+

+

+
+

+

+                <flux:button :href="route('admin.pages.index')" wire:navigate variant="ghost">Discard</flux:button>
+                <flux:button type="submit" variant="primary" wire:loading.attr="disabled">
+                    Save
+                    Saving...
+                </flux:button>
+

+

+    </form>
+

*** End Patch


Success. Updated the following files:
A /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/pages/form.blade.php

            <flux:heading size="xl">{{ $isEditing ? 'Edit page' : 'Create page' }}</flux:heading>
            <flux:text class="mt-1">Title, handle, publication state, and HTML body.</flux:text>

            @if ($isEditing)
                <flux:button type="button" wire:click="deletePage" wire:confirm="Delete this page?" variant="danger" icon="trash">Delete</flux:button>
            @endif
            <flux:button :href="route('admin.pages.index')" wire:navigate variant="filled" icon="arrow-left">Pages</flux:button>

    @if (session('status'))
        <flux:callout color="green" icon="check-circle">{{ session('status') }}</flux:callout>
    @endif

    <form wire:submit="save" class="grid gap-6 lg:grid-cols-[minmax(0,1fr)_320px]">

                    <flux:input wire:model.live.debounce.300ms="title" label="Title" />
                    <flux:error name="title" />

                    <flux:input wire:model="handle" label="Handle" />
                    <flux:error name="handle" />

                    <flux:textarea wire:model="bodyHtml" label="Body HTML" rows="16" />
                    <flux:error name="bodyHtml" />

                <flux:heading size="lg">Publishing</flux:heading>

                    <flux:select wire:model="status" label="Status">
                        <flux:select.option value="draft">Draft</flux:select.option>
                        <flux:select.option value="published">Published</flux:select.option>
                        <flux:select.option value="archived">Archived</flux:select.option>
                    </flux:select>
                    <flux:error name="status" />

                    <flux:input wire:model="publishedAt" type="datetime-local" label="Published at" />
                    <flux:error name="publishedAt" />

                <flux:button :href="route('admin.pages.index')" wire:navigate variant="ghost">Discard</flux:button>
                <flux:button type="submit" variant="primary" wire:loading.attr="disabled">
                    Save
                    Saving...
                </flux:button>

    </form>


Output: {"output":"Success. Updated the following files:\nA /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/pages/form.blade.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Delete File: /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/navigation/index.blade.php
*** End Patch


Success. Updated the following files:
D /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/navigation/index.blade.php

{{-- Be present above all else. - Naval Ravikant --}}


Output: {"output":"Success. Updated the following files:\nD /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/navigation/index.blade.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Add File: /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/navigation/index.blade.php
+

+

+        <flux:heading size="xl">Navigation</flux:heading>
+        <flux:text class="mt-1">Storefront menus and ordered links.</flux:text>
+

+
+    @if (session('status'))
+        <flux:callout color="green" icon="check-circle">{{ session('status') }}</flux:callout>
+    @endif
+
+

+

+            @foreach ($menus as $menu)
+                <button
+                    type="button"
+                    wire:click="selectMenu({{ $menu->getKey() }})"
+                    wire:key="navigation-menu-{{ $menu->getKey() }}"
+                    class="block w-full rounded-lg border px-4 py-3 text-left text-sm {{ $selectedMenuId === $menu->getKey() ? 'border-blue-500 bg-blue-50 text-blue-950 dark:border-blue-400 dark:bg-blue-950/40 dark:text-blue-100' : 'border-zinc-200 bg-white text-zinc-700 hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-200 dark:hover:bg-zinc-800' }}"
+                >
+                    {{ $menu->title }}
+                    {{ $menu->handle }}
+                </button>
+            @endforeach
+

+
+

+

+

+                    <flux:heading size="lg">{{ $selectedMenu->title }}</flux:heading>
+                    <flux:text class="mt-1">{{ count($menuItems) }} items</flux:text>
+

+
+                <flux:button type="button" wire:click="addItem" variant="filled" icon="plus">Add item</flux:button>
+

+
+

+                @forelse ($menuItems as $index => $item)
+

+

+

+                                <flux:button type="button" wire:click="moveItemUp({{ $index }})" size="sm" variant="ghost" icon="arrow-up" aria-label="Move {{ $item['label'] }} up" />
+                                <flux:button type="button" wire:click="moveItemDown({{ $index }})" size="sm" variant="ghost" icon="arrow-down" aria-label="Move {{ $item['label'] }} down" />
+

+
+

+
{{ $item['label'] }}

+
{{ $this->targetLabel($item) }}

+

+

+
+

+                            <flux:button type="button" wire:click="editItem({{ $index }})" size="sm" variant="filled">Edit</flux:button>
+                            <flux:button type="button" wire:click="removeItem({{ $index }})" size="sm" variant="danger">Remove</flux:button>
+

+

+                @empty
+
No menu items.

+                @endforelse
+

+
+

+                <flux:button type="button" wire:click="saveMenu" variant="primary">Save menu</flux:button>
+

+

+
+        <form wire:submit="saveItem" class="rounded-lg border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-900">
+            <flux:heading size="lg">{{ $editingItemIndex === null ? 'Add item' : 'Edit item' }}</flux:heading>
+
+

+                <flux:input wire:model="itemLabel" label="Label" />
+                <flux:error name="itemLabel" />
+
+                <flux:select wire:model.live="itemType" label="Type">
+                    <flux:select.option value="link">Custom link</flux:select.option>
+                    <flux:select.option value="page">Page</flux:select.option>
+                    <flux:select.option value="collection">Collection</flux:select.option>
+                    <flux:select.option value="product">Product</flux:select.option>
+                </flux:select>
+
+                @if ($itemType === 'link')
+                    <flux:input wire:model="itemUrl" label="URL" placeholder="/collections" />
+                    <flux:error name="itemUrl" />
+                @else
+                    <flux:select wire:model="itemResourceId" label="Resource">
+                        <flux:select.option value="">Select resource</flux:select.option>
+                        @foreach ($resources as $resource)
+                            <flux:select.option value="{{ $resource->getKey() }}">{{ $resource->title }}</flux:select.option>
+                        @endforeach
+                    </flux:select>
+                    <flux:error name="itemResourceId" />
+                @endif
+
+

+                    <flux:button type="button" wire:click="cancelItem" variant="ghost">Cancel</flux:button>
+                    <flux:button type="submit" variant="primary">{{ $editingItemIndex === null ? 'Add item' : 'Update item' }}</flux:button>
+

+

+        </form>
+

+

*** End Patch


Success. Updated the following files:
A /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/navigation/index.blade.php

        <flux:heading size="xl">Navigation</flux:heading>
        <flux:text class="mt-1">Storefront menus and ordered links.</flux:text>

    @if (session('status'))
        <flux:callout color="green" icon="check-circle">{{ session('status') }}</flux:callout>
    @endif

            @foreach ($menus as $menu)
                <button
                    type="button"
                    wire:click="selectMenu({{ $menu->getKey() }})"
                    wire:key="navigation-menu-{{ $menu->getKey() }}"
                    class="block w-full rounded-lg border px-4 py-3 text-left text-sm {{ $selectedMenuId === $menu->getKey() ? 'border-blue-500 bg-blue-50 text-blue-950 dark:border-blue-400 dark:bg-blue-950/40 dark:text-blue-100' : 'border-zinc-200 bg-white text-zinc-700 hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-200 dark:hover:bg-zinc-800' }}"
                >
                    {{ $menu->title }}
                    {{ $menu->handle }}
                </button>
            @endforeach

                    <flux:heading size="lg">{{ $selectedMenu->title }}</flux:heading>
                    <flux:text class="mt-1">{{ count($menuItems) }} items</flux:text>

                <flux:button type="button" wire:click="addItem" variant="filled" icon="plus">Add item</flux:button>

                @forelse ($menuItems as $index => $item)

                                <flux:button type="button" wire:click="moveItemUp({{ $index }})" size="sm" variant="ghost" icon="arrow-up" aria-label="Move {{ $item['label'] }} up" />
                                <flux:button type="button" wire:click="moveItemDown({{ $index }})" size="sm" variant="ghost" icon="arrow-down" aria-label="Move {{ $item['label'] }} down" />

{{ $item['label'] }}

{{ $this->targetLabel($item) }}

                            <flux:button type="button" wire:click="editItem({{ $index }})" size="sm" variant="filled">Edit</flux:button>
                            <flux:button type="button" wire:click="removeItem({{ $index }})" size="sm" variant="danger">Remove</flux:button>

                @empty

No menu items.

                @endforelse

                <flux:button type="button" wire:click="saveMenu" variant="primary">Save menu</flux:button>

        <form wire:submit="saveItem" class="rounded-lg border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-900">
            <flux:heading size="lg">{{ $editingItemIndex === null ? 'Add item' : 'Edit item' }}</flux:heading>

                <flux:input wire:model="itemLabel" label="Label" />
                <flux:error name="itemLabel" />

                <flux:select wire:model.live="itemType" label="Type">
                    <flux:select.option value="link">Custom link</flux:select.option>
                    <flux:select.option value="page">Page</flux:select.option>
                    <flux:select.option value="collection">Collection</flux:select.option>
                    <flux:select.option value="product">Product</flux:select.option>
                </flux:select>

                @if ($itemType === 'link')
                    <flux:input wire:model="itemUrl" label="URL" placeholder="/collections" />
                    <flux:error name="itemUrl" />
                @else
                    <flux:select wire:model="itemResourceId" label="Resource">
                        <flux:select.option value="">Select resource</flux:select.option>
                        @foreach ($resources as $resource)
                            <flux:select.option value="{{ $resource->getKey() }}">{{ $resource->title }}</flux:select.option>
                        @endforeach
                    </flux:select>
                    <flux:error name="itemResourceId" />
                @endif

                    <flux:button type="button" wire:click="cancelItem" variant="ghost">Cancel</flux:button>
                    <flux:button type="submit" variant="primary">{{ $editingItemIndex === null ? 'Add item' : 'Update item' }}</flux:button>

        </form>


Output: {"output":"Success. Updated the following files:\nA /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/navigation/index.blade.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Delete File: /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/themes/index.blade.php
*** End Patch


Success. Updated the following files:
D /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/themes/index.blade.php


Output: {"output":"Success. Updated the following files:\nD /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/themes/index.blade.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Add File: /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/themes/index.blade.php
+

+

+        <flux:heading size="xl">Themes</flux:heading>
+        <flux:text class="mt-1">Published and draft storefront themes.</flux:text>
+

+
+    @if (session('status'))
+        <flux:callout color="green" icon="check-circle">{{ session('status') }}</flux:callout>
+    @endif
+
+    <flux:error name="theme" />
+
+

+        @foreach ($themes as $theme)
+
getKey() }}" class="overflow-hidden rounded-lg border bg-white dark:bg-zinc-900 {{ $theme->isPublished() ? 'border-blue-500 ring-2 ring-blue-100 dark:border-blue-400 dark:ring-blue-950' : 'border-zinc-200 dark:border-zinc-700' }}">
+

+

+                        <flux:icon name="paint-brush" class="size-10" />
+

+

+
+

+

+

+                            <flux:heading size="lg">{{ $theme->name }}</flux:heading>
+                            <flux:text class="mt-1">v{{ $theme->version }} · {{ $theme->files_count }} files</flux:text>
+

+                        <flux:badge :color="$this->statusColor($theme)">{{ Str::headline($theme->status->value) }}</flux:badge>
+

+
+

+                        <flux:button :href="route('admin.themes.editor', $theme)" wire:navigate variant="primary">Customize</flux:button>
+                        @unless ($theme->isPublished())
+                            <flux:button type="button" wire:click="publishTheme({{ $theme->getKey() }})" variant="filled">Publish</flux:button>
+                        @endunless
+                        <flux:button type="button" wire:click="duplicateTheme({{ $theme->getKey() }})" variant="filled">Duplicate</flux:button>
+                        @unless ($theme->isPublished())
+                            <flux:button type="button" wire:click="deleteTheme({{ $theme->getKey() }})" wire:confirm="Delete this theme?" variant="danger">Delete</flux:button>
+                        @endunless
+

+

+

+        @endforeach
+

+

*** End Patch


Success. Updated the following files:
A /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/themes/index.blade.php

        <flux:heading size="xl">Themes</flux:heading>
        <flux:text class="mt-1">Published and draft storefront themes.</flux:text>

    @if (session('status'))
        <flux:callout color="green" icon="check-circle">{{ session('status') }}</flux:callout>
    @endif

    <flux:error name="theme" />

        @foreach ($themes as $theme)

getKey() }}" class="overflow-hidden rounded-lg border bg-white dark:bg-zinc-900 {{ $theme->isPublished() ? 'border-blue-500 ring-2 ring-blue-100 dark:border-blue-400 dark:ring-blue-950' : 'border-zinc-200 dark:border-zinc-700' }}">

                        <flux:icon name="paint-brush" class="size-10" />

                            <flux:heading size="lg">{{ $theme->name }}</flux:heading>
                            <flux:text class="mt-1">v{{ $theme->version }} · {{ $theme->files_count }} files</flux:text>

                        <flux:badge :color="$this->statusColor($theme)">{{ Str::headline($theme->status->value) }}</flux:badge>

                        <flux:button :href="route('admin.themes.editor', $theme)" wire:navigate variant="primary">Customize</flux:button>
                        @unless ($theme->isPublished())
                            <flux:button type="button" wire:click="publishTheme({{ $theme->getKey() }})" variant="filled">Publish</flux:button>
                        @endunless
                        <flux:button type="button" wire:click="duplicateTheme({{ $theme->getKey() }})" variant="filled">Duplicate</flux:button>
                        @unless ($theme->isPublished())
                            <flux:button type="button" wire:click="deleteTheme({{ $theme->getKey() }})" wire:confirm="Delete this theme?" variant="danger">Delete</flux:button>
                        @endunless

        @endforeach


Output: {"output":"Success. Updated the following files:\nA /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/themes/index.blade.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Delete File: /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/themes/editor.blade.php
*** End Patch


Success. Updated the following files:
D /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/themes/editor.blade.php

{{-- Breathing in, I calm body and mind. Breathing out, I smile. - Thich Nhat Hanh --}}


Output: {"output":"Success. Updated the following files:\nD /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/themes/editor.blade.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Add File: /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/themes/editor.blade.php
+

+

+        <flux:button :href="route('admin.themes.index')" wire:navigate variant="filled" icon="arrow-left">Themes</flux:button>
+
+

+            <flux:button type="button" wire:click="save" variant="filled">Save</flux:button>
+            <flux:button type="button" wire:click="publish" variant="primary">Save and publish</flux:button>
+

+

+
+    @if (session('status'))
+        <flux:callout color="green" icon="check-circle">{{ session('status') }}</flux:callout>
+    @endif
+
+

+

+            <flux:heading size="lg">Sections</flux:heading>
+
+

+                @foreach ($sections as $key => $section)
+                    <button
+                        type="button"
+                        wire:click="selectSection('{{ $key }}')"
+                        class="block w-full rounded-lg px-3 py-2 text-left text-sm {{ $selectedSection === $key ? 'bg-blue-50 font-medium text-blue-950 dark:bg-blue-950/40 dark:text-blue-100' : 'text-zinc-700 hover:bg-zinc-50 dark:text-zinc-200 dark:hover:bg-zinc-800' }}"
+                    >
+                        {{ $section['label'] }}
+                    </button>
+                @endforeach
+

+

+
+

+

+

+                    <flux:heading size="lg">Preview</flux:heading>
+                    <flux:text>{{ $theme->name }}</flux:text>
+

+                <flux:button type="button" wire:click="refreshPreview" variant="filled" icon="arrow-path">Refresh</flux:button>
+

+
+
+

+
+        <form wire:submit="save" class="rounded-lg border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-900">
+            <flux:heading size="lg">{{ $activeSection['label'] }}</flux:heading>
+
+

+                @foreach ($activeSection['fields'] as $field)
+                    @php($model = 'settings.'.str_replace('.', '.', $field['key']))
+
+                    @if ($field['type'] === 'checkbox')
+                        <flux:checkbox wire:model="{{ $model }}" label="{{ $field['label'] }}" />
+                    @elseif ($field['type'] === 'textarea')
+                        <flux:textarea wire:model.live.debounce.500ms="{{ $model }}" label="{{ $field['label'] }}" rows="3" />
+                    @elseif ($field['type'] === 'number')
+                        <flux:input wire:model.live.debounce.500ms="{{ $model }}" type="number" min="0" label="{{ $field['label'] }}" />
+                    @else
+                        <flux:input wire:model.live.debounce.500ms="{{ $model }}" label="{{ $field['label'] }}" />
+                    @endif
+                @endforeach
+

+        </form>
+

+

*** End Patch


Success. Updated the following files:
A /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/themes/editor.blade.php

        <flux:button :href="route('admin.themes.index')" wire:navigate variant="filled" icon="arrow-left">Themes</flux:button>

            <flux:button type="button" wire:click="save" variant="filled">Save</flux:button>
            <flux:button type="button" wire:click="publish" variant="primary">Save and publish</flux:button>

    @if (session('status'))
        <flux:callout color="green" icon="check-circle">{{ session('status') }}</flux:callout>
    @endif

            <flux:heading size="lg">Sections</flux:heading>

                @foreach ($sections as $key => $section)
                    <button
                        type="button"
                        wire:click="selectSection('{{ $key }}')"
                        class="block w-full rounded-lg px-3 py-2 text-left text-sm {{ $selectedSection === $key ? 'bg-blue-50 font-medium text-blue-950 dark:bg-blue-950/40 dark:text-blue-100' : 'text-zinc-700 hover:bg-zinc-50 dark:text-zinc-200 dark:hover:bg-zinc-800' }}"
                    >
                        {{ $section['label'] }}
                    </button>
                @endforeach

                    <flux:heading size="lg">Preview</flux:heading>
                    <flux:text>{{ $theme->name }}</flux:text>

                <flux:button type="button" wire:click="refreshPreview" variant="filled" icon="arrow-path">Refresh</flux:button>

        <form wire:submit="save" class="rounded-lg border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-900">
            <flux:heading size="lg">{{ $activeSection['label'] }}</flux:heading>

                @foreach ($activeSection['fields'] as $field)
                    @php($model = 'settings.'.str_replace('.', '.', $field['key']))

                    @if ($field['type'] === 'checkbox')
                        <flux:checkbox wire:model="{{ $model }}" label="{{ $field['label'] }}" />
                    @elseif ($field['type'] === 'textarea')
                        <flux:textarea wire:model.live.debounce.500ms="{{ $model }}" label="{{ $field['label'] }}" rows="3" />
                    @elseif ($field['type'] === 'number')
                        <flux:input wire:model.live.debounce.500ms="{{ $model }}" type="number" min="0" label="{{ $field['label'] }}" />
                    @else
                        <flux:input wire:model.live.debounce.500ms="{{ $model }}" label="{{ $field['label'] }}" />
                    @endif
                @endforeach

        </form>


Output: {"output":"Success. Updated the following files:\nA /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/themes/editor.blade.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/routes/web.php
@@
 use App\Livewire\Admin\Orders\Show as AdminOrderShow;
+use App\Livewire\Admin\Navigation\Index as AdminNavigationIndex;
+use App\Livewire\Admin\Pages\Form as AdminPageForm;
+use App\Livewire\Admin\Pages\Index as AdminPagesIndex;
 use App\Livewire\Admin\Products\Form as AdminProductForm;
 use App\Livewire\Admin\Products\Index as AdminProductsIndex;
+use App\Livewire\Admin\Settings\Index as AdminSettingsIndex;
+use App\Livewire\Admin\Settings\Shipping as AdminSettingsShipping;
+use App\Livewire\Admin\Settings\Taxes as AdminSettingsTaxes;
+use App\Livewire\Admin\Themes\Editor as AdminThemeEditor;
+use App\Livewire\Admin\Themes\Index as AdminThemesIndex;
@@
     Route::livewire('customers/{customer}', AdminCustomerShow::class)->name('customers.show');
     Route::livewire('discounts', AdminDiscountsIndex::class)->name('discounts.index');
     Route::livewire('discounts/create', AdminDiscountForm::class)->name('discounts.create');
     Route::livewire('discounts/{discount}/edit', AdminDiscountForm::class)->name('discounts.edit');
+    Route::livewire('pages', AdminPagesIndex::class)->name('pages.index');
+    Route::livewire('pages/create', AdminPageForm::class)->name('pages.create');
+    Route::livewire('pages/{page}/edit', AdminPageForm::class)->name('pages.edit');
+    Route::livewire('navigation', AdminNavigationIndex::class)->name('navigation.index');
+    Route::livewire('themes', AdminThemesIndex::class)->name('themes.index');
+    Route::livewire('themes/{theme}/editor', AdminThemeEditor::class)->name('themes.editor');
+    Route::livewire('settings', AdminSettingsIndex::class)->name('settings.index');
+    Route::livewire('settings/shipping', AdminSettingsShipping::class)->name('settings.shipping');
+    Route::livewire('settings/taxes', AdminSettingsTaxes::class)->name('settings.taxes');
     Route::livewire('collections', AdminCollectionsIndex::class)->name('collections.index');
*** End Patch


Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/routes/web.php

@@ -14,4 +14,12 @@
 use App\Livewire\Admin\Orders\Show as AdminOrderShow;
+use App\Livewire\Admin\Navigation\Index as AdminNavigationIndex;
+use App\Livewire\Admin\Pages\Form as AdminPageForm;
+use App\Livewire\Admin\Pages\Index as AdminPagesIndex;
 use App\Livewire\Admin\Products\Form as AdminProductForm;
 use App\Livewire\Admin\Products\Index as AdminProductsIndex;
+use App\Livewire\Admin\Settings\Index as AdminSettingsIndex;
+use App\Livewire\Admin\Settings\Shipping as AdminSettingsShipping;
+use App\Livewire\Admin\Settings\Taxes as AdminSettingsTaxes;
+use App\Livewire\Admin\Themes\Editor as AdminThemeEditor;
+use App\Livewire\Admin\Themes\Index as AdminThemesIndex;
 use App\Livewire\Storefront\Account\Auth\Login as CustomerLogin;
@@ -70,2 +78,11 @@
     Route::livewire('discounts/{discount}/edit', AdminDiscountForm::class)->name('discounts.edit');
+    Route::livewire('pages', AdminPagesIndex::class)->name('pages.index');
+    Route::livewire('pages/create', AdminPageForm::class)->name('pages.create');
+    Route::livewire('pages/{page}/edit', AdminPageForm::class)->name('pages.edit');
+    Route::livewire('navigation', AdminNavigationIndex::class)->name('navigation.index');
+    Route::livewire('themes', AdminThemesIndex::class)->name('themes.index');
+    Route::livewire('themes/{theme}/editor', AdminThemeEditor::class)->name('themes.editor');
+    Route::livewire('settings', AdminSettingsIndex::class)->name('settings.index');
+    Route::livewire('settings/shipping', AdminSettingsShipping::class)->name('settings.shipping');
+    Route::livewire('settings/taxes', AdminSettingsTaxes::class)->name('settings.taxes');
     Route::livewire('collections', AdminCollectionsIndex::class)->name('collections.index');


Output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/routes/web.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/resources/views/layouts/app/sidebar.blade.php
@@
                 <flux:sidebar.group :heading="__('Operations')" class="grid">
                     <flux:sidebar.item icon="shopping-bag" :href="route('admin.orders.index')" :current="request()->routeIs('admin.orders.*')" wire:navigate>
                         {{ __('Orders') }}
                     </flux:sidebar.item>
@@
                         {{ __('Discounts') }}
                     </flux:sidebar.item>
                 </flux:sidebar.group>
+
+                <flux:sidebar.group :heading="__('Storefront')" class="grid">
+                    <flux:sidebar.item icon="document-text" :href="route('admin.pages.index')" :current="request()->routeIs('admin.pages.*')" wire:navigate>
+                        {{ __('Pages') }}
+                    </flux:sidebar.item>
+
+                    <flux:sidebar.item icon="bars-3" :href="route('admin.navigation.index')" :current="request()->routeIs('admin.navigation.*')" wire:navigate>
+                        {{ __('Navigation') }}
+                    </flux:sidebar.item>
+
+                    <flux:sidebar.item icon="paint-brush" :href="route('admin.themes.index')" :current="request()->routeIs('admin.themes.*')" wire:navigate>
+                        {{ __('Themes') }}
+                    </flux:sidebar.item>
+                </flux:sidebar.group>
+
+                <flux:sidebar.group :heading="__('Configuration')" class="grid">
+                    <flux:sidebar.item icon="cog-6-tooth" :href="route('admin.settings.index')" :current="request()->routeIs('admin.settings.*')" wire:navigate>
+                        {{ __('Settings') }}
+                    </flux:sidebar.item>
+                </flux:sidebar.group>
             </flux:sidebar.nav>
*** End Patch


Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/resources/views/layouts/app/sidebar.blade.php

@@ -46,2 +46,22 @@
                 </flux:sidebar.group>
+
+                <flux:sidebar.group :heading="__('Storefront')" class="grid">
+                    <flux:sidebar.item icon="document-text" :href="route('admin.pages.index')" :current="request()->routeIs('admin.pages.*')" wire:navigate>
+                        {{ __('Pages') }}
+                    </flux:sidebar.item>
+
+                    <flux:sidebar.item icon="bars-3" :href="route('admin.navigation.index')" :current="request()->routeIs('admin.navigation.*')" wire:navigate>
+                        {{ __('Navigation') }}
+                    </flux:sidebar.item>
+
+                    <flux:sidebar.item icon="paint-brush" :href="route('admin.themes.index')" :current="request()->routeIs('admin.themes.*')" wire:navigate>
+                        {{ __('Themes') }}
+                    </flux:sidebar.item>
+                </flux:sidebar.group>
+
+                <flux:sidebar.group :heading="__('Configuration')" class="grid">
+                    <flux:sidebar.item icon="cog-6-tooth" :href="route('admin.settings.index')" :current="request()->routeIs('admin.settings.*')" wire:navigate>
+                        {{ __('Settings') }}
+                    </flux:sidebar.item>
+                </flux:sidebar.group>
             </flux:sidebar.nav>


Output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/resources/views/layouts/app/sidebar.blade.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


sed -n '1,220p' app/Http/Middleware/ResolveStore.php app/Http/Middleware/CheckStoreRole.php


sed -n '1,220p' app/Http/Middleware/ResolveStore.php app/Http/Middleware/CheckStoreRole.php

<?php

namespace App\Http\Middleware;

use App\Enums\StoreStatus;
use App\Models\Store;
use App\Models\StoreDomain;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpFoundation\Response;

class ResolveStore
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        $store = $request->is('admin*')
            ? $this->resolveAdminStore($request)
            : $this->resolveStorefrontStore($request);

        abort_unless($store, 404);

        app()->instance('current_store', $store);

        if (! $request->is('admin*') && $store->status === StoreStatus::Suspended) {
            abort(503);
        }

        if ($request->is('admin*') && $store->status === StoreStatus::Suspended && ! $request->isMethodSafe()) {
            abort(403);
        }

        return $next($request);
    }

    private function resolveStorefrontStore(Request $request): ?Store
    {
        $hostname = $request->getHost();
        $cacheKey = "store_domain:{$hostname}";

        $storeId = Cache::remember($cacheKey, now()->addMinutes(5), function () use ($hostname): ?int {
            return StoreDomain::query()
                ->where('hostname', $hostname)
                ->value('store_id');
        });

        return $storeId ? Store::query()->find($storeId) : null;
    }

    private function resolveAdminStore(Request $request): ?Store
    {
        $user = $request->user();

        if (! $user) {
            return null;
        }

        $storeId = $request->session()->get('current_store_id');

        if (! $storeId) {
            $storeId = $user->stores()->oldest('stores.id')->value('stores.id');

            if ($storeId) {
                $request->session()->put('current_store_id', $storeId);
            }
        }

        if (! $storeId || ! $user->stores()->whereKey($storeId)->exists()) {
            return null;
        }

        return Store::query()->find($storeId);
    }
}
<?php

namespace App\Http\Middleware;

use App\Enums\StoreUserRole;
use App\Models\Store;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class CheckStoreRole
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next, string ...$roles): Response
    {
        $store = app()->bound('current_store') ? app('current_store') : null;
        $user = $request->user();

        abort_unless($store instanceof Store && $user, 403);

        $role = $user->roleForStore($store);

        if ($roles === []) {
            abort_unless($role !== null, 403);

            return $next($request);
        }

        $allowedRoles = array_filter(array_map(
            fn (string $role): ?StoreUserRole => StoreUserRole::tryFrom($role),
            $roles,
        ));

        abort_unless($role !== null && in_array($role, $allowedRoles, true), 403);

        return $next($request);
    }
}


Output:
Chunk ID: 70c6a0
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 810
Output:
<?php

namespace App\Http\Middleware;

use App\Enums\StoreStatus;
use App\Models\Store;
use App\Models\StoreDomain;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpFoundation\Response;

class ResolveStore
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        $store = $request->is('admin*')
            ? $this->resolveAdminStore($request)
            : $this->resolveStorefrontStore($request);

        abort_unless($store, 404);

        app()->instance('current_store', $store);

        if (! $request->is('admin*') && $store->status === StoreStatus::Suspended) {
            abort(503);
        }

        if ($request->is('admin*') && $store->status === StoreStatus::Suspended && ! $request->isMethodSafe()) {
            abort(403);
        }

        return $next($request);
    }

    private function resolveStorefrontStore(Request $request): ?Store
    {
        $hostname = $request->getHost();
        $cacheKey = "store_domain:{$hostname}";

        $storeId = Cache::remember($cacheKey, now()->addMinutes(5), function () use ($hostname): ?int {
            return StoreDomain::query()
                ->where('hostname', $hostname)
                ->value('store_id');
        });

        return $storeId ? Store::query()->find($storeId) : null;
    }

    private function resolveAdminStore(Request $request): ?Store
    {
        $user = $request->user();

        if (! $user) {
            return null;
        }

        $storeId = $request->session()->get('current_store_id');

        if (! $storeId) {
            $storeId = $user->stores()->oldest('stores.id')->value('stores.id');

            if ($storeId) {
                $request->session()->put('current_store_id', $storeId);
            }
        }

        if (! $storeId || ! $user->stores()->whereKey($storeId)->exists()) {
            return null;
        }

        return Store::query()->find($storeId);
    }
}
<?php

namespace App\Http\Middleware;

use App\Enums\StoreUserRole;
use App\Models\Store;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class CheckStoreRole
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next, string ...$roles): Response
    {
        $store = app()->bound('current_store') ? app('current_store') : null;
        $user = $request->user();

        abort_unless($store instanceof Store && $user, 403);

        $role = $user->roleForStore($store);

        if ($roles === []) {
            abort_unless($role !== null, 403);

            return $next($request);
        }

        $allowedRoles = array_filter(array_map(
            fn (string $role): ?StoreUserRole => StoreUserRole::tryFrom($role),
            $roles,
        ));

        abort_unless($role !== null && in_array($role, $allowedRoles, true), 403);

        return $next($request);
    }
}


The route/sidebar surface is wired. I’m adding Pest coverage across content, themes, navigation, and settings before running anything substantial, so failures point at the new behavior instead of only smoke rendering.


Input:
*** Begin Patch
*** Delete File: /Users/fabianwesner/Herd/shop/tests/Feature/Admin/ContentManagementTest.php
*** End Patch


Success. Updated the following files:
D /Users/fabianwesner/Herd/shop/tests/Feature/Admin/ContentManagementTest.php

<?php

test('example', function () {
    $response = $this->get('/');

    $response->assertStatus(200);
});


Output: {"output":"Success. Updated the following files:\nD /Users/fabianwesner/Herd/shop/tests/Feature/Admin/ContentManagementTest.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Add File: /Users/fabianwesner/Herd/shop/tests/Feature/Admin/ContentManagementTest.php
+<?php
+
+use App\Enums\NavigationItemType;
+use App\Enums\PageStatus;
+use App\Enums\StoreUserRole;
+use App\Enums\ThemeStatus;
+use App\Livewire\Admin\Navigation\Index as AdminNavigationIndex;
+use App\Livewire\Admin\Pages\Form as AdminPageForm;
+use App\Livewire\Admin\Pages\Index as AdminPagesIndex;
+use App\Livewire\Admin\Themes\Editor as AdminThemeEditor;
+use App\Livewire\Admin\Themes\Index as AdminThemesIndex;
+use App\Models\NavigationItem;
+use App\Models\NavigationMenu;
+use App\Models\Page;
+use App\Models\Store;
+use App\Models\Theme;
+use App\Models\ThemeSettings;
+use App\Models\User;
+use Database\Seeders\DatabaseSeeder;
+use Illuminate\Foundation\Testing\RefreshDatabase;
+use Livewire\Livewire;
+
+uses(RefreshDatabase::class);
+
+beforeEach(function (): void {
+    $this->withoutVite();
+    $this->seed(DatabaseSeeder::class);
+});
+
+function adminContentStore(): Store
+{
+    $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();
+    app()->instance('current_store', $store);
+
+    return $store;
+}
+
+function adminContentUser(): User
+{
+    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
+}
+
+function adminContentUserWithRole(Store $store, StoreUserRole $role): User
+{
+    $user = User::factory()->create();
+    $user->stores()->attach($store->getKey(), [
+        'role' => $role->value,
+        'created_at' => now(),
+    ]);
+
+    return $user;
+}
+
+test('content routes render store scoped pages navigation and themes', function (): void {
+    $store = adminContentStore();
+    $user = adminContentUser();
+    Page::factory()->create([
+        'store_id' => Store::factory()->create()->getKey(),
+        'title' => 'Other Store Page',
+    ]);
+    $theme = Theme::withoutGlobalScopes()->where('store_id', $store->getKey())->firstOrFail();
+
+    $this->get('/admin/pages')->assertRedirect('/admin/login');
+
+    $this->actingAs($user)
+        ->withSession(['current_store_id' => $store->getKey()])
+        ->get('/admin/pages')
+        ->assertSuccessful()
+        ->assertSee('About')
+        ->assertDontSee('Other Store Page');
+
+    $this->actingAs($user)
+        ->withSession(['current_store_id' => $store->getKey()])
+        ->get('/admin/pages/create')
+        ->assertSuccessful()
+        ->assertSee('Create page');
+
+    $this->actingAs($user)
+        ->withSession(['current_store_id' => $store->getKey()])
+        ->get('/admin/navigation')
+        ->assertSuccessful()
+        ->assertSee('Main Menu');
+
+    $this->actingAs($user)
+        ->withSession(['current_store_id' => $store->getKey()])
+        ->get('/admin/themes')
+        ->assertSuccessful()
+        ->assertSee($theme->name);
+
+    $this->actingAs($user)
+        ->withSession(['current_store_id' => $store->getKey()])
+        ->get('/admin/themes/'.$theme->getKey().'/editor')
+        ->assertSuccessful()
+        ->assertSee('Sections');
+});
+
+test('admin pages can be created searched edited and deleted', function (): void {
+    $store = adminContentStore();
+    $user = adminContentUser();
+
+    Livewire::actingAs($user)
+        ->test(AdminPageForm::class)
+        ->set('title', 'Sizing Guide')
+        ->set('handle', 'sizing-guide')
+        ->set('bodyHtml', '
Measure twice.
')
+        ->set('status', PageStatus::Published->value)
+        ->call('save')
+        ->assertHasNoErrors();
+
+    $page = Page::withoutGlobalScopes()
+        ->where('store_id', $store->getKey())
+        ->where('handle', 'sizing-guide')
+        ->firstOrFail();
+
+    expect($page->status)->toBe(PageStatus::Published)
+        ->and($page->published_at)->not->toBeNull();
+
+    Livewire::actingAs($user)
+        ->test(AdminPagesIndex::class)
+        ->set('search', 'sizing')
+        ->assertSee('Sizing Guide')
+        ->assertDontSee('About');
+
+    Livewire::actingAs($user)
+        ->test(AdminPageForm::class, ['page' => $page])
+        ->set('title', 'Size Guide')
+        ->set('status', PageStatus::Draft->value)
+        ->call('save')
+        ->assertHasNoErrors();
+
+    expect($page->refresh()->title)->toBe('Size Guide')
+        ->and($page->status)->toBe(PageStatus::Draft);
+
+    Livewire::actingAs($user)
+        ->test(AdminPageForm::class, ['page' => $page])
+        ->call('deletePage')
+        ->assertRedirect(route('admin.pages.index', absolute: false));
+
+    expect(Page::withoutGlobalScopes()->whereKey($page->getKey())->exists())->toBeFalse();
+});
+
+test('navigation menu items can be edited and persisted in order', function (): void {
+    $store = adminContentStore();
+    $user = adminContentUser();
+    $menu = NavigationMenu::withoutGlobalScopes()
+        ->where('store_id', $store->getKey())
+        ->where('handle', 'main-menu')
+        ->firstOrFail();
+
+    Livewire::actingAs($user)
+        ->test(AdminNavigationIndex::class)
+        ->call('selectMenu', $menu->getKey())
+        ->set('itemLabel', 'Lookbook')
+        ->set('itemType', NavigationItemType::Link->value)
+        ->set('itemUrl', '/lookbook')
+        ->call('saveItem')
+        ->call('moveItemUp', count($menu->items) - 1)
+        ->call('saveMenu')
+        ->assertHasNoErrors();
+
+    $items = NavigationItem::withoutGlobalScopes()
+        ->where('menu_id', $menu->getKey())
+        ->orderBy('position')
+        ->get();
+
+    expect($items->pluck('position')->all())->toBe(range(0, $items->count() - 1))
+        ->and($items->pluck('label'))->toContain('Lookbook');
+});
+
+test('themes can be duplicated edited and published', function (): void {
+    $store = adminContentStore();
+    $user = adminContentUser();
+    $published = Theme::withoutGlobalScopes()->where('store_id', $store->getKey())->firstOrFail();
+
+    Livewire::actingAs($user)
+        ->test(AdminThemesIndex::class)
+        ->call('duplicateTheme', $published->getKey())
+        ->assertHasNoErrors();
+
+    $copy = Theme::withoutGlobalScopes()
+        ->where('store_id', $store->getKey())
+        ->where('name', $published->name.' Copy')
+        ->firstOrFail();
+
+    Livewire::actingAs($user)
+        ->test(AdminThemeEditor::class, ['theme' => $copy])
+        ->set('settings.home.hero.heading', 'New Hero')
+        ->call('save')
+        ->call('publish')
+        ->assertHasNoErrors();
+
+    expect(ThemeSettings::withoutGlobalScopes()->where('theme_id', $copy->getKey())->first()?->settings_json['home']['hero']['heading'])->toBe('New Hero')
+        ->and($copy->refresh()->status)->toBe(ThemeStatus::Published)
+        ->and($published->refresh()->status)->toBe(ThemeStatus::Draft)
+        ->and(Theme::withoutGlobalScopes()->where('store_id', $store->getKey())->where('status', ThemeStatus::Published)->count())->toBe(1);
+});
+
+test('content management honors store roles and store scoping', function (): void {
+    $store = adminContentStore();
+    $support = adminContentUserWithRole($store, StoreUserRole::Support);
+    $staff = adminContentUserWithRole($store, StoreUserRole::Staff);
+    $otherPage = Page::factory()->create([
+        'store_id' => Store::factory()->create()->getKey(),
+    ]);
+    $theme = Theme::withoutGlobalScopes()->where('store_id', $store->getKey())->firstOrFail();
+
+    Livewire::actingAs($support)
+        ->test(AdminPageForm::class)
+        ->assertStatus(403);
+
+    Livewire::actingAs($staff)
+        ->test(AdminThemesIndex::class)
+        ->assertStatus(403);
+
+    Livewire::actingAs($staff)
+        ->test(AdminNavigationIndex::class)
+        ->assertStatus(403);
+
+    Livewire::actingAs(adminContentUser())
+        ->test(AdminPageForm::class, ['page' => $otherPage])
+        ->assertStatus(404);
+
+    Livewire::actingAs(adminContentUser())
+        ->test(AdminThemeEditor::class, ['theme' => $theme])
+        ->assertSuccessful();
+});
*** End Patch


Success. Updated the following files:
A /Users/fabianwesner/Herd/shop/tests/Feature/Admin/ContentManagementTest.php

<?php

use App\Enums\NavigationItemType;
use App\Enums\PageStatus;
use App\Enums\StoreUserRole;
use App\Enums\ThemeStatus;
use App\Livewire\Admin\Navigation\Index as AdminNavigationIndex;
use App\Livewire\Admin\Pages\Form as AdminPageForm;
use App\Livewire\Admin\Pages\Index as AdminPagesIndex;
use App\Livewire\Admin\Themes\Editor as AdminThemeEditor;
use App\Livewire\Admin\Themes\Index as AdminThemesIndex;
use App\Models\NavigationItem;
use App\Models\NavigationMenu;
use App\Models\Page;
use App\Models\Store;
use App\Models\Theme;
use App\Models\ThemeSettings;
use App\Models\User;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminContentStore(): Store
{
    $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();
    app()->instance('current_store', $store);

    return $store;
}

function adminContentUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

function adminContentUserWithRole(Store $store, StoreUserRole $role): User
{
    $user = User::factory()->create();
    $user->stores()->attach($store->getKey(), [
        'role' => $role->value,
        'created_at' => now(),
    ]);

    return $user;
}

test('content routes render store scoped pages navigation and themes', function (): void {
    $store = adminContentStore();
    $user = adminContentUser();
    Page::factory()->create([
        'store_id' => Store::factory()->create()->getKey(),
        'title' => 'Other Store Page',
    ]);
    $theme = Theme::withoutGlobalScopes()->where('store_id', $store->getKey())->firstOrFail();

    $this->get('/admin/pages')->assertRedirect('/admin/login');

    $this->actingAs($user)
        ->withSession(['current_store_id' => $store->getKey()])
        ->get('/admin/pages')
        ->assertSuccessful()
        ->assertSee('About')
        ->assertDontSee('Other Store Page');

    $this->actingAs($user)
        ->withSession(['current_store_id' => $store->getKey()])
        ->get('/admin/pages/create')
        ->assertSuccessful()
        ->assertSee('Create page');

    $this->actingAs($user)
        ->withSession(['current_store_id' => $store->getKey()])
        ->get('/admin/navigation')
        ->assertSuccessful()
        ->assertSee('Main Menu');

    $this->actingAs($user)
        ->withSession(['current_store_id' => $store->getKey()])
        ->get('/admin/themes')
        ->assertSuccessful()
        ->assertSee($theme->name);

    $this->actingAs($user)
        ->withSession(['current_store_id' => $store->getKey()])
        ->get('/admin/themes/'.$theme->getKey().'/editor')
        ->assertSuccessful()
        ->assertSee('Sections');
});

test('admin pages can be created searched edited and deleted', function (): void {
    $store = adminContentStore();
    $user = adminContentUser();

    Livewire::actingAs($user)
        ->test(AdminPageForm::class)
        ->set('title', 'Sizing Guide')
        ->set('handle', 'sizing-guide')
        ->set('bodyHtml', '
Measure twice.
')
        ->set('status', PageStatus::Published->value)
        ->call('save')
        ->assertHasNoErrors();

    $page = Page::withoutGlobalScopes()
        ->where('store_id', $store->getKey())
        ->where('handle', 'sizing-guide')
        ->firstOrFail();

    expect($page->status)->toBe(PageStatus::Published)
        ->and($page->published_at)->not->toBeNull();

    Livewire::actingAs($user)
        ->test(AdminPagesIndex::class)
        ->set('search', 'sizing')
        ->assertSee('Sizing Guide')
        ->assertDontSee('About');

    Livewire::actingAs($user)
        ->test(AdminPageForm::class, ['page' => $page])
        ->set('title', 'Size Guide')
        ->set('status', PageStatus::Draft->value)
        ->call('save')
        ->assertHasNoErrors();

    expect($page->refresh()->title)->toBe('Size Guide')
        ->and($page->status)->toBe(PageStatus::Draft);

    Livewire::actingAs($user)
        ->test(AdminPageForm::class, ['page' => $page])
        ->call('deletePage')
        ->assertRedirect(route('admin.pages.index', absolute: false));

    expect(Page::withoutGlobalScopes()->whereKey($page->getKey())->exists())->toBeFalse();
});

test('navigation menu items can be edited and persisted in order', function (): void {
    $store = adminContentStore();
    $user = adminContentUser();
    $menu = NavigationMenu::withoutGlobalScopes()
        ->where('store_id', $store->getKey())
        ->where('handle', 'main-menu')
        ->firstOrFail();

    Livewire::actingAs($user)
        ->test(AdminNavigationIndex::class)
        ->call('selectMenu', $menu->getKey())
        ->set('itemLabel', 'Lookbook')
        ->set('itemType', NavigationItemType::Link->value)
        ->set('itemUrl', '/lookbook')
        ->call('saveItem')
        ->call('moveItemUp', count($menu->items) - 1)
        ->call('saveMenu')
        ->assertHasNoErrors();

    $items = NavigationItem::withoutGlobalScopes()
        ->where('menu_id', $menu->getKey())
        ->orderBy('position')
        ->get();

    expect($items->pluck('position')->all())->toBe(range(0, $items->count() - 1))
        ->and($items->pluck('label'))->toContain('Lookbook');
});

test('themes can be duplicated edited and published', function (): void {
    $store = adminContentStore();
    $user = adminContentUser();
    $published = Theme::withoutGlobalScopes()->where('store_id', $store->getKey())->firstOrFail();

    Livewire::actingAs($user)
        ->test(AdminThemesIndex::class)
        ->call('duplicateTheme', $published->getKey())
        ->assertHasNoErrors();

    $copy = Theme::withoutGlobalScopes()
        ->where('store_id', $store->getKey())
        ->where('name', $published->name.' Copy')
        ->firstOrFail();

    Livewire::actingAs($user)
        ->test(AdminThemeEditor::class, ['theme' => $copy])
        ->set('settings.home.hero.heading', 'New Hero')
        ->call('save')
        ->call('publish')
        ->assertHasNoErrors();

    expect(ThemeSettings::withoutGlobalScopes()->where('theme_id', $copy->getKey())->first()?->settings_json['home']['hero']['heading'])->toBe('New Hero')
        ->and($copy->refresh()->status)->toBe(ThemeStatus::Published)
        ->and($published->refresh()->status)->toBe(ThemeStatus::Draft)
        ->and(Theme::withoutGlobalScopes()->where('store_id', $store->getKey())->where('status', ThemeStatus::Published)->count())->toBe(1);
});

test('content management honors store roles and store scoping', function (): void {
    $store = adminContentStore();
    $support = adminContentUserWithRole($store, StoreUserRole::Support);
    $staff = adminContentUserWithRole($store, StoreUserRole::Staff);
    $otherPage = Page::factory()->create([
        'store_id' => Store::factory()->create()->getKey(),
    ]);
    $theme = Theme::withoutGlobalScopes()->where('store_id', $store->getKey())->firstOrFail();

    Livewire::actingAs($support)
        ->test(AdminPageForm::class)
        ->assertStatus(403);

    Livewire::actingAs($staff)
        ->test(AdminThemesIndex::class)
        ->assertStatus(403);

    Livewire::actingAs($staff)
        ->test(AdminNavigationIndex::class)
        ->assertStatus(403);

    Livewire::actingAs(adminContentUser())
        ->test(AdminPageForm::class, ['page' => $otherPage])
        ->assertStatus(404);

    Livewire::actingAs(adminContentUser())
        ->test(AdminThemeEditor::class, ['theme' => $theme])
        ->assertSuccessful();
});


Output: {"output":"Success. Updated the following files:\nA /Users/fabianwesner/Herd/shop/tests/Feature/Admin/ContentManagementTest.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Delete File: /Users/fabianwesner/Herd/shop/tests/Feature/Admin/SettingsManagementTest.php
*** End Patch


Success. Updated the following files:
D /Users/fabianwesner/Herd/shop/tests/Feature/Admin/SettingsManagementTest.php


Output: {"output":"Success. Updated the following files:\nD /Users/fabianwesner/Herd/shop/tests/Feature/Admin/SettingsManagementTest.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Add File: /Users/fabianwesner/Herd/shop/tests/Feature/Admin/SettingsManagementTest.php
+<?php
+
+use App\Enums\ShippingRateType;
+use App\Enums\StoreUserRole;
+use App\Enums\TaxMode;
+use App\Livewire\Admin\Settings\Index as AdminSettingsIndex;
+use App\Livewire\Admin\Settings\Shipping as AdminSettingsShipping;
+use App\Livewire\Admin\Settings\Taxes as AdminSettingsTaxes;
+use App\Models\ShippingRate;
+use App\Models\ShippingZone;
+use App\Models\Store;
+use App\Models\StoreDomain;
+use App\Models\StoreSettings;
+use App\Models\TaxSettings;
+use App\Models\User;
+use Database\Seeders\DatabaseSeeder;
+use Illuminate\Foundation\Testing\RefreshDatabase;
+use Livewire\Livewire;
+
+uses(RefreshDatabase::class);
+
+beforeEach(function (): void {
+    $this->withoutVite();
+    $this->seed(DatabaseSeeder::class);
+});
+
+function adminSettingsStore(): Store
+{
+    $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();
+    app()->instance('current_store', $store);
+
+    return $store;
+}
+
+function adminSettingsUser(): User
+{
+    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
+}
+
+function adminSettingsUserWithRole(Store $store, StoreUserRole $role): User
+{
+    $user = User::factory()->create();
+    $user->stores()->attach($store->getKey(), [
+        'role' => $role->value,
+        'created_at' => now(),
+    ]);
+
+    return $user;
+}
+
+test('settings routes render for owners and reject staff', function (): void {
+    $store = adminSettingsStore();
+    $owner = adminSettingsUser();
+    $staff = adminSettingsUserWithRole($store, StoreUserRole::Staff);
+
+    foreach (['/admin/settings', '/admin/settings/shipping', '/admin/settings/taxes'] as $path) {
+        $this->actingAs($owner)
+            ->withSession(['current_store_id' => $store->getKey()])
+            ->get($path)
+            ->assertSuccessful();
+
+        $this->actingAs($staff)
+            ->withSession(['current_store_id' => $store->getKey()])
+            ->get($path)
+            ->assertForbidden();
+    }
+});
+
+test('general settings update store defaults and domains', function (): void {
+    $store = adminSettingsStore();
+    $user = adminSettingsUser();
+
+    Livewire::actingAs($user)
+        ->test(AdminSettingsIndex::class)
+        ->set('storeName', 'Acme Atelier')
+        ->set('defaultCurrency', 'GBP')
+        ->set('defaultLocale', 'de')
+        ->set('timezone', 'Europe/Berlin')
+        ->set('announcementEnabled', true)
+        ->set('announcementText', 'Spring edits now live')
+        ->set('guestCheckoutEnabled', false)
+        ->call('save')
+        ->assertHasNoErrors()
+        ->set('newHostname', 'atelier.test')
+        ->set('newType', 'storefront')
+        ->call('addDomain')
+        ->assertHasNoErrors();
+
+    $settings = StoreSettings::query()->whereKey($store->getKey())->firstOrFail();
+
+    expect($store->refresh()->name)->toBe('Acme Atelier')
+        ->and($store->default_currency)->toBe('GBP')
+        ->and($store->default_locale)->toBe('de')
+        ->and($settings->settings_json['announcement']['text'])->toBe('Spring edits now live')
+        ->and($settings->settings_json['checkout']['guest_checkout_enabled'])->toBeFalse()
+        ->and(StoreDomain::query()->where('hostname', 'atelier.test')->where('store_id', $store->getKey())->exists())->toBeTrue();
+});
+
+test('shipping settings manage zones rates and address tests', function (): void {
+    $store = adminSettingsStore();
+    $user = adminSettingsUser();
+
+    Livewire::actingAs($user)
+        ->test(AdminSettingsShipping::class)
+        ->set('zoneName', 'Nordics')
+        ->set('zoneCountries', 'SE, NO')
+        ->call('saveZone')
+        ->assertHasNoErrors();
+
+    $zone = ShippingZone::withoutGlobalScopes()
+        ->where('store_id', $store->getKey())
+        ->where('name', 'Nordics')
+        ->firstOrFail();
+
+    Livewire::actingAs($user)
+        ->test(AdminSettingsShipping::class)
+        ->call('addRate', $zone->getKey())
+        ->set('rateName', 'Nordic Standard')
+        ->set('rateType', ShippingRateType::Flat->value)
+        ->set('rateAmount', '12.50')
+        ->call('saveRate')
+        ->assertHasNoErrors()
+        ->set('testCountry', 'SE')
+        ->call('testShippingAddress')
+        ->assertSet('testResult.zone', 'Nordics');
+
+    $rate = ShippingRate::withoutGlobalScopes()
+        ->where('zone_id', $zone->getKey())
+        ->where('name', 'Nordic Standard')
+        ->firstOrFail();
+
+    expect($zone->countries_json)->toBe(['SE', 'NO'])
+        ->and($rate->type)->toBe(ShippingRateType::Flat)
+        ->and($rate->config_json['amount'])->toBe(1250);
+});
+
+test('tax settings save manual and provider configuration', function (): void {
+    $store = adminSettingsStore();
+    $user = adminSettingsUser();
+
+    Livewire::actingAs($user)
+        ->test(AdminSettingsTaxes::class)
+        ->set('manualRates', [
+            ['country' => 'DE', 'name' => 'VAT', 'rate_percentage' => '19.00'],
+            ['country' => 'FR', 'name' => 'TVA', 'rate_percentage' => '20.00'],
+        ])
+        ->set('pricesIncludeTax', true)
+        ->call('save')
+        ->assertHasNoErrors();
+
+    $manual = TaxSettings::withoutGlobalScopes()->whereKey($store->getKey())->firstOrFail();
+
+    expect($manual->mode)->toBe(TaxMode::Manual)
+        ->and($manual->prices_include_tax)->toBeTrue()
+        ->and($manual->config_json['rates'][1]['country'])->toBe('FR')
+        ->and($manual->config_json['rates'][1]['rate_bps'])->toBe(2000);
+
+    Livewire::actingAs($user)
+        ->test(AdminSettingsTaxes::class)
+        ->set('mode', TaxMode::Provider->value)
+        ->set('provider', 'stripe_tax')
+        ->set('providerApiKey', 'sk_test_tax')
+        ->call('save')
+        ->assertHasNoErrors();
+
+    $provider = TaxSettings::withoutGlobalScopes()->whereKey($store->getKey())->firstOrFail();
+
+    expect($provider->mode)->toBe(TaxMode::Provider)
+        ->and($provider->provider)->toBe('stripe_tax')
+        ->and($provider->config_json['provider_api_key'])->toBe('sk_test_tax');
+});
*** End Patch


Success. Updated the following files:
A /Users/fabianwesner/Herd/shop/tests/Feature/Admin/SettingsManagementTest.php

<?php

use App\Enums\ShippingRateType;
use App\Enums\StoreUserRole;
use App\Enums\TaxMode;
use App\Livewire\Admin\Settings\Index as AdminSettingsIndex;
use App\Livewire\Admin\Settings\Shipping as AdminSettingsShipping;
use App\Livewire\Admin\Settings\Taxes as AdminSettingsTaxes;
use App\Models\ShippingRate;
use App\Models\ShippingZone;
use App\Models\Store;
use App\Models\StoreDomain;
use App\Models\StoreSettings;
use App\Models\TaxSettings;
use App\Models\User;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminSettingsStore(): Store
{
    $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();
    app()->instance('current_store', $store);

    return $store;
}

function adminSettingsUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

function adminSettingsUserWithRole(Store $store, StoreUserRole $role): User
{
    $user = User::factory()->create();
    $user->stores()->attach($store->getKey(), [
        'role' => $role->value,
        'created_at' => now(),
    ]);

    return $user;
}

test('settings routes render for owners and reject staff', function (): void {
    $store = adminSettingsStore();
    $owner = adminSettingsUser();
    $staff = adminSettingsUserWithRole($store, StoreUserRole::Staff);

    foreach (['/admin/settings', '/admin/settings/shipping', '/admin/settings/taxes'] as $path) {
        $this->actingAs($owner)
            ->withSession(['current_store_id' => $store->getKey()])
            ->get($path)
            ->assertSuccessful();

        $this->actingAs($staff)
            ->withSession(['current_store_id' => $store->getKey()])
            ->get($path)
            ->assertForbidden();
    }
});

test('general settings update store defaults and domains', function (): void {
    $store = adminSettingsStore();
    $user = adminSettingsUser();

    Livewire::actingAs($user)
        ->test(AdminSettingsIndex::class)
        ->set('storeName', 'Acme Atelier')
        ->set('defaultCurrency', 'GBP')
        ->set('defaultLocale', 'de')
        ->set('timezone', 'Europe/Berlin')
        ->set('announcementEnabled', true)
        ->set('announcementText', 'Spring edits now live')
        ->set('guestCheckoutEnabled', false)
        ->call('save')
        ->assertHasNoErrors()
        ->set('newHostname', 'atelier.test')
        ->set('newType', 'storefront')
        ->call('addDomain')
        ->assertHasNoErrors();

    $settings = StoreSettings::query()->whereKey($store->getKey())->firstOrFail();

    expect($store->refresh()->name)->toBe('Acme Atelier')
        ->and($store->default_currency)->toBe('GBP')
        ->and($store->default_locale)->toBe('de')
        ->and($settings->settings_json['announcement']['text'])->toBe('Spring edits now live')
        ->and($settings->settings_json['checkout']['guest_checkout_enabled'])->toBeFalse()
        ->and(StoreDomain::query()->where('hostname', 'atelier.test')->where('store_id', $store->getKey())->exists())->toBeTrue();
});

test('shipping settings manage zones rates and address tests', function (): void {
    $store = adminSettingsStore();
    $user = adminSettingsUser();

    Livewire::actingAs($user)
        ->test(AdminSettingsShipping::class)
        ->set('zoneName', 'Nordics')
        ->set('zoneCountries', 'SE, NO')
        ->call('saveZone')
        ->assertHasNoErrors();

    $zone = ShippingZone::withoutGlobalScopes()
        ->where('store_id', $store->getKey())
        ->where('name', 'Nordics')
        ->firstOrFail();

    Livewire::actingAs($user)
        ->test(AdminSettingsShipping::class)
        ->call('addRate', $zone->getKey())
        ->set('rateName', 'Nordic Standard')
        ->set('rateType', ShippingRateType::Flat->value)
        ->set('rateAmount', '12.50')
        ->call('saveRate')
        ->assertHasNoErrors()
        ->set('testCountry', 'SE')
        ->call('testShippingAddress')
        ->assertSet('testResult.zone', 'Nordics');

    $rate = ShippingRate::withoutGlobalScopes()
        ->where('zone_id', $zone->getKey())
        ->where('name', 'Nordic Standard')
        ->firstOrFail();

    expect($zone->countries_json)->toBe(['SE', 'NO'])
        ->and($rate->type)->toBe(ShippingRateType::Flat)
        ->and($rate->config_json['amount'])->toBe(1250);
});

test('tax settings save manual and provider configuration', function (): void {
    $store = adminSettingsStore();
    $user = adminSettingsUser();

    Livewire::actingAs($user)
        ->test(AdminSettingsTaxes::class)
        ->set('manualRates', [
            ['country' => 'DE', 'name' => 'VAT', 'rate_percentage' => '19.00'],
            ['country' => 'FR', 'name' => 'TVA', 'rate_percentage' => '20.00'],
        ])
        ->set('pricesIncludeTax', true)
        ->call('save')
        ->assertHasNoErrors();

    $manual = TaxSettings::withoutGlobalScopes()->whereKey($store->getKey())->firstOrFail();

    expect($manual->mode)->toBe(TaxMode::Manual)
        ->and($manual->prices_include_tax)->toBeTrue()
        ->and($manual->config_json['rates'][1]['country'])->toBe('FR')
        ->and($manual->config_json['rates'][1]['rate_bps'])->toBe(2000);

    Livewire::actingAs($user)
        ->test(AdminSettingsTaxes::class)
        ->set('mode', TaxMode::Provider->value)
        ->set('provider', 'stripe_tax')
        ->set('providerApiKey', 'sk_test_tax')
        ->call('save')
        ->assertHasNoErrors();

    $provider = TaxSettings::withoutGlobalScopes()->whereKey($store->getKey())->firstOrFail();

    expect($provider->mode)->toBe(TaxMode::Provider)
        ->and($provider->provider)->toBe('stripe_tax')
        ->and($provider->config_json['provider_api_key'])->toBe('sk_test_tax');
});


Output: {"output":"Success. Updated the following files:\nA /Users/fabianwesner/Herd/shop/tests/Feature/Admin/SettingsManagementTest.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


The Pest files now exercise route rendering, store scoping, role restrictions, and persistence for the new content/settings surfaces. I’m formatting and running the two focused test files to catch syntax and behavior issues.


php artisan test --compact tests/Feature/Admin/ContentManagementTest.php


php artisan test --compact tests/Feature/Admin/SettingsManagementTest.php


Output:
Chunk ID: cc2c11
Wall time: 0.8018 seconds
Process exited with code 0
Original token count: 21
Output:
{"result":"fixed","files":[{"path":"routes\/web.php","fixers":["ordered_imports"]}]}


/bin/zsh
-lc
vendor/bin/pint --dirty --format agent


Output:
Chunk ID: bd5f74
Wall time: 2.4394 seconds
Process exited with code 0
Original token count: 17
Output:

  .....

  Tests:    5 passed (38 assertions)
  Duration: 2.16s


php artisan test --compact tests/Feature/Admin/ContentManagementTest.php

  .....

  Tests:    5 passed (38 assertions)
  Duration: 2.16s


Output:
Chunk ID: 7b038b
Wall time: 2.7579 seconds
Process exited with code 2
Original token count: 311
Output:

  .⨯..
  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Admin\SettingsManagementTest > gene…  QueryException
  SQLSTATE[23000]: Integrity constraint violation: 19 CHECK constraint failed: tls_mode (Connection: sqlite, Database: :memory:, SQL: insert into "store_domains" ("store_id", "hostname", "type", "is_primary", "tls_mode", "created_at") values (1, atelier.test, storefront, 0, local, 2026-05-04 02:53:50))

  at vendor/laravel/framework/src/Illuminate/Database/Connection.php:584
    580▕             $this->bindValues($statement, $this->prepareBindings($bindings));
    581▕
    582▕             $this->recordsHaveBeenModified();
    583▕
  ➜ 584▕             return $statement->execute();
    585▕         });
    586▕     }
    587▕
    588▕     /**

      +14 vendor frames
  15  app/Livewire/Admin/Settings/Index.php:122
      +32 vendor frames
  48  tests/Feature/Admin/SettingsManagementTest.php:86

  Tests:    1 failed, 3 passed (22 assertions)
  Duration: 2.47s


php artisan test --compact tests/Feature/Admin/SettingsManagementTest.php

  .⨯..
  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Admin\SettingsManagementTest > gene…  QueryException
  SQLSTATE[23000]: Integrity constraint violation: 19 CHECK constraint failed: tls_mode (Connection: sqlite, Database: :memory:, SQL: insert into "store_domains" ("store_id", "hostname", "type", "is_primary", "tls_mode", "created_at") values (1, atelier.test, storefront, 0, local, 2026-05-04 02:53:50))

  at vendor/laravel/framework/src/Illuminate/Database/Connection.php:584
    580▕             $this->bindValues($statement, $this->prepareBindings($bindings));
    581▕
    582▕             $this->recordsHaveBeenModified();
    583▕
  ➜ 584▕             return $statement->execute();
    585▕         });
    586▕     }
    587▕
    588▕     /**

      +14 vendor frames
  15  app/Livewire/Admin/Settings/Index.php:122
      +32 vendor frames
  48  tests/Feature/Admin/SettingsManagementTest.php:86

  Tests:    1 failed, 3 passed (22 assertions)
  Duration: 2.47s


sed -n '1,120p' database/migrations/2026_05_03_211416_create_store_domains_table.php