Initial commit: Lightning Lottery - Bitcoin Lightning Network powered lottery
Features: - Lightning Network payments via LNbits integration - Provably fair draws using CSPRNG - Random ticket number generation - Automatic payouts with retry/redraw logic - Nostr authentication (NIP-07) - Multiple draw cycles (hourly, daily, weekly, monthly) - PostgreSQL and SQLite database support - Real-time countdown and payment animations - Swagger API documentation - Docker support Stack: - Backend: Node.js, TypeScript, Express - Frontend: Next.js, React, TailwindCSS, Redux - Payments: LNbits
This commit is contained in:
117
front_end/src/app/past-wins/page.tsx
Normal file
117
front_end/src/app/past-wins/page.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
'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;
|
||||
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-3 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.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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user