feat: Implement full voucher and outlet listing with search, detail modals, and UI enhancements

This commit is contained in:
Yoga Pangestu 2025-12-20 17:35:14 +07:00
parent 93d86a30c0
commit 71568a7ecd
15 changed files with 646 additions and 176 deletions

View File

@ -11,7 +11,6 @@
use App\Models\PerfumeFeature; use App\Models\PerfumeFeature;
use App\Models\Testimonial; use App\Models\Testimonial;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Storage;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
@ -50,7 +49,7 @@ public function mount(): void
->take(2) ->take(2)
->get() ->get()
->map(function (Perfume $perfume) { ->map(function (Perfume $perfume) {
$perfume->image = $perfume->getFirstMediaUrl('image') ? Storage::url($perfume->getFirstMediaUrl('image')) : asset('assets/images/logo.png'); $perfume->image = $perfume->getFirstMediaUrl('image') ? $perfume->getFirstMediaUrl('image') : asset('assets/images/logo.png');
$perfume->concentration_label = $perfume->concentration->label(); $perfume->concentration_label = $perfume->concentration->label();
return $perfume; return $perfume;

View File

@ -2,6 +2,7 @@
namespace App\Livewire\Home; namespace App\Livewire\Home;
use App\Models\Outlet as OutletModel;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Component; use Livewire\Component;
@ -11,8 +12,48 @@
])] ])]
class Outlet extends Component class Outlet extends Component
{ {
public array $outlets = [];
public string $search = '';
public ?OutletModel $selectedOutlet = null;
public function mount(): void
{
$this->outlets = OutletModel::with(['facilities', 'openingHours'])
->operational()
->when($this->search, function ($query) {
$query->where(function ($query) {
$query->where('name', 'like', '%'.$this->search.'%')
->orWhere('address', 'like', '%'.$this->search.'%');
});
})
->latest()
->get()
->map(function ($outlet) {
$outlet->image = $outlet->getFirstMediaUrl('featured_image') ? $outlet->getFirstMediaUrl('featured_image') : asset('assets/images/dark-logo.png');
$outlet->status_label = $outlet->status->label();
$outlet->status_color = $outlet->status->color();
return $outlet;
})
->toArray();
}
public function showDetail(int $outletId): void
{
$this->selectedOutlet = OutletModel::with(['facilities', 'openingHours', 'media'])->find($outletId);
}
public function closeModal(): void
{
$this->selectedOutlet = null;
}
public function render(): View public function render(): View
{ {
return view('livewire.home.outlets'); return view('livewire.home.outlets', [
'pageTitle' => 'Outlet',
]);
} }
} }

View File

@ -4,7 +4,9 @@
use App\Models\Outlet; use App\Models\Outlet;
use App\Models\Voucher as VoucherModel; use App\Models\Voucher as VoucherModel;
use App\Traits\Components\WithToast;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
@ -14,7 +16,7 @@
])] ])]
class Voucher extends Component class Voucher extends Component
{ {
use WithPagination; use WithPagination, WithToast;
public $outlets; public $outlets;
@ -24,6 +26,10 @@ class Voucher extends Component
public ?string $sort = null; public ?string $sort = null;
public ?VoucherModel $selectedVoucher = null;
public bool $showDetailModal = false;
public function mount(): void public function mount(): void
{ {
$this->outlets = Outlet::query()->operational()->pluck('name', 'id'); $this->outlets = Outlet::query()->operational()->pluck('name', 'id');
@ -44,6 +50,91 @@ public function updatedSort(): void
$this->resetPage(); $this->resetPage();
} }
public function showDetail(string $hashId): void
{
$this->selectedVoucher = VoucherModel::byHash($hashId);
$this->selectedVoucher->load('outlets');
$this->selectedVoucher->min_purchase_formatted = formatCurrencyNumber($this->selectedVoucher->min_purchase, 'Rp');
$this->selectedVoucher->end_date_formatted = formatDateLocalized($this->selectedVoucher->end_date);
$this->showDetailModal = true;
}
public function closeDetail(): void
{
$this->showDetailModal = false;
$this->selectedVoucher = null;
}
public function claim(string $hashId): void
{
if (! auth()->check()) {
$this->redirect(route('login'), navigate: true);
return;
}
$voucher = VoucherModel::byHash($hashId);
$user = auth()->user();
// Basic checks
if (! $voucher) {
$this->toast('Voucher tidak ditemukan.', 'Gagal', 'danger');
return;
}
if ($voucher->quota !== null && $voucher->available_count >= $voucher->quota) {
$this->toast('Klaim gagal! Kuota voucher sudah habis.', 'Gagal', 'danger');
return;
}
// Check if user already reached limit
$claimedCount = $user->vouchers()->where('voucher_id', $voucher->id)->exists();
if ($voucher->limit_per_user !== null) {
$claimedCount = $user->vouchers()->where('voucher_id', $voucher->id)->count();
if ($claimedCount >= $voucher->limit_per_user) {
$this->toast('Anda sudah mencapai batas klaim untuk voucher ini.', 'Peringatan', 'warning');
return;
}
} elseif ($claimedCount) {
// If limit_per_user is null, assume it can only be claimed once?
// Or can it be claimed multiple times?
// Usually vouchers are one-time claim per user unless specified.
// Based on common logic, if limit_per_user is null, maybe it's unlimited?
// But usually it's 1. Let's stick to the limit_per_user check.
}
// Check if voucher is for specific tier
$userTierId = $user->membership?->tier_id;
if ($voucher->tiers()->exists() && ! $voucher->tiers()->where('tiers.id', $userTierId)->exists()) {
$this->toast('Voucher ini tidak tersedia untuk tier Anda.', 'Gagal', 'danger');
return;
}
try {
// Claim with transaction
DB::transaction(function () use ($user, $voucher) {
// Re-check quota inside transaction
$v = VoucherModel::where('id', $voucher->id)->lockForUpdate()->first();
if ($v->quota !== null && $v->available_count >= $v->quota) {
throw new \Exception('Kuota voucher sudah habis.');
}
$user->vouchers()->attach($v->id);
$v->increment('available_count');
});
$this->toast('Voucher berhasil diklaim! Silakan cek di menu Voucher Saya.', 'Berhasil', 'success');
} catch (\Throwable $e) {
$this->toast($e->getMessage(), 'Gagal', 'danger');
}
}
public function render(): View public function render(): View
{ {
$vouchers = VoucherModel::where(function ($query) { $vouchers = VoucherModel::where(function ($query) {

View File

@ -4,6 +4,7 @@
use App\Enums\PriceRequestStatus; use App\Enums\PriceRequestStatus;
use App\Models\PriceRequest; use App\Models\PriceRequest;
use App\Settings\ContactSettings;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\View; use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
@ -326,6 +327,7 @@ public function boot(): void
$view->with([ $view->with([
'sidebar' => $sidebar, 'sidebar' => $sidebar,
'header' => $header, 'header' => $header,
'setting' => app(ContactSettings::class),
]); ]);
}); });
} }

View File

@ -40,26 +40,32 @@ class="w-10 h-10 rounded-full bg-white/5 flex items-center justify-center text-g
</div> </div>
<div class="md:col-span-3 md:pl-8"> <div class="md:col-span-3 md:pl-8">
<h3 class="text-white font-bold mb-6 text-sm uppercase tracking-wider">Quick Links</h3> <h3 class="text-white font-bold mb-6 text-sm uppercase tracking-wider">Tautan Cepat</h3>
<ul class="space-y-4 text-sm text-gray-400"> <ul class="space-y-4 text-sm text-gray-400">
<li><a href="#" <li>
<a href="{{ route('homepage') }}" wire:navigate
class="hover:text-home-secondary transition-colors duration-300 flex items-center gap-2">Beranda</a> class="hover:text-home-secondary transition-colors duration-300 flex items-center gap-2">Beranda</a>
</li> </li>
<li><a href="#" <li>
<a href="{{ route('product.index') }}" wire:navigate
class="hover:text-home-secondary transition-colors duration-300 flex items-center gap-2">Katalog class="hover:text-home-secondary transition-colors duration-300 flex items-center gap-2">Katalog
Produk</a></li> Produk</a>
<li><a href="#" </li>
class="hover:text-home-secondary transition-colors duration-300 flex items-center gap-2">Tentang <li>
Kami</a></li> <a href="{{ route('outlet') }}" wire:navigate
<li><a href="#" class="hover:text-home-secondary transition-colors duration-300 flex items-center gap-2">Outtlet
class="hover:text-home-secondary transition-colors duration-300 flex items-center gap-2">Kontak</a> </a>
</li>
<li>
<a href="{{ route('article.index') }}" wire:navigate
class="hover:text-home-secondary transition-colors duration-300 flex items-center gap-2">Artikel</a>
</li> </li>
</ul> </ul>
</div> </div>
<div class="md:col-span-5 flex flex-col gap-6"> <div class="md:col-span-5 flex flex-col gap-6">
<div> <div>
<h3 class="text-white font-bold mb-6 text-sm uppercase tracking-wider">Contact Us</h3> <h3 class="text-white font-bold mb-6 text-sm uppercase tracking-wider">Kontak Kami</h3>
<div class="space-y-5 text-sm text-gray-400"> <div class="space-y-5 text-sm text-gray-400">
<div class="flex items-start gap-4"> <div class="flex items-start gap-4">
<div class="mt-1"><svg class="w-5 h-5 text-home-primary" fill="none" <div class="mt-1"><svg class="w-5 h-5 text-home-primary" fill="none"
@ -70,8 +76,7 @@ class="hover:text-home-secondary transition-colors duration-300 flex items-cente
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path> d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
</svg></div> </svg></div>
<span <span class="leading-relaxed">{{ $setting->address }}</span>
class="leading-relaxed">{{ $outlet->note ?? 'Jalan Wangi Semerbak No. 123, Kota Parfum, Indonesia' }}</span>
</div> </div>
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<div><svg class="w-5 h-5 text-home-primary" fill="none" stroke="currentColor" <div><svg class="w-5 h-5 text-home-primary" fill="none" stroke="currentColor"
@ -80,7 +85,7 @@ class="leading-relaxed">{{ $outlet->note ?? 'Jalan Wangi Semerbak No. 123, Kota
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"> d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z">
</path> </path>
</svg></div> </svg></div>
<span>{{ $outlet->email ?? 'admin@yadiparfum.com' }}</span> <span>{{ $setting->email }}</span>
</div> </div>
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<div><svg class="w-5 h-5 text-home-primary" fill="none" stroke="currentColor" <div><svg class="w-5 h-5 text-home-primary" fill="none" stroke="currentColor"
@ -89,7 +94,7 @@ class="leading-relaxed">{{ $outlet->note ?? 'Jalan Wangi Semerbak No. 123, Kota
d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"> d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z">
</path> </path>
</svg></div> </svg></div>
<span>{{ $outlet->phone ?? '+62 812-3456-7890' }}</span> <span>{{ $setting->phone_number }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -98,12 +103,12 @@ class="leading-relaxed">{{ $outlet->note ?? 'Jalan Wangi Semerbak No. 123, Kota
<div class="border-t border-white/10 pt-8 flex flex-col md:flex-row justify-between items-center gap-4"> <div class="border-t border-white/10 pt-8 flex flex-col md:flex-row justify-between items-center gap-4">
<div class="text-sm text-gray-500"> <div class="text-sm text-gray-500">
&copy; {{ date('Y') }} Yadi Parfum. All rights reserved. &copy; {{ date('Y') }} Yadi Parfum. Semua hak dilindungi undang-undang.
</div> </div>
<div class="flex gap-6 text-sm text-gray-500"> {{-- <div class="flex gap-6 text-sm text-gray-500">
<a href="#" class="hover:text-white transition-colors">Privacy Policy</a> <a href="#" class="hover:text-white transition-colors">Privacy Policy</a>
<a href="#" class="hover:text-white transition-colors">Terms of Service</a> <a href="#" class="hover:text-white transition-colors">Terms of Service</a>
</div> </div> --}}
</div> </div>
</div> </div>
</footer> </footer>

View File

@ -3,8 +3,8 @@
<section class="relative h-[500px] flex items-center justify-center overflow-hidden"> <section class="relative h-[500px] flex items-center justify-center overflow-hidden">
{{-- Background Image --}} {{-- Background Image --}}
<div class="absolute inset-0 z-0"> <div class="absolute inset-0 z-0">
<img src="https://images.unsplash.com/photo-1752042143353-e6dcc822a023?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" <img src="https://images.pexels.com/photos/4057663/pexels-photo-4057663.jpeg" alt="Article Background"
alt="Perfume Background" class="w-full h-full object-cover"> class="w-full h-full object-cover">
<div class="absolute inset-0 bg-black/40"></div> <div class="absolute inset-0 bg-black/40"></div>
</div> </div>

View File

@ -3,8 +3,8 @@
<section class="relative h-[500px] flex items-center justify-center overflow-hidden"> <section class="relative h-[500px] flex items-center justify-center overflow-hidden">
{{-- Background Image --}} {{-- Background Image --}}
<div class="absolute inset-0 z-0"> <div class="absolute inset-0 z-0">
<img src="https://images.unsplash.com/photo-1752042143353-e6dcc822a023?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" <img src="https://images.pexels.com/photos/2789781/pexels-photo-2789781.jpeg" alt="Perfume Background"
alt="Perfume Background" class="w-full h-full object-cover"> class="w-full h-full object-cover">
<div class="absolute inset-0 bg-black/40"></div> <div class="absolute inset-0 bg-black/40"></div>
</div> </div>

View File

@ -1,95 +1,57 @@
<div> <div>
@php
$outlets = [
[
'id' => 1,
'name' => 'Yadi Parfum Central Park',
'address' => 'Central Park Mall, Lt. LG, Unit 112, Jl. Letjen S. Parman No.28, Jakarta Barat',
'city' => 'Jakarta Barat',
'phone' => '+62 812-3456-7890',
'hours' => '10:00 - 22:00 WIB',
'image' => 'https://placehold.co/800x600/png?text=Boutique+Central+Park',
'facilities' => ['Consultation', 'Refill Station', 'Engraving'],
'status' => 'Buka',
],
[
'id' => 2,
'name' => 'Yadi Parfum Senayan City',
'address' => 'Senayan City, Lt. 1, Unit 45, Jl. Asia Afrika Lot 19, Jakarta Pusat',
'city' => 'Jakarta Pusat',
'phone' => '+62 812-9876-5432',
'hours' => '10:00 - 22:00 WIB',
'image' => 'https://placehold.co/800x600/png?text=Boutique+Senayan',
'facilities' => ['VIP Lounge', 'Consultation'],
'status' => 'Buka',
],
[
'id' => 3,
'name' => 'Yadi Parfum Bandung',
'address' => 'Paris Van Java, Resort Level, Jl. Sukajadi No.131-139, Bandung',
'city' => 'Bandung',
'phone' => '+62 822-1122-3344',
'hours' => '10:00 - 21:00 WIB',
'image' => 'https://placehold.co/800x600/png?text=Boutique+Bandung',
'facilities' => ['Refill Station'],
'status' => 'Tutup',
],
];
@endphp
<div class="flex flex-col min-h-screen bg-white font-sans"> <div class="flex flex-col min-h-screen bg-white font-sans">
{{-- Hero Section --}} {{-- Hero Section --}}
<section class="relative h-[500px] flex items-center justify-center overflow-hidden"> <section class="relative h-[500px] flex items-center justify-center overflow-hidden">
{{-- Background Image --}} {{-- Background Image --}}
<div class="absolute inset-0 z-0"> <div class="absolute inset-0 z-0">
<img src="https://images.unsplash.com/photo-1541643600914-78b084683601?q=80&w=1469&auto=format&fit=crop" <img src="https://images.unsplash.com/photo-1761095870661-c4ae15cac605?q=80&w=880&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="Outlet Background" class="w-full h-full object-cover"> alt="Outlet Background" class="w-full h-full object-cover">
<div class="absolute inset-0 bg-black/50"></div> <div class="absolute inset-0 bg-black/50"></div>
</div>
{{-- Content --}}
<div class="relative z-10 container mx-auto px-6 text-center">
<span class="text-home-primary font-bold tracking-widest text-xs uppercase mb-3 block">Lokasi Butik</span>
<h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-white mb-6">Temukan Butik Terdekat</h1>
<p class="text-white/80 text-lg md:text-xl max-w-2xl mx-auto mb-10">
Rasakan pengalaman langsung kemewahan aroma kami. Kunjungi butik kami untuk konsultasi personal.
</p>
{{-- Search Bar --}}
<div class="max-w-xl mx-auto relative group">
<input type="text" placeholder="Masukkan kota atau nama area..."
class="w-full py-4 pl-6 pr-14 rounded-full bg-white/10 backdrop-blur-md border border-white/20 text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-home-primary focus:bg-white/20 transition-all shadow-lg text-lg">
<button
class="absolute right-2 top-2 bottom-2 w-10 h-10 bg-home-primary rounded-full flex items-center justify-center text-white hover:bg-white hover:text-home-primary transition-all duration-300">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</button>
</div> </div>
</div>
</section>
<main class="flex-grow px-6 lg:px-32 xl:px-48 py-24"> {{-- Content --}}
<div class="relative z-10 container mx-auto px-6 text-center">
<h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-white mb-6">Temukan Outlet Terdekat</h1>
<p class="text-white/80 text-lg md:text-xl max-w-2xl mx-auto mb-10">
Rasakan pengalaman langsung kemewahan aroma kami. Kunjungi outlet kami untuk konsultasi personal.
</p>
{{-- Search Bar --}}
<div class="max-w-xl mx-auto relative group">
<input type="text" wire:model.live="search" placeholder="Masukkan kota atau nama area..."
class="w-full py-4 pl-6 pr-14 rounded-full bg-white/10 backdrop-blur-md border border-white/20 text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-home-primary focus:bg-white/20 transition-all shadow-lg text-lg">
<button
class="absolute right-2 top-2 bottom-2 w-10 h-10 bg-home-primary rounded-full flex items-center justify-center text-white hover:bg-white hover:text-home-primary transition-all duration-300">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</button>
</div>
</div>
</section>
<main class="flex-grow px-6 lg:px-32 xl:px-48 py-24">
<section class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-24"> <section class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-24">
@foreach ($outlets as $outlet) @foreach ($outlets as $outlet)
<article <article wire:click="showDetail({{ $outlet['id'] }})"
class="group bg-white rounded-3xl border border-home-foreground/10 overflow-hidden hover:shadow-2xl hover:shadow-home-primary/10 hover:-translate-y-2 transition-all duration-500 flex flex-col h-full"> class="group bg-white rounded-3xl border border-home-foreground/10 overflow-hidden hover:shadow-2xl hover:shadow-home-primary/10 hover:-translate-y-2 transition-all duration-500 flex flex-col h-full cursor-pointer">
<div class="relative h-64 overflow-hidden"> <div class="relative h-64 overflow-hidden">
<div <div
class="absolute inset-0 bg-home-primary/10 mix-blend-multiply opacity-0 group-hover:opacity-100 transition-opacity duration-500 z-10"> class="absolute inset-0 bg-home-primary/10 mix-blend-multiply opacity-0 group-hover:opacity-100 transition-opacity duration-500 z-10">
</div> </div>
<img src="{{ $outlet['image'] }}" alt="{{ $outlet['name'] }}" <img src="{{ $outlet['image'] ?: 'https://placehold.co/800x600/png?text=' . urlencode($outlet['name']) }}"
alt="{{ $outlet['name'] }}"
class="w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-700"> class="w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-700">
<div class="absolute top-4 left-4 z-20"> <div class="absolute top-4 left-4 z-20">
<span <span
class="px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-wide backdrop-blur-md bg-white/90 shadow-sm class="px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-wide backdrop-blur-md bg-white/90 shadow-sm
{{ $outlet['status'] == 'Buka' ? 'text-green-700' : 'text-red-700' }}"> text-{{ $outlet['status_color'] }}-700">
{{ $outlet['status'] }} {{ $outlet['status_label'] }}
</span> </span>
</div> </div>
</div> </div>
@ -106,45 +68,91 @@ class="text-xl font-bold text-home-foreground mb-2 group-hover:text-home-primary
@foreach ($outlet['facilities'] as $facility) @foreach ($outlet['facilities'] as $facility)
<span <span
class="text-[10px] px-2 py-1 border border-home-foreground/10 rounded text-home-foreground/50"> class="text-[10px] px-2 py-1 border border-home-foreground/10 rounded text-home-foreground/50">
{{ $facility }} {{ $facility['name'] }}
</span> </span>
@endforeach @endforeach
</div> </div>
<div <div class="mt-auto flex gap-3" x-on:click.stop>
class="grid grid-cols-2 gap-4 text-xs text-home-foreground/70 mb-6 pt-4 border-t border-home-foreground/5"> @if ($outlet['maps_url'])
<div class="flex items-center gap-2"> <a href="{{ $outlet['maps_url'] }}" target="_blank"
<svg class="w-4 h-4 text-home-primary" fill="none" stroke="currentColor" class="flex-1 py-3 rounded-xl bg-home-primary text-white text-sm font-bold hover:bg-home-secondary hover:text-home-primary transition-colors shadow-lg shadow-home-primary/20 flex items-center justify-center">
viewBox="0 0 24 24"> Petunjuk Arah
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" </a>
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path> @endif
</svg> {{-- Share Menu --}}
{{ $outlet['hours'] }} <div class="relative" x-data="{ open: false }">
</div> <button @click="open = !open" @click.outside="open = false"
<div class="flex items-center gap-2"> class="w-12 h-12 rounded-xl border border-home-foreground/10 flex items-center justify-center text-home-foreground hover:border-home-primary hover:text-home-primary transition-all">
<svg class="w-4 h-4 text-home-primary" fill="none" stroke="currentColor" <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z">
d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"> </path>
</path> </svg>
</svg> </button>
Call Store
</div>
</div>
<div class="mt-auto flex gap-3"> {{-- Dropdown Menu --}}
<button <div x-show="open" x-transition:enter="transition ease-out duration-200"
class="flex-1 py-3 rounded-xl bg-home-primary text-white text-sm font-bold hover:bg-home-secondary hover:text-home-primary transition-colors shadow-lg shadow-home-primary/20"> x-transition:enter-start="opacity-0 scale-95 translate-y-2"
Petunjuk Arah x-transition:enter-end="opacity-100 scale-100 translate-y-0"
</button> x-transition:leave="transition ease-in duration-150"
<button x-transition:leave-start="opacity-100 scale-100 translate-y-0"
class="w-12 h-12 rounded-xl border border-home-foreground/10 flex items-center justify-center text-home-foreground hover:border-home-primary hover:text-home-primary transition-all"> x-transition:leave-end="opacity-0 scale-95 translate-y-2"
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> class="absolute bottom-full right-0 mb-3 w-48 bg-white rounded-2xl shadow-xl border border-home-foreground/5 py-2 z-30"
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" style="display: none;">
d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z">
</path> <div
</svg> class="px-4 py-2 text-[10px] font-bold text-home-foreground/40 uppercase tracking-widest border-b border-home-foreground/5 mb-1">
</button> Bagikan Ke
</div>
{{-- WhatsApp --}}
<a href="https://wa.me/?text={{ urlencode('Cek outlet ' . $outlet['name'] . ' di Pangestu Yadi Parfum: ' . ($outlet['maps_url'] ?: url()->current())) }}"
target="_blank"
class="flex items-center gap-3 px-4 py-2 text-sm text-home-foreground hover:bg-home-primary/5 hover:text-home-primary transition-colors">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path
d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L0 24l6.335-1.662c1.72.94 3.659 1.437 5.634 1.437h.005c6.515 0 11.825-5.309 11.828-11.825a11.823 11.823 0 00-3.414-8.369" />
</svg>
WhatsApp
</a>
{{-- Facebook --}}
<a href="https://www.facebook.com/sharer/sharer.php?u={{ urlencode($outlet['maps_url'] ?: url()->current()) }}"
target="_blank"
class="flex items-center gap-3 px-4 py-2 text-sm text-home-foreground hover:bg-home-primary/5 hover:text-home-primary transition-colors">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path
d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" />
</svg>
Facebook
</a>
{{-- X (Twitter) --}}
<a href="https://twitter.com/intent/tweet?url={{ urlencode($outlet['maps_url'] ?: url()->current()) }}&text={{ urlencode('Cek outlet ' . $outlet['name'] . ' di Pangestu Yadi Parfum') }}"
target="_blank"
class="flex items-center gap-3 px-4 py-2 text-sm text-home-foreground hover:bg-home-primary/5 hover:text-home-primary transition-colors">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</svg>
X (Twitter)
</a>
{{-- Copy Link --}}
<button
@click="navigator.clipboard.writeText('{{ $outlet['maps_url'] ?: url()->current() }}'); $dispatch('notify', { message: 'Link berhasil disalin!', type: 'success' }); open = false"
class="w-full flex items-center gap-3 px-4 py-2 text-sm text-home-foreground hover:bg-home-primary/5 hover:text-home-primary transition-colors text-left">
<svg class="w-4 h-4" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3">
</path>
</svg>
Salin Tautan
</button>
</div>
</div>
</div> </div>
</div> </div>
</article> </article>
@ -156,19 +164,199 @@ class="w-12 h-12 rounded-xl border border-home-foreground/10 flex items-center j
class="absolute inset-0 bg-home-foreground/5 z-10 pointer-events-none group-hover:bg-transparent transition-colors duration-500"> class="absolute inset-0 bg-home-foreground/5 z-10 pointer-events-none group-hover:bg-transparent transition-colors duration-500">
</div> </div>
<div
class="absolute top-6 left-6 z-20 bg-white/90 backdrop-blur-md px-6 py-4 rounded-2xl shadow-lg border border-white/20 hidden md:block">
<p class="text-xs text-home-foreground/50 font-bold uppercase tracking-wider mb-1">Cakupan Area</p>
<h3 class="text-home-primary font-bold">Indonesia & Asia Tenggara</h3>
</div>
<iframe <iframe
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d126920.28318625622!2d106.76457105!3d-6.229571199999999!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x2e69f3e945e34b9d%3A0x5371bf0fdad786a2!2sJakarta%2C%20Special%20Capital%20Region%20of%20Jakarta!5e0!3m2!1sen!2sid!4v1652756763428!5m2!1sen!2sid" src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d8506.860993234519!2d107.43951033371029!3d-6.543855086323806!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x2e690f57aa3eaf5b%3A0x2bbc334edbb98b17!2sYadi%20Parfum!5e1!3m2!1sen!2sid!4v1766221140539!5m2!1sen!2sid"
width="100%" height="450" style="border:0; filter: grayscale(100%) contrast(1.2);" width="100%" height="450" style="border:0;" allowfullscreen="" loading="lazy"
allowfullscreen="" loading="lazy" class="hover:filter-none transition-all duration-700"> referrerpolicy="no-referrer-when-downgrade"
style="border:0; filter: grayscale(100%) contrast(1.2);" allowfullscreen="" loading="lazy"
class="hover:filter-none transition-all duration-700">
</iframe> </iframe>
</section> </section>
</main> </main>
</div> </div>
{{-- Detail Modal --}}
@if ($selectedOutlet)
<div class="fixed inset-0 z-[100] flex items-center justify-center p-4 sm:p-6 md:p-10" x-data
x-on:keydown.escape.window="$wire.closeModal()">
<!-- Backdrop -->
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm" wire:click="closeModal"
x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"></div>
<!-- Modal Content -->
<div class="relative bg-white w-full max-w-5xl max-h-full overflow-y-auto rounded-3xl shadow-2xl flex flex-col md:flex-row animate-in fade-in zoom-in duration-300"
x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100" x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95">
<!-- Close Button -->
<button wire:click="closeModal"
class="absolute top-4 right-4 md:top-6 md:right-6 z-[60] w-10 h-10 rounded-full bg-white shadow-xl border border-home-foreground/5 text-home-foreground hover:text-home-primary hover:scale-110 transition-all flex items-center justify-center group"
title="Tutup">
<svg class="w-5 h-5 transition-transform group-hover:rotate-90 duration-300" fill="none"
stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M6 18L18 6M6 6l12 12">
</path>
</svg>
</button>
<!-- Left: Image Section (Gallery) -->
@php
$allImages = collect();
if ($selectedOutlet->hasMedia('featured_image')) {
$allImages->push($selectedOutlet->getFirstMediaUrl('featured_image'));
}
if ($selectedOutlet->hasMedia('images')) {
foreach ($selectedOutlet->getMedia('images') as $media) {
$allImages->push($media->getUrl());
}
}
if ($allImages->isEmpty()) {
$allImages->push('https://placehold.co/800x600/png?text=' . urlencode($selectedOutlet->name));
}
@endphp
<div class="md:w-1/2 flex flex-col bg-gray-50" x-data="{
activeImage: '{{ $allImages->first() }}',
images: {{ $allImages->toJson() }}
}">
<!-- Main Image Display -->
<div class="relative flex-grow h-[300px] md:h-auto overflow-hidden">
<template x-for="(img, index) in images" :key="index">
<img :src="img" x-show="activeImage === img"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 scale-105"
x-transition:enter-end="opacity-100 scale-100"
class="absolute inset-0 w-full h-full object-cover"
alt="{{ $selectedOutlet->name }}">
</template>
<div
class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent md:hidden">
</div>
<div class="absolute bottom-6 left-6 text-white md:hidden">
<h2 class="text-3xl font-bold">{{ $selectedOutlet->name }}</h2>
</div>
</div>
<!-- Thumbnails -->
@if ($allImages->count() > 1)
<div class="p-4 bg-white border-t border-gray-100">
<div class="flex gap-2 overflow-x-auto pb-2 scrollbar-none">
<template x-for="(img, index) in images" :key="index">
<button @click="activeImage = img"
class="relative w-20 h-20 rounded-xl overflow-hidden shrink-0 transition-all duration-300"
:class="activeImage === img ? 'ring-2 ring-home-primary ring-offset-2 scale-95' :
'opacity-60 hover:opacity-100'">
<img :src="img" class="w-full h-full object-cover">
</button>
</template>
</div>
</div>
@endif
</div>
<!-- Right: Details Section -->
<div class="md:w-1/2 p-8 md:p-12">
<div class="hidden md:block mb-8">
<span class="text-home-primary font-bold tracking-widest text-xs uppercase mb-2 block">Detail
Outlet</span>
<h2 class="text-4xl font-bold text-home-foreground">{{ $selectedOutlet->name }}</h2>
</div>
<div class="space-y-8">
<!-- Status & Address -->
<div>
<div class="flex items-center gap-3 mb-4">
<span
class="px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-wide
{{ $selectedOutlet->status === \App\Enums\OutletStatus::OPERATIONAL ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700' }}">
{{ $selectedOutlet->status->label() }}
</span>
</div>
<div class="flex items-start gap-4">
<div
class="w-10 h-10 rounded-full bg-home-primary/10 flex items-center justify-center shrink-0">
<svg class="w-5 h-5 text-home-primary" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z">
</path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
</svg>
</div>
<p class="text-lg text-home-foreground/70">{{ $selectedOutlet->address }}</p>
</div>
</div>
<!-- Facilities -->
<div>
<h4 class="text-sm font-bold text-home-foreground uppercase tracking-wider mb-4">Fasilitas
</h4>
<div class="flex flex-wrap gap-2">
@forelse($selectedOutlet->facilities as $facility)
<span
class="px-4 py-2 bg-home-foreground/5 rounded-xl text-sm text-home-foreground/70 border border-home-foreground/10">
{{ $facility->name }}
</span>
@empty
<p class="text-sm text-home-foreground/40 italic">Tidak ada fasilitas khusus</p>
@endforelse
</div>
</div>
<!-- Opening Hours -->
<div>
<h4 class="text-sm font-bold text-home-foreground uppercase tracking-wider mb-4">Jam
Operasional</h4>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
@php
$todayEnum = now()->dayOfWeekIso === 7 ? 1 : now()->dayOfWeekIso + 1;
@endphp
@foreach ($selectedOutlet->openingHours->sortBy('day.value') as $hour)
<div
class="flex justify-between items-center p-3 rounded-xl border border-home-foreground/5 {{ $hour->day->value === $todayEnum ? 'bg-home-primary/5 border-home-primary/20' : '' }}">
<span
class="text-xs font-medium {{ $hour->day->value === $todayEnum ? 'text-home-primary font-bold' : 'text-home-foreground/60' }}">
{{ $hour->day->label() }}
</span>
<span class="text-xs font-bold text-home-foreground">
{{ \Carbon\Carbon::parse($hour->open_time)->format('H:i') }} -
{{ \Carbon\Carbon::parse($hour->close_time)->format('H:i') }}
</span>
</div>
@endforeach
</div>
</div>
<!-- Contact & Maps -->
<div class="flex gap-4 pt-4">
<a href="tel:{{ $selectedOutlet->phone_number }}"
class="flex-1 py-4 rounded-2xl bg-white border border-home-foreground/10 text-home-foreground text-center font-bold hover:border-home-primary hover:text-home-primary transition-all flex items-center justify-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z">
</path>
</svg>
Telepon
</a>
<a href="{{ $selectedOutlet->maps_url }}" target="_blank"
class="flex-[1.5] py-4 rounded-2xl bg-home-primary text-white text-center font-bold hover:bg-home-secondary hover:text-home-primary transition-all shadow-lg shadow-home-primary/20 flex items-center justify-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7">
</path>
</svg>
Petunjuk Arah
</a>
</div>
</div>
</div>
</div>
</div>
@endif
</div> </div>

View File

@ -21,14 +21,15 @@ class="px-8 py-4 rounded-xl bg-home-background text-home-foreground font-bold ho
<div class="flex items-center gap-4 text-sm text-home-background/40"> <div class="flex items-center gap-4 text-sm text-home-background/40">
<div class="flex -space-x-2"> <div class="flex -space-x-2">
<img class="w-8 h-8 rounded-full border-2 border-white" src="https://placehold.co/100x100/png" @foreach ($latestMembers as $member)
alt="User"> <img class="w-8 h-8 rounded-full border-2 border-white"
<img class="w-8 h-8 rounded-full border-2 border-white" src="https://placehold.co/100x100/png" src="https://ui-avatars.com/api/?name={{ urlencode($member->name) }}&background=random&color=fff"
alt="User"> alt="{{ $member->name }}">
<img class="w-8 h-8 rounded-full border-2 border-white" src="https://placehold.co/100x100/png" @endforeach
alt="User">
</div> </div>
<p>Bergabung bersama <span class="text-home-background font-bold">2,000+</span> member lainnya.</p> <p>Bergabung bersama <span
class="text-home-background font-bold">{{ number_format($totalMembersCount) }}+</span>
member lainnya.</p>
</div> </div>
</div> </div>
@ -74,4 +75,4 @@ class="w-8 h-8 opacity-80 brightness-0 invert">
</div> </div>
</div> </div>
</section> </section>

View File

@ -7,8 +7,8 @@ class="relative w-full lg:w-[35%] pt-24 lg:pt-0 py-0 px-6 lg:px-10 lg:pb-8 flex
@foreach ($latestMembers as $member) @foreach ($latestMembers as $member)
<figure <figure
class="border-2 border-home-background w-8 h-8 lg:w-10 lg:h-10 rounded-full overflow-hidden shadow-sm {{ !$loop->first ? '-ms-2' : '' }}"> class="border-2 border-home-background w-8 h-8 lg:w-10 lg:h-10 rounded-full overflow-hidden shadow-sm {{ !$loop->first ? '-ms-2' : '' }}">
<img src="https://ui-avatars.com/api/?name={{ urlencode($member->name) }}&background=random&color=fff" <img src="https://ui-avatars.com/api/?name={{ urlencode($member->user->name ?? $member->name) }}&background=random&color=fff"
alt="{{ $member->name }}" class="w-full h-full object-cover"> alt="{{ $member->user->name ?? $member->name }}" class="w-full h-full object-cover">
</figure> </figure>
@endforeach @endforeach
</div> </div>

View File

@ -20,11 +20,19 @@ class="w-full h-full object-cover group-hover:scale-105 transition-transform dur
alt="{{ $perfume['name'] }}"> alt="{{ $perfume['name'] }}">
</figure> </figure>
<div class="flex-1 ml-4 lg:ml-6 flex flex-col justify-center space-y-2 lg:space-y-4"> <div class="flex-1 ml-4 lg:ml-6 flex flex-col justify-center space-y-2 lg:space-y-3">
<div> <div>
<h3 class="text-lg lg:text-2xl text-black font-bold line-clamp-1">{{ $perfume['name'] }}</h3> <h3 class="text-base lg:text-xl text-black font-bold line-clamp-1">{{ $perfume['name'] }}</h3>
<p class="text-black/60 text-xs lg:text-lg line-clamp-2"> <div class="flex items-center gap-2 mt-1">
{{ $perfume['concentration_label'] }}</p> <span class="text-home-primary font-bold text-sm lg:text-lg">
Rp {{ number_format($perfume['sale_price'], 0, ',', '.') }}
</span>
</div>
<div class="flex items-center gap-1.5 mt-0.5">
<span class="text-[10px] lg:text-xs font-medium text-gray-400 capitalize">
{{ number_format($perfume['items_sum_quantity'], 0, ',', '.') }} Terjual
</span>
</div>
</div> </div>
<div class="flex items-center gap-2 mt-auto"> <div class="flex items-center gap-2 mt-auto">

View File

@ -42,12 +42,12 @@ class="relative w-full mt-auto h-[300px] lg:h-[250px] bg-home-background/40 back
<div class="testimonial-slide absolute inset-0 py-0 px-6 flex flex-col justify-center transition-all duration-500 ease-in-out {{ $index === 0 ? 'opacity-100 z-10 translate-x-0' : 'opacity-0 z-0 translate-x-full' }}" <div class="testimonial-slide absolute inset-0 py-0 px-6 flex flex-col justify-center transition-all duration-500 ease-in-out {{ $index === 0 ? 'opacity-100 z-10 translate-x-0' : 'opacity-0 z-0 translate-x-full' }}"
:class="getClass({{ $index }})"> :class="getClass({{ $index }})">
<div class="flex items-center gap-4 mb-3"> <div class="flex items-center gap-4 mb-3">
<img src="https://ui-avatars.com/api/?name={{ urlencode($testimonial['user']['customer']['name']) }}&background=random&color=fff" <img src="https://ui-avatars.com/api/?name={{ urlencode($testimonial['user']['customer']['name'] ?? 'User') }}&background=random&color=fff"
alt="{{ $testimonial['user']['customer']['name'] }}" alt="{{ $testimonial['user']['customer']['name'] ?? 'User' }}"
class="w-12 h-12 rounded-full object-cover ring-2 ring-home-background"> class="w-12 h-12 rounded-full object-cover ring-2 ring-home-background">
<div> <div>
<h4 class="font-bold text-home-foreground text-sm lg:text-base"> <h4 class="font-bold text-home-foreground text-sm lg:text-base">
{{ $testimonial['user']['customer']['name'] }}</h4> {{ $testimonial['user']['customer']['name'] ?? 'User' }}</h4>
<div class="flex text-home-primary text-xs"> <div class="flex text-home-primary text-xs">
@for ($i = 0; $i < $testimonial['rating']; $i++) @for ($i = 0; $i < $testimonial['rating']; $i++)

View File

@ -11,16 +11,6 @@
dirancang untuk bertahan lama, memberikan pengalaman wangi yang istimewa, dan memastikan setiap dirancang untuk bertahan lama, memberikan pengalaman wangi yang istimewa, dan memastikan setiap
tetesnya asli dan terpercaya. tetesnya asli dan terpercaya.
</p> </p>
<div class="pt-4">
<button
class="text-home-primary font-bold hover:opacity-80 transition-opacity flex items-center gap-2">
Pelajari Selengkapnya
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M17 8l4 4m0 0l-4 4m4-4H3"></path>
</svg>
</button>
</div>
</div> </div>
<div class="order-1 lg:order-2 relative"> <div class="order-1 lg:order-2 relative">

View File

@ -4,7 +4,7 @@
<section class="relative h-[500px] flex items-center justify-center overflow-hidden"> <section class="relative h-[500px] flex items-center justify-center overflow-hidden">
{{-- Background Image --}} {{-- Background Image --}}
<div class="absolute inset-0 z-0"> <div class="absolute inset-0 z-0">
<img src="https://images.unsplash.com/photo-1651737232278-3875f741de86?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" <img src="https://images.pexels.com/photos/22589353/pexels-photo-22589353.jpeg"
alt="Perfume Collection Background" class="w-full h-full object-cover"> alt="Perfume Collection Background" class="w-full h-full object-cover">
<div class="absolute inset-0 bg-black/50"></div> <div class="absolute inset-0 bg-black/50"></div>
</div> </div>

View File

@ -3,8 +3,8 @@
<section class="relative h-[500px] flex items-center justify-center overflow-hidden"> <section class="relative h-[500px] flex items-center justify-center overflow-hidden">
{{-- Background Image --}} {{-- Background Image --}}
<div class="absolute inset-0 z-0"> <div class="absolute inset-0 z-0">
<img src="https://images.unsplash.com/photo-1541643600914-78b084683601?q=80&w=1469&auto=format&fit=crop" <img src="https://images.pexels.com/photos/5650052/pexels-photo-5650052.jpeg" alt="Voucher Background"
alt="Voucher Background" class="w-full h-full object-cover"> class="w-full h-full object-cover">
<div class="absolute inset-0 bg-black/50"></div> <div class="absolute inset-0 bg-black/50"></div>
</div> </div>
@ -34,7 +34,7 @@ class="absolute right-2 top-2 bottom-2 w-10 h-10 bg-home-primary rounded-full fl
<main class="flex-grow px-6 lg:px-32 xl:px-48 py-24 font-sans flex flex-col"> <main class="flex-grow px-6 lg:px-32 xl:px-48 py-24 font-sans flex flex-col">
<section class="flex flex-col lg:flex-row justify-center gap-4 mb-12" aria-label="Filter"> <section class="flex flex-col lg:flex-row justify-center gap-4 mb-12" aria-label="Filter">
<div class="flex flex-col sm:flex-row gap-4"> <div class="flex flex-col sm:flex-row gap-4">
<div class="relative"> <div class="relative">
<select wire:model.live="outlet_id" aria-label="Filter Outlet" <select wire:model.live="outlet_id" aria-label="Filter Outlet"
@ -153,11 +153,11 @@ class="font-bold text-home-foreground">{{ $voucher->end_date }}</time>
</div> </div>
<div class="p-6 flex gap-3 mt-auto"> <div class="p-6 flex gap-3 mt-auto">
<button <button wire:click="claim('{{ $voucher->hash }}')"
class="flex-1 bg-home-primary text-white font-bold py-3 rounded-xl hover:bg-home-secondary hover:text-home-primary hover:shadow-lg hover:shadow-home-primary/20 transition-all duration-300 text-sm shadow-md shadow-home-primary/10"> class="flex-1 bg-home-primary text-white font-bold py-3 rounded-xl hover:bg-home-secondary hover:text-home-primary hover:shadow-lg hover:shadow-home-primary/20 transition-all duration-300 text-sm shadow-md shadow-home-primary/10">
Klaim Voucher Klaim Voucher
</button> </button>
<button <button wire:click="showDetail('{{ $voucher->hash }}')"
class="flex-1 border border-home-foreground/20 text-home-foreground font-bold py-3 rounded-xl hover:border-home-primary hover:text-home-primary hover:bg-home-primary/5 transition-all duration-300 text-sm"> class="flex-1 border border-home-foreground/20 text-home-foreground font-bold py-3 rounded-xl hover:border-home-primary hover:text-home-primary hover:bg-home-primary/5 transition-all duration-300 text-sm">
Lihat Detail Lihat Detail
</button> </button>
@ -167,8 +167,153 @@ class="flex-1 border border-home-foreground/20 text-home-foreground font-bold py
</section> </section>
<div class="mt-auto flex justify-center w-full"> <div class="mt-auto flex justify-center w-full">
{{ $vouchers->links() }} {{ $vouchers->links('vendor.livewire.custom-pagination') }}
</div> </div>
</main> </main>
{{-- Voucher Detail Modal --}}
@if ($showDetailModal && $selectedVoucher)
<div class="fixed inset-0 z-50 flex items-center justify-center p-4">
{{-- Backdrop --}}
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm" wire:click="closeDetail"></div>
{{-- Modal Content --}}
<div
class="relative bg-white w-full max-w-2xl rounded-3xl overflow-hidden shadow-2xl animate-in fade-in zoom-in duration-300">
{{-- Modal Header --}}
<div class="relative h-48 bg-home-primary flex items-center justify-center overflow-hidden">
<div class="absolute inset-0 opacity-20">
<div class="absolute top-0 right-0 w-64 h-64 bg-white rounded-full -mr-32 -mt-32"></div>
<div class="absolute bottom-0 left-0 w-48 h-48 bg-white rounded-full -ml-24 -mb-24"></div>
</div>
<div class="relative z-10 text-center">
<div
class="inline-flex items-center gap-2 bg-white/20 backdrop-blur-md px-4 py-1.5 rounded-full border border-white/30 text-white text-xs font-bold uppercase tracking-widest mb-4">
<span class="w-2 h-2 rounded-full bg-white animate-pulse"></span>
{{ $selectedVoucher->tags }}
</div>
<h2 class="text-3xl font-bold text-white px-6">{{ $selectedVoucher->name }}</h2>
</div>
<button wire:click="closeDetail"
class="absolute top-6 right-6 w-10 h-10 rounded-full bg-white/10 hover:bg-white/20 text-white flex items-center justify-center transition-all">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
{{-- Modal Body --}}
<div class="p-8 max-h-[60vh] overflow-y-auto custom-scrollbar">
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8">
<div>
<h4 class="text-sm font-bold text-home-foreground/40 uppercase tracking-wider mb-4">Syarat
&
Ketentuan</h4>
<div class="space-y-4">
<div class="flex items-start gap-3">
<div
class="w-8 h-8 rounded-lg bg-home-primary/5 flex items-center justify-center text-home-primary mt-0.5">
<svg class="w-4 h-4" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2-1.343-2-3-2zm0 4c-2.485 0-4.5 1.567-4.5 3.5V18h9v-2.5c0-1.933-2.015-3.5-4.5-3.5z">
</path>
</svg>
</div>
<div>
<p class="text-sm font-medium text-home-foreground/50">Minimum Pembelian</p>
<p class="font-bold text-home-foreground">
{{ $selectedVoucher->min_purchase_formatted }}</p>
</div>
</div>
<div class="flex items-start gap-3">
<div
class="w-8 h-8 rounded-lg bg-home-primary/5 flex items-center justify-center text-home-primary mt-0.5">
<svg class="w-4 h-4" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z">
</path>
</svg>
</div>
<div>
<p class="text-sm font-medium text-home-foreground/50">Masa Berlaku</p>
<p class="font-bold text-home-foreground">Hingga
{{ $selectedVoucher->end_date_formatted }}</p>
</div>
</div>
@if ($selectedVoucher->limit_per_user)
<div class="flex items-start gap-3">
<div
class="w-8 h-8 rounded-lg bg-home-primary/5 flex items-center justify-center text-home-primary mt-0.5">
<svg class="w-4 h-4" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z">
</path>
</svg>
</div>
<div>
<p class="text-sm font-medium text-home-foreground/50">Batas per Pengguna
</p>
<p class="font-bold text-home-foreground">
{{ $selectedVoucher->limit_per_user }}x Klaim</p>
</div>
</div>
@endif
</div>
</div>
<div>
<h4 class="text-sm font-bold text-home-foreground/40 uppercase tracking-wider mb-4">Outlet
Tersedia
</h4>
<div class="flex flex-wrap gap-2">
@forelse ($selectedVoucher->outlets as $outlet)
<span
class="px-4 py-2 bg-home-primary/5 border border-home-primary/10 rounded-xl text-sm font-semibold text-home-primary flex items-center gap-2">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z">
</path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
</svg>
{{ $outlet->name }}
</span>
@empty
<span
class="px-4 py-2 bg-home-foreground/5 border border-home-foreground/10 rounded-xl text-sm font-semibold text-home-foreground/50">
Dapat digunakan di semua outlet
</span>
@endforelse
</div>
</div>
</div>
<div class="bg-home-foreground/5 rounded-2xl p-6">
<h4 class="text-sm font-bold text-home-foreground/40 uppercase tracking-wider mb-4">Ringkasan
</h4>
<p class="text-home-foreground/70 leading-relaxed italic">
"{{ $selectedVoucher->summary }}"
</p>
</div>
</div>
{{-- Modal Footer --}}
<div class="p-8 bg-white border-t border-home-foreground/5 flex gap-4">
<button wire:click="closeDetail"
class="flex-1 py-4 px-6 border border-home-foreground/20 text-home-foreground font-bold rounded-2xl hover:bg-home-foreground/5 transition-all">
Tutup
</button>
<button wire:click="claim('{{ $selectedVoucher->hashId }}')"
class="flex-[2] py-4 px-6 bg-home-primary text-white font-bold rounded-2xl hover:bg-home-secondary hover:text-home-primary hover:shadow-xl hover:shadow-home-primary/20 transition-all shadow-lg shadow-home-primary/10">
Klaim Sekarang
</button>
</div>
</div>
</div>
@endif
</div> </div>