Skip to content

Add stablecoin payouts docs#305

Merged
steven-tey merged 1 commit intomainfrom
stablecoin-payouts
Mar 15, 2026
Merged

Add stablecoin payouts docs#305
steven-tey merged 1 commit intomainfrom
stablecoin-payouts

Conversation

@steven-tey
Copy link
Copy Markdown
Contributor

@steven-tey steven-tey commented Mar 15, 2026

Summary by CodeRabbit

  • New Features

    • Added payout method filters and search on supported countries; enhanced image carousel to stop autoplay when interacted.
    • Introduced a stablecoin payout path with 1‑click connect and clear wallet warnings; updated bank-account flow with Stripe Express steps.
  • Documentation

    • Rewrote payout guide into dual payout model with side-by-side comparison table, updated onboarding steps, image carousels, FX disclaimers, revised FAQs, and refreshed cross-links and tips.

@steven-tey
Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 15, 2026

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 15, 2026

📝 Walkthrough

Walkthrough

Replaces the single onboarding flow with a dual payout model (stablecoin vs bank account) in docs, adds autoplay-disable on user interaction to the image carousel component, and adds search and payout-method filtering to the supported-countries component.

Changes

Cohort / File(s) Summary
Payout Documentation Update
help/article/receiving-payouts.mdx
Reworks payout article to present a side-by-side Stablecoin vs Bank account model, adds stablecoin onboarding path and bank-account (Stripe Express) onboarding steps, new image carousels/assets, reorganized Steps and subsections, updated FAQs and cross-links.
Image Carousel Autoplay Control
snippets/components/image-carousel.jsx
Adds internal autoplayActive state, disableAutoplay() and goToSlide(index) helpers; disables autoplay on container click and navigation actions; updates interval effect to depend on autoplayActive.
Supported Countries Filtering
snippets/components/payout-supported-countries.jsx
Introduces PAYOUT_METHODS, searchQuery, and payoutMethod state; uses useMemo to compute filtered country list by search and payout method; adds search input, filter buttons, method labels per country, no-results messaging, and adjusts "Show more" behavior based on filtered results.
Manifest
package.json
Minor changes to package.json (lines changed: +14/−4).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Poem

🐇 I nibble at code and hum a tune tonight,
Carousels pause when hands give them a light,
Nations sorted by coin or by bank,
Stable or stripe — choose what you thank,
Hops of delight as the docs take flight! ✨

🚥 Pre-merge checks | ✅ 3
✅ 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 'Add stablecoin payouts docs' directly aligns with the main changes, which introduce stablecoin payout documentation alongside bank account payouts in a dual-model approach.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch stablecoin-payouts
📝 Coding Plan
  • Generate coding plan for human review comments

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
Copy Markdown
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: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@help/article/receiving-payouts.mdx`:
- Line 221: The sentence "Unlike platforms like [PartnerStack]..., where you
have to manually withdraw your earnings to your bank account – you do not need
to manually withdraw your earnings on Dub." uses bank-specific wording; update
it to be payout-method-neutral by replacing "bank account" with "connected
payout method" (or add a clarifying parenthetical like "(e.g., bank account or
stablecoin wallet)") and ensure any follow-up FAQ answers use "connected payout
method" instead of "bank" unless the text is specifically scoped to bank
payouts; look for this exact sentence in receiving-payouts.mdx and update the
wording accordingly.
- Around line 14-19: The "Fees" cell for the "Bank account" column currently
states "1% FX conversion fee" unconditionally; update the Fees row in the
comparison table so it matches the later bank-payout note by qualifying it
(e.g., "1% FX conversion fee for international / non‑US payouts; domestic bank
payouts may not incur this fee") so the table's Bank account fee mirrors the
later note about international/non‑US applicability.
- Around line 35-39: Change the incorrect subject-verb agreement in the intro
sentence that currently reads "Stablecoin payouts is the easiest and fastest
way..." to use plural agreement (e.g., "Stablecoin payouts are the easiest and
fastest way..." or rephrase to "Receiving payouts in stablecoins is the easiest
and fastest way..."); update the sentence in help/article/receiving-payouts.mdx
where the exact phrase "Stablecoin payouts is" appears so the copy is
grammatically correct and consistent with the surrounding list items.

In `@snippets/components/image-carousel.jsx`:
- Around line 9-17: The component initializes local state autoplayActive from
the autoplay prop but never syncs it after mount, so toggling autoplay from a
parent won't pause/resume the carousel; fix by making the autoplay prop
authoritative—either remove autoplayActive and reference autoplay directly
inside the useEffect condition, or add a syncing effect that calls
setAutoplayActive(autoplay) whenever the autoplay prop changes; update the
dependency list of the existing useEffect (which uses autoplayActive,
images.length, setCurrentIndex) accordingly to ensure the interval starts/stops
when autoplay changes.

In `@snippets/components/payout-supported-countries.jsx`:
- Around line 54-68: The segmented control rendering PAYOUT_METHODS should
expose its active state to assistive tech: wrap the buttons container (the div
surrounding the map over PAYOUT_METHODS) with role="group" and an appropriate
aria-label (e.g., "Payout method filter"), and add aria-pressed={payoutMethod
=== value} to each button rendered by the map (the elements using
setPayoutMethod and comparing payoutMethod === value) so screen readers can
announce which option is selected.
- Around line 86-101: The flag URL construction calls code.toLowerCase() without
guarding for a missing or non-string code, which can throw when a country row is
malformed; in the displayed.map callback (where displayed.map and the img tag
are defined) add a guard that ensures code is a non-empty string (or falls back
to a safe placeholder like 'unknown' or skips rendering the flag) before calling
code.toLowerCase(), and handle the fallback consistently so the component does
not crash when PAYOUT_METHODS or methods are present but code is invalid.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d5e50b78-8e9c-4a49-aac9-1f8d1bd92cee

📥 Commits

Reviewing files that changed from the base of the PR and between 2121b5f and 799309e.

⛔ Files ignored due to path filters (10)
  • images/payouts/connect-bank-account-1.png is excluded by !**/*.png
  • images/payouts/connect-bank-account-2.png is excluded by !**/*.png
  • images/payouts/connect-bank-account-3.png is excluded by !**/*.png
  • images/payouts/connect-bank-account-4.png is excluded by !**/*.png
  • images/payouts/connect-bank-account-5.png is excluded by !**/*.png
  • images/payouts/connect-stablecoin-wallet-1.png is excluded by !**/*.png
  • images/payouts/connect-stablecoin-wallet-2.png is excluded by !**/*.png
  • images/payouts/connect-stablecoin-wallet-3.png is excluded by !**/*.png
  • images/payouts/payout-settings-bank-account.png is excluded by !**/*.png
  • images/payouts/payout-settings-stablecoin.png is excluded by !**/*.png
📒 Files selected for processing (3)
  • help/article/receiving-payouts.mdx
  • snippets/components/image-carousel.jsx
  • snippets/components/payout-supported-countries.jsx

Comment on lines +35 to +39
Stablecoin payouts is the easiest and fastest way to receive payouts on Dub (especially for non-US partners):

- **Get paid in USD** – instead of your local currency, you can receive payouts in USDC all around the world
- **Faster payouts** – payouts are sent within minutes instead of having to wait up to 15 business days for regular bank transfers
- **Lower fees** – 0.5% transaction fee vs 1% FX conversion fee for international payouts
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix the stablecoin intro grammar.

"Stablecoin payouts is" should be "Stablecoin payouts are" (or equivalent). This is user-facing copy on a top-level docs page.

✏️ Proposed fix
-Stablecoin payouts is the easiest and fastest way to receive payouts on Dub (especially for non-US partners):
+Stablecoin payouts are the easiest and fastest way to receive payouts on Dub (especially for non-US partners):
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Stablecoin payouts is the easiest and fastest way to receive payouts on Dub (especially for non-US partners):
- **Get paid in USD** – instead of your local currency, you can receive payouts in USDC all around the world
- **Faster payouts** – payouts are sent within minutes instead of having to wait up to 15 business days for regular bank transfers
- **Lower fees** – 0.5% transaction fee vs 1% FX conversion fee for international payouts
Stablecoin payouts are the easiest and fastest way to receive payouts on Dub (especially for non-US partners):
- **Get paid in USD** – instead of your local currency, you can receive payouts in USDC all around the world
- **Faster payouts** – payouts are sent within minutes instead of having to wait up to 15 business days for regular bank transfers
- **Lower fees** – 0.5% transaction fee vs 1% FX conversion fee for international payouts
🧰 Tools
🪛 GitHub Check: Mintlify Validation (dub) - vale-spellcheck

[warning] 35-35: help/article/receiving-payouts.mdx#L35
Did you really mean 'Stablecoin'?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@help/article/receiving-payouts.mdx` around lines 35 - 39, Change the
incorrect subject-verb agreement in the intro sentence that currently reads
"Stablecoin payouts is the easiest and fastest way..." to use plural agreement
(e.g., "Stablecoin payouts are the easiest and fastest way..." or rephrase to
"Receiving payouts in stablecoins is the easiest and fastest way..."); update
the sentence in help/article/receiving-payouts.mdx where the exact phrase
"Stablecoin payouts is" appears so the copy is grammatically correct and
consistent with the surrounding list items.

### Do I need to manually withdraw my earnings from Dub?

Unlike platforms like PartnerStack, where you have to manually withdraw your earnings to your bank account – you do not need to manually withdraw your earnings on Dub.
Unlike platforms like [PartnerStack](https://dub.co/compare/partnerstack), where you have to manually withdraw your earnings to your bank account – you do not need to manually withdraw your earnings on Dub.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use payout-method-neutral wording in the withdrawal FAQs.

This section still says payouts land in a bank account, but the page now documents stablecoin wallets too. Please either scope these answers to bank payouts or rename the destination to the connected payout method where the rule applies to both.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@help/article/receiving-payouts.mdx` at line 221, The sentence "Unlike
platforms like [PartnerStack]..., where you have to manually withdraw your
earnings to your bank account – you do not need to manually withdraw your
earnings on Dub." uses bank-specific wording; update it to be
payout-method-neutral by replacing "bank account" with "connected payout method"
(or add a clarifying parenthetical like "(e.g., bank account or stablecoin
wallet)") and ensure any follow-up FAQ answers use "connected payout method"
instead of "bank" unless the text is specifically scoped to bank payouts; look
for this exact sentence in receiving-payouts.mdx and update the wording
accordingly.

Comment on lines +9 to +17
const [autoplayActive, setAutoplayActive] = useState(autoplay);

useEffect(() => {
if (!autoplay || images.length <= 1) return;
if (!autoplayActive || images.length <= 1) return;
const interval = setInterval(() => {
setCurrentIndex((prev) => (prev === images.length - 1 ? 0 : prev + 1));
}, 3000);
return () => clearInterval(interval);
}, [autoplay, images.length]);
}, [autoplayActive, images.length]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In React, does useState(initialProp)resync state wheninitialProp changes after the component mounts?

💡 Result:

No. useState(initialProp) uses initialProp only on the initial render—after the component mounts, that argument is ignored, so later changes to initialProp will not “resync” the state automatically. [1]

If you need state to follow a prop change, you must update it yourself (commonly with an effect), or reset the component (e.g., via a changing key). [2]

Sources
[1] https://react.dev/reference/react/useState
[2] https://stackoverflow.com/questions/54865764/react-usestate-does-not-reload-state-from-props


Keep the autoplay prop authoritative after mount.

autoplayActive is initialized from autoplay on line 9, but useState(initialValue) only reads the prop once at mount. After this change, a parent toggling autoplay later will not pause/resume the carousel, causing a regression in the existing prop API.

💡 Proposed fix
   const [currentIndex, setCurrentIndex] = useState(0);
   const [autoplayActive, setAutoplayActive] = useState(autoplay);
 
+  useEffect(() => {
+    setAutoplayActive(autoplay);
+  }, [autoplay]);
+
   useEffect(() => {
     if (!autoplayActive || images.length <= 1) return;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [autoplayActive, setAutoplayActive] = useState(autoplay);
useEffect(() => {
if (!autoplay || images.length <= 1) return;
if (!autoplayActive || images.length <= 1) return;
const interval = setInterval(() => {
setCurrentIndex((prev) => (prev === images.length - 1 ? 0 : prev + 1));
}, 3000);
return () => clearInterval(interval);
}, [autoplay, images.length]);
}, [autoplayActive, images.length]);
const [autoplayActive, setAutoplayActive] = useState(autoplay);
useEffect(() => {
setAutoplayActive(autoplay);
}, [autoplay]);
useEffect(() => {
if (!autoplayActive || images.length <= 1) return;
const interval = setInterval(() => {
setCurrentIndex((prev) => (prev === images.length - 1 ? 0 : prev + 1));
}, 3000);
return () => clearInterval(interval);
}, [autoplayActive, images.length]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@snippets/components/image-carousel.jsx` around lines 9 - 17, The component
initializes local state autoplayActive from the autoplay prop but never syncs it
after mount, so toggling autoplay from a parent won't pause/resume the carousel;
fix by making the autoplay prop authoritative—either remove autoplayActive and
reference autoplay directly inside the useEffect condition, or add a syncing
effect that calls setAutoplayActive(autoplay) whenever the autoplay prop
changes; update the dependency list of the existing useEffect (which uses
autoplayActive, images.length, setCurrentIndex) accordingly to ensure the
interval starts/stops when autoplay changes.

Comment on lines +54 to +68
<div className="flex flex-wrap md:flex-nowrap gap-2">
{PAYOUT_METHODS.map(({ value, label }) => (
<button
key={value}
type="button"
onClick={() => setPayoutMethod(value)}
className={`rounded-full px-4 py-1.5 text-sm whitespace-nowrap transition-colors ${
payoutMethod === value
? "bg-neutral-900 text-white dark:bg-neutral-100 dark:text-neutral-900"
: "border border-neutral-200 bg-white text-neutral-600 hover:bg-neutral-50 hover:text-neutral-900 dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-200"
}`}
>
{label}
</button>
))}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Expose the active payout filter to assistive tech.

These buttons act like a segmented control, but the selected state is only visual today. Add aria-pressed (and ideally a group label) so screen-reader users can tell which filter is active.

♿ Proposed fix
-        <div className="flex flex-wrap md:flex-nowrap gap-2">
+        <div
+          className="flex flex-wrap md:flex-nowrap gap-2"
+          role="group"
+          aria-label="Filter supported countries by payout method"
+        >
           {PAYOUT_METHODS.map(({ value, label }) => (
             <button
               key={value}
               type="button"
               onClick={() => setPayoutMethod(value)}
+              aria-pressed={payoutMethod === value}
               className={`rounded-full px-4 py-1.5 text-sm whitespace-nowrap transition-colors ${
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@snippets/components/payout-supported-countries.jsx` around lines 54 - 68, The
segmented control rendering PAYOUT_METHODS should expose its active state to
assistive tech: wrap the buttons container (the div surrounding the map over
PAYOUT_METHODS) with role="group" and an appropriate aria-label (e.g., "Payout
method filter"), and add aria-pressed={payoutMethod === value} to each button
rendered by the map (the elements using setPayoutMethod and comparing
payoutMethod === value) so screen readers can announce which option is selected.

Comment on lines +86 to +101
{displayed.map(({ code, name, methods = [] }) => {
const methodLabels = PAYOUT_METHODS.filter((m) =>
methods.includes(m.value),
)
.map((m) => m.label)
.join(" · ");
return (
<div key={code} className="flex items-start gap-3">
<img
src={`https://hatscripts.github.io/circle-flags/flags/${code.toLowerCase()}.svg`}
alt={code}
width={32}
height={32}
className="size-5 shrink-0 rounded-full border border-neutral-200 shadow-sm dark:border-neutral-700 mt-0.5"
draggable={false}
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard code before building the flag URL.

Line 95 calls code.toLowerCase() on API data, but the filtering logic above already treats code as optional. A single malformed country row will throw here and blank the whole list.

🛡️ Proposed fix
         {displayed.map(({ code, name, methods = [] }) => {
+          const normalizedCode =
+            typeof code === "string" && code.length > 0
+              ? code.toLowerCase()
+              : null;
           const methodLabels = PAYOUT_METHODS.filter((m) =>
             methods.includes(m.value),
           )
             .map((m) => m.label)
             .join(" · ");
           return (
-            <div key={code} className="flex items-start gap-3">
-              <img
-                src={`https://hatscripts.github.io/circle-flags/flags/${code.toLowerCase()}.svg`}
-                alt={code}
+            <div key={code || name} className="flex items-start gap-3">
+              {normalizedCode ? (
+                <img
+                  src={`https://hatscripts.github.io/circle-flags/flags/${normalizedCode}.svg`}
+                  alt=""
+                  aria-hidden="true"
                   width={32}
                   height={32}
                   className="size-5 shrink-0 rounded-full border border-neutral-200 shadow-sm dark:border-neutral-700 mt-0.5"
                   draggable={false}
-              />
+                />
+              ) : null}
               <div className="min-w-0 flex-1">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{displayed.map(({ code, name, methods = [] }) => {
const methodLabels = PAYOUT_METHODS.filter((m) =>
methods.includes(m.value),
)
.map((m) => m.label)
.join(" · ");
return (
<div key={code} className="flex items-start gap-3">
<img
src={`https://hatscripts.github.io/circle-flags/flags/${code.toLowerCase()}.svg`}
alt={code}
width={32}
height={32}
className="size-5 shrink-0 rounded-full border border-neutral-200 shadow-sm dark:border-neutral-700 mt-0.5"
draggable={false}
/>
{displayed.map(({ code, name, methods = [] }) => {
const normalizedCode =
typeof code === "string" && code.length > 0
? code.toLowerCase()
: null;
const methodLabels = PAYOUT_METHODS.filter((m) =>
methods.includes(m.value),
)
.map((m) => m.label)
.join(" · ");
return (
<div key={code || name} className="flex items-start gap-3">
{normalizedCode ? (
<img
src={`https://hatscripts.github.io/circle-flags/flags/${normalizedCode}.svg`}
alt=""
aria-hidden="true"
width={32}
height={32}
className="size-5 shrink-0 rounded-full border border-neutral-200 shadow-sm dark:border-neutral-700 mt-0.5"
draggable={false}
/>
) : null}
<div className="min-w-0 flex-1">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@snippets/components/payout-supported-countries.jsx` around lines 86 - 101,
The flag URL construction calls code.toLowerCase() without guarding for a
missing or non-string code, which can throw when a country row is malformed; in
the displayed.map callback (where displayed.map and the img tag are defined) add
a guard that ensures code is a non-empty string (or falls back to a safe
placeholder like 'unknown' or skips rendering the flag) before calling
code.toLowerCase(), and handle the fallback consistently so the component does
not crash when PAYOUT_METHODS or methods are present but code is invalid.

Copy link
Copy Markdown
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

♻️ Duplicate comments (6)
help/article/receiving-payouts.mdx (3)

35-35: ⚠️ Potential issue | 🟡 Minor

Fix the stablecoin intro grammar.

“Stablecoin payouts is” should be plural here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@help/article/receiving-payouts.mdx` at line 35, The sentence "Stablecoin
payouts is the easiest and fastest way to receive payouts on Dub (especially for
non-US partners):" uses a singular verb with a plural subject; change it to
"Stablecoin payouts are the easiest and fastest way to receive payouts on Dub
(especially for non-US partners):" to fix the grammar and ensure subject-verb
agreement (locate the exact sentence starting "Stablecoin payouts is..." in
receiving-payouts.mdx).

221-227: ⚠️ Potential issue | 🟠 Major

Use payout-method-neutral wording in the withdrawal FAQs.

This section still says earnings are withdrawn or deposited to a bank account, which excludes the stablecoin flow introduced above. Use “connected payout method” unless the answer is intentionally bank-only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@help/article/receiving-payouts.mdx` around lines 221 - 227, Update the payout
wording to be payout-method-neutral: replace instances of "bank account",
"withdrawn", and "deposited into your connected bank account" with "connected
payout method" or "connected payout method (e.g., bank account or stablecoin)"
in the FAQ section under the heading "What is the minimum withdrawal amount, and
how does it work?" and the preceding paragraph comparing PartnerStack; ensure
the sentence about automated payouts reads that earnings/payouts are
"automatically sent to your connected payout method" and keep the $10 minimum
wording intact.

14-19: ⚠️ Potential issue | 🟠 Major

Make the bank-fee row match the later bank-payout note.

The table still states a 1% FX fee unconditionally, but Line 93 scopes that fee to international / non-US bank payouts. Please make these two sections consistent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@help/article/receiving-payouts.mdx` around lines 14 - 19, Update the "Fees"
row in the table so it matches the bank-payout note: change the bank account
cell from an unconditional "1% FX conversion fee" to a scoped description such
as "1% FX conversion fee (applies to international/non‑US bank payouts)" or add
a parenthetical/link that points to the same bank-payout note referenced by the
Supported countries anchor; modify the table's "Fees" row text accordingly so
the wording for bank account fees is consistent with the bank-payout note.
snippets/components/payout-supported-countries.jsx (2)

24-35: ⚠️ Potential issue | 🟠 Major

Normalize API fields before using them in search and rendering.

The new filter assumes name and code are strings, and the row renderer still assumes methods is an array plus code is present. A single malformed country record can throw here and blank the whole widget.

🛡️ Suggested hardening
   const filtered = useMemo(() => {
     const q = searchQuery.trim().toLowerCase();
     return countries.filter((c) => {
+      const name = typeof c.name === "string" ? c.name : "";
+      const code = typeof c.code === "string" ? c.code : "";
+      const methods = Array.isArray(c.methods) ? c.methods : [];
       const matchesSearch =
         !q ||
-        (c.name || "").toLowerCase().includes(q) ||
-        (c.code || "").toLowerCase().includes(q);
-      const methods = Array.isArray(c.methods) ? c.methods : [];
+        name.toLowerCase().includes(q) ||
+        code.toLowerCase().includes(q);
       const matchesMethod =
         payoutMethod === "all" || methods.includes(payoutMethod);
       return matchesSearch && matchesMethod;
@@
-        {displayed.map(({ code, name, methods = [] }) => {
-          const methodLabels = PAYOUT_METHODS.filter((m) =>
-            methods.includes(m.value),
+        {displayed.map(({ code, name, methods }) => {
+          const normalizedCode =
+            typeof code === "string" && code.length > 0
+              ? code.toLowerCase()
+              : null;
+          const normalizedMethods = Array.isArray(methods) ? methods : [];
+          const methodLabels = PAYOUT_METHODS.filter((m) =>
+            normalizedMethods.includes(m.value),
           )
             .map((m) => m.label)
             .join(" · ");
           return (
-            <div key={code} className="flex items-start gap-3">
-              <img
-                src={`https://hatscripts.github.io/circle-flags/flags/${code.toLowerCase()}.svg`}
-                alt={code}
+            <div key={code || name} className="flex items-start gap-3">
+              {normalizedCode ? (
+                <img
+                  src={`https://hatscripts.github.io/circle-flags/flags/${normalizedCode}.svg`}
+                  alt=""
+                  aria-hidden="true"
                   width={32}
                   height={32}
                   className="size-5 shrink-0 rounded-full border border-neutral-200 shadow-sm dark:border-neutral-700 mt-0.5"
                   draggable={false}
-              />
+                />
+              ) : null}

Also applies to: 86-101

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@snippets/components/payout-supported-countries.jsx` around lines 24 - 35, The
filter assumes country objects always have safe name/code/methods fields;
normalize those fields first to avoid crashing on malformed records: inside the
useMemo (and likewise in the row renderer referenced around lines 86-101) coerce
c.name and c.code to strings (e.g., const name = String(c.name || "") and const
code = String(c.code || "")) and ensure methods is an array (const methods =
Array.isArray(c.methods) ? c.methods : []), then use name, code, and methods for
searching and rendering instead of accessing c.name/c.code/c.methods directly;
update references in the filtered computation and the row rendering to use these
normalized variables.

54-68: ⚠️ Potential issue | 🟡 Minor

Expose the active payout filter to assistive tech.

The selected state is still visual only here. Add a group label on the container and aria-pressed on each button so screen readers can announce which payout filter is active.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@snippets/components/payout-supported-countries.jsx` around lines 54 - 68, The
payout filter is only visual; update the container wrapper (around where
PAYOUT_METHODS is mapped) to expose an accessible group label (e.g., add
role="group" and aria-label="Payout method filter" or aria-labelledby pointing
to a visible heading) and set aria-pressed on each generated button (in the map
where setPayoutMethod is called) to aria-pressed={payoutMethod === value} so
assistive tech can announce the active filter; keep existing onClick and visual
classes unchanged.
snippets/components/image-carousel.jsx (1)

9-17: ⚠️ Potential issue | 🟠 Major

Keep autoplay authoritative after mount.

autoplayActive is still initialized from autoplay only once. If a parent toggles autoplay later, the interval will not start or stop with the prop anymore, so this regresses the existing API.

💡 Suggested fix
   const [currentIndex, setCurrentIndex] = useState(0);
   const [autoplayActive, setAutoplayActive] = useState(autoplay);
 
+  useEffect(() => {
+    setAutoplayActive(autoplay);
+  }, [autoplay]);
+
   useEffect(() => {
     if (!autoplayActive || images.length <= 1) return;
In React, does useState(initialProp) resync local state when the prop changes after the component mounts?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@snippets/components/image-carousel.jsx` around lines 9 - 17, The component
currently initializes autoplayActive from the autoplay prop only once, so later
prop changes are ignored; update the component to keep the prop authoritative by
syncing autoplay into local state—add an effect that runs when the autoplay prop
changes and calls setAutoplayActive(autoplay) (or remove autoplayActive and use
the autoplay prop directly if you don't need local toggling), and keep the
existing interval effect using autoplayActive (or autoplay) so starting/stopping
the interval follows prop updates; reference autoplayActive, setAutoplayActive,
autoplay, and the existing useEffect that sets/clears the interval.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@help/article/receiving-payouts.mdx`:
- Around line 21-25: The Warning block and the FAQ in receiving-payouts.mdx give
conflicting guidance about the "Country" field; update the Warning element and
the FAQ section so they consistently state whether "Country" must reflect the
user's actual tax residency or the legal entity's country (choose one), and make
the wording explicit (e.g., "Country must reflect your legal tax residency" OR
"Country must reflect the legal entity's country for payouts") across both the
<Warning> block and the FAQ paragraph that tells unsupported-country users to
create a legal entity; also add a brief note describing acceptable exceptions
(if any) and where to get compliance help (support/contact) to avoid ambiguity.

---

Duplicate comments:
In `@help/article/receiving-payouts.mdx`:
- Line 35: The sentence "Stablecoin payouts is the easiest and fastest way to
receive payouts on Dub (especially for non-US partners):" uses a singular verb
with a plural subject; change it to "Stablecoin payouts are the easiest and
fastest way to receive payouts on Dub (especially for non-US partners):" to fix
the grammar and ensure subject-verb agreement (locate the exact sentence
starting "Stablecoin payouts is..." in receiving-payouts.mdx).
- Around line 221-227: Update the payout wording to be payout-method-neutral:
replace instances of "bank account", "withdrawn", and "deposited into your
connected bank account" with "connected payout method" or "connected payout
method (e.g., bank account or stablecoin)" in the FAQ section under the heading
"What is the minimum withdrawal amount, and how does it work?" and the preceding
paragraph comparing PartnerStack; ensure the sentence about automated payouts
reads that earnings/payouts are "automatically sent to your connected payout
method" and keep the $10 minimum wording intact.
- Around line 14-19: Update the "Fees" row in the table so it matches the
bank-payout note: change the bank account cell from an unconditional "1% FX
conversion fee" to a scoped description such as "1% FX conversion fee (applies
to international/non‑US bank payouts)" or add a parenthetical/link that points
to the same bank-payout note referenced by the Supported countries anchor;
modify the table's "Fees" row text accordingly so the wording for bank account
fees is consistent with the bank-payout note.

In `@snippets/components/image-carousel.jsx`:
- Around line 9-17: The component currently initializes autoplayActive from the
autoplay prop only once, so later prop changes are ignored; update the component
to keep the prop authoritative by syncing autoplay into local state—add an
effect that runs when the autoplay prop changes and calls
setAutoplayActive(autoplay) (or remove autoplayActive and use the autoplay prop
directly if you don't need local toggling), and keep the existing interval
effect using autoplayActive (or autoplay) so starting/stopping the interval
follows prop updates; reference autoplayActive, setAutoplayActive, autoplay, and
the existing useEffect that sets/clears the interval.

In `@snippets/components/payout-supported-countries.jsx`:
- Around line 24-35: The filter assumes country objects always have safe
name/code/methods fields; normalize those fields first to avoid crashing on
malformed records: inside the useMemo (and likewise in the row renderer
referenced around lines 86-101) coerce c.name and c.code to strings (e.g., const
name = String(c.name || "") and const code = String(c.code || "")) and ensure
methods is an array (const methods = Array.isArray(c.methods) ? c.methods : []),
then use name, code, and methods for searching and rendering instead of
accessing c.name/c.code/c.methods directly; update references in the filtered
computation and the row rendering to use these normalized variables.
- Around line 54-68: The payout filter is only visual; update the container
wrapper (around where PAYOUT_METHODS is mapped) to expose an accessible group
label (e.g., add role="group" and aria-label="Payout method filter" or
aria-labelledby pointing to a visible heading) and set aria-pressed on each
generated button (in the map where setPayoutMethod is called) to
aria-pressed={payoutMethod === value} so assistive tech can announce the active
filter; keep existing onClick and visual classes unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 741481da-e5d3-45e4-af43-63fc78750197

📥 Commits

Reviewing files that changed from the base of the PR and between 2121b5f and 799309e.

⛔ Files ignored due to path filters (10)
  • images/payouts/connect-bank-account-1.png is excluded by !**/*.png
  • images/payouts/connect-bank-account-2.png is excluded by !**/*.png
  • images/payouts/connect-bank-account-3.png is excluded by !**/*.png
  • images/payouts/connect-bank-account-4.png is excluded by !**/*.png
  • images/payouts/connect-bank-account-5.png is excluded by !**/*.png
  • images/payouts/connect-stablecoin-wallet-1.png is excluded by !**/*.png
  • images/payouts/connect-stablecoin-wallet-2.png is excluded by !**/*.png
  • images/payouts/connect-stablecoin-wallet-3.png is excluded by !**/*.png
  • images/payouts/payout-settings-bank-account.png is excluded by !**/*.png
  • images/payouts/payout-settings-stablecoin.png is excluded by !**/*.png
📒 Files selected for processing (3)
  • help/article/receiving-payouts.mdx
  • snippets/components/image-carousel.jsx
  • snippets/components/payout-supported-countries.jsx

Comment on lines +21 to +25
<Warning>
During onboarding, you must select the country where you legally reside for
tax purposes. Providing incorrect information may result in account
suspension, loss of payouts, and legal action.
</Warning>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and read the file
find . -name "receiving-payouts.mdx" -type f | head -1

Repository: dubinc/docs

Length of output: 91


🏁 Script executed:

#!/bin/bash
# Read the entire file to see full context
if [ -f "help/article/receiving-payouts.mdx" ]; then
  wc -l "help/article/receiving-payouts.mdx"
fi

Repository: dubinc/docs

Length of output: 93


🏁 Script executed:

#!/bin/bash
# Read the specific sections mentioned
if [ -f "help/article/receiving-payouts.mdx" ]; then
  echo "=== Lines 15-30 (context around lines 21-25) ==="
  sed -n '15,30p' "help/article/receiving-payouts.mdx"
  echo ""
  echo "=== Lines 170-195 (context around lines 178-186) ==="
  sed -n '170,195p' "help/article/receiving-payouts.mdx"
fi

Repository: dubinc/docs

Length of output: 1981


Reconcile conflicting country-selection guidance before publishing.

The warning (lines 21-25) states that you "must select the country where you legally reside for tax purposes" and warns that incorrect information may result in account suspension or legal action. However, the FAQ (lines 178-186) instructs unsupported-country users to create a legal entity in a supported country and set Country to that entity's location (e.g., "United States")—regardless of where they actually reside. This creates a compliance-sensitive conflict: users following the FAQ could appear to violate the warning's explicit requirement. Clarify whether Country should reflect actual tax residency or entity location, and update both sections consistently.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@help/article/receiving-payouts.mdx` around lines 21 - 25, The Warning block
and the FAQ in receiving-payouts.mdx give conflicting guidance about the
"Country" field; update the Warning element and the FAQ section so they
consistently state whether "Country" must reflect the user's actual tax
residency or the legal entity's country (choose one), and make the wording
explicit (e.g., "Country must reflect your legal tax residency" OR "Country must
reflect the legal entity's country for payouts") across both the <Warning> block
and the FAQ paragraph that tells unsupported-country users to create a legal
entity; also add a brief note describing acceptable exceptions (if any) and
where to get compliance help (support/contact) to avoid ambiguity.

@steven-tey steven-tey merged commit 47bfc46 into main Mar 15, 2026
4 checks passed
@steven-tey steven-tey deleted the stablecoin-payouts branch March 15, 2026 07:05
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.

1 participant