diff --git a/ui/app/providers/page.tsx b/ui/app/providers/page.tsx index c79f3e73..dcef8868 100644 --- a/ui/app/providers/page.tsx +++ b/ui/app/providers/page.tsx @@ -32,6 +32,8 @@ import { Database, ChevronDown, ChevronUp, + Copy, + AlertTriangle, } from 'lucide-react'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { @@ -53,31 +55,29 @@ 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 { useState, useEffect } from 'react'; +import { SimpleLightningTopup } from '@/components/providers/SimpleLightningTopup'; +import { SimpleCashuTopup } from '@/components/providers/SimpleCashuTopup'; +import { CashuPaymentWorkflow } from '@/components/landing/cashu-payment-workflow'; +import { LightningPaymentWorkflow } from '@/components/landing/lightning-payment-workflow'; +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 [topupAmount, setTopupAmount] = useState(''); - const [topupError, setTopupError] = useState(''); const [isHovered, setIsHovered] = useState(false); - const [invoiceData, setInvoiceData] = useState<{ - payment_request: string; - invoice_id: string; - } | null>(null); - const [paymentStatus, setPaymentStatus] = useState<'pending' | 'paid' | null>( - null - ); const queryClient = useQueryClient(); const { @@ -92,102 +92,7 @@ function ProviderBalance({ retry: 1, }); - const { data: statusData } = useQuery({ - queryKey: ['topup-status', providerId, invoiceData?.invoice_id], - queryFn: () => - AdminService.checkTopupStatus(providerId, invoiceData!.invoice_id), - enabled: !!invoiceData && paymentStatus === 'pending', - refetchInterval: 2000, - }); - - useEffect(() => { - if (statusData?.paid === true) { - setPaymentStatus('paid'); - queryClient.invalidateQueries({ - queryKey: ['provider-balance', providerId], - }); - toast.success('Payment received!', { - description: 'Your balance has been updated.', - }); - } - }, [statusData, queryClient, providerId]); - - const topupMutation = useMutation({ - mutationFn: async (amount: number) => { - console.log('Calling top-up API with:', { providerId, amount }); - try { - const result = await AdminService.initiateProviderTopup( - providerId, - amount - ); - console.log('API returned:', result); - return result; - } catch (err) { - console.error('API call failed:', err); - throw err; - } - }, - onSuccess: (data) => { - console.log('Top-up response:', data); - console.log('Type of data:', typeof data); - console.log('Keys in data:', Object.keys(data || {})); - - if (data?.topup_data?.payment_request && data?.topup_data?.invoice_id) { - setInvoiceData({ - payment_request: data.topup_data.payment_request as string, - invoice_id: data.topup_data.invoice_id as string, - }); - setPaymentStatus('pending'); - } else { - console.error('Missing invoice data:', data); - console.error('topup_data:', data?.topup_data); - toast.error('No invoice returned from provider'); - setIsTopupDialogOpen(false); - } - }, - onError: (error: Error) => { - console.error('Top-up mutation error:', error); - toast.error(`Failed to initiate top-up: ${error.message}`); - }, - }); - - const handleTopup = () => { - // If no dialog open logic (which depends on API implementation), - // we check if we should redirect or open dialog based on available info - // But since this function is called inside the dialog, we might want to change - // how the "Top Up" button behaves instead. - const amount = parseFloat(topupAmount); - - if (isNaN(amount)) { - setTopupError('Please enter a valid amount'); - return; - } - - if (amount < 1 || amount > 500) { - setTopupError('Amount must be between $1 and $500'); - return; - } - - topupMutation.mutate(amount); - }; - const handleTopUpClick = () => { - // Check if the provider supports direct topup (currently only PPQ.AI effectively) - // We can infer this if it's NOT OpenRouter or OpenAI, or strictly checking provider capability - // For now, we'll try to initiate topup for anyone, but if we know it fails (or isn't implemented), - // we should redirect. - // However, the prompt asks to redirect if topup is not implemented. - // The backend throws 500/400 if not implemented. - // A better approach is to check if we have a platform URL and maybe redirect there - // if we know it's not supported. - - // BUT, we don't know for sure if it's supported without checking metadata or trying. - // Let's rely on the "can_topup" metadata if available, but currently we only have "can_show_balance". - - // Simple heuristic: If platformUrl exists and we suspect no direct topup, redirect? - // Actually, let's try to open the dialog, but if it's OpenRouter/OpenAI, maybe we just redirect? - // The user specifically mentioned "like in openrouter". - if ( platformUrl && (platformUrl.includes('openrouter.ai') || @@ -202,10 +107,9 @@ function ProviderBalance({ const handleCloseDialog = () => { setIsTopupDialogOpen(false); - setTopupAmount(''); - setTopupError(''); - setInvoiceData(null); - setPaymentStatus(null); + queryClient.invalidateQueries({ + queryKey: ['provider-balance', providerId], + }); }; if (isLoading) { @@ -254,134 +158,58 @@ function ProviderBalance({ @@ -435,6 +263,12 @@ export default function ProvidersPage() { refetchOnWindowFocus: false, }); + const { data: globalSettings } = useQuery({ + queryKey: ['settings'], + queryFn: () => AdminService.getSettings(), + refetchOnWindowFocus: false, + }); + const { data: providers = [], isLoading, @@ -741,6 +575,65 @@ export default function ProvidersPage() {
+ The token for top-up will be created from this + mint. +
+