feat: Implement article and product show pages, refactor home components, and update migrations
This commit is contained in:
parent
b469fbff53
commit
3c1b6fc915
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,8 +77,8 @@ public function columns(): array
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
Column::make('Batas Per Pembali', 'limit_per_user')
|
||||
->format(fn ($value) => $value ? $value : 'Tidak Terbatas')
|
||||
Column::make('Batas Per Pembeli', 'limit_per_user')
|
||||
->format(fn ($value) => $value ? formatCurrencyNumber($value) : 'Tidak Terbatas')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ class VoucherForm extends Form
|
||||
|
||||
public ?string $quota = null;
|
||||
|
||||
public ?string $limit_per_user = null;
|
||||
public string $limit_per_user = '';
|
||||
|
||||
public string $summary = '';
|
||||
|
||||
@ -74,10 +74,10 @@ public function rules(): array
|
||||
),
|
||||
],
|
||||
'quota' => ['nullable', new UnsignedInteger],
|
||||
'limit_per_user' => ['nullable', new UnsignedInteger],
|
||||
'limit_per_user' => ['required', new UnsignedInteger],
|
||||
'summary' => ['required', 'string', 'max:200'],
|
||||
'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'
|
||||
)],
|
||||
'end_date' => ['nullable', 'date', 'after_or_equal:start_date'],
|
||||
@ -181,9 +181,9 @@ private function prepareSavedData(): array
|
||||
'discount_amount' => parseRupiahToInt($this->discount_amount),
|
||||
'min_purchase' => parseRupiahToInt($this->min_purchase),
|
||||
'max_discount' => $this->type == VoucherType::PERCENTAGE->value ? parseRupiahToInt($this->max_discount) : null,
|
||||
'quota' => $this->quota,
|
||||
'available_count' => empty($this->quota) ? 0 : $this->quota,
|
||||
'limit_per_user' => $this->limit_per_user,
|
||||
'quota' => parseRupiahToInt($this->quota),
|
||||
'available_count' => empty($this->quota) ? 0 : parseRupiahToInt($this->quota),
|
||||
'limit_per_user' => parseRupiahToInt($this->limit_per_user),
|
||||
'summary' => $this->summary,
|
||||
'start_date' => $this->start_date,
|
||||
'end_date' => $this->end_date,
|
||||
|
||||
@ -2,33 +2,107 @@
|
||||
|
||||
namespace App\Livewire\Home\Article;
|
||||
|
||||
use App\Enums\CategoryType;
|
||||
use App\Models\Article;
|
||||
use App\Models\Category;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Attributes\Url;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithoutUrlPagination;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
#[Layout('components.layouts.home', [
|
||||
'title' => 'Article',
|
||||
'title' => 'Artikel',
|
||||
])]
|
||||
class Index extends Component
|
||||
{
|
||||
use WithoutUrlPagination, WithPagination;
|
||||
|
||||
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->isDetail = true;
|
||||
$this->slug = $slug;
|
||||
}
|
||||
$this->categories = Category::query()
|
||||
->where('type', CategoryType::ARTICLE)
|
||||
->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
|
||||
{
|
||||
return view(
|
||||
$this->isDetail
|
||||
? 'livewire.home.article.show'
|
||||
: 'livewire.home.article.index'
|
||||
);
|
||||
$articles = Article::with(['author', 'categories'])
|
||||
->published()
|
||||
->when($this->search, function (Builder $query) {
|
||||
$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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
60
app/Livewire/Home/Article/Show.php
Normal file
60
app/Livewire/Home/Article/Show.php
Normal 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
33
app/Livewire/Home/Faq.php
Normal 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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
@ -97,7 +97,8 @@ public function render(): View
|
||||
{
|
||||
return view('livewire.home.index', [
|
||||
'pageTitle' => 'Beranda',
|
||||
'categories' => Category::orderBy('sort_order')
|
||||
'categories' => Category::perfume()
|
||||
->orderBy('sort_order')
|
||||
->paginate(12)
|
||||
->through(function (Category $category) {
|
||||
$category->image = $category->getFirstMediaUrl('image')
|
||||
|
||||
@ -42,7 +42,7 @@ public function mount(): 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
|
||||
|
||||
@ -2,10 +2,16 @@
|
||||
|
||||
namespace App\Livewire\Home\Product;
|
||||
|
||||
use App\Models\Bottle;
|
||||
use App\Models\Category;
|
||||
use App\Models\Perfume;
|
||||
use App\Models\Product;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Attributes\Url;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithoutUrlPagination;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
#[Layout('components.layouts.home', [
|
||||
@ -13,54 +19,173 @@
|
||||
])]
|
||||
class Index extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
use WithoutUrlPagination, WithPagination;
|
||||
|
||||
public ?string $search = null;
|
||||
|
||||
public ?string $sort = 'latest';
|
||||
|
||||
#[Url(as: 'type')]
|
||||
public string $productType = 'perfume';
|
||||
|
||||
#[Url(as: 'categories')]
|
||||
public array $selectedCategories = [];
|
||||
|
||||
#[Url(as: 'price')]
|
||||
public ?string $priceRange = null;
|
||||
|
||||
public array $availableCategories = [];
|
||||
|
||||
public array $recommendations = [];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->availableCategories = Category::perfume()
|
||||
->orderBy('sort_order')
|
||||
->pluck('name', 'id')
|
||||
->toArray();
|
||||
|
||||
$this->recommendations = Perfume::with(['brand', 'categories', 'items'])
|
||||
->withCount('items')
|
||||
->orderBy('items_count', 'desc')
|
||||
->take(5)
|
||||
->take(8)
|
||||
->get()
|
||||
->map(function ($perfume) {
|
||||
return [
|
||||
'id' => $perfume->id,
|
||||
'name' => $perfume->name,
|
||||
'category' => $perfume->categories->first()?->name,
|
||||
'price' => $perfume->sale_price,
|
||||
'image' => $perfume->getFirstMediaUrl() ?: 'https://placehold.co/400x500/png?text='.urlencode($perfume->name),
|
||||
'rating' => 4.5,
|
||||
];
|
||||
->map(function (Perfume $perfume) {
|
||||
$perfume->image = $perfume->getFirstMediaUrl('image') ?: asset('assets/images/dark-logo.png');
|
||||
|
||||
return $perfume;
|
||||
})
|
||||
->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
|
||||
{
|
||||
$perfumes = Perfume::with(['brand', 'categories', 'items'])
|
||||
->latest()
|
||||
->paginate(9)
|
||||
->through(function ($perfume) {
|
||||
return [
|
||||
'id' => $perfume->id,
|
||||
'name' => $perfume->name,
|
||||
'category' => $perfume->categories->first()?->name,
|
||||
'price' => $perfume->sale_price,
|
||||
'image' => $perfume->getFirstMediaUrl() ?: 'https://placehold.co/400x500/png?text='.urlencode($perfume->name),
|
||||
'rating' => 4.5,
|
||||
'reviews' => $perfume->items->count(),
|
||||
'is_new' => $perfume->created_at->diffInDays(now()) <= 30,
|
||||
'colors' => ['#78350f', '#1f2937'],
|
||||
];
|
||||
// Determine which model to query based on productType
|
||||
$query = match ($this->productType) {
|
||||
'bottle' => Bottle::query(),
|
||||
'arabian' => Product::query(),
|
||||
default => Perfume::with(['brand', 'categories']), // 'perfume'
|
||||
};
|
||||
|
||||
// Load items count for badge calculation
|
||||
$query->withCount([
|
||||
'items as total_sales' => function ($q) {
|
||||
$q->whereHas('order', function ($orderQuery) {
|
||||
$orderQuery->where('created_at', '>=', now()->subMonth());
|
||||
});
|
||||
},
|
||||
]);
|
||||
|
||||
// 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', [
|
||||
'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;
|
||||
}
|
||||
}
|
||||
|
||||
89
app/Livewire/Home/Product/Show.php
Normal file
89
app/Livewire/Home/Product/Show.php
Normal 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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -5,10 +5,12 @@
|
||||
use App\Models\Outlet;
|
||||
use App\Models\Voucher as VoucherModel;
|
||||
use App\Traits\Components\WithToast;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithoutUrlPagination;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
#[Layout('components.layouts.home', [
|
||||
@ -16,11 +18,11 @@
|
||||
])]
|
||||
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;
|
||||
|
||||
@ -32,7 +34,7 @@ class Voucher extends Component
|
||||
|
||||
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
|
||||
@ -50,13 +52,10 @@ public function updatedSort(): void
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function showDetail(string $hashId): void
|
||||
public function showDetail(VoucherModel $voucher): void
|
||||
{
|
||||
$this->selectedVoucher = VoucherModel::byHash($hashId);
|
||||
$this->selectedVoucher->load('outlets');
|
||||
|
||||
$this->selectedVoucher->min_purchase_formatted = formatCurrencyNumber($this->selectedVoucher->min_purchase, 'Rp');
|
||||
$this->selectedVoucher->end_date_formatted = formatDateLocalized($this->selectedVoucher->end_date);
|
||||
$this->selectedVoucher = $voucher;
|
||||
$this->selectedVoucher->load(['outlets', 'tiers']);
|
||||
|
||||
$this->showDetailModal = true;
|
||||
}
|
||||
@ -67,80 +66,141 @@ public function closeDetail(): void
|
||||
$this->selectedVoucher = null;
|
||||
}
|
||||
|
||||
public function claim(string $hashId): void
|
||||
public function claim(VoucherModel $voucher): void
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
if (! auth()->check()) {
|
||||
$this->redirect(route('login'), navigate: true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$voucher = VoucherModel::byHash($hashId);
|
||||
$user = auth()->user();
|
||||
// 1. Check if voucher exists and is active
|
||||
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
|
||||
if (! $voucher) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($voucher->quota !== null && $voucher->available_count >= $voucher->quota) {
|
||||
if ($result === 'stock_empty') {
|
||||
$this->toast('Klaim gagal! Kuota voucher sudah habis.', 'Gagal', 'danger');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user already reached limit
|
||||
$claimedCount = $user->vouchers()->where('voucher_id', $voucher->id)->exists();
|
||||
if ($voucher->limit_per_user !== null) {
|
||||
$claimedCount = $user->vouchers()->where('voucher_id', $voucher->id)->count();
|
||||
if ($claimedCount >= $voucher->limit_per_user) {
|
||||
$this->toast('Anda sudah mencapai batas klaim untuk voucher ini.', 'Peringatan', 'warning');
|
||||
|
||||
return;
|
||||
}
|
||||
} elseif ($claimedCount) {
|
||||
// If limit_per_user is null, assume it can only be claimed once?
|
||||
// Or can it be claimed multiple times?
|
||||
// Usually vouchers are one-time claim per user unless specified.
|
||||
// Based on common logic, if limit_per_user is null, maybe it's unlimited?
|
||||
// But usually it's 1. Let's stick to the limit_per_user check.
|
||||
}
|
||||
|
||||
// Check if voucher is for specific tier
|
||||
$userTierId = $user->membership?->tier_id;
|
||||
if ($voucher->tiers()->exists() && ! $voucher->tiers()->where('tiers.id', $userTierId)->exists()) {
|
||||
$this->toast('Voucher ini tidak tersedia untuk tier Anda.', 'Gagal', 'danger');
|
||||
if ($result === 'limit_reached') {
|
||||
$this->toast('Anda sudah mencapai batas maksimal klaim voucher ini.', 'Peringatan', 'warning');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Claim with transaction
|
||||
DB::transaction(function () use ($user, $voucher) {
|
||||
// Re-check quota inside transaction
|
||||
$v = VoucherModel::where('id', $voucher->id)->lockForUpdate()->first();
|
||||
if ($v->quota !== null && $v->available_count >= $v->quota) {
|
||||
throw new \Exception('Kuota voucher sudah habis.');
|
||||
}
|
||||
|
||||
$user->vouchers()->attach($v->id);
|
||||
$v->increment('available_count');
|
||||
});
|
||||
|
||||
$this->toast('Voucher berhasil diklaim! Silakan cek di menu Voucher Saya.', 'Berhasil', 'success');
|
||||
} catch (\Throwable $e) {
|
||||
$this->toast($e->getMessage(), 'Gagal', 'danger');
|
||||
if ($result === 'success') {
|
||||
$this->toast(
|
||||
'Voucher berhasil diklaim! Silakan cek di menu Voucher di halaman dasbor.',
|
||||
'Berhasil',
|
||||
'success'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
$vouchers = VoucherModel::where(function ($query) {
|
||||
$query->where('name', 'like', '%'.$this->search.'%')
|
||||
->orWhere('code', 'like', '%'.$this->search.'%');
|
||||
})
|
||||
$vouchers = VoucherModel::query()
|
||||
->with(['tiers', 'outlets'])
|
||||
->active()
|
||||
->show()
|
||||
->where(function ($query) {
|
||||
$query->where('name', 'like', '%'.$this->search.'%')
|
||||
->orWhere('code', 'like', '%'.$this->search.'%');
|
||||
})
|
||||
->when($this->outlet_id, function ($query) {
|
||||
$query->whereHas('outlets', function ($q) {
|
||||
$q->where('outlets.id', $this->outlet_id);
|
||||
@ -150,21 +210,33 @@ public function render(): View
|
||||
match ($this->sort) {
|
||||
'latest' => $query->orderBy('created_at', 'desc'),
|
||||
'biggest' => $query->orderBy('discount_amount', 'desc'),
|
||||
'ending-soon' => $query->orderByRaw('end_date IS NULL')
|
||||
->orderBy('end_date', 'asc'),
|
||||
'ending-soon' => $query->orderByRaw('end_date IS NULL')->orderBy('end_date', 'asc'),
|
||||
default => $query->latest(),
|
||||
};
|
||||
})
|
||||
->active()
|
||||
->latest()
|
||||
->paginate(3)
|
||||
->through(function (VoucherModel $voucher) {
|
||||
$voucher->min_purchase_formatted = formatCurrencyNumber($voucher->min_purchase, 'Rp');
|
||||
$voucher->end_date = formatDateLocalized($voucher->end_date);
|
||||
}, fn ($query) => $query->latest())
|
||||
->paginate(6);
|
||||
|
||||
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', [
|
||||
'pageTitle' => 'Voucher',
|
||||
'pageDesc' => 'Nikmati berbagai voucher menarik untuk belanja lebih hemat dan menyenangkan.',
|
||||
'vouchers' => $vouchers,
|
||||
]);
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
namespace App\Models;
|
||||
|
||||
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\Model;
|
||||
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
|
||||
{
|
||||
return SlugOptions::create()
|
||||
|
||||
@ -24,6 +24,7 @@ protected function casts(): array
|
||||
return [
|
||||
'cost_price' => 'int',
|
||||
'sale_price' => 'int',
|
||||
'views' => 'int',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -27,6 +27,7 @@ protected function casts(): array
|
||||
'concentration' => Concentration::class,
|
||||
'cost_price' => 'int',
|
||||
'sale_price' => 'int',
|
||||
'views' => 'int',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ protected function casts(): array
|
||||
return [
|
||||
'cost_price' => 'int',
|
||||
'sale_price' => 'int',
|
||||
'views' => 'int',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
{
|
||||
return $this->belongsToMany(Outlet::class);
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
use App\Enums\PriceRequestStatus;
|
||||
use App\Models\PriceRequest;
|
||||
use App\Settings\ContactSettings;
|
||||
use App\Settings\GeneralSettings;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
@ -327,7 +328,8 @@ public function boot(): void
|
||||
$view->with([
|
||||
'sidebar' => $sidebar,
|
||||
'header' => $header,
|
||||
'setting' => app(ContactSettings::class),
|
||||
'contactSettings' => app(ContactSettings::class),
|
||||
'generalSettings' => app(GeneralSettings::class),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
@ -24,8 +24,8 @@ public function up(): void
|
||||
$table->unsignedInteger('min_purchase')->nullable();
|
||||
$table->unsignedInteger('max_discount')->nullable();
|
||||
$table->unsignedInteger('quota')->nullable();
|
||||
$table->unsignedInteger('available_count')->default(0);
|
||||
$table->unsignedInteger('limit_per_user')->nullable();
|
||||
$table->unsignedInteger('available_count')->nullable();
|
||||
$table->unsignedInteger('limit_per_user')->default(1);
|
||||
$table->date('start_date');
|
||||
$table->date('end_date')->nullable();
|
||||
$table->enum('is_show', IsShow::values())->default(IsShow::SHOW)->comment(IsShow::comment());
|
||||
|
||||
@ -17,7 +17,7 @@ public function up(): void
|
||||
$table->string('name', 20);
|
||||
$table->string('slug', 30);
|
||||
$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->timestamp('created_at')->useCurrent();
|
||||
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
|
||||
|
||||
@ -25,6 +25,7 @@ public function up(): void
|
||||
$table->string('middle_notes')->nullable();
|
||||
$table->string('top_notes')->nullable();
|
||||
$table->text('description')->nullable();
|
||||
$table->unsignedInteger('views')->default(0);
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
|
||||
$table->softDeletes();
|
||||
|
||||
@ -19,6 +19,7 @@ public function up(): void
|
||||
$table->unsignedInteger('cost_price')->default(0);
|
||||
$table->unsignedInteger('sale_price')->default(0);
|
||||
$table->text('description')->nullable();
|
||||
$table->unsignedInteger('views')->default(0);
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
|
||||
$table->softDeletes();
|
||||
|
||||
@ -19,6 +19,7 @@ public function up(): void
|
||||
$table->unsignedInteger('cost_price')->default(0);
|
||||
$table->unsignedInteger('sale_price')->default(0);
|
||||
$table->text('description')->nullable();
|
||||
$table->unsignedInteger('views')->default(0);
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
|
||||
$table->softDeletes();
|
||||
|
||||
130
docs/PRODUCT_BADGE_SYSTEM.md
Normal file
130
docs/PRODUCT_BADGE_SYSTEM.md
Normal 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
|
||||
1
public/assets/animations/boxing-cat.json
Normal file
1
public/assets/animations/boxing-cat.json
Normal file
File diff suppressed because one or more lines are too long
@ -1,8 +1,8 @@
|
||||
<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;">
|
||||
</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.' !!}
|
||||
</flux:text>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -20,12 +20,11 @@
|
||||
@endpersist
|
||||
|
||||
{{ $slot }}
|
||||
|
||||
@include('components.sections.ui.home._footer')
|
||||
|
||||
@fluxScripts
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@ -9,9 +9,8 @@
|
||||
class="w-10 h-10 object-contain brightness-0 invert">
|
||||
<h2 class="text-2xl font-bold tracking-wide text-white">Yadi Parfum</h2>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm leading-relaxed">
|
||||
Menyediakan berbagai macam varian parfum berkualitas tinggi dengan wangi yang tahan lama dan harga
|
||||
terjangkau.
|
||||
<p class="text-gray-400 text-sm leading-relaxed text-justify">
|
||||
{{ $generalSettings->site_description }}
|
||||
</p>
|
||||
<div class="flex items-center gap-4 mt-2">
|
||||
<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"
|
||||
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
</svg></div>
|
||||
<span class="leading-relaxed">{{ $setting->address }}</span>
|
||||
<span class="leading-relaxed">{{ $contactSettings->address }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<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">
|
||||
</path>
|
||||
</svg></div>
|
||||
<span>{{ $setting->email }}</span>
|
||||
<span>{{ $contactSettings->email }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<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">
|
||||
</path>
|
||||
</svg></div>
|
||||
<span>{{ $setting->phone_number }}</span>
|
||||
<span>{{ $contactSettings->phone_number }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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">
|
||||
@foreach ($header as $item)
|
||||
<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>
|
||||
{{ $item['title'] }}
|
||||
</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';
|
||||
@endphp
|
||||
|
||||
<a href="{{ route('homepage') }}" wire:navigate @click="isOpen = false"
|
||||
class="{{ $baseClass }} {{ request()->routeIs('homepage') ? $activeClass : $inactiveClass }}">
|
||||
Beranda
|
||||
</a>
|
||||
|
||||
<a href="{{ route('outlet') }}" wire:navigate @click="isOpen = false"
|
||||
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>
|
||||
@foreach ($header as $item)
|
||||
<a href="{{ route($item['route']) }}" wire:navigate @click="isOpen = false"
|
||||
class="{{ $baseClass }} {{ request()->routeIs($item['match']) ? $activeClass : $inactiveClass }}">
|
||||
{{ $item['title'] }}
|
||||
</a>
|
||||
@endforeach
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
@ -10,14 +10,15 @@ class="w-full h-full object-cover">
|
||||
|
||||
{{-- 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>
|
||||
<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">
|
||||
Baca tips profesional seputar perawatan wewangian, tren lifestyle, dan rekomendasi terbaik.
|
||||
{{ $pageDesc }}
|
||||
</p>
|
||||
|
||||
{{-- Search Bar --}}
|
||||
<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">
|
||||
<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">
|
||||
@ -30,20 +31,17 @@ class="absolute right-2 top-2 bottom-2 w-10 h-10 bg-home-primary rounded-full fl
|
||||
</div>
|
||||
</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">
|
||||
|
||||
<section class="lg:col-span-3">
|
||||
|
||||
{{-- Toolbar (Sort Only) --}}
|
||||
<div class="flex justify-start mb-10">
|
||||
<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">
|
||||
<option value="">Urutkan</option>
|
||||
<option value="latest">Terbaru</option>
|
||||
<option value="name">Nama</option>
|
||||
<option value="Alphabetic">Alfabet</option>
|
||||
<option value="views">Terpopuler</option>
|
||||
</select>
|
||||
<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 class="space-y-8">
|
||||
|
||||
<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=Tips+Parfum" alt="Tips Memilih Parfum"
|
||||
loading="lazy"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500">
|
||||
<div
|
||||
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>
|
||||
@forelse($articles as $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="{{ $article->thumbnail }}" alt="{{ $article->title }}" loading="lazy"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500">
|
||||
<div
|
||||
class="absolute inset-0 bg-home-primary/10 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||
</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 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">
|
||||
<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">Siti Rahma</p>
|
||||
<p class="text-xs text-home-foreground/50">5 Nov 2024</p>
|
||||
@if ($article->categories->count() > 3)
|
||||
<span
|
||||
class="text-xs font-bold text-home-foreground/50 px-3 py-1 rounded-full border border-home-foreground/10">
|
||||
+{{ $article->categories->count() - 3 }}
|
||||
</span>
|
||||
@endif
|
||||
|
||||
<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
|
||||
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>
|
||||
</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=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>
|
||||
class="flex items-center justify-between pt-6 border-t border-home-foreground/5">
|
||||
<div class="flex items-center gap-3">
|
||||
<img src="https://ui-avatars.com/api/?name={{ urlencode($article->author?->employee?->full_name) }}&background=random&color=fff"
|
||||
alt="{{ $article->author?->employee?->full_name }}"
|
||||
class="w-9 h-9 rounded-full ring-2 ring-white shadow-sm">
|
||||
<div>
|
||||
<p class="text-sm font-bold text-home-foreground">
|
||||
{{ $article->author?->employee?->full_name }}</p>
|
||||
<p class="text-xs text-home-foreground/50">
|
||||
{{ formatDateLocalized($article->published_at) }}
|
||||
</p>
|
||||
</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">
|
||||
<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>
|
||||
</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">
|
||||
<a href="{{ route('article.show', $article->slug) }}" wire:navigate
|
||||
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="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>
|
||||
7 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">
|
||||
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>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
</article>
|
||||
@empty
|
||||
@include('components.animations.lottie.not-found')
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<nav class="flex items-center justify-center mt-16" aria-label="Pagination">
|
||||
<div
|
||||
class="flex items-center gap-3 bg-white p-2 rounded-full shadow-sm border border-home-foreground/5">
|
||||
<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>
|
||||
<div class="mt-16">
|
||||
{{ $articles->links('livewire::custom-pagination') }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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">
|
||||
Kategori</h3>
|
||||
<div class="space-y-1">
|
||||
<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">
|
||||
Perawatan Parfum
|
||||
<button wire:click="setCategory(null)"
|
||||
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' }}">
|
||||
Semua Artikel
|
||||
<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">
|
||||
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>
|
||||
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>
|
||||
@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>
|
||||
</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">
|
||||
Populer</h3>
|
||||
<div class="space-y-5">
|
||||
<a href="#" class="block group">
|
||||
<p
|
||||
class="text-sm font-bold text-home-foreground group-hover:text-home-primary transition-colors line-clamp-2 leading-snug">
|
||||
Tips Memilih Parfum yang Tepat Sesuai Kepribadian
|
||||
</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>
|
||||
2.5K views
|
||||
</p>
|
||||
</a>
|
||||
<div class="border-t border-home-foreground/5"></div>
|
||||
<a href="#" class="block group">
|
||||
<p
|
||||
class="text-sm font-bold text-home-foreground group-hover:text-home-primary transition-colors line-clamp-2 leading-snug">
|
||||
Panduan Lengkap Cara Menyimpan Parfum
|
||||
</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>
|
||||
@foreach ($popularArticles as $popular)
|
||||
<a href="{{ route('article.show', $popular['slug']) }}" class="block group">
|
||||
<p
|
||||
class="text-sm font-bold text-home-foreground group-hover:text-home-primary transition-colors line-clamp-2 leading-snug">
|
||||
{{ $popular['title'] }}
|
||||
</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>
|
||||
{{ $popular['views'] }}x dilihat
|
||||
</p>
|
||||
</a>
|
||||
<div class="border-t border-home-foreground/5"></div>
|
||||
@endforeach
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section
|
||||
{{-- <section
|
||||
class="bg-home-primary rounded-2xl p-8 text-center relative overflow-hidden group shadow-lg shadow-home-primary/20">
|
||||
<div
|
||||
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>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</section> --}}
|
||||
|
||||
</div>
|
||||
</aside>
|
||||
@ -368,3 +228,7 @@ class="w-full bg-home-secondary text-home-primary font-bold py-3 rounded-xl hove
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@assets
|
||||
<script src="https://unpkg.com/@lottiefiles/dotlottie-wc@0.8.5/dist/dotlottie-wc.js" type="module"></script>
|
||||
@endassets
|
||||
|
||||
@ -1,201 +1,167 @@
|
||||
@php
|
||||
$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="flex flex-col 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"
|
||||
style="width: 0%"></div>
|
||||
|
||||
<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">
|
||||
<div class="flex items-center justify-center gap-3 mb-6 text-sm font-bold tracking-widest uppercase">
|
||||
<span class="text-home-primary bg-home-primary/10 px-3 py-1 rounded-full">{{ $article->category }}</span>
|
||||
<span class="text-home-foreground/40">•</span>
|
||||
<span class="text-home-foreground/60">{{ $article->date }}</span>
|
||||
<header class="px-6 lg:px-32 xl:px-48 mb-12 text-center mx-auto">
|
||||
<div
|
||||
class="flex flex-wrap items-center justify-center gap-3 mb-6 text-sm font-bold tracking-widest uppercase">
|
||||
@foreach ($article->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>
|
||||
|
||||
<h1 class="text-3xl md:text-5xl font-bold text-home-foreground mb-6 leading-tight">
|
||||
{{ $article->title }}
|
||||
</h1>
|
||||
<div class="flex items-center justify-center gap-4 text-sm text-home-foreground/60">
|
||||
<div class="flex items-center gap-2">
|
||||
<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"
|
||||
class="w-full h-full object-cover">
|
||||
<img src="https://ui-avatars.com/api/?name={{ urlencode($article->author?->employee?->full_name) }}"
|
||||
alt="Author" class="w-full h-full object-cover">
|
||||
</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>
|
||||
<span>•</span>
|
||||
<span>{{ $article->read_time }}</span>
|
||||
<span>{{ formatDateLocalized($article->published_at) }}</span>
|
||||
<span>•</span>
|
||||
<span>{{ $article->views }}x dilihat</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="px-6 lg:px-32 xl:px-48 mb-16">
|
||||
<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 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">
|
||||
<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>
|
||||
<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
|
||||
class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
|
||||
{{-- WhatsApp --}}
|
||||
<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
|
||||
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" />
|
||||
</svg></button>
|
||||
<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
|
||||
class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
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 transition-colors">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
<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" />
|
||||
</svg></button>
|
||||
<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
|
||||
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.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z">
|
||||
</path>
|
||||
</svg></button>
|
||||
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($article->title) }}&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 transition-colors">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="prose prose-lg max-w-none text-home-foreground/80 leading-relaxed">
|
||||
<p
|
||||
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">
|
||||
{{ $article->content_intro }}
|
||||
</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
|
||||
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 !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-6 mt-12">
|
||||
<a href="#"
|
||||
class="group block p-6 border border-home-foreground/10 rounded-2xl hover:border-home-primary/30 transition-all">
|
||||
<span
|
||||
class="text-xs text-home-foreground/40 font-bold uppercase mb-1 block group-hover:text-home-primary">Sebelumnya</span>
|
||||
<h4
|
||||
class="font-bold text-home-foreground line-clamp-2 group-hover:text-home-primary transition-colors">
|
||||
Review Jujur: Koleksi Floral Series 2024</h4>
|
||||
</a>
|
||||
<a href="#"
|
||||
class="group block p-6 border border-home-foreground/10 rounded-2xl text-right hover:border-home-primary/30 transition-all">
|
||||
<span
|
||||
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">
|
||||
Tips Menyimpan Parfum Agar Awet Bertahun-tahun</h4>
|
||||
</a>
|
||||
@if ($previousArticle)
|
||||
<a href="{{ route('article.show', $previousArticle->slug) }}" wire:navigate
|
||||
class="group block p-6 border border-home-foreground/10 rounded-2xl hover:border-home-primary/30 transition-all">
|
||||
<span
|
||||
class="text-xs text-home-foreground/40 font-bold uppercase mb-1 block group-hover:text-home-primary">Sebelumnya</span>
|
||||
<h4
|
||||
class="font-bold text-home-foreground line-clamp-2 group-hover:text-home-primary transition-colors">
|
||||
{{ $previousArticle->title }}
|
||||
</h4>
|
||||
</a>
|
||||
@endif
|
||||
@if ($nextArticle)
|
||||
<a href="{{ route('article.show', $nextArticle->slug) }}" wire:navigate
|
||||
class="group block p-6 border border-home-foreground/10 rounded-2xl text-right hover:border-home-primary/30 transition-all">
|
||||
<span
|
||||
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>
|
||||
</article>
|
||||
|
||||
<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>
|
||||
<h3 class="text-lg font-bold text-home-primary mb-6 flex items-center gap-2">
|
||||
<span class="w-1 h-6 bg-home-primary rounded-full"></span>
|
||||
Populer Minggu Ini
|
||||
</h3>
|
||||
<div class="space-y-6">
|
||||
@foreach ($relatedArticles as $index => $item)
|
||||
<a href="#" class="flex gap-4 group">
|
||||
<div class="w-20 h-20 flex-shrink-0 rounded-xl overflow-hidden">
|
||||
<img src="{{ $item['image'] }}"
|
||||
class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500">
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
class="text-xs text-home-foreground/40 font-bold">{{ sprintf('%02d', $index + 1) }}</span>
|
||||
<h4
|
||||
class="font-bold text-home-foreground text-sm leading-snug group-hover:text-home-primary transition-colors mt-1">
|
||||
{{ $item['title'] }}</h4>
|
||||
<span
|
||||
class="text-xs text-home-foreground/50 mt-2 block">{{ $item['date'] }}</span>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
<div x-data="{
|
||||
progress: 0,
|
||||
updateProgress() {
|
||||
const winScroll = window.pageYOffset || document.documentElement.scrollTop
|
||||
const height = document.documentElement.scrollHeight - document.documentElement.clientHeight
|
||||
this.progress = height > 0 ? (winScroll / height) * 100 : 0
|
||||
}
|
||||
}" x-init="updateProgress();
|
||||
window.addEventListener('scroll', updateProgress)">
|
||||
<!-- progress bar -->
|
||||
<div class="fixed top-0 left-0 w-full h-1 bg-transparent z-50">
|
||||
<div class="h-full bg-home-primary transition-all duration-75"
|
||||
:style="`width: ${progress}%`" id="progressBar"></div>
|
||||
</div>
|
||||
|
||||
<!-- konten kamu -->
|
||||
<div>
|
||||
<h3 class="text-lg font-bold text-home-primary mb-6 flex items-center gap-2">
|
||||
<span class="w-1 h-6 bg-home-primary rounded-full"></span>
|
||||
Artikel Terbaru
|
||||
</h3>
|
||||
<div class="space-y-6">
|
||||
@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>
|
||||
|
||||
<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">
|
||||
<div
|
||||
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>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</section> --}}
|
||||
|
||||
</div>
|
||||
</aside>
|
||||
@ -231,11 +197,21 @@ class="w-full bg-home-secondary text-home-primary font-bold py-3 rounded-xl hove
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.onscroll = function() {
|
||||
let winScroll = document.body.scrollTop || document.documentElement.scrollTop;
|
||||
let height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
|
||||
let scrolled = (winScroll / height) * 100;
|
||||
document.getElementById("progressBar").style.width = scrolled + "%";
|
||||
};
|
||||
</script>
|
||||
@script
|
||||
<script>
|
||||
window.onscroll = function() {
|
||||
let winScroll = document.body.scrollTop || document.documentElement.scrollTop;
|
||||
let height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
|
||||
let scrolled = (winScroll / height) * 100;
|
||||
document.getElementById("progressBar").style.width = scrolled + "%";
|
||||
};
|
||||
</script>
|
||||
@endscript
|
||||
|
||||
@assets
|
||||
<style>
|
||||
.prose p:empty {
|
||||
min-height: 1.25rem;
|
||||
}
|
||||
</style>
|
||||
@endassets
|
||||
|
||||
@ -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>
|
||||
112
resources/views/livewire/home/faqs.blade.php
Normal file
112
resources/views/livewire/home/faqs.blade.php
Normal 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
|
||||
@ -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-start="opacity-100 scale-100 translate-y-0"
|
||||
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;">
|
||||
|
||||
<div
|
||||
@ -107,7 +107,7 @@ class="px-4 py-2 text-[10px] font-bold text-home-foreground/40 uppercase trackin
|
||||
</div>
|
||||
|
||||
{{-- 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"
|
||||
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">
|
||||
|
||||
@ -1,38 +1,24 @@
|
||||
<div class="flex flex-col min-h-screen bg-white font-sans">
|
||||
|
||||
<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="Perfume Collection Background" class="w-full h-full object-cover">
|
||||
<div class="absolute inset-0 bg-black/50"></div>
|
||||
<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">
|
||||
@if ($productType === 'perfume')
|
||||
Koleksi Parfum
|
||||
@elseif($productType === 'bottle')
|
||||
Koleksi Botol
|
||||
@else
|
||||
Koleksi Arabian
|
||||
@endif
|
||||
</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">
|
||||
@if ($productType === 'perfume')
|
||||
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
|
||||
{{ $pageDesc }}
|
||||
</p>
|
||||
|
||||
{{-- Search Bar --}}
|
||||
<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">
|
||||
<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">
|
||||
@ -45,20 +31,20 @@ class="absolute right-2 top-2 bottom-2 w-10 h-10 bg-home-primary rounded-full fl
|
||||
</div>
|
||||
</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">
|
||||
|
||||
<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">
|
||||
{{-- 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>
|
||||
<div class="space-y-4">
|
||||
@foreach ([['value' => 'perfume', 'label' => 'Parfum'], ['value' => 'bottle', 'label' => 'Botol'], ['value' => 'arabian', 'label' => 'Arabian']] as $type)
|
||||
<label class="flex items-center cursor-pointer group">
|
||||
<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">
|
||||
<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>
|
||||
|
||||
<h3 class="font-bold text-home-primary mb-6 border-b border-home-foreground/10 pb-4">Kategori</h3>
|
||||
<div class="space-y-4">
|
||||
@foreach (['Pria', 'Wanita', 'Unisex', 'Luxury', 'Best Seller', 'New Arrival'] as $cat)
|
||||
<label class="flex items-center cursor-pointer group">
|
||||
<input type="checkbox"
|
||||
class="w-5 h-5 rounded border-home-foreground/20 text-home-primary focus:ring-home-primary cursor-pointer accent-home-primary">
|
||||
<span
|
||||
class="ml-3 text-home-foreground/70 group-hover:text-home-primary transition-colors text-sm font-medium">{{ $cat }}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
@if ($productType === 'perfume')
|
||||
<h3 class="font-bold text-home-primary mb-6 border-b border-home-foreground/10 pb-4">Kategori
|
||||
</h3>
|
||||
<div class="space-y-4">
|
||||
@foreach ($availableCategories as $id => $name)
|
||||
<label class="flex items-center cursor-pointer group">
|
||||
<input type="checkbox" value="{{ $name }}"
|
||||
wire:model.live="selectedCategories"
|
||||
class="w-5 h-5 rounded border-home-foreground/20 text-home-primary focus:ring-home-primary cursor-pointer accent-home-primary">
|
||||
<span
|
||||
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>
|
||||
<div class="space-y-4">
|
||||
@foreach (['< 100k', '100k - 500k', '500k - 1jt', '> 1jt'] as $price)
|
||||
<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">
|
||||
<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) --}}
|
||||
<div class="flex justify-end mb-8">
|
||||
<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">
|
||||
<option>Urutkan: Terbaru</option>
|
||||
<option>Harga Terendah</option>
|
||||
<option>Harga Tertinggi</option>
|
||||
<option>Terpopuler</option>
|
||||
<option value="latest">Urutkan: Terbaru</option>
|
||||
<option value="price_low">Harga Terendah</option>
|
||||
<option value="price_high">Harga Tertinggi</option>
|
||||
<option value="popular">Terpopuler</option>
|
||||
</select>
|
||||
<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">
|
||||
@ -116,85 +107,113 @@ class="w-full appearance-none px-6 py-3 pr-10 rounded-xl border border-home-fore
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
@foreach ($perfumes as $perfume)
|
||||
<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 ($products->isNotEmpty())
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
@foreach ($products as $product)
|
||||
<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'])
|
||||
<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">
|
||||
@if (isset($product->badge) && $product->badge)
|
||||
<span
|
||||
class="text-[10px] font-bold tracking-widest text-home-primary uppercase bg-home-primary/10 px-2 py-1 rounded">{{ $perfume['category'] }}</span>
|
||||
<div class="flex items-center gap-1">
|
||||
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
|
||||
|
||||
<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">
|
||||
<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="text-xs font-bold text-home-foreground">{{ $perfume['rating'] }}</span>
|
||||
class="text-xs font-bold text-home-foreground">{{ $product['rating'] }}</span>
|
||||
<span
|
||||
class="text-xs text-home-foreground/40">({{ $perfume['reviews'] }})</span>
|
||||
</div>
|
||||
</div>
|
||||
class="text-xs text-home-foreground/40">({{ $product['reviews'] }})</span>
|
||||
</div> --}}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<h3
|
||||
class="text-lg font-bold text-home-foreground group-hover:text-home-primary transition-colors mb-4 line-clamp-1">
|
||||
{{ $perfume['name'] }}</h3>
|
||||
<h3
|
||||
class="text-lg font-bold text-home-foreground group-hover:text-home-primary transition-colors my-4 line-clamp-1">
|
||||
<a
|
||||
href="{{ route('product.show', $product['slug']) }}">{{ $product['name'] }}</a>
|
||||
</h3>
|
||||
|
||||
<div
|
||||
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
|
||||
{{ number_format($perfume['price'], 0, ',', '.') }}</span>
|
||||
<button
|
||||
<div
|
||||
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
|
||||
{{ formatCurrencyNumber($product['sale_price']) }}</span>
|
||||
|
||||
{{-- 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">
|
||||
<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 4v16m8-8H4"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</button> --}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@endforeach
|
||||
</div>
|
||||
</article>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
@include('components.animations.lottie.not-found')
|
||||
@endif
|
||||
|
||||
<div class="mt-16 flex justify-center">
|
||||
{{ $perfumes->links('vendor.livewire.custom-pagination') }}
|
||||
<div class="mt-16">
|
||||
{{ $products->links('vendor.livewire.custom-pagination') }}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@ -227,23 +246,41 @@ class="flex gap-6 overflow-x-auto pb-12 snap-x snap-mandatory scroll-smooth hide
|
||||
<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'] }}"
|
||||
<img src="{{ $rec['image'] }}" alt="{{ $rec['image'] }}"
|
||||
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="flex flex-wrap gap-2 mb-2">
|
||||
@foreach (array_slice($rec['categories'], 0, 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 (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">
|
||||
<span class="font-bold text-home-primary">Rp
|
||||
{{ number_format($rec['price'], 0, ',', '.') }}</span>
|
||||
<button
|
||||
{{ formatCurrencyNumber($rec['sale_price']) }}</span>
|
||||
|
||||
{{-- 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">
|
||||
<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>
|
||||
</button> --}}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@ -254,26 +291,6 @@ class="w-8 h-8 rounded bg-home-foreground/5 text-home-foreground hover:bg-home-p
|
||||
</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>
|
||||
@assets
|
||||
<script src="https://unpkg.com/@lottiefiles/dotlottie-wc@0.8.5/dist/dotlottie-wc.js" type="module"></script>
|
||||
@endassets
|
||||
|
||||
@ -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>
|
||||
226
resources/views/livewire/home/product/show.blade.php
Normal file
226
resources/views/livewire/home/product/show.blade.php
Normal 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>
|
||||
@ -1,24 +1,24 @@
|
||||
<div class="flex flex-col min-h-screen bg-white">
|
||||
<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/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">
|
||||
<div class="absolute inset-0 bg-black/50"></div>
|
||||
<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">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">
|
||||
Nikmati potongan harga spesial untuk koleksi parfum favorit Anda.
|
||||
{{ $pageDesc }}
|
||||
</p>
|
||||
|
||||
{{-- Search Bar --}}
|
||||
<div class="max-w-xl mx-auto relative group">
|
||||
<input type="text" placeholder="Cari kode voucher..." aria-label="Cari voucher"
|
||||
wire:model.live.debounce.500ms="search"
|
||||
<input wire:model.live.debounce.500="search" type="search" placeholder="Cari voucher..."
|
||||
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">
|
||||
@ -31,10 +31,8 @@ class="absolute right-2 top-2 bottom-2 w-10 h-10 bg-home-primary rounded-full fl
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<main class="flex-grow px-6 lg:px-32 xl:px-48 py-24 font-sans flex flex-col">
|
||||
|
||||
<section class="flex flex-col lg:flex-row justify-center gap-4 mb-12" aria-label="Filter">
|
||||
|
||||
<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">
|
||||
<div class="flex flex-col sm:flex-row gap-4">
|
||||
<div class="relative">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-12">
|
||||
@foreach ($vouchers as $voucher)
|
||||
<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">
|
||||
@if ($vouchers->isNotEmpty())
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-12">
|
||||
@foreach ($vouchers as $voucher)
|
||||
<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="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 class="bg-home-primary/5 p-6 relative overflow-hidden flex-1">
|
||||
<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">
|
||||
<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>
|
||||
class="absolute top-0 right-0 w-24 h-24 bg-home-primary/10 rounded-full blur-2xl -mr-10 -mt-10">
|
||||
</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 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 class="relative z-10">
|
||||
<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">
|
||||
<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>
|
||||
</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 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">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2-1.343-2-3-2zm0 4c-2.485 0-4.5 1.567-4.5 3.5V18h9v-2.5c0-1.933-2.015-3.5-4.5-3.5z">
|
||||
</path>
|
||||
</svg>
|
||||
Min. Pembelian
|
||||
</span>
|
||||
<span class="font-bold text-home-foreground">{{ $voucher->min_purchase_formatted }}</span>
|
||||
<div class="p-6 flex gap-3 mt-auto">
|
||||
<button wire:click="claim('{{ $voucher->hash }}')"
|
||||
class="flex-1 bg-home-primary text-white font-bold py-3 rounded-xl hover:bg-home-secondary hover:text-home-primary hover:shadow-lg hover:shadow-home-primary/20 transition-all duration-300 text-sm shadow-md shadow-home-primary/10">
|
||||
Klaim Voucher
|
||||
</button>
|
||||
<button wire:click="showDetail('{{ $voucher->hash }}')"
|
||||
class="flex-1 border border-home-foreground/20 text-home-foreground font-bold py-3 rounded-xl hover:border-home-primary hover:text-home-primary hover:bg-home-primary/5 transition-all duration-300 text-sm">
|
||||
Lihat Detail
|
||||
</button>
|
||||
</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="{{ $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>
|
||||
</article>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="p-6 flex gap-3 mt-auto">
|
||||
<button wire:click="claim('{{ $voucher->hash }}')"
|
||||
class="flex-1 bg-home-primary text-white font-bold py-3 rounded-xl hover:bg-home-secondary hover:text-home-primary hover:shadow-lg hover:shadow-home-primary/20 transition-all duration-300 text-sm shadow-md shadow-home-primary/10">
|
||||
Klaim Voucher
|
||||
</button>
|
||||
<button wire:click="showDetail('{{ $voucher->hash }}')"
|
||||
class="flex-1 border border-home-foreground/20 text-home-foreground font-bold py-3 rounded-xl hover:border-home-primary hover:text-home-primary hover:bg-home-primary/5 transition-all duration-300 text-sm">
|
||||
Lihat Detail
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
@endforeach
|
||||
</section>
|
||||
|
||||
<div class="mt-auto flex justify-center w-full">
|
||||
{{ $vouchers->links('vendor.livewire.custom-pagination') }}
|
||||
</div>
|
||||
<div class="flex justify-center w-full">
|
||||
{{ $vouchers->links('vendor.livewire.custom-pagination') }}
|
||||
</div>
|
||||
@else
|
||||
@include('components.animations.lottie.not-found')
|
||||
@endif
|
||||
</main>
|
||||
|
||||
{{-- Voucher Detail Modal --}}
|
||||
@if ($showDetailModal && $selectedVoucher)
|
||||
<div class="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
{{-- Backdrop --}}
|
||||
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm" wire:click="closeDetail"></div>
|
||||
|
||||
{{-- Modal Content --}}
|
||||
<div
|
||||
class="relative bg-white w-full max-w-2xl rounded-3xl overflow-hidden shadow-2xl animate-in fade-in zoom-in duration-300">
|
||||
{{-- Modal Header --}}
|
||||
<div class="relative h-48 bg-home-primary flex items-center justify-center overflow-hidden">
|
||||
<div class="absolute inset-0 opacity-20">
|
||||
<div class="absolute top-0 right-0 w-64 h-64 bg-white rounded-full -mr-32 -mt-32"></div>
|
||||
@ -203,7 +223,6 @@ class="absolute top-6 right-6 w-10 h-10 rounded-full bg-white/10 hover:bg-white/
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- Modal Body --}}
|
||||
<div class="p-8 max-h-[60vh] overflow-y-auto custom-scrollbar">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8">
|
||||
<div>
|
||||
@ -224,7 +243,7 @@ class="w-8 h-8 rounded-lg bg-home-primary/5 flex items-center justify-center tex
|
||||
<div>
|
||||
<p class="text-sm font-medium text-home-foreground/50">Minimum Pembelian</p>
|
||||
<p class="font-bold text-home-foreground">
|
||||
{{ $selectedVoucher->min_purchase_formatted }}</p>
|
||||
{{ formatCurrencyNumber($selectedVoucher->min_purchase, 'Rp') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<p class="text-sm font-medium text-home-foreground/50">Masa Berlaku</p>
|
||||
<p class="font-bold text-home-foreground">Hingga
|
||||
{{ $selectedVoucher->end_date_formatted }}</p>
|
||||
{{ formatDateLocalized($selectedVoucher->end_date) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@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>
|
||||
<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
|
||||
</h4>
|
||||
<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
|
||||
</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 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>
|
||||
|
||||
{{-- Modal Footer --}}
|
||||
<div class="p-8 bg-white border-t border-home-foreground/5 flex gap-4">
|
||||
<button wire:click="closeDetail"
|
||||
class="flex-1 py-4 px-6 border border-home-foreground/20 text-home-foreground font-bold rounded-2xl hover:bg-home-foreground/5 transition-all">
|
||||
Tutup
|
||||
</button>
|
||||
<button wire:click="claim('{{ $selectedVoucher->hashId }}')"
|
||||
<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">
|
||||
Klaim Sekarang
|
||||
</button>
|
||||
@ -317,3 +357,7 @@ class="flex-[2] py-4 px-6 bg-home-primary text-white font-bold rounded-2xl hover
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@assets
|
||||
<script src="https://unpkg.com/@lottiefiles/dotlottie-wc@0.8.5/dist/dotlottie-wc.js" type="module"></script>
|
||||
@endassets
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
@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">
|
||||
Ekspor Ecxel
|
||||
</flux:button>
|
||||
|
||||
@ -48,7 +48,7 @@
|
||||
<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"
|
||||
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:field>
|
||||
|
||||
|
||||
@ -54,9 +54,14 @@
|
||||
<flux:input label="Kuota" placeholder="100" wire:model="form.quota"
|
||||
x-mask:dynamic="$money($input, ',')" autocomplete="off" />
|
||||
|
||||
<flux:input label="Batas Per Customer" placeholder="1"
|
||||
wire:model="form.limit_per_user" x-mask:dynamic="$money($input, ',')"
|
||||
autocomplete="off" />
|
||||
<flux:field>
|
||||
<flux:label>Batas Per Customer <span class="text-red-500 ms-1">*</span>
|
||||
</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>
|
||||
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\ProfileRedirectController;
|
||||
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\Faq\Index as FaqComponent;
|
||||
use App\Livewire\Home\Faq as FaqComponent;
|
||||
use App\Livewire\Home\Index as HomeComponent;
|
||||
use App\Livewire\Home\Outlet as OutletComponent;
|
||||
use App\Livewire\Home\Product\Index as ProductIndex;
|
||||
use App\Livewire\Home\Product\Show as ProductShow;
|
||||
use App\Livewire\Home\Voucher as VoucherComponent;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
@ -13,19 +16,24 @@
|
||||
|
||||
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::prefix('articles')
|
||||
->name('article.')
|
||||
->group(function () {
|
||||
Route::get('{slug?}', ArticleIndex::class)
|
||||
->name('index');
|
||||
Route::get('/', ArticleIndex::class)->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('/profile/redirect', \App\Http\Controllers\ProfileRedirectController::class)->name('profile.redirect');
|
||||
Route::get('/profile/redirect', ProfileRedirectController::class)->name('profile.redirect');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user