Files
3dealer/resources/views/product/show.blade.php
T

293 lines
16 KiB
PHP
Raw Normal View History

2026-07-31 17:41:55 +03:00
@extends('layouts.app')
@section('title', $product['name'] . ' — ' . config('app.name'))
@section('description', $product['description'])
2026-07-31 17:41:55 +03:00
@section('content')
<div class="max-w-7xl mx-auto px-4 sm:px-8 py-12">
2026-07-31 17:41:55 +03:00
<x-breadcrumb class="mb-14 justify-end" :items="[
$collection
? ['label' => $collection['name'], 'href' => route('category.show', ['id' => $collection['id']])]
: ['label' => __('storefront.nav.products'), 'href' => route('products')],
['label' => $product['name']],
2026-07-31 17:41:55 +03:00
]" />
<div
class="grid grid-cols-1 md:grid-cols-2 gap-12"
data-controller="product-form"
data-product-form-variants-value="{{ json_encode($variantsData) }}"
data-product-form-stock-check-url-value="{{ route('product.stock-check', app()->getLocale()) }}"
data-product-form-stock-error-one-value="{{ trans_choice('storefront.product.add_to_cart_failed', 1) }}"
data-product-form-stock-error-many-value="{{ trans_choice('storefront.product.add_to_cart_failed', 2, ['count' => ':count']) }}"
2026-07-31 17:41:55 +03:00
>
{{-- Image --}}
{{-- relative: anchors the wishlist heart to the top right, which is
the main image's corner (thumbnails sit on the left). --}}
2026-07-31 17:41:55 +03:00
<div
data-controller="product-gallery"
class="relative flex gap-4 items-start"
2026-07-31 17:41:55 +03:00
>
<x-wishlist-button :product-id="$product['id']" class="absolute top-4 right-4 z-10" />
@if(!empty($product['media']))
2026-07-31 17:41:55 +03:00
{{-- Thumbnails --}}
<div class="flex flex-col items-center gap-1 w-[116px] shrink-0 -mt-12.5">
<button
type="button"
data-action="click->product-gallery#scrollUp"
class="gallery-arrow w-full flex items-center justify-center py-2"
aria-label="Scroll thumbnails up"
><x-ui.icon name="arrow-up" :size="30" /></button>
<div
data-product-gallery-target="track"
class="flex flex-col gap-4 overflow-hidden"
style="max-height: var(--gallery-height, 600px)"
>
@foreach($product['media'] as $i => $media)
2026-07-31 17:41:55 +03:00
<button
type="button"
data-action="click->product-gallery#select"
data-product-gallery-target="thumb"
data-src="{{ $media['url'] }}"
data-alt="{{ $product['name'] }}"
2026-07-31 17:41:55 +03:00
class="block w-full shrink-0 border border-black overflow-hidden "
aria-label="View image {{ $i + 1 }}"
aria-pressed="{{ $i === 0 ? 'true' : 'false' }}"
>
<!-- opacity-50 transition-opacity {{ $i === 0 ? 'opacity-100' : '' }}" -->
<img src="{{ $media['url'] }}" alt="" class="w-full h-auto object-cover" aria-hidden="true">
2026-07-31 17:41:55 +03:00
</button>
@endforeach
</div>
<button
type="button"
data-action="click->product-gallery#scrollDown"
class="gallery-arrow w-full flex items-center justify-center py-2"
aria-label="Scroll thumbnails down"
><x-ui.icon name="arrow-down" :size="30" /></button>
</div>
{{-- Main image --}}
<button
type="button"
class="flex-1 border border-black bg-white cursor-zoom-in block p-0"
data-action="click->product-gallery#openLightbox"
aria-label="View full size image"
>
<img
data-product-form-target="image"
data-product-gallery-target="main"
src="{{ $product['media'][0]['url'] }}"
alt="{{ $product['name'] }}"
2026-07-31 17:41:55 +03:00
class="w-full h-auto block"
>
</button>
{{-- Lightbox --}}
<div
id="product-lightbox"
popover
class="product-lightbox"
data-product-gallery-target="lightbox"
data-action="click->product-gallery#closeLightboxOnBackdrop"
role="dialog"
aria-label="Product image gallery"
aria-modal="true"
>
{{-- Prev --}}
<button
type="button"
data-action="click->product-gallery#prevImage click->product-gallery#noop"
class="lightbox-arrow absolute left-8 top-1/2 -translate-y-1/2"
aria-label="Previous image"
><x-ui.icon name="arrow-left" :size="72" color="white" /></button>
{{-- Image + close button as a unit --}}
<div class="relative" data-action="click->product-gallery#noop">
<img
data-product-gallery-target="lightboxImage"
src=""
alt=""
class="max-h-[90vh] max-w-[80vw] object-contain block"
>
<button
type="button"
popovertarget="product-lightbox"
popovertargetaction="hide"
class="lightbox-close absolute -top-10 -right-10"
aria-label="Close gallery"
><x-ui.icon name="close" :size="36" color="white" /></button>
</div>
{{-- Next --}}
<button
type="button"
data-action="click->product-gallery#nextImage click->product-gallery#noop"
class="lightbox-arrow absolute right-8 top-1/2 -translate-y-1/2"
aria-label="Next image"
><x-ui.icon name="arrow-right" :size="72" color="white" /></button>
{{-- Counter --}}
<p
data-product-gallery-target="lightboxCounter"
class="absolute bottom-6 right-8 text-white text-sm"
aria-live="polite"
></p>
</div>
@else
<div class="w-full bg-neutral-300 flex items-center justify-center text-neutral-500 min-h-64">
{{ __('storefront.product.no_image') }}
2026-07-31 17:41:55 +03:00
</div>
@endif
</div>
{{-- Info --}}
<div class="flex flex-col gap-6">
<h1 class="font-display font-medium text-4xl lg:text-[54px]">
{{ $product['name'] }}
2026-07-31 17:41:55 +03:00
</h1>
<x-reviews-stars
:rating="$product['reviews']['average_rating'] ?? 0"
:count="$product['reviews']['count']"
:showCount="true"
:linkable="true"
data-controller="tab-link"
data-tab-link-tabs-outlet="#product-tabs"
data-tab-link-panel-value="reviews"
/>
2026-07-31 17:41:55 +03:00
@if($product['price'] !== null)
2026-07-31 17:41:55 +03:00
<p class="text-2xl font-bold" data-product-form-target="price">
<x-ui.price :amount="$product['price']" />
2026-07-31 17:41:55 +03:00
</p>
@endif
@php
2026-09-23 17:53:23 +03:00
$desc = html_entity_decode(strip_tags($product['description'] ?? ''), ENT_QUOTES | ENT_HTML5, 'UTF-8');
2026-07-31 17:41:55 +03:00
$descTruncated = Str::limit($desc, 137);
$descNeedsMore = mb_strlen($desc) > mb_strlen(rtrim($descTruncated, '.'));
@endphp
<div class="text-base leading-relaxed">
{{ $descTruncated }}
@if($descNeedsMore)
<a
href="#tab-panel-description"
class="underline-slide font-semibold whitespace-nowrap"
data-controller="tab-link"
data-tab-link-tabs-outlet="#product-tabs"
data-tab-link-panel-value="description"
data-action="click->tab-link#activate"
>{{ __('storefront.product.read_more') }}</a>
2026-07-31 17:41:55 +03:00
@endif
</div>
@foreach($productOptions as $productOption)
@if($productOption['isColor'])
<x-ui.color-swatch :values="$productOption['values']" :option="$productOption['label']" :option-handle="$productOption['handle']" />
@else
<x-ui.option-buttons :values="$productOption['values']" :option="$productOption['label']" :option-handle="$productOption['handle']" />
@endif
@endforeach
2026-07-31 17:41:55 +03:00
@php
// Same initial pick as product-form-controller.js's connect()
// (?variant= or the first), so the button doesn't flip on load.
$initialVariant = collect($variantsData)->firstWhere('id', (int) request('variant')) ?? ($variantsData[0] ?? null);
@endphp
2026-09-04 19:39:37 +03:00
<x-checkout::add-to-cart
:purchasable="$initialVariant['id'] ?? null"
2026-09-04 19:39:37 +03:00
:quantity="false"
2026-09-23 17:53:23 +03:00
class="flex flex-col gap-6"
2026-09-04 19:39:37 +03:00
>
2026-09-23 17:53:23 +03:00
@if(!empty($product['custom_fields']))
<x-product-custom-fields :fields="$product['custom_fields']" />
@endif
<div class="flex items-stretch gap-10">
<x-ui.quantity name="quantity" />
<x-ui.button type="submit" class="flex-1" data-product-form-target="submit" :disabled="! ($initialVariant['inStock'] ?? true)">{{ __('storefront.product.add_to_cart') }}</x-ui.button>
2026-09-23 17:53:23 +03:00
</div>
2026-09-04 19:39:37 +03:00
</x-checkout::add-to-cart>
2026-07-31 17:41:55 +03:00
{{-- Storefront-owned, not part of the checkout module — the
local stock pre-check in product-form-controller.js stops
an over-limit submit before it ever reaches the module
and reports it here. --}}
<p class="text-sm text-red-600" data-product-form-target="stockError" hidden role="alert"></p>
2026-07-31 17:41:55 +03:00
</div>
</div>
<x-ui.tabs id="product-tabs" class="mt-16 scroll-mt-28" size="lg" :active="request('tab')" :tabs="[
['id' => 'description', 'label' => __('storefront.product.description')],
['id' => 'reviews', 'label' => __('storefront.product.reviews') . ' (' . $product['reviews']['count'] . ')'],
2026-07-31 17:41:55 +03:00
]">
<x-slot name="description">
<div class="leading-7 [&_p]:mt-4">
{!! $product['description'] !!}
2026-07-31 17:41:55 +03:00
</div>
<ul class="mt-8 flex flex-col gap-3 text-neutral-600 list-disc list-outside pl-5">
<li>Όλα τα προϊόντα εκτυπώνονται και προετοιμάζονται κατά παραγγελία. Ο χρόνος προετοιμασίας κυμαίνεται μεταξύ 2 και 7 εργάσιμων ημερών.</li>
<li>Όλα τα προϊόντα κατασκευάζονται με τρισδιάστατη εκτύπωση σε ειδικούς εκτυπωτές πλαστικού υλικού. Πιθανώς να έχουν εμφανείς γραμμές ένωσης, στρώσεις εκτύπωσης υλικού και μικρές ατέλειες. Είναι φυσιολογικό για το αποτέλεσμα αυτής της δημιουργικής διαδικασίας.</li>
<li>Όλα τα προϊόντα είναι σχεδιασμένα και κατασκευασμένα είτε για διακόσμηση είτε για χρήση σε λογικά, ρεαλιστικά πλαίσια. Υπερβολική ισχύς, υψηλότατες θερμοκρασίες και αποσυναρμολόγηση μπορεί να προκαλέσουν ζημιά στο προϊόν για την οποία δεν ευθύνεται το 3Dealer.</li>
</ul>
</x-slot>
<x-slot name="reviews">
@if(session('reviewSubmitted'))
<p class="mb-8 text-sm font-semibold" role="status">{{ __('storefront.review.thank_you') }}</p>
@endif
@if(!empty($product['reviews']['items']))
2026-07-31 17:41:55 +03:00
<div class="mb-10">
@foreach($product['reviews']['items'] as $review)
<x-review-card :review="[
'rating' => $review['rating'],
'name' => $review['reviewer_name'],
'date' => $review['reviewed_at'] ? \Illuminate\Support\Carbon::createFromTimestamp($review['reviewed_at'])->translatedFormat('d M Y') : '',
'text' => $review['body'],
'image' => $review['media'][0]['url'] ?? null,
'reply' => $review['reply'] ?? null,
'replyDate' => $review['replied_at'] ? \Illuminate\Support\Carbon::createFromTimestamp($review['replied_at'])->translatedFormat('d M Y') : null,
]" />
2026-07-31 17:41:55 +03:00
@endforeach
</div>
@else
<p class="text-neutral-500 mb-10">{{ __('storefront.review.no_reviews_yet') }}</p>
2026-07-31 17:41:55 +03:00
@endif
<h3 class="text-h4 font-bold">
{{ $product['reviews']['count'] > 0 ? __('storefront.review.write_new') : __('storefront.review.write_first') }}
{{ __('storefront.review.for_product', ['name' => $product['name']]) }}
</h3>
2026-07-31 17:41:55 +03:00
<x-review-form :product="$product" />
</x-slot>
</x-ui.tabs>
@if(!empty($product['recommendations']))
<x-product-grid
class="mt-20"
2026-09-25 16:19:13 +03:00
:title="__('storefront.product.related')"
:products="collect($product['recommendations'])->map(fn (array $recommendation) => [
'name' => $recommendation['name'],
'price' => $recommendation['price'],
'image' => $recommendation['image'],
'href' => route('product.show', ['id' => $recommendation['id']]),
])->all()"
/>
@endif
2026-07-31 17:41:55 +03:00
</div>
<x-newsletter class="py-20" />
@endsection