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\ChooseUs;
use App\Models\Customer; use App\Models\Customer;
use App\Models\Faq; use App\Models\Faq;
use App\Models\Perfume;
use App\Models\PerfumeFeature; use App\Models\PerfumeFeature;
use App\Models\Testimonial;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -30,6 +32,8 @@ class Index extends Component
public $latestMembers = []; public $latestMembers = [];
public array $testimonials = [];
public function mount(): void public function mount(): void
{ {
$this->brands = Brand::orderBy('sort_order') $this->brands = Brand::orderBy('sort_order')
@ -70,6 +74,30 @@ public function mount(): void
->inRandomOrder() ->inRandomOrder()
->take(3) ->take(3)
->get(); ->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 public function render(): View
@ -88,4 +116,6 @@ public function goToVoucher(): RedirectResponse
{ {
return redirect('/voucher'); 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 @endif
<h1 class="text-home-foreground text-4xl lg:text-6xl leading-tight text-start font-bold"> <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> </h1>
<p class="text-lg w-[90%] lg:w-full lg:text-lg text-home-foreground/80"> <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 Setiap parfum punya cerita. Pilih aroma yang sesuai dengan gaya hidupmu dan kesan yang ingin kamu tampilkan.
want
to make.
</p> </p>
<div class="flex items-center"> <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"> 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 Jelajahi Toko
</button> </a>
<span class="w-8 h-[2px] bg-home-primary/20"></span> <span class="w-8 h-[2px] bg-home-primary/20"></span>
<button <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"> 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> </div>
</button>
</div> </div>
@include('livewire.home.partials.jumbotron.testimonial') @include('livewire.home.partials.jumbotron.testimonial')
</section> </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 <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"> 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>
<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 @foreach ($bestSellingPerfumes as $perfume)
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"> <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"> <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" <img src="{{ $perfume['image'] }}"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500" class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
alt="Product One"> alt="{{ $perfume['name'] }}">
</figure> </figure>
<div class="flex-1 ml-4 lg:ml-6 flex flex-col justify-center space-y-2 lg:space-y-4"> <div class="flex-1 ml-4 lg:ml-6 flex flex-col justify-center space-y-2 lg:space-y-4">
<div> <div>
<h3 class="text-lg lg:text-2xl text-black font-bold">Product One</h3> <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">Fresh and elegant.</p> <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>
</article>
<div class="flex items-center gap-2 mt-auto"> @endforeach
<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>
</div> </div>
</section> </section>

View File

@ -1,115 +1,85 @@
<article <div class="mb-5 lg:mb-0">
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"> @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="relative w-full h-full py-0 px-6">
@foreach ($testimonials as $index => $testimonial)
<div <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="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"> :class="getClass({{ $index }})">
<div class="flex items-center gap-4 mb-3"> <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" <img src="https://ui-avatars.com/api/?name={{ urlencode($testimonial['user']['customer']['name']) }}&background=random&color=fff"
class="w-12 h-12 rounded-full object-cover ring-2 ring-home-background"> alt="{{ $testimonial['user']['customer']['name'] }}"
<div> class="w-12 h-12 rounded-full object-cover ring-2 ring-home-background">
<h4 class="font-bold text-home-foreground text-sm lg:text-base">Sarah Jenkins</h4> <div>
<div class="flex text-home-primary text-xs">★★★★★</div> <h4 class="font-bold text-home-foreground text-sm lg:text-base">
</div> {{ $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> </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 <div class="absolute bottom-4 right-4 flex items-center gap-2 z-30">
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"> <button @click="prev()"
<div class="flex items-center gap-4 mb-3"> 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">
<img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&q=80&w=100" <svg xmlns="http://www.w3.org/2000/svg"
class="w-12 h-12 rounded-full object-cover ring-2 ring-home-background"> class="w-4 h-4 text-home-primary group-hover:scale-110 transition-transform" fill="none"
<div> viewBox="0 0 24 24" stroke="currentColor">
<h4 class="font-bold text-home-foreground text-sm lg:text-base">Michael Chen</h4> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
<div class="flex text-home-primary text-xs">★★★★★</div> </svg>
</div> </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> </div>
<p class="text-home-neutral text-base lg:text-lg italic leading-relaxed">"Pengiriman sangat cepat dan </article>
packaging aman. Wanginya persis seperti deskripsi di website."</p> @endif
</div> </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>