Refine self-hosted trial activation result framing

Update the shared trial activation notice model so replayed handoffs reassure against current entitlement state and invalid/unavailable outcomes point back to the secure instance-bound handoff.
This commit is contained in:
rcourtman
2026-03-25 11:37:25 +00:00
parent 9a7ec5c1c3
commit 8380c9aa12
3 changed files with 21 additions and 5 deletions
@@ -277,6 +277,10 @@ The self-hosted activation return notice on `/settings/system-pro` is part of
that same boundary: the `trial` query result is a one-shot handoff outcome and
must be consumed into owned UI state, then removed from the URL rather than
becoming sticky page state across refresh and sharing.
That same shared notice owner must also keep activation-result framing
customer-facing: replayed handoffs should confirm the current entitlement state
rather than reading like a fresh failure, while invalid and unavailable states
should direct the operator back to the secure trial handoff on this instance.
That same hosted owner also applies after Stripe returns to
`/trial-signup/complete`: customer-facing completion failures must stay inside
owned trial UX rather than dropping raw control-plane error strings, and they
@@ -106,6 +106,18 @@ describe('licensePresentation', () => {
title: 'Trial activated',
tone: expect.stringContaining('green'),
});
expect(getTrialActivationNotice('replayed')).toMatchObject({
title: 'Trial already activated',
tone: expect.stringContaining('sky'),
});
expect(getTrialActivationNotice('invalid')).toMatchObject({
title: 'Activation link invalid',
body: expect.stringContaining('fresh secure trial handoff'),
});
expect(getTrialActivationNotice('unavailable')).toMatchObject({
title: 'Activation unavailable',
body: expect.stringContaining('Refresh the billing state below'),
});
expect(getTrialActivationNotice('ineligible')).toMatchObject({
title: 'Trial not available',
tone: expect.stringContaining('red'),
@@ -207,19 +207,19 @@ 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. Start the hosted checkout flow again from this Pulse instance.',
body: 'That activation handoff is invalid or expired. Return to Pulse Pro settings on this instance and start a fresh secure trial handoff.',
};
case 'replayed':
return {
tone: 'border-amber-200 dark:border-amber-900 bg-amber-50 dark:bg-amber-900 text-amber-900 dark:text-amber-100',
title: 'Activation link already used',
body: 'This checkout handoff was already redeemed. Use the current entitlement state below or start a new checkout if needed.',
tone: 'border-sky-200 dark:border-sky-900 bg-sky-50 dark:bg-sky-900 text-sky-900 dark:text-sky-100',
title: 'Trial already activated',
body: 'This activation handoff was already redeemed for this instance. Use the current entitlement state below as the source of truth.',
};
case 'unavailable':
return {
tone: 'border-amber-200 dark:border-amber-900 bg-amber-50 dark:bg-amber-900 text-amber-900 dark:text-amber-100',
title: 'Activation unavailable',
body: 'Pulse could not finish activation right now. Retry the return link from checkout or start the flow again from this instance.',
body: 'Pulse could not finish activation right now. Refresh the billing state below, then retry the return link or start the secure trial handoff again from this instance if needed.',
};
case 'ineligible':
return {