mirror of
https://github.com/apotdevin/thunderhub.git
synced 2026-08-13 12:33:08 +02:00
chore: invoice and payment search
This commit is contained in:
parent
28d1aa376b
commit
5e116d7359
14 changed files with 715 additions and 48 deletions
|
|
@ -867,6 +867,7 @@ type Query {
|
|||
getConfigState: ConfigState!
|
||||
getForwards(days: Float!): GetForwards!
|
||||
getHello: String!
|
||||
getInvoice(id: String!): InvoiceType!
|
||||
getInvoiceStatusChange(id: String!): String!
|
||||
getInvoices(token: String): GetInvoicesType!
|
||||
getLatestVersion: String!
|
||||
|
|
@ -877,6 +878,7 @@ type Query {
|
|||
getNodeBalances: Balances!
|
||||
getNodeInfo: NodeInfo!
|
||||
getNodeSocialInfo(pubkey: String!): LightningNodeSocialInfo!
|
||||
getPayment(id: String!): PaymentType!
|
||||
getPayments(token: String): GetPaymentsType!
|
||||
getPeers: [Peer!]!
|
||||
getPendingChannels: [PendingChannel!]!
|
||||
|
|
|
|||
180
src/client/src/graphql/queries/__generated__/getInvoice.generated.tsx
generated
Normal file
180
src/client/src/graphql/queries/__generated__/getInvoice.generated.tsx
generated
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
import * as Types from '../../types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type GetInvoiceQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
export type GetInvoiceQuery = {
|
||||
__typename?: 'Query';
|
||||
getInvoice: {
|
||||
__typename?: 'InvoiceType';
|
||||
chain_address?: string | null;
|
||||
confirmed_at?: string | null;
|
||||
created_at: string;
|
||||
description: string;
|
||||
description_hash?: string | null;
|
||||
expires_at: string;
|
||||
id: string;
|
||||
is_canceled?: boolean | null;
|
||||
is_confirmed: boolean;
|
||||
is_held?: boolean | null;
|
||||
is_private: boolean;
|
||||
is_push?: boolean | null;
|
||||
received: number;
|
||||
received_mtokens: string;
|
||||
request?: string | null;
|
||||
secret: string;
|
||||
tokens: string;
|
||||
type: string;
|
||||
date: string;
|
||||
payments: Array<{
|
||||
__typename?: 'InvoicePayment';
|
||||
canceled_at?: string | null;
|
||||
confirmed_at?: string | null;
|
||||
created_at: string;
|
||||
created_height: number;
|
||||
is_canceled: boolean;
|
||||
is_confirmed: boolean;
|
||||
is_held: boolean;
|
||||
mtokens: string;
|
||||
pending_index?: number | null;
|
||||
timeout: number;
|
||||
tokens: number;
|
||||
total_mtokens?: string | null;
|
||||
in_channel: string;
|
||||
messages?: { __typename?: 'MessageType'; message?: string | null } | null;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
export const GetInvoiceDocument = gql`
|
||||
query GetInvoice($id: String!) {
|
||||
getInvoice(id: $id) {
|
||||
chain_address
|
||||
confirmed_at
|
||||
created_at
|
||||
description
|
||||
description_hash
|
||||
expires_at
|
||||
id
|
||||
is_canceled
|
||||
is_confirmed
|
||||
is_held
|
||||
is_private
|
||||
is_push
|
||||
received
|
||||
received_mtokens
|
||||
request
|
||||
secret
|
||||
tokens
|
||||
type
|
||||
date
|
||||
payments {
|
||||
canceled_at
|
||||
confirmed_at
|
||||
created_at
|
||||
created_height
|
||||
is_canceled
|
||||
is_confirmed
|
||||
is_held
|
||||
mtokens
|
||||
pending_index
|
||||
timeout
|
||||
tokens
|
||||
total_mtokens
|
||||
in_channel
|
||||
messages {
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useGetInvoiceQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useGetInvoiceQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetInvoiceQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useGetInvoiceQuery({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetInvoiceQuery(
|
||||
baseOptions: Apollo.QueryHookOptions<
|
||||
GetInvoiceQuery,
|
||||
GetInvoiceQueryVariables
|
||||
> &
|
||||
(
|
||||
| { variables: GetInvoiceQueryVariables; skip?: boolean }
|
||||
| { skip: boolean }
|
||||
)
|
||||
) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useQuery<GetInvoiceQuery, GetInvoiceQueryVariables>(
|
||||
GetInvoiceDocument,
|
||||
options
|
||||
);
|
||||
}
|
||||
export function useGetInvoiceLazyQuery(
|
||||
baseOptions?: Apollo.LazyQueryHookOptions<
|
||||
GetInvoiceQuery,
|
||||
GetInvoiceQueryVariables
|
||||
>
|
||||
) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useLazyQuery<GetInvoiceQuery, GetInvoiceQueryVariables>(
|
||||
GetInvoiceDocument,
|
||||
options
|
||||
);
|
||||
}
|
||||
// @ts-ignore
|
||||
export function useGetInvoiceSuspenseQuery(
|
||||
baseOptions?: Apollo.SuspenseQueryHookOptions<
|
||||
GetInvoiceQuery,
|
||||
GetInvoiceQueryVariables
|
||||
>
|
||||
): Apollo.UseSuspenseQueryResult<GetInvoiceQuery, GetInvoiceQueryVariables>;
|
||||
export function useGetInvoiceSuspenseQuery(
|
||||
baseOptions?:
|
||||
| Apollo.SkipToken
|
||||
| Apollo.SuspenseQueryHookOptions<GetInvoiceQuery, GetInvoiceQueryVariables>
|
||||
): Apollo.UseSuspenseQueryResult<
|
||||
GetInvoiceQuery | undefined,
|
||||
GetInvoiceQueryVariables
|
||||
>;
|
||||
export function useGetInvoiceSuspenseQuery(
|
||||
baseOptions?:
|
||||
| Apollo.SkipToken
|
||||
| Apollo.SuspenseQueryHookOptions<GetInvoiceQuery, GetInvoiceQueryVariables>
|
||||
) {
|
||||
const options =
|
||||
baseOptions === Apollo.skipToken
|
||||
? baseOptions
|
||||
: { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useSuspenseQuery<GetInvoiceQuery, GetInvoiceQueryVariables>(
|
||||
GetInvoiceDocument,
|
||||
options
|
||||
);
|
||||
}
|
||||
export type GetInvoiceQueryHookResult = ReturnType<typeof useGetInvoiceQuery>;
|
||||
export type GetInvoiceLazyQueryHookResult = ReturnType<
|
||||
typeof useGetInvoiceLazyQuery
|
||||
>;
|
||||
export type GetInvoiceSuspenseQueryHookResult = ReturnType<
|
||||
typeof useGetInvoiceSuspenseQuery
|
||||
>;
|
||||
export type GetInvoiceQueryResult = Apollo.QueryResult<
|
||||
GetInvoiceQuery,
|
||||
GetInvoiceQueryVariables
|
||||
>;
|
||||
|
|
@ -17,6 +17,7 @@ export type GetOfferReadinessQuery = {
|
|||
is_peer_connected: boolean;
|
||||
has_pending_order: boolean;
|
||||
pending_order_id?: string | null;
|
||||
pending_order_status?: string | null;
|
||||
onchain_balance_sats: string;
|
||||
onchain_asset_balance: string;
|
||||
btc_channels: {
|
||||
|
|
@ -47,6 +48,7 @@ export const GetOfferReadinessDocument = gql`
|
|||
is_peer_connected
|
||||
has_pending_order
|
||||
pending_order_id
|
||||
pending_order_status
|
||||
onchain_balance_sats
|
||||
onchain_asset_balance
|
||||
btc_channels {
|
||||
|
|
|
|||
165
src/client/src/graphql/queries/__generated__/getPayment.generated.tsx
generated
Normal file
165
src/client/src/graphql/queries/__generated__/getPayment.generated.tsx
generated
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
import * as Types from '../../types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type GetPaymentQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
export type GetPaymentQuery = {
|
||||
__typename?: 'Query';
|
||||
getPayment: {
|
||||
__typename?: 'PaymentType';
|
||||
created_at: string;
|
||||
destination: string;
|
||||
fee: number;
|
||||
fee_mtokens: string;
|
||||
id: string;
|
||||
is_confirmed: boolean;
|
||||
is_outgoing: boolean;
|
||||
mtokens: string;
|
||||
request?: string | null;
|
||||
safe_fee: number;
|
||||
safe_tokens?: number | null;
|
||||
secret: string;
|
||||
tokens: string;
|
||||
type: string;
|
||||
date: string;
|
||||
destination_node: {
|
||||
__typename?: 'Node';
|
||||
node?: {
|
||||
__typename?: 'NodeType';
|
||||
alias: string;
|
||||
public_key: string;
|
||||
} | null;
|
||||
};
|
||||
hops: Array<{
|
||||
__typename?: 'Node';
|
||||
node?: {
|
||||
__typename?: 'NodeType';
|
||||
alias: string;
|
||||
public_key: string;
|
||||
} | null;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
export const GetPaymentDocument = gql`
|
||||
query GetPayment($id: String!) {
|
||||
getPayment(id: $id) {
|
||||
created_at
|
||||
destination
|
||||
destination_node {
|
||||
node {
|
||||
alias
|
||||
public_key
|
||||
}
|
||||
}
|
||||
fee
|
||||
fee_mtokens
|
||||
hops {
|
||||
node {
|
||||
alias
|
||||
public_key
|
||||
}
|
||||
}
|
||||
id
|
||||
is_confirmed
|
||||
is_outgoing
|
||||
mtokens
|
||||
request
|
||||
safe_fee
|
||||
safe_tokens
|
||||
secret
|
||||
tokens
|
||||
type
|
||||
date
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useGetPaymentQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useGetPaymentQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetPaymentQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useGetPaymentQuery({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetPaymentQuery(
|
||||
baseOptions: Apollo.QueryHookOptions<
|
||||
GetPaymentQuery,
|
||||
GetPaymentQueryVariables
|
||||
> &
|
||||
(
|
||||
| { variables: GetPaymentQueryVariables; skip?: boolean }
|
||||
| { skip: boolean }
|
||||
)
|
||||
) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useQuery<GetPaymentQuery, GetPaymentQueryVariables>(
|
||||
GetPaymentDocument,
|
||||
options
|
||||
);
|
||||
}
|
||||
export function useGetPaymentLazyQuery(
|
||||
baseOptions?: Apollo.LazyQueryHookOptions<
|
||||
GetPaymentQuery,
|
||||
GetPaymentQueryVariables
|
||||
>
|
||||
) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useLazyQuery<GetPaymentQuery, GetPaymentQueryVariables>(
|
||||
GetPaymentDocument,
|
||||
options
|
||||
);
|
||||
}
|
||||
// @ts-ignore
|
||||
export function useGetPaymentSuspenseQuery(
|
||||
baseOptions?: Apollo.SuspenseQueryHookOptions<
|
||||
GetPaymentQuery,
|
||||
GetPaymentQueryVariables
|
||||
>
|
||||
): Apollo.UseSuspenseQueryResult<GetPaymentQuery, GetPaymentQueryVariables>;
|
||||
export function useGetPaymentSuspenseQuery(
|
||||
baseOptions?:
|
||||
| Apollo.SkipToken
|
||||
| Apollo.SuspenseQueryHookOptions<GetPaymentQuery, GetPaymentQueryVariables>
|
||||
): Apollo.UseSuspenseQueryResult<
|
||||
GetPaymentQuery | undefined,
|
||||
GetPaymentQueryVariables
|
||||
>;
|
||||
export function useGetPaymentSuspenseQuery(
|
||||
baseOptions?:
|
||||
| Apollo.SkipToken
|
||||
| Apollo.SuspenseQueryHookOptions<GetPaymentQuery, GetPaymentQueryVariables>
|
||||
) {
|
||||
const options =
|
||||
baseOptions === Apollo.skipToken
|
||||
? baseOptions
|
||||
: { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useSuspenseQuery<GetPaymentQuery, GetPaymentQueryVariables>(
|
||||
GetPaymentDocument,
|
||||
options
|
||||
);
|
||||
}
|
||||
export type GetPaymentQueryHookResult = ReturnType<typeof useGetPaymentQuery>;
|
||||
export type GetPaymentLazyQueryHookResult = ReturnType<
|
||||
typeof useGetPaymentLazyQuery
|
||||
>;
|
||||
export type GetPaymentSuspenseQueryHookResult = ReturnType<
|
||||
typeof useGetPaymentSuspenseQuery
|
||||
>;
|
||||
export type GetPaymentQueryResult = Apollo.QueryResult<
|
||||
GetPaymentQuery,
|
||||
GetPaymentQueryVariables
|
||||
>;
|
||||
45
src/client/src/graphql/queries/getInvoice.ts
Normal file
45
src/client/src/graphql/queries/getInvoice.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_INVOICE = gql`
|
||||
query GetInvoice($id: String!) {
|
||||
getInvoice(id: $id) {
|
||||
chain_address
|
||||
confirmed_at
|
||||
created_at
|
||||
description
|
||||
description_hash
|
||||
expires_at
|
||||
id
|
||||
is_canceled
|
||||
is_confirmed
|
||||
is_held
|
||||
is_private
|
||||
is_push
|
||||
received
|
||||
received_mtokens
|
||||
request
|
||||
secret
|
||||
tokens
|
||||
type
|
||||
date
|
||||
payments {
|
||||
canceled_at
|
||||
confirmed_at
|
||||
created_at
|
||||
created_height
|
||||
is_canceled
|
||||
is_confirmed
|
||||
is_held
|
||||
mtokens
|
||||
pending_index
|
||||
timeout
|
||||
tokens
|
||||
total_mtokens
|
||||
in_channel
|
||||
messages {
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
35
src/client/src/graphql/queries/getPayment.ts
Normal file
35
src/client/src/graphql/queries/getPayment.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_PAYMENT = gql`
|
||||
query GetPayment($id: String!) {
|
||||
getPayment(id: $id) {
|
||||
created_at
|
||||
destination
|
||||
destination_node {
|
||||
node {
|
||||
alias
|
||||
public_key
|
||||
}
|
||||
}
|
||||
fee
|
||||
fee_mtokens
|
||||
hops {
|
||||
node {
|
||||
alias
|
||||
public_key
|
||||
}
|
||||
}
|
||||
id
|
||||
is_confirmed
|
||||
is_outgoing
|
||||
mtokens
|
||||
request
|
||||
safe_fee
|
||||
safe_tokens
|
||||
secret
|
||||
tokens
|
||||
type
|
||||
date
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
@ -948,6 +948,7 @@ export type OfferReadinessResult = {
|
|||
onchain_asset_balance: Scalars['String']['output'];
|
||||
onchain_balance_sats: Scalars['String']['output'];
|
||||
pending_order_id?: Maybe<Scalars['String']['output']>;
|
||||
pending_order_status?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type OnChainBalance = {
|
||||
|
|
@ -1157,6 +1158,7 @@ export type Query = {
|
|||
getConfigState: ConfigState;
|
||||
getForwards: GetForwards;
|
||||
getHello: Scalars['String']['output'];
|
||||
getInvoice: InvoiceType;
|
||||
getInvoiceStatusChange: Scalars['String']['output'];
|
||||
getInvoices: GetInvoicesType;
|
||||
getLatestVersion: Scalars['String']['output'];
|
||||
|
|
@ -1167,6 +1169,7 @@ export type Query = {
|
|||
getNodeBalances: Balances;
|
||||
getNodeInfo: NodeInfo;
|
||||
getNodeSocialInfo: LightningNodeSocialInfo;
|
||||
getPayment: PaymentType;
|
||||
getPayments: GetPaymentsType;
|
||||
getPeers: Array<Peer>;
|
||||
getPendingChannels: Array<PendingChannel>;
|
||||
|
|
@ -1206,6 +1209,10 @@ export type QueryGetForwardsArgs = {
|
|||
days: Scalars['Float']['input'];
|
||||
};
|
||||
|
||||
export type QueryGetInvoiceArgs = {
|
||||
id: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type QueryGetInvoiceStatusChangeArgs = {
|
||||
id: Scalars['String']['input'];
|
||||
};
|
||||
|
|
@ -1227,6 +1234,10 @@ export type QueryGetNodeSocialInfoArgs = {
|
|||
pubkey: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type QueryGetPaymentArgs = {
|
||||
id: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type QueryGetPaymentsArgs = {
|
||||
token?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { useState, useMemo, useCallback } from 'react';
|
||||
import { useState, useMemo, useCallback, FormEvent } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { InvoiceCard } from '../views/transactions/InvoiceCard';
|
||||
import { GridWrapper } from '../components/gridWrapper/GridWrapper';
|
||||
import { Settings, Loader2 } from 'lucide-react';
|
||||
import { Settings, Loader2, Search, X } from 'lucide-react';
|
||||
import { useLocalStorage } from '../hooks/UseLocalStorage';
|
||||
import { useNodeInfo } from '../hooks/UseNodeInfo';
|
||||
import {
|
||||
|
|
@ -13,6 +13,7 @@ import { format } from 'date-fns';
|
|||
import { getErrorContent } from '../utils/error';
|
||||
import { PaymentsCard } from '../views/transactions/PaymentsCards';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { FlowBox } from '../views/home/reports/flow';
|
||||
import {
|
||||
GetInvoicesQuery,
|
||||
|
|
@ -22,6 +23,8 @@ import {
|
|||
GetPaymentsQuery,
|
||||
useGetPaymentsQuery,
|
||||
} from '../graphql/queries/__generated__/getPayments.generated';
|
||||
import { useGetInvoiceLazyQuery } from '../graphql/queries/__generated__/getInvoice.generated';
|
||||
import { useGetPaymentLazyQuery } from '../graphql/queries/__generated__/getPayment.generated';
|
||||
import { Card, CardContent, CardFooter } from '@/components/ui/card';
|
||||
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
||||
import { TradeDisplayMode } from '../views/transactions/tradeMemo';
|
||||
|
|
@ -32,6 +35,41 @@ const TransactionsView = () => {
|
|||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
const [graphShow, setGraphShow] = useState('invoices');
|
||||
const [graphType, setGraphType] = useState('count');
|
||||
const [lookupValue, setLookupValue] = useState('');
|
||||
const [lookupOpen, setLookupOpen] = useState(1);
|
||||
|
||||
const [lookupInvoice, invoiceLookup] = useGetInvoiceLazyQuery({
|
||||
onError: error => toast.error(getErrorContent(error)),
|
||||
});
|
||||
const [lookupPayment, paymentLookup] = useGetPaymentLazyQuery({
|
||||
onError: error => toast.error(getErrorContent(error)),
|
||||
});
|
||||
|
||||
const activeLookup = activeTab === 'invoices' ? invoiceLookup : paymentLookup;
|
||||
|
||||
const handleLookupSubmit = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
const id = lookupValue.trim();
|
||||
if (!id) return;
|
||||
setLookupOpen(1);
|
||||
if (activeTab === 'invoices') {
|
||||
lookupInvoice({ variables: { id } });
|
||||
} else {
|
||||
lookupPayment({ variables: { id } });
|
||||
}
|
||||
};
|
||||
|
||||
const handleLookupClear = () => {
|
||||
setLookupValue('');
|
||||
invoiceLookup.reset();
|
||||
paymentLookup.reset();
|
||||
};
|
||||
|
||||
const handleTabChange = (v: string) => {
|
||||
if (!v) return;
|
||||
setActiveTab(v);
|
||||
handleLookupClear();
|
||||
};
|
||||
|
||||
const { publicKey } = useNodeInfo();
|
||||
|
||||
|
|
@ -250,7 +288,7 @@ const TransactionsView = () => {
|
|||
variant="outline"
|
||||
size="sm"
|
||||
value={activeTab}
|
||||
onValueChange={v => v && setActiveTab(v)}
|
||||
onValueChange={handleTabChange}
|
||||
>
|
||||
<ToggleGroupItem value="invoices">Invoices</ToggleGroupItem>
|
||||
<ToggleGroupItem value="payments">Payments</ToggleGroupItem>
|
||||
|
|
@ -264,6 +302,81 @@ const TransactionsView = () => {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardContent>
|
||||
<form
|
||||
onSubmit={handleLookupSubmit}
|
||||
className="flex flex-col gap-2 sm:flex-row sm:items-center"
|
||||
>
|
||||
<div className="relative flex-1">
|
||||
<Search
|
||||
size={14}
|
||||
className="absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground"
|
||||
/>
|
||||
<Input
|
||||
value={lookupValue}
|
||||
onChange={e => setLookupValue(e.target.value)}
|
||||
placeholder={
|
||||
activeTab === 'invoices'
|
||||
? 'Lookup invoice by r-hash (payment hash)'
|
||||
: 'Lookup payment by payment hash'
|
||||
}
|
||||
className="pl-8 pr-8 font-mono text-xs"
|
||||
spellCheck={false}
|
||||
/>
|
||||
{lookupValue && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleLookupClear}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||
aria-label="Clear"
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
type="submit"
|
||||
size="sm"
|
||||
disabled={!lookupValue.trim() || activeLookup.loading}
|
||||
>
|
||||
{activeLookup.loading ? (
|
||||
<Loader2 className="animate-spin" size={14} />
|
||||
) : (
|
||||
'Lookup'
|
||||
)}
|
||||
</Button>
|
||||
</form>
|
||||
{activeLookup.called && !activeLookup.loading && (
|
||||
<div className="mt-3">
|
||||
{activeTab === 'invoices' && invoiceLookup.data?.getInvoice ? (
|
||||
<InvoiceCard
|
||||
invoice={invoiceLookup.data.getInvoice as any}
|
||||
tradeDisplayMode={tradeDisplayMode}
|
||||
index={1}
|
||||
setIndexOpen={setLookupOpen}
|
||||
indexOpen={lookupOpen}
|
||||
/>
|
||||
) : activeTab === 'payments' && paymentLookup.data?.getPayment ? (
|
||||
<PaymentsCard
|
||||
payment={paymentLookup.data.getPayment as any}
|
||||
tradeDisplayMode={tradeDisplayMode}
|
||||
index={1}
|
||||
setIndexOpen={setLookupOpen}
|
||||
indexOpen={lookupOpen}
|
||||
/>
|
||||
) : activeLookup.error ? (
|
||||
<div className="text-center text-sm text-muted-foreground py-3">
|
||||
{activeTab === 'invoices'
|
||||
? 'No invoice found for that hash.'
|
||||
: 'No payment found for that hash.'}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{settingsOpen && (
|
||||
<Card>
|
||||
<CardContent>
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ interface InvoiceCardProps {
|
|||
indexOpen: number;
|
||||
}
|
||||
|
||||
const ChannelAlias: FC<{ id: string }> = ({ id }) => {
|
||||
const ChannelPeer: FC<{ id: string }> = ({ id }) => {
|
||||
const { data, loading, error } = useGetChannelQuery({
|
||||
variables: { id },
|
||||
});
|
||||
|
|
@ -35,10 +35,20 @@ const ChannelAlias: FC<{ id: string }> = ({ id }) => {
|
|||
if (loading) return <LoadingCard noCard={true} />;
|
||||
if (error) return <span className="text-muted-foreground">Unknown</span>;
|
||||
|
||||
const alias =
|
||||
data?.getChannel.partner_node_policies?.node?.node?.alias || 'Unknown';
|
||||
const peer = data?.getChannel.partner_node_policies?.node?.node;
|
||||
const alias = peer?.alias || 'Unknown';
|
||||
const pubkey = peer?.public_key;
|
||||
|
||||
return <span>{alias}</span>;
|
||||
return (
|
||||
<div className="flex flex-col items-end gap-0.5">
|
||||
<span>{alias}</span>
|
||||
{pubkey && (
|
||||
<span className="font-mono text-[10px] text-muted-foreground break-all">
|
||||
{pubkey}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const StatusBadge = ({
|
||||
|
|
@ -166,46 +176,6 @@ export const InvoiceCard = ({
|
|||
{isOpen && (
|
||||
<div className="px-3 pb-3">
|
||||
<Separator className="mb-3" />
|
||||
{payments.length > 0 && (
|
||||
<div className="mb-3 space-y-2">
|
||||
<div className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||
Payments
|
||||
</div>
|
||||
{payments.map((p, idx) => {
|
||||
const message = getTradeMemoText(
|
||||
p.messages?.message,
|
||||
tradeDisplayMode
|
||||
);
|
||||
const messageDisplay = getTradeMemoDisplay(p.messages?.message);
|
||||
return (
|
||||
<div key={idx} className="rounded bg-muted/50 p-2">
|
||||
<DetailTable>
|
||||
<DetailRow label="Amount">
|
||||
<Price amount={p.tokens} />
|
||||
</DetailRow>
|
||||
<DetailRow label="Channel">{p.in_channel}</DetailRow>
|
||||
<DetailRow label="Peer">
|
||||
<ChannelAlias id={p.in_channel} />
|
||||
</DetailRow>
|
||||
{messageDisplay?.isTradeMemo && (
|
||||
<DetailRow label="Parsed Message">
|
||||
<div className="inline-flex items-start gap-1">
|
||||
<span>{messageDisplay.computed}</span>
|
||||
<ComputedMarker />
|
||||
</div>
|
||||
</DetailRow>
|
||||
)}
|
||||
{message && (
|
||||
<DetailRow label="Message">
|
||||
{messageDisplay?.raw ?? message}
|
||||
</DetailRow>
|
||||
)}
|
||||
</DetailTable>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<DetailTable>
|
||||
{descriptionDisplay?.isTradeMemo && (
|
||||
<DetailRow label="Parsed Description">
|
||||
|
|
@ -244,6 +214,76 @@ export const InvoiceCard = ({
|
|||
{secret && <DetailRow label="Secret">{secret}</DetailRow>}
|
||||
{request && <DetailRow label="Request">{request}</DetailRow>}
|
||||
</DetailTable>
|
||||
{payments.length > 0 && (
|
||||
<div className="mt-3 space-y-2">
|
||||
<div className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||
HTLCs
|
||||
</div>
|
||||
{payments.map((p, idx) => {
|
||||
const message = getTradeMemoText(
|
||||
p.messages?.message,
|
||||
tradeDisplayMode
|
||||
);
|
||||
const messageDisplay = getTradeMemoDisplay(p.messages?.message);
|
||||
return (
|
||||
<div key={idx} className="rounded bg-muted/50 p-2">
|
||||
<DetailTable>
|
||||
<DetailRow label="State">
|
||||
<StatusBadge
|
||||
confirmed={p.is_confirmed}
|
||||
canceled={p.is_canceled}
|
||||
/>
|
||||
</DetailRow>
|
||||
{p.pending_index != null && (
|
||||
<DetailRow label="HTLC Index">
|
||||
{p.pending_index}
|
||||
</DetailRow>
|
||||
)}
|
||||
<DetailRow label="Amount">
|
||||
<Price amount={p.tokens} />
|
||||
</DetailRow>
|
||||
<DetailRow label="Amount (msat)">{p.mtokens}</DetailRow>
|
||||
{p.total_mtokens && (
|
||||
<DetailRow label="MPP Total (msat)">
|
||||
{p.total_mtokens}
|
||||
</DetailRow>
|
||||
)}
|
||||
<DetailRow label="Channel">{p.in_channel}</DetailRow>
|
||||
<DetailRow label="Peer">
|
||||
<ChannelPeer id={p.in_channel} />
|
||||
</DetailRow>
|
||||
<DetailRow label="Accepted">
|
||||
{`${getDateDif(p.created_at)} ago (${getFormatDate(p.created_at)})`}
|
||||
</DetailRow>
|
||||
<DetailRow label="Accept Height">
|
||||
{p.created_height}
|
||||
</DetailRow>
|
||||
{p.is_confirmed && p.confirmed_at && (
|
||||
<DetailRow label="Resolved">
|
||||
{`${getDateDif(p.confirmed_at)} ago (${getFormatDate(p.confirmed_at)})`}
|
||||
</DetailRow>
|
||||
)}
|
||||
<DetailRow label="Expiry Height">{p.timeout}</DetailRow>
|
||||
{p.is_held && <DetailRow label="Held">Yes</DetailRow>}
|
||||
{messageDisplay?.isTradeMemo && (
|
||||
<DetailRow label="Parsed Message">
|
||||
<div className="inline-flex items-start gap-1">
|
||||
<span>{messageDisplay.computed}</span>
|
||||
<ComputedMarker />
|
||||
</div>
|
||||
</DetailRow>
|
||||
)}
|
||||
{message && (
|
||||
<DetailRow label="Message">
|
||||
{messageDisplay?.raw ?? message}
|
||||
</DetailRow>
|
||||
)}
|
||||
</DetailTable>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,12 +3,32 @@ import { decodeMessages } from 'src/server/utils/customRecords';
|
|||
import { NodeService } from '../../node/node.service';
|
||||
import { CurrentUser } from '../../security/security.decorators';
|
||||
import { UserId } from '../../security/security.types';
|
||||
import { GetInvoicesType, GetPaymentsType } from './transactions.types';
|
||||
import {
|
||||
GetInvoicesType,
|
||||
GetPaymentsType,
|
||||
InvoiceType,
|
||||
PaymentType,
|
||||
} from './transactions.types';
|
||||
|
||||
@Resolver()
|
||||
export class TransactionsResolver {
|
||||
constructor(private nodeService: NodeService) {}
|
||||
|
||||
@Query(() => InvoiceType)
|
||||
async getInvoice(@CurrentUser() user: UserId, @Args('id') id: string) {
|
||||
const invoice = await this.nodeService.getInvoice(user.id, { id });
|
||||
|
||||
return {
|
||||
...invoice,
|
||||
type: 'invoice',
|
||||
date: invoice.confirmed_at || invoice.created_at,
|
||||
payments: invoice.payments.map(p => ({
|
||||
...p,
|
||||
messages: decodeMessages(p.messages),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@Query(() => GetInvoicesType)
|
||||
async getInvoices(
|
||||
@CurrentUser() user: UserId,
|
||||
|
|
@ -31,6 +51,30 @@ export class TransactionsResolver {
|
|||
return { next, invoices: mapped };
|
||||
}
|
||||
|
||||
@Query(() => PaymentType)
|
||||
async getPayment(@CurrentUser() user: UserId, @Args('id') id: string) {
|
||||
const result = await this.nodeService.getPayment(user.id, { id });
|
||||
const payment = result?.payment;
|
||||
|
||||
if (!payment) {
|
||||
throw new Error('Payment not found');
|
||||
}
|
||||
|
||||
return {
|
||||
...payment,
|
||||
type: 'payment',
|
||||
date: payment.created_at,
|
||||
is_confirmed: result.is_confirmed ?? false,
|
||||
is_outgoing: true,
|
||||
safe_fee: payment.fee,
|
||||
safe_tokens: payment.tokens,
|
||||
destination_node: { publicKey: payment.destination },
|
||||
hops: payment.hops.map((hop: { public_key: string }) => ({
|
||||
publicKey: hop.public_key,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@Query(() => GetPaymentsType)
|
||||
async getPayments(
|
||||
@CurrentUser() user: UserId,
|
||||
|
|
|
|||
|
|
@ -294,10 +294,12 @@ export interface LightningProvider {
|
|||
options: GetRouteToDestinationOptions
|
||||
): Promise<GetRouteToDestinationResult>;
|
||||
decodePaymentRequest(connection: any, request: string): Promise<any>;
|
||||
getPayment(connection: any, options: { id: string }): Promise<any>;
|
||||
getPayments(connection: any, options: GetPaymentsOptions): Promise<any>;
|
||||
|
||||
// ── Invoices ──
|
||||
createInvoice(connection: any, options: CreateInvoiceOptions): Promise<any>;
|
||||
getInvoice(connection: any, options: { id: string }): Promise<any>;
|
||||
getInvoices(connection: any, options: GetInvoicesOptions): Promise<any>;
|
||||
subscribeToInvoice(connection: any, id: string): EventEmitter;
|
||||
|
||||
|
|
|
|||
|
|
@ -210,6 +210,10 @@ export class LitdService implements LightningProvider, TaprootAssetsProvider {
|
|||
);
|
||||
}
|
||||
|
||||
async getPayment(connection: LitdConnection, options: { id: string }) {
|
||||
return this.lndService.getPayment(this.getLnd(connection), options);
|
||||
}
|
||||
|
||||
async getPayments(connection: LitdConnection, options: GetPaymentsOptions) {
|
||||
return this.lndService.getPayments(this.getLnd(connection), options);
|
||||
}
|
||||
|
|
@ -223,6 +227,10 @@ export class LitdService implements LightningProvider, TaprootAssetsProvider {
|
|||
return this.lndService.createInvoice(this.getLnd(connection), options);
|
||||
}
|
||||
|
||||
async getInvoice(connection: LitdConnection, options: { id: string }) {
|
||||
return this.lndService.getInvoice(this.getLnd(connection), options);
|
||||
}
|
||||
|
||||
async getInvoices(connection: LitdConnection, options: GetInvoicesOptions) {
|
||||
return this.lndService.getInvoices(this.getLnd(connection), options);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,11 +32,13 @@ import {
|
|||
payViaRoutes,
|
||||
getRouteToDestination,
|
||||
createInvoice,
|
||||
getInvoice,
|
||||
getChannel,
|
||||
closeChannel,
|
||||
openChannel,
|
||||
updateRoutingFees,
|
||||
getForwards,
|
||||
getPayment,
|
||||
getPayments,
|
||||
getInvoices,
|
||||
subscribeToInvoice,
|
||||
|
|
@ -304,10 +306,18 @@ export class LndService implements LightningProvider {
|
|||
return to(getForwards({ lnd, ...options } as any));
|
||||
}
|
||||
|
||||
async getPayment(lnd: AuthenticatedLnd, options: { id: string }) {
|
||||
return to(getPayment({ lnd, ...options } as any));
|
||||
}
|
||||
|
||||
async getPayments(lnd: AuthenticatedLnd, options: GetPaymentsOptions) {
|
||||
return to(getPayments({ lnd, ...options } as any));
|
||||
}
|
||||
|
||||
async getInvoice(lnd: AuthenticatedLnd, options: { id: string }) {
|
||||
return to(getInvoice({ lnd, ...options } as any));
|
||||
}
|
||||
|
||||
async getInvoices(lnd: AuthenticatedLnd, options: GetInvoicesOptions) {
|
||||
return to(getInvoices({ lnd, ...options } as any));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -253,11 +253,21 @@ export class NodeService {
|
|||
return provider.getForwards(account.connection, options);
|
||||
}
|
||||
|
||||
async getPayment(id: string, options: { id: string }) {
|
||||
const { account, provider } = this.getAccountAndProvider(id);
|
||||
return provider.getPayment(account.connection, options);
|
||||
}
|
||||
|
||||
async getPayments(id: string, options: GetPaymentsOptions) {
|
||||
const { account, provider } = this.getAccountAndProvider(id);
|
||||
return provider.getPayments(account.connection, options);
|
||||
}
|
||||
|
||||
async getInvoice(id: string, options: { id: string }) {
|
||||
const { account, provider } = this.getAccountAndProvider(id);
|
||||
return provider.getInvoice(account.connection, options);
|
||||
}
|
||||
|
||||
async getInvoices(id: string, options: GetInvoicesOptions) {
|
||||
const { account, provider } = this.getAccountAndProvider(id);
|
||||
return provider.getInvoices(account.connection, options);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue