mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-19 10:33:29 +00:00
Clarify v6 self-hosted plans and activation copy
This commit is contained in:
parent
c4142ae115
commit
67a4b89907
6 changed files with 45 additions and 42 deletions
|
|
@ -206,7 +206,7 @@ Community limit enforcement.
|
|||
`/settings/system/billing/usage?details=counting-rules` for explanation and
|
||||
`/settings/system/billing/plan?intent=self_hosted_plan` for plan-selection
|
||||
intent. That plan-selection arrival must reopen an explicit
|
||||
`Compare self-hosted plans` prompt inside Pulse Pro billing so operators can
|
||||
`Compare self-hosted plans` prompt inside Plans & Billing so operators can
|
||||
review the self-hosted upgrade path without falling back to the legacy
|
||||
usage surface or a dead-end placeholder CTA.
|
||||
24. Keep public-demo dashboard bootstrap route-owned on the adjacent
|
||||
|
|
@ -753,7 +753,7 @@ Pulse Account portal URLs, `return_url` parameters, or other portal-entry
|
|||
contract state in the browser once the server owns that handoff boundary.
|
||||
`GET /auth/license-purchase-activate` bridge, which auto-submits into the
|
||||
owned POST activation path; the portal must not render a second manual
|
||||
`Activate in Pulse Pro` step after checkout. Stripe cancel must return
|
||||
`Activate in Plans & Billing` step after checkout. Stripe cancel must return
|
||||
directly to the owned Pulse billing plan route rather than back into the
|
||||
portal. The owned activation callback must accept the signed state, redeem the
|
||||
completed checkout, and return the operator to the canonical billing plan
|
||||
|
|
@ -1122,10 +1122,11 @@ owns the plan/read-model presentation. Future Pro-license work must extend
|
|||
that split instead of pulling route state, entitlement derivation, and retry
|
||||
lifecycle back into the shell component.
|
||||
That owned presentation boundary includes the settings shell itself: the
|
||||
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.
|
||||
top-level self-hosted billing surface must keep its page-shell title and
|
||||
leading SettingsPanel title aligned on the canonical `Plans & Billing` label
|
||||
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
|
||||
|
|
@ -1136,20 +1137,22 @@ header metadata so the nav, shell, and route header do not narrate the same
|
|||
commercial surface differently.
|
||||
That same shared presentation owner also carries the canonical cross-surface
|
||||
referral copy used outside the billing shell itself. When infrastructure or
|
||||
other adjacent settings surfaces need to point operators toward Pulse Pro for
|
||||
billing, monitored-system limits, or license status, they must consume the
|
||||
other adjacent settings surfaces need to point operators toward Plans & Billing
|
||||
for billing, monitored-system limits, or license status, they must consume the
|
||||
settings-owned referral strings from
|
||||
`frontend-modern/src/components/Settings/selfHostedBillingPresentation.ts`
|
||||
instead of drafting route-local commercial guidance or reaching directly into
|
||||
generic commercial helpers from hosted settings routes.
|
||||
That same hosted-settings presentation boundary is explicit about bundle
|
||||
ownership. `frontend-modern/src/components/Settings/selfHostedBillingPresentation.ts`
|
||||
is the canonical settings-shell adapter for self-hosted Pro shell framing and
|
||||
referral copy, while `frontend-modern/src/utils/licensePresentation.ts`
|
||||
remains the shared commercial notice/label owner. Hosted settings surfaces
|
||||
must not import self-hosted billing framing straight from the generic helper
|
||||
module when doing so would reintroduce top-level bundle-init cycles into
|
||||
hosted tenant settings routes.
|
||||
is the canonical settings-shell adapter for self-hosted Plans & Billing shell
|
||||
framing and referral copy, while
|
||||
`frontend-modern/src/utils/licensePresentation.ts` remains the shared
|
||||
commercial notice/label owner for activation, trial, purchase, and recovery
|
||||
language such as `License or Activation Key`. Hosted settings surfaces must
|
||||
not import self-hosted billing framing straight from the generic helper module
|
||||
when doing so would reintroduce top-level bundle-init cycles into hosted
|
||||
tenant settings routes.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ describe('ProLicensePanel', () => {
|
|||
expect(loadCommercialPostureMock).not.toHaveBeenCalled();
|
||||
expect(screen.getByText('License and billing details are hidden')).toBeInTheDocument();
|
||||
expect(screen.getByText(/instead of creating a demo license/i)).toBeInTheDocument();
|
||||
expect(screen.queryByText('Pulse Pro')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Plans & Billing')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Monitored Systems')).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByRole('button', { name: /start 14-day pro trial/i }),
|
||||
|
|
@ -193,7 +193,7 @@ describe('ProLicensePanel', () => {
|
|||
expect(loadLicenseEntitlementsMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(screen.getByText('Pulse Pro')).toBeInTheDocument();
|
||||
expect(screen.getByText('Plans & Billing')).toBeInTheDocument();
|
||||
expect(screen.queryByRole('tab', { name: 'Usage' })).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByRole('button', { name: /start 14-day pro trial/i }),
|
||||
|
|
@ -628,7 +628,7 @@ describe('ProLicensePanel', () => {
|
|||
|
||||
fireEvent.click(screen.getByText('Redeem existing key'));
|
||||
|
||||
fireEvent.input(screen.getByLabelText(/pulse pro key/i), {
|
||||
fireEvent.input(screen.getByLabelText(/license or activation key/i), {
|
||||
target: { value: 'header.payload.signature' },
|
||||
});
|
||||
|
||||
|
|
@ -660,7 +660,7 @@ describe('ProLicensePanel', () => {
|
|||
it.each([
|
||||
{
|
||||
purchase: SELF_HOSTED_PRO_BILLING_PURCHASE_ACTIVATED,
|
||||
title: 'Pulse Pro activated',
|
||||
title: 'Plan activated',
|
||||
actionLabel: null,
|
||||
actionHref: null,
|
||||
},
|
||||
|
|
@ -739,7 +739,7 @@ describe('ProLicensePanel', () => {
|
|||
|
||||
renderPanel();
|
||||
|
||||
expect(screen.getByText('Pulse Pro activated')).toBeInTheDocument();
|
||||
expect(screen.getByText('Plan activated')).toBeInTheDocument();
|
||||
expect(screen.getByRole('link', { name: 'Review plan' })).toHaveAttribute(
|
||||
'href',
|
||||
SELF_HOSTED_PRO_BILLING_PLAN_HREF,
|
||||
|
|
@ -972,7 +972,7 @@ describe('ProLicensePanel', () => {
|
|||
expect(proLicensePanelStateSource).toContain('buildSelfHostedCommercialPlanModel');
|
||||
expect(proLicensePanelStateSource).toContain('runStartProTrialAction({');
|
||||
expect(proLicensePanelStateSource).not.toContain('startProTrial()');
|
||||
expect(proLicensePanelStateSource).toContain("'A Pulse Pro key is required'");
|
||||
expect(proLicensePanelStateSource).toContain("'A license or activation key is required'");
|
||||
expect(proLicensePlanSectionSource).toContain('getLicenseStatusLoadingState');
|
||||
expect(proLicensePlanSectionSource).toContain('getNoActiveProLicenseState');
|
||||
expect(proLicensePlanSectionSource).toContain('getTrialEndedProLicenseNotice');
|
||||
|
|
|
|||
|
|
@ -33,13 +33,13 @@ export interface SelfHostedProBillingPresentation {
|
|||
}
|
||||
|
||||
export const SELF_HOSTED_PRO_BILLING_PRESENTATION: SelfHostedProBillingPresentation = {
|
||||
shellTitle: 'Pulse Pro',
|
||||
shellTitle: 'Plans & Billing',
|
||||
shellDescription:
|
||||
'Manage self-hosted billing, plan features, and Pulse Pro license status.',
|
||||
infrastructureRouteReferral: 'Billing and self-hosted plan features live in Pulse Pro.',
|
||||
'Manage self-hosted billing, plan features, and activation status.',
|
||||
infrastructureRouteReferral: 'Billing and self-hosted plan features live in Plans & Billing.',
|
||||
infrastructureWorkspaceReferral:
|
||||
'Billing, self-hosted plan features, and Pulse Pro license status live in Pulse Pro, not here.',
|
||||
sectionSelectorAriaLabel: 'Pulse Pro billing section',
|
||||
'Billing, self-hosted plan features, and activation status live in Plans & Billing, not here.',
|
||||
sectionSelectorAriaLabel: 'Self-hosted billing section',
|
||||
refreshLabel: 'Refresh',
|
||||
planTabLabel: 'Plan',
|
||||
usageTabLabel: 'Usage',
|
||||
|
|
@ -57,7 +57,7 @@ export const SELF_HOSTED_PRO_BILLING_PRESENTATION: SelfHostedProBillingPresentat
|
|||
'Pulse waits for the session presentation policy before showing license, billing, or usage details.',
|
||||
planSelectionPromptTitle: 'Compare self-hosted plans',
|
||||
planSelectionPromptBody:
|
||||
'Community keeps core monitoring free. Compare Relay and Pro in Pulse Account, then return here with Pulse Pro activated automatically.',
|
||||
'Community keeps core monitoring free. Compare Relay and Pro in Pulse Account, then return here with the new plan activated automatically.',
|
||||
planSelectionPromptActionLabel: 'Compare plans',
|
||||
purchaseActivatedPlanActionLabel: 'Review plan',
|
||||
purchaseCancelledActionLabel: 'Compare plans',
|
||||
|
|
@ -70,5 +70,5 @@ export const SELF_HOSTED_PRO_BILLING_PRESENTATION: SelfHostedProBillingPresentat
|
|||
trialStartPendingActionLabel: 'Starting...',
|
||||
recoverySectionTitle: 'Recovery',
|
||||
recoverySectionDescription:
|
||||
'Use recovery tools only when you already have a Pulse Pro key or need to remove a local key from this instance.',
|
||||
'Use recovery tools only when you already have an activation key or need to remove a local key from this instance.',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -521,7 +521,7 @@ export function useProLicensePanelState() {
|
|||
const handleActivate = async () => {
|
||||
const trimmedKey = licenseKey().trim();
|
||||
if (!trimmedKey) {
|
||||
notificationStore.error('A Pulse Pro key is required');
|
||||
notificationStore.error('A license or activation key is required');
|
||||
return;
|
||||
}
|
||||
setActivating(true);
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ describe('licensePresentation', () => {
|
|||
});
|
||||
expect(SELF_HOSTED_RECOVERY_PRESENTATION).toMatchObject({
|
||||
disclosureLabel: 'Redeem existing key',
|
||||
fieldLabel: 'Pulse Pro Key',
|
||||
fieldLabel: 'License or Activation Key',
|
||||
activateIdleLabel: 'Activate License',
|
||||
clearIdleLabel: 'Clear License',
|
||||
legacyNotice: {
|
||||
|
|
@ -63,13 +63,13 @@ describe('licensePresentation', () => {
|
|||
},
|
||||
});
|
||||
expect(SELF_HOSTED_PRO_BILLING_PRESENTATION).toEqual({
|
||||
shellTitle: 'Pulse Pro',
|
||||
shellTitle: 'Plans & Billing',
|
||||
shellDescription:
|
||||
'Manage self-hosted billing, plan features, and Pulse Pro license status.',
|
||||
infrastructureRouteReferral: 'Billing and self-hosted plan features live in Pulse Pro.',
|
||||
'Manage self-hosted billing, plan features, and activation status.',
|
||||
infrastructureRouteReferral: 'Billing and self-hosted plan features live in Plans & Billing.',
|
||||
infrastructureWorkspaceReferral:
|
||||
'Billing, self-hosted plan features, and Pulse Pro license status live in Pulse Pro, not here.',
|
||||
sectionSelectorAriaLabel: 'Pulse Pro billing section',
|
||||
'Billing, self-hosted plan features, and activation status live in Plans & Billing, not here.',
|
||||
sectionSelectorAriaLabel: 'Self-hosted billing section',
|
||||
refreshLabel: 'Refresh',
|
||||
planTabLabel: 'Plan',
|
||||
usageTabLabel: 'Usage',
|
||||
|
|
@ -87,7 +87,7 @@ describe('licensePresentation', () => {
|
|||
'Pulse waits for the session presentation policy before showing license, billing, or usage details.',
|
||||
planSelectionPromptTitle: 'Compare self-hosted plans',
|
||||
planSelectionPromptBody:
|
||||
'Community keeps core monitoring free. Compare Relay and Pro in Pulse Account, then return here with Pulse Pro activated automatically.',
|
||||
'Community keeps core monitoring free. Compare Relay and Pro in Pulse Account, then return here with the new plan activated automatically.',
|
||||
planSelectionPromptActionLabel: 'Compare plans',
|
||||
purchaseActivatedPlanActionLabel: 'Review plan',
|
||||
purchaseCancelledActionLabel: 'Compare plans',
|
||||
|
|
@ -100,7 +100,7 @@ describe('licensePresentation', () => {
|
|||
trialStartPendingActionLabel: 'Starting...',
|
||||
recoverySectionTitle: 'Recovery',
|
||||
recoverySectionDescription:
|
||||
'Use recovery tools only when you already have a Pulse Pro key or need to remove a local key from this instance.',
|
||||
'Use recovery tools only when you already have an activation key or need to remove a local key from this instance.',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -294,7 +294,7 @@ describe('licensePresentation', () => {
|
|||
});
|
||||
expect(getTrialActivationNotice('invalid')).toMatchObject({
|
||||
title: 'Activation link invalid',
|
||||
body: expect.stringContaining('fresh secure trial handoff'),
|
||||
body: expect.stringContaining('Return to Plans & Billing'),
|
||||
});
|
||||
expect(getTrialActivationNotice('unavailable')).toMatchObject({
|
||||
title: 'Activation unavailable',
|
||||
|
|
@ -309,7 +309,7 @@ describe('licensePresentation', () => {
|
|||
|
||||
it('returns canonical purchase activation notices', () => {
|
||||
expect(getPurchaseActivationNotice('activated')).toMatchObject({
|
||||
title: 'Pulse Pro activated',
|
||||
title: 'Plan activated',
|
||||
tone: expect.stringContaining('green'),
|
||||
});
|
||||
expect(getPurchaseActivationNotice('cancelled')).toMatchObject({
|
||||
|
|
|
|||
|
|
@ -323,7 +323,7 @@ export const getTrialActivationNotice = (result?: string | null): LicenseInlineN
|
|||
return {
|
||||
tone: 'border-red-200 dark:border-red-900 bg-red-50 dark:bg-red-900 text-red-900 dark:text-red-100',
|
||||
title: 'Activation link invalid',
|
||||
body: 'That activation handoff is invalid or expired. Return to Pulse Pro settings on this instance and start a fresh secure trial handoff.',
|
||||
body: 'That activation handoff is invalid or expired. Return to Plans & Billing on this instance and start a fresh secure trial handoff.',
|
||||
};
|
||||
case 'replayed':
|
||||
return {
|
||||
|
|
@ -353,7 +353,7 @@ export const getPurchaseActivationNotice = (result?: string | null): LicenseInli
|
|||
case 'activated':
|
||||
return {
|
||||
tone: 'border-green-200 dark:border-green-900 bg-green-50 dark:bg-green-900 text-green-900 dark:text-green-100',
|
||||
title: 'Pulse Pro activated',
|
||||
title: 'Plan activated',
|
||||
body: 'Pulse finished checkout and activated this instance automatically. The plan state below is live.',
|
||||
};
|
||||
case 'cancelled':
|
||||
|
|
@ -448,8 +448,8 @@ export const getInactiveProUpsellNotice = (): LicenseActionNotice => ({
|
|||
export const SELF_HOSTED_RECOVERY_PRESENTATION: SelfHostedRecoveryPresentation = {
|
||||
disclosureLabel: 'Redeem existing key',
|
||||
disclosureDescription:
|
||||
'Use this only if you already have a Pulse Pro key or need to recover a legacy self-hosted purchase on this instance.',
|
||||
fieldLabel: 'Pulse Pro Key',
|
||||
'Use this only if you already have an activation key or need to recover a legacy self-hosted purchase on this instance.',
|
||||
fieldLabel: 'License or Activation Key',
|
||||
fieldPlaceholder: 'Paste your license key or activation key',
|
||||
helpTextBeforeTerms:
|
||||
'Paste the Pulse v6 activation key shown on the hosted checkout success page. A backup copy is also sent by email, but the hosted success page is the primary handoff. You can also paste a legacy Pulse v5 Pro/Lifetime license key and Pulse will exchange it automatically during activation when migration is available. By activating a license, you agree to the',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue