import { useState, useRef, useCallback, useEffect } from 'react'; import { useFollowActions, useIsFollowing } from '@/hooks/useFollowing'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useAuthor } from '@/hooks/useAuthor'; import { useToast } from '@/hooks/useToast'; import { genUserName } from '@/lib/genUserName'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; import { Progress } from '@/components/ui/progress'; import { Badge } from '@/components/ui/badge'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; import { Heart, CheckCircle2, Sparkles, UserPlus, Crown, Zap, } from 'lucide-react'; // The founder's npub - decode to hex pubkey const FOUNDER_PUBKEY = '20e7c953439e66f3882ab96fe0faa8ba246d6fb12c03b0cb4e2dc4623b8bcb21'; const HOLD_DURATION = 3000; // 3 seconds interface FounderFollowProps { className?: string; size?: 'sm' | 'md' | 'lg'; showBadge?: boolean; } export function FounderFollow({ className, size = 'md', showBadge = true }: FounderFollowProps) { const { user } = useCurrentUser(); const { follow } = useFollowActions(); const isFollowing = useIsFollowing(FOUNDER_PUBKEY); const author = useAuthor(FOUNDER_PUBKEY); const { toast } = useToast(); const [isHolding, setIsHolding] = useState(false); const [progress, setProgress] = useState(0); const [showConfetti, setShowConfetti] = useState(false); const [showDialog, setShowDialog] = useState(false); const holdTimer = useRef(null); const progressInterval = useRef(null); const startTime = useRef(0); const metadata = author.data?.metadata; const displayName = metadata?.name ?? genUserName(FOUNDER_PUBKEY); const avatar = metadata?.picture; const sizeClasses = { sm: 'h-10 w-10', md: 'h-14 w-14', lg: 'h-20 w-20', }; const cleanup = useCallback(() => { if (holdTimer.current) { clearTimeout(holdTimer.current); holdTimer.current = null; } if (progressInterval.current) { clearInterval(progressInterval.current); progressInterval.current = null; } setIsHolding(false); setProgress(0); }, []); const handleHoldComplete = useCallback(async () => { cleanup(); if (!user) { toast({ title: 'Login Required', description: 'Please log in to follow the founder', variant: 'destructive', }); return; } if (isFollowing) { toast({ title: 'Already Following', description: `You're already following ${displayName}!`, }); return; } try { await follow(FOUNDER_PUBKEY); setShowConfetti(true); setShowDialog(true); // Hide confetti after animation setTimeout(() => setShowConfetti(false), 3000); } catch (error) { console.error('[FounderFollow] Failed to follow:', error); } }, [user, isFollowing, follow, displayName, cleanup, toast]); const handleHoldStart = useCallback((e: React.TouchEvent | React.MouseEvent) => { e.preventDefault(); if (isFollowing) { toast({ title: 'Already Following', description: `You're already following ${displayName}!`, }); return; } setIsHolding(true); startTime.current = Date.now(); // Progress animation progressInterval.current = setInterval(() => { const elapsed = Date.now() - startTime.current; const newProgress = Math.min((elapsed / HOLD_DURATION) * 100, 100); setProgress(newProgress); if (newProgress >= 100) { handleHoldComplete(); } }, 16); // ~60fps // Timeout as backup holdTimer.current = setTimeout(handleHoldComplete, HOLD_DURATION); }, [isFollowing, displayName, handleHoldComplete, toast]); const handleHoldEnd = useCallback(() => { cleanup(); }, [cleanup]); // Cleanup on unmount useEffect(() => { return cleanup; }, [cleanup]); return ( <>
{/* Confetti effect */} {showConfetti && (
{Array.from({ length: 20 }).map((_, i) => (
))}
)} {/* Avatar with hold effect */}
{/* Progress ring */} {isHolding && ( )} {/* Glow effect */}
{displayName.slice(0, 2).toUpperCase()} {/* Following indicator */} {isFollowing && showBadge && (
)} {/* Crown for founder */}
{/* Hold hint text */} {!isFollowing && !isHolding && (

Hold to follow founder

)} {/* Holding progress text */} {isHolding && (

{Math.floor(progress)}% Hold...

)}
{/* Success Dialog */} Welcome! You're now following the SkateSpots founder!
{displayName.slice(0, 2).toUpperCase()}

{displayName}

Founder

Stay connected to get updates on new features, community events, and skate spot discoveries!

{/* CSS for confetti animation */} ); } export default FounderFollow;