feat: Dynamically load best-selling perfumes in jumbotron and remove hardcoded testimonials.

This commit is contained in:
Yoga Pangestu 2025-12-20 09:18:33 +07:00
parent 9bd6997359
commit 29817744b3
4 changed files with 159 additions and 182 deletions

View File

@ -6,7 +6,9 @@
use App\Models\ChooseUs;
use App\Models\Customer;
use App\Models\Faq;
use App\Models\Perfume;
use App\Models\PerfumeFeature;
use App\Models\Testimonial;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Storage;
@ -30,6 +32,8 @@ class Index extends Component
public $latestMembers = [];
public array $testimonials = [];
public function mount(): void
{
$this->brands = Brand::orderBy('sort_order')
@ -70,6 +74,30 @@ public function mount(): void
->inRandomOrder()
->take(3)
->get();
$this->testimonials = Testimonial::with(['user', 'user.customer'])
->show()
->latest()
->take(5)
->get()
->toArray();
$this->bestSellingPerfumes = Perfume::withSum(['items' => function ($query) {
$query->whereHas('order', function ($q) {
$q->paid();
});
}], 'quantity')
->orderByDesc('items_sum_quantity')
->take(2)
->get()
->map(function (Perfume $perfume) {
$perfume->image = $perfume->getFirstMediaUrl('image') ? Storage::url($perfume->getFirstMediaUrl('image')) : asset('assets/images/logo.png');
$perfume->concentration_label = $perfume->concentration->label();
return $perfume;
})
->filter(fn (Perfume $perfume) => $perfume->items_sum_quantity > 0)
->toArray();
}
public function render(): View
@ -88,4 +116,6 @@ public function goToVoucher(): RedirectResponse
{
return redirect('/voucher');
}
public $bestSellingPerfumes = [];
}

View File

@ -20,26 +20,30 @@ class="border-2 border-home-background w-8 h-8 lg:w-10 lg:h-10 rounded-full over
@endif
<h1 class="text-home-foreground text-4xl lg:text-6xl leading-tight text-start font-bold">
Discover your best<br><span class="italic font-serif font-light text-home-primary">perfume</span> at today
Temukan<br>
<span class="italic font-serif font-light text-home-primary">
signature scent-mu
</span> hari ini
</h1>
<p class="text-lg w-[90%] lg:w-full lg:text-lg text-home-foreground/80">
Selecting a perfume is a personal journey. Consider your lifestyle, favorite scents, and the impression you
want
to make.
Setiap parfum punya cerita. Pilih aroma yang sesuai dengan gaya hidupmu dan kesan yang ingin kamu tampilkan.
</p>
<div class="flex items-center">
<button
<a href="{{ route('product.perfume') }}" wire:navigate
class="bg-home-primary text-home-background py-3 px-6 rounded-full hover:shadow-lg hover:shadow-home-primary/30 transition-all font-medium">
Explore Shop
</button>
Jelajahi Toko
</a>
<span class="w-8 h-[2px] bg-home-primary/20"></span>
<button
class="flex items-center justify-center w-12 h-12 border border-home-primary rounded-full hover:bg-home-background transition-colors group bg-transparent">
<div <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" />
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
stroke="currentColor" class="w-5 h-5 text-home-primary">
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" />
</svg>
</button>
</div>
</button>
</div>
@include('livewire.home.partials.jumbotron.testimonial')
</section>

View File

@ -3,74 +3,47 @@ class="relative w-full lg:w-[65%] min-h-[600px] lg:min-h-screen hidden lg:flex f
<div
class="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1591925463023-1ca6b0636780?q=80&w=1632&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D')] bg-cover bg-center bg-no-repeat">
<div class="absolute inset-0 bg-gradient-to-t from-home-background/90 via-home-background/50 to-transparent lg:hidden"></div>
<div
class="absolute inset-0 bg-gradient-to-t from-home-background/90 via-home-background/50 to-transparent lg:hidden">
</div>
</div>
<div class="relative w-full px-6 lg:px-0 lg:w-[80%] grid grid-cols-1 lg:grid-cols-2 gap-6 mb-24 lg:mb-0">
<div class="relative w-full px-6 lg:px-0 grid grid-cols-1 lg:grid-cols-2 gap-6 mb-24 lg:mb-0">
<article
class="bg-white backdrop-blur-md rounded-2xl shadow-lg hover:shadow-xl transition-shadow flex flex-row items-center p-4 h-auto lg:h-[250px] group">
@foreach ($bestSellingPerfumes as $perfume)
<article
class="bg-white backdrop-blur-md rounded-2xl shadow-lg hover:shadow-xl transition-shadow flex flex-row items-center p-4 h-auto lg:h-[250px] group">
<figure class="w-[40%] lg:w-auto h-32 lg:h-full overflow-hidden rounded-xl bg-gray-100">
<img src="https://images.unsplash.com/photo-1693960794377-b388be4227c2?q=80&w=880&auto=format&fit=crop"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
alt="Product One">
</figure>
<figure class="w-[40%] lg:w-auto h-32 lg:h-full overflow-hidden rounded-xl bg-gray-100">
<img src="{{ $perfume['image'] }}"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
alt="{{ $perfume['name'] }}">
</figure>
<div class="flex-1 ml-4 lg:ml-6 flex flex-col justify-center space-y-2 lg:space-y-4">
<div>
<h3 class="text-lg lg:text-2xl text-black font-bold">Product One</h3>
<p class="text-black/60 text-xs lg:text-lg">Fresh and elegant.</p>
<div class="flex-1 ml-4 lg:ml-6 flex flex-col justify-center space-y-2 lg:space-y-4">
<div>
<h3 class="text-lg lg:text-2xl text-black font-bold line-clamp-1">{{ $perfume['name'] }}</h3>
<p class="text-black/60 text-xs lg:text-lg line-clamp-2">
{{ $perfume['concentration_label'] }}</p>
</div>
<div class="flex items-center gap-2 mt-auto">
<button wire:click="goToProduct"
class="flex-1 justify-center items-center border border-gray-200 py-2 px-3 bg-home-primary rounded-full text-xs lg:text-sm font-medium hover:bg-home-primary/80 transition-colors text-home-background">
Belanja Sekarang
</button>
<button wire:click="goToProduct"
class="w-8 h-8 lg:w-10 lg:h-10 bg-home-primary flex justify-center items-center rounded-full hover:bg-home-primary/80 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="size-4 lg:size-5 text-home-background">
<path stroke-linecap="round" stroke-linejoin="round"
d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" />
</svg>
</button>
</div>
</div>
<div class="flex items-center gap-2 mt-auto">
<button
class="flex-1 justify-center items-center border border-gray-200 py-2 px-3 bg-home-primary rounded-full text-xs lg:text-sm font-medium hover:bg-black hover:text-home-background transition-colors text-home-background">
Shop Now
</button>
<button
class="w-8 h-8 lg:w-10 lg:h-10 bg-home-primary flex justify-center items-center rounded-full hover:bg-gray-800 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="size-4 lg:size-5 text-home-background">
<path stroke-linecap="round" stroke-linejoin="round"
d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" />
</svg>
</button>
</div>
</div>
</article>
<article
class="bg-white backdrop-blur-md rounded-2xl shadow-lg hover:shadow-xl transition-shadow flex flex-row items-center p-4 h-auto lg:h-[250px] group">
<figure class="w-[40%] lg:w-auto h-32 lg:h-full overflow-hidden rounded-xl bg-gray-100">
<img src="https://images.unsplash.com/photo-1693960794377-b388be4227c2?q=80&w=880&auto=format&fit=crop"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
alt="Product One">
</figure>
<div class="flex-1 ml-4 lg:ml-6 flex flex-col justify-center space-y-2 lg:space-y-4">
<div>
<h3 class="text-lg lg:text-2xl text-black font-bold">Product Two</h3>
<p class="text-black/60 text-xs lg:text-lg">Woody & classic.</p>
</div>
<div class="flex items-center gap-2 mt-auto">
<button
class="flex-1 justify-center items-center border border-gray-200 py-2 px-3 bg-home-primary rounded-full text-xs lg:text-sm font-medium hover:bg-black hover:text-home-background transition-colors text-home-background">
Shop Now
</button>
<button
class="w-8 h-8 lg:w-10 lg:h-10 bg-home-primary flex justify-center items-center rounded-full hover:bg-gray-800 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="size-4 lg:size-5 text-home-background">
<path stroke-linecap="round" stroke-linejoin="round"
d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" />
</svg>
</button>
</div>
</div>
</article>
</article>
@endforeach
</div>
</section>

View File

@ -1,115 +1,85 @@
<article
class="relative w-full mt-auto h-[300px] lg:h-[250px] bg-home-background/40 backdrop-blur-md border border-home-secondary rounded-2xl z-30 shadow-sm overflow-hidden transition-all duration-300">
<div class="mb-5 lg:mb-0">
@if (!empty($testimonials))
<article x-data="{
current: 0,
total: {{ count($testimonials) }},
timer: null,
start() {
this.timer = setInterval(() => this.next(), 6000);
},
stop() {
clearInterval(this.timer);
},
next() {
this.current = (this.current + 1) % this.total;
this.stop();
this.start();
},
prev() {
this.current = (this.current - 1 + this.total) % this.total;
this.stop();
this.start();
},
getClass(index) {
const isCurrent = this.current === index;
let isBefore = index < this.current;
if (this.current === 0 && index === this.total - 1) isBefore = true;
if (this.current === this.total - 1 && index === 0) isBefore = false;
return {
'opacity-100 z-10 translate-x-0': isCurrent,
'opacity-0 z-0': !isCurrent,
'-translate-x-full': !isCurrent && isBefore,
'translate-x-full': !isCurrent && !isBefore
};
}
}" x-init="start()"
class="relative w-full mt-auto h-[300px] lg:h-[250px] bg-home-background/40 backdrop-blur-md border border-home-secondary rounded-2xl z-30 shadow-sm overflow-hidden transition-all duration-300">
<div id="testimonial-track" class="relative w-full h-full py-0 px-6">
<div
class="testimonial-slide absolute inset-0 py-0 px-6 flex flex-col justify-center transition-all duration-500 ease-in-out opacity-100 z-10 translate-x-0">
<div class="flex items-center gap-4 mb-3">
<img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&q=80&w=100"
class="w-12 h-12 rounded-full object-cover ring-2 ring-home-background">
<div>
<h4 class="font-bold text-home-foreground text-sm lg:text-base">Sarah Jenkins</h4>
<div class="flex text-home-primary text-xs">★★★★★</div>
</div>
<div class="relative w-full h-full py-0 px-6">
@foreach ($testimonials as $index => $testimonial)
<div class="testimonial-slide absolute inset-0 py-0 px-6 flex flex-col justify-center transition-all duration-500 ease-in-out {{ $index === 0 ? 'opacity-100 z-10 translate-x-0' : 'opacity-0 z-0 translate-x-full' }}"
:class="getClass({{ $index }})">
<div class="flex items-center gap-4 mb-3">
<img src="https://ui-avatars.com/api/?name={{ urlencode($testimonial['user']['customer']['name']) }}&background=random&color=fff"
alt="{{ $testimonial['user']['customer']['name'] }}"
class="w-12 h-12 rounded-full object-cover ring-2 ring-home-background">
<div>
<h4 class="font-bold text-home-foreground text-sm lg:text-base">
{{ $testimonial['user']['customer']['name'] }}</h4>
<div class="flex text-home-primary text-xs">
@for ($i = 0; $i < $testimonial['rating']; $i++)
@endfor
</div>
</div>
</div>
<p class="text-home-neutral text-base lg:text-lg italic leading-relaxed">
&quot;{{ $testimonial['content'] }}&quot;
</p>
</div>
@endforeach
</div>
<p class="text-home-neutral text-base lg:text-lg italic leading-relaxed">"Aroma yang sangat elegan dan
tahan lama. Saya sangat menyukainya untuk dipakai sehari-hari ke kantor."</p>
</div>
<div
class="testimonial-slide absolute inset-0 py-0 px-6 flex flex-col justify-center transition-all duration-500 ease-in-out opacity-0 z-0 translate-x-full">
<div class="flex items-center gap-4 mb-3">
<img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&q=80&w=100"
class="w-12 h-12 rounded-full object-cover ring-2 ring-home-background">
<div>
<h4 class="font-bold text-home-foreground text-sm lg:text-base">Michael Chen</h4>
<div class="flex text-home-primary text-xs">★★★★★</div>
</div>
<div class="absolute bottom-4 right-4 flex items-center gap-2 z-30">
<button @click="prev()"
class="w-10 h-10 rounded-full bg-home-background/80 backdrop-blur border border-home-background shadow-sm hover:bg-home-background flex justify-center items-center transition-colors group">
<svg xmlns="http://www.w3.org/2000/svg"
class="w-4 h-4 text-home-primary group-hover:scale-110 transition-transform" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
</button>
<button @click="next()"
class="w-10 h-10 rounded-full bg-home-primary shadow-md hover:bg-home-primary/80 flex justify-center items-center transition-colors group">
<svg xmlns="http://www.w3.org/2000/svg"
class="w-4 h-4 text-home-background group-hover:scale-110 transition-transform" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
<p class="text-home-neutral text-base lg:text-lg italic leading-relaxed">"Pengiriman sangat cepat dan
packaging aman. Wanginya persis seperti deskripsi di website."</p>
</div>
<div
class="testimonial-slide absolute inset-0 py-0 px-6 flex flex-col justify-center transition-all duration-500 ease-in-out opacity-0 z-0 translate-x-full">
<div class="flex items-center gap-4 mb-3">
<img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&q=80&w=100"
class="w-12 h-12 rounded-full object-cover ring-2 ring-home-background">
<div>
<h4 class="font-bold text-home-foreground text-sm lg:text-base">Jessica Tan</h4>
<div class="flex text-home-primary text-xs">★★★★★</div>
</div>
</div>
<p class="text-home-neutral text-base lg:text-lg italic leading-relaxed">"Varian Product Two adalah
favorit saya! Sangat fresh dan tidak menyengat. Recommended!"</p>
</div>
</div>
<div class="absolute bottom-4 right-4 flex items-center gap-2 z-30">
<button id="prev-testi"
class="w-10 h-10 rounded-full bg-home-background/80 backdrop-blur border border-home-background shadow-sm hover:bg-home-background flex justify-center items-center transition-colors group">
<svg xmlns="http://www.w3.org/2000/svg"
class="w-4 h-4 text-home-primary group-hover:scale-110 transition-transform" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
</button>
<button id="next-testi"
class="w-10 h-10 rounded-full bg-home-primary shadow-md hover:bg-home-primary/80 flex justify-center items-center transition-colors group">
<svg xmlns="http://www.w3.org/2000/svg"
class="w-4 h-4 text-home-background group-hover:scale-110 transition-transform" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</article>
<script>
document.addEventListener("DOMContentLoaded", () => {
const slides = document.querySelectorAll('.testimonial-slide');
const prevBtn = document.getElementById('prev-testi');
const nextBtn = document.getElementById('next-testi');
let currentIndex = 0;
function updateSlides() {
slides.forEach((slide, index) => {
if (index === currentIndex) {
slide.classList.remove('opacity-0', 'translate-x-full', '-translate-x-full', 'z-0');
slide.classList.add('opacity-100', 'translate-x-0', 'z-10');
} else {
slide.classList.remove('opacity-100', 'translate-x-0', 'z-10');
slide.classList.add('opacity-0', 'z-0');
if (index < currentIndex) {
slide.classList.add('-translate-x-full');
slide.classList.remove('translate-x-full');
} else {
slide.classList.add('translate-x-full');
slide.classList.remove('-translate-x-full');
}
}
});
}
if (nextBtn && prevBtn) {
nextBtn.addEventListener('click', () => {
currentIndex = (currentIndex + 1) % slides.length;
updateSlides();
});
prevBtn.addEventListener('click', () => {
currentIndex = (currentIndex - 1 + slides.length) % slides.length;
updateSlides();
});
}
setInterval(() => {
currentIndex = (currentIndex + 1) % slides.length;
updateSlides();
}, 6000);
});
</script>
</article>
@endif
</div>