Skip to content

Commit ad0df49

Browse files
committed
add recent operations table and minor improvements
1 parent 05366a2 commit ad0df49

File tree

4 files changed

+194
-5
lines changed

4 files changed

+194
-5
lines changed

app/page.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useHathor } from '@/contexts/HathorContext';
66
import Header from '@/components/Header';
77
import BalanceCard from '@/components/BalanceCard';
88
import RecentBetsTable from '@/components/RecentBetsTable';
9+
import RecentOperationsTable from '@/components/RecentOperationsTable';
910
import TokenSelector from '@/components/TokenSelector';
1011
import PlaceBetCard from '@/components/PlaceBetCard';
1112
import AddLiquidityCard from '@/components/AddLiquidityCard';
@@ -171,6 +172,7 @@ export default function Home() {
171172
<div className="lg:col-span-2 space-y-6">
172173
{connected && <BalanceCard selectedToken={selectedToken} />}
173174
<RecentBetsTable />
175+
<RecentOperationsTable selectedToken={selectedToken} />
174176
</div>
175177

176178
<div className="space-y-6">
@@ -246,11 +248,6 @@ export default function Home() {
246248
)}
247249
</div>
248250
</div>
249-
{claimableBalance > 0n && !isLoadingClaimable && !claimableError && (
250-
<div className="text-xs text-slate-400 italic">
251-
* Can be used for betting without deposits
252-
</div>
253-
)}
254251
</div>
255252
)}
256253

components/RecentBetsTable.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,10 @@ export default function RecentBetsTable() {
131131
<th className="px-4 py-3 text-left text-xs font-medium text-slate-400 uppercase">Player</th>
132132
<th className="px-4 py-3 text-left text-xs font-medium text-slate-400 uppercase">Bet</th>
133133
<th className="px-4 py-3 text-left text-xs font-medium text-slate-400 uppercase">Threshold</th>
134+
<th className="px-4 py-3 text-left text-xs font-medium text-slate-400 uppercase">Lucky Number</th>
134135
<th className="px-4 py-3 text-left text-xs font-medium text-slate-400 uppercase">Result</th>
135136
<th className="px-4 py-3 text-left text-xs font-medium text-slate-400 uppercase">Payout</th>
137+
<th className="px-4 py-3 text-left text-xs font-medium text-slate-400 uppercase">TX ID</th>
136138
</tr>
137139
</thead>
138140
<tbody className="divide-y divide-slate-700">
@@ -161,12 +163,29 @@ export default function RecentBetsTable() {
161163
<td className="px-4 py-3 text-sm text-slate-300">
162164
{bet.threshold.toLocaleString()}
163165
</td>
166+
<td className="px-4 py-3 text-sm">
167+
{bet.luckyNumber !== undefined ? (
168+
<span className="text-slate-300">{bet.luckyNumber.toLocaleString()}</span>
169+
) : (
170+
<span className="text-slate-500 italic">Unknown</span>
171+
)}
172+
</td>
164173
<td className="px-4 py-3 text-sm">
165174
{getResultDisplay(bet)}
166175
</td>
167176
<td className="px-4 py-3 text-sm font-medium">
168177
{getPayoutDisplay(bet)}
169178
</td>
179+
<td className="px-4 py-3 text-sm text-blue-400 font-mono">
180+
<a
181+
href={`https://explorer.testnet.hathor.network/transaction/${bet.id}`}
182+
target="_blank"
183+
rel="noopener noreferrer"
184+
className="hover:underline"
185+
>
186+
{formatAddress(bet.id)}
187+
</a>
188+
</td>
170189
</tr>
171190
))}
172191
</tbody>
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
'use client';
2+
3+
import { useState, useEffect } from 'react';
4+
import { useHathor } from '@/contexts/HathorContext';
5+
import { formatAddress } from '@/lib/utils';
6+
7+
interface Operation {
8+
tx_id: string;
9+
timestamp: number;
10+
nc_method: string;
11+
nc_caller: string;
12+
first_block: string | null;
13+
is_voided: boolean;
14+
}
15+
16+
interface RecentOperationsTableProps {
17+
selectedToken: string;
18+
}
19+
20+
export default function RecentOperationsTable({ selectedToken }: RecentOperationsTableProps) {
21+
const { getContractIdForToken, coreAPI, isConnected } = useHathor();
22+
const [operations, setOperations] = useState<Operation[]>([]);
23+
const [isLoading, setIsLoading] = useState(false);
24+
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
25+
26+
const fetchOperations = async () => {
27+
if (!isConnected) {
28+
setOperations([]);
29+
return;
30+
}
31+
32+
const contractId = getContractIdForToken(selectedToken);
33+
if (!contractId) {
34+
return;
35+
}
36+
37+
setIsLoading(true);
38+
try {
39+
const history = await coreAPI.getContractHistory(contractId, 50);
40+
// Filter for claim_balance, remove_liquidity, and add_liquidity operations
41+
const relevantOps = history.transactions.filter(
42+
(tx: any) =>
43+
tx.nc_method === 'claim_balance' ||
44+
tx.nc_method === 'remove_liquidity' ||
45+
tx.nc_method === 'add_liquidity'
46+
);
47+
setOperations(relevantOps);
48+
setLastUpdated(new Date());
49+
} catch (error) {
50+
console.error('Failed to fetch operations:', error);
51+
setOperations([]);
52+
} finally {
53+
setIsLoading(false);
54+
}
55+
};
56+
57+
useEffect(() => {
58+
fetchOperations();
59+
// Refresh every 10 seconds
60+
const interval = setInterval(fetchOperations, 10000);
61+
return () => clearInterval(interval);
62+
}, [selectedToken, isConnected, getContractIdForToken, coreAPI]);
63+
64+
const getOperationStatus = (op: Operation) => {
65+
if (op.is_voided) {
66+
return { text: 'Failed', color: 'text-red-400', bg: 'bg-red-900/20', border: 'border-red-700' };
67+
}
68+
if (!op.first_block) {
69+
return { text: 'Pending', color: 'text-yellow-400', bg: 'bg-yellow-900/20', border: 'border-yellow-700' };
70+
}
71+
return { text: 'Executed', color: 'text-green-400', bg: 'bg-green-900/20', border: 'border-green-700' };
72+
};
73+
74+
const getOperationLabel = (method: string) => {
75+
if (method === 'claim_balance') return '💸 Withdraw';
76+
if (method === 'remove_liquidity') return '💸 Remove Liquidity';
77+
if (method === 'add_liquidity') return '💰 Add Liquidity';
78+
return method;
79+
};
80+
81+
const formatTimestamp = (timestamp: number) => {
82+
const date = new Date(timestamp * 1000);
83+
return date.toLocaleString();
84+
};
85+
86+
const formatLastUpdated = () => {
87+
if (!lastUpdated) return '';
88+
const date = lastUpdated.toLocaleDateString();
89+
const time = lastUpdated.toLocaleTimeString();
90+
return `${date} ${time}`;
91+
};
92+
93+
if (!isConnected) {
94+
return null;
95+
}
96+
97+
return (
98+
<div className="bg-slate-800 rounded-xl border border-slate-700 p-6">
99+
<div className="flex items-center justify-between mb-4">
100+
<div>
101+
<h2 className="text-lg font-bold text-white">Recent Operations</h2>
102+
{lastUpdated && (
103+
<p className="text-xs text-slate-400 mt-1">
104+
Last updated at {formatLastUpdated()}
105+
</p>
106+
)}
107+
</div>
108+
<button
109+
onClick={fetchOperations}
110+
disabled={isLoading}
111+
className="px-3 py-1.5 bg-slate-700 hover:bg-slate-600 disabled:opacity-50 disabled:cursor-not-allowed text-white text-sm rounded-lg transition-colors flex items-center gap-2"
112+
>
113+
<span className={isLoading ? 'animate-spin' : ''}>🔄</span>
114+
Refresh
115+
</button>
116+
</div>
117+
118+
{operations.length === 0 ? (
119+
<div className="text-center py-8 text-slate-400">
120+
No recent operations found
121+
</div>
122+
) : (
123+
<div className="overflow-x-auto">
124+
<table className="w-full">
125+
<thead>
126+
<tr className="border-b border-slate-700">
127+
<th className="text-left text-sm font-medium text-slate-400 pb-3">Operation</th>
128+
<th className="text-left text-sm font-medium text-slate-400 pb-3">Status</th>
129+
<th className="text-left text-sm font-medium text-slate-400 pb-3">Time</th>
130+
<th className="text-left text-sm font-medium text-slate-400 pb-3">Address</th>
131+
<th className="text-left text-sm font-medium text-slate-400 pb-3">TX ID</th>
132+
</tr>
133+
</thead>
134+
<tbody>
135+
{operations.map((op) => {
136+
const status = getOperationStatus(op);
137+
return (
138+
<tr key={op.tx_id} className="border-b border-slate-700/50">
139+
<td className="py-3 text-white text-sm">
140+
{getOperationLabel(op.nc_method)}
141+
</td>
142+
<td className="py-3">
143+
<span className={`px-2 py-1 rounded text-xs font-medium ${status.color} ${status.bg} border ${status.border}`}>
144+
{status.text}
145+
</span>
146+
</td>
147+
<td className="py-3 text-slate-300 text-sm">
148+
{formatTimestamp(op.timestamp)}
149+
</td>
150+
<td className="py-3 text-slate-300 text-sm font-mono">
151+
{formatAddress(op.nc_caller)}
152+
</td>
153+
<td className="py-3 text-blue-400 text-sm font-mono">
154+
<a
155+
href={`https://explorer.testnet.hathor.network/transaction/${op.tx_id}`}
156+
target="_blank"
157+
rel="noopener noreferrer"
158+
className="hover:underline"
159+
>
160+
{formatAddress(op.tx_id)}
161+
</a>
162+
</td>
163+
</tr>
164+
);
165+
})}
166+
</tbody>
167+
</table>
168+
</div>
169+
)}
170+
</div>
171+
);
172+
}

types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export interface Bet {
33
player: string;
44
amount: number;
55
threshold: number;
6+
luckyNumber?: number;
67
result: 'win' | 'lose' | 'pending' | 'failed';
78
payout: number;
89
potentialPayout?: number;

0 commit comments

Comments
 (0)