-
Notifications
You must be signed in to change notification settings - Fork 7
feat: implement viewing and managing of recurring payments #82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughThis 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
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
Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested reviewers
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ 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)
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
926c70c to
1cf64d1
Compare
There was a problem hiding this 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:ipis hard-coded to an empty string
iplooks 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-fororreq.socket.remoteAddressso 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
Providerinside every Tooltip instance causes an extra React subtree and resets the delay / skip-delay timers on each hover. Hoisting the provider once (e.g. inapp/layout.tsxor 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
Removingmax-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-6xlvia 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 exportingmetadatahelps 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 undefinedaddress
address?.substring(...)is safe, but string concatenation will coerceundefinedto 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 readinessRelying on
setTimeoutintroduces 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
CreateRecurringPaymentis 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
RecurringPaymentsNavigationis 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 nitType imports (
type AppRouter) placed before value imports (appRouter) is great for clarity; consider grouping allimport typelines together to keep lint happy (if@typescript-eslint/consistent-type-importsis enabled).src/server/routers/recurring-payment.ts (1)
8-11: Pagination missing – potential large result sets
findManywithoutlimit/offsetwill 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 standalonePlusicon. Wrap it in a visually-hiddenspanor providearia-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-somethingor even/blog/how-to-create-recurringif 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.writeTextcan fail (e.g. insecure context, user blocks permission).
Wrapping the call intry/catchprevents 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
substringassumes 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 pathIf a user lands on
/payouts(no subtype), none of theincludeschecks match/single,/batch, or/recurring.
Today this falls through to “single”, which is fine but implicit.
Consider redirecting/payoutsto/payouts/singleat 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).
Atry/catchwith 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 displayIf the
paymentsarray isn’t pre-sorted, slicing first can surface arbitrary items.
Sort bydate(descending) before applying theisExpandedlogic.- 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: Userel="noreferrer"when linking to external explorersAdding
noreferreralongsidenoopenerremoves theRefererheader, 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: ResetcurrentPagewhen the incoming dataset changesIf the user is on page > 1 and a refetch returns fewer items (e.g., after cancelling a payment),
currentPagemay exceedtotalPages, rendering an empty table. Resetting or clamping the page inside anuseEffecttied torecurringPayments?.lengthprevents this edge-case.Also applies to: 67-75
106-109: Pass theDateobject directly toformatDateinstead oftoString()Converting first to string discards timezone information and forces
formatDateto 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 necessaryToday both buttons are always rendered; disabling is correct, but hiding the entire pagination bar when
totalPages === 1avoids redundant UI.src/server/db/schema.ts (1)
59-67: Enum namefrequency_enummay clash with existing enumsYou already store recurrence frequency in the
requesttable 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
⛔ Files ignored due to path filters (1)
package-lock.jsonis 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 regeneratedThe addition of
@radix-ui/react-tooltipaligns with the new Tooltip component. Just remember to runnpm 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 correctSwitching to
import typekeeps 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"ifBatchPayoutis a client component
app/pages are server components by default in Next 14. IfBatchPayoutuses 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 toimport type { … }prevents theAppRoutertype 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
recurringPaymentRouteris imported and exposed asrecurringPayment—consistent with the front-end call patternapi.recurringPayment.*.Also applies to: 14-14
src/server/context.ts (1)
2-3: 👍 Type-only imports are a good callSwitching
trpc/trpcNexttoimport typekeeps 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 screensDropping
max-w-2xlmeans 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: 👍 ConvertingTRPCErrorResponseto a type-only importKeeps 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 firstw-fullis unnecessary.src/app/payouts/page.tsx (1)
9-11:return redirect()is redundant
redirect()throwsRedirectTypeand never returns; the extrareturnis 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 ofcharAt(0).toUpperCase()for presentation; no issues spotted.src/server/db/schema.ts (1)
233-238: Potential incompatibility: storingDateinside a JSON columnDrizzle will serialise
Dateto an ISO string on insert, returning it asstringon select.
Declaring the type asDatecan therefore break type-safety (toISOStringetc.). Consider usingstringconsistently or switch to atimestampcolumn if you need nativeDatehandling.
src/components/view-recurring-payments/view-recurring-payments.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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 typemetadatafor stronger compile-time safety
metadatais currently inferred as a plain object. Importing and annotating it withMetadatafromnextgives 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
SinglePayoutPageis a server component that directly renders theDirectPaymentclient component.
That’s perfectly valid, but it forces an extra RSC/Client boundary which slightly inflates the bundle.
IfSinglePayoutPageitself 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: Callredirect()directly instead of returning it
redirect()fromnext/navigationhas the return typenever; it immediately throws a Response that Next.js catches.
Usingreturn redirect(...)is harmless but a bit noisy and can confuse editors/linters about the function’s return type.
Invokingredirect()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 supportsCREATE TYPE IF NOT EXISTS; using it ensures the migration won’t error if the enum already exists. Alternatively, wrap these statements in aDOblock catchingduplicate_object.
3-16: Optimize queries and validate data types
- Add an index on
"userId"(and optionally on"status") to speed up lookups by user or status.- Storing monetary values as
textmay complicate calculations—consider usingnumericfortotalAmountPerMonth.src/app/layout.tsx (1)
41-46: Consider hoisting<Toaster />out of the tooltip provider
Toasterdoesn’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
📒 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
TheDO … EXCEPTIONblock correctly ignores duplicate constraint errors, making this migration idempotent.drizzle/meta/0006_snapshot.json (3)
361-459: Recurring payment table in snapshot
Theeasyinvoice_recurring_paymenttable, its columns, defaults, and theuserIdforeign key are accurately captured in the snapshot.
787-791: Frequency enum in snapshot
Thefrequency_enumvalues (DAILY,WEEKLY,MONTHLY,YEARLY) align with the migration script.
812-815: Recurring payment status enum in snapshot
Therecurring_payment_statusenum (pending,active,paused,completed,cancelled) matches the migration definition.src/app/layout.tsx (1)
7-7: ```shellCheck 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 -->
74e8733 to
d91bb22
Compare
There was a problem hiding this 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 -
protectedProcedureguarantees user presence.
15-15: Remove redundantand()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
protectedProcedurealready 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
?? undefinedis redundant sinceundefinedis 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
📒 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
RecurrenceFrequencyfrom the database schema is the correct approach for maintaining consistency between the validation layer and database constraints.
6-6: LGTM!The rename from
paymentFormSchematopaymentApiSchemabetter 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
payoutSchemaby 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.
src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx
Show resolved
Hide resolved
There was a problem hiding this 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_enumuses UPPERCASE values whilerecurring_payment_statususes 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: AddNOT NULL& anupdated_atcolumn.
created_atshould be non-nullable for auditability, and most tables benefit from anupdated_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 UPDATEtrigger can keepupdated_atin sync.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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
src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx
Show resolved
Hide resolved
src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx
Outdated
Show resolved
Hide resolved
src/components/view-recurring-payments/view-recurring-payments.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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
protectedProceduremiddleware 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
protectedProceduremiddleware 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()andinvalidate()which may cause redundant network requests. Consider using onlyinvalidate()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
📒 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
…ayment as cancelled
There was a problem hiding this 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
onSuccesshandler callsrefetch(), but line 122 already invalidates the cache withutils.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
📒 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
initialDatafrom 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.
Changes
New stuff
ShortAddresscomponent which display an address with...in between and can be copied to clipboard.Tooltipcomponent that used radix UIReworked
payouts/single,payouts/batch,payouts/recurring,payouts/recurring/createTesting
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:
After that check the following cases
/payoutsredirects you to/payouts/single/payouts/batch/payouts/recurringworks both via clicking the tab and going to the URL directly./payouts/recurring/create. Switching between the two subtabs should also work normally and the URL should reflect the changes.Testing creating payments:
Prerequisites:
/payouts/recurring/createand verify that it loads appkit and then shows up the form3.Immediately try to submit the form and verify that validation happens
/payouts/recurringafter it's createdView transactionshould take you to request scan.Resolves #19 and #26
Summary by CodeRabbit
New Features
Bug Fixes
Chores
Refactor