Skip to content

Conversation

@rodrigopavezi
Copy link
Member

@rodrigopavezi rodrigopavezi commented Mar 3, 2025

Changes

  • Add recurring invoice support with recurrence details. Fixes EasyInvoice - Recurring Invoices UI #16
  • Add webhook handling for showing the recurring invoices in the dashboard
  • Add stop recurrence feature

NOTE: I moved the webhook route from /webhook/payment to just /webhook. That will require us to update our webhook endpoint in the api when releasing this to production

Screen.Recording.2025-03-03.at.23.02.09.mov

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Introduced recurring invoice functionality: Users can now mark invoices as recurring and specify details such as start date and frequency options.
    • Enhanced invoice displays to clearly show recurring invoice information, including frequency and start dates.
    • Added a new checkbox component for streamlined selection when setting an invoice as recurring.
    • New JSON schema defined for managing invoice-related data effectively.
    • Added support for processing recurring requests through webhook events.
    • New functionality to stop recurring payments directly from the invoice table.
  • Bug Fixes

    • Improved error handling for recurring invoice validation in forms.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 3, 2025

Walkthrough

This pull request implements recurring invoice support across the system. It adds a new JSON column (recurrence) to the easyinvoice_request table, updates the PostgreSQL schema and migration journal, and expands the invoice form and API to handle recurring invoice data (including start date and frequency). UI components such as invoice pages, previews, and tables are enhanced to display recurrence details. Additionally, a new reusable Checkbox component is introduced via a Radix UI dependency.

Changes

Files Change Summary
drizzle/0002_clean_sage.sql, drizzle/meta/0002_snapshot.json, drizzle/meta/_journal.json, src/server/db/schema.ts Added a new recurrence JSON field to the easyinvoice_request table, updated the PostgreSQL schema definition, and inserted a new journal entry for the migration.
package.json, src/components/ui/checkbox.tsx Added the dependency @radix-ui/react-checkbox and introduced a new Checkbox component built with Radix UI.
src/app/invoices/[ID]/page.tsx, src/components/invoice-preview.tsx, src/components/invoice-table.tsx Enhanced UI components to conditionally render recurring invoice details, such as frequency and a formatted start date.
src/components/invoice-form.tsx, src/lib/schemas/invoice.ts, src/server/routers/invoice.ts Integrated recurring invoice functionality by adding a checkbox and extra form fields (isRecurring, startDate, frequency) along with API updates to include recurrence data when creating an invoice.
drizzle/0003_hesitant_dormammu.sql, drizzle/0004_thankful_deathstrike.sql Added a new column originalRequestPaymentReference and a boolean column isRecurrenceStopped to the easyinvoice_request table.
src/app/api/webhook/route.ts, src/app/api/webhook/payment/route.ts Introduced a new POST endpoint for handling webhook requests, replacing the previous payment webhook endpoint which was removed.

Possibly related PRs

Suggested reviewers

  • MantisClone
  • sstefdev

📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8ca3e9a and 0e35fdd.

📒 Files selected for processing (2)
  • src/app/invoices/[ID]/page.tsx (2 hunks)
  • src/components/invoice-table.tsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/invoice-table.tsx
  • src/app/invoices/[ID]/page.tsx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

Copy link
Contributor

@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 (9)
src/server/db/schema.ts (1)

73-76: Consider enhancing type definition with documentation and enum for frequency

The JSON type definition with TypeScript typing is well-structured. To improve maintainability and developer experience, consider adding:

  1. Documentation comments explaining the expected format for startDate (e.g., ISO 8601)
  2. An enum type for frequency to restrict valid values (e.g., 'weekly', 'monthly', 'quarterly', 'annually')
  recurrence: json().$type<{
    startDate: string;
-    frequency: string;
+    frequency: string; // 'weekly' | 'monthly' | 'quarterly' | 'annually'
  }>(),
src/components/invoice-form.tsx (2)

26-26: Consider moving RecurringFrequency type to a shared types file.

This type definition would be more maintainable if placed in a shared types file, especially since it likely needs to match values expected by both the API and database schema.

-type RecurringFrequency = "DAILY" | "WEEKLY" | "MONTHLY" | "YEARLY";

Consider adding this type to a shared location, such as src/lib/types.ts:

// src/lib/types.ts
export type RecurringFrequency = "DAILY" | "WEEKLY" | "MONTHLY" | "YEARLY";

And then importing it:

import { RecurringFrequency } from "@/lib/types";

84-121: Add validation to ensure start date is not in the past.

The conditional fields for recurring invoices are well-implemented, but there's no validation to ensure the start date is not in the past, which could lead to confusion or errors for users.

Consider adding validation for the start date, either in the form component or in the invoice schema:

<Input {...form.register("startDate")} type="date" />
+{/* Add client-side validation to complement schema validation */}
+{new Date(form.watch("startDate") || "") < new Date() && !form.formState.errors.startDate && (
+  <p className="text-sm text-amber-500">
+    Start date should be today or in the future
+  </p>
+)}
src/server/routers/invoice.ts (1)

56-61: Add validation for recurrence data consistency.

The implementation correctly handles the recurrence data for database storage, but there's no validation to ensure the data is consistent (e.g., if isRecurring is true, both startDate and frequency must be present).

Consider adding validation to ensure data consistency:

recurrence: input.isRecurring
  ? {
+     // Ensure both fields are present when isRecurring is true
+     startDate: input.startDate || new Date().toISOString().split('T')[0],
+     frequency: input.frequency || "MONTHLY",
      startDate: input.startDate,
      frequency: input.frequency,
    }
  : null,

Alternatively, add validation at the schema level to ensure these fields are required when isRecurring is true.

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

51-63: Standardize date formatting approach.

The preview component uses two different date formatting approaches: the local formatDate function for due date and date-fns format for recurrence start date. This inconsistency could lead to maintenance issues and visual inconsistencies.

Consider standardizing the date formatting approach:

{data.startDate && (
  <span>
-    • Starting {format(new Date(data.startDate), "do MMM yyyy")}
+    • Starting {formatDate(data.startDate)}
  </span>
)}

Or update the formatDate function to use date-fns for all dates:

const formatDate = (date: string) => {
-  return new Date(date).toLocaleDateString("en-GB", {
-    year: "2-digit",
-    month: "2-digit",
-    day: "2-digit",
-  });
+  return format(new Date(date), "dd/MM/yy");
};

54-56: Add accessibility improvement for the recurrence symbol.

The "↻" symbol used for recurring invoices might not be properly announced by screen readers. Consider adding an appropriate ARIA label or alternative text.

<div className="text-sm flex items-center gap-1">
-  <span>↻ {data.frequency.toLowerCase()}</span>
+  <span aria-label={`Recurring ${data.frequency.toLowerCase()}`}>
+    <span aria-hidden="true">↻</span> {data.frequency.toLowerCase()}
+  </span>
  {data.startDate && (
src/components/ui/checkbox.tsx (1)

3-7: Consider adding unit tests for the Checkbox component.

As this is a reusable UI component, it would benefit from having unit tests to ensure its behavior is consistent across refactors and updates.

Would you like me to generate unit tests for this component using a testing framework like Jest and React Testing Library?

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

231-235: Simplify optional chaining in frequency display

There's inconsistent use of optional chaining in the recurrence frequency display. If you're already checking for invoice.recurrence on line 231, there's no need for the additional optional chaining on the frequency property.

- <span className="text-xs text-zinc-500 flex items-center gap-1">
-   ↻ {invoice.recurrence?.frequency?.toLowerCase()}
- </span>
+ <span className="text-xs text-zinc-500 flex items-center gap-1">
+   ↻ {invoice.recurrence.frequency.toLowerCase()}
+ </span>

251-256: Simplify optional chaining in start date display

Similarly, there's inconsistent use of optional chaining in the start date display. If you're already checking for invoice?.recurrence?.startDate on line 251, you shouldn't need repeated optional chaining when accessing it again.

- {invoice?.recurrence?.startDate && (
-   <span className="text-xs text-zinc-500">
-     from{" "}
-     {format(new Date(invoice?.recurrence?.startDate), "do MMM yyyy")}
-   </span>
- )}
+ {invoice.recurrence?.startDate && (
+   <span className="text-xs text-zinc-500">
+     from{" "}
+     {format(new Date(invoice.recurrence.startDate), "do MMM yyyy")}
+   </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 179eb86 and 85606ff.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (12)
  • drizzle/0002_clean_sage.sql (1 hunks)
  • drizzle/meta/0002_snapshot.json (1 hunks)
  • drizzle/meta/_journal.json (1 hunks)
  • package.json (1 hunks)
  • src/app/invoices/[ID]/page.tsx (2 hunks)
  • src/components/invoice-form.tsx (3 hunks)
  • src/components/invoice-preview.tsx (3 hunks)
  • src/components/invoice-table.tsx (2 hunks)
  • src/components/ui/checkbox.tsx (1 hunks)
  • src/lib/schemas/invoice.ts (1 hunks)
  • src/server/db/schema.ts (1 hunks)
  • src/server/routers/invoice.ts (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build
🔇 Additional comments (13)
drizzle/0002_clean_sage.sql (1)

1-1: Good implementation of the recurrence feature with JSON type

Using JSON as the column type for recurrence is a good choice for storing structured data with a flexible schema. This approach allows for storing both the start date and frequency properties while maintaining the ability to extend this structure in the future if needed.

package.json (1)

26-26: Good choice of component library for checkbox functionality

Using Radix UI's checkbox component is a solid decision as it provides excellent accessibility features, keyboard navigation support, and consistent UI behavior. This aligns well with implementing the recurring invoice toggle in the invoice form.

drizzle/meta/_journal.json (1)

18-25: Migration journal entry correctly structured

The journal entry for the new migration follows the proper format and sequence. The timestamp and tag match the expected pattern for Drizzle migrations, and this will ensure proper tracking of the database schema changes.

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

73-76:

Details

✅ Verification successful

Verify integration with Request Network API for recurring invoices

Make sure the recurring invoice feature is fully supported by the Request Network API. If the API doesn't natively support recurring invoices, document how this feature works as a UI/application layer enhancement.


🌐 Web query:

Does Request Network API support recurring invoices?

💡 Result:

Yes, the Request Network API supports recurring invoices through its infrastructure designed for Web3 financial operations. The system enables automated recurring billing with features tailored for decentralized applications and blockchain-based payments.

Key capabilities include:

  • Recurrence Scheduling: Users can set up invoices to regenerate at specified intervals (monthly, weekly, etc.) without manual intervention[3][7]. The API provides a scheduled status to indicate future invoice generation[7].

  • Payment Automation: Integration with Superfluid Protocol allows streaming payments where funds are transferred incrementally over time (e.g., per block) to match predetermined recurring amounts[1][6]. This enables:

    • Continuous salary distributions for remote teams
    • Subscription-based revenue models
    • DAO contributor payouts
  • Workflow Integration:

    • Batch Payments: Users can manually approve grouped recurring invoices in one click[1].
    • API Endpoints: Dedicated endpoints like POST /api/v1/recurring_invoices allow programmatic creation and management[4][9].

Technical Implementation:

  1. Invoices use a predefined schema validated by the API[3][7].
  2. Recurring invoices transition through statuses like openscheduledpaid via polling mechanisms[7].
  3. Encryption ensures on-chain data privacy for recurring transactions[3][6].

The system has processed $900M+ across 3,000+ companies, demonstrating scalability for Web3 recurring billing needs[2][6]. Partners like Superfluid enhance functionality with gasless streaming payments that auto-generate invoices until manually stopped[1][6].

Citations:


Confirmed Request Network Integration for Recurring Invoices

  • The Request Network API natively supports recurring invoices through scheduled billing and automated payment features.
  • The recurring invoice schema in src/server/db/schema.ts (lines 73-76) is appropriate. No additional UI/application layer work is required on our end.
src/components/invoice-form.tsx (2)

4-4: LGTM: Proper import of the new Checkbox component.

The Checkbox component is correctly imported from the UI component library.


73-82: LGTM: Well-implemented checkbox for recurring invoice toggle.

The Checkbox implementation with proper label and state handling looks good. The component is correctly connected to the form state.

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

25-30: LGTM: Clean conditional inclusion of recurrence data.

The conditional spread operator pattern is a clean way to include optional properties in the API request when needed.


54-55:

Details

❓ Verification inconclusive

Verify error handling for recurring invoices.

The code doesn't specifically handle cases where API request creation might fail due to invalid recurrence data. Make sure the API client and error handling logic can properly deal with recurrence-related errors.

Also applies to: 57-61


🏁 Script executed:

#!/bin/bash
# Check if the API client has error handling for recurrence data

# Look for error handling specific to recurrence in the API client
rg -A 5 "recurrence.*error|error.*recurrence" src/lib/axios.ts

# Check how errors from the API are handled
rg -A 10 "catch.*api|error.*response" --type ts

Length of output: 114


Below is an updated review comment that asks for additional manual verification before final approval. I've provided extra shell script commands to search for any recurrence-specific error handling in both the invoice router and API client files. Please run these extra commands and inspect whether error handling for invalid recurrence data is implemented appropriately.


Action required: Verify recurrence error handling in both the invoice router and API client.

It appears that our initial search (using the provided regex) did not turn up any recurrence-related error handling in the API client (in src/lib/axios.ts). To further investigate, please run the following shell script commands to search for occurrences of “recurrence” and associated error handling logic:

#!/bin/bash
echo "Searching for recurrence usage in the invoice router:"
rg -n "recurrence" src/server/routers/invoice.ts

echo "Searching for try/catch blocks with potential recurrence handling in the invoice router:"
rg -n "try|catch" src/server/routers/invoice.ts | grep -i "recurrence"

echo "Searching for recurrence usage in the axios client:"
rg -n "recurrence" src/lib/axios.ts

echo "Searching for catch blocks mentioning recurrence in the axios client:"
rg -n "catch" src/lib/axios.ts | grep -i "recurrence"

Once you confirm whether the codebase properly handles API request failures due to invalid recurrence data, please update the error handling as needed.

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

4-4: LGTM: Added date-fns import for better date formatting.

The import of the date-fns format function is appropriate for the new date formatting needs.

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

1-31: Well-implemented Checkbox component with proper accessibility considerations.

The Checkbox component is well-structured using Radix UI primitives, which provides good accessibility support. The component correctly:

  • Forwards refs using React.forwardRef
  • Sets the appropriate displayName for debugging
  • Uses comprehensive styling for different states (focus, checked, disabled)
  • Properly handles component composition with the CheckboxIndicator

15-18:

Details

✅ Verification successful

Verify the color contrast of checkbox states.

Ensure that the color contrast of the checkbox in different states (especially checked vs unchecked) meets WCAG accessibility standards.


🏁 Script executed:

#!/bin/bash
# Try to find the theme colors to check contrast

# Look for primary color definition in the theme
rg -A 3 "primary.*:" src/
rg -A 3 "colors.*primary" src/

# Look for themed CSS variables
rg -A 5 "primary(-foreground)?" src/

Length of output: 6534


Contrast Verification: WCAG Standards Passed
The theme definitions in src/app/globals.css show that the checkbox's checked state colors—using bg-primary and text-primary-foreground—are set to nearly inverse values (one instance using a dark primary paired with a light primary-foreground and the reverse in another). These pairings inherently provide a very high contrast ratio (approaching that of black versus white), and should therefore meet WCAG accessibility standards for contrast.

No changes are needed regarding the checkbox color contrast.

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

84-104: Implementation of recurring invoice display looks good!

The conditional rendering for recurring invoice information is implemented well. You're correctly checking for the existence of the recurrence data before accessing its properties, and the date formatting provides a nice user experience.

drizzle/meta/0002_snapshot.json (1)

186-191: Database schema for recurring invoices looks appropriate

The addition of the recurrence column as a JSON field is a good design choice for storing the recurring invoice data. Making it nullable is appropriate since not all invoices will have recurrence information.

rodrigopavezi and others added 2 commits March 3, 2025 23:22
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@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 (3)
src/app/api/webhook/route.ts (3)

49-83: Set appropriate status for newly inserted recurring requests.
When creating a new request from the original, the status is inherited from ...requestWithoutId (which may be “paid” or another terminal state). Consider resetting it to a default value (e.g., "pending").

Example fix:

await tx.insert(requestTable).values({
  id: ulid(),
  issuedDate: now.toISOString(),
  ...requestWithoutId,
+ status: "pending", // or another default
  dueDate: newDueDate.toISOString(),
  paymentReference: paymentReference,
  originalRequestPaymentReference: originalRequestPaymentReference,
});

85-87: Return a 400 response for unrecognized events.
Silently ignoring invalid event types can mask potential integration issues. Consider returning an error and logging the event type.

Example fix:

default:
+  return NextResponse.json({ error: "Unknown event type" }, { status: 400 });

90-96: Avoid logging full error details if they may contain sensitive info.
Consider filtering sensitive data from the error logs or using a more secure logging mechanism to prevent possible PII leakage in logs.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1e1b6ad and 02f5a08.

📒 Files selected for processing (7)
  • drizzle/0003_hesitant_dormammu.sql (1 hunks)
  • drizzle/meta/0003_snapshot.json (1 hunks)
  • drizzle/meta/_journal.json (1 hunks)
  • src/app/api/webhook/payment/route.ts (0 hunks)
  • src/app/api/webhook/route.ts (1 hunks)
  • src/lib/schemas/invoice.ts (1 hunks)
  • src/server/db/schema.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/app/api/webhook/payment/route.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/server/db/schema.ts
  • drizzle/meta/_journal.json
  • src/lib/schemas/invoice.ts
🔇 Additional comments (5)
drizzle/0003_hesitant_dormammu.sql (1)

1-1: Schema Column Addition Check
The SQL statement correctly adds the "originalRequestPaymentReference" column to the "easyinvoice_request" table as a text field. Since no NOT NULL constraint is enforced, existing records will remain unaffected.

drizzle/meta/0003_snapshot.json (3)

167-172: Field Definition: originalRequestPaymentReference
The "originalRequestPaymentReference" field is properly defined as a text column and is nullable, which is in line with the migration in the SQL file. Ensure that the application logic that consumes this field handles NULL values appropriately.


192-197: New Recurrence Column Inclusion
The addition of the "recurrence" field (of type JSON) enables storing structured recurrence data for recurring invoices. Please confirm that all supporting application layers (e.g., API and UI components) adequately manage serialization/deserialization and potential NULL values for this field.


1-318: Comprehensive Schema Snapshot Review
The snapshot file provides a complete view of the current database schema, including the recent additions (both "originalRequestPaymentReference" and "recurrence") along with the definitions for other tables. It’s important to ensure that this snapshot remains consistent with the ongoing migration history, especially with regard to the newly introduced recurring invoice functionality.

src/app/api/webhook/route.ts (1)

28-28: Validate presence of required fields in the request body.
The code destructures properties (paymentReference, event, etc.) without verifying they exist. If any field is absent or malformed, subsequent statements may fail.

Copy link
Contributor

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

216-251: The stopRecurrence procedure is well-implemented

The implementation of the stopRecurrence procedure is solid:

  1. It correctly takes the payment reference as input
  2. Makes an API request to stop the recurrence externally
  3. Updates the local database to reflect the stopped recurrence
  4. Has proper error handling for both API failures and database issues
  5. Returns the updated invoice

A minor improvement could be to check if the recurrence is already stopped before making the API call.

Consider adding a check to prevent unnecessary API calls if the recurrence is already stopped:

  stopRecurrence: publicProcedure
    .input(
      z.object({
        paymentReference: z.string(),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      const { paymentReference } = input;

+     // Check if recurrence is already stopped
+     const existingInvoice = await ctx.db.query.requestTable.findFirst({
+       where: eq(requestTable.paymentReference, paymentReference),
+     });
+
+     if (!existingInvoice) {
+       throw new TRPCError({
+         code: "NOT_FOUND",
+         message: "Invoice with this payment reference not found",
+       });
+     }
+
+     if (existingInvoice.isRecurrenceStopped) {
+       return existingInvoice; // Recurrence already stopped, no need for API call
+     }

      const request = await apiClient.patch(
        `/v1/request/${paymentReference}/stop-recurrence`,
      );

      if (request.status !== 200) {
        throw new TRPCError({
          code: "BAD_REQUEST",
          message: "Failed to stop recurrence",
        });
      }
      const updatedInvoice = await ctx.db
        .update(requestTable)
        .set({
          isRecurrenceStopped: true,
        })
        .where(eq(requestTable.paymentReference, paymentReference))
        .returning();

      if (!updatedInvoice.length) {
        throw new TRPCError({
          code: "NOT_FOUND",
          message: "Invoice with this payment reference not found",
        });
      }

      return updatedInvoice[0];
    }),
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 02f5a08 and 8ca3e9a.

📒 Files selected for processing (6)
  • drizzle/0004_thankful_deathstrike.sql (1 hunks)
  • drizzle/meta/0004_snapshot.json (1 hunks)
  • drizzle/meta/_journal.json (1 hunks)
  • src/components/invoice-table.tsx (5 hunks)
  • src/server/db/schema.ts (2 hunks)
  • src/server/routers/invoice.ts (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • drizzle/0004_thankful_deathstrike.sql
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/invoice-table.tsx
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build
🔇 Additional comments (13)
src/server/db/schema.ts (4)

5-5: Import of boolean type added correctly

The boolean type import is correctly added to support the new isRecurrenceStopped field.


75-78: Add validation for recurrence data

The recurrence field is defined as a JSON type with specific structure requirements. However, consider adding stronger validation either at the database level or in your application code to ensure:

  1. startDate is a valid date string
  2. frequency contains only allowed values (e.g., DAILY, WEEKLY, MONTHLY, YEARLY)

79-79: Flag for tracking stopped recurrences is well-defined

The isRecurrenceStopped field is a good design choice as it:

  1. Allows filtering of active vs. stopped recurring invoices
  2. Uses a default value (false) to ensure consistent behavior
  3. Complements the recurrence field to provide a complete picture of the invoice status

67-67:

Details

❓ Verification inconclusive

Ensure backward compatibility for originalRequestPaymentReference

The new originalRequestPaymentReference field correctly uses the nullable text() type. However, consider what will happen to existing records when this migration is applied.


🏁 Script executed:

#!/bin/bash
# Check for any usage of originalRequestPaymentReference field
echo "Checking for usage of originalRequestPaymentReference field"
rg "originalRequestPaymentReference" --type ts -A 3 -B 3 src/

Length of output: 2208


Ensure Backward Compatibility for the originalRequestPaymentReference Field

After verifying, we see that the originalRequestPaymentReference field is used in several parts of the application (notably in src/app/api/webhook/route.ts). Since this field now uses a nullable text() type, please review the following points:

  • Usage in Webhook Route: Ensure that all parts of the webhook logic gracefully handle cases where originalRequestPaymentReference might be null (e.g., in query conditions and error messages).
  • Legacy Data Consideration: Confirm that the migration strategy accounts for existing records that might not have a value for this field. If needed, consider adding a data migration or default value to avoid unexpected behavior.

Please verify that the current handling is sufficient to prevent runtime issues with legacy data.

drizzle/meta/_journal.json (3)

19-25: Migration for recurrence field is properly registered

The migration entry for "0002_clean_sage" correctly tracks the addition of the recurrence field to the database schema.


26-32: Migration for originalRequestPaymentReference is properly registered

The migration entry for "0003_hesitant_dormammu" correctly tracks the addition of the originalRequestPaymentReference field.


33-39: Migration for isRecurrenceStopped is properly registered

The migration entry for "0004_thankful_deathstrike" correctly tracks the addition of the isRecurrenceStopped field.

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

25-30: Properly conditional recurrence data in API request

Good implementation using the spread operator to conditionally include recurrence data in the API request only when isRecurring is true.


56-61: Database insert handles recurrence fields correctly

The implementation correctly:

  1. Conditionally sets recurrence data based on isRecurring flag
  2. Maintains the same structure between the API request and database storage
  3. Sets to null when not a recurring invoice
drizzle/meta/0004_snapshot.json (4)

167-172: originalRequestPaymentReference field is correctly defined

The field definition in the snapshot correctly matches the schema definition:

  • It's defined as nullable text
  • Maintains consistency with the schema.ts definition

192-197: recurrence field is correctly defined in snapshot

The recurrence field is properly defined as a JSON type in the snapshot, consistent with its definition in the schema.


198-204: isRecurrenceStopped field has correct default value

The isRecurrenceStopped field correctly:

  • Uses boolean type
  • Sets a default value of false
  • Is defined as nullable

1-325:

Details

✅ Verification successful

Verify database snapshot consistency

The DB snapshot correctly includes all the new fields across tables. However, it's important to verify that the actual database state matches this snapshot after migrations run.


🏁 Script executed:

#!/bin/bash
# Look for any migration scripts that might be related to these changes
echo "--- Checking migration scripts ---"
ls -la drizzle/0002_*.sql
ls -la drizzle/0003_*.sql
ls -la drizzle/0004_*.sql

# Check content of migration scripts to verify they align with schema changes
echo -e "\n--- Migration script for recurrence ---"
cat drizzle/0002_*.sql
echo -e "\n--- Migration script for originalRequestPaymentReference ---"
cat drizzle/0003_*.sql
echo -e "\n--- Migration script for isRecurrenceStopped ---"
cat drizzle/0004_*.sql

Length of output: 1149


Database Snapshot and Migration Scripts Consistency Verified

The migration scripts clearly reflect the changes present in the snapshot. Specifically:

  • drizzle/0002_clean_sage.sql: Adds the "recurrence" column to easyinvoice_request.
  • drizzle/0003_hesitant_dormammu.sql: Adds the "originalRequestPaymentReference" column.
  • drizzle/0004_thankful_deathstrike.sql: Adds the "isRecurrenceStopped" column with a default of false.

All added fields in the snapshot (in drizzle/meta/0004_snapshot.json) match the migration scripts, confirming that the database state should align with the snapshot after migration.

@rodrigopavezi rodrigopavezi merged commit 4dd99bb into main Mar 6, 2025
5 checks passed
@rodrigopavezi rodrigopavezi deleted the feat/recurring-invoices branch March 6, 2025 12:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EasyInvoice - Recurring Invoices UI

3 participants