popular product, brand

This commit is contained in:
Bayu Lukman Yusuf 2026-01-09 10:20:44 +07:00
parent 6117a3580a
commit 3b91c813da
16 changed files with 812 additions and 328 deletions

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ComponentController extends Controller
{
public function load(Request $request, $component)
{
$template = $request->get('template');
// Map component names to their blade views
$componentMap = [
'home-popular-products' => 'components.home.home-popular-products',
'product-highlight' => 'components.home.product-highlight',
'new-arrivals' => 'components.home.new-arrivals',
'brand-home' => 'components.home.brand-home-' . ($template ?? 'fashion-v1'),
];
$view = $componentMap[$component] ?? null;
if (!$view) {
return response()->json(['error' => 'Component not found'], 404);
}
// For brand-home component, we need to pass the template parameter
if ($component === 'brand-home') {
return view($view, ['template' => $template]);
}
return view($view);
}
}

View File

@ -174,6 +174,98 @@ class ProductController extends Controller
]);
}
public function brands(Request $request)
{
$brandRepository = new \App\Repositories\Catalog\BrandRepository;
$brands = $brandRepository->getList([]);
// Render brand HTML
$brandHtml = '';
foreach ($brands as $brand) {
$brandHtml .= '<a class="swiper-slide text-body" href="' . route('product.index', ['brand' => $brand->slug]) . '" aria-label="' . $brand->name . '">';
$brandHtml .= '<img src="' . $brand->image_url . '" alt="' . $brand->name . '" class="object-fit-contain">';
$brandHtml .= '</a>';
}
return response()->json([
'success' => true,
'brands' => $brandHtml
]);
}
public function highlights(Request $request)
{
$type = $request->input('type', 'new');
$limit = 8;
$user = auth()->user();
$userId = $user ? $user->id : 0;
[$location_id, $is_consignment] = Cache::remember('employee_user_'.$userId, 60 * 60 * 24, function () use ($user) {
if ($user == null) {
return [10, false];
}
$employee = @$user->employee;
$location_id = @$employee->location_id;
$location = @$employee->location;
$is_consignment = (bool) @$location->is_consignment;
return [$location_id, $is_consignment];
});
$productRepository = new ProductRepository;
// Set parameters based on type
$params = [
'limit' => $limit,
'location_id' => $location_id,
'is_consignment' => $is_consignment,
];
switch ($type) {
case 'best_sellers':
$params['sort'] = 'random';
break;
case 'new_arrivals':
$params['sort'] = 'new';
break;
case 'sale_items':
$params['event'] = 'special-offer';
break;
case 'top_rated':
$params['sort'] = 'random';
break;
default:
$params['sort'] = 'new';
break;
}
$products = $productRepository->getList($params);
// Render product cards HTML
$productHtml = '';
if (count($products) == 0) {
$productHtml = '<div class="col-12 text-center py-4">';
$productHtml .= 'No products found';
$productHtml .= '</div>';
} else {
foreach ($products as $product) {
$productHtml .= '<div class="col mb-2 mb-sm-3 mb-md-0">';
$productHtml .= view('components.home.product-card', ['product' => $product])->render();
$productHtml .= '</div>';
}
}
return response()->json([
'success' => true,
'products' => $productHtml,
'count' => count($products),
'type' => $type
]);
}
public function detail($slug, Request $request, ProductRepository $productRepository)
{

View File

@ -9,24 +9,16 @@ use Illuminate\View\Component;
class BrandHome extends Component
{
public $brands;
public $template = 'fashion-v1';
/**
* Create a new component instance.
*/
public function __construct(BrandRepository $brandRepository)
public function __construct()
{
$this->brands = $brandRepository->getList([]);
if ($this->brands->count() < 9) {
// Ensure we have at least 9 brands by duplicating if necessary
while ($this->brands->count() < 9) {
$this->brands = $this->brands->concat($this->brands);
}
$this->brands = $this->brands->take(9);
}
}
/**

View File

@ -0,0 +1,22 @@
<?php
namespace App\View\Components\Home;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class HomePopularProducts extends Component
{
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.home.home-popular-products');
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\View\Components\Home;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class HomeSlider extends Component
{
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.home.home-slider');
}
}

View File

@ -9,14 +9,11 @@ use Illuminate\View\Component;
class ProductHighlight extends Component
{
public $products;
public function __construct(ProductRepository $productRepository)
public function __construct()
{
$params = [
'sort' => 'new',
];
$this->products = $productRepository->getList($params);
}
/**

File diff suppressed because one or more lines are too long

View File

@ -34,4 +34,4 @@ import './components/binded-label'
import './components/image-zoom'
import './components/code-highlight'
import './components/copy-text'
import './components/chart'
import './components/chart'

View File

@ -1,6 +1,6 @@
<section class="container pb-5 mb-1 mb-sm-3 mb-lg-4 mb-xl-5">
<div class="swiper my-md-3"
data-swiper='{
<section class="container pb-5 mb-1 mb-sm-3 mb-lg-4 mb-xl-5" id="brand-home-section">
<div class="swiper my-md-3"
data-swiper='{
"slidesPerView": 2,
"pagination": {
"el": ".swiper-pagination",
@ -24,21 +24,80 @@
}
}
}'>
<div class="swiper-wrapper">
<div class="swiper-wrapper" id="brand-container">
<!-- Brands will be loaded via AJAX -->
</div>
<!-- Pagination (Bullets) -->
<div class="swiper-pagination position-static mt-3"></div>
</div>
<!-- Item -->
@foreach($brands as $brand)
<a class="swiper-slide text-body" href="#!" aria-label="{{ $brand->name }}">
<img src="{{ $brand->image_url }}" alt="{{ $brand->name }}" class="object-fit-contain">
</a>
@endforeach
</div>
<!-- Pagination (Bullets) -->
<div class="swiper-pagination position-static mt-3"></div>
<!-- Loading state -->
<div class="text-center py-5 d-none" id="brand-loading">
<div class="swiper my-md-3">
<div class="swiper-wrapper">
@for ($i = 1; $i <= 6; $i++)
<div class="swiper-slide text-body">
<div class="shimmer-wrapper">
<div class="shimmer">
<div class="shimmer-content rounded">
<div class="shimmer-line shimmer-image rounded"
style="height: 60px; width: 100%; background-color: rgba(0, 0, 0, 0.1);">
</div>
</div>
</div>
</div>
</div>
@endfor
</div>
</section>
</div>
</div>
</section>
<script>
document.addEventListener('DOMContentLoaded', function() {
const brandContainer = document.getElementById('brand-container');
const loadingSpinner = document.getElementById('brand-loading');
const section = document.getElementById('brand-home-section');
if (!brandContainer || !loadingSpinner) {
return;
}
// Load brands on page load
loadBrands();
function loadBrands() {
// Show loading spinner
loadingSpinner.classList.remove('d-none');
// Make AJAX request for brands
fetch('{{ route("product.ajax.brands") }}')
.then(response => response.json())
.then(data => {
if (data.success && data.brands) {
brandContainer.innerHTML = data.brands;
} else {
// Handle error
brandContainer.innerHTML = '<div class="swiper-slide text-center py-4">Error loading brands</div>';
}
})
.catch(error => {
console.error('Error loading brands:', error);
brandContainer.innerHTML = '<div class="swiper-slide text-center py-4">Error loading brands</div>';
})
.finally(() => {
// Hide loading spinner
loadingSpinner.classList.add('d-none');
// Re-initialize Swiper
if (window.Swiper) {
const swiper = section.querySelector('.swiper');
if (swiper && swiper.swiper) {
swiper.swiper.update();
}
}
});
}
});
</script>

View File

@ -0,0 +1,248 @@
<section class="container py-5 my-2 my-sm-3 my-lg-4 my-xl-5" id="popular-products-section">
<div class="row align-items-lg-center py-xxl-3">
<!-- Products -->
<div class="col-md-6 col-xl-5 offset-xl-1 order-md-2 mb-4 mb-md-0">
<div class="ps-md-3 ps-lg-4 ps-xl-0">
<div class="d-flex align-items-center justify-content-between pb-4 mb-md-1 mb-lg-2 mb-xl-3">
<h2 class="me-3 mb-0">Popular products</h2>
<!-- Slider prev/next buttons -->
<div class="d-flex gap-2">
<button type="button"
class="btn btn-icon btn-outline-secondary animate-slide-start rounded-circle me-1"
id="popularPrev" aria-label="Prev">
<i class="ci-chevron-left fs-lg animate-target"></i>
</button>
<button type="button"
class="btn btn-icon btn-outline-secondary animate-slide-end rounded-circle"
id="popularNext" aria-label="Next">
<i class="ci-chevron-right fs-lg animate-target"></i>
</button>
</div>
</div>
<!-- Products master slider -->
<div class="swiper"
data-swiper='{
"spaceBetween": 24,
"loop": true,
"speed": 400,
"controlSlider": "#sliderImages",
"navigation": {
"prevEl": "#popularPrev",
"nextEl": "#popularNext"
}
}'>
<div class="swiper-wrapper" id="popular-products-container">
<!-- Products will be loaded via AJAX -->
</div>
</div>
</div>
</div>
<!-- Images -->
<div class="col-md-6 col-xl-5 order-md-1">
<div class="position-relative">
<div class="ratio d-none d-md-block" style="--cz-aspect-ratio: calc(720 / 636 * 100%)">
</div>
<div class="ratio ratio-4x3 d-md-none"></div>
<div class="swiper position-absolute top-0 start-0 w-100 h-100 user-select-none"
id="sliderImages"
data-swiper='{
"allowTouchMove": false,
"loop": true,
"effect": "fade",
"fadeEffect": {
"crossFade": true
}
}'>
<div class="swiper-wrapper" id="popular-images-container">
<!-- Images will be loaded via AJAX -->
</div>
</div>
</div>
</div>
</div>
<!-- Loading state -->
<div class="text-center py-5 d-none" id="popular-products-loading">
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-4 gy-4 gy-md-5 pb-xxl-3">
@for ($i = 1; $i <= 6; $i++)
<div class="col mb-2 mb-sm-3 mb-md-0">
<div class="shimmer-wrapper">
<div class="shimmer">
<div class="shimmer-content rounded">
<div class="shimmer-line shimmer-image rounded mb-3"
style="height: 0; padding-bottom: 100%; position: relative;">
<div
style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.1); border-radius: 4px;">
</div>
</div>
<div class="shimmer-line shimmer-title rounded mb-2"
style="height: 20px; width: 80%;"></div>
<div class="shimmer-line shimmer-price rounded" style="height: 16px; width: 60%;">
</div>
</div>
</div>
</div>
</div>
@endfor
</div>
</div>
</section>
<script>
document.addEventListener('DOMContentLoaded', function() {
const productsContainer = document.getElementById('popular-products-container');
const imagesContainer = document.getElementById('popular-images-container');
const loadingSpinner = document.getElementById('popular-products-loading');
const section = document.getElementById('popular-products-section');
if (!productsContainer || !imagesContainer || !loadingSpinner) {
return;
}
// Load popular products on page load
loadPopularProducts();
function loadPopularProducts() {
// Show loading spinner
if (loadingSpinner) {
loadingSpinner.classList.remove('d-none');
}
// Make AJAX request for popular products
fetch('{{ route("product.ajax.highlights") }}?type=best_sellers&limit=6')
.then(response => response.json())
.then(data => {
if (data.success && data.products) {
// Parse the HTML to extract individual product cards
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.products;
const productCards = tempDiv.querySelectorAll('.col');
const productsArray = Array.from(productCards);
// Split products into two slides (3 products each)
const slide1 = productsArray.slice(0, 3);
const slide2 = productsArray.slice(3, 6);
// Create slides with product lists
productsContainer.innerHTML = '';
[slide1, slide2].forEach((slide, index) => {
const slideDiv = document.createElement('div');
slideDiv.className = 'swiper-slide';
const listDiv = document.createElement('div');
listDiv.className = 'd-flex flex-column gap-3 gap-lg-4';
slide.forEach(product => {
// Convert product card to list item format
const productImg = product.querySelector('img');
const productLink = product.querySelector('a');
const productPrice = product.querySelector('.h6, .price');
const listItem = document.createElement('div');
listItem.className = 'd-flex align-items-center position-relative bg-body-tertiary rounded overflow-hidden animate-underline';
const img = document.createElement('img');
img.src = productImg ? productImg.src : '/img/shop/fashion/thumbs/0' + (index * 3 + slide.indexOf(product) + 1) + '.png';
img.width = 110;
img.alt = 'Thumbnail';
const navDiv = document.createElement('div');
navDiv.className = 'nav flex-column gap-2 min-w-0 p-3';
const link = document.createElement('a');
link.className = 'nav-link text-dark-emphasis stretched-link w-100 min-w-0 p-0';
link.href = productLink ? productLink.href : '#';
const span = document.createElement('span');
span.className = 'animate-target text-truncate';
span.textContent = productLink ? productLink.textContent.trim() : 'Product Name';
const price = document.createElement('div');
price.className = 'h6 mb-0';
price.textContent = productPrice ? productPrice.textContent : '$0.00';
link.appendChild(span);
navDiv.appendChild(link);
navDiv.appendChild(price);
listItem.appendChild(img);
listItem.appendChild(navDiv);
listDiv.appendChild(listItem);
});
slideDiv.appendChild(listDiv);
productsContainer.appendChild(slideDiv);
});
// Load corresponding images
loadPopularImages();
} else {
// Handle error
productsContainer.innerHTML = '<div class="swiper-slide"><div class="text-center py-4">Error loading products</div></div>';
}
})
.catch(error => {
console.error('Error loading popular products:', error);
productsContainer.innerHTML = '<div class="swiper-slide"><div class="text-center py-4">Error loading products</div></div>';
})
.finally(() => {
// Hide loading spinner
if (loadingSpinner) {
loadingSpinner.classList.add('d-none');
}
// Re-initialize Swiper
if (window.Swiper) {
const swiperElements = section.querySelectorAll('.swiper');
swiperElements.forEach(element => {
if (element.swiper) {
element.swiper.update();
}
});
}
});
}
function loadPopularImages() {
// Load images for the slider
const images = [
'/img/home/fashion/v1/popular/01.jpg',
'/img/home/fashion/v1/popular/02.jpg'
];
imagesContainer.innerHTML = '';
images.forEach((src, index) => {
const slideDiv = document.createElement('div');
slideDiv.className = 'swiper-slide';
const ratioDiv = document.createElement('div');
ratioDiv.className = index === 0 ? 'ratio d-none d-md-block' : 'ratio d-none d-md-block';
ratioDiv.style.setProperty('--cz-aspect-ratio', 'calc(720 / 636 * 100%)');
const mobileRatioDiv = document.createElement('div');
mobileRatioDiv.className = 'ratio ratio-4x3 d-md-none';
const img = document.createElement('img');
img.src = src;
img.className = 'position-absolute top-0 start-0 w-100 h-100 object-fit-cover rounded-5';
img.alt = 'Image';
if (index === 1) {
img.style.objectPosition = 'center top';
}
slideDiv.appendChild(ratioDiv);
slideDiv.appendChild(mobileRatioDiv);
slideDiv.appendChild(img);
imagesContainer.appendChild(slideDiv);
});
}
});
</script>

View File

@ -0,0 +1,99 @@
<section class="bg-body-tertiary">
<div class="container">
<div class="row">
<!-- Titles master slider -->
<div class="col-md-6 col-lg-5 d-flex flex-column">
<div class="py-4 mt-auto">
<div class="swiper pb-1 pt-3 pt-sm-4 py-md-4 py-lg-3"
data-swiper='{
"spaceBetween": 24,
"loop": true,
"speed": 400,
"controlSlider": "#heroImages",
"pagination": {
"el": "#sliderBullets",
"clickable": true
},
"autoplay": {
"delay": 5500,
"disableOnInteraction": false
}
}'>
<div class="swiper-wrapper align-items-center">
<!-- Item -->
<div class="swiper-slide text-center text-md-start">
<p class="fs-xl mb-2 mb-lg-3 mb-xl-4">The new warm collection</p>
<h2 class="display-4 text-uppercase mb-4 mb-xl-5">New fall <br
class="d-none d-md-inline">season 2024</h2>
<a class="btn btn-lg btn-outline-dark" href="{{ route('second', ['shop', 'catalog-fashion']) }}">
Shop now
<i class="ci-arrow-up-right fs-lg ms-2 me-n1"></i>
</a>
</div>
<!-- Item -->
<div class="swiper-slide text-center text-md-start">
<p class="fs-xl mb-2 mb-lg-3 mb-xl-4">Ready for the party?</p>
<h2 class="display-4 text-uppercase mb-4 mb-xl-5">Choose outfits for parties</h2>
<a class="btn btn-lg btn-outline-dark" href="{{ route('second', ['shop', 'catalog-fashion']) }}">
Shop now
<i class="ci-arrow-up-right fs-lg ms-2 me-n1"></i>
</a>
</div>
<!-- Item -->
<div class="swiper-slide text-center text-md-start">
<p class="fs-xl mb-2 mb-lg-3 mb-xl-4">Shades of gray for your look</p>
<h2 class="display-4 text-uppercase mb-4 mb-xl-5">-50% on gray Collection</h2>
<a class="btn btn-lg btn-outline-dark" href="{{ route('second', ['shop', 'catalog-fashion']) }}">
Shop now
<i class="ci-arrow-up-right fs-lg ms-2 me-n1"></i>
</a>
</div>
</div>
</div>
</div>
<!-- Slider bullets (pagination) -->
<div
class="d-flex justify-content-center justify-content-md-start pb-4 pb-xl-5 mt-n1 mt-md-auto mb-md-3 mb-lg-4">
<div class="swiper-pagination position-static w-auto pb-1" id="sliderBullets"></div>
</div>
</div>
<!-- Linked images (controlled slider) -->
<div class="col-md-6 col-lg-7 align-self-end">
<div class="position-relative ms-md-n4">
<div class="ratio" style="--cz-aspect-ratio: calc(662 / 770 * 100%)"></div>
<div class="swiper position-absolute top-0 start-0 w-100 h-100 user-select-none"
id="heroImages"
data-swiper='{
"allowTouchMove": false,
"loop": true,
"effect": "fade",
"fadeEffect": {
"crossFade": true
}
}'>
<div class="swiper-wrapper">
<div class="swiper-slide">
<img src="/img/home/fashion/v1/hero-slider/01.png" class="rtl-flip"
alt="Image">
</div>
<div class="swiper-slide">
<img src="/img/home/fashion/v1/hero-slider/02.png" class="rtl-flip"
alt="Image">
</div>
<div class="swiper-slide">
<img src="/img/home/fashion/v1/hero-slider/03.png" class="rtl-flip"
alt="Image">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>

View File

@ -76,7 +76,7 @@
<span class="text-truncate">{{ $product->name }}</span>
</a>
</div>
<div class="h6 mb-0">{{ number_format($product->price, 0, ',', '.') }}</div>
<div class="h6 mb-0">Rp {{ number_format($product->display_price, 0, ',', '.') }}</div>
</div>
</div>
@endforeach

View File

@ -1,35 +1,204 @@
<section class="container pb-5 mb-2 mb-sm-3 mb-lg-4 mb-xl-5">
<h2 class="text-center pb-2 pb-sm-3">{{ __('weeks_highlights.this_weeks_highlights') }}</h2>
<style>
.shimmer-wrapper-highlight {
overflow: hidden;
}
<!-- Nav pills -->
<div class="row g-0 overflow-x-auto pb-2 pb-sm-3 mb-3">
<div class="col-auto pb-1 pb-sm-0 mx-auto">
<ul class="nav nav-pills flex-nowrap text-nowrap">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#!">{{ __('weeks_highlights.best_sellers') }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#!">{{ __('weeks_highlights.new_arrivals') }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#!">{{ __('weeks_highlights.sale_items') }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#!">{{ __('weeks_highlights.top_rated') }}</a>
</li>
</ul>
</div>
</div>
.shimmer-wrapper-highlight .shimmer {
position: relative;
overflow: hidden;
}
<!-- Products grid -->
.shimmer-wrapper-highlight .shimmer::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(90deg,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.3) 50%,
rgba(255, 255, 255, 0) 100%);
animation: shimmer-wrapper-highlight-shimmer 1.5s infinite;
}
@keyframes shimmer-wrapper-highlight-shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
.shimmer-wrapper-highlight .shimmer-line {
background-color: rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
.shimmer-wrapper-highlight .shimmer-content {
position: relative;
overflow: hidden;
}
</style>
<section class="container pb-5 mb-2 mb-sm-3 mb-lg-4 mb-xl-5" id="product-highlights-section">
<h2 class="text-center pb-2 pb-sm-3">{{ __('weeks_highlights.this_weeks_highlights') }}</h2>
<div class="row g-0 overflow-x-auto pb-2 pb-sm-3 mb-3">
<div class="col-auto pb-1 pb-sm-0 mx-auto">
<ul class="nav nav-pills flex-nowrap text-nowrap" id="product-highlights-tabs">
<li class="nav-item">
<a class="nav-link active" href="#" data-type="best_sellers"
data-url="{{ route('product.ajax.highlights') }}">
{{ __('weeks_highlights.best_sellers') }}
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" data-type="new_arrivals"
data-url="{{ route('product.ajax.highlights') }}">
{{ __('weeks_highlights.new_arrivals') }}
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" data-type="sale_items"
data-url="{{ route('product.ajax.highlights') }}">
{{ __('weeks_highlights.sale_items') }}
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" data-type="top_rated"
data-url="{{ route('product.ajax.highlights') }}">
{{ __('weeks_highlights.top_rated') }}
</a>
</li>
</ul>
</div>
</div>
<div class="position-relative">
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-4 gy-4 gy-md-5 pb-xxl-3" id="product-highlights-container">
</div>
<div class="text-center py-5 d-none" id="product-highlights-loading">
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-4 gy-4 gy-md-5 pb-xxl-3">
<!-- Item -->
@foreach ($products as $key => $product)
<div class="col mb-2 mb-sm-3 mb-md-0">
<x-home.product-card :product="$product" />
</div>
@endforeach
@for ($i = 1; $i <= 8; $i++)
<div class="col-6 col-md-4 mb-2 mb-sm-3 mb-md-0">
<div class="shimmer-wrapper-highlight">
<div class="shimmer">
<div class="shimmer-content rounded">
<div class="shimmer-line shimmer-image rounded mb-3"
style="height: 0; padding-bottom: 100%; position: relative;">
<div
style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.1); border-radius: 4px;">
</div>
</div>
<div class="shimmer-line shimmer-title rounded mb-2"
style="height: 20px; width: 80%;"></div>
<div class="shimmer-line shimmer-price rounded" style="height: 16px; width: 60%;">
</div>
</div>
</div>
</div>
</div>
@endfor
</div>
</section>
</div>
</div>
</section>
<script>
/*!
* Product Highlights AJAX functionality
* Handles tab switching and AJAX loading for product highlights section
*/
document.addEventListener('DOMContentLoaded', function() {
const tabsContainer = document.getElementById('product-highlights-tabs');
const productsContainer = document.getElementById('product-highlights-container');
const loadingSpinner = document.getElementById('product-highlights-loading');
if (!tabsContainer || !productsContainer || !loadingSpinner) {
return;
}
// Load first tab content on page load
const firstTab = tabsContainer.querySelector('.nav-link.active');
if (firstTab) {
const type = firstTab.dataset.type;
const url = firstTab.dataset.url;
if (type && url) {
loadProductHighlights(type, url);
}
}
// Handle tab clicks
tabsContainer.addEventListener('click', function(e) {
e.preventDefault();
const tab = e.target.closest('.nav-link');
if (!tab) return;
// Get the type and URL from data attributes
const type = tab.dataset.type;
const url = tab.dataset.url;
if (!type || !url) return;
// Update active tab
tabsContainer.querySelectorAll('.nav-link').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
// Load products via AJAX
loadProductHighlights(type, url);
});
function loadProductHighlights(type, url) {
// Show loading spinner
productsContainer.style.display = 'none';
loadingSpinner.classList.remove('d-none');
// Build URL with parameters
const requestUrl = `${url}?type=${type}`;
// Make AJAX request
fetch(requestUrl)
.then(response => response.json())
.then(data => {
if (data.success) {
// Update products container with new HTML
productsContainer.innerHTML = data.products;
} else {
// Handle error
productsContainer.innerHTML =
'<div class="col-12 text-center py-4">Error loading products</div>';
}
})
.catch(error => {
console.error('Error loading product highlights:', error);
productsContainer.innerHTML =
'<div class="col-12 text-center py-4">Error loading products</div>';
})
.finally(() => {
// Hide loading spinner and show products
loadingSpinner.classList.add('d-none');
productsContainer.style.display = 'flex';
// Re-initialize any components that might be needed for the new products
if (window.bootstrap && window.bootstrap.Tooltip) {
const tooltipTriggerList = [].slice.call(productsContainer.querySelectorAll(
'[data-bs-toggle="tooltip"]'));
tooltipTriggerList.map(function(tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
}
});
}
});
</script>

View File

@ -8,267 +8,11 @@
<main class="content-wrapper">
<!-- Hero slider -->
<section class="bg-body-tertiary">
<div class="container">
<div class="row">
<!-- Titles master slider -->
<div class="col-md-6 col-lg-5 d-flex flex-column">
<div class="py-4 mt-auto">
<div class="swiper pb-1 pt-3 pt-sm-4 py-md-4 py-lg-3"
data-swiper='{
"spaceBetween": 24,
"loop": true,
"speed": 400,
"controlSlider": "#heroImages",
"pagination": {
"el": "#sliderBullets",
"clickable": true
},
"autoplay": {
"delay": 5500,
"disableOnInteraction": false
}
}'>
<div class="swiper-wrapper align-items-center">
<!-- Item -->
<div class="swiper-slide text-center text-md-start">
<p class="fs-xl mb-2 mb-lg-3 mb-xl-4">The new warm collection</p>
<h2 class="display-4 text-uppercase mb-4 mb-xl-5">New fall <br
class="d-none d-md-inline">season 2024</h2>
<a class="btn btn-lg btn-outline-dark" href="{{ route('second', ['shop', 'catalog-fashion']) }}">
Shop now
<i class="ci-arrow-up-right fs-lg ms-2 me-n1"></i>
</a>
</div>
<!-- Item -->
<div class="swiper-slide text-center text-md-start">
<p class="fs-xl mb-2 mb-lg-3 mb-xl-4">Ready for the party?</p>
<h2 class="display-4 text-uppercase mb-4 mb-xl-5">Choose outfits for parties</h2>
<a class="btn btn-lg btn-outline-dark" href="{{ route('second', ['shop', 'catalog-fashion']) }}">
Shop now
<i class="ci-arrow-up-right fs-lg ms-2 me-n1"></i>
</a>
</div>
<!-- Item -->
<div class="swiper-slide text-center text-md-start">
<p class="fs-xl mb-2 mb-lg-3 mb-xl-4">Shades of gray for your look</p>
<h2 class="display-4 text-uppercase mb-4 mb-xl-5">-50% on gray Collection</h2>
<a class="btn btn-lg btn-outline-dark" href="{{ route('second', ['shop', 'catalog-fashion']) }}">
Shop now
<i class="ci-arrow-up-right fs-lg ms-2 me-n1"></i>
</a>
</div>
</div>
</div>
</div>
<!-- Slider bullets (pagination) -->
<div
class="d-flex justify-content-center justify-content-md-start pb-4 pb-xl-5 mt-n1 mt-md-auto mb-md-3 mb-lg-4">
<div class="swiper-pagination position-static w-auto pb-1" id="sliderBullets"></div>
</div>
</div>
<!-- Linked images (controlled slider) -->
<div class="col-md-6 col-lg-7 align-self-end">
<div class="position-relative ms-md-n4">
<div class="ratio" style="--cz-aspect-ratio: calc(662 / 770 * 100%)"></div>
<div class="swiper position-absolute top-0 start-0 w-100 h-100 user-select-none"
id="heroImages"
data-swiper='{
"allowTouchMove": false,
"loop": true,
"effect": "fade",
"fadeEffect": {
"crossFade": true
}
}'>
<div class="swiper-wrapper">
<div class="swiper-slide">
<img src="/img/home/fashion/v1/hero-slider/01.png" class="rtl-flip"
alt="Image">
</div>
<div class="swiper-slide">
<img src="/img/home/fashion/v1/hero-slider/02.png" class="rtl-flip"
alt="Image">
</div>
<div class="swiper-slide">
<img src="/img/home/fashion/v1/hero-slider/03.png" class="rtl-flip"
alt="Image">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<x-home.home-slider />
<!-- Popular products carousel -->
<section class="container py-5 my-2 my-sm-3 my-lg-4 my-xl-5">
<div class="row align-items-lg-center py-xxl-3">
<!-- Products -->
<div class="col-md-6 col-xl-5 offset-xl-1 order-md-2 mb-4 mb-md-0">
<div class="ps-md-3 ps-lg-4 ps-xl-0">
<div class="d-flex align-items-center justify-content-between pb-4 mb-md-1 mb-lg-2 mb-xl-3">
<h2 class="me-3 mb-0">Popular products</h2>
<!-- Slider prev/next buttons -->
<div class="d-flex gap-2">
<button type="button"
class="btn btn-icon btn-outline-secondary animate-slide-start rounded-circle me-1"
id="popularPrev" aria-label="Prev">
<i class="ci-chevron-left fs-lg animate-target"></i>
</button>
<button type="button"
class="btn btn-icon btn-outline-secondary animate-slide-end rounded-circle"
id="popularNext" aria-label="Next">
<i class="ci-chevron-right fs-lg animate-target"></i>
</button>
</div>
</div>
<!-- Products master slider -->
<div class="swiper"
data-swiper='{
"spaceBetween": 24,
"loop": true,
"speed": 400,
"controlSlider": "#sliderImages",
"navigation": {
"prevEl": "#popularPrev",
"nextEl": "#popularNext"
}
}'>
<div class="swiper-wrapper">
<!-- Products list slide -->
<div class="swiper-slide">
<div class="d-flex flex-column gap-3 gap-lg-4">
<div
class="d-flex align-items-center position-relative bg-body-tertiary rounded overflow-hidden animate-underline">
<img src="/img/shop/fashion/thumbs/01.png" width="110" alt="Thumbnail">
<div class="nav flex-column gap-2 min-w-0 p-3">
<a class="nav-link text-dark-emphasis stretched-link w-100 min-w-0 p-0"
href="{{ route('second', ['shop', 'product-fashion']) }}">
<span class="animate-target text-truncate">Short jacket in long-pile
faux fur</span>
</a>
<div class="h6 mb-0">$218.00</div>
</div>
</div>
<div
class="d-flex align-items-center position-relative bg-body-tertiary rounded overflow-hidden animate-underline">
<img src="/img/shop/fashion/thumbs/02.png" width="110" alt="Thumbnail">
<div class="nav flex-column gap-2 min-w-0 p-3">
<a class="nav-link text-dark-emphasis stretched-link w-100 min-w-0 p-0"
href="{{ route('second', ['shop', 'product-fashion']) }}">
<span class="animate-target text-truncate">Women's walking shoes tennis
sneakers</span>
</a>
<div class="h6 mb-0">$54.99</div>
</div>
</div>
<div
class="d-flex align-items-center position-relative bg-body-tertiary rounded overflow-hidden animate-underline">
<img src="/img/shop/fashion/thumbs/03.png" width="110" alt="Thumbnail">
<div class="nav flex-column gap-2 min-w-0 p-3">
<a class="nav-link text-dark-emphasis stretched-link w-100 min-w-0 p-0"
href="{{ route('second', ['shop', 'product-fashion']) }}">
<span class="animate-target text-truncate">Classic aviator sunglasses
for women</span>
</a>
<div class="h6 mb-0">$76.00</div>
</div>
</div>
</div>
</div>
<!-- Products list slide -->
<div class="swiper-slide">
<div class="d-flex flex-column gap-3 gap-lg-4">
<div
class="d-flex align-items-center position-relative bg-body-tertiary rounded overflow-hidden animate-underline">
<img src="/img/shop/fashion/thumbs/04.png" width="110" alt="Thumbnail">
<div class="nav flex-column gap-2 min-w-0 p-3">
<a class="nav-link text-dark-emphasis stretched-link w-100 min-w-0 p-0"
href="{{ route('second', ['shop', 'product-fashion']) }}">
<span class="animate-target text-truncate">Vintage oversized wool
blazer jacket</span>
</a>
<div class="h6 mb-0">$174.00</div>
</div>
</div>
<div
class="d-flex align-items-center position-relative bg-body-tertiary rounded overflow-hidden animate-underline">
<img src="/img/shop/fashion/thumbs/05.png" width="110" alt="Thumbnail">
<div class="nav flex-column gap-2 min-w-0 p-3">
<a class="nav-link text-dark-emphasis stretched-link w-100 min-w-0 p-0"
href="{{ route('second', ['shop', 'product-fashion']) }}">
<span class="animate-target text-truncate">Classic pilot style
polarized sunglasses</span>
</a>
<div class="h6 mb-0">$93.00</div>
</div>
</div>
<div
class="d-flex align-items-center position-relative bg-body-tertiary rounded overflow-hidden animate-underline">
<img src="/img/shop/fashion/thumbs/06.png" width="110" alt="Thumbnail">
<div class="nav flex-column gap-2 min-w-0 p-3">
<a class="nav-link text-dark-emphasis stretched-link w-100 min-w-0 p-0"
href="{{ route('second', ['shop', 'product-fashion']) }}">
<span class="animate-target text-truncate">Cotton dress straight-leg
pants</span>
</a>
<div class="h6 mb-0">$65.00</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Complete look images (controlled slider) -->
<div class="col-md-6 order-md-1">
<div class="swiper user-select-none" id="sliderImages"
data-swiper='{
"allowTouchMove": false,
"loop": true,
"effect": "fade",
"fadeEffect": {
"crossFade": true
}
}'>
<div class="swiper-wrapper">
<div class="swiper-slide">
<div class="ratio d-none d-md-block" style="--cz-aspect-ratio: calc(720 / 636 * 100%)">
</div>
<div class="ratio ratio-4x3 d-md-none"></div>
<img src="/img/home/fashion/v1/popular/01.jpg"
class="position-absolute top-0 start-0 w-100 h-100 object-fit-cover rounded-5"
alt="Image">
</div>
<div class="swiper-slide">
<div class="ratio d-none d-md-block" style="--cz-aspect-ratio: calc(720 / 636 * 100%)">
</div>
<div class="ratio ratio-4x3 d-md-none"></div>
<img src="/img/home/fashion/v1/popular/02.jpg"
class="position-absolute top-0 start-0 w-100 h-100 object-fit-cover rounded-5"
style="object-position: center top" alt="Image">
</div>
</div>
</div>
</div>
</div>
</section>
<x-home.home-popular-products />
<!-- Featured products -->

View File

@ -7,6 +7,7 @@ use App\Http\Controllers\LocationController;
use App\Http\Controllers\LocaleController;
use App\Http\Controllers\ProductController;
use App\Http\Controllers\SearchController;
use App\Http\Controllers\ComponentController;
Route::group(['prefix' => '/dummy'], function () {
Route::get('', [RoutingController::class, 'index'])->name('root');
@ -26,9 +27,14 @@ Route::get('/', [HomeController::class, 'index'])->name('home');
Route::get('/products',[ProductController::class, 'index'])->name('product.index');
Route::get('/products/ajax',[ProductController::class, 'ajax'])->name('product.ajax');
Route::get('/products/ajax/highlights',[ProductController::class, 'highlights'])->name('product.ajax.highlights');
Route::get('/products/ajax/brands',[ProductController::class, 'brands'])->name('product.ajax.brands');
Route::get('/products/ajax/categories',[ProductController::class, 'categories'])->name('product.ajax.categories');
Route::get('/products/ajax/genders',[ProductController::class, 'genders'])->name('product.ajax.genders');
Route::get('/product/{slug}',[ProductController::class, 'detail'])->name('product.detail');
// Search routes
Route::get('/search', [SearchController::class, 'search'])->name('search.ajax');
Route::get('/search', [SearchController::class, 'search'])->name('search.ajax');
// Component loading routes
Route::get('/components/{component}', [ComponentController::class, 'load'])->name('component.load');