mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
Converge Pro license actions on shared Button primitives
This commit is contained in:
parent
51c47a2279
commit
66002e8ba7
8 changed files with 121 additions and 13 deletions
|
|
@ -594,6 +594,10 @@ or other self-hosted uncapped continuity plans.
|
|||
URLs must compose `ButtonLink`. `ProLicensePlanSection.tsx` must not
|
||||
recreate local anchor button shells for purchase returns, private-runtime
|
||||
actions, or plan-comparison CTAs.
|
||||
Self-hosted plan refresh, entitlement retry, activation, and clear-license
|
||||
actions are cloud-paid behaviors, but their button chrome must compose the
|
||||
frontend-primitives `Button` family instead of carrying Pro-license-local
|
||||
primary, secondary, or warning action class strings.
|
||||
18. Add or change paid relay settings and pairing presentation through `frontend-modern/src/components/Settings/RelaySettingsPanel.tsx`, `frontend-modern/src/components/Settings/RelayPairingSection.tsx`, and `frontend-modern/src/components/Settings/useRelaySettingsPanelState.ts`. The retired Dashboard shell must not be restored to carry a Relay onboarding card or equivalent blanket upsell — relay discovery stays inside its owning settings surface.
|
||||
Public demo and other read-only presentation policy states must suppress
|
||||
relay setup and upsell onboarding instead of inviting pairing or commercial
|
||||
|
|
|
|||
|
|
@ -559,6 +559,11 @@ not a replacement status card, CTA band, or page-local nested card.
|
|||
restoring footer-local action shells.
|
||||
Reporting surfaces must use the same primitive for retry, generate, and
|
||||
export actions rather than restoring large local CTA button shells.
|
||||
Self-hosted commercial plan, retry, activation, and clear-key actions follow
|
||||
the same shared Button boundary: commercial surfaces own the labels,
|
||||
entitlement state, and click handlers, while `Button`, `ButtonLink`, and
|
||||
`UpgradeButtonLink` own the primary, outline, warning, and upgrade/link
|
||||
chrome.
|
||||
If a new surface needs a variant that the shared primitive does not expose,
|
||||
extend the primitive and registry guard rather than adding a page-local
|
||||
class string.
|
||||
|
|
|
|||
|
|
@ -936,8 +936,11 @@
|
|||
{ "path": "src/components/Settings/InfrastructureInstallerSection.tsx" },
|
||||
{ "path": "src/components/Settings/InfrastructureSourceManager.tsx" },
|
||||
{ "path": "src/components/Settings/InfrastructureWorkspace.tsx" },
|
||||
{ "path": "src/components/Settings/ProLicensePanel.tsx" },
|
||||
{ "path": "src/components/Settings/ProLicensePlanSection.tsx" },
|
||||
{ "path": "src/components/Settings/ReportingPanel.tsx" },
|
||||
{ "path": "src/components/Settings/ResourcePicker.tsx" },
|
||||
{ "path": "src/components/Settings/SelfHostedCommercialRecoverySection.tsx" },
|
||||
{ "path": "src/components/UpdateConfirmationModal.tsx" },
|
||||
{ "path": "src/components/UpdateProgressModal.tsx" },
|
||||
{ "path": "src/components/Workloads/GuestDrawer.tsx" },
|
||||
|
|
@ -979,6 +982,25 @@
|
|||
"bg-emerald-600 text-white hover:bg-emerald-700"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/ProLicensePanel.tsx",
|
||||
"patterns": [
|
||||
"inline-flex min-h-10 sm:min-h-9 items-center gap-2 px-3 py-2 text-sm font-medium rounded-md border border-border text-base-content hover:bg-surface-hover transition-colors disabled:opacity-60"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/ProLicensePlanSection.tsx",
|
||||
"patterns": [
|
||||
"mt-2 inline-flex min-h-10 sm:min-h-9 items-center gap-2 px-3 py-2 text-xs font-medium rounded-md border border-amber-300 dark:border-amber-700 text-amber-800 dark:text-amber-200 hover:bg-amber-100 dark:hover:bg-amber-800 transition-colors disabled:opacity-60"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/SelfHostedCommercialRecoverySection.tsx",
|
||||
"patterns": [
|
||||
"min-h-10 sm:min-h-9 px-4 py-2.5 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700 transition-colors disabled:opacity-60 disabled:cursor-not-allowed",
|
||||
"min-h-10 sm:min-h-9 px-4 py-2.5 text-sm font-medium rounded-md border border-border text-base-content hover:bg-surface-hover transition-colors disabled:opacity-60 disabled:cursor-not-allowed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/InfrastructureInstallerSection.tsx",
|
||||
"patterns": [
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { ProLicensePlanSection } from './ProLicensePlanSection';
|
|||
import { SelfHostedCommercialRecoverySection } from './SelfHostedCommercialRecoverySection';
|
||||
import { useProLicensePanelState } from './useProLicensePanelState';
|
||||
import { SELF_HOSTED_PRO_BILLING_PRESENTATION } from './selfHostedBillingPresentation';
|
||||
import { Button } from '@/components/shared/Button';
|
||||
import { Subtabs } from '@/components/shared/Subtabs';
|
||||
import {
|
||||
presentationPolicyHidesCommercialSurfaces,
|
||||
|
|
@ -53,14 +54,16 @@ const ProLicensePanelContent: Component = () => {
|
|||
<CommercialBillingShell
|
||||
title={SELF_HOSTED_PRO_BILLING_PRESENTATION.shellTitle}
|
||||
action={
|
||||
<button
|
||||
class="inline-flex min-h-10 sm:min-h-9 items-center gap-2 px-3 py-2 text-sm font-medium rounded-md border border-border text-base-content hover:bg-surface-hover transition-colors disabled:opacity-60"
|
||||
<Button
|
||||
variant="outline"
|
||||
size="settingsAction"
|
||||
class="gap-2"
|
||||
disabled={state.loading()}
|
||||
onClick={state.loadPanelData}
|
||||
>
|
||||
<RefreshCw class={`w-3.5 h-3.5 ${state.loading() ? 'animate-spin' : ''}`} />
|
||||
{SELF_HOSTED_PRO_BILLING_PRESENTATION.refreshLabel}
|
||||
</button>
|
||||
</Button>
|
||||
}
|
||||
loading={false}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Component, For, Show } from 'solid-js';
|
||||
import RefreshCw from 'lucide-solid/icons/refresh-cw';
|
||||
import { ButtonLink } from '@/components/shared/Button';
|
||||
import { Button, ButtonLink } from '@/components/shared/Button';
|
||||
import { UpgradeButtonLink } from '@/components/shared/UpgradeLink';
|
||||
import { licenseEntitlementsLoadError } from '@/stores/licenseEntitlements';
|
||||
import {
|
||||
|
|
@ -356,15 +356,17 @@ export const ProLicensePlanSection: Component<ProLicensePlanSectionProps> = (pro
|
|||
<p class="text-xs text-amber-700 dark:text-amber-300 mt-1">
|
||||
The license server could not be reached. Some features may be temporarily restricted.
|
||||
</p>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
class="mt-2 inline-flex min-h-10 sm:min-h-9 items-center gap-2 px-3 py-2 text-xs font-medium rounded-md border border-amber-300 dark:border-amber-700 text-amber-800 dark:text-amber-200 hover:bg-amber-100 dark:hover:bg-amber-800 transition-colors disabled:opacity-60"
|
||||
variant="warning"
|
||||
size="settingsActionXs"
|
||||
class="mt-2 gap-2"
|
||||
disabled={props.loading}
|
||||
onClick={props.onReload}
|
||||
>
|
||||
<RefreshCw class={`w-3 h-3 ${props.loading ? 'animate-spin' : ''}`} />
|
||||
Retry
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={!licenseEntitlementsLoadError()}>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Component, Show } from 'solid-js';
|
|||
import { formField, controlClass } from '@/components/shared/Form';
|
||||
import { ExternalTextLink } from '@/components/shared/ExternalTextLink';
|
||||
import { FormTextarea } from '@/components/shared/FormTextarea';
|
||||
import { Button } from '@/components/shared/Button';
|
||||
import { SELF_HOSTED_RECOVERY_PRESENTATION } from '@/utils/licensePresentation';
|
||||
import { TERMS_DOC_URL } from '@/utils/docsLinks';
|
||||
import { CommercialSection } from './CommercialBillingSections';
|
||||
|
|
@ -91,24 +92,26 @@ export const SelfHostedCommercialRecoverySection: Component<
|
|||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button
|
||||
class="min-h-10 sm:min-h-9 px-4 py-2.5 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700 transition-colors disabled:opacity-60 disabled:cursor-not-allowed"
|
||||
<Button
|
||||
variant="primary"
|
||||
size="settingsAction"
|
||||
onClick={props.onActivate}
|
||||
disabled={props.activating || !props.licenseKey.trim()}
|
||||
>
|
||||
{props.activating
|
||||
? SELF_HOSTED_RECOVERY_PRESENTATION.activatePendingLabel
|
||||
: SELF_HOSTED_RECOVERY_PRESENTATION.activateIdleLabel}
|
||||
</button>
|
||||
<button
|
||||
class="min-h-10 sm:min-h-9 px-4 py-2.5 text-sm font-medium rounded-md border border-border text-base-content hover:bg-surface-hover transition-colors disabled:opacity-60 disabled:cursor-not-allowed"
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="settingsAction"
|
||||
onClick={props.onClear}
|
||||
disabled={props.clearing || props.loading || !props.hasLicenseDetails}
|
||||
>
|
||||
{props.clearing
|
||||
? SELF_HOSTED_RECOVERY_PRESENTATION.clearPendingLabel
|
||||
: SELF_HOSTED_RECOVERY_PRESENTATION.clearIdleLabel}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
|
|
|||
|
|
@ -1138,6 +1138,16 @@ describe('ProLicensePanel', () => {
|
|||
expect(proLicensePanelSource).toContain('SelfHostedCommercialRecoverySection');
|
||||
expect(selfHostedCommercialRecoverySectionSource).toContain('FormTextarea');
|
||||
expect(selfHostedCommercialRecoverySectionSource).not.toContain('<textarea');
|
||||
expect(selfHostedCommercialRecoverySectionSource).toContain('@/components/shared/Button');
|
||||
expect(selfHostedCommercialRecoverySectionSource).toContain('variant="primary"');
|
||||
expect(selfHostedCommercialRecoverySectionSource).toContain('variant="outline"');
|
||||
expect(selfHostedCommercialRecoverySectionSource).toContain('size="settingsAction"');
|
||||
expect(selfHostedCommercialRecoverySectionSource).not.toContain(
|
||||
'min-h-10 sm:min-h-9 px-4 py-2.5 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700 transition-colors disabled:opacity-60 disabled:cursor-not-allowed',
|
||||
);
|
||||
expect(selfHostedCommercialRecoverySectionSource).not.toContain(
|
||||
'min-h-10 sm:min-h-9 px-4 py-2.5 text-sm font-medium rounded-md border border-border text-base-content hover:bg-surface-hover transition-colors disabled:opacity-60 disabled:cursor-not-allowed',
|
||||
);
|
||||
expect(proLicensePanelSource).toContain('SELF_HOSTED_PRO_BILLING_PRESENTATION');
|
||||
expect(proLicensePanelSource).toContain('value={state.activeSection()}');
|
||||
expect(proLicensePanelSource).toContain('<Subtabs');
|
||||
|
|
@ -1159,6 +1169,12 @@ describe('ProLicensePanel', () => {
|
|||
expect(proLicensePanelStateSource).not.toContain('runStartProTrialAction({');
|
||||
expect(proLicensePanelStateSource).not.toContain('startProTrial()');
|
||||
expect(proLicensePanelStateSource).toContain("'A license or activation key is required'");
|
||||
expect(proLicensePanelSource).toContain('@/components/shared/Button');
|
||||
expect(proLicensePanelSource).toContain('variant="outline"');
|
||||
expect(proLicensePanelSource).toContain('size="settingsAction"');
|
||||
expect(proLicensePanelSource).not.toContain(
|
||||
'inline-flex min-h-10 sm:min-h-9 items-center gap-2 px-3 py-2 text-sm font-medium rounded-md border border-border text-base-content hover:bg-surface-hover transition-colors disabled:opacity-60',
|
||||
);
|
||||
expect(proLicensePlanSectionSource).toContain('getLicenseStatusLoadingState');
|
||||
expect(proLicensePlanSectionSource).toContain('getNoActiveSelfHostedActivationState');
|
||||
expect(proLicensePlanSectionSource).not.toContain('getTrialEndedProLicenseNotice');
|
||||
|
|
@ -1167,7 +1183,13 @@ describe('ProLicensePanel', () => {
|
|||
expect(proLicensePlanSectionSource).toContain('currentPlanSummary.privateRuntimeAction');
|
||||
expect(proLicensePlanSectionSource).toContain('props.activationSuccessSummary');
|
||||
expect(proLicensePlanSectionSource).toContain('summary().actionUrl');
|
||||
expect(proLicensePlanSectionSource).toContain('Button');
|
||||
expect(proLicensePlanSectionSource).toContain('ButtonLink');
|
||||
expect(proLicensePlanSectionSource).toContain('variant="warning"');
|
||||
expect(proLicensePlanSectionSource).toContain('size="settingsActionXs"');
|
||||
expect(proLicensePlanSectionSource).not.toContain(
|
||||
'mt-2 inline-flex min-h-10 sm:min-h-9 items-center gap-2 px-3 py-2 text-xs font-medium rounded-md border border-amber-300 dark:border-amber-700 text-amber-800 dark:text-amber-200 hover:bg-amber-100 dark:hover:bg-amber-800 transition-colors disabled:opacity-60',
|
||||
);
|
||||
expect(proLicensePlanSectionSource).toContain('UpgradeButtonLink');
|
||||
expect(selfHostedCommercialRecoverySectionSource).toContain('ExternalTextLink');
|
||||
expect(selfHostedCommercialRecoverySectionSource).not.toContain('target="_blank"');
|
||||
|
|
|
|||
|
|
@ -254,6 +254,7 @@ import organizationAccessMembersSectionSource from '@/components/Settings/Organi
|
|||
import organizationIncomingSharesSectionSource from '@/components/Settings/OrganizationIncomingSharesSection.tsx?raw';
|
||||
import organizationOutgoingSharesSectionSource from '@/components/Settings/OrganizationOutgoingSharesSection.tsx?raw';
|
||||
import organizationOverviewMembersSectionSource from '@/components/Settings/OrganizationOverviewMembersSection.tsx?raw';
|
||||
import proLicensePanelSource from '@/components/Settings/ProLicensePanel.tsx?raw';
|
||||
import proLicensePlanSectionSource from '@/components/Settings/ProLicensePlanSection.tsx?raw';
|
||||
import reportingPanelSource from '@/components/Settings/ReportingPanel.tsx?raw';
|
||||
import rolesPanelSource from '@/components/Settings/RolesPanel.tsx?raw';
|
||||
|
|
@ -2949,8 +2950,11 @@ describe('shared primitive guardrails', () => {
|
|||
'src/components/Settings/InfrastructureInstallerSection.tsx',
|
||||
'src/components/Settings/InfrastructureSourceManager.tsx',
|
||||
'src/components/Settings/InfrastructureWorkspace.tsx',
|
||||
'src/components/Settings/ProLicensePanel.tsx',
|
||||
'src/components/Settings/ProLicensePlanSection.tsx',
|
||||
'src/components/Settings/ReportingPanel.tsx',
|
||||
'src/components/Settings/ResourcePicker.tsx',
|
||||
'src/components/Settings/SelfHostedCommercialRecoverySection.tsx',
|
||||
'src/components/UpdateConfirmationModal.tsx',
|
||||
'src/components/UpdateProgressModal.tsx',
|
||||
'src/components/Workloads/GuestDrawer.tsx',
|
||||
|
|
@ -2993,6 +2997,25 @@ describe('shared primitive guardrails', () => {
|
|||
'bg-emerald-600 text-white hover:bg-emerald-700',
|
||||
]),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
path: 'src/components/Settings/ProLicensePanel.tsx',
|
||||
patterns: expect.arrayContaining([
|
||||
'inline-flex min-h-10 sm:min-h-9 items-center gap-2 px-3 py-2 text-sm font-medium rounded-md border border-border text-base-content hover:bg-surface-hover transition-colors disabled:opacity-60',
|
||||
]),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
path: 'src/components/Settings/ProLicensePlanSection.tsx',
|
||||
patterns: expect.arrayContaining([
|
||||
'mt-2 inline-flex min-h-10 sm:min-h-9 items-center gap-2 px-3 py-2 text-xs font-medium rounded-md border border-amber-300 dark:border-amber-700 text-amber-800 dark:text-amber-200 hover:bg-amber-100 dark:hover:bg-amber-800 transition-colors disabled:opacity-60',
|
||||
]),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
path: 'src/components/Settings/SelfHostedCommercialRecoverySection.tsx',
|
||||
patterns: expect.arrayContaining([
|
||||
'min-h-10 sm:min-h-9 px-4 py-2.5 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700 transition-colors disabled:opacity-60 disabled:cursor-not-allowed',
|
||||
'min-h-10 sm:min-h-9 px-4 py-2.5 text-sm font-medium rounded-md border border-border text-base-content hover:bg-surface-hover transition-colors disabled:opacity-60 disabled:cursor-not-allowed',
|
||||
]),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
path: 'src/components/Settings/InfrastructureInstallerSection.tsx',
|
||||
patterns: expect.arrayContaining([
|
||||
|
|
@ -3581,6 +3604,30 @@ describe('shared primitive guardrails', () => {
|
|||
);
|
||||
expect(reportingPanelSource).not.toContain('bg-blue-600 text-white hover:bg-blue-700');
|
||||
expect(reportingPanelSource).not.toContain('bg-emerald-600 text-white hover:bg-emerald-700');
|
||||
expect(proLicensePanelSource).toContain('@/components/shared/Button');
|
||||
expect(proLicensePanelSource).toContain('<Button');
|
||||
expect(proLicensePanelSource).toContain('variant="outline"');
|
||||
expect(proLicensePanelSource).toContain('size="settingsAction"');
|
||||
expect(proLicensePanelSource).not.toContain(
|
||||
'inline-flex min-h-10 sm:min-h-9 items-center gap-2 px-3 py-2 text-sm font-medium rounded-md border border-border text-base-content hover:bg-surface-hover transition-colors disabled:opacity-60',
|
||||
);
|
||||
expect(proLicensePlanSectionSource).toContain('@/components/shared/Button');
|
||||
expect(proLicensePlanSectionSource).toContain('<Button');
|
||||
expect(proLicensePlanSectionSource).toContain('variant="warning"');
|
||||
expect(proLicensePlanSectionSource).toContain('size="settingsActionXs"');
|
||||
expect(proLicensePlanSectionSource).not.toContain(
|
||||
'mt-2 inline-flex min-h-10 sm:min-h-9 items-center gap-2 px-3 py-2 text-xs font-medium rounded-md border border-amber-300 dark:border-amber-700 text-amber-800 dark:text-amber-200 hover:bg-amber-100 dark:hover:bg-amber-800 transition-colors disabled:opacity-60',
|
||||
);
|
||||
expect(selfHostedCommercialRecoverySectionSource).toContain('@/components/shared/Button');
|
||||
expect(selfHostedCommercialRecoverySectionSource).toContain('variant="primary"');
|
||||
expect(selfHostedCommercialRecoverySectionSource).toContain('variant="outline"');
|
||||
expect(selfHostedCommercialRecoverySectionSource).toContain('size="settingsAction"');
|
||||
expect(selfHostedCommercialRecoverySectionSource).not.toContain(
|
||||
'min-h-10 sm:min-h-9 px-4 py-2.5 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700 transition-colors disabled:opacity-60 disabled:cursor-not-allowed',
|
||||
);
|
||||
expect(selfHostedCommercialRecoverySectionSource).not.toContain(
|
||||
'min-h-10 sm:min-h-9 px-4 py-2.5 text-sm font-medium rounded-md border border-border text-base-content hover:bg-surface-hover transition-colors disabled:opacity-60 disabled:cursor-not-allowed',
|
||||
);
|
||||
expect(discoveryTabSource).toContain('@/components/shared/Button');
|
||||
expect(discoveryTabSource).toContain('@/components/shared/CopyableCodeRow');
|
||||
expect(discoveryTabSource).toContain('CopyValueButton');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue