Skip to content

feat: implement viewing 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

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

Conversation

bassgeta
Copy link

@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

Resolves #19

Summary by CodeRabbit

  • New Features

    • Introduced recurring payments functionality with creation, management, and viewing capabilities.
    • Added navigation components for payouts and recurring payments.
    • Implemented badges for payment frequency and status display.
    • Added tooltip and short address components for enhanced usability.
    • Added paginated table and payment history view for recurring payments.
  • Improvements

    • Updated layouts and navigation for payouts, including batch, single, and recurring payout pages.
    • Removed maximum width constraints from certain payout components for a more flexible layout.
  • Bug Fixes

    • None.
  • Chores

    • Added @radix-ui/react-tooltip dependency.
    • Minor import and formatting adjustments for code consistency.
    • Added tooltip provider wrapping main app layout.
  • Refactor

    • Replaced /payouts page with redirect to /payouts/single.
    • Removed old payout tabs component in favor of new navigation and layout.
  • Documentation

    • None.
  • Tests

    • None.
  • Revert

    • None.

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

coderabbitai bot commented Jun 17, 2025

Walkthrough

This update introduces a recurring payments feature, adding new database schema, server-side router, and UI components for managing and displaying recurring payments. It restructures the payouts section with new layouts and navigation, removes the previous payout tabs component, and adds supporting UI elements such as tooltips and address shortening. Minor import style and dependency updates are also included.

Changes

File(s) Change Summary
drizzle.config.ts Added runtime validation for DATABASE_URL; reformatted config export.
package.json Added dependency: @radix-ui/react-tooltip@^1.2.7.
src/app/payouts/layout.tsx Added new authenticated layout for payouts section with header, navigation, and footer.
src/app/payouts/page.tsx Changed to immediate redirect to /payouts/single, removing previous UI and session logic.
src/app/payouts/batch/page.tsx, src/app/payouts/recurring/create/page.tsx, src/app/payouts/recurring/page.tsx, src/app/payouts/single/page.tsx Added new page components as wrappers for payout-related features.
src/app/payouts/recurring/layout.tsx Added layout for recurring payments section with navigation.
src/components/batch-payout.tsx, src/components/direct-payout.tsx Removed max-width constraint from outer container divs.
src/components/create-recurring-payment.tsx New component for recurring payment creation UI.
src/components/payout-navigation.tsx New tabbed navigation component for payout types.
src/components/payout-tabs.tsx Removed previous payout tabs component.
src/components/recurring-payments-navigation.tsx New navigation component for recurring payments section.
src/components/short-address.tsx New component for displaying/copying shortened Ethereum addresses.
src/components/ui/tooltip.tsx New tooltip component using Radix UI.
src/components/view-recurring-payments/blocks/completed-payments.tsx New component to display a list of completed recurring payments.
src/components/view-recurring-payments/blocks/frequency-badge.tsx New badge component for recurrence frequency.
src/components/view-recurring-payments/blocks/status-badge.tsx New badge component for payment status.
src/components/view-recurring-payments/view-recurring-payments.tsx New paginated table component for viewing recurring payments.
src/server/context.ts Changed imports to type-only imports for trpc modules.
src/server/db/schema.ts Added enums, types, and table for recurring payments; exported relevant types.
src/server/index.ts Added recurringPaymentRouter to main app router.
src/server/routers/recurring-payment.ts New router exposing a query for user recurring payment requests.
src/trpc/react.tsx, src/trpc/server.ts, src/trpc/shared.ts Changed imports to type-only imports and reordered imports; no logic changes.
drizzle/0006_dry_wendell_vaughn.sql Added migration for recurring payments enums and table with constraints.
drizzle/meta/0006_snapshot.json Added full database schema snapshot including recurring payments.
drizzle/meta/_journal.json Added new journal entry for migration 0006.
src/app/layout.tsx Wrapped main app content with Radix UI TooltipProvider.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as RecurringPayments UI
    participant TRPC as TRPC Client
    participant Server
    participant DB

    User->>UI: Navigates to /payouts/recurring
    UI->>TRPC: trpc.recurringPayment.getRecurringRequests()
    TRPC->>Server: getRecurringRequests (with user session)
    Server->>DB: Query recurringPaymentTable by userId
    DB-->>Server: Recurring payment records
    Server-->>TRPC: Return payment data
    TRPC-->>UI: Payment data
    UI-->>User: Render paginated recurring payments table
Loading

Possibly related PRs

Suggested reviewers

  • rodrigopavezi
  • MantisClone
✨ 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 from 5629c11 to 480266b Compare June 18, 2025 10:43
@bassgeta bassgeta force-pushed the feat/19-manage-recurring-payments branch from 480266b to 15bbf01 Compare June 18, 2025 10:44
@bassgeta bassgeta force-pushed the feat/19-manage-recurring-payments branch 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.

Comment on lines 8 to 11
const recurringRequests = await db.query.recurringPaymentTable.findMany({
where: and(eq(recurringPaymentTable.userId, user?.id as string)),
orderBy: desc(recurringPaymentTable.createdAt),
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider removing redundant and() wrapper & assert user.id presence
and() with a single eq() clause adds no value and slightly clutters the query; Drizzle accepts the bare eq() condition.
Also, user is guaranteed by protectedProcedure, so the non-null assertion can be avoided in favour of an explicit runtime guard (throw 401) or by refining the procedure’s input type to make user.id non-nullable.

- where: and(eq(recurringPaymentTable.userId, user?.id as string)),
+ where: eq(recurringPaymentTable.userId, user.id),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const recurringRequests = await db.query.recurringPaymentTable.findMany({
where: and(eq(recurringPaymentTable.userId, user?.id as string)),
orderBy: desc(recurringPaymentTable.createdAt),
});
const recurringRequests = await db.query.recurringPaymentTable.findMany({
where: eq(recurringPaymentTable.userId, user.id),
orderBy: desc(recurringPaymentTable.createdAt),
});
🤖 Prompt for AI Agents
In src/server/routers/recurring-payment.ts around lines 8 to 11, remove the
redundant and() wrapper around the single eq() condition in the query to
simplify it. Also, since user is guaranteed by protectedProcedure, add an
explicit runtime check to ensure user.id is present and throw a 401 error if
not, instead of using a non-null assertion. Alternatively, refine the
procedure’s input type to make user.id non-nullable to avoid the assertion.

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

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 - Manage Existing Single Recurring Payouts
1 participant