diff options
| author | IT Fixcomart <it@fixcomart.co.id> | 2025-07-17 09:20:33 +0000 |
|---|---|---|
| committer | IT Fixcomart <it@fixcomart.co.id> | 2025-07-17 09:20:33 +0000 |
| commit | dc31efb2fec4c7b79917324d922ae820c4b5bb50 (patch) | |
| tree | 8055bb512eb091d2d033390bf3067796bec92be2 /src/lib/home | |
| parent | 48ad534fd5dfca0576d8a624a7c793454d09d065 (diff) | |
| parent | 7494d68e936833d3958563a99a5fe977f24d7536 (diff) | |
Merged in fix-popup-bottom (pull request #429)
Fix popup bottom
Diffstat (limited to 'src/lib/home')
| -rw-r--r-- | src/lib/home/components/PopupBannerPromotion.jsx | 325 |
1 files changed, 186 insertions, 139 deletions
diff --git a/src/lib/home/components/PopupBannerPromotion.jsx b/src/lib/home/components/PopupBannerPromotion.jsx index 347617d6..538f35e6 100644 --- a/src/lib/home/components/PopupBannerPromotion.jsx +++ b/src/lib/home/components/PopupBannerPromotion.jsx @@ -2,13 +2,16 @@ import { useRouter } from 'next/router'; import { useEffect, useState, useRef } from 'react'; import Image from 'next/image'; import Link from 'next/link'; -import { getAuth } from '~/libs/auth'; 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); @@ -19,193 +22,237 @@ const PagePopupInformation = () => { const dragStartPos = useRef({ x: 0, y: 0 }); const isDragging = useRef(false); const isTouching = useRef(false); - - const [isSnapping, setIsSnapping] = useState(false); // 🔥 Penanda kapan transisi diaktifkan + const [isSnapping, setIsSnapping] = useState(false); + const [containerLeft, setContainerLeft] = useState(0); + + const [isClient, setIsClient] = useState(false); useEffect(() => { - const getData = async () => { - try { - const res = await fetch(`/api/hero-banner?type=dragable-banner`); - const { data } = await res.json(); - if (Array.isArray(data) && data.length > 0 && data[0]?.image) { - setData(data); - } else { - setActive(false); - } - } catch (error) { - console.error('Failed to fetch popup banner:', error); - } - setLoading(false); - }; + setIsClient(true); + }, []); + useEffect(() => { if (isHomePage && !auth) { setActive(true); - getData(); + fetchData(); } }, [isHomePage, auth]); - useEffect(() => { - const handleGlobalTouchMove = (e) => { - if (isTouching.current) { - e.preventDefault(); - isDragging.current = true; - setIsSnapping(false); // 🔥 Matikan transisi saat drag - - let newX = e.touches[0].clientX - dragStartPos.current.x; - let newY = e.touches[0].clientY - dragStartPos.current.y; + 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); + } + }; - const popupWidth = popupRef.current?.offsetWidth || 0; - const popupHeight = popupRef.current?.offsetHeight || 0; - const maxX = window.innerWidth - popupWidth - 20; - const maxY = window.innerHeight - popupHeight - 20; + useEffect(() => { + const handleResizeOrZoom = () => { + if (!popupRef.current) return; - newX = Math.max(0, Math.min(newX, maxX)); - newY = Math.max(0, Math.min(newY, maxY)); + const popupWidth = popupRef.current.offsetWidth || 85; + const popupHeight = popupRef.current.offsetHeight || 85; - setPosition({ x: newX, y: newY }); - } + setPosition({ + x: 20, + y: window.innerHeight - popupHeight - 20, + }); }; - const handleGlobalTouchEnd = () => { - if (isDragging.current) { - const popupWidth = popupRef.current?.offsetWidth || 0; - const screenMiddle = window.innerWidth / 2; - - setIsSnapping(true); // 🔥 Aktifkan transisi saat snap + window.addEventListener('resize', handleResizeOrZoom); - if (position.x + popupWidth / 2 < screenMiddle) { - // Snap ke kiri - setPosition({ x: 20, y: position.y }); - } else { - // Snap ke kanan - setPosition({ x: window.innerWidth - popupWidth - 20, y: position.y }); - } - } - - isTouching.current = false; - isDragging.current = false; + return () => { + window.removeEventListener('resize', handleResizeOrZoom); }; + }, []); - window.addEventListener('touchmove', handleGlobalTouchMove, { passive: false }); - window.addEventListener('touchend', handleGlobalTouchEnd); + 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('touchmove', handleGlobalTouchMove); - window.removeEventListener('touchend', handleGlobalTouchEnd); + window.removeEventListener('resize', updateContainerLeft); + window.removeEventListener('scroll', updateContainerLeft); }; - }, [position]); + }, []); - const handleMouseDown = (e) => { - e.preventDefault(); - dragStartPos.current = { x: e.clientX - position.x, y: e.clientY - position.y }; - isDragging.current = false; - setIsSnapping(false); // 🔥 Matikan transisi saat drag + useEffect(() => { + if (isDesktop && containerLeft) { + const popupWidth = popupRef.current?.offsetWidth || 85; + setPosition({ + x: containerLeft - popupWidth - 20, + y: window.innerHeight - 130, + }); + } + }, [isDesktop, containerLeft]); - window.addEventListener('mousemove', handleMouseMove); - window.addEventListener('mouseup', handleMouseUp); + const startDrag = (clientX, clientY) => { + dragStartPos.current = { x: clientX - position.x, y: clientY - position.y }; + isDragging.current = false; + setIsSnapping(false); }; - const handleMouseMove = (e) => { + 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; - let newX = e.clientX - dragStartPos.current.x; - let newY = e.clientY - dragStartPos.current.y; + const topPadding = isDesktop ? 0 : 130; + const bottomPadding = isDesktop ? 0 : 80; + const maxY = window.innerHeight - popupHeight - bottomPadding; - const popupWidth = popupRef.current?.offsetWidth || 0; - const popupHeight = popupRef.current?.offsetHeight || 0; - const maxX = window.innerWidth - popupWidth - 20; - const maxY = window.innerHeight - popupHeight - 20; + const minX = 0; + + let newX = clientX - dragStartPos.current.x; + let newY = clientY - dragStartPos.current.y; - newX = Math.max(0, Math.min(newX, maxX)); - newY = Math.max(0, Math.min(newY, maxY)); + newX = Math.max(minX, Math.min(newX, maxX)); + newY = Math.max(topPadding, Math.min(newY, maxY)); setPosition({ x: newX, y: newY }); }; - const handleMouseUp = () => { + const endDrag = () => { if (isDragging.current) { - const popupWidth = popupRef.current?.offsetWidth || 0; - const screenMiddle = window.innerWidth / 2; - - setIsSnapping(true); // 🔥 Aktifkan transisi saat snap - - if (position.x + popupWidth / 2 < screenMiddle) { - // Snap ke kiri - setPosition({ x: 20, y: position.y }); + setIsSnapping(true); + + if (isDesktop) { + const popupWidth = popupRef.current?.offsetWidth || 85; + setPosition({ + x: containerLeft - popupWidth - 20, + y: window.innerHeight - 130, + }); } else { - // Snap ke kanan - setPosition({ x: window.innerWidth - popupWidth - 20, y: position.y }); + 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); - - isDragging.current = false; }; - const handleTouchStart = (e) => { + const handleTouchStart = e => { if (e.touches.length === 1) { - dragStartPos.current = { x: e.touches[0].clientX - position.x, y: e.touches[0].clientY - position.y }; - isDragging.current = false; isTouching.current = true; - setIsSnapping(false); // 🔥 Matikan transisi saat drag + startDrag(e.touches[0].clientX, e.touches[0].clientY); } }; - if (!active || !data || loading || !Array.isArray(data) || !data[0]?.image) return null; + useEffect(() => { + const handleTouchMove = e => { + if (isTouching.current) { + e.preventDefault(); + moveDrag(e.touches[0].clientX, e.touches[0].clientY); + } + }; - const banner = data[0]; + const handleTouchEnd = () => endDrag(); - return ( - <div className="fixed inset-0 z-[9999] pointer-events-none"> - <div - ref={popupRef} - className={`absolute pointer-events-auto ${isSnapping ? 'transition-transform duration-300 ease-out' : ''}`} - style={{ - transform: `translate(${position.x}px, ${position.y}px)`, - cursor: 'grab', - width: '85px', + 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" > - <div className="relative w-full h-auto"> - <div - onMouseDown={handleMouseDown} - onTouchStart={handleTouchStart} - className="cursor-grab active:cursor-grabbing" - > - <Link - href={typeof banner.url === 'boolean' && banner.url === false ? '/' : banner.url} - onClick={(e) => { - if (isDragging.current) { - e.preventDefault(); - isDragging.current = false; - } else { - setActive(false); - } - }} - draggable="false" - > - <Image - src={banner.image} - alt={banner.name || 'popup'} - width={85} - height={85} - className="w-full h-auto select-none" - draggable="false" - /> - </Link> - </div> - - <button - onClick={() => setActive(false)} - className="absolute -top-2 -right-2 z-10 p-1 bg-gray-500 rounded-full hover:bg-gray-600 transition-colors" - aria-label="Close popup" - > - <X className="w-3 h-3 text-white" /> - </button> - </div> - </div> + <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> ); }; |
