feat: Add BillingSettingsThree & i18n Multi-Currency Support#362
feat: Add BillingSettingsThree & i18n Multi-Currency Support#362CodenRust wants to merge 16 commits intododopayments:mainfrom
Conversation
… and animated metrics
feat: Add BillingSettingsThree & i18n Multi-Currency Support
|
@CodenRust is attempting to deploy a commit to the Dodo Payments Team on Vercel. A member of the Team first needs to authorize it. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds an i18n BillingProvider, two new UI components (BillingSettingsThree, CustomerPortal) with demos, docs, and registry entries; and updates many barrel exports to narrow or reformat public type exports. Changes
Sequence Diagram(s)sequenceDiagram
participant App as App Component
participant BP as BillingProvider (useBilling)
participant API as Frankfurter API
participant Component as BillingSettingsThree / UI
App->>BP: Wrap app with BillingProvider
BP->>BP: Init locale/currency and STATIC_RATES
BP->>API: Fetch latest FX rates
API-->>BP: Return rates (or error)
BP->>Component: provide formatCurrency/convert via context
Component->>BP: call formatCurrency(amount, {from})
BP-->>Component: return converted & localized string
sequenceDiagram
participant User as User
participant CP as CustomerPortal
participant State as React State
participant Child as Tab Content
User->>CP: Render portal with user data
CP->>State: init activeTab, mobile menu state
User->>CP: Click nav item
CP->>State: set activeTab, close mobile menu
State-->>CP: re-render
CP->>Child: render active tab content (or placeholder)
Child-->>User: display content
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 3❌ Failed checks (1 warning, 2 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 15
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
registry.json (1)
20-56:⚠️ Potential issue | 🟠 MajorThe
allblock doesn't include the newbilling-settings-3ori18n-provider.New registry items should be referenced in the
"all"meta-block so that@billingsdk/allinstalls the complete set.Proposed fix
"@billingsdk/payment-failure", - "@billingsdk/billing-screen" + "@billingsdk/billing-screen", + "@billingsdk/billing-settings-3", + "@billingsdk/i18n-provider" ]
🤖 Fix all issues with AI agents
In `@content/docs/components/billing-settings-three/index.mdx`:
- Around line 27-32: The doc text contradicts the PR description about
animations; update the component docs for BillingSettingsThree to be consistent
by clarifying whether the classic theme uses animated transitions or is strictly
no-animation: change the "Liquid Payment Methods" bullet and the "Minimalist
Aesthetic" bullet to reflect the authoritative behavior (use the component name
BillingSettingsThree and the prop minimal/onAddNew/addNewHref to guide readers),
e.g., state that liquid list uses animated transitions only when minimal is
false (or state "no animations" if PR dictates) and that minimal mode disables
all decorative/transition animations; ensure both bullets and any mention of
animations match the PR claim exactly.
- Around line 6-7: Remove the unused import by deleting the import statement
that references BillingSettingsThree; specifically remove or stop importing the
symbol BillingSettingsThree from
"@/components/billingsdk/billing-settings-three" since only
BillingSettingsThreeDemo is actually used in the MDX content and the static code
block does not require an actual import. Ensure only BillingSettingsThreeDemo
remains imported to avoid dead imports and linter warnings.
In `@content/docs/i18n-provider.mdx`:
- Around line 63-75: The example CustomPrice component is missing TypeScript
types for its props; update the function signature to explicitly type the props
(e.g., define an interface or inline type for amount as number) and annotate the
component return type if desired so CustomPrice({ amount }: { amount: number })
uses a typed amount passed into useBilling().formatCurrency; ensure references
to the useBilling import and the CustomPrice function name remain consistent.
- Around line 35-45: The RootLayout example omits TypeScript types for its
props; update the RootLayout signature to declare the children prop type (e.g.,
{ children: React.ReactNode } or React.PropsWithChildren) so the snippet is
TypeScript-safe—adjust imports if needed—and keep the existing BillingProvider
usage with defaultCurrency and defaultLocale unchanged.
In `@registry.json`:
- Around line 1061-1087: The registry entry for billing-settings-3 is missing
required registryDependencies: add "badge" (since the BillingSettingsThree
component imports Badge from "@/components/ui/badge") and add
"@billingsdk/i18n-provider" (since the component calls useBilling()/i18n
provider hooks); update the "registryDependencies" array for the
billing-settings-3 block to include both "badge" and "@billingsdk/i18n-provider"
so the component's imports resolve correctly.
- Around line 1105-1108: The registry entry's "dependencies" array currently
lists "lucide-react" and "motion" but neither src/lib/i18n-provider.tsx nor
src/registry/billingsdk/demo/i18n-demo.tsx import or use those packages; remove
"lucide-react" and "motion" from the registry.json dependencies array so only
actual imports (React, card/select/provider packages used by i18n-demo and
i18n-provider) remain, ensuring the registry entry matches the symbols
referenced by i18n-provider.tsx and i18n-demo.tsx.
In `@src/components/billingsdk/billing-settings-three.tsx`:
- Around line 3-4: Remove the unused imports from
src/components/billingsdk/billing-settings-three.tsx: delete
ComponentPropsWithoutRef, useEffect, useRef from the React import and remove
useInView, useMotionValue, useSpring from the motion/react import so the file
matches the registry copy; keep only useState and motion/AnimatePresence (or any
symbols actually used in this file) to satisfy ESLint unused-import rules.
In `@src/components/billingsdk/spend-summary.tsx`:
- Around line 24-26: The component currently prefixes totalSpend with a
hardcoded "$" which breaks i18n; update the render to use the billing formatter
instead (call useBilling().formatCurrency(totalSpend)) or accept a
formatCurrency callback prop and use that so the currency symbol and locale
match other components (refer to totalSpend, formatCurrency, and useBilling() in
spend-summary.tsx); ensure you replace the literal "$" prefix with the formatted
string returned by formatCurrency and keep prop-based option if you want the
component context-free.
In `@src/components/billingsdk/subscription-status-card.tsx`:
- Around line 35-38: The "Renews {renewalDate}" text is misleading for canceled
or past_due subscriptions; update the rendering in SubscriptionStatusCard to
choose the label based on the subscription status (e.g., if status ===
"canceled" show "Canceled on {renewalDate}", if status === "past_due" show
"Payment due {renewalDate}", otherwise show "Renews {renewalDate}"). Locate the
span that currently renders Calendar and Renews {renewalDate} and replace the
static text with a small conditional that selects the appropriate label using
the existing renewalDate and status variables so semantics match the
subscription state.
- Around line 32-34: The Badge currently shows "Inode" for any non-active status
in subscription-status-card.tsx; update the label logic in the component (where
Badge is rendered) to display meaningful status text instead of "Inode" — e.g.,
if status === "active" show "Active", else map known statuses like "canceled" ->
"Canceled", "past_due" or "past-due" -> "Past Due", etc., and fall back to a
capitalized version of the raw status string for unknown values; adjust the
Badge variant if needed to reflect destructive states.
In `@src/components/billingsdk/user-wallet-card.tsx`:
- Around line 50-54: The balance is being formatted without converting from the
card's currency; update the display in user-wallet-card.tsx to first
convert(balance, currency, currentCurrency) and then call formatCurrency on the
converted amount (similar to BillingSettingsThree). Locate the CardContent block
where formatCurrency(balance) is used and replace it with
formatCurrency(convert(balance, currency, currentCurrency)), ensuring you
reference the component props/variables named balance, currency, and
currentCurrency and keep any existing locale/format options intact.
In `@src/registry/billingsdk/billing-settings-three.tsx`:
- Around line 333-338: The status icon currently always renders CheckCircle2
inside the JSX block that uses tx.status to decide colors; change the rendering
to pick the icon based on tx.status (use CheckCircle2 for "completed", Clock for
"pending", and XCircle for "failed"), and keep or adjust the existing color
logic in the same wrapper (the div using cn and tx.status) so icon + background
reflect the actual state; update imports to include Clock and XCircle and
replace the single <CheckCircle2 /> usage with a conditional/icon-selection
expression tied to tx.status.
- Around line 3-4: Remove the unused imports from the top of the module: delete
ComponentPropsWithoutRef, useEffect, useRef from the React import and remove
useInView, useMotionValue, useSpring from the motion/react import so only
useState and the actually used motion/AnimatePresence remain; update the import
statements in billing-settings-three.tsx accordingly to avoid ESLint
unused-import errors and keep only the identifiers referenced in this file.
- Around line 169-176: The getCurrencySymbol function's replace regex doesn't
remove non-breaking spaces, so update its replacement to strip all digit
characters, ASCII/Unicode spaces and punctuation used in number formatting;
specifically change the regex used in the .replace call (referencing
getCurrencySymbol, currentCurrency and currency) to something like a
Unicode-aware character class that includes digits and whitespace (e.g., use a
regex with \p{Nd} and \p{Zs} or explicitly include \u00A0) and enable the u flag
so the currency symbol extraction removes NBSPs and other locale spaces.
In `@src/registry/billingsdk/customer-portal.tsx`:
- Around line 120-127: The mobile menu toggle Button is missing accessibility
attributes; update the Button (the component rendering the toggle using
isMobileMenuOpen and setIsMobileMenuOpen) to include
aria-expanded={isMobileMenuOpen} and a descriptive aria-label (e.g.,
aria-label={isMobileMenuOpen ? "Close menu" : "Open menu"}) so screen readers
can announce the open/closed state; ensure these props are passed to the Button
component that wraps the X and Menu icons referenced in this snippet.
🧹 Nitpick comments (6)
src/lib/i18n-provider.tsx (2)
67-80: MissingAbortControllercleanup in the fetch effect.If the component unmounts before the fetch completes,
setRateswill be called on an unmounted component. Add an abort signal to cancel the in-flight request on cleanup.Proposed fix
useEffect(() => { + const controller = new AbortController(); const fetchRates = async () => { try { - const response = await fetch("https://api.frankfurter.dev/v1/latest?base=USD"); + const response = await fetch("https://api.frankfurter.dev/v1/latest?base=USD", { + signal: controller.signal, + }); const data = await response.json(); if (data.rates) { setRates({ USD: 1, ...data.rates }); } } catch (error) { - console.error("Failed to fetch exchange rates:", error); + if (error instanceof DOMException && error.name === "AbortError") return; + console.error("Failed to fetch exchange rates:", error); } }; fetchRates(); + return () => controller.abort(); }, []);
82-96:convertwraps a closure inuseMemo— consideruseCallbackfor clarity.
useMemoreturning a function is functionally identical touseCallback, butuseCallbackbetter communicates intent. Same applies toformatCurrencyon lines 98–113. This is a minor readability nit.src/registry/billingsdk/billing-settings-three.tsx (1)
31-36: Card logos depend on an external third-party CDN (img.icons8.com).If icons8 goes down or changes URLs, card logos will break silently (falling back to the generic icon). Consider bundling these small images as local assets or using inline SVGs for reliability. The
onErrorfallback mitigates the impact, but this is still a runtime dependency on a third party for core UI.src/registry/billingsdk/customer-portal.tsx (2)
174-176: Conflictingflexandhiddenutilities on the same element.Line 174 has both
flexandhiddenat the base breakpoint. This works in practice because Tailwind generateshidden(display: none) afterflexin the stylesheet, but it's fragile and non-idiomatic. Drop the baseflexand keephidden md:flex.✏️ Suggested fix
-<header className="h-16 border-b flex items-center px-8 hidden md:flex"> +<header className="h-16 border-b items-center px-8 hidden md:flex">
129-134: Mobile sidebar overlay has no dismiss-on-Escape or backdrop click.When
isMobileMenuOpenis true, the sidebar becomes afixed inset-0 z-50overlay. Users can only close it via the X button. Consider adding anonKeyDownhandler for Escape and/or a backdrop<div>that closes the menu on click for improved usability and accessibility.content/docs/i18n-provider.mdx (1)
24-29: Feature descriptions are accurate and well-organized.The four features (real-time conversion, static fallbacks, localized formatting, auto-conversion) align with the
BillingProviderimplementation. One minor note: "caches them" on line 26 could be more precise—the rates are held in React state (in-memory for the provider's lifetime), not persisted to any durable cache. Consider rewording to "stores them in memory" if persistence is not planned.
| import { BillingSettingsThree } from "@/components/billingsdk/billing-settings-three"; | ||
| import BillingSettingsThreeDemo from "@/registry/billingsdk/demo/billing-settings-three-demo"; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd 'index.mdx' --full-path 'content/docs/components/billing-settings-three' --type fRepository: dodopayments/billingsdk
Length of output: 123
🏁 Script executed:
cat -n content/docs/components/billing-settings-three/index.mdxRepository: dodopayments/billingsdk
Length of output: 4943
Remove unused import: BillingSettingsThree is never referenced in the MDX body.
Only BillingSettingsThreeDemo is used in the Preview tab (line 16). The component is referenced in the usage code example block but that's static documentation content, not rendered JSX.
✏️ Proposed fix
-import { BillingSettingsThree } from "@/components/billingsdk/billing-settings-three";
import BillingSettingsThreeDemo from "@/registry/billingsdk/demo/billing-settings-three-demo";🤖 Prompt for AI Agents
In `@content/docs/components/billing-settings-three/index.mdx` around lines 6 - 7,
Remove the unused import by deleting the import statement that references
BillingSettingsThree; specifically remove or stop importing the symbol
BillingSettingsThree from "@/components/billingsdk/billing-settings-three" since
only BillingSettingsThreeDemo is actually used in the MDX content and the static
code block does not require an actual import. Ensure only
BillingSettingsThreeDemo remains imported to avoid dead imports and linter
warnings.
| - **Minimalist Metrics**: Clean, static display of wallet balances, spend amounts, and trend percentages for maximum stability and clarity. | ||
| - **Trend Indicators**: Built-in support for displaying positive/negative trends with automatic color coding and icons. | ||
| - **Liquid Payment Methods**: Smooth, animated "liquid" list for adding/removing payment methods with high-fidelity transitions. | ||
| - **Smart Brand Recognition**: Automatically displays high-quality icons for Visa, Mastercard, AMEX, and Rupay, with a fallback to standard card icons if external assets fail to load. | ||
| - **Flexible "Add New" Logic**: Support for both in-app modal callbacks (`onAddNew`) and direct external redirects (`addNewHref`). | ||
| - **Minimalist Aesthetic**: Features a zero-animation `minimal` mode that removes background decorative elements for a clean dashboard look. |
There was a problem hiding this comment.
Documentation contradicts PR description regarding animations.
Line 29 describes "Smooth, animated 'liquid' list… with high-fidelity transitions," while line 32 mentions "zero-animation minimal mode." The PR description explicitly states "no animations" for BillingSettingsThree. These claims are inconsistent—clarify whether the classic theme includes animations or if the feature descriptions should be corrected.
🤖 Prompt for AI Agents
In `@content/docs/components/billing-settings-three/index.mdx` around lines 27 -
32, The doc text contradicts the PR description about animations; update the
component docs for BillingSettingsThree to be consistent by clarifying whether
the classic theme uses animated transitions or is strictly no-animation: change
the "Liquid Payment Methods" bullet and the "Minimalist Aesthetic" bullet to
reflect the authoritative behavior (use the component name BillingSettingsThree
and the prop minimal/onAddNew/addNewHref to guide readers), e.g., state that
liquid list uses animated transitions only when minimal is false (or state "no
animations" if PR dictates) and that minimal mode disables all
decorative/transition animations; ensure both bullets and any mention of
animations match the PR claim exactly.
| ```tsx title="app/layout.tsx" | ||
| import { BillingProvider } from "@/lib/i18n-provider"; | ||
|
|
||
| export default function RootLayout({ children }) { | ||
| return ( | ||
| <BillingProvider defaultCurrency="USD" defaultLocale="en-US"> | ||
| {children} | ||
| </BillingProvider> | ||
| ); | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Setup code example is missing TypeScript types.
The RootLayout example omits prop types for children, which is inconsistent with a TypeScript-oriented SDK. This is the first code snippet users will copy.
✏️ Suggested fix
-export default function RootLayout({ children }) {
+export default function RootLayout({ children }: { children: React.ReactNode }) {🤖 Prompt for AI Agents
In `@content/docs/i18n-provider.mdx` around lines 35 - 45, The RootLayout example
omits TypeScript types for its props; update the RootLayout signature to declare
the children prop type (e.g., { children: React.ReactNode } or
React.PropsWithChildren) so the snippet is TypeScript-safe—adjust imports if
needed—and keep the existing BillingProvider usage with defaultCurrency and
defaultLocale unchanged.
| ```tsx | ||
| import { useBilling } from "@/lib/i18n-provider"; | ||
|
|
||
| export function CustomPrice({ amount }) { | ||
| const { formatCurrency } = useBilling(); | ||
|
|
||
| return ( | ||
| <p> | ||
| Trial ends in: {formatCurrency(amount, { from: "USD" })} | ||
| </p> | ||
| ); | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Same missing types in the CustomPrice example.
✏️ Suggested fix
-export function CustomPrice({ amount }) {
+export function CustomPrice({ amount }: { amount: number }) {🤖 Prompt for AI Agents
In `@content/docs/i18n-provider.mdx` around lines 63 - 75, The example CustomPrice
component is missing TypeScript types for its props; update the function
signature to explicitly type the props (e.g., define an interface or inline type
for amount as number) and annotate the component return type if desired so
CustomPrice({ amount }: { amount: number }) uses a typed amount passed into
useBilling().formatCurrency; ensure references to the useBilling import and the
CustomPrice function name remain consistent.
| { | ||
| "name": "billing-settings-3", | ||
| "type": "registry:block", | ||
| "title": "Billing Settings 3", | ||
| "description": "Premium billing dashboard with wallet balance and liquid UI payment methods", | ||
| "files": [ | ||
| { | ||
| "path": "src/registry/billingsdk/billing-settings-three.tsx", | ||
| "type": "registry:component", | ||
| "target": "components/billingsdk/billing-settings-three.tsx" | ||
| }, | ||
| { | ||
| "path": "src/registry/billingsdk/demo/billing-settings-three-demo.tsx", | ||
| "type": "registry:component", | ||
| "target": "components/billing-settings-three-demo.tsx" | ||
| } | ||
| ], | ||
| "dependencies": [ | ||
| "lucide-react", | ||
| "motion" | ||
| ], | ||
| "registryDependencies": [ | ||
| "button", | ||
| "card", | ||
| "utils" | ||
| ] | ||
| }, |
There was a problem hiding this comment.
billing-settings-3 registry block is missing badge in registryDependencies.
The BillingSettingsThree component imports and uses Badge from @/components/ui/badge (for the "Default" badge on payment methods). Add "badge" to registryDependencies. Also, since the component requires useBilling() from the i18n provider, consider adding "@billingsdk/i18n-provider" as a registry dependency.
Proposed fix
"registryDependencies": [
"button",
"card",
+ "badge",
- "utils"
+ "utils",
+ "@billingsdk/i18n-provider"
]📝 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.
| { | |
| "name": "billing-settings-3", | |
| "type": "registry:block", | |
| "title": "Billing Settings 3", | |
| "description": "Premium billing dashboard with wallet balance and liquid UI payment methods", | |
| "files": [ | |
| { | |
| "path": "src/registry/billingsdk/billing-settings-three.tsx", | |
| "type": "registry:component", | |
| "target": "components/billingsdk/billing-settings-three.tsx" | |
| }, | |
| { | |
| "path": "src/registry/billingsdk/demo/billing-settings-three-demo.tsx", | |
| "type": "registry:component", | |
| "target": "components/billing-settings-three-demo.tsx" | |
| } | |
| ], | |
| "dependencies": [ | |
| "lucide-react", | |
| "motion" | |
| ], | |
| "registryDependencies": [ | |
| "button", | |
| "card", | |
| "utils" | |
| ] | |
| }, | |
| { | |
| "name": "billing-settings-3", | |
| "type": "registry:block", | |
| "title": "Billing Settings 3", | |
| "description": "Premium billing dashboard with wallet balance and liquid UI payment methods", | |
| "files": [ | |
| { | |
| "path": "src/registry/billingsdk/billing-settings-three.tsx", | |
| "type": "registry:component", | |
| "target": "components/billingsdk/billing-settings-three.tsx" | |
| }, | |
| { | |
| "path": "src/registry/billingsdk/demo/billing-settings-three-demo.tsx", | |
| "type": "registry:component", | |
| "target": "components/billing-settings-three-demo.tsx" | |
| } | |
| ], | |
| "dependencies": [ | |
| "lucide-react", | |
| "motion" | |
| ], | |
| "registryDependencies": [ | |
| "button", | |
| "card", | |
| "badge", | |
| "utils", | |
| "@billingsdk/i18n-provider" | |
| ] | |
| }, |
🤖 Prompt for AI Agents
In `@registry.json` around lines 1061 - 1087, The registry entry for
billing-settings-3 is missing required registryDependencies: add "badge" (since
the BillingSettingsThree component imports Badge from "@/components/ui/badge")
and add "@billingsdk/i18n-provider" (since the component calls useBilling()/i18n
provider hooks); update the "registryDependencies" array for the
billing-settings-3 block to include both "badge" and "@billingsdk/i18n-provider"
so the component's imports resolve correctly.
| <CardContent> | ||
| <div className="flex items-end justify-between"> | ||
| <div className="text-4xl font-bold tracking-tight"> | ||
| {formatCurrency(balance)} | ||
| </div> |
There was a problem hiding this comment.
Balance is formatted without currency conversion.
formatCurrency(balance) displays the raw numeric value in the provider's current currency without converting from the card's currency prop. If balance is 1250 USD and the user switches to EUR, it will display €1,250.00 instead of the converted amount.
BillingSettingsThree handles this correctly by calling convert(balance, "USD", currentCurrency) before display. Apply the same pattern here:
Proposed fix
- const { formatCurrency } = useBilling();
+ const { formatCurrency, convert, currency: currentCurrency } = useBilling();
return (
<Card className={cn(walletCardVariants({ theme }), className)}>
{/* ... */}
<CardContent>
<div className="flex items-end justify-between">
<div className="text-4xl font-bold tracking-tight">
- {formatCurrency(balance)}
+ {formatCurrency(convert(balance, currency, currentCurrency))}
</div>🤖 Prompt for AI Agents
In `@src/components/billingsdk/user-wallet-card.tsx` around lines 50 - 54, The
balance is being formatted without converting from the card's currency; update
the display in user-wallet-card.tsx to first convert(balance, currency,
currentCurrency) and then call formatCurrency on the converted amount (similar
to BillingSettingsThree). Locate the CardContent block where
formatCurrency(balance) is used and replace it with
formatCurrency(convert(balance, currency, currentCurrency)), ensuring you
reference the component props/variables named balance, currency, and
currentCurrency and keep any existing locale/format options intact.
| const getCurrencySymbol = () => { | ||
| return new Intl.NumberFormat("en-US", { | ||
| style: "currency", | ||
| currency: currentCurrency || currency, | ||
| }) | ||
| .format(0) | ||
| .replace(/[0. \d]/g, ""); | ||
| }; |
There was a problem hiding this comment.
getCurrencySymbol regex may not strip non-breaking spaces.
Some locales (e.g., fr-FR) use non-breaking spaces (\u00A0) in formatted currency. The regex [0. \d] only matches ASCII space, leaving \u00A0 in the result.
Proposed fix
- .replace(/[0. \d]/g, "");
+ .replace(/[\d\s.,\u00A0]/g, "").trim();🤖 Prompt for AI Agents
In `@src/registry/billingsdk/billing-settings-three.tsx` around lines 169 - 176,
The getCurrencySymbol function's replace regex doesn't remove non-breaking
spaces, so update its replacement to strip all digit characters, ASCII/Unicode
spaces and punctuation used in number formatting; specifically change the regex
used in the .replace call (referencing getCurrencySymbol, currentCurrency and
currency) to something like a Unicode-aware character class that includes digits
and whitespace (e.g., use a regex with \p{Nd} and \p{Zs} or explicitly include
\u00A0) and enable the u flag so the currency symbol extraction removes NBSPs
and other locale spaces.
| <div className={cn( | ||
| "w-8 h-8 rounded-full flex items-center justify-center", | ||
| tx.status === "completed" ? "bg-emerald-500/10 text-emerald-500" : "bg-yellow-500/10 text-yellow-500" | ||
| )}> | ||
| <CheckCircle2 className="w-4 h-4" /> | ||
| </div> |
There was a problem hiding this comment.
Transaction status icon doesn't differentiate between pending and failed.
CheckCircle2 is rendered for all statuses — only the background color changes between completed (green) and non-completed (yellow). A failed transaction showing a checkmark icon (even yellow) is misleading. Consider using distinct icons (e.g., Clock for pending, XCircle for failed).
🤖 Prompt for AI Agents
In `@src/registry/billingsdk/billing-settings-three.tsx` around lines 333 - 338,
The status icon currently always renders CheckCircle2 inside the JSX block that
uses tx.status to decide colors; change the rendering to pick the icon based on
tx.status (use CheckCircle2 for "completed", Clock for "pending", and XCircle
for "failed"), and keep or adjust the existing color logic in the same wrapper
(the div using cn and tx.status) so icon + background reflect the actual state;
update imports to include Clock and XCircle and replace the single <CheckCircle2
/> usage with a conditional/icon-selection expression tied to tx.status.
| <Button | ||
| variant="ghost" | ||
| size="icon" | ||
| onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)} | ||
| > | ||
| {isMobileMenuOpen ? <X className="size-5" /> : <Menu className="size-5" />} | ||
| </Button> | ||
| </div> |
There was a problem hiding this comment.
Mobile menu toggle lacks accessibility attributes.
The toggle button should communicate its state to assistive technology. Add aria-expanded and aria-label so screen readers can convey the sidebar's open/closed state.
♿ Proposed fix
<Button
variant="ghost"
size="icon"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
+ aria-expanded={isMobileMenuOpen}
+ aria-label={isMobileMenuOpen ? "Close menu" : "Open menu"}
>📝 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.
| <Button | |
| variant="ghost" | |
| size="icon" | |
| onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)} | |
| > | |
| {isMobileMenuOpen ? <X className="size-5" /> : <Menu className="size-5" />} | |
| </Button> | |
| </div> | |
| <Button | |
| variant="ghost" | |
| size="icon" | |
| onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)} | |
| aria-expanded={isMobileMenuOpen} | |
| aria-label={isMobileMenuOpen ? "Close menu" : "Open menu"} | |
| > | |
| {isMobileMenuOpen ? <X className="size-5" /> : <Menu className="size-5" />} | |
| </Button> | |
| </div> |
🤖 Prompt for AI Agents
In `@src/registry/billingsdk/customer-portal.tsx` around lines 120 - 127, The
mobile menu toggle Button is missing accessibility attributes; update the Button
(the component rendering the toggle using isMobileMenuOpen and
setIsMobileMenuOpen) to include aria-expanded={isMobileMenuOpen} and a
descriptive aria-label (e.g., aria-label={isMobileMenuOpen ? "Close menu" :
"Open menu"}) so screen readers can announce the open/closed state; ensure these
props are passed to the Button component that wraps the X and Menu icons
referenced in this snippet.
|
@CodenRust build is failing - |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Fix all issues with AI agents
In `@public/r/billing-settings-3.json`:
- Around line 6-7: The JSON is missing declared packages used by the component:
add "class-variance-authority" to the "dependencies" array (used via the cva
symbol), and add "badge" and "i18n-provider" to "registryDependencies" because
the component imports Badge (from "@/components/ui/badge") and calls useBilling
(from "@/lib/i18n-provider"); also ensure the consumer is signaled to include a
BillingProvider (or add a registry dependency that provides it) so useBilling()
won't throw at runtime.
In `@public/r/i18n-provider.json`:
- Around line 10-11: The fetch in the BillingProvider's useEffect (function
fetchRates) must check response.ok before calling response.json(); modify
fetchRates to do: await fetch(...); if (!response.ok) throw new Error(`HTTP
${response.status}`) (or similar) so non-2xx responses are handled by the
existing catch block and you don't attempt to parse an HTML error body—update
the error message logged in the catch in fetchRates if desired.
- Line 6: The i18n provider metadata wrongly lists unused packages; remove
"lucide-react" and "motion" from the "dependencies" array in
public/r/i18n-provider.json so the file only lists packages actually imported by
i18n-provider.tsx / i18n-demo.tsx; locate the "dependencies" entry in that JSON
and delete those two strings (or replace the array with only true runtime deps)
to avoid forcing unnecessary installs.
In `@public/r/registry.json`:
- Around line 904-923: The registry entry for "billing-settings-3" is missing
required dependencies; update its "dependencies" and/or "registryDependencies"
arrays in the JSON for the billing-settings-3 block so they include "badge",
"class-variance-authority", and "i18n-provider" (matching the individual
billing-settings-3.json), ensuring the "dependencies" and "registryDependencies"
fields in the billing-settings-3 registry object are kept in sync with the
component's manifest.
- Around line 924-943: The registry entry for "i18n-provider" incorrectly
includes unused dependencies "lucide-react" and "motion"; update the
registry.json block for this package by removing those two strings from the
"dependencies" array so it only lists actual packages used by
src/lib/i18n-provider.tsx and src/registry/billingsdk/demo/i18n-demo.tsx (which
rely on React and registry components like card/select), ensuring the
"dependencies" array reflects real imports and leaving "registryDependencies"
unchanged.
In `@src/components/billingsdk/billing-settings-three.tsx`:
- Around line 145-149: The code incorrectly assumes balance/spend are in USD by
calling convert(balance, "USD", ...); update the conversion to treat the
component prop currency as the source: call convert(balance, currency || "USD",
currentCurrency || currency || "USD") and similarly for spend (use the same
source/target logic), and replace manual getCurrencySymbol(...) + toFixed(2)
displays with the provider's formatCurrency(convertedBalance, currentCurrency ||
currency) and formatCurrency(convertedSpend, currentCurrency || currency) so
formatting is consistent; adjust useBilling usages (formatCurrency, convert) and
ensure convertedBalance/convertedSpend variable names remain aligned.
In `@src/components/billingsdk/user-wallet-card.tsx`:
- Around line 51-53: The liquid theme sub-component (UserWalletCard / billingsdk
user-wallet-card.tsx) still contains animation classes that contradict the PR
description "no animations": remove the animate-pulse class from the avatar
placeholder and remove/replace transition-all duration-300 and active:scale-95
on the card root (or add a clear comment if animations must remain) so the
component has no animations; update className in the main component (e.g., the
divs that include "animate-pulse", "transition-all duration-300", and
"active:scale-95") accordingly and ensure the change is reflected in any theme
variants referenced by BillingSettingsThree.
🧹 Nitpick comments (2)
src/components/billingsdk/user-wallet-card.tsx (1)
47-48: Hardcoded UI string "Wallet Balance" in an i18n-enabled component.Given this PR introduces an i18n provider (
BillingProvider), consider externalizing this string so it can be localized alongside currency formatting.src/components/billingsdk/billing-settings-three.tsx (1)
152-169:useStateinitialized from props won't react to prop changes.
methodsis seeded frominitialPaymentMethodsonly on mount. If the parent re-renders with new payment methods (e.g. after a server fetch), the component ignores the update. The same applies tohistory(lines 171–195).If props are the source of truth, derive state from them or use a
keyto reset the component. If internal mutation (delete/set-default) is the intent, consider syncing viauseEffector lifting state.
| "dependencies": ["lucide-react", "motion"], | ||
| "registryDependencies": ["button", "card", "utils"], |
There was a problem hiding this comment.
Missing dependencies and registry dependencies — consumer installs will break.
The component imports Badge (@/components/ui/badge), cva (class-variance-authority), and useBilling (@/lib/i18n-provider), but these are not declared:
| Missing from | Item |
|---|---|
dependencies |
class-variance-authority |
registryDependencies |
badge |
registryDependencies |
i18n-provider (the component calls useBilling() and will throw without BillingProvider) |
Proposed fix
- "dependencies": ["lucide-react", "motion"],
- "registryDependencies": ["button", "card", "utils"],
+ "dependencies": ["lucide-react", "motion", "class-variance-authority"],
+ "registryDependencies": ["button", "card", "badge", "i18n-provider", "utils"],📝 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.
| "dependencies": ["lucide-react", "motion"], | |
| "registryDependencies": ["button", "card", "utils"], | |
| "dependencies": ["lucide-react", "motion", "class-variance-authority"], | |
| "registryDependencies": ["button", "card", "badge", "i18n-provider", "utils"], |
🤖 Prompt for AI Agents
In `@public/r/billing-settings-3.json` around lines 6 - 7, The JSON is missing
declared packages used by the component: add "class-variance-authority" to the
"dependencies" array (used via the cva symbol), and add "badge" and
"i18n-provider" to "registryDependencies" because the component imports Badge
(from "@/components/ui/badge") and calls useBilling (from
"@/lib/i18n-provider"); also ensure the consumer is signaled to include a
BillingProvider (or add a registry dependency that provides it) so useBilling()
won't throw at runtime.
| "name": "i18n-provider", | ||
| "title": "i18n Provider", | ||
| "description": "Global currency conversion and localized formatting provider", | ||
| "dependencies": ["lucide-react", "motion"], |
There was a problem hiding this comment.
Spurious dependencies: neither lucide-react nor motion is used.
The embedded i18n-provider.tsx and i18n-demo.tsx sources don't import lucide-react or motion. Listing them here forces consumers to install packages they don't need.
Proposed fix
- "dependencies": ["lucide-react", "motion"],
+ "dependencies": [],📝 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.
| "dependencies": ["lucide-react", "motion"], | |
| "dependencies": [], |
🤖 Prompt for AI Agents
In `@public/r/i18n-provider.json` at line 6, The i18n provider metadata wrongly
lists unused packages; remove "lucide-react" and "motion" from the
"dependencies" array in public/r/i18n-provider.json so the file only lists
packages actually imported by i18n-provider.tsx / i18n-demo.tsx; locate the
"dependencies" entry in that JSON and delete those two strings (or replace the
array with only true runtime deps) to avoid forcing unnecessary installs.
public/r/i18n-provider.json
Outdated
| "path": "src/lib/i18n-provider.tsx", | ||
| "content": "\"use client\";\n\nimport React, { createContext, useContext, useMemo, useState, useEffect } from \"react\";\n\nexport type Locale = string;\nexport type Currency = string;\n\ninterface BillingContextType {\n locale: Locale;\n currency: Currency;\n rates: Record<string, number>;\n setLocale: (locale: Locale) => void;\n setCurrency: (currency: Currency) => void;\n formatCurrency: (amount: number | string, options?: { from?: Currency }) => string;\n convert: (amount: number, from: Currency, to: Currency) => number;\n}\n\nconst BillingContext = createContext<BillingContextType | undefined>(undefined);\n\nconst STATIC_RATES = {\n \"USD\": 1.0,\n \"AUD\": 1.4312,\n \"BRL\": 5.2372,\n \"CAD\": 1.3666,\n \"CHF\": 0.77794,\n \"CNY\": 6.939,\n \"CZK\": 20.553,\n \"DKK\": 6.3314,\n \"EUR\": 0.84789,\n \"GBP\": 0.73588,\n \"HKD\": 7.8135,\n \"HUF\": 320.46,\n \"IDR\": 16873,\n \"ILS\": 3.1301,\n \"INR\": 90.66,\n \"ISK\": 123.11,\n \"JPY\": 157.09,\n \"KRW\": 1467.42,\n \"MXN\": 17.3639,\n \"MYR\": 3.9475,\n \"NOK\": 9.7249,\n \"NZD\": 1.6681,\n \"PHP\": 58.544,\n \"PLN\": 3.5768,\n \"RON\": 4.3174,\n \"SEK\": 9.0499,\n \"SGD\": 1.2732,\n \"THB\": 31.6,\n \"TRY\": 43.615,\n \"ZAR\": 16.1243\n};\n\nexport function BillingProvider({\n children,\n defaultLocale = \"en-US\",\n defaultCurrency = \"USD\",\n}: {\n children: React.ReactNode;\n defaultLocale?: Locale;\n defaultCurrency?: Currency;\n}) {\n const [locale, setLocale] = useState<Locale>(defaultLocale);\n const [currency, setCurrency] = useState<Currency>(defaultCurrency);\n const [rates, setRates] = useState<Record<string, number>>(STATIC_RATES);\n\n // Fetch exchange rates from Frankfurter\n useEffect(() => {\n const fetchRates = async () => {\n try {\n const response = await fetch(\"https://api.frankfurter.dev/v1/latest?base=USD\");\n const data = await response.json();\n if (data.rates) {\n setRates({ USD: 1, ...data.rates });\n }\n } catch (error) {\n console.error(\"Failed to fetch exchange rates:\", error);\n }\n };\n fetchRates();\n }, []);\n\n const convert = useMemo(() => {\n return (amount: number, from: Currency, to: Currency) => {\n if (from === to) return amount;\n\n // Fallback: If rates aren't loaded or currency missing, return original amount\n if (!rates[from] && from !== \"USD\") return amount;\n if (!rates[to] && to !== \"USD\") return amount;\n\n // Convert to USD (base) first, then to target\n const amountInUSD = from === \"USD\" ? amount : amount / (rates[from] || 1);\n const targetAmount = to === \"USD\" ? amountInUSD : amountInUSD * (rates[to] || 1);\n\n return targetAmount;\n };\n }, [rates]);\n\n const formatCurrency = useMemo(() => {\n return (amount: number | string, options?: { from?: Currency }) => {\n let value = typeof amount === \"string\" ? parseFloat(amount) : amount;\n if (isNaN(value)) return amount.toString();\n\n // Auto-convert if 'from' is specified and different from current currency\n if (options?.from && options.from !== currency) {\n value = convert(value, options.from, currency);\n }\n\n return new Intl.NumberFormat(locale, {\n style: \"currency\",\n currency: currency,\n }).format(value);\n };\n }, [locale, currency, convert]);\n\n const value = useMemo(\n () => ({\n locale,\n currency,\n rates,\n setLocale,\n setCurrency,\n formatCurrency,\n convert,\n }),\n [locale, currency, rates, formatCurrency, convert]\n );\n\n return (\n <BillingContext.Provider value={value}>{children}</BillingContext.Provider>\n );\n}\n\nexport function useBilling() {\n const context = useContext(BillingContext);\n if (context === undefined) {\n throw new Error(\"useBilling must be used within a BillingProvider\");\n }\n return context;\n}\n", |
There was a problem hiding this comment.
Missing response.ok check before parsing the Frankfurter API response.
In the embedded i18n-provider.tsx, the fetch call at https://api.frankfurter.dev/v1/latest?base=USD doesn't verify response.ok. A non-2xx response (e.g. 500 with an HTML body) will throw on response.json(), and the catch block only logs — silently falling back to stale static rates without informing the consumer. Add a guard:
if (!response.ok) throw new Error(`HTTP ${response.status}`);🤖 Prompt for AI Agents
In `@public/r/i18n-provider.json` around lines 10 - 11, The fetch in the
BillingProvider's useEffect (function fetchRates) must check response.ok before
calling response.json(); modify fetchRates to do: await fetch(...); if
(!response.ok) throw new Error(`HTTP ${response.status}`) (or similar) so
non-2xx responses are handled by the existing catch block and you don't attempt
to parse an HTML error body—update the error message logged in the catch in
fetchRates if desired.
| { | ||
| "name": "billing-settings-3", | ||
| "type": "registry:block", | ||
| "title": "Billing Settings 3", | ||
| "description": "Premium billing dashboard with wallet balance and liquid UI payment methods", | ||
| "files": [ | ||
| { | ||
| "path": "src/registry/billingsdk/billing-settings-three.tsx", | ||
| "type": "registry:component", | ||
| "target": "components/billingsdk/billing-settings-three.tsx" | ||
| }, | ||
| { | ||
| "path": "src/registry/billingsdk/demo/billing-settings-three-demo.tsx", | ||
| "type": "registry:component", | ||
| "target": "components/billing-settings-three-demo.tsx" | ||
| } | ||
| ], | ||
| "dependencies": ["lucide-react", "motion"], | ||
| "registryDependencies": ["button", "card", "utils"] | ||
| }, |
There was a problem hiding this comment.
billing-settings-3 registry entry is missing required dependencies.
Same issue as in public/r/billing-settings-3.json — badge, class-variance-authority, and i18n-provider are missing. This manifest and the individual JSON must stay in sync.
Proposed fix
"dependencies": ["lucide-react", "motion"],
- "registryDependencies": ["button", "card", "utils"]
+ "dependencies": ["lucide-react", "motion", "class-variance-authority"],
+ "registryDependencies": ["button", "card", "badge", "i18n-provider", "utils"]📝 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.
| { | |
| "name": "billing-settings-3", | |
| "type": "registry:block", | |
| "title": "Billing Settings 3", | |
| "description": "Premium billing dashboard with wallet balance and liquid UI payment methods", | |
| "files": [ | |
| { | |
| "path": "src/registry/billingsdk/billing-settings-three.tsx", | |
| "type": "registry:component", | |
| "target": "components/billingsdk/billing-settings-three.tsx" | |
| }, | |
| { | |
| "path": "src/registry/billingsdk/demo/billing-settings-three-demo.tsx", | |
| "type": "registry:component", | |
| "target": "components/billing-settings-three-demo.tsx" | |
| } | |
| ], | |
| "dependencies": ["lucide-react", "motion"], | |
| "registryDependencies": ["button", "card", "utils"] | |
| }, | |
| { | |
| "name": "billing-settings-3", | |
| "type": "registry:block", | |
| "title": "Billing Settings 3", | |
| "description": "Premium billing dashboard with wallet balance and liquid UI payment methods", | |
| "files": [ | |
| { | |
| "path": "src/registry/billingsdk/billing-settings-three.tsx", | |
| "type": "registry:component", | |
| "target": "components/billingsdk/billing-settings-three.tsx" | |
| }, | |
| { | |
| "path": "src/registry/billingsdk/demo/billing-settings-three-demo.tsx", | |
| "type": "registry:component", | |
| "target": "components/billing-settings-three-demo.tsx" | |
| } | |
| ], | |
| "dependencies": ["lucide-react", "motion", "class-variance-authority"], | |
| "registryDependencies": ["button", "card", "badge", "i18n-provider", "utils"] | |
| }, |
🤖 Prompt for AI Agents
In `@public/r/registry.json` around lines 904 - 923, The registry entry for
"billing-settings-3" is missing required dependencies; update its "dependencies"
and/or "registryDependencies" arrays in the JSON for the billing-settings-3
block so they include "badge", "class-variance-authority", and "i18n-provider"
(matching the individual billing-settings-3.json), ensuring the "dependencies"
and "registryDependencies" fields in the billing-settings-3 registry object are
kept in sync with the component's manifest.
| { | ||
| "name": "i18n-provider", | ||
| "type": "registry:block", | ||
| "title": "i18n Provider", | ||
| "description": "Global currency conversion and localized formatting provider", | ||
| "files": [ | ||
| { | ||
| "path": "src/lib/i18n-provider.tsx", | ||
| "type": "registry:lib", | ||
| "target": "lib/i18n-provider.tsx" | ||
| }, | ||
| { | ||
| "path": "src/registry/billingsdk/demo/i18n-demo.tsx", | ||
| "type": "registry:component", | ||
| "target": "components/i18n-demo.tsx" | ||
| } | ||
| ], | ||
| "dependencies": ["lucide-react", "motion"], | ||
| "registryDependencies": ["button", "card", "select", "utils"] | ||
| } |
There was a problem hiding this comment.
i18n-provider lists lucide-react and motion as dependencies but neither is used.
The provider (i18n-provider.tsx) only uses React, and the demo (i18n-demo.tsx) uses Card/Select UI components — no direct imports from lucide-react or motion.
Proposed fix
- "dependencies": ["lucide-react", "motion"],
+ "dependencies": [],📝 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.
| { | |
| "name": "i18n-provider", | |
| "type": "registry:block", | |
| "title": "i18n Provider", | |
| "description": "Global currency conversion and localized formatting provider", | |
| "files": [ | |
| { | |
| "path": "src/lib/i18n-provider.tsx", | |
| "type": "registry:lib", | |
| "target": "lib/i18n-provider.tsx" | |
| }, | |
| { | |
| "path": "src/registry/billingsdk/demo/i18n-demo.tsx", | |
| "type": "registry:component", | |
| "target": "components/i18n-demo.tsx" | |
| } | |
| ], | |
| "dependencies": ["lucide-react", "motion"], | |
| "registryDependencies": ["button", "card", "select", "utils"] | |
| } | |
| { | |
| "name": "i18n-provider", | |
| "type": "registry:block", | |
| "title": "i18n Provider", | |
| "description": "Global currency conversion and localized formatting provider", | |
| "files": [ | |
| { | |
| "path": "src/lib/i18n-provider.tsx", | |
| "type": "registry:lib", | |
| "target": "lib/i18n-provider.tsx" | |
| }, | |
| { | |
| "path": "src/registry/billingsdk/demo/i18n-demo.tsx", | |
| "type": "registry:component", | |
| "target": "components/i18n-demo.tsx" | |
| } | |
| ], | |
| "dependencies": [], | |
| "registryDependencies": ["button", "card", "select", "utils"] | |
| } |
🤖 Prompt for AI Agents
In `@public/r/registry.json` around lines 924 - 943, The registry entry for
"i18n-provider" incorrectly includes unused dependencies "lucide-react" and
"motion"; update the registry.json block for this package by removing those two
strings from the "dependencies" array so it only lists actual packages used by
src/lib/i18n-provider.tsx and src/registry/billingsdk/demo/i18n-demo.tsx (which
rely on React and registry components like card/select), ensuring the
"dependencies" array reflects real imports and leaving "registryDependencies"
unchanged.
| const { formatCurrency, currency: currentCurrency, convert } = useBilling(); | ||
|
|
||
| // Convert values from USD if needed | ||
| const convertedBalance = convert(balance, "USD", currentCurrency || currency); | ||
| const convertedSpend = convert(spend, "USD", currentCurrency || currency); |
There was a problem hiding this comment.
Balance/spend always assumed to be in USD — currency prop is misleading.
convert(balance, "USD", ...) hardcodes the source currency. If a consumer passes balance={1250} currency="EUR", the value is still treated as USD. Additionally, the cards use manual getCurrencySymbol() + toFixed(2) formatting instead of the provider's formatCurrency(), creating an inconsistency with how transaction amounts are rendered (line 430).
Consider using formatCurrency consistently and documenting (or using) the currency prop as the source currency:
Proposed fix
- const convertedBalance = convert(balance, "USD", currentCurrency || currency);
- const convertedSpend = convert(spend, "USD", currentCurrency || currency);
+ const sourceCurrency = currency;
+ const targetCurrency = currentCurrency || currency;
+ const convertedBalance = convert(balance, sourceCurrency, targetCurrency);
+ const convertedSpend = convert(spend, sourceCurrency, targetCurrency);And for display, use formatCurrency instead of manual symbol + toFixed:
- <span className="mr-1 text-xl font-medium opacity-70">
- {getCurrencySymbol()}
- </span>
- <span>{convertedBalance.toFixed(2)}</span>
+ <span>{formatCurrency(balance, { from: currency })}</span>🤖 Prompt for AI Agents
In `@src/components/billingsdk/billing-settings-three.tsx` around lines 145 - 149,
The code incorrectly assumes balance/spend are in USD by calling
convert(balance, "USD", ...); update the conversion to treat the component prop
currency as the source: call convert(balance, currency || "USD", currentCurrency
|| currency || "USD") and similarly for spend (use the same source/target
logic), and replace manual getCurrencySymbol(...) + toFixed(2) displays with the
provider's formatCurrency(convertedBalance, currentCurrency || currency) and
formatCurrency(convertedSpend, currentCurrency || currency) so formatting is
consistent; adjust useBilling usages (formatCurrency, convert) and ensure
convertedBalance/convertedSpend variable names remain aligned.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/components/billingsdk/billing-settings-three.tsx`:
- Line 362: The action-buttons container currently uses the class string "flex
items-center gap-2 opacity-0 transition-opacity group-hover:opacity-100" which
makes the "Make Default" and delete buttons invisible but still focusable; fix
this by either adding keyboard-focus visibility to the parent (e.g., add
group-focus-within:opacity-100 or group-focus:opacity-100 to that same class
string) or by adding focus:opacity-100 to the buttons themselves (the "Make
Default" and delete button elements) so they become visible when focused via
keyboard.
| </div> | ||
| </div> | ||
|
|
||
| <div className="flex items-center gap-2 opacity-0 transition-opacity group-hover:opacity-100"> |
There was a problem hiding this comment.
Action buttons with opacity-0 are invisible but still focusable — keyboard accessibility issue.
The "Make Default" and delete buttons use opacity-0 group-hover:opacity-100, so they're invisible until hover. However, they remain in the tab order, meaning keyboard users will focus on invisible buttons with no visual indication. Consider adding focus-within:opacity-100 on the group or focus:opacity-100 on the buttons themselves.
Proposed fix
- <div className="flex items-center gap-2 opacity-0 transition-opacity group-hover:opacity-100">
+ <div className="flex items-center gap-2 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100">🤖 Prompt for AI Agents
In `@src/components/billingsdk/billing-settings-three.tsx` at line 362, The
action-buttons container currently uses the class string "flex items-center
gap-2 opacity-0 transition-opacity group-hover:opacity-100" which makes the
"Make Default" and delete buttons invisible but still focusable; fix this by
either adding keyboard-focus visibility to the parent (e.g., add
group-focus-within:opacity-100 or group-focus:opacity-100 to that same class
string) or by adding focus:opacity-100 to the buttons themselves (the "Make
Default" and delete button elements) so they become visible when focused via
keyboard.
tsahil01
left a comment
There was a problem hiding this comment.
Hey @CodenRust can you check this?
There was a problem hiding this comment.
@CodenRust We reuse these components in other files, so please check the other components in this directory.
You need to import them from the registry directory and then export them again from here.
There was a problem hiding this comment.
I feel this file is not being used anywhere, Can you tell whats the use of this?
There was a problem hiding this comment.
Hey even this is not being used anywhere.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/components/billingsdk/cancel-subscription-dialog.tsx`:
- Around line 1-2: The barrel currently only re-exports CancelSubscriptionDialog
so imports of CancelSubscriptionDialogProps break; update the export statement
to also re-export the type CancelSubscriptionDialogProps from the original
module (i.e. change the export in the CancelSubscriptionDialog barrel to include
both CancelSubscriptionDialog and type CancelSubscriptionDialogProps) so files
like subscription-management that import CancelSubscriptionDialogProps continue
to work.
In `@src/components/billingsdk/pricing-table-five.tsx`:
- Around line 1-2: The barrel file currently only re-exports the component
(export { PricingTableFive } from "@/registry/billingsdk/pricing-table-five")
and must also re-export the Props type to preserve the public API; update this
file to export the type alias (type PricingTableFiveProps) from the registry
implementation alongside the component export, e.g. add a matching export for
the type from "@/registry/billingsdk/pricing-table-five"; apply the same change
to the other pricing-table barrel files so each exports both the component
(PricingTableFive) and its Props type (PricingTableFiveProps).
🧹 Nitpick comments (1)
src/components/subscription-management-demo.tsx (1)
45-47: UnnecessaryasyncononKeepSubscription.This callback contains no
awaitand the prop type is(planId: string) => void. Theasynckeyword is superfluous here — unlikeonCancel, there's noPromisebeing constructed or awaited.Suggested fix
- onKeepSubscription: async (planId: string) => { + onKeepSubscription: (planId: string) => { console.log("keep subscription", planId); },
| "use client"; | ||
| export { | ||
| CancelSubscriptionDialog, | ||
| type CancelSubscriptionDialogProps, | ||
| } from "@/registry/billingsdk/cancel-subscription-dialog"; | ||
| export { CancelSubscriptionDialog } from "@/registry/billingsdk/cancel-subscription-dialog"; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for imports of CancelSubscriptionDialogProps from the barrel module
rg "from.*cancel-subscription-dialog" -t ts -t tsx --max-count=20Repository: dodopayments/billingsdk
Length of output: 94
🏁 Script executed:
#!/bin/bash
# Broader search for any references to CancelSubscriptionDialogProps
rg "CancelSubscriptionDialogProps" -t ts -t tsx -A 2 -B 2Repository: dodopayments/billingsdk
Length of output: 94
🏁 Script executed:
#!/bin/bash
# Check the actual content of the cancel-subscription-dialog module to see what's exported
fd cancel-subscription-dialog --type f | head -20Repository: dodopayments/billingsdk
Length of output: 410
🏁 Script executed:
#!/bin/bash
# Search for CancelSubscriptionDialogProps without type flags
rg "CancelSubscriptionDialogProps" --max-count=50Repository: dodopayments/billingsdk
Length of output: 18597
🏁 Script executed:
#!/bin/bash
# Check imports from the barrel module specifically
rg "from.*cancel-subscription-dialog\"" --max-count=50Repository: dodopayments/billingsdk
Length of output: 805
🏁 Script executed:
#!/bin/bash
# Read the barrel module file
cat -n src/components/billingsdk/cancel-subscription-dialog.tsxRepository: dodopayments/billingsdk
Length of output: 187
🏁 Script executed:
#!/bin/bash
# Read the actual implementation module
cat -n src/registry/billingsdk/cancel-subscription-dialog.tsxRepository: dodopayments/billingsdk
Length of output: 12122
The barrel module must re-export CancelSubscriptionDialogProps to prevent breaking imports.
src/registry/billingsdk/subscription-management.tsx imports CancelSubscriptionDialogProps from this barrel module. Removing or failing to re-export this type will break the import statement on line 12 of that file where it's used to type the cancelSubscription prop.
Add the type re-export to the barrel:
export { CancelSubscriptionDialog, type CancelSubscriptionDialogProps } from "@/registry/billingsdk/cancel-subscription-dialog";🤖 Prompt for AI Agents
In `@src/components/billingsdk/cancel-subscription-dialog.tsx` around lines 1 - 2,
The barrel currently only re-exports CancelSubscriptionDialog so imports of
CancelSubscriptionDialogProps break; update the export statement to also
re-export the type CancelSubscriptionDialogProps from the original module (i.e.
change the export in the CancelSubscriptionDialog barrel to include both
CancelSubscriptionDialog and type CancelSubscriptionDialogProps) so files like
subscription-management that import CancelSubscriptionDialogProps continue to
work.
| "use client"; | ||
| export { PricingTableFive } from "@/registry/billingsdk/pricing-table-five"; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if any file in the repo imports the removed Props types
rg -n 'PricingTableFiveProps|PricingTableOneProps|PricingTableFourProps' --type=ts --type=tsx -g '!src/registry/**' -g '!src/components/billingsdk/pricing-table-*'Repository: dodopayments/billingsdk
Length of output: 94
🏁 Script executed:
cat -n src/components/billingsdk/pricing-table-five.tsxRepository: dodopayments/billingsdk
Length of output: 171
🏁 Script executed:
fd 'pricing-table.*\.tsx$' src/components/billingsdk/ | xargs -I {} sh -c 'echo "=== {} ===" && head -2 {}'Repository: dodopayments/billingsdk
Length of output: 1110
🏁 Script executed:
# Try rg search without the tsx file type filter, or use correct flags
rg 'PricingTableFiveProps|PricingTableOneProps|PricingTableFourProps' --type ts -g '!src/registry/**' -g '!src/components/billingsdk/pricing-table-*'Repository: dodopayments/billingsdk
Length of output: 49
🏁 Script executed:
# Check the registry files to see if Props types are exported there
fd 'pricing-table-five' src/registry/Repository: dodopayments/billingsdk
Length of output: 170
🏁 Script executed:
# Check what is exported from one of the registry files
head -30 src/registry/billingsdk/pricing-table-five.tsxRepository: dodopayments/billingsdk
Length of output: 1005
🏁 Script executed:
# Search for any exports in the barrel files including Props or types
rg 'export.*Props' src/components/billingsdk/Repository: dodopayments/billingsdk
Length of output: 176
🏁 Script executed:
# Check if PricingTableFiveProps type exists in the registry implementation
rg 'type PricingTableFiveProps|interface PricingTableFiveProps' src/registry/billingsdk/pricing-table-five.tsxRepository: dodopayments/billingsdk
Length of output: 127
🏁 Script executed:
# Check the git diff to see what was removed from this file
git diff HEAD src/components/billingsdk/pricing-table-five.tsxRepository: dodopayments/billingsdk
Length of output: 49
🏁 Script executed:
# Check if similar pricing table components export their Props types
rg 'export.*type.*PricingTable.*Props' src/Repository: dodopayments/billingsdk
Length of output: 49
🏁 Script executed:
# Check what is exported from the registry pricing-table-five.tsx
rg '^export' src/registry/billingsdk/pricing-table-five.tsxRepository: dodopayments/billingsdk
Length of output: 162
🏁 Script executed:
# Search for imports of PricingTableFiveProps or similar Props types in the entire codebase
rg 'import.*PricingTableFiveProps|import.*PricingTableOneProps|import.*PricingTableFourProps'Repository: dodopayments/billingsdk
Length of output: 50379
🏁 Script executed:
# Check the full export structure of the registry file
tail -20 src/registry/billingsdk/pricing-table-five.tsxRepository: dodopayments/billingsdk
Length of output: 649
Export PricingTableFiveProps type from the barrel file to avoid breaking changes.
The Props interface is defined in the registry implementation but not re-exported from src/components/billingsdk/pricing-table-five.tsx. This breaks the public API for consumers importing the type from the barrel file. Other components in this codebase (e.g., Banner) export their Props types, so this pattern should be consistent. Add type PricingTableFiveProps to the export:
"use client";
export { PricingTableFive } from "@/registry/billingsdk/pricing-table-five";
+ export type { PricingTableFiveProps } from "@/registry/billingsdk/pricing-table-five";Apply the same fix to the other pricing-table files.
🤖 Prompt for AI Agents
In `@src/components/billingsdk/pricing-table-five.tsx` around lines 1 - 2, The
barrel file currently only re-exports the component (export { PricingTableFive }
from "@/registry/billingsdk/pricing-table-five") and must also re-export the
Props type to preserve the public API; update this file to export the type alias
(type PricingTableFiveProps) from the registry implementation alongside the
component export, e.g. add a matching export for the type from
"@/registry/billingsdk/pricing-table-five"; apply the same change to the other
pricing-table barrel files so each exports both the component (PricingTableFive)
and its Props type (PricingTableFiveProps).
|
Hey @CodenRust why are there 46 file changes? Is it required for billing settings 3? |
tsahil01
left a comment
There was a problem hiding this comment.
@CodenRust can you expain why do we need to add these files?
|
@CodenRust apologies, but we’re unable to merge this PR at the moment as it contains several unnecessary changes. |
Summary
Added
BillingSettingsThreecomponent and global i18n/multi-currency support. Removed the Analytics Dashboard to streamline the codebase.Changes
BillingProviderwith real-time currency conversion using Frankfurter APIBillingSettingsThreeandi18n-providerScreenshots/Recordings (if UI)
2026-02-14.12-05-27.1.1.mp4
How to Test
Steps to validate locally:
npm cinpm run typechecknpm run buildnpm run dev- Navigate to docs to verify new components render correctlyChecklist
Summary by CodeRabbit
New Features
Documentation