-
Notifications
You must be signed in to change notification settings - Fork 2
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
Conversation
WalkthroughThis 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
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
Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested reviewers
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🧰 Additional context used🧠 Learnings (1)📓 Common learnings
🧬 Code Graph Analysis (1)src/app/dashboard/layout.tsx (5)
⏰ 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)
🔇 Additional comments (4)
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
8ac252e
to
ed4b9b4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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
asMath.max(1, Math.ceil(totalItems / itemsPerPage))
which ensures at least 1 page, but then returnsnull
whentotalItems === 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
tonull
both in theonSuccess
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:
- Removing the commented code if not needed immediately
- Adding a TODO comment explaining the planned feature
- 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 countssrc/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 checkThe 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
📒 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 }
(fromgetCurrentSession
)- When present,
ctx.session
contains only{ id, userId, expiresAt }
(no raw token)ctx.user
is the full row fromuserTable
(fields:id
,googleId
,name
,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
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 thecn
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 asgetAllIssuedByMe
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 syntaxThe 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 toBrowserProvider
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
andchain
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
tototalAmount
is more generic and appropriate for various frequencies. The addition of the nullablesubscriptionId
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 documentedThe 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 filteringThe 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 buttonThe 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 logicThe 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.ExternalProviderWe’re casting
walletProvider
(fromuseAppKitProvider("eip155")
) toethers.providers.ExternalProvider
, but since that hook comes from@reown/appkit/react
, we can’t verify its returned shape here. Please ensure the hook’swalletProvider
truly implements the ExternalProvider interface (i.e. hasrequest
,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 preventionThe 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 delayThe 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 usespublicProcedure
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.
src/components/subscription-plans/blocks/subscription-plan-link.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (2)
drizzle/0007_messy_silver_fox.sql (2)
1-12
: Considertimestamp with time zone
and NOT NULL forcreated_at
timestamp
in Postgres is without time-zone information. Unless you have a compelling reason, prefertimestamptz
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
📒 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. UsingON UPDATE CASCADE
is usually safer and consistent withON 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;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (1)
drizzle/0007_awesome_gertrude_yorkes.sql (1)
8-10
: Consider stricter column types for monetary values
Storingamount
astext
pushes parsing/validation into application code and blocks numeric comparison in SQL. If amounts are on-chain decimals,numeric
with a fixed scale orbigint
(wei) is safer.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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
andsession
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 addedsubscriptionId
Existing rows ineasyinvoice_recurring_payment
will haveNULL
in the new column. Confirm this is acceptable and that application code handles legacy recurring payments without a subscription link.
src/components/subscription-plans/blocks/create-subscription-plan.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (2)
drizzle/0007_slimy_tyger_tiger.sql (2)
3-4
: Add indexes on foreign-key columns for query performancePostgres 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
: UseIF NOT EXISTS
when adding the column for extra idempotencyThe 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
📒 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
andACTIVE_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 extendsRecurringPayment
with optional subscription details, and theSubscriptionProps
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 ofamount
to numeric
Monetary values in this project are intentionally stored astext
across all tables to ensure consistency. Switching this single column tonumeric
would conflict with the established schema. Likewise, other tables omitNOT NULL
oncreated_at
—adding it here in isolation would break consistency.Likely an incorrect or invalid review comment.
Implements subscription links and overview of your plans and your subscribed to plans
Changes
Resolves #18 and the outdated Request API one
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Database & API