diff --git a/docs/release-control/v6/internal/subsystems/cloud-paid.md b/docs/release-control/v6/internal/subsystems/cloud-paid.md index 85761bffc0..ea2749a75a 100644 --- a/docs/release-control/v6/internal/subsystems/cloud-paid.md +++ b/docs/release-control/v6/internal/subsystems/cloud-paid.md @@ -516,6 +516,10 @@ The same rule applies to self-hosted pricing framing: page title/description, badge copy, primary CTA labels, and adjacent upsell-link copy on `PricingV6.tsx` must come from the shared self-hosted plan-definition owner rather than page-local strings. +The shared license presentation owner also holds self-hosted Pro settings +upsell and trial-ended notice copy for `ProLicensePlanSection.tsx`; that +surface must consume canonical helper notices instead of carrying inline +upgrade copy or local status-tone branches. That same counted-unit boundary also owns the disclosure rule for retail copy: default billing and pricing surfaces should use concise monitored-system copy, while the full counted-unit definition appears only behind explicit disclosure diff --git a/frontend-modern/src/components/Settings/ProLicensePlanSection.tsx b/frontend-modern/src/components/Settings/ProLicensePlanSection.tsx index 6d5cab0c58..57af30c69a 100644 --- a/frontend-modern/src/components/Settings/ProLicensePlanSection.tsx +++ b/frontend-modern/src/components/Settings/ProLicensePlanSection.tsx @@ -2,8 +2,10 @@ import { Component, For, Show } from 'solid-js'; import RefreshCw from 'lucide-solid/icons/refresh-cw'; import { getUpgradeActionUrlOrFallback, licenseLoadError } from '@/stores/license'; import { + getInactiveProUpsellNotice, getLicenseStatusLoadingState, getNoActiveProLicenseState, + getTrialEndedProLicenseNotice, } from '@/utils/licensePresentation'; import { CommercialStatGrid } from './CommercialBillingSections'; @@ -44,6 +46,9 @@ const formatDate = (value?: string | null) => { return date.toLocaleDateString(); }; +const trialEndedNotice = getTrialEndedProLicenseNotice(); +const inactiveProUpsellNotice = getInactiveProUpsellNotice(); + export const ProLicensePlanSection: Component = (props) => ( <> @@ -71,16 +76,16 @@ export const ProLicensePlanSection: Component = (pro )} -
-

Your Pro trial has ended

-

Upgrade to keep Pro features.

+
+

{trialEndedNotice.title}

+

{trialEndedNotice.body}

- View Pro plans + {trialEndedNotice.actionLabel}
@@ -149,18 +154,16 @@ export const ProLicensePlanSection: Component = (pro -
-

Upgrade to Pro

-

- Unlock Pulse Patrol, alert analysis, auto-fix, and more. -

+
+

{inactiveProUpsellNotice.title}

+

{inactiveProUpsellNotice.body}

- View Pro plans + {inactiveProUpsellNotice.actionLabel}
diff --git a/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx b/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx index 163c4e7972..3d28802afa 100644 --- a/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx @@ -466,5 +466,9 @@ describe('ProLicensePanel', () => { expect(proLicensePanelStateSource).not.toContain('startProTrial()'); expect(proLicensePlanSectionSource).toContain('getLicenseStatusLoadingState'); expect(proLicensePlanSectionSource).toContain('getNoActiveProLicenseState'); + expect(proLicensePlanSectionSource).toContain('getTrialEndedProLicenseNotice'); + expect(proLicensePlanSectionSource).toContain('getInactiveProUpsellNotice'); + expect(proLicensePlanSectionSource).not.toContain('Your Pro trial has ended'); + expect(proLicensePlanSectionSource).not.toContain('Unlock Pulse Patrol, alert analysis, auto-fix, and more.'); }); }); diff --git a/frontend-modern/src/utils/__tests__/licensePresentation.test.ts b/frontend-modern/src/utils/__tests__/licensePresentation.test.ts index c4a10a0af9..1705d36f95 100644 --- a/frontend-modern/src/utils/__tests__/licensePresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/licensePresentation.test.ts @@ -14,6 +14,8 @@ import { getLicenseTierLabel, getNoActiveProLicenseState, getOrganizationBillingLicenseStatusLabel, + getInactiveProUpsellNotice, + getTrialEndedProLicenseNotice, getTrialActivationNotice, isDisplayableLicenseFeature, } from '@/utils/licensePresentation'; @@ -33,6 +35,18 @@ describe('licensePresentation', () => { expect(getNoActiveProLicenseState()).toEqual({ text: 'No Pro license is active.', }); + expect(getTrialEndedProLicenseNotice()).toEqual({ + tone: 'border-red-200 dark:border-red-900 bg-red-50 dark:bg-red-900 text-red-900 dark:text-red-100', + title: 'Your Pro trial has ended', + body: 'Upgrade to keep Pro features.', + actionLabel: 'View Pro plans', + }); + expect(getInactiveProUpsellNotice()).toEqual({ + tone: 'border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900 text-amber-800 dark:text-amber-200', + title: 'Upgrade to Pro', + body: 'Unlock Pulse Patrol, alert analysis, auto-fix, and more.', + actionLabel: 'View Pro plans', + }); }); it('returns canonical feature labels', () => { diff --git a/frontend-modern/src/utils/licensePresentation.ts b/frontend-modern/src/utils/licensePresentation.ts index 1dc978be19..27793407c3 100644 --- a/frontend-modern/src/utils/licensePresentation.ts +++ b/frontend-modern/src/utils/licensePresentation.ts @@ -65,6 +65,10 @@ export interface LicenseInlineNotice { body: string; } +export interface LicenseActionNotice extends LicenseInlineNotice { + actionLabel: string; +} + export interface BillingAdminOrganizationBadge { label: string; badgeClass: string; @@ -290,6 +294,20 @@ export const getNoActiveProLicenseState = (): LicenseLoadingStateCopy => ({ text: 'No Pro license is active.', }); +export const getTrialEndedProLicenseNotice = (): LicenseActionNotice => ({ + tone: 'border-red-200 dark:border-red-900 bg-red-50 dark:bg-red-900 text-red-900 dark:text-red-100', + title: 'Your Pro trial has ended', + body: 'Upgrade to keep Pro features.', + actionLabel: 'View Pro plans', +}); + +export const getInactiveProUpsellNotice = (): LicenseActionNotice => ({ + tone: 'border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900 text-amber-800 dark:text-amber-200', + title: 'Upgrade to Pro', + body: 'Unlock Pulse Patrol, alert analysis, auto-fix, and more.', + actionLabel: 'View Pro plans', +}); + export const getOrganizationBillingLicenseStatusLabel = ( status?: Pick | null, ): string => {