-
-
Notifications
You must be signed in to change notification settings - Fork 65
feat(WiP): tokens #996
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat(WiP): tokens #996
Conversation
WalkthroughThis change introduces a comprehensive credits (tokens) system for the Capgo platform, enabling organizations to purchase, manage, and view usage history for credits (tokens) applicable to MAU, storage, or bandwidth. The update adds database tables, migration scripts, backend Stripe integration, frontend Vue components, localization strings, and seed data for tiered pricing, supporting both one-off and recurring purchases, usage tracking, and detailed UI for credits management. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Frontend
participant Supabase
participant Stripe
participant DB
User->>Frontend: Open Tokens page
Frontend->>Supabase: Fetch token history, pricing steps
Supabase->>DB: Query capgo_tokens_history, capgo_tokens_steps
DB-->>Supabase: Return data
Supabase-->>Frontend: Return data
Frontend-->>User: Display tokens, history, buy options
User->>Frontend: Click Buy Tokens
Frontend->>Supabase: Request session (openCheckoutForOneOff)
Supabase->>Stripe: Create checkout session (with tiered pricing, quantity)
Stripe-->>Supabase: Return session URL
Supabase-->>Frontend: Return URL
Frontend->>User: Redirect to Stripe
Stripe->>User: Complete payment
Stripe->>Supabase: Send payment_intent.succeeded webhook
Supabase->>DB: Insert into capgo_tokens_history
Supabase-->>Stripe: Respond success
Assessment against linked issues
Assessment against linked issues: Out-of-scope changesNo out-of-scope changes found. Suggested reviewers
Poem
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
npm error code ERESOLVE ✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 16
🔭 Outside diff range comments (1)
supabase/functions/_backend/utils/stripe.ts (1)
248-277
:⚠️ Potential issueAdd missing
howMany
parameter to function signature.The function uses
howMany
on line 276 but it's not defined in the parameters.-export async function createCheckout(c: Context, customerId: string, reccurence: string, planId: string, successUrl: string, cancelUrl: string, clientReferenceId?: string) { +export async function createCheckout(c: Context, customerId: string, reccurence: string, planId: string, successUrl: string, cancelUrl: string, clientReferenceId?: string, howMany?: number) {
♻️ Duplicate comments (2)
messages/id.json (1)
123-125
: Duplicate localization changes: please apply the same reviews as for messages/ja.json
The Indonesian file includes the same new keys. Refer to the feedback on messages/ja.json to ensure consistency, completeness across locales, and correct translations.Also applies to: 165-167, 285-285, 294-294, 302-302, 304-304, 309-311, 324-324, 363-364, 408-408, 425-425, 516-516, 532-532, 546-546, 552-552, 555-555, 557-561, 563-563
messages/it.json (1)
123-125
: Duplicate localization changes: please apply the same reviews as for messages/ja.json
The Italian file contains identical additions. Please ensure the keys and translations adhere to the same standards and address any typos identified in the Japanese locale.Also applies to: 165-167, 285-285, 294-294, 302-302, 304-304, 309-311, 324-324, 363-364, 408-408, 425-425, 516-516, 532-532, 546-546, 552-552, 555-555, 557-561, 563-563
🧹 Nitpick comments (8)
src/components/BuyTokens.vue (1)
24-33
: Consider improving accessibility and text formatting.The heading structure and conditional rendering work well, but there are some considerations:
- Accessibility: Consider adding proper ARIA labels and semantic structure
- Text transformation: The
.toLocaleLowerCase()
call may not handle all languages correctlyConsider these improvements:
- <h1 class="text-3xl"> + <h1 class="text-3xl" role="heading" aria-level="1"> {{ t('buy') }} {{ !custom ? amount : t('custom').toLocaleLowerCase() }} </h1> - <h2 class="text-xl"> + <h2 class="text-xl" role="heading" aria-level="2">For better internationalization, consider using a dedicated translation key instead of
.toLocaleLowerCase()
:- {{ t('buy') }} {{ !custom ? amount : t('custom').toLocaleLowerCase() }} + {{ !custom ? t('buy-amount', { amount }) : t('buy-custom') }}src/services/stripe.ts (1)
104-104
: Add parameter validation for thehowMany
parameter.Consider adding validation to ensure
howMany
is a positive number to prevent invalid requests to the backend.export async function openCheckoutForOneOff(priceId: string, successUrl: string, cancelUrl: string, orgId: string, howMany: number) { + if (howMany <= 0) { + toast.error('Invalid quantity: must be a positive number') + return + }supabase/functions/_backend/utils/stripe.ts (2)
285-287
: Fix typo in function name.-function toMilion(price: number) { +function toMillion(price: number) { return `${price / 1000000}M` }Also update all usages of this function in the file.
308-314
: Fix typo in variable name.- const formatedAmmount = + const formattedAmount = howManyInStep % 1000000 === 0 && howManyInStep > 0 ? - toMilion(howManyInStep) : + toMillion(howManyInStep) : howManyInStep % 1000 === 0 && howManyInStep > 0 ? `${howManyInStep / 1000}K` : `${howManyInStep}`Also update the usage on line 321:
- name: `${formatedAmmount} tokens`, + name: `${formattedAmount} tokens`,src/pages/settings/organization/Tokens.vue (2)
85-119
: Optimize chart data processing for better performance.The chart data processing in this watcher could be expensive as it processes a full year of data and runs on every
tokensHistory
change. Consider memoizing or debouncing this computation.Consider this optimization:
-watch(tokensHistory, () => { +const debouncedChartUpdate = debounce(() => { const oneYearAgoDate = new Date() // ... existing logic +}, 300) + +watch(tokensHistory, debouncedChartUpdate)Or use a computed property with proper dependency tracking for better performance.
🧰 Tools
🪛 GitHub Actions: autofix.ci
[error] 101-101: ESLint: 'd' is not modified in this loop (no-unmodified-loop-condition)
[error] 101-101: ESLint: 'today' is not modified in this loop (no-unmodified-loop-condition)
1-258
: Consider component decomposition for better maintainability.This component handles multiple responsibilities: data visualization, purchasing flow, and history management. Consider extracting some functionality into separate components or composables.
Potential improvements:
- Extract chart logic into a separate composable (
useTokensChart
)- Create separate components for purchase flow and history view
- Move price calculation logic to a separate utility or composable
This would improve testability, reusability, and maintainability of the code.
🧰 Tools
🪛 GitHub Actions: autofix.ci
[error] 101-101: ESLint: 'd' is not modified in this loop (no-unmodified-loop-condition)
[error] 101-101: ESLint: 'today' is not modified in this loop (no-unmodified-loop-condition)
supabase/migrations/20250131091941_capgo_tokens.sql (2)
1-9
: Verify naming of thesum
column
Usingsum
as a column name may shadow the SQL aggregate function. Consider renaming it to something likequantity
ordelta
to avoid confusion.
36-81
:get_tokens_history
function is well implemented
The security-definer function checks admin rights, computes running totals, and limits to one year. Consider adding a composite index on(org_id, created_at)
for even faster filtering and ordering.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
package-lock.json
is excluded by!**/package-lock.json
src/assets/capgo-token-gold.svg
is excluded by!**/*.svg
src/assets/mau.svg
is excluded by!**/*.svg
📒 Files selected for processing (28)
messages/de.json
(11 hunks)messages/en.json
(11 hunks)messages/es.json
(11 hunks)messages/fr.json
(11 hunks)messages/id.json
(11 hunks)messages/it.json
(11 hunks)messages/ja.json
(11 hunks)messages/ko.json
(11 hunks)messages/pl.json
(11 hunks)messages/pt-br.json
(11 hunks)messages/ru.json
(11 hunks)messages/tr.json
(11 hunks)messages/vi.json
(11 hunks)messages/zh-cn.json
(11 hunks)package.json
(2 hunks)src/components.d.ts
(2 hunks)src/components/BuyTokens.vue
(1 hunks)src/layouts/settings.vue
(2 hunks)src/pages/settings/organization/Tokens.vue
(1 hunks)src/services/stripe.ts
(1 hunks)src/types/supabase.types.ts
(2 hunks)supabase/functions/_backend/private/stripe_checkout.ts
(2 hunks)supabase/functions/_backend/triggers/stripe_event.ts
(2 hunks)supabase/functions/_backend/utils/stripe.ts
(3 hunks)supabase/functions/_backend/utils/supabase.types.ts
(2 hunks)supabase/migrations/20250131091941_capgo_tokens.sql
(1 hunks)supabase/seed.sql
(1 hunks)vite.config.mts
(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
supabase/functions/_backend/private/stripe_checkout.ts (3)
supabase/functions/_backend/utils/loggin.ts (1)
cloudlog
(3-17)supabase/functions/_backend/utils/stripe.ts (2)
createCheckout
(248-284)createCheckoutForOneOff
(289-357)supabase/functions/_backend/utils/utils.ts (1)
getEnv
(160-164)
supabase/functions/_backend/triggers/stripe_event.ts (1)
supabase/functions/_backend/utils/supabase.ts (1)
supabaseAdmin
(60-69)
src/services/stripe.ts (1)
src/services/supabase.ts (1)
useSupabase
(53-66)
🪛 Biome (1.9.4)
supabase/functions/_backend/utils/stripe.ts
[error] 6-6: Shouldn't redeclare 'supabaseAdmin'. Consider to delete it or rename it.
'supabaseAdmin' is defined here:
(lint/suspicious/noRedeclare)
[error] 303-303: This number literal will lose precision at runtime.
The value at runtime will be 9223372036854776000
(lint/correctness/noPrecisionLoss)
🪛 GitHub Actions: Run tests
supabase/functions/_backend/utils/stripe.ts
[error] 5-5: Uncaught SyntaxError: Identifier 'supabaseAdmin' has already been declared. This causes worker boot errors and prevents proper function execution.
🪛 GitHub Actions: autofix.ci
src/pages/settings/organization/Tokens.vue
[error] 101-101: ESLint: 'd' is not modified in this loop (no-unmodified-loop-condition)
[error] 101-101: ESLint: 'today' is not modified in this loop (no-unmodified-loop-condition)
🔇 Additional comments (55)
package.json (1)
167-167
: LGTM! SVG loader dependency added correctly.The
vite-svg-loader
dependency is properly added to support SVG loading capabilities for the new token management UI components.messages/de.json (1)
123-563
: Comprehensive German localization for token features.The extensive set of German translations properly supports the new token purchasing and management functionality. The translations appear grammatically correct and contextually appropriate for the German market.
vite.config.mts (2)
11-11
: SVG loader import added correctly.The
vite-svg-loader
import is properly added to support SVG loading capabilities.
52-52
: SVG loader plugin configured properly.The
svgLoader()
plugin is correctly added to the plugins array to enable SVG loading throughout the application.supabase/seed.sql (1)
535-548
: Tiered pricing seed data looks well-structured.The pricing tiers follow a logical volume-based discount model with decreasing unit costs for higher quantities. The ranges cover the full spectrum from small to enterprise-level purchases, providing good flexibility for different customer segments.
src/layouts/settings.vue (1)
2-2
: Imports added correctly for token functionality.The new imports for
SVGAttributes
,h
function, andCurrencyIcon
are properly added to support the tokens tab implementation.Also applies to: 6-6, 8-8
src/components.d.ts (1)
6-6
: LGTM! Proper component type declarations added.The auto-generated TypeScript declarations correctly include the new components (
BuyTokens
,copy
,Dashboard
,KeyCreateDialog
) introduced in this PR, enabling proper type support throughout the Vue application.Also applies to: 18-18, 20-21, 29-29
messages/zh-cn.json (1)
123-125
: LGTM! Comprehensive Chinese localization for token features.The extensive additions provide proper Chinese translations for all token-related functionality including purchasing, management, history, and billing concepts. The translations appear contextually appropriate and follow consistent naming patterns.
Also applies to: 165-167, 285-285, 294-294, 302-302, 304-304, 309-311, 324-324, 363-365, 408-408, 424-424, 516-516, 532-532, 546-546, 551-551, 554-554, 557-563
supabase/functions/_backend/private/stripe_checkout.ts (4)
5-5
: LGTM! Proper import added for one-off checkout functionality.The import correctly adds the new
createCheckoutForOneOff
function needed for token purchases.
13-13
: LGTM! Interface properly extended for token purchases.The
PortalData
interface correctly adds:
'one_off'
option to thereccurence
type for one-time token purchaseshowMany: number
field to specify token quantityThese additions align with the new token purchasing functionality.
Also applies to: 16-16
53-53
: LGTM! Enhanced logging with context.The addition of
context: 'user'
to the logging call provides better categorization for debugging and monitoring purposes.
54-56
:✅ Verification successful
Verify the conditional logic and URL construction.
The conditional logic correctly routes to different checkout functions based on the presence of
howMany
. However, there are a few considerations:
- The success URL construction for one-off checkouts includes a query parameter
?thankYou=true
- Both paths use different default success URLs
Let me verify that the referenced URLs and query parameters are properly handled:
🏁 Script executed:
#!/bin/bash # Description: Check if the tokens page and thankYou parameter are properly handled in the frontend # Search for the tokens page route and thankYou parameter handling rg -A 5 -B 5 "thankYou" --type vue --type ts --type js rg -A 3 "/dashboard/settings/organization/tokens" --type vue --type ts --type jsLength of output: 191
🏁 Script executed:
#!/bin/bash # Re-verify thankYou parameter and tokens page route handling in the frontend # Search for any occurrences of "thankYou" across common frontend file types rg -g '*.vue' -g '*.ts' -g '*.js' "thankYou" -A5 -B5 # Search for the tokens page route in the same set of files rg -g '*.vue' -g '*.ts' -g '*.js' "/dashboard/settings/organization/tokens" -A5 -B5Length of output: 10412
Conditional Logic & URL Handling Verified
The one-off checkout success URL (…/dashboard/settings/organization/tokens?thankYou=true
) is correctly surfaced in Tokens.vue viaroute.query.thankYou
, and the recurring checkout default (…/app/usage
) aligns with the existing usage page. No changes needed.src/components/BuyTokens.vue (3)
6-11
: LGTM! Well-typed component props.The props are properly typed with TypeScript, covering all necessary data for the token purchase UI:
price
andamount
as numbers for pricing informationcustom
boolean for conditional renderingicon
string for dynamic icon rendering
13-15
: LGTM! Clean event definition.The component properly defines a single
click
event emission with correct TypeScript typing.
27-27
: LGTM! Dynamic icon rendering implemented correctly.The use of
component :is="props.icon"
properly enables dynamic icon rendering based on the passed prop, providing flexibility for different token types.messages/en.json (1)
123-126
: LGTM! Comprehensive localization support for token features.The new localization keys provide comprehensive coverage for the token purchase and management features. The key names are descriptive and follow existing naming conventions. All additions are new keys without modifying existing functionality.
Also applies to: 165-167, 287-287, 296-296, 304-304, 309-312, 324-325, 364-366, 409-409, 424-424, 516-516, 531-531, 546-546, 551-551, 554-554, 557-563
messages/fr.json (1)
123-126
: Excellent French localization for token features.The French translations are comprehensive and natural. The key structure matches the English version perfectly. The correction in the encryption-related key improves accuracy.
Also applies to: 165-167, 285-285, 294-294, 302-302, 309-312, 324-324, 363-365, 408-408, 424-424, 516-516, 532-532, 546-546, 551-551, 554-554, 557-563
messages/ru.json (1)
123-126
: Comprehensive Russian localization for token features.The Russian translations provide complete coverage for all token-related features with consistent key structure across all language files. The translations maintain appropriate technical terminology while remaining accessible.
Also applies to: 165-167, 285-285, 294-294, 302-302, 309-312, 324-324, 363-365, 408-408, 424-424, 516-516, 532-532, 546-546, 551-551, 554-554, 557-563
messages/ko.json (1)
123-563
: Comprehensive Korean localization for token features looks good.The extensive addition of Korean localization keys for the new token management system is well-structured and appears to cover all necessary UI elements including:
- Token purchasing and management
- Usage history and explanations
- MAU token functionality
- Billing and limit increase messaging
The key naming follows consistent patterns with the existing localization structure.
src/types/supabase.types.ts (3)
288-322
:capgo_tokens_history
table definition looks well-structured.The table definition includes:
- Proper primary key (
id
)- Foreign key relationship to
orgs
table viaorg_id
- Appropriate fields for tracking token transactions (
sum
,reason
)- Standard timestamp fields with proper defaults
- Complete Row/Insert/Update type definitions
The schema design follows established patterns in the codebase.
323-352
:capgo_tokens_steps
table definition is properly implemented.The table effectively models tiered pricing steps with:
- Price tier boundaries (
step_min
,step_max
)- Stripe integration via
price_id
- Unit pricing information (
price_per_unit
)- Complete type definitions for all operations
The lack of foreign key relationships is appropriate as this appears to be a reference/configuration table.
1611-1622
:get_tokens_history
function definition is correctly typed.The function signature properly:
- Accepts organization ID parameter
- Returns structured history data with running totals
- Includes all necessary fields for token transaction tracking
- Follows the established function definition pattern
The return type structure aligns well with typical usage history requirements.
messages/pl.json (1)
123-563
: LGTM! Polish translations added for token features.The localization entries properly support the new token purchase and management functionality.
supabase/functions/_backend/utils/supabase.types.ts (1)
288-352
: LGTM! Well-structured database schema for token management.The new tables and function properly support token purchase tracking and tiered pricing.
Also applies to: 1611-1622
messages/ja.json (6)
285-285
: Approve generic UI term additions
These single-term keys (for
,gb
,history
,how-many-tokens
,or
,see-your
,show-full-history
,sum
,thank-you-for-money
,this-will-allow-you-to
,total-tokens
) are straightforward UI strings that align with other locales. No issues detected.Also applies to: 294-294, 302-302, 304-304, 425-425, 516-516, 532-532, 546-546, 552-552, 555-555, 563-563
309-311
: Approve limit-increase description keys
The keys describing limit increases (increase-bandwidth-limit-by
,increase-mau-limit-by
,increase-storage-limit-by
,it-will-increase-your-mau-by
) are correctly added and follow naming conventions. Looks good.Also applies to: 324-324
363-364
: Approve token management page UI keys
The keysmanage-your
,manage-your-tokens-here
, andno-tokens-history
support the management overview UI. They are well-named and consistent.Also applies to: 408-408
557-558
: Approve token explanation and history summary keys
The keystokens-cost
,tokens-explanation-mau
,tokens-history
, andtokens-key-mau
provide necessary information for the user. They align with the frontend UI needs.Also applies to: 560-561
165-167
:✅ Verification successful
Consistent token management keys across locales
The keyscapgo-tokens
,capgo-tokens-mau
, andcapgo-tokens-mau-history
support the token management page. Confirm they exist in every localization to maintain feature parity.
🏁 Script executed:
rg -L '"capgo-tokens"' messages/*.jsonLength of output: 982
🏁 Script executed:
#!/bin/bash echo "Missing capgo-tokens-mau:" rg -L '"capgo-tokens-mau"' messages/*.json echo "" echo "Missing capgo-tokens-mau-history:" rg -L '"capgo-tokens-mau-history"' messages/*.jsonLength of output: 2437
All locales include token management keys
Verified thatcapgo-tokens
,capgo-tokens-mau
, andcapgo-tokens-mau-history
are present in everymessages/*.json
file—no missing entries found.
123-125
:✅ Verification successful
Validate purchase-related localization keys
Ensure that the new keysbuy
,buy-any-amount
, andbuy-tokens
are present and consistent across all locale JSON files to prevent missing translations in the token purchase UI.
🏁 Script executed:
#!/bin/bash # Verify presence of purchase keys in all locale files rg -L '"buy"' messages/*.jsonLength of output: 753
🏁 Script executed:
#!/bin/bash # Verify presence of all purchase-related localization keys in every locale file for key in buy buy-any-amount buy-tokens; do echo "=== Checking key: \"$key\" ===" missing=$(rg -L "\"$key\"" messages/*.json) if [ -n "$missing" ]; then echo "❌ Key \"$key\" is missing in:" echo "$missing" else echo "✔️ Key \"$key\" found in all files." fi echo doneLength of output: 9818
🏁 Script executed:
#!/bin/bash # Verify presence of purchase-related localization keys in all locale files using grep for key in buy buy-any-amount buy-tokens; do echo "=== Checking key: \"$key\" ===" missing=$(grep -L "\"$key\"" messages/*.json) if [ -n "$missing" ]; then echo "❌ Missing \"$key\" in:" echo "$missing" else echo "✔️ \"$key\" present in all locale files." fi echo doneLength of output: 1562
Purchase-related localization keys verified
All three keys—buy
,buy-any-amount
, andbuy-tokens
—have been confirmed in every locale JSON file. No missing translations detected.messages/tr.json (1)
123-167
: LGTM: Turkish localization additions look consistent and complete.The Turkish translations for the new token system features are well-structured and follow the existing localization patterns. The translations appear grammatically correct and comprehensive for the token purchasing, management, and history features.
Also applies to: 285-285, 302-302, 304-304, 309-311, 324-324, 363-365, 408-408, 424-424, 516-516, 532-532, 546-546, 551-551, 554-554, 557-564
messages/es.json (1)
123-167
: LGTM: Spanish localization additions are consistent and comprehensive.The Spanish translations properly support the new token system features with grammatically correct translations that follow the existing localization patterns.
Also applies to: 285-285, 302-302, 304-304, 309-311, 324-324, 363-365, 408-408, 424-424, 516-516, 532-532, 546-546, 551-551, 554-554, 557-564
messages/pt-br.json (9)
123-125
: Purchase-related localization keys look good
The new"buy"
,"buy-any-amount"
, and"buy-tokens"
entries have accurate Portuguese translations that match the UI tone.
165-167
: Capgo tokens labels are correctly added
The"capgo-tokens"
,"capgo-tokens-mau"
, and"capgo-tokens-mau-history"
keys have clear and consistent Portuguese labels.
285-285
: General UI term additions – verify consistency
Multiple common UI terms ("for"
,"gb"
,"history"
,"how-many-tokens"
,"or"
,"see-your"
,"show-full-history"
,"sum"
) were added. Please double-check they match other language files and styling guidelines.Also applies to: 294-294, 303-304, 424-424, 516-516, 532-532, 547-547
309-311
: Limit-increase message translations are accurate
The"increase-bandwidth-limit-by"
,"increase-mau-limit-by"
, and"increase-storage-limit-by"
keys have clear, imperative Portuguese translations.
363-364
: Management section translations – looks good
The"manage-your"
and"manage-your-tokens-here"
keys are translated consistently.
408-408
: Empty history message is clear
The"no-tokens-history"
translation communicates the empty-state appropriately.
551-551
: Thank-you message translated correctly
The"thank-you-for-money"
key has a polite and direct Portuguese equivalent.
555-555
: Action prompt translation is fine
The"this-will-allow-you-to"
message is clear and invites the user correctly.
557-561
: Tokens overview and explanation keys
Most of the token-related strings ("tokens-cost"
,"tokens-explanation-mau"
,"tokens-explanation-message-mau"
,"tokens-history"
,"total-tokens"
) are well translated.
However, the"tokens-key-mau": "RUIM"
translation appears incorrect—“ruim” means “bad” in Portuguese. Please verify and correct this label to match the intended MAU token key.Also applies to: 563-563
messages/vi.json (9)
123-125
: Purchase UI keys in Vietnamese are accurate
The"buy"
,"buy-any-amount"
, and"buy-tokens"
translations clearly convey the purchase actions.
165-167
: Capgo token labels added correctly
The"capgo-tokens"
,"capgo-tokens-mau"
, and"capgo-tokens-mau-history"
keys have clear Vietnamese equivalents.
285-285
: Common UI term additions – please verify
Several general UI terms ("for"
,"gb"
,"history"
,"how-many-tokens"
,"or"
,"see-your"
,"show-full-history"
,"sum"
) were introduced. Ensure they align with other languages and the UI style guide.Also applies to: 294-294, 302-302, 304-304, 424-424, 516-516, 532-532, 546-546
309-311
: Limit-increase keys are well translated
The"increase-bandwidth-limit-by"
,"increase-mau-limit-by"
, and"increase-storage-limit-by"
strings accurately reflect the intended action.
363-364
: Management prompts in Vietnamese are clear
The"manage-your"
and"manage-your-tokens-here"
entries read naturally.
408-408
: Empty-state history message is fine
The"no-tokens-history"
translation properly indicates an empty history.
551-551
: Thank-you message translation looks good
The"thank-you-for-money"
entry thanks the user appropriately in Vietnamese.
554-554
: Feature introduction prompt
The"this-will-allow-you-to"
translation is clear and inviting.
557-561
: Tokens explanation and cost keys
The batch of new token-related strings ("tokens-cost"
,"tokens-explanation-mau"
,"tokens-explanation-message-mau"
,"tokens-history"
,"total-tokens"
) are generally well translated.
One anomaly:"tokens-key-mau": "MÀU"
currently reads “color” in Vietnamese. Please verify that this key matches the intended MAU token abbreviation.Also applies to: 563-563
supabase/migrations/20250131091941_capgo_tokens.sql (5)
11-27
: Indexes and comments forcapgo_tokens_history
look solid
Great use of descriptive comments and individual indexes onorg_id
andcreated_at
for performance.
94-119
: Pricing tier comments, trigger, index, and read policy are correct
The strategy to enable RLS, grant public SELECT, and document the table is well structured.
121-150
:get_extra_mau_for_org
logic – confirm handling of negative entries
The function sums absolute values for'MAU limit increased'
. If refunds or negative adjustments exist, ABS may misrepresent the net. Please verify this behavior matches business requirements.
157-182
:get_total_mau_tokens
mirrors extra MAU logic
This one-year lookback is consistent. Same note applies regarding the use ofABS
on negative adjustments—confirm expected.
184-189
: Privilege grants for service_role are correct
Revoking public/anonymous access and granting only toservice_role
follows best security practices.
<button @click="$emit('click')" class="bg-[#f8b324] text-white p-3 rounded-full aspect-square transform transition-transform hover:scale-120"> | ||
<CalculatorIcon v-if="custom" class="w-6 h-6 text-black" /> | ||
<ShoppingCartIcon v-else class="w-6 h-6 text-black" /> | ||
</button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider improving button accessibility and styling.
The button implementation has good interactivity but could benefit from accessibility improvements:
- Missing accessibility attributes: No ARIA labels or descriptions
- Color contrast: Ensure sufficient contrast for accessibility
- Focus management: Consider focus indicators
Apply these accessibility improvements:
- <button @click="$emit('click')" class="bg-[#f8b324] text-white p-3 rounded-full aspect-square transform transition-transform hover:scale-120">
+ <button
+ @click="$emit('click')"
+ :aria-label="custom ? t('open-calculator') : t('buy-tokens')"
+ class="bg-[#f8b324] text-white p-3 rounded-full aspect-square transform transition-transform hover:scale-120 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#f8b324]"
+ >
Also consider using CSS custom properties instead of hardcoded colors for better maintainability.
📝 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 @click="$emit('click')" class="bg-[#f8b324] text-white p-3 rounded-full aspect-square transform transition-transform hover:scale-120"> | |
<CalculatorIcon v-if="custom" class="w-6 h-6 text-black" /> | |
<ShoppingCartIcon v-else class="w-6 h-6 text-black" /> | |
</button> | |
<button | |
@click="$emit('click')" | |
:aria-label="custom ? t('open-calculator') : t('buy-tokens')" | |
class="bg-[#f8b324] text-white p-3 rounded-full aspect-square transform transition-transform hover:scale-120 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#f8b324]" | |
> | |
<CalculatorIcon v-if="custom" class="w-6 h-6 text-black" /> | |
<ShoppingCartIcon v-else class="w-6 h-6 text-black" /> | |
</button> |
🤖 Prompt for AI Agents
In src/components/BuyTokens.vue around lines 37 to 40, the button lacks
accessibility features such as ARIA labels, sufficient color contrast, and
visible focus indicators. Add an appropriate aria-label attribute to the button
to describe its action, replace the hardcoded background color with a CSS custom
property for maintainability, ensure the text and background colors meet
contrast standards, and add a visible focus style (like an outline or
box-shadow) to improve keyboard navigation visibility.
src/services/stripe.ts
Outdated
export async function openCheckoutForOneOff(priceId: string, successUrl: string, cancelUrl: string, orgId: string, howMany: number) { | ||
// console.log('openCheckout') | ||
const supabase = useSupabase() | ||
const session = await supabase.auth.getSession() | ||
if (!session) | ||
return | ||
try { | ||
const resp = await supabase.functions.invoke('private/stripe_checkout', { | ||
body: JSON.stringify({ | ||
priceId, | ||
successUrl, | ||
cancelUrl, | ||
reccurence: 'one_off', | ||
orgId, | ||
howMany, | ||
}), | ||
}) | ||
if (!resp.error && resp.data && resp.data.url) | ||
openBlank(resp.data.url) | ||
} | ||
catch (error) { | ||
console.error(error) | ||
toast.error('Cannot get your checkout') | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider refactoring to reduce code duplication.
The openCheckoutForOneOff
function is nearly identical to the existing openCheckout
function, creating significant code duplication. Consider refactoring both functions to use a shared implementation.
Here's a suggested refactor to eliminate duplication:
+interface CheckoutOptions {
+ priceId: string
+ successUrl: string
+ cancelUrl: string
+ orgId: string
+ recurrence: 'year' | 'month' | 'one_off'
+ howMany?: number
+}
+
+async function createCheckoutSession(options: CheckoutOptions) {
+ const supabase = useSupabase()
+ const session = await supabase.auth.getSession()
+ if (!session)
+ return
+
+ try {
+ const payload: any = {
+ priceId: options.priceId,
+ successUrl: options.successUrl,
+ cancelUrl: options.cancelUrl,
+ reccurence: options.recurrence,
+ orgId: options.orgId,
+ }
+
+ if (options.howMany !== undefined) {
+ payload.howMany = options.howMany
+ }
+
+ const resp = await supabase.functions.invoke('private/stripe_checkout', {
+ body: JSON.stringify(payload),
+ })
+ if (!resp.error && resp.data && resp.data.url)
+ openBlank(resp.data.url)
+ }
+ catch (error) {
+ console.error(error)
+ toast.error('Cannot get your checkout')
+ }
+}
export async function openCheckout(priceId: string, successUrl: string, cancelUrl: string, isYear: boolean, orgId: string) {
- const supabase = useSupabase()
- const session = await supabase.auth.getSession()
- if (!session)
- return
- try {
- const resp = await supabase.functions.invoke('private/stripe_checkout', {
- body: JSON.stringify({
- priceId,
- successUrl,
- cancelUrl,
- reccurence: isYear ? 'year' : 'month',
- orgId,
- }),
- })
- if (!resp.error && resp.data && resp.data.url)
- openBlank(resp.data.url)
- }
- catch (error) {
- console.error(error)
- toast.error('Cannot get your checkout')
- }
+ return createCheckoutSession({
+ priceId,
+ successUrl,
+ cancelUrl,
+ orgId,
+ recurrence: isYear ? 'year' : 'month'
+ })
}
export async function openCheckoutForOneOff(priceId: string, successUrl: string, cancelUrl: string, orgId: string, howMany: number) {
- // console.log('openCheckout')
- const supabase = useSupabase()
- const session = await supabase.auth.getSession()
- if (!session)
- return
- try {
- const resp = await supabase.functions.invoke('private/stripe_checkout', {
- body: JSON.stringify({
- priceId,
- successUrl,
- cancelUrl,
- reccurence: 'one_off',
- orgId,
- howMany,
- }),
- })
- if (!resp.error && resp.data && resp.data.url)
- openBlank(resp.data.url)
- }
- catch (error) {
- console.error(error)
- toast.error('Cannot get your checkout')
- }
+ return createCheckoutSession({
+ priceId,
+ successUrl,
+ cancelUrl,
+ orgId,
+ recurrence: 'one_off',
+ howMany
+ })
}
🤖 Prompt for AI Agents
In src/services/stripe.ts between lines 104 and 128, the openCheckoutForOneOff
function duplicates much of the logic found in the openCheckout function. To fix
this, create a shared helper function that accepts parameters for priceId,
successUrl, cancelUrl, orgId, howMany, and recurrence type. Then refactor both
openCheckoutForOneOff and openCheckout to call this helper with the appropriate
recurrence value, eliminating duplicated code and centralizing the checkout
invocation logic.
.single() | ||
|
||
if (!org || orgError) { | ||
console.log({ requestId: c.get('requestId'), context: 'no org found for payment intent', paymentIntent }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use consistent logging with cloudlog
instead of console.log
.
For consistency with the rest of the codebase and proper request tracking, replace console.log
with cloudlog
.
- console.log({ requestId: c.get('requestId'), context: 'no org found for payment intent', paymentIntent })
+ cloudlog({ requestId: c.get('requestId'), context: 'no org found for payment intent', paymentIntent })
- console.log({ requestId: c.get('requestId'), context: 'no howMany found for payment intent', paymentIntent })
+ cloudlog({ requestId: c.get('requestId'), context: 'no howMany found for payment intent', paymentIntent })
- console.log({ requestId: c.get('requestId'), context: 'error inserting capgo_tokens_history', dbError })
+ cloudlog({ requestId: c.get('requestId'), context: 'error inserting capgo_tokens_history', dbError })
Also applies to: 50-50, 64-64
🤖 Prompt for AI Agents
In supabase/functions/_backend/triggers/stripe_event.ts at lines 43, 50, and 64,
replace all instances of console.log with cloudlog to maintain consistent
logging practices across the codebase and ensure proper request tracking. Update
the logging calls to use cloudlog with the same parameters currently passed to
console.log.
const howMany = paymentIntent.metadata?.howMany | ||
const parsedHowMany = Number.parseInt(howMany ?? '0') | ||
if (!howMany || Number.isNaN(parsedHowMany)) { | ||
console.log({ requestId: c.get('requestId'), context: 'no howMany found for payment intent', paymentIntent }) | ||
return c.json({ received: false }) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add validation for positive token quantity.
The current validation only checks if howMany
is a valid number, but doesn't ensure it's positive.
const howMany = paymentIntent.metadata?.howMany
const parsedHowMany = Number.parseInt(howMany ?? '0')
- if (!howMany || Number.isNaN(parsedHowMany)) {
+ if (!howMany || Number.isNaN(parsedHowMany) || parsedHowMany <= 0) {
- console.log({ requestId: c.get('requestId'), context: 'no howMany found for payment intent', paymentIntent })
+ cloudlog({ requestId: c.get('requestId'), context: 'invalid howMany for payment intent', paymentIntent, howMany, parsedHowMany })
return c.json({ received: false })
}
📝 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 howMany = paymentIntent.metadata?.howMany | |
const parsedHowMany = Number.parseInt(howMany ?? '0') | |
if (!howMany || Number.isNaN(parsedHowMany)) { | |
console.log({ requestId: c.get('requestId'), context: 'no howMany found for payment intent', paymentIntent }) | |
return c.json({ received: false }) | |
} | |
const howMany = paymentIntent.metadata?.howMany | |
const parsedHowMany = Number.parseInt(howMany ?? '0') | |
if (!howMany || Number.isNaN(parsedHowMany) || parsedHowMany <= 0) { | |
cloudlog({ | |
requestId: c.get('requestId'), | |
context: 'invalid howMany for payment intent', | |
paymentIntent, | |
howMany, | |
parsedHowMany | |
}) | |
return c.json({ received: false }) | |
} |
🤖 Prompt for AI Agents
In supabase/functions/_backend/triggers/stripe_event.ts around lines 47 to 53,
the validation for the `howMany` value only checks if it is a valid number but
does not verify that it is positive. Update the condition to also check that
`parsedHowMany` is greater than zero. If `parsedHowMany` is zero or negative,
log the appropriate message and return the JSON response indicating the request
was not received.
<h2 class="text-base 2xl:text-lg font-semibold text-center"> | ||
{{ t('how-many-tokens') }} | ||
</h2> | ||
<input v-model="tokensToBuy" type="number" min="1" step="1" class="mt-2 bg-gray-700 rounded-full text-center text-white" @input="tokensToBuy = Math.max(1, Math.floor(Math.abs(tokensToBuy)))"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add input validation and accessibility attributes.
The number input for token amount needs better validation and accessibility support.
Apply these improvements:
-<input v-model="tokensToBuy" type="number" min="1" step="1" class="mt-2 bg-gray-700 rounded-full text-center text-white" @input="tokensToBuy = Math.max(1, Math.floor(Math.abs(tokensToBuy)))">
+<input
+ v-model="tokensToBuy"
+ type="number"
+ min="1"
+ step="1"
+ :aria-label="t('how-many-tokens')"
+ :placeholder="t('how-many-tokens')"
+ class="mt-2 bg-gray-700 rounded-full text-center text-white focus:ring-2 focus:ring-blue-500 focus:outline-none"
+ @input="tokensToBuy = Math.max(1, Math.floor(Math.abs(tokensToBuy || 0)))"
+>
📝 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.
<input v-model="tokensToBuy" type="number" min="1" step="1" class="mt-2 bg-gray-700 rounded-full text-center text-white" @input="tokensToBuy = Math.max(1, Math.floor(Math.abs(tokensToBuy)))"> | |
<input | |
v-model="tokensToBuy" | |
type="number" | |
min="1" | |
step="1" | |
:aria-label="t('how-many-tokens')" | |
:placeholder="t('how-many-tokens')" | |
class="mt-2 bg-gray-700 rounded-full text-center text-white focus:ring-2 focus:ring-blue-500 focus:outline-none" | |
@input="tokensToBuy = Math.max(1, Math.floor(Math.abs(tokensToBuy || 0)))" | |
> |
🤖 Prompt for AI Agents
In src/pages/settings/organization/Tokens.vue at line 357, the number input for
tokensToBuy lacks proper validation and accessibility attributes. Enhance
validation by ensuring the input value is always a positive integer using
Math.max, Math.floor, and Math.abs as currently done, but also add attributes
like aria-label or aria-describedby for accessibility. Additionally, consider
adding input validation feedback or constraints such as required and pattern if
applicable to improve user experience and accessibility compliance.
if (tokensHistoryValue.error) { | ||
console.error('tokensHistory.error', tokensHistoryValue.error) | ||
return | ||
} | ||
if (tokensStepsValue.error) { | ||
console.error('tokensSteps.error', tokensStepsValue.error) | ||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve error handling for better user experience.
The current error handling only logs to console, which doesn't provide feedback to users when data loading fails.
Consider showing user-friendly error messages:
if (tokensHistoryValue.error) {
- console.error('tokensHistory.error', tokensHistoryValue.error)
+ console.error('tokensHistory.error', tokensHistoryValue.error)
+ displayStore.dialogOption = {
+ header: t('error'),
+ message: t('cannot-load-tokens-history'),
+ buttons: [{ text: t('ok'), role: 'cancel' }]
+ }
+ displayStore.showDialog = true
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.
if (tokensHistoryValue.error) { | |
console.error('tokensHistory.error', tokensHistoryValue.error) | |
return | |
} | |
if (tokensStepsValue.error) { | |
console.error('tokensSteps.error', tokensStepsValue.error) | |
return | |
} | |
if (tokensHistoryValue.error) { | |
console.error('tokensHistory.error', tokensHistoryValue.error) | |
displayStore.dialogOption = { | |
header: t('error'), | |
message: t('cannot-load-tokens-history'), | |
buttons: [{ text: t('ok'), role: 'cancel' }] | |
} | |
displayStore.showDialog = true | |
return | |
} | |
if (tokensStepsValue.error) { | |
console.error('tokensSteps.error', tokensStepsValue.error) | |
return | |
} |
🤖 Prompt for AI Agents
In src/pages/settings/organization/Tokens.vue around lines 150 to 157, the error
handling only logs errors to the console without informing the user. Update the
code to display user-friendly error messages in the UI when
tokensHistoryValue.error or tokensStepsValue.error occurs, such as using a
notification component or rendering an error message element, so users are aware
of the data loading failure.
}) | ||
|
||
// Iterate through each day | ||
for (let d = new Date(oneYearAgo); d <= today; d.setDate(d.getDate() + 1)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix ESLint loop condition errors.
The ESLint errors indicate issues with the loop condition variables. The loop variable d
is being modified correctly with d.setDate(d.getDate() + 1)
, but the condition checking against today
could be problematic since today
is derived from oneYearAgo
.
Apply this fix to address the loop condition issues:
- const today = new Date(+oneYearAgo)
- oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1)
-
- const data = []
- let lastToken = 0
-
- const tokensChanges = tokensHistory.value.map((token) => {
- return {
- date: new Date(token.created_at),
- change: token.sum,
- }
- })
-
- // Iterate through each day
- for (let d = new Date(oneYearAgo); d <= today; d.setDate(d.getDate() + 1)) {
+ const oneYearAgoDate = new Date()
+ oneYearAgoDate.setFullYear(oneYearAgoDate.getFullYear() - 1)
+ const today = new Date()
+
+ const data = []
+ let lastToken = 0
+
+ const tokensChanges = tokensHistory.value.map((token) => {
+ return {
+ date: new Date(token.created_at),
+ change: token.sum,
+ }
+ })
+
+ // Iterate through each day
+ for (const d = new Date(oneYearAgoDate); d <= today; d.setDate(d.getDate() + 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.
for (let d = new Date(oneYearAgo); d <= today; d.setDate(d.getDate() + 1)) { | |
const oneYearAgoDate = new Date() | |
oneYearAgoDate.setFullYear(oneYearAgoDate.getFullYear() - 1) | |
const today = new Date() | |
const data = [] | |
let lastToken = 0 | |
const tokensChanges = tokensHistory.value.map((token) => { | |
return { | |
date: new Date(token.created_at), | |
change: token.sum, | |
} | |
}) | |
// Iterate through each day | |
for (const d = new Date(oneYearAgoDate); d <= today; d.setDate(d.getDate() + 1)) { | |
// ... existing loop body ... | |
} |
🧰 Tools
🪛 GitHub Actions: autofix.ci
[error] 101-101: ESLint: 'd' is not modified in this loop (no-unmodified-loop-condition)
[error] 101-101: ESLint: 'today' is not modified in this loop (no-unmodified-loop-condition)
🤖 Prompt for AI Agents
In src/pages/settings/organization/Tokens.vue at line 101, the for-loop
condition uses a Date object 'd' compared to 'today', which may cause ESLint
loop condition errors due to object reference comparison. To fix this, convert
both 'd' and 'today' to their numeric time values using getTime() in the loop
condition, ensuring a proper numeric comparison. This will resolve the ESLint
errors related to the loop condition.
-- Create trigger for updating updated_at column | ||
CREATE TRIGGER handle_updated_at | ||
BEFORE UPDATE ON capgo_tokens_history | ||
FOR EACH ROW | ||
EXECUTE FUNCTION extensions.moddatetime('updated_at'); | ||
|
||
ALTER TABLE capgo_tokens_history ENABLE ROW LEVEL SECURITY; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RLS enabled without policies on history table
You’ve enabled row-level security on capgo_tokens_history
but haven’t defined any SELECT/INSERT policies, which will block all access. Please add appropriate RLS policies for service roles or authenticated functions.
🤖 Prompt for AI Agents
In supabase/migrations/20250131091941_capgo_tokens.sql around lines 28 to 35,
row-level security (RLS) is enabled on the capgo_tokens_history table without
defining any RLS policies, which will block all access. To fix this, add
appropriate RLS policies that allow SELECT and INSERT operations for the service
roles or authenticated users as needed, ensuring the intended access is granted
while maintaining security.
-- Create the capgo_tokens_steps table | ||
CREATE TABLE IF NOT EXISTS capgo_tokens_steps ( | ||
id BIGSERIAL PRIMARY KEY, | ||
step_min bigint NOT NULL, | ||
step_max bigint NOT NULL, | ||
price_per_unit FLOAT NOT NULL, | ||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | ||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | ||
CONSTRAINT step_range_check CHECK (step_min < step_max) | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
capgo_tokens_steps
table design – reconsider data type
Defining price_per_unit
as FLOAT
can lead to rounding issues. It’s safer to use NUMERIC(10,4)
or DECIMAL
for monetary values.
🤖 Prompt for AI Agents
In supabase/migrations/20250131091941_capgo_tokens.sql around lines 83 to 92,
the column price_per_unit is defined as FLOAT, which can cause rounding errors
for monetary values. Change the data type of price_per_unit to NUMERIC(10,4) or
DECIMAL to ensure precise storage of monetary amounts and avoid floating-point
inaccuracies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🔭 Outside diff range comments (1)
supabase/functions/_backend/utils/stripe.ts (1)
247-275
:⚠️ Potential issueFix undefined variable error in createCheckout function.
The function uses
howMany
at line 275 but it's not defined as a parameter.Add the missing parameter to the function signature:
-export async function createCheckout(c: Context, customerId: string, reccurence: string, planId: string, successUrl: string, cancelUrl: string, clientReferenceId?: string) { +export async function createCheckout(c: Context, customerId: string, reccurence: string, planId: string, successUrl: string, cancelUrl: string, clientReferenceId?: string, howMany?: number) {
♻️ Duplicate comments (6)
supabase/functions/_backend/utils/stripe.ts (3)
291-295
:⚠️ Potential issueAdd validation for empty pricing steps.
The while loop could run indefinitely if
tokensSteps
is empty or malformed.const { data: tokensSteps, error } = await supabaseAdmin(c).from('capgo_tokens_steps').select('*').order('step_min', { ascending: true }) if (error) return { url: '' } + + if (!tokensSteps || tokensSteps.length === 0) { + cloudlog({ requestId: c.get('requestId'), context: 'createCheckoutForOneOff', error: 'No pricing steps configured' }) + return { url: '' } + }
298-329
:⚠️ Potential issueAdd bounds checking for array access in pricing loop.
The loop increments
i
without checking if it exceeds the array bounds.while (true) { + if (i >= tokensSteps.length) { + cloudlog({ requestId: c.get('requestId'), context: 'createCheckoutForOneOff', error: 'Pricing steps exhausted', howMany, stepsCount: tokensSteps.length }) + return { url: '' } + } const step = tokensSteps[i]🧰 Tools
🪛 Biome (1.9.4)
[error] 302-302: This number literal will lose precision at runtime.
The value at runtime will be 9223372036854776000
(lint/correctness/noPrecisionLoss)
302-302
:⚠️ Potential issueUse BigInt or constant for large number literal to avoid precision loss.
The number
9223372036854775807
exceeds JavaScript's safe integer range and will lose precision.Define a constant to represent "unlimited":
const UNLIMITED_TOKENS = 9223372036854775807n // Use BigInt // Then in the comparison: step.step_max !== Number(UNLIMITED_TOKENS)Or use a more practical maximum:
- step.step_max !== 9223372036854775807 + step.step_max !== Number.MAX_SAFE_INTEGER🧰 Tools
🪛 Biome (1.9.4)
[error] 302-302: This number literal will lose precision at runtime.
The value at runtime will be 9223372036854776000
(lint/correctness/noPrecisionLoss)
src/pages/settings/organization/Tokens.vue (3)
233-242
: Strengthen Stripe checkout parameter validation and error handlingBoth the client‐side helper and the serverless function need tighter guards to prevent invalid or malicious requests.
Run the following script to verify the current implementation of openCheckoutForOneOff:
#!/bin/bash # Description: Check the implementation of openCheckoutForOneOff and related server-side validation # Check the openCheckoutForOneOff implementation echo "=== Checking openCheckoutForOneOff implementation ===" rg -A 20 "function openCheckoutForOneOff" src/services/stripe.ts # Check if the stripe_checkout serverless function validates parameters echo -e "\n=== Checking stripe_checkout parameter validation ===" rg -A 30 "stripe_checkout" supabase/functions/_backend/private/
158-165
: 🛠️ Refactor suggestionImprove error handling for better user experience.
The current error handling only logs to console, which doesn't provide feedback to users when data loading fails.
Consider showing user-friendly error messages:
if (tokensHistoryValue.error) { - console.error('tokensHistory.error', tokensHistoryValue.error) + console.error('tokensHistory.error', tokensHistoryValue.error) + displayStore.dialogOption = { + header: t('error'), + message: t('cannot-load-tokens-history'), + buttons: [{ text: t('ok'), role: 'cancel' }] + } + displayStore.showDialog = true return }
389-389
: 🛠️ Refactor suggestionAdd input validation and accessibility attributes.
The number input for token amount needs better validation and accessibility support.
Apply these improvements:
-<input v-model="tokensToBuy" type="number" min="1" step="1" class="mt-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-full text-center text-gray-900 dark:text-white" @input="tokensToBuy = Math.max(1, Math.floor(Math.abs(tokensToBuy)))"> +<input + v-model="tokensToBuy" + type="number" + min="1" + step="1" + :aria-label="t('how-many-tokens')" + :placeholder="t('enter-token-amount')" + class="mt-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-full text-center text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:outline-none" + @input="tokensToBuy = Math.max(1, Math.floor(Math.abs(tokensToBuy || 0)))" +>
🧹 Nitpick comments (2)
supabase/functions/_backend/utils/stripe.ts (1)
284-286
: Fix typo in function name.The function name has a spelling error.
-function toMilion(price: number) { - return `${price / 1000000}M` -} +function toMillion(price: number) { + return `${price / 1000000}M` +}Also update the function calls at lines 303-305:
- ? `Price per token: ${step.price_per_unit} between ${toMilion(step.step_min)} and ${toMilion(step.step_max)}` - : `Price per token: ${step.price_per_unit} after ${toMilion(step.step_min)}` - : `Price per token: ${step.price_per_unit} up to ${toMilion(step.step_max)}` + ? `Price per token: ${step.price_per_unit} between ${toMillion(step.step_min)} and ${toMillion(step.step_max)}` + : `Price per token: ${step.price_per_unit} after ${toMillion(step.step_min)}` + : `Price per token: ${step.price_per_unit} up to ${toMillion(step.step_max)}`And at line 309:
- toMilion(howManyInStep) : + toMillion(howManyInStep) :src/pages/settings/organization/Tokens.vue (1)
1-511
: Consider decomposing this large component for better maintainability.This 500+ line component handles many responsibilities. Consider splitting into:
- A composable for token data fetching and calculations
- Separate components for history display, purchase UI, and charts
- Extract price calculation logic into a utility module
Example structure:
// composables/useTokens.ts export function useTokens() { const tokensHistory = ref<...>([]) const tokensSteps = ref<...>([]) async function loadData() { ... } function computePrice(howMany: number) { ... } function computeTokens(euros: number) { ... } return { tokensHistory, tokensSteps, loadData, computePrice, computeTokens } } // components/TokensChart.vue // components/TokensHistory.vue // components/TokensPurchase.vue
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
src/assets/storage.svg
is excluded by!**/*.svg
📒 Files selected for processing (21)
messages/de.json
(11 hunks)messages/en.json
(16 hunks)messages/es.json
(11 hunks)messages/fr.json
(11 hunks)messages/id.json
(11 hunks)messages/it.json
(11 hunks)messages/ja.json
(11 hunks)messages/ko.json
(11 hunks)messages/pl.json
(11 hunks)messages/pt-br.json
(11 hunks)messages/ru.json
(11 hunks)messages/tr.json
(11 hunks)messages/vi.json
(11 hunks)messages/zh-cn.json
(11 hunks)src/components.d.ts
(1 hunks)src/components/BlurBg.vue
(2 hunks)src/components/BuyTokens.vue
(1 hunks)src/layouts/settings.vue
(2 hunks)src/pages/settings/organization/Tokens.vue
(1 hunks)src/typed-router.d.ts
(1 hunks)supabase/functions/_backend/utils/stripe.ts
(2 hunks)
✅ Files skipped from review due to trivial changes (2)
- src/typed-router.d.ts
- messages/it.json
🚧 Files skipped from review as they are similar to previous changes (16)
- src/layouts/settings.vue
- src/components.d.ts
- src/components/BuyTokens.vue
- messages/ko.json
- messages/pl.json
- messages/fr.json
- messages/es.json
- messages/de.json
- messages/en.json
- messages/zh-cn.json
- messages/ru.json
- messages/ja.json
- messages/id.json
- messages/tr.json
- messages/pt-br.json
- messages/vi.json
🧰 Additional context used
🪛 Biome (1.9.4)
supabase/functions/_backend/utils/stripe.ts
[error] 302-302: This number literal will lose precision at runtime.
The value at runtime will be 9223372036854776000
(lint/correctness/noPrecisionLoss)
🔇 Additional comments (3)
src/components/BlurBg.vue (1)
87-106
: Well-implemented CSS animations with proper Safari optimization!The animation implementation includes good performance optimizations:
will-change
for GPU accelerationbackface-visibility
to prevent flickering- Proper vendor prefixes for Safari compatibility
- Reasonable 8-second duration for subtle background effect
src/pages/settings/organization/Tokens.vue (2)
1-66
: Good implementation of core Vue 3 features!The component demonstrates several best practices:
- Proper TypeScript typing with Database types
- Clean use of Vue 3 composition API
- Comprehensive internationalization support
- Dark mode handling with
useDark()
- Well-structured imports and setup
94-109
:⚠️ Potential issueFix date initialization logic error.
The
today
variable is created fromoneYearAgo
beforeoneYearAgo
is modified, resulting in both variables having the same initial value.- const oneYearAgo = new Date() - const today = new Date(+oneYearAgo) - oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1) + const today = new Date() + const oneYearAgo = new Date() + oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1)To fix the ESLint warnings, use numeric comparison:
- for (let d = new Date(oneYearAgo); d <= today; d.setDate(d.getDate() + 1)) { + for (let d = new Date(oneYearAgo); d.getTime() <= today.getTime(); d.setDate(d.getDate() + 1)) {Likely an incorrect or invalid review comment.
// Extract colors from the gradient | ||
const colors = ['#44ff9a', '#44b0ff', '#8b44ff', '#ff6644', '#ebff70'] | ||
const positions = ['-0.55%', '22.86%', '48.36%', '73.33%', '99.34%'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Extract gradient colors dynamically to avoid maintenance issues.
The hardcoded colors
and positions
arrays won't update if the background
prop changes. This could lead to inconsistencies between the static and rotating gradients.
Extract colors dynamically from the gradient prop:
-// Extract colors from the gradient
-const colors = ['#44ff9a', '#44b0ff', '#8b44ff', '#ff6644', '#ebff70']
-const positions = ['-0.55%', '22.86%', '48.36%', '73.33%', '99.34%']
+// Extract colors from the gradient
+const gradientColors = computed(() => {
+ const match = props.background.match(/linear-gradient\([^,]+,(.+)\)/)
+ if (!match) return { colors: [], positions: [] }
+
+ const colorStops = match[1].split(',').map(s => s.trim())
+ const colors = []
+ const positions = []
+
+ colorStops.forEach(stop => {
+ const parts = stop.split(' ')
+ colors.push(parts[0])
+ positions.push(parts[1] || '')
+ })
+
+ return { colors, positions }
+})
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/components/BlurBg.vue around lines 22 to 24, the colors and positions
arrays are hardcoded, which can cause inconsistencies if the background prop
changes. To fix this, parse the background gradient string dynamically to
extract the color stops and their positions instead of using fixed arrays. This
ensures the colors and positions always reflect the current background prop
value.
function injectKeyframes() { | ||
// Remove existing keyframes if any | ||
const existingStyle = document.getElementById('dynamic-blur-keyframes') | ||
if (existingStyle) { | ||
existingStyle.remove() | ||
} | ||
|
||
// Create and inject new keyframes | ||
const style = document.createElement('style') | ||
style.id = 'dynamic-blur-keyframes' | ||
style.textContent = generateKeyframes() | ||
document.head.appendChild(style) | ||
} | ||
|
||
onMounted(() => { | ||
if (props.rotate) { | ||
injectKeyframes() | ||
} | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add cleanup on component unmount to prevent memory leaks.
The injected style element should be removed when the component unmounts to prevent accumulation of style elements.
Add cleanup logic:
+import { computed, onMounted, onUnmounted, ref } from 'vue'
onMounted(() => {
if (props.rotate) {
injectKeyframes()
}
})
+
+onUnmounted(() => {
+ if (props.rotate) {
+ const existingStyle = document.getElementById('dynamic-blur-keyframes')
+ if (existingStyle) {
+ existingStyle.remove()
+ }
+ }
+})
Also update the import statement at line 2:
-import { computed, onMounted, ref } from 'vue'
+import { computed, onMounted, onUnmounted, ref } from 'vue'
📝 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.
function injectKeyframes() { | |
// Remove existing keyframes if any | |
const existingStyle = document.getElementById('dynamic-blur-keyframes') | |
if (existingStyle) { | |
existingStyle.remove() | |
} | |
// Create and inject new keyframes | |
const style = document.createElement('style') | |
style.id = 'dynamic-blur-keyframes' | |
style.textContent = generateKeyframes() | |
document.head.appendChild(style) | |
} | |
onMounted(() => { | |
if (props.rotate) { | |
injectKeyframes() | |
} | |
}) | |
import { computed, onMounted, onUnmounted, ref } from 'vue' | |
function injectKeyframes() { | |
// Remove existing keyframes if any | |
const existingStyle = document.getElementById('dynamic-blur-keyframes') | |
if (existingStyle) { | |
existingStyle.remove() | |
} | |
// Create and inject new keyframes | |
const style = document.createElement('style') | |
style.id = 'dynamic-blur-keyframes' | |
style.textContent = generateKeyframes() | |
document.head.appendChild(style) | |
} | |
onMounted(() => { | |
if (props.rotate) { | |
injectKeyframes() | |
} | |
}) | |
onUnmounted(() => { | |
if (props.rotate) { | |
const existingStyle = document.getElementById('dynamic-blur-keyframes') | |
if (existingStyle) { | |
existingStyle.remove() | |
} | |
} | |
}) |
🤖 Prompt for AI Agents
In src/components/BlurBg.vue around lines 45 to 63, the injectKeyframes function
adds a style element to the document head but does not remove it on component
unmount, causing potential memory leaks. Add a cleanup function inside the
onUnmounted lifecycle hook to remove the injected style element with id
'dynamic-blur-keyframes'. Also, update the import statement at line 2 to include
onUnmounted from Vue to enable this cleanup.
function computePrice(howMany: number) { | ||
if (howMany > 0 && tokensSteps.value && tokensSteps.value.length > 0) { | ||
let i = 0 | ||
let price = 0 | ||
|
||
while (true) { | ||
const step = tokensSteps.value[i] | ||
price += step.price_per_unit * (Math.min(howMany, step.step_max) - Math.max(0, step.step_min)) | ||
if (howMany >= step.step_min && howMany <= step.step_max) { | ||
break | ||
} | ||
i++ | ||
} | ||
return price | ||
} | ||
else { | ||
return 0 | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add bounds checking in computePrice function.
The while loop could access out-of-bounds array elements.
function computePrice(howMany: number) {
if (howMany > 0 && tokensSteps.value && tokensSteps.value.length > 0) {
let i = 0
let price = 0
while (true) {
+ if (i >= tokensSteps.value.length) {
+ console.error('Pricing steps exhausted', { howMany, stepsCount: tokensSteps.value.length })
+ break
+ }
const step = tokensSteps.value[i]
price += step.price_per_unit * (Math.min(howMany, step.step_max) - Math.max(0, step.step_min))
if (howMany >= step.step_min && howMany <= step.step_max) {
break
}
i++
}
return price
}
else {
return 0
}
}
📝 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.
function computePrice(howMany: number) { | |
if (howMany > 0 && tokensSteps.value && tokensSteps.value.length > 0) { | |
let i = 0 | |
let price = 0 | |
while (true) { | |
const step = tokensSteps.value[i] | |
price += step.price_per_unit * (Math.min(howMany, step.step_max) - Math.max(0, step.step_min)) | |
if (howMany >= step.step_min && howMany <= step.step_max) { | |
break | |
} | |
i++ | |
} | |
return price | |
} | |
else { | |
return 0 | |
} | |
} | |
function computePrice(howMany: number) { | |
if (howMany > 0 && tokensSteps.value && tokensSteps.value.length > 0) { | |
let i = 0 | |
let price = 0 | |
while (true) { | |
if (i >= tokensSteps.value.length) { | |
console.error('Pricing steps exhausted', { howMany, stepsCount: tokensSteps.value.length }) | |
break | |
} | |
const step = tokensSteps.value[i] | |
price += | |
step.price_per_unit * | |
(Math.min(howMany, step.step_max) - Math.max(0, step.step_min)) | |
if (howMany >= step.step_min && howMany <= step.step_max) { | |
break | |
} | |
i++ | |
} | |
return price | |
} | |
else { | |
return 0 | |
} | |
} |
🤖 Prompt for AI Agents
In src/pages/settings/organization/Tokens.vue between lines 182 and 200, the
computePrice function's while loop can access tokensSteps.value out of bounds.
Add a condition to check that the index i is less than tokensSteps.value.length
before accessing tokensSteps.value[i] to prevent runtime errors. Modify the loop
condition or add a break when i reaches the array length.
async function buyTokens(howMany: number) { | ||
const orgId = organizationStore.currentOrganization!.gid | ||
if (!orgId) { | ||
return | ||
} | ||
if (howMany < 1) { | ||
return | ||
} | ||
openCheckoutForOneOff('', 'https://capgo.app', 'https://capgo.app', orgId, howMany) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve buyTokens function implementation.
Several issues need attention:
- Hardcoded URLs should be dynamic
- Empty string as first parameter seems incorrect
- Missing error handling
async function buyTokens(howMany: number) {
const orgId = organizationStore.currentOrganization!.gid
if (!orgId) {
+ console.error('No organization ID found')
return
}
if (howMany < 1) {
+ console.error('Invalid token amount:', howMany)
return
}
- openCheckoutForOneOff('', 'https://capgo.app', 'https://capgo.app', orgId, howMany)
+
+ const baseUrl = window.location.origin
+ const successUrl = `${baseUrl}${router.currentRoute.value.path}?thankYou=true`
+ const cancelUrl = `${baseUrl}${router.currentRoute.value.path}`
+
+ try {
+ await openCheckoutForOneOff(orgId, successUrl, cancelUrl, orgId, howMany)
+ } catch (error) {
+ console.error('Checkout failed:', error)
+ displayStore.dialogOption = {
+ header: t('error'),
+ message: t('checkout-failed'),
+ buttons: [{ text: t('ok'), role: 'cancel' }]
+ }
+ displayStore.showDialog = 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.
async function buyTokens(howMany: number) { | |
const orgId = organizationStore.currentOrganization!.gid | |
if (!orgId) { | |
return | |
} | |
if (howMany < 1) { | |
return | |
} | |
openCheckoutForOneOff('', 'https://capgo.app', 'https://capgo.app', orgId, howMany) | |
} | |
async function buyTokens(howMany: number) { | |
const orgId = organizationStore.currentOrganization!.gid | |
if (!orgId) { | |
console.error('No organization ID found') | |
return | |
} | |
if (howMany < 1) { | |
console.error('Invalid token amount:', howMany) | |
return | |
} | |
const baseUrl = window.location.origin | |
const successUrl = `${baseUrl}${router.currentRoute.value.path}?thankYou=true` | |
const cancelUrl = `${baseUrl}${router.currentRoute.value.path}` | |
try { | |
await openCheckoutForOneOff(orgId, successUrl, cancelUrl, orgId, howMany) | |
} catch (error) { | |
console.error('Checkout failed:', error) | |
displayStore.dialogOption = { | |
header: t('error'), | |
message: t('checkout-failed'), | |
buttons: [{ text: t('ok'), role: 'cancel' }] | |
} | |
displayStore.showDialog = true | |
} | |
} |
🤖 Prompt for AI Agents
In src/pages/settings/organization/Tokens.vue around lines 233 to 242, the
buyTokens function has hardcoded URLs which should be replaced with dynamic
values from configuration or environment variables. The first parameter to
openCheckoutForOneOff is an empty string, which should be replaced with a
meaningful value such as a product or plan identifier. Add proper error handling
around the function to catch and handle any exceptions during the checkout
process to improve robustness.
There was a problem hiding this 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 (9)
supabase/functions/_backend/utils/stripe.ts (3)
291-293
: Add validation for empty pricing steps.The while loop could run indefinitely if
tokensSteps
is empty or malformed.
304-304
: Use BigInt or string for large number literal to avoid precision loss.The number
9223372036854775807
exceeds JavaScript's safe integer range and will lose precision.Also applies to: 310-310
🧰 Tools
🪛 Biome (1.9.4)
[error] 304-304: This number literal will lose precision at runtime.
The value at runtime will be 9223372036854776000
(lint/correctness/noPrecisionLoss)
298-337
: Add bounds checking for array access in pricing loop.The loop increments
i
without checking if it exceeds the array bounds.🧰 Tools
🪛 Biome (1.9.4)
[error] 304-304: This number literal will lose precision at runtime.
The value at runtime will be 9223372036854776000
(lint/correctness/noPrecisionLoss)
[error] 310-310: This number literal will lose precision at runtime.
The value at runtime will be 9223372036854776000
(lint/correctness/noPrecisionLoss)
src/pages/settings/organization/Tokens.vue (4)
154-188
: Fix ESLint loop condition errors.The ESLint errors indicate issues with the loop condition variables. The loop variable
d
is being modified correctly withd.setDate(d.getDate() + 1)
, but the condition checking againsttoday
could be problematic sincetoday
is derived fromoneYearAgo
.
219-226
: Improve error handling for better user experience.The current error handling only logs to console, which doesn't provide feedback to users when data loading fails.
247-265
: Add bounds checking in computePrice function.The while loop could access out-of-bounds array elements.
298-307
: Improve buyTokens function implementation.Several issues need attention:
- Hardcoded URLs should be dynamic
- Empty string as first parameter seems incorrect
- Missing error handling
supabase/migrations/20250131091941_capgo_tokens.sql (2)
34-34
: RLS enabled without policies on history tableYou've enabled row-level security on
capgo_tokens_history
but haven't defined any SELECT/INSERT policies, which will block all access. Please add appropriate RLS policies for service roles or authenticated functions.
88-88
:capgo_tokens_steps
table design – reconsider data typeDefining
price_per_unit
asFLOAT
can lead to rounding issues. It's safer to useNUMERIC(10,4)
orDECIMAL
for monetary values.
🧹 Nitpick comments (1)
supabase/functions/_backend/utils/stripe.ts (1)
284-286
: Fix typo in function name.The function name
toMilion
should betoMillion
for correct spelling.Apply this diff to fix the typo:
-function toMilion(price: number) { +function toMillion(price: number) {Remember to update the usage in lines 305 and 306 as well.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
src/assets/bandwidth.svg
is excluded by!**/*.svg
📒 Files selected for processing (22)
messages/de.json
(12 hunks)messages/en.json
(16 hunks)messages/es.json
(12 hunks)messages/fr.json
(12 hunks)messages/id.json
(12 hunks)messages/it.json
(12 hunks)messages/ja.json
(12 hunks)messages/ko.json
(12 hunks)messages/pl.json
(12 hunks)messages/pt-br.json
(12 hunks)messages/ru.json
(12 hunks)messages/tr.json
(12 hunks)messages/vi.json
(12 hunks)messages/zh-cn.json
(12 hunks)src/components/BuyTokens.vue
(1 hunks)src/pages/settings/organization/Tokens.vue
(1 hunks)src/services/stripe.ts
(2 hunks)src/types/supabase.types.ts
(13 hunks)supabase/functions/_backend/private/stripe_checkout.ts
(2 hunks)supabase/functions/_backend/utils/stripe.ts
(2 hunks)supabase/migrations/20250131091941_capgo_tokens.sql
(1 hunks)supabase/seed.sql
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (18)
- supabase/seed.sql
- messages/pl.json
- supabase/functions/_backend/private/stripe_checkout.ts
- messages/de.json
- src/services/stripe.ts
- src/components/BuyTokens.vue
- messages/ko.json
- messages/zh-cn.json
- messages/fr.json
- messages/ru.json
- messages/en.json
- messages/it.json
- messages/ja.json
- messages/pt-br.json
- messages/vi.json
- messages/tr.json
- messages/es.json
- messages/id.json
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/types/supabase.types.ts (1)
supabase/functions/_backend/utils/supabase.types.ts (1)
Json
(1-7)
🪛 Biome (1.9.4)
supabase/functions/_backend/utils/stripe.ts
[error] 304-304: This number literal will lose precision at runtime.
The value at runtime will be 9223372036854776000
(lint/correctness/noPrecisionLoss)
[error] 310-310: This number literal will lose precision at runtime.
The value at runtime will be 9223372036854776000
(lint/correctness/noPrecisionLoss)
🔇 Additional comments (11)
src/pages/settings/organization/Tokens.vue (1)
1-106
: LGTM! Well-structured component setup.The component setup with imports, reactive data structures, and store integrations is well-organized and follows Vue 3 composition API best practices.
supabase/migrations/20250131091941_capgo_tokens.sql (2)
37-81
: LGTM! Well-implemented security function.The
get_tokens_history
function properly implements access control checks and uses secure query patterns with running totals calculation.
123-190
: LGTM! Secure utility functions with proper access control.The MAU token aggregation functions are well-designed with appropriate security restrictions (service_role only) and clear documentation.
src/types/supabase.types.ts (8)
10-34
: LGTM! GraphQL schema addition is well-structured.The new
graphql_public
schema follows the existing patterns and provides proper typing for GraphQL endpoint integration. The function signature correctly handles typical GraphQL parameters (operationName, query, variables, extensions) with appropriate JSON typing.
313-377
: LGTM! Token tables are well-designed.The new token management tables follow proper database design principles:
capgo_tokens_history
correctly tracks token transactions with org relationshipcapgo_tokens_steps
provides pricing configuration with appropriate numeric fields- Both tables have proper TypeScript typing with appropriate optional fields for Insert/Update operations
- Foreign key relationship to
orgs
table is correctly defined
1037-1037
: LGTM! Version field addition follows proper patterns.The new
version
field in theplans
table is correctly typed:
- Required in Row type for data consistency
- Optional in Insert/Update types allowing for defaults or server-side generation
1473-1476
: LGTM! New function definitions are properly typed.All new function signatures follow proper TypeScript patterns:
- Token-related functions (
get_extra_mau_for_org
,get_tokens_history
,get_total_mau_tokens
) have appropriate parameter and return types- Database replication function (
replicate_to_d1
) correctly accepts JSON records and metadata- Reset functions properly parameterize operations with app_id for targeted functionality
Also applies to: 1613-1622, 1627-1630, 1931-1939, 1941-1941, 1945-1945
2195-2197
: LGTM! Constants addition is consistent.The new
graphql_public
constants section properly mirrors the schema definition and follows the existing pattern with an empty Enums object.
1-2278
: Excellent implementation of comprehensive token management types.This file successfully integrates new Capgo token management functionality while maintaining type safety and consistency. The additions include:
- Well-structured database tables for token history and pricing
- Proper function definitions for token operations
- Consistent typing patterns throughout
- Appropriate relationships and constraints
The implementation supports the full token billing system while preserving existing functionality.
610-610
:✅ Verification successful
Verify impact of making email field required.
The
deleted_account
Insert type is now required (no?
marker). This could be a breaking change if there's existing code that inserts records without providing an email value.
🏁 Script executed:
#!/bin/bash # Description: Search for deleted_account inserts to verify if email is always provided # Expected: Find all INSERT operations to check if they provide email values # Search for direct table inserts rg -A 5 -B 2 "deleted_account.*insert|insert.*deleted_account" # Search for Supabase client inserts rg -A 10 -B 2 "\.from\(['\"]deleted_account['\"].*\.insert"Length of output: 130
🏁 Script executed:
#!/bin/bash # Search for any references to deleted_account outside of the type definitions rg -n "deleted_account" --glob '!src/types/supabase.types.ts' # Specifically look for Supabase client inserts on deleted_account rg -n "\.from\(['\"]deleted_account['\"]\).*\.insert" --glob '!src/types/supabase.types.ts'Length of output: 1686
No breaking change for deleted_account email field
All inserts into
deleted_account
are confined to SQL migrations and seed files—and each of those always supplies an.from('deleted_account').insert(...)
calls were found in the application code, so making the TypeScriptInsert
type’s
2044-2044
:✅ Verification successful
Verify enum reordering impact.
The
usage_mode
enum values have been reordered. While this should be safe for string enums, verify that no application logic depends on the specific ordering of these values.
🏁 Script executed:
#!/bin/bash # Description: Check if usage_mode enum ordering is used in application logic # Expected: Find any code that might depend on enum value ordering # Search for usage_mode enum usage rg -A 5 -B 5 "usage_mode" # Search for any array operations or ordering logic with these enum values rg -A 3 -B 3 "(last_saved|5min|day|cycle).*\.(sort|indexOf|findIndex)"Length of output: 1231
🏁 Script executed:
#!/bin/bash # Search all TypeScript files for usage_mode references rg -n "usage_mode" -t ts # Look for any array ordering or index operations involving the enum values in TS code rg -nE "(\"last_saved\"|\"5min\"|\"day\"|\"cycle\").*(sort|indexOf|findIndex)" -t tsLength of output: 500
Please share the output so we can confirm if any logic relies on enum ordering.
🏁 Script executed:
#!/bin/bash # Refined search for TypeScript references to usage_mode rg -n "usage_mode" -g "*.ts" # Refined search for any ordering operations (sort, indexOf, findIndex) on the enum’s string values rg -nE "\"(last_saved|5min|day|cycle)\".*(sort|indexOf|findIndex)" -g "*.ts"Length of output: 933
No enum ordering dependencies detected.
A search across all TypeScript files shows that
usage_mode
is only used in type definitions and array literals—there are no calls to.sort()
,.indexOf()
, or.findIndex()
on these values. Since this is a string union, reordering the members will not introduce a breaking change.
@@ -272,7 +272,7 @@ export async function createCheckout(c: Context, customerId: string, reccurence: | |||
line_items: [ | |||
{ | |||
price: prices.priceId, | |||
quantity: 1, | |||
quantity: howMany ?? 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add missing parameter to function signature.
The howMany
parameter is used in the function body but is not defined in the function signature, which will cause a TypeScript error.
Apply this diff to fix the function signature:
-export async function createCheckout(c: Context, customerId: string, reccurence: string, planId: string, successUrl: string, cancelUrl: string, clientReferenceId?: string) {
+export async function createCheckout(c: Context, customerId: string, reccurence: string, planId: string, successUrl: string, cancelUrl: string, clientReferenceId?: string, howMany?: number) {
📝 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.
quantity: howMany ?? 1, | |
export async function createCheckout( | |
c: Context, | |
customerId: string, | |
reccurence: string, | |
planId: string, | |
successUrl: string, | |
cancelUrl: string, | |
clientReferenceId?: string, | |
howMany?: number | |
) { |
🤖 Prompt for AI Agents
In supabase/functions/_backend/utils/stripe.ts at line 275, the parameter
'howMany' is used but missing from the function signature, causing a TypeScript
error. Update the function signature to include 'howMany' as a parameter with
the appropriate type to ensure it is defined and can be used within the function
body.
@WcaleNieWolny i think we will go for credits instead of token |
To me all plan should give credits, no more different price structure. |
fix #941
Summary by CodeRabbit
New Features
Improvements
Chores