Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add direct payment page. #23

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

aimensahnoun
Copy link
Member

@aimensahnoun aimensahnoun commented Feb 27, 2025

Resolves #15

Changes:

  • Created a new /direct-payment route with a dedicated page and UI component
  • Fixed gradient backgrounds by replacing Tailwind class string interpolation with explicit hex values
  • Added validator dependency for Ethereum address validation
  • Bumped version from 0.2.0 to 0.3.0 in package.json

Example Video:

CleanShot.2025-02-27.at.16.46.02.mp4

Summary by CodeRabbit

  • New Features

    • Launched a new Direct Payment page featuring cryptocurrency wallet integration, a guided payment workflow, and responsive feedback throughout the process.
    • Enhanced the payment form with dynamic updates and improved Ethereum address validation.
    • Expanded navigation options by adding a new link to access the Direct Payment section.
  • Refactor

    • Upgraded background gradient styling using dynamic color mapping for a more consistent visual experience.
  • Chores

    • Updated the application version and managed dependency enhancements to improve overall stability.

Copy link

coderabbitai bot commented Feb 27, 2025

Walkthrough

The changes update the project version and add a new dependency for Ethereum address validation. A new direct payment page and corresponding React component have been introduced to enable cryptocurrency payments with a two-step wallet connection and payment submission process. The background wrapper now includes a dynamic gradient color mapping function. Additionally, existing validation schemas have been improved and extended with a new payment schema, and the server routing has been expanded with a protected payment router that integrates with an external API for processing payments.

Changes

Files Change Summary
package.json Updated version (0.2.2 → 0.3.0); added validator dependency and corresponding type definitions in devDependencies.
src/app/direct-payment/page.tsx Introduced a new direct payment page that exports metadata and retrieves the user session, redirecting if not logged in.
src/components/direct-payment.tsx Added a new component for facilitating direct cryptocurrency payments, managing wallet connection and payment submission with validation.
src/components/header.tsx Simplified function signature and added a new conditional navigation link to the direct payment page.
src/components/background-wrapper.tsx Introduced a getTailwindColor function for dynamic gradient color handling and modified gradient styles to use inline CSS.
src/lib/schemas/invoice.ts Updated validation for walletAddress to use isEthereumAddress for more specific checks.
src/lib/schemas/payment.ts Introduced a new payment form schema with validations for payee, amount, and currencies, along with a corresponding TypeScript type (PaymentFormValues).
src/server/index.ts Integrated a new paymentRouter into the main app router.
src/server/routers/payment.ts Added a protected pay procedure that validates input using the payment form schema and processes payments via an external API.

Suggested reviewers

  • MantisClone
  • sstefdev
✨ Finishing Touches
  • 📝 Generate Docstrings

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (8)
src/server/routers/payment.ts (1)

6-35: Well-structured router implementation with room for improvement.

The implementation follows tRPC patterns well with protected routes, input validation, and error handling. A few suggestions to enhance this implementation:

  1. The authentication check at lines 12-17 may be redundant since protectedProcedure should already ensure the user is authenticated.
  2. Consider enhancing the error message at line 29 to include more details from the response for better debugging.
  3. Add logging for successful/failed payment attempts to aid in monitoring and troubleshooting.
export const paymentRouter = router({
  pay: protectedProcedure
    .input(paymentFormSchema)
    .mutation(async ({ ctx, input }) => {
      const { user } = ctx;

-      if (!user) {
-        throw new TRPCError({
-          code: "UNAUTHORIZED",
-          message: "You must be logged in to pay",
-        });
-      }
+      // Authentication is already handled by protectedProcedure

      const response = await apiClient.post("v1/pay", {
        amount: input.amount.toString(),
        payee: input.payee,
        invoiceCurrency: input.invoiceCurrency,
        paymentCurrency: input.paymentCurrency,
      });

      if (response.status !== 201) {
        throw new TRPCError({
          code: "INTERNAL_SERVER_ERROR",
-          message: "Failed to pay",
+          message: `Failed to pay: ${response.status} ${response.statusText}`,
        });
      }

+      // Log successful payment
+      console.log(`Payment successful: ${input.amount} ${input.invoiceCurrency} to ${input.payee}`);

      return response.data;
    }),
});
src/lib/schemas/payment.ts (1)

1-17: Well-structured schema with appropriate validations.

The payment form schema properly validates all fields with meaningful error messages. Good use of the validator library for Ethereum address validation.

Consider enhancing the validation for paymentCurrency to ensure it's a valid currency code or from a predefined list, similar to how invoiceCurrency is validated:

-  paymentCurrency: z.string().min(1, "Payment currency is required"),
+  paymentCurrency: z.string().min(1, "Payment currency is required")
+    .refine(
+      (value) => ["ETH", "DAI", /* add other supported currencies */].includes(value),
+      "Invalid payment currency"
+    ),
src/components/background-wrapper.tsx (1)

26-49: Good implementation of color mapping function.

The getTailwindColor function provides a clean way to convert Tailwind color names to hex values, addressing the PR objective of replacing string interpolation with explicit values.

Consider extracting this color mapping to a separate utility file to avoid duplication if other components need the same functionality:

+ // src/lib/utils/tailwind-colors.ts
+ export const tailwindColors: Record<string, string> = {
+   // Orange colors
+   "orange-100": "#ffedd5",
+   "orange-200": "#fed7aa",
+   // Blue colors
+   "blue-100": "#dbeafe",
+   "blue-200": "#bfdbfe",
+   // Indigo colors
+   "indigo-100": "#e0e7ff",
+   "indigo-200": "#c7d2fe",
+   // Zinc colors
+   "zinc-100": "#f4f4f5",
+   "zinc-200": "#e4e4e7",
+ };
+ 
+ export function getTailwindColor(colorName: string): string {
+   return tailwindColors[colorName] || tailwindColors["zinc-100"]; // Default to zinc-100
+ }

// Then in background-wrapper.tsx
+ import { getTailwindColor } from "@/lib/utils/tailwind-colors";

- // Convert Tailwind color names to CSS variables or hex values
- const getTailwindColor = (colorName: string): string => {
-   const colors: Record<string, string> = {
-     // Orange colors
-     "orange-100": "#ffedd5",
-     "orange-200": "#fed7aa",
-
-     // Blue colors
-     "blue-100": "#dbeafe",
-     "blue-200": "#bfdbfe",
-
-     // Indigo colors
-     "indigo-100": "#e0e7ff",
-     "indigo-200": "#c7d2fe",
-
-     // Zinc colors
-     "zinc-100": "#f4f4f5",
-     "zinc-200": "#e4e4e7",
-
-     // Add any other colors you need here
-   };
-
-   return colors[colorName] || "#f4f4f5"; // Default to zinc-100 if color not found
- };
src/components/direct-payment.tsx (4)

91-97: Clarify the step reset logic when the wallet disconnects.
Switching back to step 1 clears the form context if the user has partially filled the payment fields. Consider preserving filled data in local state if you anticipate partial disconnection followed by reconnection.


167-177: Revisit automatic form reset after three seconds.
Automatically resetting the form can be surprising if users want to confirm the transaction details again or reference the final data. Consider letting the user reset manually or providing a visual countdown.


378-389: Expand on the security notice for higher user confidence.
While this section mentions blockchain transparency, consider also reminding users to double-check the payee address, confirm gas fees, and stay attentive to the wallet’s prompts to avoid potential phishing attacks.


397-400: Validate displayed address substring to improve UX.
Showing only a short substring of the connected address can be confusing if users deal with multiple wallets or addresses. You might supplement this display with a tooltip or button to copy the full address.

src/app/direct-payment/page.tsx (1)

27-39: Consider adding loading state during authentication

While the authentication logic is sound, there's no loading state displayed while checking the user session.

Consider adding a loading state to improve user experience:

export default async function DirectPaymentPage() {
  const { user } = await getCurrentSession();

  // Redirect to home if not logged in
  if (!user) {
    redirect("/");
  }

  return (
    <BackgroundWrapper
      topGradient={{ from: "blue-100", to: "indigo-200" }}
      bottomGradient={{ from: "zinc-100", to: "zinc-200" }}
    >
      <Header user={user} />
      <main className="flex-grow flex flex-col max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 z-10">
+       {/* Page content */}
        <h1 className="mb-2 text-4xl font-bold tracking-tight">
          Direct Payment
        </h1>
        <p className="mb-8 text-lg text-muted-foreground">
          Send payments quickly without having to create a request first.
        </p>

        <DirectPayment />
      </main>
      <Footer />
    </BackgroundWrapper>
  );
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4fd6e71 and 25cdcbf.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (9)
  • package.json (3 hunks)
  • src/app/direct-payment/page.tsx (1 hunks)
  • src/components/background-wrapper.tsx (1 hunks)
  • src/components/direct-payment.tsx (1 hunks)
  • src/components/header.tsx (2 hunks)
  • src/lib/schemas/invoice.ts (2 hunks)
  • src/lib/schemas/payment.ts (1 hunks)
  • src/server/index.ts (1 hunks)
  • src/server/routers/payment.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/components/background-wrapper.tsx (1)
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#21
File: src/components/background-wrapper.tsx:31-31
Timestamp: 2025-02-20T10:26:33.809Z
Learning: Dynamic Tailwind class interpolation for gradient classes (from-${color} and to-${color}) in the BackgroundWrapper component is intentionally implemented this way and doesn't require a mapping object solution.
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build
🔇 Additional comments (16)
src/lib/schemas/invoice.ts (2)

2-2: LGTM! Validator import added.

The validator library is correctly imported for Ethereum address validation.


27-27: Improved wallet address validation.

The validation for wallet addresses has been enhanced to verify that input is a valid Ethereum address rather than just a non-empty string, which significantly improves input validation and prevents potential errors.

src/components/header.tsx (2)

9-9: Clean function signature.

Simplified the function signature while maintaining the same functionality.


38-43: Well-integrated navigation link.

The new Direct Payment link follows the same pattern and styling as other navigation items, maintaining UI consistency.

src/components/background-wrapper.tsx (2)

56-59: Clean implementation of gradient using explicit hex values.

Using the getTailwindColor function to convert Tailwind color names to hex values for the gradient is a good approach.


64-67: Consistent implementation for bottom gradient.

The bottom gradient follows the same pattern as the top gradient, ensuring consistency.

src/components/direct-payment.tsx (1)

120-147: Handle potential edge cases during transaction approval.
If metadata.approvalTransactionIndex is invalid or the user rejects the approval, the code simply falls back to the catch block. For a clearer UX, consider handling the rejection case explicitly and providing a more user-friendly error message or state update.

src/server/index.ts (2)

4-4: Clean integration of new payment router

The import of the payment router is well-structured and follows the existing pattern in the file.


11-11: Proper router integration

The payment router is correctly added to the appRouter configuration, maintaining consistency with the existing architecture.

package.json (3)

4-4: Version increment aligns with feature addition

The version bump from 0.2.2 to 0.3.0 appropriately follows semantic versioning for the addition of a new feature (direct payment page).


59-59: New dependency for Ethereum address validation

The validator package addition aligns with the PR objective of enhancing address validation functionality.


70-70:

✅ Verification successful

Type definitions for validator package

Good practice including TypeScript type definitions alongside the validator package. Note the slight version mismatch between the package (13.12.0) and types (13.12.2), but this is common and unlikely to cause issues.


🌐 Web query:

Are there any compatibility issues between validator 13.12.0 and @types/validator 13.12.2?

💡 Result:

Validator.js 13.12.0 and its corresponding TypeScript definitions (@types/validator 13.12.2) are designed to work together without compatibility issues when properly aligned in versions[1][7][10]. Here's the breakdown:

Version Alignment

  • validator.js 13.12.0 (released May 2024) introduced updates like isUUID v7 support and isDate timezone fixes[1][7].
  • @types/validator 13.12. follows semantic versioning where minor/patch updates (e.g., 13.12.2) only add type fixes without breaking changes[10].

Key Considerations

  1. Type Definition Coverage
    The TypeScript types explicitly track validator.js' API surface[10]. Version 13.x types include:

    • All validators/sanitizers from validator.js ≤13.x
    • Optional configuration parameters for methods like isEmail
    • Locale support typing
  2. Potential Mismatch Scenarios
    Issues could theoretically occur if:

    • A validator.js method was added/modified in 13.12. but not reflected in @types/validator (no evidence found in sources)
    • Using deprecated validators removed in validator.js but still present in types (not indicated for this version range)
  3. Installation Verification
    Always confirm:

    npm list validator @types/validator

    Should show:

    ├── [email protected].**0**
    └── @types/[email protected].**2**
    

The latest available data shows no reported compatibility issues between these specific versions[1][9][10]. For critical production use, test key validators like isUUID v7[1] and isDate with timezones[1][7] to confirm type coverage matches runtime behavior[10].

Citations:


Compatibility Confirmed for Validator Type Definitions

The slight version mismatch between [email protected] and @types/[email protected] has been verified via the latest compatibility analysis. The TypeScript definitions accurately track the API surface of validator.js, and the minor patch difference is common practice without any known issues.

src/app/direct-payment/page.tsx (4)

9-12: Well-defined metadata for SEO

The metadata is clearly defined with an appropriate title and descriptive text that accurately reflects the page's purpose.


14-21: Authentication protection properly implemented

The authentication check and redirect logic are correctly implemented to ensure only authenticated users can access the direct payment functionality.


23-26: Explicit gradient values match PR objective

The explicit gradient color mapping aligns with the PR objective of replacing Tailwind class string interpolation with explicit values.


28-37: Well-structured layout with clear component hierarchy

The page layout follows good design practices with appropriate spacing, typography hierarchy, and component organization. The DirectPayment component is correctly integrated into the page.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/components/direct-payment.tsx (4)

112-185: Consider breaking down the onSubmit function for better maintainability.

The current implementation is functional but quite lengthy. Consider extracting specific operations like approval handling, transaction execution, and success/error handling into separate helper functions.

-  const onSubmit = async (data: PaymentFormValues) => {
+  const handleApprovalTransaction = async (signer: ethers.providers.JsonRpcSigner, paymentData: any) => {
+    toast.info("Approval required", {
+      description: "Please approve the transaction in your wallet",
+    });
+
+    const approvalIndex = paymentData.metadata.approvalTransactionIndex;
+    const approvalTransaction = await signer.sendTransaction(
+      paymentData.transactions[approvalIndex],
+    );
+
+    await approvalTransaction.wait();
+    return true;
+  };
+
+  const handlePaymentTransaction = async (signer: ethers.providers.JsonRpcSigner, paymentData: any, isApprovalNeeded: boolean) => {
+    toast.info("Sending payment", {
+      description: "Please confirm the transaction in your wallet",
+    });
+
+    const paymentTransaction = await signer.sendTransaction(
+      paymentData.transactions[isApprovalNeeded ? 1 : 0],
+    );
+
+    await paymentTransaction.wait();
+  };
+
+  const resetFormAfterSuccess = (data: PaymentFormValues) => {
+    toast.success("Payment successful", {
+      description: `You've paid ${data.amount} ${formatCurrencyLabel(
+        data.invoiceCurrency,
+      )} to ${data.payee.substring(0, 6)}...${data.payee.substring(
+        data.payee.length - 4,
+      )}`,
+    });
+
+    setPaymentStatus("success");
+
+    // Reset form after successful payment
+    setTimeout(() => {
+      form.reset({
+        payee: "",
+        amount: 0,
+        invoiceCurrency: "USD",
+        paymentCurrency: "ETH-sepolia-sepolia",
+      });
+      setPaymentStatus("idle");
+    }, 3000);
+  };
+
+  const onSubmit = async (data: PaymentFormValues) => {

168-177: Extract the timeout duration as a constant.

The magic number 3000ms could be extracted to improve code readability and maintainability.

+const FORM_RESET_DELAY_MS = 3000;
+
 export function DirectPayment() {
   // Other code...

   // In the onSubmit function
-      }, 3000);
+      }, FORM_RESET_DELAY_MS);

397-400: Consider creating a reusable helper for address formatting.

The address truncation logic appears in multiple places (here and in the success toast). Creating a helper function would improve code maintainability.

+// Add to an appropriate utility file
+const formatAddress = (address: string | undefined): string => {
+  if (!address) return '';
+  return `${address.substring(0, 6)}...${address.substring(address.length - 4)}`;
+};

// Then use in the component
<span className="font-mono mr-2">
-  {address?.substring(0, 6)}...
-  {address?.substring(address?.length - 4)}
+  {formatAddress(address)}
</span>

// And in the success toast (line 158-163)
-  `You've paid ${data.amount} ${formatCurrencyLabel(
-    data.invoiceCurrency,
-  )} to ${data.payee.substring(0, 6)}...${data.payee.substring(
-    data.payee.length - 4,
-  )}`,
+  `You've paid ${data.amount} ${formatCurrencyLabel(data.invoiceCurrency)} to ${formatAddress(data.payee)}`,

177-185: Improve error handling with more specific error messages.

The current error handling doesn't distinguish between different types of errors. Consider adding more granular error handling to provide better feedback to users.

 } catch (error) {
   console.error("Payment error:", error);
+  
+  // More specific error messages based on error type
+  let errorMessage = "There was an error processing your payment. Please try again.";
+  
+  if (error instanceof Error) {
+    if (error.message.includes("user rejected")) {
+      errorMessage = "Transaction was rejected. Please try again when ready.";
+    } else if (error.message.includes("insufficient funds")) {
+      errorMessage = "Insufficient funds in your wallet to complete this transaction.";
+    } else if (error.message.includes("network")) {
+      errorMessage = "Network error. Please check your connection and try again.";
+    }
+  }
+  
   toast.error("Payment failed", {
-    description:
-      "There was an error processing your payment. Please try again.",
+    description: errorMessage,
   });
   setPaymentStatus("error");
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25cdcbf and ceb1da1.

📒 Files selected for processing (1)
  • src/components/direct-payment.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/components/direct-payment.tsx (1)
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#23
File: src/components/direct-payment.tsx:63-89
Timestamp: 2025-02-27T13:58:33.590Z
Learning: The AppKit wallet module requires initialization time before it's ready for use. A 2-second delay is intentionally used in the DirectPayment component to ensure the wallet is fully initialized before allowing interactions.
🔇 Additional comments (2)
src/components/direct-payment.tsx (2)

63-89: The AppKit initialization approach is appropriate.

The 2-second delay is intentionally used to ensure the wallet module is fully initialized before allowing interactions, as noted in the retrieved learnings.


403-429: Button state handling is correctly implemented.

The submit button is appropriately disabled during processing to prevent multiple submissions, and the UI provides clear feedback on the current payment status.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

EasyInvoice - Direct Payment Page
1 participant