Skip to content

feat: add payout confirmation component to batch payouts#189

Merged
aimensahnoun merged 1 commit intomainfrom
01-12-feat_add_payout_confirmation_component_to_batch_payouts
Jan 13, 2026
Merged

feat: add payout confirmation component to batch payouts#189
aimensahnoun merged 1 commit intomainfrom
01-12-feat_add_payout_confirmation_component_to_batch_payouts

Conversation

@aimensahnoun
Copy link
Member

@aimensahnoun aimensahnoun commented Jan 12, 2026

CleanShot 2026-01-13 at 14.23.59.png

TL;DR

Added a confirmation dialog for batch payouts to improve the payment flow and user experience.

What changed?

  • Added a new PayoutConfirmationDialog component to the batch payout flow
  • Implemented a two-step payment process:
    1. First step prepares the batch payment and shows a confirmation dialog
    2. Second step executes the payment after user confirmation
  • Added state management for pending payment data
  • Modified the payment totals calculation to return numeric values instead of strings
  • Updated UI text and loading states to better reflect the current process
  • Added error handling with appropriate user feedback

How to test?

  1. Navigate to the batch payouts page
  2. Fill in payment details for one or more recipients
  3. Click "Process Batch Payment"
  4. Verify that a confirmation dialog appears showing:
    • Currency totals
    • Platform fee
    • Protocol fee
    • Connected wallet address
  5. Confirm the payment and verify the transaction completes successfully
  6. Test error scenarios by canceling the confirmation or disconnecting wallet

Why make this change?

This change enhances user safety by adding an explicit confirmation step before executing batch payments. It gives users a chance to review the total amounts and fees before committing to the transaction, reducing the risk of errors and improving the overall payment experience.

Summary by CodeRabbit

  • New Features

    • Added payout confirmation dialog for batch and direct payments
    • Enhanced status messaging during payout processing ("Preparing..." and "Awaiting Confirmation..." states)
    • Added ability to cancel payout confirmation
  • Bug Fixes

    • Improved dialog state management to properly reset when confirmation is cancelled
    • Removed redundant protocol fee display in batch payout mode

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 12, 2026

Walkthrough

This PR integrates a payout confirmation dialog into batch and direct payout components. The batch payout component now prepares data, displays a confirmation dialog, and manages a new "confirming" state. The confirmation dialog component adds cancel callback support and adjusts its lifecycle to track user cancellation. The direct payout component wires the cancel handler to reset payment status.

Changes

Cohort / File(s) Summary
Batch Payout Dialog Integration
src/app/(dashboard)/payouts/batch/_components/batch-payout.tsx
Added PayoutConfirmationDialog import and ref. Created dialogRef for dialog control. Introduced new handleConfirmBatchPayment handler with network switching and error handling. Expanded paymentStatus state with "confirming" phase. Changed getTotalsByCurrency to return numeric values. Reworked onSubmit to prepare batch data and trigger dialog. Updated UI states and disabled controls during confirming phase.
Direct Payout Cancel Handler
src/app/(dashboard)/payouts/direct/_components/direct-payout.tsx
Added onCancel handler to reset payment status to idle when confirmation dialog is dismissed.
Confirmation Dialog Lifecycle
src/components/payout-confirmation-dialog.tsx
Added onCancel callback method to PayoutConfirmationDialogRef. Introduced cancelCallbackRef and confirmedRef to track cancellation state. Added handleOpenChange to fire cancel callback on close without confirmation. Reset confirmed state on dialog open. Removed total protocol fee display logic for batch mode.

Sequence Diagram

sequenceDiagram
    actor User
    participant Component as Batch Payout<br/>Component
    participant Dialog as Payout<br/>Confirmation<br/>Dialog
    participant Handler as Confirmation<br/>Handler

    User->>Component: Submit batch payout
    Component->>Component: Prepare batch data<br/>(paymentStatus: preparing)
    Component->>Dialog: Show dialog
    Dialog->>Dialog: Reset confirmed state
    Dialog->>User: Display confirmation
    alt User Confirms
        User->>Dialog: Click Confirm
        Dialog->>Dialog: Set confirmed=true
        Dialog->>Handler: Call onConfirm
        Handler->>Component: Process payment<br/>(paymentStatus: confirming)
        Handler->>Component: Update UI on success
    else User Cancels
        User->>Dialog: Close/Cancel dialog
        Dialog->>Handler: Call onCancel
        Handler->>Component: Reset status to idle
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 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: adding a payout confirmation component to batch payouts, which aligns with the primary objective and the substantial changes to batch-payout.tsx.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 211cba7 and c734d2e.

📒 Files selected for processing (3)
  • src/app/(dashboard)/payouts/batch/_components/batch-payout.tsx
  • src/app/(dashboard)/payouts/direct/_components/direct-payout.tsx
  • src/components/payout-confirmation-dialog.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*

⚙️ CodeRabbit configuration file

**/*: - Only comment on issues that would block merging — ignore minor or stylistic concerns.

  • Restrict feedback to errors, security risks, or functionality-breaking problems.
  • Do not post comments on code style, formatting, or non-critical improvements.
  • Keep reviews short: flag only issues that make the PR unsafe to merge.
  • Limit review comments to 3–5 items maximum, unless additional blockers exist.
  • Group similar issues into a single comment instead of posting multiple notes.
  • Skip repetition — if a pattern repeats, mention it once at a summary level only.
  • Do not add general suggestions; focus strictly on merge-blocking concerns.
  • If there are no critical problems, respond with minimal approval (e.g., 'Looks good'). Do not add additional review.
  • Avoid line-by-line commentary unless it highlights a critical bug or security hole.
  • Highlight only issues that could cause runtime errors, data loss, or severe maintainability issues.
  • Ignore minor optimization opportunities — focus solely on correctness and safety.
  • Provide a top-level summary of critical blockers rather than detailed per-line notes.
  • Comment only when the issue must be resolved before merge — otherwise, remain silent.
  • When in doubt, err on the side of fewer comments — brevity and blocking issues only.
  • Avoid posting any refactoring issues

Files:

  • src/app/(dashboard)/payouts/direct/_components/direct-payout.tsx
  • src/components/payout-confirmation-dialog.tsx
  • src/app/(dashboard)/payouts/batch/_components/batch-payout.tsx
🧠 Learnings (9)
📓 Common learnings
Learnt from: aimensahnoun
Repo: RequestNetwork/easy-invoice PR: 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.
📚 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/app/(dashboard)/payouts/direct/_components/direct-payout.tsx
  • src/app/(dashboard)/payouts/batch/_components/batch-payout.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/app/(dashboard)/payouts/direct/_components/direct-payout.tsx
  • src/app/(dashboard)/payouts/batch/_components/batch-payout.tsx
📚 Learning: 2025-06-04T10:08:40.123Z
Learnt from: aimensahnoun
Repo: RequestNetwork/easy-invoice PR: 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/app/(dashboard)/payouts/direct/_components/direct-payout.tsx
  • src/app/(dashboard)/payouts/batch/_components/batch-payout.tsx
📚 Learning: 2025-10-28T12:17:14.899Z
Learnt from: bassgeta
Repo: RequestNetwork/easy-invoice PR: 168
File: src/components/payment-widget/README.md:29-31
Timestamp: 2025-10-28T12:17:14.899Z
Learning: The payment-widget component in src/components/payment-widget/ is an external component installed via ShadCN from the Request Network registry (https://ui.request.network). Its README and documentation should not be modified as it's maintained externally.

Applied to files:

  • src/app/(dashboard)/payouts/batch/_components/batch-payout.tsx
📚 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/app/(dashboard)/payouts/batch/_components/batch-payout.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/app/(dashboard)/payouts/batch/_components/batch-payout.tsx
📚 Learning: 2025-07-11T11:17:32.659Z
Learnt from: aimensahnoun
Repo: RequestNetwork/easy-invoice PR: 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/app/(dashboard)/payouts/batch/_components/batch-payout.tsx
📚 Learning: 2025-05-22T18:19:12.366Z
Learnt from: MantisClone
Repo: RequestNetwork/easy-invoice PR: 59
File: src/app/api/webhook/route.ts:77-95
Timestamp: 2025-05-22T18:19:12.366Z
Learning: For webhook handlers in the Easy Invoice project, unknown or unexpected subStatus values in payment processing should be treated as errors (using console.error) rather than warnings, and should return a 422 Unprocessable Entity status code.

Applied to files:

  • src/app/(dashboard)/payouts/batch/_components/batch-payout.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

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

Copy link
Member Author

aimensahnoun commented Jan 12, 2026

@aimensahnoun aimensahnoun self-assigned this Jan 12, 2026
@aimensahnoun aimensahnoun marked this pull request as ready for review January 12, 2026 12:59
@aimensahnoun aimensahnoun linked an issue Jan 12, 2026 that may be closed by this pull request
@greptile-apps
Copy link

greptile-apps bot commented Jan 12, 2026

Greptile Overview

Greptile Summary

This PR adds a confirmation dialog to the batch payout flow, showing users a breakdown of fees and totals before executing payments. The implementation follows the same pattern as the direct payout confirmation (base branch), splitting the payment process into two steps: preparation (calling the backend API) and execution (sending blockchain transactions).

Key Changes

  • Added PayoutConfirmationDialog component integration with ref-based imperative API
  • Introduced pendingPaymentData state to store prepared payment data between dialog show and confirm
  • Split payment logic into onSubmit (prepares & shows dialog) and handleConfirmBatchPayment (executes payment)
  • Modified getTotalsByCurrency to return numeric values instead of strings for dialog display
  • Updated button text from "Processing Batch..." to "Preparing..." for clarity

Critical Issues Found

🔴 Race Condition Bug (Lines 300-302): The callback passed to dialogRef.current?.onConfirm() calls setPendingPaymentData() immediately followed by handleConfirmBatchPayment(). Since React state updates are asynchronous, when handleConfirmBatchPayment checks if (!pendingPaymentData) on line 218, the state is still null, causing an early return. This prevents batch payments from executing entirely.

🟡 Network Switching Issue (Line 223): Only switches to the network of the first payout (data.payouts[0].paymentCurrency), which will fail if subsequent payouts use different networks.

🟡 Missing Wallet Validation: handleConfirmBatchPayment doesn't check if walletProvider exists before using it on line 227, which could crash if wallet disconnects between dialog show and confirm.

🟡 Incorrect Decimal Precision (Line 200): Uses 18 decimals for all currencies when calculating totals, but fiat currencies and various ERC20 tokens have different decimal places, leading to incorrect amounts shown in the confirmation dialog.

Other Issues

  • Missing user-facing error toast in handleConfirmBatchPayment catch block (line 262)
  • Payment status incorrectly set to "idle" after showing dialog (line 305), causing confusing button states
  • No cleanup of pendingPaymentData or dialog state when wallet disconnects (lines 130-136)

The race condition bug is critical and will break the feature entirely. The other issues affect UX and edge case handling but won't prevent basic functionality once the race condition is fixed.

Confidence Score: 1/5

  • This PR contains a critical race condition bug that completely breaks batch payment execution
  • Score of 1/5 reflects a critical logic error on lines 300-302 where asynchronous state updates cause the payment confirmation callback to fail silently. The callback sets pendingPaymentData state and immediately calls handleConfirmBatchPayment(), but the state hasn't updated yet, so the function returns early without executing the payment. This is a blocking bug that makes the feature non-functional. Additionally, there are 4 other logic issues (wallet validation, network switching, decimal precision, wallet disconnect cleanup) and 2 UX issues (missing error toast, incorrect button state) that compound the problems.
  • src/app/(dashboard)/payouts/batch/_components/batch-payout.tsx requires immediate attention - the race condition on lines 300-302 must be fixed before this PR can be merged

Important Files Changed

File Analysis

Filename Score Overview
src/app/(dashboard)/payouts/batch/_components/batch-payout.tsx 1/5 Added confirmation dialog for batch payouts with critical race condition bug that prevents payment execution, plus several other issues with state management, error handling, and network switching

Sequence Diagram

sequenceDiagram
    actor User
    participant UI as BatchPayout Component
    participant Dialog as PayoutConfirmationDialog
    participant Backend as TRPC API
    participant Wallet as Web3 Wallet
    participant Blockchain

    User->>UI: Click "Send Batch Payment"
    UI->>UI: Validate form & connection
    UI->>UI: setPaymentStatus("processing")
    UI->>User: Show toast "Preparing batch payment..."
    UI->>Backend: batchPay(payouts, payer)
    Backend->>Backend: Create payment requests
    Backend-->>UI: Return batchPaymentData (tx data, fees)
    UI->>UI: Calculate getTotalsByCurrency()
    UI->>Dialog: show({ mode: "batch", currencyTotals, fees, wallet })
    UI->>Dialog: onConfirm(callback)
    UI->>UI: setPaymentStatus("idle")
    Dialog->>User: Display confirmation with fees & totals
    
    alt User Cancels
        User->>Dialog: Click "Cancel"
        Dialog->>Dialog: setOpen(false)
        Note over UI,Dialog: BUG: pendingPaymentData not cleared
    else User Confirms
        User->>Dialog: Click "Confirm & Send Payment"
        Dialog->>Dialog: setOpen(false)
        Dialog->>UI: Execute confirmCallback()
        UI->>UI: setPendingPaymentData(data, batchPaymentData)
        UI->>UI: handleConfirmBatchPayment()
        Note over UI: BUG: Race condition - state not updated yet
        UI->>UI: Check pendingPaymentData (still null!)
        UI-->>UI: Early return (payment fails silently)
    end
    
    Note over UI,Blockchain: Intended flow (if race condition fixed):
    UI->>UI: switchToPaymentNetwork(first payout's currency)
    Note over UI: ISSUE: Assumes all payouts use same network
    UI->>UI: setPaymentStatus("processing")
    UI->>Wallet: Create ethers provider & signer
    
    loop For each ERC20 approval needed
        UI->>Wallet: sendTransaction(approvalTx)
        Wallet->>User: Request approval signature
        User->>Wallet: Sign approval
        Wallet->>Blockchain: Submit approval tx
        Blockchain-->>Wallet: Tx receipt
    end
    
    UI->>Wallet: sendTransaction(batchPaymentTx)
    Wallet->>User: Request payment signature
    User->>Wallet: Sign transaction
    Wallet->>Blockchain: Submit batch payment
    Blockchain-->>Wallet: Tx receipt
    Wallet-->>UI: Transaction complete
    
    UI->>UI: setPaymentStatus("success")
    UI->>User: Show success toast
    UI->>UI: Reset form & clear pendingPaymentData
Loading

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

7 files reviewed, 7 comments

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link

greptile-apps bot commented Jan 12, 2026

Additional Comments (2)

src/app/(dashboard)/payouts/batch/_components/batch-payout.tsx
Missing cleanup when wallet disconnects. If the wallet disconnects while the confirmation dialog is open with pending payment data, the dialog remains open and pendingPaymentData stays set. The user could attempt to confirm a payment without a connected wallet.

  useEffect(() => {
    if (isConnected) {
      setCurrentStep(2);
    } else {
      setCurrentStep(1);
      setPendingPaymentData(null);
      dialogRef.current?.close();
    }
  }, [isConnected]);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/(dashboard)/payouts/batch/_components/batch-payout.tsx
Line: 130:136

Comment:
Missing cleanup when wallet disconnects. If the wallet disconnects while the confirmation dialog is open with pending payment data, the dialog remains open and `pendingPaymentData` stays set. The user could attempt to confirm a payment without a connected wallet.

```suggestion
  useEffect(() => {
    if (isConnected) {
      setCurrentStep(2);
    } else {
      setCurrentStep(1);
      setPendingPaymentData(null);
      dialogRef.current?.close();
    }
  }, [isConnected]);
```


How can I resolve this? If you propose a fix, please make it concise.

src/app/(dashboard)/payouts/batch/_components/batch-payout.tsx
Incorrect decimal precision for all currencies. Using 18 decimals for all currencies is incorrect because:

  • Fiat currencies like USD, EUR typically use 2 decimals for display
  • ERC20 tokens have varying decimals (USDC uses 6, DAI uses 18, etc.)

This causes incorrect totals to be displayed in the confirmation dialog. While the actual payment amounts sent to the backend are correct, users see wrong totals when confirming.

Consider using a currency-specific decimal lookup or removing this BigNumber conversion and just summing the raw amounts since these are display-only totals.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/(dashboard)/payouts/batch/_components/batch-payout.tsx
Line: 200:200

Comment:
Incorrect decimal precision for all currencies. Using 18 decimals for all currencies is incorrect because:
- Fiat currencies like USD, EUR typically use 2 decimals for display
- ERC20 tokens have varying decimals (USDC uses 6, DAI uses 18, etc.)

This causes incorrect totals to be displayed in the confirmation dialog. While the actual payment amounts sent to the backend are correct, users see wrong totals when confirming.

Consider using a currency-specific decimal lookup or removing this BigNumber conversion and just summing the raw amounts since these are display-only totals.

How can I resolve this? If you propose a fix, please make it concise.

@aimensahnoun aimensahnoun force-pushed the 01-12-feat_add_payout_confirmation_component_to_batch_payouts branch from c3a6489 to 5d576d2 Compare January 13, 2026 09:04
@aimensahnoun aimensahnoun force-pushed the 01-12-feat_add_payout_confirmation_component_to_direct_payout branch from 0815703 to ef1b9e3 Compare January 13, 2026 09:04
@aimensahnoun aimensahnoun force-pushed the 01-12-feat_add_payout_confirmation_component_to_batch_payouts branch from 5d576d2 to 9b8f29b Compare January 13, 2026 09:30
@aimensahnoun aimensahnoun force-pushed the 01-12-feat_add_payout_confirmation_component_to_direct_payout branch from ff5383f to a9ec460 Compare January 13, 2026 09:55
@aimensahnoun aimensahnoun force-pushed the 01-12-feat_add_payout_confirmation_component_to_batch_payouts branch 2 times, most recently from 7850343 to 3def0b4 Compare January 13, 2026 10:23
@aimensahnoun aimensahnoun force-pushed the 01-12-feat_add_payout_confirmation_component_to_direct_payout branch 2 times, most recently from 08edb2b to 24ec17b Compare January 13, 2026 13:59
@aimensahnoun aimensahnoun force-pushed the 01-12-feat_add_payout_confirmation_component_to_batch_payouts branch from 3def0b4 to 2725851 Compare January 13, 2026 13:59
@aimensahnoun aimensahnoun changed the base branch from 01-12-feat_add_payout_confirmation_component_to_direct_payout to graphite-base/189 January 13, 2026 14:03
@aimensahnoun aimensahnoun force-pushed the 01-12-feat_add_payout_confirmation_component_to_batch_payouts branch from 2725851 to 31bfc93 Compare January 13, 2026 14:04
@graphite-app graphite-app bot changed the base branch from graphite-base/189 to main January 13, 2026 14:04
@aimensahnoun aimensahnoun force-pushed the 01-12-feat_add_payout_confirmation_component_to_batch_payouts branch from 31bfc93 to c734d2e Compare January 13, 2026 14:04
Copy link
Member Author

aimensahnoun commented Jan 13, 2026

Merge activity

  • Jan 13, 2:08 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jan 13, 2:08 PM UTC: @aimensahnoun merged this pull request with Graphite.

@aimensahnoun aimensahnoun merged commit 90ee845 into main Jan 13, 2026
5 checks passed
@aimensahnoun aimensahnoun deleted the 01-12-feat_add_payout_confirmation_component_to_batch_payouts branch January 13, 2026 14:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Payout flow missing all fee information

2 participants