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: integrate Request API with Easy Invoice #2

Merged

Conversation

aimensahnoun
Copy link
Member

@aimensahnoun aimensahnoun commented Feb 10, 2025

Resolves #1

Added EasyInvoice payment platform with Request API :

  • Implemented secure payment processing using Request API
  • Added wallet connection via AppKit integration (supports Sepolia)
  • Created invoice management system with currency conversion support
  • Added invoice preview and payment section components
  • Implemented toast notifications for payment status
  • Added database schema for invoices with new fields
  • Created API routes for invoice CRUD operations
  • Added webhook handling for payment processing
  • Updated UI components (InvoiceTable, PaymentSection, UserMenu)
  • Added support for multiple currencies (invoice/payment currency pairs)
  • Implemented CSRF protection for routes
  • Updated Docker and dependencies configurations
Easy.Invoice_.Streamlining.Payments.with.Tella.mp4

Summary by CodeRabbit

  • New Features

    • Introduced new environment variables for API configuration.
    • Added a new LICENSE file specifying MIT License terms.
    • Enhanced the README with detailed application description, features, and setup instructions.
    • Implemented a new payment webhook for processing incoming payment notifications.
    • Added a new InvoiceTable component for displaying invoices with pagination.
    • Introduced a PaymentSection component for managing invoice payments.
    • Updated the DashboardPage to dynamically fetch invoice data.
    • Created a new PaymentPage component for processing invoice payments.
    • Enhanced currency handling in the InvoiceForm component.
    • Introduced a new Toaster component for notifications.
    • Updated the RootLayout component with new metadata and layout structure.
    • Added a new AppKit component for application initialization.
    • Enhanced the handling of invoice and payment currencies.
  • Bug Fixes

    • Improved error handling in the invoice creation process.
  • Documentation

    • Updated README with new sections for features, getting started, and tech stack.
  • Chores

    • Streamlined package scripts and updated dependencies for improved performance.

@aimensahnoun aimensahnoun force-pushed the 1-request-api---integrate-request-api-into-easyinvoice branch from 098755c to 1256700 Compare February 11, 2025 10:54
@aimensahnoun aimensahnoun force-pushed the 1-request-api---integrate-request-api-into-easyinvoice branch from 09855ca to 4417d1c Compare February 11, 2025 13:13
@aimensahnoun aimensahnoun marked this pull request as ready for review February 11, 2025 20:06
@MantisClone
Copy link
Member

@coderabbitai full review

Copy link

coderabbitai bot commented Feb 12, 2025

Walkthrough

This pull request introduces extensive updates across environment configuration, documentation, backend, and frontend components. New environment variables have been added, and the MIT license file included. The README has been completely overhauled with an updated project title, new sections, and revised installation instructions. Configuration files such as Docker Compose and Drizzle are modified, and the package scripts and dependencies updated. The invoice system is enhanced with expanded database schema fields, enriched API routes for creating and paying invoices (including webhook validation), and reworked UI components for invoice management and payment processing.

Changes

File(s) Change Summary
.env.example, LICENSE, README.md Added new environment variables; introduced a new MIT LICENSE file; completely overhauled README with updated project title, features, getting started, tech stack, documentation, contributing guidelines, and branding changes.
dev/docker-compose.yml, drizzle.config.ts Updated the PostgreSQL image to postgres:16-alpine and modified the tablesFilter from ["invoicepro_*"] to ["easyinvoice_*"].
package.json Simplified scripts by removing the dotenv prefix, added new scripts for database operations, new dependencies (@reown/appkit, axios, date-fns, next-themes, sonner), and updated version specifiers for existing packages.
src/app/api/trpc/[trpc]/route.ts, src/app/api/webhook/payment/route.ts Changed TRPC import to a type-only import; added a new asynchronous POST function for payment webhooks with HMAC signature validation and database update.
src/app/dashboard/page.tsx, src/app/invoices/[ID]/page.tsx, src/app/invoices/create/page.tsx, src/app/page.tsx, src/app/layout.tsx Updated dashboard and invoice pages to use dynamic data, introduced metadata exports, replaced static content with API calls and new components (InvoiceTable), and integrated AppKit/Toaster into the layout with updated branding.
src/components/{app-kit, invoice-creator, invoice-form, invoice-preview, invoice-table, payment-section, ui/sonner, user-menu}.tsx Added new components (AppKit, InvoiceTable, PaymentSection, Toaster); modified invoice creator and form components to update default values and field names; streamlined type-only imports and UI presentation.
src/lib/{axios, currencies, schemas/invoice}.ts Introduced a centralized API client using Axios; added new constants, types, and helper functions for handling invoice and payment currencies; updated the invoice schema to include invoiceCurrency and paymentCurrency while removing clientWallet.
src/{middleware.ts, server/db/schema.ts, server/routers/invoice.ts} Modified CSRF middleware to skip webhook endpoints; expanded the database schema with new invoice fields and defined relationships; enhanced the invoice router by adding methods for creating, retrieving, and processing payments.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant WebhookAPI
    participant HMACUtil
    participant Database

    Client->>WebhookAPI: POST /api/webhook/payment (payload + signature header)
    WebhookAPI->>HMACUtil: Compute expected signature using secret
    HMACUtil-->>WebhookAPI: Expected signature
    alt Signature mismatch
        WebhookAPI-->>Client: 401 Invalid signature
    else Signature match
        WebhookAPI->>Database: Update invoice status to "paid" (using paymentReference)
        Database-->>WebhookAPI: Confirmation of update
        WebhookAPI-->>Client: 200 Success response
    end
Loading
sequenceDiagram
    participant User
    participant Frontend
    participant InvoiceRouter
    participant PaymentAPI
    participant Database

    User->>Frontend: Submit invoice creation form
    Frontend->>InvoiceRouter: API request to create invoice
    InvoiceRouter->>InvoiceRouter: Calculate totals & process invoice items
    InvoiceRouter->>PaymentAPI: Create payment request
    PaymentAPI-->>InvoiceRouter: Return payment details
    InvoiceRouter->>Database: Insert invoice record with payment reference
    Database-->>InvoiceRouter: Record confirmation
    InvoiceRouter-->>Frontend: Return success response with invoice details
    Frontend-->>User: Display newly created invoice information
Loading

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. (Beta)
  • @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: 11

🧹 Nitpick comments (29)
src/components/invoice-creator.tsx (2)

19-35: Consider cleanup for delayed navigation.
Relying on setTimeout to redirect after 3 seconds is functional but could trigger a call to router.push when the component unmounts or if the user navigates away beforehand, potentially causing a memory leak or warning. A cleanup method in a React effect would help avoid this.


53-62: Enhance error handling.
The try/catch block is good for surfacing errors via toasts. Consider logging to a monitoring service or providing more user-friendly suggestions if the error is known or common.

src/app/dashboard/page.tsx (2)

19-20: Possible performance consideration.
Fetching all invoices at once may degrade performance for larger datasets. Consider server-side pagination or limiting the query if the dataset can grow significantly.


77-83: Dynamic invoice table usage.
Passing invoice data to InvoiceTable aligns well with the new server-side fetch. Ensure large data sets are handled gracefully.

src/server/routers/invoice.ts (1)

59-80: Optimize aggregation for large volumes.
Summing amounts and filtering invoices in-memory is okay for small datasets. If the user has many invoices, consider a database-level aggregation for better performance.

src/components/payment-section.tsx (4)

19-21: Clarify the interface naming.
The Request type name can be ambiguous, given that “request” might also refer to network requests. Consider renaming the interface or adding more context to avoid confusion.


47-55: Use an event-driven approach for AppKit readiness.
Relying on a fixed 2-second timeout to mark isAppKitReady as true can be brittle if initialization takes longer or finishes quicker. Consider triggering this state via a callback/event from the AppKit library to ensure accuracy.

 useEffect(() => {
-  const timer = setTimeout(() => {
-    setIsAppKitReady(true);
-  }, 2000);
-  return () => clearTimeout(timer);
+  // Example of an event-driven approach:
+  someAppKit.on("ready", () => {
+    setIsAppKitReady(true);
+  });
 }, []);

66-122: Enhance error handling & chain checks.
While user rejection or chain mismatch can trigger the generic “Payment Failed” toast, more granular error handling can improve UX. Consider verifying chain ID and capturing user rejection distinctly, then provide precise error messages.

 try {
   const paymentData = await payRequest(invoice.paymentReference).then(
     ...
   );

+  // Example chain check (for Ethereum mainnet or a specific testnet)
+  const { chainId } = await ethersProvider.getNetwork();
+  if (chainId !== expectedChainId) {
+    toast("Wrong Network", {
+      description: `Please switch to the appropriate network.`,
+    });
+    return;
+  }

   // ... rest of payment logic
 } catch (error) {
   ...
 }

124-308: Handle intermediate payment status in the UI steps.
Currently, the invoice.status property can be processing or pending, but there are no transitions to move the UI from these states to a final outcome if something goes wrong. Consider updating the UI steps or fallback logic to reflect partial or stalled payments, especially when an approval transaction succeeds but the payment transaction is pending.

src/lib/axios.ts (1)

3-9: Guard against missing or undefined environment variables.
If REQUEST_API_URL or REQUEST_API_KEY is not set, axios will silently fail with incorrect or undefined values. Consider adding a robust fallback, a check, or an error message to prevent accidental misconfigurations.

export const apiClient = axios.create({
-  baseURL: process.env.REQUEST_API_URL,
-  headers: {
-    "x-api-key": process.env.REQUEST_API_KEY,
-  },
+  baseURL: process.env.REQUEST_API_URL ?? "",
+  headers: {
+    "x-api-key": process.env.REQUEST_API_KEY ?? "",
+  },
});

if (!process.env.REQUEST_API_URL || !process.env.REQUEST_API_KEY) {
  console.warn("Warning: REQUEST_API_URL or REQUEST_API_KEY is not set.");
}
drizzle.config.ts (1)

9-9: Verify potential data migration impact.
Switching the table prefix from "invoicepro_*" to "easyinvoice_*" can disrupt existing tables if used in production. Ensure there’s a clear migration or rename strategy in place to preserve historical data.

❌ Verification inconclusive

🏁 Script executed:

#!/bin/bash
# Attempt to detect old table names and new table names in your DB schema or migrations
# searching for relevant references:
rg "invoicepro_|easyinvoice_"

Length of output: 183


Action: Revisit Data Migration Strategy for Table Prefix Change

The current codebase shows that both the schema definition (in src/server/db/schema.ts) and the configuration (in drizzle.config.ts) exclusively reference the new prefix "easyinvoice_*". No occurrences of the old "invoicepro_*" prefix were found.

  • The DB table creation function now uses easyinvoice_${name} as the table name.
  • The configuration filters also specify "easyinvoice_*".

However, there’s no visible migration or renaming logic to handle legacy tables that might still use the "invoicepro_*" prefix. If production still has tables with the old prefix, this change could indeed cause a disruption in data continuity.

Please verify on your end whether:

  • Your production environment has existing "invoicepro_*" tables.
  • A dedicated migration strategy (e.g., renaming tables or synchronizing legacy data) is planned and implemented.
src/components/ui/sonner.tsx (1)

13-14: Improve type safety for theme prop.

The theme type casting could be handled more safely.

- theme={theme as ToasterProps["theme"]}
+ theme={(theme === "system" ? theme : theme || "system") as ToasterProps["theme"]}
src/lib/schemas/invoice.ts (2)

9-17: Enhance validation for invoice items.

The items validation could be improved:

  1. Add maximum limit to prevent excessive items
  2. Add maximum price validation
  3. Add total validation
items: z
  .array(
    z.object({
      description: z.string().min(1, "Description is required"),
      quantity: z.number().min(1, "Quantity must be at least 1"),
-     price: z.number().min(0, "Price must be positive"),
+     price: z.number()
+       .min(0, "Price must be positive")
+       .max(1000000, "Price cannot exceed 1,000,000"),
    }),
  )
  .min(1, "At least one item is required")
+ .max(10, "Cannot exceed 10 items")
+ .refine(
+   (items) => {
+     const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
+     return total <= 1000000;
+   },
+   { message: "Total invoice amount cannot exceed 1,000,000" }
+ ),

18-18: Add length validation for notes field.

The optional notes field should have a maximum length constraint.

- notes: z.string().optional(),
+ notes: z.string().max(1000, "Notes cannot exceed 1000 characters").optional(),
src/app/layout.tsx (1)

9-18: Consider versioning font files.

Font files should be versioned to enable cache busting when updated.

- src: "./fonts/GeistVF.woff",
+ src: "./fonts/GeistVF.v1.woff",

- src: "./fonts/GeistMonoVF.woff",
+ src: "./fonts/GeistMonoVF.v1.woff",
src/app/api/webhook/payment/route.ts (1)

31-37: Improve error handling specificity.

The current error handling is too generic. Different types of errors should be handled differently.

-  } catch (error) {
-    console.error("Payment webhook error:", error);
-    return NextResponse.json(
-      { error: "Internal server error" },
-      { status: 500 },
-    );
+  } catch (error) {
+    console.error("Payment webhook error:", error);
+    if (error instanceof Error && error.message.includes("No request found")) {
+      return NextResponse.json(
+        { error: "Payment reference not found" },
+        { status: 404 }
+      );
+    }
+    return NextResponse.json(
+      { error: "Internal server error" },
+      { status: 500 }
+    );
   }
src/lib/currencies.ts (2)

1-8: Consider using an enum for invoice currencies.

Using an enum would provide better type safety and maintainability.

-export const INVOICE_CURRENCIES = [
-  'USD', 
-  'ETH-sepolia-sepolia', 
-  'FAU-sepolia',
-  'fUSDC-sepolia',
-  'fUSDT-sepolia'
-] as const;
-export type InvoiceCurrency = (typeof INVOICE_CURRENCIES)[number];
+export enum InvoiceCurrency {
+  USD = 'USD',
+  ETH_SEPOLIA = 'ETH-sepolia-sepolia',
+  FAU_SEPOLIA = 'FAU-sepolia',
+  FUSDC_SEPOLIA = 'fUSDC-sepolia',
+  FUSDT_SEPOLIA = 'fUSDT-sepolia'
+}
+export const INVOICE_CURRENCIES = Object.values(InvoiceCurrency);

24-39: Refactor currency formatting for better maintainability.

Consider using a mapping object instead of a switch statement for better maintainability.

+const CURRENCY_LABELS: Record<string, string> = {
+  'ETH-sepolia-sepolia': 'Sepolia ETH',
+  'FAU-sepolia': 'Faucet Token (FAU)',
+  'fUSDC-sepolia': 'Sepolia USDC',
+  'fUSDT-sepolia': 'Sepolia USDT',
+  'USD': 'US Dollar',
+};
+
 export function formatCurrencyLabel(currency: string): string {
-  switch (currency) {
-    case 'ETH-sepolia-sepolia':
-      return 'Sepolia ETH';
-    case 'FAU-sepolia':
-      return 'Faucet Token (FAU)';
-    case 'fUSDC-sepolia':
-      return 'Sepolia USDC';
-    case 'fUSDT-sepolia':
-      return 'Sepolia USDT';
-    case 'USD':
-      return 'US Dollar';
-    default:
-      return currency;
-  }
+  return CURRENCY_LABELS[currency] ?? currency;
 }
src/components/invoice-preview.tsx (2)

17-23: Consider moving date formatting to a utility function.

The date formatting logic could be reused across components.

Create a new utility file:

+// src/lib/utils/date.ts
+export const formatDate = (date: string) => {
+  return new Date(date).toLocaleDateString("en-GB", {
+    year: "2-digit",
+    month: "2-digit",
+    day: "2-digit",
+  });
+};

137-159: Consider adding payment instructions.

The payment details section could benefit from additional instructions or guidance for the client.

Add payment instructions:

 <div>
   <div className="text-xs text-neutral-500 mb-1">PAYABLE IN</div>
   <div className="text-sm">
     {!data.paymentCurrency
       ? "Choose payment currency"
       : formatCurrencyLabel(
           data.paymentCurrency ||
             data.invoiceCurrency ||
             "Select currency",
         )}
   </div>
+  <div className="text-xs text-neutral-500 mt-2">
+    Please ensure to use the specified currency for payment.
+    Contact support if you need assistance.
+  </div>
 </div>
src/components/invoice-form.tsx (1)

195-220: Consider adding currency conversion information.

When selecting different payment currencies for USD invoices, it would be helpful to show the conversion rate.

Add conversion rate display:

 <div className="space-y-2">
   <Label htmlFor="paymentCurrency">Payment Currency</Label>
   <Select
     onValueChange={(value) => form.setValue("paymentCurrency", value)}
     defaultValue={form.getValues("paymentCurrency")}
   >
     <SelectTrigger>
       <SelectValue placeholder="Select payment currency" />
     </SelectTrigger>
     <SelectContent>
       {getPaymentCurrenciesForInvoice("USD").map((currency) => (
         <SelectItem key={currency} value={currency}>
           {formatCurrencyLabel(currency)}
         </SelectItem>
       ))}
     </SelectContent>
   </Select>
+  {form.watch("paymentCurrency") && (
+    <div className="text-sm text-neutral-500">
+      Exchange rates will be calculated at the time of payment
+    </div>
+  )}
   {form.formState.errors.paymentCurrency && (
     <p className="text-sm text-red-500">
       {form.formState.errors.paymentCurrency.message}
     </p>
   )}
 </div>
src/components/invoice-table.tsx (3)

55-60: Consider optimizing refetch strategy.

The current configuration enables refetching on window focus, mount, and reconnect. This might lead to unnecessary API calls and potential rate limiting.

   const { data: invoices } = api.invoice.getAll.useQuery(undefined, {
     initialData: initialInvoices,
-    refetchOnWindowFocus: true,
-    refetchOnMount: true,
-    refetchOnReconnect: true,
+    refetchOnWindowFocus: false,
+    refetchOnMount: false,
+    refetchOnReconnect: false,
+    // Consider using a polling interval instead
+    refetchInterval: 30000, // Refetch every 30 seconds
   });

164-165: Add currency symbol to amount display.

The amount is displayed without a currency symbol, which might confuse users about the currency being used.

-                        {Number(invoice.amount).toLocaleString()}
+                        {Number(invoice.amount).toLocaleString(undefined, {
+                          style: 'currency',
+                          currency: invoice.invoiceCurrency
+                        })}

204-223: Enhance pagination accessibility.

The pagination controls lack proper ARIA labels for screen readers.

               <Button
                 variant="outline"
                 onClick={() => setPage((p) => Math.max(1, p - 1))}
                 disabled={page === 1}
+                aria-label="Previous page"
               >
                 Previous
               </Button>
-              <span className="text-sm text-gray-600">
+              <span className="text-sm text-gray-600" role="status" aria-live="polite">
                 Page {page} of {totalPages}
               </span>
               <Button
                 variant="outline"
                 onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
                 disabled={page === totalPages}
+                aria-label="Next page"
               >
                 Next
               </Button>
src/app/invoices/[ID]/page.tsx (1)

31-37: Consider moving date formatting to a utility function.

The formatDate function could be reused across components. Consider moving it to a shared utility file.

Create a new file src/lib/utils/date.ts:

export const formatDate = (date: string) => {
  return new Date(date).toLocaleDateString("en-GB", {
    year: "2-digit",
    month: "2-digit",
    day: "2-digit",
  });
};
package.json (1)

11-12: Consider keeping environment variables for database commands.

Removing dotenv -e .env -- from database commands might cause issues if environment variables are not properly set.

-    "db:push": "drizzle-kit push",
-    "db:studio": "drizzle-kit studio",
+    "db:push": "dotenv -e .env -- drizzle-kit push:pg",
+    "db:studio": "dotenv -e .env -- drizzle-kit studio:pg"
README.md (3)

12-72: Enhance the getting started instructions.

While the setup instructions are comprehensive, consider these improvements:

  1. Add instructions for setting up PostgreSQL locally or via Docker
  2. Standardize on either npm or yarn commands to avoid confusion
  3. Include links or instructions for obtaining the Request Network API key and Reown Project ID

Here's a suggested addition for the database setup:

 ### Prerequisites
 
 - Node.js 18+
 - PostgreSQL database
+   - Option 1: Install PostgreSQL locally
+   - Option 2: Use Docker (recommended):
+     ```bash
+     docker-compose up -d
+     ```
 - Google OAuth credentials (for authentication)
 - Request Network API key
+   - Sign up at [Request Network Portal](https://dashboard.request.network/)
 - Reown Project ID
+   - Create a project at [Reown Dashboard](https://dashboard.reown.xyz/)
🧰 Tools
🪛 LanguageTool

[uncategorized] ~39-~39: Loose punctuation mark.
Context: ...t variables in .env: - DATABASE_URL: Your PostgreSQL connection string - `GO...

(UNLIKELY_OPENING_PUNCTUATION)


74-92: Add more specific documentation links for payment integration.

Consider adding links to Request Network documentation specific to invoice creation and payment processing:

 For more information about the technologies used:
 
 - [Request Network Documentation](https://docs.request.network/)
+- [Request Network Invoice Creation Guide](https://docs.request.network/guides/1.create-a-request)
+- [Request Network Payment Processing](https://docs.request.network/guides/4.pay-a-request)
 - [Next.js Documentation](https://nextjs.org/docs)

93-100: Enhance the contributing section's professionalism.

Consider rephrasing to sound more professional:

-Contributions are welcome! Please feel free to submit a Pull Request.
+Contributions are welcome! We encourage you to submit a Pull Request.
🧰 Tools
🪛 LanguageTool

[style] ~95-~95: The phrase ‘feel free to’ is used quite frequently. Consider using a less frequent alternative to set your writing apart from others and make it sound more professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## 📝 License ...

(FEEL_FREE_TO_STYLE_ME)

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between adc3252 and 810b525.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (28)
  • .env.example (1 hunks)
  • LICENSE (1 hunks)
  • README.md (1 hunks)
  • dev/docker-compose.yml (1 hunks)
  • drizzle.config.ts (1 hunks)
  • package.json (3 hunks)
  • src/app/api/trpc/[trpc]/route.ts (1 hunks)
  • src/app/api/webhook/payment/route.ts (1 hunks)
  • src/app/dashboard/page.tsx (3 hunks)
  • src/app/invoices/[ID]/page.tsx (1 hunks)
  • src/app/invoices/[requestID]/page.tsx (0 hunks)
  • src/app/invoices/create/page.tsx (2 hunks)
  • src/app/layout.tsx (1 hunks)
  • src/app/page.tsx (3 hunks)
  • src/components/app-kit.tsx (1 hunks)
  • src/components/invoice-creator.tsx (2 hunks)
  • src/components/invoice-form.tsx (3 hunks)
  • src/components/invoice-preview.tsx (2 hunks)
  • src/components/invoice-table.tsx (1 hunks)
  • src/components/payment-section.tsx (1 hunks)
  • src/components/ui/sonner.tsx (1 hunks)
  • src/components/user-menu.tsx (1 hunks)
  • src/lib/axios.ts (1 hunks)
  • src/lib/currencies.ts (1 hunks)
  • src/lib/schemas/invoice.ts (2 hunks)
  • src/middleware.ts (1 hunks)
  • src/server/db/schema.ts (3 hunks)
  • src/server/routers/invoice.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/app/invoices/[requestID]/page.tsx
✅ Files skipped from review due to trivial changes (4)
  • .env.example
  • LICENSE
  • src/app/api/trpc/[trpc]/route.ts
  • src/components/user-menu.tsx
🧰 Additional context used
🪛 LanguageTool
README.md

[uncategorized] ~39-~39: Loose punctuation mark.
Context: ...t variables in .env: - DATABASE_URL: Your PostgreSQL connection string - `GO...

(UNLIKELY_OPENING_PUNCTUATION)


[style] ~95-~95: The phrase ‘feel free to’ is used quite frequently. Consider using a less frequent alternative to set your writing apart from others and make it sound more professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## 📝 License ...

(FEEL_FREE_TO_STYLE_ME)

🔇 Additional comments (22)
src/components/invoice-creator.tsx (3)

6-9: Good usage of type imports.
Importing InvoiceFormValues with the type keyword helps reduce type-checking overhead and keeps imports consistent.


12-14: Clear approach for routing and notifications.
Using useRouter for navigation and toast for feedback is aligned with Next.js patterns and ensures a good user experience.

Also applies to: 17-17


46-47: Verify default for payment currency.
Using an empty string for paymentCurrency could lead to unexpected behavior if it's required. Confirm whether it must be explicitly set before form submission.

src/app/dashboard/page.tsx (3)

1-2: Aligned imports for new features.
Combined imports for InvoiceTable, UserMenu, api, PlusCircle, and redirect are consistent with the new functionalities.

Also applies to: 4-6, 8-8


10-14: Effective metadata addition.
Defining and exporting page metadata improves SEO and user context.


68-74: Clear "Create Invoice" flow.
Using a styled link to /invoices/create is straightforward and improves navigation clarity.

src/server/routers/invoice.ts (2)

1-7: Imports appear appropriate.
The newly introduced imports (apiClient, requestTable, etc.) accurately reflect the extended invoice features.


81-87: Reevaluate public access to invoice data.
getById is a public procedure, which might expose sensitive invoice details to unauthorized users. Ensure data access is intentional or add an auth check.

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

23-45: Validate existing payment status before initializing.
When the component initializes paymentStatus to invoice.status, if the invoice is already paid, it might be good to gracefully handle or lock the payment UI to avoid confusion.

src/components/app-kit.tsx (1)

7-12: Verify metadata configuration for development environment.

The metadata URL is set to production (easyinvoice.request.network) which may not be appropriate for development. Additionally, the icon path is relative and may not resolve correctly.

Consider:

  1. Using environment variables for the URL to support different environments
  2. Using absolute path or public directory prefix for the icon:
- icons: ["./assets/logo.svg"],
+ icons: ["/assets/logo.svg"],
src/app/invoices/create/page.tsx (2)

8-11: LGTM! Good SEO implementation.

The metadata implementation follows Next.js best practices for SEO optimization.


47-47: LGTM! Branding update is consistent.

The branding update from "IP" to "EI" aligns with the new EasyInvoice branding.

src/server/db/schema.ts (2)

47-68: LGTM! Well-structured schema with proper constraints.

The schema design follows best practices:

  • Required fields are properly constrained with notNull()
  • JSON field for items allows flexible data structure
  • Foreign key with cascade delete prevents orphaned records

83-102: LGTM! Well-defined relationships between tables.

The relationships are properly defined with:

  • Clear one-to-many and many-to-one relationships
  • Proper field references
  • Consistent naming conventions
src/app/page.tsx (3)

7-10: LGTM! Good SEO optimization.

The metadata is well-structured with descriptive title and description.


45-45: LGTM! Consistent branding update.

Logo text updated to match the new "EasyInvoice" branding.


88-88: Verify the copyright year.

The copyright year is set to 2025. Please verify if this is intentional or should be dynamically generated.

const currentYear = new Date().getFullYear();
src/components/invoice-preview.tsx (1)

29-50: LGTM! Well-structured grid layout.

The grid layout provides clear organization of invoice details with proper spacing and alignment.

src/components/invoice-form.tsx (2)

167-174: LGTM! Well-implemented currency selection logic.

The currency selection logic properly handles the relationship between invoice and payment currencies.


239-245: LGTM! Clear form submission states.

The button states clearly indicate the form's status: initial, loading, and success.

dev/docker-compose.yml (1)

6-6: Verify PostgreSQL version compatibility.

Using a specific version is good practice, but let's verify if PostgreSQL 16 is compatible with all dependencies and if it's the latest stable version.

✅ Verification successful

🌐 Web query:

What is the latest stable version of PostgreSQL? Are there any known issues with version 16?

💡 Result:

The latest stable version of PostgreSQL as of February 2025 is PostgreSQL 17.2, with ongoing support for minor versions like 16.6 under its five-year support lifecycle[1][4][7].

Known Issues in PostgreSQL 16

While PostgreSQL 16 introduced significant performance improvements[6][9], several issues have been identified and addressed in subsequent updates:

  1. Extension Compatibility:

    • Specific extensions like pg_store_plans, redis_fdw, and sslutils had regression test failures or compatibility issues[2].
    • Unmaintained extensions (e.g., wal2mongo) lack support for newer versions[2].
  2. Security Vulnerabilities:

    • CVE-2024-0985 allowed non-owners of materialized views to execute arbitrary SQL during concurrent refreshes (fixed in v16.2)[5].
  3. Bugs Addressed in Minor Updates:

    • Memory leaks during JIT inlining[5].
    • Query planner misbehavior and MERGE command inconsistencies[5][8].
    • Issues with pg_sequence_last_value() returning errors for unlogged/temporary sequences (resolved in v16.4)[3].
  4. Ongoing Monitoring:

    • Open items like ICU rule handling quirks and rare HOT chain indexing anomalies were under review[8].

Most issues were resolved through minor updates (e.g., v16.6)[1][3]. Users are advised to:

  • Upgrade to the latest minor release (e.g., v16.6).
  • Verify extension compatibility with official documentation or maintainers[2][6].
  • Review release notes for patch details[1][3][7].

For new deployments, PostgreSQL 17 is recommended for enhanced features and extended support[1][7].

Citations:


PostgreSQL Version Compatibility Verification

  • Current Setup: The postgres:16-alpine image used in the docker-compose file points to PostgreSQL 16.
  • Latest Stable Release: As of February 2025, the latest stable release is PostgreSQL 17.2. However, PostgreSQL 16 is still actively maintained with minor updates (e.g., 16.6) providing fixes.
  • Known Issues: PostgreSQL 16 had some known issues (like extension compatibility problems and minor bugs) that have been resolved in subsequent minor releases. Ensure that any extensions your application relies on are compatible with version 16 or update accordingly.
  • Recommendation: If your dependencies and application do not require features exclusive to PostgreSQL 17, using PostgreSQL 16 (properly updated to the latest minor release) is acceptable. Otherwise, consider upgrading to PostgreSQL 17 for enhanced features and longer support.
README.md (1)

1-11: Well-structured introduction and features section!

The introduction clearly communicates the project's purpose, and the features section effectively highlights key functionalities, particularly the Request Network API integration and crypto payment capabilities.

Copy link

coderabbitai bot commented Feb 12, 2025

✅ Actions performed

Full review triggered.

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

♻️ Duplicate comments (1)
package.json (1)

27-28: ⚠️ Potential issue

Update @reown/appkit to a valid stable version.

The specified version ^1.6.8 for both @reown/appkit and @reown/appkit-adapter-ethers5 appears to be invalid. The latest stable version is 1.3.0.

Apply this diff to update to the latest stable version:

-    "@reown/appkit": "^1.6.8",
-    "@reown/appkit-adapter-ethers5": "^1.6.8",
+    "@reown/appkit": "^1.3.0",
+    "@reown/appkit-adapter-ethers5": "^1.3.0",
🧹 Nitpick comments (28)
src/components/invoice-creator.tsx (3)

19-35: Well-structured success callback with dynamic routing.
The conditional check for data.invoice followed by toast and setTimeout-based navigation is functional. As a small improvement, consider providing a direct link to the newly created invoice instead of an enforced 3-second delay for enhanced user control.


46-47: Revised default values for currency.
Using "USD" for invoiceCurrency and an empty string for paymentCurrency synchronizes with your updated schema. If you expect consistent usage of a default payment currency, consider assigning a default here as well, to prevent potential empty string edge cases.


53-62: Enhanced error handling with try/catch.
Wrapping the mutation call in try/catch and displaying error details improves user feedback. Logging or sending error data to an error-tracking service could further aid debugging in production.

src/server/db/schema.ts (2)

6-6: JSON column usage is valid.
Storing line items in a JSON column is flexible for variable data structures. Keep in mind indexing or performance concerns if queries on this field become more complex.


49-68: Added fields and references in requestTable.
The broad set of text fields for amounts, IDs, and references is understandable. However, consider the following refinements:

  • Use numeric or decimal types for financial fields (e.g., amount) to avoid parsing overhead and potential string conversion bugs.
  • Add uniqueness constraints or indexes for fields like invoiceNumber or requestId if they must be unique or frequently queried.
src/app/dashboard/page.tsx (2)

19-20: Dynamic data retrieval from api.invoice.getAll.query().
Fetching real invoice data aligns the dashboard with live backend data. Consider adding error handling or fallback UI if the request fails or returns an empty result.


77-83: Rendering InvoiceTable with initial data.
Passing invoices, totalPayments, and outstandingInvoices as props is a clear approach to unify and display invoice data. For large data sets, consider lazy loading or pagination.

src/server/routers/invoice.ts (2)

10-58: Consider applying transactional logic when creating invoices.

Within the create mutation, if the external request is successful but the subsequent database insertion fails (or vice versa), partial inconsistencies may occur (e.g., an invoice is created externally but not reflected in the local DB). Consider using a transaction or compensating actions to handle failures atomically.


88-109: Guard against repeated payments or attempts on already-paid invoices.

In the payRequest mutation, there's no explicit check to ensure the status isn't already "paid." Malicious or accidental repeated payment attempts might lead to confusion or undesired behaviors. Consider adding a guard to verify that the invoice’s status is still "pending" before proceeding.

    // ...
    if (invoice.status === "paid") {
+     return { success: false, message: "Invoice already paid" };
    }
    const response = await apiClient.get(
      `/v1/request/${invoice.paymentReference}/pay`,
    );
    // ...
src/components/invoice-form.tsx (2)

195-220: Provide a fallback or explanation if payment currency selection is needed for other invoice currencies.

You currently show the payment currency selector only if invoiceCurrency is "USD." If future requirements allow a different payment currency for non-USD invoices, consider offering a similar selector.


239-245: Allow creating subsequent invoices without reloading the form.

Disabling the submit button after a successful submission (because isSubmitSuccessful is true) may force users to refresh the page to create another invoice. Consider providing a “New Invoice” button or resetting the form after submission for better UX.

src/components/invoice-table.tsx (1)

70-111: Ensure displayed currency matches data currency.

The "Total Payments" field is prefixed with a $ sign, which implies USD, yet your app supports multiple currencies. Consider formatting this total consistently with the selected invoice/payment currency or providing an explicit currency label.

src/components/payment-section.tsx (3)

23-28: Consider extracting state into a custom hook.

The component maintains multiple pieces of local state (e.g., paymentStatus, paymentProgress, currentStep, isAppKitReady) which results in a fairly large and complex component. Extracting payment logic and state transitions into a custom hook (e.g., usePaymentFlow) can reduce complexity, promote reusability, and improve testability.


114-115: Update invoice status in real-time.

After setting paymentStatus to "paid", consider refetching the invoice data from the server or performing other updates (e.g., invalidating TRPC queries) to keep the UI in sync with your backend’s record of paid invoices. This helps ensure the displayed invoice details and status are consistent across the application.


127-137: Provide a fallback or partial discount status for “processing” or “pending” states.

If the user’s payment is still pending or processing, consider explaining that partial or final amounts may still be deducted. This ensures clarity regarding the financial and UI state while the user transaction is not fully settled.

src/lib/axios.ts (1)

3-8: Validate environment variable availability.

If REQUEST_API_URL or REQUEST_API_KEY are undefined, axios calls will fail or lack authentication. Consider adding a fallback or an explicit error if these environment variables are not set to avoid silent misconfigurations.

drizzle.config.ts (1)

9-9: Allow configurable table filtering if possible.

Changing tablesFilter from ["invoicepro_*"] to ["easyinvoice_*"] is aligned with the new naming convention. If you anticipate future renaming or multi-project usage, consider making this property configurable via environment variables for improved maintainability.

src/app/api/trpc/[trpc]/route.ts (1)

17-32: Fix the type error instead of using ts-expect-error.

Replace the ts-expect-error comment with proper type assertions or fix the underlying type issue.

-    // @ts-expect-error: This works , no need to fix
-    createContext: () => createContext(req),
+    createContext: async () => await createContext(req),
src/app/api/webhook/payment/route.ts (1)

31-37: Enhance error handling with specific error types.

The catch block should handle different types of errors (validation, database, etc.) separately for better error reporting.

-  } catch (error) {
+  } catch (error) {
+    if (error instanceof Error) {
+      console.error("Payment webhook error:", error.message);
+      if (error.message.includes("No request found")) {
+        return NextResponse.json(
+          { error: "Payment reference not found" },
+          { status: 404 }
+        );
+      }
+    }
     console.error("Payment webhook error:", error);
     return NextResponse.json(
       { error: "Internal server error" },
       { status: 500 }
     );
   }
src/lib/currencies.ts (1)

1-8: Add JSDoc documentation for better code clarity.

The currency constants and types would benefit from documentation explaining their purpose and usage.

+/**
+ * List of supported invoice currencies.
+ * Includes both fiat (USD) and Sepolia testnet tokens.
+ */
 export const INVOICE_CURRENCIES = [
   'USD', 
   'ETH-sepolia-sepolia', 
   'FAU-sepolia',
   'fUSDC-sepolia',
   'fUSDT-sepolia'
 ] as const;
+
+/** Type representing valid invoice currencies */
 export type InvoiceCurrency = (typeof INVOICE_CURRENCIES)[number];
src/components/invoice-preview.tsx (3)

17-23: Consider adding date validation.

While the date formatting function works well, it might throw an error if an invalid date string is provided.

Consider adding validation:

 const formatDate = (date: string) => {
+  if (!date || isNaN(new Date(date).getTime())) {
+    return "Invalid date";
+  }
   return new Date(date).toLocaleDateString("en-GB", {
     year: "2-digit",
     month: "2-digit",
     day: "2-digit",
   });
 };

93-111: Consider adding type safety for items array.

The default item structure could benefit from type safety to ensure consistent data shape.

Consider defining an interface:

interface InvoiceItem {
  description: string;
  quantity: number;
  price: number;
}

const defaultItem: InvoiceItem = {
  description: "Description",
  quantity: 1,
  price: 0,
};

141-149: Simplify currency display logic.

The nested ternary and multiple fallbacks make the code harder to read.

Consider simplifying:

-            {!data.paymentCurrency
-              ? "Choose payment currency"
-              : formatCurrencyLabel(
-                  data.paymentCurrency ||
-                    data.invoiceCurrency ||
-                    "Select currency",
-                )}
+            {formatCurrencyLabel(data.paymentCurrency || data.invoiceCurrency) || "Choose payment currency"}
src/app/invoices/[ID]/page.tsx (2)

31-37: Consider moving date formatting to a utility file and add error handling.

The date formatting function could be:

  1. Moved to a separate utility file for reusability
  2. Enhanced with error handling for invalid dates
+// src/utils/date.ts
+export const formatDate = (date: string) => {
+  try {
+    return new Date(date).toLocaleDateString("en-GB", {
+      year: "2-digit",
+      month: "2-digit",
+      day: "2-digit",
+    });
+  } catch (error) {
+    console.error('Error formatting date:', error);
+    return 'Invalid date';
+  }
+};

160-175: Consider improving type safety for invoice items.

While invalid items are prevented at creation, the type casting could be improved for better type safety.

-{(invoice.items as InvoiceItem[]).map((item, index) => (
+{(invoice.items as readonly InvoiceItem[]).map((item, index) => (
.env.example (1)

6-9: Add documentation for new environment variables.

Please add comments explaining:

  • Required format for each variable
  • Whether these variables are required or optional
  • Example values where applicable
+# Request API configuration (required)
+# Example: https://api.request.network/v1
 REQUEST_API_URL=""
+# API key from Request Network dashboard
 REQUEST_API_KEY=""
+# Project ID from Reown dashboard
 NEXT_PUBLIC_REOWN_PROJECT_ID=""
+# Secret for webhook validation (min 32 characters)
 WEBHOOK_SECRET=""
package.json (1)

65-65: Remove unused dependency.

The dotenv-cli package is added as a dev dependency but is no longer used in any scripts.

Apply this diff to remove the unused dependency:

-    "dotenv-cli": "^7.3.0",
README.md (1)

39-44: Enhance environment variables documentation.

Consider adding brief explanations for the purpose and format of each environment variable, especially:

  • REQUEST_API_KEY: How to obtain it and its purpose
  • NEXT_PUBLIC_REOWN_PROJECT_ID: Where to get it and why it's needed
🧰 Tools
🪛 LanguageTool

[uncategorized] ~39-~39: Loose punctuation mark.
Context: ...t variables in .env: - DATABASE_URL: Your PostgreSQL connection string - `GO...

(UNLIKELY_OPENING_PUNCTUATION)

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between adc3252 and 810b525.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (28)
  • .env.example (1 hunks)
  • LICENSE (1 hunks)
  • README.md (1 hunks)
  • dev/docker-compose.yml (1 hunks)
  • drizzle.config.ts (1 hunks)
  • package.json (3 hunks)
  • src/app/api/trpc/[trpc]/route.ts (1 hunks)
  • src/app/api/webhook/payment/route.ts (1 hunks)
  • src/app/dashboard/page.tsx (3 hunks)
  • src/app/invoices/[ID]/page.tsx (1 hunks)
  • src/app/invoices/[requestID]/page.tsx (0 hunks)
  • src/app/invoices/create/page.tsx (2 hunks)
  • src/app/layout.tsx (1 hunks)
  • src/app/page.tsx (3 hunks)
  • src/components/app-kit.tsx (1 hunks)
  • src/components/invoice-creator.tsx (2 hunks)
  • src/components/invoice-form.tsx (3 hunks)
  • src/components/invoice-preview.tsx (2 hunks)
  • src/components/invoice-table.tsx (1 hunks)
  • src/components/payment-section.tsx (1 hunks)
  • src/components/ui/sonner.tsx (1 hunks)
  • src/components/user-menu.tsx (1 hunks)
  • src/lib/axios.ts (1 hunks)
  • src/lib/currencies.ts (1 hunks)
  • src/lib/schemas/invoice.ts (2 hunks)
  • src/middleware.ts (1 hunks)
  • src/server/db/schema.ts (3 hunks)
  • src/server/routers/invoice.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/app/invoices/[requestID]/page.tsx
🧰 Additional context used
🧠 Learnings (1)
src/server/routers/invoice.ts (1)
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/server/routers/invoice.ts:88-109
Timestamp: 2025-02-12T12:40:14.742Z
Learning: The payRequest endpoint in src/server/routers/invoice.ts is intentionally kept public (using publicProcedure) to allow invoice sharing and payment by anyone with the payment reference, similar to how payment links work in other payment systems.
🪛 LanguageTool
README.md

[uncategorized] ~39-~39: Loose punctuation mark.
Context: ...t variables in .env: - DATABASE_URL: Your PostgreSQL connection string - `GO...

(UNLIKELY_OPENING_PUNCTUATION)


[style] ~95-~95: The phrase ‘feel free to’ is used quite frequently. Consider using a less frequent alternative to set your writing apart from others and make it sound more professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## 📝 License ...

(FEEL_FREE_TO_STYLE_ME)

🔇 Additional comments (46)
src/components/invoice-creator.tsx (4)

6-9: Use of type-only import is a best practice.
Importing InvoiceFormValues as a type is a clean approach that helps keep bundle size small and code organization clear.


12-12: Appropriate usage of useRouter.
Bringing in useRouter from next/navigation aligns with Next.js 13 conventions. No issues here.


14-14: Toast import is valid.
Using sonner for toast notifications is straightforward and fits well into the current architecture.


17-17: Router instance usage.
Initializing const router = useRouter() is correct. Consider verifying that the environment is client-side before relying on router-based navigation in other contexts, but in this case, the use client directive at the top ensures a client component.

src/server/db/schema.ts (2)

3-3: Type-based import strategy is appropriate.
Including type InferSelectModel and relations from drizzle-orm clarifies usage in both schema definitions and relationships.


83-102: New table relations.
Mapping one-to-many relationships between userTable, requestTable, and sessionTable is clearly defined. This approach keeps DB constraints explicit and consistent.

src/app/dashboard/page.tsx (5)

1-2: New imports for invoice display and user menu.
Fetching the InvoiceTable and UserMenu components modularizes the dashboard logic, improving clarity.


4-6: Additional imports for data fetching, icons, and navigation.
Using api from the tRPC server, importing PlusCircle, and leveraging redirect from Next.js are aligned with the framework’s best practices.

Also applies to: 8-8


10-14: SEO-friendly metadata.
Including the Metadata export improves the page’s SEO and helps clarify the page’s intent for visitors.


53-53: Use of "EI" branding.
Renaming from "IP" to "EI" aligns with the “EasyInvoice” brand. Consistent use of branding across components is advisable for a cohesive experience.


68-74: Navigational link for invoice creation.
Replacing a button with a Link styled as a button is a recommended pattern for Next.js to preserve client-side transitions.

src/server/routers/invoice.ts (1)

81-87: Verify that making getById public matches your data-privacy expectations.

Currently, getById is a publicProcedure, which means anyone with a valid invoice ID can retrieve its data. If sharing invoices publicly is your intended design (similar to a public invoice link), this is fine. Otherwise, consider scoping access to authorized users.

src/components/invoice-form.tsx (1)

165-174: Confirm that auto-syncing payment currency for non-USD invoices aligns with user expectations.

When invoiceCurrency is not "USD," the paymentCurrency is automatically set to the same value. This may reduce flexibility if users want to define different invoice and payment currencies. If the current logic is intentional, please disregard this comment.

src/components/invoice-table.tsx (2)

31-31: Refine the type of the items field.

Using unknown bypasses type safety and can lead to runtime errors. If you have a known shape for invoice items, replace unknown with a dedicated interface or type.

- items: unknown;
+ items: InvoiceItem[];

153-185: Check time-zone sensitivity in overdue logic.

Currently, isOverdue is computed by checking whether status is "pending" and dueDate is in the past. Time zone differences might cause unexpected overdue statuses if users in different locales access the system. If you need robust date-handling, consider storing times/offsets or using a standardized approach (e.g., UTC) for consistent behavior.

src/components/app-kit.tsx (3)

14-24: Add validation for required environment variables.

The project ID is cast as string without validation, which could cause runtime errors if the environment variable is missing.


26-28: LGTM!

The component is correctly implemented as a client-side wrapper component with proper TypeScript types.


7-12: Verify the icon path accessibility.

The icon path ./assets/logo.svg is relative and might not be accessible. Ensure that the icon is available at the correct path relative to the application's root.

❌ Verification inconclusive

🏁 Script executed:

#!/bin/bash
# Description: Check if the logo file exists at the specified path.
fd -t f "logo.svg" -x echo "Found logo at: {}"

Length of output: 46


Icon Accessibility Check
The initial FD command did not return any results for “logo.svg”. This outcome suggests that the file might not be present at the expected location relative to the application’s root. To gather more evidence, let’s run an alternative file search using the standard find command.

Please execute this script and share the output. If no matching file is found, manually verify whether the icon file exists in your repository and update the metadata icon path accordingly.


🏁 Script executed:

#!/bin/bash
# Search the repository for any file named "logo.svg" (case-insensitive)
find . -type f | grep -i "logo.svg"

Length of output: 35


Icon file missing from the repository

The search using both fd and find returned no results for "logo.svg", indicating that the file is not present at the expected location. Please verify whether the icon file exists, or update the metadata to point to the correct file path if it has been moved or renamed.

src/components/ui/sonner.tsx (2)

31-31: LGTM!

The component is correctly exported.


8-29: Verify the CSS classes are defined.

The component uses several CSS classes for styling the toast notifications. Please ensure that these classes are defined in your CSS files:

  • toaster
  • group
  • toast
  • bg-background
  • text-foreground
  • border-border
  • shadow-lg
  • text-muted-foreground
  • bg-primary
  • text-primary-foreground
  • bg-muted
  • text-muted-foreground
❌ Verification inconclusive

🏁 Script executed:

#!/bin/bash
# Description: Search for CSS class definitions in the codebase.
rg -t css -t scss "(\.|@apply).*?(\btoaster\b|\bgroup\b|\btoast\b|\bbg-background\b|\btext-foreground\b|\bborder-border\b|\bshadow-lg\b|\btext-muted-foreground\b|\bbg-primary\b|\btext-primary-foreground\b|\bbg-muted\b)"

Length of output: 250


🏁 Script executed:

#!/bin/bash
echo "Searching CSS files for class definitions:"
rg -g "*.css" -e "(\.|@apply).*?\b(toaster|group|toast|bg-background|text-foreground|border-border|shadow-lg|text-muted-foreground|bg-primary|text-primary-foreground|bg-muted)\b" .

echo "Searching SCSS files for class definitions:"
rg -g "*.scss" -e "(\.|@apply).*?\b(toaster|group|toast|bg-background|text-foreground|border-border|shadow-lg|text-muted-foreground|bg-primary|text-primary-foreground|bg-muted)\b" .

Length of output: 670


CSS Classes Definition Verification

After searching for class definitions in CSS, we found that some classes (e.g., border-border, bg-background, and text-foreground) are defined in the global CSS file via @apply in src/app/globals.css. The remaining classes used in the component—such as toaster, group, toast, shadow-lg, text-muted-foreground, bg-primary, text-primary-foreground, and bg-muted—are standard Tailwind CSS utilities (e.g., group and shadow-lg) or likely defined via your Tailwind configuration or additional CSS layers.

Please verify that these additional classes (if they are custom tokens rather than default Tailwind utilities) are either defined in your Tailwind configuration (or in other CSS files) or intentionally rely on Tailwind's built-in classes.

src/lib/schemas/invoice.ts (2)

4-24: LGTM!

The invoice form schema is well-structured with comprehensive validation rules for all fields:

  • Required fields have appropriate error messages
  • Email format is validated
  • Numeric fields have appropriate minimum values
  • Array fields require at least one item
  • Currency fields use predefined values or minimum length validation
  • Optional fields are correctly marked

26-26: LGTM!

The type is correctly inferred from the schema using zod's type inference.

src/app/api/trpc/[trpc]/route.ts (3)

1-5: LGTM!

The imports are correctly implemented with type-only import for NextRequest.


11-15: LGTM!

The context creation is correctly implemented using request headers.


34-34: LGTM!

The handler is correctly exported for both GET and POST methods.

src/app/layout.tsx (2)

20-23: Update metadata with actual application information.

The metadata still contains default Next.js values.

export const metadata: Metadata = {
-  title: "Create Next App",
-  description: "Generated by create next app",
+  title: "Easy Invoice - Simple and Secure Invoice Payment Platform",
+  description: "Easy Invoice is a simple and secure invoice payment platform powered by Request Network.",
};

30-42: LGTM! Well-structured layout with proper component hierarchy.

The integration of AppKit and Toaster components is well-implemented, providing a solid foundation for wallet connection and user notifications.

src/app/api/webhook/payment/route.ts (2)

12-15: Validate webhook secret environment variable.

The webhook secret is crucial for security but lacks validation.

+  const webhookSecret = process.env.WEBHOOK_SECRET;
+  if (!webhookSecret) {
+    console.error("WEBHOOK_SECRET is not configured");
+    return NextResponse.json(
+      { error: "Server configuration error" },
+      { status: 500 }
+    );
+  }
   const expectedSignature = crypto
-    .createHmac("sha256", process.env.WEBHOOK_SECRET as string)
+    .createHmac("sha256", webhookSecret)
     .update(JSON.stringify(body))
     .digest("hex");

21-29: Add transaction safety for database update.

The database update should be wrapped in a transaction to ensure atomicity.

   const { paymentReference } = body;

+  await db.transaction(async (tx) => {
+    const result = await tx
+      .update(requestTable)
+      .set({
+        status: "paid",
+        updatedAt: new Date(),
+      })
+      .where(eq(requestTable.paymentReference, paymentReference))
+      .returning({ id: requestTable.id });
+
+    if (!result.length) {
+      throw new Error(`No request found with payment reference: ${paymentReference}`);
+    }
+  });
-  await db
-    .update(requestTable)
-    .set({
-      status: "paid",
-    })
-    .where(eq(requestTable.paymentReference, paymentReference));
src/lib/currencies.ts (2)

10-22: LGTM! Well-structured currency mapping with type safety.

The payment currencies mapping is well-implemented with proper type constraints and const assertions.


24-39: LGTM! Clear and maintainable currency formatting.

The currency formatting function is well-structured with comprehensive coverage of all currency types.

src/middleware.ts (1)

23-26: LGTM! Webhook bypass implementation is appropriate.

The broad path matching for webhook endpoints is intentional as confirmed in the learnings, and the implementation is correct.

src/components/user-menu.tsx (1)

12-12: LGTM! Type-only import improves bundle optimization.

The change to a type-only import is a good practice as it helps with tree-shaking and potentially reduces bundle size.

src/app/invoices/create/page.tsx (2)

8-11: LGTM! Added SEO metadata.

The metadata addition improves SEO by providing clear title and description for the create invoice page.


47-47: LGTM! Updated branding.

The logo text update from "IP" to "EI" aligns with the rebranding to "EasyInvoice".

src/app/page.tsx (3)

7-10: LGTM! Added SEO metadata.

The metadata addition improves SEO by providing clear title and description for the landing page.


45-45: LGTM! Updated branding.

The logo text update from "IP" to "EI" aligns with the rebranding to "EasyInvoice".


86-93: LGTM! Updated footer layout and year.

The footer updates improve layout with centered content and display the current year.

src/components/invoice-preview.tsx (1)

29-50: LGTM! Improved header section layout.

The grid-based layout with clear labels and consistent spacing improves readability. Default values prevent UI breaking with missing data.

src/app/invoices/[ID]/page.tsx (3)

1-18: LGTM! Well-structured imports and type definitions.

The imports are appropriate for the functionality, metadata is well-defined for SEO, and the InvoiceItem type is properly structured.


20-29: LGTM! Proper error handling and API integration.

The component properly handles the case of non-existent invoices and makes good use of TypeScript types.


104-104: Use invoice creation date instead of current date.

The issue date is using the current date instead of the invoice's creation date.

dev/docker-compose.yml (1)

6-6: LGTM! Good practice using specific version and Alpine variant.

Using postgres:16-alpine instead of latest provides better version control and reduces image size.

LICENSE (1)

1-21: LGTM! Proper MIT License implementation.

The MIT License is appropriately implemented with current year and correct copyright holder.

README.md (2)

1-10: LGTM!

The introduction and features section is well-written, providing a clear overview of the application and its key features.


74-100: LGTM!

The tech stack and documentation sections are comprehensive and well-organized, providing valuable information for users and contributors.

🧰 Tools
🪛 LanguageTool

[style] ~95-~95: The phrase ‘feel free to’ is used quite frequently. Consider using a less frequent alternative to set your writing apart from others and make it sound more professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## 📝 License ...

(FEEL_FREE_TO_STYLE_ME)

Check was required when we were forwarding to the invoice page, which is not the case anymore
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: 2

🧹 Nitpick comments (11)
src/components/invoice-table.tsx (3)

13-13: Avoid naming collisions with the built-in Request type

Because the type Request is also used by Next.js and the DOM, consider renaming this model (e.g., PaymentRequest or DbRequest) to avoid confusion or overshadowing built-in objects.

- import type { Request } from "@/server/db/schema";
+ import type { PaymentRequest as BaseRequest } from "@/server/db/schema";

35-40: Add loading and error state handling

Currently, there's no user feedback for loading or error states. Consider including them for a better user experience.

- const { data: invoices } = api.invoice.getAll.useQuery(undefined, {
+ const { data: invoices, isLoading, isError } = api.invoice.getAll.useQuery(undefined, {
    initialData: initialInvoices,
    refetchOnWindowFocus: true,
    refetchOnMount: true,
    refetchOnReconnect: true,
  });

+ if (isLoading) {
+   return <div>Loading invoices...</div>;
+ }
+ if (isError) {
+   return <div>Failed to load invoices. Please try again later.</div>;
+ }

42-48: Consider server-side pagination for scalability

Paginating on the client might become slower for large datasets since all invoices are fetched at once. Moving pagination logic to the server can improve performance and scalability.

src/app/api/webhook/payment/route.ts (3)

7-25: Add null check for the incoming signature

Although an empty or null signature will fail the comparison, explicitly returning a 400 error if the signature header is missing can improve clarity.

  const signature = req.headers.get("x-request-network-signature");

+ if (!signature) {
+   return NextResponse.json(
+     { error: "Missing signature" },
+     { status: 400 }
+   );
+ }

27-43: Validate presence of paymentReference

If body.paymentReference is missing or empty, the transaction will not properly identify the invoice. Adding a check can prevent unexpected issues.

  const { paymentReference } = body;

+ if (!paymentReference) {
+   return NextResponse.json(
+     { error: "Missing paymentReference" },
+     { status: 400 }
+   );
+ }

45-53: Enhance error logging

Using console.error is a good start, but you could adopt a full-featured logging library (e.g., winston, pino) for better log management and structured output.

src/server/db/schema.ts (1)

49-69: Use domain-appropriate column types and reduce reliance on text fields

Storing dates and amounts as text may hamper queries and cause parsing issues. Consider using timestamp columns (e.g., timestamp() or date()) for dates and numeric columns for money amounts to enforce data integrity.

src/server/routers/invoice.ts (2)

57-60: Enhance error handling with specific error messages.

The current error handling only logs the error and returns a generic failure response. Consider providing more specific error messages to help with debugging and user feedback.

 } catch (error) {
   console.log("Error: ", error);
-  return { success: false };
+  return {
+    success: false,
+    error: error instanceof Error ? error.message : "An unexpected error occurred",
+  };
 }

62-83: Consider pagination for better performance.

The current implementation fetches all invoices at once, which could impact performance with large datasets. Consider implementing pagination.

- getAll: protectedProcedure.query(async ({ ctx }) => {
+ getAll: protectedProcedure
+   .input(
+     z.object({
+       page: z.number().min(1).default(1),
+       limit: z.number().min(1).max(100).default(10),
+     })
+   )
+   .query(async ({ ctx, input }) => {
    const { db } = ctx;
+   const offset = (input.page - 1) * input.limit;
    const invoices = await db.query.requestTable.findMany({
      where: eq(requestTable.userId, ctx.user?.id as string),
      orderBy: desc(requestTable.createdAt),
+     limit: input.limit,
+     offset,
    });
src/app/invoices/[ID]/page.tsx (2)

25-29: Add error boundary for better error handling.

Consider wrapping the component with an error boundary to gracefully handle runtime errors.

+ import { ErrorBoundary } from '@/components/error-boundary';

 export default async function PaymentPage({
   params,
 }: {
   params: { ID: string };
 }) {
   const invoice = await api.invoice.getById.query(params.ID);

   if (!invoice) {
     notFound();
   }

+  return (
+    <ErrorBoundary fallback={<div>Something went wrong</div>}>
       {/* existing component JSX */}
+    </ErrorBoundary>
+  );
 }

186-188: Use a consistent approach for number formatting.

Consider using a dedicated currency formatting utility for consistent number formatting across the application.

- <span className="text-sm">
-   {Number(invoice.amount).toFixed(2)}
- </span>
+ <span className="text-sm">
+   {formatCurrencyAmount(invoice.amount, invoice.invoiceCurrency)}
+ </span>
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 810b525 and 6575304.

📒 Files selected for processing (8)
  • src/app/api/webhook/payment/route.ts (1 hunks)
  • src/app/invoices/[ID]/page.tsx (1 hunks)
  • src/app/layout.tsx (3 hunks)
  • src/components/app-kit.tsx (1 hunks)
  • src/components/invoice-table.tsx (1 hunks)
  • src/components/payment-section.tsx (1 hunks)
  • src/server/db/schema.ts (3 hunks)
  • src/server/routers/invoice.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/payment-section.tsx
  • src/components/app-kit.tsx
🧰 Additional context used
🧠 Learnings (2)
src/app/invoices/[ID]/page.tsx (1)
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/app/invoices/[ID]/page.tsx:160-175
Timestamp: 2025-02-12T12:42:40.076Z
Learning: Invoice items are validated at creation time through the schema validation in src/lib/schemas/invoice.ts, ensuring that items array is non-empty and each item has required description, quantity, and price fields. Runtime validation in display components is not necessary.
src/server/routers/invoice.ts (1)
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/server/routers/invoice.ts:88-109
Timestamp: 2025-02-12T12:40:14.742Z
Learning: The payRequest endpoint in src/server/routers/invoice.ts is intentionally kept public (using publicProcedure) to allow invoice sharing and payment by anyone with the payment reference, similar to how payment links work in other payment systems.
🔇 Additional comments (8)
src/app/layout.tsx (3)

4-5: LGTM! Imports are well-organized and necessary.

The new imports support the PR objectives by adding wallet connection capabilities (AppKit) and toast notifications (Toaster).

Also applies to: 7-7


20-23: Update metadata with more descriptive information.

The metadata could be more descriptive to better reflect the application's features and integration with Request Network.

Apply this diff to enhance the metadata:

 export const metadata: Metadata = {
-  title: "Easy Invoice",
-  description: "Easy Invoice is a simple and secure invoice payment platform.",
+  title: "Easy Invoice - Simple and Secure Invoice Payment Platform",
+  description: "Easy Invoice is a simple and secure invoice payment platform powered by Request Network.",
 };

35-40: Verify AppKit configuration for Sepolia network.

The layout structure looks good with proper component hierarchy. However, let's verify that AppKit is configured correctly for the Sepolia network as mentioned in the PR objectives.

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Description: Check AppKit configuration in the component file

# Search for Sepolia network configuration in AppKit component
rg -A 5 'Sepolia|chainId|networkId' src/components/app-kit.tsx

Length of output: 64


Let's get more context on the AppKit configuration. The previous search for keywords (“Sepolia”, “chainId”, “networkId”) in src/components/app-kit.tsx returned no matches. This might mean that the network configuration is injected indirectly or defined elsewhere.

To be sure, please run a script to output the initial section of src/components/app-kit.tsx so we can inspect its configuration details. We can then determine if any Sepolia-specific setup exists or if additional manual verification is needed.

Please provide the output from this script for further analysis.


🏁 Script executed:

#!/bin/bash
# Dump the first 200 lines of the AppKit component to inspect its configuration details.
head -n 200 src/components/app-kit.tsx

Length of output: 852


AppKit configuration correctly includes Sepolia network settings.

The AppKit component’s initialization via the createAppKit function clearly sets networks: [sepolia], confirming that the Sepolia network configuration is in place. This meets the intended configuration requirements.

src/components/invoice-table.tsx (1)

144-145: Verify that invoice amounts are always numeric

The code assumes invoice.amount is numeric, but it's stored as text in the DB. Ensure strict validation or consider using a numeric type.

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Searching the codebase for references to 'amount'
rg -A 2 $'amount:'

Length of output: 667


Invoice amounts are derived from numeric values and are safely converted at display time.

  • The DB schema defines amount as text (see src/server/db/schema.ts), but in src/server/routers/invoice.ts the value is generated from a numeric totalAmount and explicitly converted to a string using .toString().
  • Because of this controlled conversion, the front-end’s conversion using Number(invoice.amount) will function correctly, as the underlying value is always numeric in nature.
  • While storing the value as text can be acceptable, you might consider using a numeric type or adding additional validation to enforce strict typing at the database level.
src/server/db/schema.ts (1)

84-104: Relationships look consistent

These newly defined relationships provide clarity and cohesion between the tables, enabling straightforward joins and references.

src/server/routers/invoice.ts (1)

84-90: LGTM!

The implementation is clean and straightforward.

src/app/invoices/[ID]/page.tsx (2)

9-12: LGTM!

The metadata is well-defined with appropriate title and description.


160-175: LGTM!

The invoice items rendering is correct. As per the learnings, the items are validated at creation time through the schema validation, so no additional runtime validation is needed here.

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: 0

🧹 Nitpick comments (2)
src/components/invoice-creator.tsx (2)

17-28: Consider improving the navigation pattern.

While the success notification and automatic navigation are good additions, a fixed 3-second delay might not be optimal for user experience. Consider these alternatives:

  1. Add a manual "Go to Dashboard" button in the success toast
  2. Use a loading state transition instead of a fixed delay

Example implementation with a button:

 const { mutate: createInvoice, isLoading } = api.invoice.create.useMutation({
   onSuccess: () => {
     toast.success("Invoice created successfully", {
-      description: "Redirecting to dashboard in 3 seconds",
+      description: "Your invoice has been created",
+      action: {
+        label: "Go to Dashboard",
+        onClick: () => router.push("/dashboard")
+      },
     });
-    setTimeout(() => {
-      router.push("/dashboard");
-    }, 3000);
   },
 });

45-56: Consider enhancing error handling with specific error types.

While the current error handling is good, it could be more specific to help users understand and resolve issues better.

Example implementation with specific error types:

 const onSubmit = async (data: InvoiceFormValues) => {
   try {
     await createInvoice(data);
   } catch (error) {
+    if (error instanceof TRPCClientError) {
+      toast.error("Validation Error", {
+        description: error.message,
+      });
+      return;
+    }
+    if (error instanceof NetworkError) {
+      toast.error("Network Error", {
+        description: "Please check your connection and try again",
+      });
+      return;
+    }
     toast.error("Failed to create invoice", {
       description:
-        error instanceof Error
-          ? error.message
-          : "An unexpected error occurred",
+        "An unexpected error occurred. Please try again later.",
     });
   }
 };
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6575304 and 3540840.

📒 Files selected for processing (1)
  • src/components/invoice-creator.tsx (2 hunks)
🔇 Additional comments (2)
src/components/invoice-creator.tsx (2)

6-14: LGTM! Import changes improve type safety and functionality.

The changes properly separate type imports and add necessary dependencies for navigation and toast notifications.


32-43: Verify currency handling across the application.

The form configuration has been updated to support multiple currencies with invoiceCurrency defaulting to "USD" and paymentCurrency as required. Please ensure:

  1. These changes align with the supported currencies in the Request API
  2. The currency conversion logic handles these fields correctly
✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Search for currency-related configurations and validations
rg -A 5 "Currency|currency" --type typescript

Length of output: 82


I'll re-run the search using file globbing for TypeScript files instead of the unrecognized file type. Let's run a new script to search for occurrences of the currency fields and logic in the codebase:


🏁 Script executed:

#!/bin/bash
# Searching for explicit usage of the new currency form fields in TypeScript files
echo "Searching for 'invoiceCurrency' and 'paymentCurrency' references:"
rg -A 5 "(invoiceCurrency|paymentCurrency)" -g "*.ts" -g "*.tsx"

echo "Searching for general currency occurrences:"
rg -A 5 "currency" -g "*.ts" -g "*.tsx"

Length of output: 15144


Currency Handling Consistency Verified

The changes are consistently applied across the application. Specifically:

  • The invoice form in src/components/invoice-form.tsx updates the currency fields correctly, auto-setting paymentCurrency when the selected invoice currency is not "USD" and providing a selector when it is "USD".
  • The invoice schema (src/lib/schemas/invoice.ts) validates both invoiceCurrency and paymentCurrency as required, aligning with the API requirements.
  • Currency display and conversion logic using formatCurrencyLabel and the handling in src/components/invoice-preview.tsx, src/components/invoice-table.tsx, and src/components/payment-section.tsx confirm that the updated fields flow through the presentation and transaction processes correctly.

@aimensahnoun
Copy link
Member Author

@coderabbitai review

Copy link

coderabbitai bot commented Feb 12, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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/server/routers/invoice.ts (4)

17-20: Consider using a more precise number type for financial calculations.

Using floating-point arithmetic for financial calculations can lead to precision issues.

Consider using a decimal type library like decimal.js:

-const totalAmount = input.items.reduce(
-  (acc, item) => acc + item.price * item.quantity,
-  0,
-);
+import { Decimal } from 'decimal.js';
+const totalAmount = input.items.reduce(
+  (acc, item) => acc.plus(new Decimal(item.price).times(item.quantity)),
+  new Decimal(0)
+).toString();

57-60: Enhance error handling with specific error types.

The current error handling logs the error but returns a generic failure response.

Consider categorizing errors for better client-side handling:

 } catch (error) {
   console.log("Error: ", error);
-  return { success: false };
+  if (error instanceof ApiError) {
+    return { success: false, error: 'API_ERROR', message: error.message };
+  } else if (error instanceof DatabaseError) {
+    return { success: false, error: 'DB_ERROR', message: 'Database operation failed' };
+  }
+  return { success: false, error: 'UNKNOWN_ERROR', message: 'An unexpected error occurred' };
 }

69-72: Use decimal arithmetic for payment totals.

Similar to the create method, using floating-point arithmetic for financial calculations can lead to precision issues.

-const totalPayments = invoices.reduce(
-  (acc, invoice) => acc + Number(invoice.amount),
-  0,
-);
+import { Decimal } from 'decimal.js';
+const totalPayments = invoices.reduce(
+  (acc, invoice) => acc.plus(new Decimal(invoice.amount)),
+  new Decimal(0)
+).toString();

64-67: Consider implementing pagination for better performance.

Fetching all invoices without pagination could lead to performance issues as the number of invoices grows.

-const invoices = await db.query.requestTable.findMany({
+const invoices = await db.query.requestTable.findMany({
+  take: input.limit ?? 10,
+  skip: input.offset ?? 0,
   where: eq(requestTable.userId, ctx.user?.id as string),
   orderBy: desc(requestTable.createdAt),
 });
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3540840 and 9860d98.

📒 Files selected for processing (1)
  • src/server/routers/invoice.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/server/routers/invoice.ts (1)
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/server/routers/invoice.ts:88-109
Timestamp: 2025-02-12T12:40:14.742Z
Learning: The payRequest endpoint in src/server/routers/invoice.ts is intentionally kept public (using publicProcedure) to allow invoice sharing and payment by anyone with the payment reference, similar to how payment links work in other payment systems.
🔇 Additional comments (2)
src/server/routers/invoice.ts (2)

1-9: LGTM! Well-organized imports and router setup.

All imports are properly utilized, and the router setup appropriately uses both protected and public procedures based on the requirements.


91-115: LGTM! Well-implemented payment request handler.

The implementation correctly handles the retrieval of payment transactions calldata with appropriate error handling. The public access is intentional for invoice sharing capabilities.

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 (1)
src/server/routers/invoice.ts (1)

63-84: Add pagination and safer number parsing to getAll procedure.

The current implementation loads all invoices at once and uses unsafe number parsing.

Consider these improvements:

-getAll: protectedProcedure.query(async ({ ctx }) => {
+getAll: protectedProcedure
+  .input(
+    z.object({
+      limit: z.number().min(1).max(100).default(10),
+      offset: z.number().min(0).default(0),
+    })
+  )
+  .query(async ({ ctx, input }) => {
   const { db } = ctx;
   const invoices = await db.query.requestTable.findMany({
     where: eq(requestTable.userId, ctx.user?.id as string),
     orderBy: desc(requestTable.createdAt),
+    limit: input.limit,
+    offset: input.offset,
   });

   const totalPayments = invoices.reduce(
-    (acc, invoice) => acc + Number(invoice.amount),
+    (acc, invoice) => {
+      const amount = parseFloat(invoice.amount);
+      return acc + (Number.isFinite(amount) ? amount : 0);
+    },
     0,
   );

   const outstandingInvoices = invoices.filter(
     (invoice) => invoice.status !== "paid",
   );

   return {
     invoices,
     totalPayments,
     outstandingInvoices: outstandingInvoices.length,
+    hasMore: invoices.length === input.limit,
   };
 }),
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9860d98 and 77a0ead.

📒 Files selected for processing (1)
  • src/server/routers/invoice.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/server/routers/invoice.ts (1)
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/server/routers/invoice.ts:88-109
Timestamp: 2025-02-12T12:40:14.742Z
Learning: The payRequest endpoint in src/server/routers/invoice.ts is intentionally kept public (using publicProcedure) to allow invoice sharing and payment by anyone with the payment reference, similar to how payment links work in other payment systems.
🔇 Additional comments (3)
src/server/routers/invoice.ts (3)

1-10: LGTM! Well-organized imports and router setup.

The imports are properly organized and include all necessary dependencies for tRPC, database operations, and validation.


85-99: LGTM! Well-implemented getById procedure.

The implementation includes proper error handling and follows the project's requirements for public invoice access.


100-124: LGTM! Well-designed payRequest procedure.

The implementation correctly:

  • Keeps the endpoint public to allow invoice sharing (as intended)
  • Returns payment transactions calldata instead of processing payment
  • Includes proper error handling

Copy link
Member

@MantisClone MantisClone left a comment

Choose a reason for hiding this comment

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

Approved 👍 pending comment resolution 🚧

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 (10)
src/app/invoices/[ID]/page.tsx (3)

14-18: Consider making the type properties readonly.

Since these properties are only used for display and shouldn't be modified, consider making them readonly for better type safety.

 type InvoiceItem = {
-  description: string;
-  quantity: number;
-  price: number;
+  readonly description: string;
+  readonly quantity: number;
+  readonly price: number;
 };

31-37: Move formatDate to utils for reusability.

The date formatting function could be reused across components. Consider moving it to a utils file.

Create a new file src/lib/utils/date.ts:

export const formatDate = (date: string) => {
  return new Date(date).toLocaleDateString(undefined, {
    year: "2-digit",
    month: "2-digit",
    day: "2-digit",
  });
};

179-203: Simplify amount calculations.

The subtotal and total sections show the same amount without any additional calculations. Consider combining them or clarifying why they're separate.

-                      <div className="flex justify-between py-2">
-                        <span className="text-sm text-neutral-600">
-                          Subtotal
-                        </span>
-                        <span className="text-sm">
-                          {Number(invoice.amount).toFixed(2)}
-                        </span>
-                      </div>
                      <div className="flex justify-between py-2 border-t border-neutral-200">
                        <span className="text-sm font-medium">Total</span>
                        <div>
README.md (5)

5-11: Consider adding Request Network-specific features.

Given that this PR focuses on Request API integration, consider adding features that highlight specific Request Network capabilities, such as:

  • Multi-currency support
  • Payment status tracking through Request Network
  • Secure payment processing

14-21: Enhance prerequisites with version requirements and resource links.

Consider adding:

  • PostgreSQL version requirement
  • Links to obtain Request Network API key and Reown Project ID
  • Link to Google Cloud Console for OAuth credentials setup

37-44: Improve environment variables documentation.

  1. Fix the formatting of the list by adding proper line breaks.
  2. Add descriptions for:
    • REQUEST_API_URL: What's the default value?
    • NEXT_PUBLIC_REOWN_PROJECT_ID: What is Reown and why is it needed?
🧰 Tools
🪛 LanguageTool

[uncategorized] ~39-~39: Loose punctuation mark.
Context: ...t variables in .env: - DATABASE_URL: Your PostgreSQL connection string - `GO...

(UNLIKELY_OPENING_PUNCTUATION)


82-100: Consider adding more Request Network resources.

Given the focus on Request Network integration, consider adding links to:

  • Request Network API reference
  • Request Network integration guides
  • Request Network payment flow documentation

101-108: Enhance contributing guidelines.

Consider adding:

  • Code of conduct reference
  • Step-by-step contribution process
  • Development workflow
  • Testing requirements
    Also, consider rewording "feel free to" to make it more professional, e.g., "We welcome contributions! Please submit a Pull Request."
🧰 Tools
🪛 LanguageTool

[style] ~103-~103: The phrase ‘feel free to’ is used quite frequently. Consider using a less frequent alternative to set your writing apart from others and make it sound more professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## 📝 License ...

(FEEL_FREE_TO_STYLE_ME)

drizzle/0000_chunky_albert_cleary.sql (1)

14-14: Add status field validation.

The status field should be constrained to valid values using an enum or check constraint to prevent invalid states.

+	CONSTRAINT "easyinvoice_request_status_check" CHECK (status IN ('draft', 'pending', 'paid', 'cancelled')),
package.json (1)

10-14: Consider organizing scripts by category.

The scripts could be better organized by grouping them into categories (e.g., development, database, linting).

Consider this organization:

-    "lint": "biome lint --write",
-    "db:push": "drizzle-kit push",
-    "db:studio": "drizzle-kit studio",
-    "db:generate": "drizzle-kit generate",
-    "db:migrate": "drizzle-kit migrate",
+    // Database scripts
+    "db:push": "drizzle-kit push",
+    "db:studio": "drizzle-kit studio",
+    "db:generate": "drizzle-kit generate",
+    "db:migrate": "drizzle-kit migrate",
+    // Linting and formatting
+    "lint": "biome lint --write",
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 77a0ead and a2f1844.

📒 Files selected for processing (7)
  • README.md (1 hunks)
  • drizzle/0000_chunky_albert_cleary.sql (1 hunks)
  • drizzle/meta/0000_snapshot.json (1 hunks)
  • drizzle/meta/_journal.json (1 hunks)
  • package.json (3 hunks)
  • src/app/invoices/[ID]/page.tsx (1 hunks)
  • src/lib/currencies.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • drizzle/meta/_journal.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/currencies.ts
🧰 Additional context used
🧠 Learnings (1)
src/app/invoices/[ID]/page.tsx (1)
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/app/invoices/[ID]/page.tsx:160-175
Timestamp: 2025-02-12T12:42:40.076Z
Learning: Invoice items are validated at creation time through the schema validation in src/lib/schemas/invoice.ts, ensuring that items array is non-empty and each item has required description, quantity, and price fields. Runtime validation in display components is not necessary.
🪛 LanguageTool
README.md

[uncategorized] ~39-~39: Loose punctuation mark.
Context: ...t variables in .env: - DATABASE_URL: Your PostgreSQL connection string - `GO...

(UNLIKELY_OPENING_PUNCTUATION)


[style] ~103-~103: The phrase ‘feel free to’ is used quite frequently. Consider using a less frequent alternative to set your writing apart from others and make it sound more professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## 📝 License ...

(FEEL_FREE_TO_STYLE_ME)

🔇 Additional comments (8)
src/app/invoices/[ID]/page.tsx (2)

1-12: LGTM! Clean imports and well-structured metadata.

The imports are properly organized and the metadata follows Next.js conventions.


160-175: LGTM! Type casting is safe due to schema validation.

Based on the retrieved learnings, invoice items are validated at creation time through schema validation, making the type casting safe.

README.md (2)

1-4: LGTM! Clear and informative introduction.

The title and introduction effectively communicate the project's purpose and technology stack.


46-81: LGTM! Clear and comprehensive installation instructions.

The instructions clearly differentiate between development and production setups, and provide commands for both npm and yarn.

drizzle/0000_chunky_albert_cleary.sql (2)

22-26: LGTM!

The session table schema is well-designed with appropriate data types and constraints.


28-33: LGTM!

The user table schema is well-designed for Google OAuth integration with appropriate constraints.

drizzle/meta/0000_snapshot.json (1)

1-229: LGTM!

The JSON snapshot accurately reflects the SQL schema definition.

package.json (1)

61-61: LGTM!

The version changes look good:

  • Using caret range for Biome allows for minor updates
  • dotenv-cli version is appropriate

Also applies to: 67-67

@aimensahnoun aimensahnoun merged commit 54f8ba5 into main Feb 12, 2025
5 checks passed
@aimensahnoun aimensahnoun deleted the 1-request-api---integrate-request-api-into-easyinvoice branch February 12, 2025 20:51
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.

Easy Invoice - Integrate Request API into EasyInvoice
2 participants