contact page, legal pages and category page

This commit is contained in:
elvira
2026-08-26 13:43:30 +03:00
parent 45057f9083
commit fab9cc08a8
38 changed files with 1726 additions and 182 deletions
+107
View File
@@ -0,0 +1,107 @@
@extends('layouts.app')
@section('title', $collection->translateAttribute('name') . ' — ' . config('app.name'))
@section('description', strip_tags($collection->translateAttribute('description') ?? ''))
@section('content')
<div class="max-w-7xl mx-auto px-4 sm:px-8 py-12">
<div class="flex items-end justify-between gap-6 flex-wrap mb-10">
<h1 class="font-display font-extrabold text-h1">{{ $collection->translateAttribute('name') }}</h1>
<x-breadcrumb :items="[
['label' => __('general.nav.home'), 'href' => route('home')],
['label' => $collection->translateAttribute('name')],
]" />
</div>
<div class="flex items-center justify-between gap-6 flex-wrap border-b border-black pb-6 mb-10">
<p class="text-neutral-600">
{{ trans_choice('general.shop.showing_results', $products->total(), [
'first' => $products->firstItem() ?? 0,
'last' => $products->lastItem() ?? 0,
'total' => $products->total(),
]) }}
</p>
{{-- Dummy — not wired to real sorting yet --}}
<x-ui.select
:ariaLabel="__('general.shop.sort_label')"
:options="[
['value' => 'default', 'label' => __('general.shop.sort_default')],
['value' => 'popularity', 'label' => __('general.shop.sort_popularity')],
['value' => 'price-asc', 'label' => __('general.shop.sort_price_asc')],
['value' => 'price-desc', 'label' => __('general.shop.sort_price_desc')],
['value' => 'newest', 'label' => __('general.shop.sort_newest')],
]"
value="default"
/>
</div>
<div class="grid grid-cols-1 lg:grid-cols-[1fr_300px] gap-12">
{{-- Products --}}
<div>
@if($products->isEmpty())
<p class="text-neutral-500">{{ __('general.shop.no_products') }}</p>
@else
<x-product-grid :products="$products->items()" cols="3" />
<div class="mt-12">
<x-ui.pagination :paginator="$products" />
</div>
@endif
</div>
{{-- Sidebar — search, price and availability filters are dummy for now --}}
<aside class="flex flex-col gap-10">
<div>
<label for="shop-search" class="sr-only">{{ __('general.shop.search_label') }}</label>
<div class="relative">
<x-ui.input
type="search"
id="shop-search"
:placeholder="__('general.shop.search_placeholder')"
class="pr-10"
/>
<button
type="button"
class="absolute right-0 top-1/2 -translate-y-1/2"
aria-label="{{ __('general.shop.search_label') }}"
>
<x-ui.icon name="search" :size="20" />
</button>
</div>
</div>
<div class="flex flex-col gap-4">
<h2 class="font-display font-extrabold uppercase">{{ __('general.shop.filter_price') }}</h2>
<div class="flex items-center gap-2" aria-hidden="true">
<x-ui.icon name="arrow-left" :size="20" />
<span class="flex-1 h-px bg-black"></span>
<x-ui.icon name="arrow-right" :size="20" />
</div>
<div class="flex items-center justify-between gap-4">
<span class="text-sm">€10 - €50</span>
<button type="button" class="underline-slide [--slide-h:1px] font-display font-bold italic uppercase text-sm">
{{ __('general.shop.apply') }}
</button>
</div>
</div>
<div class="flex flex-col gap-4">
<h2 class="font-display font-extrabold uppercase">{{ __('general.shop.availability') }}</h2>
<x-ui.checkbox id="shop-in-stock" name="in_stock">
{{ __('general.shop.in_stock_only') }}
</x-ui.checkbox>
</div>
</aside>
</div>
</div>
@endsection
+6 -5
View File
@@ -10,9 +10,8 @@
{{-- Links --}}
<nav aria-label="Footer navigation">
<ul class="flex flex-wrap justify-center items-center gap-x-8 gap-y-3 font-semibold uppercase">
<li><a href="/contact" class="underline-slide [--slide-h:5px] font-extrabold italic">Επικοινωνία</a></li>
<li><a href="/shipping" class="underline-slide [--slide-h:5px] font-extrabold italic">Πληροφορίες αποστολών</a></li>
<li><a href="/returns" class="underline-slide [--slide-h:5px] font-extrabold italic">Πολιτική επιστροφών</a></li>
<li><a href="{{ route('contact') }}" class="underline-slide [--slide-h:5px] font-extrabold italic">Επικοινωνία</a></li>
<li><a href="{{ route('legal.shipping-returns') }}" class="underline-slide [--slide-h:5px] font-extrabold italic">Αποστολές & Επιστροφές</a></li>
</ul>
</nav>
@@ -57,9 +56,11 @@
&copy; {{ date('Y') }} {{ config('app.name') }}. Με επιφύλαξη παντός δικαιώματος.
</p>
<a href="/terms" class="underline-slide [--slide-h:1px] pb-0">Όροι χρήσης</a>
<a href="{{ route('legal.terms') }}" class="underline-slide [--slide-h:1px] pb-0">Όροι & Προϋποθέσεις</a>
<span>|</span>
<a href="/privacy" class="underline-slide [--slide-h:1px] pb-0 ">Πολιτική απορρήτου</a>
<a href="{{ route('legal.privacy') }}" class="underline-slide [--slide-h:1px] pb-0">Πολιτική Απορρήτου</a>
<span>|</span>
<a href="{{ route('legal.cookies') }}" class="underline-slide [--slide-h:1px] pb-0">Πολιτική Cookies</a>
</div>
</div>
+1 -1
View File
@@ -27,7 +27,7 @@
</div>
{{-- Contact --}}
<a href="{{ url('/'.app()->getLocale().'/contact') }}" class="nav-link uppercase inline-flex items-center gap-1 relative font-display font-extrabold italic text-black no-underline"><span>{{ __('general.nav.contact') }}</span></a>
<a href="{{ route('contact') }}" class="nav-link uppercase inline-flex items-center gap-1 relative font-display font-extrabold italic text-black no-underline"><span>{{ __('general.nav.contact') }}</span></a>
</nav>
@@ -49,14 +49,15 @@ class="absolute right-0 bottom-2 cursor-pointer"
</form>
</div>
<div class="relative bg-neutral-300 min-h-[320px] lg:min-h-full border-t border-black lg:border-t-0 lg:border-l">
@if($image)
<img src="{{ $image }}" alt="" aria-hidden="true" loading="lazy" width="800" height="800" class="w-full h-full object-cover">
@else
<div class="absolute inset-0 flex items-center justify-center text-neutral-500 text-sm">
Χωρίς εικόνα
</div>
@endif
<div class=" min-h-[320px] lg:min-h-full border-t border-black lg:border-t-0 lg:border-l flex items-center justify-center">
<x-ui.stoic-image
src="middle-fingers2.png"
preset="medium"
alt=""
aria-hidden="true"
loading="lazy"
class="mix-blend-darken max-w-md"
/>
</div>
</div>
</section>
@@ -1,14 +1,23 @@
@props([
'title' => null,
'products' => [],
'cols' => 4,
])
@php
$gridCols = match((int) $cols) {
3 => 'grid-cols-2 md:grid-cols-3',
2 => 'grid-cols-1 md:grid-cols-2',
default => 'grid-cols-2 md:grid-cols-4',
};
@endphp
<section {{ $attributes }}>
@if($title)
<h2 class="font-display font-extrabold text-h2 mb-10">{{ $title }}</h2>
@endif
<div class="grid grid-cols-2 md:grid-cols-4 gap-6">
<div class="grid {{ $gridCols }} gap-6">
@foreach($products as $product)
<x-ui.product-card
:name="$product['name']"
@@ -0,0 +1,31 @@
@props(['paginator'])
{{--
Numbered pager (01 02 03 …) matching the site's underline-slide convention,
plus a trailing arrow to the next page. Real pagination — takes any
Illuminate\Contracts\Pagination\LengthAwarePaginator.
Usage: <x-ui.pagination :paginator="$products" />
--}}
@if($paginator->hasPages())
<nav aria-label="{{ __('general.pagination.nav_label') }}" {{ $attributes->merge(['class' => 'flex items-center gap-6 font-display font-bold']) }}>
<ol class="flex items-center gap-6">
@foreach($paginator->getUrlRange(1, $paginator->lastPage()) as $page => $url)
<li>
@if($page === $paginator->currentPage())
<span class="underline-slide is-active" aria-current="page">{{ str_pad((string) $page, 2, '0', STR_PAD_LEFT) }}</span>
@else
<a href="{{ $url }}" class="underline-slide" aria-label="{{ __('general.pagination.page', ['page' => $page]) }}">{{ str_pad((string) $page, 2, '0', STR_PAD_LEFT) }}</a>
@endif
</li>
@endforeach
</ol>
@if($paginator->hasMorePages())
<a href="{{ $paginator->nextPageUrl() }}" aria-label="{{ __('general.pagination.next') }}">
<x-ui.icon name="arrow-right" :size="24" />
</a>
@endif
</nav>
@endif
@@ -7,7 +7,7 @@
<div class="group">
<div class="relative flex items-center justify-center mb-4">
<a href="{{ $href }}" class="block w-full border border-black overflow-hidden bg-white">
<a href="{{ $href }}" class="block w-full border border-black overflow-hidden bg-white aspect-[251/335] flex items-center justify-center">
@if($image)
<img
src="{{ $image }}"
@@ -0,0 +1,30 @@
@props([
'options' => [],
'value' => null,
'name' => null,
'ariaLabel' => null,
])
{{--
Native <select>, styled to match the site's bordered/uppercase look.
Usage:
<x-ui.select
:options="[['value' => 'a', 'label' => 'Option A'], ...]"
value="a"
ariaLabel="Sort products"
/>
--}}
<div class="relative inline-flex items-center">
<select
@if($name) name="{{ $name }}" @endif
@if($ariaLabel) aria-label="{{ $ariaLabel }}" @endif
{{ $attributes->merge(['class' => 'appearance-none bg-transparent border border-black pl-4 pr-10 py-2.5 font-display font-bold cursor-pointer focus:outline-none']) }}
>
@foreach($options as $option)
<option value="{{ $option['value'] }}" @selected($value === $option['value'])>{{ $option['label'] }}</option>
@endforeach
</select>
<x-ui.icon name="arrow-down" :size="16" class="pointer-events-none absolute right-4" />
</div>
@@ -0,0 +1,45 @@
@props([
'src', // Stoic filename string e.g. "hero.png"
'preset' => 'medium', // which preset sets the src + default sizes hint
'alt' => '',
'loading' => 'lazy',
'fetchpriority' => 'auto',
'sizes' => null, // override when you know the render context
'width' => null,
'height' => null,
'preload' => false, // push a <link rel=preload> into <head> for LCP images
])
@php
$ext = strtolower(pathinfo($src, PATHINFO_EXTENSION));
$imageSrc = \App\Services\Stoic::image($src, $preset);
$srcset = \App\Services\Stoic::srcset($src);
$presetW = collect(\App\Services\Stoic::presets())->firstWhere('code', $preset)['width'] ?? 800;
$sizesAttr = $sizes ?? "(min-width: 1024px) {$presetW}px, 100vw";
[$width, $height] = $width !== null && $height !== null
? [$width, $height]
: \App\Services\Stoic::dimensions($src, $preset);
@endphp
@if($preload)
@push('seo')
<link rel="preload" as="image" href="{{ $imageSrc }}"
@if($srcset) imagesrcset="{{ $srcset }}" imagesizes="{{ $sizesAttr }}" @endif
@if($ext !== 'svg') type="image/webp" @endif
fetchpriority="high" />
@endpush
@endif
<img
src="{{ $imageSrc }}"
@if($srcset)
srcset="{{ $srcset }}"
sizes="{{ $sizesAttr }}"
@endif
@if($width !== null) width="{{ $width }}" @endif
@if($height !== null) height="{{ $height }}" @endif
alt="{{ $alt }}"
loading="{{ $loading }}"
fetchpriority="{{ $fetchpriority }}"
{{ $attributes }}
/>
+55
View File
@@ -0,0 +1,55 @@
@extends('layouts.app')
@section('title', __('general.nav.contact'))
@section('description', 'Επικοινώνησε μαζί μας για οποιαδήποτε απορία ή ιδέα έχεις και θα σου απαντήσουμε το συντομότερο δυνατό.')
@section('content')
<section class="grid grid-cols-1 lg:grid-cols-2 lg:divide-x lg:divide-black lg:min-h-[calc(100vh-102px)]">
<div class="flex flex-col justify-center gap-10 px-8 py-16 lg:px-16">
<svg viewBox="0 0 135 124" class="rotating-star text-brand w-24 h-24 lg:w-40 lg:h-40" aria-hidden="true" fill="currentColor">
<path d="m88.4 0 4 28.9 29.7-5.2-14.3 25.7L135 62l-27.2 12.6 14.3 25.7-29.7-5.2-4 28.9-20.9-21.1L46.6 124l-4-28.9-29.7 5.2 14.3-25.7L0 62l27.2-12.6-14.3-25.7 29.7 5.2 4-28.9 20.9 21.1L88.4 0z"/>
</svg>
<h1 class="font-display text-h2 font-semibold leading-tight lg:text-h2"
data-controller="appear"
data-appear-visible-class="is-visible">
<span class="text-dance">Επικοινώνησε</span><br>
μαζί μας <span class="text-dance">άμεσα</span>
</h1>
<p class="max-w-md ">
Έχεις απορία, ιδέα ή θες να μας πεις κάτι; Γράψε μας το μήνυμά σου και θα σου απαντήσουμε το συντομότερο δυνατό.
</p>
<form method="POST" action="#" class="flex flex-col gap-8 max-w-xl">
@csrf
<div class="grid grid-cols-1 gap-8 sm:grid-cols-2">
<x-ui.field label="Το όνομά σου" for="contact-name">
<x-ui.input id="contact-name" name="name" autocomplete="name" :required="true" />
</x-ui.field>
<x-ui.field label="Το email σου" for="contact-email">
<x-ui.input id="contact-email" name="email" type="email" autocomplete="email" :required="true" />
</x-ui.field>
</div>
<x-ui.field label="Το μήνυμά σου" for="contact-message">
<x-ui.textarea id="contact-message" name="message" rows="5" :required="true" />
</x-ui.field>
<div>
<x-ui.button type="submit" size="md">Υποβολή</x-ui.button>
</div>
</form>
</div>
<div class="min-h-[320px] bg-neutral-400 lg:min-h-full" aria-hidden="true"></div>
</section>
@endsection
+44 -25
View File
@@ -1,7 +1,7 @@
@extends('layouts.app')
@section('title', config('app.name') . ' — Ό,τι φαντάζεσαι, τυπωμένο σε 3D')
@section('description', 'Gaming φιγούρες, κρανία, κλειδοθήκες και ό,τι πιο τρελό σου περνάει απ\' το μυαλό. Σχεδιασμένα και τυπωμένα σε 3D, ένα προς ένα.')
@section('title', $page->meta_title)
@section('description', $page->meta_description)
@section('content')
@@ -10,12 +10,12 @@
{{-- Hero --}}
<div class="grid grid-cols-1 lg:grid-cols-2 lg:divide-x lg:divide-black border-b border-black min-h-[calc(100vh-102px)]">
<div class="flex flex-col justify-center gap-8 px-8 py-16 lg:px-16">
<svg viewBox="0 0 135 124" class="hero-star w-[173px] h-[159px] text-[#f3d121]" aria-hidden="true" fill="currentColor">
<svg viewBox="0 0 135 124" class="rotating-star w-[173px] h-[159px] text-[#f3d121]" aria-hidden="true" fill="currentColor">
<path d="m88.4 0 4 28.9 29.7-5.2-14.3 25.7L135 62l-27.2 12.6 14.3 25.7-29.7-5.2-4 28.9-20.9-21.1L46.6 124l-4-28.9-29.7 5.2 14.3-25.7L0 62l27.2-12.6-14.3-25.7 29.7 5.2 4-28.9 20.9 21.1L88.4 0z"/>
</svg>
<h1
class="font-display font-semibold text-6xl lg:text-[80px] leading-tight"
class="font-display font-semibold text-6xl lg:text-[72px] leading-tight"
aria-label="Ό,τι φαντάζεσαι, το τυπώνουμε"
data-controller="appear"
data-appear-visible-class="is-visible"
@@ -40,10 +40,10 @@ class="absolute left-4 lg:left-8 top-1/2 z-10 -translate-y-1/2 hover:-translate-
@foreach($heroProducts as $product)
<div
data-carousel-target="slide"
class="{{ $loop->first ? '' : 'hidden' }} group h-full flex flex-col justify-between"
class="{{ $loop->first ? '' : 'hidden' }} group h-full flex flex-col justify-center pb-8 relative"
>
<div class="relative flex items-center justify-center">
<a href="{{ $product['href'] }}" class="block border border-black bg-white flex items-center justify-center overflow-hidden max-w-sm">
<a href="{{ $product['href'] }}" class="block border border-black bg-white flex items-center justify-center overflow-hidden max-w-xs xl:max-w-sm">
@if($product['image'])
<img
src="{{ $product['image'] }}"
@@ -52,6 +52,7 @@ class="{{ $loop->first ? '' : 'hidden' }} group h-full flex flex-col justify-bet
height="400"
loading="eager"
fetchpriority="high"
preload="{{ $loop->first ? true : false }}"
class="w-full h-full object-cover"
>
@else
@@ -64,7 +65,7 @@ class="w-full h-full object-cover"
</x-ui.button>
</div>
<div class="flex items-baseline justify-between gap-4">
<div class="flex items-baseline justify-between gap-4 absolute bottom-0 left-0 w-full">
<a href="{{ $product['href'] }}" class="font-display font-bold text-2xl leading-snug hover:text-brand">
{{ $product['name'] }}
</a>
@@ -92,23 +93,41 @@ class="absolute right-4 lg:right-8 top-1/2 z-10 -translate-y-1/2 hover:translate
{{-- Info / trivia --}}
<div class="grid grid-cols-1 lg:grid-cols-2 lg:divide-x lg:divide-black">
<div class="bg-neutral-300 min-h-[320px] lg:min-h-full flex items-center justify-center text-neutral-500 text-sm">
Χωρίς εικόνα
<div class="lg:min-h-full flex items-center justify-center text-neutral-500 text-sm" data-stoic="pages/home#trivia_body_image">
@if(!empty($page->trivia_image))
<x-ui.stoic-image
src="{{ $page->trivia_image }}"
preset="medium"
alt=""
aria-hidden="true"
loading="lazy"
class="w-full h-full object-cover "
/>
@endif
</div>
<div class="flex flex-col justify-center gap-6 px-8 py-16 lg:px-16 text-center">
<p class="max-w-md mx-auto text-neutral-600">
Gaming φιγούρες, κρανία, κλειδοθήκες, dark humor και ό,τι πιο τρελό σου περνάει απ' το μυαλό — όλα σχεδιασμένα και τυπωμένα σε 3D, ένα προς ένα, εδώ στην Ελλάδα.
</p>
<h2 class="font-display font-extrabold text-h2 leading-tight">
Στρώση-στρώση,<br>
<span class="italic">0.1mm τη φορά</span>
</h2>
<p class="max-w-md mx-auto text-neutral-600">
Τόσο λεπτή είναι κάθε στρώση υλικού σε ένα 3D εκτυπωμένο κομμάτι — πιο λεπτή κι από τρίχα. Δεν κρατάμε αποθέματα: κάθε παραγγελία τυπώνεται ειδικά για σένα, στο χρώμα και στο μέγεθος που θες.
</p>
<h2 class="font-semibold text-h2"
data-controller="appear"
data-appear-visible-class="is-visible">100% σουρεάλ & <span class="text-dance" data-text="3d-printed">3d-printed</span></h2>
<div class="max-w-md mx-auto" data-stoic="pages/home#trivia_body">
{!! Str::markdown($page->trivia_body) !!}
</div>
<div class="flex items-center justify-center mt-4 mb-6">
@if(!empty($page->trivia_body_image))
<x-ui.stoic-image
src="{{ $page->trivia_body_image }}"
preset="medium"
alt=""
aria-hidden="true"
loading="lazy"
class="max-w-xs"
/>
@endif
</div>
<div>
<x-ui.button size="md" :href="url('/'.app()->getLocale().'/products')">Δες τα προϊόντα</x-ui.button>
{{-- <x-ui.button size="md" :href="url('/'.app()->getLocale().'/products')">Δες τα προϊόντα</x-ui.button> --}}
<x-ui.button size="md" :href="url('/'.app()->getLocale().'/about')">Διάβασε περισσότερα</x-ui.button>
</div>
</div>
</div>
@@ -117,9 +136,9 @@ class="absolute right-4 lg:right-8 top-1/2 z-10 -translate-y-1/2 hover:translate
{{-- Classics --}}
<div class="border-b border-black" data-controller="carousel">
<div class="flex items-center justify-between gap-4 px-8 py-8 border-b border-black">
<h2 class="font-display font-extrabold text-h3">Τα αγαπημένα μας</h2>
<x-ui.button size="sm" :href="url('/'.app()->getLocale().'/products')">Δες όλα</x-ui.button>
<div class="flex items-center justify-between gap-4 px-8 py-12 border-b border-black">
<h2 class="font-display font-extrabold text-h2" data-stoic="pages/home#classics_title">{{ $page->classics_title }}</h2>
<x-ui.button size="lg" :href="url('/'.app()->getLocale().'/products')">Όλα τα προϊόντα</x-ui.button>
</div>
<div class="flex items-center gap-6 px-8 py-12">
@@ -128,9 +147,9 @@ class="absolute right-4 lg:right-8 top-1/2 z-10 -translate-y-1/2 hover:translate
</button>
<div class="flex-1 overflow-hidden">
@forelse($classicsProducts->chunk(4) as $page)
@forelse($classicsProducts->chunk(4) as $chunk)
<div data-carousel-target="slide" class="{{ $loop->first ? '' : 'hidden' }} grid grid-cols-2 md:grid-cols-4 gap-6">
@foreach($page as $product)
@foreach($chunk as $product)
<x-ui.product-card
:name="$product['name']"
:price="$product['price']"
+9
View File
@@ -26,6 +26,15 @@
@vite(['resources/css/app.css', 'resources/js/app.js'])
@auth('staff')
@php($stoicToken = \App\Services\Stoic::token(auth('staff')->user()->email))
<script>
window.STOIC_HOST = "{{ config('services.stoic.host') }}";
window.STOIC_SSO_TOKEN = "{!! $stoicToken !!}";
</script>
<script src="{{ asset('js/stoic_embed.js') }}"></script>
@endauth
@stack('head')
</head>
<body class="min-h-screen antialiased">
+28
View File
@@ -0,0 +1,28 @@
@extends('layouts.app')
@section('title', $page->meta_title ?: $page->name)
@section('description', $page->meta_description)
@if($page->noindex ?? false)
@push('seo')
<meta name="robots" content="noindex, follow">
@endpush
@endif
@section('content')
<section class="mx-auto max-w-2xl px-4 py-20 sm:px-8 sm:py-32">
<div class="mb-12 text-center">
<h1 class="mt-2 font-display text-h3 font-black tracking-tight sm:text-h2" data-stoic="pages/{{ $page->slug }}#name">
{{ $page->name }}
</h1>
</div>
<div class="legal-content" data-stoic="pages/{{ $page->slug }}#body">
{!! Str::markdown($page->content ?? '') !!}
</div>
</section>
@endsection
+2 -2
View File
@@ -10,8 +10,8 @@
<x-breadcrumb class="mb-14 justify-end" :items="[
$collection
? ['label' => $collection->translateAttribute('name'), 'href' => '/category/' . $collection->id]
: ['label' => 'Products', 'href' => '/products'],
? ['label' => $collection->translateAttribute('name'), 'href' => route('category.show', ['collection' => $collection])]
: ['label' => __('general.nav.products'), 'href' => '/products'],
['label' => $product->translateAttribute('name')],
]" />