summaryrefslogtreecommitdiff
path: root/src/lib/home
diff options
context:
space:
mode:
authorit-fixcomart <it@fixcomart.co.id>2025-07-29 09:46:05 +0700
committerit-fixcomart <it@fixcomart.co.id>2025-07-29 09:46:05 +0700
commit077467cf53b46d8049df8b812577cd1a03011eba (patch)
tree0dc641a9acb1237a3caca3f7f8a157a3e938c0b8 /src/lib/home
parent0d28dc8ff5fb8c5399e356ed6ecae4fce2019ca6 (diff)
parentdc31efb2fec4c7b79917324d922ae820c4b5bb50 (diff)
<hafid> merging new release
Diffstat (limited to 'src/lib/home')
-rw-r--r--src/lib/home/components/BannerSection.jsx158
-rw-r--r--src/lib/home/components/CategoryHomeId.jsx2
-rw-r--r--src/lib/home/components/PopupBannerPromotion.jsx260
3 files changed, 372 insertions, 48 deletions
diff --git a/src/lib/home/components/BannerSection.jsx b/src/lib/home/components/BannerSection.jsx
index 898f1bf5..1eac9592 100644
--- a/src/lib/home/components/BannerSection.jsx
+++ b/src/lib/home/components/BannerSection.jsx
@@ -1,62 +1,126 @@
import Link from '@/core/components/elements/Link/Link';
-import Image from 'next/image';
import { useEffect, useState } from 'react';
-import { bannerApi } from '../../../api/bannerApi';
-
-const { useQuery } = require('react-query');
-const { default: bannerSectionApi } = require('../api/bannerSectionApi');
+import useDevice from '@/core/hooks/useDevice';
+import { Swiper, SwiperSlide } from 'swiper/react';
const BannerSection = () => {
- const [data, setData] = useState(null);
- const [shouldFetch, setShouldFetch] = useState(false);
-
+ const [privateBrandData, setPrivateBrandData] = useState([]);
+ const [homeBannerData, setHomeBannerData] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const { isMobile, isDesktop } = useDevice();
useEffect(() => {
- const fetchCategoryData = async () => {
- const res = await fetch('/api/banner-section');
- const { data } = await res.json();
- if (data) {
- setData(data);
+ const fetchAllBanners = async () => {
+ try {
+ // Fetch private brand banners
+ const privateBrandRes = await fetch(
+ '/api/banner-section?type=private-brand'
+ );
+ if (privateBrandRes.ok) {
+ const privateBrandResult = await privateBrandRes.json();
+ setPrivateBrandData(privateBrandResult.data || []);
+ }
+
+ // Fetch home banners
+ const homeBannerRes = await fetch(
+ '/api/banner-section?type=home-banner'
+ );
+ if (homeBannerRes.ok) {
+ const homeBannerResult = await homeBannerRes.json();
+ setHomeBannerData(homeBannerResult.data || []);
+ }
+ } catch (err) {
+ setError('Network error');
+ } finally {
+ setLoading(false);
}
};
- fetchCategoryData();
+ fetchAllBanners();
}, []);
- // const fetchBannerSection = async () => await bannerSectionApi();
- const getBannerSection = useQuery(
- 'bannerSection',
- bannerApi({ type: 'home-banner' }),
- {
- enabled: shouldFetch,
- onSuccess: (data) => {
- if (data) {
- localStorage.setItem('Homepage_bannerSection', JSON.stringify(data));
- setData(data);
- }
- },
- }
- );
+ // if (loading) return <div>Loading...</div>;
+ // if (error) return <div>Error: {error}</div>;
- const bannerSection = data;
return (
- bannerSection &&
- bannerSection?.length > 0 && (
- <div className='grid grid-cols-1 sm:grid-cols-2 gap-4'>
- {bannerSection?.map((banner) => (
- <Link key={banner.id} href={banner.url}>
- <Image
- width={1024}
- height={512}
- quality={85}
- src={banner.image}
- alt={banner.name}
- className='h-auto w-full rounded'
- loading='eager'
- />
- </Link>
- ))}
- </div>
- )
+ <div className='space-y-12'>
+ {/* Private Brand Section */}
+ {privateBrandData && privateBrandData.length > 0 && (
+ <div className='px-4 sm:px-0'>
+ <div
+ className='text-black font-semibold sm:text-h-lg mb-6'
+ id='private-brand'
+ >
+ Private Brand
+ </div>
+
+ {/* Desktop Grid View */}
+ {isDesktop && (
+ <div className='grid grid-cols-3 sm:grid-cols-3 gap-4 rounded-md'>
+ {privateBrandData.map((banner, index) => (
+ <Link key={banner.id || index} href={banner.url || '#'}>
+ <img
+ width={439}
+ height={150}
+ quality={85}
+ src={banner.image}
+ alt={banner.name || `Private Brand Banner ${index + 1}`}
+ className='rounded hover:scale-105 transition duration-500 ease-in-out'
+ loading='eager'
+ />
+ </Link>
+ ))}
+ </div>
+ )}
+
+ {/* Mobile Swiper View */}
+ {isMobile && (
+ <Swiper slidesPerView={1.1} spaceBetween={8} freeMode>
+ {privateBrandData.map((banner, index) => (
+ <SwiperSlide key={banner.id || index}>
+ <Link href={banner.url || '#'}>
+ <img
+ width={350}
+ height={100}
+ quality={70}
+ src={banner.image}
+ alt={banner.name || `Private Brand Banner ${index + 1}`}
+ className='rounded'
+ loading='eager'
+ />
+ </Link>
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ )}
+ </div>
+ )}
+
+ {/* Home Banner Section */}
+ {homeBannerData && homeBannerData.length > 0 && (
+ <div className='grid grid-cols-1 sm:grid-cols-2 gap-4'>
+ {homeBannerData.map((banner, index) => (
+ <div key={banner.id || index} className='relative'>
+ <Link href={banner.url || '#'}>
+ <img
+ width={1024}
+ height={512}
+ quality={85}
+ src={banner.image}
+ alt={banner.name || `Home Banner ${index + 1}`}
+ // className='h-40 w-full rounded object-cover transition-transform hover:scale-105'
+ className='h-auto w-full rounded'
+ loading='eager'
+ onError={(e) => {
+ e.target.style.display = 'none';
+ }}
+ />
+ </Link>
+ </div>
+ ))}
+ </div>
+ )}
+ </div>
);
};
diff --git a/src/lib/home/components/CategoryHomeId.jsx b/src/lib/home/components/CategoryHomeId.jsx
index 9f436dac..db473a1c 100644
--- a/src/lib/home/components/CategoryHomeId.jsx
+++ b/src/lib/home/components/CategoryHomeId.jsx
@@ -8,7 +8,7 @@ const CategoryHomeId = () => {
return (
<div>
<h1 className='font-semibold text-[14px] sm:text-h-lg mb-6 px-4 sm:px-0'>
- Kategori Pilihan
+ Paket Bundling / Paket UMKM
</h1>
<div className='flex flex-col gap-y-10'>
{categoryHomeIds.data?.map((id) => (
diff --git a/src/lib/home/components/PopupBannerPromotion.jsx b/src/lib/home/components/PopupBannerPromotion.jsx
new file mode 100644
index 00000000..538f35e6
--- /dev/null
+++ b/src/lib/home/components/PopupBannerPromotion.jsx
@@ -0,0 +1,260 @@
+import { useRouter } from 'next/router';
+import { useEffect, useState, useRef } from 'react';
+import Image from 'next/image';
+import Link from 'next/link';
+import { X } from 'lucide-react';
+import { getAuth } from '~/libs/auth';
+import useDevice from '@/core/hooks/useDevice';
+import { createPortal } from 'react-dom';
+
+const PagePopupInformation = () => {
+ const router = useRouter();
+ const isHomePage = router.pathname === '/';
+ const auth = getAuth();
+ const { isDesktop } = useDevice();
+
+ const [active, setActive] = useState(false);
+ const [data, setData] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ const popupRef = useRef(null);
+ const [position, setPosition] = useState({ x: 20, y: window.innerHeight - 170 });
+ const dragStartPos = useRef({ x: 0, y: 0 });
+ const isDragging = useRef(false);
+ const isTouching = useRef(false);
+ const [isSnapping, setIsSnapping] = useState(false);
+ const [containerLeft, setContainerLeft] = useState(0);
+
+ const [isClient, setIsClient] = useState(false);
+
+ useEffect(() => {
+ setIsClient(true);
+ }, []);
+
+ useEffect(() => {
+ if (isHomePage && !auth) {
+ setActive(true);
+ fetchData();
+ }
+ }, [isHomePage, auth]);
+
+ const fetchData = async () => {
+ try {
+ const res = await fetch(`/api/hero-banner?type=dragable-banner`);
+ const { data } = await res.json();
+ if (Array.isArray(data) && data[0]?.image) {
+ setData(data[0]);
+ } else {
+ setActive(false);
+ }
+ } catch (error) {
+ console.error('Failed to fetch popup banner:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ const handleResizeOrZoom = () => {
+ if (!popupRef.current) return;
+
+ const popupWidth = popupRef.current.offsetWidth || 85;
+ const popupHeight = popupRef.current.offsetHeight || 85;
+
+ setPosition({
+ x: 20,
+ y: window.innerHeight - popupHeight - 20,
+ });
+ };
+
+ window.addEventListener('resize', handleResizeOrZoom);
+
+ return () => {
+ window.removeEventListener('resize', handleResizeOrZoom);
+ };
+ }, []);
+
+
+ const updateContainerLeft = () => {
+ const container = document.querySelector('.container');
+ if (container) {
+ setContainerLeft(container.getBoundingClientRect().left);
+ }
+ };
+
+ useEffect(() => {
+ updateContainerLeft();
+ window.addEventListener('resize', updateContainerLeft);
+ window.addEventListener('scroll', updateContainerLeft);
+ return () => {
+ window.removeEventListener('resize', updateContainerLeft);
+ window.removeEventListener('scroll', updateContainerLeft);
+ };
+ }, []);
+
+ useEffect(() => {
+ if (isDesktop && containerLeft) {
+ const popupWidth = popupRef.current?.offsetWidth || 85;
+ setPosition({
+ x: containerLeft - popupWidth - 20,
+ y: window.innerHeight - 130,
+ });
+ }
+ }, [isDesktop, containerLeft]);
+
+ const startDrag = (clientX, clientY) => {
+ dragStartPos.current = { x: clientX - position.x, y: clientY - position.y };
+ isDragging.current = false;
+ setIsSnapping(false);
+ };
+
+ const moveDrag = (clientX, clientY) => {
+ isDragging.current = true;
+ const popupWidth = popupRef.current?.offsetWidth || 85;
+ const popupHeight = popupRef.current?.offsetHeight || 85;
+ const maxX = window.innerWidth - popupWidth - 20;
+
+ const topPadding = isDesktop ? 0 : 130;
+ const bottomPadding = isDesktop ? 0 : 80;
+ const maxY = window.innerHeight - popupHeight - bottomPadding;
+
+ const minX = 0;
+
+ let newX = clientX - dragStartPos.current.x;
+ let newY = clientY - dragStartPos.current.y;
+
+ newX = Math.max(minX, Math.min(newX, maxX));
+ newY = Math.max(topPadding, Math.min(newY, maxY));
+
+ setPosition({ x: newX, y: newY });
+ };
+
+ const endDrag = () => {
+ if (isDragging.current) {
+ setIsSnapping(true);
+
+ if (isDesktop) {
+ const popupWidth = popupRef.current?.offsetWidth || 85;
+ setPosition({
+ x: containerLeft - popupWidth - 20,
+ y: window.innerHeight - 130,
+ });
+ } else {
+ const popupWidth = popupRef.current?.offsetWidth || 85;
+ const screenMiddle = window.innerWidth / 2;
+
+ setPosition(pos => ({
+ x: pos.x + popupWidth / 2 < screenMiddle ? 20 : window.innerWidth - popupWidth - 20,
+ y: pos.y,
+ }));
+ }
+ }
+ isDragging.current = false;
+ isTouching.current = false;
+ };
+
+
+
+ const handleMouseDown = e => {
+ e.preventDefault();
+ startDrag(e.clientX, e.clientY);
+ window.addEventListener('mousemove', handleMouseMove);
+ window.addEventListener('mouseup', handleMouseUp);
+ };
+
+ const handleMouseMove = e => moveDrag(e.clientX, e.clientY);
+
+ const handleMouseUp = () => {
+ endDrag();
+ window.removeEventListener('mousemove', handleMouseMove);
+ window.removeEventListener('mouseup', handleMouseUp);
+ };
+
+ const handleTouchStart = e => {
+ if (e.touches.length === 1) {
+ isTouching.current = true;
+ startDrag(e.touches[0].clientX, e.touches[0].clientY);
+ }
+ };
+
+ useEffect(() => {
+ const handleTouchMove = e => {
+ if (isTouching.current) {
+ e.preventDefault();
+ moveDrag(e.touches[0].clientX, e.touches[0].clientY);
+ }
+ };
+
+ const handleTouchEnd = () => endDrag();
+
+ window.addEventListener('touchmove', handleTouchMove, { passive: false });
+ window.addEventListener('touchend', handleTouchEnd);
+ return () => {
+ window.removeEventListener('touchmove', handleTouchMove);
+ window.removeEventListener('touchend', handleTouchEnd);
+ };
+ }, []);
+
+ if (!active || !data || loading) return null;
+
+ const popupContent = (
+ <div
+ ref={popupRef}
+ className={`relative pointer-events-auto ${isSnapping ? 'transition-transform duration-300 ease-out' : ''}`}
+ style={{
+ transform: `translate(${position.x}px, ${position.y}px)`,
+ cursor: 'grab',
+ width: '85px',
+ }}
+ onMouseDown={handleMouseDown}
+ onTouchStart={handleTouchStart}
+ >
+ <Link
+ href={typeof data.url === 'boolean' && data.url === false ? '/' : data.url}
+ onClick={e => {
+ if (isDragging.current) {
+ e.preventDefault();
+ isDragging.current = false;
+ } else {
+ setActive(false);
+ }
+ }}
+ draggable="false"
+ >
+ <Image
+ src={data.image}
+ alt={data.name || 'popup'}
+ width={85}
+ height={85}
+ className="w-full h-auto select-none"
+ draggable="false"
+ />
+ </Link>
+
+ <button
+ onClick={() => setActive(false)}
+ className="absolute -top-2 -right-2 z-10 p-1 bg-red-500 rounded-full hover:bg-red-600 transition-colors"
+ aria-label="Close popup"
+ >
+ <X className="w-3 h-3 text-white" />
+ </button>
+ </div>
+ );
+
+ if (isDesktop && isClient) {
+ return createPortal(
+ <div className="fixed z-[9999] pointer-events-none top-0 left-0 w-screen h-screen">
+ {popupContent}
+ </div>,
+ document.body
+ );
+ }
+
+ return (
+ <div className="fixed z-[9999] pointer-events-none top-[40px] left-0">
+ {popupContent}
+ </div>
+ );
+};
+
+export default PagePopupInformation;