feat(clerk-js,localizations,shared,ui): Add support for rendering discounts on subscription items - #9316
feat(clerk-js,localizations,shared,ui): Add support for rendering discounts on subscription items#9316dstaley wants to merge 2 commits into
Conversation
🦋 Changeset detectedLatest commit: 73f5daf The changes in this PR will be included in the next version bump. This PR includes changesets to release 23 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/electron
@clerk/electron-passkeys
@clerk/eslint-plugin
@clerk/expo
@clerk/expo-google-signin
@clerk/expo-passkeys
@clerk/express
@clerk/fastify
@clerk/hono
@clerk/localizations
@clerk/nextjs
@clerk/nuxt
@clerk/react
@clerk/react-router
@clerk/shared
@clerk/tanstack-react-start
@clerk/testing
@clerk/ui
@clerk/upgrade
@clerk/vue
commit: |
API Changes Report
Summary
@clerk/sharedCurrent version: 4.25.10 Subpath
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (58)
🔗 Linked repositories identifiedCodeRabbit considers these linked repositories for cross-repo context during reviews:
🚧 Files skipped from review as they are similar to previous changes (58)
📝 WalkthroughWalkthroughAdded billing discount contracts and JSON conversion. Subscription items now expose applied discount redemptions. The subscription overview renders active discount details and localized amounts. Billing localization keys were added across supported resources. Tests cover fixed discounts, percentage discounts, and redemption mapping. A patch changeset and bundle size limit update were added. Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Warning Review ran into problems🔥 ProblemsLinked repositories: Couldn't analyze
Errors logged to '/home/jailuser/git/.git/lfs/logs/20260803T202050.40595017.log'. Errors logged to '/home/jailuser/git/.git/lfs/logs/20260803T202210.199794141.log'. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
packages/shared/src/types/billing.ts (1)
1032-1069: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd per-field JSDoc to the new discount interfaces.
BillingAppliedDiscountandBillingDiscountRedemptiononly carry an interface-level@experimentalcomment. Every other interface in this file, includingBillingSubscriptionItemResourceandBillingDiscounts, documents each property individually. Add a short doc comment to each field (amount,discountId,effect,percentOff,amountOff,promoCode,cyclesRemaining,source,status,redeemedAt,redeemedBy, etc.) so consumers of this new public, experimental API understand the field semantics without cross-referencing the JSON layer.As per coding guidelines, "All public APIs must be documented with JSDoc" and "TypeScript is required for all packages... Maintain comprehensive JSDoc comments for public APIs."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/shared/src/types/billing.ts` around lines 1032 - 1069, Add individual JSDoc comments for every property in the public interfaces BillingAppliedDiscount and BillingDiscountRedemption, including identifiers, monetary fields, discount effects, cycle counters, source/status values, redemption timestamps, and redeemer information. Keep the existing interface-level experimental documentation and ensure each comment clearly describes the field’s semantics without relying on JSON-layer definitions.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/localizations/src/en-US.ts`:
- Around line 125-126: Update the discount localization usage associated with
SubscriptionDiscountRow so a single cycle uses the singular billing.month or
billing.year label, while multiple cycles retain billing.months or
billing.years. Ensure both discountCyclesRemaining and discountDuration render
grammatically correct singular and plural wording.
In `@packages/ui/src/components/Subscriptions/SubscriptionsList.tsx`:
- Around line 258-302: Update the period-label logic near discountTitle so
singular keys are selected when each relevant cycle count equals 1, reusing the
existing billing.month/year keys and plural keys otherwise. Replace the shared
period value in discountDuration with a label based on totalCycles and in
discountCyclesRemaining with a label based on appliedDiscount.cyclesRemaining,
using the existing getPeriodLabel helper if available.
---
Nitpick comments:
In `@packages/shared/src/types/billing.ts`:
- Around line 1032-1069: Add individual JSDoc comments for every property in the
public interfaces BillingAppliedDiscount and BillingDiscountRedemption,
including identifiers, monetary fields, discount effects, cycle counters,
source/status values, redemption timestamps, and redeemer information. Keep the
existing interface-level experimental documentation and ensure each comment
clearly describes the field’s semantics without relying on JSON-layer
definitions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 6b94bfa3-1098-4c76-ae70-37dedd04e586
📒 Files selected for processing (57)
.changeset/green-dolphins-discount.mdpackages/clerk-js/src/core/resources/BillingSubscription.tspackages/clerk-js/src/utils/__tests__/billing.test.tspackages/clerk-js/src/utils/billing.tspackages/localizations/src/ar-SA.tspackages/localizations/src/be-BY.tspackages/localizations/src/bg-BG.tspackages/localizations/src/bn-IN.tspackages/localizations/src/ca-ES.tspackages/localizations/src/cs-CZ.tspackages/localizations/src/da-DK.tspackages/localizations/src/de-DE.tspackages/localizations/src/el-GR.tspackages/localizations/src/en-GB.tspackages/localizations/src/en-US.tspackages/localizations/src/es-CR.tspackages/localizations/src/es-ES.tspackages/localizations/src/es-MX.tspackages/localizations/src/es-UY.tspackages/localizations/src/fa-IR.tspackages/localizations/src/fi-FI.tspackages/localizations/src/fr-FR.tspackages/localizations/src/he-IL.tspackages/localizations/src/hi-IN.tspackages/localizations/src/hr-HR.tspackages/localizations/src/hu-HU.tspackages/localizations/src/id-ID.tspackages/localizations/src/is-IS.tspackages/localizations/src/it-IT.tspackages/localizations/src/ja-JP.tspackages/localizations/src/kk-KZ.tspackages/localizations/src/ko-KR.tspackages/localizations/src/mn-MN.tspackages/localizations/src/ms-MY.tspackages/localizations/src/nb-NO.tspackages/localizations/src/nl-BE.tspackages/localizations/src/nl-NL.tspackages/localizations/src/pl-PL.tspackages/localizations/src/pt-BR.tspackages/localizations/src/pt-PT.tspackages/localizations/src/ro-RO.tspackages/localizations/src/ru-RU.tspackages/localizations/src/sk-SK.tspackages/localizations/src/sr-RS.tspackages/localizations/src/sv-SE.tspackages/localizations/src/ta-IN.tspackages/localizations/src/te-IN.tspackages/localizations/src/th-TH.tspackages/localizations/src/tr-TR.tspackages/localizations/src/uk-UA.tspackages/localizations/src/vi-VN.tspackages/localizations/src/zh-CN.tspackages/localizations/src/zh-TW.tspackages/shared/src/types/billing.tspackages/shared/src/types/json.tspackages/shared/src/types/localization.tspackages/ui/src/components/Subscriptions/SubscriptionsList.tsx
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
clerk/clerk_go(manual)clerk/dashboard(manual)clerk/accounts(manual)clerk/backoffice(manual)clerk/clerk(manual)clerk/clerk-docs(manual)clerk/cloudflare-workers(manual)clerk/clerk-ios(auto-detected)clerk/clerk-android(auto-detected)clerk/cli(auto-detected)
| discountCyclesRemaining: '{{cycles}} {{period}} remaining', | ||
| discountDuration: '({{amount}} off first {{cycles}} {{period}})', |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Fix singular discount wording.
SubscriptionDiscountRow always passes the plural billing.months or billing.years label. When cycles is 1, the UI renders first 1 years and 1 years remaining. Select billing.month or billing.year for one cycle, or add singular/plural-aware localization variants.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/localizations/src/en-US.ts` around lines 125 - 126, Update the
discount localization usage associated with SubscriptionDiscountRow so a single
cycle uses the singular billing.month or billing.year label, while multiple
cycles retain billing.months or billing.years. Ensure both
discountCyclesRemaining and discountDuration render grammatically correct
singular and plural wording.
| const totalCycles = | ||
| appliedDiscount.cyclesRemaining === null ? null : appliedDiscount.cyclesApplied + appliedDiscount.cyclesRemaining; | ||
| const period = t( | ||
| subscriptionItem.planPeriod === 'annual' ? localizationKeys('billing.years') : localizationKeys('billing.months'), | ||
| ).toLocaleLowerCase(); | ||
|
|
||
| const discountAmount = | ||
| appliedDiscount.effect === 'percentage' && appliedDiscount.percentOff !== undefined | ||
| ? `${appliedDiscount.percentOff}%` | ||
| : appliedDiscount.amountOff | ||
| ? $(appliedDiscount.amountOff) | ||
| : ''; | ||
| const discountTitle = `${appliedDiscount.name} ${t( | ||
| totalCycles === null | ||
| ? localizationKeys('billing.discountAmount', { amount: discountAmount }) | ||
| : localizationKeys('billing.discountDuration', { | ||
| amount: discountAmount, | ||
| cycles: totalCycles, | ||
| period, | ||
| }), | ||
| )}`; | ||
|
|
||
| return ( | ||
| <Tr | ||
| sx={t => | ||
| subscriptionItem.status === 'upcoming' | ||
| ? { | ||
| background: common.mutedBackground(t), | ||
| } | ||
| : {} | ||
| } | ||
| > | ||
| <Td sx={{ verticalAlign: 'top' }}> | ||
| <Col gap={1}> | ||
| <Text variant='subtitle'>{discountTitle}</Text> | ||
| {appliedDiscount.cyclesRemaining !== null ? ( | ||
| <Text | ||
| variant='subtitle' | ||
| colorScheme='secondary' | ||
| localizationKey={localizationKeys('billing.discountCyclesRemaining', { | ||
| cycles: appliedDiscount.cyclesRemaining, | ||
| period, | ||
| })} | ||
| /> | ||
| ) : null} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Fix incorrect singular/plural period text ("1 years").
period always resolves to the plural billing.years or billing.months string, regardless of the actual count. discountTitle (lines 270-278) and the remaining-cycles caption (lines 293-302) both interpolate this always-plural period into the count-based message. For a count of 1, this produces incorrect text such as "25% off first 1 years" and "1 years remaining", exactly as shown in the PR's own screenshot.
Select the singular form when the relevant count equals 1. The singular keys billing.month and billing.year are already used elsewhere in this file (see the fee-suffix logic near line 400).
✏️ Proposed fix for singular/plural period selection
- const totalCycles =
- appliedDiscount.cyclesRemaining === null ? null : appliedDiscount.cyclesApplied + appliedDiscount.cyclesRemaining;
- const period = t(
- subscriptionItem.planPeriod === 'annual' ? localizationKeys('billing.years') : localizationKeys('billing.months'),
- ).toLocaleLowerCase();
+ const totalCycles =
+ appliedDiscount.cyclesRemaining === null ? null : appliedDiscount.cyclesApplied + appliedDiscount.cyclesRemaining;
+ const getPeriodLabel = (count: number) => {
+ const isAnnual = subscriptionItem.planPeriod === 'annual';
+ const key = count === 1 ? (isAnnual ? 'billing.year' : 'billing.month') : isAnnual ? 'billing.years' : 'billing.months';
+ return t(localizationKeys(key)).toLocaleLowerCase();
+ };Then use getPeriodLabel(totalCycles ?? 0) for the discountDuration call and getPeriodLabel(appliedDiscount.cyclesRemaining ?? 0) for the discountCyclesRemaining call, in place of the single shared period variable.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/components/Subscriptions/SubscriptionsList.tsx` around lines
258 - 302, Update the period-label logic near discountTitle so singular keys are
selected when each relevant cycle count equals 1, reusing the existing
billing.month/year keys and plural keys otherwise. Replace the shared period
value in discountDuration with a label based on totalCycles and in
discountCyclesRemaining with a label based on appliedDiscount.cyclesRemaining,
using the existing getPeriodLabel helper if available.
53e7936 to
6af04e8
Compare
…counts on subscription items
6af04e8 to
73f5daf
Compare
Description
This PR adds support for rendering discounts on subscription items.
Checklist
pnpm testruns as expected.pnpm buildruns as expected.Type of change