Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds a new Tokens settings UI: a TokensView component with token balance, transactions table, and Add Tokens dialog; wires the view into the client-demo app routing and sidebar; and exposes TokensView through the React SDK exports and supporting files. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
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 |
Coverage Report for CI Build 24117771025Coverage remained the same at 41.146%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
web/sdk/react/views-new/tokens/tokens-view.tsx (1)
18-19: Consider consolidating Radix icon imports.These two imports from the same package can be merged into a single import statement.
-import { InfoCircledIcon, PlusIcon } from '@radix-ui/react-icons'; -import { ExclamationTriangleIcon } from '@radix-ui/react-icons'; +import { InfoCircledIcon, PlusIcon, ExclamationTriangleIcon } from '@radix-ui/react-icons';
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 37a7c21e-aac2-4e3d-b4ea-7a09d1629893
📒 Files selected for processing (9)
web/apps/client-demo/src/Router.tsxweb/apps/client-demo/src/pages/Settings.tsxweb/apps/client-demo/src/pages/settings/Tokens.tsxweb/sdk/react/index.tsweb/sdk/react/views-new/tokens/components/add-tokens-dialog.tsxweb/sdk/react/views-new/tokens/components/columns.tsxweb/sdk/react/views-new/tokens/index.tsweb/sdk/react/views-new/tokens/tokens-view.module.cssweb/sdk/react/views-new/tokens/tokens-view.tsx
| const tokensSchema = yup | ||
| .object({ | ||
| tokens: yup | ||
| .number() | ||
| .required('Please enter valid number') | ||
| .min(minQuantity, `Minimum ${minQuantity} token is required`) | ||
| .max(maxQuantity, `Maximum ${maxQuantity} tokens are allowed`) | ||
| .typeError('Please enter valid number of tokens') | ||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify schema vs BigInt conversion mismatch.
rg -n "yup\\.number\\(|\\.integer\\(|BigInt\\(data\\.tokens\\)" web/sdk/react/views-new/tokens/components/add-tokens-dialog.tsx -n -C2Repository: raystack/frontier
Length of output: 207
🏁 Script executed:
cat -n web/sdk/react/views-new/tokens/components/add-tokens-dialog.tsx | sed -n '50,120p'Repository: raystack/frontier
Length of output: 2600
Add .integer() validator to prevent decimal input before BigInt conversion.
The validation schema at lines 55-63 uses yup.number(), which accepts decimals (e.g., 1.5). However, line 117 converts the value with BigInt(data.tokens), which throws a RangeError for non-integer numbers. Add the .integer() validator to enforce integer-only input:
Proposed fix
tokens: yup
.number()
.required('Please enter valid number')
+ .integer('Please enter a whole number of tokens')
.min(minQuantity, `Minimum ${minQuantity} token is required`)
.max(maxQuantity, `Maximum ${maxQuantity} tokens are allowed`)
.typeError('Please enter valid number of tokens')📝 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.
| const tokensSchema = yup | |
| .object({ | |
| tokens: yup | |
| .number() | |
| .required('Please enter valid number') | |
| .min(minQuantity, `Minimum ${minQuantity} token is required`) | |
| .max(maxQuantity, `Maximum ${maxQuantity} tokens are allowed`) | |
| .typeError('Please enter valid number of tokens') | |
| }) | |
| const tokensSchema = yup | |
| .object({ | |
| tokens: yup | |
| .number() | |
| .required('Please enter valid number') | |
| .integer('Please enter a whole number of tokens') | |
| .min(minQuantity, `Minimum ${minQuantity} token is required`) | |
| .max(maxQuantity, `Maximum ${maxQuantity} tokens are allowed`) | |
| .typeError('Please enter valid number of tokens') | |
| }) |
| const query = qs.stringify( | ||
| { | ||
| details: btoa( | ||
| qs.stringify({ | ||
| organization_id: activeOrganization?.id, | ||
| type: 'tokens' | ||
| }) | ||
| ), | ||
| checkout_id: '{{.CheckoutID}}' | ||
| }, | ||
| { encode: false } | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify current query construction/encoding settings.
rg -n "btoa\\(|qs\\.stringify\\(|encode:\\s*false|checkout_id" web/sdk/react/views-new/tokens/components/add-tokens-dialog.tsx -n -C2Repository: raystack/frontier
Length of output: 527
🏁 Script executed:
# Search for other qs.stringify usages in the file and across the codebase
rg "qs\.stringify" web/sdk/react/views-new/tokens/components/add-tokens-dialog.tsx -B2 -A3Repository: raystack/frontier
Length of output: 308
🏁 Script executed:
# Check for server-side handler of checkout or billing parameters
fd -e "go|ts|tsx|js" | xargs rg -l "CheckoutID|checkout.*details|details.*btoa" | head -20Repository: raystack/frontier
Length of output: 856
🏁 Script executed:
# Search for any tests related to add-tokens-dialog
fd "add-tokens-dialog" web/sdk/react --type fRepository: raystack/frontier
Length of output: 173
🏁 Script executed:
# Check if there are comments or issues about encoding in this file
rg -i "encod|query|checkout" web/sdk/react/views-new/tokens/components/add-tokens-dialog.tsx -B1 -A1Repository: raystack/frontier
Length of output: 1376
🏁 Script executed:
# Check the old version of add-tokens-dialog to see if it also has this issue
cat -n web/sdk/react/views/tokens/add-tokens-dialog.tsx | sed -n '80,120p'Repository: raystack/frontier
Length of output: 1529
🏁 Script executed:
# Look at server-side checkout service to understand how it processes details param
cat -n billing/checkout/service.go | head -100Repository: raystack/frontier
Length of output: 4078
🏁 Script executed:
# Search for how details parameter is parsed/used in checkout tests
rg "details.*btoa|CheckoutID|query.*details" test/e2e/regression/billing_test.go -B3 -A3Repository: raystack/frontier
Length of output: 484
🏁 Script executed:
# Check qs library behavior - look for any documentation or usage patterns
rg "query-string|qs\." web/sdk/react/views-new/tokens/components/add-tokens-dialog.tsx -B5 -A5Repository: raystack/frontier
Length of output: 882
🏁 Script executed:
# Search for how the details parameter is parsed on server side
rg "details|Details" billing/checkout/service.go -B2 -A2 | head -50Repository: raystack/frontier
Length of output: 406
🏁 Script executed:
# Look for query parameter handling or URL parsing in checkout
rg -i "query|param|details" billing/checkout/service.go | head -20Repository: raystack/frontier
Length of output: 1283
🏁 Script executed:
# Check the old version behavior - see if it also uses encode: false
rg -A5 "qs.stringify" web/sdk/react/views/tokens/add-tokens-dialog.tsx | grep -A5 "encode"Repository: raystack/frontier
Length of output: 43
🏁 Script executed:
# Create a simple test to understand btoa output and qs.stringify behavior
python3 << 'EOF'
import base64
import json
import urllib.parse
# Simulate what btoa does in the code
inner_data = {"organization_id": "test-org-123", "type": "tokens"}
inner_json = json.dumps(inner_data, separators=(',', ':'))
print(f"Inner JSON: {inner_json}")
# Simulate btoa (base64 encode)
base64_encoded = base64.b64encode(inner_json.encode()).decode()
print(f"Base64 encoded: {base64_encoded}")
print(f"Contains special chars: +={'+' in base64_encoded}, /={('/' in base64_encoded)}, =={('=' in base64_encoded)}")
# Simulate with encode: false (no URL encoding)
query_unencoded = f"details={base64_encoded}&checkout_id={'{{'}.CheckoutID{'}}'}"
print(f"\nQuery with encode: false:\n{query_unencoded}")
# Simulate with encode: true (URL encoding)
query_encoded = f"details={urllib.parse.quote(base64_encoded, safe='')}&checkout_id={'{{'}.CheckoutID{'}}'}"
print(f"\nQuery with encode: true:\n{query_encoded}")
# What happens when parsing unencoded version
print(f"\nWhen server receives unencoded query:")
parsed = urllib.parse.parse_qs(query_unencoded)
print(f"Parsed details value: {parsed.get('details', [''])}")
print(f"Matches original base64: {parsed.get('details', [''])[0] == base64_encoded}")
EOFRepository: raystack/frontier
Length of output: 686
🏁 Script executed:
# Search for any related issues or TODOs about encoding/checkout
rg -i "TODO|FIXME|BUG|issue" web/sdk/react/views-new/tokens/components/add-tokens-dialog.tsxRepository: raystack/frontier
Length of output: 43
🏁 Script executed:
# Check if there are tests that verify the checkout URL encoding
fd -e test.tsx -e test.ts web/sdk/react/views-new/tokens/ 2>/dev/nullRepository: raystack/frontier
Length of output: 43
🏁 Script executed:
# Look for how the query string is actually used - check if Stripe docs mention encoding
rg "successUrl|cancelUrl|checkoutUrl" web/sdk/react/views-new/tokens/components/add-tokens-dialog.tsx -B2 -A2Repository: raystack/frontier
Length of output: 735
🏁 Script executed:
# Check if qs.stringify with encode: true is used elsewhere in the codebase for comparison
rg "qs\.stringify.*encode" web/sdk/react/ -B1 -A1 | head -40Repository: raystack/frontier
Length of output: 43
Use encode: true (or remove the option) for proper URL encoding of base64-encoded query parameters.
The { encode: false } setting bypasses URL encoding, leaving base64 special characters unencoded. The = padding character remains in the query string, but more critically, + characters become spaces and / characters can corrupt parsing. This violates URL encoding standards and risks breaking checkout redirects when base64 output contains these characters.
💡 Proposed fix
const query = qs.stringify(
{
details: btoa(
qs.stringify({
organization_id: activeOrganization?.id,
type: 'tokens'
})
),
checkout_id: '{{.CheckoutID}}'
},
- { encode: false }
+ { encode: true }
);📝 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.
| const query = qs.stringify( | |
| { | |
| details: btoa( | |
| qs.stringify({ | |
| organization_id: activeOrganization?.id, | |
| type: 'tokens' | |
| }) | |
| ), | |
| checkout_id: '{{.CheckoutID}}' | |
| }, | |
| { encode: false } | |
| ); | |
| const query = qs.stringify( | |
| { | |
| details: btoa( | |
| qs.stringify({ | |
| organization_id: activeOrganization?.id, | |
| type: 'tokens' | |
| }) | |
| ), | |
| checkout_id: '{{.CheckoutID}}' | |
| }, | |
| { encode: true } | |
| ); |
web/sdk/react/views-new/tokens/components/add-tokens-dialog.tsx
Outdated
Show resolved
Hide resolved
| const value = getValue() as bigint; | ||
| const prefix = row?.original?.type === 'credit' ? '+' : '-'; | ||
| return ( | ||
| <Text size="regular" variant="secondary"> | ||
| {prefix} | ||
| {Number(value)} | ||
| </Text> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify int64 rendering path and risky bigint->number conversion.
rg -n "accessorKey: 'amount'|Number\\(value\\)|as bigint|BillingTransaction" web/sdk/react/views-new/tokens/components/columns.tsx -n -C2Repository: raystack/frontier
Length of output: 972
🏁 Script executed:
# 1. Find BillingTransaction type definition and amount field type
rg "type BillingTransaction|interface BillingTransaction|message BillingTransaction" -A 10
# 2. Check for other Number(bigint) conversions in the codebase
rg "Number\([a-zA-Z_][a-zA-Z0-9_]*\)\s*(?://|$)" --type ts --type tsx | head -20
# 3. Look for similar getValue() patterns with bigint casting
rg "getValue\(\)\s*as\s*bigint" --type ts --type tsxRepository: raystack/frontier
Length of output: 2070
🏁 Script executed:
# Search for TypeScript/JavaScript files with Number(bigint) pattern
rg "Number\([a-zA-Z_]" web/sdk/react/ -t js | head -20
# Look for other getValue() patterns with type casting
rg "getValue\(\).*as" web/sdk/react/views-new/tokens/components/ -A 1
# Check for any token amount limits or documentation
rg "token|amount" web/sdk/react/views-new/tokens/ -i -l
# Find any .d.ts or type files related to BillingTransaction
fd "\.d\.ts|types\." | xargs rg "BillingTransaction" 2>/dev/null | head -20Repository: raystack/frontier
Length of output: 1059
🏁 Script executed:
# Find TypeScript type definitions - check common generated type locations
fd -e ts -e d.ts web/sdk/ | xargs rg "BillingTransaction" 2>/dev/null
# Search entire codebase for Number() conversions on potentially large numbers
rg "Number\([a-zA-Z_][a-zA-Z0-9_]*\)" web/ --type ts -B 2 -A 1 | head -40
# Check package.json to understand proto generation setup
rg "@raystack/proton" web/sdk/ -A 2 -B 2
# Look at imports in the columns file more carefully
head -10 web/sdk/react/views-new/tokens/components/columns.tsxRepository: raystack/frontier
Length of output: 50729
🏁 Script executed:
# Check which token view files are being changed in this PR context
# Look for the old view's current state to compare
cat -n web/sdk/react/views/tokens/transactions/columns.tsx | grep -A 10 "Number(value)"
# Check if there are any constants or tests defining max token amounts
rg "MAX.*AMOUNT|max.*token|token.*limit" web/ -i --max-count 10
# Look at imports and type definition for clarity on what getValue returns
sed -n '70,85p' web/sdk/react/views-new/tokens/components/columns.tsxRepository: raystack/frontier
Length of output: 1284
Use bigint.toString() instead of Number() to prevent precision loss for large token amounts.
Line 82 converts bigint to Number, which loses precision for values exceeding 9,007,199,254,740,991. Since BillingTransaction.amount is int64 (max 9,223,372,036,854,775,807), large token balances will display incorrectly. Render as a string instead.
Proposed fix
const value = getValue() as bigint;
const prefix = row?.original?.type === 'credit' ? '+' : '-';
return (
<Text size="regular" variant="secondary">
{prefix}
- {Number(value)}
+ {value.toString()}
</Text>
);| import { AddTokensDialog } from './components/add-tokens-dialog'; | ||
| import styles from './tokens-view.module.css'; | ||
|
|
||
| const addTokensDialogHandle = Dialog.createHandle(); |
There was a problem hiding this comment.
Module-level dialog handle creates shared state across component instances.
Dialog.createHandle() at module scope means all TokensView instances share the same dialog state. This causes issues if:
- Multiple
TokensViewcomponents are rendered simultaneously - During SSR, state may leak between requests
- Hot module replacement may preserve stale handles
Move the handle creation inside the component using useMemo or a ref.
Proposed fix
-const addTokensDialogHandle = Dialog.createHandle();
-
export interface TokensViewProps {}
export function TokensView(_props: TokensViewProps = {}) {
+ const addTokensDialogHandle = useMemo(() => Dialog.createHandle(), []);
+
const {Note: You'll need to add useMemo to the existing import from react if not already present (it is imported on line 3).
📝 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.
| const addTokensDialogHandle = Dialog.createHandle(); | |
| export interface TokensViewProps {} | |
| export function TokensView(_props: TokensViewProps = {}) { | |
| const addTokensDialogHandle = useMemo(() => Dialog.createHandle(), []); | |
| const { |
| const isPostpaid = | ||
| billingDetails?.creditMin && billingDetails.creditMin < BigInt(0); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for how creditMin is used elsewhere to understand the expected semantics
rg -n "creditMin" --type ts --type tsx -C 3Repository: raystack/frontier
Length of output: 88
🏁 Script executed:
#!/bin/bash
# Search for creditMin usage in TypeScript files
rg -n "creditMin" -t ts -C 3Repository: raystack/frontier
Length of output: 9735
🏁 Script executed:
#!/bin/bash
# Also search in all files to catch any .tsx or other extensions
rg -n "creditMin" -C 3Repository: raystack/frontier
Length of output: 12308
Simplify postpaid detection to be consistent with other usages.
Change line 106-107 to use direct comparison:
const isPostpaid = billingDetails?.creditMin? < BigInt(0);The current billingDetails?.creditMin && billingDetails.creditMin < BigInt(0) pattern is redundant. The optional chaining (?.) already handles null/undefined cases. Throughout the codebase (e.g., billing.tsx line 98, tokens-details-section.tsx line 85), postpaid detection uses direct comparison (creditMin < 0n or Number(creditMin) < 0). Adopt the same pattern for consistency.
paanSinghCoder
left a comment
There was a problem hiding this comment.
Approved with minor comments.
| import { Avatar, Flex, Text, getAvatarColor } from '@raystack/apsara-v1'; | ||
| import type { DataTableColumnDef } from '@raystack/apsara-v1'; | ||
| import type { BillingTransaction } from '@raystack/proton/frontier'; | ||
| import * as _ from 'lodash'; |
There was a problem hiding this comment.
Lets avoid * import.
|
|
||
| const addTokensDialogHandle = Dialog.createHandle(); | ||
|
|
||
| export interface TokensViewProps {} |
There was a problem hiding this comment.
export interface TokensViewProps {} Remove if not used anywhere.
| } from '@raystack/apsara-v1'; | ||
| import { toastManager } from '@raystack/apsara-v1'; | ||
| import { InfoCircledIcon, PlusIcon } from '@radix-ui/react-icons'; | ||
| import { ExclamationTriangleIcon } from '@radix-ui/react-icons'; |
There was a problem hiding this comment.
import { InfoCircledIcon, PlusIcon, ExclamationTriangleIcon } from '@radix-ui/react-icons'; Import can be merged.
Summary
views-new/tokens/using apsara-v1 components (DataTable, Dialog, Callout, etc.)