Skip to content

Conversation

@Blaumaus
Copy link
Member

@Blaumaus Blaumaus commented Feb 1, 2026

Changes

If applicable, please describe what changes were made in this pull request.

Community Edition support

  • Your feature is implemented for the Swetrix Community Edition
  • This PR only updates the Cloud (Enterprise) Edition code (e.g. Paddle webhooks, blog, payouts, etc.)

Database migrations

  • Clickhouse / MySQL migrations added for this PR
  • No table schemas changed in this PR

Documentation

  • You have updated the documentation according to your PR
  • This PR did not change any publicly documented endpoints

Summary by CodeRabbit

  • New Features

    • Billing moved into User Settings with a dedicated Billing tab, subscription management UI, cancellation modal, and usage overview with progress visuals.
    • Simplified billing-frequency control and yearly discount calculation shown on pricing.
  • Removed

    • Legacy standalone Billing page and its separate route/loader/action handlers.
  • Localization

    • Added billing-related translation keys and a billedYearly label; removed old billing section headings.

✏️ Tip: You can customize this high-level summary in your review settings.

@Blaumaus Blaumaus self-assigned this Feb 1, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 1, 2026

Warning

Rate limit exceeded

@Blaumaus has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 17 minutes and 46 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Standalone Billing page removed; billing moved into a new Billing tab inside UserSettings. Pricing components replace radio selectors with a yearly toggle and discount badge; server actions and API calls moved from /billing to /user-settings. Paddle checkout is integrated in UserSettings for subscription actions.

Changes

Cohort / File(s) Summary
Pricing components
web/app/components/pricing/BillingPricing.tsx, web/app/components/pricing/MarketingPricing.tsx
Replaced Headless UI RadioGroup with a button + Switch toggle for billing frequency, added yearly discount calculation/display (rounded percent), changed fetcher/action types to UserSettingsActionData, and updated endpoints to /user-settings.
Removed standalone Billing page
web/app/pages/Billing/index.tsx
Deleted the Billing page component and all related UI/state (Paddle integration, usage charts, modals).
UserSettings — billing integration
web/app/pages/UserSettings/UserSettings.tsx, web/app/routes/user-settings.tsx
Added BILLING tab, Paddle script/init and checkout handlers, usage/metainfo loader integration, MultiProgress usage UI, cancel modal, and new action handlers (preview-subscription-update, change-subscription-plan, get-metainfo) under /user-settings.
Billing route simplified
web/app/routes/billing.tsx
Removed loader/action logic and data types; route now redirects to /user-settings?tab=billing and returns null.
Localization updates
web/public/locales/*.json (en.json, de.json, fr.json, pl.json, uk.json)
Added profileSettings keys for billing tab, descriptions, trial messaging, and pricing.billedYearly; removed older billing title/nextBillDate/inactive keys.
Misc imports/types
package.json (manifest), other touched files
Added _round import usage, ui/Switch import, and introduced UserSettingsActionData type usage in fetchers.

Sequence Diagram(s)

sequenceDiagram
  participant User as Browser (UserSettings UI)
  participant BillingUI as BillingPricing Component
  participant Fetcher as fetcher -> /user-settings (action)
  participant Server as Remix Route (user-settings action)
  participant API as Internal API (user/preview-plan, user/change-plan)

  rect rgba(200,220,255,0.5)
    User->>BillingUI: click "Yearly" / select plan
    BillingUI->>Fetcher: POST preview-subscription-update { planId }
    Fetcher->>Server: action: preview-subscription-update
    Server->>API: POST user/preview-plan { planId }
    API-->>Server: preview data
    Server-->>Fetcher: { success:true, data: preview }
    Fetcher-->>BillingUI: preview response
    BillingUI-->>User: show preview/discount
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 Hoppity hop, the billing found a nook,

Tabs now hold the plan and the book,
Yearly badges gleam with discounted cheer,
Paddle pops up when the checkout is near,
I nibble code and celebrate — a new billing frontier!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description follows the template structure but lacks specific details about what changes were made, leaving the 'Changes' section empty. Add a detailed summary in the 'Changes' section describing the specific modifications made (e.g., components moved, API endpoints updated, new billing tab added).
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Move Billing to user settings' clearly and concisely summarizes the main structural change in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch improvement/billing-in-user-settings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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: 5

🤖 Fix all issues with AI agents
In `@web/app/components/pricing/BillingPricing.tsx`:
- Around line 318-341: The Switch is inert because its onChange is a no-op and
it stops propagation; update the Switch component inside BillingPricing so its
onChange toggles billingFrequency using the same logic as the surrounding button
(call setBillingFrequency with billingFrequency === BillingFrequency.yearly ?
BillingFrequency.monthly : BillingFrequency.yearly), keeping checked bound to
billingFrequency === BillingFrequency.yearly so the control actually toggles
when clicked.

In `@web/app/components/pricing/MarketingPricing.tsx`:
- Around line 161-184: The Switch's onChange is a no-op so clicking the control
does nothing; wire the Switch to the same state updater used by the surrounding
button by calling setBillingFrequency with the toggled value (using
billingFrequency and BillingFrequency enum) inside the Switch's onChange handler
so it mirrors the button's onClick behavior (ensure you don't double-toggle by
making the Switch handler perform the toggle logic itself rather than relying on
event propagation).

In `@web/app/pages/UserSettings/UserSettings.tsx`:
- Around line 293-297: The percentage math for totalUsage and remainingUsage
isn't guarded against maxEventsCount being 0; update the totalUsage calculation
in the UserSettings component to avoid division-by-zero and clamp results: when
computing totalUsage (using maxEventsCount, usageInfo.total and _round) treat a
zero maxEventsCount as a safe base (e.g., treat denominator as at least 1 or if
maxEventsCount === 0 and usageInfo.total > 0 set totalUsage to 100) and then
clamp totalUsage to [0,100]; compute remainingUsage as _round(Math.max(0, 100 -
totalUsage), 2) so it can never be negative or NaN. Apply the same
guard/clamping logic wherever the same block is repeated (the other occurrence
around lines 1418-1455).
- Around line 298-312: The useEffect creating the polling interval for
paddleSetup leaks the timer if the component unmounts before window.Paddle
appears; add a cleanup function to always clear the interval on unmount. In the
useEffect that calls loadScript(PADDLE_JS_URL) and sets const interval =
setInterval(paddleSetup, 200), return a cleanup callback that calls
clearInterval(interval) (and optionally nulls it) so the interval is cleared
regardless of whether paddleSetup ever finds window.Paddle; keep existing
paddleSetup, PADDLE_VENDOR_ID, and setLastEvent usage unchanged.

In `@web/app/routes/user-settings.tsx`:
- Around line 474-514: Validate the planId parsed from formData.get('planId')
before calling billing endpoints: in both the 'preview-subscription-update' and
'change-subscription-plan' branches, parse planId and check that
Number.isFinite(planId) (or !Number.isNaN(planId)) and that it's a positive
integer as required; if invalid, return data<UserSettingsActionData>({ intent,
error: 'Invalid planId' }, { status: 400 }) early instead of calling
serverFetch('user/preview-plan'/'user/change-plan'); keep the existing response
shape and header handling (createHeadersWithCookies) for the success path.
🧹 Nitpick comments (3)
web/app/components/pricing/MarketingPricing.tsx (1)

161-171: Align toggle transition with motion guidelines.

Add a 200ms ease-out transition and respect reduced-motion preferences on the new toggle button.

🎨 Suggested tweak
-                className='flex cursor-pointer items-center gap-2 rounded-lg bg-white/10 px-3 py-2 transition-colors hover:bg-white/20'
+                className='flex cursor-pointer items-center gap-2 rounded-lg bg-white/10 px-3 py-2 transition-colors duration-200 ease-out motion-reduce:transition-none hover:bg-white/20'
As per coding guidelines: Apply default transition of 200ms with ease-out easing for entrances and ease-in for exits; Respect reduced-motion preferences in animations and transitions.
web/app/components/pricing/BillingPricing.tsx (1)

318-327: Match toggle transition to 200ms + reduced-motion.

Apply the standard transition duration/easing and reduced-motion handling for the new toggle button.

🎨 Suggested tweak
-            className='flex cursor-pointer items-center gap-2 rounded-lg bg-gray-100 px-3 py-2 transition-colors hover:bg-gray-200 dark:bg-slate-800 dark:hover:bg-slate-700'
+            className='flex cursor-pointer items-center gap-2 rounded-lg bg-gray-100 px-3 py-2 transition-colors duration-200 ease-out motion-reduce:transition-none hover:bg-gray-200 dark:bg-slate-800 dark:hover:bg-slate-700'
As per coding guidelines: Apply default transition of 200ms with ease-out easing for entrances and ease-in for exits; Respect reduced-motion preferences in animations and transitions.
web/app/pages/UserSettings/UserSettings.tsx (1)

182-191: Use primary indigo accent for the new Billing tab icon.

The Billing icon color should align with the app’s primary accent.

🎨 Suggested tweak
-    case TAB_MAPPING.BILLING:
-      return 'text-emerald-500'
+    case TAB_MAPPING.BILLING:
+      return 'text-indigo-600 dark:text-indigo-500'
As per coding guidelines: Use indigo-600 as the primary accent color in light mode and indigo-500 in dark mode.

@Blaumaus Blaumaus merged commit 8108333 into main Feb 1, 2026
5 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Feb 7, 2026
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant