mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
Move Pro billing shell copy into shared presentation
This commit is contained in:
@@ -481,6 +481,10 @@ top-level Pulse Pro surface must keep its page-shell title and leading
|
||||
SettingsPanel title aligned so commercial activation, trial, and pricing state
|
||||
do not present as one surface in navigation and a differently named surface in
|
||||
the actual paid settings UI.
|
||||
That same boundary also owns the shell and section framing copy for the
|
||||
self-hosted Pro billing surface. `ProLicensePanel.tsx` must consume shared
|
||||
presentation for its shell title/description, refresh CTA label, and section
|
||||
headings rather than carrying those commercial labels inline.
|
||||
Paid Pulse Pro v5 grandfathering is now part of that same canonical boundary:
|
||||
when a recurring v5 customer migrates into v6, billing persistence,
|
||||
entitlement evaluation, renewal handling, and Pro-license presentation must
|
||||
|
||||
@@ -6,6 +6,7 @@ import { CommercialBillingShell, CommercialSection } from './CommercialBillingSe
|
||||
import { ProLicensePlanSection } from './ProLicensePlanSection';
|
||||
import { SelfHostedCommercialActivationSection } from './SelfHostedCommercialActivationSection';
|
||||
import { useProLicensePanelState } from './useProLicensePanelState';
|
||||
import { SELF_HOSTED_PRO_BILLING_PRESENTATION } from '@/utils/licensePresentation';
|
||||
import { getMonitoredSystemBriefSummary } from '@/utils/monitoredSystemPresentation';
|
||||
|
||||
export const ProLicensePanel: Component = () => {
|
||||
@@ -14,8 +15,8 @@ export const ProLicensePanel: Component = () => {
|
||||
return (
|
||||
<div class="space-y-6">
|
||||
<CommercialBillingShell
|
||||
title="Pulse Pro"
|
||||
description="Manage self-hosted billing, monitored-system limits, and Pulse Pro license status."
|
||||
title={SELF_HOSTED_PRO_BILLING_PRESENTATION.shellTitle}
|
||||
description={SELF_HOSTED_PRO_BILLING_PRESENTATION.shellDescription}
|
||||
icon={<ShieldCheck class="w-5 h-5" />}
|
||||
action={
|
||||
<button
|
||||
@@ -24,15 +25,15 @@ export const ProLicensePanel: Component = () => {
|
||||
onClick={state.loadPanelData}
|
||||
>
|
||||
<RefreshCw class={`w-3.5 h-3.5 ${state.loading() ? 'animate-spin' : ''}`} />
|
||||
Refresh
|
||||
{SELF_HOSTED_PRO_BILLING_PRESENTATION.refreshLabel}
|
||||
</button>
|
||||
}
|
||||
loading={false}
|
||||
>
|
||||
<div class="space-y-6">
|
||||
<CommercialSection
|
||||
title="Plan"
|
||||
description="Review your active plan, expiry, included limits, and paid capabilities."
|
||||
title={SELF_HOSTED_PRO_BILLING_PRESENTATION.planSectionTitle}
|
||||
description={SELF_HOSTED_PRO_BILLING_PRESENTATION.planSectionDescription}
|
||||
>
|
||||
<ProLicensePlanSection
|
||||
commercialMigrationNotice={state.commercialMigrationNotice()}
|
||||
@@ -51,7 +52,7 @@ export const ProLicensePanel: Component = () => {
|
||||
</CommercialSection>
|
||||
|
||||
<CommercialSection
|
||||
title="Usage"
|
||||
title={SELF_HOSTED_PRO_BILLING_PRESENTATION.usageSectionTitle}
|
||||
description={getMonitoredSystemBriefSummary()}
|
||||
>
|
||||
<MonitoredSystemLedgerPanel embedded />
|
||||
|
||||
@@ -458,6 +458,7 @@ describe('ProLicensePanel', () => {
|
||||
expect(proLicensePanelSource).toContain('./useProLicensePanelState');
|
||||
expect(proLicensePanelSource).toContain('./ProLicensePlanSection');
|
||||
expect(proLicensePanelSource).toContain('SelfHostedCommercialActivationSection');
|
||||
expect(proLicensePanelSource).toContain('SELF_HOSTED_PRO_BILLING_PRESENTATION');
|
||||
expect(proLicensePanelSource).not.toContain('createSignal(');
|
||||
expect(proLicensePanelSource).not.toContain('useLocation()');
|
||||
expect(proLicensePanelStateSource).toContain('useLocation');
|
||||
|
||||
@@ -533,10 +533,16 @@ describe('Settings architecture guardrails', () => {
|
||||
);
|
||||
expect(monitoredSystemDefinitionDisclosureSource).not.toContain('summary?: string');
|
||||
expect(monitoredSystemDefinitionDisclosureSource).not.toContain('{props.summary}');
|
||||
expect(proLicensePanelSource).toContain('@/utils/licensePresentation');
|
||||
expect(proLicensePanelSource).toContain('SELF_HOSTED_PRO_BILLING_PRESENTATION');
|
||||
expect(proLicensePanelStateSource).toContain('buildSelfHostedCommercialPlanModel');
|
||||
expect(proLicensePanelStateSource).toContain('loadLicenseStatus(true)');
|
||||
expect(proLicensePanelStateSource).toContain('runStartProTrialAction({');
|
||||
expect(proLicensePanelStateSource).not.toContain('startProTrial()');
|
||||
expect(proLicensePanelSource).not.toContain('title="Pulse Pro"');
|
||||
expect(proLicensePanelSource).not.toContain('description="Manage self-hosted billing');
|
||||
expect(proLicensePanelSource).not.toContain('title="Plan"');
|
||||
expect(proLicensePanelSource).not.toContain('title="Usage"');
|
||||
expect(proLicensePlanSectionSource).toContain('CommercialStatGrid');
|
||||
expect(proLicensePlanSectionSource).toContain('getLicenseStatusLoadingState');
|
||||
expect(monitoredSystemPresentationSource).toContain(
|
||||
@@ -1279,7 +1285,11 @@ describe('Settings architecture guardrails', () => {
|
||||
title,
|
||||
);
|
||||
const allowedTitleExpression =
|
||||
tab === 'system-updates' ? `title={UPDATES_PANEL_COPY.title}` : `title="${title}"`;
|
||||
tab === 'system-updates'
|
||||
? `title={UPDATES_PANEL_COPY.title}`
|
||||
: tab === 'system-billing'
|
||||
? 'title={SELF_HOSTED_PRO_BILLING_PRESENTATION.shellTitle}'
|
||||
: `title="${title}"`;
|
||||
expect(
|
||||
source,
|
||||
`${tab} should keep its leading SettingsPanel title aligned with the shell title`,
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
getTrialActivationNotice,
|
||||
isDisplayableLicenseFeature,
|
||||
SELF_HOSTED_ACTIVATION_PRESENTATION,
|
||||
SELF_HOSTED_PRO_BILLING_PRESENTATION,
|
||||
} from '@/utils/licensePresentation';
|
||||
|
||||
describe('licensePresentation', () => {
|
||||
@@ -61,6 +62,16 @@ describe('licensePresentation', () => {
|
||||
idleActionLabel: 'Start 14-day Pro Trial',
|
||||
},
|
||||
});
|
||||
expect(SELF_HOSTED_PRO_BILLING_PRESENTATION).toEqual({
|
||||
shellTitle: 'Pulse Pro',
|
||||
shellDescription:
|
||||
'Manage self-hosted billing, monitored-system limits, and Pulse Pro license status.',
|
||||
refreshLabel: 'Refresh',
|
||||
planSectionTitle: 'Plan',
|
||||
planSectionDescription:
|
||||
'Review your active plan, expiry, included limits, and paid capabilities.',
|
||||
usageSectionTitle: 'Usage',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns canonical feature labels', () => {
|
||||
|
||||
@@ -102,6 +102,15 @@ export interface SelfHostedActivationPresentation {
|
||||
trial: SelfHostedActivationTrialCopy;
|
||||
}
|
||||
|
||||
export interface SelfHostedProBillingPresentation {
|
||||
shellTitle: string;
|
||||
shellDescription: string;
|
||||
refreshLabel: string;
|
||||
planSectionTitle: string;
|
||||
planSectionDescription: string;
|
||||
usageSectionTitle: string;
|
||||
}
|
||||
|
||||
const GRANDFATHERED_V5_PLAN_LABELS: Record<string, string> = {
|
||||
v5_lifetime_grandfathered: 'V5 Lifetime Grandfathered',
|
||||
v5_pro_monthly_grandfathered: 'V5 Pro Monthly (Grandfathered)',
|
||||
@@ -362,6 +371,17 @@ export const SELF_HOSTED_ACTIVATION_PRESENTATION: SelfHostedActivationPresentati
|
||||
},
|
||||
};
|
||||
|
||||
export const SELF_HOSTED_PRO_BILLING_PRESENTATION: SelfHostedProBillingPresentation = {
|
||||
shellTitle: 'Pulse Pro',
|
||||
shellDescription:
|
||||
'Manage self-hosted billing, monitored-system limits, and Pulse Pro license status.',
|
||||
refreshLabel: 'Refresh',
|
||||
planSectionTitle: 'Plan',
|
||||
planSectionDescription:
|
||||
'Review your active plan, expiry, included limits, and paid capabilities.',
|
||||
usageSectionTitle: 'Usage',
|
||||
};
|
||||
|
||||
export const getOrganizationBillingLicenseStatusLabel = (
|
||||
status?: Pick<LicenseStatus, 'valid' | 'in_grace_period'> | null,
|
||||
): string => {
|
||||
|
||||
Reference in New Issue
Block a user