[PM-34898] feat: Self-hosted support for PremiumUpgradeView#2558
Conversation
|
Great job! No new security vulnerabilities introduced in this pull request |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2558 +/- ##
==========================================
- Coverage 87.16% 86.08% -1.09%
==========================================
Files 1883 2115 +232
Lines 166523 181478 +14955
==========================================
+ Hits 145150 156219 +11069
- Misses 21373 25259 +3886 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Adds self-hosted-aware UI behavior to the Premium upgrade flow so users on self-hosted environments are guided to manage subscriptions via the web vault, and updates ActionCard to support message-only cards.
Changes:
- Add a dismissible self-hosted info banner and hide in-app purchase controls (Upgrade button + Stripe footer) when self-hosted.
- Introduce
isSelfHosted/isBannerDismissedstate + processor logic to derive self-hosted behavior. - Update
ActionCardto allow an optional title and add coverage for the message-only variant.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| BitwardenShared/UI/Billing/PremiumUpgrade/PremiumUpgradeView.swift | Conditionally shows self-hosted banner and hides purchase UI; triggers .appeared effect. |
| BitwardenShared/UI/Billing/PremiumUpgrade/PremiumUpgradeView+ViewInspectorTests.swift | Adds ViewInspector coverage for banner visibility and self-hosted button hiding. |
| BitwardenShared/UI/Billing/PremiumUpgrade/PremiumUpgradeView+SnapshotTests.swift | Adds (disabled) snapshot entries for self-hosted states. |
| BitwardenShared/UI/Billing/PremiumUpgrade/PremiumUpgradeState.swift | Adds self-hosted + banner dismissal state and a computed showSelfHostedBanner. |
| BitwardenShared/UI/Billing/PremiumUpgrade/PremiumUpgradeProcessorTests.swift | Adds tests for .appeared self-host detection and banner dismissal action. |
| BitwardenShared/UI/Billing/PremiumUpgrade/PremiumUpgradeProcessor.swift | Reads environment region on .appeared; handles banner dismissal. |
| BitwardenShared/UI/Billing/PremiumUpgrade/PremiumUpgradeEffect.swift | Adds .appeared effect. |
| BitwardenShared/UI/Billing/PremiumUpgrade/PremiumUpgradeAction.swift | Adds .dismissBannerTapped action. |
| BitwardenShared/UI/Billing/BillingCoordinator.swift | Expands coordinator services to include HasEnvironmentService. |
| BitwardenResources/Localizations/en.lproj/Localizable.strings | Adds banner message localization key/value. |
| BitwardenKit/UI/Platform/Application/Views/ActionCard.swift | Makes title optional and hides it when nil. |
| BitwardenKit/UI/Platform/Application/Views/ActionCard+ViewInspectorTests.swift | Adds test for the nil-title/message-only rendering case. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| override func perform(_ effect: PremiumUpgradeEffect) async { | ||
| switch effect { | ||
| case .appeared: | ||
| state.isSelfHosted = services.environmentService.region == .selfHosted | ||
| case .upgradeNowTapped: | ||
| await createCheckoutSession() |
There was a problem hiding this comment.
isSelfHosted is only set inside the async .appeared effect. Since the view renders with the default isSelfHosted = false before the .task runs, self-hosted users will initially see the cloud layout (price section + Upgrade button + Stripe footer) and then have it removed once .appeared updates state. Consider initializing state.isSelfHosted synchronously in init (or when constructing PremiumUpgradeState) using services.environmentService.region, and then either removing the .appeared effect or keeping it only if you expect the region to change while the view is on-screen.
| processor.state.isSelfHosted = true | ||
| XCTAssertThrowsError( | ||
| try subject.inspect().find(asyncButton: Localizations.upgradeNow), | ||
| ) |
There was a problem hiding this comment.
The self-hosted layout change hides both the Upgrade button and the Stripe footer, but the current test only asserts the button is missing. Add an assertion (or a separate test) that the Stripe footer text (e.g., Localizations.youllGoToStripeSecureCheckoutToCompleteYourPurchase) is also not present when isSelfHosted = true.
| ) | |
| ) | |
| XCTAssertThrowsError( | |
| try subject.inspect().find( | |
| text: Localizations.youllGoToStripeSecureCheckoutToCompleteYourPurchase, | |
| ), | |
| ) |

🎟️ Tracking
https://bitwarden.atlassian.net/browse/PM-34898
📔 Objective
When the user is on a self-hosted server, the Premium Upgrade screen now shows a different layout:
ActionCardbanner above the content card informing the user to manage their Premium subscription via the web vault on a computer.Additionally,
ActionCardwas updated to support an optionaltitle(String?), allowing cards with only a message and no title.📸 Screenshots
Screen.Recording.2026-04-20.at.10.38.14.mov