Skip to content

Commit 6392818

Browse files
authored
Merge branch 'main' into fix/issue-9-event-poll
2 parents 6eb92e1 + 6ae051a commit 6392818

17 files changed

Lines changed: 672 additions & 454 deletions

File tree

src/app/page.tsx

Lines changed: 285 additions & 188 deletions
Large diffs are not rendered by default.

src/components/DashboardLayout/SortableWidget.tsx

Lines changed: 0 additions & 50 deletions
This file was deleted.

src/components/DashboardLayout/WidgetGrid.tsx

Lines changed: 0 additions & 121 deletions
This file was deleted.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { Download, Copy } from 'lucide-react';
3+
import { fetchProofHistory, type ProofRecord } from '@/services/proofService';
4+
import { useTranslation } from 'react-i18next';
5+
6+
/**
7+
* Premium styled table displaying ZK‑proof submission history.
8+
* Includes copy‑to‑clipboard for proof hash and download button for proof blob.
9+
*/
10+
const ProofHistoryTable: React.FC = () => {
11+
const { t } = useTranslation();
12+
const [proofs, setProofs] = useState<ProofRecord[]>([]);
13+
const [loading, setLoading] = useState(true);
14+
const [error, setError] = useState<string | null>(null);
15+
16+
useEffect(() => {
17+
const load = async () => {
18+
try {
19+
const data = await fetchProofHistory();
20+
setProofs(data);
21+
} catch (e: any) {
22+
setError(e.message ?? t('proofs.failedLoad'));
23+
} finally {
24+
setLoading(false);
25+
}
26+
};
27+
load();
28+
}, [t]);
29+
30+
const handleCopy = (hash: string) => {
31+
navigator.clipboard.writeText(hash).catch(() => {
32+
// ignore clipboard errors for now
33+
});
34+
};
35+
36+
const handleDownload = (proof: ProofRecord) => {
37+
const blob = new Blob([proof.proofBlob], { type: 'application/octet-stream' });
38+
const url = URL.createObjectURL(blob);
39+
const a = document.createElement('a');
40+
a.href = url;
41+
a.download = `${proof.taskId}-${proof.timestamp}.proof`;
42+
a.click();
43+
URL.revokeObjectURL(url);
44+
};
45+
46+
if (loading) {
47+
return <div className="text-gray-500">{t('proofs.loading')}</div>;
48+
}
49+
50+
if (error) {
51+
return <div className="text-red-600">{t('proofs.error', { message: error })}</div>;
52+
}
53+
54+
return (
55+
<div className="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-4 overflow-x-auto">
56+
<h3 className="text-lg font-semibold mb-4 text-slate-800 dark:text-slate-100">{t('proofs.heading')}</h3>
57+
<table className="min-w-full divide-y divide-slate-200 dark:divide-slate-700">
58+
<thead className="bg-slate-50 dark:bg-slate-700">
59+
<tr>
60+
<th className="px-4 py-2 text-left text-sm font-medium text-slate-600 dark:text-slate-300">{t('proofs.timestamp')}</th>
61+
<th className="px-4 py-2 text-left text-sm font-medium text-slate-600 dark:text-slate-300">{t('proofs.taskId')}</th>
62+
<th className="px-4 py-2 text-left text-sm font-medium text-slate-600 dark:text-slate-300">{t('proofs.proofHash')}</th>
63+
<th className="px-4 py-2 text-center text-sm font-medium text-slate-600 dark:text-slate-300">{t('proofs.actions')}</th>
64+
</tr>
65+
</thead>
66+
<tbody className="divide-y divide-slate-100 dark:divide-slate-600">
67+
{proofs.map((p) => (
68+
<tr key={p.proofHash} className="hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors">
69+
<td className="px-4 py-2 text-sm text-slate-800 dark:text-slate-200">{new Date(p.timestamp).toLocaleString()}</td>
70+
<td className="px-4 py-2 text-sm text-slate-800 dark:text-slate-200">{p.taskId}</td>
71+
<td className="px-4 py-2 text-sm text-slate-800 dark:text-slate-200 break-all">{p.proofHash}</td>
72+
<td className="px-4 py-2 text-center space-x-2">
73+
<button
74+
onClick={() => handleCopy(p.proofHash)}
75+
className="p-1 rounded hover:bg-slate-200 dark:hover:bg-slate-600 transition-colors"
76+
aria-label={t('proofs.copyHash')}
77+
>
78+
<Copy size={16} className="text-slate-600 dark:text-slate-300" />
79+
</button>
80+
<button
81+
onClick={() => handleDownload(p)}
82+
className="p-1 rounded hover:bg-slate-200 dark:hover:bg-slate-600 transition-colors"
83+
aria-label={t('proofs.download')}
84+
>
85+
<Download size={16} className="text-slate-600 dark:text-slate-300" />
86+
</button>
87+
</td>
88+
</tr>
89+
))}
90+
</tbody>
91+
</table>
92+
</div>
93+
);
94+
};
95+
96+
export default ProofHistoryTable;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as ProofHistoryTable } from './ProofHistoryTable';

src/components/VoteButton/__tests__/VoteButton.test.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@ jest.mock('@/services/contractClient', () => ({
1111
jest.mock('@/context/RoleContext', () => ({
1212
useRole: jest.fn(),
1313
}));
14-
jest.mock('@/context/NetworkContext', () => ({
15-
useNetwork: jest.fn(),
16-
}));
1714
jest.mock('@/components/Toast');
1815
jest.mock('@/utils/logger', () => ({
1916
appendAuditEvent: jest.fn(() => Promise.resolve()),

0 commit comments

Comments
 (0)