rc
This commit is contained in:
parent
e9913b1a4f
commit
ce9599bc6f
|
|
@ -1,5 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { ENTRY_FEE_DINO, MAX_ATTEMPTS } from '../shared';
|
||||
|
||||
interface LeaderboardEntry {
|
||||
|
|
@ -7,14 +8,88 @@ interface LeaderboardEntry {
|
|||
score: string;
|
||||
}
|
||||
|
||||
interface LeaderboardHistoryEntry {
|
||||
owner: string;
|
||||
highest_score: number;
|
||||
}
|
||||
|
||||
interface LeaderboardHistoryData {
|
||||
leaderboard_id: number;
|
||||
metadata: string;
|
||||
total_players: number;
|
||||
highest_score: number;
|
||||
lowest_score: number;
|
||||
average_score: number;
|
||||
players: LeaderboardHistoryEntry[];
|
||||
}
|
||||
|
||||
interface LeaderboardHistoryResponse {
|
||||
success: boolean;
|
||||
total_leaderboards: number;
|
||||
data: LeaderboardHistoryData[];
|
||||
}
|
||||
|
||||
interface LeaderboardProps {
|
||||
leaderboard: LeaderboardEntry[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
attemptsCount: number;
|
||||
isRefreshing?: boolean; // New prop to track background refreshes
|
||||
refreshCounter?: number; // New prop to track refresh cycles
|
||||
}
|
||||
|
||||
export default function Leaderboard({ leaderboard, loading, error, attemptsCount }: LeaderboardProps) {
|
||||
export default function Leaderboard({ leaderboard, loading, error, attemptsCount, isRefreshing = false, refreshCounter = 0 }: LeaderboardProps) {
|
||||
const [showHistory, setShowHistory] = useState(false);
|
||||
const [historyData, setHistoryData] = useState<LeaderboardHistoryData[]>([]);
|
||||
const [historyLoading, setHistoryLoading] = useState(false);
|
||||
const [historyError, setHistoryError] = useState<string | null>(null);
|
||||
const [currentLeaderboardIndex, setCurrentLeaderboardIndex] = useState(0);
|
||||
|
||||
const fetchHistoryData = async () => {
|
||||
try {
|
||||
setHistoryLoading(true);
|
||||
setHistoryError(null);
|
||||
|
||||
const response = await fetch('https://vps.playpoolstudios.com/dino/api/get_leaderboard_history.php');
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch leaderboard history');
|
||||
}
|
||||
|
||||
const data: LeaderboardHistoryResponse = await response.json();
|
||||
|
||||
if (data.success && data.data.length > 0) {
|
||||
setHistoryData(data.data);
|
||||
setCurrentLeaderboardIndex(0); // Reset to first leaderboard
|
||||
} else {
|
||||
throw new Error('No history data available');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching leaderboard history:', err);
|
||||
setHistoryError(err instanceof Error ? err.message : 'Failed to fetch leaderboard history');
|
||||
} finally {
|
||||
setHistoryLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const openHistoryModal = () => {
|
||||
setShowHistory(true);
|
||||
fetchHistoryData();
|
||||
};
|
||||
|
||||
const nextLeaderboard = () => {
|
||||
setCurrentLeaderboardIndex((prev) =>
|
||||
prev === historyData.length - 1 ? 0 : prev + 1
|
||||
);
|
||||
};
|
||||
|
||||
const prevLeaderboard = () => {
|
||||
setCurrentLeaderboardIndex((prev) =>
|
||||
prev === 0 ? historyData.length - 1 : prev - 1
|
||||
);
|
||||
};
|
||||
|
||||
const currentLeaderboard = historyData[currentLeaderboardIndex];
|
||||
|
||||
// Only show loading state if we have no data yet
|
||||
if (loading && leaderboard.length === 0) {
|
||||
|
|
@ -51,70 +126,93 @@ export default function Leaderboard({ leaderboard, loading, error, attemptsCount
|
|||
);
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow-lg p-6">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-4">🏆 Leaderboard</h3>
|
||||
|
||||
{/* Combined Prize and Attempts Information */}
|
||||
<div className="mb-6 p-4 bg-gradient-to-r from-green-50 to-emerald-50 rounded-lg border border-green-200">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h4 className="font-semibold text-green-800">💰 Prize ({MAX_ATTEMPTS * ENTRY_FEE_DINO} DINO Total)</h4>
|
||||
<div className="text-right">
|
||||
<div className="text-sm text-blue-700">🎯 Attempts Remaining:</div>
|
||||
<div className="font-bold text-blue-800 text-lg">{MAX_ATTEMPTS - attemptsCount}</div>
|
||||
<>
|
||||
<div className={`bg-white rounded-lg shadow-lg p-6 transition-all duration-300 ${
|
||||
isRefreshing ? 'ring-2 ring-green-200 ring-opacity-50' : ''
|
||||
}`}>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-xl font-bold text-gray-900">🏆 Leaderboard</h3>
|
||||
<div className="flex items-center space-x-3">
|
||||
{/* Update indicator */}
|
||||
{isRefreshing && (
|
||||
<div className="flex items-center space-x-2 text-xs text-green-600 bg-green-50 px-2 py-1 rounded-full">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||
<span>Updating...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700">🥇 1st Place:</span>
|
||||
<span className="font-bold text-yellow-600">{(MAX_ATTEMPTS * ENTRY_FEE_DINO * 0.8).toFixed(1)} DINO (80%)</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700">🥈 2nd Place:</span>
|
||||
<span className="font-bold text-gray-600">{(MAX_ATTEMPTS * ENTRY_FEE_DINO * 0.1).toFixed(1)} DINO (10%)</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700">🥉 3rd Place:</span>
|
||||
<span className="font-bold text-orange-600">{(MAX_ATTEMPTS * ENTRY_FEE_DINO * 0.05).toFixed(1)} DINO (5%)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 pt-3 border-t border-green-200">
|
||||
<p className="text-xs text-blue-600 text-center">Leaderboard resets after {MAX_ATTEMPTS} attempts</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{sortedLeaderboard.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-gray-500">No scores yet. Be the first to play!</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{sortedLeaderboard.map((entry, index) => (
|
||||
<div
|
||||
key={`${entry.owner}-${entry.score}`}
|
||||
className={`flex items-center justify-between p-3 rounded-lg ${
|
||||
index === 0 ? 'bg-yellow-50 border border-yellow-200' :
|
||||
index === 1 ? 'bg-gray-50 border border-gray-200' :
|
||||
index === 2 ? 'bg-orange-50 border border-orange-200' :
|
||||
'bg-gray-50 border border-gray-100'
|
||||
}`}
|
||||
)}
|
||||
<button
|
||||
onClick={openHistoryModal}
|
||||
className="flex items-center space-x-2 px-3 py-2 text-sm bg-blue-50 hover:bg-blue-100 text-blue-700 rounded-lg transition-colors duration-200"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className={`flex items-center justify-center w-8 h-8 rounded-full text-sm font-bold ${
|
||||
index === 0 ? 'bg-yellow-400 text-yellow-900' :
|
||||
index === 1 ? 'bg-gray-400 text-gray-900' :
|
||||
index === 2 ? 'bg-orange-400 text-orange-900' :
|
||||
'bg-gray-300 text-gray-700'
|
||||
}`}>
|
||||
{index + 1}
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-gray-900">
|
||||
{entry.owner.length > 20
|
||||
? `${entry.owner.slice(0, 8)}...${entry.owner.slice(-8)}`
|
||||
: entry.owner
|
||||
}
|
||||
</p>
|
||||
{/* Prize indicators for top 3 */}
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span>History</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Combined Prize and Attempts Information */}
|
||||
<div className="mb-6 p-4 bg-gradient-to-r from-green-50 to-emerald-50 rounded-lg border border-green-200">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h4 className="font-semibold text-green-800">💰 Prize ({MAX_ATTEMPTS * ENTRY_FEE_DINO} DINO Total)</h4>
|
||||
<div className="text-right">
|
||||
<div className="text-sm text-blue-700">🎯 Attempts Remaining:</div>
|
||||
<div className="font-bold text-blue-800 text-lg">{MAX_ATTEMPTS - attemptsCount}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700">🥇 1st Place:</span>
|
||||
<span className="font-bold text-yellow-600">{(MAX_ATTEMPTS * ENTRY_FEE_DINO * 0.8).toFixed(1)} DINO (80%)</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700">🥈 2nd Place:</span>
|
||||
<span className="font-bold text-gray-600">{(MAX_ATTEMPTS * ENTRY_FEE_DINO * 0.1).toFixed(1)} DINO (10%)</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700">🥉 3rd Place:</span>
|
||||
<span className="font-bold text-orange-600">{(MAX_ATTEMPTS * ENTRY_FEE_DINO * 0.05).toFixed(1)} DINO (5%)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 pt-3 border-t border-green-200">
|
||||
<p className="text-xs text-blue-600 text-center">Leaderboard resets after {MAX_ATTEMPTS} attempts</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{sortedLeaderboard.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-gray-500">No scores yet. Be the first to play!</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{sortedLeaderboard.map((entry, index) => (
|
||||
<div
|
||||
key={`${entry.owner}-${entry.score}-${refreshCounter}`}
|
||||
className={`flex items-center justify-between p-3 rounded-lg transition-all duration-300 ease-in-out ${
|
||||
index === 0 ? 'bg-yellow-50 border border-yellow-200' :
|
||||
index === 1 ? 'bg-gray-50 border border-gray-200' :
|
||||
index === 2 ? 'bg-orange-50 border border-orange-200' :
|
||||
'bg-gray-50 border border-gray-100'
|
||||
} ${isRefreshing ? 'animate-pulse' : ''}`}
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className={`flex items-center justify-center w-8 h-8 rounded-full text-sm font-bold ${
|
||||
index === 0 ? 'bg-yellow-400 text-yellow-900' :
|
||||
index === 1 ? 'bg-gray-400 text-gray-900' :
|
||||
index === 2 ? 'bg-orange-400 text-orange-900' :
|
||||
'bg-gray-300 text-gray-700'
|
||||
}`}>
|
||||
{index + 1}
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-gray-900">
|
||||
{entry.owner.length > 20
|
||||
? `${entry.owner.slice(0, 8)}...${entry.owner.slice(-8)}`
|
||||
: entry.owner
|
||||
}
|
||||
</p>
|
||||
{/* Prize indicators for top 3 */}
|
||||
{index < 3 && (
|
||||
<div className="flex items-center space-x-1 mt-1">
|
||||
<span className={`text-xs px-2 py-1 rounded-full ${
|
||||
|
|
@ -131,30 +229,173 @@ export default function Leaderboard({ leaderboard, loading, error, attemptsCount
|
|||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className={`text-lg font-bold text-gray-900 transition-all duration-300 ${isRefreshing ? 'scale-105' : 'scale-100'}`}>
|
||||
{entry.score}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">points</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-lg font-bold text-gray-900">{entry.score}</p>
|
||||
<p className="text-xs text-gray-500">points</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-4 text-center">
|
||||
<div className="flex items-center justify-center space-x-2">
|
||||
<p className="text-xs text-gray-500">
|
||||
{sortedLeaderboard.length} player{sortedLeaderboard.length !== 1 ? 's' : ''}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* History Modal */}
|
||||
{showHistory && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
||||
<div className="bg-white rounded-lg shadow-xl max-w-6xl w-full max-h-[90vh] overflow-hidden">
|
||||
{/* Modal Header */}
|
||||
<div className="flex items-center justify-between p-6 border-b border-gray-200">
|
||||
<h2 className="text-2xl font-bold text-gray-900">📊 Leaderboard History</h2>
|
||||
<button
|
||||
onClick={() => setShowHistory(false)}
|
||||
className="text-gray-400 hover:text-gray-600 transition-colors duration-200"
|
||||
>
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Modal Content */}
|
||||
<div className="p-6 overflow-y-auto max-h-[calc(90vh-120px)]">
|
||||
{historyLoading ? (
|
||||
<div className="flex justify-center items-center py-12">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-green-600"></div>
|
||||
</div>
|
||||
) : historyError ? (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-red-600 mb-4">{historyError}</p>
|
||||
<button
|
||||
onClick={fetchHistoryData}
|
||||
className="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors duration-200"
|
||||
>
|
||||
Try Again
|
||||
</button>
|
||||
</div>
|
||||
) : historyData.length > 0 ? (
|
||||
<div className="space-y-6">
|
||||
{/* Leaderboard Carousel Navigation */}
|
||||
<div className="flex items-center justify-between p-4 bg-gradient-to-r from-purple-50 to-pink-50 rounded-lg border border-purple-200">
|
||||
<button
|
||||
onClick={prevLeaderboard}
|
||||
disabled={historyData.length <= 1}
|
||||
className="flex items-center space-x-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors duration-200"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
<span>Previous</span>
|
||||
</button>
|
||||
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-purple-800">
|
||||
Leaderboard #{currentLeaderboard.leaderboard_id}
|
||||
</h3>
|
||||
<p className="text-sm text-purple-600">
|
||||
{currentLeaderboardIndex + 1} of {historyData.length}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={nextLeaderboard}
|
||||
disabled={historyData.length <= 1}
|
||||
className="flex items-center space-x-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors duration-200"
|
||||
>
|
||||
<span>Next</span>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Statistics Summary */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 p-4 bg-gradient-to-r from-blue-50 to-indigo-50 rounded-lg border border-blue-200">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-blue-600">{currentLeaderboard.total_players}</div>
|
||||
<div className="text-sm text-blue-700">Total Players</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-green-600">{currentLeaderboard.highest_score}</div>
|
||||
<div className="text-sm text-green-700">Highest Score</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-orange-600">{currentLeaderboard.lowest_score}</div>
|
||||
<div className="text-sm text-orange-700">Lowest Score</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-purple-600">{currentLeaderboard.average_score.toFixed(1)}</div>
|
||||
<div className="text-sm text-purple-700">Average Score</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* History Entries */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">All Time Entries</h3>
|
||||
<div className="space-y-3">
|
||||
{currentLeaderboard.players.map((entry, index) => (
|
||||
<div
|
||||
key={`${entry.owner}-${entry.highest_score}`}
|
||||
className={`flex items-center justify-between p-3 rounded-lg ${
|
||||
index === 0 ? 'bg-yellow-50 border border-yellow-200' :
|
||||
index === 1 ? 'bg-gray-50 border border-gray-200' :
|
||||
index === 2 ? 'bg-orange-50 border border-orange-200' :
|
||||
'bg-gray-50 border border-gray-100'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className={`flex items-center justify-center w-8 h-8 rounded-full text-sm font-bold ${
|
||||
index === 0 ? 'bg-yellow-400 text-yellow-900' :
|
||||
index === 1 ? 'bg-gray-400 text-gray-900' :
|
||||
index === 2 ? 'bg-orange-400 text-orange-900' :
|
||||
'bg-gray-300 text-gray-700'
|
||||
}`}>
|
||||
{index + 1}
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-gray-900">
|
||||
{entry.owner.length > 20
|
||||
? `${entry.owner.slice(0, 8)}...${entry.owner.slice(-8)}`
|
||||
: entry.owner
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-lg font-bold text-gray-900">{entry.highest_score}</p>
|
||||
<p className="text-xs text-gray-500">points</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{/* Modal Footer */}
|
||||
<div className="flex justify-end p-6 border-t border-gray-200">
|
||||
<button
|
||||
onClick={() => setShowHistory(false)}
|
||||
className="px-6 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors duration-200"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-4 text-center">
|
||||
<div className="flex items-center justify-center space-x-2">
|
||||
<p className="text-xs text-gray-500">
|
||||
Updates every 5 seconds • {sortedLeaderboard.length} player{sortedLeaderboard.length !== 1 ? 's' : ''}
|
||||
</p>
|
||||
{loading && leaderboard.length > 0 && (
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className="animate-spin rounded-full h-3 w-3 border-b-2 border-green-600"></div>
|
||||
<span className="text-xs text-green-600">Refreshing...</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
34
app/page.tsx
34
app/page.tsx
|
|
@ -27,6 +27,8 @@ export default function Home() {
|
|||
const [dashboardData, setDashboardData] = useState<DashboardData>({ leaderboard: [], available_tx: null, attempts_count: 0 });
|
||||
const [dashboardLoading, setDashboardLoading] = useState(true);
|
||||
const [dashboardError, setDashboardError] = useState<string | null>(null);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const [refreshCounter, setRefreshCounter] = useState(0);
|
||||
const headerRef = useRef<HeaderRef>(null);
|
||||
|
||||
// Poll for available tickets every 5 seconds (only when no ticket is available)
|
||||
|
|
@ -101,9 +103,16 @@ export default function Home() {
|
|||
|
||||
// Fetch dashboard data every 5 seconds
|
||||
useEffect(() => {
|
||||
const fetchDashboardData = async () => {
|
||||
const fetchDashboardData = async (isInitialFetch = false) => {
|
||||
try {
|
||||
setDashboardLoading(true);
|
||||
// Only show loading state on initial fetch, not on subsequent polls
|
||||
if (isInitialFetch) {
|
||||
setDashboardLoading(true);
|
||||
} else {
|
||||
// Show refreshing state for background updates
|
||||
setIsRefreshing(true);
|
||||
}
|
||||
|
||||
const response = await fetch('https://vps.playpoolstudios.com/dino/api/get_dashboard_data.php');
|
||||
|
||||
if (!response.ok) {
|
||||
|
|
@ -113,18 +122,29 @@ export default function Home() {
|
|||
const data: DashboardData = await response.json();
|
||||
setDashboardData(data);
|
||||
setDashboardError(null);
|
||||
|
||||
// Increment refresh counter for smooth updates
|
||||
if (!isInitialFetch) {
|
||||
setRefreshCounter(prev => prev + 1);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching dashboard data:', err);
|
||||
setDashboardError(err instanceof Error ? err.message : 'Failed to fetch dashboard data');
|
||||
} finally {
|
||||
setDashboardLoading(false);
|
||||
if (isInitialFetch) {
|
||||
setDashboardLoading(false);
|
||||
} else {
|
||||
// Hide refreshing state after a short delay for smooth UX
|
||||
setTimeout(() => setIsRefreshing(false), 500);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchDashboardData();
|
||||
// Initial fetch with loading state
|
||||
fetchDashboardData(true);
|
||||
|
||||
// Refresh dashboard data every 5 seconds
|
||||
const interval = setInterval(fetchDashboardData, 5000);
|
||||
// Refresh dashboard data every 5 seconds without loading state
|
||||
const interval = setInterval(() => fetchDashboardData(false), 5000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
|
@ -374,6 +394,8 @@ export default function Home() {
|
|||
loading={dashboardLoading}
|
||||
error={dashboardError}
|
||||
attemptsCount={dashboardData.attempts_count}
|
||||
isRefreshing={isRefreshing}
|
||||
refreshCounter={refreshCounter}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user