@qhristen/paygrid 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/login-screen.d.ts +1 -2
- package/dist/auth/login-screen.js +9 -0
- package/dist/checkout/index.d.ts +2 -1
- package/dist/checkout/index.js +92 -0
- package/dist/dashboard/api-section.js +88 -0
- package/dist/dashboard/constant.d.ts +5 -6
- package/dist/dashboard/constant.js +13 -0
- package/dist/dashboard/index.d.ts +2 -1
- package/dist/dashboard/index.js +157 -0
- package/dist/dashboard/payment-table.js +16 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/dist/auth/login-screen.jsx +0 -49
- package/dist/checkout/index.jsx +0 -235
- package/dist/dashboard/api-section.jsx +0 -146
- package/dist/dashboard/constant.jsx +0 -13
- package/dist/dashboard/index.jsx +0 -319
- package/dist/dashboard/payment-table.jsx +0 -85
package/dist/dashboard/index.jsx
DELETED
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { initWASM, isWASMSupported, ShadowWireClient } from "@radr/shadowwire";
|
|
3
|
-
import { clsx } from "clsx";
|
|
4
|
-
import { LogOut, Menu, X } from "lucide-react";
|
|
5
|
-
import { useEffect, useState } from "react";
|
|
6
|
-
import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis, } from "recharts";
|
|
7
|
-
import { twMerge } from "tailwind-merge";
|
|
8
|
-
import { logout } from "../auth/actions";
|
|
9
|
-
import { LoginScreen } from "../auth/login-screen";
|
|
10
|
-
import ApiKeysSection from "./api-section";
|
|
11
|
-
import { Icons } from "./constant";
|
|
12
|
-
import PaymentsTable from "./payment-table";
|
|
13
|
-
function cn(...inputs) {
|
|
14
|
-
return twMerge(clsx(inputs));
|
|
15
|
-
}
|
|
16
|
-
export function PayGridDashboard({ baseUrl = "/api/paygrid", }) {
|
|
17
|
-
const [activeTab, setActiveTab] = useState("overview");
|
|
18
|
-
const [analytics, setAnalytics] = useState(null);
|
|
19
|
-
const [intents, setIntents] = useState([]);
|
|
20
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
21
|
-
const [isAnalyticsLoading, setIsAnalyticsLoading] = useState(false);
|
|
22
|
-
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
|
23
|
-
const [timeframe, setTimeframe] = useState(30);
|
|
24
|
-
const [searchQuery, setSearchQuery] = useState("");
|
|
25
|
-
const [isAdmin, setIsAdmin] = useState(null);
|
|
26
|
-
const apiKey = process.env.NEXT_PUBLIC_PAYGRID_API_SECRET;
|
|
27
|
-
const merchantAddress = process.env.NEXT_PUBLIC_MERCHANT_WALLET_ADDRESS;
|
|
28
|
-
const [client] = useState(() => new ShadowWireClient());
|
|
29
|
-
const [wasmInitialized, setWasmInitialized] = useState(false);
|
|
30
|
-
const [balance, setBalance] = useState(null);
|
|
31
|
-
const fetchAnalytics = async (showLoading = false) => {
|
|
32
|
-
if (showLoading)
|
|
33
|
-
setIsAnalyticsLoading(true);
|
|
34
|
-
try {
|
|
35
|
-
const headers = {};
|
|
36
|
-
if (apiKey)
|
|
37
|
-
headers["x-api-key"] = apiKey;
|
|
38
|
-
const res = await fetch(`${baseUrl}/analytics?days=${timeframe}`, {
|
|
39
|
-
headers,
|
|
40
|
-
});
|
|
41
|
-
if (res.ok) {
|
|
42
|
-
const stats = await res.json();
|
|
43
|
-
setAnalytics(stats);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
console.error("Failed to fetch analytics:", error);
|
|
48
|
-
}
|
|
49
|
-
finally {
|
|
50
|
-
if (showLoading)
|
|
51
|
-
setIsAnalyticsLoading(false);
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
const fetchPayments = async () => {
|
|
55
|
-
try {
|
|
56
|
-
const headers = {};
|
|
57
|
-
if (apiKey)
|
|
58
|
-
headers["x-api-key"] = apiKey;
|
|
59
|
-
const res = await fetch(`${baseUrl}/payments`, { headers });
|
|
60
|
-
if (res.ok) {
|
|
61
|
-
const list = await res.json();
|
|
62
|
-
setIntents(list);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
catch (error) {
|
|
66
|
-
console.error("Failed to fetch payments:", error);
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
async function init() {
|
|
71
|
-
if (!isWASMSupported()) {
|
|
72
|
-
// setError('WebAssembly not supported');
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
try {
|
|
76
|
-
await initWASM("/wasm/settler_wasm_bg.wasm");
|
|
77
|
-
setWasmInitialized(true);
|
|
78
|
-
await loadBalance();
|
|
79
|
-
}
|
|
80
|
-
catch (err) {
|
|
81
|
-
// setError('Initialization failed: ' + err.message);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
init();
|
|
85
|
-
}, []);
|
|
86
|
-
console.log(balance, "wallet balance");
|
|
87
|
-
const loadBalance = async () => {
|
|
88
|
-
try {
|
|
89
|
-
const data = await client.getBalance(merchantAddress ?? "", "USDC");
|
|
90
|
-
setBalance(data.available / 1e9);
|
|
91
|
-
}
|
|
92
|
-
catch (err) {
|
|
93
|
-
console.error("Balance load failed:", err);
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
// Initial load for payments
|
|
97
|
-
useEffect(() => {
|
|
98
|
-
setIsLoading(true);
|
|
99
|
-
fetchPayments().then(() => setIsLoading(false));
|
|
100
|
-
}, [apiKey, baseUrl]);
|
|
101
|
-
// Analytics load (including timeframe changes)
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
fetchAnalytics(true);
|
|
104
|
-
// Check admin session cookie on client side
|
|
105
|
-
const hasSession = document.cookie.includes("paygrid_admin_session=true");
|
|
106
|
-
setIsAdmin(hasSession);
|
|
107
|
-
}, [apiKey, baseUrl, timeframe]);
|
|
108
|
-
const filteredIntents = intents.filter((intent) => {
|
|
109
|
-
const query = searchQuery.toLowerCase();
|
|
110
|
-
return (intent.id.toLowerCase().includes(query) ||
|
|
111
|
-
intent.status.toLowerCase().includes(query) ||
|
|
112
|
-
intent.amount.toString().includes(query) ||
|
|
113
|
-
(intent.walletAddress &&
|
|
114
|
-
intent.walletAddress.toLowerCase().includes(query)));
|
|
115
|
-
});
|
|
116
|
-
const renderContent = () => {
|
|
117
|
-
switch (activeTab) {
|
|
118
|
-
case "overview":
|
|
119
|
-
return (<div className="space-y-6">
|
|
120
|
-
<div className="grid grid-cols-3 md:grid-cols-3 gap-2 md:gap-6">
|
|
121
|
-
<div className="bg-[#111] border border-white/10 p-6 rounded-2xl relative overflow-hidden">
|
|
122
|
-
{isAnalyticsLoading && (<div className="absolute inset-0 bg-black/20 backdrop-blur-[1px] flex items-center justify-center z-10">
|
|
123
|
-
<div className="w-4 h-4 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin"></div>
|
|
124
|
-
</div>)}
|
|
125
|
-
<p className="text-gray-400 text-sm font-medium">
|
|
126
|
-
Total Volume
|
|
127
|
-
</p>
|
|
128
|
-
<h3 className="text-3xl font-bold mt-1 text-white">
|
|
129
|
-
${analytics?.totalRevenue.toFixed(2)}
|
|
130
|
-
</h3>
|
|
131
|
-
<p className={cn("text-xs mt-2", (analytics?.revenueGrowth || 0) >= 0
|
|
132
|
-
? "text-emerald-400"
|
|
133
|
-
: "text-red-400")}>
|
|
134
|
-
{(analytics?.revenueGrowth || 0) >= 0 ? "↑" : "↓"}{" "}
|
|
135
|
-
{Math.abs(analytics?.revenueGrowth || 0).toFixed(1)}% from
|
|
136
|
-
last {timeframe === 30 ? "month" : "week"}
|
|
137
|
-
</p>
|
|
138
|
-
</div>
|
|
139
|
-
<div className="bg-[#111] border border-white/10 p-6 rounded-2xl relative overflow-hidden">
|
|
140
|
-
{isAnalyticsLoading && (<div className="absolute inset-0 bg-black/20 backdrop-blur-[1px] flex items-center justify-center z-10">
|
|
141
|
-
<div className="w-4 h-4 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin"></div>
|
|
142
|
-
</div>)}
|
|
143
|
-
<p className="text-gray-400 text-sm font-medium">
|
|
144
|
-
Payment Intents
|
|
145
|
-
</p>
|
|
146
|
-
<h3 className="text-3xl font-bold mt-1 text-white">
|
|
147
|
-
{analytics?.transactionCount}
|
|
148
|
-
</h3>
|
|
149
|
-
<p className="text-gray-500 text-xs mt-2">
|
|
150
|
-
Past {timeframe} days
|
|
151
|
-
</p>
|
|
152
|
-
</div>
|
|
153
|
-
<div className="bg-[#111] border border-white/10 p-6 rounded-2xl relative overflow-hidden">
|
|
154
|
-
{isAnalyticsLoading && (<div className="absolute inset-0 bg-black/20 backdrop-blur-[1px] flex items-center justify-center z-10">
|
|
155
|
-
<div className="w-4 h-4 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin"></div>
|
|
156
|
-
</div>)}
|
|
157
|
-
<p className="text-gray-400 text-sm font-medium">
|
|
158
|
-
Settlement Rate
|
|
159
|
-
</p>
|
|
160
|
-
<h3 className="text-3xl font-bold mt-1 text-white">
|
|
161
|
-
{analytics?.settlementRate.toFixed(1)}%
|
|
162
|
-
</h3>
|
|
163
|
-
<p className="text-emerald-400 text-xs mt-2">
|
|
164
|
-
High performance
|
|
165
|
-
</p>
|
|
166
|
-
</div>
|
|
167
|
-
</div>
|
|
168
|
-
|
|
169
|
-
<div className="bg-[#111] border border-white/10 p-6 rounded-2xl h-[400px] relative overflow-hidden">
|
|
170
|
-
{isAnalyticsLoading && (<div className="absolute inset-0 bg-black/20 backdrop-blur-[1px] flex items-center justify-center z-10">
|
|
171
|
-
<div className="w-8 h-8 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin"></div>
|
|
172
|
-
</div>)}
|
|
173
|
-
<div className="flex justify-between items-center mb-6">
|
|
174
|
-
<h3 className="font-semibold text-lg">Revenue Over Time</h3>
|
|
175
|
-
<div className="flex gap-2">
|
|
176
|
-
<button onClick={() => setTimeframe(7)} className={cn("text-xs px-3 py-1 rounded-full transition-all border", timeframe === 7
|
|
177
|
-
? "bg-indigo-500/20 border-indigo-500/30 text-indigo-400"
|
|
178
|
-
: "bg-white/5 border-white/10 text-gray-400 hover:bg-white/10")}>
|
|
179
|
-
7D
|
|
180
|
-
</button>
|
|
181
|
-
<button onClick={() => setTimeframe(30)} className={cn("text-xs px-3 py-1 rounded-full transition-all border", timeframe === 30
|
|
182
|
-
? "bg-indigo-500/20 border-indigo-500/30 text-indigo-400"
|
|
183
|
-
: "bg-white/5 border-white/10 text-gray-400 hover:bg-white/10")}>
|
|
184
|
-
30D
|
|
185
|
-
</button>
|
|
186
|
-
</div>
|
|
187
|
-
</div>
|
|
188
|
-
<ResponsiveContainer width="100%" height="80%" minWidth={0}>
|
|
189
|
-
<AreaChart data={analytics?.history || []}>
|
|
190
|
-
<defs>
|
|
191
|
-
<linearGradient id="colorAmt" x1="0" y1="0" x2="0" y2="1">
|
|
192
|
-
<stop offset="5%" stopColor="#6366f1" stopOpacity={0.3}/>
|
|
193
|
-
<stop offset="95%" stopColor="#6366f1" stopOpacity={0}/>
|
|
194
|
-
</linearGradient>
|
|
195
|
-
</defs>
|
|
196
|
-
<CartesianGrid strokeDasharray="3 3" stroke="#222" vertical={false}/>
|
|
197
|
-
<XAxis dataKey="date" stroke="#444" fontSize={12} tickLine={false} axisLine={false}/>
|
|
198
|
-
<YAxis stroke="#444" fontSize={12} tickLine={false} axisLine={false}/>
|
|
199
|
-
<Tooltip contentStyle={{
|
|
200
|
-
backgroundColor: "#111",
|
|
201
|
-
border: "1px solid #333",
|
|
202
|
-
borderRadius: "8px",
|
|
203
|
-
}} itemStyle={{ color: "#fff" }}/>
|
|
204
|
-
<Area type="monotone" dataKey="amount" stroke="#6366f1" fillOpacity={1} fill="url(#colorAmt)" strokeWidth={2}/>
|
|
205
|
-
</AreaChart>
|
|
206
|
-
</ResponsiveContainer>
|
|
207
|
-
</div>
|
|
208
|
-
|
|
209
|
-
<div className="bg-[#111] border border-white/10 rounded-2xl overflow-hidden">
|
|
210
|
-
<div className="px-6 py-4 border-b border-white/10 flex justify-between items-center">
|
|
211
|
-
<h3 className="font-semibold">Recent Activity</h3>
|
|
212
|
-
<button onClick={() => setActiveTab("payments")} className="text-xs text-indigo-400 hover:text-indigo-300">
|
|
213
|
-
View all
|
|
214
|
-
</button>
|
|
215
|
-
</div>
|
|
216
|
-
<PaymentsTable intents={filteredIntents.slice(0, 5)}/>
|
|
217
|
-
</div>
|
|
218
|
-
</div>);
|
|
219
|
-
case "payments":
|
|
220
|
-
return <PaymentsTable intents={filteredIntents} isFullPage/>;
|
|
221
|
-
case "apikeys":
|
|
222
|
-
return <ApiKeysSection apiKey={apiKey} baseUrl={baseUrl}/>;
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
if (isAdmin === null) {
|
|
226
|
-
return null; // or loading spinner
|
|
227
|
-
}
|
|
228
|
-
if (!isAdmin) {
|
|
229
|
-
return <LoginScreen />;
|
|
230
|
-
}
|
|
231
|
-
return (<div className="flex min-h-screen">
|
|
232
|
-
{/* Sidebar Overlay */}
|
|
233
|
-
{isSidebarOpen && (<div className="fixed inset-0 bg-black/60 backdrop-blur-sm z-30 lg:hidden" onClick={() => setIsSidebarOpen(false)}/>)}
|
|
234
|
-
|
|
235
|
-
{/* Sidebar */}
|
|
236
|
-
<aside className={cn("w-64 border-r border-white/10 flex flex-col fixed h-full bg-[#050505] z-40 transition-transform duration-300 ease-in-out", isSidebarOpen
|
|
237
|
-
? "translate-x-0"
|
|
238
|
-
: "-translate-x-full lg:translate-x-0")}>
|
|
239
|
-
<div className="p-6">
|
|
240
|
-
<div className="flex items-center justify-between mb-8">
|
|
241
|
-
<div className="flex items-center gap-2">
|
|
242
|
-
<div className="w-8 h-8 bg-indigo-600 rounded-lg flex items-center justify-center font-bold text-lg">
|
|
243
|
-
P
|
|
244
|
-
</div>
|
|
245
|
-
<span className="font-bold text-xl tracking-tight">PayGrid</span>
|
|
246
|
-
</div>
|
|
247
|
-
<button className="lg:hidden text-gray-400 hover:text-white" onClick={() => setIsSidebarOpen(false)}>
|
|
248
|
-
<X size={20}/>
|
|
249
|
-
</button>
|
|
250
|
-
</div>
|
|
251
|
-
|
|
252
|
-
<nav className="space-y-1">
|
|
253
|
-
<button onClick={() => {
|
|
254
|
-
setActiveTab("overview");
|
|
255
|
-
setIsSidebarOpen(false);
|
|
256
|
-
}} className={`w-full cursor-pointer flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all ${activeTab === "overview" ? "bg-white/10 text-white" : "text-gray-400 hover:text-white hover:bg-white/5"}`}>
|
|
257
|
-
<Icons.Dashboard /> Overview
|
|
258
|
-
</button>
|
|
259
|
-
<button onClick={() => {
|
|
260
|
-
setActiveTab("payments");
|
|
261
|
-
setIsSidebarOpen(false);
|
|
262
|
-
}} className={`w-full cursor-pointer flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all ${activeTab === "payments" ? "bg-white/10 text-white" : "text-gray-400 hover:text-white hover:bg-white/5"}`}>
|
|
263
|
-
<Icons.Payments /> Payments
|
|
264
|
-
</button>
|
|
265
|
-
<button onClick={() => {
|
|
266
|
-
setActiveTab("apikeys");
|
|
267
|
-
setIsSidebarOpen(false);
|
|
268
|
-
}} className={`w-full cursor-pointer flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all ${activeTab === "apikeys" ? "bg-white/10 text-white" : "text-gray-400 hover:text-white hover:bg-white/5"}`}>
|
|
269
|
-
<Icons.Key /> API Keys
|
|
270
|
-
</button>
|
|
271
|
-
</nav>
|
|
272
|
-
</div>
|
|
273
|
-
|
|
274
|
-
<div className="mt-auto p-6 border-t border-white/10">
|
|
275
|
-
<div className="flex items-center gap-3">
|
|
276
|
-
<img src="https://picsum.photos/32/32" className="w-8 h-8 rounded-full" alt="User"/>
|
|
277
|
-
<div className="flex flex-col">
|
|
278
|
-
<span className="text-sm font-medium">Merchant Account</span>
|
|
279
|
-
<span className="text-xs text-gray-500">Live Mode</span>
|
|
280
|
-
</div>
|
|
281
|
-
</div>
|
|
282
|
-
{<button onClick={logout} className="w-full cursor-pointer flex items-center gap-2 mt-4 px-2 py-2 text-red-400 hover:text-red-300 hover:bg-white/5 rounded-lg transition-all text-sm font-medium">
|
|
283
|
-
<LogOut size={16}/> Sign Out
|
|
284
|
-
</button>}
|
|
285
|
-
</div>
|
|
286
|
-
</aside>
|
|
287
|
-
|
|
288
|
-
{/* Main Content */}
|
|
289
|
-
<main className="flex-1 lg:ml-64 p-4 md:p-8 pt-20 lg:pt-8 w-full bg-[#050505] overflow-hidden">
|
|
290
|
-
<header className="flex flex-col md:flex-row md:justify-between md:items-center mb-8 gap-4">
|
|
291
|
-
<div className="flex items-center gap-4">
|
|
292
|
-
<button className="lg:hidden p-2 bg-[#111] border border-white/10 rounded-lg text-gray-400" onClick={() => setIsSidebarOpen(true)}>
|
|
293
|
-
<Menu size={20}/>
|
|
294
|
-
</button>
|
|
295
|
-
<div>
|
|
296
|
-
<h1 className="text-xl md:text-2xl font-bold capitalize">
|
|
297
|
-
{activeTab}
|
|
298
|
-
</h1>
|
|
299
|
-
<p className="text-gray-500 text-xs md:text-sm">
|
|
300
|
-
Manage your Solana payments infrastructure
|
|
301
|
-
</p>
|
|
302
|
-
</div>
|
|
303
|
-
</div>
|
|
304
|
-
<div className="flex gap-3 w-full md:w-auto">
|
|
305
|
-
<div className="relative w-full md:w-auto">
|
|
306
|
-
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-500">
|
|
307
|
-
<Icons.Search />
|
|
308
|
-
</div>
|
|
309
|
-
<input type="text" placeholder="Search intents..." value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} className="w-full bg-[#111] border border-white/10 rounded-xl pl-10 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500/50"/>
|
|
310
|
-
</div>
|
|
311
|
-
</div>
|
|
312
|
-
</header>
|
|
313
|
-
|
|
314
|
-
{isLoading ? (<div className="flex items-center justify-center h-64">
|
|
315
|
-
<div className="w-8 h-8 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin"></div>
|
|
316
|
-
</div>) : (renderContent())}
|
|
317
|
-
</main>
|
|
318
|
-
</div>);
|
|
319
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { PaymentStatus } from '../types';
|
|
4
|
-
import { Icons } from './constant';
|
|
5
|
-
const statusColors = {
|
|
6
|
-
[PaymentStatus.SETTLED]: 'text-emerald-400 bg-emerald-400/10 border-emerald-400/20',
|
|
7
|
-
[PaymentStatus.PENDING_CONFIRMATION]: 'text-amber-400 bg-amber-400/10 border-amber-400/20',
|
|
8
|
-
[PaymentStatus.AWAITING_PAYMENT]: 'text-indigo-400 bg-indigo-400/10 border-indigo-400/20',
|
|
9
|
-
[PaymentStatus.CREATED]: 'text-gray-400 bg-gray-400/10 border-gray-400/20',
|
|
10
|
-
[PaymentStatus.EXPIRED]: 'text-red-400 bg-red-400/10 border-red-400/20',
|
|
11
|
-
[PaymentStatus.FAILED]: 'text-red-500 bg-red-500/10 border-red-500/20',
|
|
12
|
-
};
|
|
13
|
-
const PaymentsTable = ({ intents, isFullPage }) => {
|
|
14
|
-
return (<div className={`overflow-x-auto ${isFullPage ? 'bg-[#111] border border-white/10 rounded-2xl' : ''}`}>
|
|
15
|
-
{/* Desktop Table */}
|
|
16
|
-
<table className="hidden md:table w-full text-left text-sm min-w-[800px]">
|
|
17
|
-
<thead className="border-b border-white/10 text-gray-400 font-medium">
|
|
18
|
-
<tr>
|
|
19
|
-
<th className="px-6 py-4">Status</th>
|
|
20
|
-
<th className="px-6 py-4">Amount</th>
|
|
21
|
-
<th className="px-6 py-4">Intent ID</th>
|
|
22
|
-
<th className="px-6 py-4">Destination</th>
|
|
23
|
-
<th className="px-6 py-4">Created</th>
|
|
24
|
-
<th className="px-6 py-4 text-right">Action</th>
|
|
25
|
-
</tr>
|
|
26
|
-
</thead>
|
|
27
|
-
<tbody className="divide-y divide-white/5">
|
|
28
|
-
{intents?.map((intent) => (<tr key={intent.id} className="hover:bg-white/[0.02] transition-colors">
|
|
29
|
-
<td className="px-6 py-4">
|
|
30
|
-
<span className={`px-2 py-1 rounded-full text-[10px] uppercase font-bold border ${statusColors[intent.status]}`}>
|
|
31
|
-
{intent.status.replace('_', ' ')}
|
|
32
|
-
</span>
|
|
33
|
-
</td>
|
|
34
|
-
<td className="px-6 py-4 font-mono font-medium">
|
|
35
|
-
{intent.amount} {intent.tokenSymbol}
|
|
36
|
-
</td>
|
|
37
|
-
<td className="px-6 py-4 text-gray-400 font-mono text-xs">
|
|
38
|
-
{intent.id}
|
|
39
|
-
</td>
|
|
40
|
-
<td className="px-6 py-4 text-gray-500 text-xs font-mono">
|
|
41
|
-
{intent?.walletAddress ? `${intent.walletAddress.slice(0, 8)}...${intent.walletAddress.slice(-8)}` : 'Main Treasury'}
|
|
42
|
-
</td>
|
|
43
|
-
<td className="px-6 py-4 text-gray-500 whitespace-nowrap text-xs">
|
|
44
|
-
{new Date(intent.createdAt).toLocaleString()}
|
|
45
|
-
</td>
|
|
46
|
-
<td className="px-6 py-4 text-right">
|
|
47
|
-
<button className="p-2 hover:bg-white/5 rounded-lg text-gray-400 hover:text-white transition-colors">
|
|
48
|
-
<Icons.External />
|
|
49
|
-
</button>
|
|
50
|
-
</td>
|
|
51
|
-
</tr>))}
|
|
52
|
-
{intents?.length === 0 && (<tr>
|
|
53
|
-
<td colSpan={6} className="px-6 py-12 text-center text-gray-500">
|
|
54
|
-
No payment intents found.
|
|
55
|
-
</td>
|
|
56
|
-
</tr>)}
|
|
57
|
-
</tbody>
|
|
58
|
-
</table>
|
|
59
|
-
|
|
60
|
-
{/* Mobile Card View */}
|
|
61
|
-
<div className="md:hidden divide-y divide-white/5">
|
|
62
|
-
{intents?.map((intent) => (<div key={intent.id} className="p-4 space-y-3">
|
|
63
|
-
<div className="flex justify-between items-center">
|
|
64
|
-
<span className={`px-2 py-0.5 rounded-full text-[10px] uppercase font-bold border ${statusColors[intent.status]}`}>
|
|
65
|
-
{intent.status.replace('_', ' ')}
|
|
66
|
-
</span>
|
|
67
|
-
<p className="text-gray-500 text-[10px]">{new Date(intent.createdAt).toLocaleString()}</p>
|
|
68
|
-
</div>
|
|
69
|
-
<div className="flex justify-between items-end">
|
|
70
|
-
<div>
|
|
71
|
-
<h4 className="text-lg font-bold text-white">{intent.amount} {intent.tokenSymbol}</h4>
|
|
72
|
-
<p className="text-xs text-gray-500 font-mono mt-1">{intent.id.slice(0, 18)}...</p>
|
|
73
|
-
</div>
|
|
74
|
-
<button className="p-2 bg-white/5 rounded-lg text-gray-400">
|
|
75
|
-
<Icons.External />
|
|
76
|
-
</button>
|
|
77
|
-
</div>
|
|
78
|
-
</div>))}
|
|
79
|
-
{intents?.length === 0 && (<div className="px-6 py-12 text-center text-gray-500">
|
|
80
|
-
No payment intents found.
|
|
81
|
-
</div>)}
|
|
82
|
-
</div>
|
|
83
|
-
</div>);
|
|
84
|
-
};
|
|
85
|
-
export default PaymentsTable;
|