Skip to content

Conversation

@envoy1084
Copy link
Contributor

@envoy1084 envoy1084 commented Nov 30, 2025

This PR Fixes the following things in Invoice Creation Form

  • Push to /invoices after successful creation
  • Add formSchema.superrefine() to handle all internal validations based on data.
  • Add check for due date greater that today's date for Non-Recurring Payment Invoices
  • Fix issue where Item with Price 0 was passing on handleSubmit
  • Add Address Schema for better Ethereum Address Validation

Summary by CodeRabbit

  • New Features

    • Allow submitting invoices when Crypto-to-fiat is enabled without a linked payment method.
    • Post-success navigation now returns to the Invoices list.
  • Bug Fixes / Validation

    • Added Ethereum address validation for wallet fields.
    • Invoice item prices must be greater than zero.
    • Improved recurring-invoice and due-date validations.
  • UI

    • Adjusted item-row alignment in the invoice form.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 30, 2025

Walkthrough

Updated 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

Cohort / File(s) Change Summary
Navigation routing
src/components/invoice/invoice-creator.tsx
Post-success navigation updated to route to /invoices instead of /dashboard for both createFromInvoiceMe and create mutations.
Form submission logic & UI
src/components/invoice/invoice-form/invoice-form.tsx
Removed pre-submit validation that blocked submission when crypto-to-fiat was enabled and no linked payment method; removed initial setIsSubmitting(true) call; simplified useCallback dependency array for handleFormSubmit; adjusted item row alignment from items-end to items-start.
Schema validation consolidation
src/lib/schemas/invoice.ts
Added exported AddressSchema for Ethereum addresses and changed walletAddress to AddressSchema.optional(); changed item price validation from min(0) to positive(); replaced multiple refine calls with a single superRefine implementing cross-field rules (recurring invoice startDate/frequency, walletAddress vs paymentDetailsId requirements based on crypto-to-fiat availability, and dueDate future check).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Inspect superRefine logic to ensure error paths map correctly to form fields.
  • Verify removal of the pre-submit payment-method guard does not allow invalid states when crypto-to-fiat is enabled.
  • Validate AddressSchema behavior against expected Ethereum address formats and confirm positive() pricing aligns with UX/error messages.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: fixing invoice form validation across multiple files and schema improvements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2efaae6 and 7ac720a.

📒 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.tsx
  • src/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.tsx
  • src/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 /invoices instead 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 useState are stable and don't need to be listed.


740-740: LGTM!

Using items-start provides 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 AddressSchema to 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.isRecurring condition.

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

Copy link
Contributor

@bassgeta bassgeta left a comment

Choose a reason for hiding this comment

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

Looking good, just a nitpick regarding the address validator!
Other than that the existing errors work nicely
image

And the crypto to fiat error is now added to the validation in a much cleaner way 👌

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/lib/schemas/invoice.ts (1)

5-7: AddressSchema extraction is good; double‑check optional/empty string handling for walletAddress.

Good call extracting AddressSchema with isEthereumAddress and reusing it for walletAddress; this keeps Ethereum address validation centralized and consistent with other usages in the codebase. Using it here via AddressSchema.optional() is also the right direction.

One thing to verify: how empty values are passed from the form for optional fields. With AddressSchema.optional(), undefined is allowed, but a blank string "" will still go through the base string schema and then fail refine, yielding an "Invalid Ethereum Address" error even in flows where walletAddress is not required (e.g. crypto‑to‑fiat). If your form library sends "" rather than undefined for 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 undefined or explicitly allowing "", for example:

const AddressSchema = z
  .preprocess(
    (value) => (value === "" ? undefined : value),
    z.string().refine(isEthereumAddress, "Invalid Ethereum Address"),
  );

This keeps required checks in superRefine while making the field truly optional elsewhere.

Also applies to: 32-32

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03aca06 and d3f255e.

📒 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 price from .min(0, ...) to .positive("Price must be greater than 0") aligns the schema with the requirement that items with price 0 should not pass validation and keeps the intent clear in the message. This looks good.


39-56: Cross‑field superRefine rules for recurring and payment mode look consistent.

The consolidated superRefine logic reads well:

  • For recurring invoices, startDate and frequency are both enforced when isRecurring is true.
  • For payment mode:
    • When isCryptoToFiatAvailable is false, walletAddress is required.
    • When isCryptoToFiatAvailable is true, paymentDetailsId is 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 a YYYY‑MM‑DD string 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"],
+      });
+    }

@MantisClone MantisClone moved this from 🔖 Sprint Backlog to 👀 In Review in Request Network Tech Backlog Dec 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 👀 In Review

Development

Successfully merging this pull request may close these issues.

2 participants