diff --git a/frontend/src/pages/CrateDigger.tsx b/frontend/src/pages/CrateDigger.tsx index 3309bea..8472f0a 100644 --- a/frontend/src/pages/CrateDigger.tsx +++ b/frontend/src/pages/CrateDigger.tsx @@ -1,4 +1,4 @@ -import { useState, useCallback, useEffect } from 'react' +import { useState, useCallback, useEffect, useRef } from 'react' import { Disc3, X, Heart, ExternalLink, Loader2, RotateCcw } from 'lucide-react' import { fillCrate, crateSave, type CrateItem } from '../lib/api' @@ -80,6 +80,52 @@ export default function CrateDigger() { advanceCard() }, [cardState, crate, currentIndex, advanceCard]) + // Touch swipe handling + const touchStartX = useRef(null) + const touchStartY = useRef(null) + const [swipeOffset, setSwipeOffset] = useState(0) + const cardRef = useRef(null) + + const handleTouchStart = useCallback((e: React.TouchEvent) => { + touchStartX.current = e.touches[0].clientX + touchStartY.current = e.touches[0].clientY + }, []) + + const handleTouchMove = useCallback((e: React.TouchEvent) => { + if (touchStartX.current === null || cardState !== 'visible') return + const dx = e.touches[0].clientX - touchStartX.current + const dy = e.touches[0].clientY - (touchStartY.current || 0) + // Only swipe horizontally if horizontal movement > vertical + if (Math.abs(dx) > Math.abs(dy)) { + e.preventDefault() + setSwipeOffset(dx) + } + }, [cardState]) + + const handleTouchEnd = useCallback(() => { + if (touchStartX.current === null) return + const threshold = 80 + if (swipeOffset > threshold) { + handleSave() + } else if (swipeOffset < -threshold) { + handlePass() + } + setSwipeOffset(0) + touchStartX.current = null + touchStartY.current = null + }, [swipeOffset, handleSave, handlePass]) + + // Keyboard support + useEffect(() => { + const handleKey = (e: KeyboardEvent) => { + if (cardState !== 'visible' || finished || crate.length === 0) return + if (e.key === 'ArrowLeft') handlePass() + if (e.key === 'ArrowRight') handleSave() + } + window.addEventListener('keydown', handleKey) + return () => window.removeEventListener('keydown', handleKey) + }, [cardState, finished, crate.length, handlePass, handleSave]) + const currentItem = crate[currentIndex] // Empty state โ€” no crate loaded yet @@ -194,13 +240,23 @@ export default function CrateDigger() { {/* Card */} {currentItem && (
{/* Album art placeholder */}
@@ -239,6 +295,18 @@ export default function CrateDigger() {
)} + {/* Swipe indicators */} + {swipeOffset !== 0 && ( +
+ + โ† PASS + + 40 ? 'opacity-100 text-green-500' : 'opacity-0'}`}> + SAVE โ†’ + +
+ )} + {/* Action buttons */}
)