Output:
Chunk ID: 7b076f
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 7511
Output:
database/seeders/DiscountSeeder.php:31:                        'rules_json' => $discount['rules_json'],
database/seeders/DiscountSeeder.php:40:     * @return array<int, array{code: string, value_type: string, value_amount: int, rules_json: array<string, mixed>}>
database/seeders/DiscountSeeder.php:45:            ['code' => 'SAVE10', 'value_type' => 'percent', 'value_amount' => 10, 'rules_json' => ['customer_eligibility' => 'all']],
database/seeders/DiscountSeeder.php:46:            ['code' => '5OFF', 'value_type' => 'fixed', 'value_amount' => 500, 'rules_json' => ['min_purchase_amount' => 2500, 'customer_eligibility' => 'all']],
database/seeders/DiscountSeeder.php:47:            ['code' => 'FREESHIP', 'value_type' => 'free_shipping', 'value_amount' => 0, 'rules_json' => ['customer_eligibility' => 'all']],
tests/Pest.php:8:| The closure you provide to your test functions is always bound to a specific PHPUnit test
tests/Pest.php:38:| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
tests/Feature/Catalog/CatalogFoundationTest.php:24:        ->and(Schema::hasColumns('collections', ['store_id', 'title', 'handle', 'type', 'status']))->toBeTrue()
tests/Feature/Catalog/CatalogUiTest.php:31:        ->get('/collections/t-shirts')
tests/Feature/Catalog/CatalogUiTest.php:63:        ->get('/admin/collections')
tests/Feature/Catalog/CatalogUiTest.php:154:test('admin collection form creates and assigns products', function () {
tests/Feature/Catalog/CatalogUiTest.php:163:        ->set('handle', 'e2e-test-collection')
tests/Feature/Catalog/CatalogUiTest.php:169:    $collection = Collection::query()->where('handle', 'e2e-test-collection')->firstOrFail();
tests/Feature/Catalog/CatalogUiTest.php:171:    expect($collection->products()->pluck('products.id')->all())->toBe([$product->getKey()]);
tests/Feature/Cart/CartServiceTest.php:107:test('cart service allows oversell for continue policy and merges guest carts into customer carts', function () {
tests/Feature/Cart/CartServiceTest.php:113:    $customer = Customer::factory()->create(['store_id' => $store->getKey()]);
tests/Feature/Cart/CartServiceTest.php:114:    $customerCart = $service->create($store, $customer);
tests/Feature/Cart/CartServiceTest.php:117:    $service->addLine($customerCart, $firstVariant->getKey(), 2);
tests/Feature/Cart/CartServiceTest.php:118:    $service->addLine($customerCart, $secondVariant->getKey(), 3);
tests/Feature/Cart/CartServiceTest.php:120:    $merged = $service->mergeOnLogin($guestCart, $customerCart);
tests/Feature/Foundation/CustomerAuthTest.php:13:test('customer authentication is scoped to the current store', function () {
tests/Feature/Foundation/CustomerAuthTest.php:31:    expect(Auth::guard('customer')->attempt([
tests/Feature/Foundation/CustomerAuthTest.php:36:    Auth::guard('customer')->logout();
tests/Feature/Foundation/CustomerAuthTest.php:38:    expect(Auth::guard('customer')->attempt([
tests/Feature/Foundation/CustomerAuthTest.php:45:    expect(Auth::guard('customer')->attempt([
tests/Feature/Foundation/CustomerAuthTest.php:51:test('customer login component authenticates within the resolved store', function () {
tests/Feature/Foundation/CustomerAuthTest.php:53:    $customer = Customer::factory()->create([
tests/Feature/Foundation/CustomerAuthTest.php:55:        'email' => 'customer@example.test',
tests/Feature/Foundation/CustomerAuthTest.php:62:        ->set('email', $customer->email)
tests/Feature/Foundation/CustomerAuthTest.php:68:    $this->assertAuthenticatedAs($customer, 'customer');
tests/Feature/Foundation/CustomerAuthTest.php:71:test('customer registration is unique per store', function () {
tests/Feature/Storefront/OrderViewsTest.php:126:test('order confirmation is visible only for the session order or customer order', function () {
tests/Feature/Storefront/OrderViewsTest.php:128:    $customer = Customer::withoutGlobalScopes()->where('store_id', $store->getKey())->firstOrFail();
tests/Feature/Storefront/OrderViewsTest.php:130:    $order = Order::factory()->forCustomer($customer)->paid()->create([
tests/Feature/Storefront/OrderViewsTest.php:132:        'customer_id' => $customer->getKey(),
tests/Feature/Storefront/OrderViewsTest.php:133:        'email' => $customer->email,
tests/Feature/Storefront/OrderViewsTest.php:146:    $this->actingAs($otherCustomer, 'customer');
tests/Feature/Storefront/OrderViewsTest.php:151:    $this->actingAs($customer, 'customer');
tests/Feature/Storefront/OrderViewsTest.php:157:test('customer account lists and shows customer orders', function () {
tests/Feature/Storefront/OrderViewsTest.php:159:    $customer = Customer::withoutGlobalScopes()->where('store_id', $store->getKey())->firstOrFail();
tests/Feature/Storefront/OrderViewsTest.php:161:    $order = Order::factory()->forCustomer($customer)->paid()->create([
tests/Feature/Storefront/OrderViewsTest.php:163:        'customer_id' => $customer->getKey(),
tests/Feature/Storefront/OrderViewsTest.php:168:        'customer_id' => $otherCustomer->getKey(),
tests/Feature/Storefront/OrderViewsTest.php:176:    $this->actingAs($customer, 'customer');
tests/Feature/Storefront/CartCheckoutUiTest.php:118:test('customer login merges an existing guest cart without creating empty carts', function () {
tests/Feature/Storefront/CartCheckoutUiTest.php:121:    $customer = Customer::withoutGlobalScopes()
tests/Feature/Storefront/CartCheckoutUiTest.php:123:        ->where('email', 'customer@acme.test')
tests/Feature/Storefront/CartCheckoutUiTest.php:125:    $customerCart = app(CartService::class)->create($store, $customer);
tests/Feature/Storefront/CartCheckoutUiTest.php:129:    app(CartService::class)->addLine($customerCart, $variant->getKey(), 1);
tests/Feature/Storefront/CartCheckoutUiTest.php:133:        ->set('email', 'customer@acme.test')
tests/Feature/Storefront/CartCheckoutUiTest.php:138:        ->and($customerCart->refresh()->lines()->withoutGlobalScopes()->where('variant_id', $variant->getKey())->first()?->quantity)->toBe(2)
tests/Feature/Admin/CustomerManagementTest.php:43:function adminCustomerManagementOrder(Store $store, Customer $customer, string $orderNumber, int $totalAmount): Order
tests/Feature/Admin/CustomerManagementTest.php:47:        'customer_id' => $customer->getKey(),
tests/Feature/Admin/CustomerManagementTest.php:53:        'email' => $customer->email,
tests/Feature/Admin/CustomerManagementTest.php:57:test('admin customer routes require authentication and render store scoped customers', function (): void {
tests/Feature/Admin/CustomerManagementTest.php:59:    $customer = adminCustomerManagementCustomer($store, 'jane@example.test', 'Jane Example');
tests/Feature/Admin/CustomerManagementTest.php:60:    adminCustomerManagementOrder($store, $customer, '#8101', 7000);
tests/Feature/Admin/CustomerManagementTest.php:62:        'customer_id' => $customer->getKey(),
tests/Feature/Admin/CustomerManagementTest.php:69:    $this->get('/admin/customers')->assertRedirect('/admin/login');
tests/Feature/Admin/CustomerManagementTest.php:73:        ->get('/admin/customers')
tests/Feature/Admin/CustomerManagementTest.php:81:        ->get('/admin/customers/'.$customer->getKey())
tests/Feature/Admin/CustomerManagementTest.php:89:        ->get('/admin/customers/'.$otherCustomer->getKey())
tests/Feature/Admin/CustomerManagementTest.php:93:test('admin customer index filters by name and email', function (): void {
tests/Feature/Admin/CustomerManagementTest.php:111:test('admin customer detail manages addresses', function (): void {
tests/Feature/Admin/CustomerManagementTest.php:114:    $customer = adminCustomerManagementCustomer($store, 'jane@example.test', 'Jane Example');
tests/Feature/Admin/CustomerManagementTest.php:116:        'customer_id' => $customer->getKey(),
tests/Feature/Admin/CustomerManagementTest.php:121:        ->test(AdminCustomerShow::class, ['customer' => $customer])
tests/Feature/Admin/CustomerManagementTest.php:132:        ->where('customer_id', $customer->getKey())
tests/Feature/Admin/CustomerManagementTest.php:139:        ->test(AdminCustomerShow::class, ['customer' => $customer])
tests/Feature/Admin/CustomerManagementTest.php:154:test('admin customer detail rejects customers from another store', function (): void {
tests/Feature/Admin/CustomerManagementTest.php:162:        ->test(AdminCustomerShow::class, ['customer' => $otherCustomer])
tests/Feature/Storefront/ThemeDataTest.php:104:        ->and(collect($items)->firstWhere('label', 'New Arrivals')['url'])->toBe('/collections/new-arrivals')
app/Policies/CollectionPolicy.php:17:    public function view(User $user, object $collection): bool
app/Policies/CollectionPolicy.php:19:        return $this->isAnyRole($user, $this->storeIdForModel($collection));
app/Policies/CollectionPolicy.php:27:    public function update(User $user, object $collection): bool
app/Policies/CollectionPolicy.php:29:        return $this->isOwnerAdminOrStaff($user, $this->storeIdForModel($collection));
app/Policies/CollectionPolicy.php:32:    public function delete(User $user, object $collection): bool
app/Policies/CollectionPolicy.php:34:        return $this->isOwnerOrAdmin($user, $this->storeIdForModel($collection));
app/Policies/CustomerPolicy.php:17:    public function view(User $user, object $customer): bool
app/Policies/CustomerPolicy.php:19:        return $this->isAnyRole($user, $this->storeIdForModel($customer));
app/Policies/CustomerPolicy.php:22:    public function update(User $user, object $customer): bool
app/Policies/CustomerPolicy.php:24:        return $this->isOwnerAdminOrStaff($user, $this->storeIdForModel($customer));
app/Services/CheckoutService.php:28:    public function createFromCart(Cart $cart, ?Customer $customer = null): Checkout
app/Services/CheckoutService.php:43:            'customer_id' => $customer?->getKey() ?? $cart->customer_id,
app/Services/DiscountService.php:52:        $minimum = (int) data_get($discount->rules_json, 'min_purchase_amount', data_get($discount->rules_json, 'minimum_purchase', 0));
app/Services/DiscountService.php:54:        if ($minimum > 0 && $subtotal < $minimum) {
app/Services/DiscountService.php:55:            throw InvalidDiscountException::because('discount_min_purchase_not_met', 'Cart does not meet the minimum purchase amount.');
app/Services/DiscountService.php:142:        $productIds = collect(data_get($discount->rules_json, 'applicable_product_ids', []))
app/Services/DiscountService.php:146:        $collectionIds = collect(data_get($discount->rules_json, 'applicable_collection_ids', []))
app/Services/DiscountService.php:151:        if ($productIds->isEmpty() && $collectionIds->isEmpty()) {
app/Services/DiscountService.php:155:        return $lines->filter(function (CartLine $line) use ($productIds, $collectionIds): bool {
app/Services/DiscountService.php:157:                ->with(['product' => fn ($query) => $query->withoutGlobalScopes()->with('collections')])
app/Services/DiscountService.php:168:            return $variant->product->collections
app/Services/DiscountService.php:171:                ->intersect($collectionIds)
app/Livewire/Admin/Collections/Form.php:16:    public ?Collection $collection = null;
app/Livewire/Admin/Collections/Form.php:33:    public function mount(?Collection $collection = null): void
app/Livewire/Admin/Collections/Form.php:35:        if ($collection?->exists) {
app/Livewire/Admin/Collections/Form.php:38:            abort_unless($store instanceof Store && (int) $collection->store_id === $store->getKey(), 404);
app/Livewire/Admin/Collections/Form.php:40:            $this->collection = $collection->load('products');
app/Livewire/Admin/Collections/Form.php:41:            $this->fillFromCollection($this->collection);
app/Livewire/Admin/Collections/Form.php:47:        if ($this->collection === null && $this->handle === '') {
app/Livewire/Admin/Collections/Form.php:80:                Rule::unique('collections', 'handle')
app/Livewire/Admin/Collections/Form.php:82:                    ->ignore($this->collection?->getKey()),
app/Livewire/Admin/Collections/Form.php:97:        $collection = $this->collection instanceof Collection
app/Livewire/Admin/Collections/Form.php:98:            ? tap($this->collection)->update($attributes)
app/Livewire/Admin/Collections/Form.php:101:        $collection->products()->sync(collect($this->assignedProductIds)
app/Livewire/Admin/Collections/Form.php:106:        $this->collection = $collection->refresh()->load('products');
app/Livewire/Admin/Collections/Form.php:107:        $this->fillFromCollection($this->collection);
app/Livewire/Admin/Collections/Form.php:145:        return view('livewire.admin.collections.form', [
app/Livewire/Admin/Collections/Form.php:148:            'isEditing' => $this->collection !== null,
app/Livewire/Admin/Collections/Form.php:150:            'title' => $this->collection ? $this->collection->title : __('Add collection'),
app/Livewire/Admin/Collections/Form.php:154:    private function fillFromCollection(Collection $collection): void
app/Livewire/Admin/Collections/Form.php:156:        $this->title = $collection->title;
app/Livewire/Admin/Collections/Form.php:157:        $this->handle = $collection->handle;
app/Livewire/Admin/Collections/Form.php:158:        $this->descriptionHtml = (string) $collection->description_html;
app/Livewire/Admin/Collections/Form.php:159:        $this->status = $collection->status->value;
app/Livewire/Admin/Collections/Form.php:160:        $this->assignedProductIds = $collection->products->pluck('id')->map(fn (int $id): int => $id)->all();
app/Livewire/Storefront/Collections/Show.php:57:    public function collection(): Collection
app/Livewire/Storefront/Collections/Show.php:67:        $collection = $this->collection();
app/Livewire/Storefront/Collections/Show.php:69:        return $collection->products()
app/Livewire/Storefront/Collections/Show.php:97:        return $this->collection()->products()
app/Livewire/Storefront/Collections/Show.php:108:        return $this->collection()->products()
app/Livewire/Storefront/Collections/Show.php:119:        $collection = $this->collection();
app/Livewire/Storefront/Collections/Show.php:121:        return view('livewire.storefront.collections.show', [
app/Livewire/Storefront/Collections/Show.php:122:            'collection' => $collection,
app/Livewire/Storefront/Collections/Show.php:127:            'title' => $collection->title,
app/Livewire/Admin/Collections/Index.php:36:    public function collections(): LengthAwarePaginator
app/Livewire/Admin/Collections/Index.php:50:        return view('livewire.admin.collections.index', [
app/Livewire/Admin/Collections/Index.php:51:            'collections' => $this->collections(),
app/Livewire/Storefront/Collections/Index.php:11:    public function collections(): SupportCollection
app/Livewire/Storefront/Collections/Index.php:24:        return view('livewire.storefront.collections.index', [
app/Livewire/Storefront/Collections/Index.php:25:            'collections' => $this->collections(),
app/Services/CartService.php:24:    public function create(Store $store, ?Customer $customer = null): Cart
app/Services/CartService.php:28:            'customer_id' => $customer?->getKey(),
app/Services/CartService.php:35:    public function getOrCreateForSession(Store $store, ?Customer $customer = null): Cart
app/Services/CartService.php:39:        if ($customer instanceof Customer) {
app/Services/CartService.php:40:            $customerCart = Cart::withoutGlobalScopes()
app/Services/CartService.php:42:                ->where('customer_id', $customer->getKey())
app/Services/CartService.php:45:                ->first() ?? $this->create($store, $customer);
app/Services/CartService.php:50:                    ->whereNull('customer_id')
app/Services/CartService.php:54:                if ($guestCart instanceof Cart && $guestCart->isNot($customerCart)) {
app/Services/CartService.php:55:                    $customerCart = $this->mergeOnLogin($guestCart, $customerCart);
app/Services/CartService.php:61:            return $customerCart->refresh();
app/Services/CartService.php:67:                ->whereNull('customer_id')
app/Services/CartService.php:82:    public function currentForSession(Store $store, ?Customer $customer = null): ?Cart
app/Services/CartService.php:84:        if ($customer instanceof Customer) {
app/Services/CartService.php:85:            $customerCart = Cart::withoutGlobalScopes()
app/Services/CartService.php:87:                ->where('customer_id', $customer->getKey())
app/Services/CartService.php:92:            if ($customerCart instanceof Cart) {
app/Services/CartService.php:93:                return $customerCart;
app/Services/CartService.php:106:            ->where(function ($query) use ($customer): void {
app/Services/CartService.php:107:                if ($customer instanceof Customer) {
app/Services/CartService.php:109:                        ->whereNull('customer_id')
app/Services/CartService.php:110:                        ->orWhere('customer_id', $customer->getKey());
app/Services/CartService.php:115:                $query->whereNull('customer_id');
app/Services/CartService.php:197:    public function mergeOnLogin(Cart $guest, Cart $customer): Cart
app/Services/CartService.php:199:        return DB::transaction(function () use ($guest, $customer): Cart {
app/Services/CartService.php:201:            $customerCart = $this->freshCart($customer);
app/Services/CartService.php:206:                ->each(function (CartLine $guestLine) use ($customerCart): void {
app/Services/CartService.php:208:                        ->where('cart_id', $customerCart->getKey())
app/Services/CartService.php:221:                    $guestLine->forceFill(['cart_id' => $customerCart->getKey()])->save();
app/Services/CartService.php:225:            $this->incrementVersion($customerCart);
app/Services/CartService.php:227:            return $customerCart->refresh();
app/Http/Controllers/Api/Admin/V1/OrderController.php:29:            ->with('customer')
app/Http/Controllers/Api/Admin/V1/OrderController.php:42:                        ->orWhereHas('customer', fn (Builder $query) => $query->where('name', 'like', $like));
app/Http/Controllers/Api/Admin/V1/OrderController.php:49:        return OrderResource::collection($orders);
app/Http/Controllers/Api/Admin/V1/OrderController.php:75:            'customer',
app/Services/OrderService.php:81:                    'customer_id' => $checkout->customer_id,
app/Livewire/Admin/Orders/Show.php:185:                'customer',
app/Services/ShippingCalculator.php:57:                $specificity = $regions->contains($region) ? 2 : 1;
app/Services/ShippingCalculator.php:59:                if ($specificity > $bestSpecificity) {
app/Services/ShippingCalculator.php:61:                    $bestSpecificity = $specificity;
app/Services/ShippingCalculator.php:97:            $minimum = (int) data_get($range, 'min_g', 0);
app/Services/ShippingCalculator.php:100:            if ($totalWeight >= $minimum && ($maximum === null || $totalWeight <= (int) $maximum)) {
app/Services/ShippingCalculator.php:115:            $minimum = (int) data_get($range, 'min_amount', 0);
app/Services/ShippingCalculator.php:118:            if ($subtotal >= $minimum && ($maximum === null || $subtotal <= (int) $maximum)) {
app/Livewire/Admin/Products/Form.php:43:    public array $collectionIds = [];
app/Livewire/Admin/Products/Form.php:62:            $this->product = $product->load(['options.values', 'variants.inventoryItem', 'variants.optionValues.option', 'collections']);
app/Livewire/Admin/Products/Form.php:154:            $product->collections()->sync($this->collectionIds);
app/Livewire/Admin/Products/Form.php:159:        $this->product = $product->load(['options.values', 'variants.inventoryItem', 'variants.optionValues.option', 'collections']);
app/Livewire/Admin/Products/Form.php:196:        $this->collectionIds = $product->collections->pluck('id')->map(fn (int $id): int => $id)->all();
app/Livewire/Storefront/Account/Orders/Show.php:23:        $customer = Auth::guard('customer')->user();
app/Livewire/Storefront/Account/Orders/Show.php:25:        abort_unless($store instanceof Store && $customer instanceof Customer, 404);
app/Livewire/Storefront/Account/Orders/Show.php:29:            ->where('customer_id', $customer->getKey())
app/Livewire/Storefront/Account/Orders/Show.php:53:            ->where('customer_id', Auth::guard('customer')->id())
app/Livewire/Admin/Orders/Index.php:82:            ->with('customer')
app/Livewire/Admin/Orders/Index.php:92:                        ->orWhereHas('customer', fn (Builder $query) => $query->where('name', 'like', $search));
app/Livewire/Storefront/Account/Orders/Index.php:41:        $customer = Auth::guard('customer')->user();
app/Livewire/Storefront/Account/Orders/Index.php:43:        abort_unless($customer instanceof Customer, 403);
app/Livewire/Storefront/Account/Orders/Index.php:47:            ->where('customer_id', $customer->getKey())
app/Livewire/Admin/Customers/Show.php:23:    public int $customerId;
app/Livewire/Admin/Customers/Show.php:43:    public function mount(Customer $customer): void
app/Livewire/Admin/Customers/Show.php:49:        $customer = Customer::withoutGlobalScopes()
app/Livewire/Admin/Customers/Show.php:51:            ->whereKey($customer->getKey())
app/Livewire/Admin/Customers/Show.php:54:        abort_unless($customer instanceof Customer, 404);
app/Livewire/Admin/Customers/Show.php:57:        $this->customerId = $customer->getKey();
app/Livewire/Admin/Customers/Show.php:91:            : new CustomerAddress(['customer_id' => $this->customerId]);
app/Livewire/Admin/Customers/Show.php:96:            'is_default' => $address->exists ? $address->is_default : ! $this->customer()->addresses()->exists(),
app/Livewire/Admin/Customers/Show.php:124:                ->where('customer_id', $this->customerId)
app/Livewire/Admin/Customers/Show.php:137:            ->where('customer_id', $this->customerId)
app/Livewire/Admin/Customers/Show.php:145:        $customer = $this->customer();
app/Livewire/Admin/Customers/Show.php:147:        return view('livewire.admin.customers.show', [
app/Livewire/Admin/Customers/Show.php:148:            'customer' => $customer,
app/Livewire/Admin/Customers/Show.php:151:            'title' => $customer->name ?: $customer->email,
app/Livewire/Admin/Customers/Show.php:155:    private function customer(): Customer
app/Livewire/Admin/Customers/Show.php:160:            ->whereKey($this->customerId)
app/Livewire/Admin/Customers/Show.php:167:            ->where('customer_id', $this->customerId)
app/Livewire/Admin/Customers/Show.php:175:            ->where('customer_id', $this->customerId)
app/Livewire/Storefront/Account/Auth/Register.php:46:                Rule::unique('customers', 'email')->where('store_id', $store->getKey()),
app/Livewire/Storefront/Account/Auth/Register.php:52:        $customer = Customer::withoutGlobalScopes()->create([
app/Livewire/Storefront/Account/Auth/Register.php:60:        Auth::guard('customer')->login($customer);
app/Livewire/Admin/Customers/Index.php:39:    public function customers(): LengthAwarePaginator
app/Livewire/Admin/Customers/Index.php:60:        return view('livewire.admin.customers.index', [
app/Livewire/Admin/Customers/Index.php:61:            'customers' => $this->customers(),
app/Services/NavigationService.php:59:            NavigationItemType::Collection => $this->collectionUrl($item),
app/Services/NavigationService.php:78:    private function collectionUrl(NavigationItem $item): string
app/Services/NavigationService.php:80:        $collection = ProductCollection::withoutGlobalScopes()
app/Services/NavigationService.php:84:        return $collection ? route('collections.show', $collection->handle, false) : '#';
app/Livewire/Storefront/Checkout/Confirmation.php:33:        $customer = Auth::guard('customer')->user();
app/Livewire/Storefront/Checkout/Confirmation.php:35:        $isCustomerOrder = $customer instanceof Customer && $order->customer_id === $customer->getKey();
app/Livewire/Storefront/Checkout/Confirmation.php:55:        $customer = Auth::guard('customer')->user();
app/Livewire/Storefront/Checkout/Confirmation.php:58:        abort_if(! $customer instanceof Customer && $sessionOrderId === null, 404);
app/Livewire/Storefront/Checkout/Confirmation.php:64:            ->where(function ($query) use ($customer, $sessionOrderId): void {
app/Livewire/Storefront/Checkout/Confirmation.php:65:                if ($customer instanceof Customer) {
app/Livewire/Storefront/Checkout/Confirmation.php:66:                    $query->orWhere('customer_id', $customer->getKey());
app/Livewire/Storefront/Products/Show.php:75:        $customer = Auth::guard('customer')->user();
app/Livewire/Storefront/Products/Show.php:76:        $customer = $customer instanceof Customer ? $customer : null;
app/Livewire/Storefront/Products/Show.php:79:            app(CartService::class)->getOrCreateForSession($store, $customer),
app/Livewire/Storefront/Products/Show.php:109:            ->with(['options.values', 'variants.inventoryItem', 'variants.optionValues.option', 'collections'])
app/Services/ThemeSettingsService.php:71:                    'primary_url' => '/collections/new-arrivals',
app/Services/ThemeSettingsService.php:72:                    'secondary_label' => 'View collections',
app/Services/ThemeSettingsService.php:73:                    'secondary_url' => '/collections',
app/Services/ThemeSettingsService.php:76:                'featured_collection_limit' => 4,
app/Livewire/Storefront/Account/Auth/Login.php:40:        $key = 'customer-login:'.request()->ip();
app/Livewire/Storefront/Account/Auth/Login.php:52:        if (! Auth::guard('customer')->attempt([
app/Livewire/Storefront/Account/Auth/Login.php:69:        $customer = Auth::guard('customer')->user();
app/Livewire/Storefront/Account/Auth/Login.php:71:        if ($customer instanceof Customer && session()->has('cart_id')) {
app/Livewire/Storefront/Account/Auth/Login.php:72:            app(CartService::class)->getOrCreateForSession(Store::query()->findOrFail($this->storeId), $customer);
app/Models/CustomerAddress.php:20:        'customer_id',
app/Models/CustomerAddress.php:29:    public function customer(): BelongsTo
app/Livewire/Storefront/Home.php:41:            ->limit((int) data_get($this->themeSettings(), 'home.featured_collection_limit', 4))
app/Livewire/Storefront/Checkout/Show.php:82:        $this->email = $this->customer()?->email ?? '';
app/Livewire/Storefront/Checkout/Show.php:271:        $cart = app(CartService::class)->currentForSession($this->store(), $this->customer());
app/Livewire/Storefront/Checkout/Show.php:294:            $checkout = app(CheckoutService::class)->createFromCart($cart, $this->customer());
app/Livewire/Storefront/Checkout/Show.php:390:    private function customer(): ?Customer
app/Livewire/Storefront/Checkout/Show.php:392:        $customer = Auth::guard('customer')->user();
app/Livewire/Storefront/Checkout/Show.php:394:        return $customer instanceof Customer ? $customer : null;
app/Livewire/Storefront/Cart/Show.php:158:        $cart = app(CartService::class)->currentForSession($this->store(), $this->customer());
app/Livewire/Storefront/Cart/Show.php:286:    private function customer(): ?Customer
app/Livewire/Storefront/Cart/Show.php:288:        $customer = Auth::guard('customer')->user();
app/Livewire/Storefront/Cart/Show.php:290:        return $customer instanceof Customer ? $customer : null;
app/Livewire/Storefront/CartDrawer.php:90:        $cart = app(CartService::class)->currentForSession($this->store(), $this->customer());
app/Livewire/Storefront/CartDrawer.php:126:    private function customer(): ?Customer
app/Livewire/Storefront/CartDrawer.php:128:        $customer = Auth::guard('customer')->user();
app/Livewire/Storefront/CartDrawer.php:130:        return $customer instanceof Customer ? $customer : null;
app/Models/Discount.php:31:        'rules_json',
app/Models/Discount.php:42:        'rules_json' => '{}',
app/Models/Discount.php:67:            'rules_json' => 'array',
app/Models/Order.php:26:        'customer_id',
app/Models/Order.php:78:    public function customer(): BelongsTo
app/Models/Cart.php:22:        'customer_id',
app/Models/Cart.php:48:    public function customer(): BelongsTo
app/Models/Product.php:76:    public function collections(): BelongsToMany
app/Models/Product.php:78:        return $this->belongsToMany(Collection::class, 'collection_products')
app/Models/Checkout.php:22:        'customer_id',
app/Models/Checkout.php:61:    public function customer(): BelongsTo
app/Models/Collection.php:51:        return $this->belongsToMany(Product::class, 'collection_products')
app/Http/Resources/Admin/V1/OrderResource.php:22:            'customer' => $this->whenLoaded('customer', fn () => $this->customer ? [
app/Http/Resources/Admin/V1/OrderResource.php:23:                'id' => $this->customer->id,
app/Http/Resources/Admin/V1/OrderResource.php:24:                'name' => $this->customer->name,
app/Http/Resources/Admin/V1/OrderResource.php:25:                'email' => $this->customer->email,
app/Http/Resources/Admin/V1/OrderResource.php:57:            'refunds' => RefundResource::collection($this->whenLoaded('refunds')),
app/Http/Resources/Admin/V1/OrderResource.php:58:            'fulfillments' => FulfillmentResource::collection($this->whenLoaded('fulfillments')),
app/Enums/NavigationItemType.php:9:    case Collection = 'collection';
app/Http/Resources/Storefront/V1/CartResource.php:22:            'customer_id' => $this->customer_id,
app/Http/Resources/Storefront/V1/CartResource.php:33:            'lines' => CartLineResource::collection($lines),
app/Http/Resources/Storefront/V1/CheckoutResource.php:21:            'customer_id' => $this->customer_id,
app/Http/Resources/Storefront/V1/CheckoutResource.php:31:            'available_shipping_rates' => ShippingRateResource::collection($this->whenLoaded('availableRates')),


rg -n "function save|redirect" app/Livewire/Admin/Collections/Form.php app/Livewire/Admin/Products/Form.php


rg -n "function save|redirect" app/Livewire/Admin/Collections/Form.php app/Livewire/Admin/Products/Form.php

rg -n 'function save|redirect' app/Livewire/Admin/Collections/Form.php app/Livewire/Admin/Products/Form.php

Question: function save|redirect

app/Livewire/Admin/Collections/Form.php:69:    public function save(): void
app/Livewire/Admin/Products/Form.php:100:    public function save(): void
app/Livewire/Admin/Products/Form.php:173:        $this->redirectRoute('admin.products.index', navigate: true);


Output:
Chunk ID: 1537b2
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 66
Output:
app/Livewire/Admin/Collections/Form.php:69:    public function save(): void
app/Livewire/Admin/Products/Form.php:100:    public function save(): void
app/Livewire/Admin/Products/Form.php:173:        $this->redirectRoute('admin.products.index', navigate: true);


sed -n '60,115p' app/Livewire/Admin/Collections/Form.php && sed -n '90,180p' app/Livewire/Admin/Products/Form.php


sed -n '60,115p' app/Livewire/Admin/Collections/Form.php && sed -n '90,180p' app/Livewire/Admin/Products/Form.php

Form.php

    public function removeProduct(int $productId): void
    {
        $this->assignedProductIds = array_values(array_filter(
            $this->assignedProductIds,
            fn (int $assignedProductId): bool => $assignedProductId !== $productId,
        ));
    }

    public function save(): void
    {
        $store = app('current_store');
        abort_unless($store instanceof Store, 404);

        $this->validate([
            'title' => ['required', 'string', 'max:255'],
            'handle' => [
                'required',
                'string',
                'max:255',
                Rule::unique('collections', 'handle')
                    ->where('store_id', $store->getKey())
                    ->ignore($this->collection?->getKey()),
            ],
            'descriptionHtml' => ['nullable', 'string', 'max:65535'],
            'status' => ['required', Rule::in(['draft', 'active', 'archived'])],
        ]);

        $attributes = [
            'store_id' => $store->getKey(),
            'title' => $this->title,
            'handle' => Str::slug($this->handle),
            'description_html' => $this->descriptionHtml ?: null,
            'type' => 'manual',
            'status' => $this->status,
        ];

        $collection = $this->collection instanceof Collection
            ? tap($this->collection)->update($attributes)
            : Collection::query()->create($attributes);

        $collection->products()->sync(collect($this->assignedProductIds)
            ->values()
            ->mapWithKeys(fn (int $productId, int $position): array => [$productId => ['position' => $position]])
            ->all());

        $this->collection = $collection->refresh()->load('products');
        $this->fillFromCollection($this->collection);

        session()->flash('status', 'Collection saved');
        $this->dispatch('toast', type: 'success', message: __('Collection saved'));
    }

    public function searchResults(): SupportCollection
    {
        if (trim($this->productSearch) === '') {
            'values' => '',
        ];
    }

    public function removeOption(int $index): void
    {
        unset($this->options[$index]);
        $this->options = array_values($this->options);
    }

    public function save(): void
    {
        $store = app('current_store');
        abort_unless($store instanceof Store, 404);

        $this->validate([
            'title' => ['required', 'string', 'max:255'],
            'descriptionHtml' => ['nullable', 'string', 'max:65535'],
            'status' => ['required', Rule::in(['draft', 'active', 'archived'])],
            'vendor' => ['nullable', 'string', 'max:255'],
            'productType' => ['nullable', 'string', 'max:255'],
            'tags' => ['nullable', 'string'],
            'handle' => [
                'required',
                'string',
                'max:255',
                Rule::unique('products', 'handle')
                    ->where('store_id', $store->getKey())
                    ->ignore($this->product?->getKey()),
            ],
            'variants' => ['required', 'array', 'min:1'],
            'variants.*.sku' => ['nullable', 'string', 'max:255'],
            'variants.*.price' => ['required', 'numeric', 'min:0'],
            'variants.*.compareAtPrice' => ['nullable', 'numeric', 'min:0'],
            'variants.*.quantity' => ['required', 'integer', 'min:0'],
            'variants.*.requiresShipping' => ['boolean'],
        ]);

        if (! $this->validateActiveVariantPricing() || ! $this->validateVariantSkus($store)) {
            return;
        }

        $product = DB::transaction(function () use ($store): Product {
            $productService = app(ProductService::class);

            if ($this->product === null) {
                $product = $productService->create($store, [
                    ...$this->productPayload(),
                    'options' => $this->optionPayload(),
                    'variants' => $this->variantPayload($store),
                ]);
            } else {
                $payload = $this->productPayload();
                $requestedStatus = ProductStatus::from((string) $payload['status']);
                unset($payload['status']);

                $product = $productService->update($this->product, $payload);
                $this->syncExistingVariants($product, $store);

                if ($requestedStatus !== $product->refresh()->status) {
                    $productService->transitionStatus($product, $requestedStatus);
                }
            }

            $product->collections()->sync($this->collectionIds);

            return $product->refresh();
        });

        $this->product = $product->load(['options.values', 'variants.inventoryItem', 'variants.optionValues.option', 'collections']);
        $this->fillFromProduct($this->product);

        session()->flash('status', 'Product saved');
        $this->dispatch('toast', type: 'success', message: __('Product saved'));
    }

    public function deleteProduct(): void
    {
        abort_unless($this->product instanceof Product, 404);

        app(ProductService::class)->transitionStatus($this->product, ProductStatus::Archived);

        session()->flash('status', 'Product saved');
        $this->redirectRoute('admin.products.index', navigate: true);
    }

    public function render(): mixed
    {
        return view('livewire.admin.products.form', [
            'availableCollections' => Collection::query()->orderBy('title')->get(),
            'isEditing' => $this->product !== null,


Output:
Chunk ID: a228bd
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1393
Output:

    public function removeProduct(int $productId): void
    {
        $this->assignedProductIds = array_values(array_filter(
            $this->assignedProductIds,
            fn (int $assignedProductId): bool => $assignedProductId !== $productId,
        ));
    }

    public function save(): void
    {
        $store = app('current_store');
        abort_unless($store instanceof Store, 404);

        $this->validate([
            'title' => ['required', 'string', 'max:255'],
            'handle' => [
                'required',
                'string',
                'max:255',
                Rule::unique('collections', 'handle')
                    ->where('store_id', $store->getKey())
                    ->ignore($this->collection?->getKey()),
            ],
            'descriptionHtml' => ['nullable', 'string', 'max:65535'],
            'status' => ['required', Rule::in(['draft', 'active', 'archived'])],
        ]);

        $attributes = [
            'store_id' => $store->getKey(),
            'title' => $this->title,
            'handle' => Str::slug($this->handle),
            'description_html' => $this->descriptionHtml ?: null,
            'type' => 'manual',
            'status' => $this->status,
        ];

        $collection = $this->collection instanceof Collection
            ? tap($this->collection)->update($attributes)
            : Collection::query()->create($attributes);

        $collection->products()->sync(collect($this->assignedProductIds)
            ->values()
            ->mapWithKeys(fn (int $productId, int $position): array => [$productId => ['position' => $position]])
            ->all());

        $this->collection = $collection->refresh()->load('products');
        $this->fillFromCollection($this->collection);

        session()->flash('status', 'Collection saved');
        $this->dispatch('toast', type: 'success', message: __('Collection saved'));
    }

    public function searchResults(): SupportCollection
    {
        if (trim($this->productSearch) === '') {
            'values' => '',
        ];
    }

    public function removeOption(int $index): void
    {
        unset($this->options[$index]);
        $this->options = array_values($this->options);
    }

    public function save(): void
    {
        $store = app('current_store');
        abort_unless($store instanceof Store, 404);

        $this->validate([
            'title' => ['required', 'string', 'max:255'],
            'descriptionHtml' => ['nullable', 'string', 'max:65535'],
            'status' => ['required', Rule::in(['draft', 'active', 'archived'])],
            'vendor' => ['nullable', 'string', 'max:255'],
            'productType' => ['nullable', 'string', 'max:255'],
            'tags' => ['nullable', 'string'],
            'handle' => [
                'required',
                'string',
                'max:255',
                Rule::unique('products', 'handle')
                    ->where('store_id', $store->getKey())
                    ->ignore($this->product?->getKey()),
            ],
            'variants' => ['required', 'array', 'min:1'],
            'variants.*.sku' => ['nullable', 'string', 'max:255'],
            'variants.*.price' => ['required', 'numeric', 'min:0'],
            'variants.*.compareAtPrice' => ['nullable', 'numeric', 'min:0'],
            'variants.*.quantity' => ['required', 'integer', 'min:0'],
            'variants.*.requiresShipping' => ['boolean'],
        ]);

        if (! $this->validateActiveVariantPricing() || ! $this->validateVariantSkus($store)) {
            return;
        }

        $product = DB::transaction(function () use ($store): Product {
            $productService = app(ProductService::class);

            if ($this->product === null) {
                $product = $productService->create($store, [
                    ...$this->productPayload(),
                    'options' => $this->optionPayload(),
                    'variants' => $this->variantPayload($store),
                ]);
            } else {
                $payload = $this->productPayload();
                $requestedStatus = ProductStatus::from((string) $payload['status']);
                unset($payload['status']);

                $product = $productService->update($this->product, $payload);
                $this->syncExistingVariants($product, $store);

                if ($requestedStatus !== $product->refresh()->status) {
                    $productService->transitionStatus($product, $requestedStatus);
                }
            }

            $product->collections()->sync($this->collectionIds);

            return $product->refresh();
        });

        $this->product = $product->load(['options.values', 'variants.inventoryItem', 'variants.optionValues.option', 'collections']);
        $this->fillFromProduct($this->product);

        session()->flash('status', 'Product saved');
        $this->dispatch('toast', type: 'success', message: __('Product saved'));
    }

    public function deleteProduct(): void
    {
        abort_unless($this->product instanceof Product, 404);

        app(ProductService::class)->transitionStatus($this->product, ProductStatus::Archived);

        session()->flash('status', 'Product saved');
        $this->redirectRoute('admin.products.index', navigate: true);
    }

    public function render(): mixed
    {
        return view('livewire.admin.products.form', [
            'availableCollections' => Collection::query()->orderBy('title')->get(),
            'isEditing' => $this->product !== null,


For discounts I’ll mirror the existing product/collection admin pattern: index plus create/edit form, store-scoped validation, rule JSON for minimums and product/collection targeting, and tests around listing, saving, and scoping.


php artisan make:livewire Admin/Discounts/Index --class --no-interaction


php artisan make:livewire Admin/Discounts/Form --class --no-interaction


php artisan make:test --pest Admin/DiscountManagementTest --no-interaction


Output:
Chunk ID: 56ca8f
Wall time: 0.1493 seconds
Process exited with code 0
Original token count: 24
Output:

   INFO  Livewire component [app/Livewire/Admin/Discounts/Index.php] created successfully.


Output:
Chunk ID: 82cc8a
Wall time: 0.1352 seconds
Process exited with code 0
Original token count: 24
Output:

   INFO  Livewire component [app/Livewire/Admin/Discounts/Form.php] created successfully.


Output:
Chunk ID: cc1f8b
Wall time: 0.1337 seconds
Process exited with code 0
Original token count: 23
Output:

   INFO  Test [tests/Feature/Admin/DiscountManagementTest.php] created successfully.


php artisan make:test --pest Admin/DiscountManagementTest --no-interaction

INFO  Test [tests/Feature/Admin/DiscountManagementTest.php] created successfully.


php artisan make:livewire Admin/Discounts/Index --class --no-interaction

INFO  Livewire component [app/Livewire/Admin/Discounts/Index.php] created successfully.


php artisan make:livewire Admin/Discounts/Form --class --no-interaction

INFO  Livewire component [app/Livewire/Admin/Discounts/Form.php] created successfully.


Inspect discount admin spec and discount data model

Build discount routes, sidebar, list/form components

Run formatter, affected/full tests, build, browser, progress, commit

Explanation: The customer slice is committed; the active slice is now discount administration from Spec 03.


Output: Plan updated


Input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Discounts/Index.php
@@
 <?php

 namespace App\Livewire\Admin\Discounts;

+use App\Enums\DiscountStatus;
+use App\Enums\DiscountValueType;
+use App\Models\Discount;
+use App\Models\Store;
+use App\Support\Money;
+use Illuminate\Contracts\Pagination\LengthAwarePaginator;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Support\Str;
+use Livewire\Attributes\Locked;
 use Livewire\Component;
+use Livewire\WithPagination;

 class Index extends Component
 {
-    public function render()
+    use WithPagination;
+
+    #[Locked]
+    public int $storeId;
+
+    public string $storeCurrency = 'EUR';
+
+    public string $search = '';
+
+    public string $statusFilter = 'all';
+
+    public function mount(): void
+    {
+        $store = app('current_store');
+
+        abort_unless($store instanceof Store, 404);
+
+        $this->storeId = $store->getKey();
+        $this->storeCurrency = $store->default_currency;
+    }
+
+    public function updatedSearch(): void
+    {
+        $this->resetPage();
+    }
+
+    public function updatedStatusFilter(): void
+    {
+        $this->resetPage();
+    }
+
+    public function discounts(): LengthAwarePaginator
+    {
+        return Discount::withoutGlobalScopes()
+            ->where('store_id', $this->storeId)
+            ->when($this->search !== '', function (Builder $query): void {
+                $search = '%'.$this->search.'%';
+
+                $query->where(function (Builder $query) use ($search): void {
+                    $query
+                        ->where('code', 'like', $search)
+                        ->orWhere('type', 'like', $search);
+                });
+            })
+            ->when($this->statusFilter !== 'all', fn (Builder $query) => $this->applyStatusFilter($query))
+            ->latest('starts_at')
+            ->latest('id')
+            ->paginate(20);
+    }
+
+    public function valueLabel(Discount $discount): string
+    {
+        return match ($discount->value_type) {
+            DiscountValueType::Percent => $discount->value_amount.'%',
+            DiscountValueType::Fixed => Money::format($discount->value_amount, $this->storeCurrency),
+            DiscountValueType::FreeShipping => 'Free shipping',
+        };
+    }
+
+    public function effectiveStatus(Discount $discount): string
+    {
+        if ($discount->status === DiscountStatus::Disabled) {
+            return 'disabled';
+        }
+
+        if ($discount->starts_at->isFuture()) {
+            return 'scheduled';
+        }
+
+        if ($discount->status === DiscountStatus::Expired || ($discount->ends_at !== null && $discount->ends_at->isPast())) {
+            return 'expired';
+        }
+
+        return $discount->status->value;
+    }
+
+    public function statusColor(string $status): string
+    {
+        return match ($status) {
+            'active' => 'green',
+            'expired' => 'red',
+            'scheduled' => 'amber',
+            default => 'zinc',
+        };
+    }
+
+    public function render(): mixed
     {
-        return view('livewire.admin.discounts.index');
+        return view('livewire.admin.discounts.index', [
+            'discounts' => $this->discounts(),
+        ])->layout('layouts.app', [
+            'title' => __('Discounts'),
+        ]);
+    }
+
+    private function applyStatusFilter(Builder $query): void
+    {
+        match ($this->statusFilter) {
+            'active' => $query
+                ->where('status', DiscountStatus::Active)
+                ->where('starts_at', '<=', now())
+                ->where(function (Builder $query): void {
+                    $query->whereNull('ends_at')->orWhere('ends_at', '>=', now());
+                }),
+            'expired' => $query->where(function (Builder $query): void {
+                $query->where('status', DiscountStatus::Expired)->orWhere('ends_at', '<', now());
+            }),
+            'scheduled' => $query->where('starts_at', '>', now()),
+            default => null,
+        };
     }
 }
*** End Patch


Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Discounts/Index.php

@@ -4,3 +4,13 @@

+use App\Enums\DiscountStatus;
+use App\Enums\DiscountValueType;
+use App\Models\Discount;
+use App\Models\Store;
+use App\Support\Money;
+use Illuminate\Contracts\Pagination\LengthAwarePaginator;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Support\Str;
+use Livewire\Attributes\Locked;
 use Livewire\Component;
+use Livewire\WithPagination;

@@ -8,5 +18,112 @@
 {
-    public function render()
+    use WithPagination;
+
+    #[Locked]
+    public int $storeId;
+
+    public string $storeCurrency = 'EUR';
+
+    public string $search = '';
+
+    public string $statusFilter = 'all';
+
+    public function mount(): void
     {
-        return view('livewire.admin.discounts.index');
+        $store = app('current_store');
+
+        abort_unless($store instanceof Store, 404);
+
+        $this->storeId = $store->getKey();
+        $this->storeCurrency = $store->default_currency;
+    }
+
+    public function updatedSearch(): void
+    {
+        $this->resetPage();
+    }
+
+    public function updatedStatusFilter(): void
+    {
+        $this->resetPage();
+    }
+
+    public function discounts(): LengthAwarePaginator
+    {
+        return Discount::withoutGlobalScopes()
+            ->where('store_id', $this->storeId)
+            ->when($this->search !== '', function (Builder $query): void {
+                $search = '%'.$this->search.'%';
+
+                $query->where(function (Builder $query) use ($search): void {
+                    $query
+                        ->where('code', 'like', $search)
+                        ->orWhere('type', 'like', $search);
+                });
+            })
+            ->when($this->statusFilter !== 'all', fn (Builder $query) => $this->applyStatusFilter($query))
+            ->latest('starts_at')
+            ->latest('id')
+            ->paginate(20);
+    }
+
+    public function valueLabel(Discount $discount): string
+    {
+        return match ($discount->value_type) {
+            DiscountValueType::Percent => $discount->value_amount.'%',
+            DiscountValueType::Fixed => Money::format($discount->value_amount, $this->storeCurrency),
+            DiscountValueType::FreeShipping => 'Free shipping',
+        };
+    }
+
+    public function effectiveStatus(Discount $discount): string
+    {
+        if ($discount->status === DiscountStatus::Disabled) {
+            return 'disabled';
+        }
+
+        if ($discount->starts_at->isFuture()) {
+            return 'scheduled';
+        }
+
+        if ($discount->status === DiscountStatus::Expired || ($discount->ends_at !== null && $discount->ends_at->isPast())) {
+            return 'expired';
+        }
+
+        return $discount->status->value;
+    }
+
+    public function statusColor(string $status): string
+    {
+        return match ($status) {
+            'active' => 'green',
+            'expired' => 'red',
+            'scheduled' => 'amber',
+            default => 'zinc',
+        };
+    }
+
+    public function render(): mixed
+    {
+        return view('livewire.admin.discounts.index', [
+            'discounts' => $this->discounts(),
+        ])->layout('layouts.app', [
+            'title' => __('Discounts'),
+        ]);
+    }
+
+    private function applyStatusFilter(Builder $query): void
+    {
+        match ($this->statusFilter) {
+            'active' => $query
+                ->where('status', DiscountStatus::Active)
+                ->where('starts_at', '<=', now())
+                ->where(function (Builder $query): void {
+                    $query->whereNull('ends_at')->orWhere('ends_at', '>=', now());
+                }),
+            'expired' => $query->where(function (Builder $query): void {
+                $query->where('status', DiscountStatus::Expired)->orWhere('ends_at', '<', now());
+            }),
+            'scheduled' => $query->where('starts_at', '>', now()),
+            default => null,
+        };
     }


Output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Discounts/Index.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Discounts/Form.php
@@
 <?php

 namespace App\Livewire\Admin\Discounts;

+use App\Enums\DiscountStatus;
+use App\Enums\DiscountType;
+use App\Enums\DiscountValueType;
+use App\Models\Collection as ProductCollection;
+use App\Models\Discount;
+use App\Models\Product;
+use App\Models\Store;
+use App\Support\Money;
+use Illuminate\Support\Collection as SupportCollection;
+use Illuminate\Support\Str;
+use Illuminate\Validation\Rule;
 use Livewire\Component;

 class Form extends Component
 {
-    public function render()
+    public ?Discount $discount = null;
+
+    public string $type = 'code';
+
+    public string $code = '';
+
+    public string $valueType = 'percent';
+
+    public string $valueAmount = '10';
+
+    public string $minimumPurchaseAmount = '';
+
+    /**
+     * @var array<int, int>
+     */
+    public array $specificProductIds = [];
+
+    /**
+     * @var array<int, int>
+     */
+    public array $specificCollectionIds = [];
+
+    public string $usageLimit = '';
+
+    public bool $onePerCustomer = false;
+
+    public string $startsAt = '';
+
+    public string $endsAt = '';
+
+    public bool $isActive = true;
+
+    public string $productSearch = '';
+
+    public string $collectionSearch = '';
+
+    public string $storeCurrency = 'EUR';
+
+    public function mount(?Discount $discount = null): void
+    {
+        $store = app('current_store');
+
+        abort_unless($store instanceof Store, 404);
+
+        $this->storeCurrency = $store->default_currency;
+
+        if ($discount?->exists) {
+            abort_unless((int) $discount->store_id === $store->getKey(), 404);
+
+            $this->discount = $discount;
+            $this->fillFromDiscount($discount);
+
+            return;
+        }
+
+        $this->startsAt = now()->format('Y-m-d\TH:i');
+    }
+
+    public function save(): void
+    {
+        $store = app('current_store');
+
+        abort_unless($store instanceof Store, 404);
+
+        $this->validate([
+            'type' => ['required', Rule::in(['code', 'automatic'])],
+            'code' => [
+                Rule::requiredIf($this->type === 'code'),
+                'nullable',
+                'string',
+                'max:255',
+                Rule::unique('discounts', 'code')
+                    ->where('store_id', $store->getKey())
+                    ->ignore($this->discount?->getKey()),
+            ],
+            'valueType' => ['required', Rule::in(['percent', 'fixed', 'free_shipping'])],
+            'valueAmount' => [Rule::requiredIf($this->valueType !== 'free_shipping'), 'nullable', 'numeric', 'min:0.01'],
+            'minimumPurchaseAmount' => ['nullable', 'numeric', 'min:0'],
+            'usageLimit' => ['nullable', 'integer', 'min:1'],
+            'startsAt' => ['required', 'date'],
+            'endsAt' => ['nullable', 'date', 'after:startsAt'],
+            'onePerCustomer' => ['boolean'],
+            'isActive' => ['boolean'],
+        ]);
+
+        $discount = $this->discount instanceof Discount
+            ? tap($this->discount)->update($this->payload($store))
+            : Discount::withoutGlobalScopes()->create($this->payload($store));
+
+        $this->discount = $discount->refresh();
+        $this->fillFromDiscount($this->discount);
+
+        session()->flash('status', 'Discount saved');
+        $this->dispatch('toast', type: 'success', message: __('Discount saved'));
+    }
+
+    public function generateCode(): void
+    {
+        do {
+            $code = Str::upper(Str::random(8));
+        } while (Discount::withoutGlobalScopes()
+            ->where('store_id', $this->storeId())
+            ->where('code', $code)
+            ->exists());
+
+        $this->code = $code;
+    }
+
+    public function addProduct(int $productId): void
+    {
+        abort_unless($this->productBelongsToStore($productId), 404);
+
+        $this->specificProductIds = collect([...$this->specificProductIds, $productId])
+            ->unique()
+            ->values()
+            ->all();
+        $this->productSearch = '';
+    }
+
+    public function removeProduct(int $productId): void
+    {
+        $this->specificProductIds = array_values(array_filter(
+            $this->specificProductIds,
+            fn (int $selectedProductId): bool => $selectedProductId !== $productId,
+        ));
+    }
+
+    public function addCollection(int $collectionId): void
+    {
+        abort_unless($this->collectionBelongsToStore($collectionId), 404);
+
+        $this->specificCollectionIds = collect([...$this->specificCollectionIds, $collectionId])
+            ->unique()
+            ->values()
+            ->all();
+        $this->collectionSearch = '';
+    }
+
+    public function removeCollection(int $collectionId): void
+    {
+        $this->specificCollectionIds = array_values(array_filter(
+            $this->specificCollectionIds,
+            fn (int $selectedCollectionId): bool => $selectedCollectionId !== $collectionId,
+        ));
+    }
+
+    public function productResults(): SupportCollection
+    {
+        if (trim($this->productSearch) === '') {
+            return collect();
+        }
+
+        return Product::withoutGlobalScopes()
+            ->where('store_id', $this->storeId())
+            ->whereNotIn('id', $this->specificProductIds)
+            ->where('title', 'like', '%'.$this->productSearch.'%')
+            ->orderBy('title')
+            ->limit(5)
+            ->get();
+    }
+
+    public function collectionResults(): SupportCollection
+    {
+        if (trim($this->collectionSearch) === '') {
+            return collect();
+        }
+
+        return ProductCollection::withoutGlobalScopes()
+            ->where('store_id', $this->storeId())
+            ->whereNotIn('id', $this->specificCollectionIds)
+            ->where('title', 'like', '%'.$this->collectionSearch.'%')
+            ->orderBy('title')
+            ->limit(5)
+            ->get();
+    }
+
+    public function selectedProducts(): SupportCollection
+    {
+        return Product::withoutGlobalScopes()
+            ->where('store_id', $this->storeId())
+            ->whereKey($this->specificProductIds)
+            ->orderBy('title')
+            ->get();
+    }
+
+    public function selectedCollections(): SupportCollection
+    {
+        return ProductCollection::withoutGlobalScopes()
+            ->where('store_id', $this->storeId())
+            ->whereKey($this->specificCollectionIds)
+            ->orderBy('title')
+            ->get();
+    }
+
+    public function render(): mixed
     {
-        return view('livewire.admin.discounts.form');
+        return view('livewire.admin.discounts.form', [
+            'isEditing' => $this->discount instanceof Discount,
+            'productResults' => $this->productResults(),
+            'collectionResults' => $this->collectionResults(),
+            'selectedProducts' => $this->selectedProducts(),
+            'selectedCollections' => $this->selectedCollections(),
+        ])->layout('layouts.app', [
+            'title' => $this->discount ? __('Edit discount') : __('Create discount'),
+        ]);
+    }
+
+    private function fillFromDiscount(Discount $discount): void
+    {
+        $this->type = $discount->type->value;
+        $this->code = (string) $discount->code;
+        $this->valueType = $discount->value_type->value;
+        $this->valueAmount = $discount->value_type === DiscountValueType::Fixed
+            ? number_format($discount->value_amount / 100, 2, '.', '')
+            : (string) $discount->value_amount;
+        $this->minimumPurchaseAmount = data_get($discount->rules_json, 'min_purchase_amount')
+            ? number_format(((int) data_get($discount->rules_json, 'min_purchase_amount')) / 100, 2, '.', '')
+            : '';
+        $this->specificProductIds = collect(data_get($discount->rules_json, 'applicable_product_ids', []))
+            ->map(fn (mixed $id): int => (int) $id)
+            ->all();
+        $this->specificCollectionIds = collect(data_get($discount->rules_json, 'applicable_collection_ids', []))
+            ->map(fn (mixed $id): int => (int) $id)
+            ->all();
+        $this->usageLimit = $discount->usage_limit !== null ? (string) $discount->usage_limit : '';
+        $this->onePerCustomer = (bool) data_get($discount->rules_json, 'one_per_customer', false);
+        $this->startsAt = $discount->starts_at->format('Y-m-d\TH:i');
+        $this->endsAt = $discount->ends_at?->format('Y-m-d\TH:i') ?? '';
+        $this->isActive = $discount->status !== DiscountStatus::Disabled;
+    }
+
+    /**
+     * @return array<string, mixed>
+     */
+    private function payload(Store $store): array
+    {
+        return [
+            'store_id' => $store->getKey(),
+            'type' => DiscountType::from($this->type),
+            'code' => $this->type === 'code' ? Str::upper(trim($this->code)) : null,
+            'value_type' => DiscountValueType::from($this->valueType),
+            'value_amount' => $this->normalizedValueAmount(),
+            'starts_at' => $this->startsAt,
+            'ends_at' => $this->endsAt !== '' ? $this->endsAt : null,
+            'usage_limit' => $this->usageLimit !== '' ? (int) $this->usageLimit : null,
+            'rules_json' => $this->rulesPayload(),
+            'status' => $this->isActive ? DiscountStatus::Active : DiscountStatus::Disabled,
+        ];
+    }
+
+    /**
+     * @return array<string, mixed>
+     */
+    private function rulesPayload(): array
+    {
+        return [
+            'min_purchase_amount' => $this->minimumPurchaseAmount !== '' ? Money::fromDecimalString($this->minimumPurchaseAmount) : 0,
+            'applicable_product_ids' => $this->validProductIds(),
+            'applicable_collection_ids' => $this->validCollectionIds(),
+            'one_per_customer' => $this->onePerCustomer,
+            'customer_eligibility' => 'all',
+        ];
+    }
+
+    private function normalizedValueAmount(): int
+    {
+        return match ($this->valueType) {
+            'fixed' => Money::fromDecimalString($this->valueAmount),
+            'free_shipping' => 0,
+            default => (int) round((float) $this->valueAmount),
+        };
+    }
+
+    /**
+     * @return array<int, int>
+     */
+    private function validProductIds(): array
+    {
+        return Product::withoutGlobalScopes()
+            ->where('store_id', $this->storeId())
+            ->whereKey($this->specificProductIds)
+            ->pluck('id')
+            ->map(fn (int $id): int => $id)
+            ->all();
+    }
+
+    /**
+     * @return array<int, int>
+     */
+    private function validCollectionIds(): array
+    {
+        return ProductCollection::withoutGlobalScopes()
+            ->where('store_id', $this->storeId())
+            ->whereKey($this->specificCollectionIds)
+            ->pluck('id')
+            ->map(fn (int $id): int => $id)
+            ->all();
+    }
+
+    private function productBelongsToStore(int $productId): bool
+    {
+        return Product::withoutGlobalScopes()
+            ->where('store_id', $this->storeId())
+            ->whereKey($productId)
+            ->exists();
+    }
+
+    private function collectionBelongsToStore(int $collectionId): bool
+    {
+        return ProductCollection::withoutGlobalScopes()
+            ->where('store_id', $this->storeId())
+            ->whereKey($collectionId)
+            ->exists();
+    }
+
+    private function storeId(): int
+    {
+        $store = app('current_store');
+
+        abort_unless($store instanceof Store, 404);
+
+        return $store->getKey();
     }
 }
*** End Patch


Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Discounts/Form.php

@@ -4,2 +4,13 @@

+use App\Enums\DiscountStatus;
+use App\Enums\DiscountType;
+use App\Enums\DiscountValueType;
+use App\Models\Collection as ProductCollection;
+use App\Models\Discount;
+use App\Models\Product;
+use App\Models\Store;
+use App\Support\Money;
+use Illuminate\Support\Collection as SupportCollection;
+use Illuminate\Support\Str;
+use Illuminate\Validation\Rule;
 use Livewire\Component;
@@ -8,5 +19,324 @@
 {
-    public function render()
+    public ?Discount $discount = null;
+
+    public string $type = 'code';
+
+    public string $code = '';
+
+    public string $valueType = 'percent';
+
+    public string $valueAmount = '10';
+
+    public string $minimumPurchaseAmount = '';
+
+    /**
+     * @var array<int, int>
+     */
+    public array $specificProductIds = [];
+
+    /**
+     * @var array<int, int>
+     */
+    public array $specificCollectionIds = [];
+
+    public string $usageLimit = '';
+
+    public bool $onePerCustomer = false;
+
+    public string $startsAt = '';
+
+    public string $endsAt = '';
+
+    public bool $isActive = true;
+
+    public string $productSearch = '';
+
+    public string $collectionSearch = '';
+
+    public string $storeCurrency = 'EUR';
+
+    public function mount(?Discount $discount = null): void
+    {
+        $store = app('current_store');
+
+        abort_unless($store instanceof Store, 404);
+
+        $this->storeCurrency = $store->default_currency;
+
+        if ($discount?->exists) {
+            abort_unless((int) $discount->store_id === $store->getKey(), 404);
+
+            $this->discount = $discount;
+            $this->fillFromDiscount($discount);
+
+            return;
+        }
+
+        $this->startsAt = now()->format('Y-m-d\TH:i');
+    }
+
+    public function save(): void
+    {
+        $store = app('current_store');
+
+        abort_unless($store instanceof Store, 404);
+
+        $this->validate([
+            'type' => ['required', Rule::in(['code', 'automatic'])],
+            'code' => [
+                Rule::requiredIf($this->type === 'code'),
+                'nullable',
+                'string',
+                'max:255',
+                Rule::unique('discounts', 'code')
+                    ->where('store_id', $store->getKey())
+                    ->ignore($this->discount?->getKey()),
+            ],
+            'valueType' => ['required', Rule::in(['percent', 'fixed', 'free_shipping'])],
+            'valueAmount' => [Rule::requiredIf($this->valueType !== 'free_shipping'), 'nullable', 'numeric', 'min:0.01'],
+            'minimumPurchaseAmount' => ['nullable', 'numeric', 'min:0'],
+            'usageLimit' => ['nullable', 'integer', 'min:1'],
+            'startsAt' => ['required', 'date'],
+            'endsAt' => ['nullable', 'date', 'after:startsAt'],
+            'onePerCustomer' => ['boolean'],
+            'isActive' => ['boolean'],
+        ]);
+
+        $discount = $this->discount instanceof Discount
+            ? tap($this->discount)->update($this->payload($store))
+            : Discount::withoutGlobalScopes()->create($this->payload($store));
+
+        $this->discount = $discount->refresh();
+        $this->fillFromDiscount($this->discount);
+
+        session()->flash('status', 'Discount saved');
+        $this->dispatch('toast', type: 'success', message: __('Discount saved'));
+    }
+
+    public function generateCode(): void
+    {
+        do {
+            $code = Str::upper(Str::random(8));
+        } while (Discount::withoutGlobalScopes()
+            ->where('store_id', $this->storeId())
+            ->where('code', $code)
+            ->exists());
+
+        $this->code = $code;
+    }
+
+    public function addProduct(int $productId): void
+    {
+        abort_unless($this->productBelongsToStore($productId), 404);
+
+        $this->specificProductIds = collect([...$this->specificProductIds, $productId])
+            ->unique()
+            ->values()
+            ->all();
+        $this->productSearch = '';
+    }
+
+    public function removeProduct(int $productId): void
+    {
+        $this->specificProductIds = array_values(array_filter(
+            $this->specificProductIds,
+            fn (int $selectedProductId): bool => $selectedProductId !== $productId,
+        ));
+    }
+
+    public function addCollection(int $collectionId): void
+    {
+        abort_unless($this->collectionBelongsToStore($collectionId), 404);
+
+        $this->specificCollectionIds = collect([...$this->specificCollectionIds, $collectionId])
+            ->unique()
+            ->values()
+            ->all();
+        $this->collectionSearch = '';
+    }
+
+    public function removeCollection(int $collectionId): void
+    {
+        $this->specificCollectionIds = array_values(array_filter(
+            $this->specificCollectionIds,
+            fn (int $selectedCollectionId): bool => $selectedCollectionId !== $collectionId,
+        ));
+    }
+
+    public function productResults(): SupportCollection
+    {
+        if (trim($this->productSearch) === '') {
+            return collect();
+        }
+
+        return Product::withoutGlobalScopes()
+            ->where('store_id', $this->storeId())
+            ->whereNotIn('id', $this->specificProductIds)
+            ->where('title', 'like', '%'.$this->productSearch.'%')
+            ->orderBy('title')
+            ->limit(5)
+            ->get();
+    }
+
+    public function collectionResults(): SupportCollection
+    {
+        if (trim($this->collectionSearch) === '') {
+            return collect();
+        }
+
+        return ProductCollection::withoutGlobalScopes()
+            ->where('store_id', $this->storeId())
+            ->whereNotIn('id', $this->specificCollectionIds)
+            ->where('title', 'like', '%'.$this->collectionSearch.'%')
+            ->orderBy('title')
+            ->limit(5)
+            ->get();
+    }
+
+    public function selectedProducts(): SupportCollection
+    {
+        return Product::withoutGlobalScopes()
+            ->where('store_id', $this->storeId())
+            ->whereKey($this->specificProductIds)
+            ->orderBy('title')
+            ->get();
+    }
+
+    public function selectedCollections(): SupportCollection
+    {
+        return ProductCollection::withoutGlobalScopes()
+            ->where('store_id', $this->storeId())
+            ->whereKey($this->specificCollectionIds)
+            ->orderBy('title')
+            ->get();
+    }
+
+    public function render(): mixed
+    {
+        return view('livewire.admin.discounts.form', [
+            'isEditing' => $this->discount instanceof Discount,
+            'productResults' => $this->productResults(),
+            'collectionResults' => $this->collectionResults(),
+            'selectedProducts' => $this->selectedProducts(),
+            'selectedCollections' => $this->selectedCollections(),
+        ])->layout('layouts.app', [
+            'title' => $this->discount ? __('Edit discount') : __('Create discount'),
+        ]);
+    }
+
+    private function fillFromDiscount(Discount $discount): void
+    {
+        $this->type = $discount->type->value;
+        $this->code = (string) $discount->code;
+        $this->valueType = $discount->value_type->value;
+        $this->valueAmount = $discount->value_type === DiscountValueType::Fixed
+            ? number_format($discount->value_amount / 100, 2, '.', '')
+            : (string) $discount->value_amount;
+        $this->minimumPurchaseAmount = data_get($discount->rules_json, 'min_purchase_amount')
+            ? number_format(((int) data_get($discount->rules_json, 'min_purchase_amount')) / 100, 2, '.', '')
+            : '';
+        $this->specificProductIds = collect(data_get($discount->rules_json, 'applicable_product_ids', []))
+            ->map(fn (mixed $id): int => (int) $id)
+            ->all();
+        $this->specificCollectionIds = collect(data_get($discount->rules_json, 'applicable_collection_ids', []))
+            ->map(fn (mixed $id): int => (int) $id)
+            ->all();
+        $this->usageLimit = $discount->usage_limit !== null ? (string) $discount->usage_limit : '';
+        $this->onePerCustomer = (bool) data_get($discount->rules_json, 'one_per_customer', false);
+        $this->startsAt = $discount->starts_at->format('Y-m-d\TH:i');
+        $this->endsAt = $discount->ends_at?->format('Y-m-d\TH:i') ?? '';
+        $this->isActive = $discount->status !== DiscountStatus::Disabled;
+    }
+
+    /**
+     * @return array<string, mixed>
+     */
+    private function payload(Store $store): array
     {
-        return view('livewire.admin.discounts.form');
+        return [
+            'store_id' => $store->getKey(),
+            'type' => DiscountType::from($this->type),
+            'code' => $this->type === 'code' ? Str::upper(trim($this->code)) : null,
+            'value_type' => DiscountValueType::from($this->valueType),
+            'value_amount' => $this->normalizedValueAmount(),
+            'starts_at' => $this->startsAt,
+            'ends_at' => $this->endsAt !== '' ? $this->endsAt : null,
+            'usage_limit' => $this->usageLimit !== '' ? (int) $this->usageLimit : null,
+            'rules_json' => $this->rulesPayload(),
+            'status' => $this->isActive ? DiscountStatus::Active : DiscountStatus::Disabled,
+        ];
+    }
+
+    /**
+     * @return array<string, mixed>
+     */
+    private function rulesPayload(): array
+    {
+        return [
+            'min_purchase_amount' => $this->minimumPurchaseAmount !== '' ? Money::fromDecimalString($this->minimumPurchaseAmount) : 0,
+            'applicable_product_ids' => $this->validProductIds(),
+            'applicable_collection_ids' => $this->validCollectionIds(),
+            'one_per_customer' => $this->onePerCustomer,
+            'customer_eligibility' => 'all',
+        ];
+    }
+
+    private function normalizedValueAmount(): int
+    {
+        return match ($this->valueType) {
+            'fixed' => Money::fromDecimalString($this->valueAmount),
+            'free_shipping' => 0,
+            default => (int) round((float) $this->valueAmount),
+        };
+    }
+
+    /**
+     * @return array<int, int>
+     */
+    private function validProductIds(): array
+    {
+        return Product::withoutGlobalScopes()
+            ->where('store_id', $this->storeId())
+            ->whereKey($this->specificProductIds)
+            ->pluck('id')
+            ->map(fn (int $id): int => $id)
+            ->all();
+    }
+
+    /**
+     * @return array<int, int>
+     */
+    private function validCollectionIds(): array
+    {
+        return ProductCollection::withoutGlobalScopes()
+            ->where('store_id', $this->storeId())
+            ->whereKey($this->specificCollectionIds)
+            ->pluck('id')
+            ->map(fn (int $id): int => $id)
+            ->all();
+    }
+
+    private function productBelongsToStore(int $productId): bool
+    {
+        return Product::withoutGlobalScopes()
+            ->where('store_id', $this->storeId())
+            ->whereKey($productId)
+            ->exists();
+    }
+
+    private function collectionBelongsToStore(int $collectionId): bool
+    {
+        return ProductCollection::withoutGlobalScopes()
+            ->where('store_id', $this->storeId())
+            ->whereKey($collectionId)
+            ->exists();
+    }
+
+    private function storeId(): int
+    {
+        $store = app('current_store');
+
+        abort_unless($store instanceof Store, 404);
+
+        return $store->getKey();
     }


Output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/app/Livewire/Admin/Discounts/Form.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


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


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

{{-- Knowing is not enough; we must apply. Being willing is not enough; we must do. - Leonardo da Vinci --}}


Output: {"output":"Success. Updated the following files:\nD /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/discounts/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/discounts/index.blade.php
+

+

+

+            <flux:heading size="xl">Discounts</flux:heading>
+            <flux:text class="mt-1">Manage discount codes, automatic promotions, and eligibility rules.</flux:text>
+

+
+        <flux:button :href="route('admin.discounts.create')" wire:navigate variant="primary" icon="plus">
+            Create discount
+        </flux:button>
+

+
+

+        <flux:input wire:model.live.debounce.300ms="search" icon="magnifying-glass" placeholder="Search discounts..." aria-label="Search discounts" />
+
+        <flux:select wire:model.live="statusFilter" aria-label="Status filter">
+            <flux:select.option value="all">All statuses</flux:select.option>
+            <flux:select.option value="active">Active</flux:select.option>
+            <flux:select.option value="scheduled">Scheduled</flux:select.option>
+            <flux:select.option value="expired">Expired</flux:select.option>
+        </flux:select>
+

+
+

+

+

+

+

+                        Code
+                        Type
+                        Value
+                        Usage
+                        Status
+                        Dates
+

+

+

+                    @forelse ($discounts as $discount)
+                        @php
+                            $status = $this->effectiveStatus($discount);
+                            $statusColor = $this->statusColor($status);
+                            $typeColor = $discount->type->value === 'automatic' ? 'sky' : 'zinc';
+                        @endphp
+
+
getKey() }}">
+
+
+                                    {{ $discount->code ?: 'Automatic' }}
+
+
+
+                                <flux:badge :color="$typeColor">{{ Str::headline($discount->type->value) }}</flux:badge>
+
+                            {{ $this->valueLabel($discount) }}
+                            {{ $discount->usage_count }} / {{ $discount->usage_limit ?? 'unlimited' }}
+
+                                <flux:badge :color="$statusColor">{{ Str::headline($status) }}</flux:badge>
+
+
+                                {{ $discount->starts_at?->format('M j, Y') }}
+                                -
+                                {{ $discount->ends_at?->format('M j, Y') ?? 'No end' }}
+
+

+                    @empty
+

+
+

+

+                                        <flux:icon name="tag" class="size-6" />
+

+                                    <flux:heading size="lg">No discounts found</flux:heading>
+                                    <flux:text>Discounts will appear here after creation.</flux:text>
+                                    <flux:button :href="route('admin.discounts.create')" wire:navigate variant="primary">Create discount</flux:button>
+

+
+

+                    @endforelse
+

+

+

+

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

*** End Patch


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

            <flux:heading size="xl">Discounts</flux:heading>
            <flux:text class="mt-1">Manage discount codes, automatic promotions, and eligibility rules.</flux:text>

        <flux:button :href="route('admin.discounts.create')" wire:navigate variant="primary" icon="plus">
            Create discount
        </flux:button>

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

        <flux:select wire:model.live="statusFilter" aria-label="Status filter">
            <flux:select.option value="all">All statuses</flux:select.option>
            <flux:select.option value="active">Active</flux:select.option>
            <flux:select.option value="scheduled">Scheduled</flux:select.option>
            <flux:select.option value="expired">Expired</flux:select.option>
        </flux:select>

                        Code
                        Type
                        Value
                        Usage
                        Status
                        Dates

                    @forelse ($discounts as $discount)
                        @php
                            $status = $this->effectiveStatus($discount);
                            $statusColor = $this->statusColor($status);
                            $typeColor = $discount->type->value === 'automatic' ? 'sky' : 'zinc';
                        @endphp

getKey() }}">

                                    {{ $discount->code ?: 'Automatic' }}

                                <flux:badge :color="$typeColor">{{ Str::headline($discount->type->value) }}</flux:badge>

                            {{ $this->valueLabel($discount) }}
                            {{ $discount->usage_count }} / {{ $discount->usage_limit ?? 'unlimited' }}

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

                                {{ $discount->starts_at?->format('M j, Y') }}
                                -
                                {{ $discount->ends_at?->format('M j, Y') ?? 'No end' }}

                    @empty

                                        <flux:icon name="tag" class="size-6" />

                                    <flux:heading size="lg">No discounts found</flux:heading>
                                    <flux:text>Discounts will appear here after creation.</flux:text>
                                    <flux:button :href="route('admin.discounts.create')" wire:navigate variant="primary">Create discount</flux:button>

                    @endforelse

    {{ $discounts->links() }}


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


rg -n "flux:switch|flux:radio|flux:checkbox" resources/views/livewire/admin resources/views/livewire/storefront -n


rg -n "flux:switch|flux:radio|flux:checkbox" resources/views/livewire/admin resources/views/livewire/storefront -n

rg -n 'flux:switch|flux:radio|flux:checkbox' resources/views/livewire/admin resources/views/livewire/storefront -n

Question: flux:switch|flux:radio|flux:checkbox

resources/views/livewire/storefront/collections/show.blade.php:22:                <flux:checkbox wire:model.live="inStock" label="In stock" />
resources/views/livewire/storefront/collections/show.blade.php:36:                            <flux:checkbox wire:model.live="types" value="{{ $type }}" :label="$type" wire:key="type-filter-{{ $type }}" />
resources/views/livewire/storefront/collections/show.blade.php:45:                            <flux:checkbox wire:model.live="vendors" value="{{ $vendor }}" :label="$vendor" wire:key="vendor-filter-{{ $vendor }}" />
resources/views/livewire/storefront/account/auth/login.blade.php:25:        <flux:checkbox wire:model="remember" :label="__('Remember me')" />
resources/views/livewire/storefront/account/auth/register.blade.php:41:        <flux:checkbox wire:model="marketing_opt_in" :label="__('Email me about new products and offers')" />
resources/views/livewire/admin/auth/login.blade.php:25:        <flux:checkbox wire:model="remember" :label="__('Remember me')" />
resources/views/livewire/storefront/checkout/show.blade.php:75:                    <flux:checkbox wire:model="billingSame" label="Billing address is the same" class="mt-5" />
resources/views/livewire/admin/products/form.blade.php:69:                                        <flux:checkbox wire:model="variants.{{ $index }}.requiresShipping" aria-label="Requires shipping" />
resources/views/livewire/admin/products/form.blade.php:127:                        <flux:checkbox wire:model="collectionIds" value="{{ $collection->getKey() }}" :label="$collection->title" wire:key="product-collection-{{ $collection->getKey() }}" />


Output:
Chunk ID: 98bb0c
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 411
Output:
resources/views/livewire/storefront/collections/show.blade.php:22:                <flux:checkbox wire:model.live="inStock" label="In stock" />
resources/views/livewire/storefront/collections/show.blade.php:36:                            <flux:checkbox wire:model.live="types" value="{{ $type }}" :label="$type" wire:key="type-filter-{{ $type }}" />
resources/views/livewire/storefront/collections/show.blade.php:45:                            <flux:checkbox wire:model.live="vendors" value="{{ $vendor }}" :label="$vendor" wire:key="vendor-filter-{{ $vendor }}" />
resources/views/livewire/storefront/account/auth/login.blade.php:25:        <flux:checkbox wire:model="remember" :label="__('Remember me')" />
resources/views/livewire/storefront/account/auth/register.blade.php:41:        <flux:checkbox wire:model="marketing_opt_in" :label="__('Email me about new products and offers')" />
resources/views/livewire/admin/auth/login.blade.php:25:        <flux:checkbox wire:model="remember" :label="__('Remember me')" />
resources/views/livewire/storefront/checkout/show.blade.php:75:                    <flux:checkbox wire:model="billingSame" label="Billing address is the same" class="mt-5" />
resources/views/livewire/admin/products/form.blade.php:69:                                        <flux:checkbox wire:model="variants.{{ $index }}.requiresShipping" aria-label="Requires shipping" />
resources/views/livewire/admin/products/form.blade.php:127:                        <flux:checkbox wire:model="collectionIds" value="{{ $collection->getKey() }}" :label="$collection->title" wire:key="product-collection-{{ $collection->getKey() }}" />


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


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


Output: {"output":"Success. Updated the following files:\nD /Users/fabianwesner/Herd/shop/resources/views/livewire/admin/discounts/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/discounts/form.blade.php
+

+

+

+            <flux:heading size="xl">{{ $isEditing ? 'Edit discount' : 'Create discount' }}</flux:heading>
+            <flux:text class="mt-1">Configure the promotion type, value, eligibility, limits, and dates.</flux:text>
+

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

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

+            <flux:radio.group wire:model.live="type" label="Type" class="grid gap-3 sm:grid-cols-2">
+                <flux:radio value="code" label="Discount code" description="Customers enter a code at checkout" />
+                <flux:radio value="automatic" label="Automatic discount" description="Applied automatically at checkout" />
+            </flux:radio.group>
+            <flux:error name="type" />
+

+
+        @if ($type === 'code')
+

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

+

+                        <flux:input wire:model="code" label="Discount code" placeholder="SUMMER20" />
+                        <flux:error name="code" />
+

+

+                        <flux:button type="button" wire:click="generateCode" variant="filled">Generate</flux:button>
+

+

+

+        @endif
+
+

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

+                <flux:radio.group wire:model.live="valueType" label="Value type" class="grid gap-3 sm:grid-cols-3">
+                    <flux:radio value="percent" label="Percentage" />
+                    <flux:radio value="fixed" label="Fixed amount" />
+                    <flux:radio value="free_shipping" label="Free shipping" />
+                </flux:radio.group>
+
+                @if ($valueType !== 'free_shipping')
+

+                        <flux:input wire:model="valueAmount" type="number" step="0.01" min="0" label="{{ $valueType === 'percent' ? 'Percentage' : 'Amount' }}" />
+                        <flux:error name="valueAmount" />
+

+                @endif
+

+

+
+

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

+

+                    <flux:input wire:model="minimumPurchaseAmount" type="number" step="0.01" min="0" label="Minimum purchase amount" placeholder="0.00" />
+                    <flux:error name="minimumPurchaseAmount" />
+

+
+

+

+                        <flux:input wire:model.live.debounce.300ms="productSearch" icon="magnifying-glass" label="Specific products" placeholder="Search products..." />
+
+                        @if ($productResults->isNotEmpty())
+

+                                @foreach ($productResults as $product)
+                                    <button type="button" wire:click="addProduct({{ $product->getKey() }})" wire:key="discount-product-result-{{ $product->getKey() }}" class="block w-full px-3 py-2 text-left text-sm hover:bg-zinc-50 dark:hover:bg-zinc-800">
+                                        {{ $product->title }}
+                                    </button>
+                                @endforeach
+

+                        @endif
+
+

+                            @foreach ($selectedProducts as $product)
+
getKey() }}" class="flex items-center justify-between gap-3 rounded-lg border border-zinc-200 px-3 py-2 text-sm dark:border-zinc-700">
+                                    {{ $product->title }}
+                                    <flux:button type="button" wire:click="removeProduct({{ $product->getKey() }})" size="sm" variant="ghost" icon="x-mark" aria-label="Remove {{ $product->title }}" />
+

+                            @endforeach
+

+

+
+

+                        <flux:input wire:model.live.debounce.300ms="collectionSearch" icon="magnifying-glass" label="Specific collections" placeholder="Search collections..." />
+
+                        @if ($collectionResults->isNotEmpty())
+

+                                @foreach ($collectionResults as $collection)
+                                    <button type="button" wire:click="addCollection({{ $collection->getKey() }})" wire:key="discount-collection-result-{{ $collection->getKey() }}" class="block w-full px-3 py-2 text-left text-sm hover:bg-zinc-50 dark:hover:bg-zinc-800">
+                                        {{ $collection->title }}
+                                    </button>
+                                @endforeach
+

+                        @endif
+
+

+                            @foreach ($selectedCollections as $collection)
+
getKey() }}" class="flex items-center justify-between gap-3 rounded-lg border border-zinc-200 px-3 py-2 text-sm dark:border-zinc-700">
+                                    {{ $collection->title }}
+                                    <flux:button type="button" wire:click="removeCollection({{ $collection->getKey() }})" size="sm" variant="ghost" icon="x-mark" aria-label="Remove {{ $collection->title }}" />
+

+                            @endforeach
+

+

+

+

+

+
+

+            <flux:heading size="lg">Usage limits</flux:heading>
+
+

+                <flux:input wire:model="usageLimit" type="number" min="1" label="Total usage limit" placeholder="Unlimited" />
+

+                    <flux:checkbox wire:model="onePerCustomer" label="Limit to one use per customer" />
+

+

+            <flux:error name="usageLimit" />
+

+
+

+            <flux:heading size="lg">Active dates</flux:heading>
+
+

+                <flux:input wire:model="startsAt" type="datetime-local" label="Start date" />
+                <flux:input wire:model="endsAt" type="datetime-local" label="End date" />
+

+

+                <flux:error name="startsAt" />
+                <flux:error name="endsAt" />
+

+

+
+

+            <flux:checkbox wire:model="isActive" label="{{ $isActive ? 'Active' : 'Disabled' }}" />
+

+
+

+

+                <flux:button :href="route('admin.discounts.index')" wire:navigate variant="filled">Discard</flux:button>
+                <flux:button type="submit" variant="primary">Save</flux:button>
+

+

+    </form>
+

*** End Patch


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

            <flux:heading size="xl">{{ $isEditing ? 'Edit discount' : 'Create discount' }}</flux:heading>
            <flux:text class="mt-1">Configure the promotion type, value, eligibility, limits, and dates.</flux:text>

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

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

            <flux:radio.group wire:model.live="type" label="Type" class="grid gap-3 sm:grid-cols-2">
                <flux:radio value="code" label="Discount code" description="Customers enter a code at checkout" />
                <flux:radio value="automatic" label="Automatic discount" description="Applied automatically at checkout" />
            </flux:radio.group>
            <flux:error name="type" />

        @if ($type === 'code')

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

                        <flux:input wire:model="code" label="Discount code" placeholder="SUMMER20" />
                        <flux:error name="code" />

                        <flux:button type="button" wire:click="generateCode" variant="filled">Generate</flux:button>

        @endif

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

                <flux:radio.group wire:model.live="valueType" label="Value type" class="grid gap-3 sm:grid-cols-3">
                    <flux:radio value="percent" label="Percentage" />
                    <flux:radio value="fixed" label="Fixed amount" />
                    <flux:radio value="free_shipping" label="Free shipping" />
                </flux:radio.group>

                @if ($valueType !== 'free_shipping')

                        <flux:input wire:model="valueAmount" type="number" step="0.01" min="0" label="{{ $valueType === 'percent' ? 'Percentage' : 'Amount' }}" />
                        <flux:error name="valueAmount" />

                @endif

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

                    <flux:input wire:model="minimumPurchaseAmount" type="number" step="0.01" min="0" label="Minimum purchase amount" placeholder="0.00" />
                    <flux:error name="minimumPurchaseAmount" />

                        <flux:input wire:model.live.debounce.300ms="productSearch" icon="magnifying-glass" label="Specific products" placeholder="Search products..." />

                        @if ($productResults->isNotEmpty())

                                @foreach ($productResults as $product)
                                    <button type="button" wire:click="addProduct({{ $product->getKey() }})" wire:key="discount-product-result-{{ $product->getKey() }}" class="block w-full px-3 py-2 text-left text-sm hover:bg-zinc-50 dark:hover:bg-zinc-800">
                                        {{ $product->title }}
                                    </button>
                                @endforeach

                        @endif

                            @foreach ($selectedProducts as $product)

getKey() }}" class="flex items-center justify-between gap-3 rounded-lg border border-zinc-200 px-3 py-2 text-sm dark:border-zinc-700">
                                    {{ $product->title }}
                                    <flux:button type="button" wire:click="removeProduct({{ $product->getKey() }})" size="sm" variant="ghost" icon="x-mark" aria-label="Remove {{ $product->title }}" />

                            @endforeach

                        <flux:input wire:model.live.debounce.300ms="collectionSearch" icon="magnifying-glass" label="Specific collections" placeholder="Search collections..." />

                        @if ($collectionResults->isNotEmpty())

                                @foreach ($collectionResults as $collection)
                                    <button type="button" wire:click="addCollection({{ $collection->getKey() }})" wire:key="discount-collection-result-{{ $collection->getKey() }}" class="block w-full px-3 py-2 text-left text-sm hover:bg-zinc-50 dark:hover:bg-zinc-800">
                                        {{ $collection->title }}
                                    </button>
                                @endforeach

                        @endif

                            @foreach ($selectedCollections as $collection)

getKey() }}" class="flex items-center justify-between gap-3 rounded-lg border border-zinc-200 px-3 py-2 text-sm dark:border-zinc-700">
                                    {{ $collection->title }}
                                    <flux:button type="button" wire:click="removeCollection({{ $collection->getKey() }})" size="sm" variant="ghost" icon="x-mark" aria-label="Remove {{ $collection->title }}" />

                            @endforeach

            <flux:heading size="lg">Usage limits</flux:heading>

                <flux:input wire:model="usageLimit" type="number" min="1" label="Total usage limit" placeholder="Unlimited" />

                    <flux:checkbox wire:model="onePerCustomer" label="Limit to one use per customer" />

            <flux:error name="usageLimit" />

            <flux:heading size="lg">Active dates</flux:heading>

                <flux:input wire:model="startsAt" type="datetime-local" label="Start date" />
                <flux:input wire:model="endsAt" type="datetime-local" label="End date" />

                <flux:error name="startsAt" />
                <flux:error name="endsAt" />

            <flux:checkbox wire:model="isActive" label="{{ $isActive ? 'Active' : 'Disabled' }}" />

                <flux:button :href="route('admin.discounts.index')" wire:navigate variant="filled">Discard</flux:button>
                <flux:button type="submit" variant="primary">Save</flux:button>

    </form>


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