Skip to content

Commit dee5210

Browse files
authored
Prevent request to pay being clicked twice (#70)
* Request to Pay * Remove console log * Rename mint to pay * Don't fetch if there is no bill id * Invalidate query on press
1 parent 06e2639 commit dee5210

File tree

3 files changed

+159
-75
lines changed

3 files changed

+159
-75
lines changed

src/generated/client/sdk.gen.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// This file is auto-generated by @hey-api/openapi-ts
22

33
import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch';
4-
import type { ListQuotesData, ListQuotesResponse, ListPendingQuotesData, ListPendingQuotesResponse, AdminLookupQuoteData, AdminLookupQuoteResponse, AdminUpdateQuoteData, AdminUpdateQuoteResponse, ResolveOfferData, EnquireQuoteData, EnquireQuoteResponse, LookupQuoteData, LookupQuoteResponse, ActivateKeysetData, ActivateKeysetResponse, DebitData, CreditData, ECashBalance, OnChainBalanceData, OnChainData, KeysetInfoData, KeySetInfo, RequestToMintData, RequestToMintResponseInfo, IdentityDetailData, IdentityDetailInfo} from './types.gen';
4+
import type { ListQuotesData, ListQuotesResponse, ListPendingQuotesData, ListPendingQuotesResponse, AdminLookupQuoteData, AdminLookupQuoteResponse, AdminUpdateQuoteData, AdminUpdateQuoteResponse, ResolveOfferData, EnquireQuoteData, EnquireQuoteResponse, LookupQuoteData, LookupQuoteResponse, ActivateKeysetData, ActivateKeysetResponse, DebitData, CreditData, ECashBalance, OnChainBalanceData, OnChainData, KeysetInfoData, KeySetInfo, RequestToMintData, RequestToMintResponseInfo, IdentityDetailData, IdentityDetailInfo, BillPaymentData, BillPaymentState} from './types.gen';
55

66
import { client as _heyApiClient } from './client.gen';
77

@@ -168,3 +168,18 @@ export const identityDetail = <ThrowOnError extends boolean = false>(options?: O
168168
}
169169
});
170170
};
171+
172+
/**
173+
* --------------------------- BillPaymentStatus
174+
*/
175+
176+
export const paymentStatus = <ThrowOnError extends boolean = false>(options?: Options<BillPaymentData, ThrowOnError>) => {
177+
return (_heyApiClient).get<BillPaymentState, unknown, ThrowOnError>({
178+
url: '/v1/admin/bill/payment_status/{bill_id}',
179+
...options,
180+
headers: {
181+
'Content-Type': 'application/json',
182+
...options?.headers
183+
}
184+
});
185+
};

src/generated/client/types.gen.ts

Lines changed: 87 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -498,67 +498,102 @@ export type KeysetInfoData = {
498498
};
499499

500500
/**
501-
* Request Mint
502-
*/
501+
* Request Mint
502+
*/
503+
504+
export type RequestToMintRequest = {
505+
ebill_id: string;
506+
amount: number;
507+
};
503508

504-
export type RequestToMintRequest = {
505-
ebill_id: string;
506-
amount: number;
507-
};
509+
export type RequestToMintResponseInfo = {
510+
request_id: string;
511+
request: string;
512+
};
508513

509-
export type RequestToMintResponseInfo = {
510-
request_id: string;
511-
request: string;
512-
};
514+
export type RequestToMintResponse = {
515+
/**
516+
* Successful response
517+
*/
518+
200: RequestToMintResponseInfo;
519+
};
513520

514-
export type RequestToMintResponse = {
515-
/**
521+
export type RequestToMintData = {
522+
body?: RequestToMintRequest;
523+
path?: never;
524+
query?: never;
525+
url: '/v1/admin/treasury/debit/request_to_mint_from_ebill'
526+
};
527+
528+
/**
529+
* Node ID
530+
*/
531+
532+
export type IdentityDetailRequest = {
533+
};
534+
535+
export type IdentityDetailInfo = {
536+
node_id: string;
537+
name: string;
538+
email: string | null;
539+
bitcoin_public_key: string;
540+
npub: string;
541+
postal_address: PostalAddress | null;
542+
date_of_birth: string | null;
543+
country_of_birth: string | null;
544+
city_of_birth: string | null;
545+
identification_number: string | null;
546+
profile_picture_file: string | null;
547+
identity_document_file: string | null;
548+
nostr_relays: Array<string>;
549+
};
550+
551+
export type IdentityDetailResponse = {
552+
/**
516553
* Successful response
517554
*/
518-
200: RequestToMintResponseInfo;
519-
};
520-
521-
export type RequestToMintData = {
522-
body?: RequestToMintRequest;
523-
path?: never;
524-
query?: never;
525-
url: '/v1/admin/treasury/debit/request_to_mint_from_ebill'
526-
};
555+
200: IdentityDetailInfo;
556+
};
527557

528-
/**
529-
* Node ID
530-
*/
531558

532-
export type IdentityDetailRequest = {
533-
};
559+
export type IdentityDetailData = {
560+
body?: never;
561+
path?: never;
562+
query?: never;
563+
url: '/v1/admin/identity/detail'
564+
};
534565

535-
export type IdentityDetailInfo = {
536-
node_id: string;
537-
name: string;
538-
email: string | null;
539-
bitcoin_public_key: string;
540-
npub: string;
541-
postal_address: PostalAddress | null;
542-
date_of_birth: string | null;
543-
country_of_birth: string | null;
544-
city_of_birth: string | null;
545-
identification_number: string | null;
546-
profile_picture_file: string | null;
547-
identity_document_file: string | null;
548-
nostr_relays: Array<string>;
549-
};
566+
/**
567+
* Bill Payment Status
568+
*/
569+
570+
export type BillWaitingForPaymentState = {
571+
time_of_request: number,
572+
currency: string,
573+
sum: string,
574+
link_to_pay: string,
575+
address_to_pay: string,
576+
mempool_link_for_address_to_pay: string,
577+
}
550578

551-
export type IdentityDetailResponse = {
552-
/**
553-
* Successful response
554-
*/
555-
200: IdentityDetailInfo;
556-
};
579+
export type BillPaymentStatus = {
580+
time_of_request_to_pay: number | null,
581+
requested_to_pay: boolean,
582+
paid: boolean,
583+
request_to_pay_timed_out: boolean,
584+
rejected_to_pay: boolean,
585+
}
557586

587+
export type BillPaymentState = {
588+
payment_status: BillPaymentStatus,
589+
payment_details: BillWaitingForPaymentState | null,
590+
}
558591

559-
export type IdentityDetailData = {
560-
body?: never;
561-
path?: never;
562-
query?: never;
563-
url: '/v1/admin/identity/detail'
592+
export type BillPaymentData = {
593+
body?: never;
594+
path: {
595+
bill_id: string;
564596
};
597+
query?: never;
598+
url: '/v1/admin/bill/payment_status/{bill_id}';
599+
};

src/pages/quotes/QuotePage.tsx

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
adminLookupQuoteQueryKey,
1919
adminUpdateQuoteMutation,
2020
} from "@/generated/client/@tanstack/react-query.gen"
21-
import { activateKeyset, keysetInfo, requestToMint } from "@/generated/client/sdk.gen"
21+
import { activateKeyset, keysetInfo, paymentStatus, requestToMint } from "@/generated/client/sdk.gen"
2222
import { cn, getInitials } from "@/lib/utils"
2323
import { formatDate, humanReadableDuration } from "@/utils/dates"
2424

@@ -222,19 +222,21 @@ function QuoteActions({
222222
isFetching,
223223
newKeyset,
224224
ebillPaid,
225+
requestedToPay,
225226
}: {
226227
value: InfoReply
227228
isFetching: boolean
228229
newKeyset: boolean
229230
ebillPaid: boolean
231+
requestedToPay: boolean
230232
}) {
231233
const [offerFormData, setOfferFormData] = useState<OfferFormResult>()
232234
const [offerFormDrawerOpen, setOfferFormDrawerOpen] = useState(false)
233235
const [offerConfirmDrawerOpen, setOfferConfirmDrawerOpen] = useState(false)
234236
const [denyConfirmDrawerOpen, setDenyConfirmDrawerOpen] = useState(false)
235237
const [activateKeysetConfirmDrawerOpen, setActivateKeysetConfirmDrawerOpen] = useState(false)
236-
const [requestToMintConfirmDrawerOpen, setRequestToMintConfirmDrawerOpen] = useState(false)
237-
const [mintRequestResponse, setMintRequestResponse] = useState<RequestToMintResponseInfo | null>(null)
238+
const [requestToPayConfirmDrawerOpen, setRequestToPayConfirmDrawerOpen] = useState(false)
239+
const [payRequestResponse, setPayRequestResponse] = useState<RequestToMintResponseInfo | null>(null)
238240

239241
const effectiveDiscount = useMemo(() => {
240242
if (!offerFormData) return
@@ -311,7 +313,7 @@ function QuoteActions({
311313
},
312314
})
313315

314-
const requestToMintMutation = useMutation({
316+
const requestToPayMutation = useMutation({
315317
mutationFn: async () => {
316318
const { data } = await requestToMint({
317319
body: {
@@ -323,7 +325,7 @@ function QuoteActions({
323325
return data
324326
},
325327
onMutate: () => {
326-
toast.loading("Requesting to pay…", { id: `quote-${value.id}-request-to-mint` })
328+
toast.loading("Requesting to pay…", { id: `quote-${value.id}-request-to-pay` })
327329
},
328330
onSettled: () => {
329331
toast.dismiss(`quote-${value.id}-request-to-pay`)
@@ -334,7 +336,10 @@ function QuoteActions({
334336
},
335337
onSuccess: (data) => {
336338
toast.success("Payment request has been created.")
337-
setMintRequestResponse(data)
339+
setPayRequestResponse(data)
340+
void queryClient.invalidateQueries({
341+
queryKey: ["bill_id", value.bill.id],
342+
})
338343
},
339344
})
340345

@@ -371,8 +376,8 @@ function QuoteActions({
371376
activateKeysetMutation.mutate()
372377
}
373378

374-
const onRequestToMint = () => {
375-
requestToMintMutation.mutate()
379+
const onRequestToPay = () => {
380+
requestToPayMutation.mutate()
376381
}
377382
return (
378383
<>
@@ -476,20 +481,20 @@ function QuoteActions({
476481
<></>
477482
)}
478483

479-
{value.status === "Accepted" && "keyset_id" in value && !ebillPaid && !newKeyset ? (
484+
{value.status === "Accepted" && "keyset_id" in value && !ebillPaid && !newKeyset && !requestedToPay ? (
480485
<ConfirmDrawer
481-
title="Confirm requesting to mint"
482-
description="Are you sure you want to request to mint from this e-bill?"
483-
open={requestToMintConfirmDrawerOpen}
484-
onOpenChange={setRequestToMintConfirmDrawerOpen}
486+
title="Confirm requesting to pay"
487+
description="Are you sure you want to request to pay this e-bill?"
488+
open={requestToPayConfirmDrawerOpen}
489+
onOpenChange={setRequestToPayConfirmDrawerOpen}
485490
onSubmit={() => {
486-
onRequestToMint()
487-
setRequestToMintConfirmDrawerOpen(false)
491+
onRequestToPay()
492+
setRequestToPayConfirmDrawerOpen(false)
488493
}}
489-
submitButtonText="Yes, request to mint"
494+
submitButtonText="Yes, request to pay"
490495
trigger={
491-
<Button className="flex-1" disabled={isFetching || requestToMintMutation.isPending} variant="default">
492-
Request to Pay {requestToMintMutation.isPending && <LoaderIcon className="stroke-1 animate-spin" />}
496+
<Button className="flex-1" disabled={isFetching || requestToPayMutation.isPending} variant="default">
497+
Request to Pay {requestToPayMutation.isPending && <LoaderIcon className="stroke-1 animate-spin" />}
493498
</Button>
494499
}
495500
/>
@@ -498,18 +503,18 @@ function QuoteActions({
498503
)}
499504
</div>
500505

501-
{mintRequestResponse && (
506+
{payRequestResponse && (
502507
<div className="mt-4 p-4 bg-gray-50 rounded-lg">
503508
<h3 className="font-bold mb-2">Payment Request</h3>
504509
<div className="space-y-2">
505510
<div>
506511
<span className="font-bold">ID</span>
507-
<span className="font-mono ml-2">{mintRequestResponse.request_id}</span>
512+
<span className="font-mono ml-2">{payRequestResponse.request_id}</span>
508513
</div>
509514
<div>
510515
<span className="font-bold">Details</span>
511516
<div className="font-mono text-sm mt-1 p-2 bg-white rounded border break-all">
512-
{mintRequestResponse.request}
517+
{payRequestResponse.request}
513518
</div>
514519
</div>
515520
</div>
@@ -676,6 +681,7 @@ function Quote({ value, isFetching }: { value: InfoReply; isFetching: boolean })
676681
const shouldFetchKeyset = (value.status === "Offered" || value.status === "Accepted") && "keyset_id" in value
677682

678683
const keysetId = "keyset_id" in value ? value.keyset_id : ""
684+
const billId = "bill" in value && "id" in value.bill ? value.bill.id : ""
679685

680686
const { data: keysetData } = useQuery({
681687
queryKey: ["keyset", keysetId],
@@ -686,6 +692,18 @@ function Quote({ value, isFetching }: { value: InfoReply; isFetching: boolean })
686692
enabled: shouldFetchKeyset,
687693
})
688694

695+
const { data: paymentData } = useQuery({
696+
queryKey: ["bill_id", billId],
697+
queryFn: () =>
698+
paymentStatus({
699+
path: { bill_id: billId },
700+
}),
701+
enabled: !!billId,
702+
})
703+
704+
const requestedToPay = paymentData?.data?.payment_status.requested_to_pay ?? false
705+
const paymentAddress = paymentData?.data?.payment_details?.address_to_pay ?? ""
706+
689707
const ebillPaid = keysetData?.data && "active" in keysetData.data && keysetData.data.active === false
690708
const newKeyset = "keyset_id" in value && (!keysetData?.data || !("active" in keysetData.data))
691709

@@ -727,6 +745,16 @@ function Quote({ value, isFetching }: { value: InfoReply; isFetching: boolean })
727745
) : (
728746
<></>
729747
)}
748+
{requestedToPay ? (
749+
<TableRow>
750+
<TableCell className="font-bold">Payment Address: </TableCell>
751+
<TableCell className="flex items-center gap-2">
752+
<span className="font-mono">{paymentAddress}</span>
753+
</TableCell>
754+
</TableRow>
755+
) : (
756+
<></>
757+
)}
730758
<TableRow>
731759
<TableCell className="font-bold">Status: </TableCell>
732760
<TableCell>
@@ -796,7 +824,13 @@ function Quote({ value, isFetching }: { value: InfoReply; isFetching: boolean })
796824
</TableBody>
797825
</Table>
798826

799-
<QuoteActions value={value} isFetching={isFetching} newKeyset={newKeyset} ebillPaid={ebillPaid ?? false} />
827+
<QuoteActions
828+
value={value}
829+
isFetching={isFetching}
830+
newKeyset={newKeyset}
831+
ebillPaid={ebillPaid ?? false}
832+
requestedToPay={requestedToPay ?? false}
833+
/>
800834
</div>
801835
)
802836
}

0 commit comments

Comments
 (0)