fix: lướt xem ảnh ở màn hình index của người dùng public
This commit is contained in:
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+24
-14
File diff suppressed because one or more lines are too long
+2
File diff suppressed because one or more lines are too long
-2
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -6,8 +6,8 @@
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<script src="https://accounts.google.com/gsi/client" async defer></script>
|
||||
<title>Travel Planner</title>
|
||||
<script type="module" crossorigin src="/assets/index-BX4qY0bE.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-pT9caalc.css">
|
||||
<script type="module" crossorigin src="/assets/index-Chfn55jq.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D_50XTpY.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -34,36 +34,7 @@ export const LandingPage: React.FC<LandingPageProps> = ({ onGoToSignup, onGoToMa
|
||||
const [fade2, setFade2] = useState(false);
|
||||
const [activeSlot, setActiveSlot] = useState<1 | 2>(1);
|
||||
|
||||
const [touchOffsetX, setTouchOffsetX] = useState(0);
|
||||
const touchStartXRef = useRef(0);
|
||||
const isSwipingRef = useRef(false);
|
||||
const panScale = 1.25;
|
||||
const maxPanX = typeof window !== 'undefined' ? -(window.innerWidth * (panScale - 1)) : -250;
|
||||
|
||||
const isLoggedIn = !!localStorage.getItem('token') && !localStorage.getItem('guest_token');
|
||||
|
||||
const handleTouchStart = (e: React.TouchEvent) => {
|
||||
touchStartXRef.current = e.touches[0].clientX;
|
||||
isSwipingRef.current = true;
|
||||
};
|
||||
|
||||
const handleTouchMove = (e: React.TouchEvent) => {
|
||||
if (!isSwipingRef.current) return;
|
||||
const currentX = e.touches[0].clientX;
|
||||
const diffX = currentX - touchStartXRef.current;
|
||||
|
||||
// Allow panning within bounds (for panorama viewing)
|
||||
const clampedX = Math.max(maxPanX, Math.min(0, diffX));
|
||||
setTouchOffsetX(clampedX);
|
||||
};
|
||||
|
||||
const handleTouchEnd = () => {
|
||||
if (!isSwipingRef.current) return;
|
||||
isSwipingRef.current = false;
|
||||
|
||||
// Reset panning position when touch ends
|
||||
setTouchOffsetX(0);
|
||||
};
|
||||
const isLoggedIn = !!localStorage.getItem('token') && !localStorage.getItem('guest_token');
|
||||
|
||||
useEffect(() => {
|
||||
const nextUrl = publicPhotos.length > 0 ? publicPhotos[currentBgIndex]?.imageUrl : '/background.avif';
|
||||
@@ -253,79 +224,106 @@ notify({ title: 'Thành công!', message: 'Ảnh của bạn đã được tải
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
return (
|
||||
<div className="h-dvh w-full overflow-hidden font-sans bg-gray-900 relative">
|
||||
{/* Background Image - Hiển thị trên mọi thiết bị với hiệu ứng Crossfade & Panning & Swiping */}
|
||||
<div
|
||||
className="absolute inset-0 z-0 bg-gray-950 overflow-hidden"
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchMove={handleTouchMove}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
>
|
||||
<div
|
||||
className="absolute inset-y-0 left-0 right-0 cursor-grab active:cursor-grabbing"
|
||||
style={{
|
||||
transform: `translateX(${touchOffsetX}px)`,
|
||||
transition: touchOffsetX === 0 ? 'transform 0.3s ease-out' : 'none',
|
||||
touchAction: 'none'
|
||||
}}
|
||||
>
|
||||
{/* Slot 1 */}
|
||||
{bg1 && (
|
||||
<img
|
||||
key={bg1}
|
||||
src={bg1}
|
||||
draggable="false"
|
||||
onContextMenu={(e) => { if (!isLoggedIn) e.preventDefault(); }}
|
||||
onDragStart={(e) => { if (!isLoggedIn) e.preventDefault(); }}
|
||||
className={`absolute inset-0 h-full w-full object-cover transition-opacity duration-[1200ms] ease-in-out ${
|
||||
!isLoggedIn ? 'select-none pointer-events-none' : ''
|
||||
}`}
|
||||
style={{
|
||||
opacity: fade1 ? 0.8 : 0,
|
||||
transform: `scale(${panScale}) translateX(-${(panScale - 1) * 50}%)`,
|
||||
}}
|
||||
alt="Travel Background 1"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Slot 2 */}
|
||||
{bg2 && (
|
||||
<img
|
||||
key={bg2}
|
||||
src={bg2}
|
||||
draggable="false"
|
||||
onContextMenu={(e) => { if (!isLoggedIn) e.preventDefault(); }}
|
||||
onDragStart={(e) => { if (!isLoggedIn) e.preventDefault(); }}
|
||||
className={`absolute inset-0 h-full w-full object-cover transition-opacity duration-[1200ms] ease-in-out ${
|
||||
!isLoggedIn ? 'select-none pointer-events-none' : ''
|
||||
}`}
|
||||
style={{
|
||||
opacity: fade2 ? 0.8 : 0,
|
||||
transform: `scale(${panScale}) translateX(-${(panScale - 1) * 50}%)`,
|
||||
}}
|
||||
alt="Travel Background 2"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{/* Background Image with Horizontal Panning */}
|
||||
<div className="absolute inset-0 z-0">
|
||||
{/* Active image for panning */}
|
||||
<div className={`absolute inset-0 image-pan-container ${activeSlot === 1 && fade1 ? 'block' : 'hidden'}`}>
|
||||
{bg1 && (
|
||||
<img
|
||||
key={bg1}
|
||||
src={bg1}
|
||||
draggable="false"
|
||||
onContextMenu={(e) => { if (!isLoggedIn) e.preventDefault(); }}
|
||||
onDragStart={(e) => { if (!isLoggedIn) e.preventDefault(); }}
|
||||
className={`h-full image-pan-element ${
|
||||
!isLoggedIn ? 'select-none pointer-events-none' : ''
|
||||
}`}
|
||||
alt="Travel Background 1"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className={`absolute inset-0 image-pan-container ${activeSlot === 2 && fade2 ? 'block' : 'hidden'}`}>
|
||||
{bg2 && (
|
||||
<img
|
||||
key={bg2}
|
||||
src={bg2}
|
||||
draggable="false"
|
||||
onContextMenu={(e) => { if (!isLoggedIn) e.preventDefault(); }}
|
||||
onDragStart={(e) => { if (!isLoggedIn) e.preventDefault(); }}
|
||||
className={`h-full image-pan-element ${
|
||||
!isLoggedIn ? 'select-none pointer-events-none' : ''
|
||||
}`}
|
||||
alt="Travel Background 2"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Keyframes cho hiệu ứng panning từ trái sang phải */}
|
||||
{/* Style for horizontal panning */}
|
||||
<style>{`
|
||||
@keyframes pan-left-to-right {
|
||||
0% {
|
||||
transform: scale(1.08) translate(0, 0) translateZ(0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1.16) translate(-1%, 0.5%) translateZ(0);
|
||||
}
|
||||
.image-pan-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scroll-behavior: smooth;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
.animate-panning {
|
||||
animation: pan-left-to-right 13500ms linear forwards;
|
||||
will-change: transform;
|
||||
.image-pan-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.image-pan-container {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.image-pan-element {
|
||||
width: auto;
|
||||
max-width: none !important;
|
||||
height: 100%;
|
||||
display: block;
|
||||
min-width: 100%;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
{/* Swipe Indicator Arrows - for image switching */}
|
||||
{publicPhotos.length > 1 && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => setCurrentBgIndex((prev) => (prev - 1 + publicPhotos.length) % publicPhotos.length)}
|
||||
className="absolute left-4 top-1/2 -translate-y-1/2 z-10 p-2 bg-black/30 hover:bg-black/50 backdrop-blur-md rounded-full text-white transition-all md:hidden"
|
||||
aria-label="Previous photo"
|
||||
>
|
||||
<span className="text-xl">‹</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentBgIndex((prev) => (prev + 1) % publicPhotos.length)}
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 z-10 p-2 bg-black/30 hover:bg-black/50 backdrop-blur-md rounded-full text-white transition-all md:hidden"
|
||||
aria-label="Next photo"
|
||||
>
|
||||
<span className="text-xl">›</span>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Photo Progress Dots */}
|
||||
{publicPhotos.length > 1 && (
|
||||
<div className="absolute bottom-20 left-1/2 -translate-x-1/2 z-10 flex gap-1.5">
|
||||
{publicPhotos.slice(0, 20).map((_, idx) => (
|
||||
<button
|
||||
key={idx}
|
||||
onClick={() => setCurrentBgIndex(idx)}
|
||||
className={`w-2 h-2 rounded-full transition-all ${
|
||||
currentBgIndex === idx
|
||||
? 'bg-emerald-500 w-6'
|
||||
: 'bg-white/40 hover:bg-white/60'
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Top Bar - Thanh điều hướng trên cùng */}
|
||||
<div className="absolute top-0 left-0 right-0 z-20 p-3 sm:p-4 pt-[calc(0.75rem+env(safe-area-inset-top,0px))] sm:pt-[calc(1rem+env(safe-area-inset-top,0px))] flex justify-between items-center bg-gradient-to-b from-slate-950/40 to-transparent">
|
||||
<div className="flex items-center gap-1.5 sm:gap-2 text-white drop-shadow-lg">
|
||||
|
||||
Reference in New Issue
Block a user