mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-19 10:33:29 +00:00
Move hosted signup copy into shared contract
This commit is contained in:
parent
aa6dc76092
commit
428596aea3
5 changed files with 95 additions and 19 deletions
|
|
@ -509,9 +509,9 @@ campaign badge copy, and annual summary text must come from the shared
|
|||
plan-definition owners rather than page-local string parsing or hardcoded
|
||||
retail amounts inside hosted pricing/signup screens.
|
||||
The same owner also holds shared hosted commercial copy such as page title,
|
||||
introductory description, common Cloud inclusions, and setup-step guidance
|
||||
when those facts describe the canonical offer rather than one page's local
|
||||
layout.
|
||||
introductory description, common Cloud inclusions, setup-step guidance,
|
||||
hosted-signup section headings, and primary hosted-signup CTA labels when
|
||||
those facts describe the canonical offer rather than one page's local layout.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
import { Show, createMemo, createSignal } from 'solid-js';
|
||||
import { For, Show, createMemo, createSignal } from 'solid-js';
|
||||
import { useLocation } from '@solidjs/router';
|
||||
import { Card } from '@/components/shared/Card';
|
||||
import { PageHeader } from '@/components/shared/PageHeader';
|
||||
import { HostedSignupAPI, type HostedAPIError } from '@/api/hostedSignup';
|
||||
import { getUpgradeActionUrlOrFallback } from '@/stores/license';
|
||||
import { logger } from '@/utils/logger';
|
||||
import { getCloudPlanForTier, getCloudPlanPricePresentation } from '@/utils/cloudPlans';
|
||||
import {
|
||||
HOSTED_SIGNUP_PRESENTATION,
|
||||
getCloudPlanForTier,
|
||||
getCloudPlanPricePresentation,
|
||||
} from '@/utils/cloudPlans';
|
||||
|
||||
type SignupStatus = 'idle' | 'submitting' | 'success' | 'unavailable' | 'error';
|
||||
|
||||
|
|
@ -131,13 +135,15 @@ export default function HostedSignup() {
|
|||
return (
|
||||
<div class="space-y-6">
|
||||
<PageHeader
|
||||
title={`Pulse Cloud ${selectedPlan().name}`}
|
||||
description="Create your managed Pulse Cloud workspace."
|
||||
title={`${HOSTED_SIGNUP_PRESENTATION.pageTitlePrefix} ${selectedPlan().name}`}
|
||||
description={HOSTED_SIGNUP_PRESENTATION.pageDescription}
|
||||
/>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
<Card padding="lg" class="space-y-4">
|
||||
<h2 class="text-lg font-semibold text-base-content">Workspace</h2>
|
||||
<h2 class="text-lg font-semibold text-base-content">
|
||||
{HOSTED_SIGNUP_PRESENTATION.workspaceHeading}
|
||||
</h2>
|
||||
|
||||
<form class="space-y-3" onSubmit={submitSignup}>
|
||||
<label class="block text-sm font-medium text-base-content" for="hosted-email">
|
||||
|
|
@ -173,8 +179,11 @@ export default function HostedSignup() {
|
|||
class="w-full inline-flex items-center justify-center rounded-md bg-blue-600 px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
disabled={!canSubmit()}
|
||||
>
|
||||
<Show when={status() === 'submitting'} fallback="Create Workspace">
|
||||
Creating...
|
||||
<Show
|
||||
when={status() === 'submitting'}
|
||||
fallback={HOSTED_SIGNUP_PRESENTATION.createWorkspaceLabel}
|
||||
>
|
||||
{HOSTED_SIGNUP_PRESENTATION.creatingWorkspaceLabel}
|
||||
</Show>
|
||||
</button>
|
||||
</form>
|
||||
|
|
@ -208,7 +217,9 @@ export default function HostedSignup() {
|
|||
</Card>
|
||||
|
||||
<Card padding="lg" class="space-y-4">
|
||||
<h2 class="text-lg font-semibold text-base-content">Plan</h2>
|
||||
<h2 class="text-lg font-semibold text-base-content">
|
||||
{HOSTED_SIGNUP_PRESENTATION.planHeading}
|
||||
</h2>
|
||||
<div class="rounded-md border border-border bg-surface-alt p-4">
|
||||
<div class="flex items-baseline justify-between gap-3">
|
||||
<div>
|
||||
|
|
@ -239,16 +250,20 @@ export default function HostedSignup() {
|
|||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
<h3 class="text-sm font-semibold text-base-content">Next</h3>
|
||||
<h3 class="text-sm font-semibold text-base-content">
|
||||
{HOSTED_SIGNUP_PRESENTATION.nextHeading}
|
||||
</h3>
|
||||
<ol class="list-decimal space-y-2 pl-5 text-sm text-base-content">
|
||||
<li>Continue through checkout if prompted.</li>
|
||||
<li>Finish sign-in from the email link.</li>
|
||||
<li>Open your workspace and start connecting systems.</li>
|
||||
<For each={HOSTED_SIGNUP_PRESENTATION.nextSteps}>{(step) => <li>{step}</li>}</For>
|
||||
</ol>
|
||||
|
||||
<div class="border-t border-border pt-4">
|
||||
<h3 class="text-sm font-semibold text-base-content">Already signed up?</h3>
|
||||
<p class="mt-1 text-sm text-muted">Request a fresh sign-in link.</p>
|
||||
<h3 class="text-sm font-semibold text-base-content">
|
||||
{HOSTED_SIGNUP_PRESENTATION.existingAccountHeading}
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-muted">
|
||||
{HOSTED_SIGNUP_PRESENTATION.existingAccountDescription}
|
||||
</p>
|
||||
<form class="mt-3 space-y-3" onSubmit={requestMagicLink}>
|
||||
<input
|
||||
id="hosted-magic-link-email"
|
||||
|
|
@ -265,8 +280,11 @@ export default function HostedSignup() {
|
|||
class="w-full inline-flex items-center justify-center rounded-md border border-border bg-surface px-4 py-2 text-sm font-semibold text-base-content transition-colors hover:bg-surface-hover disabled:cursor-not-allowed disabled:opacity-60"
|
||||
disabled={requestingMagicLink()}
|
||||
>
|
||||
<Show when={requestingMagicLink()} fallback="Email Sign-In Link">
|
||||
Sending...
|
||||
<Show
|
||||
when={requestingMagicLink()}
|
||||
fallback={HOSTED_SIGNUP_PRESENTATION.emailSignInLinkLabel}
|
||||
>
|
||||
{HOSTED_SIGNUP_PRESENTATION.sendingSignInLinkLabel}
|
||||
</Show>
|
||||
</button>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ describe('HostedSignup', () => {
|
|||
expect(screen.getByText('Plan')).toBeInTheDocument();
|
||||
expect(screen.getByText('Next')).toBeInTheDocument();
|
||||
expect(screen.getByText('Continue through checkout if prompted.')).toBeInTheDocument();
|
||||
expect(screen.getByText('Already signed up?')).toBeInTheDocument();
|
||||
expect(screen.getByText('Request a fresh sign-in link.')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Email Sign-In Link' })).toBeInTheDocument();
|
||||
|
||||
fireEvent.input(screen.getByLabelText('Work Email'), {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest';
|
|||
import {
|
||||
CLOUD_COMMERCIAL_PRESENTATION,
|
||||
CLOUD_PLAN_BY_TIER,
|
||||
HOSTED_SIGNUP_PRESENTATION,
|
||||
getCloudPlanPricePresentation,
|
||||
} from '@/utils/cloudPlans';
|
||||
|
||||
|
|
@ -48,4 +49,25 @@ describe('cloudPlans', () => {
|
|||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps hosted signup commercial copy in the common contract', () => {
|
||||
expect(HOSTED_SIGNUP_PRESENTATION).toEqual({
|
||||
pageTitlePrefix: 'Pulse Cloud',
|
||||
pageDescription: 'Create your managed Pulse Cloud workspace.',
|
||||
workspaceHeading: 'Workspace',
|
||||
planHeading: 'Plan',
|
||||
nextHeading: 'Next',
|
||||
nextSteps: [
|
||||
'Continue through checkout if prompted.',
|
||||
'Finish sign-in from the email link.',
|
||||
'Open your workspace and start connecting systems.',
|
||||
],
|
||||
existingAccountHeading: 'Already signed up?',
|
||||
existingAccountDescription: 'Request a fresh sign-in link.',
|
||||
createWorkspaceLabel: 'Create Workspace',
|
||||
creatingWorkspaceLabel: 'Creating...',
|
||||
emailSignInLinkLabel: 'Email Sign-In Link',
|
||||
sendingSignInLinkLabel: 'Sending...',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -30,6 +30,21 @@ export interface CloudCommercialPresentation {
|
|||
setupSteps: readonly string[];
|
||||
}
|
||||
|
||||
export interface HostedSignupPresentation {
|
||||
pageTitlePrefix: string;
|
||||
pageDescription: string;
|
||||
workspaceHeading: string;
|
||||
planHeading: string;
|
||||
nextHeading: string;
|
||||
nextSteps: readonly string[];
|
||||
existingAccountHeading: string;
|
||||
existingAccountDescription: string;
|
||||
createWorkspaceLabel: string;
|
||||
creatingWorkspaceLabel: string;
|
||||
emailSignInLinkLabel: string;
|
||||
sendingSignInLinkLabel: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_CLOUD_TIER: CloudTierKey = 'starter';
|
||||
|
||||
export const CLOUD_PLAN_DEFINITIONS: readonly CloudPlanDefinition[] = [
|
||||
|
|
@ -101,6 +116,25 @@ export const CLOUD_COMMERCIAL_PRESENTATION: CloudCommercialPresentation = {
|
|||
],
|
||||
} as const;
|
||||
|
||||
export const HOSTED_SIGNUP_PRESENTATION: HostedSignupPresentation = {
|
||||
pageTitlePrefix: 'Pulse Cloud',
|
||||
pageDescription: 'Create your managed Pulse Cloud workspace.',
|
||||
workspaceHeading: 'Workspace',
|
||||
planHeading: 'Plan',
|
||||
nextHeading: 'Next',
|
||||
nextSteps: [
|
||||
'Continue through checkout if prompted.',
|
||||
'Finish sign-in from the email link.',
|
||||
'Open your workspace and start connecting systems.',
|
||||
],
|
||||
existingAccountHeading: 'Already signed up?',
|
||||
existingAccountDescription: 'Request a fresh sign-in link.',
|
||||
createWorkspaceLabel: 'Create Workspace',
|
||||
creatingWorkspaceLabel: 'Creating...',
|
||||
emailSignInLinkLabel: 'Email Sign-In Link',
|
||||
sendingSignInLinkLabel: 'Sending...',
|
||||
} as const;
|
||||
|
||||
export function parseCloudTier(value?: string | null): CloudTierKey {
|
||||
switch ((value || '').trim().toLowerCase()) {
|
||||
case 'power':
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue