'use client' import { useEffect, useRef, useState } from 'react' import { cn } from '@/lib/utils' import { getSignatureCount } from '@/app/(frontend)/actions/getSignatureCount' const POLL_INTERVAL_MS = 10_000 const ANIMATION_DURATION_MS = 1200 export const SIGNATURE_CREATED_EVENT = 'signature:created' export type SignatureCreatedDetail = { petitionId: string; count?: number } const formatter = new Intl.NumberFormat('pt-BR') const useAnimatedNumber = (target: number, durationMs = ANIMATION_DURATION_MS): number => { const [displayed, setDisplayed] = useState(target) const displayedRef = useRef(displayed) displayedRef.current = displayed useEffect(() => { const from = displayedRef.current if (from === target) return const start = performance.now() let raf = 0 const tick = (now: number) => { const t = Math.min(1, (now - start) / durationMs) const eased = 1 - Math.pow(1 - t, 3) const next = Math.round(from + (target - from) * eased) setDisplayed(next) if (t < 1) raf = requestAnimationFrame(tick) } raf = requestAnimationFrame(tick) return () => cancelAnimationFrame(raf) }, [target, durationMs]) return displayed } interface SignatureCounterProps { petitionId: string initialCount: number variant?: 'hero' | 'card' className?: string } export const SignatureCounter = ({ petitionId, initialCount, variant = 'card', className, }: SignatureCounterProps) => { const [count, setCount] = useState(initialCount) const animated = useAnimatedNumber(count) const formatted = formatter.format(animated) const countRef = useRef(count) countRef.current = count useEffect(() => { let cancelled = false const refetch = async () => { try { const next = await getSignatureCount(petitionId) if (cancelled) return if (next > countRef.current) setCount(next) } catch { // transient failures are fine — next poll will retry } } // Busca o valor real imediatamente ao montar, // pois o initialCount pode estar defasado (página gerada estaticamente). refetch() const interval = setInterval(() => { if (document.visibilityState === 'visible') refetch() }, POLL_INTERVAL_MS) const handleVisibility = () => { if (document.visibilityState === 'visible') refetch() } document.addEventListener('visibilitychange', handleVisibility) const handleCreated = (event: Event) => { const detail = (event as CustomEvent).detail if (detail?.petitionId !== petitionId) return if (typeof detail.count === 'number' && detail.count > countRef.current) { setCount(detail.count) } else { setCount((prev) => prev + 1) } } window.addEventListener(SIGNATURE_CREATED_EVENT, handleCreated) return () => { cancelled = true clearInterval(interval) document.removeEventListener('visibilitychange', handleVisibility) window.removeEventListener(SIGNATURE_CREATED_EVENT, handleCreated) } }, [petitionId]) const isHero = variant === 'hero' return (
{isHero ? ( {formatted} pessoas já assinaram ) : ( {formatted} pessoas já assinaram este abaixo-assinado )}
) }