From ce5fa136c268949b9ec8cc61246ea9f556cd9884 Mon Sep 17 00:00:00 2001 From: 9qeklajc Date: Mon, 9 Mar 2026 22:41:38 +0100 Subject: [PATCH] improve ui --- ui/app/providers/page.tsx | 525 +----------------- ui/components/providers/ProviderBalance.tsx | 172 ++++++ .../providers/RoutstrNodeSettings.tsx | 135 +++++ 3 files changed, 334 insertions(+), 498 deletions(-) create mode 100644 ui/components/providers/ProviderBalance.tsx create mode 100644 ui/components/providers/RoutstrNodeSettings.tsx diff --git a/ui/app/providers/page.tsx b/ui/app/providers/page.tsx index 89fc534b..16ff04ef 100644 --- a/ui/app/providers/page.tsx +++ b/ui/app/providers/page.tsx @@ -53,167 +53,14 @@ import { SelectValue, } from '@/components/ui/select'; import { Switch } from '@/components/ui/switch'; -import { Separator } from '@/components/ui/separator'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { RoutstrProviderCard } from '@/components/providers/RoutstrProviderCard'; import { RoutstrCreateKeySection } from '@/components/providers/RoutstrCreateKeySection'; -import { SimpleLightningTopup } from '@/components/providers/SimpleLightningTopup'; -import { SimpleCashuTopup } from '@/components/providers/SimpleCashuTopup'; +import { RoutstrNodeSettings } from '@/components/providers/RoutstrNodeSettings'; +import { ProviderBalance } from '@/components/providers/ProviderBalance'; import { useState } from 'react'; import { toast } from 'sonner'; -function ProviderBalance({ - providerId, - platformUrl, - isRoutstr = false, - nodeUrl, -}: { - providerId: number; - platformUrl?: string | null; - isRoutstr?: boolean; - nodeUrl?: string; -}) { - const [isTopupDialogOpen, setIsTopupDialogOpen] = useState(false); - const [isHovered, setIsHovered] = useState(false); - const queryClient = useQueryClient(); - - const { - data: balanceData, - isLoading, - error, - } = useQuery({ - queryKey: ['provider-balance', providerId], - queryFn: () => AdminService.getProviderBalance(providerId), - refetchInterval: 30000, - refetchOnWindowFocus: true, - retry: 1, - }); - - const handleTopUpClick = () => { - if ( - platformUrl && - (platformUrl.includes('openrouter.ai') || - platformUrl.includes('openai.com')) - ) { - window.open(platformUrl, '_blank'); - return; - } - - setIsTopupDialogOpen(true); - }; - - const handleCloseDialog = () => { - setIsTopupDialogOpen(false); - queryClient.invalidateQueries({ - queryKey: ['provider-balance', providerId], - }); - }; - - if (isLoading) { - return ; - } - - if ( - error || - !balanceData?.ok || - balanceData.balance_data === undefined || - balanceData.balance_data === null - ) { - return null; - } - - const balance = balanceData.balance_data; - let displayValue = 'N/A'; - - if (typeof balance === 'number') { - displayValue = isRoutstr - ? `${balance.toLocaleString()} sats` - : `$${balance.toFixed(2)}`; - } else if (balance && typeof balance === 'object') { - // Legacy support for object response - const b = balance as Record; - if (typeof b.balance === 'number') { - displayValue = `$${b.balance.toFixed(2)}`; - } else if (typeof b.balance === 'string') { - displayValue = b.balance; - } else if (b.amount !== undefined) { - displayValue = `$${Number(b.amount).toFixed(2)}`; - } - } - - return ( - <> - - - - - - Top Up Balance - - {isRoutstr - ? `Top up your balance on node ${nodeUrl}` - : 'Choose a payment method to top up your account balance.'} - - - -
-
- - { - queryClient.invalidateQueries({ - queryKey: ['provider-balance', providerId], - }); - }} - /> -
- - - -
- - { - queryClient.invalidateQueries({ - queryKey: ['provider-balance', providerId], - }); - }} - /> -
-
- - - - -
-
- - ); -} - export default function ProvidersPage() { const queryClient = useQueryClient(); const [editingProvider, setEditingProvider] = @@ -501,6 +348,8 @@ export default function ProvidersPage() { setBatchOverrideProviderId(providerId); }; + const availableMints = (globalSettings?.cashu_mints as string[]) || []; + return ( @@ -527,7 +376,7 @@ export default function ProvidersPage() { Add Provider - + Add Upstream Provider @@ -561,184 +410,16 @@ export default function ProvidersPage() { {formData.provider_type === 'routstr' && ( -
-
- - - JSONB Storage - -
- -
-
- - -

- The token for top-up will be created from this - mint. -

-
-
- - - setFormData({ - ...formData, - provider_settings: { - ...formData.provider_settings, - auto_topup: checked, - }, - }) - } - /> -
- - {formData.provider_settings?.auto_topup && ( -
-
- - - setFormData({ - ...formData, - provider_settings: { - ...formData.provider_settings, - topup_threshold: parseInt( - e.target.value - ), - }, - }) - } - /> -
- -
- - - setFormData({ - ...formData, - provider_settings: { - ...formData.provider_settings, - topup_amount_limit: parseInt( - e.target.value - ), - }, - }) - } - /> -
-
- )} - -
- - - setFormData({ - ...formData, - provider_settings: { - ...formData.provider_settings, - refund_on_expiry: checked, - }, - }) - } - /> -
-
-
+ + setFormData((prev) => ({ + ...prev, + provider_settings: settings, + })) + } + availableMints={availableMints} + /> )}
@@ -1243,7 +924,7 @@ export default function ProvidersPage() {
- + Edit Upstream Provider @@ -1277,169 +958,17 @@ export default function ProvidersPage() { {formData.provider_type === 'routstr' && ( -
-
- - - JSONB Storage - -
- -
-
- - -

- The token for top-up will be created from this mint. -

-
-
- - - setFormData({ - ...formData, - provider_settings: { - ...formData.provider_settings, - auto_topup: checked, - }, - }) - } - /> -
- - {formData.provider_settings?.auto_topup && ( -
-
- - - setFormData({ - ...formData, - provider_settings: { - ...formData.provider_settings, - topup_threshold: parseInt(e.target.value), - }, - }) - } - /> -
- -
- - - setFormData({ - ...formData, - provider_settings: { - ...formData.provider_settings, - topup_amount_limit: parseInt(e.target.value), - }, - }) - } - /> -
-
- )} - -
- - - setFormData({ - ...formData, - provider_settings: { - ...formData.provider_settings, - refund_on_expiry: checked, - }, - }) - } - /> -
-
-
+ + setFormData((prev) => ({ + ...prev, + provider_settings: settings, + })) + } + availableMints={availableMints} + idPrefix='edit' + /> )}
diff --git a/ui/components/providers/ProviderBalance.tsx b/ui/components/providers/ProviderBalance.tsx new file mode 100644 index 00000000..2ebb3966 --- /dev/null +++ b/ui/components/providers/ProviderBalance.tsx @@ -0,0 +1,172 @@ +'use client'; + +import { useState } from 'react'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { AdminService } from '@/lib/api/services/admin'; +import { Button } from '@/components/ui/button'; +import { Label } from '@/components/ui/label'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Separator } from '@/components/ui/separator'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { SimpleLightningTopup } from './SimpleLightningTopup'; +import { SimpleCashuTopup } from './SimpleCashuTopup'; + +interface ProviderBalanceProps { + providerId: number; + platformUrl?: string | null; + isRoutstr?: boolean; + nodeUrl?: string; +} + +export function ProviderBalance({ + providerId, + platformUrl, + isRoutstr = false, + nodeUrl, +}: ProviderBalanceProps) { + const [isTopupDialogOpen, setIsTopupDialogOpen] = useState(false); + const [isHovered, setIsHovered] = useState(false); + const queryClient = useQueryClient(); + + const { + data: balanceData, + isLoading, + error, + } = useQuery({ + queryKey: ['provider-balance', providerId], + queryFn: () => AdminService.getProviderBalance(providerId), + refetchInterval: 30000, + refetchOnWindowFocus: true, + retry: 1, + }); + + const handleTopUpClick = () => { + if ( + platformUrl && + (platformUrl.includes('openrouter.ai') || + platformUrl.includes('openai.com')) + ) { + window.open(platformUrl, '_blank'); + return; + } + + setIsTopupDialogOpen(true); + }; + + const handleCloseDialog = () => { + setIsTopupDialogOpen(false); + queryClient.invalidateQueries({ + queryKey: ['provider-balance', providerId], + }); + }; + + if (isLoading) { + return ; + } + + if ( + error || + !balanceData?.ok || + balanceData.balance_data === undefined || + balanceData.balance_data === null + ) { + return null; + } + + const balance = balanceData.balance_data; + let displayValue = 'N/A'; + + if (typeof balance === 'number') { + displayValue = isRoutstr + ? `${balance.toLocaleString()} sats` + : `$${balance.toFixed(2)}`; + } else if (balance && typeof balance === 'object') { + const b = balance as Record; + if (typeof b.balance === 'number') { + displayValue = `$${b.balance.toFixed(2)}`; + } else if (typeof b.balance === 'string') { + displayValue = b.balance; + } else if (b.amount !== undefined) { + displayValue = `$${Number(b.amount).toFixed(2)}`; + } + } + + return ( + <> + + + + + + Top Up Balance + + {isRoutstr + ? `Top up your balance on node ${nodeUrl}` + : 'Choose a payment method to top up your account balance.'} + + + +
+
+ + { + queryClient.invalidateQueries({ + queryKey: ['provider-balance', providerId], + }); + }} + /> +
+ + + +
+ + { + queryClient.invalidateQueries({ + queryKey: ['provider-balance', providerId], + }); + }} + /> +
+
+ + + + +
+
+ + ); +} diff --git a/ui/components/providers/RoutstrNodeSettings.tsx b/ui/components/providers/RoutstrNodeSettings.tsx new file mode 100644 index 00000000..18ace0dc --- /dev/null +++ b/ui/components/providers/RoutstrNodeSettings.tsx @@ -0,0 +1,135 @@ +'use client'; + +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Switch } from '@/components/ui/switch'; + +interface ProviderSettings { + topup_mint_url?: string; + auto_topup?: boolean; + topup_threshold?: number; + topup_amount_limit?: number; + refund_on_expiry?: boolean; + [key: string]: unknown; +} + +interface RoutstrNodeSettingsProps { + settings: ProviderSettings; + onSettingsChange: (settings: ProviderSettings) => void; + availableMints: string[]; + idPrefix?: string; +} + +export function RoutstrNodeSettings({ + settings, + onSettingsChange, + availableMints, + idPrefix = '', +}: RoutstrNodeSettingsProps) { + const prefix = idPrefix ? `${idPrefix}_` : ''; + + const update = (patch: Partial) => { + onSettingsChange({ ...settings, ...patch }); + }; + + return ( +
+ + +
+
+ + +

+ The token for top-up will be created from this mint. +

+
+ +
+ + update({ auto_topup: checked })} + /> +
+ + {settings.auto_topup && ( +
+
+ + + update({ topup_threshold: parseInt(e.target.value) }) + } + /> +
+ +
+ + + update({ topup_amount_limit: parseInt(e.target.value) }) + } + /> +
+
+ )} +
+
+ ); +}