Skip to content

feat: add Premium upsell to the Licenses page - #30909

Draft
designertyler wants to merge 1 commit into
mainfrom
designertyler/des-22147-licenses-premium-upsell
Draft

designertyler wants to merge 1 commit into
mainfrom
designertyler/des-22147-licenses-premium-upsell

Conversation

@designertyler

Copy link
Copy Markdown
Contributor

When a deployment has no licenses, /deployment/licenses now shows the shared PremiumPaywall card (same one used on Audit, Groups, Appearance, etc.) instead of a one-line "You don't have any licenses!" message. Clicks report a new licenses premium funnel source so this page is attributable in funnel data.

The page also no longer renders a blank area in two cases:

  • The licenses request fails: shows ErrorAlert, no upsell.
  • The user can't read licenses: GET /licenses filters by RBAC and returns [], which previously looked like "no licenses". These users now see an EmptyState explaining they lack permission, not an upsell meant for admins.

Also fixes the Refresh button toasts, which said "Successfully removed license." / "Failed to remove license.", and switches that handler to mutate() callbacks.

Fixes DES-22147

Implementation notes and decisions

Investigation

  • The existing empty state rendered correctly when licenses was [] (enterprise handler returns [] since fix: marshal convertLicenses() into a [] instead of nil #22366), but it was a single line of text with inline links, not the Premium upsell used elsewhere.
  • The view had no branch for a failed query: data stayed undefined after loading, so only the header rendered.
  • GET /api/v2/licenses is only registered in the enterprise router; on an AGPL-only build it returns 404. That case now shows ErrorAlert. A dedicated "open source edition" message was considered and skipped as niche.

Decisions

  • Reuse PremiumPaywall (wired to funnel telemetry) rather than the bare PaywallPremium or a new component.
  • Add PremiumFunnelSourceLicenses rather than reusing direct, so Licenses-page clicks are distinguishable. Generated files updated via make gen targets.
  • Paywall only renders when permissions.viewAllLicenses is true and the list is empty. canViewPremium is therefore always true on that branch.
  • Licenses-specific description copy instead of the default "You need a Premium license to use this feature."; design may want to tune it.

Verification

  • pnpm lint:types, Biome check on the page folder, go build ./codersdk/.
  • Storybook tests for LicensesSettingsPage.stories.tsx and LicensesSettingsPageView.stories.tsx (8 passing). New stories: NoLicensePermission, LoadError; Empty now shows the paywall.
  • frontend-deep-review (8 roles): all checkpoints pass after fixes for paywall copy (TN6), the no-permission state (DF5), and the refresh mutation (DF3).
  • Not run: full make pre-commit / make test (workspace was too slow for a full Go test compile).

Generated by Coder Agents on behalf of @designertyler.

When a deployment has no licenses, /deployment/licenses now shows the
shared PremiumPaywall card instead of a one-line message. Clicks are
attributed to a new "licenses" premium funnel source.

The page also stops rendering a blank area when the licenses request
fails (shows ErrorAlert) or when the user cannot read licenses (shows
an EmptyState). GET /licenses filters by RBAC and returns an empty list
for those users, so they no longer see an upsell meant for admins.

The Refresh button toasts said "removed license"; they now describe the
entitlement refresh.

Fixes DES-22147
@linear-code

linear-code Bot commented Oct 16, 2026

Copy link
Copy Markdown

DES-22147

This branch has not been deployed

No deployments
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