feat: Implement article and product show pages, refactor home components, and update migrations

This commit is contained in:
Yoga Pangestu 2025-12-26 20:35:03 +07:00
parent b469fbff53
commit 3c1b6fc915
42 changed files with 1709 additions and 1230 deletions

View File

@ -82,3 +82,42 @@ function convertNumberToIndonesianWords($angka)
} }
} }
} }
if (! function_exists('formatToWhatsApp')) {
function formatToWhatsApp(string $phone): ?string
{
// Remove spaces, dashes, parentheses
$cleaned = preg_replace('/[\s\-\(\)]/', '', $phone);
if ($cleaned === null || $cleaned === '') {
return null;
}
// Remove leading +
if (str_starts_with($cleaned, '+62')) {
$cleaned = substr($cleaned, 1);
}
// Replace 08 with 62
if (str_starts_with($cleaned, '08')) {
$cleaned = '62'.substr($cleaned, 1);
}
// If starts with 8, assume Indonesia
if (str_starts_with($cleaned, '8')) {
$cleaned = '62'.$cleaned;
}
// Final validation
if (! str_starts_with($cleaned, '62')) {
return null;
}
// Indonesia phone length sanity check
if (strlen($cleaned) < 11 || strlen($cleaned) > 15) {
return null;
}
return $cleaned;
}
}

View File

@ -77,8 +77,8 @@ public function columns(): array
->searchable() ->searchable()
->sortable(), ->sortable(),
Column::make('Batas Per Pembali', 'limit_per_user') Column::make('Batas Per Pembeli', 'limit_per_user')
->format(fn ($value) => $value ? $value : 'Tidak Terbatas') ->format(fn ($value) => $value ? formatCurrencyNumber($value) : 'Tidak Terbatas')
->searchable() ->searchable()
->sortable(), ->sortable(),

View File

@ -30,7 +30,7 @@ class VoucherForm extends Form
public ?string $quota = null; public ?string $quota = null;
public ?string $limit_per_user = null; public string $limit_per_user = '';
public string $summary = ''; public string $summary = '';
@ -74,10 +74,10 @@ public function rules(): array
), ),
], ],
'quota' => ['nullable', new UnsignedInteger], 'quota' => ['nullable', new UnsignedInteger],
'limit_per_user' => ['nullable', new UnsignedInteger], 'limit_per_user' => ['required', new UnsignedInteger],
'summary' => ['required', 'string', 'max:200'], 'summary' => ['required', 'string', 'max:200'],
'start_date' => ['required', 'date', Rule::when( 'start_date' => ['required', 'date', Rule::when(
$this->start_date !== $this->voucher->start_date, $this->voucher && $this->start_date !== $this->voucher->start_date,
'after_or_equal:today' 'after_or_equal:today'
)], )],
'end_date' => ['nullable', 'date', 'after_or_equal:start_date'], 'end_date' => ['nullable', 'date', 'after_or_equal:start_date'],
@ -181,9 +181,9 @@ private function prepareSavedData(): array
'discount_amount' => parseRupiahToInt($this->discount_amount), 'discount_amount' => parseRupiahToInt($this->discount_amount),
'min_purchase' => parseRupiahToInt($this->min_purchase), 'min_purchase' => parseRupiahToInt($this->min_purchase),
'max_discount' => $this->type == VoucherType::PERCENTAGE->value ? parseRupiahToInt($this->max_discount) : null, 'max_discount' => $this->type == VoucherType::PERCENTAGE->value ? parseRupiahToInt($this->max_discount) : null,
'quota' => $this->quota, 'quota' => parseRupiahToInt($this->quota),
'available_count' => empty($this->quota) ? 0 : $this->quota, 'available_count' => empty($this->quota) ? 0 : parseRupiahToInt($this->quota),
'limit_per_user' => $this->limit_per_user, 'limit_per_user' => parseRupiahToInt($this->limit_per_user),
'summary' => $this->summary, 'summary' => $this->summary,
'start_date' => $this->start_date, 'start_date' => $this->start_date,
'end_date' => $this->end_date, 'end_date' => $this->end_date,

View File

@ -2,33 +2,107 @@
namespace App\Livewire\Home\Article; namespace App\Livewire\Home\Article;
use App\Enums\CategoryType;
use App\Models\Article;
use App\Models\Category;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Url;
use Livewire\Component; use Livewire\Component;
use Livewire\WithoutUrlPagination;
use Livewire\WithPagination;
#[Layout('components.layouts.home', [ #[Layout('components.layouts.home', [
'title' => 'Article', 'title' => 'Artikel',
])] ])]
class Index extends Component class Index extends Component
{ {
use WithoutUrlPagination, WithPagination;
public bool $isDetail = false; public bool $isDetail = false;
public ?string $slug = null; public ?string $search = null;
public function mount(?string $slug = null): void public ?string $sort = null;
public array $categories = [];
public array $popularArticles = [];
public function mount(): void
{ {
if ($slug) { $this->categories = Category::query()
$this->isDetail = true; ->where('type', CategoryType::ARTICLE)
$this->slug = $slug; ->orderBy('sort_order')
} ->get()
->toArray();
$this->popularArticles = Article::published()
->with(['author', 'categories'])
->orderBy('views', 'desc')
->take(5)
->get()
->map(function ($article) {
$article->thumbnail = $article->getFirstMediaUrl('thumbnail') ? $article->getFirstMediaUrl('thumbnail') : asset('assets/images/dark-logo.png');
return $article;
})
->toArray();
}
#[Url(as: 'category')]
public ?string $categorySlug = null;
public function setCategory(?string $slug = null): void
{
$this->categorySlug = $slug;
$this->resetPage();
}
public function updatedSearch(): void
{
$this->resetPage();
}
public function updateSort(): void
{
$this->resetPage();
} }
public function render(): View public function render(): View
{ {
return view( $articles = Article::with(['author', 'categories'])
$this->isDetail ->published()
? 'livewire.home.article.show' ->when($this->search, function (Builder $query) {
: 'livewire.home.article.index' $query->where('title', 'like', '%'.$this->search.'%');
); })
->when($this->sort, function (Builder $query) {
match ($this->sort) {
'latest' => $query->orderBy('created_at', 'desc'),
'Alphabetic' => $query->orderBy('title', 'asc'),
'views' => $query->orderBy('views', 'desc'),
default => $query->latest(),
};
}, fn ($query) => $query->latest())
->when($this->categorySlug, function (Builder $query) {
$query->whereHas('categories', function ($q) {
$q->where('slug', $this->categorySlug);
});
})
->latest()
->paginate(9)
->through(function (Article $article) {
$article->thumbnail = $article->getFirstMediaUrl('thumbnail') ? $article->getFirstMediaUrl('thumbnail') : asset('assets/images/dark-logo.png');
return $article;
});
return view('livewire.home.article.index', [
'pageTitle' => 'Artikel',
'pageDesc' => 'Baca tips profesional seputar perawatan wewangian, tren lifestyle, dan rekomendasi terbaik.',
'articles' => $articles,
]);
} }
} }

View File

@ -0,0 +1,60 @@
<?php
namespace App\Livewire\Home\Article;
use App\Models\Article;
use Illuminate\Contracts\View\View;
use Livewire\Attributes\Layout;
use Livewire\Component;
#[Layout('components.layouts.home', [
'title' => 'Detail Artikel',
])]
class Show extends Component
{
public Article $article;
public array $latestArticles = [];
public ?Article $previousArticle = null;
public ?Article $nextArticle = null;
public function mount(Article $article): void
{
$article->load(['author', 'author.employee', 'categories']);
$this->article = $article;
$this->article->increment('views');
$article->thumbnail = $article->getFirstMediaUrl('thumbnail') ? $article->getFirstMediaUrl('thumbnail') : asset('assets/images/dark-logo.png');
$this->latestArticles = Article::where('id', '!=', $article->id)
->published()
->orderBy('published_at', 'desc')
->take(5)
->get()
->map(function ($article) {
$article->thumbnail = $article->getFirstMediaUrl('thumbnail') ? $article->getFirstMediaUrl('thumbnail') : asset('assets/images/dark-logo.png');
return $article;
})
->toArray();
$this->previousArticle = Article::where('id', '<', $article->id)
->published()
->orderBy('published_at', 'desc')
->first();
$this->nextArticle = Article::where('id', '>', $article->id)
->published()
->orderBy('published_at', 'asc')
->first();
}
public function render(): View
{
return view('livewire.home.article.show', [
'pageTitle' => $this->article->title,
]);
}
}

33
app/Livewire/Home/Faq.php Normal file
View File

@ -0,0 +1,33 @@
<?php
namespace App\Livewire\Home;
use App\Models\Faq as FaqModel;
use Illuminate\Contracts\View\View;
use Livewire\Attributes\Layout;
use Livewire\Component;
#[Layout('components.layouts.home', [
'title' => 'FAQs',
])]
class Faq extends Component
{
public ?string $search = null;
public function render(): View
{
$faqs = FaqModel::orderBy('sort_order')
->when($this->search, function ($query) {
$query->where('question', 'like', '%'.$this->search.'%')
->orWhere('answer', 'like', '%'.$this->search.'%');
})
->get()
->toArray();
return view('livewire.home.faqs', [
'pageTitle' => 'FAQs',
'pageDesc' => 'Daftar pertanyaan yang sering diajukan beserta jawabannya.',
'faqs' => $faqs,
]);
}
}

View File

@ -1,18 +0,0 @@
<?php
namespace App\Livewire\Home\Faq;
use Illuminate\Contracts\View\View;
use Livewire\Attributes\Layout;
use Livewire\Component;
#[Layout('components.layouts.home', [
'title' => 'FAQ',
])]
class Index extends Component
{
public function render(): View
{
return view('livewire.home.faq.index');
}
}

View File

@ -97,7 +97,8 @@ public function render(): View
{ {
return view('livewire.home.index', [ return view('livewire.home.index', [
'pageTitle' => 'Beranda', 'pageTitle' => 'Beranda',
'categories' => Category::orderBy('sort_order') 'categories' => Category::perfume()
->orderBy('sort_order')
->paginate(12) ->paginate(12)
->through(function (Category $category) { ->through(function (Category $category) {
$category->image = $category->getFirstMediaUrl('image') $category->image = $category->getFirstMediaUrl('image')

View File

@ -42,7 +42,7 @@ public function mount(): void
public function showDetail(int $outletId): void public function showDetail(int $outletId): void
{ {
$this->selectedOutlet = OutletModel::with(['facilities', 'openingHours', 'media'])->find($outletId); $this->selectedOutlet = OutletModel::with(['facilities', 'openingHours'])->find($outletId);
} }
public function closeModal(): void public function closeModal(): void

View File

@ -2,10 +2,16 @@
namespace App\Livewire\Home\Product; namespace App\Livewire\Home\Product;
use App\Models\Bottle;
use App\Models\Category;
use App\Models\Perfume; use App\Models\Perfume;
use App\Models\Product;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Url;
use Livewire\Component; use Livewire\Component;
use Livewire\WithoutUrlPagination;
use Livewire\WithPagination; use Livewire\WithPagination;
#[Layout('components.layouts.home', [ #[Layout('components.layouts.home', [
@ -13,54 +19,173 @@
])] ])]
class Index extends Component class Index extends Component
{ {
use WithPagination; use WithoutUrlPagination, WithPagination;
public ?string $search = null;
public ?string $sort = 'latest';
#[Url(as: 'type')]
public string $productType = 'perfume'; public string $productType = 'perfume';
#[Url(as: 'categories')]
public array $selectedCategories = [];
#[Url(as: 'price')]
public ?string $priceRange = null;
public array $availableCategories = [];
public array $recommendations = []; public array $recommendations = [];
public function mount(): void public function mount(): void
{ {
$this->availableCategories = Category::perfume()
->orderBy('sort_order')
->pluck('name', 'id')
->toArray();
$this->recommendations = Perfume::with(['brand', 'categories', 'items']) $this->recommendations = Perfume::with(['brand', 'categories', 'items'])
->withCount('items') ->withCount('items')
->orderBy('items_count', 'desc') ->orderBy('items_count', 'desc')
->take(5) ->take(8)
->get() ->get()
->map(function ($perfume) { ->map(function (Perfume $perfume) {
return [ $perfume->image = $perfume->getFirstMediaUrl('image') ?: asset('assets/images/dark-logo.png');
'id' => $perfume->id,
'name' => $perfume->name, return $perfume;
'category' => $perfume->categories->first()?->name,
'price' => $perfume->sale_price,
'image' => $perfume->getFirstMediaUrl() ?: 'https://placehold.co/400x500/png?text='.urlencode($perfume->name),
'rating' => 4.5,
];
}) })
->toArray(); ->toArray();
} }
public function updatedSearch(): void
{
$this->resetPage();
}
public function updatedSort(): void
{
$this->resetPage();
}
public function updatedProductType(): void
{
$this->resetPage();
}
public function updatedSelectedCategories(): void
{
$this->resetPage();
}
public function updatedPriceRange(): void
{
$this->resetPage();
}
public function render(): View public function render(): View
{ {
$perfumes = Perfume::with(['brand', 'categories', 'items']) // Determine which model to query based on productType
->latest() $query = match ($this->productType) {
->paginate(9) 'bottle' => Bottle::query(),
->through(function ($perfume) { 'arabian' => Product::query(),
return [ default => Perfume::with(['brand', 'categories']), // 'perfume'
'id' => $perfume->id, };
'name' => $perfume->name,
'category' => $perfume->categories->first()?->name, // Load items count for badge calculation
'price' => $perfume->sale_price, $query->withCount([
'image' => $perfume->getFirstMediaUrl() ?: 'https://placehold.co/400x500/png?text='.urlencode($perfume->name), 'items as total_sales' => function ($q) {
'rating' => 4.5, $q->whereHas('order', function ($orderQuery) {
'reviews' => $perfume->items->count(), $orderQuery->where('created_at', '>=', now()->subMonth());
'is_new' => $perfume->created_at->diffInDays(now()) <= 30, });
'colors' => ['#78350f', '#1f2937'], },
]; ]);
// Apply search filter
$query->when($this->search, function (Builder $q) {
$q->where('name', 'like', '%'.$this->search.'%');
});
// Apply category filter (only for perfumes)
if ($this->productType === 'perfume' && $this->selectedCategories) {
$query->whereHas('categories', function (Builder $q) {
$q->whereIn('name', $this->selectedCategories);
}); });
}
// Apply price range filter
$query->when($this->priceRange, function (Builder $q) {
match ($this->priceRange) {
'< 100k' => $q->where('sale_price', '<', 100000),
'100k - 500k' => $q->whereBetween('sale_price', [100000, 500000]),
'500k - 1jt' => $q->whereBetween('sale_price', [500000, 1000000]),
'> 1jt' => $q->where('sale_price', '>', 1000000),
default => null,
};
});
// Apply sorting
$query->when($this->sort, function (Builder $q) {
match ($this->sort) {
'price_low' => $q->orderBy('sale_price', 'asc'),
'price_high' => $q->orderBy('sale_price', 'desc'),
'popular' => $q->withCount('items')->orderBy('items_count', 'desc'),
default => $q->latest(), // 'latest'
};
}, fn (Builder $q) => $q->latest());
// Paginate and transform results
$products = $query->paginate(9)->through(function ($product) {
$product->image = $product->getFirstMediaUrl('image') ?: asset('assets/images/dark-logo.png');
// Add categories for perfumes if not already loaded
if ($this->productType === 'perfume' && ! $product->relationLoaded('categories')) {
$product->load('categories');
}
// Determine badge with priority
$product->badge = $this->determineBadge($product);
return $product;
});
return view('livewire.home.product.index', [ return view('livewire.home.product.index', [
'pageTitle' => 'Produk', 'pageTitle' => 'Produk',
'perfumes' => $perfumes, 'pageDesc' => 'Temukan aroma yang mencerminkan kepribadian Anda, memadukan kemewahan parfum dan desain botol eksklusif.',
'products' => $products,
]); ]);
} }
/**
* Determine badge for product based on priority
* Priority: 1. Baru, 2. Terlaris, 3. Hot
*/
private function determineBadge($product): ?array
{
// Priority 1: Terlaris (sales >= 500 in last month)
if (isset($product->total_sales) && $product->total_sales >= 500) {
return [
'label' => 'Terlaris',
'class' => 'bg-red-500 text-white',
];
}
// Priority 2: Baru (created within last month)
if ($product->created_at && $product->created_at->isAfter(now()->subMonth())) {
return [
'label' => 'Baru',
'class' => 'bg-home-primary text-white',
];
}
// Priority 3: Hot (high views - top 20% or > 100 views)
if (isset($product->views) && $product->views > 100) {
return [
'label' => 'Hot',
'class' => 'bg-orange-500 text-white',
];
}
return null;
}
} }

View File

@ -0,0 +1,89 @@
<?php
namespace App\Livewire\Home\Product;
use App\Models\Bottle;
use App\Models\Perfume;
use App\Models\Product;
use Illuminate\Contracts\View\View;
use Livewire\Attributes\Layout;
use Livewire\Component;
#[Layout('components.layouts.home', [
'title' => 'Detail Produk',
])]
class Show extends Component
{
public $product;
public $productType;
public array $relatedProducts = [];
public function mount(string $slug): void
{
// Try to find product in all tables
$this->product = Perfume::where('slug', $slug)->first();
if ($this->product) {
$this->productType = 'perfume';
$this->product->load(['brand', 'categories']);
} else {
$this->product = Bottle::where('slug', $slug)->first();
if ($this->product) {
$this->productType = 'bottle';
} else {
$this->product = Product::where('slug', $slug)->firstOrFail();
$this->productType = 'arabian';
}
}
// Increment views
$this->product->increment('views');
// Load related products
$this->loadRelatedProducts();
}
private function loadRelatedProducts(): void
{
$query = match ($this->productType) {
'perfume' => Perfume::query()
->where('id', '!=', $this->product->id)
->when($this->product->categories->isNotEmpty(), function ($q) {
$q->whereHas('categories', function ($categoryQuery) {
$categoryQuery->whereIn('categories.id', $this->product->categories->pluck('id'));
});
})
->with(['brand', 'categories']),
'bottle' => Bottle::query()->where('id', '!=', $this->product->id),
'arabian' => Product::query()->where('id', '!=', $this->product->id),
};
$this->relatedProducts = $query
->inRandomOrder()
->take(4)
->get()
->map(function ($product) {
return [
'id' => $product->id,
'slug' => $product->slug,
'name' => $product->name,
'price' => $product->sale_price,
'image' => $product->getFirstMediaUrl('image') ?: asset('assets/images/dark-logo.png'),
'category' => $this->productType === 'perfume' ? $product->categories->first()?->name : null,
];
})
->toArray();
}
public function render(): View
{
$product = $this->product;
$product->image = $product->getFirstMediaUrl('image') ?: asset('assets/images/dark-logo.png');
return view('livewire.home.product.show', [
'pageTitle' => $this->product->name,
'pageDesc' => 'Detail produk',
]);
}
}

View File

@ -5,10 +5,12 @@
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 App\Traits\Components\WithToast;
use Carbon\Carbon;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Component; use Livewire\Component;
use Livewire\WithoutUrlPagination;
use Livewire\WithPagination; use Livewire\WithPagination;
#[Layout('components.layouts.home', [ #[Layout('components.layouts.home', [
@ -16,11 +18,11 @@
])] ])]
class Voucher extends Component class Voucher extends Component
{ {
use WithPagination, WithToast; use WithoutUrlPagination, WithPagination, WithToast;
public $outlets; public array $outlets = [];
public string $search = ''; public ?string $search = null;
public ?string $outlet_id = null; public ?string $outlet_id = null;
@ -32,7 +34,7 @@ class Voucher extends Component
public function mount(): void public function mount(): void
{ {
$this->outlets = Outlet::query()->operational()->pluck('name', 'id'); $this->outlets = Outlet::query()->operational()->pluck('name', 'id')->toArray();
} }
public function updatedSearch(): void public function updatedSearch(): void
@ -50,13 +52,10 @@ public function updatedSort(): void
$this->resetPage(); $this->resetPage();
} }
public function showDetail(string $hashId): void public function showDetail(VoucherModel $voucher): void
{ {
$this->selectedVoucher = VoucherModel::byHash($hashId); $this->selectedVoucher = $voucher;
$this->selectedVoucher->load('outlets'); $this->selectedVoucher->load(['outlets', 'tiers']);
$this->selectedVoucher->min_purchase_formatted = formatCurrencyNumber($this->selectedVoucher->min_purchase, 'Rp');
$this->selectedVoucher->end_date_formatted = formatDateLocalized($this->selectedVoucher->end_date);
$this->showDetailModal = true; $this->showDetailModal = true;
} }
@ -67,80 +66,141 @@ public function closeDetail(): void
$this->selectedVoucher = null; $this->selectedVoucher = null;
} }
public function claim(string $hashId): void public function claim(VoucherModel $voucher): void
{ {
$user = auth()->user();
if (! auth()->check()) { if (! auth()->check()) {
$this->redirect(route('login'), navigate: true); $this->redirect(route('login'), navigate: true);
return; return;
} }
$voucher = VoucherModel::byHash($hashId); // 1. Check if voucher exists and is active
$user = auth()->user(); if (
Carbon::parse($voucher->start_date)->gt(now()) ||
(
$voucher->end_date &&
Carbon::parse($voucher->end_date)->lt(now())
)
) {
$this->toast('Voucher tidak tersedia atau sudah kadaluarsa.', 'Gagal', 'danger');
// Basic checks return;
if (! $voucher) { }
// 2. Check Tier Requirement (before transaction)
$userTierId = $user->membership?->tier_id;
$voucher->load('tiers'); // Ensure tiers are loaded
$voucherTierIds = $voucher->tiers->pluck('id');
if (
$voucherTierIds->isNotEmpty() &&
(! $userTierId || ! $voucherTierIds->contains($userTierId))
) {
$this->toast('Voucher ini hanya tersedia untuk tier tertentu.', 'Gagal', 'danger');
return;
}
// 3. Execute claim within transaction for data consistency
$result = DB::transaction(function () use ($user, $voucher) {
// Lock voucher row to prevent race condition
$v = VoucherModel::whereKey($voucher->id)
->lockForUpdate()
->first();
if (! $v) {
return 'not_found';
}
// Check if available_count (stock) is sufficient
// available_count harus > 0 untuk bisa di-claim
if ($v->quota !== null && $v->available_count <= 0) {
return 'stock_empty';
}
// Check how many times this user has claimed this voucher
// Using SUM quantity to calculate total voucher that has been claimed
$userClaimedQuantity = DB::table('user_voucher')
->where('user_id', $user->id)
->where('voucher_id', $v->id)
->sum('quantity');
// Check if user has reached the limit
if ($v->limit_per_user !== null && $userClaimedQuantity >= $v->limit_per_user) {
return 'limit_reached';
}
// Check if user already has a record for this voucher
$existingPivot = DB::table('user_voucher')
->where('user_id', $user->id)
->where('voucher_id', $v->id)
->first();
if ($existingPivot) {
// User already has this voucher, increment the quantity
DB::table('user_voucher')
->where('user_id', $user->id)
->where('voucher_id', $v->id)
->update([
'quantity' => $existingPivot->quantity + 1,
'updated_at' => now(),
]);
} else {
// User doesn't have this voucher yet, create new record
$user->vouchers()->attach($v->id, [
'quantity' => 1,
'created_at' => now(),
'updated_at' => now(),
]);
}
// Decrease available stock
$v->decrement('available_count');
return 'success';
});
// Handle transaction results
if ($result === 'not_found') {
$this->toast('Voucher tidak ditemukan.', 'Gagal', 'danger'); $this->toast('Voucher tidak ditemukan.', 'Gagal', 'danger');
return; return;
} }
if ($voucher->quota !== null && $voucher->available_count >= $voucher->quota) { if ($result === 'stock_empty') {
$this->toast('Klaim gagal! Kuota voucher sudah habis.', 'Gagal', 'danger'); $this->toast('Klaim gagal! Kuota voucher sudah habis.', 'Gagal', 'danger');
return; return;
} }
// Check if user already reached limit if ($result === 'limit_reached') {
$claimedCount = $user->vouchers()->where('voucher_id', $voucher->id)->exists(); $this->toast('Anda sudah mencapai batas maksimal klaim voucher ini.', 'Peringatan', 'warning');
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; return;
} }
try { if ($result === 'success') {
// Claim with transaction $this->toast(
DB::transaction(function () use ($user, $voucher) { 'Voucher berhasil diklaim! Silakan cek di menu Voucher di halaman dasbor.',
// Re-check quota inside transaction 'Berhasil',
$v = VoucherModel::where('id', $voucher->id)->lockForUpdate()->first(); 'success'
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::query()
$query->where('name', 'like', '%'.$this->search.'%') ->with(['tiers', 'outlets'])
->orWhere('code', 'like', '%'.$this->search.'%'); ->active()
}) ->show()
->where(function ($query) {
$query->where('name', 'like', '%'.$this->search.'%')
->orWhere('code', 'like', '%'.$this->search.'%');
})
->when($this->outlet_id, function ($query) { ->when($this->outlet_id, function ($query) {
$query->whereHas('outlets', function ($q) { $query->whereHas('outlets', function ($q) {
$q->where('outlets.id', $this->outlet_id); $q->where('outlets.id', $this->outlet_id);
@ -150,21 +210,33 @@ public function render(): View
match ($this->sort) { match ($this->sort) {
'latest' => $query->orderBy('created_at', 'desc'), 'latest' => $query->orderBy('created_at', 'desc'),
'biggest' => $query->orderBy('discount_amount', 'desc'), 'biggest' => $query->orderBy('discount_amount', 'desc'),
'ending-soon' => $query->orderByRaw('end_date IS NULL') 'ending-soon' => $query->orderByRaw('end_date IS NULL')->orderBy('end_date', 'asc'),
->orderBy('end_date', 'asc'), default => $query->latest(),
}; };
}) }, fn ($query) => $query->latest())
->active() ->paginate(6);
->latest()
->paginate(3)
->through(function (VoucherModel $voucher) {
$voucher->min_purchase_formatted = formatCurrencyNumber($voucher->min_purchase, 'Rp');
$voucher->end_date = formatDateLocalized($voucher->end_date);
return $voucher; $user = auth()->user();
}); $userTierId = $user?->membership?->tier_id;
$voucherIds = $vouchers->pluck('id');
if ($this->selectedVoucher) {
$voucherIds->push($this->selectedVoucher->id);
}
$userClaimedCounts = $user
? DB::table('user_voucher')
->where('user_id', $user->id)
->whereIn('voucher_id', $voucherIds->unique())
->select('voucher_id', DB::raw('SUM(quantity) as total_quantity'))
->groupBy('voucher_id')
->pluck('total_quantity', 'voucher_id')
->toArray()
: [];
return view('livewire.home.vouchers', [ return view('livewire.home.vouchers', [
'pageTitle' => 'Voucher',
'pageDesc' => 'Nikmati berbagai voucher menarik untuk belanja lebih hemat dan menyenangkan.',
'vouchers' => $vouchers, 'vouchers' => $vouchers,
]); ]);
} }

View File

@ -3,6 +3,8 @@
namespace App\Models; namespace App\Models;
use App\Enums\ArticleStatus; use App\Enums\ArticleStatus;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -29,6 +31,12 @@ protected function casts(): array
]; ];
} }
#[Scope]
protected function published(Builder $query): void
{
$query->where('status', ArticleStatus::PUBLISHED);
}
public function getSlugOptions(): SlugOptions public function getSlugOptions(): SlugOptions
{ {
return SlugOptions::create() return SlugOptions::create()

View File

@ -24,6 +24,7 @@ protected function casts(): array
return [ return [
'cost_price' => 'int', 'cost_price' => 'int',
'sale_price' => 'int', 'sale_price' => 'int',
'views' => 'int',
]; ];
} }

View File

@ -27,6 +27,7 @@ protected function casts(): array
'concentration' => Concentration::class, 'concentration' => Concentration::class,
'cost_price' => 'int', 'cost_price' => 'int',
'sale_price' => 'int', 'sale_price' => 'int',
'views' => 'int',
]; ];
} }

View File

@ -24,6 +24,7 @@ protected function casts(): array
return [ return [
'cost_price' => 'int', 'cost_price' => 'int',
'sale_price' => 'int', 'sale_price' => 'int',
'views' => 'int',
]; ];
} }

View File

@ -64,6 +64,12 @@ public function forTier(Builder $query, ?int $tierId): void
}); });
} }
#[Scope]
public function show(Builder $query): void
{
$query->where('is_show', IsShow::SHOW->value);
}
public function outlets(): BelongsToMany public function outlets(): BelongsToMany
{ {
return $this->belongsToMany(Outlet::class); return $this->belongsToMany(Outlet::class);

View File

@ -5,6 +5,7 @@
use App\Enums\PriceRequestStatus; use App\Enums\PriceRequestStatus;
use App\Models\PriceRequest; use App\Models\PriceRequest;
use App\Settings\ContactSettings; use App\Settings\ContactSettings;
use App\Settings\GeneralSettings;
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;
@ -327,7 +328,8 @@ public function boot(): void
$view->with([ $view->with([
'sidebar' => $sidebar, 'sidebar' => $sidebar,
'header' => $header, 'header' => $header,
'setting' => app(ContactSettings::class), 'contactSettings' => app(ContactSettings::class),
'generalSettings' => app(GeneralSettings::class),
]); ]);
}); });
} }

View File

@ -24,8 +24,8 @@ public function up(): void
$table->unsignedInteger('min_purchase')->nullable(); $table->unsignedInteger('min_purchase')->nullable();
$table->unsignedInteger('max_discount')->nullable(); $table->unsignedInteger('max_discount')->nullable();
$table->unsignedInteger('quota')->nullable(); $table->unsignedInteger('quota')->nullable();
$table->unsignedInteger('available_count')->default(0); $table->unsignedInteger('available_count')->nullable();
$table->unsignedInteger('limit_per_user')->nullable(); $table->unsignedInteger('limit_per_user')->default(1);
$table->date('start_date'); $table->date('start_date');
$table->date('end_date')->nullable(); $table->date('end_date')->nullable();
$table->enum('is_show', IsShow::values())->default(IsShow::SHOW)->comment(IsShow::comment()); $table->enum('is_show', IsShow::values())->default(IsShow::SHOW)->comment(IsShow::comment());

View File

@ -17,7 +17,7 @@ public function up(): void
$table->string('name', 20); $table->string('name', 20);
$table->string('slug', 30); $table->string('slug', 30);
$table->text('description')->nullable(); $table->text('description')->nullable();
$table->integer('type')->default(CategoryType::PERFUME->value)->comment(CategoryType::comment()); $table->enum('type', CategoryType::values())->default(CategoryType::PERFUME)->comment(CategoryType::comment());
$table->unsignedInteger('sort_order'); $table->unsignedInteger('sort_order');
$table->timestamp('created_at')->useCurrent(); $table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate(); $table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();

View File

@ -25,6 +25,7 @@ public function up(): void
$table->string('middle_notes')->nullable(); $table->string('middle_notes')->nullable();
$table->string('top_notes')->nullable(); $table->string('top_notes')->nullable();
$table->text('description')->nullable(); $table->text('description')->nullable();
$table->unsignedInteger('views')->default(0);
$table->timestamp('created_at')->useCurrent(); $table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate(); $table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
$table->softDeletes(); $table->softDeletes();

View File

@ -19,6 +19,7 @@ public function up(): void
$table->unsignedInteger('cost_price')->default(0); $table->unsignedInteger('cost_price')->default(0);
$table->unsignedInteger('sale_price')->default(0); $table->unsignedInteger('sale_price')->default(0);
$table->text('description')->nullable(); $table->text('description')->nullable();
$table->unsignedInteger('views')->default(0);
$table->timestamp('created_at')->useCurrent(); $table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate(); $table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
$table->softDeletes(); $table->softDeletes();

View File

@ -19,6 +19,7 @@ public function up(): void
$table->unsignedInteger('cost_price')->default(0); $table->unsignedInteger('cost_price')->default(0);
$table->unsignedInteger('sale_price')->default(0); $table->unsignedInteger('sale_price')->default(0);
$table->text('description')->nullable(); $table->text('description')->nullable();
$table->unsignedInteger('views')->default(0);
$table->timestamp('created_at')->useCurrent(); $table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate(); $table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
$table->softDeletes(); $table->softDeletes();

View File

@ -0,0 +1,130 @@
# Product Badge System
## Overview
Sistem badge produk yang menampilkan label dinamis pada setiap produk berdasarkan kondisi tertentu. Hanya 1 badge yang ditampilkan per produk sesuai dengan prioritas yang telah ditentukan.
## Prioritas Badge
Badge ditampilkan berdasarkan prioritas berikut (dari tertinggi ke terendah):
### 1. **Terlaris** (Prioritas Tertinggi)
- **Kondisi**: Produk memiliki penjualan ≥ 500 unit dalam 1 bulan terakhir
- **Warna**: Merah (`bg-red-500 text-white`)
- **Label**: "Terlaris"
### 2. **Baru**
- **Kondisi**: Produk dibuat dalam 1 bulan terakhir
- **Warna**: Primary (`bg-home-primary text-white`)
- **Label**: "Baru"
### 3. **Hot** (Prioritas Terendah)
- **Kondisi**: Produk memiliki views > 100
- **Warna**: Oranye (`bg-orange-500 text-white`)
- **Label**: "Hot"
## Implementasi
### Database Migration
Kolom `view` telah ditambahkan ke tabel:
- `perfumes`
- `bottles`
- `products`
File migration: `2025_12_26_001500_add_view_column_to_product_tables.php`
### Backend Logic
File: `app/Livewire/Home/Product/Index.php`
#### Method `render()`
- Menambahkan `withCount` untuk menghitung total penjualan dalam 1 bulan terakhir
- Memanggil method `determineBadge()` untuk setiap produk
#### Method `determineBadge()`
```php
private function determineBadge($product): ?array
{
// Priority 1: Baru (created within last month)
if ($product->created_at && $product->created_at->isAfter(now()->subMonth())) {
return [
'label' => 'Baru',
'class' => 'bg-blue-500 text-white',
];
}
// Priority 2: Terlaris (sales >= 500 in last month)
if (isset($product->total_sales) && $product->total_sales >= 500) {
return [
'label' => 'Terlaris',
'class' => 'bg-red-500 text-white',
];
}
// Priority 3: Hot (high views - > 100 views)
if (isset($product->view) && $product->view > 100) {
return [
'label' => 'Hot',
'class' => 'bg-orange-500 text-white',
];
}
return null;
}
```
### Frontend Display
File: `resources/views/livewire/home/product/index.blade.php`
Badge ditampilkan secara kondisional:
```blade
@if (isset($product->badge) && $product->badge)
<span class="absolute top-4 left-4 {{ $product->badge['class'] }} text-[10px] uppercase tracking-wider font-bold px-3 py-1 rounded-full z-10 shadow-md">
{{ $product->badge['label'] }}
</span>
@endif
```
## Kustomisasi
### Mengubah Threshold
Anda dapat mengubah nilai threshold di method `determineBadge()`:
- **Baru**: Ubah `now()->subMonth()` menjadi periode lain (misal: `now()->subWeeks(2)`)
- **Terlaris**: Ubah `>= 500` menjadi nilai lain
- **Hot**: Ubah `> 100` menjadi nilai lain
### Menambah Badge Baru
Tambahkan kondisi baru di method `determineBadge()` dengan memperhatikan prioritas:
```php
// Priority 4: Diskon (contoh)
if (isset($product->discount) && $product->discount > 0) {
return [
'label' => 'Diskon',
'class' => 'bg-green-500 text-white',
];
}
```
### Mengubah Warna Badge
Ubah class Tailwind di array return:
```php
return [
'label' => 'Baru',
'class' => 'bg-purple-500 text-white', // Ganti warna di sini
];
```
## Tracking View Count
Untuk meningkatkan view count produk, tambahkan logika di halaman detail produk:
```php
// Di controller/livewire detail produk
$product->increment('view');
```
## Notes
- Badge hanya ditampilkan jika kondisi terpenuhi
- Jika tidak ada kondisi yang terpenuhi, tidak ada badge yang ditampilkan
- Sistem menggunakan prioritas, jadi jika produk memenuhi multiple kondisi, hanya badge dengan prioritas tertinggi yang ditampilkan

File diff suppressed because one or more lines are too long

View File

@ -1,8 +1,8 @@
<div class="flex justify-center flex-col"> <div class="flex justify-center flex-col">
<dotlottie-wc src="https://lottie.host/4282c234-6d0f-4a8d-ad04-3d82f2783814/FuhTCVgPGi.lottie" autoplay loop <dotlottie-wc src="{{ asset('assets/animations/boxing-cat.json') }}" autoplay loop
style="width: 200px; height: 200px; margin: 0 auto;"> style="width: 200px; height: 200px; margin: 0 auto;">
</dotlottie-wc> </dotlottie-wc>
<flux:text class="block text-center italic mt-5"> <p class="block text-center italic mt-5">
{!! isset($message) ? $message : 'Yahhh, sepertinya belum ada data yang tersedia.' !!} {!! isset($message) ? $message : 'Yahhh, sepertinya belum ada data yang tersedia.' !!}
</flux:text> </p>
</div> </div>

View File

@ -20,12 +20,11 @@
@endpersist @endpersist
{{ $slot }} {{ $slot }}
@include('components.sections.ui.home._footer') @include('components.sections.ui.home._footer')
@fluxScripts @fluxScripts
</body> </body>
</html> </html>

View File

@ -9,9 +9,8 @@
class="w-10 h-10 object-contain brightness-0 invert"> class="w-10 h-10 object-contain brightness-0 invert">
<h2 class="text-2xl font-bold tracking-wide text-white">Yadi Parfum</h2> <h2 class="text-2xl font-bold tracking-wide text-white">Yadi Parfum</h2>
</div> </div>
<p class="text-gray-400 text-sm leading-relaxed"> <p class="text-gray-400 text-sm leading-relaxed text-justify">
Menyediakan berbagai macam varian parfum berkualitas tinggi dengan wangi yang tahan lama dan harga {{ $generalSettings->site_description }}
terjangkau.
</p> </p>
<div class="flex items-center gap-4 mt-2"> <div class="flex items-center gap-4 mt-2">
<a href="#" <a href="#"
@ -76,7 +75,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 class="leading-relaxed">{{ $setting->address }}</span> <span class="leading-relaxed">{{ $contactSettings->address }}</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"
@ -85,7 +84,7 @@ class="hover:text-home-secondary transition-colors duration-300 flex items-cente
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>{{ $setting->email }}</span> <span>{{ $contactSettings->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"
@ -94,7 +93,7 @@ class="hover:text-home-secondary transition-colors duration-300 flex items-cente
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>{{ $setting->phone_number }}</span> <span>{{ $contactSettings->phone_number }}</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -47,7 +47,7 @@ class="hidden lg:flex items-center gap-6 font-medium px-8 py-3 rounded-full tran
aria-label="Main Navigation"> aria-label="Main Navigation">
@foreach ($header as $item) @foreach ($header as $item)
<a href="{{ route($item['route']) }}" <a href="{{ route($item['route']) }}"
class="{{ request()->routeIs($item['route']) ? 'font-bold text-home-primary' : 'hover:opacity-80' }} transition-opacity" class="{{ request()->routeIs($item['match']) ? 'font-bold text-home-primary' : 'hover:opacity-80' }} transition-opacity"
wire:navigate> wire:navigate>
{{ $item['title'] }} {{ $item['title'] }}
</a> </a>
@ -118,34 +118,11 @@ class="fixed inset-0 h-screen w-screen bg-home-foreground/80 backdrop-blur-md z-
$inactiveClass = 'text-white hover:text-gray-300 hover:opacity-80'; $inactiveClass = 'text-white hover:text-gray-300 hover:opacity-80';
@endphp @endphp
<a href="{{ route('homepage') }}" wire:navigate @click="isOpen = false" @foreach ($header as $item)
class="{{ $baseClass }} {{ request()->routeIs('homepage') ? $activeClass : $inactiveClass }}"> <a href="{{ route($item['route']) }}" wire:navigate @click="isOpen = false"
Beranda class="{{ $baseClass }} {{ request()->routeIs($item['match']) ? $activeClass : $inactiveClass }}">
</a> {{ $item['title'] }}
</a>
<a href="{{ route('outlet') }}" wire:navigate @click="isOpen = false" @endforeach
class="{{ $baseClass }} {{ request()->routeIs('outlet') ? $activeClass : $inactiveClass }}">
Outlet
</a>
<a href="{{ route('product.index') }}" wire:navigate @click="isOpen = false"
class="{{ $baseClass }} {{ request()->routeIs('product.*') ? $activeClass : $inactiveClass }}">
Produk
</a>
<a href="{{ route('voucher') }}" wire:navigate @click="isOpen = false"
class="{{ $baseClass }} {{ request()->routeIs('voucher') ? $activeClass : $inactiveClass }}">
Voucher
</a>
<a href="{{ route('article.index') }}" wire:navigate @click="isOpen = false"
class="{{ $baseClass }} {{ request()->routeIs('article.*') ? $activeClass : $inactiveClass }}">
Artikel
</a>
<a href="{{ route('faq') }}" wire:navigate @click="isOpen = false"
class="{{ $baseClass }} {{ request()->routeIs('faq') ? $activeClass : $inactiveClass }}">
FAQ
</a>
</nav> </nav>
</header> </header>

View File

@ -10,14 +10,15 @@ class="w-full h-full object-cover">
{{-- Content --}} {{-- Content --}}
<div class="relative z-10 container mx-auto px-6 text-center"> <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">Artikel Terbaru</h1> <h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-white mb-6">{{ $pageTitle }}</h1>
<p class="text-white/80 text-lg md:text-xl max-w-2xl mx-auto mb-10"> <p class="text-white/80 text-lg md:text-xl max-w-2xl mx-auto mb-10">
Baca tips profesional seputar perawatan wewangian, tren lifestyle, dan rekomendasi terbaik. {{ $pageDesc }}
</p> </p>
{{-- Search Bar --}} {{-- Search Bar --}}
<div class="max-w-xl mx-auto relative group"> <div class="max-w-xl mx-auto relative group">
<input type="search" placeholder="Cari artikel menarik..." aria-label="Cari artikel" <input wire:model.live.debounce.500="search" type="search" placeholder="Cari artikel..."
aria-label="Cari artikel"
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"> 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 <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"> 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">
@ -30,20 +31,17 @@ class="absolute right-2 top-2 bottom-2 w-10 h-10 bg-home-primary rounded-full fl
</div> </div>
</section> </section>
<main class="px-6 lg:px-32 xl:px-48 py-24 bg-white min-h-screen font-sans">k <main class="px-6 lg:px-32 xl:px-48 py-24 bg-white text-black font-sans">
<div class="grid grid-cols-1 lg:grid-cols-4 gap-12"> <div class="grid grid-cols-1 lg:grid-cols-4 gap-12">
<section class="lg:col-span-3"> <section class="lg:col-span-3">
{{-- Toolbar (Sort Only) --}} {{-- Toolbar (Sort Only) --}}
<div class="flex justify-start mb-10"> <div class="flex justify-start mb-10">
<div class="relative w-full md:w-auto"> <div class="relative w-full md:w-auto">
<select aria-label="Urutkan artikel" <select wire:model.live="sort" aria-label="Urutkan Artikel"
class="appearance-none px-6 py-3 pr-10 rounded-xl border border-home-foreground/10 bg-white text-home-foreground font-medium focus:outline-none focus:ring-2 focus:ring-home-primary focus:border-transparent transition cursor-pointer min-w-[160px] shadow-sm"> class="appearance-none px-6 py-3 pr-10 rounded-xl border border-home-foreground/10 bg-white text-home-foreground font-medium focus:outline-none focus:ring-2 focus:ring-home-primary focus:border-transparent transition cursor-pointer min-w-[160px] shadow-sm">
<option value="">Urutkan</option> <option value="">Urutkan</option>
<option value="latest">Terbaru</option> <option value="latest">Terbaru</option>
<option value="name">Nama</option> <option value="Alphabetic">Alfabet</option>
<option value="views">Terpopuler</option> <option value="views">Terpopuler</option>
</select> </select>
<svg class="absolute right-4 top-4 w-4 h-4 text-home-foreground/50 pointer-events-none" <svg class="absolute right-4 top-4 w-4 h-4 text-home-foreground/50 pointer-events-none"
@ -55,208 +53,92 @@ class="appearance-none px-6 py-3 pr-10 rounded-xl border border-home-foreground/
</div> </div>
<div class="space-y-8"> <div class="space-y-8">
@forelse($articles as $article)
<article <article
class="bg-white rounded-2xl border border-home-foreground/10 overflow-hidden hover:shadow-xl hover:shadow-home-primary/5 hover:border-home-primary/20 transition-all duration-300 group"> class="bg-white rounded-2xl border border-home-foreground/10 overflow-hidden hover:shadow-xl hover:shadow-home-primary/5 hover:border-home-primary/20 transition-all duration-300 group">
<div class="flex flex-col sm:flex-row h-full"> <div class="flex flex-col sm:flex-row h-full">
<div class="sm:w-2/5 h-64 sm:h-auto overflow-hidden relative"> <div class="sm:w-2/5 h-64 sm:h-auto overflow-hidden relative">
<img src="https://placehold.co/600x400/png?text=Tips+Parfum" alt="Tips Memilih Parfum" <img src="{{ $article->thumbnail }}" alt="{{ $article->title }}" loading="lazy"
loading="lazy" class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500">
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"> <div
<div class="absolute inset-0 bg-home-primary/10 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
class="absolute inset-0 bg-home-primary/10 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
</div>
</div>
<div class="p-8 sm:w-3/5 flex flex-col justify-between">
<div>
<div class="flex items-center gap-3 mb-4">
<span
class="text-xs font-bold text-home-primary bg-home-primary/10 px-3 py-1 rounded-full border border-home-primary/10 uppercase tracking-wider">Perawatan</span>
<span class="text-xs text-home-foreground/50 flex items-center gap-1">
<svg class="w-3 h-3" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
5 min read
</span>
</div> </div>
<h3
class="text-2xl font-bold text-home-foreground mb-3 group-hover:text-home-primary transition-colors line-clamp-2 leading-tight">
Tips Memilih Parfum yang Tepat Sesuai Kepribadian Anda
</h3>
<p class="text-home-foreground/60 text-sm mb-4 line-clamp-3 leading-relaxed">
Memilih parfum yang tepat bukan hanya tentang aroma yang disukai, tetapi juga
tentang menemukan signature scent yang mencerminkan karakter...
</p>
</div> </div>
<div class="p-8 sm:w-3/5 flex flex-col justify-between">
<div>
<div class="flex items-center gap-3 mb-4 flex-wrap">
@foreach ($article->categories->take(3) as $category)
<span
class="text-xs font-bold text-home-primary bg-home-primary/10 px-3 py-1 rounded-full border border-home-primary/10 uppercase tracking-wider">
{{ $category->name }}
</span>
@endforeach
<div class="flex items-center justify-between pt-6 border-t border-home-foreground/5"> @if ($article->categories->count() > 3)
<div class="flex items-center gap-3"> <span
<img src="https://placehold.co/100x100/png?text=User" alt="Author" class="text-xs font-bold text-home-foreground/50 px-3 py-1 rounded-full border border-home-foreground/10">
class="w-9 h-9 rounded-full ring-2 ring-white shadow-sm"> +{{ $article->categories->count() - 3 }}
<div> </span>
<p class="text-sm font-bold text-home-foreground">Siti Rahma</p> @endif
<p class="text-xs text-home-foreground/50">5 Nov 2024</p>
<span class="text-xs text-home-foreground/50 flex items-center gap-1">
<svg class="w-3 h-3" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
{{ ceil(str_word_count(strip_tags($article->content)) / 200) }} menit
baca
</span>
</div>
<h3
class="text-2xl font-bold text-home-foreground mb-3 group-hover:text-home-primary transition-colors line-clamp-2 leading-tight">
<a href="{{ route('article.show', $article->slug) }}" wire:navigate>
{{ $article->title }}
</a>
</h3>
<div class="text-base text-home-foreground/60 mb-4 line-clamp-6 leading-relaxed"
style="text-align: justify !important;">
{!! $article->excerpt !!}
</div> </div>
</div> </div>
<div <div
class="w-10 h-10 rounded-full bg-home-foreground/5 flex items-center justify-center group-hover:bg-home-primary group-hover:text-white transition-all duration-300"> class="flex items-center justify-between pt-6 border-t border-home-foreground/5">
<svg class="w-5 h-5 transition-transform group-hover:translate-x-0.5" <div class="flex items-center gap-3">
fill="none" stroke="currentColor" viewBox="0 0 24 24"> <img src="https://ui-avatars.com/api/?name={{ urlencode($article->author?->employee?->full_name) }}&background=random&color=fff"
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" alt="{{ $article->author?->employee?->full_name }}"
d="M14 5l7 7m0 0l-7 7m7-7H3"></path> class="w-9 h-9 rounded-full ring-2 ring-white shadow-sm">
</svg> <div>
</div> <p class="text-sm font-bold text-home-foreground">
</div> {{ $article->author?->employee?->full_name }}</p>
</div> <p class="text-xs text-home-foreground/50">
</div> {{ formatDateLocalized($article->published_at) }}
</article> </p>
</div>
<article
class="bg-white rounded-2xl border border-home-foreground/10 overflow-hidden hover:shadow-xl hover:shadow-home-primary/5 hover:border-home-primary/20 transition-all duration-300 group">
<div class="flex flex-col sm:flex-row h-full">
<div class="sm:w-2/5 h-64 sm:h-auto overflow-hidden relative">
<img src="https://placehold.co/600x400/png?text=Menyimpan+Parfum"
alt="Cara Menyimpan Parfum" loading="lazy"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500">
</div>
<div class="p-8 sm:w-3/5 flex flex-col justify-between">
<div>
<div class="flex items-center gap-3 mb-4">
<span
class="text-xs font-bold text-home-primary bg-home-primary/10 px-3 py-1 rounded-full border border-home-primary/10 uppercase tracking-wider">Lifestyle</span>
<span class="text-xs text-home-foreground/50 flex items-center gap-1">
<svg class="w-3 h-3" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
8 min read
</span>
</div>
<h3
class="text-2xl font-bold text-home-foreground mb-3 group-hover:text-home-primary transition-colors line-clamp-2 leading-tight">
Panduan Lengkap Cara Menyimpan Parfum agar Tahan Lama
</h3>
<p class="text-home-foreground/60 text-sm mb-4 line-clamp-3 leading-relaxed">
Parfum adalah investasi yang berharga. Penyimpanan yang tepat adalah kunci untuk
menjaga kualitas aroma agar tetap orisinil...
</p>
</div>
<div class="flex items-center justify-between pt-6 border-t border-home-foreground/5">
<div class="flex items-center gap-3">
<img src="https://placehold.co/100x100/png?text=User" alt="Author"
class="w-9 h-9 rounded-full ring-2 ring-white shadow-sm">
<div>
<p class="text-sm font-bold text-home-foreground">Ahmad Rizki</p>
<p class="text-xs text-home-foreground/50">3 Nov 2024</p>
</div> </div>
</div> <a href="{{ route('article.show', $article->slug) }}" wire:navigate
<div class="w-10 h-10 rounded-full bg-home-foreground/5 flex items-center justify-center group-hover:bg-home-primary group-hover:text-white transition-all duration-300">
class="w-10 h-10 rounded-full bg-home-foreground/5 flex items-center justify-center group-hover:bg-home-primary group-hover:text-white transition-all duration-300"> <svg class="w-5 h-5 transition-transform group-hover:translate-x-0.5"
<svg class="w-5 h-5 transition-transform group-hover:translate-x-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
</svg>
</div>
</div>
</div>
</div>
</article>
<article
class="bg-white rounded-2xl border border-home-foreground/10 overflow-hidden hover:shadow-xl hover:shadow-home-primary/5 hover:border-home-primary/20 transition-all duration-300 group">
<div class="flex flex-col sm:flex-row h-full">
<div class="sm:w-2/5 h-64 sm:h-auto overflow-hidden relative">
<img src="https://placehold.co/600x400/png?text=Parfum+Unisex" alt="Parfum Unisex"
loading="lazy"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500">
</div>
<div class="p-8 sm:w-3/5 flex flex-col justify-between">
<div>
<div class="flex items-center gap-3 mb-4">
<span
class="text-xs font-bold text-home-primary bg-home-primary/10 px-3 py-1 rounded-full border border-home-primary/10 uppercase tracking-wider">Tren</span>
<span class="text-xs text-home-foreground/50 flex items-center gap-1">
<svg class="w-3 h-3" fill="none" stroke="currentColor"
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="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path> d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
</svg> </svg>
7 min read </a>
</span>
</div>
<h3
class="text-2xl font-bold text-home-foreground mb-3 group-hover:text-home-primary transition-colors line-clamp-2 leading-tight">
Parfum Unisex Terbaik 2024: Pilihan Untuk Semua
</h3>
<p class="text-home-foreground/60 text-sm mb-4 line-clamp-3 leading-relaxed">
Parfum unisex semakin populer karena fleksibilitas dan keunikannya. Kami
mengumpulkan rekomendasi terbaik tahun ini...
</p>
</div>
<div class="flex items-center justify-between pt-6 border-t border-home-foreground/5">
<div class="flex items-center gap-3">
<img src="https://placehold.co/100x100/png?text=User" alt="Author"
class="w-9 h-9 rounded-full ring-2 ring-white shadow-sm">
<div>
<p class="text-sm font-bold text-home-foreground">Budi Santoso</p>
<p class="text-xs text-home-foreground/50">28 Oct 2024</p>
</div>
</div>
<div
class="w-10 h-10 rounded-full bg-home-foreground/5 flex items-center justify-center group-hover:bg-home-primary group-hover:text-white transition-all duration-300">
<svg class="w-5 h-5 transition-transform group-hover:translate-x-0.5"
fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
</svg>
</div> </div>
</div> </div>
</div> </div>
</div> </article>
</article> @empty
@include('components.animations.lottie.not-found')
@endforelse
</div> </div>
<nav class="flex items-center justify-center mt-16" aria-label="Pagination"> <div class="mt-16">
<div {{ $articles->links('livewire::custom-pagination') }}
class="flex items-center gap-3 bg-white p-2 rounded-full shadow-sm border border-home-foreground/5"> </div>
<button
class="w-10 h-10 flex items-center justify-center rounded-full border border-home-foreground/10 text-home-foreground/30 cursor-not-allowed hover:bg-gray-50 transition-colors"
disabled>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
clip-rule="evenodd"></path>
</svg>
</button>
@for ($page = 1; $page <= 3; $page++)
@if ($page == 1)
<button
class="w-10 h-10 flex items-center justify-center rounded-full bg-home-primary text-white font-bold shadow-md shadow-home-primary/20"
aria-current="page">{{ $page }}</button>
@else
<button
class="w-10 h-10 flex items-center justify-center rounded-full text-home-foreground hover:bg-home-primary/10 hover:text-home-primary transition-colors font-medium">{{ $page }}</button>
@endif
@endfor
<button
class="w-10 h-10 flex items-center justify-center rounded-full border border-home-foreground/10 text-home-foreground hover:bg-home-primary/10 hover:text-home-primary hover:border-home-primary transition-all">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
</svg>
</button>
</div>
</nav>
</section> </section>
<aside class="lg:col-span-1"> <aside class="lg:col-span-1">
@ -266,30 +148,20 @@ class="w-10 h-10 flex items-center justify-center rounded-full border border-hom
<h3 class="text-lg font-bold text-home-primary mb-6 border-b border-home-foreground/5 pb-4"> <h3 class="text-lg font-bold text-home-primary mb-6 border-b border-home-foreground/5 pb-4">
Kategori</h3> Kategori</h3>
<div class="space-y-1"> <div class="space-y-1">
<button <button wire:click="setCategory(null)"
class="w-full text-left px-4 py-3 rounded-xl text-home-foreground/70 hover:bg-home-primary/5 hover:text-home-primary transition-all text-sm font-medium flex justify-between items-center group"> class="w-full text-left px-4 py-3 rounded-xl transition-all text-sm font-medium flex justify-between items-center group {{ !$categorySlug ? 'bg-home-primary/10 text-home-primary' : 'text-home-foreground/70 hover:bg-home-primary/5 hover:text-home-primary' }}">
Perawatan Parfum Semua Artikel
<span <span
class="text-home-primary opacity-0 -translate-x-2 group-hover:translate-x-0 group-hover:opacity-100 transition-all duration-300"></span> class="text-home-primary transition-all duration-300 {{ !$categorySlug ? 'translate-x-0 opacity-100' : '-translate-x-2 opacity-0 group-hover:translate-x-0 group-hover:opacity-100' }}"></span>
</button>
<button
class="w-full text-left px-4 py-3 rounded-xl text-home-foreground/70 hover:bg-home-primary/5 hover:text-home-primary transition-all text-sm font-medium flex justify-between items-center group">
Lifestyle
<span
class="text-home-primary opacity-0 -translate-x-2 group-hover:translate-x-0 group-hover:opacity-100 transition-all duration-300"></span>
</button>
<button
class="w-full text-left px-4 py-3 rounded-xl text-home-foreground/70 hover:bg-home-primary/5 hover:text-home-primary transition-all text-sm font-medium flex justify-between items-center group">
Tren Terkini
<span
class="text-home-primary opacity-0 -translate-x-2 group-hover:translate-x-0 group-hover:opacity-100 transition-all duration-300"></span>
</button>
<button
class="w-full text-left px-4 py-3 rounded-xl text-home-foreground/70 hover:bg-home-primary/5 hover:text-home-primary transition-all text-sm font-medium flex justify-between items-center group">
Tips & Trik
<span
class="text-home-primary opacity-0 -translate-x-2 group-hover:translate-x-0 group-hover:opacity-100 transition-all duration-300"></span>
</button> </button>
@foreach ($categories as $category)
<button wire:click="setCategory('{{ $category['slug'] }}')"
class="w-full text-left px-4 py-3 rounded-xl transition-all text-sm font-medium flex justify-between items-center group {{ $categorySlug === $category['slug'] ? 'bg-home-primary/10 text-home-primary' : 'text-home-foreground/70 hover:bg-home-primary/5 hover:text-home-primary' }}">
{{ $category['name'] }}
<span
class="text-home-primary transition-all duration-300 {{ $categorySlug === $category['slug'] ? 'translate-x-0 opacity-100' : '-translate-x-2 opacity-0 group-hover:translate-x-0 group-hover:opacity-100' }}"></span>
</button>
@endforeach
</div> </div>
</nav> </nav>
@ -297,43 +169,31 @@ class="text-home-primary opacity-0 -translate-x-2 group-hover:translate-x-0 grou
<h3 class="text-lg font-bold text-home-primary mb-6 border-b border-home-foreground/5 pb-4"> <h3 class="text-lg font-bold text-home-primary mb-6 border-b border-home-foreground/5 pb-4">
Populer</h3> Populer</h3>
<div class="space-y-5"> <div class="space-y-5">
<a href="#" class="block group"> @foreach ($popularArticles as $popular)
<p <a href="{{ route('article.show', $popular['slug']) }}" class="block group">
class="text-sm font-bold text-home-foreground group-hover:text-home-primary transition-colors line-clamp-2 leading-snug"> <p
Tips Memilih Parfum yang Tepat Sesuai Kepribadian class="text-sm font-bold text-home-foreground group-hover:text-home-primary transition-colors line-clamp-2 leading-snug">
</p> {{ $popular['title'] }}
<p class="text-xs text-home-foreground/40 mt-2 font-medium flex items-center gap-1"> </p>
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <p
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="text-xs text-home-foreground/40 mt-2 font-medium flex items-center gap-1">
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path> <svg class="w-3 h-3" fill="none" stroke="currentColor"
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24">
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
</path> d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
</svg> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
2.5K views d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z">
</p> </path>
</a> </svg>
<div class="border-t border-home-foreground/5"></div> {{ $popular['views'] }}x dilihat
<a href="#" class="block group"> </p>
<p </a>
class="text-sm font-bold text-home-foreground group-hover:text-home-primary transition-colors line-clamp-2 leading-snug"> <div class="border-t border-home-foreground/5"></div>
Panduan Lengkap Cara Menyimpan Parfum @endforeach
</p>
<p class="text-xs text-home-foreground/40 mt-2 font-medium flex items-center gap-1">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z">
</path>
</svg>
1.8K views
</p>
</a>
</div> </div>
</nav> </nav>
<section {{-- <section
class="bg-home-primary rounded-2xl p-8 text-center relative overflow-hidden group shadow-lg shadow-home-primary/20"> class="bg-home-primary rounded-2xl p-8 text-center relative overflow-hidden group shadow-lg shadow-home-primary/20">
<div <div
class="absolute top-0 right-0 w-32 h-32 bg-home-secondary/20 rounded-full blur-2xl -mr-10 -mt-10"> class="absolute top-0 right-0 w-32 h-32 bg-home-secondary/20 rounded-full blur-2xl -mr-10 -mt-10">
@ -359,7 +219,7 @@ class="w-full py-3 px-4 rounded-xl border border-white/20 bg-white/10 text-white
class="w-full bg-home-secondary text-home-primary font-bold py-3 rounded-xl hover:bg-white transition-all duration-300 text-sm shadow-lg">Subscribe</button> class="w-full bg-home-secondary text-home-primary font-bold py-3 rounded-xl hover:bg-white transition-all duration-300 text-sm shadow-lg">Subscribe</button>
</form> </form>
</div> </div>
</section> </section> --}}
</div> </div>
</aside> </aside>
@ -368,3 +228,7 @@ class="w-full bg-home-secondary text-home-primary font-bold py-3 rounded-xl hove
</main> </main>
</div> </div>
@assets
<script src="https://unpkg.com/@lottiefiles/dotlottie-wc@0.8.5/dist/dotlottie-wc.js" type="module"></script>
@endassets

View File

@ -1,201 +1,167 @@
@php <div class="flex flex-col bg-home-background font-sans">
$article = (object) [
'title' => 'Meluncurkan "Oud Royal": Mahakarya Aroma Eksotis dari Timur Tengah',
'slug' => 'meluncurkan-oud-royal',
'category' => 'Event & Peluncuran',
'author' => 'Tim Editorial',
'date' => '12 Desember 2025',
'read_time' => '5 menit baca',
'image' => 'https://cdn.mos.cms.futurecdn.net/VzUqgr8pfbNcfXrpzeVBPE-1920-80.jpg',
'content_intro' =>
'Setelah penantian panjang dan riset mendalam selama dua tahun di jantung Kalimantan dan Dubai, kami dengan bangga mempersembahkan koleksi terbaru kami: Oud Royal. Ini bukan sekadar parfum, melainkan sebuah perjalanan aroma yang melintasi batas budaya.',
'tags' => ['Luxury', 'Oud', 'New Arrival', 'Event'],
];
$relatedArticles = [
[
'title' => 'Cara Membedakan Parfum Original dan Palsu',
'date' => '10 Des 2025',
'image' => 'https://placehold.co/400x300/png?text=Tips+Parfum',
],
[
'title' => 'Tren Wewangian Musk di Tahun 2026',
'date' => '08 Des 2025',
'image' => 'https://placehold.co/400x300/png?text=Tren+Musk',
],
[
'title' => 'Kenapa Parfum Tidak Tahan Lama di Kulit Kering?',
'date' => '05 Des 2025',
'image' => 'https://placehold.co/400x300/png?text=Kulit+Kering',
],
];
@endphp
<div class="flex flex-col min-h-screen bg-home-background font-sans">
<div class="fixed top-0 left-0 h-1 bg-home-primary z-50 transition-all duration-100" id="progressBar" <div class="fixed top-0 left-0 h-1 bg-home-primary z-50 transition-all duration-100" id="progressBar"
style="width: 0%"></div> style="width: 0%"></div>
<main class="flex-grow pb-24 pt-36"> <main class="flex-grow pb-24 pt-36">
<header class="px-6 lg:px-32 xl:px-48 mb-12 text-center max-w-6xl mx-auto"> <header class="px-6 lg:px-32 xl:px-48 mb-12 text-center mx-auto">
<div class="flex items-center justify-center gap-3 mb-6 text-sm font-bold tracking-widest uppercase"> <div
<span class="text-home-primary bg-home-primary/10 px-3 py-1 rounded-full">{{ $article->category }}</span> class="flex flex-wrap items-center justify-center gap-3 mb-6 text-sm font-bold tracking-widest uppercase">
<span class="text-home-foreground/40">&bull;</span> @foreach ($article->categories as $category)
<span class="text-home-foreground/60">{{ $article->date }}</span> <span
class="text-xs font-bold text-home-primary bg-home-primary/10 px-3 py-1 rounded-full border border-home-primary/10 uppercase tracking-wider">
{{ $category->name }}
</span>
@endforeach
</div> </div>
<h1 class="text-3xl md:text-5xl font-bold text-home-foreground mb-6 leading-tight"> <h1 class="text-3xl md:text-5xl font-bold text-home-foreground mb-6 leading-tight">
{{ $article->title }} {{ $article->title }}
</h1> </h1>
<div class="flex items-center justify-center gap-4 text-sm text-home-foreground/60"> <div class="flex items-center justify-center gap-4 text-sm text-home-foreground/60">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="w-8 h-8 rounded-full bg-home-foreground/10 overflow-hidden"> <div class="w-8 h-8 rounded-full bg-home-foreground/10 overflow-hidden">
<img src="https://placehold.co/100x100/png?text=A" alt="Author" <img src="https://ui-avatars.com/api/?name={{ urlencode($article->author?->employee?->full_name) }}"
class="w-full h-full object-cover"> alt="Author" class="w-full h-full object-cover">
</div> </div>
<span>Oleh <strong class="text-home-foreground">{{ $article->author }}</strong></span> <span>Oleh <strong
class="text-home-foreground">{{ $article->author?->employee?->full_name }}</strong></span>
</div> </div>
<span>&bull;</span> <span>&bull;</span>
<span>{{ $article->read_time }}</span> <span>{{ formatDateLocalized($article->published_at) }}</span>
<span>&bull;</span>
<span>{{ $article->views }}x dilihat</span>
</div> </div>
</header> </header>
<div class="px-6 lg:px-32 xl:px-48 mb-16"> <div class="px-6 lg:px-32 xl:px-48 mb-16">
<div class="aspect-video w-full rounded-xl overflow-hidden shadow-2xl"> <div class="aspect-video w-full rounded-xl overflow-hidden shadow-2xl">
<img src="{{ $article->image }}" alt="{{ $article->title }}" class="w-full h-full object-cover"> <img src="{{ $article->thumbnail }}" alt="{{ $article->title }}" class="w-full h-full object-cover">
</div> </div>
</div> </div>
<div class="px-6 lg:px-32 xl:px-48 grid grid-cols-1 lg:grid-cols-12 gap-12"> <div class="px-6 lg:px-32 xl:px-48 grid grid-cols-1 lg:grid-cols-12 gap-12">
<article class="lg:col-span-8"> <article class="lg:col-span-8">
<div class="flex items-center gap-4 mb-8 pb-8 border-b border-home-foreground/10 lg:hidden"> <div class="flex items-center gap-4 mb-8 pb-8 border-b border-home-foreground/10">
<span class="text-sm font-bold text-home-foreground">Bagikan:</span> <span class="text-sm font-bold text-home-foreground">Bagikan:</span>
<button
class="w-10 h-10 rounded-full border border-home-foreground/10 flex items-center justify-center text-home-foreground hover:bg-home-primary hover:text-white transition-colors"><svg {{-- WhatsApp --}}
class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"> <a href="https://wa.me/?text={{ urlencode($article->title . ' ' . url()->current()) }}"
target="_blank" rel="noopener noreferrer"
class="w-10 h-10 rounded-full border border-home-foreground/10 flex items-center justify-center text-home-foreground hover:bg-home-primary hover:text-white transition-colors">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path <path
d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z" /> 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></button> </svg>
<button </a>
class="w-10 h-10 rounded-full border border-home-foreground/10 flex items-center justify-center text-home-foreground hover:bg-home-primary hover:text-white transition-colors"><svg
class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"> {{-- Facebook --}}
<a href="https://www.facebook.com/sharer/sharer.php?u={{ urlencode(url()->current()) }}"
target="_blank" rel="noopener noreferrer"
class="w-10 h-10 rounded-full border border-home-foreground/10 flex items-center justify-center text-home-foreground hover:bg-home-primary hover:text-white transition-colors">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path <path
d="M9 8h-3v4h3v12h5v-12h3.642l.358-4h-4v-1.667c0-.955.192-1.333 1.115-1.333h2.885v-5h-3.808c-3.596 0-5.192 1.583-5.192 4.615v3.385z" /> 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></button> </svg>
<button </a>
class="w-10 h-10 rounded-full border border-home-foreground/10 flex items-center justify-center text-home-foreground hover:bg-home-primary hover:text-white transition-colors"><svg
class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> {{-- X (Twitter) --}}
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <a href="https://twitter.com/intent/tweet?text={{ urlencode($article->title) }}&url={{ urlencode(url()->current()) }}"
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"> target="_blank" rel="noopener noreferrer"
</path> class="w-10 h-10 rounded-full border border-home-foreground/10 flex items-center justify-center text-home-foreground hover:bg-home-primary hover:text-white transition-colors">
</svg></button> <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>
</a>
</div> </div>
<div class="prose prose-lg max-w-none text-home-foreground/80 leading-relaxed"> <div class="prose prose-lg max-w-none text-home-foreground/80 leading-relaxed">
<p <div
class="first-letter:text-6xl first-letter:font-bold first-letter:text-home-primary first-letter:float-left first-letter:mr-3 first-letter:mt-[-10px] mb-8 text-xl text-home-foreground font-medium"> class="first-letter:text-6xl first-letter:font-bold first-letter:text-home-primary first-letter:float-left first-letter:mr-3 first-letter:mt-[-10px] mb-8 text-home-foreground text-justify">
{{ $article->content_intro }} {!! $article->content !!}
</p>
<p class="mb-6">
Dalam dunia wewangian, Oud (Gaharu) sering disebut sebagai "emas cair". Harganya yang fantastis
dan aromanya yang kompleks menjadikannya bahan primadona di kalangan kolektor parfum luxury.
Yadi Parfum mengambil langkah berani dengan menghadirkan interpretasi Oud yang lebih modern,
namun tetap menghormati tradisi Timur Tengah.
</p>
<h3 class="text-2xl font-bold text-home-foreground mt-10 mb-4">Filosofi di Balik Botol</h3>
<p class="mb-6">
"Kami ingin menciptakan sesuatu yang abadi," ujar Yadi, Founder dari Yadi Parfum. "Botol Oud
Royal didesain dengan kaca kristal berat dan tutup magnetik berlapis emas, merefleksikan
kualitas cairan di dalamnya."
</p>
<blockquote
class="border-l-4 border-home-primary pl-6 py-2 my-10 italic text-xl text-home-foreground font-serif bg-home-primary/5 rounded-r-lg">
"Parfum adalah seni yang tidak terlihat, puisi yang dihirup, dan kenangan yang tak terhapuskan."
</blockquote>
<p class="mb-6">
Peluncuran yang diadakan di Grand Ballroom Ritz Carlton Jakarta dihadiri oleh lebih dari 200
tamu undangan, termasuk selebriti, influencer kecantikan, dan kolektor parfum ternama. Acara ini
dimeriahkan dengan instalasi seni aroma interaktif.
</p>
<div class="my-10 rounded-2xl overflow-hidden shadow-lg">
<img src="https://placehold.co/800x400/png?text=Suasana+Event+Peluncuran" alt="Event"
class="w-full">
<p class="text-sm text-center text-home-foreground/50 py-3 italic bg-gray-50">Suasana peluncuran
Oud Royal di Jakarta.</p>
</div>
<h3 class="text-2xl font-bold text-home-foreground mt-10 mb-4">Ketersediaan</h3>
<p class="mb-6">
Oud Royal kini tersedia secara eksklusif di website resmi Yadi Parfum dan butik flagship kami di
Central Park Mall. Mengingat kelangkaan bahan bakunya, produksi batch pertama sangat terbatas.
</p>
</div>
<div class="mt-12 pt-8 border-t border-home-foreground/10">
<div class="flex flex-wrap gap-2">
@foreach ($article->tags as $tag)
<a href="#"
class="px-4 py-2 bg-home-foreground/5 text-home-foreground/70 rounded-lg text-sm font-bold hover:bg-home-primary hover:text-white transition-all">#{{ $tag }}</a>
@endforeach
</div> </div>
</div> </div>
<div class="grid grid-cols-2 gap-6 mt-12"> <div class="grid grid-cols-2 gap-6 mt-12">
<a href="#" @if ($previousArticle)
class="group block p-6 border border-home-foreground/10 rounded-2xl hover:border-home-primary/30 transition-all"> <a href="{{ route('article.show', $previousArticle->slug) }}" wire:navigate
<span class="group block p-6 border border-home-foreground/10 rounded-2xl hover:border-home-primary/30 transition-all">
class="text-xs text-home-foreground/40 font-bold uppercase mb-1 block group-hover:text-home-primary">Sebelumnya</span> <span
<h4 class="text-xs text-home-foreground/40 font-bold uppercase mb-1 block group-hover:text-home-primary">Sebelumnya</span>
class="font-bold text-home-foreground line-clamp-2 group-hover:text-home-primary transition-colors"> <h4
Review Jujur: Koleksi Floral Series 2024</h4> class="font-bold text-home-foreground line-clamp-2 group-hover:text-home-primary transition-colors">
</a> {{ $previousArticle->title }}
<a href="#" </h4>
class="group block p-6 border border-home-foreground/10 rounded-2xl text-right hover:border-home-primary/30 transition-all"> </a>
<span @endif
class="text-xs text-home-foreground/40 font-bold uppercase mb-1 block group-hover:text-home-primary">Selanjutnya</span> @if ($nextArticle)
<h4 <a href="{{ route('article.show', $nextArticle->slug) }}" wire:navigate
class="font-bold text-home-foreground line-clamp-2 group-hover:text-home-primary transition-colors"> class="group block p-6 border border-home-foreground/10 rounded-2xl text-right hover:border-home-primary/30 transition-all">
Tips Menyimpan Parfum Agar Awet Bertahun-tahun</h4> <span
</a> class="text-xs text-home-foreground/40 font-bold uppercase mb-1 block group-hover:text-home-primary">Selanjutnya</span>
<h4
class="font-bold text-home-foreground line-clamp-2 group-hover:text-home-primary transition-colors">
{{ $nextArticle->title }}</h4>
</a>
@endif
</div> </div>
</article> </article>
<aside class="lg:col-span-4 pl-0 lg:pl-8 border-l-0 lg:border-l border-home-foreground/5"> <aside class="lg:col-span-4 pl-0 lg:pl-8 border-l-0 lg:border-l border-home-foreground/5">
<div class="sticky top-32 space-y-10"> <div class="sticky top-32 space-y-10">
<div> <div x-data="{
<h3 class="text-lg font-bold text-home-primary mb-6 flex items-center gap-2"> progress: 0,
<span class="w-1 h-6 bg-home-primary rounded-full"></span> updateProgress() {
Populer Minggu Ini const winScroll = window.pageYOffset || document.documentElement.scrollTop
</h3> const height = document.documentElement.scrollHeight - document.documentElement.clientHeight
<div class="space-y-6"> this.progress = height > 0 ? (winScroll / height) * 100 : 0
@foreach ($relatedArticles as $index => $item) }
<a href="#" class="flex gap-4 group"> }" x-init="updateProgress();
<div class="w-20 h-20 flex-shrink-0 rounded-xl overflow-hidden"> window.addEventListener('scroll', updateProgress)">
<img src="{{ $item['image'] }}" <!-- progress bar -->
class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"> <div class="fixed top-0 left-0 w-full h-1 bg-transparent z-50">
</div> <div class="h-full bg-home-primary transition-all duration-75"
<div> :style="`width: ${progress}%`" id="progressBar"></div>
<span </div>
class="text-xs text-home-foreground/40 font-bold">{{ sprintf('%02d', $index + 1) }}</span>
<h4 <!-- konten kamu -->
class="font-bold text-home-foreground text-sm leading-snug group-hover:text-home-primary transition-colors mt-1"> <div>
{{ $item['title'] }}</h4> <h3 class="text-lg font-bold text-home-primary mb-6 flex items-center gap-2">
<span <span class="w-1 h-6 bg-home-primary rounded-full"></span>
class="text-xs text-home-foreground/50 mt-2 block">{{ $item['date'] }}</span> Artikel Terbaru
</div> </h3>
</a> <div class="space-y-6">
@endforeach @foreach ($latestArticles as $latest)
<a href="{{ route('article.show', $latest['slug']) }}" wire:navigate
class="flex gap-4 group">
<div class="w-20 h-20 flex-shrink-0 rounded-xl overflow-hidden">
<img src="{{ $latest['thumbnail'] }}"
class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500">
</div>
<div>
<h4
class="font-bold text-home-foreground text-sm leading-snug group-hover:text-home-primary transition-colors mt-1">
{{ $latest['title'] }}
</h4>
<span class="text-xs text-home-foreground/50 mt-2 block">
{{ formatDateLocalized($latest['published_at']) }}
</span>
<span class="text-xs text-home-foreground/40 font-bold">
{{ $latest['views'] }}x dilihat
</span>
</div>
</a>
@endforeach
</div>
</div> </div>
</div> </div>
<section {{-- Not used yet --}}
{{-- <section
class="bg-home-primary rounded-2xl p-8 text-center relative overflow-hidden group shadow-lg shadow-home-primary/20"> class="bg-home-primary rounded-2xl p-8 text-center relative overflow-hidden group shadow-lg shadow-home-primary/20">
<div <div
class="absolute top-0 right-0 w-32 h-32 bg-home-secondary/20 rounded-full blur-2xl -mr-10 -mt-10"> class="absolute top-0 right-0 w-32 h-32 bg-home-secondary/20 rounded-full blur-2xl -mr-10 -mt-10">
@ -221,7 +187,7 @@ class="w-full py-3 px-4 rounded-xl border border-white/20 bg-white/10 text-white
class="w-full bg-home-secondary text-home-primary font-bold py-3 rounded-xl hover:bg-white transition-all duration-300 text-sm shadow-lg">Subscribe</button> class="w-full bg-home-secondary text-home-primary font-bold py-3 rounded-xl hover:bg-white transition-all duration-300 text-sm shadow-lg">Subscribe</button>
</form> </form>
</div> </div>
</section> </section> --}}
</div> </div>
</aside> </aside>
@ -231,11 +197,21 @@ class="w-full bg-home-secondary text-home-primary font-bold py-3 rounded-xl hove
</main> </main>
</div> </div>
<script> @script
window.onscroll = function() { <script>
let winScroll = document.body.scrollTop || document.documentElement.scrollTop; window.onscroll = function() {
let height = document.documentElement.scrollHeight - document.documentElement.clientHeight; let winScroll = document.body.scrollTop || document.documentElement.scrollTop;
let scrolled = (winScroll / height) * 100; let height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
document.getElementById("progressBar").style.width = scrolled + "%"; let scrolled = (winScroll / height) * 100;
}; document.getElementById("progressBar").style.width = scrolled + "%";
</script> };
</script>
@endscript
@assets
<style>
.prose p:empty {
min-height: 1.25rem;
}
</style>
@endassets

View File

@ -1,102 +0,0 @@
<div>
{{-- Hero Section --}}
<section class="relative h-[500px] flex items-center justify-center overflow-hidden">
{{-- Background Image --}}
<div class="absolute inset-0 z-0">
<img src="https://images.pexels.com/photos/2789781/pexels-photo-2789781.jpeg" alt="Perfume Background"
class="w-full h-full object-cover">
<div class="absolute inset-0 bg-black/40"></div>
</div>
{{-- 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">Artikel Terbaru</h1>
<p class="text-white/80 text-lg md:text-xl max-w-2xl mx-auto mb-10">
Baca tips profesional seputar perawatan wewangian, tren lifestyle, dan rekomendasi terbaik.
</p>
{{-- Search Bar --}}
<div class="max-w-xl mx-auto relative group">
<input type="search" placeholder="Cari artikel menarik..." aria-label="Cari artikel"
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="px-6 lg:px-32 xl:px-48 py-24 bg-white min-h-screen font-sans">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 items-start" x-data="{ activeAccordion: null }">
@foreach ([
['q' => 'Bagaimana cara melakukan pemesanan?', 'a' => 'Anda dapat melakukan pemesanan melalui website atau aplikasi kami. Pilih produk yang diinginkan, masukkan ke keranjang, dan ikuti langkah checkout.'],
['q' => 'Berapa lama waktu pengiriman?', 'a' => 'Pengiriman untuk area Jakarta 1-2 hari kerja dan luar Jakarta 3-5 hari kerja tergantung ekspedisi yang dipilih.'],
['q' => 'Apakah produk original?', 'a' => 'Semua produk 100% original dari distributor resmi dengan sertifikat keaslian dan garansi uang kembali.'],
['q' => 'Bagaimana kebijakan pengembalian barang?', 'a' => 'Pengembalian dalam 30 hari dengan syarat barang belum digunakan, segel utuh, dan menyertakan video unboxing.'],
['q' => 'Metode pembayaran apa saja yang tersedia?', 'a' => 'Kami menerima Transfer bank (BCA, Mandiri), Kartu Kredit, E-wallet (GoPay, OVO), dan cicilan 0%.'],
['q' => 'Bagaimana cara menggunakan voucher?', 'a' => 'Masukkan kode voucher pada halaman checkout di kolom "Kode Promo", diskon akan otomatis terpotong.'],
] as $index => $faq)
<article
class="bg-white rounded-2xl border border-home-foreground/10 overflow-hidden shadow-sm hover:shadow-md transition-shadow group">
<button
@click="activeAccordion = (activeAccordion === {{ $index }} ? null : {{ $index }})"
class="w-full px-6 py-5 flex items-center justify-between text-left focus:outline-none transition-colors"
:class="activeAccordion === {{ $index }} ? 'bg-home-primary/5' :
'bg-white hover:bg-home-foreground/5'">
<h3 class="text-lg font-bold pr-4 transition-colors"
:class="activeAccordion === {{ $index }} ? 'text-home-primary' :
'text-home-foreground group-hover:text-home-primary'">
{{ $faq['q'] }}
</h3>
<span class="flex-shrink-0 ml-4 p-1 rounded-full transition-all duration-300"
:class="activeAccordion === {{ $index }} ? 'bg-home-primary text-white rotate-180' :
'bg-home-foreground/10 text-home-foreground group-hover:bg-home-primary group-hover:text-white'">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
</svg>
</span>
</button>
<div x-show="activeAccordion === {{ $index }}" x-collapse x-cloak
class="px-6 pb-6 pt-2 bg-home-primary/5 border-t border-home-primary/10 text-home-foreground/70 leading-relaxed text-sm md:text-base">
{{ $faq['a'] }}
</div>
</article>
@endforeach
</div>
<div
class="mt-16 bg-home-primary rounded-2xl p-10 text-center relative overflow-hidden shadow-xl shadow-home-primary/20">
<div class="absolute top-0 right-0 w-48 h-48 bg-white/10 rounded-full blur-3xl -mr-12 -mt-12"></div>
<div class="absolute bottom-0 left-0 w-32 h-32 bg-home-secondary/20 rounded-full blur-2xl -ml-8 -mb-8">
</div>
<div class="relative z-10">
<h3 class="text-2xl font-bold text-white mb-3">Masih ada pertanyaan?</h3>
<p class="text-white/80 mb-8 max-w-xl mx-auto">Jika Anda tidak menemukan jawaban yang dicari, tim
customer service kami siap membantu Anda 24/7.</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<button
class="bg-white text-home-primary font-bold px-8 py-3 rounded-xl hover:bg-home-secondary hover:text-white transition-all shadow-lg">
Chat WhatsApp
</button>
<button
class="bg-home-primary border border-white/20 text-white font-bold px-8 py-3 rounded-xl hover:bg-white/10 transition-all">
Kirim Email
</button>
</div>
</div>
</div>
</section>
</div>

View File

@ -0,0 +1,112 @@
<div>
{{-- Hero Section --}}
<section class="relative h-[500px] flex items-center justify-center overflow-hidden">
{{-- Background Image --}}
<div class="absolute inset-0 z-0">
<img src="https://images.pexels.com/photos/2789781/pexels-photo-2789781.jpeg" alt="Perfume Background"
class="w-full h-full object-cover">
<div class="absolute inset-0 bg-black/40"></div>
</div>
{{-- 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">{{ $pageTitle }}</h1>
<p class="text-white/80 text-lg md:text-xl max-w-2xl mx-auto mb-10">
{{ $pageDesc }}
</p>
{{-- Search Bar --}}
<div class="max-w-xl mx-auto relative group">
<input wire:model.live.debounce.500="search" type="search" placeholder="Cari pertanyaan..."
aria-label="Cari artikel"
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="px-6 lg:px-32 xl:px-48 py-24 bg-white text-black font-sans">
@if (!empty($faqs))
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 items-start" x-data="{ activeAccordion: null }">
@foreach ($faqs as $index => $faq)
<article
class="bg-white rounded-2xl border border-home-foreground/10 overflow-hidden shadow-sm hover:shadow-md transition-shadow group">
<button
@click="activeAccordion = (activeAccordion === {{ $index }} ? null : {{ $index }})"
class="w-full px-6 py-5 flex items-center justify-between text-left focus:outline-none transition-colors"
:class="activeAccordion === {{ $index }} ? 'bg-home-primary/5' :
'bg-white hover:bg-home-foreground/5'">
<h3 class="text-lg font-bold pr-4 transition-colors"
:class="activeAccordion === {{ $index }} ? 'text-home-primary' :
'text-home-foreground group-hover:text-home-primary'">
{{ $faq['question'] }}
</h3>
<span class="flex-shrink-0 ml-4 p-1 rounded-full transition-all duration-300"
:class="activeAccordion === {{ $index }} ? 'bg-home-primary text-white rotate-180' :
'bg-home-foreground/10 text-home-foreground group-hover:bg-home-primary group-hover:text-white'">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none"
viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
</svg>
</span>
</button>
<div x-show="activeAccordion === {{ $index }}"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 max-h-0" x-transition:enter-end="opacity-100 max-h-96"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 max-h-96" x-transition:leave-end="opacity-0 max-h-0"
x-cloak
class="px-6 pb-6 pt-2 bg-home-primary/5 border-t border-home-primary/10 text-home-foreground/70 leading-relaxed text-sm md:text-base overflow-hidden">
{{ $faq['answer'] }}
</div>
</article>
@endforeach
</div>
@else
@include('components.animations.lottie.not-found')
@endif
<div
class="mt-16 bg-home-primary rounded-2xl p-10 text-center relative overflow-hidden shadow-xl shadow-home-primary/20">
<div class="absolute top-0 right-0 w-48 h-48 bg-white/10 rounded-full blur-3xl -mr-12 -mt-12"></div>
<div class="absolute bottom-0 left-0 w-32 h-32 bg-home-secondary/20 rounded-full blur-2xl -ml-8 -mb-8">
</div>
<div class="relative z-10">
<h3 class="text-2xl font-bold text-white mb-3">Masih ada pertanyaan?</h3>
<p class="text-white/80 mb-8 max-w-xl mx-auto">
Jika Anda tidak menemukan jawaban yang dicari, silakan hubungi kami dan kami siap membantu Anda
24/7.
</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<!-- WhatsApp -->
<a href="https://wa.me/{{ formatToWhatsApp($contactSettings->phone_number) }}" target="_blank"
class="bg-white text-home-primary font-bold px-8 py-3 rounded-xl hover:bg-home-secondary hover:text-white transition-all shadow-lg text-center">
Chat WhatsApp
</a>
<!-- Email -->
<a href="mailto:{{ $contactSettings->email }}"
class="bg-home-primary border border-white/20 text-white font-bold px-8 py-3 rounded-xl hover:bg-white/10 transition-all text-center">
Kirim Email
</a>
</div>
</div>
</div>
</main>
</div>
@assets
<script src="https://unpkg.com/@lottiefiles/dotlottie-wc@0.8.5/dist/dotlottie-wc.js" type="module"></script>
@endassets

View File

@ -98,7 +98,7 @@ class="w-12 h-12 rounded-xl border border-home-foreground/10 flex items-center j
x-transition:leave="transition ease-in duration-150" x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100 scale-100 translate-y-0" x-transition:leave-start="opacity-100 scale-100 translate-y-0"
x-transition:leave-end="opacity-0 scale-95 translate-y-2" x-transition:leave-end="opacity-0 scale-95 translate-y-2"
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" class="absolute bottom-full mb-3 w-48 bg-white rounded-2xl shadow-xl border border-home-foreground/5 py-2 z-30 {{ $outlet['maps_url'] ? 'right-0' : 'left-0' }}"
style="display: none;"> style="display: none;">
<div <div
@ -107,7 +107,7 @@ class="px-4 py-2 text-[10px] font-bold text-home-foreground/40 uppercase trackin
</div> </div>
{{-- WhatsApp --}} {{-- WhatsApp --}}
<a href="https://wa.me/?text={{ urlencode('Cek outlet ' . $outlet['name'] . ' di Pangestu Yadi Parfum: ' . ($outlet['maps_url'] ?: url()->current())) }}" <a href="https://wa.me/?text={{ urlencode('Cek ' . $outlet['name'] . ' di Yadi Parfum: ' . ($outlet['maps_url'] ?: url()->current())) }}"
target="_blank" 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"> 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"> <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">

View File

@ -1,38 +1,24 @@
<div class="flex flex-col min-h-screen bg-white font-sans"> <div>
{{-- 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.pexels.com/photos/22589353/pexels-photo-22589353.jpeg" <img src="https://images.pexels.com/photos/22589353/pexels-photo-22589353.jpeg" alt="Article Background"
alt="Perfume Collection 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/40"></div>
</div> </div>
{{-- Content --}} {{-- Content --}}
<div class="relative z-10 container mx-auto px-6 text-center"> <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"> <h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-white mb-6">{{ $pageTitle }}</h1>
@if ($productType === 'perfume')
Koleksi Parfum
@elseif($productType === 'bottle')
Koleksi Botol
@else
Koleksi Arabian
@endif
</h1>
<p class="text-white/80 text-lg md:text-xl max-w-2xl mx-auto mb-10"> <p class="text-white/80 text-lg md:text-xl max-w-2xl mx-auto mb-10">
@if ($productType === 'perfume') {{ $pageDesc }}
Temukan aroma khas yang mencerminkan kepribadian dan gaya Anda.
@elseif($productType === 'bottle')
Pilihan botol berkualitas untuk parfum Anda.
@else
Koleksi produk Arabian eksklusif dan mewah.
@endif
</p> </p>
{{-- Search Bar --}} {{-- Search Bar --}}
<div class="max-w-xl mx-auto relative group"> <div class="max-w-xl mx-auto relative group">
<input type="text" placeholder="Cari produk..." <input wire:model.live.debounce.500="search" type="search" placeholder="Cari produk..."
aria-label="Cari produk"
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"> 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 <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"> 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">
@ -45,20 +31,20 @@ class="absolute right-2 top-2 bottom-2 w-10 h-10 bg-home-primary rounded-full fl
</div> </div>
</section> </section>
<main class="flex-grow px-6 lg:px-32 xl:px-48 py-24"> <main class="px-6 lg:px-32 xl:px-48 py-24 bg-white text-black font-sans">
<div class="grid grid-cols-1 lg:grid-cols-4 gap-12 mb-24"> <div class="grid grid-cols-1 lg:grid-cols-4 gap-12 mb-24">
<aside class="hidden lg:block lg:col-span-1 space-y-8"> <aside class="hidden lg:block lg:col-span-1 space-y-8">
<div class="bg-white rounded-2xl border border-home-foreground/10 p-6 shadow-sm sticky top-32"> <div class="bg-white rounded-2xl border border-home-foreground/10 p-6 shadow-sm sticky top-32">
{{-- Filter Jenis Produk --}} {{-- Product type filter --}}
<h3 class="font-bold text-home-primary mb-6 border-b border-home-foreground/10 pb-4">Jenis Produk <h3 class="font-bold text-home-primary mb-6 border-b border-home-foreground/10 pb-4">Jenis Produk
</h3> </h3>
<div class="space-y-4"> <div class="space-y-4">
@foreach ([['value' => 'perfume', 'label' => 'Parfum'], ['value' => 'bottle', 'label' => 'Botol'], ['value' => 'arabian', 'label' => 'Arabian']] as $type) @foreach ([['value' => 'perfume', 'label' => 'Parfum'], ['value' => 'bottle', 'label' => 'Botol'], ['value' => 'arabian', 'label' => 'Arabian']] as $type)
<label class="flex items-center cursor-pointer group"> <label class="flex items-center cursor-pointer group">
<input type="radio" name="productType" value="{{ $type['value'] }}" <input type="radio" name="productType" value="{{ $type['value'] }}"
wire:model.live="productType" {{ $productType === $type['value'] ? 'checked' : '' }} wire:model.live="productType"
class="w-5 h-5 border-home-foreground/20 text-home-primary focus:ring-home-primary cursor-pointer accent-home-primary"> class="w-5 h-5 border-home-foreground/20 text-home-primary focus:ring-home-primary cursor-pointer accent-home-primary">
<span <span
class="ml-3 text-home-foreground/70 group-hover:text-home-primary transition-colors text-sm font-medium {{ $productType === $type['value'] ? 'font-bold text-home-primary' : '' }}">{{ $type['label'] }}</span> class="ml-3 text-home-foreground/70 group-hover:text-home-primary transition-colors text-sm font-medium {{ $productType === $type['value'] ? 'font-bold text-home-primary' : '' }}">{{ $type['label'] }}</span>
@ -68,25 +54,30 @@ class="ml-3 text-home-foreground/70 group-hover:text-home-primary transition-col
<div class="border-t border-home-foreground/10 my-8"></div> <div class="border-t border-home-foreground/10 my-8"></div>
<h3 class="font-bold text-home-primary mb-6 border-b border-home-foreground/10 pb-4">Kategori</h3> @if ($productType === 'perfume')
<div class="space-y-4"> <h3 class="font-bold text-home-primary mb-6 border-b border-home-foreground/10 pb-4">Kategori
@foreach (['Pria', 'Wanita', 'Unisex', 'Luxury', 'Best Seller', 'New Arrival'] as $cat) </h3>
<label class="flex items-center cursor-pointer group"> <div class="space-y-4">
<input type="checkbox" @foreach ($availableCategories as $id => $name)
class="w-5 h-5 rounded border-home-foreground/20 text-home-primary focus:ring-home-primary cursor-pointer accent-home-primary"> <label class="flex items-center cursor-pointer group">
<span <input type="checkbox" value="{{ $name }}"
class="ml-3 text-home-foreground/70 group-hover:text-home-primary transition-colors text-sm font-medium">{{ $cat }}</span> wire:model.live="selectedCategories"
</label> class="w-5 h-5 rounded border-home-foreground/20 text-home-primary focus:ring-home-primary cursor-pointer accent-home-primary">
@endforeach <span
</div> class="ml-3 text-home-foreground/70 group-hover:text-home-primary transition-colors text-sm font-medium">{{ $name }}</span>
</label>
@endforeach
</div>
<div class="border-t border-home-foreground/10 my-8"></div> <div class="border-t border-home-foreground/10 my-8"></div>
@endif
<h3 class="font-bold text-home-primary mb-6 border-b border-home-foreground/10 pb-4">Harga</h3> <h3 class="font-bold text-home-primary mb-6 border-b border-home-foreground/10 pb-4">Harga</h3>
<div class="space-y-4"> <div class="space-y-4">
@foreach (['< 100k', '100k - 500k', '500k - 1jt', '> 1jt'] as $price) @foreach (['< 100k', '100k - 500k', '500k - 1jt', '> 1jt'] as $price)
<label class="flex items-center cursor-pointer group"> <label class="flex items-center cursor-pointer group">
<input type="radio" name="price" <input type="radio" name="price" value="{{ $price }}"
wire:model.live="priceRange"
class="w-5 h-5 border-home-foreground/20 text-home-primary focus:ring-home-primary cursor-pointer accent-home-primary"> class="w-5 h-5 border-home-foreground/20 text-home-primary focus:ring-home-primary cursor-pointer accent-home-primary">
<span <span
class="ml-3 text-home-foreground/70 group-hover:text-home-primary transition-colors text-sm font-medium">{{ $price }}</span> class="ml-3 text-home-foreground/70 group-hover:text-home-primary transition-colors text-sm font-medium">{{ $price }}</span>
@ -101,12 +92,12 @@ class="ml-3 text-home-foreground/70 group-hover:text-home-primary transition-col
{{-- Toolbar (Sort Only) --}} {{-- Toolbar (Sort Only) --}}
<div class="flex justify-end mb-8"> <div class="flex justify-end mb-8">
<div class="relative min-w-[200px]"> <div class="relative min-w-[200px]">
<select <select wire:model.live="sort"
class="w-full appearance-none px-6 py-3 pr-10 rounded-xl border border-home-foreground/10 bg-white text-home-foreground font-medium focus:outline-none focus:ring-2 focus:ring-home-primary focus:border-transparent transition cursor-pointer shadow-sm"> class="w-full appearance-none px-6 py-3 pr-10 rounded-xl border border-home-foreground/10 bg-white text-home-foreground font-medium focus:outline-none focus:ring-2 focus:ring-home-primary focus:border-transparent transition cursor-pointer shadow-sm">
<option>Urutkan: Terbaru</option> <option value="latest">Urutkan: Terbaru</option>
<option>Harga Terendah</option> <option value="price_low">Harga Terendah</option>
<option>Harga Tertinggi</option> <option value="price_high">Harga Tertinggi</option>
<option>Terpopuler</option> <option value="popular">Terpopuler</option>
</select> </select>
<svg class="absolute right-4 top-4 w-4 h-4 text-home-foreground/50 pointer-events-none" <svg class="absolute right-4 top-4 w-4 h-4 text-home-foreground/50 pointer-events-none"
fill="none" stroke="currentColor" viewBox="0 0 24 24"> fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -116,85 +107,113 @@ class="w-full appearance-none px-6 py-3 pr-10 rounded-xl border border-home-fore
</div> </div>
</div> </div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8"> @if ($products->isNotEmpty())
@foreach ($perfumes as $perfume) <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8">
<article @foreach ($products as $product)
class="group bg-white rounded-2xl border border-home-foreground/10 overflow-hidden hover:shadow-xl hover:shadow-home-primary/5 hover:border-home-primary/30 hover:-translate-y-1 transition-all duration-300 flex flex-col h-full relative"> <article
class="group bg-white rounded-2xl border border-home-foreground/10 overflow-hidden hover:shadow-xl hover:shadow-home-primary/5 hover:border-home-primary/30 hover:-translate-y-1 transition-all duration-300 flex flex-col h-full relative">
@if ($perfume['is_new']) @if (isset($product->badge) && $product->badge)
<span
class="absolute top-4 left-4 bg-home-primary text-white text-[10px] uppercase tracking-wider font-bold px-3 py-1 rounded-full z-10 shadow-md">New</span>
@endif
<div class="relative h-72 overflow-hidden bg-home-primary/5">
<img src="{{ $perfume['image'] }}" alt="{{ $perfume['name'] }}"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500">
<div
class="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center gap-3">
<a href="#"
class="w-10 h-10 bg-white rounded-full flex items-center justify-center text-home-foreground hover:bg-home-primary hover:text-white transition-all shadow-lg transform hover:scale-110"
title="Lihat Detail">
<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="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z">
</path>
</svg>
</a>
<button
class="w-10 h-10 bg-white rounded-full flex items-center justify-center text-home-foreground hover:bg-home-primary hover:text-white transition-all shadow-lg transform hover:scale-110"
title="Simpan ke Wishlist">
<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="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z">
</path>
</svg>
</button>
</div>
</div>
<div class="p-5 flex flex-col flex-grow">
<div class="flex justify-between items-start mb-2">
<span <span
class="text-[10px] font-bold tracking-widest text-home-primary uppercase bg-home-primary/10 px-2 py-1 rounded">{{ $perfume['category'] }}</span> class="absolute top-4 left-4 {{ $product->badge['class'] }} text-[10px] uppercase tracking-wider font-bold px-3 py-1 rounded-full z-10 shadow-md">
<div class="flex items-center gap-1"> {{ $product->badge['label'] }}
</span>
@endif
<div class="relative h-72 overflow-hidden bg-home-primary/5">
<img src="{{ $product['image'] }}" alt="{{ $product['name'] }}"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500">
<div
class="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center gap-3">
<a href="{{ route('product.show', $product->slug) }}" wire:navigate
class="w-10 h-10 bg-white rounded-full flex items-center justify-center text-home-foreground hover:bg-home-primary hover:text-white transition-all shadow-lg transform hover:scale-110"
title="Lihat Detail">
<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="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z">
</path>
</svg>
</a>
{{-- Not used yet --}}
{{-- <button
class="w-10 h-10 bg-white rounded-full flex items-center justify-center text-home-foreground hover:bg-home-primary hover:text-white transition-all shadow-lg transform hover:scale-110"
title="Simpan ke Wishlist">
<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="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z">
</path>
</svg>
</button> --}}
</div>
</div>
<div class="p-5 flex flex-col flex-grow">
@if ($productType === 'perfume')
<div class="flex flex-wrap gap-2 mb-2">
@foreach ($product->categories->take(3) as $category)
<span
class="text-xs font-bold text-home-primary bg-home-primary/10 px-3 py-1 rounded-full border border-home-primary/10 uppercase tracking-wider whitespace-nowrap">
{{ $category->name }}
</span>
@endforeach
@if ($product->categories->count() > 3)
<span
class="text-xs font-bold text-home-foreground/50 px-3 py-1 rounded-full border border-home-foreground/10 whitespace-nowrap">
+{{ $product->categories->count() - 3 }}
</span>
@endif
{{-- Not used yet --}}
{{-- <div class="flex items-center gap-1">
<svg class="w-3 h-3 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <svg class="w-3 h-3 text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
<path <path
d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" /> d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</svg> </svg>
<span <span
class="text-xs font-bold text-home-foreground">{{ $perfume['rating'] }}</span> class="text-xs font-bold text-home-foreground">{{ $product['rating'] }}</span>
<span <span
class="text-xs text-home-foreground/40">({{ $perfume['reviews'] }})</span> class="text-xs text-home-foreground/40">({{ $product['reviews'] }})</span>
</div> </div> --}}
</div> </div>
@endif
<h3 <h3
class="text-lg font-bold text-home-foreground group-hover:text-home-primary transition-colors mb-4 line-clamp-1"> class="text-lg font-bold text-home-foreground group-hover:text-home-primary transition-colors my-4 line-clamp-1">
{{ $perfume['name'] }}</h3> <a
href="{{ route('product.show', $product['slug']) }}">{{ $product['name'] }}</a>
</h3>
<div <div
class="mt-auto pt-4 border-t border-home-foreground/5 flex items-center justify-between"> class="mt-auto pt-4 border-t border-home-foreground/5 flex items-center justify-between">
<span class="text-xl font-bold text-home-primary">Rp <span class="text-xl font-bold text-home-primary">Rp
{{ number_format($perfume['price'], 0, ',', '.') }}</span> {{ formatCurrencyNumber($product['sale_price']) }}</span>
<button
{{-- Not used yet --}}
{{-- <button
class="w-10 h-10 rounded-lg bg-home-foreground text-white flex items-center justify-center hover:bg-home-primary transition-colors shadow-md"> class="w-10 h-10 rounded-lg bg-home-foreground text-white flex items-center justify-center hover:bg-home-primary transition-colors shadow-md">
<svg class="w-5 h-5" 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="M12 4v16m8-8H4"></path> d="M12 4v16m8-8H4"></path>
</svg> </svg>
</button> </button> --}}
</div>
</div> </div>
</div> </article>
</article> @endforeach
@endforeach </div>
</div> @else
@include('components.animations.lottie.not-found')
@endif
<div class="mt-16 flex justify-center"> <div class="mt-16">
{{ $perfumes->links('vendor.livewire.custom-pagination') }} {{ $products->links('vendor.livewire.custom-pagination') }}
</div> </div>
</section> </section>
</div> </div>
@ -227,23 +246,41 @@ class="flex gap-6 overflow-x-auto pb-12 snap-x snap-mandatory scroll-smooth hide
<article <article
class="min-w-[280px] md:min-w-[300px] bg-white rounded-2xl border border-home-foreground/10 overflow-hidden hover:shadow-xl hover:border-home-primary/30 transition-all duration-300 snap-start group flex flex-col h-full"> class="min-w-[280px] md:min-w-[300px] bg-white rounded-2xl border border-home-foreground/10 overflow-hidden hover:shadow-xl hover:border-home-primary/30 transition-all duration-300 snap-start group flex flex-col h-full">
<div class="h-60 overflow-hidden relative bg-home-primary/5"> <div class="h-60 overflow-hidden relative bg-home-primary/5">
<img src="{{ $rec['image'] }}" alt="{{ $rec['name'] }}" <img src="{{ $rec['image'] }}" alt="{{ $rec['image'] }}"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"> class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500">
</div> </div>
<div class="p-5 flex flex-col flex-grow"> <div class="p-5 flex flex-col flex-grow">
<div class="text-[10px] font-bold tracking-widest text-home-primary uppercase mb-2"> <div class="flex flex-wrap gap-2 mb-2">
{{ $rec['category'] }}</div> @foreach (array_slice($rec['categories'], 0, 3) as $category)
<h3 class="text-lg font-bold text-home-foreground mb-1">{{ $rec['name'] }}</h3> <span
class="text-xs font-bold text-home-primary bg-home-primary/10 px-3 py-1 rounded-full border border-home-primary/10 uppercase tracking-wider whitespace-nowrap">
{{ $category['name'] }}
</span>
@endforeach
@if (count($rec['categories']) > 3)
<span
class="text-xs font-bold text-home-foreground/50 px-3 py-1 rounded-full border border-home-foreground/10 whitespace-nowrap">
+{{ count($rec['categories']) - 3 }}
</span>
@endif
</div>
<h3 class="text-lg font-bold text-home-foreground mb-1">
<a href="{{ route('product.show', $rec['slug']) }}">
{{ $rec['name'] }}</a>
</h3>
<div class="mt-auto pt-4 flex items-center justify-between"> <div class="mt-auto pt-4 flex items-center justify-between">
<span class="font-bold text-home-primary">Rp <span class="font-bold text-home-primary">Rp
{{ number_format($rec['price'], 0, ',', '.') }}</span> {{ formatCurrencyNumber($rec['sale_price']) }}</span>
<button
{{-- Not used yet --}}
{{-- <button
class="w-8 h-8 rounded bg-home-foreground/5 text-home-foreground hover:bg-home-primary hover:text-white flex items-center justify-center transition-colors"> class="w-8 h-8 rounded bg-home-foreground/5 text-home-foreground hover:bg-home-primary hover:text-white flex items-center justify-center transition-colors">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <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" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 4v16m8-8H4"></path> d="M12 4v16m8-8H4"></path>
</svg> </svg>
</button> </button> --}}
</div> </div>
</div> </div>
</article> </article>
@ -254,26 +291,6 @@ class="w-8 h-8 rounded bg-home-foreground/5 text-home-foreground hover:bg-home-p
</main> </main>
</div> </div>
<script> @assets
document.addEventListener("DOMContentLoaded", () => { <script src="https://unpkg.com/@lottiefiles/dotlottie-wc@0.8.5/dist/dotlottie-wc.js" type="module"></script>
const slider = document.getElementById('productSlider'); @endassets
const leftBtn = document.getElementById('slideLeft');
const rightBtn = document.getElementById('slideRight');
if (slider && leftBtn && rightBtn) {
rightBtn.addEventListener('click', () => {
slider.scrollBy({
left: 320,
behavior: 'smooth'
});
});
leftBtn.addEventListener('click', () => {
slider.scrollBy({
left: -320,
behavior: 'smooth'
});
});
}
});
</script>

View File

@ -1,274 +0,0 @@
@php
$product = (object) [
'name' => 'Oud Royal Premium',
'category' => 'Luxury Collection',
'price' => 1200000,
'original_price' => 1500000,
'rating' => 4.9,
'reviews_count' => 128,
'description' => 'Oud Royal adalah wewangian oriental yang memikat, menggabungkan kemewahan kayu gaharu (oud) dengan kelembutan mawar Bulgaria dan sentuhan rempah eksotis. Dirancang untuk pria dan wanita yang menginginkan aroma elegan dan tahan lama hingga 12 jam.',
'stock' => 15,
'sizes' => ['30ml', '50ml', '100ml'],
'images' => [
'https://placehold.co/600x600/png?text=Oud+Royal+1',
'https://placehold.co/600x600/png?text=Oud+Royal+2',
'https://placehold.co/600x600/png?text=Oud+Royal+3',
'https://placehold.co/600x600/png?text=Oud+Royal+4',
],
'notes' => [
'top' => 'Bergamot, Lemon, Pink Pepper',
'middle' => 'Bulgarian Rose, Saffron, Jasmine',
'base' => 'Agarwood (Oud), Amber, Musk, Vanilla'
]
];
$relatedProducts = [
[
'name' => 'Golden Amber',
'category' => 'Oriental',
'price' => 890000,
'image' => 'https://placehold.co/400x500/png?text=Golden+Amber',
'rating' => 4.9,
],
[
'name' => 'Spring Blossom',
'category' => 'Floral',
'price' => 380000,
'image' => 'https://placehold.co/400x500/png?text=Spring+Blossom',
'rating' => 4.7,
],
[
'name' => 'Woody Intense',
'category' => 'Woody',
'price' => 650000,
'image' => 'https://placehold.co/400x500/png?text=Woody+Intense',
'rating' => 4.8,
],
[
'name' => 'Aqua Marine',
'category' => 'Fresh',
'price' => 420000,
'image' => 'https://placehold.co/400x500/png?text=Aqua+Marine',
'rating' => 4.6,
],
[
'name' => 'Spicy Leather',
'category' => 'Leather',
'price' => 750000,
'image' => 'https://placehold.co/400x500/png?text=Spicy+Leather',
'rating' => 4.9,
],
];
@endphp
<div class="flex flex-col min-h-screen bg-home-background font-sans text-home-foreground" x-data="{
selectedSize: '50ml',
mainImage: '{{ $product->images[0] }}',
qty: 1
}">
<main class="flex-grow px-6 lg:px-32 xl:px-48 pb-24 pt-32">
<nav class="flex items-center text-sm text-home-foreground/50 mb-8 overflow-x-auto whitespace-nowrap">
<a href="#" class="hover:text-home-primary transition-colors">Beranda</a>
<svg class="w-3 h-3 mx-2 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
<a href="#" class="hover:text-home-primary transition-colors">Produk</a>
<svg class="w-3 h-3 mx-2 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
<a href="#" class="hover:text-home-primary transition-colors">{{ $product->category }}</a>
<svg class="w-3 h-3 mx-2 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
<span class="text-home-primary font-medium">{{ $product->name }}</span>
</nav>
<section class="grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-20 mb-24">
<div class="lg:col-span-7">
<div class="sticky top-32 space-y-6">
<div class="aspect-square bg-home-primary/5 rounded-3xl overflow-hidden border border-home-foreground/5 relative group">
<img :src="mainImage" alt="{{ $product->name }}" class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105">
<div class="absolute top-6 left-6">
<span class="bg-home-primary text-white text-xs font-bold px-3 py-1.5 rounded-full uppercase tracking-wider shadow-lg">Best Seller</span>
</div>
</div>
<div class="grid grid-cols-4 gap-4">
@foreach($product->images as $img)
<button @click="mainImage = '{{ $img }}'"
class="aspect-square rounded-xl overflow-hidden border-2 transition-all duration-300"
:class="mainImage === '{{ $img }}' ? 'border-home-primary ring-2 ring-home-primary/20' : 'border-transparent hover:border-home-foreground/20'">
<img src="{{ $img }}" class="w-full h-full object-cover">
</button>
@endforeach
</div>
</div>
</div>
<div class="lg:col-span-5 flex flex-col h-full">
<div class="mb-auto">
<div class="flex items-center justify-between mb-4">
<span class="text-sm font-bold tracking-widest text-home-primary uppercase bg-home-primary/10 px-3 py-1 rounded">{{ $product->category }}</span>
<div class="flex items-center gap-1 text-yellow-500 text-sm">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/></svg>
<span class="font-bold text-home-foreground">{{ $product->rating }}</span>
<span class="text-home-foreground/40 underline decoration-home-foreground/20 cursor-pointer">({{ $product->reviews_count }} Ulasan)</span>
</div>
</div>
<h1 class="text-3xl md:text-4xl font-bold text-home-foreground mb-4 leading-tight">{{ $product->name }}</h1>
<div class="flex items-end gap-3 mb-8 border-b border-home-foreground/10 pb-8">
<span class="text-3xl font-bold text-home-primary">Rp {{ number_format($product->price, 0, ',', '.') }}</span>
<span class="text-lg text-home-foreground/40 line-through mb-1">Rp {{ number_format($product->original_price, 0, ',', '.') }}</span>
</div>
<div class="mb-8">
<p class="text-home-foreground/70 leading-relaxed text-lg">
{{ $product->description }}
</p>
</div>
<div class="bg-home-foreground/5 rounded-2xl p-6 mb-8 border border-home-foreground/5">
<h3 class="font-bold text-home-foreground mb-4 flex items-center gap-2">
<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="M19.428 15.428a2 2 0 00-1.022-.547l-2.384-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path></svg>
Fragrance Notes
</h3>
<div class="space-y-3">
<div class="flex">
<span class="w-24 text-sm font-bold text-home-foreground/50">Top Notes</span>
<span class="text-sm font-medium text-home-foreground">{{ $product->notes['top'] }}</span>
</div>
<div class="flex">
<span class="w-24 text-sm font-bold text-home-foreground/50">Middle</span>
<span class="text-sm font-medium text-home-foreground">{{ $product->notes['middle'] }}</span>
</div>
<div class="flex">
<span class="w-24 text-sm font-bold text-home-foreground/50">Base Notes</span>
<span class="text-sm font-medium text-home-foreground">{{ $product->notes['base'] }}</span>
</div>
</div>
</div>
<div class="mb-8">
<label class="block text-sm font-bold text-home-foreground mb-3">Pilih Ukuran</label>
<div class="flex flex-wrap gap-3">
@foreach($product->sizes as $size)
<button @click="selectedSize = '{{ $size }}'"
class="px-6 py-3 rounded-xl border-2 font-bold transition-all duration-200"
:class="selectedSize === '{{ $size }}'
? 'border-home-primary bg-home-primary text-white shadow-lg shadow-home-primary/20'
: 'border-home-foreground/10 text-home-foreground hover:border-home-primary/50'">
{{ $size }}
</button>
@endforeach
</div>
</div>
<div class="flex flex-col sm:flex-row gap-4">
<div class="flex items-center border-2 border-home-foreground/10 rounded-xl px-4 w-full sm:w-auto">
<button @click="qty > 1 ? qty-- : null" class="w-8 h-8 flex items-center justify-center text-home-foreground/50 hover:text-home-primary transition-colors font-bold text-xl">-</button>
<input type="text" x-model="qty" readonly class="w-12 text-center bg-transparent font-bold text-home-foreground border-none focus:ring-0">
<button @click="qty++" class="w-8 h-8 flex items-center justify-center text-home-foreground/50 hover:text-home-primary transition-colors font-bold text-xl">+</button>
</div>
<button class="flex-1 bg-home-primary text-white font-bold py-4 rounded-xl hover:bg-home-secondary hover:text-home-primary transition-all duration-300 shadow-xl 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="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"></path></svg>
Tambah ke Keranjang
</button>
<button class="w-14 h-full border-2 border-home-foreground/10 rounded-xl flex items-center justify-center text-home-foreground/50 hover:text-red-500 hover:border-red-200 hover:bg-red-50 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="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"></path></svg>
</button>
</div>
<div class="mt-6 flex gap-6 text-xs font-bold text-home-foreground/60 uppercase tracking-widest">
<span class="flex items-center gap-2"><svg class="w-4 h-4 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> 100% Original</span>
<span class="flex items-center gap-2"><svg class="w-4 h-4 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> Garansi Pengiriman</span>
</div>
</div>
</div>
</section>
<section class="mb-24">
<div class="border-b border-home-foreground/10 mb-8">
<div class="flex gap-8 overflow-x-auto">
<button class="pb-4 border-b-2 border-home-primary text-home-primary font-bold text-lg whitespace-nowrap">Detail Produk</button>
<button class="pb-4 border-b-2 border-transparent text-home-foreground/50 font-medium text-lg hover:text-home-foreground transition-colors whitespace-nowrap">Cara Penggunaan</button>
<button class="pb-4 border-b-2 border-transparent text-home-foreground/50 font-medium text-lg hover:text-home-foreground transition-colors whitespace-nowrap">Ulasan (128)</button>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-12 text-home-foreground/70 leading-relaxed">
<div class="lg:col-span-2 space-y-4">
<p>Oud Royal Premium adalah mahakarya wewangian yang diciptakan untuk mereka yang menghargai kualitas tanpa kompromi. Menggunakan minyak gaharu (oud) langka dari Kalimantan yang difermentasi selama 5 tahun, dipadukan dengan kesegaran Bergamot Italia di top notes.</p>
<p>Jantung aromanya mekar dengan mawar Bulgaria yang dipetik saat fajar, memberikan nuansa floral yang mewah namun tidak berlebihan. Base notes yang hangat dari Amber dan Musk memastikan aroma ini menempel di kulit Anda, menciptakan jejak aroma (sillage) yang tak terlupakan.</p>
<ul class="list-disc pl-5 space-y-2 mt-4">
<li>Konsentrasi: Eau de Parfum (EDP)</li>
<li>Longevity: 8-12 Jam</li>
<li>Projection: 2-3 Meter</li>
<li>Gender: Unisex (Condong ke Maskulin)</li>
</ul>
</div>
<div class="bg-home-foreground/5 p-8 rounded-2xl border border-home-foreground/5 h-fit">
<h4 class="font-bold text-home-foreground mb-4">Pengiriman & Pengembalian</h4>
<p class="text-sm mb-4">Pesanan sebelum jam 14.00 dikirim hari yang sama. Pengiriman aman dengan bubble wrap tebal dan box eksklusif.</p>
<p class="text-sm">Garansi uang kembali 100% jika produk pecah atau tidak original (wajib video unboxing).</p>
</div>
</div>
</section>
<section class="border-t border-home-foreground/10 pt-16 mb-16">
<div class="flex items-center justify-between mb-10">
<h2 class="text-2xl md:text-3xl font-bold text-home-primary">Anda Mungkin Juga Suka</h2>
<div class="flex gap-2">
<button id="slideLeft" class="w-10 h-10 rounded-full border border-home-foreground/10 flex items-center justify-center text-home-foreground hover:bg-home-primary hover:text-white transition-all"><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="M15 19l-7-7 7-7"></path></svg></button>
<button id="slideRight" class="w-10 h-10 rounded-full border border-home-foreground/10 flex items-center justify-center text-home-foreground hover:bg-home-primary hover:text-white transition-all"><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 5l7 7-7 7"></path></svg></button>
</div>
</div>
<div id="productSlider"
class="flex gap-6 overflow-x-auto pb-12 snap-x snap-mandatory scroll-smooth hide-scrollbar"
style="scrollbar-width: none; -ms-overflow-style: none;">
@foreach ($relatedProducts as $rec)
<article
class="min-w-[280px] md:min-w-[300px] bg-white rounded-2xl border border-home-foreground/10 overflow-hidden hover:shadow-xl hover:border-home-primary/30 transition-all duration-300 snap-start group flex flex-col h-full">
<div class="h-60 overflow-hidden relative bg-home-primary/5">
<img src="{{ $rec['image'] }}" alt="{{ $rec['name'] }}"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500">
</div>
<div class="p-5 flex flex-col flex-grow">
<div class="text-[10px] font-bold tracking-widest text-home-primary uppercase mb-2">
{{ $rec['category'] }}</div>
<h3 class="text-lg font-bold text-home-foreground mb-1">{{ $rec['name'] }}</h3>
<div class="mt-auto pt-4 flex items-center justify-between">
<span class="font-bold text-home-primary">Rp
{{ number_format($rec['price'], 0, ',', '.') }}</span>
<button
class="w-8 h-8 rounded bg-home-foreground/5 text-home-foreground hover:bg-home-primary hover:text-white flex items-center justify-center transition-colors">
<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 4v16m8-8H4"></path>
</svg>
</button>
</div>
</div>
</article>
@endforeach
</div>
</section>
</main>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
const slider = document.getElementById('productSlider');
const leftBtn = document.getElementById('slideLeft');
const rightBtn = document.getElementById('slideRight');
if (slider && leftBtn && rightBtn) {
rightBtn.addEventListener('click', () => {
slider.scrollBy({ left: 320, behavior: 'smooth' });
});
leftBtn.addEventListener('click', () => {
slider.scrollBy({ left: -320, behavior: 'smooth' });
});
}
});
</script>

View File

@ -0,0 +1,226 @@
<div>
{{-- Hero Section --}}
<section class="relative h-[500px] flex items-center justify-center overflow-hidden">
{{-- Background Image --}}
<div class="absolute inset-0 z-0">
<img src="https://images.pexels.com/photos/22589353/pexels-photo-22589353.jpeg" alt="Article Background"
class="w-full h-full object-cover">
<div class="absolute inset-0 bg-black/40"></div>
</div>
{{-- 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">{{ $pageTitle }}</h1>
<p class="text-white/80 text-lg md:text-xl max-w-2xl mx-auto mb-10">
{{ $pageDesc }}
</p>
</div>
</section>
<main class="px-6 lg:px-32 xl:px-48 py-24 bg-white text-black font-sans">
{{-- Product Detail Section --}}
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 mb-24">
{{-- Product Image --}}
<div class="space-y-4">
<div
class="aspect-square w-full rounded-2xl overflow-hidden shadow-2xl bg-home-primary/5 border border-home-foreground/10">
<img src="{{ $product->image }}" alt="{{ $product->name }}"
class="w-full h-full object-cover hover:scale-105 transition-transform duration-500">
</div>
</div>
{{-- Product Info --}}
<div class="space-y-6">
{{-- Categories (for perfume) --}}
@if ($productType === 'perfume' && $product->categories->isNotEmpty())
<div class="flex flex-wrap gap-2">
@foreach ($product->categories as $category)
<span
class="text-xs font-bold text-home-primary bg-home-primary/10 px-3 py-1 rounded-full border border-home-primary/10 uppercase tracking-wider">
{{ $category->name }}
</span>
@endforeach
</div>
@endif
{{-- Product Name --}}
<h1 class="text-3xl md:text-4xl lg:text-5xl font-bold text-home-foreground leading-tight">
{{ $product->name }}
</h1>
{{-- Brand (for perfume) --}}
@if ($productType === 'perfume' && $product->brand)
<div class="flex items-center gap-2">
<span class="text-home-foreground/60">Brand:</span>
<span class="font-bold text-home-primary">{{ $product->brand->name }}</span>
</div>
@endif
{{-- Concentration (for perfume) --}}
@if ($productType === 'perfume' && isset($product->concentration))
<div class="flex items-center gap-2">
<span class="text-home-foreground/60">Konsentrasi:</span>
<span class="font-medium text-home-foreground">{{ $product->concentration->label() }}</span>
</div>
@endif
{{-- Price --}}
<div class="py-6 border-y border-home-foreground/10">
<div class="flex items-baseline gap-3">
<span class="text-4xl font-bold text-home-primary">Rp
{{ formatCurrencyNumber($product->sale_price) }}</span>
</div>
</div>
{{-- Notes (for perfume) --}}
@if ($productType === 'perfume')
<div class="space-y-4">
@if ($product->top_notes)
<div class="flex gap-4">
<span class="text-home-foreground/60 font-medium min-w-[120px]">Top Notes:</span>
<span class="text-home-foreground">{{ $product->top_notes }}</span>
</div>
@endif
@if ($product->middle_notes)
<div class="flex gap-4">
<span class="text-home-foreground/60 font-medium min-w-[120px]">Middle Notes:</span>
<span class="text-home-foreground">{{ $product->middle_notes }}</span>
</div>
@endif
@if ($product->base_notes)
<div class="flex gap-4">
<span class="text-home-foreground/60 font-medium min-w-[120px]">Base Notes:</span>
<span class="text-home-foreground">{{ $product->base_notes }}</span>
</div>
@endif
</div>
@endif
{{-- Description --}}
@if ($product->description)
<div class="space-y-3">
<h3 class="text-lg font-bold text-home-foreground">Deskripsi</h3>
<p class="text-home-foreground/70 leading-relaxed text-justify">
{!! $product->description !!}
</p>
</div>
@endif
{{-- Stats --}}
<div class="flex items-center gap-6 pt-4">
<div class="flex items-center gap-2 text-home-foreground/60">
<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="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z">
</path>
</svg>
<span class="text-sm">{{ $product->views }}x dilihat</span>
</div>
<div class="flex items-center gap-2 text-home-foreground/60">
<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="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span class="text-sm">{{ formatDateLocalized($product->created_at) }}</span>
</div>
</div>
{{-- Share Buttons --}}
<div class="flex items-center gap-4 pt-6 border-t border-home-foreground/10">
<span class="text-sm font-bold text-home-foreground">Bagikan:</span>
{{-- WhatsApp --}}
<a href="https://wa.me/?text={{ urlencode($product->name . ' - ' . url()->current()) }}"
target="_blank" rel="noopener noreferrer"
class="w-10 h-10 rounded-full border border-home-foreground/10 flex items-center justify-center text-home-foreground hover:bg-home-primary hover:text-white hover:border-home-primary transition-all">
<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>
</a>
{{-- Facebook --}}
<a href="https://www.facebook.com/sharer/sharer.php?u={{ urlencode(url()->current()) }}"
target="_blank" rel="noopener noreferrer"
class="w-10 h-10 rounded-full border border-home-foreground/10 flex items-center justify-center text-home-foreground hover:bg-home-primary hover:text-white hover:border-home-primary transition-all">
<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>
</a>
{{-- X (Twitter) --}}
<a href="https://twitter.com/intent/tweet?text={{ urlencode($product->name) }}&url={{ urlencode(url()->current()) }}"
target="_blank" rel="noopener noreferrer"
class="w-10 h-10 rounded-full border border-home-foreground/10 flex items-center justify-center text-home-foreground hover:bg-home-primary hover:text-white hover:border-home-primary transition-all">
<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>
</a>
</div>
</div>
</div>
{{-- Related Products --}}
@if (count($relatedProducts) > 0)
<section class="mt-24 pt-16 border-t border-home-foreground/10">
<div class="mb-12">
<h2 class="text-2xl md:text-3xl font-bold text-home-primary">Produk Terkait</h2>
<p class="text-home-foreground/60 mt-2">Produk lain yang mungkin Anda suka</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
@foreach ($relatedProducts as $related)
<article
class="group bg-white rounded-2xl border border-home-foreground/10 overflow-hidden hover:shadow-xl hover:border-home-primary/30 transition-all duration-300 flex flex-col h-full">
<div class="h-60 overflow-hidden relative bg-home-primary/5">
<img src="{{ $related['image'] }}" alt="{{ $related['name'] }}"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500">
<div
class="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
<a href="{{ route('product.show', $related['slug']) }}" wire:navigate
class="w-10 h-10 bg-white rounded-full flex items-center justify-center text-home-foreground hover:bg-home-primary hover:text-white transition-all shadow-lg transform hover:scale-110">
<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="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z">
</path>
</svg>
</a>
</div>
</div>
<div class="p-5 flex flex-col flex-grow">
@if ($related['category'])
<div
class="text-[10px] font-bold tracking-widest text-home-primary uppercase mb-2">
{{ $related['category'] }}
</div>
@endif
<h3 class="text-lg font-bold text-home-foreground mb-1 line-clamp-2">
{{ $related['name'] }}
</h3>
<div class="mt-auto pt-4 flex items-center justify-between">
<span class="font-bold text-home-primary">Rp
{{ formatCurrencyNumber($related['price']) }}</span>
</div>
</div>
</article>
@endforeach
</div>
</section>
@endif
</main>
</div>

View File

@ -1,24 +1,24 @@
<div class="flex flex-col min-h-screen bg-white"> <div>
{{-- 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.pexels.com/photos/5650052/pexels-photo-5650052.jpeg" alt="Voucher Background" <img src="https://images.pexels.com/photos/5650052/pexels-photo-5650052.jpeg" alt="Perfume 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/40"></div>
</div> </div>
{{-- Content --}} {{-- Content --}}
<div class="relative z-10 container mx-auto px-6 text-center"> <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">Voucher Eksklusif</h1> <h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-white mb-6">{{ $pageTitle }}</h1>
<p class="text-white/80 text-lg md:text-xl max-w-2xl mx-auto mb-10"> <p class="text-white/80 text-lg md:text-xl max-w-2xl mx-auto mb-10">
Nikmati potongan harga spesial untuk koleksi parfum favorit Anda. {{ $pageDesc }}
</p> </p>
{{-- Search Bar --}} {{-- Search Bar --}}
<div class="max-w-xl mx-auto relative group"> <div class="max-w-xl mx-auto relative group">
<input type="text" placeholder="Cari kode voucher..." aria-label="Cari voucher" <input wire:model.live.debounce.500="search" type="search" placeholder="Cari voucher..."
wire:model.live.debounce.500ms="search" aria-label="Cari artikel"
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"> 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 <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"> 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">
@ -31,10 +31,8 @@ class="absolute right-2 top-2 bottom-2 w-10 h-10 bg-home-primary rounded-full fl
</div> </div>
</section> </section>
<main class="flex-grow px-6 lg:px-32 xl:px-48 py-24 font-sans flex flex-col"> <main class="px-6 lg:px-32 xl:px-48 py-24 bg-white text-black font-sans">
<div 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"
@ -64,123 +62,145 @@ class="appearance-none px-6 py-3 pr-10 rounded-xl border border-home-foreground/
</svg> </svg>
</div> </div>
</div> </div>
</section> </div>
<section class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-12"> @if ($vouchers->isNotEmpty())
@foreach ($vouchers as $voucher) <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-12">
<article @foreach ($vouchers as $voucher)
class="bg-white rounded-2xl border border-home-foreground/10 overflow-hidden hover:shadow-xl hover:shadow-home-primary/5 hover:border-home-primary/30 hover:-translate-y-1 transition-all duration-300 group flex flex-col h-full"> <article
class="bg-white rounded-2xl border border-home-foreground/10 overflow-hidden hover:shadow-xl hover:shadow-home-primary/5 hover:border-home-primary/30 hover:-translate-y-1 transition-all duration-300 group flex flex-col h-full">
<div class="bg-home-primary/5 p-6 relative overflow-hidden flex-1"> <div class="bg-home-primary/5 p-6 relative overflow-hidden flex-1">
<div
class="absolute top-0 right-0 w-24 h-24 bg-home-primary/10 rounded-full blur-2xl -mr-10 -mt-10">
</div>
<div class="relative z-10">
<div <div
class="inline-flex items-center gap-2 bg-white px-3 py-1 rounded-full border border-home-primary/10 shadow-sm mb-4"> class="absolute top-0 right-0 w-24 h-24 bg-home-primary/10 rounded-full blur-2xl -mr-10 -mt-10">
<span class="w-2 h-2 rounded-full bg-home-primary"></span>
<span
class="text-xs font-bold text-home-primary uppercase tracking-wider">{{ $voucher->tags }}</span>
</div> </div>
<h3 <div class="relative z-10">
class="text-xl font-bold text-home-foreground mb-2 group-hover:text-home-primary transition-colors line-clamp-2">
{{ $voucher->name }}
</h3>
<p class="text-sm text-home-foreground/60 mb-6 line-clamp-2 leading-relaxed">
{{ $voucher->summary }}
</p>
<div
class="bg-white rounded-xl p-4 border border-dashed border-home-primary/30 flex items-center justify-between group-hover:border-home-primary transition-colors">
<div>
<p
class="text-[10px] text-home-foreground/50 uppercase tracking-widest font-bold mb-1">
Kode Voucher</p>
<p class="text-lg font-bold text-home-primary font-mono tracking-widest select-all">
{{ $voucher->code }}
</p>
</div>
<div <div
class="w-8 h-8 rounded-full bg-home-primary/10 flex items-center justify-center text-home-primary"> class="inline-flex items-center gap-2 bg-white px-3 py-1 rounded-full border border-home-primary/10 shadow-sm mb-4">
<span class="w-2 h-2 rounded-full bg-home-primary"></span>
<span
class="text-xs font-bold text-home-primary uppercase tracking-wider">{{ $voucher->tags }}</span>
</div>
<h3
class="text-xl font-bold text-home-foreground mb-2 group-hover:text-home-primary transition-colors line-clamp-2">
{{ $voucher->name }}
</h3>
<p class="text-sm text-home-foreground/60 mb-6 line-clamp-2 leading-relaxed">
{{ $voucher->summary }}
</p>
<div class="bg-white rounded-xl p-4 border border-dashed border-home-primary/30 flex items-center justify-between cursor-pointer hover:border-home-primary transition-colors"
x-data="{ textToCopy: '{{ $voucher->code }}', copied: false }"
@click="
navigator.clipboard.writeText(textToCopy);
copied = true;
setTimeout(() => copied = false, 1500);
">
<div>
<p
class="text-[10px] text-home-foreground/50 uppercase tracking-widest font-bold mb-1">
Kode Voucher
</p>
<p
class="text-lg font-bold text-home-primary font-mono tracking-widest select-all">
{{ $voucher->code }}
</p>
</div>
<div
class="w-8 h-8 rounded-full bg-home-primary/10 flex items-center justify-center text-home-primary">
<template x-if="!copied">
<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 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
</template>
<template x-if="copied">
<svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M5 13l4 4L19 7" />
</svg>
</template>
</div>
</div>
</div>
</div>
<div class="px-6 pt-4 pb-0 space-y-3">
<div
class="flex justify-between items-center text-sm border-b border-home-foreground/5 pb-3">
<span class="text-home-foreground/50 flex items-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <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" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"> 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> </path>
</svg> </svg>
</div> Min. Pembelian
</span>
<span
class="font-bold text-home-foreground">{{ formatCurrencyNumber($voucher->min_purchase, 'Rp') }}</span>
</div>
<div
class="flex justify-between items-center text-sm border-b border-home-foreground/5 pb-3">
<span class="text-home-foreground/50 flex items-center gap-2">
<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>
Berlaku Hingga
</span>
<time datetime="{{ formatDateLocalized($voucher->end_date) }}"
class="font-bold text-home-foreground">{{ formatDateLocalized($voucher->end_date) }}</time>
</div>
<div class="flex justify-between items-center text-sm">
<span class="text-home-foreground/50 flex items-center gap-2">
<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="M20 12H4"></path>
</svg>
Sisa Kuota
</span>
<span
class="font-bold text-home-primary bg-home-primary/10 px-2 py-0.5 rounded text-xs">
{{ $voucher->available_count }} / {{ $voucher->quota ?? '∞' }}
</span>
</div> </div>
</div> </div>
</div>
<div class="px-6 pt-4 pb-0 space-y-3"> <div class="p-6 flex gap-3 mt-auto">
<div class="flex justify-between items-center text-sm border-b border-home-foreground/5 pb-3"> <button wire:click="claim('{{ $voucher->hash }}')"
<span class="text-home-foreground/50 flex items-center gap-2"> 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">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> Klaim Voucher
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" </button>
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"> <button wire:click="showDetail('{{ $voucher->hash }}')"
</path> 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">
</svg> Lihat Detail
Min. Pembelian </button>
</span>
<span class="font-bold text-home-foreground">{{ $voucher->min_purchase_formatted }}</span>
</div> </div>
<div class="flex justify-between items-center text-sm border-b border-home-foreground/5 pb-3"> </article>
<span class="text-home-foreground/50 flex items-center gap-2"> @endforeach
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> </div>
<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>
Berlaku Hingga
</span>
<time datetime="{{ $voucher->end_date }}"
class="font-bold text-home-foreground">{{ $voucher->end_date }}</time>
</div>
<div class="flex justify-between items-center text-sm">
<span class="text-home-foreground/50 flex items-center gap-2">
<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="M20 12H4"></path>
</svg>
Sisa Kuota
</span>
<span class="font-bold text-home-primary bg-home-primary/10 px-2 py-0.5 rounded text-xs">
{{ $voucher->available_count }} / {{ $voucher->quota ?? '∞' }}
</span>
</div>
</div>
<div class="p-6 flex gap-3 mt-auto"> <div class="flex justify-center w-full">
<button wire:click="claim('{{ $voucher->hash }}')" {{ $vouchers->links('vendor.livewire.custom-pagination') }}
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"> </div>
Klaim Voucher @else
</button> @include('components.animations.lottie.not-found')
<button wire:click="showDetail('{{ $voucher->hash }}')" @endif
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
</button>
</div>
</article>
@endforeach
</section>
<div class="mt-auto flex justify-center w-full">
{{ $vouchers->links('vendor.livewire.custom-pagination') }}
</div>
</main> </main>
{{-- Voucher Detail Modal --}}
@if ($showDetailModal && $selectedVoucher) @if ($showDetailModal && $selectedVoucher)
<div class="fixed inset-0 z-50 flex items-center justify-center p-4"> <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> <div class="absolute inset-0 bg-black/60 backdrop-blur-sm" wire:click="closeDetail"></div>
{{-- Modal Content --}}
<div <div
class="relative bg-white w-full max-w-2xl rounded-3xl overflow-hidden shadow-2xl animate-in fade-in zoom-in duration-300"> 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="relative h-48 bg-home-primary flex items-center justify-center overflow-hidden">
<div class="absolute inset-0 opacity-20"> <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 top-0 right-0 w-64 h-64 bg-white rounded-full -mr-32 -mt-32"></div>
@ -203,7 +223,6 @@ class="absolute top-6 right-6 w-10 h-10 rounded-full bg-white/10 hover:bg-white/
</button> </button>
</div> </div>
{{-- Modal Body --}}
<div class="p-8 max-h-[60vh] overflow-y-auto custom-scrollbar"> <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 class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8">
<div> <div>
@ -224,7 +243,7 @@ class="w-8 h-8 rounded-lg bg-home-primary/5 flex items-center justify-center tex
<div> <div>
<p class="text-sm font-medium text-home-foreground/50">Minimum Pembelian</p> <p class="text-sm font-medium text-home-foreground/50">Minimum Pembelian</p>
<p class="font-bold text-home-foreground"> <p class="font-bold text-home-foreground">
{{ $selectedVoucher->min_purchase_formatted }}</p> {{ formatCurrencyNumber($selectedVoucher->min_purchase, 'Rp') }}</p>
</div> </div>
</div> </div>
<div class="flex items-start gap-3"> <div class="flex items-start gap-3">
@ -240,7 +259,7 @@ class="w-8 h-8 rounded-lg bg-home-primary/5 flex items-center justify-center tex
<div> <div>
<p class="text-sm font-medium text-home-foreground/50">Masa Berlaku</p> <p class="text-sm font-medium text-home-foreground/50">Masa Berlaku</p>
<p class="font-bold text-home-foreground">Hingga <p class="font-bold text-home-foreground">Hingga
{{ $selectedVoucher->end_date_formatted }}</p> {{ formatDateLocalized($selectedVoucher->end_date) }}</p>
</div> </div>
</div> </div>
@if ($selectedVoucher->limit_per_user) @if ($selectedVoucher->limit_per_user)
@ -266,7 +285,8 @@ class="w-8 h-8 rounded-lg bg-home-primary/5 flex items-center justify-center tex
</div> </div>
<div> <div>
<h4 class="text-sm font-bold text-home-foreground/40 uppercase tracking-wider mb-4">Outlet <h4 class="text-sm font-bold text-home-foreground/40 uppercase tracking-wider mb-4">
Outlet
Tersedia Tersedia
</h4> </h4>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
@ -291,6 +311,27 @@ class="px-4 py-2 bg-home-foreground/5 border border-home-foreground/10 rounded-x
@endforelse @endforelse
</div> </div>
</div> </div>
<div>
<h4 class="text-sm font-bold text-home-foreground/40 uppercase tracking-wider mb-4">
Tier
</h4>
<div class="flex flex-wrap gap-2">
@forelse ($selectedVoucher->tiers as $tier)
<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>
{{ $tier->name }}
</span>
@endforeach
</div>
</div>
</div> </div>
<div class="bg-home-foreground/5 rounded-2xl p-6"> <div class="bg-home-foreground/5 rounded-2xl p-6">
@ -302,13 +343,12 @@ class="px-4 py-2 bg-home-foreground/5 border border-home-foreground/10 rounded-x
</div> </div>
</div> </div>
{{-- Modal Footer --}}
<div class="p-8 bg-white border-t border-home-foreground/5 flex gap-4"> <div class="p-8 bg-white border-t border-home-foreground/5 flex gap-4">
<button wire:click="closeDetail" <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"> 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 Tutup
</button> </button>
<button wire:click="claim('{{ $selectedVoucher->hashId }}')" <button wire:click="claim('{{ $selectedVoucher->hash }}')"
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"> 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 Klaim Sekarang
</button> </button>
@ -317,3 +357,7 @@ class="flex-[2] py-4 px-6 bg-home-primary text-white font-bold rounded-2xl hover
</div> </div>
@endif @endif
</div> </div>
@assets
<script src="https://unpkg.com/@lottiefiles/dotlottie-wc@0.8.5/dist/dotlottie-wc.js" type="module"></script>
@endassets

View File

@ -5,7 +5,7 @@
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
@can('view brand') @can('view brand')
<a href="{{ route('studio.export.brands') }}" class="inline-flex"> <a href="{{ route('studio.export.brand') }}" class="inline-flex">
<flux:button variant="primary" color="zinc" class="text-sm"> <flux:button variant="primary" color="zinc" class="text-sm">
Ekspor Ecxel Ekspor Ecxel
</flux:button> </flux:button>

View File

@ -48,7 +48,7 @@
<flux:label>Icon <span class="text-red-500 ms-1">*</span></flux:label> <flux:label>Icon <span class="text-red-500 ms-1">*</span></flux:label>
<flux:description>Lihat dan copy icon nya di <a href="https://heroicons.com/" class="text-blue-500" <flux:description>Lihat dan copy icon nya di <a href="https://heroicons.com/" class="text-blue-500"
target="_blank">Heroicons</a></flux:description> target="_blank">Heroicons</a></flux:description>
<flux:input placeholder="tag" wire:model="form.icon" autofocus autocomplete="off" /> <flux:input placeholder="..." wire:model="form.icon" autofocus autocomplete="off" />
<flux:error name="form.icon" /> <flux:error name="form.icon" />
</flux:field> </flux:field>

View File

@ -54,9 +54,14 @@
<flux:input label="Kuota" placeholder="100" wire:model="form.quota" <flux:input label="Kuota" placeholder="100" wire:model="form.quota"
x-mask:dynamic="$money($input, ',')" autocomplete="off" /> x-mask:dynamic="$money($input, ',')" autocomplete="off" />
<flux:input label="Batas Per Customer" placeholder="1" <flux:field>
wire:model="form.limit_per_user" x-mask:dynamic="$money($input, ',')" <flux:label>Batas Per Customer <span class="text-red-500 ms-1">*</span>
autocomplete="off" /> </flux:label>
<flux:input placeholder="1" wire:model="form.limit_per_user"
x-mask:dynamic="$money($input, ',')" autocomplete="off" />
<flux:error name="form.limit_per_user" />
</flux:field>
</div> </div>
</div> </div>

View File

@ -1,11 +1,14 @@
<?php <?php
use App\Http\Controllers\ProfileRedirectController;
use App\Livewire\Home\Article\Index as ArticleIndex; use App\Livewire\Home\Article\Index as ArticleIndex;
use App\Livewire\Home\Article\Show as ArticleShow;
use App\Livewire\Home\Cart\Index as CartComponent; use App\Livewire\Home\Cart\Index as CartComponent;
use App\Livewire\Home\Faq\Index as FaqComponent; use App\Livewire\Home\Faq as FaqComponent;
use App\Livewire\Home\Index as HomeComponent; use App\Livewire\Home\Index as HomeComponent;
use App\Livewire\Home\Outlet as OutletComponent; use App\Livewire\Home\Outlet as OutletComponent;
use App\Livewire\Home\Product\Index as ProductIndex; use App\Livewire\Home\Product\Index as ProductIndex;
use App\Livewire\Home\Product\Show as ProductShow;
use App\Livewire\Home\Voucher as VoucherComponent; use App\Livewire\Home\Voucher as VoucherComponent;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@ -13,19 +16,24 @@
Route::get('/outlets', OutletComponent::class)->name('outlet'); Route::get('/outlets', OutletComponent::class)->name('outlet');
Route::get('/products', ProductIndex::class)->name('product.index'); Route::prefix('products')
->name('product.')
->group(function () {
Route::get('/', ProductIndex::class)->name('index');
Route::get('/{slug}', ProductShow::class)->name('show');
});
Route::get('/vouchers', VoucherComponent::class)->name('voucher'); Route::get('/vouchers', VoucherComponent::class)->name('voucher');
Route::prefix('articles') Route::prefix('articles')
->name('article.') ->name('article.')
->group(function () { ->group(function () {
Route::get('{slug?}', ArticleIndex::class) Route::get('/', ArticleIndex::class)->name('index');
->name('index'); Route::get('/{article:slug}', ArticleShow::class)->name('show');
}); });
Route::get('/faq', FaqComponent::class)->name('faq'); Route::get('/faqs', FaqComponent::class)->name('faq');
Route::get('/cart', CartComponent::class)->name('cart'); Route::get('/cart', CartComponent::class)->name('cart');
Route::get('/profile/redirect', \App\Http\Controllers\ProfileRedirectController::class)->name('profile.redirect'); Route::get('/profile/redirect', ProfileRedirectController::class)->name('profile.redirect');