@@ -6,13 +6,19 @@ import { Button } from "@/components/ui/button"
66import { Skeleton } from "@/components/ui/skeleton"
77import { Table , TableBody , TableCell , TableRow } from "@/components/ui/table"
88import { Tooltip , TooltipContent , TooltipProvider , TooltipTrigger } from "@/components/ui/tooltip"
9- import { IdentityPublicData , PayeePublicData , InfoReply , AnonPublicData } from "@/generated/client"
9+ import {
10+ IdentityPublicData ,
11+ PayeePublicData ,
12+ InfoReply ,
13+ AnonPublicData ,
14+ RequestToMintResponseInfo ,
15+ } from "@/generated/client"
1016import {
1117 adminLookupQuoteOptions ,
1218 adminLookupQuoteQueryKey ,
1319 adminUpdateQuoteMutation ,
1420} from "@/generated/client/@tanstack/react-query.gen"
15- import { activateKeyset , keysetInfo } from "@/generated/client/sdk.gen"
21+ import { activateKeyset , keysetInfo , requestToMint } from "@/generated/client/sdk.gen"
1622import { cn , getInitials } from "@/lib/utils"
1723import { formatDate , humanReadableDuration } from "@/utils/dates"
1824
@@ -211,12 +217,24 @@ function DenyConfirmDrawer({ children, onSubmit, ...drawerProps }: DenyConfirmDr
211217 )
212218}
213219
214- function QuoteActions ( { value, isFetching, newKeyset } : { value : InfoReply ; isFetching : boolean ; newKeyset : boolean } ) {
220+ function QuoteActions ( {
221+ value,
222+ isFetching,
223+ newKeyset,
224+ ebillPaid,
225+ } : {
226+ value : InfoReply
227+ isFetching : boolean
228+ newKeyset : boolean
229+ ebillPaid : boolean
230+ } ) {
215231 const [ offerFormData , setOfferFormData ] = useState < OfferFormResult > ( )
216232 const [ offerFormDrawerOpen , setOfferFormDrawerOpen ] = useState ( false )
217233 const [ offerConfirmDrawerOpen , setOfferConfirmDrawerOpen ] = useState ( false )
218234 const [ denyConfirmDrawerOpen , setDenyConfirmDrawerOpen ] = useState ( false )
219235 const [ activateKeysetConfirmDrawerOpen , setActivateKeysetConfirmDrawerOpen ] = useState ( false )
236+ const [ requestToMintConfirmDrawerOpen , setRequestToMintConfirmDrawerOpen ] = useState ( false )
237+ const [ mintRequestResponse , setMintRequestResponse ] = useState < RequestToMintResponseInfo | null > ( null )
220238
221239 const effectiveDiscount = useMemo ( ( ) => {
222240 if ( ! offerFormData ) return
@@ -293,6 +311,35 @@ function QuoteActions({ value, isFetching, newKeyset }: { value: InfoReply; isFe
293311 } ,
294312 } )
295313
314+ const requestToMintMutation = useMutation ( {
315+ mutationFn : async ( ) => {
316+ const { data } = await requestToMint ( {
317+ body : {
318+ ebill_id : value . bill . id ,
319+ amount : value . bill . sum ,
320+ } ,
321+ throwOnError : true ,
322+ } )
323+ console . log ( data )
324+ return data
325+ } ,
326+ onMutate : ( ) => {
327+ toast . loading ( "Requesting to mint…" , { id : `quote-${ value . id } -request-to-mint` } )
328+ } ,
329+ onSettled : ( ) => {
330+ toast . dismiss ( `quote-${ value . id } -request-to-mint` )
331+ } ,
332+ onError : ( error ) => {
333+ toast . error ( "Error while requesting to mint: " + error . message )
334+ console . warn ( error )
335+ } ,
336+ onSuccess : ( data ) => {
337+ toast . success ( "Payment request has been created." )
338+ console . log ( data )
339+ setMintRequestResponse ( data )
340+ } ,
341+ } )
342+
296343 const onDenyQuote = ( ) => {
297344 toast . loading ( "Denying quote…" , { id : `quote-${ value . id } -deny` } )
298345 denyQuote . mutate ( {
@@ -325,107 +372,152 @@ function QuoteActions({ value, isFetching, newKeyset }: { value: InfoReply; isFe
325372 const onActivateKeyset = ( ) => {
326373 activateKeysetMutation . mutate ( )
327374 }
375+
376+ const onRequestToMint = ( ) => {
377+ requestToMintMutation . mutate ( )
378+ }
328379 return (
329- < div className = "flex items-center gap-2" >
330- { value . status === "Pending" ? (
331- < DenyConfirmDrawer
332- title = "Confirm denying quote"
333- open = { denyConfirmDrawerOpen }
334- onOpenChange = { setDenyConfirmDrawerOpen }
335- onSubmit = { ( ) => {
336- onDenyQuote ( )
337- setDenyConfirmDrawerOpen ( false )
338- } }
339- >
340- < Button
341- className = "flex-1"
342- disabled = { isFetching || denyQuote . isPending || value . status !== "Pending" }
343- variant = { value . status !== "Pending" ? "outline" : "destructive" }
380+ < >
381+ < div className = "flex items-center gap-2" >
382+ { value . status === "Pending" ? (
383+ < DenyConfirmDrawer
384+ title = "Confirm denying quote"
385+ open = { denyConfirmDrawerOpen }
386+ onOpenChange = { setDenyConfirmDrawerOpen }
387+ onSubmit = { ( ) => {
388+ onDenyQuote ( )
389+ setDenyConfirmDrawerOpen ( false )
390+ } }
344391 >
345- Deny { denyQuote . isPending && < LoaderIcon className = "stroke-1 animate-spin" /> }
346- </ Button >
347- </ DenyConfirmDrawer >
348- ) : (
349- < > </ >
350- ) }
351- { value . status === "Pending" ? (
352- < OfferFormDrawer
353- title = "Offer quote"
354- description = "Make an offer to the current holder of this bill"
355- value = { value }
356- open = { offerFormDrawerOpen }
357- onOpenChange = { setOfferFormDrawerOpen }
358- onSubmit = { ( data ) => {
359- setOfferFormData ( data )
360- setOfferConfirmDrawerOpen ( true )
361- setOfferFormDrawerOpen ( false )
362- } }
363- >
364- < Button className = "flex-1" disabled = { isFetching || offerQuote . isPending || value . status !== "Pending" } >
365- Offer { offerQuote . isPending && < LoaderIcon className = "stroke-1 animate-spin" /> }
366- </ Button >
367- </ OfferFormDrawer >
368- ) : (
369- < > </ >
370- ) }
371-
372- < OfferConfirmDrawer
373- title = "Confirm offering quote"
374- description = "Review your inputs and confirm the offer"
375- open = { offerConfirmDrawerOpen }
376- onOpenChange = { setOfferConfirmDrawerOpen }
377- onSubmit = { ( ) => {
378- if ( ! offerFormData ) return
379- onOfferQuote ( offerFormData )
380- setOfferConfirmDrawerOpen ( false )
381- } }
382- >
383- < div className = "flex flex-col justify-center gap-1 py-8 mb-8" >
384- < span >
385- < span className = "font-bold" > Effective discount (relative):</ span > { " " }
386- { effectiveDiscount ?. mul ( new Big ( "100" ) ) . toFixed ( 2 ) } %
387- </ span >
388- < span >
389- < span className = "font-bold" > Effective discount (absolute):</ span > { " " }
390- { offerFormData ?. discount . gross . value . minus ( offerFormData ?. discount . net . value ) . toFixed ( 0 ) } { " " }
391- { offerFormData ?. discount . net . currency }
392- </ span >
393- < span >
394- < span className = "font-bold" > Net amount:</ span > { offerFormData ?. discount . net . value . round ( 0 ) . toFixed ( 0 ) } { " " }
395- { offerFormData ?. discount . net . currency }
396- </ span >
397- < span >
398- < span className = "font-bold" > Valid until:</ span > { offerFormData ?. ttl . ttl . toDateString ( ) } (
399- { offerFormData && humanReadableDuration ( "en" , offerFormData . ttl . ttl ) } )
400- </ span >
401- </ div >
402- </ OfferConfirmDrawer >
403-
404- { value . status === "Accepted" && "keyset_id" in value ? (
405- < ConfirmDrawer
406- title = "Confirm activating keyset"
407- description = "Are you sure you want to activate the keyset for this quote?"
408- open = { activateKeysetConfirmDrawerOpen }
409- onOpenChange = { setActivateKeysetConfirmDrawerOpen }
410- onSubmit = { ( ) => {
411- onActivateKeyset ( )
412- setActivateKeysetConfirmDrawerOpen ( false )
413- } }
414- submitButtonText = "Yes, activate keyset"
415- trigger = {
416392 < Button
417393 className = "flex-1"
418- disabled = { isFetching || activateKeysetMutation . isPending || ! newKeyset }
419- variant = "default"
394+ disabled = { isFetching || denyQuote . isPending || value . status !== "Pending" }
395+ variant = { value . status !== "Pending" ? "outline" : "destructive" }
420396 >
421- Activate Keyset { activateKeysetMutation . isPending && < LoaderIcon className = "stroke-1 animate-spin" /> }
397+ Deny { denyQuote . isPending && < LoaderIcon className = "stroke-1 animate-spin" /> }
422398 </ Button >
423- }
424- />
425- ) : (
426- < > </ >
399+ </ DenyConfirmDrawer >
400+ ) : (
401+ < > </ >
402+ ) }
403+ { value . status === "Pending" ? (
404+ < OfferFormDrawer
405+ title = "Offer quote"
406+ description = "Make an offer to the current holder of this bill"
407+ value = { value }
408+ open = { offerFormDrawerOpen }
409+ onOpenChange = { setOfferFormDrawerOpen }
410+ onSubmit = { ( data ) => {
411+ setOfferFormData ( data )
412+ setOfferConfirmDrawerOpen ( true )
413+ setOfferFormDrawerOpen ( false )
414+ } }
415+ >
416+ < Button className = "flex-1" disabled = { isFetching || offerQuote . isPending || value . status !== "Pending" } >
417+ Offer { offerQuote . isPending && < LoaderIcon className = "stroke-1 animate-spin" /> }
418+ </ Button >
419+ </ OfferFormDrawer >
420+ ) : (
421+ < > </ >
422+ ) }
423+
424+ < OfferConfirmDrawer
425+ title = "Confirm offering quote"
426+ description = "Review your inputs and confirm the offer"
427+ open = { offerConfirmDrawerOpen }
428+ onOpenChange = { setOfferConfirmDrawerOpen }
429+ onSubmit = { ( ) => {
430+ if ( ! offerFormData ) return
431+ onOfferQuote ( offerFormData )
432+ setOfferConfirmDrawerOpen ( false )
433+ } }
434+ >
435+ < div className = "flex flex-col justify-center gap-1 py-8 mb-8" >
436+ < span >
437+ < span className = "font-bold" > Effective discount (relative):</ span > { " " }
438+ { effectiveDiscount ?. mul ( new Big ( "100" ) ) . toFixed ( 2 ) } %
439+ </ span >
440+ < span >
441+ < span className = "font-bold" > Effective discount (absolute):</ span > { " " }
442+ { offerFormData ?. discount . gross . value . minus ( offerFormData ?. discount . net . value ) . toFixed ( 0 ) } { " " }
443+ { offerFormData ?. discount . net . currency }
444+ </ span >
445+ < span >
446+ < span className = "font-bold" > Net amount:</ span > { offerFormData ?. discount . net . value . round ( 0 ) . toFixed ( 0 ) } { " " }
447+ { offerFormData ?. discount . net . currency }
448+ </ span >
449+ < span >
450+ < span className = "font-bold" > Valid until:</ span > { offerFormData ?. ttl . ttl . toDateString ( ) } (
451+ { offerFormData && humanReadableDuration ( "en" , offerFormData . ttl . ttl ) } )
452+ </ span >
453+ </ div >
454+ </ OfferConfirmDrawer >
455+
456+ { value . status === "Accepted" && "keyset_id" in value ? (
457+ < ConfirmDrawer
458+ title = "Confirm activating keyset"
459+ description = "Are you sure you want to activate the keyset for this quote?"
460+ open = { activateKeysetConfirmDrawerOpen }
461+ onOpenChange = { setActivateKeysetConfirmDrawerOpen }
462+ onSubmit = { ( ) => {
463+ onActivateKeyset ( )
464+ setActivateKeysetConfirmDrawerOpen ( false )
465+ } }
466+ submitButtonText = "Yes, activate keyset"
467+ trigger = {
468+ < Button
469+ className = "flex-1"
470+ disabled = { isFetching || activateKeysetMutation . isPending || ! newKeyset }
471+ variant = "default"
472+ >
473+ Activate Keyset { activateKeysetMutation . isPending && < LoaderIcon className = "stroke-1 animate-spin" /> }
474+ </ Button >
475+ }
476+ />
477+ ) : (
478+ < > </ >
479+ ) }
480+
481+ { value . status === "Accepted" && "keyset_id" in value && ! ebillPaid && ! newKeyset ? (
482+ < ConfirmDrawer
483+ title = "Confirm requesting to mint"
484+ description = "Are you sure you want to request to mint from this e-bill?"
485+ open = { requestToMintConfirmDrawerOpen }
486+ onOpenChange = { setRequestToMintConfirmDrawerOpen }
487+ onSubmit = { ( ) => {
488+ onRequestToMint ( )
489+ setRequestToMintConfirmDrawerOpen ( false )
490+ } }
491+ submitButtonText = "Yes, request to mint"
492+ trigger = {
493+ < Button className = "flex-1" disabled = { isFetching || requestToMintMutation . isPending } variant = "default" >
494+ Request to Pay { requestToMintMutation . isPending && < LoaderIcon className = "stroke-1 animate-spin" /> }
495+ </ Button >
496+ }
497+ />
498+ ) : (
499+ < > </ >
500+ ) }
501+ </ div >
502+
503+ { mintRequestResponse && (
504+ < div className = "mt-4 p-4 bg-gray-50 rounded-lg" >
505+ < h3 className = "font-bold mb-2" > Payment Request</ h3 >
506+ < div className = "space-y-2" >
507+ < div >
508+ < span className = "font-bold" > ID</ span >
509+ < span className = "font-mono ml-2" > { mintRequestResponse . request_id } </ span >
510+ </ div >
511+ < div >
512+ < span className = "font-bold" > Details</ span >
513+ < div className = "font-mono text-sm mt-1 p-2 bg-white rounded border break-all" >
514+ { mintRequestResponse . request }
515+ </ div >
516+ </ div >
517+ </ div >
518+ </ div >
427519 ) }
428- </ div >
520+ </ >
429521 )
430522}
431523
@@ -706,7 +798,7 @@ function Quote({ value, isFetching }: { value: InfoReply; isFetching: boolean })
706798 </ TableBody >
707799 </ Table >
708800
709- < QuoteActions value = { value } isFetching = { isFetching } newKeyset = { newKeyset } />
801+ < QuoteActions value = { value } isFetching = { isFetching } newKeyset = { newKeyset } ebillPaid = { ebillPaid ?? false } />
710802 </ div >
711803 )
712804}
0 commit comments