Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@
// Utils
import { formatDate } from "@requestnetwork/shared-utils/formatDate";
import { calculateItemTotal } from "@requestnetwork/shared-utils/invoiceTotals";
import { exportToPDF } from "@requestnetwork/shared-utils/generateInvoice";
import { getCurrencyFromManager } from "@requestnetwork/shared-utils/getCurrency";
Comment on lines +23 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

LGTM! Consider consolidating utility imports.

The refactoring to use shared utilities for exportToPDF and getCurrencyFromManager is a good practice for improving code reusability.

Consider moving walletClientToSigner to the shared utils as well if it's used in multiple places across the project. This would further improve consistency and maintainability.

Also applies to: 30-30

// Types
import type { WalletState } from "@requestnetwork/shared-types/web3Onboard";
import { onMount } from "svelte";
import { formatUnits } from "viem";
import { exportToPDF, walletClientToSigner } from "../../utils";
import { getCurrencyFromManager } from "../../utils/getCurrency";
import { walletClientToSigner } from "../../utils";
export let config;
export let wallet: WalletState | undefined;
Expand Down
10 changes: 6 additions & 4 deletions packages/invoice-dashboard/src/lib/view-requests.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@
// Utils
import { config as defaultConfig } from "@requestnetwork/shared-utils/config";
import { initializeCurrencyManager } from "@requestnetwork/shared-utils/initCurrencyManager";
import { exportToPDF } from "@requestnetwork/shared-utils/generateInvoice";
import { getCurrencyFromManager } from "@requestnetwork/shared-utils/getCurrency";

import { CurrencyManager } from "@requestnetwork/currency";
import type { RequestNetwork } from "@requestnetwork/request-client.js";
import { Types } from "@requestnetwork/request-client.js";
import { onMount } from "svelte";
import { formatUnits } from "viem";
import { capitalize, debounce, exportToPDF, formatAddress } from "../utils";
import { getCurrencyFromManager } from "../utils/getCurrency";
import { capitalize, debounce, formatAddress } from "../utils";

import { Drawer, InvoiceView } from "./dashboard";

export let config: IConfig;
Expand Down Expand Up @@ -515,8 +517,8 @@
<span
>{formatAddress(
currentTab === "Pay"
? (request.payee?.value ?? "")
: (request.payer?.value ?? "")
? request.payee?.value ?? ""
: request.payer?.value ?? ""
)}</span
>
<Copy
Expand Down
1 change: 0 additions & 1 deletion packages/invoice-dashboard/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export { debounce } from "./debounce";
export { formatAddress } from "./formatAddress";
export { exportToPDF } from "./generateInvoice";
export { publicClientToProvider, walletClientToSigner } from "./wallet-utils";
export { capitalize } from "./capitalize";
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
<script lang="ts">
import { fade } from "svelte/transition";
import { exportToPDF } from "@requestnetwork/shared-utils/generateInvoice";
import { getCurrencyFromManager } from "@requestnetwork/shared-utils/getCurrency";
import { initializeCurrencyManager } from "@requestnetwork/shared-utils/initCurrencyManager";
export let createdRequest: any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use specific TypeScript types instead of 'any' for 'createdRequest'

Using any negates the benefits of TypeScript's type safety. It's recommended to define a specific interface for createdRequest to enhance maintainability and catch potential type-related errors during development.

Consider defining an interface for createdRequest:

interface CreatedRequest {
  inMemoryInfo?: {
    requestData?: RequestDataType; // Define RequestDataType appropriately
  };
  requestId: string;
  // ... other properties
}

export let createdRequest: CreatedRequest;

export let enablePdfReceipt: boolean = true;
export let enableRequestScanLink: boolean = true;
export let sellerLogo: string | undefined;
async function handleDownloadReceipt() {
if (createdRequest) {
try {
const currencyManager = initializeCurrencyManager([]);
const currencyData = createdRequest?.inMemoryInfo?.requestData;
await exportToPDF(
currencyData,
getCurrencyFromManager(currencyData.currencyInfo, currencyManager),
sellerLogo!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle possible undefined 'currencyData' to prevent runtime errors

currencyData is assigned using optional chaining and might be undefined if createdRequest.inMemoryInfo.requestData doesn't exist. However, currencyData is used without checking for undefined, specifically when accessing currencyData.currencyInfo. This could lead to runtime errors.

Consider adding a check to ensure currencyData is defined before using it:

async function handleDownloadReceipt() {
  if (createdRequest) {
    try {
      const currencyManager = initializeCurrencyManager([]);

-     const currencyData = createdRequest?.inMemoryInfo?.requestData;

+     const currencyData = createdRequest?.inMemoryInfo?.requestData;
+     if (!currencyData) {
+       throw new Error("Currency data is unavailable.");
+     }

      await exportToPDF(
        currencyData,
        getCurrencyFromManager(currencyData.currencyInfo, currencyManager),
        sellerLogo!
      );
    } catch (error) {
      console.error("Error downloading receipt:", error);
    }
  }
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const currencyData = createdRequest?.inMemoryInfo?.requestData;
await exportToPDF(
currencyData,
getCurrencyFromManager(currencyData.currencyInfo, currencyManager),
sellerLogo!
const currencyData = createdRequest?.inMemoryInfo?.requestData;
if (!currencyData) {
throw new Error("Currency data is unavailable.");
}
await exportToPDF(
currencyData,
getCurrencyFromManager(currencyData.currencyInfo, currencyManager),
sellerLogo!

);
} catch (error) {
console.error("Error downloading receipt:", error);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need a sonner or alert when this error occurs so the user is informed.

}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Provide user feedback when receipt download fails

Currently, if an error occurs during the receipt download, it is only logged to the console. This might leave users unaware of the failure.

Consider displaying an error message to the user:

  } catch (error) {
    console.error("Error downloading receipt:", error);
+   alert("Failed to download receipt. Please try again later.");
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (error) {
console.error("Error downloading receipt:", error);
}
}
} catch (error) {
console.error("Error downloading receipt:", error);
alert("Failed to download receipt. Please try again later.");
}
}

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle possible undefined values to prevent runtime errors

In handleDownloadReceipt(), the sellerLogo variable is asserted as non-null with sellerLogo!, but it can be undefined based on its type. This could lead to runtime errors if sellerLogo is not provided.

Consider providing a default value or checking if sellerLogo is defined before using it:

+ const logo = sellerLogo ?? '';
+ await exportToPDF(
    currencyData,
    getCurrencyFromManager(currencyData.currencyInfo, currencyManager),
-   sellerLogo!
+   logo
  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function handleDownloadReceipt() {
if (createdRequest) {
try {
const currencyManager = initializeCurrencyManager([]);
const currencyData = createdRequest?.inMemoryInfo?.requestData;
await exportToPDF(
currencyData,
getCurrencyFromManager(currencyData.currencyInfo, currencyManager),
sellerLogo!
);
} catch (error) {
console.error("Error downloading receipt:", error);
}
}
}
async function handleDownloadReceipt() {
if (createdRequest) {
try {
const currencyManager = initializeCurrencyManager([]);
const currencyData = createdRequest?.inMemoryInfo?.requestData;
const logo = sellerLogo ?? '';
await exportToPDF(
currencyData,
getCurrencyFromManager(currencyData.currencyInfo, currencyManager),
logo
);
} catch (error) {
console.error("Error downloading receipt:", error);
}
}
}

</script>

<div class="payment-complete" transition:fade={{ duration: 300 }}>
Expand All @@ -19,9 +45,24 @@
</div>
<h2>Payment Complete</h2>
<p>Thank you for your payment. Your transaction was successful.</p>

{#if enablePdfReceipt || (enableRequestScanLink && createdRequest)}
<div class="buttons-container">
{#if enablePdfReceipt}
<button on:click={handleDownloadReceipt}>Download Receipt</button>
{/if}
{#if enableRequestScanLink && createdRequest}
<a
href={`https://scan.request.network/request/${createdRequest.requestId}`}
>
View on Request Scan
</a>
{/if}
</div>
{/if}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure createdRequest.requestId is defined before using it in URL

In the link to Request Scan, you're using createdRequest.requestId without checking if requestId exists. If requestId is undefined, the URL will be incorrect.

Consider adding a check or fallback:

{#if enableRequestScanLink && createdRequest}
  <a
-   href={`https://scan.request.network/request/${createdRequest.requestId}`}
+   href={`https://scan.request.network/request/${createdRequest.requestId ?? ''}`}
  >
    View on Request Scan
  </a>
{/if}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{#if enablePdfReceipt || (enableRequestScanLink && createdRequest)}
<div class="buttons-container">
{#if enablePdfReceipt}
<button on:click={handleDownloadReceipt}>Download Receipt</button>
{/if}
{#if enableRequestScanLink && createdRequest}
<a
href={`https://scan.request.network/request/${createdRequest.requestId}`}
>
View on Request Scan
</a>
{/if}
</div>
{/if}
{#if enablePdfReceipt || (enableRequestScanLink && createdRequest)}
<div class="buttons-container">
{#if enablePdfReceipt}
<button on:click={handleDownloadReceipt}>Download Receipt</button>
{/if}
{#if enableRequestScanLink && createdRequest}
<a
href={`https://scan.request.network/request/${createdRequest.requestId ?? ''}`}
>
View on Request Scan
</a>
{/if}
</div>
{/if}

</div>

<style>
<style lang="scss">
.payment-complete {
display: flex;
flex-direction: column;
Expand All @@ -41,6 +82,42 @@
border-radius: 50%;
}
.buttons-container {
display: flex;
gap: 16px;
margin-top: 24px;
button,
a {
padding: 10px 20px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
text-decoration: none;
transition: background-color 0.3s ease;
}
button {
background-color: #0bb489;
color: white;
border: none;
cursor: pointer;
&:hover {
background-color: darken(#0bb489, 10%);
}
}
a {
background-color: #f5f5f5;
color: #333;
&:hover {
background-color: darken(#f5f5f5, 10%);
}
}
}
Comment on lines +96 to +130
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize CSS by reducing redundancy in button styles

The styles for button and a elements share common properties. To improve maintainability, consider combining shared styles.

Apply this diff to merge common styles:

.buttons-container {
  display: flex;
  gap: 16px;
  margin-top: 24px;

- button,
- a {
-   padding: 10px 20px;
-   border-radius: 6px;
-   font-size: 14px;
-   font-weight: 500;
-   text-decoration: none;
-   transition: background-color 0.3s ease;
- }
+ .button-style {
+   padding: 10px 20px;
+   border-radius: 6px;
+   font-size: 14px;
+   font-weight: 500;
+   text-decoration: none;
+   transition: background-color 0.3s ease;
+ }
+
+ button,
+ a {
+   @extend .button-style;
+ }

  button {
    background-color: #0bb489;
    color: white;
    border: none;
    cursor: pointer;

    &:hover {
      background-color: darken(#0bb489, 10%);
    }
  }

  a {
    background-color: #f5f5f5;
    color: #333;

    &:hover {
      background-color: darken(#f5f5f5, 10%);
    }
  }
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.buttons-container {
display: flex;
gap: 16px;
margin-top: 24px;
button,
a {
padding: 10px 20px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
text-decoration: none;
transition: background-color 0.3s ease;
}
button {
background-color: #0bb489;
color: white;
border: none;
cursor: pointer;
&:hover {
background-color: darken(#0bb489, 10%);
}
}
a {
background-color: #f5f5f5;
color: #333;
&:hover {
background-color: darken(#f5f5f5, 10%);
}
}
}
.buttons-container {
display: flex;
gap: 16px;
margin-top: 24px;
.button-style {
padding: 10px 20px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
text-decoration: none;
transition: background-color 0.3s ease;
}
button,
a {
@extend .button-style;
}
button {
background-color: #0bb489;
color: white;
border: none;
cursor: pointer;
&:hover {
background-color: darken(#0bb489, 10%);
}
}
a {
background-color: #f5f5f5;
color: #333;
&:hover {
background-color: darken(#f5f5f5, 10%);
}
}
}

h2 {
margin-top: 1rem;
font-size: 1.5rem;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
export let invoiceNumber: string | undefined;
export let feeAddress: string;
export let feeAmountInUSD: number;
export let createdRequest: any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using a more specific type for createdRequest.

The use of any type for createdRequest bypasses TypeScript's type checking, which may lead to potential runtime errors. Consider using a more specific type that accurately represents the structure of the request object. Additionally, it would be helpful to add a comment explaining the purpose and usage of this variable within the component.

Here's a suggested improvement:

// Assuming there's a Request type defined elsewhere
import type { Request } from '../types';

// The created request object after successful payment processing
export let createdRequest: Request | null = null;

This change provides type safety and sets a default value, making the code more robust and self-documenting.

const COUNTDOWN_INTERVAL = 30;
Expand Down Expand Up @@ -275,6 +276,8 @@
persistRequest,
});

createdRequest = request;

Comment on lines +279 to +280
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

LGTM! Consider adding type assertion for createdRequest.

The assignment of the request to createdRequest is correctly placed within the try block, ensuring it only occurs on successful payment processing. This change aligns well with the PR objectives of enhancing the payment widget functionality.

To further improve type safety and error handling, consider adding a type assertion and a null check:

if (request) {
  createdRequest = request as Request; // Assuming Request is the correct type
} else {
  console.warn('Request object is null or undefined after payment processing');
}

This change ensures that createdRequest is only assigned when request is not null or undefined, and it provides a type assertion for better type checking.

if (onPaymentSuccess) {
onPaymentSuccess(request);
}
Expand Down
12 changes: 10 additions & 2 deletions packages/payment-widget/src/lib/payment-widget.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
export let invoiceNumber: string | undefined = undefined;
export let feeAddress: string = ethers.constants.AddressZero;
export let feeAmountInUSD: number = 0;
export let enablePdfReceipt: boolean = true;
export let enableRequestScanLink: boolean = true;
Comment on lines +42 to +43
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

LGTM! Consider adding JSDoc comments.

The new exported properties enablePdfReceipt and enableRequestScanLink align well with the PR objectives for enhancing receipt management options. The default value of true ensures backward compatibility.

Consider adding JSDoc comments to describe these new properties and their purpose. For example:

/** Enable the option to generate and download a PDF receipt. Default is true. */
export let enablePdfReceipt: boolean = true;

/** Enable the option to view the request on RequestScan. Default is true. */
export let enableRequestScanLink: boolean = true;

// State
let web3Modal: Web3Modal | null = null;
Expand All @@ -47,7 +49,7 @@
let selectedCurrency: Currency | null = null;
let connectionCheckInterval: ReturnType<typeof setInterval> | null = null;
let currentPaymentStep: PaymentStep = "currency";
let createdRequest: any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

LGTM! Consider adding a type annotation.

The new createdRequest variable is appropriately placed and its purpose can be inferred from the context. It's correctly bound to the PaymentConfirmation component where it's likely to be updated.

Consider adding a type annotation to createdRequest for better type safety and code readability. For example:

let createdRequest: PaymentRequest | null = null;

Replace PaymentRequest with the actual type of the created request object.

let scrollPosition = 0;
// Effects
Expand Down Expand Up @@ -246,12 +248,18 @@
onPaymentError={onError}
bind:currentPaymentStep
bind:isConnected
bind:createdRequest
{sellerInfo}
buyerInfo={currentBuyerInfo}
{invoiceNumber}
/>
{:else}
<PaymentComplete />
<PaymentComplete
{createdRequest}
{enablePdfReceipt}
{enableRequestScanLink}
sellerLogo={sellerInfo.logo}
/>
{/if}
</Modal>
</section>
Expand Down
2 changes: 2 additions & 0 deletions packages/payment-widget/src/lib/react/PaymentWidget.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export interface PaymentWidgetProps {
invoiceNumber?: string;
feeAddress?: string;
feeAmountInUSD?: number;
enablePdfReceipt?: boolean;
enableRequestScanLink?: boolean;
Comment on lines +26 to +27
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

LGTM! Consider updating documentation.

The new properties enablePdfReceipt and enableRequestScanLink are well-aligned with the PR objectives and the linked issue #113. They provide the necessary configuration options for the new receipt export and viewing functionality.

Consider updating the JSDoc comment for the PaymentWidget component to include these new properties in the example usage. This will help developers understand how to use these new features. Here's a suggested addition to the example:

 * @example
 * <PaymentWidget
 *   // ... other props ...
 *   enablePdfReceipt={true}
 *   enableRequestScanLink={true}
 * />

}

/**
Expand Down
10 changes: 9 additions & 1 deletion packages/payment-widget/src/lib/utils/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ export const handleRequestPayment = async ({
skipPersistence: true,
});

const inMemoryRequest =
let inMemoryRequest =
await inMemoryRequestNetwork.createRequest(requestParameters);

const signer = await ethersProvider!.getSigner();
Expand Down Expand Up @@ -328,6 +328,14 @@ export const handleRequestPayment = async ({
await persistingRequestNetwork.persistRequest(inMemoryRequest);
}

if (inMemoryRequest?.inMemoryInfo?.requestData) {
inMemoryRequest.inMemoryInfo.requestData = {
...inMemoryRequest.inMemoryInfo.requestData,
payer: requestParameters.requestInfo.payer,
payee: requestParameters.requestInfo.payee,
};
}

return inMemoryRequest;
};

Expand Down
Loading