Skip to content

Conversation

@envoy1084
Copy link
Contributor

@envoy1084 envoy1084 commented Aug 15, 2025

Fixes: #124, #123, #119, #121

  • Subscription Form has correct Checksum address as pre-populated field.
  • Fix to correct chain as per payment currency in single, batch and recurring payouts.
  • Creating Invoice through InvoiceMe links correctly redirects and shows up in dashboard.
image

Summary by CodeRabbit

  • New Features

    • Centralized, retry-capable network switching and a public retry utility exposed for resilient operations.
    • Invoice page now receives current user context for authorization-aware flows.
  • Improvements

    • Wallet/network auto-switches before direct, batch, and recurring payments; switching logic simplified.
    • Recipient addresses normalized (checksummed) for subscription plans.
    • Invoice submission is async with improved button states/labels and unified success handling.
  • Chores

    • Tooltip dependency bumped to latest patch.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 15, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds a centralized, retryable network-switching hook and retry utility; applies network switching across payment flows; normalizes subscription payee addresses to checksummed form; unifies invoice mutation to async mutate and currentUser-aware navigation; updates InvoiceForm onSubmit signature and submit-button state; bumps a Radix dependency.

Changes

Cohort / File(s) Summary
Network switch & retry infra
src/lib/hooks/use-switch-network.ts, src/lib/utils.ts, src/lib/helpers/chain.ts
New useSwitchNetwork hook exposing switchToPaymentNetwork, switchToChainId, and chainId; new generic retry utility with lifecycle hooks/types; helper getChainFromPaymentCurrency mapping paymentCurrency → appkit chain.
Applied switching in payment flows
src/components/batch-payout.tsx, src/components/direct-payout.tsx, src/components/create-recurring-payment/.../create-recurring-payment-form.tsx, src/components/dashboard/invoices-received.tsx, src/components/payment-section.tsx
Replace prior AppKit switching with useSwitchNetwork; call switchToPaymentNetwork(...) or switchToChainId(...) before payment actions; some switch-related error handling simplified/centralized.
Invoice creation & page behavior
src/components/invoice-creator.tsx, src/app/i/[id]/page.tsx, src/components/invoice-form.tsx
Switch to mutateAsync for invoice creation, update onSuccess to consider currentUser and redirect logic, pass currentUser prop into InvoiceCreator, change InvoiceForm.onSubmit to async and adjust submit-button label/state on created.
Subscription payee address normalization
src/components/subscription-plans/blocks/create-subscription-plan.tsx
Normalize stored payee address using getAddress (viem) so the form is pre-populated with a checksummed address.
Misc / deps
package.json
Bumped @radix-ui/react-tooltip from ^1.2.7^1.2.8.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant UI as Payment Form
  participant Hook as useSwitchNetwork
  participant Wallet
  participant API as Backend
  rect rgb(245,250,255)
  User->>UI: Submit payment (includes paymentCurrency)
  UI->>Hook: switchToPaymentNetwork(paymentCurrency)
  Hook->>Wallet: switchWithRetry(targetChain)
  end
  alt switch success
    Wallet-->>UI: switched
    UI->>API: proceed with payment request
    API-->>UI: response
    UI-->>User: toast/result
  else switch failure
    Wallet-->>UI: error
    UI-->>User: error toast
  end
Loading
sequenceDiagram
  participant User
  participant UI as InvoiceCreator
  participant API as api.invoice.create (mutateAsync)
  participant Cache as utils.invoice.getAll
  User->>UI: Submit invoice form
  UI->>API: await createInvoice(data)
  alt success
    UI->>Cache: invalidate
    UI-->>User: success toast
    UI->>UI: navigate to /dashboard (if applicable)
  else failure
    UI-->>User: error toast
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Assessment against linked issues

Objective Addressed Explanation
Pre-populate checksummed address in Create Subscription form (#124)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Add network switching hook & retry utility (src/lib/hooks/use-switch-network.ts, src/lib/utils.ts, src/lib/helpers/chain.ts) Infrastructure unrelated to the address-normalization objective in #124.
Apply network switching across payment components (src/components/* such as payment-section.tsx, batch-payout.tsx, direct-payout.tsx) Payment-flow changes extend beyond the address-normalization objective in #124.
Consolidate invoice mutation & add currentUser redirect (src/components/invoice-creator.tsx, src/app/i/[id]/page.tsx) Invoice creation/navigation behavior unrelated to address pre-population for subscriptions.

Possibly related PRs

Suggested reviewers

  • rodrigopavezi
  • bassgeta
  • MantisClone

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between dc0defa and 39eb2fd.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • package.json (1 hunks)
  • src/components/payment-section.tsx (3 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • 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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (5)
src/components/subscription-plans/blocks/create-subscription-plan.tsx (1)

86-90: Avoid overwriting user input and guard against unexpected address normalization errors

If the user edits the payee field and the wallet address changes, this effect will overwrite their manual input. Also, while unlikely, getAddress can throw if fed an unexpected value. Consider only setting when payee is empty and add a small safeguard.

Apply this diff:

-  useEffect(() => {
-    if (address) {
-      form.setValue("payee", getAddress(address));
-    }
-  }, [address, form]);
+  useEffect(() => {
+    if (address && !form.getValues("payee")) {
+      try {
+        form.setValue("payee", getAddress(address), { shouldValidate: true });
+      } catch {
+        toast.error("Failed to normalize connected address");
+      }
+    }
+  }, [address, form]);
src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx (1)

98-106: Confirm product intent: Recurring payments were previously Sepolia-only

Per prior preference captured in learnings, recurring payments were deliberately limited to Sepolia. This PR makes the target chain dynamic based on invoiceCurrency. Please confirm whether this change is desired for recurring flows; otherwise, we should hardcode Sepolia or validate the selection accordingly.

src/components/invoice-creator.tsx (3)

75-86: Awaiting mutate() is a no-op; simplify onSubmit or switch to mutateAsync

createInvoice here is mutate (void-returning). await createInvoice(data) won't wait for the server call, and the try/catch won’t catch server errors (they're handled by onError). Either:

  • Minimal change: keep mutate and drop async/await and the try/catch, or
  • If you need to await and handle errors here, switch to mutateAsync in the hook and keep try/catch.

Minimal-change diff (recommended):

-  const onSubmit = async (data: InvoiceFormValues) => {
-    try {
-      await createInvoice(data);
-    } catch (error) {
-      toast.error("Failed to create invoice", {
-        description:
-          error instanceof Error
-            ? error.message
-            : "An unexpected error occurred",
-      });
-    }
-  };
+  const onSubmit = (data: InvoiceFormValues) => {
+    createInvoice(data);
+  };

46-53: Avoid surfacing raw error messages to end-users

error.message can be technical. Consider logging the raw error for observability and showing a stable, friendly message in the toast description to avoid leaking internals.

-    onError: (error) => {
-      toast.error("Failed to create invoice", {
-        description:
-          error instanceof Error
-            ? error.message
-            : "An unexpected error occurred",
-      });
-    },
+    onError: (error) => {
+      console.error("invoice.create failed:", error);
+      toast.error("Failed to create invoice", {
+        description: "Please try again. If the problem persists, contact support.",
+      });
+    },

96-104: Avoid force-casting to User; pass a narrower type or adjust InvoiceForm’s props

The cast to User (including the unknown as User fallback) is a code smell and can mask type mismatches. Prefer narrowing the prop type consumed by InvoiceForm to exactly what it needs (e.g., Pick<User, "id" | "name" | "email">) and make it optional, so you don’t need unsafe casts.

📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3b41f40 and 52522cb.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • package.json (1 hunks)
  • src/components/batch-payout.tsx (4 hunks)
  • src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx (4 hunks)
  • src/components/direct-payout.tsx (4 hunks)
  • src/components/invoice-creator.tsx (1 hunks)
  • src/components/subscription-plans/blocks/create-subscription-plan.tsx (2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-06-04T10:08:40.123Z
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.

Applied to files:

  • src/components/batch-payout.tsx
📚 Learning: 2025-07-11T11:17:32.659Z
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.659Z
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.

Applied to files:

  • src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx
  • src/components/direct-payout.tsx
📚 Learning: 2025-08-08T09:52:43.700Z
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#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-creator.tsx
🧬 Code Graph Analysis (4)
src/components/batch-payout.tsx (1)
src/lib/constants/chains.ts (2)
  • NETWORK_TO_ID (19-27)
  • ID_TO_APPKIT_NETWORK (29-36)
src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx (1)
src/lib/constants/chains.ts (2)
  • NETWORK_TO_ID (19-27)
  • ID_TO_APPKIT_NETWORK (29-36)
src/components/invoice-creator.tsx (2)
src/trpc/react.tsx (1)
  • api (12-12)
src/trpc/server.ts (1)
  • api (31-61)
src/components/direct-payout.tsx (1)
src/lib/constants/chains.ts (2)
  • NETWORK_TO_ID (19-27)
  • ID_TO_APPKIT_NETWORK (29-36)
🔇 Additional comments (11)
package.json (1)

34-34: Patch bump to @radix-ui/react-tooltip looks safe

Minor version bump within the same major range; low risk and no downstream changes in this PR depend on it.

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

40-40: Using viem.getAddress to checksum the address is correct

This aligns with EIP-55 and the stated PR objective to pre-populate a checksummed address.

src/components/direct-payout.tsx (2)

7-7: Importing useAppKitNetwork is appropriate here

Enables safe pre-submit chain checks/switching. Usage downstream is consistent.


42-42: Chain-ID mappings import is correct and consistent with other components

Matches the approach used elsewhere in this PR.

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

23-23: Importing chain mappings is fine

Consistent with how other payout components derive and switch networks.


56-56: useAppKitNetwork usage LGTM

Correctly retrieving chainId and switchNetwork for pre-submit checks.

src/components/batch-payout.tsx (3)

7-7: Importing useAppKitNetwork for pre-submit checks is appropriate

Prepares this component for chain-aware batch execution.


53-53: Chain mappings import is consistent with other components

No issues.


79-79: Hook usage LGTM

Correctly pulls chainId and switchNetwork for the submit path.

src/components/invoice-creator.tsx (2)

40-54: Consolidation to a single invoice.create mutation looks good

Switching to a unified mutation and handling success (toast, invalidate, redirect) within the mutation callback is clean and reduces branching complexity. The invalidate-then-redirect order is sensible.


119-121: Confirmed: paymentDetailsId is declared and optional in the form schema

The Zod schema in src/lib/schemas/invoice.ts defines

paymentDetailsId: z.string().optional()

so InvoiceFormValues includes paymentDetailsId?: string.
form.watch("paymentDetailsId") correctly returns string | undefined and matches InvoicePreviewProps.paymentDetailsId. No further action needed.

@envoy1084 envoy1084 requested a review from aimensahnoun August 18, 2025 11:34
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: 5

🔭 Outside diff range comments (1)
src/components/payment-section.tsx (1)

331-340: Await the network switch and handle failures to avoid race conditions and bad UX.

switchToChainId returns a Promise (via the retry wrapper). Not awaiting it means subsequent provider/signer operations may run on the wrong chain or race the switch. Also, errors during switching won’t be surfaced.

Apply this diff:

-      switchToChainId(targetChain);
+      try {
+        await switchToChainId(targetChain);
+      } catch (err) {
+        console.error("Network switch failed:", err);
+        toast("Network switch failed", {
+          description:
+            (err as Error)?.message ??
+            "We couldn't switch your wallet to the required network.",
+        });
+        setPaymentProgress("idle");
+        return;
+      }

Additional note:

  • Consider guarding selectedRoute: if it’s null (e.g., brief race before selection), targetChain could be undefined, and targetAppkitNetwork.name access will throw. A simple early return with a toast would help.
🧹 Nitpick comments (3)
src/lib/utils.ts (2)

42-46: Clarify semantics of "retries" (it currently means max attempts).

The loop below runs at most retries attempts total. The inline comment implies "number of retries," which often means "retries after the initial attempt." Please clarify to avoid misuse.

Apply this diff to make the intent explicit:

 export interface RetryConfig {
-  retries?: number; // number of retries (default: 3)
+  retries?: number; // max attempts including the first try (default: 3)
   delay?: number; // delay between retries in ms (default: 1000)
   backoff?: boolean; // exponential backoff (default: false)
 }

56-68: Guard against 0 (or negative) retries and add an optional non-retryable error predicate.

  • If retries is 0, the loop never runs, lastError stays undefined, and you end up throwing undefined.
  • For wallet UX, we typically should not retry on user-rejected requests (EIP-1193 code === 4001). A shouldRetry predicate lets callers short-circuit on non-retryable errors.

Apply this diff:

 export async function retry<T>(
   fn: () => Promise<T>,
   options: RetryOptions<T> = {},
 ): Promise<T> {
   const {
     retries = 3,
     delay = 1000,
     backoff = false,
     onError,
     onSuccess,
     onRetry,
+    // Optional predicate to decide if we should continue retrying
+    // Returning false will stop retrying immediately.
+    // Useful to avoid retrying on user-rejected wallet requests (e.g., code 4001).
+    // Not provided => always retry (until attempts are exhausted).
+    shouldRetry,
   } = options;
 
+  // Ensure at least one attempt
+  const maxAttempts = Math.max(1, retries);
   let lastError: unknown;
 
-  for (let attempt = 1; attempt <= retries; attempt++) {
+  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
     try {
       const result = await fn();
       if (onSuccess) await onSuccess(result, attempt);
       return result;
     } catch (err) {
       lastError = err;
 
-      if (attempt < retries) {
+      if (attempt < maxAttempts) {
+        if (shouldRetry) {
+          const retryable = await Promise.resolve(shouldRetry(err, attempt));
+          if (!retryable) break;
+        }
         if (onRetry) await onRetry(attempt);
         const waitTime = backoff ? delay * 2 ** (attempt - 1) : delay;
         await new Promise((res) => setTimeout(res, waitTime));
       }
     }
   }
 
   if (onError) await onError(lastError);
 
   throw lastError;
 }

And extend the options type:

 export interface RetryConfig {
   retries?: number; // max attempts including the first try (default: 3)
   delay?: number; // delay between retries in ms (default: 1000)
   backoff?: boolean; // exponential backoff (default: false)
+  shouldRetry?: (error: unknown, attempt: number) => boolean | Promise<boolean>;
 }
 
 export interface RetryHooks<T> {
   onError?: (error: unknown) => void | Promise<void>;
   onSuccess?: (result: T, attempt: number) => void | Promise<void>;
   onRetry?: (attempt: number) => void | Promise<void>;
 }
 
 export type RetryOptions<T> = RetryConfig & RetryHooks<T>;

Also applies to: 71-85

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

76-76: Also grab chainId to avoid unnecessary switches.

Without checking, you may prompt redundant wallet switches even when already on the correct network.

Apply this diff:

-  const { switchToChainId } = useSwitchNetwork();
+  const { switchToChainId, chainId } = useSwitchNetwork();
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 56f3fd0 and cabad6f.

📒 Files selected for processing (7)
  • src/components/batch-payout.tsx (3 hunks)
  • src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx (3 hunks)
  • src/components/dashboard/invoices-received.tsx (3 hunks)
  • src/components/direct-payout.tsx (3 hunks)
  • src/components/payment-section.tsx (3 hunks)
  • src/lib/hooks/use-switch-network.ts (1 hunks)
  • src/lib/utils.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/components/direct-payout.tsx
  • src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx
  • src/components/batch-payout.tsx
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/components/payment-section.tsx (1)
src/lib/hooks/use-switch-network.ts (1)
  • useSwitchNetwork (7-37)
src/lib/hooks/use-switch-network.ts (3)
src/lib/utils.ts (2)
  • RetryHooks (48-52)
  • retry (56-90)
src/lib/helpers/chain.ts (1)
  • getChainFromPaymentCurrency (3-10)
src/lib/constants/chains.ts (1)
  • ID_TO_APPKIT_NETWORK (29-36)
src/components/dashboard/invoices-received.tsx (2)
src/lib/hooks/use-switch-network.ts (1)
  • useSwitchNetwork (7-37)
src/lib/constants/chains.ts (1)
  • NETWORK_TO_ID (19-27)
🔇 Additional comments (3)
src/components/payment-section.tsx (2)

24-24: Good move to centralize network switching via a hook.

Shifting to useSwitchNetwork helps standardize behavior and makes future improvements (e.g., non-retryable errors) easy to propagate.


109-109: Expose and use chainId via the centralized hook — LGTM.

This keeps consumers in sync with the same source of truth used by switching logic.

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

19-19: Adopting centralized network switching hook — LGTM.

This aligns batch payments with the same retry and error-handling strategy used elsewhere.

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

🔭 Outside diff range comments (1)
src/components/payment-section.tsx (1)

328-340: Network switch can throw before try/catch and freeze UI; also route.chain may be undefined for direct RN route

Two coupled issues here:

  • switchToChainId is awaited outside any try/catch. If the wallet rejects or the hook exhausts retries, the exception escapes, leaving paymentProgress stuck at "getting-transactions" and the button disabled.
  • targetChain derives from selectedRoute?.chain. For REQUEST_NETWORK_PAYMENT, route.chain can be undefined; indexing CHAIN_TO_ID with undefined yields undefined, which then breaks ID_TO_APPKIT_NETWORK[targetChain].name and the switch call.

Harden the logic by:

  • Deriving the target chain id with a safe fallback to invoiceChain for direct RN route.
  • Guarding for unmapped/unknown chains.
  • Wrapping the network switch in try/catch and resetting paymentProgress on failure.

Apply this diff:

-    const targetChain =
-      CHAIN_TO_ID[selectedRoute?.chain as keyof typeof CHAIN_TO_ID];
-
-    if (targetChain !== chainId) {
-      const targetAppkitNetwork =
-        ID_TO_APPKIT_NETWORK[targetChain as keyof typeof ID_TO_APPKIT_NETWORK];
-
-      toast("Switching to network", {
-        description: `Switching to ${targetAppkitNetwork.name} network`,
-      });
-
-      await switchToChainId(targetChain);
-    }
+    // Derive target network robustly:
+    // - For direct RN route, fall back to the invoice chain.
+    // - For crosschain routes, use the route's chain.
+    const targetNetworkKey =
+      selectedRoute?.id === "REQUEST_NETWORK_PAYMENT"
+        ? invoiceChain
+        : selectedRoute?.chain;
+
+    const targetChainId = targetNetworkKey
+      ? CHAIN_TO_ID[
+          targetNetworkKey.toLowerCase() as keyof typeof CHAIN_TO_ID
+        ]
+      : undefined;
+
+    if (!targetChainId) {
+      toast("Unsupported network", {
+        description:
+          "Could not determine the target network for this payment route.",
+      });
+      setPaymentProgress("idle");
+      return;
+    }
+
+    if (targetChainId !== chainId) {
+      const targetAppkitNetwork =
+        ID_TO_APPKIT_NETWORK[
+          targetChainId as keyof typeof ID_TO_APPKIT_NETWORK
+        ];
+
+      toast("Switching to network", {
+        description: targetAppkitNetwork?.name
+          ? `Switching to ${targetAppkitNetwork.name} network`
+          : "Switching network in your wallet",
+      });
+
+      try {
+        await switchToChainId(targetChainId);
+      } catch (_err) {
+        toast("Network switch failed", {
+          description: "Please switch networks in your wallet and try again.",
+        });
+        setPaymentProgress("idle");
+        return;
+      }
+    }
🧹 Nitpick comments (1)
src/components/payment-section.tsx (1)

109-109: Optional: Prefer switchToPaymentNetwork for direct RN route

Given the hook already maps payment currency → chain, you can simplify direct-request flows by calling switchToPaymentNetwork(invoice.paymentCurrency) when selectedRoute?.id === "REQUEST_NETWORK_PAYMENT". That removes the need to juggle CHAIN_TO_ID/ID_TO_APPKIT_NETWORK here and keeps all mapping logic in one place.

📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cabad6f and a12fff4.

📒 Files selected for processing (6)
  • src/components/batch-payout.tsx (3 hunks)
  • src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx (3 hunks)
  • src/components/dashboard/invoices-received.tsx (3 hunks)
  • src/components/direct-payout.tsx (3 hunks)
  • src/components/payment-section.tsx (3 hunks)
  • src/lib/hooks/use-switch-network.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/components/batch-payout.tsx
  • src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx
  • src/components/direct-payout.tsx
  • src/components/dashboard/invoices-received.tsx
  • src/lib/hooks/use-switch-network.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/payment-section.tsx (1)
src/lib/hooks/use-switch-network.ts (1)
  • useSwitchNetwork (7-40)
🔇 Additional comments (1)
src/components/payment-section.tsx (1)

24-24: Good move to centralized network switching

Switching to the shared useSwitchNetwork hook aligns this component with the new cross-app pattern and reduces duplicated switch logic. LGTM.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/payment-section.tsx (1)

332-345: Guard undefined target chain and handle switch failures to avoid a stuck UI

Two issues:

  • targetChain can be undefined (e.g., REQUEST_NETWORK_PAYMENT route), leading to an invalid switch call.
  • switchToChainId errors are uncaught and occur outside the try/catch, leaving paymentProgress stuck on “getting-transactions”.

Add proper chain resolution + try/catch around the switch and early-return on failure.

Apply this diff:

-    const targetChain =
-      CHAIN_TO_ID[selectedRoute?.chain as keyof typeof CHAIN_TO_ID];
-
-    if (targetChain !== chainId) {
-      const targetAppkitNetwork =
-        ID_TO_APPKIT_NETWORK[targetChain as keyof typeof ID_TO_APPKIT_NETWORK];
-
-      toast("Switching to network", {
-        description: `Switching to ${targetAppkitNetwork?.name} network`,
-      });
-
-      await switchToChainId(targetChain);
-    }
+    // Resolve target chain id for the selected route (direct route falls back to invoice chain)
+    const directRouteChain =
+      invoiceChain &&
+      REQUEST_NETWORK_CHAIN_TO_PAYMENT_NETWORK[
+        invoiceChain as keyof typeof REQUEST_NETWORK_CHAIN_TO_PAYMENT_NETWORK
+      ];
+    const chainKeyRaw =
+      selectedRoute?.id === "REQUEST_NETWORK_PAYMENT"
+        ? directRouteChain
+        : selectedRoute?.chain;
+    const chainKey = chainKeyRaw?.toLowerCase();
+    const targetChainId = chainKey
+      ? CHAIN_TO_ID[chainKey as keyof typeof CHAIN_TO_ID]
+      : undefined;
+
+    if (targetChainId == null) {
+      toast("Unsupported network", {
+        description:
+          "Could not determine the target network for the selected route.",
+      });
+      setPaymentProgress("idle");
+      return;
+    }
+
+    if (targetChainId !== chainId) {
+      const targetAppkitNetwork =
+        ID_TO_APPKIT_NETWORK[
+          targetChainId as keyof typeof ID_TO_APPKIT_NETWORK
+        ];
+
+      toast("Switching to network", {
+        description: `Switching to ${targetAppkitNetwork?.name} network`,
+      });
+
+      try {
+        await switchToChainId(targetChainId);
+      } catch {
+        toast("Network switch failed", {
+          description: "Please switch in your wallet and try again.",
+        });
+        setPaymentProgress("idle");
+        return;
+      }
+    }
🧹 Nitpick comments (1)
src/components/payment-section.tsx (1)

109-109: Hook usage is fine; consider pushing “name” concerns into the hook (optional)

To reduce coupling with ID_TO_APPKIT_NETWORK here, consider exposing a getNetworkName(targetChainId) from the hook or returning the resolved Chain to describe the toast. Keeps constants localized.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a12fff4 and 8fedb95.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • package.json (1 hunks)
  • src/components/payment-section.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/payment-section.tsx (1)
src/lib/hooks/use-switch-network.ts (1)
  • useSwitchNetwork (7-40)
⏰ 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 (1)
src/components/payment-section.tsx (1)

24-24: Centralized network switching hook adoption — LGTM

Switching to the shared useSwitchNetwork hook aligns this component with the new centralized logic and retry behavior.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/app/i/[id]/page.tsx (1)

25-33: Guard against null before accessing invoiceMeLink.user; reorder checks to avoid runtime crash

invoiceMeLink is dereferenced before the notFound() guard. If getById returns null, invoiceMeLink.user.id will throw. Move the existence check immediately after the fetch, then fetch session and run the redirect check.

Apply this diff:

-  const currentUser = await api.auth.getSessionInfo.query();
-
-  if (currentUser.user?.id === invoiceMeLink.user.id) {
-    return redirect("/dashboard");
-  }
-
-  if (!invoiceMeLink) {
-    notFound();
-  }
+  if (!invoiceMeLink) {
+    notFound();
+  }
+  const currentUser = await api.auth.getSessionInfo.query();
+  if (currentUser.user?.id && currentUser.user.id === invoiceMeLink.user.id) {
+    return redirect("/dashboard");
+  }
src/components/invoice-form.tsx (3)

56-70: Invalid cast: filterValidPaymentDetails returns wrong shape for LinkedPaymentDetail

You’re returning (PaymentDetails & { paymentDetailsPayers: ... })[] and casting to LinkedPaymentDetail[], but the rest of the code expects detail.paymentDetails.*. This will be undefined at runtime.

Map to the expected shape instead of casting:

-const filterValidPaymentDetails = (
-  paymentDetails: (PaymentDetails & {
-    paymentDetailsPayers: (User & PaymentDetailsPayers)[];
-  })[],
-): LinkedPaymentDetail[] => {
-  // Return all payment details without filtering by client email
-  const validPaymentDetails = paymentDetails.filter((detail) => {
-    // Check if any payer is compliant
-    return detail.paymentDetailsPayers.some(
-      (payer: User & PaymentDetailsPayers) => payer.isCompliant,
-    );
-  });
-  return validPaymentDetails as unknown as LinkedPaymentDetail[];
-};
+const filterValidPaymentDetails = (
+  paymentDetails: (PaymentDetails & {
+    paymentDetailsPayers: (User & PaymentDetailsPayers)[];
+  })[],
+): LinkedPaymentDetail[] => {
+  return paymentDetails
+    .filter((detail) =>
+      detail.paymentDetailsPayers.some((payer) => payer.isCompliant),
+    )
+    .map(({ paymentDetailsPayers, ...rest }) => ({
+      paymentDetails: rest as PaymentDetails,
+      paymentDetailsPayers,
+    }));
+};

480-491: Effect won’t re-run on paymentCurrency/isCryptoToFiat changes

Using form.watch() inside the effect but not in deps means the effect only runs on mount (and when form identity changes). The “disable crypto-to-fiat when currency ≠ fUSDC” rule won’t consistently apply.

Switch to useWatch and depend on those values:

-useEffect(() => {
-  const paymentCurrency = form.watch("paymentCurrency");
-  const isCryptoToFiatEnabled = form.watch("isCryptoToFiatAvailable");
-
-  if (paymentCurrency !== "fUSDC-sepolia" && isCryptoToFiatEnabled) {
-    form.setValue("isCryptoToFiatAvailable", false);
-    // Clear payment details when disabling crypto-to-fiat
-    form.setValue("paymentDetailsId", "");
-    form.clearErrors("paymentDetailsId");
-  }
-}, [form]);
+useEffect(() => {
+  if (paymentCurrency !== "fUSDC-sepolia" && isCryptoToFiatEnabled) {
+    form.setValue("isCryptoToFiatAvailable", false);
+    form.setValue("paymentDetailsId", "");
+    form.clearErrors("paymentDetailsId");
+  }
+}, [paymentCurrency, isCryptoToFiatEnabled, form]);

Add these outside the effect (top-level of component):

import { useFieldArray, useWatch } from "react-hook-form";

const paymentCurrency = useWatch({ control: form.control, name: "paymentCurrency" });
const isCryptoToFiatEnabled = useWatch({
  control: form.control,
  name: "isCryptoToFiatAvailable",
});

405-413: Missing early return causes invoice creation while approval is pending

In the “no payer linked” branch you set waitingForPaymentApproval and link the payment method, but you then fall through to onSubmit, bypassing the approval gate. This can create invoices with unapproved payment methods and even trigger duplicate submissions (approval flow will also submit).

Add an early return after linking:

         } else {
           setShowPendingApprovalModal(true);
           setWaitingForPaymentApproval(true);
           await handleBankAccountSuccess({
             success: true,
             message: "Payment method linked successfully",
             paymentDetails: selectedPaymentDetail.paymentDetails,
           });
+          // Wait for approval before submitting
+          return;
         }
🧹 Nitpick comments (2)
src/components/invoice-form.tsx (2)

493-519: Keep button disabled during submitAfterApproval to avoid double submit

submitAfterApproval sets isSubmitting to false before awaiting onSubmit, momentarily enabling the button and creating a race with user clicks.

Set isSubmitting(true) before submit and clear in finally:

-  // Reset the submitting state and directly call onSubmit
-  setIsSubmitting(false);
+  // Ensure UI stays disabled during the async submit
+  setIsSubmitting(true);
@@
-  await onSubmit(formData);
-  setInvoiceCreated(true);
-  setWaitingForPaymentApproval(false);
+  await onSubmit(formData);
+  setInvoiceCreated(true);
+  setWaitingForPaymentApproval(false);
@@
-  setInvoiceCreated(false);
-  setWaitingForPaymentApproval(false);
-  setIsSubmitting(false);
+  setInvoiceCreated(false);
+  setWaitingForPaymentApproval(false);
+  setIsSubmitting(false);

103-116: Minor: normalize status label to Title Case

Pure nit. If API returns uppercase enums, the current logic renders “APPROVED”. For a cleaner label:

-    {status ? status.charAt(0).toUpperCase() + status.slice(1) : "Unknown"}
+    {status ? status.toLowerCase().replace(/^\w/, (c) => c.toUpperCase()) : "Unknown"}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8fedb95 and 3294a8e.

📒 Files selected for processing (3)
  • src/app/i/[id]/page.tsx (3 hunks)
  • src/components/invoice-creator.tsx (1 hunks)
  • src/components/invoice-form.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/invoice-creator.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-05-19T13:00:48.790Z
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.

Applied to files:

  • src/components/invoice-form.tsx
📚 Learning: 2025-08-08T09:52:43.700Z
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#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/app/i/[id]/page.tsx
🧬 Code graph analysis (1)
src/components/invoice-form.tsx (1)
src/lib/schemas/invoice.ts (1)
  • InvoiceFormValues (75-75)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (4)
src/app/i/[id]/page.tsx (2)

10-10: Importing redirect is correct for server components

Import looks good and matches usage below.


62-70: Prop shape for currentUser looks sane; verify InvoiceCreator typing

Passing an optional { id, name, email } aligns with the summary. Ensure InvoiceCreator’s prop type marks currentUser optional and handles empty string fallbacks as intended.

Would you like a quick repo scan script to confirm all InvoiceCreator usages are updated?

src/components/invoice-form.tsx (2)

93-94: Prop type update to async onSubmit is correct; ensure callers await it

Good change. Confirm all InvoiceForm callers now provide an async handler and properly reflect the loading state back via isLoading.

If helpful, I can generate a quick search to flag non-async onSubmit usages.


1036-1041: Nice UX: disable submit after success

Disabling the button when invoiceCreated is true prevents accidental duplicate creations. Looks good.

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 (4)
src/app/i/[id]/page.tsx (4)

25-25: Fetch session and link in parallel to shave an RTT.
Cuts latency and keeps semantics identical.

Apply:

-  const invoiceMeLink = await api.invoiceMe.getById.query(params.id);
-  const currentUser = await api.auth.getSessionInfo.query();
+  const [invoiceMeLink, currentUser] = await Promise.all([
+    api.invoiceMe.getById.query(params.id),
+    api.auth.getSessionInfo.query(),
+  ]);

31-33: Authorization intent unclear; current guard blocks authenticated non-owners from using InvoiceMe links.
If InvoiceMe is meant for clients (non-owners), this will bounce legitimate users.

Options:

  • Allow anyone (public link): remove the guard.
-  if (currentUser.user && currentUser.user.id !== invoiceMeLink.user.id) {
-    return redirect("/dashboard");
-  }
+  // Public link: no auth-based redirect
  • Owner-only: require exact owner match.
-  if (currentUser.user && currentUser.user.id !== invoiceMeLink.user.id) {
-    return redirect("/dashboard");
-  }
+  if (!currentUser.user || currentUser.user.id !== invoiceMeLink.user.id) {
+    redirect("/dashboard");
+  }

Also ensure server-side invoice creation pins recipient to invoiceMeLink.user.id, not a client-provided value.


31-33: Nit: no need to return redirect in Next.js.
redirect() throws; the return is redundant.

-    return redirect("/dashboard");
+    redirect("/dashboard");

62-70: Confirm InvoiceCreator prop types; consider avoiding empty-string fallbacks.
If name/email are optional in InvoiceCreator, prefer undefined to distinguish “unknown” from “intentionally empty.”

-              ? {
-                  id: currentUser.user.id,
-                  name: currentUser.user.name ?? "",
-                  email: currentUser.user.email ?? "",
-                }
+              ? {
+                  id: currentUser.user.id,
+                  name: currentUser.user.name ?? undefined,
+                  email: currentUser.user.email ?? undefined,
+                }

If the component requires non-empty strings for controlled inputs, keep as-is and ignore this nit.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3294a8e and 2f5db08.

📒 Files selected for processing (1)
  • src/app/i/[id]/page.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-08T09:52:43.700Z
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#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/app/i/[id]/page.tsx
⏰ 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 (1)
src/app/i/[id]/page.tsx (1)

10-10: Import of redirect alongside notFound looks correct.
No concerns here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/app/i/[id]/page.tsx (1)

31-33: Owner-only redirect logic is correct; tiny cleanup: drop return.
Matches prior feedback and prevents locking out other users. Prefer calling redirect() directly since it throws.

Apply this diff:

-  if (currentUser.user && currentUser.user.id === invoiceMeLink.user.id) {
-    return redirect("/dashboard");
-  }
+  if (currentUser.user?.id === invoiceMeLink.user.id) {
+    redirect("/dashboard");
+  }
🧹 Nitpick comments (2)
src/app/i/[id]/page.tsx (2)

25-25: Parallelize session + link fetch to reduce latency.
These two queries are independent; fetch them concurrently to shave one RTT.

Apply this diff:

-  const invoiceMeLink = await api.invoiceMe.getById.query(params.id);
-  const currentUser = await api.auth.getSessionInfo.query();
+  const [invoiceMeLink, currentUser] = await Promise.all([
+    api.invoiceMe.getById.query(params.id),
+    api.auth.getSessionInfo.query(),
+  ]);

62-70: Prop wiring looks good; consider avoiding empty-string fallbacks.
If InvoiceCreator can handle missing name/email, prefer making these optional to avoid sentinel empty strings propagating through UI/validation.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 62c789b and 4a2d4f0.

📒 Files selected for processing (1)
  • src/app/i/[id]/page.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-08T09:52:43.700Z
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#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/app/i/[id]/page.tsx
🔇 Additional comments (1)
src/app/i/[id]/page.tsx (1)

10-10: Importing redirect with notFound — OK.
Usage is consistent with Next.js App Router APIs.

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

♻️ Duplicate comments (1)
src/app/i/[id]/page.tsx (1)

31-33: Redirect only the owner of the InvoiceMe link — correct fix.

This aligns with the earlier review feedback and prevents redirecting other authenticated users.

🧹 Nitpick comments (3)
src/app/i/[id]/page.tsx (3)

10-10: Importing redirect is correct; ensure route is dynamic to avoid session-dependent caching pitfalls.

Since the redirect depends on the logged-in user, confirm this page is rendered dynamically (not statically cached). If not already inferred via session access in your tRPC call, explicitly force dynamic rendering.

Apply this addition at module scope:

+export const dynamic = "force-dynamic";

25-25: Fetch session and invoice link concurrently to reduce TTFB.

Both queries are independent; run them in parallel.

-  const invoiceMeLink = await api.invoiceMe.getById.query(params.id);
-  const currentUser = await api.auth.getSessionInfo.query();
+  const [invoiceMeLink, currentUser] = await Promise.all([
+    api.invoiceMe.getById.query(params.id),
+    api.auth.getSessionInfo.query(),
+  ]);

62-70: Confirmed currentUser Prop Typing – Empty-String Fallback Is Required

I’ve verified that InvoiceCreatorProps declares currentUser? as an object whose name and email fields are required string types (not optional). That means:

  • Using currentUser.user.name ?? "" and currentUser.user.email ?? "" is necessary to satisfy the prop’s type and ensure the form’s defaultValues never receive undefined.
  • If you instead want to allow name/email to be undefined, you would need to update the interface to:
    interface InvoiceCreatorProps {
      currentUser?: {
        id: string;
        name?: string;
        email?: string;
      };
      /* … */
    }
    and add checks downstream. Otherwise, the existing fallbacks are correct.

Optional readability tweak: destructure the mapped currentUser before JSX:

- <InvoiceCreator
-   currentUser={
-     currentUser.user
-       ? {
-           id: currentUser.user.id,
-           name: currentUser.user.name ?? "",
-           email: currentUser.user.email ?? "",
-         }
-       : undefined
-   }
-   invoiceCount={invoiceCount}
- />
+ const u = currentUser.user;
+
+ <InvoiceCreator
+   currentUser={
+     u
+       ? { id: u.id, name: u.name ?? "", email: u.email ?? "" }
+       : undefined
+   }
+   invoiceCount={invoiceCount}
+ />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4a2d4f0 and dc0defa.

📒 Files selected for processing (1)
  • src/app/i/[id]/page.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-08T09:52:43.700Z
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#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/app/i/[id]/page.tsx
⏰ 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

@aimensahnoun aimensahnoun merged commit 4bdc63c into RequestNetwork:main Aug 27, 2025
4 of 5 checks passed
@MantisClone
Copy link
Member

Congratulations, your pull request has been merged! Thank you for your valuable contribution to Request Network. As a reminder, every merged PR is automatically entered into our Best PR Initiative, offering a quarterly prize of $500. Your work significantly supports our project's growth, and we encourage you to continue engaging with our community. Additionally, if you want to build or add crypto payments and invoicing features, explore how our API can reduce deployment time from months to hours while offering advanced features. Book a call with our expert to learn more and fast-track your development.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment