Skip to content

Conversation

@bassgeta
Copy link
Contributor

@bassgeta bassgeta commented Jun 17, 2025

Changes

New stuff

  • Database table for recurring payments
  • Component for navigating between recurring payment tabs
  • Component for viewing recurring payments and its building blocks (badge for the payment status, frequency and a list of payments for that payment)
  • Dummy section for creating new payments
  • Recurring payment API router
  • ShortAddress component which display an address with ... in between and can be copied to clipboard.
  • Tooltip component that used radix UI
  • Dumb pages for the new payout subroutes

Reworked

  • All the payout tabs are now separate pages, which is better for SEO and we can link directly to the pages
  • New routes payouts/single, payouts/batch, payouts/recurring, payouts/recurring/create

Testing

Before you check this out, please run npm run db:push.
Then you copy the contents of this file
recurring_payment_inserts.txt
into Drizzle Studio's SQL Editor, run it and verify that you get the data inserted:
image
image

After that check the following cases

  1. Going to /payouts redirects you to /payouts/single
    image
  2. The batch payouts page works and the URL should be /payouts/batch
    image
  3. Navigating to /payouts/recurring works both via clicking the tab and going to the URL directly.
    image
  4. Verify that the table looks OK and you can browse the data normally. Check out the UX with the payments array since that one was kinda winged 😅
    image
  5. Verify that you can copy the recipient's address and the toast shows up
    image
  6. The payment history links should take you to sepolia.etherscan.io but it's gonna be a 404 since it's just mock data
  7. The pause button also doesn't do anything yet :D
  8. Verify that the create new recurring payment tab also works as it should and that the URL is /payouts/recurring/create. Switching between the two subtabs should also work normally and the URL should reflect the changes.
    image

Testing creating payments:

Prerequisites:

  • Test it with this API branch.
  • With easy invoice running, create a new webhook on request API via OpenAPI docs or this cURL
curl --location 'http://localhost:8080/v1/webhook' \
--header 'Content-Type: application/json' \
--header 'Cookie: session_token=<YOUR_TOKEN>' \
--data '{
    "url" : "http://localhost:3000/api/webhook"
}'
  1. Go to /payouts/recurring/create and verify that it loads appkit and then shows up the form
    image
    image
  2. Disconnect your wallet and verify that you are prompted to connect it.
    image
    3.Immediately try to submit the form and verify that validation happens
    image
  3. Try to create a new recurring payment, do something like 1 FAU 2 times Daily
    image
  4. Verify that you get prompted for signing the transaction
    image
  5. Sign it and verify that you get redirected to /payouts/recurring after it's created
    image
  6. Wait for a few minutes, the first transaction should occur, draining 1 FAU from your wallet and the webhook should add the payment to the database. It should show up in the table. Clicking on View transaction should take you to request scan.
    image

Resolves #19 and #26

Summary by CodeRabbit

  • New Features

    • Introduced full support for recurring payments: creation, management, and detailed views with pagination.
    • Added new navigation and layout components for single, batch, and recurring payouts.
    • Implemented tooltips, address-shortening UI, and secure transaction messaging for enhanced usability.
    • Added badges indicating status and frequency for recurring payments.
    • Enabled wallet integration for creating recurring payments with blockchain transaction signing.
  • Bug Fixes

    • Improved runtime validation for required environment variables.
  • Chores

    • Added new dependencies including tooltip support.
    • Updated database schema and backend APIs to support recurring payments.
    • Refined type-only imports and adjusted code style for maintainability.
  • Refactor

    • Enhanced payment schemas and batch payout validation.
    • Reorganized payout pages and navigation for clearer user experience.
    • Replaced inline security notices with reusable components in payment forms.

@bassgeta bassgeta self-assigned this Jun 17, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 17, 2025

Walkthrough

This update introduces recurring payment management to the EasyInvoice platform. It adds database schema, backend API routes, and frontend components for creating, viewing, and managing single recurring payouts. The recurring payments table and related UI are integrated into the payouts section, with supporting schemas, navigation, and status tracking.

Changes

File(s) / Group Change Summary
drizzle/0006_open_owl.sql, drizzle/meta/0006_snapshot.json, drizzle/meta/_journal.json Add recurring payment table, enums, and update schema snapshot/journal.
src/server/db/schema.ts Add recurring payment enums, types, and table to ORM schema.
src/lib/schemas/payment.ts, src/lib/schemas/recurring-payment.ts Extend payment schema for recurring, add recurring payment creation schema/type.
src/server/routers/recurring-payment.ts, src/server/index.ts Add recurring payment router with get/create procedures to API.
src/app/payouts/recurring/page.tsx, src/app/payouts/recurring/layout.tsx, src/app/payouts/recurring/create/page.tsx Add recurring payments page, layout, and create page components.
src/components/view-recurring-payments/view-recurring-payments.tsx,
src/components/view-recurring-payments/blocks/completed-payments.tsx,
src/components/view-recurring-payments/blocks/frequency-badge.tsx,
src/components/view-recurring-payments/blocks/status-badge.tsx
Add components for viewing recurring payments, completed payments, frequency/status badges.
src/components/recurring-payments-navigation.tsx Add navigation tabs for recurring payment pages.
src/app/payouts/layout.tsx, src/app/payouts/page.tsx, src/components/payout-navigation.tsx Refactor payouts section: add layout, navigation, and route logic.
src/components/create-recurring-payment/create-recurring-payment.tsx,
src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx
Add recurring payment creation UI and form logic.
src/components/short-address.tsx, src/components/ui/tooltip.tsx, src/components/payment-secured-using-request.tsx Add utility components: short address, tooltip, secured payment notice.
src/lib/constants/currencies.ts Add recurring payment currencies/type.
src/app/api/webhook/route.ts Add webhook handler for updating recurring payment status on payment confirmation.
src/components/batch-payout.tsx, src/components/direct-payout.tsx Refactor batch/single payout schemas and UI, extract security notice.
src/components/payout-tabs.tsx Remove old payout tabs component.
src/app/layout.tsx, package.json Add Radix UI tooltip provider and dependency.
src/server/context.ts, src/trpc/react.tsx, src/trpc/server.ts, src/trpc/shared.ts Refactor imports for type-only usage and order.
drizzle.config.ts Add runtime check for DATABASE_URL, reformat.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Frontend
    participant Wallet
    participant Backend
    participant Database

    User->>Frontend: Navigate to Recurring Payments
    Frontend->>Backend: Fetch recurring payments (getRecurringPayments)
    Backend->>Database: Query recurringPaymentTable for user
    Database-->>Backend: Return recurring payments
    Backend-->>Frontend: Return recurring payments data
    Frontend-->>User: Display recurring payments table

    User->>Frontend: Initiate Create Recurring Payment
    Frontend->>Wallet: Connect wallet, sign permit, approve tx
    Frontend->>Backend: Create recurring payment (createRecurringPayment)
    Backend->>Database: Insert new recurringPaymentTable row
    Database-->>Backend: Confirm insert
    Backend-->>Frontend: Return created payment
    Frontend-->>User: Show success, redirect to view
Loading

Assessment against linked issues

Objective Addressed Explanation
Add a recurring payments table to the database (static MVP, extensible for batch) (#19)
Integrate recurring payments table into the existing Payouts page (#19)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Refactor batch/single payout schemas and UI, extract security notice (src/components/batch-payout.tsx, src/components/direct-payout.tsx) These changes are not directly related to recurring payouts management. However, they may be refactoring for consistency or code reuse.
Add utility components: short address, tooltip, secured payment notice (src/components/short-address.tsx, src/components/ui/tooltip.tsx, src/components/payment-secured-using-request.tsx) These utility components are not strictly required for recurring payout management, but may support UI enhancements.
Add runtime check for DATABASE_URL, reformat (drizzle.config.ts) Not related to recurring payouts objectives.
Refactor imports for type-only usage and order (src/server/context.ts, src/trpc/react.tsx, src/trpc/server.ts, src/trpc/shared.ts) Import style changes are unrelated to recurring payouts.
Add Radix UI tooltip provider and dependency (src/app/layout.tsx, package.json) Tooltips are a UI enhancement, not required for recurring payouts.

Possibly related PRs

Suggested reviewers

  • MantisClone
  • aimensahnoun

📜 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 d610574 and cfe1dd1.

📒 Files selected for processing (1)
  • src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate sequence diagram to generate a sequence diagram of the changes in 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.

@bassgeta bassgeta force-pushed the feat/19-manage-recurring-payments branch 3 times, most recently from 926c70c to 1cf64d1 Compare June 18, 2025 12:14
@bassgeta bassgeta marked this pull request as ready for review June 18, 2025 12:21
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: 4

🔭 Outside diff range comments (1)
src/server/context.ts (1)

12-12: ip is hard-coded to an empty string

ip looks intended to hold the requester’s address but is never populated.
Unless another middleware later enriches this field, the value will always be "", making it effectively useless.

-  const ip = "";
+  const ip =
+    ctx.req.headers["x-forwarded-for"]?.toString().split(",")[0]?.trim() ??
+    ctx.req.socket.remoteAddress ??
+    "";

Consider extracting it from x-forwarded-for or req.socket.remoteAddress so downstream logging / rate-limiting has the data it needs.

🧹 Nitpick comments (23)
src/components/ui/tooltip.tsx (1)

11-28: Provider nesting & re-mounting — hoist the <RadixTooltip.Provider>

Placing a new Provider inside every Tooltip instance causes an extra React subtree and resets the delay / skip-delay timers on each hover. Hoisting the provider once (e.g. in app/layout.tsx or a shared UI root) keeps the state consistent and avoids unnecessary DOM nodes.

-export function Tooltip({ tooltipTrigger, tooltipContent }: TooltipProps) {
-  return (
-    <RadixTooltip.Provider>
-      <RadixTooltip.Root>
+export function Tooltip({ tooltipTrigger, tooltipContent }: TooltipProps) {
+  return (
+      <RadixTooltip.Root>
 ...
-      </RadixTooltip.Root>
-    </RadixTooltip.Provider>
+      </RadixTooltip.Root>
   );
 }
src/components/batch-payout.tsx (1)

253-253: Full-width container might hurt readability on very wide screens
Removing max-w-* allows the card to stretch indefinitely on ultra-wide monitors. If the design intent is to keep the form centred with reasonable line-lengths, consider capping width again (e.g. max-w-6xl via a breakpoint).

src/app/payouts/single/page.tsx (1)

1-5: Add page-level metadata for SEO / share previews
The new route renders correctly, but exporting metadata helps Next.js set <title> and description.

+export const metadata = {
+  title: "Single Payout • Easy Invoice",
+  description: "Send a one-off payout with Easy Invoice.",
+};
src/app/payouts/recurring/page.tsx (1)

1-5: Expose metadata for the new recurring-payments page

+export const metadata = {
+  title: "Recurring Payments • Easy Invoice",
+  description: "View and manage all your scheduled payouts.",
+};

Helps search engines and improves the tab’s label in browsers.

src/components/direct-payout.tsx (2)

394-398: Guard against undefined address

address?.substring(...) is safe, but string concatenation will coerce undefined to the literal "undefined", yielding "undefined...undefined" when the wallet disconnects between renders.

-{address?.substring(0, 6)}...{address?.substring(address?.length - 4)}
+{address
+  ? `${address.substring(0, 6)}...${address.substring(address.length - 4)}`
+  : "Not connected"}

Minor, yet prevents an odd UX edge-case.


82-87: Avoid arbitrary 2 s timeout to flag AppKit readiness

Relying on setTimeout introduces race conditions (slow networks, long cold-starts).
Prefer listening to an explicit AppKit “ready”/“connected” event if available, or query its actual state in a polling loop.

src/app/payouts/recurring/create/page.tsx (1)

1-5: Consider marking the page as a client component

CreateRecurringPayment is almost certainly interactive; wrapping it inside a server component is fine, but keeping the entire file client-side avoids React ↔ Server Component boundary surprises:

+'use client';

Not mandatory—RSCs can embed client components—but adding the directive keeps mental overhead low.

src/app/payouts/recurring/layout.tsx (1)

3-14: Potential double RSC ↔ Client boundary

RecurringPaymentsNavigation is interactive (tabs). If it’s already marked 'use client', importing it inside an RSC is legal but creates an extra boundary that prevents automatic revalidation & hooks sharing.

If you expect the whole recurring payout area to be interactive, declare the layout itself as a client component:

+'use client';

Otherwise, no action needed.

src/trpc/server.ts (1)

14-14: Minor import-ordering nit

Type imports (type AppRouter) placed before value imports (appRouter) is great for clarity; consider grouping all import type lines together to keep lint happy (if @typescript-eslint/consistent-type-imports is enabled).

src/server/routers/recurring-payment.ts (1)

8-11: Pagination missing – potential large result sets
findMany without limit/offset will return every recurring payment for the user. Once the table grows this could hurt latency and memory. Recommend cursor-based or page-size-capped pagination before this goes to production.

src/components/create-recurring-payment.tsx (1)

11-14: Add accessible text for the icon
Screen-reader users will hear nothing for the standalone Plus icon. Wrap it in a visually-hidden span or provide aria-label/title.

- <Plus className="h-5 w-5" />
+ <Plus className="h-5 w-5" aria-hidden="true" />
+ <span className="sr-only">Create</span>
src/app/payouts/page.tsx (1)

4-7: Metadata description outdated
The description still references “single or batch” payouts only. Now that recurring payouts exist, update to avoid misleading search-engine snippets.

src/components/recurring-payments-navigation.tsx (1)

12-18: Consider making the pathname check stricter

pathname.includes("/create") will also match URLs such as /payouts/recurring/create-something or even /blog/how-to-create-recurring if the component is rendered in another context.
A more explicit check (e.g. pathname.endsWith("/create") or comparing against a predefined set of exact routes) would avoid accidental matches.

src/components/short-address.tsx (2)

17-20: Add error-handling around clipboard operations

navigator.clipboard.writeText can fail (e.g. insecure context, user blocks permission).
Wrapping the call in try/catch prevents an uncaught promise rejection and lets you surface a helpful toast message.

-  const handleCopy = () => {
-    navigator.clipboard.writeText(address);
-    toast.success("Address copied to clipboard");
-  };
+  const handleCopy = async () => {
+    try {
+      await navigator.clipboard.writeText(address);
+      toast.success("Address copied to clipboard");
+    } catch (err) {
+      console.error("Clipboard copy failed", err);
+      toast.error("Failed to copy address");
+    }
+  };

13-16: Guard against unexpectedly short addresses

substring assumes the address is at least 10 chars long.
For robustness, bail out (or return the raw string) if the length check fails.

src/components/payout-navigation.tsx (1)

12-20: Edge-case: “/payouts” root path

If a user lands on /payouts (no subtype), none of the includes checks match /single, /batch, or /recurring.
Today this falls through to “single”, which is fine but implicit.
Consider redirecting /payouts to /payouts/single at the route level or making that assumption explicit in a comment.

src/app/payouts/layout.tsx (1)

13-15: Handle session-fetch errors explicitly

getCurrentSession() may throw (network issues, auth service down).
A try/catch with a fallback redirect (or error page) would prevent the whole layout from crashing.

src/components/view-recurring-payments/blocks/completed-payments.tsx (2)

20-24: Sort payments before slicing to ensure newest-first display

If the payments array isn’t pre-sorted, slicing first can surface arbitrary items.
Sort by date (descending) before applying the isExpanded logic.

-  const visiblePayments = isExpanded ? payments : payments.slice(0, 2);
+  const ordered = [...payments].sort(
+    (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),
+  );
+  const visiblePayments = isExpanded ? ordered : ordered.slice(0, 2);

30-41: Use rel="noreferrer" when linking to external explorers

Adding noreferrer alongside noopener removes the Referer header, preventing the external site from seeing the origin URL.

-            rel="noopener noreferrer"
+            rel="noopener noreferrer"

(If you intentionally keep Referer, feel free to ignore.)

src/components/view-recurring-payments/view-recurring-payments.tsx (3)

27-28: Reset currentPage when the incoming dataset changes

If the user is on page > 1 and a refetch returns fewer items (e.g., after cancelling a payment), currentPage may exceed totalPages, rendering an empty table. Resetting or clamping the page inside an useEffect tied to recurringPayments?.length prevents this edge-case.

Also applies to: 67-75


106-109: Pass the Date object directly to formatDate instead of toString()

Converting first to string discards timezone information and forces formatDate to re-parse the value, adding unnecessary overhead and potential locale issues.

-? formatDate(payment.recurrence.startDate.toString())
+? formatDate(payment.recurrence.startDate)

157-180: Minor UX: disable pagination buttons only when necessary

Today both buttons are always rendered; disabling is correct, but hiding the entire pagination bar when totalPages === 1 avoids redundant UI.

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

59-67: Enum name frequency_enum may clash with existing enums

You already store recurrence frequency in the request table as a plain string. Introducing a new Postgres enum with the same semantic meaning can make future migrations harder. Confirm that a duplicate enum is intended and that all write paths use the same canonical list.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 80ffb08 and 1cf64d1.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (28)
  • drizzle.config.ts (1 hunks)
  • package.json (1 hunks)
  • src/app/payouts/batch/page.tsx (1 hunks)
  • src/app/payouts/layout.tsx (1 hunks)
  • src/app/payouts/page.tsx (1 hunks)
  • src/app/payouts/recurring/create/page.tsx (1 hunks)
  • src/app/payouts/recurring/layout.tsx (1 hunks)
  • src/app/payouts/recurring/page.tsx (1 hunks)
  • src/app/payouts/single/page.tsx (1 hunks)
  • src/components/batch-payout.tsx (1 hunks)
  • src/components/create-recurring-payment.tsx (1 hunks)
  • src/components/direct-payout.tsx (1 hunks)
  • src/components/payout-navigation.tsx (1 hunks)
  • src/components/payout-tabs.tsx (0 hunks)
  • src/components/recurring-payments-navigation.tsx (1 hunks)
  • src/components/short-address.tsx (1 hunks)
  • src/components/ui/tooltip.tsx (1 hunks)
  • src/components/view-recurring-payments/blocks/completed-payments.tsx (1 hunks)
  • src/components/view-recurring-payments/blocks/frequency-badge.tsx (1 hunks)
  • src/components/view-recurring-payments/blocks/status-badge.tsx (1 hunks)
  • src/components/view-recurring-payments/view-recurring-payments.tsx (1 hunks)
  • src/server/context.ts (1 hunks)
  • src/server/db/schema.ts (4 hunks)
  • src/server/index.ts (1 hunks)
  • src/server/routers/recurring-payment.ts (1 hunks)
  • src/trpc/react.tsx (3 hunks)
  • src/trpc/server.ts (1 hunks)
  • src/trpc/shared.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/components/payout-tabs.tsx
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build
🔇 Additional comments (14)
package.json (1)

34-34: Radix Tooltip dependency looks good — ensure lock-file is regenerated

The addition of @radix-ui/react-tooltip aligns with the new Tooltip component. Just remember to run npm i (or the project’s package-manager equivalent) so that the lock-file is updated in the same commit/PR, avoiding CI drift.

src/trpc/shared.ts (1)

1-4: Style-only import tweak is correct

Switching to import type keeps the compiled bundle lean and avoids inadvertent value imports. No further action required.

src/app/payouts/batch/page.tsx (1)

1-5: Confirm component type — needs "use client" if BatchPayout is a client component

app/ pages are server components by default in Next 14. If BatchPayout uses client-side hooks (useState, react-hook-form, etc.), this page must opt-in to the client runtime:

+ "use client";
 
 import { BatchPayout } from "@/components/batch-payout";
 
 export default function BatchPayoutSlot() {
   return <BatchPayout />;
 }

Please verify; otherwise Next.js will throw an RSC violation at build time.

src/trpc/react.tsx (2)

9-9: Type-only import is the right call
Switching to import type { … } prevents the AppRouter type from ending up in the JS bundle—good for tree-shaking and avoids a future ESM/CJS mismatch warning.


28-28: Stylistic comma tweak only
The extra trailing commas are harmless and consistent with Prettier defaults. No action required.

Also applies to: 50-50

src/server/index.ts (1)

6-6: Router wiring looks correct
recurringPaymentRouter is imported and exposed as recurringPayment—consistent with the front-end call pattern api.recurringPayment.*.

Also applies to: 14-14

src/server/context.ts (1)

2-3: 👍 Type-only imports are a good call

Switching trpc / trpcNext to import type keeps the runtime bundle leaner and avoids accidental value-level imports.

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

186-186: Full-width card may hurt readability on large screens

Dropping max-w-2xl means the payment form can now stretch beyond ~640 px.
On wide monitors this produces extremely long input rows and dotted focus outlines. Please confirm the parent layout imposes another width cap, or consider a responsive max-width:

-    <div className="flex justify-center mx-auto w-full">
+    <div className="flex justify-center mx-auto w-full max-w-[52rem]">
src/trpc/server.ts (1)

10-10: 👍 Converting TRPCErrorResponse to a type-only import

Keeps build output slimmer and matches TypeScript 5 recommendations.

src/components/create-recurring-payment.tsx (1)

8-10: Minor layout redundancy
<div className="flex … w-full max-w-6xl"> wraps a <Card className="w-full">; the first w-full is unnecessary.

src/app/payouts/page.tsx (1)

9-11: return redirect() is redundant
redirect() throws RedirectType and never returns; the extra return is harmless but superfluous.

src/components/view-recurring-payments/blocks/frequency-badge.tsx (1)

6-11: LGTM – clear colour mapping
The frequency-to-colour mapping is straightforward and type-safe.

src/components/view-recurring-payments/blocks/status-badge.tsx (1)

18-22: Capitalisation handled nicely
Good use of charAt(0).toUpperCase() for presentation; no issues spotted.

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

233-238: Potential incompatibility: storing Date inside a JSON column

Drizzle will serialise Date to an ISO string on insert, returning it as string on select.
Declaring the type as Date can therefore break type-safety (toISOString etc.). Consider using string consistently or switch to a timestamp column if you need native Date handling.

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 (6)
src/app/payouts/single/page.tsx (2)

3-6: Explicitly type metadata for stronger compile-time safety

metadata is currently inferred as a plain object. Importing and annotating it with Metadata from next gives the compiler a chance to warn if any required keys are missing or mis-typed.

+import type { Metadata } from "next";

-export const metadata = {
+export const metadata: Metadata = {
   title: "Single Payouts | Easy Invoice",
   description: "Create one time payments using Easy Invoice",
 };

7-9: Consider converting this to a client component only if needed

SinglePayoutPage is a server component that directly renders the DirectPayment client component.
That’s perfectly valid, but it forces an extra RSC/Client boundary which slightly inflates the bundle.
If SinglePayoutPage itself doesn’t do any server-only work (data fetching, auth, etc.), adding "use client" at the top and making the whole file a client component would avoid the extra boundary.

No change required—just pointing out a potential micro-optimisation.

src/app/payouts/page.tsx (1)

10-12: Call redirect() directly instead of returning it

redirect() from next/navigation has the return type never; it immediately throws a Response that Next.js catches.
Using return redirect(...) is harmless but a bit noisy and can confuse editors/linters about the function’s return type.
Invoking redirect() by itself is the idiomatic pattern shown in the docs.

 export default function PayoutsPage() {
-  return redirect("/payouts/single");
+  redirect("/payouts/single");
 }
drizzle/0006_dry_wendell_vaughn.sql (2)

1-2: Idempotent enum creation
PostgreSQL supports CREATE TYPE IF NOT EXISTS; using it ensures the migration won’t error if the enum already exists. Alternatively, wrap these statements in a DO block catching duplicate_object.


3-16: Optimize queries and validate data types

  1. Add an index on "userId" (and optionally on "status") to speed up lookups by user or status.
  2. Storing monetary values as text may complicate calculations—consider using numeric for totalAmountPerMonth.
src/app/layout.tsx (1)

41-46: Consider hoisting <Toaster /> out of the tooltip provider

Toaster doesn’t use tooltip context, so nesting it adds an unnecessary re-render whenever tooltip settings change.

-          <TooltipProvider>
-            <TRPCReactProvider cookies={cookies().toString()}>
-              <BackgroundWrapper>{children}</BackgroundWrapper>
-            </TRPCReactProvider>
-            <Toaster />
-          </TooltipProvider>
+          <TooltipProvider>
+            <TRPCReactProvider cookies={cookies().toString()}>
+              <BackgroundWrapper>{children}</BackgroundWrapper>
+            </TRPCReactProvider>
+          </TooltipProvider>
+          <Toaster />
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1cf64d1 and 74e8733.

📒 Files selected for processing (15)
  • drizzle.config.ts (1 hunks)
  • drizzle/0006_dry_wendell_vaughn.sql (1 hunks)
  • drizzle/meta/0006_snapshot.json (1 hunks)
  • drizzle/meta/_journal.json (1 hunks)
  • src/app/layout.tsx (2 hunks)
  • src/app/payouts/batch/page.tsx (1 hunks)
  • src/app/payouts/page.tsx (1 hunks)
  • src/app/payouts/recurring/create/page.tsx (1 hunks)
  • src/app/payouts/recurring/layout.tsx (1 hunks)
  • src/app/payouts/recurring/page.tsx (1 hunks)
  • src/app/payouts/single/page.tsx (1 hunks)
  • src/components/ui/tooltip.tsx (1 hunks)
  • src/components/view-recurring-payments/view-recurring-payments.tsx (1 hunks)
  • src/server/db/schema.ts (4 hunks)
  • src/server/routers/recurring-payment.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 (9)
  • src/app/payouts/recurring/layout.tsx
  • src/components/ui/tooltip.tsx
  • drizzle.config.ts
  • src/app/payouts/batch/page.tsx
  • src/server/routers/recurring-payment.ts
  • src/app/payouts/recurring/page.tsx
  • src/app/payouts/recurring/create/page.tsx
  • src/components/view-recurring-payments/view-recurring-payments.tsx
  • src/server/db/schema.ts
🧰 Additional context used
🪛 GitHub Actions: Build and Lint
src/app/layout.tsx

[error] 1-1: Module not found: Can't resolve '@radix-ui/react-tooltip'. Build failed due to missing module dependency.

🔇 Additional comments (5)
drizzle/0006_dry_wendell_vaughn.sql (1)

18-22: Graceful foreign key constraint handling
The DO … EXCEPTION block correctly ignores duplicate constraint errors, making this migration idempotent.

drizzle/meta/0006_snapshot.json (3)

361-459: Recurring payment table in snapshot
The easyinvoice_recurring_payment table, its columns, defaults, and the userId foreign key are accurately captured in the snapshot.


787-791: Frequency enum in snapshot
The frequency_enum values (DAILY, WEEKLY, MONTHLY, YEARLY) align with the migration script.


812-815: Recurring payment status enum in snapshot
The recurring_payment_status enum (pending, active, paused, completed, cancelled) matches the migration definition.

src/app/layout.tsx (1)

7-7: ```shell

Check if @radix-ui/react-tooltip is recorded in package-lock.json

grep -R '"@radix-ui/react-tooltip":' -n package-lock.json


</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

@bassgeta bassgeta force-pushed the feat/19-manage-recurring-payments branch from 74e8733 to d91bb22 Compare July 1, 2025 14:29
@bassgeta bassgeta changed the title feat: implement viewing of recurring payments feat: implement viewing and managing of recurring payments Jul 7, 2025
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: 4

♻️ Duplicate comments (3)
src/server/routers/recurring-payment.ts (2)

27-29: Remove redundant authentication check.

Same as above - protectedProcedure guarantees user presence.


15-15: Remove redundant and() wrapper.

Per the past review comment, the and() wrapper with a single condition is unnecessary.

-      where: and(eq(recurringPaymentTable.userId, user.id)),
+      where: eq(recurringPaymentTable.userId, user.id),
src/server/routers/payment.ts (1)

87-92: Remove redundant authentication check.

The protectedProcedure already ensures user authentication.

-      if (!user) {
-        throw new TRPCError({
-          code: "UNAUTHORIZED",
-          message: "You must be logged in to submit signature",
-        });
-      }
🧹 Nitpick comments (3)
src/app/api/webhook/route.ts (1)

62-62: Use safer array access method.

Consider using .at(0) with a non-null assertion or destructuring for better TypeScript safety.

-    const recurringPayment = recurringPayments[0];
+    const recurringPayment = recurringPayments.at(0)!;
src/server/routers/recurring-payment.ts (1)

55-55: Use safer array access after insert.

Consider using .at(0) with proper error handling for the returning result.

-      return recurringPayment[0];
+      const result = recurringPayment.at(0);
+      if (!result) {
+        throw new Error("Failed to create recurring payment");
+      }
+      return result;
src/server/routers/payment.ts (1)

28-28: Remove redundant nullish coalescing.

The ?? undefined is redundant since undefined is already the default value for missing properties.

-        recurrence: input.recurrence ?? undefined,
+        recurrence: input.recurrence,
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d91bb22 and 78ab42d.

📒 Files selected for processing (14)
  • src/app/api/webhook/route.ts (3 hunks)
  • src/app/payouts/recurring/create/page.tsx (1 hunks)
  • src/components/batch-payout.tsx (3 hunks)
  • src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx (1 hunks)
  • src/components/create-recurring-payment/create-recurring-payment.tsx (1 hunks)
  • src/components/direct-payout.tsx (5 hunks)
  • src/components/payment-secured-using-request.tsx (1 hunks)
  • src/components/view-recurring-payments/blocks/completed-payments.tsx (1 hunks)
  • src/lib/constants/currencies.ts (1 hunks)
  • src/lib/schemas/payment.ts (2 hunks)
  • src/lib/schemas/recurring-payment.ts (1 hunks)
  • src/server/db/schema.ts (4 hunks)
  • src/server/routers/payment.ts (4 hunks)
  • src/server/routers/recurring-payment.ts (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • src/lib/schemas/recurring-payment.ts
  • src/components/payment-secured-using-request.tsx
  • src/lib/constants/currencies.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/app/payouts/recurring/create/page.tsx
  • src/components/view-recurring-payments/blocks/completed-payments.tsx
  • src/components/batch-payout.tsx
  • src/components/direct-payout.tsx
  • src/server/db/schema.ts
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#83
File: src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx:127-138
Timestamp: 2025-06-23T09:14:42.979Z
Learning: In the RequestNetwork/easy-invoice codebase, when suggesting improvements like error handling for transaction approvals, the user bassgeta prefers consistency over isolated improvements. They prefer not to implement error handling in one place unless it's done consistently across all similar transaction flows in the codebase.
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#45
File: src/components/invoice-form.tsx:316-319
Timestamp: 2025-05-19T13:00:48.790Z
Learning: The handleFormSubmit function in src/components/invoice-form.tsx correctly uses data.clientEmail from the form submission data to find matching payers, which is the proper implementation.
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.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#67
File: src/server/routers/payment.ts:47-49
Timestamp: 2025-06-04T12:02:39.411Z
Learning: In `src/server/routers/payment.ts`, the batchPay input validation already handles empty arrays correctly. The `batchPaymentFormSchema.shape.payouts.optional()` inherits the `.min(1, "At least one payment is required")` validation from the original schema, so empty payouts arrays are automatically rejected even when the field is made optional.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#8
File: src/app/invoice-me/page.tsx:21-21
Timestamp: 2025-02-14T12:48:42.125Z
Learning: In the easy-invoice codebase, error handling for TRPC queries is not required at the component level as confirmed by the maintainer.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/server/routers/invoice.ts:0-0
Timestamp: 2025-02-12T13:04:46.135Z
Learning: The `payRequest` function in the invoice router retrieves payment transactions calldata from the Request API and does not process the payment itself. The actual payment status update happens through the webhook handler.
src/server/routers/recurring-payment.ts (6)
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.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/server/routers/invoice.ts:0-0
Timestamp: 2025-02-12T13:04:46.135Z
Learning: The `payRequest` function in the invoice router retrieves payment transactions calldata from the Request API and does not process the payment itself. The actual payment status update happens through the webhook handler.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#67
File: src/server/routers/payment.ts:47-49
Timestamp: 2025-06-04T12:02:39.411Z
Learning: In `src/server/routers/payment.ts`, the batchPay input validation already handles empty arrays correctly. The `batchPaymentFormSchema.shape.payouts.optional()` inherits the `.min(1, "At least one payment is required")` validation from the original schema, so empty payouts arrays are automatically rejected even when the field is made optional.
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#83
File: src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx:127-138
Timestamp: 2025-06-23T09:14:42.979Z
Learning: In the RequestNetwork/easy-invoice codebase, when suggesting improvements like error handling for transaction approvals, the user bassgeta prefers consistency over isolated improvements. They prefer not to implement error handling in one place unless it's done consistently across all similar transaction flows in the codebase.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#8
File: src/app/invoice-me/page.tsx:21-21
Timestamp: 2025-02-14T12:48:42.125Z
Learning: In the easy-invoice codebase, error handling for TRPC queries is not required at the component level as confirmed by the maintainer.
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#45
File: src/server/routers/compliance.ts:441-484
Timestamp: 2025-05-19T08:47:38.063Z
Learning: In the payment details authorization logic, payees (users who created invoices/requests) should be allowed to access the payment details associated with their payers, in addition to the payment details owners and authorized payers. The payee is identified by the userId field in the requestTable.
src/server/routers/payment.ts (4)
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#67
File: src/server/routers/payment.ts:47-49
Timestamp: 2025-06-04T12:02:39.411Z
Learning: In `src/server/routers/payment.ts`, the batchPay input validation already handles empty arrays correctly. The `batchPaymentFormSchema.shape.payouts.optional()` inherits the `.min(1, "At least one payment is required")` validation from the original schema, so empty payouts arrays are automatically rejected even when the field is made optional.
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.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/server/routers/invoice.ts:0-0
Timestamp: 2025-02-12T13:04:46.135Z
Learning: The `payRequest` function in the invoice router retrieves payment transactions calldata from the Request API and does not process the payment itself. The actual payment status update happens through the webhook handler.
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#45
File: src/components/invoice-form.tsx:316-319
Timestamp: 2025-05-19T13:00:48.790Z
Learning: The handleFormSubmit function in src/components/invoice-form.tsx correctly uses data.clientEmail from the form submission data to find matching payers, which is the proper implementation.
src/components/create-recurring-payment/create-recurring-payment.tsx (2)
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#64
File: src/components/batch-payout.tsx:100-106
Timestamp: 2025-06-04T10:08:40.123Z
Learning: In src/components/batch-payout.tsx, the user prefers to keep the simple 2-second timeout for AppKit initialization over more complex polling mechanisms when the current approach is working adequately. They favor simplicity over potentially more robust but complex solutions.
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#45
File: src/components/invoice-form.tsx:316-319
Timestamp: 2025-05-19T13:00:48.790Z
Learning: The handleFormSubmit function in src/components/invoice-form.tsx correctly uses data.clientEmail from the form submission data to find matching payers, which is the proper implementation.
src/app/api/webhook/route.ts (5)
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/server/routers/invoice.ts:0-0
Timestamp: 2025-02-12T13:04:46.135Z
Learning: The `payRequest` function in the invoice router retrieves payment transactions calldata from the Request API and does not process the payment itself. The actual payment status update happens through the webhook handler.
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.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/middleware.ts:23-26
Timestamp: 2025-02-12T12:41:52.713Z
Learning: The project has only one webhook endpoint (/api/webhook/payment) and uses a broad path matching pattern (startsWith("/api/webhook")) in the middleware, which is intentional.
Learnt from: MantisClone
PR: RequestNetwork/easy-invoice#59
File: src/app/api/webhook/route.ts:77-95
Timestamp: 2025-05-22T18:19:12.366Z
Learning: For webhook handlers in the Easy Invoice project, unknown or unexpected subStatus values in payment processing should be treated as errors (using console.error) rather than warnings, and should return a 422 Unprocessable Entity status code.
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#45
File: src/components/invoice-form.tsx:451-453
Timestamp: 2025-05-20T12:59:44.665Z
Learning: In the Easy Invoice project, setTimeout is required when submitting a form after modal state changes in the crypto-to-fiat payment flow. Directly calling handleFormSubmit without setTimeout after closing modals and updating state causes issues.
src/lib/schemas/payment.ts (4)
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#67
File: src/server/routers/payment.ts:47-49
Timestamp: 2025-06-04T12:02:39.411Z
Learning: In `src/server/routers/payment.ts`, the batchPay input validation already handles empty arrays correctly. The `batchPaymentFormSchema.shape.payouts.optional()` inherits the `.min(1, "At least one payment is required")` validation from the original schema, so empty payouts arrays are automatically rejected even when the field is made optional.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#23
File: src/components/direct-payment.tsx:343-360
Timestamp: 2025-02-27T14:02:35.047Z
Learning: For the DirectPayment component, amount validation is already handled through Zod schema validation in paymentFormSchema, which ensures the amount is greater than zero.
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#45
File: src/components/invoice-form.tsx:316-319
Timestamp: 2025-05-19T13:00:48.790Z
Learning: The handleFormSubmit function in src/components/invoice-form.tsx correctly uses data.clientEmail from the form submission data to find matching payers, which is the proper implementation.
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.
src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx (3)
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#45
File: src/components/invoice-form.tsx:316-319
Timestamp: 2025-05-19T13:00:48.790Z
Learning: The handleFormSubmit function in src/components/invoice-form.tsx correctly uses data.clientEmail from the form submission data to find matching payers, which is the proper implementation.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#67
File: src/server/routers/payment.ts:47-49
Timestamp: 2025-06-04T12:02:39.411Z
Learning: In `src/server/routers/payment.ts`, the batchPay input validation already handles empty arrays correctly. The `batchPaymentFormSchema.shape.payouts.optional()` inherits the `.min(1, "At least one payment is required")` validation from the original schema, so empty payouts arrays are automatically rejected even when the field is made optional.
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#45
File: src/components/invoice-form.tsx:451-453
Timestamp: 2025-05-20T12:59:44.665Z
Learning: In the Easy Invoice project, setTimeout is required when submitting a form after modal state changes in the crypto-to-fiat payment flow. Directly calling handleFormSubmit without setTimeout after closing modals and updating state causes issues.
🧬 Code Graph Analysis (3)
src/server/routers/recurring-payment.ts (4)
src/server/trpc.ts (2)
  • router (11-11)
  • protectedProcedure (39-39)
src/server/db/index.ts (1)
  • db (10-12)
src/server/db/schema.ts (1)
  • recurringPaymentTable (218-253)
src/lib/schemas/recurring-payment.ts (1)
  • createRecurringPaymentSchema (10-27)
src/app/api/webhook/route.ts (3)
src/server/db/index.ts (1)
  • db (10-12)
src/server/db/schema.ts (1)
  • recurringPaymentTable (218-253)
src/lib/errors.ts (1)
  • ResourceNotFoundError (1-6)
src/lib/schemas/payment.ts (1)
src/server/db/schema.ts (1)
  • RecurrenceFrequency (59-64)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (8)
src/components/create-recurring-payment/create-recurring-payment.tsx (1)

20-26: Clean implementation of AppKit initialization.

Good use of the 2-second timeout pattern, which aligns with the established pattern in the codebase for AppKit initialization.

src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx (1)

169-172: Good use of setTimeout pattern.

Properly using setTimeout before navigation, which aligns with the established pattern in the codebase for handling state changes after form submission.

src/lib/schemas/payment.ts (6)

2-2: LGTM!

Importing RecurrenceFrequency from the database schema is the correct approach for maintaining consistency between the validation layer and database constraints.


6-6: LGTM!

The rename from paymentFormSchema to paymentApiSchema better reflects its broader usage across API endpoints, not just forms.


16-46: Comprehensive recurring payment validation is well-designed.

The recurrence validation logic is thorough and handles key edge cases:

  • Start date validation prevents past dates with proper day normalization
  • Frequency validation uses the imported enum for consistency
  • Total executions range (2-256) includes reasonable bounds with contract limit documentation
  • Payer validation maintains consistency with existing address validation

The optional nature allows the schema to support both single and recurring payments.


49-49: LGTM!

Type name change is consistent with the schema rename.


51-53: Good separation of concerns.

Creating payoutSchema by omitting the recurrence field provides a clean way to handle non-recurring payments without duplicating validation logic.


55-67: Robust batch payment validation with proper constraints.

The schema design effectively handles the complexity of batch payments:

  • Array bounds (1-10) provide reasonable operational limits
  • Optional payouts and requestIds with refinement validation ensures at least one is provided
  • The refinement logic correctly uses logical OR to enforce the "either/or" requirement

This approach is more user-friendly than making both fields required, as it provides clear error messaging when neither is provided.

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 (2)
drizzle/0006_confused_sage.sql (2)

1-2: Inconsistent enum casing; pick one style.

frequency_enum uses UPPERCASE values while recurring_payment_status uses lowercase. Mixing styles is error-prone and hurts readability; align both (prefer uppercase for enum literals).

-CREATE TYPE "public"."recurring_payment_status" AS ENUM('pending', 'active', 'paused', 'completed', 'cancelled');
+CREATE TYPE "public"."recurring_payment_status" AS ENUM('PENDING', 'ACTIVE', 'PAUSED', 'COMPLETED', 'CANCELLED');

12-13: Add NOT NULL & an updated_at column.

created_at should be non-nullable for auditability, and most tables benefit from an updated_at (with trigger) for optimistic concurrency & troubleshooting.

-  "created_at" timestamp DEFAULT now(),
+  "created_at" timestamp NOT NULL DEFAULT now(),
+  "updated_at" timestamp NOT NULL DEFAULT now(),

A simple BEFORE UPDATE trigger can keep updated_at in sync.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 78ab42d and fe5977d.

📒 Files selected for processing (7)
  • drizzle/0006_confused_sage.sql (1 hunks)
  • drizzle/meta/0006_snapshot.json (1 hunks)
  • drizzle/meta/_journal.json (1 hunks)
  • src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx (1 hunks)
  • src/lib/schemas/payment.ts (2 hunks)
  • src/lib/schemas/recurring-payment.ts (1 hunks)
  • src/server/routers/recurring-payment.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • drizzle/meta/_journal.json
  • src/server/routers/recurring-payment.ts
  • src/lib/schemas/recurring-payment.ts
  • drizzle/meta/0006_snapshot.json
  • src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx
  • src/lib/schemas/payment.ts
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#83
File: src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx:127-138
Timestamp: 2025-06-23T09:14:42.979Z
Learning: In the RequestNetwork/easy-invoice codebase, when suggesting improvements like error handling for transaction approvals, the user bassgeta prefers consistency over isolated improvements. They prefer not to implement error handling in one place unless it's done consistently across all similar transaction flows in the codebase.
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#45
File: src/components/invoice-form.tsx:316-319
Timestamp: 2025-05-19T13:00:48.790Z
Learning: The handleFormSubmit function in src/components/invoice-form.tsx correctly uses data.clientEmail from the form submission data to find matching payers, which is the proper implementation.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#8
File: src/app/i/[id]/page.tsx:18-22
Timestamp: 2025-02-13T21:57:39.869Z
Learning: The invoice me links in the easy-invoice project do not have an expiration mechanism. The invoiceMeTable schema only includes id, label, userId, and createdAt fields.
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#29
File: src/components/invoice-form.tsx:98-103
Timestamp: 2025-03-03T22:21:37.028Z
Learning: For recurring invoices in the easy-invoice application, the frequency field should not have a default value as the developer wants to enforce users to explicitly choose a value.
drizzle/0006_confused_sage.sql (2)
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#29
File: src/components/invoice-form.tsx:98-103
Timestamp: 2025-03-03T22:21:37.028Z
Learning: For recurring invoices in the easy-invoice application, the frequency field should not have a default value as the developer wants to enforce users to explicitly choose a value.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#8
File: src/app/i/[id]/page.tsx:18-22
Timestamp: 2025-02-13T21:57:39.869Z
Learning: The invoice me links in the easy-invoice project do not have an expiration mechanism. The invoiceMeTable schema only includes id, label, userId, and createdAt fields.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build

@MantisClone MantisClone linked an issue Jul 10, 2025 that may be closed by this pull request
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

♻️ Duplicate comments (2)
src/server/routers/recurring-payment.ts (2)

26-29: Remove redundant authentication check.

The protectedProcedure middleware already ensures the user is authenticated, making this check unnecessary.

-    // Probably not happening, but let's make TS happy
-    if (!user || !user.id) {
-      throw new Error("User not authenticated");
-    }

43-45: Remove redundant authentication check.

Same issue as above - the protectedProcedure middleware guarantees user authentication.

-      if (!user || !user.id) {
-        throw new Error("User not authenticated");
-      }
🧹 Nitpick comments (4)
src/server/routers/recurring-payment.ts (1)

143-158: Add request timeout and better error context.

External API calls should have timeouts and provide more specific error information for debugging.

      const response = await apiClient.patch(
        `v2/payouts/recurring/${input.externalPaymentId}`,
        {
          action: input.action,
        },
+       {
+         timeout: 10000, // 10 second timeout
+       }
      );

      if (response.status < 200 || response.status >= 300) {
        throw new TRPCError({
          code: "INTERNAL_SERVER_ERROR",
-         message: "Failed to update recurring payment",
+         message: `Failed to update recurring payment: ${response.status} ${response.statusText}`,
        });
      }
src/components/view-recurring-payments/view-recurring-payments.tsx (3)

87-88: Update to ethers v6 provider pattern.

The current code uses the deprecated ethers v5 provider pattern. Consider updating to ethers v6 for better compatibility and future-proofing.

-      const ethersProvider = new ethers.providers.Web3Provider(walletProvider);
-      const signer = ethersProvider.getSigner();
+      const ethersProvider = new ethers.BrowserProvider(walletProvider);
+      const signer = await ethersProvider.getSigner();

268-272: Add currency display for total amount.

The total amount should include the currency for better clarity, consistent with the recipient amount display.

                    <TableCell>
                      <span className="text-sm font-bold">
-                        {payment.totalAmount}
+                        {payment.totalAmount} {payment.paymentCurrency}
                      </span>
                    </TableCell>

123-128: Optimize database invalidation.

The component calls both refetch() and invalidate() which may cause redundant network requests. Consider using only invalidate() for better performance.

-      await utils.recurringPayment.getRecurringPayments.invalidate();
-      toast.success("Recurring payment cancelled successfully");
+      await utils.recurringPayment.getRecurringPayments.invalidate();

Note: Remove the duplicate success toast since line 52 already shows this message.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 781c599 and cd78c6a.

📒 Files selected for processing (2)
  • src/components/view-recurring-payments/view-recurring-payments.tsx (1 hunks)
  • src/server/routers/recurring-payment.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#83
File: src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx:127-138
Timestamp: 2025-06-23T09:14:42.979Z
Learning: In the RequestNetwork/easy-invoice codebase, when suggesting improvements like error handling for transaction approvals, the user bassgeta prefers consistency over isolated improvements. They prefer not to implement error handling in one place unless it's done consistently across all similar transaction flows in the codebase.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#82
File: src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx:131-131
Timestamp: 2025-07-11T11:17:32.618Z
Learning: In the RequestNetwork/easy-invoice codebase, the user aimensahnoun prefers to keep recurring payment functionality limited to Sepolia only. Hardcoding the chain to "sepolia" in recurring payment components is intentional and acceptable, rather than making it dynamic based on the connected network.
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#45
File: src/components/invoice-form.tsx:316-319
Timestamp: 2025-05-19T13:00:48.790Z
Learning: The handleFormSubmit function in src/components/invoice-form.tsx correctly uses data.clientEmail from the form submission data to find matching payers, which is the proper implementation.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#8
File: src/app/i/[id]/page.tsx:18-22
Timestamp: 2025-02-13T21:57:39.869Z
Learning: The invoice me links in the easy-invoice project do not have an expiration mechanism. The invoiceMeTable schema only includes id, label, userId, and createdAt fields.
src/server/routers/recurring-payment.ts (7)
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.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/server/routers/invoice.ts:0-0
Timestamp: 2025-02-12T13:04:46.135Z
Learning: The `payRequest` function in the invoice router retrieves payment transactions calldata from the Request API and does not process the payment itself. The actual payment status update happens through the webhook handler.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#82
File: src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx:131-131
Timestamp: 2025-07-11T11:17:32.618Z
Learning: In the RequestNetwork/easy-invoice codebase, the user aimensahnoun prefers to keep recurring payment functionality limited to Sepolia only. Hardcoding the chain to "sepolia" in recurring payment components is intentional and acceptable, rather than making it dynamic based on the connected network.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#67
File: src/server/routers/payment.ts:47-49
Timestamp: 2025-06-04T12:02:39.411Z
Learning: In `src/server/routers/payment.ts`, the batchPay input validation already handles empty arrays correctly. The `batchPaymentFormSchema.shape.payouts.optional()` inherits the `.min(1, "At least one payment is required")` validation from the original schema, so empty payouts arrays are automatically rejected even when the field is made optional.
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#83
File: src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx:127-138
Timestamp: 2025-06-23T09:14:42.979Z
Learning: In the RequestNetwork/easy-invoice codebase, when suggesting improvements like error handling for transaction approvals, the user bassgeta prefers consistency over isolated improvements. They prefer not to implement error handling in one place unless it's done consistently across all similar transaction flows in the codebase.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#8
File: src/app/invoice-me/page.tsx:21-21
Timestamp: 2025-02-14T12:48:42.125Z
Learning: In the easy-invoice codebase, error handling for TRPC queries is not required at the component level as confirmed by the maintainer.
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#45
File: src/server/routers/compliance.ts:441-484
Timestamp: 2025-05-19T08:47:38.063Z
Learning: In the payment details authorization logic, payees (users who created invoices/requests) should be allowed to access the payment details associated with their payers, in addition to the payment details owners and authorized payers. The payee is identified by the userId field in the requestTable.
src/components/view-recurring-payments/view-recurring-payments.tsx (5)
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#45
File: src/components/invoice-form.tsx:316-319
Timestamp: 2025-05-19T13:00:48.790Z
Learning: The handleFormSubmit function in src/components/invoice-form.tsx correctly uses data.clientEmail from the form submission data to find matching payers, which is the proper implementation.
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.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#8
File: src/app/invoice-me/page.tsx:21-21
Timestamp: 2025-02-14T12:48:42.125Z
Learning: In the easy-invoice codebase, error handling for TRPC queries is not required at the component level as confirmed by the maintainer.
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#83
File: src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx:127-138
Timestamp: 2025-06-23T09:14:42.979Z
Learning: In the RequestNetwork/easy-invoice codebase, when suggesting improvements like error handling for transaction approvals, the user bassgeta prefers consistency over isolated improvements. They prefer not to implement error handling in one place unless it's done consistently across all similar transaction flows in the codebase.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#64
File: src/components/batch-payout.tsx:100-106
Timestamp: 2025-06-04T10:08:40.123Z
Learning: In src/components/batch-payout.tsx, the user prefers to keep the simple 2-second timeout for AppKit initialization over more complex polling mechanisms when the current approach is working adequately. They favor simplicity over potentially more robust but complex solutions.
🧬 Code Graph Analysis (2)
src/server/routers/recurring-payment.ts (5)
src/server/trpc.ts (2)
  • router (11-11)
  • protectedProcedure (39-39)
src/server/db/index.ts (1)
  • db (10-12)
src/server/db/schema.ts (2)
  • recurringPaymentTable (218-253)
  • RecurringPaymentStatus (70-76)
src/lib/schemas/recurring-payment.ts (1)
  • createRecurringPaymentSchema (10-27)
src/lib/axios.ts (1)
  • apiClient (3-8)
src/components/view-recurring-payments/view-recurring-payments.tsx (9)
src/server/db/schema.ts (1)
  • RecurringPayment (346-346)
src/components/ui/card.tsx (4)
  • Card (79-79)
  • CardContent (79-79)
  • CardHeader (79-79)
  • CardTitle (79-79)
src/components/ui/button.tsx (1)
  • Button (56-56)
src/components/ui/table.tsx (6)
  • Table (109-109)
  • TableHeader (110-110)
  • TableRow (114-114)
  • TableHead (113-113)
  • TableBody (111-111)
  • TableCell (115-115)
src/lib/date-utils.ts (1)
  • formatDate (9-17)
src/components/view-recurring-payments/blocks/status-badge.tsx (1)
  • StatusBadge (18-24)
src/components/view-recurring-payments/blocks/frequency-badge.tsx (1)
  • FrequencyBadge (17-23)
src/components/short-address.tsx (1)
  • ShortAddress (12-37)
src/components/view-recurring-payments/blocks/completed-payments.tsx (1)
  • CompletedPayments (14-68)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build

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/components/view-recurring-payments/view-recurring-payments.tsx (1)

51-58: Consider removing redundant refetch call.

The onSuccess handler calls refetch(), but line 122 already invalidates the cache with utils.recurringPayment.getRecurringPayments.invalidate(). The invalidation is sufficient and more efficient.

  const updateRecurringPaymentMutation =
    api.recurringPayment.updateRecurringPayment.useMutation({
      onSuccess: () => {
        toast.success("Recurring payment cancelled successfully");
-       refetch();
      },
      onError: (error) => {
        toast.error(`Failed to cancel recurring payment: ${error.message}`);
      },
    });
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between cd78c6a and d610574.

📒 Files selected for processing (3)
  • src/app/payouts/recurring/page.tsx (1 hunks)
  • src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx (1 hunks)
  • src/components/view-recurring-payments/view-recurring-payments.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#83
File: src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx:127-138
Timestamp: 2025-06-23T09:14:42.979Z
Learning: In the RequestNetwork/easy-invoice codebase, when suggesting improvements like error handling for transaction approvals, the user bassgeta prefers consistency over isolated improvements. They prefer not to implement error handling in one place unless it's done consistently across all similar transaction flows in the codebase.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#82
File: src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx:131-131
Timestamp: 2025-07-11T11:17:32.618Z
Learning: In the RequestNetwork/easy-invoice codebase, the user aimensahnoun prefers to keep recurring payment functionality limited to Sepolia only. Hardcoding the chain to "sepolia" in recurring payment components is intentional and acceptable, rather than making it dynamic based on the connected network.
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#45
File: src/components/invoice-form.tsx:316-319
Timestamp: 2025-05-19T13:00:48.790Z
Learning: The handleFormSubmit function in src/components/invoice-form.tsx correctly uses data.clientEmail from the form submission data to find matching payers, which is the proper implementation.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#8
File: src/app/i/[id]/page.tsx:18-22
Timestamp: 2025-02-13T21:57:39.869Z
Learning: The invoice me links in the easy-invoice project do not have an expiration mechanism. The invoiceMeTable schema only includes id, label, userId, and createdAt fields.
src/app/payouts/recurring/page.tsx (3)
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.
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#45
File: src/components/invoice-form.tsx:316-319
Timestamp: 2025-05-19T13:00:48.790Z
Learning: The handleFormSubmit function in src/components/invoice-form.tsx correctly uses data.clientEmail from the form submission data to find matching payers, which is the proper implementation.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#64
File: src/components/batch-payout.tsx:100-106
Timestamp: 2025-06-04T10:08:40.123Z
Learning: In src/components/batch-payout.tsx, the user prefers to keep the simple 2-second timeout for AppKit initialization over more complex polling mechanisms when the current approach is working adequately. They favor simplicity over potentially more robust but complex solutions.
src/components/view-recurring-payments/view-recurring-payments.tsx (6)
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#45
File: src/components/invoice-form.tsx:316-319
Timestamp: 2025-05-19T13:00:48.790Z
Learning: The handleFormSubmit function in src/components/invoice-form.tsx correctly uses data.clientEmail from the form submission data to find matching payers, which is the proper implementation.
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.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#8
File: src/app/invoice-me/page.tsx:21-21
Timestamp: 2025-02-14T12:48:42.125Z
Learning: In the easy-invoice codebase, error handling for TRPC queries is not required at the component level as confirmed by the maintainer.
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#83
File: src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx:127-138
Timestamp: 2025-06-23T09:14:42.979Z
Learning: In the RequestNetwork/easy-invoice codebase, when suggesting improvements like error handling for transaction approvals, the user bassgeta prefers consistency over isolated improvements. They prefer not to implement error handling in one place unless it's done consistently across all similar transaction flows in the codebase.
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#82
File: src/server/routers/recurring-payment.ts:143-148
Timestamp: 2025-07-11T12:56:11.323Z
Learning: The `v2/payouts/recurring/{externalPaymentId}` PATCH endpoint in the RequestNetwork/easy-invoice project specifically returns a 200 status code on success or an error status code - it does not return other 2xx success codes like 201 or 204. The status check `response.status !== 200` is correct for this specific endpoint.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#64
File: src/components/batch-payout.tsx:100-106
Timestamp: 2025-06-04T10:08:40.123Z
Learning: In src/components/batch-payout.tsx, the user prefers to keep the simple 2-second timeout for AppKit initialization over more complex polling mechanisms when the current approach is working adequately. They favor simplicity over potentially more robust but complex solutions.
🧬 Code Graph Analysis (2)
src/app/payouts/recurring/page.tsx (7)
src/app/payouts/recurring/create/page.tsx (1)
  • metadata (3-6)
src/app/layout.tsx (1)
  • metadata (24-27)
src/app/payouts/batch/page.tsx (1)
  • metadata (3-6)
src/app/payouts/page.tsx (1)
  • metadata (4-8)
src/app/payouts/single/page.tsx (1)
  • metadata (3-6)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/components/view-recurring-payments/view-recurring-payments.tsx (1)
  • ViewRecurringPayments (35-356)
src/components/view-recurring-payments/view-recurring-payments.tsx (9)
src/server/db/schema.ts (1)
  • RecurringPayment (346-346)
src/components/ui/card.tsx (4)
  • Card (79-79)
  • CardContent (79-79)
  • CardHeader (79-79)
  • CardTitle (79-79)
src/components/ui/button.tsx (1)
  • Button (56-56)
src/components/ui/table.tsx (6)
  • Table (109-109)
  • TableHeader (110-110)
  • TableRow (114-114)
  • TableHead (113-113)
  • TableBody (111-111)
  • TableCell (115-115)
src/lib/date-utils.ts (1)
  • formatDate (9-17)
src/components/view-recurring-payments/blocks/status-badge.tsx (1)
  • StatusBadge (18-24)
src/components/view-recurring-payments/blocks/frequency-badge.tsx (1)
  • FrequencyBadge (17-23)
src/components/short-address.tsx (1)
  • ShortAddress (12-37)
src/components/view-recurring-payments/blocks/completed-payments.tsx (1)
  • CompletedPayments (14-68)
🔇 Additional comments (6)
src/app/payouts/recurring/page.tsx (1)

1-21: LGTM! Well-structured server component.

The implementation follows Next.js best practices with proper authentication checks, server-side data fetching, and client-side hydration using initial data. The metadata is appropriately defined and the code structure is clean.

src/components/view-recurring-payments/view-recurring-payments.tsx (5)

45-47: LGTM! Proper use of initial data for server-side hydration.

The use of initialData from props enables efficient server-side hydration, avoiding unnecessary loading states on initial render.


149-181: LGTM! Comprehensive error handling with retry functionality.

The error state implementation provides clear feedback to users and includes a retry mechanism with proper loading states. This addresses previous concerns about error handling.


250-256: LGTM! Proper date formatting with timezone handling.

The date formatting now correctly uses new Date().toISOString() which addresses previous timezone concerns and ensures consistent date display.


107-114: LGTM! Proper transaction error handling.

The transaction error handling is well-implemented with a clear comment explaining that transaction failures are expected (reducing spending cap) and don't affect the backend cancellation. This provides good user experience by not blocking the process on transaction failures.


327-351: LGTM! Well-implemented pagination controls.

The pagination logic correctly handles edge cases with proper button disabling and clear page indicators. The conditional rendering based on total items is efficient.

@aimensahnoun aimensahnoun self-requested a review July 11, 2025 13:20
@bassgeta bassgeta merged commit 6ce2b9c into main Jul 11, 2025
5 checks passed
@bassgeta bassgeta deleted the feat/19-manage-recurring-payments branch July 11, 2025 13:58
This was referenced Aug 20, 2025
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 - Create Single Recurring Payouts EasyInvoice - Manage Existing Single Recurring Payouts

3 participants