@wopr-network/platform-ui-core 1.26.0 → 1.27.1
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/package.json +1 -1
- package/src/__tests__/billing.test.tsx +2 -2
- package/src/app/(dashboard)/billing/usage/hosted/page.tsx +1 -1
- package/src/app/(dashboard)/onboarding/page.tsx +0 -1
- package/src/app/(dashboard)/settings/profile/page.tsx +1 -0
- package/src/app/admin/pool-config/page.tsx +5 -0
- package/src/app/globals.css +53 -53
- package/src/app/instances/new/create-instance-client.tsx +0 -1
- package/src/components/admin/admin-nav.tsx +1 -0
- package/src/components/admin/pool-config-dashboard.tsx +139 -0
- package/src/components/billing/confirmation-tracker.tsx +78 -90
- package/src/components/billing/crypto-checkout.tsx +3 -1
- package/src/components/billing/deposit-view.tsx +10 -2
- package/src/components/billing/payment-method-picker.tsx +1 -5
- package/src/components/landing/portfolio-chart.tsx +1 -1
- package/src/components/sidebar.tsx +5 -2
- package/src/lib/admin-pool-api.ts +15 -0
- package/src/lib/trpc-types.ts +2 -0
package/package.json
CHANGED
|
@@ -408,8 +408,8 @@ describe("Payment page", () => {
|
|
|
408
408
|
const amountCells = await screen.findAllByText("$29.00");
|
|
409
409
|
// inv-001 has no downloadUrl and no hostedUrl — the row should render no <a> element at all
|
|
410
410
|
// The 3rd amount cell belongs to inv-001 (oldest invoice, no URLs)
|
|
411
|
-
const inv001Row = amountCells[2].closest("tr")
|
|
412
|
-
expect(within(inv001Row).queryByRole("link")).toBeNull();
|
|
411
|
+
const inv001Row = amountCells[2].closest("tr");
|
|
412
|
+
expect(inv001Row ? within(inv001Row).queryByRole("link") : null).toBeNull();
|
|
413
413
|
});
|
|
414
414
|
|
|
415
415
|
it("renders BYOK messaging", async () => {
|
|
@@ -311,7 +311,7 @@ export default function HostedUsageDetailPage() {
|
|
|
311
311
|
</TableRow>
|
|
312
312
|
</TableHeader>
|
|
313
313
|
<TableBody>
|
|
314
|
-
{filteredEvents.map((event,
|
|
314
|
+
{filteredEvents.map((event, _index) => (
|
|
315
315
|
<tr
|
|
316
316
|
key={event.id}
|
|
317
317
|
className="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted"
|
|
@@ -32,7 +32,6 @@ export default function OnboardingPage() {
|
|
|
32
32
|
const [botName, setBotName] = useState("");
|
|
33
33
|
const [selectedPreset, setSelectedPreset] = useState<string | null>(null);
|
|
34
34
|
|
|
35
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: router.push is stable; using [router] causes infinite re-renders
|
|
36
35
|
useEffect(() => {
|
|
37
36
|
if (isOnboardingComplete()) {
|
|
38
37
|
router.push(homePath());
|
|
@@ -253,6 +253,7 @@ export default function ProfilePage() {
|
|
|
253
253
|
disabled={uploading}
|
|
254
254
|
>
|
|
255
255
|
{profile.avatarUrl ? (
|
|
256
|
+
// biome-ignore lint/performance/noImgElement: external avatar URL — domain not configured for next/image
|
|
256
257
|
<img
|
|
257
258
|
src={profile.avatarUrl}
|
|
258
259
|
alt={profile.name}
|
package/src/app/globals.css
CHANGED
|
@@ -261,114 +261,114 @@
|
|
|
261
261
|
|
|
262
262
|
/* ── Sonner toast — match platform theme ─────────────────────────── */
|
|
263
263
|
[data-sonner-toaster] {
|
|
264
|
-
font-family: var(--font-mono), ui-monospace, monospace
|
|
265
|
-
--width: 320px
|
|
266
|
-
position: fixed
|
|
267
|
-
bottom: 20px
|
|
268
|
-
right: 20px
|
|
269
|
-
left: auto
|
|
270
|
-
top: auto
|
|
271
|
-
z-index: 99999
|
|
272
|
-
transform: none
|
|
264
|
+
font-family: var(--font-mono), ui-monospace, monospace;
|
|
265
|
+
--width: 320px;
|
|
266
|
+
position: fixed;
|
|
267
|
+
bottom: 20px;
|
|
268
|
+
right: 20px;
|
|
269
|
+
left: auto;
|
|
270
|
+
top: auto;
|
|
271
|
+
z-index: 99999;
|
|
272
|
+
transform: none;
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
[data-sonner-toast] {
|
|
276
|
-
background: var(--card)
|
|
277
|
-
border: 1px solid var(--border)
|
|
278
|
-
border-radius: var(--radius-md)
|
|
279
|
-
color: var(--foreground)
|
|
280
|
-
font-family: var(--font-mono), ui-monospace, monospace
|
|
281
|
-
padding: 10px 14px
|
|
282
|
-
gap: 6px
|
|
283
|
-
text-align: center
|
|
284
|
-
justify-content: center
|
|
276
|
+
background: var(--card);
|
|
277
|
+
border: 1px solid var(--border);
|
|
278
|
+
border-radius: var(--radius-md);
|
|
279
|
+
color: var(--foreground);
|
|
280
|
+
font-family: var(--font-mono), ui-monospace, monospace;
|
|
281
|
+
padding: 10px 14px;
|
|
282
|
+
gap: 6px;
|
|
283
|
+
text-align: center;
|
|
284
|
+
justify-content: center;
|
|
285
285
|
}
|
|
286
286
|
|
|
287
287
|
.dark [data-sonner-toast] {
|
|
288
288
|
box-shadow:
|
|
289
289
|
0 4px 16px rgba(0, 0, 0, 0.5),
|
|
290
|
-
inset 0 0 0 1px rgba(255, 255, 255, 0.03)
|
|
290
|
+
inset 0 0 0 1px rgba(255, 255, 255, 0.03);
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
[data-sonner-toast] [data-title] {
|
|
294
|
-
color: var(--foreground)
|
|
295
|
-
font-weight: 600
|
|
296
|
-
font-size: 12px
|
|
297
|
-
line-height: 1.4
|
|
294
|
+
color: var(--foreground);
|
|
295
|
+
font-weight: 600;
|
|
296
|
+
font-size: 12px;
|
|
297
|
+
line-height: 1.4;
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
[data-sonner-toast] [data-description] {
|
|
301
|
-
color: var(--muted-foreground)
|
|
302
|
-
font-size: 11px
|
|
301
|
+
color: var(--muted-foreground);
|
|
302
|
+
font-size: 11px;
|
|
303
303
|
}
|
|
304
304
|
|
|
305
305
|
[data-sonner-toast] [data-close-button] {
|
|
306
|
-
background: var(--muted)
|
|
307
|
-
border-color: var(--border)
|
|
308
|
-
color: var(--muted-foreground)
|
|
306
|
+
background: var(--muted);
|
|
307
|
+
border-color: var(--border);
|
|
308
|
+
color: var(--muted-foreground);
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
[data-sonner-toast] [data-close-button]:hover {
|
|
312
|
-
background: var(--accent)
|
|
313
|
-
color: var(--foreground)
|
|
312
|
+
background: var(--accent);
|
|
313
|
+
color: var(--foreground);
|
|
314
314
|
}
|
|
315
315
|
|
|
316
316
|
/* Success — subtle terminal-color tint */
|
|
317
317
|
.dark [data-sonner-toast][data-type="success"] {
|
|
318
|
-
background: color-mix(in srgb, var(--terminal) 6%, var(--card))
|
|
319
|
-
border-color: color-mix(in srgb, var(--terminal) 30%, transparent)
|
|
318
|
+
background: color-mix(in srgb, var(--terminal) 6%, var(--card));
|
|
319
|
+
border-color: color-mix(in srgb, var(--terminal) 30%, transparent);
|
|
320
320
|
}
|
|
321
321
|
[data-sonner-toast][data-type="success"] [data-icon] {
|
|
322
|
-
color: var(--terminal)
|
|
322
|
+
color: var(--terminal);
|
|
323
323
|
}
|
|
324
324
|
|
|
325
325
|
/* Error — subtle red tint */
|
|
326
326
|
.dark [data-sonner-toast][data-type="error"] {
|
|
327
|
-
background: color-mix(in srgb, var(--destructive) 6%, var(--card))
|
|
328
|
-
border-color: color-mix(in srgb, var(--destructive) 30%, transparent)
|
|
327
|
+
background: color-mix(in srgb, var(--destructive) 6%, var(--card));
|
|
328
|
+
border-color: color-mix(in srgb, var(--destructive) 30%, transparent);
|
|
329
329
|
}
|
|
330
330
|
[data-sonner-toast][data-type="error"] [data-icon] {
|
|
331
|
-
color: var(--destructive)
|
|
331
|
+
color: var(--destructive);
|
|
332
332
|
}
|
|
333
333
|
|
|
334
334
|
/* Warning — subtle amber tint */
|
|
335
335
|
.dark [data-sonner-toast][data-type="warning"] {
|
|
336
|
-
background: color-mix(in srgb, #f59e0b 6%, var(--card))
|
|
337
|
-
border-color: color-mix(in srgb, #f59e0b 30%, transparent)
|
|
336
|
+
background: color-mix(in srgb, #f59e0b 6%, var(--card));
|
|
337
|
+
border-color: color-mix(in srgb, #f59e0b 30%, transparent);
|
|
338
338
|
}
|
|
339
339
|
[data-sonner-toast][data-type="warning"] [data-icon] {
|
|
340
|
-
color: #f59e0b
|
|
340
|
+
color: #f59e0b;
|
|
341
341
|
}
|
|
342
342
|
|
|
343
343
|
/* Info */
|
|
344
344
|
.dark [data-sonner-toast][data-type="info"] {
|
|
345
|
-
background: color-mix(in srgb, #3b82f6 6%, var(--card))
|
|
346
|
-
border-color: color-mix(in srgb, #3b82f6 30%, transparent)
|
|
345
|
+
background: color-mix(in srgb, #3b82f6 6%, var(--card));
|
|
346
|
+
border-color: color-mix(in srgb, #3b82f6 30%, transparent);
|
|
347
347
|
}
|
|
348
348
|
[data-sonner-toast][data-type="info"] [data-icon] {
|
|
349
|
-
color: #3b82f6
|
|
349
|
+
color: #3b82f6;
|
|
350
350
|
}
|
|
351
351
|
|
|
352
352
|
/* Loading — faint terminal pulse */
|
|
353
353
|
.dark [data-sonner-toast][data-type="loading"] {
|
|
354
|
-
border-color: color-mix(in srgb, var(--terminal) 20%, transparent)
|
|
354
|
+
border-color: color-mix(in srgb, var(--terminal) 20%, transparent);
|
|
355
355
|
}
|
|
356
356
|
[data-sonner-toast][data-type="loading"] [data-icon] {
|
|
357
|
-
color: var(--muted-foreground)
|
|
357
|
+
color: var(--muted-foreground);
|
|
358
358
|
}
|
|
359
359
|
|
|
360
360
|
/* Action & cancel buttons inside toasts */
|
|
361
361
|
[data-sonner-toast] button[data-button] {
|
|
362
|
-
background: var(--primary)
|
|
363
|
-
color: var(--primary-foreground)
|
|
364
|
-
border-radius: var(--radius-sm)
|
|
365
|
-
font-size: 12px
|
|
366
|
-
font-weight: 600
|
|
362
|
+
background: var(--primary);
|
|
363
|
+
color: var(--primary-foreground);
|
|
364
|
+
border-radius: var(--radius-sm);
|
|
365
|
+
font-size: 12px;
|
|
366
|
+
font-weight: 600;
|
|
367
367
|
}
|
|
368
368
|
|
|
369
369
|
[data-sonner-toast] button[data-cancel] {
|
|
370
|
-
background: var(--muted)
|
|
371
|
-
color: var(--muted-foreground)
|
|
372
|
-
border-radius: var(--radius-sm)
|
|
373
|
-
font-size: 12px
|
|
370
|
+
background: var(--muted);
|
|
371
|
+
color: var(--muted-foreground);
|
|
372
|
+
border-radius: var(--radius-sm);
|
|
373
|
+
font-size: 12px;
|
|
374
374
|
}
|
|
@@ -20,7 +20,6 @@ import { Separator } from "@/components/ui/separator";
|
|
|
20
20
|
import { usePluginRegistry } from "@/hooks/use-plugin-registry";
|
|
21
21
|
import { createInstance } from "@/lib/api";
|
|
22
22
|
import { productName } from "@/lib/brand-config";
|
|
23
|
-
import { toUserMessage } from "@/lib/errors";
|
|
24
23
|
import { cn } from "@/lib/utils";
|
|
25
24
|
|
|
26
25
|
const PRESET_ACCENT_COLORS: Record<string, string> = {
|
|
@@ -23,6 +23,7 @@ const adminNavItems = [
|
|
|
23
23
|
{ label: "Migrations", href: "/admin/migrations" },
|
|
24
24
|
{ label: "GPU", href: "/admin/gpu" },
|
|
25
25
|
{ label: "Fleet Updates", href: "/admin/fleet-updates" },
|
|
26
|
+
{ label: "Pool", href: "/admin/pool-config" },
|
|
26
27
|
{ label: "Incidents", href: "/admin/incidents" },
|
|
27
28
|
];
|
|
28
29
|
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from "react";
|
|
4
|
+
import { toast } from "sonner";
|
|
5
|
+
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { Input } from "@/components/ui/input";
|
|
8
|
+
import { getPoolConfig, type PoolConfig, setPoolSize } from "@/lib/admin-pool-api";
|
|
9
|
+
|
|
10
|
+
export function PoolConfigDashboard() {
|
|
11
|
+
const [config, setConfig] = useState<PoolConfig | null>(null);
|
|
12
|
+
const [loading, setLoading] = useState(true);
|
|
13
|
+
const [saving, setSaving] = useState(false);
|
|
14
|
+
const [sizeInput, setSizeInput] = useState("");
|
|
15
|
+
|
|
16
|
+
const load = useCallback(async () => {
|
|
17
|
+
try {
|
|
18
|
+
const data = await getPoolConfig();
|
|
19
|
+
setConfig(data);
|
|
20
|
+
setSizeInput(String(data.poolSize));
|
|
21
|
+
} catch {
|
|
22
|
+
toast.error("Failed to load pool config");
|
|
23
|
+
} finally {
|
|
24
|
+
setLoading(false);
|
|
25
|
+
}
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
load();
|
|
30
|
+
}, [load]);
|
|
31
|
+
|
|
32
|
+
const handleSave = async () => {
|
|
33
|
+
const size = Number.parseInt(sizeInput, 10);
|
|
34
|
+
if (Number.isNaN(size) || size < 0 || size > 50) {
|
|
35
|
+
toast.error("Pool size must be between 0 and 50");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
setSaving(true);
|
|
39
|
+
try {
|
|
40
|
+
const result = await setPoolSize(size);
|
|
41
|
+
setConfig((prev) => (prev ? { ...prev, poolSize: result.poolSize } : prev));
|
|
42
|
+
toast.success(`Pool size updated to ${result.poolSize}`);
|
|
43
|
+
} catch {
|
|
44
|
+
toast.error("Failed to update pool size");
|
|
45
|
+
} finally {
|
|
46
|
+
setSaving(false);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (loading) {
|
|
51
|
+
return (
|
|
52
|
+
<div className="space-y-6 p-6">
|
|
53
|
+
<div className="h-8 w-48 animate-pulse rounded bg-muted" />
|
|
54
|
+
<div className="grid grid-cols-3 gap-4">
|
|
55
|
+
{[1, 2, 3].map((i) => (
|
|
56
|
+
<div key={i} className="h-24 animate-pulse rounded-lg bg-muted" />
|
|
57
|
+
))}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!config) {
|
|
64
|
+
return <div className="p-6 text-muted-foreground">Failed to load pool configuration.</div>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!config.enabled) {
|
|
68
|
+
return (
|
|
69
|
+
<div className="p-6">
|
|
70
|
+
<h2 className="text-lg font-semibold mb-2">Hot Pool</h2>
|
|
71
|
+
<p className="text-muted-foreground">
|
|
72
|
+
Hot pool is not enabled for this product. Enable the{" "}
|
|
73
|
+
<code className="text-xs bg-muted px-1 py-0.5 rounded">hotPool</code> feature flag in the
|
|
74
|
+
boot config to use pre-provisioned instances.
|
|
75
|
+
</p>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div className="space-y-6 p-6">
|
|
82
|
+
<div>
|
|
83
|
+
<h2 className="text-lg font-semibold">Hot Pool</h2>
|
|
84
|
+
<p className="text-sm text-muted-foreground">
|
|
85
|
+
Pre-provisioned warm containers for instant instance creation.
|
|
86
|
+
</p>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
|
90
|
+
<div className="rounded-lg border border-border bg-card p-4">
|
|
91
|
+
<div className="text-sm text-muted-foreground">Target Size</div>
|
|
92
|
+
<div className="mt-1 text-2xl font-bold text-terminal">{config.poolSize}</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<div className="rounded-lg border border-border bg-card p-4">
|
|
96
|
+
<div className="text-sm text-muted-foreground">Warm Containers</div>
|
|
97
|
+
<div className="mt-1 text-2xl font-bold text-green-400">{config.warmCount}</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div className="rounded-lg border border-border bg-card p-4">
|
|
101
|
+
<div className="text-sm text-muted-foreground">Status</div>
|
|
102
|
+
<div className="mt-1 text-2xl font-bold">
|
|
103
|
+
{config.warmCount >= config.poolSize ? (
|
|
104
|
+
<span className="text-green-400">Full</span>
|
|
105
|
+
) : config.warmCount > 0 ? (
|
|
106
|
+
<span className="text-amber-400">Filling</span>
|
|
107
|
+
) : (
|
|
108
|
+
<span className="text-red-400">Empty</span>
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div className="rounded-lg border border-border bg-card p-4">
|
|
115
|
+
<div className="text-sm font-medium mb-3">Adjust Pool Size</div>
|
|
116
|
+
<div className="flex items-center gap-3">
|
|
117
|
+
<Input
|
|
118
|
+
type="number"
|
|
119
|
+
min={0}
|
|
120
|
+
max={50}
|
|
121
|
+
value={sizeInput}
|
|
122
|
+
onChange={(e) => setSizeInput(e.target.value)}
|
|
123
|
+
className="w-24"
|
|
124
|
+
/>
|
|
125
|
+
<Button
|
|
126
|
+
onClick={handleSave}
|
|
127
|
+
disabled={saving || sizeInput === String(config.poolSize)}
|
|
128
|
+
size="sm"
|
|
129
|
+
>
|
|
130
|
+
{saving ? "Saving..." : "Update"}
|
|
131
|
+
</Button>
|
|
132
|
+
<span className="text-xs text-muted-foreground">
|
|
133
|
+
The pool manager will replenish to this target within 60 seconds.
|
|
134
|
+
</span>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
@@ -3,102 +3,90 @@
|
|
|
3
3
|
import { Check } from "lucide-react";
|
|
4
4
|
|
|
5
5
|
interface ConfirmationTrackerProps {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
confirmations: number;
|
|
7
|
+
confirmationsRequired: number;
|
|
8
|
+
displayAmount: string;
|
|
9
|
+
credited: boolean;
|
|
10
|
+
txHash?: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function ConfirmationTracker({
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
confirmations,
|
|
15
|
+
confirmationsRequired,
|
|
16
|
+
displayAmount,
|
|
17
|
+
credited,
|
|
18
|
+
txHash,
|
|
19
19
|
}: ConfirmationTrackerProps) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
const pct =
|
|
21
|
+
confirmationsRequired > 0
|
|
22
|
+
? Math.min(100, Math.round((confirmations / confirmationsRequired) * 100))
|
|
23
|
+
: 0;
|
|
24
|
+
const detected = confirmations > 0 || credited;
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
return (
|
|
27
|
+
<div className="space-y-4 text-center">
|
|
28
|
+
<p className="text-sm text-muted-foreground">
|
|
29
|
+
{credited ? "Payment complete!" : "Payment received!"}
|
|
30
|
+
</p>
|
|
31
|
+
<p className="text-xl font-semibold">{displayAmount}</p>
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
33
|
+
<div className="rounded-lg border border-border p-3 space-y-2">
|
|
34
|
+
<div className="flex justify-between text-xs">
|
|
35
|
+
<span className="text-muted-foreground">Confirmations</span>
|
|
36
|
+
<span>
|
|
37
|
+
{confirmations} / {confirmationsRequired}
|
|
38
|
+
</span>
|
|
39
|
+
</div>
|
|
40
|
+
<div
|
|
41
|
+
className="h-1.5 rounded-full bg-muted overflow-hidden"
|
|
42
|
+
role="progressbar"
|
|
43
|
+
aria-valuenow={pct}
|
|
44
|
+
aria-valuemin={0}
|
|
45
|
+
aria-valuemax={100}
|
|
46
|
+
>
|
|
47
|
+
<div
|
|
48
|
+
className="h-full rounded-full bg-primary transition-all duration-500"
|
|
49
|
+
style={{ width: `${pct}%` }}
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
</div>
|
|
89
|
-
<span
|
|
90
|
-
className={`text-xs ${credited ? "text-foreground" : "text-muted-foreground/50"}`}
|
|
91
|
-
>
|
|
92
|
-
Credits applied
|
|
93
|
-
</span>
|
|
94
|
-
</div>
|
|
95
|
-
</div>
|
|
54
|
+
<div className="space-y-2 text-left">
|
|
55
|
+
<div className="flex items-center gap-2">
|
|
56
|
+
<div
|
|
57
|
+
className={`flex h-4 w-4 items-center justify-center rounded-full text-[10px] ${detected ? "bg-green-500 text-white" : "bg-muted"}`}
|
|
58
|
+
>
|
|
59
|
+
{detected && <Check className="h-2.5 w-2.5" />}
|
|
60
|
+
</div>
|
|
61
|
+
<span
|
|
62
|
+
className={`text-xs ${detected ? "text-muted-foreground" : "text-muted-foreground/50"}`}
|
|
63
|
+
>
|
|
64
|
+
Payment detected
|
|
65
|
+
</span>
|
|
66
|
+
</div>
|
|
67
|
+
<div className="flex items-center gap-2">
|
|
68
|
+
<div
|
|
69
|
+
className={`flex h-4 w-4 items-center justify-center rounded-full text-[10px] ${credited ? "bg-green-500 text-white" : detected ? "bg-primary text-white animate-pulse" : "bg-muted"}`}
|
|
70
|
+
>
|
|
71
|
+
{credited ? <Check className="h-2.5 w-2.5" /> : detected ? <span>·</span> : null}
|
|
72
|
+
</div>
|
|
73
|
+
<span className={`text-xs ${detected ? "text-foreground" : "text-muted-foreground/50"}`}>
|
|
74
|
+
{credited ? "Confirmed" : "Confirming on chain"}
|
|
75
|
+
</span>
|
|
76
|
+
</div>
|
|
77
|
+
<div className="flex items-center gap-2">
|
|
78
|
+
<div
|
|
79
|
+
className={`flex h-4 w-4 items-center justify-center rounded-full text-[10px] ${credited ? "bg-green-500 text-white" : "bg-muted"}`}
|
|
80
|
+
>
|
|
81
|
+
{credited && <Check className="h-2.5 w-2.5" />}
|
|
82
|
+
</div>
|
|
83
|
+
<span className={`text-xs ${credited ? "text-foreground" : "text-muted-foreground/50"}`}>
|
|
84
|
+
Credits applied
|
|
85
|
+
</span>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
96
88
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
</p>
|
|
101
|
-
)}
|
|
102
|
-
</div>
|
|
103
|
-
);
|
|
89
|
+
{txHash && <p className="text-xs text-muted-foreground font-mono truncate">tx: {txHash}</p>}
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
104
92
|
}
|
|
@@ -45,8 +45,16 @@ export function DepositView({ checkout, status, onBack }: DepositViewProps) {
|
|
|
45
45
|
<p className="text-xs text-muted-foreground">
|
|
46
46
|
on {checkout.chain} · ${checkout.amountUsd.toFixed(2)} USD
|
|
47
47
|
</p>
|
|
48
|
-
<div
|
|
49
|
-
|
|
48
|
+
<div
|
|
49
|
+
className="mx-auto w-fit rounded-lg border border-border bg-background p-3"
|
|
50
|
+
aria-hidden="true"
|
|
51
|
+
>
|
|
52
|
+
<QRCodeSVG
|
|
53
|
+
value={checkout.depositAddress}
|
|
54
|
+
size={140}
|
|
55
|
+
bgColor="hsl(var(--background))"
|
|
56
|
+
fgColor="hsl(var(--foreground))"
|
|
57
|
+
/>
|
|
50
58
|
</div>
|
|
51
59
|
<div className="flex items-center gap-2 rounded-lg border border-border bg-muted/50 px-3 py-2">
|
|
52
60
|
<code className="flex-1 truncate text-xs font-mono">{checkout.depositAddress}</code>
|
|
@@ -24,11 +24,7 @@ interface PaymentMethodPickerProps {
|
|
|
24
24
|
onBack?: () => void;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export function PaymentMethodPicker({
|
|
28
|
-
methods,
|
|
29
|
-
onSelect,
|
|
30
|
-
onBack,
|
|
31
|
-
}: PaymentMethodPickerProps) {
|
|
27
|
+
export function PaymentMethodPicker({ methods, onSelect, onBack }: PaymentMethodPickerProps) {
|
|
32
28
|
const [search, setSearch] = useState("");
|
|
33
29
|
const [filter, setFilter] = useState<Filter>("popular");
|
|
34
30
|
|
|
@@ -20,7 +20,7 @@ function parseHexToRgb(hex: string): [number, number, number] {
|
|
|
20
20
|
function getTerminalColor(): [number, number, number] {
|
|
21
21
|
if (typeof document === "undefined") return [0, 255, 65];
|
|
22
22
|
const val = getComputedStyle(document.documentElement).getPropertyValue("--terminal").trim();
|
|
23
|
-
if (val
|
|
23
|
+
if (val?.startsWith("#") && val.length >= 4) return parseHexToRgb(val);
|
|
24
24
|
return [0, 255, 65];
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -117,7 +117,7 @@ export function SidebarContent({ onNavigate }: { onNavigate?: () => void }) {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
return (
|
|
120
|
-
<div className="flex h-full flex-col">
|
|
120
|
+
<div data-slot="sidebar" className="flex h-full flex-col">
|
|
121
121
|
<div className="flex h-14 items-center border-b border-sidebar-border px-6">
|
|
122
122
|
<span
|
|
123
123
|
className="text-lg font-semibold tracking-tight text-terminal"
|
|
@@ -226,7 +226,10 @@ export function SidebarContent({ onNavigate }: { onNavigate?: () => void }) {
|
|
|
226
226
|
|
|
227
227
|
export function Sidebar() {
|
|
228
228
|
return (
|
|
229
|
-
<aside
|
|
229
|
+
<aside
|
|
230
|
+
data-slot="sidebar"
|
|
231
|
+
className="flex h-screen w-64 flex-col border-r border-sidebar-border bg-sidebar text-sidebar-foreground"
|
|
232
|
+
>
|
|
230
233
|
<SidebarContent />
|
|
231
234
|
</aside>
|
|
232
235
|
);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { trpcVanilla } from "./trpc";
|
|
2
|
+
|
|
3
|
+
export interface PoolConfig {
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
poolSize: number;
|
|
6
|
+
warmCount: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function getPoolConfig(): Promise<PoolConfig> {
|
|
10
|
+
return trpcVanilla.admin.getPoolConfig.query({});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function setPoolSize(size: number): Promise<{ poolSize: number }> {
|
|
14
|
+
return trpcVanilla.admin.setPoolSize.mutate({ size });
|
|
15
|
+
}
|
package/src/lib/trpc-types.ts
CHANGED
|
@@ -80,6 +80,8 @@ type AppRouterRecord = {
|
|
|
80
80
|
listAllOrgs: AnyTRPCQueryProcedure;
|
|
81
81
|
billingOverview: AnyTRPCQueryProcedure;
|
|
82
82
|
listAvailableModels: AnyTRPCQueryProcedure;
|
|
83
|
+
getPoolConfig: AnyTRPCQueryProcedure;
|
|
84
|
+
setPoolSize: AnyTRPCMutationProcedure;
|
|
83
85
|
};
|
|
84
86
|
promotions: {
|
|
85
87
|
list: AnyTRPCQueryProcedure;
|