Skip to content

Commit 6215425

Browse files
authored
feat: Add Advanced Filtering Options to Invoice Dashboard and Improve Balance Check (#270)
1 parent ac29606 commit 6215425

File tree

5 files changed

+693
-183
lines changed

5 files changed

+693
-183
lines changed

packages/invoice-dashboard/src/lib/dashboard/invoice-view.svelte

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -373,24 +373,68 @@
373373
paymentCurrencies: any[],
374374
signer: any
375375
) => {
376-
const approvalCheckers: { [key: string]: () => Promise<boolean> } = {
377-
[Types.Extension.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT]: () =>
378-
hasErc20Approval(requestData!, address!, signer),
379-
[Types.Extension.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: () =>
380-
hasErc20ApprovalForProxyConversion(
376+
try {
377+
if (!paymentNetworkExtension?.id || !address || !signer) {
378+
console.log("Missing required arguments for approval check:", {
379+
network: paymentNetworkExtension?.id,
380+
address,
381+
signer: !!signer,
382+
});
383+
return false;
384+
}
385+
386+
// Skip approval check if payment is not required
387+
if (requestData?.balance?.balance >= requestData?.expectedAmount) {
388+
console.log("Payment already completed, skipping approval check");
389+
return true;
390+
}
391+
392+
// Validate payment currency
393+
if (!paymentCurrencies[0]?.address) {
394+
console.error("Invalid payment currency:", paymentCurrencies[0]);
395+
return false;
396+
}
397+
398+
// Check if we're on the correct network
399+
const chainId = await signer.getChainId();
400+
const expectedChainId = getNetworkIdFromNetworkName(network);
401+
const expectedChainIdNumber = parseInt(expectedChainId, 16);
402+
403+
if (chainId !== expectedChainIdNumber) {
404+
console.error("Wrong network:", {
405+
current: `0x${chainId.toString(16)}`,
406+
expected: expectedChainId,
407+
});
408+
return false;
409+
}
410+
411+
if (
412+
paymentNetworkExtension.id ===
413+
Types.Extension.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT
414+
) {
415+
return await hasErc20Approval(requestData!, address!, signer).catch(
416+
() => false
417+
);
418+
}
419+
420+
if (
421+
paymentNetworkExtension.id ===
422+
Types.Extension.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY
423+
) {
424+
return await hasErc20ApprovalForProxyConversion(
381425
requestData!,
382426
address!,
383427
paymentCurrencies[0]?.address,
384428
signer,
385429
requestData.expectedAmount
386-
),
387-
};
430+
).catch(() => false);
431+
}
388432
389-
return (
390-
(paymentNetworkExtension?.id &&
391-
(await approvalCheckers[paymentNetworkExtension.id]?.())) ||
392-
false
393-
);
433+
return false;
434+
} catch (error) {
435+
console.error("General approval check error:", error);
436+
return false;
437+
}
394438
};
395439
396440
async function approve() {
@@ -485,7 +529,7 @@
485529
async function checkBalance() {
486530
try {
487531
if (!address || !paymentCurrencies[0] || !network) {
488-
console.log("Missing required parameters for balance check:", {
532+
console.error("Missing required parameters for balance check:", {
489533
address,
490534
paymentCurrency: paymentCurrencies[0],
491535
network,

packages/invoice-dashboard/src/lib/view-requests.svelte

Lines changed: 176 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import DashboardSkeleton from "@requestnetwork/shared-components/dashboard-skeleton.svelte";
1818
import { toast } from "svelte-sonner";
1919
import Modal from "@requestnetwork/shared-components/modal.svelte";
20+
import SearchableDropdownCheckbox from "@requestnetwork/shared-components/searchable-checkbox-dropdown.svelte";
2021
// Icons
2122
import ChevronDown from "@requestnetwork/shared-icons/chevron-down.svelte";
2223
import ChevronLeft from "@requestnetwork/shared-icons/chevron-left.svelte";
@@ -106,6 +107,27 @@
106107
let sortOrder = "desc";
107108
let sortColumn = "timestamp";
108109
110+
let selectedNetworks: string[] = [];
111+
let networkOptions: { value: string; checked: boolean }[] = [];
112+
113+
let selectedTxTypes: string[] = [];
114+
let txTypeOptions = [
115+
{ value: "IN", checked: false },
116+
{ value: "OUT", checked: false },
117+
];
118+
119+
let selectedStatuses: string[] = [];
120+
let statusOptions = [
121+
{ value: "paid", checked: false },
122+
{ value: "partially paid", checked: false },
123+
{ value: "accepted", checked: false },
124+
{ value: "awaiting payment", checked: false },
125+
{ value: "canceled", checked: false },
126+
{ value: "rejected", checked: false },
127+
{ value: "overdue", checked: false },
128+
{ value: "pending", checked: false },
129+
];
130+
109131
const handleWalletConnection = async () => {
110132
account = getAccount(wagmiConfig);
111133
await loadRequests(sliderValueForDecryption, account, requestNetwork);
@@ -175,9 +197,23 @@
175197
type: Types.Identity.TYPE.ETHEREUM_ADDRESS,
176198
value: account?.address,
177199
});
200+
178201
requests = requestsData
179202
?.map((request) => request.getData())
180203
.sort((a, b) => b.timestamp - a.timestamp);
204+
205+
const uniqueNetworks = new Set<string>();
206+
requests?.forEach((request) => {
207+
const network = request.currencyInfo.network;
208+
if (network) {
209+
uniqueNetworks.add(network);
210+
}
211+
});
212+
213+
networkOptions = Array.from(uniqueNetworks).map((network) => ({
214+
value: network,
215+
checked: selectedNetworks.includes(network),
216+
}));
181217
} catch (error) {
182218
console.error("Failed to fetch requests:", error);
183219
} finally {
@@ -233,13 +269,29 @@
233269
234270
$: filteredRequests = requests?.filter((request) => {
235271
const terms = searchQuery.toLowerCase();
272+
const network = request.currencyInfo.network;
273+
const txType = signer === request.payer?.value ? "OUT" : "IN";
274+
const status = checkStatus(request).toLowerCase();
275+
276+
const networkMatch =
277+
selectedNetworks.length === 0 ||
278+
(network && selectedNetworks.includes(network));
279+
280+
const txTypeMatch =
281+
selectedTxTypes.length === 0 || selectedTxTypes.includes(txType);
282+
283+
const statusMatch =
284+
selectedStatuses.length === 0 || selectedStatuses.includes(status);
236285
237286
if (
238-
currentTab === "All" ||
239-
(currentTab === "Get Paid" &&
240-
request.payee?.value?.toLowerCase() === signer?.toLowerCase()) ||
241-
(currentTab === "Pay" &&
242-
request.payer?.value?.toLowerCase() === signer?.toLowerCase())
287+
networkMatch &&
288+
txTypeMatch &&
289+
statusMatch &&
290+
(currentTab === "All" ||
291+
(currentTab === "Get Paid" &&
292+
request.payee?.value?.toLowerCase() === signer?.toLowerCase()) ||
293+
(currentTab === "Pay" &&
294+
request.payer?.value?.toLowerCase() === signer?.toLowerCase()))
243295
) {
244296
const invoiceMatches = request.contentData?.invoiceNumber
245297
?.toString()
@@ -374,6 +426,7 @@
374426
const handleSearchChange = (event: Event) => {
375427
const { value } = event.target as HTMLInputElement;
376428
searchQuery = value;
429+
currentPage = 1;
377430
};
378431
379432
const handleSort = (column: string) => {
@@ -410,38 +463,66 @@
410463
return;
411464
412465
loading = true;
413-
if (sliderValue === "on") {
414-
try {
415-
const signer = await getEthersSigner(wagmiConfig);
416-
if (signer && currentAccount?.address) {
417-
loadSessionSignatures =
418-
localStorage?.getItem("lit-wallet-sig") === null;
419-
await cipherProvider?.getSessionSignatures(
420-
signer,
421-
currentAccount.address,
422-
window.location.host,
423-
"Sign in to Lit Protocol through Request Network"
424-
);
425-
cipherProvider?.enableDecryption(true);
426-
localStorage?.setItem("isDecryptionEnabled", JSON.stringify(true));
466+
const previousNetworks = [...selectedNetworks]; // Store current selection
467+
468+
try {
469+
if (sliderValue === "on") {
470+
try {
471+
const signer = await getEthersSigner(wagmiConfig);
472+
if (signer && currentAccount?.address) {
473+
loadSessionSignatures =
474+
localStorage?.getItem("lit-wallet-sig") === null;
475+
await cipherProvider?.getSessionSignatures(
476+
signer,
477+
currentAccount.address,
478+
window.location.host,
479+
"Sign in to Lit Protocol through Request Network"
480+
);
481+
cipherProvider?.enableDecryption(true);
482+
localStorage?.setItem("isDecryptionEnabled", JSON.stringify(true));
483+
}
484+
} catch (error) {
485+
console.error("Failed to enable decryption:", error);
486+
toast.error("Failed to enable decryption.");
487+
return;
488+
} finally {
489+
loadSessionSignatures = false;
427490
}
428-
} catch (error) {
429-
console.error("Failed to enable decryption:", error);
430-
toast.error("Failed to enable decryption.");
431-
loading = false;
432-
return;
433-
} finally {
434-
loadSessionSignatures = false;
491+
} else {
492+
cipherProvider?.enableDecryption(false);
493+
localStorage?.setItem("isDecryptionEnabled", JSON.stringify(false));
435494
}
436-
} else {
437-
cipherProvider?.enableDecryption(false);
438-
localStorage?.setItem("isDecryptionEnabled", JSON.stringify(false));
495+
await getRequests(currentAccount, currentRequestNetwork);
496+
selectedNetworks = previousNetworks; // Restore selection
497+
} finally {
498+
loading = false;
439499
}
440-
await getRequests(currentAccount, currentRequestNetwork);
441-
loading = false;
442500
};
443501
444502
$: loadRequests(sliderValueForDecryption, account, requestNetwork);
503+
504+
const handleNetworkSelection = async (networks: string[]) => {
505+
selectedNetworks = networks;
506+
currentPage = 1;
507+
if (networks.length === 0 && selectedNetworks.length > 0) {
508+
loading = true;
509+
try {
510+
await getRequests(account!, requestNetwork!);
511+
} finally {
512+
loading = false;
513+
}
514+
}
515+
};
516+
517+
const handleTxTypeSelection = (types: string[]) => {
518+
selectedTxTypes = types;
519+
currentPage = 1;
520+
};
521+
522+
const handleStatusSelection = (statuses: string[]) => {
523+
selectedStatuses = statuses;
524+
currentPage = 1;
525+
};
445526
</script>
446527

447528
<div
@@ -485,35 +566,59 @@
485566
</div>
486567
<div style="display: flex; flex-direction: column;">
487568
<div class="search-wrapper">
488-
<div class="search-wrapper" style="gap: 10px;">
489-
<Input
490-
placeholder="Search..."
491-
width="w-[300px]"
492-
handleInput={handleSearchChange}
493-
>
494-
<div slot="icon">
495-
<Search />
496-
</div>
497-
</Input>
498-
{#if cipherProvider}
499-
<div class="width: fit-content;">
500-
<Switch
501-
bind:value={sliderValueForDecryption}
502-
label="Show encrypted requests"
503-
fontSize={14}
504-
design="slider"
505-
/>
506-
</div>
507-
{/if}
569+
<Input
570+
placeholder="Search..."
571+
width="w-[300px]"
572+
handleInput={handleSearchChange}
573+
>
574+
<div slot="icon">
575+
<Search />
576+
</div>
577+
</Input>
578+
{#if cipherProvider}
579+
<div class="switch-wrapper">
580+
<Switch
581+
bind:value={sliderValueForDecryption}
582+
label="Show encrypted requests"
583+
fontSize={14}
584+
design="slider"
585+
/>
586+
</div>
587+
{/if}
588+
589+
<div class="dropdown-controls">
590+
<SearchableDropdownCheckbox
591+
config={activeConfig}
592+
options={statusOptions}
593+
placeholder="Filter by Status"
594+
onchange={handleStatusSelection}
595+
searchPlaceholder="Search statuses..."
596+
type="status"
597+
/>
598+
<SearchableDropdownCheckbox
599+
config={activeConfig}
600+
options={txTypeOptions}
601+
placeholder="Filter by Type"
602+
onchange={handleTxTypeSelection}
603+
type="transaction"
604+
noSearch={true}
605+
/>
606+
<SearchableDropdownCheckbox
607+
config={activeConfig}
608+
options={networkOptions}
609+
placeholder="Filter by Chain"
610+
onchange={handleNetworkSelection}
611+
searchPlaceholder="Search chains..."
612+
type="network"
613+
/>
614+
<Dropdown
615+
config={activeConfig}
616+
type="checkbox"
617+
options={columnOptions}
618+
placeholder="Select Columns"
619+
onchange={handleColumnChange}
620+
/>
508621
</div>
509-
510-
<Dropdown
511-
config={activeConfig}
512-
type="checkbox"
513-
options={columnOptions}
514-
placeholder="Select Columns"
515-
onchange={handleColumnChange}
516-
/>
517622
</div>
518623
<div class="table-wrapper">
519624
<table>
@@ -921,6 +1026,7 @@
9211026
display: flex;
9221027
justify-content: space-between;
9231028
align-items: center;
1029+
margin-bottom: 12px;
9241030
}
9251031
9261032
@media only screen and (max-width: 880px) {
@@ -1107,4 +1213,15 @@
11071213
margin-bottom: 0.5rem;
11081214
color: #4b5563;
11091215
}
1216+
1217+
.dropdown-controls {
1218+
display: flex;
1219+
align-items: center;
1220+
margin-left: auto;
1221+
gap: 8px;
1222+
}
1223+
1224+
.switch-wrapper {
1225+
margin-left: 8px;
1226+
}
11101227
</style>

0 commit comments

Comments
 (0)