-
Notifications
You must be signed in to change notification settings - Fork 7
fix: invoice create form validation #175
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
base: main
Are you sure you want to change the base?
Conversation
WalkthroughUpdated invoice creation routing, simplified form submission logic by removing a pre-submit payment-method guard and an early isSubmitting set, and consolidated invoice schema validations (added Ethereum address schema, switched item price to positive, and implemented cross-field checks via superRefine). Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/components/invoice/invoice-creator.tsx(2 hunks)src/components/invoice/invoice-form/invoice-form.tsx(2 hunks)src/lib/schemas/invoice.ts(3 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: aimensahnoun
Repo: RequestNetwork/easy-invoice PR: 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
Repo: RequestNetwork/easy-invoice PR: 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
Repo: RequestNetwork/easy-invoice PR: 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
Repo: RequestNetwork/easy-invoice PR: 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.
📚 Learning: 2025-08-08T09:52:43.700Z
Learnt from: bassgeta
Repo: RequestNetwork/easy-invoice PR: 117
File: src/app/not-found.tsx:29-67
Timestamp: 2025-08-08T09:52:43.700Z
Learning: Repository: RequestNetwork/easy-invoice
Context: src/app/not-found.tsx (Next.js, TypeScript/React)
Learning: It is acceptable (by current product decision) for the NotFound page to display links to /invoices/create and /subscription-plans even though these routes are auth-gated. Rationale: prevent the 404 from feeling empty; most users are authenticated. No lock icons or conditional rendering required for now; can be revisited later as an enhancement.
Applied to files:
src/components/invoice/invoice-creator.tsx
📚 Learning: 2025-05-19T13:00:48.790Z
Learnt from: rodrigopavezi
Repo: RequestNetwork/easy-invoice PR: 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.
Applied to files:
src/components/invoice/invoice-creator.tsxsrc/components/invoice/invoice-form/invoice-form.tsx
📚 Learning: 2025-05-20T12:59:44.665Z
Learnt from: rodrigopavezi
Repo: RequestNetwork/easy-invoice PR: 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.
Applied to files:
src/components/invoice/invoice-form/invoice-form.tsx
📚 Learning: 2025-10-28T12:16:58.341Z
Learnt from: bassgeta
Repo: RequestNetwork/easy-invoice PR: 168
File: src/components/payment-widget/components/payment-success.tsx:57-60
Timestamp: 2025-10-28T12:16:58.341Z
Learning: The payment widget at src/components/payment-widget/** is an external component installed via ShadCN registry and should not receive detailed code modification suggestions. The project treats this directory as external/third-party code (configured in biome.json to be ignored).
Applied to files:
src/components/invoice/invoice-form/invoice-form.tsx
📚 Learning: 2025-02-12T12:42:40.076Z
Learnt from: aimensahnoun
Repo: RequestNetwork/easy-invoice PR: 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.
Applied to files:
src/components/invoice/invoice-form/invoice-form.tsxsrc/lib/schemas/invoice.ts
🧬 Code graph analysis (1)
src/components/invoice/invoice-creator.tsx (1)
src/server/trpc.ts (1)
router(11-11)
⏰ 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). (2)
- GitHub Check: Lint
- GitHub Check: Build
🔇 Additional comments (7)
src/components/invoice/invoice-creator.tsx (1)
52-52: LGTM!Post-creation navigation updated consistently for both mutation paths to route users to
/invoicesinstead of/dashboard.Also applies to: 67-67
src/components/invoice/invoice-form/invoice-form.tsx (2)
420-420: LGTM!The dependency array correctly reflects the values used within the callback. State setters from
useStateare stable and don't need to be listed.
740-740: LGTM!Using
items-startprovides better vertical alignment when validation error messages appear below inputs, preventing layout shifts.src/lib/schemas/invoice.ts (4)
4-7: LGTM!The regex correctly validates standard Ethereum address format (0x prefix + 40 hex characters). Note that this validates format only, not EIP-55 checksums, which is appropriate for form input validation.
22-22: LGTM!Using
positive()ensures items cannot have a zero price, correctly addressing the PR objective to prevent zero-priced items from passing validation.
31-31: LGTM!Good use of the new
AddressSchemato enforce valid Ethereum address format when a wallet address is provided.
75-83: Verify: Due date validation scope.The PR description states: "Add check for due date greater than today's date for Non-Recurring Payment Invoices." However, this validation applies to all invoices. If this is intentional, the behavior is reasonable. If the validation should only apply to non-recurring invoices, wrap this check in an
!data.isRecurringcondition.Additionally, comparing timestamps may reject "today" as a due date since the date input yields midnight UTC while
new Date()includes the current time. Consider using date-only comparison if same-day due dates should be allowed:- const dueDate = new Date(data.dueDate).getTime(); - const now = new Date().getTime(); - if (dueDate < now) { + const dueDate = new Date(data.dueDate); + const today = new Date(); + today.setHours(0, 0, 0, 0); + if (dueDate < today) {
bassgeta
left a comment
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/lib/schemas/invoice.ts (1)
5-7: AddressSchema extraction is good; double‑check optional/empty string handling forwalletAddress.Good call extracting
AddressSchemawithisEthereumAddressand reusing it forwalletAddress; this keeps Ethereum address validation centralized and consistent with other usages in the codebase. Using it here viaAddressSchema.optional()is also the right direction.One thing to verify: how empty values are passed from the form for optional fields. With
AddressSchema.optional(),undefinedis allowed, but a blank string""will still go through the base string schema and then failrefine, yielding an"Invalid Ethereum Address"error even in flows wherewalletAddressis not required (e.g. crypto‑to‑fiat). If your form library sends""rather thanundefinedfor untouched/cleared fields, that could make this effectively required or show confusing errors in optional contexts.If that shows up in the UI, consider normalizing empty strings to
undefinedor explicitly allowing"", for example:const AddressSchema = z .preprocess( (value) => (value === "" ? undefined : value), z.string().refine(isEthereumAddress, "Invalid Ethereum Address"), );This keeps required checks in
superRefinewhile making the field truly optional elsewhere.Also applies to: 32-32
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/lib/schemas/invoice.ts(3 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: aimensahnoun
Repo: RequestNetwork/easy-invoice PR: 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
Repo: RequestNetwork/easy-invoice PR: 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
Repo: RequestNetwork/easy-invoice PR: 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
Repo: RequestNetwork/easy-invoice PR: 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
Repo: RequestNetwork/easy-invoice PR: 21
File: src/app/invoices/[ID]/page.tsx:113-148
Timestamp: 2025-02-20T10:27:02.993Z
Learning: The easy-invoice project prefers simpler, direct implementations over abstract utilities. For example, using `.toFixed(2)` directly instead of creating separate number formatting utilities.
📚 Learning: 2025-02-12T12:42:40.076Z
Learnt from: aimensahnoun
Repo: RequestNetwork/easy-invoice PR: 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.
Applied to files:
src/lib/schemas/invoice.ts
📚 Learning: 2025-06-04T12:02:39.411Z
Learnt from: aimensahnoun
Repo: RequestNetwork/easy-invoice PR: 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.
Applied to files:
src/lib/schemas/invoice.ts
📚 Learning: 2025-10-13T19:12:34.359Z
Learnt from: MantisClone
Repo: RequestNetwork/easy-invoice PR: 0
File: :0-0
Timestamp: 2025-10-13T19:12:34.359Z
Learning: In `src/server/routers/ecommerce.ts`, the `create` procedure for client IDs should use `?? undefined` for `feePercentage` and `feeAddress` when calling the external API, because the backend create endpoint uses `.optional()` and rejects `null`. However, the `edit` procedure should use `?? null` for these fields because the backend update endpoint uses `.nullable().optional()`, which allows `null` values to support explicitly unsetting fees.
Applied to files:
src/lib/schemas/invoice.ts
⏰ 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 (3)
src/lib/schemas/invoice.ts (3)
18-26: Item price.positive()correctly blocks 0‑price items.Switching
pricefrom.min(0, ...)to.positive("Price must be greater than 0")aligns the schema with the requirement that items with price0should not pass validation and keeps the intent clear in the message. This looks good.
39-56: Cross‑fieldsuperRefinerules for recurring and payment mode look consistent.The consolidated
superRefinelogic reads well:
- For recurring invoices,
startDateandfrequencyare both enforced whenisRecurringistrue.- For payment mode:
- When
isCryptoToFiatAvailableisfalse,walletAddressis required.- When
isCryptoToFiatAvailableistrue,paymentDetailsIdis required.This matches the intended behavior, keeps cross‑field rules in the schema where they belong (consistent with prior learnings about centralizing invoice validation), and the updated error messages now correctly reflect the crypto‑to‑fiat logic rather than “recurring invoices”. No changes needed here.
Also applies to: 58-74
76-85: Fix due‑date comparison to avoid timezone bugs and confirm requirement scope.Using
new Date(data.dueDate).getTime()with aYYYY‑MM‑DDstring is unsafe: in JavaScript this parses as UTC, not local time. For users in negative timezones (e.g. US), a due date that is "tomorrow" in their calendar can be interpreted as yesterday afternoon local time, causing valid future dates to be rejected.Example:
- Local: 2025‑12‑08 17:00 PST
- User selects 2025‑12‑09 in
<input type="date">→"2025-12-09"new Date("2025-12-09")→ 2025‑12‑09T00:00:00Z = 2025‑12‑08 16:00 PST- Current validation incorrectly rejects this as invalid.
Also confirm whether the validation should apply only to non‑recurring invoices (per PR objective) or to all invoices. If scope is limited to non‑recurring, wrap the validation accordingly.
Compare local calendar dates instead of timestamps to resolve the timezone issue:
- const dueDate = new Date(data.dueDate).getTime(); - const now = new Date().getTime(); - if (dueDate < now) { - ctx.addIssue({ - code: "custom", - message: "Due date must be in future", - path: ["dueDate"], - }); - } + const [year, month, day] = data.dueDate.split("-").map(Number); + const dueDate = new Date(year, month - 1, day); + const today = new Date(); + today.setHours(0, 0, 0, 0); + + if (dueDate <= today) { + ctx.addIssue({ + code: "custom", + message: "Due date must be in the future", + path: ["dueDate"], + }); + }

This PR Fixes the following things in Invoice Creation Form
/invoicesafter successful creationformSchema.superrefine()to handle all internal validations based on data.handleSubmitSummary by CodeRabbit
New Features
Bug Fixes / Validation
UI
✏️ Tip: You can customize this high-level summary in your review settings.