top header annoucement

This commit is contained in:
Bayu Lukman Yusuf 2026-01-09 13:48:19 +07:00
parent 3a66f08579
commit 36a763d6c0
5 changed files with 160 additions and 41 deletions

View File

@ -209,6 +209,41 @@ class ProductController extends Controller
]);
}
public function announcements(Request $request)
{
// Static announcements for now - can be moved to database later
$announcements = [
[
'icon' => '🎉',
'message' => 'Free Shipping on orders over $250',
'detail' => "Don't miss a discount!"
],
[
'icon' => '💰',
'message' => 'Money back guarantee',
'detail' => 'We return money within 30 days'
],
[
'icon' => '💪',
'message' => 'Friendly 24/7 customer support',
'detail' => "We've got you covered!"
]
];
// Render announcement slides HTML
$announcementHtml = '';
foreach ($announcements as $announcement) {
$announcementHtml .= '<div class="swiper-slide text-truncate text-center">';
$announcementHtml .= $announcement['icon'] . ' ' . $announcement['message'] . ' <span class="d-none d-sm-inline">' . $announcement['detail'] . '</span>';
$announcementHtml .= '</div>';
}
return response()->json([
'success' => true,
'announcements' => $announcementHtml
]);
}
public function highlights(Request $request)
{
$type = $request->input('type', 'new');

View File

@ -0,0 +1,25 @@
<?php
namespace App\View\Components\Layout;
use Illuminate\View\Component;
use Illuminate\View\View;
class TopAnnouncementHeader extends Component
{
/**
* Create a new component instance.
*/
public function __construct()
{
//
}
/**
* Get the view/contents that represent the component.
*/
public function render(): View|string
{
return view('components.layout.top-announcement-header');
}
}

View File

@ -145,47 +145,7 @@
</div>
</div>
<div class="alert alert-dismissible bg-dark text-white rounded-0 py-2 px-0 m-0 fade show" data-bs-theme="dark">
<div class="container position-relative d-flex min-w-0">
<div class="d-flex flex-nowrap align-items-center g-2 w-100 min-w-0 mx-auto mt-n1"
style="max-width: 458px">
<div class="nav me-2">
<button type="button" class="nav-link fs-lg p-0" id="topbarPrev" aria-label="Prev">
<i class="ci-chevron-left"></i>
</button>
</div>
<div class="swiper fs-sm text-white"
data-swiper='{
"spaceBetween": 24,
"loop": true,
"autoplay": {
"delay": 5000,
"disableOnInteraction": false
},
"navigation": {
"prevEl": "#topbarPrev",
"nextEl": "#topbarNext"
}
}'>
<div class="swiper-wrapper min-w-0">
<div class="swiper-slide text-truncate text-center">🎉 Free Shipping on orders over $250. <span
class="d-none d-sm-inline">Don't miss a discount!</span></div>
<div class="swiper-slide text-truncate text-center">💰 Money back guarantee. <span
class="d-none d-sm-inline">We return money within 30 days.</span></div>
<div class="swiper-slide text-truncate text-center">💪 Friendly 24/7 customer support. <span
class="d-none d-sm-inline">We've got you covered!</span></div>
</div>
</div>
<div class="nav ms-2">
<button type="button" class="nav-link fs-lg p-0" id="topbarNext" aria-label="Next">
<i class="ci-chevron-right"></i>
</button>
</div>
</div>
<button type="button" class="btn-close position-static flex-shrink-0 p-1 ms-3 ms-md-n4"
data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</div>
<x-layout.top-announcement-header />
<header class="navbar navbar-expand-lg navbar-sticky bg-body d-block z-fixed p-0"
data-sticky-navbar='{"offset": 500}'>
<div class="container py-2 py-lg-3">

View File

@ -0,0 +1,98 @@
<div class="alert alert-dismissible bg-dark text-white rounded-0 py-2 px-0 m-0 d-none" id="top-announcement-bar" data-bs-theme="dark">
<div class="container position-relative d-flex min-w-0">
<div class="d-flex flex-nowrap align-items-center g-2 w-100 min-w-0 mx-auto mt-n1" style="max-width: 458px">
<div class="nav me-2">
<button type="button" class="nav-link fs-lg p-0" id="topbarPrev" aria-label="Prev">
<i class="ci-chevron-left"></i>
</button>
</div>
<div class="swiper fs-sm text-white"
data-swiper='{
"spaceBetween": 24,
"loop": true,
"autoplay": {
"delay": 5000,
"disableOnInteraction": false
},
"navigation": {
"prevEl": "#topbarPrev",
"nextEl": "#topbarNext"
}
}'>
<div class="swiper-wrapper min-w-0" id="announcements-wrapper">
<!-- Announcements will be loaded here via AJAX -->
</div>
</div>
<div class="nav ms-2">
<button type="button" class="nav-link fs-lg p-0" id="topbarNext" aria-label="Next">
<i class="ci-chevron-right"></i>
</button>
</div>
</div>
<button type="button" class="btn-close position-static flex-shrink-0 p-1 ms-3 ms-md-n4" data-bs-dismiss="alert"
aria-label="Close"></button>
</div>
</div>
<style>
#top-announcement-bar {
transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
opacity: 0;
transform: translateY(-100%);
}
#top-announcement-bar.show-with-animation {
opacity: 1;
transform: translateY(0);
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
loadAnnouncements();
});
function loadAnnouncements() {
fetch(`{{ route('product.ajax.announcements') }}`, {
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.success && data.announcements.trim() !== '') {
document.getElementById('announcements-wrapper').innerHTML = data.announcements;
const announcementBar = document.getElementById('top-announcement-bar');
announcementBar.classList.remove('d-none');
// Trigger animation
setTimeout(() => {
announcementBar.classList.add('show-with-animation');
}, 10);
// Reinitialize swiper if it exists
if (typeof Swiper !== 'undefined') {
const swiperElement = document.querySelector('.swiper');
if (swiperElement && swiperElement.swiper) {
swiperElement.swiper.update();
}
}
} else {
// Hide announcement bar if no data
const announcementBar = document.getElementById('top-announcement-bar');
if (announcementBar) {
announcementBar.classList.add('d-none');
}
}
})
.catch(error => {
console.error('Error loading announcements:', error);
// Hide announcement bar on error
const announcementBar = document.getElementById('top-announcement-bar');
if (announcementBar) {
announcementBar.classList.add('d-none');
}
});
}
</script>

View File

@ -31,6 +31,7 @@ Route::get('/products/ajax/highlights',[ProductController::class, '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('/products/ajax/announcements',[ProductController::class, 'announcements'])->name('product.ajax.announcements');
Route::get('/product/{slug}',[ProductController::class, 'detail'])->name('product.detail');
// Search routes