2026-07-31 17:41:55 +03:00
|
|
|
|
@extends('layouts.app')
|
|
|
|
|
|
|
2026-08-27 00:55:56 +03:00
|
|
|
|
@section('title', $product['name'] . ' — ' . config('app.name'))
|
|
|
|
|
|
@section('description', $product['description'])
|
2026-07-31 17:41:55 +03:00
|
|
|
|
|
|
|
|
|
|
@section('content')
|
2026-08-08 14:50:10 +03:00
|
|
|
|
<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
|
2026-08-29 00:17:34 +03:00
|
|
|
|
? ['label' => $collection['name'], 'href' => route('category.show', ['id' => $collection['id']])]
|
2026-09-03 16:28:10 +03:00
|
|
|
|
: ['label' => __('storefront.nav.products'), 'href' => route('products')],
|
2026-08-27 00:55:56 +03:00
|
|
|
|
['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) }}"
|
2026-09-17 21:52:27 +03:00
|
|
|
|
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 --}}
|
2026-09-24 17:27:58 +03:00
|
|
|
|
{{-- 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"
|
2026-09-24 17:27:58 +03:00
|
|
|
|
class="relative flex gap-4 items-start"
|
2026-07-31 17:41:55 +03:00
|
|
|
|
>
|
2026-09-24 17:27:58 +03:00
|
|
|
|
<x-wishlist-button :product-id="$product['id']" class="absolute top-4 right-4 z-10" />
|
|
|
|
|
|
|
2026-08-27 00:55:56 +03:00
|
|
|
|
@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)"
|
|
|
|
|
|
>
|
2026-08-27 00:55:56 +03:00
|
|
|
|
@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"
|
2026-08-27 00:55:56 +03:00
|
|
|
|
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' : '' }}" -->
|
2026-08-27 00:55:56 +03:00
|
|
|
|
<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"
|
2026-08-27 00:55:56 +03:00
|
|
|
|
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">
|
2026-08-27 01:21:57 +03:00
|
|
|
|
{{ __('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]">
|
2026-08-27 00:55:56 +03:00
|
|
|
|
{{ $product['name'] }}
|
2026-07-31 17:41:55 +03:00
|
|
|
|
</h1>
|
|
|
|
|
|
|
2026-09-22 19:00:34 +03:00
|
|
|
|
<x-reviews-stars
|
|
|
|
|
|
:rating="$product['reviews']['average_rating'] ?? 0"
|
|
|
|
|
|
:count="$product['reviews']['count']"
|
|
|
|
|
|
:showCount="true"
|
|
|
|
|
|
:linkable="true"
|
2026-09-22 19:18:55 +03:00
|
|
|
|
data-controller="tab-link"
|
|
|
|
|
|
data-tab-link-tabs-outlet="#product-tabs"
|
|
|
|
|
|
data-tab-link-panel-value="reviews"
|
2026-09-22 19:00:34 +03:00
|
|
|
|
/>
|
2026-07-31 17:41:55 +03:00
|
|
|
|
|
2026-08-27 00:55:56 +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">
|
2026-08-27 00:55:56 +03:00
|
|
|
|
<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)
|
2026-09-22 19:18:55 +03:00
|
|
|
|
<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>
|
|
|
|
|
|
|
2026-09-21 18:53:45 +03:00
|
|
|
|
@foreach($productOptions as $productOption)
|
|
|
|
|
|
@if($productOption['isColor'])
|
|
|
|
|
|
<x-ui.color-swatch :values="$productOption['values']" :option="$productOption['label']" :option-handle="$productOption['handle']" />
|
2026-09-17 21:52:27 +03:00
|
|
|
|
@else
|
2026-09-21 18:53:45 +03:00
|
|
|
|
<x-ui.option-buttons :values="$productOption['values']" :option="$productOption['label']" :option-handle="$productOption['handle']" />
|
2026-09-17 21:52:27 +03:00
|
|
|
|
@endif
|
2026-09-21 18:53:45 +03:00
|
|
|
|
@endforeach
|
2026-07-31 17:41:55 +03:00
|
|
|
|
|
2026-09-25 20:23:19 +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
|
2026-09-25 20:23:19 +03:00
|
|
|
|
: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" />
|
2026-09-25 20:23:19 +03:00
|
|
|
|
<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
|
|
|
|
|
2026-09-17 21:52:27 +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>
|
|
|
|
|
|
|
2026-09-22 19:00:34 +03:00
|
|
|
|
<x-ui.tabs id="product-tabs" class="mt-16 scroll-mt-28" size="lg" :active="request('tab')" :tabs="[
|
2026-08-27 01:21:57 +03:00
|
|
|
|
['id' => 'description', 'label' => __('storefront.product.description')],
|
2026-08-27 11:46:33 +03:00
|
|
|
|
['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">
|
2026-08-27 00:55:56 +03:00
|
|
|
|
{!! $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">
|
2026-09-22 19:00:34 +03:00
|
|
|
|
@if(session('reviewSubmitted'))
|
|
|
|
|
|
<p class="mb-8 text-sm font-semibold" role="status">{{ __('storefront.review.thank_you') }}</p>
|
|
|
|
|
|
@endif
|
|
|
|
|
|
|
2026-08-27 11:46:33 +03:00
|
|
|
|
@if(!empty($product['reviews']['items']))
|
2026-07-31 17:41:55 +03:00
|
|
|
|
<div class="mb-10">
|
2026-08-27 11:46:33 +03:00
|
|
|
|
@foreach($product['reviews']['items'] as $review)
|
2026-08-27 00:55:56 +03:00
|
|
|
|
<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,
|
2026-09-22 19:00:34 +03:00
|
|
|
|
'reply' => $review['reply'] ?? null,
|
|
|
|
|
|
'replyDate' => $review['replied_at'] ? \Illuminate\Support\Carbon::createFromTimestamp($review['replied_at'])->translatedFormat('d M Y') : null,
|
2026-08-27 00:55:56 +03:00
|
|
|
|
]" />
|
2026-07-31 17:41:55 +03:00
|
|
|
|
@endforeach
|
|
|
|
|
|
</div>
|
|
|
|
|
|
@else
|
2026-08-27 01:21:57 +03:00
|
|
|
|
<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">
|
2026-08-27 11:46:33 +03:00
|
|
|
|
{{ $product['reviews']['count'] > 0 ? __('storefront.review.write_new') : __('storefront.review.write_first') }}
|
2026-08-27 01:21:57 +03:00
|
|
|
|
{{ __('storefront.review.for_product', ['name' => $product['name']]) }}
|
2026-08-27 00:55:56 +03:00
|
|
|
|
</h3>
|
2026-07-31 17:41:55 +03:00
|
|
|
|
|
|
|
|
|
|
<x-review-form :product="$product" />
|
|
|
|
|
|
</x-slot>
|
|
|
|
|
|
</x-ui.tabs>
|
|
|
|
|
|
|
2026-09-01 13:00:26 +03:00
|
|
|
|
@if(!empty($product['recommendations']))
|
|
|
|
|
|
<x-product-grid
|
|
|
|
|
|
class="mt-20"
|
2026-09-25 16:19:13 +03:00
|
|
|
|
:title="__('storefront.product.related')"
|
2026-09-01 13:00:26 +03:00
|
|
|
|
: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
|