diff --git a/src/components/PredictionHistory.tsx b/src/components/PredictionHistory.tsx index b8a323b..02ec491 100644 --- a/src/components/PredictionHistory.tsx +++ b/src/components/PredictionHistory.tsx @@ -82,6 +82,7 @@ export default function PredictionHistory({ userId, optimisticPrediction }: Pred }, [history, userId]); useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect void loadHistory(); }, [loadHistory]); diff --git a/src/components/PriceChart.tsx b/src/components/PriceChart.tsx index 8f6cdc7..11f3ec4 100644 --- a/src/components/PriceChart.tsx +++ b/src/components/PriceChart.tsx @@ -487,6 +487,7 @@ const PriceChart = ({ height = 300, asset = "XLM", entryPrice, onPriceUpdate }: // Reload data when asset changes — reset and load mock/API data useEffect(() => { + /* eslint-disable react-hooks/set-state-in-effect */ setData([]); setIsLoading(true); setLoadError(null); @@ -499,8 +500,8 @@ const PriceChart = ({ height = 300, asset = "XLM", entryPrice, onPriceUpdate }: setIsLoading(false); } - // Also attempt to fetch live data from API void loadInitialPrices(); + /* eslint-enable react-hooks/set-state-in-effect */ }, [asset]); // eslint-disable-line react-hooks/exhaustive-deps // Update chart line color when asset changes diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 7b3bcb4..c5fd2c2 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -181,7 +181,9 @@ const activeRoundId = useRoundStore((state) => state.activeRound?.id ?? null); // Clear the entry marker whenever the active round changes. useEffect(() => { + /* eslint-disable react-hooks/set-state-in-effect */ setEntryPrice(null); + /* eslint-enable react-hooks/set-state-in-effect */ }, [activeRoundId]); const [stats, setStats] = useState(null); @@ -283,8 +285,10 @@ const activeRoundId = useRoundStore((state) => state.activeRound?.id ?? null); }, [isWalletConnected, publicKey]); useEffect(() => { + /* eslint-disable react-hooks/set-state-in-effect */ void fetchStats(); void fetchActivities(); + /* eslint-enable react-hooks/set-state-in-effect */ }, [fetchStats, fetchActivities]); const refreshInspector = useCallback(async () => { @@ -309,7 +313,9 @@ const activeRoundId = useRoundStore((state) => state.activeRound?.id ?? null); }, [isWalletConnected, publicKey]); useEffect(() => { + /* eslint-disable react-hooks/set-state-in-effect */ void refreshInspector(); + /* eslint-enable react-hooks/set-state-in-effect */ }, [refreshInspector]); const handleRoundSoundToggle = (enabled: boolean) => { diff --git a/src/pages/Landing.tsx b/src/pages/Landing.tsx index dae36ef..7efc4cb 100644 --- a/src/pages/Landing.tsx +++ b/src/pages/Landing.tsx @@ -4,14 +4,18 @@ import { useTranslation } from 'react-i18next'; import HowItWorks from '../components/HowItWorks'; import ModeCards from '../components/ModeCards'; import { useNetworkStats } from '../hooks/useNetworkStats'; +import { useReducedMotion } from '../hooks/useReducedMotion'; -function useCountUp(target: number, durationMs = 1800) { - const [value, setValue] = useState(0); - // Track the last displayed value so re-targeting (mock -> live stats) animates - // smoothly from where it is rather than snapping back to zero. - const latestRef = useRef(0); +function useCountUp(target: number, durationMs = 1800, skipAnimation = false) { + const [value, setValue] = useState(skipAnimation ? target : 0); + const latestRef = useRef(skipAnimation ? target : 0); useEffect(() => { + if (skipAnimation) { + latestRef.current = target; + return; + } + let frame = 0; const startValue = latestRef.current; const start = performance.now(); @@ -27,7 +31,7 @@ function useCountUp(target: number, durationMs = 1800) { frame = requestAnimationFrame(tick); return () => cancelAnimationFrame(frame); - }, [target, durationMs]); + }, [target, durationMs, skipAnimation]); return value; } @@ -43,9 +47,10 @@ function formatStat(value: number, type: 'rounds' | 'vxlm' | 'players') { export default function Landing() { const { t } = useTranslation(); const { stats, isStale } = useNetworkStats(); - const rounds = useCountUp(stats.totalRounds); - const vxlm = useCountUp(stats.vXlmDistributed); - const players = useCountUp(stats.activePlayers); + const { reduced } = useReducedMotion(); + const rounds = useCountUp(stats.totalRounds, 1800, reduced); + const vxlm = useCountUp(stats.vXlmDistributed, 1800, reduced); + const players = useCountUp(stats.activePlayers, 1800, reduced); return (
@@ -96,26 +101,26 @@ export default function Landing() { )}
-
-
-

{formatStat(rounds, 'rounds')}

-

+

+
+

{formatStat(rounds, 'rounds')}

+

{t('landing.roundsResolved')}

-
-

+

+

{formatStat(vxlm, 'vxlm')} vXLM

-

+

{t('landing.practiceVolume')}

-
-

+

+

{formatStat(players, 'players')}

-

+

{t('landing.activePredictors')}

diff --git a/src/pages/Learn.tsx b/src/pages/Learn.tsx index 6def81f..3b49617 100644 --- a/src/pages/Learn.tsx +++ b/src/pages/Learn.tsx @@ -52,7 +52,9 @@ const LearnPage = () => { }, []); useEffect(() => { + /* eslint-disable react-hooks/set-state-in-effect */ fetchData(); + /* eslint-enable react-hooks/set-state-in-effect */ }, [fetchData]); if (loading) {