-
app/Livewire/ProductPage.php
Open in GitHubuse App\Traits\FetchesUrls; use Illuminate\Support\Collection; use Illuminate\View\View; use Livewire\Component; use Lunar\Models\Product; use Lunar\Models\ProductVariant; use Spatie\MediaLibrary\MediaCollections\Models\Media; class ProductPage extends Component { use FetchesUrls; public array $selectedOptionValues = []; public function mount($slug): void { $this->url = $this->fetchUrl( $slug, (new Product)->getMorphClass(), [ 'element.media', 'element.variants.basePrices.currency', 'element.variants.basePrices.priceable', 'element.variants.values.option', ] ); $this->selectedOptionValues = $this->productOptions->mapWithKeys(function ($data) { return [$data['option']->id => $data['values']->first()->id]; })->toArray(); if (! $this->variant) { abort(404); } } public function getVariantProperty(): ProductVariant { return $this->product->variants->first(function ($variant) { return ! $variant->values->pluck('id') ->diff( collect($this->selectedOptionValues)->values() )->count(); }); } public function getProductOptionValuesProperty(): Collection { return $this->product->variants->pluck('values')->flatten(); } public function getProductOptionsProperty(): Collection { return $this->productOptionValues->unique('id')->groupBy('product_option_id') ->map(function ($values) { return [ 'option' => $values->first()->option, 'values' => $values, ]; })->values(); } public function getProductProperty(): Product { return $this->url->element; } public function getImagesProperty(): Collection { return $this->product->media->sortBy('order_column'); } public function getImageProperty(): ?Media { if (count($this->variant->images)) { return $this->variant->images->first(); } if ($primary = $this->images->first(fn ($media) => $media->getCustomProperty('primary'))) { return $primary; } return $this->images->first(); } public function render(): View { return view('livewire.product-page'); } }
-
resources/views/livewire/product-page.blade.php
Open in GitHub<section> <div class="max-w-screen-xl px-4 py-12 mx-auto sm:px-6 lg:px-8"> <div class="grid items-start grid-cols-1 gap-8 md:grid-cols-2"> <div class="grid grid-cols-2 gap-4 md:grid-cols-1"> @if ($this->image) <div class="aspect-w-1 aspect-h-1"> <img class="object-cover rounded-xl" src="{{ $this->image->getUrl('large') }}" alt="{{ $this->product->translateAttribute('name') }}" /> </div> @endif <div class="grid grid-cols-2 gap-4 sm:grid-cols-4"> @foreach ($this->images as $image) <div class="aspect-w-1 aspect-h-1" wire:key="image_{{ $image->id }}"> <img loading="lazy" class="object-cover rounded-xl" src="{{ $image->getUrl('small') }}" alt="{{ $this->product->translateAttribute('name') }}" /> </div> @endforeach </div> </div> <div> <div class="flex items-center justify-between"> <h1 class="text-xl font-bold"> {{ $this->product->translateAttribute('name') }} </h1> <x-product-price class="ml-4 font-medium" :variant="$this->variant" /> </div> <p class="mt-1 text-sm text-gray-500"> {{ $this->variant->sku }} </p> <article class="mt-4 text-gray-700"> {!! $this->product->translateAttribute('description') !!} </article> <form class="mt-4"> <div class="space-y-4"> @foreach ($this->productOptions as $option) <fieldset> <legend class="text-xs font-medium text-gray-700"> {{ $option['option']->translate('name') }} </legend> <div class="flex flex-wrap gap-2 mt-2 text-xs tracking-wide uppercase" x-data="{ selectedOption: @entangle('selectedOptionValues').live, selectedValues: [], }" x-init="selectedValues = Object.values(selectedOption); $watch('selectedOption', value => selectedValues = Object.values(selectedOption) )"> @foreach ($option['values'] as $value) <button class="px-6 py-4 font-medium border rounded-lg focus:outline-none focus:ring" type="button" wire:click=" $set('selectedOptionValues.{{ $option['option']->id }}', {{ $value->id }}) " :class="{ 'bg-indigo-600 border-indigo-600 text-white hover:bg-indigo-700': selectedValues .includes({{ $value->id }}), 'hover:bg-gray-100': !selectedValues.includes({{ $value->id }}) }"> {{ $value->translate('name') }} </button> @endforeach </div> </fieldset> @endforeach </div> <div class="max-w-xs mt-8"> <livewire:components.add-to-cart :purchasable="$this->variant" :wire:key="$this->variant->id"> </div> </form> </div> </div> </div> </section>