Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/PredictionHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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]);

Expand Down
3 changes: 2 additions & 1 deletion src/components/PriceChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/pages/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<UserStats | null>(null);
Expand Down Expand Up @@ -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 () => {
Expand All @@ -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) => {
Expand Down
43 changes: 24 additions & 19 deletions src/pages/Landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -27,7 +31,7 @@ function useCountUp(target: number, durationMs = 1800) {

frame = requestAnimationFrame(tick);
return () => cancelAnimationFrame(frame);
}, [target, durationMs]);
}, [target, durationMs, skipAnimation]);

return value;
}
Expand All @@ -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 (
<div className="xelma-grid-bg min-h-screen text-[#F3F4F6]">
Expand Down Expand Up @@ -96,26 +101,26 @@ export default function Landing() {
)}
</div>

<div className="mx-auto mt-4 grid max-w-3xl gap-4 sm:grid-cols-3">
<div className="glass-card rounded-xl p-5 text-left">
<p className="text-2xl font-black text-white">{formatStat(rounds, 'rounds')}</p>
<p className="mt-1 text-xs font-medium uppercase tracking-wider text-[#808897]">
<div className="mx-auto mt-4 grid max-w-3xl grid-cols-1 gap-3 sm:grid-cols-3 sm:gap-4">
<div className="glass-card rounded-xl p-5 text-left transition-all duration-300 hover:border-[#BEC7FE]/25 hover:shadow-[0_0_24px_rgba(255,255,255,0.03)]">
<p className="text-xl font-black text-white sm:text-2xl">{formatStat(rounds, 'rounds')}</p>
<p className="mt-1.5 text-xs font-medium uppercase tracking-wider text-[#808897]">
{t('landing.roundsResolved')}
</p>
</div>
<div className="glass-card rounded-xl p-5 text-left">
<p className="text-2xl font-black text-cyan-300">
<div className="glass-card rounded-xl p-5 text-left transition-all duration-300 hover:border-cyan-400/25 hover:shadow-[0_0_24px_rgba(6,182,212,0.06)]">
<p className="text-xl font-black text-cyan-300 sm:text-2xl">
{formatStat(vxlm, 'vxlm')} vXLM
</p>
<p className="mt-1 text-xs font-medium uppercase tracking-wider text-[#808897]">
<p className="mt-1.5 text-xs font-medium uppercase tracking-wider text-[#808897]">
{t('landing.practiceVolume')}
</p>
</div>
<div className="glass-card rounded-xl p-5 text-left">
<p className="text-2xl font-black text-[#BEC7FE]">
<div className="glass-card rounded-xl p-5 text-left transition-all duration-300 hover:border-[#2C4BFD]/25 hover:shadow-[0_0_24px_rgba(44,75,253,0.08)]">
<p className="text-xl font-black text-[#BEC7FE] sm:text-2xl">
{formatStat(players, 'players')}
</p>
<p className="mt-1 text-xs font-medium uppercase tracking-wider text-[#808897]">
<p className="mt-1.5 text-xs font-medium uppercase tracking-wider text-[#808897]">
{t('landing.activePredictors')}
</p>
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/pages/Learn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading