Skip to content

feat(settings): new billing page and paywall view#4293

Merged
dev-rb merged 22 commits into
mainfrom
rahul/feat-settings-billing-and-paywall
Jun 24, 2026
Merged

feat(settings): new billing page and paywall view#4293
dev-rb merged 22 commits into
mainfrom
rahul/feat-settings-billing-and-paywall

Conversation

@dev-rb

@dev-rb dev-rb commented Jun 23, 2026

Copy link
Copy Markdown
Contributor
Screenshot 2026-06-23 at 6 26 25 PM Screenshot 2026-06-24 at 10 55 36 AM Screenshot 2026-06-23 at 6 21 54 PM Screenshot 2026-06-24 at 10 55 57 AM

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7e3a76fc-1af1-4798-8a08-f0c885169477

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The pull request refactors the subscription and billing UI across two areas. First, PaywallComponent is simplified from a multi-step plan-selection flow into a single "Unlock Premium features" screen with a checklist, fixed pricing, "Dismiss" button, and one upgrade/manage CTA. PaywallState gains a TEAMS enum member and restructures PaywallMessages from plain strings to typed PaywallMessageMetadata objects. Second, a new dedicated Billing settings tab is introduced (Billing.tsx) that renders plan status, conditional "Manage" and "Upgrade now" buttons backed by Stripe session calls, and a feature list. The Billing tab is wired into Settings.tsx and the tab configuration, while all billing UI is removed from Account.tsx (including the TeamSubscriptionPill helper).

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess whether it relates to the changeset. Add a description explaining the changes, including the refactoring of the paywall component, the new billing settings page, and the motivation for these changes.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title follows conventional commits format (feat: prefix) and is under 72 characters, accurately describing the main changes of adding a new billing page and refactored paywall view.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the 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.

@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
js/app/packages/app/component/paywall/PaywallComponent.tsx (1)

93-95: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use PaywallMessageMetadata.title in the heading.

The new metadata model makes title required, but the UI still shows a hardcoded header. Wiring the title here keeps per-key copy consistent and avoids metadata drift.

Suggested diff
-            <h2 class="text-2xl text-ink font-semibold">
-              Unlock Premium features
-            </h2>
+            <h2 class="text-2xl text-ink font-semibold">
+              {paywallMetadata()?.title ?? 'Unlock Premium features'}
+            </h2>
🤖 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 `@js/app/packages/app/component/paywall/PaywallComponent.tsx` around lines 93 -
95, The h2 heading in PaywallComponent is using a hardcoded string "Unlock
Premium features" instead of leveraging the PaywallMessageMetadata.title
property. Replace the hardcoded heading text with the title value from
PaywallMessageMetadata to ensure consistency across different paywall
configurations and avoid metadata drift. This aligns with the metadata model
where title is now required.
js/app/packages/app/component/settings/Billing.tsx (1)

73-92: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Surface checkout/portal errors to the user.

Both handlers swallow failures with only console.error. If Stripe session creation fails, the user clicks and nothing happens with no feedback. Consider a toast/error state so the action doesn't appear broken.

🤖 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 `@js/app/packages/app/component/settings/Billing.tsx` around lines 73 - 92,
Both the handleCheckout and handleManage functions catch errors from Stripe API
calls but only log them to console without providing any user-facing feedback,
leaving users uncertain when the action fails. Replace the console.error calls
in both catch blocks with user-visible error handling such as a toast
notification or error state variable that communicates the failure to the user,
ensuring they understand why the checkout or portal session creation did not
proceed.
🤖 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 `@js/app/packages/app/component/settings/Billing.tsx`:
- Line 114: In the Billing.tsx file, the header element has a misspelled
Tailwind CSS utility class. Locate the header with class="flex items-cetner
gap-2" and correct the typo by changing items-cetner to items-center. This will
ensure the flex container properly centers its items vertically as intended.
- Around line 137-183: The Premium upgrade card wrapped in the Show component
with `when={!hasPaid()}` is missing authorization checks that are present on the
"Manage" button. To fix this, add authorization conditions to the Show
component's when prop to gate the upgrade card by the same checks used for the
"Manage" button. Specifically, combine the existing `!hasPaid()` condition with
`canManageSubscription()` and the team role check `(!teamRole() || teamRole()
=== 'owner')` using logical AND operators to ensure only authorized users (with
proper permissions and owner role when applicable) can see and trigger the
`handleCheckout` function.

---

Nitpick comments:
In `@js/app/packages/app/component/paywall/PaywallComponent.tsx`:
- Around line 93-95: The h2 heading in PaywallComponent is using a hardcoded
string "Unlock Premium features" instead of leveraging the
PaywallMessageMetadata.title property. Replace the hardcoded heading text with
the title value from PaywallMessageMetadata to ensure consistency across
different paywall configurations and avoid metadata drift. This aligns with the
metadata model where title is now required.

In `@js/app/packages/app/component/settings/Billing.tsx`:
- Around line 73-92: Both the handleCheckout and handleManage functions catch
errors from Stripe API calls but only log them to console without providing any
user-facing feedback, leaving users uncertain when the action fails. Replace the
console.error calls in both catch blocks with user-visible error handling such
as a toast notification or error state variable that communicates the failure to
the user, ensuring they understand why the checkout or portal session creation
did not proceed.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: da36bdfa-a90e-43cd-8309-72efdf95e03a

📥 Commits

Reviewing files that changed from the base of the PR and between 06e3c13 and d11ec2a.

📒 Files selected for processing (9)
  • js/app/packages/app/component/paywall/Paywall.tsx
  • js/app/packages/app/component/paywall/PaywallComponent.tsx
  • js/app/packages/app/component/settings/Account.tsx
  • js/app/packages/app/component/settings/Billing.tsx
  • js/app/packages/app/component/settings/Settings.tsx
  • js/app/packages/app/component/settings/Team.tsx
  • js/app/packages/core/constant/PaywallState.tsx
  • js/app/packages/core/constant/SettingsState.tsx
  • js/app/packages/core/constant/settingsTabsConfig.tsx


<Surface class="flex flex-col rounded-lg p-4" depth={2}>
<section class="flex flex-col gap-4">
<header class="flex items-cetner gap-2">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Typo in class name: items-cetneritems-center.

The misspelled utility class is a no-op, so this header won't get the intended centering.

🔤 Proposed fix
-          <header class="flex items-cetner gap-2">
+          <header class="flex items-center gap-2">
📝 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
<header class="flex items-cetner gap-2">
<header class="flex items-center gap-2">
🤖 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 `@js/app/packages/app/component/settings/Billing.tsx` at line 114, In the
Billing.tsx file, the header element has a misspelled Tailwind CSS utility
class. Locate the header with class="flex items-cetner gap-2" and correct the
typo by changing items-cetner to items-center. This will ensure the flex
container properly centers its items vertically as intended.

Comment on lines +137 to +183
<Show
when={
canManageSubscription() &&
hasPaid() &&
(!teamRole() || teamRole() === 'owner')
}
>
<Button
class="ml-auto rounded-full bg-active"
size="sm"
depth={2}
variant="base"
onClick={handleManage}
>
Manage
</Button>
</Show>
</header>
<ul class="border-t border-t-edge-muted pt-4 flex flex-wrap gap-4 text-sm text-ink-muted">
<PlanFeatures tier={hasPaid() ? 'premium' : 'free'} />
</ul>
</section>
</Surface>
<Show when={!hasPaid()}>
<Surface class="flex flex-col rounded-lg p-4" depth={2}>
<section class="flex flex-col gap-4">
<header class="flex items-center gap-2">
<div class="flex flex-col">
<h1 class="text-lg font-medium text-ink">Premium</h1>
<p class="text-ink-extra-muted text-xs">$40 per seat / month</p>
</div>

<Button
class="ml-auto rounded-full py-1.5 px-3"
depth={2}
variant="cta"
onClick={handleCheckout}
>
Upgrade now
</Button>
</header>
<ul class="border-t border-t-edge-muted pt-4 flex flex-wrap gap-4 text-sm text-ink-muted">
<PlanFeatures tier="premium" />
</ul>
</section>
</Surface>
</Show>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "Billing.tsx" | head -5

Repository: macro-inc/macro

Length of output: 206


🏁 Script executed:

# Search for canManageSubscription definition
rg "canManageSubscription" -t ts -t tsx --max-count=20

Repository: macro-inc/macro

Length of output: 181


🏁 Script executed:

# Look for handleCheckout implementation
rg "handleCheckout" -t ts -t tsx -B 2 -A 10

Repository: macro-inc/macro

Length of output: 181


🏁 Script executed:

rg "canManageSubscription" --max-count=20

Repository: macro-inc/macro

Length of output: 153


🏁 Script executed:

rg "handleCheckout" -B 2 -A 10

Repository: macro-inc/macro

Length of output: 153


🏁 Script executed:

# Read the Billing.tsx file to understand the full context
cat -n ./js/app/packages/app/component/settings/Billing.tsx | head -200

Repository: macro-inc/macro

Length of output: 7240


🏁 Script executed:

# Look for stripeServiceClient implementation
find . -type f -name "*stripe*client*" -o -name "*stripe*service*"

Repository: macro-inc/macro

Length of output: 222


🏁 Script executed:

# Search for createCheckoutSessionV2 implementation
rg "createCheckoutSessionV2" -B 3 -A 15

Repository: macro-inc/macro

Length of output: 153


🏁 Script executed:

# Look for Stripe endpoints/handlers on the backend
find . -type f -name "*.rs" | xargs grep -l "checkout" 2>/dev/null | head -5

Repository: macro-inc/macro

Length of output: 492


🏁 Script executed:

# Search for WRITE_STRIPE_SUBSCRIPTION permission usage
rg "WRITE_STRIPE_SUBSCRIPTION" -B 2 -A 5

Repository: macro-inc/macro

Length of output: 153


🏁 Script executed:

# Read the checkout session endpoint
cat -n ./rust/cloud-storage/authentication_service/src/api/user/stripe/create_checkout_session_v2.rs

Repository: macro-inc/macro

Length of output: 7949


🏁 Script executed:

# Check the stripe module for authorization
cat -n ./rust/cloud-storage/authentication_service/src/api/user/stripe/mod.rs

Repository: macro-inc/macro

Length of output: 331


🏁 Script executed:

# Search for permission checks on stripe endpoints
rg "WRITE_STRIPE_SUBSCRIPTION\|checkout\|stripe" ./rust/cloud-storage/authentication_service/src/api/user/stripe/ -B 2 -A 3

Repository: macro-inc/macro

Length of output: 153


🏁 Script executed:

# Look for team/role checks in authentication service
rg "team.*owner\|owner.*role" ./rust/cloud-storage/authentication_service/ -B 1 -A 2 | head -50

Repository: macro-inc/macro

Length of output: 153


🏁 Script executed:

# Check the entity_access extractor to understand OwnerTeamRole behavior
rg "OptionalMacroUserTeamExtractor\|OwnerTeamRole" ./rust/cloud-storage/entity_access/ -B 2 -A 5 | head -60

Repository: macro-inc/macro

Length of output: 153


🏁 Script executed:

# Look for how the checkout endpoint is actually secured/restricted
cat -n ./rust/cloud-storage/authentication_service/src/api/user/mod.rs | grep -A 5 -B 5 "checkout"

Repository: macro-inc/macro

Length of output: 639


🏁 Script executed:

# Check if there's any permission enforcement middleware or guards
rg "WRITE_STRIPE\|permission.*check\|authorize" ./rust/cloud-storage/authentication_service/src/ -B 1 -A 3 | head -80

Repository: macro-inc/macro

Length of output: 153


🏁 Script executed:

# Check the OptionalMacroUserTeamExtractor implementation
find ./rust/cloud-storage -name "*.rs" -type f | xargs grep -l "OptionalMacroUserTeamExtractor" | head -3

Repository: macro-inc/macro

Length of output: 428


🏁 Script executed:

# Look at the user permissions middleware
cat -n ./rust/cloud-storage/authentication_service/src/middleware/user_permissions/attach_user_permissions.rs | head -100

Repository: macro-inc/macro

Length of output: 288


🏁 Script executed:

# Search for permission enforcement on stripe endpoints specifically
rg "stripe\|checkout" ./rust/cloud-storage/authentication_service/src/middleware/ -B 3 -A 5

Repository: macro-inc/macro

Length of output: 253


🏁 Script executed:

# Check if there are guards or extractors that enforce WRITE_STRIPE_SUBSCRIPTION
rg "OwnerTeamRole\|from_request\|FromRequest" ./rust/cloud-storage/entity_access/ -B 1 -A 8 | head -80

Repository: macro-inc/macro

Length of output: 153


🏁 Script executed:

# Find entity_access module structure
find ./rust/cloud-storage -type d -name "entity_access"

Repository: macro-inc/macro

Length of output: 188


🏁 Script executed:

# Search for OptionalMacroUserTeamExtractor definition
find ./rust/cloud-storage -name "*.rs" -type f -exec grep -l "struct OptionalMacroUserTeamExtractor\|impl.*OptionalMacroUserTeamExtractor" {} \;

Repository: macro-inc/macro

Length of output: 224


🏁 Script executed:

# Look for axum extractors in entity_access
find ./rust/cloud-storage -path "*entity_access*" -name "*.rs" | xargs grep -l "axum_extractor\|FromRequest" 2>/dev/null

Repository: macro-inc/macro

Length of output: 1021


🏁 Script executed:

# Check what OwnerTeamRole actually validates
rg "OwnerTeamRole" ./rust/cloud-storage -B 3 -A 10 | head -100

Repository: macro-inc/macro

Length of output: 8786


🏁 Script executed:

# Read the team extractor implementation
cat -n ./rust/cloud-storage/entity_access/src/inbound/axum_extractors/team.rs

Repository: macro-inc/macro

Length of output: 6342


🏁 Script executed:

# Look for how OptionalMacroUserTeamExtractor works differently
rg "Optional.*Extractor|impl.*Optional" ./rust/cloud-storage/entity_access/src/inbound/axum_extractors/ -B 3 -A 10

Repository: macro-inc/macro

Length of output: 32907


🏁 Script executed:

# Check if there are any permission enforcement guards on the stripe routes
rg "WRITE_STRIPE\|stripe\|checkout" ./rust/cloud-storage/authentication_service/src/api/user/mod.rs -B 5 -A 5

Repository: macro-inc/macro

Length of output: 153


🏁 Script executed:

# Look for how permissions are attached/enforced on routes
find ./rust/cloud-storage -name "*macro_middleware*" -o -name "*permission*middleware*" | head -5

Repository: macro-inc/macro

Length of output: 191


🏁 Script executed:

# Check whether the permissions extractor/middleware enforces anything
rg "attach_user_permissions\|user_permissions" ./rust/cloud-storage -l | head -5

Repository: macro-inc/macro

Length of output: 153


🏁 Script executed:

# Look for macro_middleware structure and user_permissions
find ./rust/cloud-storage/macro_middleware -type f -name "*.rs" | head -10

Repository: macro-inc/macro

Length of output: 853


🏁 Script executed:

# Check the user/mod.rs more carefully for the full routing and any guard logic
cat -n ./rust/cloud-storage/authentication_service/src/api/user/mod.rs | head -100

Repository: macro-inc/macro

Length of output: 4080


🏁 Script executed:

# Look for any subscription-related business logic or comments
rg "subscription\|checkout\|upgrade" ./rust/cloud-storage/authentication_service/src/ -B 2 -A 2 | head -100

Repository: macro-inc/macro

Length of output: 153


Add authorization checks to the Premium upgrade card for consistency with the "Manage" button.

The checkout endpoint (/stripe/checkoutv2) does not enforce WRITE_STRIPE_SUBSCRIPTION permission or owner-only access server-side. Any authenticated team member can invoke handleCheckout(), even though the "Manage" button is gated by both permission and role checks. Gate the upgrade card by the same conditions to prevent unauthorized members from initiating checkout:

-      <Show when={!hasPaid()}>
+      <Show when={!hasPaid() && canManageSubscription() && (!teamRole() || teamRole() === 'owner')}>
📝 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
<Show
when={
canManageSubscription() &&
hasPaid() &&
(!teamRole() || teamRole() === 'owner')
}
>
<Button
class="ml-auto rounded-full bg-active"
size="sm"
depth={2}
variant="base"
onClick={handleManage}
>
Manage
</Button>
</Show>
</header>
<ul class="border-t border-t-edge-muted pt-4 flex flex-wrap gap-4 text-sm text-ink-muted">
<PlanFeatures tier={hasPaid() ? 'premium' : 'free'} />
</ul>
</section>
</Surface>
<Show when={!hasPaid()}>
<Surface class="flex flex-col rounded-lg p-4" depth={2}>
<section class="flex flex-col gap-4">
<header class="flex items-center gap-2">
<div class="flex flex-col">
<h1 class="text-lg font-medium text-ink">Premium</h1>
<p class="text-ink-extra-muted text-xs">$40 per seat / month</p>
</div>
<Button
class="ml-auto rounded-full py-1.5 px-3"
depth={2}
variant="cta"
onClick={handleCheckout}
>
Upgrade now
</Button>
</header>
<ul class="border-t border-t-edge-muted pt-4 flex flex-wrap gap-4 text-sm text-ink-muted">
<PlanFeatures tier="premium" />
</ul>
</section>
</Surface>
</Show>
<Show
when={
canManageSubscription() &&
hasPaid() &&
(!teamRole() || teamRole() === 'owner')
}
>
<Button
class="ml-auto rounded-full bg-active"
size="sm"
depth={2}
variant="base"
onClick={handleManage}
>
Manage
</Button>
</Show>
</header>
<ul class="border-t border-t-edge-muted pt-4 flex flex-wrap gap-4 text-sm text-ink-muted">
<PlanFeatures tier={hasPaid() ? 'premium' : 'free'} />
</ul>
</section>
</Surface>
<Show when={!hasPaid() && canManageSubscription() && (!teamRole() || teamRole() === 'owner')}>
<Surface class="flex flex-col rounded-lg p-4" depth={2}>
<section class="flex flex-col gap-4">
<header class="flex items-center gap-2">
<div class="flex flex-col">
<h1 class="text-lg font-medium text-ink">Premium</h1>
<p class="text-ink-extra-muted text-xs">$40 per seat / month</p>
</div>
<Button
class="ml-auto rounded-full py-1.5 px-3"
depth={2}
variant="cta"
onClick={handleCheckout}
>
Upgrade now
</Button>
</header>
<ul class="border-t border-t-edge-muted pt-4 flex flex-wrap gap-4 text-sm text-ink-muted">
<PlanFeatures tier="premium" />
</ul>
</section>
</Surface>
</Show>
🤖 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 `@js/app/packages/app/component/settings/Billing.tsx` around lines 137 - 183,
The Premium upgrade card wrapped in the Show component with `when={!hasPaid()}`
is missing authorization checks that are present on the "Manage" button. To fix
this, add authorization conditions to the Show component's when prop to gate the
upgrade card by the same checks used for the "Manage" button. Specifically,
combine the existing `!hasPaid()` condition with `canManageSubscription()` and
the team role check `(!teamRole() || teamRole() === 'owner')` using logical AND
operators to ensure only authorized users (with proper permissions and owner
role when applicable) can see and trigger the `handleCheckout` function.

@dev-rb dev-rb merged commit 60bcfa6 into main Jun 24, 2026
20 checks passed
@dev-rb dev-rb deleted the rahul/feat-settings-billing-and-paywall branch June 24, 2026 15:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant