@@ -5,7 +5,7 @@ import { useState } from 'react';
55import { Button } from '@/components/ui/button' ;
66import { MoneyInput } from '@/components/ui/money-input' ;
77
8- import { Check , Loader2 } from 'lucide-react' ;
8+ import { Check , Loader2 , AlertCircle } from 'lucide-react' ;
99import { api } from '@/trpc/client' ;
1010
1111interface Props {
@@ -14,24 +14,69 @@ interface Props {
1414
1515export const AddCredits : React . FC < Props > = ( { appId } ) => {
1616 const [ amount , setAmount ] = useState < number > ( ) ;
17+ const [ error , setError ] = useState < string | null > ( null ) ;
18+ const [ isSuccessFromBalance , setIsSuccessFromBalance ] =
19+ useState < boolean > ( false ) ;
20+
21+ const utils = api . useUtils ( ) ;
1722
1823 const {
1924 mutate : createPaymentLink ,
2025 isPending,
2126 isSuccess,
2227 } = api . apps . app . freeTier . payments . create . useMutation ( {
2328 onSuccess : data => {
29+ setError ( null ) ;
2430 window . location . href = data . paymentLink . url ;
2531 } ,
32+ onError : error => {
33+ setError ( error . message || 'Failed to create payment link' ) ;
34+ } ,
35+ } ) ;
36+
37+ const {
38+ mutate : createPaymentFromBalance ,
39+ isPending : isPendingFromBalance ,
40+ error : balancePaymentError ,
41+ } = api . apps . app . freeTier . payments . createFromBalance . useMutation ( {
42+ onSuccess : data => {
43+ if ( ! data . success ) {
44+ setError ( data . error_message || 'Payment failed' ) ;
45+ return ;
46+ }
47+ setError ( null ) ;
48+ setIsSuccessFromBalance ( true ) ;
49+
50+ // Invalidate balance queries
51+ utils . user . balance . get . invalidate ( ) ;
52+ utils . user . balance . app . free . invalidate ( { appId } ) ;
53+ utils . apps . app . freeTier . get . invalidate ( { appId } ) ;
54+ } ,
55+ onError : error => {
56+ setError ( error . message || 'Failed to process payment from balance' ) ;
57+ } ,
2658 } ) ;
2759
2860 const onAddCredits = ( ) => {
29- if ( ! amount ) {
30- throw new Error ( 'Amount is required' ) ;
61+ if ( ! amount || amount <= 0 ) {
62+ setError ( 'Please enter a valid amount' ) ;
63+ return ;
3164 }
65+ setError ( null ) ;
3266 createPaymentLink ( { appId, amount } ) ;
3367 } ;
3468
69+ const onAddCreditsFromBalance = ( ) => {
70+ if ( ! amount || amount <= 0 ) {
71+ setError ( 'Please enter a valid amount' ) ;
72+ return ;
73+ }
74+ setError ( null ) ;
75+ createPaymentFromBalance ( { appId, amountInDollars : amount } ) ;
76+ } ;
77+
78+ const [ currentUserBalance ] = api . user . balance . get . useSuspenseQuery ( ) ;
79+
3580 return (
3681 < div className = "flex flex-col w-full gap-4" >
3782 < MoneyInput
@@ -41,20 +86,52 @@ export const AddCredits: React.FC<Props> = ({ appId }) => {
4186 min = "1"
4287 step = "0.01"
4388 />
44- < Button
45- onClick = { onAddCredits }
46- disabled = { isPending || ! amount || amount <= 0 || isSuccess }
47- size = "lg"
48- variant = "turbo"
49- >
50- { isPending ? (
51- < Loader2 className = "size-4 animate-spin" />
52- ) : isSuccess ? (
53- < Check className = "size-4" />
54- ) : (
55- 'Add Credits'
56- ) }
57- </ Button >
89+
90+ { error && (
91+ < div className = "flex items-center gap-2 p-3 text-sm text-red-600 bg-red-50 border border-red-200 rounded-md" >
92+ < AlertCircle className = "size-4 flex-shrink-0" />
93+ < span > { error } </ span >
94+ </ div >
95+ ) }
96+
97+ < div className = "flex gap-3" >
98+ < Button
99+ onClick = { onAddCredits }
100+ disabled = { isPending || ! amount || amount < 1 || isSuccess }
101+ size = "lg"
102+ variant = "turboSecondary"
103+ className = "flex-1"
104+ >
105+ { isPending ? (
106+ < Loader2 className = "size-4 animate-spin" />
107+ ) : isSuccess ? (
108+ < Check className = "size-4" />
109+ ) : (
110+ 'Add Credits'
111+ ) }
112+ </ Button >
113+ < Button
114+ onClick = { onAddCreditsFromBalance }
115+ disabled = {
116+ isPendingFromBalance ||
117+ ! amount ||
118+ amount < 0 ||
119+ isSuccessFromBalance ||
120+ currentUserBalance . balance < amount
121+ }
122+ size = "lg"
123+ variant = "turbo"
124+ className = "flex-1"
125+ >
126+ { isPendingFromBalance ? (
127+ < Loader2 className = "size-4 animate-spin" />
128+ ) : isSuccessFromBalance && ! balancePaymentError ? (
129+ < Check className = "size-4" />
130+ ) : (
131+ 'Add Credits From Balance'
132+ ) }
133+ </ Button >
134+ </ div >
58135 </ div >
59136 ) ;
60137} ;
0 commit comments