Skip to content

chore(clerk-js,types): Update checkout flow to support free trials #6494

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

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

panteliselef
Copy link
Member

@panteliselef panteliselef commented Aug 8, 2025

Description

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • New Features

    • Added support for free trial properties across billing, plans, subscriptions, and checkout, including trial duration and eligibility.
    • Checkout and pricing UI now display free trial status, end dates, and the amount due after the trial period.
    • Subscription badges and labels now indicate free trial status.
  • Localization

    • Introduced new localization keys and messages for free trial and trial period states.
  • Bug Fixes

    • Ensured consistent presence of free trial properties in checkout data, even when not available.
  • Documentation

    • Updated documentation and type declarations to reflect new free trial properties.

@panteliselef panteliselef self-assigned this Aug 8, 2025
Copy link

changeset-bot bot commented Aug 8, 2025

🦋 Changeset detected

Latest commit: 1c6b47c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 22 packages
Name Type
@clerk/clerk-js Minor
@clerk/types Minor
@clerk/localizations Minor
@clerk/chrome-extension Patch
@clerk/clerk-expo Patch
@clerk/agent-toolkit Patch
@clerk/astro Patch
@clerk/backend Patch
@clerk/elements Patch
@clerk/expo-passkeys Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/clerk-react Patch
@clerk/remix Patch
@clerk/shared Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch
@clerk/themes Patch
@clerk/vue Patch

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

Copy link

vercel bot commented Aug 8, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
clerk-js-sandbox ✅ Ready (Inspect) Visit Preview 💬 Add feedback Aug 8, 2025 6:05pm

@panteliselef panteliselef changed the title Elef/com 1123 update checkout flow to support free trials chore(clerk-js,types): Update checkout flow to support free trials Aug 8, 2025
@panteliselef panteliselef marked this pull request as draft August 8, 2025 18:05
Copy link
Contributor

coderabbitai bot commented Aug 8, 2025

📝 Walkthrough

Walkthrough

This change set introduces comprehensive support for free trial functionality across multiple packages and components. New properties related to free trial status, eligibility, and end dates are added to commerce-related TypeScript interfaces, JSON types, and classes. UI components such as checkout forms and pricing tables are updated to display free trial information, including badges, trial end dates, and trial-specific messaging. Localization resources are extended with new keys for free trial-related labels and notices. The logic for determining button labels and displaying plan information is centralized and enhanced to reflect free trial states. Changesets document minor version updates for affected packages.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–25 minutes

Complexity label: Moderate
Rationale:
The changes span several core resource classes, type definitions, UI components, and localization files, introducing new properties and UI logic for free trial support. While the updates are systematic and well-scoped, they touch multiple layers (types, business logic, UI, and localization) and require careful review to ensure consistency and correctness across the codebase. The review effort is moderate due to the number of files and the need to verify that new free trial features are integrated properly without breaking existing functionality.

Note

🔌 MCP (Model Context Protocol) integration is now available in Early Access!

Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context.


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🧹 Nitpick comments (12)
.changeset/sour-lemons-talk.md (1)

6-6: Consider a more descriptive summary for release notes.
E.g., “Add free trial fields to CommerceCheckout and related resources; wire into UI and localization.”

.changeset/tender-planets-win.md (1)

7-7: Nit: Expand the message for clarity.
E.g., “PricingTable: show trial footer notice and date when free trials are enabled; add localization keys.”

packages/clerk-js/src/core/resources/CommerceCheckout.ts (1)

26-26: Document new public property with JSDoc.
Add succinct JSDoc for freeTrialEndsAt to clarify source and timezone.

Apply:

 export class CommerceCheckout extends BaseResource implements CommerceCheckoutResource {
@@
-  freeTrialEndsAt!: Date | null;
+  /**
+   * End date of the free trial for this checkout, or null if not on trial.
+   * Derived from API field `free_trial_ends_at` (unix epoch seconds) and converted to a JS Date in UTC.
+   */
+  freeTrialEndsAt!: Date | null;
packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx (1)

238-244: Localization key selection is correct; minor tidy and tests suggested.

  • Redundant optional chaining on subscription inside a guarded branch; optional.
  • Please add tests covering footer behavior for: upcoming, active with trial in future, active with trial in past, and active with period mismatch.

I can draft component tests (React Testing Library) for these scenarios if helpful.

packages/clerk-js/src/core/resources/CommercePlan.ts (2)

30-31: Public API docs for new fields.

Add JSDoc on the new properties to keep the public API self-documented and discoverable.

 export class CommercePlan extends BaseResource implements CommercePlanResource {
   ...
-  freeTrialDays!: number | null;
-  freeTrialEnabled!: boolean;
+  /** Number of free trial days for this plan; null when no trial is configured. */
+  freeTrialDays!: number | null;
+  /** Whether free trials are enabled for this plan. */
+  freeTrialEnabled!: boolean;

33-36: Tests missing for new fields.

No tests were provided for mapping free trial fields from JSON and snapshot parity. Please add minimal unit tests covering:

  • fromJSON sets freeTrialDays/freeTrialEnabled defaults and values.
  • __internal_toSnapshot includes these fields (when you add them).
packages/clerk-js/src/core/resources/CommerceSubscription.ts (1)

81-85: Tests recommended for free trial mapping and date conversion.

Add tests verifying:

  • eligibleForFreeTrial default/value mapping.
  • freeTrialEndsAt date conversion with undefined/null present.
  • Stability of behavior when JSON omits experimental fields.

Also applies to: 86-113

packages/types/src/json.ts (3)

785-788: Remove commented-out schema line.

Avoid commented schema fields in published types.

-  // is_free_trial: boolean;
   // TODO(@COMMERCE): Remove optional after GA.
   free_trial_ends_at?: number | null;

817-818: Subscription JSON addition approved; mark experimental for clarity.

Consider annotating with an inline JSDoc to match surrounding experimental notes.

 export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
   ...
-  eligible_for_free_trial?: boolean;
+  /** @experimental */
+  eligible_for_free_trial?: boolean;

882-884: Checkout JSON addition approved; keep GA note, add JSDoc.

Matches the item JSON pattern.

 export interface CommerceCheckoutJSON extends ClerkResourceJSON {
   ...
-  // TODO(@COMMERCE): Remove optional after GA.
-  free_trial_ends_at?: number | null;
+  // TODO(@COMMERCE): Remove optional after GA.
+  /** @experimental */
+  free_trial_ends_at?: number | null;
packages/clerk-js/src/ui/components/Subscriptions/badge.tsx (1)

31-37: Stale @ts-expect-error comment

The comment still mentions “ended is included” while the suppressed key is now "free_trial".
Refresh the comment (or delete it once the enum is fixed) to avoid confusion.

packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (1)

56-60: Minor – avoid rendering empty badge container

SubscriptionBadge is rendered inside the JSX conditional, but the parent passes null when the condition fails, leaving an unnecessary badge={null} prop.

-badge={
-  plan.freeTrialEnabled && freeTrialEndsAt ? (
-    <SubscriptionBadge subscription={{ status: 'free_trial' }} />
-  ) : null
-}
+badge={
+  plan.freeTrialEnabled && freeTrialEndsAt
+    ? <SubscriptionBadge subscription={{ status: 'free_trial' }} />
+    : undefined
+}

Passing undefined spares React a noop render.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf284ee and 1c6b47c.

📒 Files selected for processing (15)
  • .changeset/sour-lemons-talk.md (1 hunks)
  • .changeset/tender-planets-win.md (1 hunks)
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts (3 hunks)
  • packages/clerk-js/src/core/resources/CommercePlan.ts (2 hunks)
  • packages/clerk-js/src/core/resources/CommerceSubscription.ts (4 hunks)
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx (10 hunks)
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx (2 hunks)
  • packages/clerk-js/src/ui/components/Subscriptions/badge.tsx (1 hunks)
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx (4 hunks)
  • packages/clerk-js/src/ui/elements/LineItems.tsx (2 hunks)
  • packages/localizations/src/en-US.ts (4 hunks)
  • packages/shared/src/react/hooks/useCheckout.ts (1 hunks)
  • packages/types/src/commerce.ts (4 hunks)
  • packages/types/src/json.ts (4 hunks)
  • packages/types/src/localization.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (13)
.changeset/**

📄 CodeRabbit Inference Engine (.cursor/rules/monorepo.mdc)

Automated releases must use Changesets.

Files:

  • .changeset/tender-planets-win.md
  • .changeset/sour-lemons-talk.md
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/development.mdc)

**/*.{js,jsx,ts,tsx}: All code must pass ESLint checks with the project's configuration
Follow established naming conventions (PascalCase for components, camelCase for variables)
Maintain comprehensive JSDoc comments for public APIs
Use dynamic imports for optional features
All public APIs must be documented with JSDoc
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Profile and optimize critical paths
Validate all inputs and sanitize outputs
Implement proper logging with different levels

Files:

  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/core/resources/CommercePlan.ts
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/clerk-js/src/ui/elements/LineItems.tsx
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/types/src/json.ts
  • packages/clerk-js/src/ui/components/Subscriptions/badge.tsx
  • packages/clerk-js/src/core/resources/CommerceSubscription.ts
  • packages/types/src/commerce.ts
  • packages/types/src/localization.ts
  • packages/localizations/src/en-US.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
**/*.{js,jsx,ts,tsx,json,css,scss,md,yaml,yml}

📄 CodeRabbit Inference Engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/core/resources/CommercePlan.ts
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/clerk-js/src/ui/elements/LineItems.tsx
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/types/src/json.ts
  • packages/clerk-js/src/ui/components/Subscriptions/badge.tsx
  • packages/clerk-js/src/core/resources/CommerceSubscription.ts
  • packages/types/src/commerce.ts
  • packages/types/src/localization.ts
  • packages/localizations/src/en-US.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
packages/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/core/resources/CommercePlan.ts
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/clerk-js/src/ui/elements/LineItems.tsx
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/types/src/json.ts
  • packages/clerk-js/src/ui/components/Subscriptions/badge.tsx
  • packages/clerk-js/src/core/resources/CommerceSubscription.ts
  • packages/types/src/commerce.ts
  • packages/types/src/localization.ts
  • packages/localizations/src/en-US.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
packages/**/*.{ts,tsx,d.ts}

📄 CodeRabbit Inference Engine (.cursor/rules/development.mdc)

Packages should export TypeScript types alongside runtime code

Files:

  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/core/resources/CommercePlan.ts
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/clerk-js/src/ui/elements/LineItems.tsx
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/types/src/json.ts
  • packages/clerk-js/src/ui/components/Subscriptions/badge.tsx
  • packages/clerk-js/src/core/resources/CommerceSubscription.ts
  • packages/types/src/commerce.ts
  • packages/types/src/localization.ts
  • packages/localizations/src/en-US.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Prefer readonly for properties that shouldn't change after construction
Prefer composition and interfaces over deep inheritance chains
Use mixins for shared behavior across unrelated classes
Implement dependency injection for loose coupling
Let TypeScript infer when types are obvious
Use const assertions for literal types: as const
Use satisfies operator for type checking without widening
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Use type-only imports: import type { ... } from ...
No any types without justification
Proper error handling with typed errors
Consistent use of readonly for immutable data
Proper generic constraints
No unused type parameters
Proper use of utility types instead of manual type construction
Type-only imports where possible
Proper tree-shaking friendly exports
No circular dependencies
Efficient type computations (avoid deep recursion)

Files:

  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/core/resources/CommercePlan.ts
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/clerk-js/src/ui/elements/LineItems.tsx
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/types/src/json.ts
  • packages/clerk-js/src/ui/components/Subscriptions/badge.tsx
  • packages/clerk-js/src/core/resources/CommerceSubscription.ts
  • packages/types/src/commerce.ts
  • packages/types/src/localization.ts
  • packages/localizations/src/en-US.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit Inference Engine (.cursor/rules/monorepo.mdc)

Support multiple Clerk environment variables (CLERK_, NEXT_PUBLIC_CLERK_, etc.) for configuration.

Files:

  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/core/resources/CommercePlan.ts
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/clerk-js/src/ui/elements/LineItems.tsx
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/types/src/json.ts
  • packages/clerk-js/src/ui/components/Subscriptions/badge.tsx
  • packages/clerk-js/src/core/resources/CommerceSubscription.ts
  • packages/types/src/commerce.ts
  • packages/types/src/localization.ts
  • packages/localizations/src/en-US.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
**/*

⚙️ CodeRabbit Configuration File

If there are no tests added or modified as part of the PR, please suggest that tests be added to cover the changes.

Files:

  • packages/shared/src/react/hooks/useCheckout.ts
  • packages/clerk-js/src/core/resources/CommercePlan.ts
  • packages/clerk-js/src/core/resources/CommerceCheckout.ts
  • packages/clerk-js/src/ui/elements/LineItems.tsx
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/types/src/json.ts
  • packages/clerk-js/src/ui/components/Subscriptions/badge.tsx
  • packages/clerk-js/src/core/resources/CommerceSubscription.ts
  • packages/types/src/commerce.ts
  • packages/types/src/localization.ts
  • packages/localizations/src/en-US.ts
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
packages/clerk-js/src/ui/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/clerk-js-ui.mdc)

packages/clerk-js/src/ui/**/*.{ts,tsx}: Element descriptors should always be camelCase
Use element descriptors in UI components to enable consistent theming and styling via appearance.elements
Element descriptors should generate unique, stable CSS classes for theming
Element descriptors should handle state classes (e.g., cl-loading, cl-active, cl-error, cl-open) automatically based on component state
Do not render hard-coded values; all user-facing strings must be localized using provided localization methods
Use the useLocalizations hook and localizationKeys utility for all text and error messages
Use the styled system (sx prop, theme tokens, responsive values) for custom component styling
Use useCardState for card-level state, useFormState for form-level state, and useLoadingStatus for loading states
Always use handleError utility for API errors and use translateError for localized error messages
Use useFormControl for form field state, implement proper validation, and handle loading and error states in forms
Use localization keys for all form labels and placeholders
Use element descriptors for consistent styling and follow the theme token system
Use the Card and FormContainer patterns for consistent UI structure

Files:

  • packages/clerk-js/src/ui/elements/LineItems.tsx
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/clerk-js/src/ui/components/Subscriptions/badge.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
**/*.{jsx,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/development.mdc)

**/*.{jsx,tsx}: Use error boundaries in React components
Minimize re-renders in React components

**/*.{jsx,tsx}: Always use functional components with hooks instead of class components
Follow PascalCase naming for components: UserProfile, NavigationMenu
Keep components focused on a single responsibility - split large components
Limit component size to 150-200 lines; extract logic into custom hooks
Use composition over inheritance - prefer smaller, composable components
Export components as named exports for better tree-shaking
One component per file with matching filename and component name
Use useState for simple state management
Use useReducer for complex state logic
Implement proper state initialization
Use proper state updates with callbacks
Implement proper state cleanup
Use Context API for theme/authentication
Implement proper state selectors
Use proper state normalization
Implement proper state persistence
Use React.memo for expensive components
Implement proper useCallback for handlers
Use proper useMemo for expensive computations
Implement proper virtualization for lists
Use proper code splitting with React.lazy
Implement proper cleanup in useEffect
Use proper refs for DOM access
Implement proper event listener cleanup
Use proper abort controllers for fetch
Implement proper subscription cleanup
Use proper HTML elements
Implement proper ARIA attributes
Use proper heading hierarchy
Implement proper form labels
Use proper button types
Implement proper focus management
Use proper keyboard shortcuts
Implement proper tab order
Use proper skip links
Implement proper focus traps
Implement proper error boundaries
Use proper error logging
Implement proper error recovery
Use proper error messages
Implement proper error fallbacks
Use proper form validation
Implement proper error states
Use proper error messages
Implement proper form submission
Use proper form reset
Use proper component naming
Implement proper file naming
Use proper prop naming
Implement proper...

Files:

  • packages/clerk-js/src/ui/elements/LineItems.tsx
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/clerk-js/src/ui/components/Subscriptions/badge.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
**/*.tsx

📄 CodeRabbit Inference Engine (.cursor/rules/react.mdc)

**/*.tsx: Use proper type definitions for props and state
Leverage TypeScript's type inference where possible
Use proper event types for handlers
Implement proper generic types for reusable components
Use proper type guards for conditional rendering

Files:

  • packages/clerk-js/src/ui/elements/LineItems.tsx
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
  • packages/clerk-js/src/ui/components/Subscriptions/badge.tsx
  • packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
  • packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
packages/localizations/**/*

📄 CodeRabbit Inference Engine (.cursor/rules/monorepo.mdc)

Localization files must be placed in 'packages/localizations/'.

Files:

  • packages/localizations/src/en-US.ts
**/localizations/**/*.ts

⚙️ CodeRabbit Configuration File

**/localizations/**/*.ts: Review the changes to localization files with the following guidelines:

  • Ensure that no existing translations are accidentally removed unless they are being replaced or fixed. If a string is removed, verify that it is intentional and justified.
  • Check that all translations are friendly, formal, or semi-formal. Explicit, offensive, or inappropriate language is not allowed. If you find any potentially offensive language or are unsure, tag the @clerk/sdk-infra team in a separate comment. If you do not intend to tag the team, refer to it as "Clerk SDK Infra team" instead.
  • Use the most up-to-date base localization file (https://github.com/clerk/javascript/blob/main/packages/localizations/src/en-US.ts) to validate changes, ensuring consistency and completeness.
  • Confirm that new translations are accurate, contextually appropriate, and match the intent of the original English strings.
  • Check for formatting issues, such as missing placeholders, incorrect variable usage, or syntax errors.
  • Ensure that all keys are unique and that there are no duplicate or conflicting entries.
  • If you notice missing translations for new keys, flag them for completion.

Files:

  • packages/localizations/src/en-US.ts
🧠 Learnings (1)
📚 Learning: 2025-07-22T08:43:52.095Z
Learnt from: panteliselef
PR: clerk/javascript#6317
File: packages/clerk-js/src/ui/contexts/components/Plans.tsx:56-68
Timestamp: 2025-07-22T08:43:52.095Z
Learning: The `useSubscription` hook exported from `packages/clerk-js/src/ui/contexts/components/Plans.tsx` is only used internally within clerk-js UI components and is not exposed to external consumers, making renames and modifications to this hook non-breaking for end users.

Applied to files:

  • packages/clerk-js/src/ui/contexts/components/Plans.tsx
🧬 Code Graph Analysis (2)
packages/clerk-js/src/ui/components/Subscriptions/badge.tsx (1)
packages/types/src/commerce.ts (1)
  • CommerceSubscriptionItemResource (994-1146)
packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx (1)
packages/clerk-js/src/ui/localization/localizationKeys.ts (1)
  • localizationKeys (72-77)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (8)
.changeset/sour-lemons-talk.md (1)

1-4: Changeset frontmatter looks correct for a minor bump.
No structural issues detected.

.changeset/tender-planets-win.md (1)

1-5: Minor bumps are appropriate for these packages.
Scope aligns with UI + types + localization updates.

packages/clerk-js/src/core/resources/CommerceCheckout.ts (2)

10-11: Import for date conversion is appropriate.
Alias usage matches other parts of the repo; no issues.


49-49: Approve mapping and types
All checks passed—free_trial_ends_at is defined on CommerceCheckoutJSON and freeTrialEndsAt: Date | null exists on CommerceCheckoutResource. The conversion via unixEpochToDate mirrors the pattern in CommerceSubscription. No changes required.

packages/types/src/localization.ts (1)

146-146: All new localization keys are present and correctly parameterized

  • Verified that packages/localizations/src/en-US.ts defines:
    • badge__freeTrial
    • badge__trialEndsAt (uses {{ date }})
    • startFreeTrial
    • startFreeTrial__days (uses {{ days }})
    • totalDueAfterTrial (uses {{ days }})
  • Confirmed types in packages/types/src/localization.ts match:
    • badge__trialEndsAt: LocalizationValue<'date'>
    • startFreeTrial__days: LocalizationValue<'days'>
    • totalDueAfterTrial: LocalizationValue<'days'>
  • Call sites supply the correct params (date for trialEndsAt, days for days-based keys).

No further changes needed.

packages/types/src/json.ts (2)

652-654: All new JSON fields are consumed.
Verified that free_trial_days, free_trial_enabled, eligible_for_free_trial, and free_trial_ends_at are referenced in both core resources and all relevant UI components—no further action needed.


652-654: Resource mapping confirmed for new optional JSON fields

The free_trial_days and free_trial_enabled properties are correctly handled in CommercePlan:

  • packages/clerk-js/src/core/resources/CommercePlan.ts:
    • Line 61: this.freeTrialDays = this.withDefault(data.free_trial_days, null);
    • Line 62: this.freeTrialEnabled = this.withDefault(data.free_trial_enabled, false);

No further changes needed.

packages/localizations/src/en-US.ts (1)

43-48: Localization keys look good – remember to add translations for other locales

New keys and placeholders are syntactically correct and non-duplicated.
Ensure equivalent entries are added to every supported locale before release.

Also applies to: 92-93, 148-150

Comment on lines +61 to 63
this.freeTrialDays = this.withDefault(data.free_trial_days, null);
this.freeTrialEnabled = this.withDefault(data.free_trial_enabled, false);
this.features = (data.features || []).map(feature => new CommerceFeature(feature));
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Snapshot parity: include free trial fields in __internal_toSnapshot.

Downstream consumers that rely on snapshots will miss these new fields. Include them (and update the corresponding snapshot type if needed).

   public __internal_toSnapshot(): CommercePlanJSONSnapshot {
     return {
       object: 'commerce_plan',
       id: this.id,
       name: this.name,
       amount: this.amount,
       amount_formatted: this.amountFormatted,
       annual_amount: this.annualAmount,
       annual_amount_formatted: this.annualAmountFormatted,
       annual_monthly_amount: this.annualMonthlyAmount,
       annual_monthly_amount_formatted: this.annualMonthlyAmountFormatted,
       currency: this.currency,
       currency_symbol: this.currencySymbol,
       description: this.description,
       is_default: this.isDefault,
       is_recurring: this.isRecurring,
       has_base_fee: this.hasBaseFee,
       for_payer_type: this.forPayerType,
       publicly_visible: this.publiclyVisible,
       slug: this.slug,
       avatar_url: this.avatarUrl,
+      free_trial_days: this.freeTrialDays,
+      free_trial_enabled: this.freeTrialEnabled,
       features: this.features.map(feature => feature.__internal_toSnapshot()),
     };
   }

To confirm types and call sites:

Also applies to: 68-91


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check if the snapshot type includes the new fields
rg -n "CommercePlanJSONSnapshot" packages | sed -n '1,200p'

# Check if code reads the new fields from snapshots
rg -n "free_trial_days|free_trial_enabled" packages

Length of output: 1029


Add free trial fields to __internal_toSnapshot for snapshot parity

We verified that the CommercePlanJSONSnapshot type already includes

free_trial_days?: number | null;
free_trial_enabled?: boolean;

so no type updates are needed. However, the two new properties must be
exported in the snapshot method:

• File: packages/clerk-js/src/core/resources/CommercePlan.ts
• Method: public __internal_toSnapshot(): CommercePlanJSONSnapshot

Apply this diff:

    slug: this.slug,
    avatar_url: this.avatarUrl,
+   free_trial_days: this.freeTrialDays,
+   free_trial_enabled: this.freeTrialEnabled,
    features: this.features.map(feature => feature.__internal_toSnapshot()),
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/CommercePlan.ts around lines 61 to 63,
the __internal_toSnapshot method is missing the freeTrialDays and
freeTrialEnabled properties in its returned snapshot object. Update the
__internal_toSnapshot method to include these two properties so that the
snapshot output matches the CommercePlanJSONSnapshot type and maintains parity
with the data model.

Comment on lines +30 to 31
eligibleForFreeTrial?: boolean;

Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Align typing and default for eligibleForFreeTrial.

Currently typed as optional and directly assigned, which can propagate undefined. Consider one of:

  • Option A (preferred): make it non-optional boolean and default to false via withDefault.
  • Option B: keep optional but still default to false to simplify consumers (then consider tightening type later).

Option A:

-  eligibleForFreeTrial?: boolean;
+  eligibleForFreeTrial!: boolean;
...
-    this.eligibleForFreeTrial = data.eligible_for_free_trial;
+    this.eligibleForFreeTrial = this.withDefault(data.eligible_for_free_trial, false);

To gauge impact, search call sites:

Also applies to: 55-56


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "eligibleForFreeTrial" packages

Length of output: 494


Align eligibleForFreeTrial typing and default to false

To ensure consumers always get a boolean (never undefined), update both the runtime resource and the TS definitions:

  • In packages/clerk-js/src/core/resources/CommerceSubscription.ts
     // before
  • eligibleForFreeTrial?: boolean;
  • eligibleForFreeTrial!: boolean;

    constructor(data: API.CommerceSubscription) {
  • this.eligibleForFreeTrial = data.eligible_for_free_trial;
  • this.eligibleForFreeTrial = this.withDefault(data.eligible_for_free_trial, false);
    }

- In `packages/types/src/commerce.ts` (around line 1264)  
```diff
 export interface CommerceSubscription {
…
-  eligibleForFreeTrial?: boolean;
+  eligibleForFreeTrial: boolean;
 …
 }

This change makes eligibleForFreeTrial a required boolean with a default of false, removing any need for downstream undefined checks.

📝 Committable suggestion

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

Suggested change
eligibleForFreeTrial?: boolean;
// packages/clerk-js/src/core/resources/CommerceSubscription.ts
export class CommerceSubscription extends Resource {
// …
- eligibleForFreeTrial?: boolean;
+ eligibleForFreeTrial!: boolean;
// …
constructor(data: API.CommerceSubscription) {
super();
// …
- this.eligibleForFreeTrial = data.eligible_for_free_trial;
+ this.eligibleForFreeTrial = this.withDefault(data.eligible_for_free_trial, false);
// …
}
}
Suggested change
eligibleForFreeTrial?: boolean;
// packages/types/src/commerce.ts
export interface CommerceSubscription {
// …
- eligibleForFreeTrial?: boolean;
+ eligibleForFreeTrial: boolean;
// …
}
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/CommerceSubscription.ts around lines 30
to 31, update the eligibleForFreeTrial property to be a required boolean instead
of optional. Set its default value to false in the runtime resource to ensure it
is never undefined. Also, update the TypeScript type definition in
packages/types/src/commerce.ts near line 1264 to reflect eligibleForFreeTrial as
a non-optional boolean. This will guarantee consumers always receive a boolean
value without needing to check for undefined.

Comment on lines +110 to 111
this.freeTrialEndsAt = data.free_trial_ends_at ? unixEpochToDate(data.free_trial_ends_at) : null;
return this;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use nullish check when mapping timestamps.

Avoid truthy checks for numeric timestamps; use a nullish check to prevent edge-case misclassification.

-    this.freeTrialEndsAt = data.free_trial_ends_at ? unixEpochToDate(data.free_trial_ends_at) : null;
+    this.freeTrialEndsAt =
+      data.free_trial_ends_at != null ? unixEpochToDate(data.free_trial_ends_at) : null;
📝 Committable suggestion

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

Suggested change
this.freeTrialEndsAt = data.free_trial_ends_at ? unixEpochToDate(data.free_trial_ends_at) : null;
return this;
this.freeTrialEndsAt =
data.free_trial_ends_at != null ? unixEpochToDate(data.free_trial_ends_at) : null;
return this;
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/CommerceSubscription.ts at lines 110 to
111, replace the truthy check on data.free_trial_ends_at with a nullish check
(e.g., data.free_trial_ends_at != null) to correctly handle numeric timestamps
that could be zero or falsy but valid. This ensures the timestamp is only
considered absent if it is null or undefined, preventing misclassification of
valid zero values.

Comment on lines +95 to +106
{freeTrialEndsAt && plan.freeTrialDays && (
<LineItems.Group variant='tertiary'>
<LineItems.Title
title={localizationKeys('commerce.checkout.totalDueAfterTrial', {
days: plan.freeTrialDays,
})}
/>
<LineItems.Description
text={`${totals.grandTotal?.currencySymbol}${totals.grandTotal?.amountFormatted}`}
/>
</LineItems.Group>
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Handle missing freeTrialDays gracefully

The label always injects plan.freeTrialDays; when it’s null the UI will show “after trial ends in null days”.

Guard the value or fall back to commerce.checkout.totalDueAfterTrial without the days variable.

🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx around lines 95
to 106, the code injects plan.freeTrialDays into the label without checking if
it is null, causing the UI to display "null" days. To fix this, add a
conditional check to only pass the days variable if plan.freeTrialDays is a
valid number; otherwise, render the label without the days variable to avoid
showing "null" in the UI.

Comment on lines +299 to +318
const useSubmitLabel = () => {
const { checkout } = useCheckout();
const { status, totals } = checkout;
const { status, freeTrialEndsAt, totals } = checkout;

if (status === 'needs_initialization') {
return null;
throw new Error('Clerk: Invalid state');
}

if (freeTrialEndsAt) {
return localizationKeys('commerce.startFreeTrial');
}

if (totals.totalDueNow.amount > 0) {
return localizationKeys('commerce.pay', {
amount: `${totals.totalDueNow.currencySymbol}${totals.totalDueNow.amountFormatted}`,
});
}

return localizationKeys('commerce.subscribe');
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

useSubmitLabel can throw at runtime

Throwing when status === 'needs_initialization' propagates to the component tree and crashes the checkout drawer if the hook is called too early.
Return a safe placeholder instead (e.g., '...') until the state stabilises, or guard the hook’s usage.

- if (status === 'needs_initialization') {
-   throw new Error('Clerk: Invalid state');
- }
+ if (status === 'needs_initialization') {
+   return localizationKeys('formButtonPrimary'); // fallback label
+ }

Avoids unhandled exceptions in edge cases.

📝 Committable suggestion

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

Suggested change
const useSubmitLabel = () => {
const { checkout } = useCheckout();
const { status, totals } = checkout;
const { status, freeTrialEndsAt, totals } = checkout;
if (status === 'needs_initialization') {
return null;
throw new Error('Clerk: Invalid state');
}
if (freeTrialEndsAt) {
return localizationKeys('commerce.startFreeTrial');
}
if (totals.totalDueNow.amount > 0) {
return localizationKeys('commerce.pay', {
amount: `${totals.totalDueNow.currencySymbol}${totals.totalDueNow.amountFormatted}`,
});
}
return localizationKeys('commerce.subscribe');
};
const useSubmitLabel = () => {
const { checkout } = useCheckout();
const { status, freeTrialEndsAt, totals } = checkout;
if (status === 'needs_initialization') {
return localizationKeys('formButtonPrimary'); // fallback label
}
if (freeTrialEndsAt) {
return localizationKeys('commerce.startFreeTrial');
}
if (totals.totalDueNow.amount > 0) {
return localizationKeys('commerce.pay', {
amount: `${totals.totalDueNow.currencySymbol}${totals.totalDueNow.amountFormatted}`,
});
}
return localizationKeys('commerce.subscribe');
};
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx around lines
299 to 318, the useSubmitLabel hook throws an error when status is
'needs_initialization', which can crash the component tree if called too early.
Instead of throwing, modify the hook to return a safe placeholder string like
'...' in this state to prevent unhandled exceptions and allow the UI to
stabilize gracefully.

Comment on lines +20 to 25
export const SubscriptionBadge = <T extends { status: CommerceSubscriptionItemResource['status'] }>({
subscription,
elementDescriptor,
}: {
subscription: CommerceSubscriptionItemResource;
subscription: T | { status: 'free_trial' };
elementDescriptor?: ElementDescriptor;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Inconsistent typing for status – consider extending the core enum instead of local work-arounds

The component now unions { status: 'free_trial' } to work around the fact that CommerceSubscriptionStatus doesn’t include "free_trial".
This leaks an ad-hoc type downstream and forces the @ts-expect-error casts later on.

Prefer updating CommerceSubscriptionStatus (and upstream API) to include "free_trial" and keep the prop simply subscription: { status: CommerceSubscriptionStatus }.

This restores compile-time safety and removes the need for the union / ignores.

🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/Subscriptions/badge.tsx around lines 20
to 25, the subscription prop type uses a union to add 'free_trial' status
separately, causing inconsistent typing and requiring @ts-expect-error casts. To
fix this, update the CommerceSubscriptionStatus enum in the core types and
upstream API to include 'free_trial' as a valid status, then change the
subscription prop type to use only { status: CommerceSubscriptionStatus }
without unions. This will ensure consistent typing and remove the need for type
ignores.

Comment on lines +215 to +221
const freeTrialOr = (localizationKey: LocalizationKey): LocalizationKey => {
if (plan?.freeTrialEnabled && topLevelSubscription?.eligibleForFreeTrial) {
return localizationKeys('commerce.startFreeTrial__days', { days: plan.freeTrialDays ?? 0 });
}
return localizationKey;
};

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard against zero / null trial days

freeTrialOr interpolates plan.freeTrialDays ?? 0.
If the backend returns 0 or null, the UI will generate “Start 0-day free trial”, which looks broken.

- return localizationKeys('commerce.startFreeTrial__days', { days: plan.freeTrialDays ?? 0 });
+const days = plan.freeTrialDays ?? 0;
+return days > 0
+  ? localizationKeys('commerce.startFreeTrial__days', { days })
+  : localizationKeys('commerce.startFreeTrial');

This keeps the copy natural when the duration is unknown or disabled.

📝 Committable suggestion

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

Suggested change
const freeTrialOr = (localizationKey: LocalizationKey): LocalizationKey => {
if (plan?.freeTrialEnabled && topLevelSubscription?.eligibleForFreeTrial) {
return localizationKeys('commerce.startFreeTrial__days', { days: plan.freeTrialDays ?? 0 });
}
return localizationKey;
};
const freeTrialOr = (localizationKey: LocalizationKey): LocalizationKey => {
if (plan?.freeTrialEnabled && topLevelSubscription?.eligibleForFreeTrial) {
const days = plan.freeTrialDays ?? 0;
return days > 0
? localizationKeys('commerce.startFreeTrial__days', { days })
: localizationKeys('commerce.startFreeTrial');
}
return localizationKey;
};
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/contexts/components/Plans.tsx around lines 215 to
221, the function freeTrialOr uses plan.freeTrialDays ?? 0, which can produce
"Start 0-day free trial" if freeTrialDays is 0 or null. Update the logic to
check if freeTrialDays is a positive number before interpolating it; if not,
return the original localizationKey to avoid displaying unnatural "0-day" trial
text.

Comment on lines +87 to 88
badge?: React.ReactNode;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Badge slot theming/localization contract; add JSDoc (and consider a theming wrapper).

Good addition. To align with our UI guidelines:

  • Document that callers must pass a localized component and that theming should be handled by that component (or expose a theming slot).
  • Optionally wrap the badge with a dedicated element descriptor to enable stable theming of the slot.

Apply JSDoc on the prop (Lines 87-88):

 interface TitleProps {
   title?: string | LocalizationKey;
   description?: string | LocalizationKey;
   icon?: React.ComponentType;
-  badge?: React.ReactNode;
+  /**
+   * Optional badge element rendered next to the title.
+   * Caller is responsible for localization and theming of the badge contents (e.g. pass <SubscriptionBadge />).
+   */
+  badge?: React.ReactNode;
 }

Optional theming slot wrapper (Lines 124-125) — requires adding a corresponding descriptor (e.g. lineItemsTitleBadge) to descriptors before use:

-          {badge}
+          {/* wrap to expose a stable theming hook */}
+          {badge ? (
+            <Span elementDescriptor={descriptors.lineItemsTitleBadge}>
+              {badge}
+            </Span>
+          ) : null}

To ensure consistency, verify the SubscriptionBadge component uses elementDescriptor and localized strings:

Also applies to: 90-90, 124-125


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify the badge implementation uses elementDescriptor and localization
fd badge.tsx packages | xargs -I {} sh -c 'echo "----- {}"; sed -n "1,200p" "{}" | rg -n "elementDescriptor|localizationKey|localizationKeys" -n || true'

Length of output: 1468


Document badge prop and expose a theming slot

The SubscriptionBadge component already accepts an elementDescriptor and uses localizationKeys, so adding a JSDoc and a wrapper here will align with our UI guidelines.

• At Line 87–88 (TitleProps), add:

 interface TitleProps {
   title?: string | LocalizationKey;
   description?: string | LocalizationKey;
   icon?: React.ComponentType;
-  badge?: React.ReactNode;
+  /**
+   * Optional badge element rendered next to the title.
+   * The caller is responsible for providing a localized and themed badge
+   * (e.g. `<SubscriptionBadge elementDescriptor={descriptors.lineItemsTitleBadge} />`).
+   */
+  badge?: React.ReactNode;
 }

• At the render site (around Line 124–125), wrap badge in a stable theming slot:

-          {badge}
+          {badge ? (
+            <Span elementDescriptor={descriptors.lineItemsTitleBadge}>
+              {badge}
+            </Span>
+          ) : null}

This ensures consumers can target lineItemsTitleBadge in our design system for consistent theming.

🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/elements/LineItems.tsx around lines 87 to 88, add a
JSDoc comment to document the `badge` prop in the `TitleProps` interface
explaining its purpose and usage. Then, around lines 124 to 125 where `badge` is
rendered, wrap it inside a stable theming slot component named
`lineItemsTitleBadge` to expose a theming slot for consistent styling according
to UI guidelines.

@@ -109,6 +109,7 @@ export const useCheckout = (options?: Params): __experimental_UseCheckoutReturn
planPeriod: null,
plan: null,
paymentSource: null,
freeTrialEndsAt: null,
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Good addition: include freeTrialEndsAt in fallback state.
Prevents undefined access and keeps the fallback shape aligned with the resource.

Run this script to confirm types and JSON contracts include the new property everywhere:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Search for types and JSON fields for free trial support..."
rg -n "CommerceCheckoutJSON|CommerceCheckoutResource" -A 40 packages | sed -n '1,200p' || true
rg -n "free_trial_ends_at|freeTrialEndsAt" packages || true

echo "Ensure localization keys exist for trial UI:"
rg -n "badge__trialEndsAt|badge__startsAt" packages/localizations || true

Length of output: 20507


Approved: freeTrialEndsAt Fallback Aligns With Types—Missing Localization Keys

All core types and resources correctly include and map freeTrialEndsAt (JSON: free_trial_ends_at?: number | null; TS: freeTrialEndsAt: Date | null), and the null fallback in useCheckout.ts prevents undefined access. Core resource classes and UI components already guard and render this field.

However, the localization key for “Trial ends” only exists in en-US. Please add badge__trialEndsAt with the appropriate translation to each locale:

  • Location: packages/localizations/src/*.ts
  • Key to add alongside badge__startsAt in each file:
    badge__trialEndsAt: "<localized ‘Trial ends {{ date | shortDate(...) }}’>",
🤖 Prompt for AI Agents
In packages/shared/src/react/hooks/useCheckout.ts at line 112, the
freeTrialEndsAt fallback is correctly set to null, but the localization key
badge__trialEndsAt is missing in all locale files except en-US. To fix this, add
the badge__trialEndsAt key with the appropriate localized string for "Trial ends
{{ date | shortDate(...) }}" alongside badge__startsAt in each localization file
under packages/localizations/src/*.ts.

Comment on lines +440 to 458
/**
* @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change.
* It is advised to pin the SDK version and the clerk-js version to a specific version to avoid breaking changes.
* @example
* ```tsx
* <ClerkProvider clerkJsVersion="x.x.x" />
* ```
*/
freeTrialDays: number | null;
/**
* @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change.
* It is advised to pin the SDK version and the clerk-js version to a specific version to avoid breaking changes.
* @example
* ```tsx
* <ClerkProvider clerkJsVersion="x.x.x" />
* ```
*/
freeTrialEnabled: boolean;
__internal_toSnapshot: () => CommercePlanJSONSnapshot;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Enum drift – add "free_trial" to CommerceSubscriptionStatus

UI components rely on a "free_trial" status yet the source of truth (CommerceSubscriptionStatus) still omits it.
Add the literal to the union so SDK consumers and the UI stay type-safe.

-export type CommerceSubscriptionStatus = 'active' | 'ended' | 'upcoming' | 'past_due';
+export type CommerceSubscriptionStatus =
+  | 'active'
+  | 'ended'
+  | 'upcoming'
+  | 'past_due'
+  | 'free_trial';

Afterward, remove the local union workaround in SubscriptionBadge.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/types/src/commerce.ts around lines 440 to 458, the
CommerceSubscriptionStatus union type is missing the "free_trial" literal, which
causes type-safety issues for UI components relying on this status. Add the
string literal "free_trial" to the CommerceSubscriptionStatus union type to keep
the SDK and UI components consistent and type-safe. After this update, remove
any local union type workarounds related to this status in the SubscriptionBadge
component.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants