- Add truncateLightningAddress utility (shows first 2 chars + ******) - Backend: Include winner_address in past-wins API response - Frontend: Display truncated address in past winners list - Telegram: Add truncated address to draw announcements for transparency Example: username@blink.sv -> us******@blink.sv
125 lines
4.1 KiB
TypeScript
125 lines
4.1 KiB
TypeScript
'use client';
|
||
|
||
import { useEffect, useState } from 'react';
|
||
import api from '@/lib/api';
|
||
import { LoadingSpinner } from '@/components/LoadingSpinner';
|
||
import STRINGS from '@/constants/strings';
|
||
import { formatDateTime } from '@/lib/format';
|
||
|
||
interface PastWin {
|
||
cycle_id: string;
|
||
cycle_type: string;
|
||
scheduled_at: string;
|
||
pot_total_sats: number;
|
||
pot_after_fee_sats: number | null;
|
||
winner_name: string;
|
||
winner_address: string | null;
|
||
winning_ticket_serial: number | null;
|
||
}
|
||
|
||
export default function PastWinsPage() {
|
||
const [wins, setWins] = useState<PastWin[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState<string | null>(null);
|
||
|
||
useEffect(() => {
|
||
const loadWins = async () => {
|
||
try {
|
||
const response = await api.getPastWins();
|
||
setWins(response.data?.wins || []);
|
||
} catch (err: any) {
|
||
setError(err.message || 'Failed to load past wins');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
loadWins();
|
||
}, []);
|
||
|
||
if (loading) {
|
||
return <LoadingSpinner />;
|
||
}
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="max-w-3xl mx-auto text-center py-12">
|
||
<div className="text-red-500 text-xl mb-2">⚠️ {error}</div>
|
||
<p className="text-gray-400">Please try again in a moment.</p>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="max-w-5xl mx-auto">
|
||
<div className="text-center mb-10">
|
||
<h1 className="text-3xl md:text-4xl font-bold text-white mb-3">
|
||
{STRINGS.pastWins.title}
|
||
</h1>
|
||
<p className="text-gray-400">{STRINGS.pastWins.description}</p>
|
||
</div>
|
||
|
||
{wins.length === 0 ? (
|
||
<div className="bg-gray-900 rounded-xl p-12 text-center border border-gray-800">
|
||
<div className="text-4xl mb-4">⏳</div>
|
||
<div className="text-gray-300 text-lg">{STRINGS.pastWins.noWins}</div>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-4">
|
||
{wins.map((win) => (
|
||
<div
|
||
key={win.cycle_id}
|
||
className="bg-gray-900 rounded-xl p-6 border border-gray-800"
|
||
>
|
||
<div className="flex flex-col md:flex-row md:items-center md:justify-between mb-4">
|
||
<div>
|
||
<div className="text-sm uppercase tracking-wide text-gray-500">
|
||
{win.cycle_type} • {formatDateTime(win.scheduled_at)}
|
||
</div>
|
||
<div className="text-white font-mono text-sm">
|
||
{win.cycle_id.substring(0, 16)}...
|
||
</div>
|
||
</div>
|
||
<div className="mt-4 md:mt-0 text-right">
|
||
<div className="text-gray-400 text-sm">{STRINGS.pastWins.pot}</div>
|
||
<div className="text-2xl font-bold text-bitcoin-orange">
|
||
{win.pot_total_sats.toLocaleString()} sats
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 text-sm">
|
||
<div>
|
||
<div className="text-gray-400 mb-1">{STRINGS.pastWins.winner}</div>
|
||
<div className="text-white font-semibold">
|
||
{win.winner_name || 'Anon'}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div className="text-gray-400 mb-1">{STRINGS.pastWins.address}</div>
|
||
<div className="text-white font-mono text-xs">
|
||
{win.winner_address || 'N/A'}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div className="text-gray-400 mb-1">{STRINGS.pastWins.ticket}</div>
|
||
<div className="text-white">
|
||
{win.winning_ticket_serial !== null
|
||
? `#${win.winning_ticket_serial}`
|
||
: 'N/A'}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div className="text-gray-400 mb-1">{STRINGS.pastWins.drawTime}</div>
|
||
<div className="text-white">{formatDateTime(win.scheduled_at)}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|