Skip to content

feat: easy invoice subscriptions #91

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

Merged
merged 27 commits into from
Jul 18, 2025
Merged

Conversation

bassgeta
Copy link
Contributor

@bassgeta bassgeta commented Jul 9, 2025

Implements subscription links and overview of your plans and your subscribed to plans

Changes

  • New "Subscription Plans" tab where you can view, create and delete your subscription plans.
image image - New subscription preview page where other users can subscribe to the given plan. image - New subscription tab on dashboard so users can view what they are subscribed to. image - Reworked the dashboard tabs to be separate routes like the payouts page. - Additionally reworked the tables from those tabs so they reuse components instead of being the same component. - Removed the select checkbox for sent invoices, since that was only used for batch payments. image image

Resolves #18 and the outdated Request API one

Summary by CodeRabbit

  • New Features

    • Introduced subscription plans with creation, management, and subscription capabilities including trial periods and frequency options.
    • Added dedicated dashboard pages for sent invoices, received invoices, and subscriptions with detailed statistics and pagination.
    • Enabled users to view subscription plan details, subscriber statistics, and cancel subscriptions.
    • Added a "Subscription Plans" link to the main navigation menu.
    • Introduced batch payment functionality and improved invoice management with separate views and enhanced navigation.
    • Added components for empty states, statistics cards, pagination controls, and dashboard navigation tabs.
  • Improvements

    • Refactored recurring payment creation and cancellation flows using custom hooks for better user experience and code maintainability.
    • Simplified form components by abstracting complex payment logic into reusable hooks.
    • Enhanced user authentication checks and redirects to secure dashboard access.
    • Added utility functions and hooks to streamline payment and subscription operations.
  • Bug Fixes

    • Fixed redirection and access control to prevent unauthenticated access to dashboard pages.
  • Database & API

    • Added new database tables and API endpoints to support subscription plans, recurring payments, and enhanced invoice queries.
    • Updated schemas with subscription-related fields and validation rules.
    • Introduced new enumerations for payment and subscription statuses, account types, and frequencies.

Copy link
Contributor

coderabbitai bot commented Jul 9, 2025

Walkthrough

This update introduces a comprehensive subscription plan feature, including database schema changes, new backend API endpoints, and extensive frontend components for managing, displaying, and subscribing to plans. The recurring payment system is extended to support linkage with subscription plans. The dashboard is refactored to provide separate pages for sent invoices, received invoices, and subscriptions, each with paginated tables, batch actions, and summary statistics. Several reusable UI blocks and custom React hooks are added to encapsulate recurring payment and cancellation logic.

Changes

File(s) Change Summary
drizzle/meta/0006_snapshot.json, drizzle/meta/0007_snapshot.json, drizzle/meta/_journal.json, drizzle/0007_slimy_tyger_tiger.sql Database migration and schema snapshot updates: add easyinvoice_subscription_plans table, link recurring payments to subscription plans, update metadata and journal.
src/server/db/schema.ts Add subscriptionPlanTable and link recurringPaymentTable to subscriptionPlanTable with new relations and type alias.
src/server/routers/subscription-plan.ts New tRPC router for subscription plan CRUD, fetching, and subscriber queries.
src/server/index.ts Register subscriptionPlanRouter in the main API router.
src/server/routers/invoice.ts Add queries to fetch invoices issued by or to the current user.
src/server/routers/recurring-payment.ts Remove redundant auth checks; include subscriptionId in recurring payment creation.
src/server/routers/auth.ts Add getSessionInfo query; update import paths.
src/server/trpc.ts Adjust authentication middleware context passing.
src/lib/schemas/subscription-plan.ts Add subscriptionPlanApiSchema for validating subscription plan data.
src/lib/schemas/recurring-payment.ts Add optional subscriptionId to recurring payment schema.
src/lib/hooks/use-cancel-recurring-payment.ts, src/lib/hooks/use-create-recurring-payment.ts New hooks for recurring payment cancellation and creation, encapsulating wallet and API logic.
src/lib/utils.ts Add getCanCancelPayment utility.
src/app/dashboard/layout.tsx, src/app/dashboard/get-paid/page.tsx, src/app/dashboard/pay/page.tsx, src/app/dashboard/subscriptions/page.tsx, src/app/subscription-plans/page.tsx, src/app/s/[id]/page.tsx New/updated dashboard and subscription plan pages with authentication guards and data fetching.
src/app/dashboard/page.tsx Replace dashboard page with redirect to /dashboard/get-paid.
src/components/dashboard-navigation.tsx New dashboard tab navigation component.
src/components/dashboard/blocks/empty-state.tsx, src/components/dashboard/blocks/invoice-row.tsx, src/components/dashboard/blocks/pagination.tsx, src/components/dashboard/blocks/stat-card.tsx, src/components/dashboard/blocks/table-head-cell.tsx New reusable UI blocks for dashboard tables, stats, and empty states.
src/components/dashboard/invoices-sent.tsx, src/components/dashboard/invoices-received.tsx, src/components/dashboard/subscriptions.tsx New paginated table components for sent invoices, received invoices, and subscriptions with stats and actions.
src/components/subscription-plan-preview.tsx New component for displaying and subscribing to a plan, with wallet integration.
src/components/subscription-plans/blocks/create-subscription-plan.tsx, src/components/subscription-plans/blocks/subscription-plan-link.tsx, src/components/subscription-plans/subscription-plans.tsx New components for creating, listing, and managing subscription plans.
src/components/header.tsx Add navigation link for subscription plans.
src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx, src/components/view-recurring-payments/view-recurring-payments.tsx Refactor recurring payment creation and cancellation to use new hooks.
src/components/dashboard-view.tsx, src/components/invoice-table.tsx Remove old dashboard and invoice table components in favor of new modular blocks.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant DashboardPage
    participant API
    participant Wallet
    participant SubscriptionPlanPage
    participant SubscriptionPlanRouter

    User->>DashboardPage: Navigates to dashboard
    DashboardPage->>API: Fetch user session
    alt Not authenticated
        DashboardPage->>User: Redirect to home
    else Authenticated
        DashboardPage->>API: Fetch invoices/subscriptions
        DashboardPage->>User: Render dashboard with data
    end

    User->>SubscriptionPlanPage: Opens subscription plan link
    SubscriptionPlanPage->>API: Fetch plan by ID
    alt Plan not found
        SubscriptionPlanPage->>User: Show 404
    else Plan found
        SubscriptionPlanPage->>User: Show plan details
        User->>Wallet: Connect wallet
        User->>SubscriptionPlanPage: Click subscribe
        SubscriptionPlanPage->>Wallet: Initiate payment/signature
        SubscriptionPlanPage->>API: Create recurring payment
        API->>SubscriptionPlanRouter: Link payment to plan
        SubscriptionPlanPage->>User: Show success
    end
Loading

Assessment against linked issues

Objective Addressed Explanation
Option on Invoice Payment page to pre-approve future payments in response to a recurring invoice (#18) No UI or flow for pre-approving future payments on the invoice payment page is present; changes focus on subscription plans and recurring payment linkage.

Assessment against linked issues: Out-of-scope changes

Code Change (file_path) Explanation
Addition of full subscription plan management (src/server/routers/subscription-plan.ts, src/components/subscription-plans/...) Subscription plan CRUD and UI are not required for pre-approving recurring invoice payments as per #18.
Refactoring of dashboard UI and table components (src/components/dashboard/..., src/app/dashboard/...) These changes restructure dashboard navigation and invoice management, unrelated to the specific objective of pre-approving recurring payments for invoices.
Addition of subscription linkage in recurring payments (src/server/db/schema.ts, src/lib/schemas/recurring-payment.ts) Linking recurring payments to subscription plans is not specified in #18, which focuses on invoice-based recurring payments.

Possibly related PRs

Suggested reviewers

  • MantisClone
  • aimensahnoun

📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c13814b and 981826b.

📒 Files selected for processing (1)
  • src/app/dashboard/layout.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 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: bassgeta
PR: RequestNetwork/easy-invoice#91
File: drizzle/0007_awesome_gertrude_yorkes.sql:8-8
Timestamp: 2025-07-14T14:29:03.237Z
Learning: In the RequestNetwork/easy-invoice codebase, database migration 006 creates the frequency_enum type that migration 007 references for the subscription plans table. The migration dependencies are properly structured with foundational types created in earlier migrations.
🧬 Code Graph Analysis (1)
src/app/dashboard/layout.tsx (5)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/components/background-wrapper.tsx (1)
  • BackgroundWrapper (15-85)
src/components/header.tsx (1)
  • Header (9-94)
src/components/dashboard-navigation.tsx (1)
  • DashboardNavigation (8-37)
src/components/footer.tsx (1)
  • Footer (3-17)
⏰ 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 (4)
src/app/dashboard/layout.tsx (4)

1-8: LGTM - Well-organized imports

All imports are properly structured and used within the component. The imports align well with the layout's responsibilities for authentication, navigation, and UI rendering.


10-14: LGTM - Proper Next.js layout component structure

The function signature correctly follows Next.js app router conventions for layout components with proper async/await usage and children prop typing.


15-16: LGTM - Secure authentication check

The authentication logic properly verifies user session before allowing dashboard access. The redirect to the home page for unauthenticated users is appropriate and follows security best practices.


18-40: ✅ Layout Approved and Route Verified

The dashboard layout is well-organized with consistent theming, responsive design, and a clear component hierarchy. The “Create Invoice” route exists at src/app/invoices/create/page.tsx, so the navigation link is valid.

• Verified route: src/app/invoices/create/page.tsx

✨ 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/18-recurring-invoice-payments branch from 8ac252e to ed4b9b4 Compare July 10, 2025 10:06
@bassgeta bassgeta changed the base branch from main to feat/19-manage-recurring-payments July 11, 2025 09:27
@bassgeta bassgeta changed the title Feat/18 recurring invoice payments feat: easy invoice subscriptions Jul 11, 2025
Base automatically changed from feat/19-manage-recurring-payments to main July 11, 2025 13:58
@bassgeta bassgeta marked this pull request as ready for review July 14, 2025 13:02
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: 10

🧹 Nitpick comments (11)
src/components/dashboard/blocks/pagination.tsx (2)

16-20: Consider consistency in totalPages calculation and early return logic.

The component calculates totalPages as Math.max(1, Math.ceil(totalItems / itemsPerPage)) which ensures at least 1 page, but then returns null when totalItems === 0. This creates a slight logical inconsistency.

Consider this alternative approach for better consistency:

-  const totalPages = Math.max(1, Math.ceil(totalItems / itemsPerPage));
-
-  if (totalItems === 0) {
-    return null;
-  }
+  if (totalItems === 0) {
+    return null;
+  }
+
+  const totalPages = Math.ceil(totalItems / itemsPerPage);

10-15: Consider adding prop validation for page bounds.

The component doesn't validate that the initial page prop is within valid bounds (1 to totalPages). While the button handlers properly bound the values, an invalid initial page could cause display issues.

Consider adding validation:

export const Pagination = ({
  page,
  setPage,
  totalItems,
  itemsPerPage = 10,
}: PaginationProps) => {
+  // Ensure page is within valid bounds
+  const validPage = Math.max(1, Math.min(page, Math.ceil(totalItems / itemsPerPage)));
+  
+  if (validPage !== page) {
+    setPage(validPage);
+  }
src/app/dashboard/layout.tsx (1)

22-22: Consider simplifying the responsive width constraints.

The main element has many breakpoint-specific max-width classes. Consider using a single container class or extracting this into a reusable component/utility class for better maintainability.

-      <main className="flex-grow flex flex-col w-full max-w-sm sm:max-w-2xl md:max-w-4xl lg:max-w-6xl xl:max-w-[72rem] mx-auto px-4 sm:px-6 lg:px-8 py-8 z-10">
+      <main className="flex-grow flex flex-col w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 z-10">

Or create a reusable container class:

.dashboard-container {
  @apply w-full mx-auto px-4 sm:px-6 lg:px-8;
  @apply max-w-sm sm:max-w-2xl md:max-w-4xl lg:max-w-6xl xl:max-w-[72rem];
}
src/lib/schemas/subscription-plan.ts (1)

7-21: Add descriptions to all fields for consistency.

Only the chain field has a description. Consider adding descriptions to other fields for better documentation and consistency.

 export const subscriptionPlanApiSchema = z.object({
-  label: z.string().min(1, "Label is required"),
-  amount: z.number().gt(0, "Amount must be greater than 0"),
+  label: z.string().min(1, "Label is required").describe("The subscription plan label"),
+  amount: z.number().gt(0, "Amount must be greater than 0").describe("The subscription amount per payment"),
   payee: z
     .string()
     .min(1, "Recipient address is required")
-    .refine(isEthereumAddress, "Invalid Ethereum address format"),
-  paymentCurrency: z.string().min(1, "Payment currency is required"),
+    .refine(isEthereumAddress, "Invalid Ethereum address format")
+    .describe("The wallet address of the payee"),
+  paymentCurrency: z.string().min(1, "Payment currency is required").describe("The currency for payments"),
   chain: z
     .string()
     .min(1, "Chain is required")
     .describe("The blockchain network"),
   frequency: recurrenceFields.frequency,
   totalPayments: recurrenceFields.totalPayments,
 });
src/components/dashboard-navigation.tsx (1)

12-20: Consider using exact path matching for more precision.

The current implementation using includes() works for the existing routes but could potentially match unintended paths in the future (e.g., "/dashboard/payments" would match "/pay").

   useEffect(() => {
-    if (pathname.includes("/pay")) {
+    if (pathname === "/dashboard/pay" || pathname.startsWith("/dashboard/pay/")) {
       setActiveTab("pay");
-    } else if (pathname.includes("/subscriptions")) {
+    } else if (pathname === "/dashboard/subscriptions" || pathname.startsWith("/dashboard/subscriptions/")) {
       setActiveTab("subscriptions");
     } else {
       setActiveTab("get-paid");
     }
   }, [pathname]);
src/components/dashboard/invoices-sent.tsx (1)

24-24: Consider optimizing the polling interval.

A 3-second polling interval might be too aggressive and could create unnecessary server load. Consider increasing it to 10-30 seconds or implementing real-time updates via WebSockets/SSE for better performance.

Also applies to: 45-50

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

57-61: Verify the double state setting in handleCancelRecurringPayment.

The function sets cancellingPaymentId to null both in the onSuccess callback (line 53) and after the hook call (line 60). This could lead to redundant state updates.

Consider removing the redundant state setting since it's already handled in the onSuccess callback:

  const handleCancelRecurringPayment = async (payment: RecurringPayment) => {
    setCancellingPaymentId(payment.id);
    await cancelRecurringPayment(payment);
-   setCancellingPaymentId(null);
  };
src/server/routers/invoice.ts (1)

245-267: Clean up commented code or document future implementation.

The commented-out code suggests planned aggregation features (total, outstanding counts) but should either be implemented or removed to maintain code cleanliness.

Consider either:

  1. Removing the commented code if not needed immediately
  2. Adding a TODO comment explaining the planned feature
  3. Implementing the aggregation if it's needed now
  return receivables;
- // {
- //   issuedByMe: {
- //     invoices: receivables,
- //     total: receivables.reduce((acc, inv) => acc + Number(inv.amount), 0),
- //     outstanding: receivables.filter((inv) => inv.status !== "paid").length,
- //   },
- // };
+ // TODO: Add aggregation support for total and outstanding counts
src/components/subscription-plans/blocks/create-subscription-plan.tsx (1)

84-95: Proper wallet validation and secure data handling.

The form submission correctly validates wallet connection and securely injects the wallet address as the payee. The hardcoded chain value should be made configurable.

Consider making the chain configurable instead of hardcoded:

    await createSubscriptionPlan({
      ...data,
      payee: address,
-     chain: "sepolia",
+     chain: process.env.NEXT_PUBLIC_DEFAULT_CHAIN || "sepolia",
    });
src/lib/hooks/use-create-recurring-payment.ts (1)

47-50: Use a more descriptive error message for wallet provider check

The error message is identical to the wallet connection check above. Consider making it more specific to help users understand the issue better.

    if (!walletProvider) {
-     toast.error("Please connect your wallet first");
+     toast.error("Wallet provider not available. Please reconnect your wallet");
      return;
    }
src/components/dashboard/blocks/invoice-row.tsx (1)

43-49: Consider using a modal instead of native confirm()

The native confirm() dialog provides a poor user experience. Consider using a proper confirmation modal component for consistency with the rest of the application.

Would you like me to help implement a confirmation modal using your existing UI components?

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6ce2b9c and ee54f28.

📒 Files selected for processing (40)
  • drizzle/0007_exotic_snowbird.sql (1 hunks)
  • drizzle/meta/0006_snapshot.json (2 hunks)
  • drizzle/meta/0007_snapshot.json (1 hunks)
  • drizzle/meta/_journal.json (1 hunks)
  • src/app/dashboard/get-paid/page.tsx (1 hunks)
  • src/app/dashboard/layout.tsx (1 hunks)
  • src/app/dashboard/page.tsx (1 hunks)
  • src/app/dashboard/pay/page.tsx (1 hunks)
  • src/app/dashboard/subscriptions/page.tsx (1 hunks)
  • src/app/s/[id]/page.tsx (1 hunks)
  • src/app/subscription-plans/page.tsx (1 hunks)
  • src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx (3 hunks)
  • src/components/dashboard-navigation.tsx (1 hunks)
  • src/components/dashboard-view.tsx (0 hunks)
  • src/components/dashboard/blocks/empty-state.tsx (1 hunks)
  • src/components/dashboard/blocks/invoice-row.tsx (1 hunks)
  • src/components/dashboard/blocks/pagination.tsx (1 hunks)
  • src/components/dashboard/blocks/stat-card.tsx (1 hunks)
  • src/components/dashboard/blocks/table-head-cell.tsx (1 hunks)
  • src/components/dashboard/invoices-received.tsx (1 hunks)
  • src/components/dashboard/invoices-sent.tsx (1 hunks)
  • src/components/dashboard/subscriptions.tsx (1 hunks)
  • src/components/header.tsx (1 hunks)
  • src/components/invoice-table.tsx (0 hunks)
  • src/components/subscription-plan-preview.tsx (1 hunks)
  • src/components/subscription-plans/blocks/create-subscription-plan.tsx (1 hunks)
  • src/components/subscription-plans/blocks/subscription-plan-link.tsx (1 hunks)
  • src/components/subscription-plans/subscription-plans.tsx (1 hunks)
  • src/components/view-recurring-payments/view-recurring-payments.tsx (4 hunks)
  • src/lib/hooks/use-cancel-recurring-payment.ts (1 hunks)
  • src/lib/hooks/use-create-recurring-payment.ts (1 hunks)
  • src/lib/schemas/recurring-payment.ts (1 hunks)
  • src/lib/schemas/subscription-plan.ts (1 hunks)
  • src/lib/utils.ts (1 hunks)
  • src/server/db/schema.ts (5 hunks)
  • src/server/index.ts (1 hunks)
  • src/server/routers/auth.ts (1 hunks)
  • src/server/routers/invoice.ts (1 hunks)
  • src/server/routers/recurring-payment.ts (1 hunks)
  • src/server/routers/subscription-plan.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • src/components/invoice-table.tsx
  • src/components/dashboard-view.tsx
🧰 Additional context used
🧠 Learnings (23)
📓 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#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: 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.
drizzle/meta/_journal.json (1)
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#8
File: drizzle/meta/0001_snapshot.json:125-130
Timestamp: 2025-02-13T21:59:01.427Z
Learning: Drizzle migration files (drizzle/meta/*.json) are auto-generated and should not be modified directly. Schema changes should be made in the source files.
src/lib/schemas/recurring-payment.ts (1)
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.
src/server/index.ts (1)
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/server/routers/invoice.ts:88-109
Timestamp: 2025-02-12T12:40:14.742Z
Learning: The payRequest endpoint in src/server/routers/invoice.ts is intentionally kept public (using publicProcedure) to allow invoice sharing and payment by anyone with the payment reference, similar to how payment links work in other payment systems.
src/app/dashboard/get-paid/page.tsx (2)
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/app/dashboard/pay/page.tsx (4)
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#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#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/server/routers/auth.ts (1)
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/server/routers/invoice.ts:88-109
Timestamp: 2025-02-12T12:40:14.742Z
Learning: The payRequest endpoint in src/server/routers/invoice.ts is intentionally kept public (using publicProcedure) to allow invoice sharing and payment by anyone with the payment reference, similar to how payment links work in other payment systems.
src/server/routers/recurring-payment.ts (1)
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.
drizzle/meta/0006_snapshot.json (2)
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#8
File: drizzle/meta/0001_snapshot.json:125-130
Timestamp: 2025-02-13T21:59:01.427Z
Learning: Drizzle migration files (drizzle/meta/*.json) are auto-generated and should not be modified directly. Schema changes should be made in the source files.
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/components/view-recurring-payments/view-recurring-payments.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#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: 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/lib/schemas/subscription-plan.ts (3)
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#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#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.
src/components/dashboard/invoices-sent.tsx (2)
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/app/invoices/[ID]/page.tsx:160-175
Timestamp: 2025-02-12T12:42:40.076Z
Learning: Invoice items are validated at creation time through the schema validation in src/lib/schemas/invoice.ts, ensuring that items array is non-empty and each item has required description, quantity, and price fields. Runtime validation in display components is not necessary.
src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx (7)
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: 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.
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#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#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: 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.
src/components/subscription-plans/blocks/create-subscription-plan.tsx (1)
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/server/routers/invoice.ts (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: 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: 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/components/subscription-plan-preview.tsx (1)
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/server/routers/subscription-plan.ts (1)
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/server/routers/invoice.ts:88-109
Timestamp: 2025-02-12T12:40:14.742Z
Learning: The payRequest endpoint in src/server/routers/invoice.ts is intentionally kept public (using publicProcedure) to allow invoice sharing and payment by anyone with the payment reference, similar to how payment links work in other payment systems.
src/components/dashboard/blocks/invoice-row.tsx (4)
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/app/invoices/[ID]/page.tsx:160-175
Timestamp: 2025-02-12T12:42:40.076Z
Learning: Invoice items are validated at creation time through the schema validation in src/lib/schemas/invoice.ts, ensuring that items array is non-empty and each item has required description, quantity, and price fields. Runtime validation in display components is not necessary.
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: 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/dashboard/invoices-received.tsx (4)
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#2
File: src/app/invoices/[ID]/page.tsx:160-175
Timestamp: 2025-02-12T12:42:40.076Z
Learning: Invoice items are validated at creation time through the schema validation in src/lib/schemas/invoice.ts, ensuring that items array is non-empty and each item has required description, quantity, and price fields. Runtime validation in display components is not necessary.
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.
drizzle/0007_exotic_snowbird.sql (1)
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/lib/hooks/use-create-recurring-payment.ts (2)
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#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/server/db/schema.ts (1)
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.
drizzle/meta/0007_snapshot.json (2)
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: aimensahnoun
PR: RequestNetwork/easy-invoice#8
File: drizzle/meta/0001_snapshot.json:125-130
Timestamp: 2025-02-13T21:59:01.427Z
Learning: Drizzle migration files (drizzle/meta/*.json) are auto-generated and should not be modified directly. Schema changes should be made in the source files.
🧬 Code Graph Analysis (16)
src/app/dashboard/get-paid/page.tsx (2)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/components/dashboard/invoices-sent.tsx (1)
  • InvoicesSent (42-127)
src/server/routers/auth.ts (1)
src/server/trpc.ts (2)
  • router (11-11)
  • publicProcedure (12-12)
src/app/dashboard/layout.tsx (5)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/components/background-wrapper.tsx (1)
  • BackgroundWrapper (15-85)
src/components/header.tsx (1)
  • Header (9-94)
src/components/dashboard-navigation.tsx (1)
  • DashboardNavigation (8-37)
src/components/footer.tsx (1)
  • Footer (3-17)
src/components/dashboard/blocks/table-head-cell.tsx (2)
src/components/ui/table.tsx (1)
  • TableHead (113-113)
src/lib/utils.ts (1)
  • cn (4-6)
src/app/subscription-plans/page.tsx (4)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/components/header.tsx (1)
  • Header (9-94)
src/components/subscription-plans/subscription-plans.tsx (1)
  • SubscriptionPlans (16-61)
src/components/footer.tsx (1)
  • Footer (3-17)
src/app/dashboard/subscriptions/page.tsx (2)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/components/dashboard/subscriptions.tsx (1)
  • Subscriptions (136-216)
src/lib/schemas/subscription-plan.ts (1)
src/lib/schemas/payment.ts (1)
  • paymentApiSchema (6-47)
src/components/subscription-plans/subscription-plans.tsx (4)
src/server/db/schema.ts (1)
  • SubscriptionPlan (384-384)
src/components/ui/button.tsx (1)
  • Button (56-56)
src/components/subscription-plans/blocks/subscription-plan-link.tsx (1)
  • SubscriptionPlanLink (32-168)
src/components/subscription-plans/blocks/create-subscription-plan.tsx (1)
  • CreateSubscriptionPlan (52-275)
src/components/dashboard/invoices-sent.tsx (6)
src/components/dashboard/blocks/table-head-cell.tsx (1)
  • TableHeadCell (9-13)
src/server/db/schema.ts (1)
  • Request (380-380)
src/components/dashboard/blocks/stat-card.tsx (1)
  • StatCard (9-21)
src/components/dashboard/blocks/empty-state.tsx (1)
  • EmptyState (10-24)
src/components/dashboard/blocks/invoice-row.tsx (1)
  • InvoiceRow (24-131)
src/components/dashboard/blocks/pagination.tsx (1)
  • Pagination (10-45)
src/lib/hooks/use-cancel-recurring-payment.ts (2)
src/server/db/schema.ts (1)
  • RecurringPayment (389-389)
src/lib/utils.ts (1)
  • getCanCancelPayment (21-23)
src/components/subscription-plans/blocks/subscription-plan-link.tsx (5)
src/server/db/schema.ts (1)
  • SubscriptionPlan (384-384)
src/lib/constants/currencies.ts (1)
  • formatCurrencyLabel (94-139)
src/components/ui/card.tsx (2)
  • Card (79-79)
  • CardContent (79-79)
src/components/ui/button.tsx (1)
  • Button (56-56)
src/components/ui/alert-dialog.tsx (9)
  • AlertDialog (130-130)
  • AlertDialogTrigger (133-133)
  • AlertDialogContent (134-134)
  • AlertDialogHeader (135-135)
  • AlertDialogTitle (137-137)
  • AlertDialogDescription (138-138)
  • AlertDialogFooter (136-136)
  • AlertDialogCancel (140-140)
  • AlertDialogAction (139-139)
src/server/routers/invoice.ts (3)
src/server/trpc.ts (1)
  • protectedProcedure (39-39)
src/server/db/index.ts (1)
  • db (10-12)
src/server/db/schema.ts (1)
  • requestTable (179-216)
src/components/subscription-plan-preview.tsx (8)
src/server/db/schema.ts (1)
  • SubscriptionPlan (384-384)
src/lib/hooks/use-create-recurring-payment.ts (1)
  • useCreateRecurringPayment (22-127)
src/lib/constants/currencies.ts (2)
  • formatCurrencyLabel (94-139)
  • PayoutCurrency (46-46)
src/components/ui/card.tsx (4)
  • Card (79-79)
  • CardHeader (79-79)
  • CardTitle (79-79)
  • CardContent (79-79)
src/components/ui/label.tsx (1)
  • Label (26-26)
src/components/ui/input.tsx (1)
  • Input (22-22)
src/components/ui/button.tsx (1)
  • Button (56-56)
src/components/payment-secured-using-request.tsx (1)
  • PaymentSecuredUsingRequest (1-13)
src/server/routers/subscription-plan.ts (4)
src/server/trpc.ts (3)
  • router (11-11)
  • protectedProcedure (39-39)
  • publicProcedure (12-12)
src/lib/schemas/subscription-plan.ts (1)
  • subscriptionPlanApiSchema (7-21)
src/server/db/index.ts (1)
  • db (10-12)
src/server/db/schema.ts (2)
  • subscriptionPlanTable (280-295)
  • recurringPaymentTable (218-254)
src/components/dashboard/blocks/invoice-row.tsx (6)
src/server/db/schema.ts (1)
  • Request (380-380)
src/components/ui/table.tsx (2)
  • TableRow (114-114)
  • TableCell (115-115)
src/components/ui/badge.tsx (1)
  • Badge (36-36)
src/lib/constants/currencies.ts (1)
  • formatCurrencyLabel (94-139)
src/lib/invoice-status.ts (2)
  • getInvoiceTableStatusClass (48-67)
  • getStatusDisplayText (12-40)
src/components/ui/button.tsx (1)
  • Button (56-56)
src/lib/hooks/use-create-recurring-payment.ts (1)
src/lib/schemas/payment.ts (1)
  • PaymentAPIValues (49-49)
⏰ 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 (58)
src/components/dashboard/blocks/pagination.tsx (1)

22-44: Well-implemented pagination component with proper accessibility and styling.

The component correctly handles button states, uses semantic HTML, and provides a clean user experience. The boundary checking in click handlers is solid.

drizzle/meta/_journal.json (1)

50-60: Auto-generated migration journal update looks correct.

This file is auto-generated by Drizzle and tracks database migration history. The timestamp adjustment and new entry for migration 0007_exotic_snowbird represent the normal progression of database schema changes.

src/lib/utils.ts (1)

21-23: Clean utility function with clear purpose.

The function provides a clear abstraction for determining payment cancellation eligibility. The implementation is straightforward and follows good naming conventions.

src/lib/schemas/recurring-payment.ts (1)

27-31: Proper schema extension for subscription linking.

The optional subscriptionId field follows zod best practices with proper typing and descriptive documentation. This aligns well with the subscription plan feature implementation.

src/server/index.ts (2)

7-7: Proper router import following established patterns.

The import follows the existing pattern used by other routers in the codebase.


17-17: Correct router integration in the main app router.

The subscription plan router is properly integrated into the main router, following the established tRPC pattern used by other routers.

src/components/header.tsx (1)

50-55: LGTM! Navigation link addition is consistent.

The new "Subscription Plans" navigation link follows the same pattern as existing navigation links with consistent styling and conditional rendering.

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

59-59: LGTM! Subscription ID linkage is properly implemented.

The addition of subscriptionId: input.subscriptionId correctly links recurring payments to subscription plans following the established pattern.

drizzle/meta/0006_snapshot.json (1)

2-2: Skip review - auto-generated migration file.

Drizzle migration snapshots are auto-generated and should not be modified directly. Schema changes should be made in the source files.

Also applies to: 385-386

src/app/dashboard/get-paid/page.tsx (1)

6-16: LGTM! Server component follows best practices.

The authentication check with redirect and data fetching pattern is implemented correctly. The component properly handles the server-side data fetching and passes initial data to the client component.

src/app/dashboard/pay/page.tsx (1)

6-16: LGTM! Consistent implementation with proper authentication.

The page component correctly implements the same authentication and data fetching pattern as the get-paid page, appropriately fetching invoices issued to the user.

src/components/dashboard/blocks/empty-state.tsx (1)

1-24: Well-designed empty state component.

This component provides a clean, reusable pattern for empty states across the dashboard. The interface is properly typed, the layout is centered and visually balanced, and the optional callToAction prop adds flexibility for different use cases.

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

1-3: Good improvement to use absolute imports.

The change from relative imports to absolute imports improves maintainability and consistency across the codebase.


6-11: Session info exposure verified

  • Unauthenticated requests yield { session: null, user: null } (from getCurrentSession)
  • When present, ctx.session contains only { id, userId, expiresAt } (no raw token)
  • ctx.user is the full row from userTable (fields: id, googleId, name, email, agreementStatus, kycStatus, isCompliant)
  • No secrets (passwords, raw tokens) are returned and only the authenticated user’s own record is exposed

If you’d prefer to limit PII (for example omitting email or googleId), replace the raw return with a mapped object containing only the needed fields. Otherwise, this public procedure is safe as implemented.

src/components/dashboard/blocks/stat-card.tsx (1)

1-21: Well-implemented stat card component.

This component provides a clean, reusable pattern for displaying statistics across the dashboard. The interface is properly typed with flexible value prop (string or number), and the layout effectively uses the Card components with appropriate visual hierarchy.

src/components/dashboard/blocks/table-head-cell.tsx (1)

1-13: Well-designed table head cell component.

This component provides a good abstraction over the base TableHead component with consistent default styling and the flexibility to override classes when needed. The use of the cn utility ensures proper class merging, and the TypeScript interface is appropriate.

src/app/subscription-plans/page.tsx (1)

8-16: Good authentication and data fetching pattern.

The authentication check with redirect and the subscription plans data fetching follow proper Next.js server component patterns. The session validation ensures only authenticated users can access this page.

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

8-15: LGTM!

The authentication check and redirect pattern is consistent with other dashboard pages and properly protects the dashboard routes.

src/app/dashboard/subscriptions/page.tsx (1)

6-17: LGTM!

The implementation follows best practices:

  • Consistent authentication pattern
  • Server-side data fetching for better performance
  • Proper data hydration pattern with initial props
src/lib/schemas/subscription-plan.ts (1)

5-5: Clever reuse of validation rules!

Using unwrap() to extract fields from the optional recurrence schema is an elegant way to maintain consistency and avoid duplication.

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

8-37: LGTM!

The navigation component properly integrates tabs with Next.js routing using the asChild pattern and maintains synchronized state with the URL.

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

3-5: Clean refactoring approach!

Redirecting the main dashboard route to a specific tab is a good pattern that maintains backward compatibility while supporting the new modular dashboard structure.

src/components/subscription-plans/subscription-plans.tsx (1)

1-61: Well-structured subscription plans component!

The component properly handles initial data hydration, polling for updates, and modal state management. Good use of TypeScript interfaces and separation of concerns.

src/components/subscription-plans/blocks/subscription-plan-link.tsx (1)

43-60: Good implementation of stats calculation and display!

The data transformation in the query select function is clean and efficient. Proper use of optional chaining and fallback values in the UI.

Also applies to: 82-91

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

27-27: Excellent refactoring to use custom hook!

Moving the complex wallet interaction logic to useCreateRecurringPayment hook improves code organization and reusability. This aligns well with the codebase's consistency preferences.

Also applies to: 58-67, 94-108

src/components/dashboard/invoices-sent.tsx (1)

52-127: Clean and well-structured dashboard component!

Good use of modular sub-components, proper empty state handling, and safe number calculations with fallbacks. The pagination implementation is clean and user-friendly.

src/app/s/[id]/page.tsx (1)

14-40: Well-implemented subscription plan preview page!

Good use of server components for data fetching, proper 404 handling, and defensive programming with the email fallback. The metadata configuration helps with SEO.

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

15-16: LGTM! Good refactoring with proper hook usage.

The refactoring to use the useCancelRecurringPayment hook improves code organization by separating concerns and making the cancellation logic reusable. The import of the utility function from @/lib/utils is also a good practice.


49-55: Good hook configuration with proper callback handling.

The hook is properly configured with a confirmation message and success callback that handles refetch and state cleanup. This ensures the UI stays in sync after cancellation.


173-174: Consistent variable naming with the refactored logic.

The variable naming isCurrentPaymentCancelling is clear and consistent with the refactored state management pattern.

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

268-284: Consistent implementation with getAllIssuedByMe.

The getAllIssuedToMe procedure follows the same pattern as getAllIssuedByMe with appropriate filtering for payable invoices. The implementation is consistent and correct.


248-257: Verify the invoice filtering logic for accuracy.

The filtering logic correctly identifies invoices issued by the current user while excluding self-invoices. This matches the existing pattern in the getAll procedure.

src/lib/hooks/use-cancel-recurring-payment.ts (6)

10-13: Well-defined hook interface with proper TypeScript types.

The interface clearly defines the required props with proper typing for the success callback and confirmation message.


29-43: Good validation flow with proper user feedback.

The hook properly validates payment cancellability, wallet connection, and user confirmation before proceeding. The error messages are clear and actionable.


62-73: Excellent error handling for transaction failures.

The transaction error handling is well-implemented with proper logging and user-friendly messaging. The comment correctly explains that transaction failures don't affect the backend cancellation status.


80-82: Proper cache invalidation and success handling.

The hook correctly invalidates the recurring payments cache and provides success feedback, ensuring the UI stays in sync.


94-97: Proper loading state management across mutations.

The loading state correctly combines both mutation states to provide accurate loading feedback to the UI.


44-46: No update required for ethers provider syntax

The project’s package.json shows ethers@^5.7.2 in use, so new ethers.providers.Web3Provider(...) is correct and not deprecated. You only need to switch to BrowserProvider when you upgrade to ethers v6.

Likely an incorrect or invalid review comment.

src/components/subscription-plans/blocks/create-subscription-plan.tsx (5)

41-44: Good schema composition with proper field omission.

The form schema properly omits payee and chain fields which are automatically populated from the wallet connection and configuration.


73-82: Comprehensive form setup with sensible defaults.

The form configuration includes proper validation, reasonable default values, and good initial state. The default values are appropriate for a monthly subscription plan.


101-112: Excellent wallet modal integration handling.

The custom onInteractOutside handler properly prevents the dialog from closing when users interact with the wallet modal. This provides a much better user experience.


175-183: Good numeric input validation with proper constraints.

The total payments field includes appropriate min/max validation and proper number conversion handling.


247-258: Clean wallet address display with proper formatting.

The wallet address display is well-formatted with truncation and includes a logical logout button that opens the wallet modal.

drizzle/0007_exotic_snowbird.sql (3)

1-12: Well-structured subscription plans table with comprehensive fields.

The table definition includes all necessary fields for subscription plans with appropriate data types. The use of frequency_enum maintains consistency with existing schema patterns.


14-15: Proper column rename and foreign key addition.

The column rename from totalAmountPerMonth to totalAmount is more generic and appropriate for various frequencies. The addition of the nullable subscriptionId column enables proper linking to subscription plans.


16-20: Excellent migration safety with duplicate constraint handling.

The PL/pgSQL block properly handles potential duplicate constraint errors, making the migration safe to run multiple times without failure.

src/lib/hooks/use-create-recurring-payment.ts (1)

73-73: Hardcoded chain is intentional and documented

The chain is hardcoded to "sepolia" with a clear comment explaining this is intentional. This aligns with the retrieved learning that recurring payments are limited to Sepolia only.

src/components/dashboard/subscriptions.tsx (1)

146-150: Good implementation of active subscription filtering

The total spent calculation correctly filters only active subscriptions using the ACTIVE_STATUSES constant before summing amounts.

src/components/dashboard/blocks/invoice-row.tsx (1)

109-111: Well-structured conditional rendering for stop button

The stop recurrence button is correctly shown only for sent invoices that have recurrence enabled and haven't been stopped yet.

src/components/dashboard/invoices-received.tsx (2)

85-110: Excellent implementation of network constraint logic

The invoice selection handler properly enforces that only invoices from the same network can be selected together, preventing batch payment issues. The error messages are clear and helpful.


147-149: Confirm walletProvider’s compatibility with ethers.providers.ExternalProvider

We’re casting walletProvider (from useAppKitProvider("eip155")) to ethers.providers.ExternalProvider, but since that hook comes from @reown/appkit/react, we can’t verify its returned shape here. Please ensure the hook’s walletProvider truly implements the ExternalProvider interface (i.e. has request, on, removeListener, etc.), or add runtime/type checks to guard against mismatches.

Files to verify:

  • src/components/dashboard/invoices-received.tsx (around line 147)
  • src/components/payment-section.tsx (around line 107)
  • src/components/direct-payout.tsx (around line 84)

If the hook’s return type can’t be updated upstream, consider narrowing or validating the provider before casting.

src/components/subscription-plan-preview.tsx (2)

67-70: Good implementation of self-subscription prevention

The logic correctly prevents users from subscribing to their own plans by checking the session userId against the plan's userId.


60-65: Keep the existing 2-second AppKit initialization delay

The hardcoded timeout is an intentional, consistent pattern across Easy Invoice’s AppKit integrations (see DirectPayment and BatchPayout components) and reflects the team’s preference for a simple, reliable workaround in the absence of a documented “ready” event or property in ReOwn AppKit. No changes required here.

Likely an incorrect or invalid review comment.

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

233-233: LGTM! Nullable field allows flexibility.

The nullable subscriptionId field is appropriately designed to allow recurring payments to exist independently or be linked to subscription plans.


280-295: Well-structured subscription plan table.

The table design is solid with appropriate fields, data types, and a cascade delete constraint on the user relationship. Good use of the existing frequency enum.


317-329: Proper relation setup for subscription linkage.

The updated relation correctly establishes the one-to-one relationship between recurring payments and subscription plans through the nullable subscriptionId field.

src/server/routers/subscription-plan.ts (1)

74-100: Good use of public procedure for sharing.

The getById endpoint correctly uses publicProcedure to allow subscription plan sharing, consistent with the design pattern established for invoice sharing in this codebase.

drizzle/meta/0007_snapshot.json (1)

1-940: Auto-generated file - no review needed.

This is an auto-generated Drizzle migration metadata file. Schema changes should be made in the source files, not here.

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

🧹 Nitpick comments (2)
drizzle/0007_messy_silver_fox.sql (2)

1-12: Consider timestamp with time zone and NOT NULL for created_at
timestamp in Postgres is without time-zone information. Unless you have a compelling reason, prefer timestamptz so you don’t lose offset context.

-	"created_at" timestamp DEFAULT now()
+	"created_at" timestamptz NOT NULL DEFAULT now()

1-12: Add indexes for high-cardinality filter columns (userId, label)
Subscription-plan listing, look-ups by user, and duplicate-label checks will scan the whole table without indexes.

CREATE INDEX IF NOT EXISTS easyinvoice_subscription_plans_userId_idx
  ON "easyinvoice_subscription_plans"("userId");
CREATE UNIQUE INDEX IF NOT EXISTS easyinvoice_subscription_plans_user_label_uq
  ON "easyinvoice_subscription_plans"("userId","label");
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between ee54f28 and bb84f2a.

📒 Files selected for processing (4)
  • drizzle/0007_messy_silver_fox.sql (1 hunks)
  • drizzle/meta/0006_snapshot.json (1 hunks)
  • drizzle/meta/0007_snapshot.json (1 hunks)
  • drizzle/meta/_journal.json (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • drizzle/meta/0006_snapshot.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • drizzle/meta/_journal.json
  • drizzle/meta/0007_snapshot.json
🧰 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: 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.
drizzle/0007_messy_silver_fox.sql (1)
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.
🔇 Additional comments (1)
drizzle/0007_messy_silver_fox.sql (1)

15-19: ON UPDATE NO ACTION may break if user IDs change
If user IDs are ever updated (unlikely but possible with UUID regeneration), the foreign key will block the operation. Using ON UPDATE CASCADE is usually safer and consistent with ON DELETE CASCADE.

-  REFERENCES "public"."easyinvoice_user"("id") ON DELETE cascade ON UPDATE no action;
+  REFERENCES "public"."easyinvoice_user"("id") ON DELETE CASCADE ON UPDATE CASCADE;

@bassgeta bassgeta self-assigned this Jul 14, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
drizzle/0007_awesome_gertrude_yorkes.sql (1)

8-10: Consider stricter column types for monetary values
Storing amount as text pushes parsing/validation into application code and blocks numeric comparison in SQL. If amounts are on-chain decimals, numeric with a fixed scale or bigint (wei) is safer.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between bb84f2a and a913d24.

📒 Files selected for processing (10)
  • drizzle/0007_awesome_gertrude_yorkes.sql (1 hunks)
  • drizzle/meta/0007_snapshot.json (1 hunks)
  • drizzle/meta/_journal.json (1 hunks)
  • src/app/subscription-plans/page.tsx (1 hunks)
  • src/components/dashboard/subscriptions.tsx (1 hunks)
  • src/components/subscription-plans/blocks/subscription-plan-link.tsx (1 hunks)
  • src/server/db/schema.ts (5 hunks)
  • src/server/routers/recurring-payment.ts (2 hunks)
  • src/server/routers/subscription-plan.ts (1 hunks)
  • src/server/trpc.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/server/routers/recurring-payment.ts
  • drizzle/meta/_journal.json
  • src/app/subscription-plans/page.tsx
  • src/components/dashboard/subscriptions.tsx
  • src/components/subscription-plans/blocks/subscription-plan-link.tsx
  • src/server/routers/subscription-plan.ts
  • src/server/db/schema.ts
  • drizzle/meta/0007_snapshot.json
🧰 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: 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.
drizzle/0007_awesome_gertrude_yorkes.sql (1)
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
🔇 Additional comments (2)
src/server/trpc.ts (1)

18-18: LGTM! Good TypeScript type narrowing practice.

The explicit assignment of user and session properties after the authentication check helps TypeScript understand that these properties are guaranteed to be non-null in downstream procedures. While functionally redundant, this is a solid pattern for type safety in authentication middleware.

drizzle/0007_awesome_gertrude_yorkes.sql (1)

14-25: No default value for the newly added subscriptionId
Existing rows in easyinvoice_recurring_payment will have NULL in the new column. Confirm this is acceptable and that application code handles legacy recurring payments without a subscription link.

@bassgeta bassgeta requested a review from aimensahnoun July 17, 2025 10:00
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
drizzle/0007_slimy_tyger_tiger.sql (2)

3-4: Add indexes on foreign-key columns for query performance

Postgres does NOT create an index on the referencing column (userId, subscriptionId).
Both columns are expected to be used in joins / filters (e.g. “all plans for a user”, “all payments for a subscription”). Lack of an index will lead to seq-scans as the table grows.

+-- Speed up look-ups by userId
+CREATE INDEX IF NOT EXISTS easyinvoice_subscription_plans_user_idx
+  ON "easyinvoice_subscription_plans" ("userId");
+
+-- Speed up look-ups by subscriptionId
+CREATE INDEX IF NOT EXISTS easyinvoice_recurring_payment_subscription_idx
+  ON "easyinvoice_recurring_payment" ("subscriptionId");

No behaviour change, just better scaling.

Also applies to: 15-24


15-15: Use IF NOT EXISTS when adding the column for extra idempotency

The migration engine guarantees single execution, but adding the guard costs nothing and avoids local/dev DB clashes when a teammate rolls back & re-applies:

-ALTER TABLE "easyinvoice_recurring_payment" ADD COLUMN "subscriptionId" text;
+ALTER TABLE "easyinvoice_recurring_payment"
+  ADD COLUMN IF NOT EXISTS "subscriptionId" text;

Same pattern you already use for the FK blocks.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 94992c5 and b191249.

📒 Files selected for processing (10)
  • drizzle/0007_slimy_tyger_tiger.sql (1 hunks)
  • drizzle/meta/0007_snapshot.json (1 hunks)
  • drizzle/meta/_journal.json (1 hunks)
  • src/components/dashboard/subscriptions.tsx (1 hunks)
  • src/components/subscription-plan-preview.tsx (1 hunks)
  • src/components/subscription-plans/blocks/create-subscription-plan.tsx (1 hunks)
  • src/components/subscription-plans/blocks/subscription-plan-link.tsx (1 hunks)
  • src/lib/schemas/subscription-plan.ts (1 hunks)
  • src/server/db/schema.ts (5 hunks)
  • src/server/routers/subscription-plan.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/components/subscription-plans/blocks/create-subscription-plan.tsx
  • src/lib/schemas/subscription-plan.ts
  • src/components/subscription-plans/blocks/subscription-plan-link.tsx
  • drizzle/meta/_journal.json
  • src/server/routers/subscription-plan.ts
  • src/components/subscription-plan-preview.tsx
  • src/server/db/schema.ts
  • drizzle/meta/0007_snapshot.json
🧰 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: bassgeta
PR: RequestNetwork/easy-invoice#91
File: drizzle/0007_awesome_gertrude_yorkes.sql:8-8
Timestamp: 2025-07-14T14:29:03.237Z
Learning: In the RequestNetwork/easy-invoice codebase, database migration 006 creates the frequency_enum type that migration 007 references for the subscription plans table. The migration dependencies are properly structured with foundational types created in earlier migrations.
drizzle/0007_slimy_tyger_tiger.sql (3)
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#91
File: drizzle/0007_awesome_gertrude_yorkes.sql:8-8
Timestamp: 2025-07-14T14:29:03.237Z
Learning: In the RequestNetwork/easy-invoice codebase, database migration 006 creates the frequency_enum type that migration 007 references for the subscription plans table. The migration dependencies are properly structured with foundational types created in earlier migrations.
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: bassgeta
PR: RequestNetwork/easy-invoice#91
File: drizzle/0007_awesome_gertrude_yorkes.sql:1-12
Timestamp: 2025-07-14T14:27:58.530Z
Learning: In the RequestNetwork/easy-invoice codebase, the user bassgeta prefers to maintain consistent timestamp formats across all database tables. They use `timestamp DEFAULT now()` rather than `timestamptz NOT NULL DEFAULT now()` and prefer not to change this in isolated places unless it's done consistently throughout the entire codebase.
🔇 Additional comments (10)
src/components/dashboard/subscriptions.tsx (9)

1-36: Clean imports and well-structured constants.

The imports are well-organized and the constants ITEMS_PER_PAGE and ACTIVE_STATUSES are appropriately defined. The component structure follows React best practices with proper separation of concerns.


38-50: Table column headers are properly structured.

The SubscriptionTableColumns component provides a clean, reusable header structure for the subscription table with appropriate styling and semantic HTML.


52-70: Subscription row setup and cancellation logic are well-implemented.

The component properly uses the custom hook useCancelRecurringPayment with appropriate success callback to invalidate the cache. The cancellation handler includes proper loading state checks.


71-83: Trial date calculation logic is robust.

The getTrialEndDate function properly handles edge cases where trial days or creation date might be missing, providing fallback values and proper date formatting.


84-141: Comprehensive subscription row rendering with proper accessibility.

The table row implementation is well-structured with:

  • Proper date formatting and fallback handling
  • Consistent badge usage for status and frequency
  • Accessible button with screen reader support
  • Loading states and proper disable logic
  • Clean layout with currency formatting

143-152: Type definitions are clear and well-structured.

The SubscriptionWithDetails type properly extends RecurringPayment with optional subscription details, and the SubscriptionProps interface is concise and appropriate.


154-169: Data fetching and calculation logic are properly implemented.

The component uses TRPC query with initial data hydration correctly, and the totalSpent calculation properly filters by active statuses and handles potential null values.


170-191: Statistics cards provide valuable user insights.

The three stat cards (Active Subscriptions, Total Plans, Total Spent) offer meaningful metrics with appropriate icons and proper null handling.


192-234: Table implementation with proper empty state and pagination.

The table structure is well-implemented with:

  • Proper empty state handling when no subscriptions exist
  • Client-side pagination that slices data appropriately
  • Consistent styling and layout
  • Proper conditional rendering for pagination controls
drizzle/0007_slimy_tyger_tiger.sql (1)

10-13: Dismiss suggested conversion of amount to numeric
Monetary values in this project are intentionally stored as text across all tables to ensure consistency. Switching this single column to numeric would conflict with the established schema. Likewise, other tables omit NOT NULL on created_at—adding it here in isolation would break consistency.

Likely an incorrect or invalid review comment.

@aimensahnoun aimensahnoun self-requested a review July 18, 2025 11:09
@bassgeta bassgeta merged commit 7202ca3 into main Jul 18, 2025
5 checks passed
@bassgeta bassgeta deleted the feat/18-recurring-invoice-payments branch July 18, 2025 11:31
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 - Pre-aprove Recurring Payment for Recurring Invoice
2 participants