feat: add logo display in AppSidebar and improve layout in Home.vue for better user experience
This commit is contained in:
parent
9df8fe1ce2
commit
6aa818e562
@ -92,7 +92,13 @@ const menuGroups: MenuGroup[] = [
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton size="lg" as-child>
|
||||
<Link href="/admin/dashboard">
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<img
|
||||
v-if="page.props.logo_url"
|
||||
:src="page.props.logo_url"
|
||||
:alt="page.props.name"
|
||||
class="h-8 w-auto object-contain"
|
||||
/>
|
||||
<div v-else class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-semibold">{{ page.props.name }}</span>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { Head } from '@inertiajs/vue3';
|
||||
import {
|
||||
Search,
|
||||
ShoppingBag,
|
||||
Sun,
|
||||
Moon,
|
||||
X,
|
||||
Plus,
|
||||
Minus,
|
||||
ArrowRight,
|
||||
Star,
|
||||
Check,
|
||||
Info,
|
||||
import {
|
||||
Search,
|
||||
ShoppingBag,
|
||||
Sun,
|
||||
Moon,
|
||||
X,
|
||||
Plus,
|
||||
Minus,
|
||||
ArrowRight,
|
||||
Star,
|
||||
Check,
|
||||
Info,
|
||||
ChevronRight,
|
||||
Eye,
|
||||
Phone,
|
||||
@ -107,7 +107,7 @@ const toggleCategory = (slug: string) => {
|
||||
|
||||
// Helper to get ecer price of a product for filtering/sorting
|
||||
const getPrimaryEcerPrice = (product: Product): number => {
|
||||
const ecerPrices = product.variants.flatMap(v =>
|
||||
const ecerPrices = product.variants.flatMap(v =>
|
||||
v.prices.filter(p => p.type === 'ecer').map(p => p.price)
|
||||
);
|
||||
|
||||
@ -126,16 +126,16 @@ const filteredProducts = computed(() => {
|
||||
// Search filter
|
||||
const matchesSearch = product.name.toLowerCase().includes(searchInput.value.toLowerCase()) ||
|
||||
(product.description && product.description.toLowerCase().includes(searchInput.value.toLowerCase()));
|
||||
|
||||
|
||||
// Category filter
|
||||
const matchesCategory = !selectedCategory.value ||
|
||||
const matchesCategory = !selectedCategory.value ||
|
||||
product.categories.some(cat => cat.slug === selectedCategory.value);
|
||||
|
||||
|
||||
// Price filter
|
||||
const price = getPrimaryEcerPrice(product);
|
||||
const matchesMinPrice = minPrice.value === null || price >= minPrice.value;
|
||||
const matchesMaxPrice = maxPrice.value === null || price <= maxPrice.value;
|
||||
|
||||
|
||||
return matchesSearch && matchesCategory && matchesMinPrice && matchesMaxPrice;
|
||||
});
|
||||
|
||||
@ -158,7 +158,7 @@ const getProductImage = (product: Product, indexOffset = 0) => {
|
||||
return variant.images[0].url;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Fallbacks based on category slug
|
||||
const firstCat = product.categories?.[0]?.slug || '';
|
||||
const fallbacks: Record<string, string[]> = {
|
||||
@ -183,12 +183,12 @@ const getProductImage = (product: Product, indexOffset = 0) => {
|
||||
'https://images.unsplash.com/photo-1496747611176-843222e1e57c?w=800&auto=format&fit=crop&q=80'
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
const categoryUrls = fallbacks[firstCat] || [
|
||||
'https://images.unsplash.com/photo-1490481651871-ab68de25d43d?w=800&auto=format&fit=crop&q=80',
|
||||
'https://images.unsplash.com/photo-1434389677669-e08b4cac3105?w=800&auto=format&fit=crop&q=80'
|
||||
];
|
||||
|
||||
|
||||
return categoryUrls[indexOffset % categoryUrls.length];
|
||||
};
|
||||
|
||||
@ -199,10 +199,10 @@ const formatPrice = (price: number) => {
|
||||
|
||||
// Price Range Resolver
|
||||
const getProductPriceRange = (product: Product) => {
|
||||
const prices = product.variants.flatMap(v =>
|
||||
const prices = product.variants.flatMap(v =>
|
||||
v.prices.filter(p => p.type === 'ecer').map(p => p.price)
|
||||
);
|
||||
|
||||
|
||||
if (prices.length === 0) {
|
||||
const fallbackPrices = product.variants.flatMap(v => v.prices.map(p => p.price));
|
||||
|
||||
@ -215,7 +215,7 @@ return 'Rp 0';
|
||||
|
||||
return min === max ? formatPrice(min) : `${formatPrice(min)} - ${formatPrice(max)}`;
|
||||
}
|
||||
|
||||
|
||||
const min = Math.min(...prices);
|
||||
const max = Math.max(...prices);
|
||||
|
||||
@ -239,11 +239,11 @@ const seconds = ref(0);
|
||||
const startCountdown = () => {
|
||||
const now = new Date();
|
||||
const target = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59);
|
||||
|
||||
|
||||
const update = () => {
|
||||
const current = new Date().getTime();
|
||||
const diff = target.getTime() - current;
|
||||
|
||||
|
||||
if (diff <= 0) {
|
||||
days.value = 0;
|
||||
hours.value = 0;
|
||||
@ -252,13 +252,13 @@ const startCountdown = () => {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
days.value = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
hours.value = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||
minutes.value = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
||||
seconds.value = Math.floor((diff % (1000 * 60)) / 1000);
|
||||
};
|
||||
|
||||
|
||||
update();
|
||||
const interval = setInterval(update, 1000);
|
||||
|
||||
@ -354,7 +354,7 @@ return;
|
||||
}
|
||||
|
||||
isCheckingOut.value = true;
|
||||
|
||||
|
||||
// Construct WhatsApp message
|
||||
let message = `Halo ${props.appName}, saya ingin memesan produk berikut:\n\n`;
|
||||
cart.value.forEach((item, index) => {
|
||||
@ -366,11 +366,11 @@ return;
|
||||
});
|
||||
message += `*Total Pembayaran: ${formatPrice(cartTotal.value)}*\n\n`;
|
||||
message += `Mohon info untuk detail rekening transfer dan pengirimannya. Terima kasih!`;
|
||||
|
||||
|
||||
const formattedPhone = props.contactPhone.replace(/[^0-9]/g, '');
|
||||
const finalPhone = formattedPhone.startsWith('0') ? '62' + formattedPhone.slice(1) : formattedPhone;
|
||||
const waUrl = `https://wa.me/${finalPhone || '6281234567890'}?text=${encodeURIComponent(message)}`;
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
isCheckingOut.value = false;
|
||||
window.open(waUrl, '_blank');
|
||||
@ -402,21 +402,21 @@ clearInterval(countdownInterval);
|
||||
</Head>
|
||||
|
||||
<div class="min-h-screen bg-gradient-to-tr from-cyan-50/20 via-[#FDFDFC] to-sky-50/30 text-slate-800 transition-colors duration-300 dark:from-slate-950 dark:via-[#0E1726] dark:to-cyan-950/20 dark:text-slate-100 font-sans scroll-smooth">
|
||||
|
||||
|
||||
<!-- Sticky Header & Navbar -->
|
||||
<header class="sticky top-0 z-40 bg-white/70 backdrop-blur-lg dark:bg-[#0E1726]/70 border-b border-cyan-100/50 dark:border-cyan-950/30 transition-all">
|
||||
<div class="max-w-7xl mx-auto px-6 h-20 flex items-center justify-between">
|
||||
|
||||
|
||||
<!-- Logo Frame from settings -->
|
||||
<div class="flex items-center space-x-3">
|
||||
<a href="#" class="flex items-center space-x-2">
|
||||
<img
|
||||
v-if="logoUrl"
|
||||
:src="logoUrl"
|
||||
:alt="appName"
|
||||
class="h-10 w-auto object-contain bg-white/80 p-1 rounded-lg shadow-sm"
|
||||
<img
|
||||
v-if="logoUrl"
|
||||
:src="logoUrl"
|
||||
:alt="appName"
|
||||
class="h-10 w-auto object-contain p-1 rounded-lg shadow-sm"
|
||||
/>
|
||||
<span class="font-serif text-2xl font-bold tracking-[0.1em] text-cyan-600 dark:text-cyan-400">
|
||||
<span v-else class="font-serif text-2xl font-bold tracking-[0.1em] text-cyan-600 dark:text-cyan-400">
|
||||
{{ appName }}
|
||||
</span>
|
||||
</a>
|
||||
@ -434,9 +434,9 @@ clearInterval(countdownInterval);
|
||||
<!-- Action Utilities (Search, Dark Mode, Cart) -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- Theme Toggle -->
|
||||
<button
|
||||
@click="toggleDarkMode"
|
||||
class="p-2 rounded-full hover:bg-cyan-50 dark:hover:bg-cyan-950/20 text-slate-600 dark:text-slate-300 transition-colors cursor-pointer"
|
||||
<button
|
||||
@click="toggleDarkMode"
|
||||
class="p-2 rounded-full hover:bg-cyan-50 dark:hover:bg-cyan-950/20 text-slate-600 dark:text-slate-300 transition-colors cursor-pointer"
|
||||
aria-label="Ubah Tema"
|
||||
>
|
||||
<Sun v-if="isDark" class="w-4.5 h-4.5 text-amber-400" />
|
||||
@ -444,14 +444,14 @@ clearInterval(countdownInterval);
|
||||
</button>
|
||||
|
||||
<!-- Cart button -->
|
||||
<button
|
||||
@click="isCartOpen = true"
|
||||
<button
|
||||
@click="isCartOpen = true"
|
||||
class="relative p-2 rounded-full hover:bg-cyan-50 dark:hover:bg-cyan-950/20 text-slate-600 dark:text-slate-300 transition-colors cursor-pointer"
|
||||
aria-label="Keranjang Belanja"
|
||||
>
|
||||
<ShoppingBag class="w-4.5 h-4.5 text-cyan-600 dark:text-cyan-400" />
|
||||
<span
|
||||
v-if="cartCount > 0"
|
||||
<span
|
||||
v-if="cartCount > 0"
|
||||
class="absolute -top-0.5 -right-0.5 bg-cyan-600 text-white dark:bg-cyan-400 dark:text-slate-950 text-[9px] font-bold w-4.5 h-4.5 flex items-center justify-center rounded-full shadow-sm animate-bounce"
|
||||
>
|
||||
{{ cartCount }}
|
||||
@ -471,32 +471,32 @@ clearInterval(countdownInterval);
|
||||
|
||||
<!-- Main Layout Content -->
|
||||
<div class="max-w-7xl mx-auto px-6 w-full lg:grow grid grid-cols-1 lg:grid-cols-12 gap-8 items-center py-12 z-10">
|
||||
|
||||
|
||||
<!-- Left Column: Serif Text and CTA -->
|
||||
<div class="lg:col-span-5 flex flex-col justify-center space-y-6 lg:pr-6">
|
||||
<div class="inline-flex items-center space-x-2 bg-cyan-500/10 dark:bg-cyan-400/10 text-[10px] text-cyan-700 dark:text-cyan-300 uppercase tracking-widest px-3 py-1 rounded-full w-fit font-bold shadow-sm">
|
||||
<span>Koleksi Segar 2026</span>
|
||||
</div>
|
||||
|
||||
|
||||
<h1 class="text-5xl md:text-6xl font-serif leading-[1.1] tracking-tight font-light select-none text-slate-800 dark:text-slate-100">
|
||||
Ekspresikan <br />
|
||||
Gaya <span class="font-normal italic text-cyan-600 dark:text-cyan-400 bg-cyan-100/40 dark:bg-cyan-900/20 px-2 rounded-lg">Segar Anda</span>.
|
||||
</h1>
|
||||
|
||||
|
||||
<p class="text-sm text-slate-500 dark:text-slate-300 max-w-md leading-relaxed font-light">
|
||||
Selamat datang di {{ appName }}. Temukan keanggunan motif batik modern dan setelan pakaian santai berkualitas premium. Dirancang khusus dengan bahan adem yang menyejukkan aktivitas harian Anda.
|
||||
</p>
|
||||
|
||||
<div class="flex items-center space-x-4 pt-4">
|
||||
<a
|
||||
href="#catalog"
|
||||
<a
|
||||
href="#catalog"
|
||||
class="inline-flex items-center justify-center bg-cyan-600 text-white px-8 py-3.5 text-xs font-semibold uppercase tracking-widest rounded-xl shadow-lg shadow-cyan-200/50 hover:bg-cyan-500 hover:shadow-cyan-300/50 hover:-translate-y-0.5 transition-all duration-300 group dark:bg-cyan-500 dark:text-slate-950 dark:shadow-none dark:hover:bg-cyan-400 cursor-pointer"
|
||||
>
|
||||
Beli Sekarang
|
||||
<ArrowRight class="w-3.5 h-3.5 ml-2 transform group-hover:translate-x-1 transition-transform" />
|
||||
</a>
|
||||
<a
|
||||
href="#about"
|
||||
<a
|
||||
href="#about"
|
||||
class="inline-flex items-center justify-center border border-cyan-200 text-xs font-semibold uppercase tracking-widest px-8 py-3.5 rounded-xl hover:bg-cyan-500/5 hover:border-cyan-400 transition-all dark:border-cyan-800 dark:hover:bg-cyan-950/20 cursor-pointer"
|
||||
>
|
||||
Tentang Kami
|
||||
@ -506,7 +506,7 @@ clearInterval(countdownInterval);
|
||||
|
||||
<!-- Center Column: Visual Model Showcase & Overlapping Titles (Cyan Aura) -->
|
||||
<div class="lg:col-span-7 relative flex justify-center items-center h-[500px] lg:h-[650px] w-full">
|
||||
|
||||
|
||||
<!-- Massive Overlapping Serif Heading (Behind Model) -->
|
||||
<div class="absolute inset-0 flex justify-center items-center pointer-events-none select-none z-0">
|
||||
<div class="text-[14vw] lg:text-[160px] font-serif font-bold leading-none tracking-tight flex opacity-10 dark:opacity-[0.03]">
|
||||
@ -523,9 +523,9 @@ clearInterval(countdownInterval);
|
||||
|
||||
<!-- Main Model Central Image (With Floating Animation) -->
|
||||
<div class="relative w-[310px] h-[440px] lg:w-[390px] lg:h-[540px] rounded-[2rem] overflow-hidden shadow-2xl shadow-cyan-100/50 dark:shadow-none z-10 transition-transform duration-500 hover:scale-[1.02] border-4 border-white dark:border-cyan-950/80 animate-float-model">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1490481651871-ab68de25d43d?w=1000&auto=format&fit=crop&q=80"
|
||||
alt="DSTCLO Model Unggulan"
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1490481651871-ab68de25d43d?w=1000&auto=format&fit=crop&q=80"
|
||||
alt="DSTCLO Model Unggulan"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<!-- Subtle Gradient Overlay -->
|
||||
@ -533,14 +533,14 @@ clearInterval(countdownInterval);
|
||||
</div>
|
||||
|
||||
<!-- FLOATING HERO TAG 1: Product Showcase -->
|
||||
<div
|
||||
<div
|
||||
v-if="featuredProducts[0]"
|
||||
@click="openQuickView(featuredProducts[0])"
|
||||
class="absolute top-10 left-4 md:-left-6 z-20 flex items-center space-x-3 bg-white/90 backdrop-blur-md dark:bg-slate-900/90 p-3 rounded-2xl shadow-lg border border-cyan-100/40 dark:border-cyan-950/85 cursor-pointer transform hover:-translate-y-1 transition-all animate-float-tag-1"
|
||||
>
|
||||
<img
|
||||
:src="getProductImage(featuredProducts[0])"
|
||||
alt="Preview Produk"
|
||||
<img
|
||||
:src="getProductImage(featuredProducts[0])"
|
||||
alt="Preview Produk"
|
||||
class="w-10 h-10 object-cover rounded-lg"
|
||||
/>
|
||||
<div class="text-left">
|
||||
@ -554,14 +554,14 @@ clearInterval(countdownInterval);
|
||||
</div>
|
||||
|
||||
<!-- FLOATING HERO TAG 2: Rating -->
|
||||
<div
|
||||
<div
|
||||
v-if="featuredProducts[1]"
|
||||
@click="openQuickView(featuredProducts[1])"
|
||||
class="absolute top-28 right-4 md:-right-6 z-20 flex items-center space-x-3 bg-white/90 backdrop-blur-md dark:bg-slate-900/90 p-3 rounded-2xl shadow-lg border border-cyan-100/40 dark:border-cyan-950/85 cursor-pointer transform hover:-translate-y-1 transition-all animate-float-tag-2"
|
||||
>
|
||||
<img
|
||||
:src="getProductImage(featuredProducts[1], 1)"
|
||||
alt="Preview Produk"
|
||||
<img
|
||||
:src="getProductImage(featuredProducts[1], 1)"
|
||||
alt="Preview Produk"
|
||||
class="w-10 h-10 object-cover rounded-lg"
|
||||
/>
|
||||
<div class="text-left">
|
||||
@ -593,7 +593,7 @@ clearInterval(countdownInterval);
|
||||
|
||||
<!-- Catalog Section with Price Filters & Categories -->
|
||||
<section id="catalog" class="max-w-7xl mx-auto px-6 py-20 border-t border-cyan-100/30 dark:border-cyan-950/20">
|
||||
|
||||
|
||||
<div class="space-y-6 mb-12">
|
||||
<div class="text-center space-y-2">
|
||||
<h2 class="text-xs uppercase tracking-[0.3em] font-bold text-cyan-600 dark:text-cyan-400">Katalog Eksklusif</h2>
|
||||
@ -605,13 +605,13 @@ clearInterval(countdownInterval);
|
||||
|
||||
<!-- Interactive Filters & Search Controls bar -->
|
||||
<div class="bg-white/80 dark:bg-[#111A2E]/80 backdrop-blur-md p-6 rounded-3xl shadow-sm border border-cyan-100/30 dark:border-cyan-950/30 flex flex-col md:flex-row items-center gap-4 justify-between">
|
||||
|
||||
|
||||
<!-- Search Input -->
|
||||
<div class="relative w-full md:w-80">
|
||||
<input
|
||||
v-model="searchInput"
|
||||
type="text"
|
||||
placeholder="Cari nama pakaian..."
|
||||
<input
|
||||
v-model="searchInput"
|
||||
type="text"
|
||||
placeholder="Cari nama pakaian..."
|
||||
class="w-full bg-slate-50 dark:bg-slate-900 border border-slate-200 dark:border-cyan-950 text-xs px-4 py-3 pr-10 rounded-xl outline-none focus:border-cyan-500 dark:focus:border-cyan-400 text-slate-700 dark:text-white transition-all"
|
||||
/>
|
||||
<Search class="absolute right-3 top-3.5 w-4 h-4 text-slate-400" />
|
||||
@ -619,25 +619,25 @@ clearInterval(countdownInterval);
|
||||
|
||||
<!-- Category Chips -->
|
||||
<div class="flex flex-wrap gap-2 w-full md:w-auto justify-center">
|
||||
<button
|
||||
<button
|
||||
@click="selectedCategory = null"
|
||||
:class="[
|
||||
'px-4 py-2 text-[11px] font-semibold uppercase tracking-wider rounded-xl border transition-all cursor-pointer',
|
||||
!selectedCategory
|
||||
? 'bg-cyan-600 text-white border-transparent shadow-md shadow-cyan-200/50 dark:bg-cyan-500 dark:text-slate-950 dark:shadow-none'
|
||||
!selectedCategory
|
||||
? 'bg-cyan-600 text-white border-transparent shadow-md shadow-cyan-200/50 dark:bg-cyan-500 dark:text-slate-950 dark:shadow-none'
|
||||
: 'bg-transparent text-slate-600 border-slate-200 hover:border-cyan-500 dark:text-slate-300 dark:border-cyan-900/60 dark:hover:border-cyan-400'
|
||||
]"
|
||||
>
|
||||
Semua
|
||||
</button>
|
||||
<button
|
||||
v-for="category in categories"
|
||||
<button
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
@click="toggleCategory(category.slug)"
|
||||
:class="[
|
||||
'px-4 py-2 text-[11px] font-semibold uppercase tracking-wider rounded-xl border transition-all cursor-pointer',
|
||||
selectedCategory === category.slug
|
||||
? 'bg-cyan-600 text-white border-transparent shadow-md shadow-cyan-200/50 dark:bg-cyan-500 dark:text-slate-950 dark:shadow-none'
|
||||
selectedCategory === category.slug
|
||||
? 'bg-cyan-600 text-white border-transparent shadow-md shadow-cyan-200/50 dark:bg-cyan-500 dark:text-slate-950 dark:shadow-none'
|
||||
: 'bg-transparent text-slate-600 border-slate-200 hover:border-cyan-500 dark:text-slate-300 dark:border-cyan-900/60 dark:hover:border-cyan-400'
|
||||
]"
|
||||
>
|
||||
@ -646,7 +646,7 @@ clearInterval(countdownInterval);
|
||||
</div>
|
||||
|
||||
<!-- Filter panel toggler -->
|
||||
<button
|
||||
<button
|
||||
@click="showFilters = !showFilters"
|
||||
class="flex items-center space-x-2 px-4 py-2.5 rounded-xl border border-slate-200 dark:border-cyan-900 text-xs font-semibold text-slate-600 dark:text-slate-300 hover:border-cyan-500 dark:hover:border-cyan-400 cursor-pointer transition-colors"
|
||||
>
|
||||
@ -657,23 +657,23 @@ clearInterval(countdownInterval);
|
||||
</div>
|
||||
|
||||
<!-- Expandable Filter Panel -->
|
||||
<div
|
||||
v-if="showFilters"
|
||||
<div
|
||||
v-if="showFilters"
|
||||
class="bg-white/50 dark:bg-slate-900/40 p-6 rounded-2xl border border-cyan-100/20 dark:border-cyan-950/20 grid grid-cols-1 md:grid-cols-3 gap-6 animate-fade-in-up"
|
||||
>
|
||||
<!-- Price Range -->
|
||||
<div class="space-y-2">
|
||||
<label class="text-[11px] uppercase tracking-wider font-bold text-slate-500 dark:text-slate-400">Rentang Harga</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RupiahInput
|
||||
v-model="minPrice"
|
||||
placeholder="Min"
|
||||
<RupiahInput
|
||||
v-model="minPrice"
|
||||
placeholder="Min"
|
||||
class="w-full bg-white dark:bg-slate-950 text-slate-700 dark:text-white"
|
||||
/>
|
||||
<span class="text-xs text-slate-400">s/d</span>
|
||||
<RupiahInput
|
||||
v-model="maxPrice"
|
||||
placeholder="Max"
|
||||
<RupiahInput
|
||||
v-model="maxPrice"
|
||||
placeholder="Max"
|
||||
class="w-full bg-white dark:bg-slate-950 text-slate-700 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
@ -697,7 +697,7 @@ clearInterval(countdownInterval);
|
||||
|
||||
<!-- Reset Trigger -->
|
||||
<div class="flex items-end">
|
||||
<button
|
||||
<button
|
||||
@click="resetPriceFilters"
|
||||
class="w-full bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-750 text-slate-700 dark:text-slate-300 py-2.5 rounded-lg text-xs font-semibold uppercase tracking-wider transition-colors cursor-pointer"
|
||||
>
|
||||
@ -709,16 +709,16 @@ clearInterval(countdownInterval);
|
||||
|
||||
<!-- Product Grid -->
|
||||
<div v-if="filteredProducts.length > 0" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-x-8 gap-y-12">
|
||||
<div
|
||||
v-for="(product, idx) in filteredProducts"
|
||||
<div
|
||||
v-for="(product, idx) in filteredProducts"
|
||||
:key="product.id"
|
||||
class="group relative flex flex-col space-y-4"
|
||||
>
|
||||
<!-- Product Image Container -->
|
||||
<div class="relative aspect-[3/4] w-full overflow-hidden rounded-2xl bg-cyan-50/50 dark:bg-neutral-900 shadow-md transition-shadow hover:shadow-cyan-100/30">
|
||||
<img
|
||||
:src="getProductImage(product, idx)"
|
||||
:alt="product.name"
|
||||
<img
|
||||
:src="getProductImage(product, idx)"
|
||||
:alt="product.name"
|
||||
class="h-full w-full object-cover object-center transition-transform duration-700 ease-out group-hover:scale-105"
|
||||
/>
|
||||
<!-- Subtle Gradient Overlay -->
|
||||
@ -726,20 +726,20 @@ clearInterval(countdownInterval);
|
||||
|
||||
<!-- Absolute Status Badges -->
|
||||
<div class="absolute top-4 left-4 flex flex-col space-y-2">
|
||||
<span
|
||||
v-if="getTotalStock(product) === 0"
|
||||
<span
|
||||
v-if="getTotalStock(product) === 0"
|
||||
class="bg-red-500 text-white text-[9px] font-bold uppercase tracking-wider px-3 py-1 rounded-md"
|
||||
>
|
||||
Habis
|
||||
</span>
|
||||
<span
|
||||
v-else-if="getTotalStock(product) < 15"
|
||||
<span
|
||||
v-else-if="getTotalStock(product) < 15"
|
||||
class="bg-amber-500 text-white text-[9px] font-bold uppercase tracking-wider px-3 py-1 rounded-md animate-pulse"
|
||||
>
|
||||
Menipis
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
<span
|
||||
v-else
|
||||
class="bg-cyan-600 text-white text-[9px] font-bold uppercase tracking-wider px-3 py-1 rounded-md dark:bg-cyan-500 dark:text-slate-950"
|
||||
>
|
||||
Terbaru
|
||||
@ -748,7 +748,7 @@ clearInterval(countdownInterval);
|
||||
|
||||
<!-- Hover Action Quick View Button -->
|
||||
<div class="absolute bottom-4 inset-x-4 flex justify-center opacity-0 transform translate-y-2 group-hover:opacity-100 group-hover:translate-y-0 transition-all duration-300">
|
||||
<button
|
||||
<button
|
||||
@click="openQuickView(product)"
|
||||
class="w-full flex items-center justify-center bg-white/95 backdrop-blur-sm text-slate-800 hover:bg-cyan-600 hover:text-white px-4 py-3 rounded-xl shadow-md text-xs font-bold uppercase tracking-widest transition-all duration-200 cursor-pointer"
|
||||
>
|
||||
@ -764,7 +764,7 @@ clearInterval(countdownInterval);
|
||||
<span class="text-[10px] font-bold uppercase tracking-widest text-cyan-600 dark:text-cyan-400">
|
||||
{{ product.categories.map(c => c.name).join(', ') }}
|
||||
</span>
|
||||
|
||||
|
||||
<!-- Title -->
|
||||
<h4 class="text-base font-semibold text-slate-700 dark:text-neutral-200 line-clamp-1 group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors">
|
||||
{{ product.name }}
|
||||
@ -790,8 +790,8 @@ clearInterval(countdownInterval);
|
||||
<p class="text-sm text-slate-400 dark:text-neutral-500">
|
||||
Mohon maaf, kami tidak menemukan pakaian yang cocok dengan kata kunci pencarian Anda.
|
||||
</p>
|
||||
<button
|
||||
@click="searchInput = ''; selectedCategory = null; resetPriceFilters()"
|
||||
<button
|
||||
@click="searchInput = ''; selectedCategory = null; resetPriceFilters()"
|
||||
class="mt-2 text-xs font-bold uppercase tracking-widest text-cyan-600 dark:text-cyan-400 underline underline-offset-4 cursor-pointer"
|
||||
>
|
||||
Reset Pencarian
|
||||
@ -830,7 +830,7 @@ clearInterval(countdownInterval);
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a
|
||||
<a
|
||||
href="#catalog"
|
||||
class="inline-flex items-center justify-center bg-cyan-600 text-white px-8 py-3.5 text-xs font-semibold uppercase tracking-widest rounded-xl shadow-lg shadow-cyan-200/50 hover:bg-cyan-500 transition-all dark:bg-cyan-500 dark:text-slate-950 cursor-pointer"
|
||||
>
|
||||
@ -842,32 +842,32 @@ clearInterval(countdownInterval);
|
||||
<div class="lg:col-span-7 grid grid-cols-2 gap-4">
|
||||
<div class="space-y-4">
|
||||
<div class="aspect-[4/5] rounded-3xl overflow-hidden shadow-md border-2 border-white dark:border-cyan-950">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1595777457583-95e059d581b8?w=800&auto=format&fit=crop&q=80"
|
||||
alt="Daster Batik parang"
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1595777457583-95e059d581b8?w=800&auto=format&fit=crop&q=80"
|
||||
alt="Daster Batik parang"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="aspect-square rounded-3xl overflow-hidden shadow-md border-2 border-white dark:border-cyan-950">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1572804013309-59a88b7e92f1?w=800&auto=format&fit=crop&q=80"
|
||||
alt="Daster busui styling"
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1572804013309-59a88b7e92f1?w=800&auto=format&fit=crop&q=80"
|
||||
alt="Daster busui styling"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pt-8 space-y-4">
|
||||
<div class="aspect-square rounded-3xl overflow-hidden shadow-md border-2 border-white dark:border-cyan-950">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1515886657613-9f3515b0c78f?w=800&auto=format&fit=crop&q=80"
|
||||
alt="Blouse Batik Modern"
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1515886657613-9f3515b0c78f?w=800&auto=format&fit=crop&q=80"
|
||||
alt="Blouse Batik Modern"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="aspect-[4/5] rounded-3xl overflow-hidden shadow-md border-2 border-white dark:border-cyan-950">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1539109136881-3be0616acf4b?w=800&auto=format&fit=crop&q=80"
|
||||
alt="Setelan Celana Batik"
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1539109136881-3be0616acf4b?w=800&auto=format&fit=crop&q=80"
|
||||
alt="Setelan Celana Batik"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
@ -939,9 +939,9 @@ clearInterval(countdownInterval);
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-12 items-center">
|
||||
<!-- Column Left -->
|
||||
<div class="lg:col-span-6 relative aspect-square max-w-md mx-auto lg:max-w-none rounded-3xl overflow-hidden shadow-lg border-2 border-white dark:border-cyan-950">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1595777457583-95e059d581b8?w=800&auto=format&fit=crop&q=80"
|
||||
alt="Proses Pembuatan Batik"
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1595777457583-95e059d581b8?w=800&auto=format&fit=crop&q=80"
|
||||
alt="Proses Pembuatan Batik"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
@ -1007,10 +1007,10 @@ clearInterval(countdownInterval);
|
||||
<div class="space-y-3">
|
||||
<input type="text" placeholder="Nama Anda" class="w-full text-xs px-4 py-3 bg-white dark:bg-slate-900 border border-slate-200 dark:border-cyan-950 rounded-lg outline-none focus:border-cyan-500 text-slate-700 dark:text-white" />
|
||||
<textarea placeholder="Tulis pesan Anda di sini..." rows="4" class="w-full text-xs px-4 py-3 bg-white dark:bg-slate-900 border border-slate-200 dark:border-cyan-950 rounded-lg outline-none focus:border-cyan-500 text-slate-700 dark:text-white scrollbar-thin"></textarea>
|
||||
|
||||
|
||||
<!-- WhatsApp Direct Send Button -->
|
||||
<a
|
||||
:href="`https://wa.me/${contactPhone.replace(/[^0-9]/g, '') || '6281234567890'}`"
|
||||
<a
|
||||
:href="`https://wa.me/${contactPhone.replace(/[^0-9]/g, '') || '6281234567890'}`"
|
||||
target="_blank"
|
||||
class="w-full inline-flex items-center justify-center bg-emerald-600 hover:bg-emerald-500 text-white py-3 rounded-lg text-xs font-bold uppercase tracking-wider transition-colors"
|
||||
>
|
||||
@ -1028,13 +1028,13 @@ clearInterval(countdownInterval);
|
||||
<!-- Brand Profile -->
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<img
|
||||
v-if="logoUrl"
|
||||
:src="logoUrl"
|
||||
:alt="appName"
|
||||
<img
|
||||
v-if="logoUrl"
|
||||
:src="logoUrl"
|
||||
:alt="appName"
|
||||
class="h-8 w-auto object-contain bg-white p-0.5 rounded shadow-sm"
|
||||
/>
|
||||
<span class="font-serif text-xl font-bold tracking-widest text-black dark:text-white text-cyan-600 dark:text-cyan-400">
|
||||
<span v-else class="font-serif text-xl font-bold tracking-widest text-black dark:text-white text-cyan-600 dark:text-cyan-400">
|
||||
{{ appName }}
|
||||
</span>
|
||||
</div>
|
||||
@ -1087,20 +1087,20 @@ clearInterval(countdownInterval);
|
||||
</footer>
|
||||
|
||||
<!-- CENTERED DETAIL MODAL (MODA DETAIL) -->
|
||||
<div
|
||||
v-if="activeProduct"
|
||||
<div
|
||||
v-if="activeProduct"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm transition-opacity duration-300"
|
||||
@click.self="closeQuickView"
|
||||
>
|
||||
<div
|
||||
<div
|
||||
class="w-full max-w-3xl bg-[#FDFDFC] dark:bg-[#0E1726] rounded-3xl p-6 md:p-8 shadow-2xl flex flex-col md:flex-row gap-8 max-h-[90vh] overflow-y-auto border border-cyan-100/30 dark:border-cyan-950/30 scrollbar-thin"
|
||||
>
|
||||
<!-- Image Side -->
|
||||
<div class="w-full md:w-1/2 flex flex-col space-y-3">
|
||||
<div class="aspect-[3/4] rounded-2xl overflow-hidden bg-slate-50 dark:bg-neutral-900 border border-slate-200/50 dark:border-cyan-950/50 shadow-sm relative">
|
||||
<img
|
||||
:src="getProductImage(activeProduct)"
|
||||
:alt="activeProduct.name"
|
||||
<img
|
||||
:src="getProductImage(activeProduct)"
|
||||
:alt="activeProduct.name"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
@ -1116,8 +1116,8 @@ clearInterval(countdownInterval);
|
||||
</span>
|
||||
<h3 class="text-2xl font-serif mt-0.5 text-slate-800 dark:text-white">{{ activeProduct.name }}</h3>
|
||||
</div>
|
||||
<button
|
||||
@click="closeQuickView"
|
||||
<button
|
||||
@click="closeQuickView"
|
||||
class="p-1.5 rounded-full hover:bg-slate-100 dark:hover:bg-cyan-950/30 transition-colors cursor-pointer"
|
||||
>
|
||||
<X class="w-5 h-5 text-slate-500" />
|
||||
@ -1136,14 +1136,14 @@ clearInterval(countdownInterval);
|
||||
<div v-if="activeProduct.variants.length > 0" class="space-y-2">
|
||||
<h4 class="text-[10px] uppercase font-bold tracking-widest text-slate-400">Pilih Ukuran / Varian</h4>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<button
|
||||
v-for="variant in activeProduct.variants"
|
||||
<button
|
||||
v-for="variant in activeProduct.variants"
|
||||
:key="variant.id"
|
||||
@click="activeVariant = variant"
|
||||
:class="[
|
||||
'px-3 py-1.5 rounded-xl text-xs font-semibold uppercase tracking-wider border cursor-pointer transition-all',
|
||||
activeVariant?.id === variant.id
|
||||
? 'bg-cyan-600 text-white border-transparent dark:bg-cyan-500 dark:text-slate-950 shadow-md shadow-cyan-200/30'
|
||||
activeVariant?.id === variant.id
|
||||
? 'bg-cyan-600 text-white border-transparent dark:bg-cyan-500 dark:text-slate-950 shadow-md shadow-cyan-200/30'
|
||||
: 'bg-transparent text-slate-600 border-slate-200 dark:text-slate-300 dark:border-cyan-900/60'
|
||||
]"
|
||||
>
|
||||
@ -1157,14 +1157,14 @@ clearInterval(countdownInterval);
|
||||
<div v-if="activeVariant && activeVariant.prices.length > 0" class="space-y-2">
|
||||
<h4 class="text-[10px] uppercase font-bold tracking-widest text-slate-400">Saluran Harga (Grosir/Eceran)</h4>
|
||||
<div class="grid grid-cols-2 gap-1.5 max-h-36 overflow-y-auto scrollbar-thin pr-1">
|
||||
<button
|
||||
v-for="price in activeVariant.prices"
|
||||
<button
|
||||
v-for="price in activeVariant.prices"
|
||||
:key="price.id"
|
||||
@click="activePriceType = price.type"
|
||||
:class="[
|
||||
'px-3 py-2 text-left rounded-xl border transition-all cursor-pointer',
|
||||
activePriceType === price.type
|
||||
? 'border-cyan-500 bg-cyan-500/5 dark:border-cyan-400 dark:bg-cyan-400/5'
|
||||
activePriceType === price.type
|
||||
? 'border-cyan-500 bg-cyan-500/5 dark:border-cyan-400 dark:bg-cyan-400/5'
|
||||
: 'border-slate-200 dark:border-cyan-900/60 hover:border-cyan-500 dark:hover:border-cyan-400'
|
||||
]"
|
||||
>
|
||||
@ -1196,7 +1196,7 @@ clearInterval(countdownInterval);
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
<button
|
||||
v-if="activeVariant && selectedPrice"
|
||||
@click="addToCart(activeProduct, activeVariant, selectedPrice)"
|
||||
:disabled="activeVariant.stock <= 0"
|
||||
@ -1215,12 +1215,12 @@ clearInterval(countdownInterval);
|
||||
</div>
|
||||
|
||||
<!-- FLOATING CART SLIDE-OVER SHEET -->
|
||||
<div
|
||||
v-if="isCartOpen"
|
||||
<div
|
||||
v-if="isCartOpen"
|
||||
class="fixed inset-0 z-50 flex items-center justify-end bg-black/60 backdrop-blur-sm transition-opacity duration-300"
|
||||
@click.self="isCartOpen = false"
|
||||
>
|
||||
<div
|
||||
<div
|
||||
class="w-full max-w-md h-full bg-[#FDFDFC] dark:bg-[#0E1726] p-6 shadow-2xl flex flex-col justify-between overflow-y-auto transform translate-x-0 transition-transform duration-300 border-l border-cyan-100/30 dark:border-cyan-950/30"
|
||||
>
|
||||
<div class="space-y-6">
|
||||
@ -1230,8 +1230,8 @@ clearInterval(countdownInterval);
|
||||
<ShoppingBag class="w-5 h-5 text-cyan-600 dark:text-cyan-400" />
|
||||
<h3 class="text-lg font-serif text-slate-800 dark:text-white">Keranjang Belanja ({{ cartCount }})</h3>
|
||||
</div>
|
||||
<button
|
||||
@click="isCartOpen = false"
|
||||
<button
|
||||
@click="isCartOpen = false"
|
||||
class="p-2 rounded-full hover:bg-slate-100 dark:hover:bg-cyan-950/30 transition-colors cursor-pointer"
|
||||
>
|
||||
<X class="w-5 h-5 text-slate-500" />
|
||||
@ -1240,25 +1240,25 @@ clearInterval(countdownInterval);
|
||||
|
||||
<!-- Cart List -->
|
||||
<div v-if="cart.length > 0" class="space-y-4 divide-y divide-slate-100 dark:divide-cyan-950/45 max-h-[calc(100vh-280px)] overflow-y-auto pr-1 scrollbar-thin">
|
||||
<div
|
||||
v-for="item in cart"
|
||||
<div
|
||||
v-for="item in cart"
|
||||
:key="`${item.variant.id}-${item.price.type}`"
|
||||
class="flex space-x-4 pt-4 first:pt-0"
|
||||
>
|
||||
<img
|
||||
:src="getProductImage(item.product)"
|
||||
:alt="item.product.name"
|
||||
<img
|
||||
:src="getProductImage(item.product)"
|
||||
:alt="item.product.name"
|
||||
class="w-16 h-20 object-cover rounded-xl bg-slate-50 dark:bg-slate-900 border border-slate-200/50 dark:border-cyan-950/50"
|
||||
/>
|
||||
|
||||
|
||||
<div class="flex-1 flex flex-col justify-between">
|
||||
<div class="space-y-1">
|
||||
<div class="flex justify-between items-start">
|
||||
<h4 class="text-xs font-semibold text-slate-700 dark:text-neutral-200 line-clamp-1">
|
||||
{{ item.product.name }}
|
||||
</h4>
|
||||
<button
|
||||
@click="removeFromCart(item.variant.id, item.price.type)"
|
||||
<button
|
||||
@click="removeFromCart(item.variant.id, item.price.type)"
|
||||
class="text-slate-400 hover:text-red-500 p-1 cursor-pointer transition-colors"
|
||||
>
|
||||
<X class="w-3.5 h-3.5" />
|
||||
@ -1268,18 +1268,18 @@ clearInterval(countdownInterval);
|
||||
Ukuran: <strong class="text-slate-700 dark:text-neutral-300 font-semibold">{{ item.variant.name }}</strong> • Tipe: <strong class="text-slate-700 dark:text-neutral-300 font-semibold">{{ item.price.type_label }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex justify-between items-center mt-2">
|
||||
<!-- Quantity Stepper -->
|
||||
<div class="flex items-center space-x-2 border border-slate-200 dark:border-cyan-900 rounded-lg px-2 py-0.5">
|
||||
<button
|
||||
<button
|
||||
@click="updateCartQuantity(item.variant.id, item.price.type, -1)"
|
||||
class="p-1 text-slate-500 hover:text-black dark:hover:text-white cursor-pointer"
|
||||
>
|
||||
<Minus class="w-3 h-3" />
|
||||
</button>
|
||||
<span class="text-xs font-semibold px-1 text-slate-800 dark:text-slate-100">{{ item.quantity }}</span>
|
||||
<button
|
||||
<button
|
||||
@click="updateCartQuantity(item.variant.id, item.price.type, 1)"
|
||||
class="p-1 text-slate-500 hover:text-black dark:hover:text-white cursor-pointer"
|
||||
>
|
||||
@ -1301,8 +1301,8 @@ clearInterval(countdownInterval);
|
||||
<p class="text-xs text-slate-400 dark:text-neutral-500 max-w-xs mx-auto font-light leading-relaxed">
|
||||
Jelajahi produk batik dan pakaian pilihan kami untuk menambahkan item ke dalam keranjang.
|
||||
</p>
|
||||
<button
|
||||
@click="isCartOpen = false"
|
||||
<button
|
||||
@click="isCartOpen = false"
|
||||
class="bg-cyan-600 text-white dark:bg-cyan-500 dark:text-slate-950 px-6 py-2.5 text-xs font-semibold uppercase tracking-widest rounded-xl hover:opacity-90 transition-all cursor-pointer shadow-md shadow-cyan-200/50"
|
||||
>
|
||||
Lanjutkan Belanja
|
||||
@ -1327,7 +1327,7 @@ clearInterval(countdownInterval);
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
<button
|
||||
@click="handleWhatsAppCheckout"
|
||||
:disabled="isCheckingOut"
|
||||
class="w-full flex items-center justify-center bg-emerald-600 text-white hover:bg-emerald-500 py-3.5 text-xs font-bold uppercase tracking-widest rounded-xl transition-all cursor-pointer shadow-md shadow-emerald-100/50"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user