Harden protected release access guidance
This commit is contained in:
parent
18f4a32990
commit
e8b27022e8
4 changed files with 454 additions and 8 deletions
|
|
@ -191,6 +191,9 @@ describe('DashboardOverviewPage', () => {
|
|||
expect(screen.getByText('Billing price-plan map: missing')).toBeTruthy()
|
||||
expect(screen.getByText('Paddle webhook secret: missing')).toBeTruthy()
|
||||
expect(screen.getByText('Public auth runtime posture: local-or-mixed')).toBeTruthy()
|
||||
expect(screen.getByText('Current release action')).toBeTruthy()
|
||||
expect(screen.getByText('Desktop access is visible, but no packaged target is published yet.')).toBeTruthy()
|
||||
expect(screen.getAllByRole('link', { name: 'Review release notes' })[0]?.getAttribute('href')).toBe('/changelog')
|
||||
expect(screen.getByText('Protected operator quick routes')).toBeTruthy()
|
||||
expect(screen.getByText('Signed-in help lanes')).toBeTruthy()
|
||||
expect(screen.getByText('Protected browser-versus-desktop reality')).toBeTruthy()
|
||||
|
|
@ -396,5 +399,8 @@ describe('DashboardOverviewPage', () => {
|
|||
expect(await screen.findByText('Live authority sync')).toBeTruthy()
|
||||
expect(screen.getByText(/plan changed from local free to live operator/i)).toBeTruthy()
|
||||
expect(screen.getByText(/desktop download access changed from local not entitled to live enabled/i)).toBeTruthy()
|
||||
expect(screen.getByText('Current release action')).toBeTruthy()
|
||||
expect(screen.getByText('Windows package is ready for this account.')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Download Windows package' }).getAttribute('href')).toBe('https://downloads.hypertwist.app/windows.exe')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ describe('DownloadCenterPage', () => {
|
|||
|
||||
expect(screen.getByText(/preserved your Windows selection/i)).toBeTruthy()
|
||||
expect(screen.getAllByText('Requested target').length).toBeGreaterThan(0)
|
||||
expect((await screen.findByRole('link', { name: /download windows/i })).getAttribute('href')).toBe('https://downloads.hypertwist.app/windows.exe')
|
||||
expect((await screen.findByRole('link', { name: 'Download Windows package' })).getAttribute('href')).toBe('https://downloads.hypertwist.app/windows.exe')
|
||||
expect(screen.getByText('Release guide')).toBeTruthy()
|
||||
expect(screen.getByText('First launch follow-through')).toBeTruthy()
|
||||
expect(screen.getByText('Verify the current training lanes')).toBeTruthy()
|
||||
|
|
@ -291,7 +291,7 @@ describe('DownloadCenterPage', () => {
|
|||
</QueryClientProvider>,
|
||||
)
|
||||
|
||||
expect((await screen.findByRole('link', { name: /download windows/i })).getAttribute('href')).toBe('https://downloads.hypertwist.app/windows.exe')
|
||||
expect((await screen.findByRole('link', { name: 'Download Windows package' })).getAttribute('href')).toBe('https://downloads.hypertwist.app/windows.exe')
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(/does not yet have desktop download entitlement/i)).toBeNull()
|
||||
})
|
||||
|
|
@ -316,8 +316,9 @@ describe('DownloadCenterPage', () => {
|
|||
</QueryClientProvider>,
|
||||
)
|
||||
|
||||
expect(await screen.findByText(/fallback release posture/i)).toBeTruthy()
|
||||
expect(screen.getByText(/your signed-in account still resolves to desktop access/i)).toBeTruthy()
|
||||
expect(await screen.findByText('Current release action')).toBeTruthy()
|
||||
expect(await screen.findAllByText(/your signed-in account still resolves to desktop access/i)).toHaveLength(2)
|
||||
expect(screen.getAllByText((_content, node) => node?.textContent === 'Live release authority must recover before package delivery resumes.').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('Live release authority temporarily unavailable').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('What stays intentionally withheld')).toBeTruthy()
|
||||
expect(screen.getAllByRole('link', { name: 'Open dashboard' }).every((link) => link.getAttribute('href') === '/app')).toBe(true)
|
||||
|
|
|
|||
|
|
@ -213,6 +213,9 @@ describe('protected app pages', () => {
|
|||
expect(await screen.findByText('Release targets')).toBeTruthy()
|
||||
expect(await screen.findByText('Version: 1.0.0')).toBeTruthy()
|
||||
expect(screen.getAllByText('Requested target').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Current release action')).toBeTruthy()
|
||||
expect(screen.getByText('Windows package is ready for this account.')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Download Windows package' }).getAttribute('href')).toBe('https://downloads.hypertwist.app/windows.exe')
|
||||
expect(screen.getByText('Current packaged desktop proof')).toBeTruthy()
|
||||
expect(screen.getAllByText('Packaged validation passed').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Protected operator quick routes')).toBeTruthy()
|
||||
|
|
@ -232,14 +235,17 @@ describe('protected app pages', () => {
|
|||
renderPage(<AccountPage />, '/app/account')
|
||||
|
||||
expect(await screen.findByText('Session profile')).toBeTruthy()
|
||||
expect(await screen.findByRole('link', { name: 'Download Windows package' })).toBeTruthy()
|
||||
expect(screen.getByText('Current packaged desktop proof')).toBeTruthy()
|
||||
expect(screen.getByText('Packaged validation passed')).toBeTruthy()
|
||||
expect(screen.getByText('Protected browser-versus-desktop reality')).toBeTruthy()
|
||||
expect(screen.getByText('Why the browser is intentionally narrower')).toBeTruthy()
|
||||
expect(screen.getByText('Current simulator control ownership')).toBeTruthy()
|
||||
expect(screen.getByText('Current release action')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Download Windows package' }).getAttribute('href')).toBe('https://downloads.hypertwist.app/windows.exe')
|
||||
expect(screen.getAllByText(/classic-wca-keyboard\/v1/i).length).toBeGreaterThan(0)
|
||||
expect(screen.getByText(byExactTextContent('Billing source: session', 'LI'))).toBeTruthy()
|
||||
expect(await screen.findByText(byExactTextContent('Manifest viewer access: active', 'LI'))).toBeTruthy()
|
||||
expect(await screen.findAllByText(byExactTextContent('Manifest viewer access: active', 'LI'))).toHaveLength(2)
|
||||
expect(await screen.findByText(byExactTextContent('Windows: Primary shipping lane (Version 1.0.0, Channel candidate)', 'LI'))).toBeTruthy()
|
||||
expect(screen.getAllByRole('link', { name: 'Open downloads' }).every((link) => link.getAttribute('href') === '/app/downloads')).toBe(true)
|
||||
expect(screen.getByRole('link', { name: 'Get help' }).getAttribute('href')).toBe('/support?topic=operator-access')
|
||||
|
|
@ -272,8 +278,8 @@ describe('protected app pages', () => {
|
|||
|
||||
expect(await screen.findByText('Session profile')).toBeTruthy()
|
||||
expect(await screen.findByText(byExactTextContent('Manifest viewer authenticated: yes', 'LI'))).toBeTruthy()
|
||||
expect(await screen.findByText(byExactTextContent('Manifest viewer plan: operator', 'LI'))).toBeTruthy()
|
||||
expect(await screen.findByText(byExactTextContent('Manifest viewer access: active', 'LI'))).toBeTruthy()
|
||||
expect(await screen.findAllByText(byExactTextContent('Manifest viewer plan: operator', 'LI'))).toHaveLength(2)
|
||||
expect(await screen.findAllByText(byExactTextContent('Manifest viewer access: active', 'LI'))).toHaveLength(2)
|
||||
expect(screen.getByText(/bounded fallback metadata until auth-server release authority returns/i)).toBeTruthy()
|
||||
})
|
||||
|
||||
|
|
@ -376,4 +382,99 @@ describe('protected app pages', () => {
|
|||
expect(screen.getByText(/desktop download access changed from local not entitled to live enabled/i)).toBeTruthy()
|
||||
expect(screen.getByText(/access status changed from local session-default to live active/i)).toBeTruthy()
|
||||
})
|
||||
|
||||
it('turns protected non-entitled account posture into explicit checkout guidance when live commerce targets exist', async () => {
|
||||
mockUsePlatformAuth.mockReturnValue({
|
||||
user: {
|
||||
id: 'viewer-1',
|
||||
name: 'Viewer',
|
||||
email: 'viewer@hypertwist.app',
|
||||
authMethod: 'email',
|
||||
plan: 'free',
|
||||
role: 'viewer',
|
||||
canDownload: false,
|
||||
billing: {
|
||||
source: 'billing-state',
|
||||
accessStatus: 'payment_required',
|
||||
canDownload: false,
|
||||
lastEventType: 'transaction.payment_failed',
|
||||
},
|
||||
},
|
||||
superTokensConfigured: true,
|
||||
invalidateSession: (...args: unknown[]) => mockInvalidateSession(...args),
|
||||
reconcileReleaseAuthority: vi.fn(),
|
||||
releaseAuthoritySyncItems: [],
|
||||
})
|
||||
|
||||
mockGetReleaseManifest.mockResolvedValue({
|
||||
ok: true,
|
||||
manifest: {
|
||||
generated_at: '2026-06-23T12:00:00.000Z',
|
||||
support_email: 'hello@hypertwist.app',
|
||||
public_docs_url: 'https://docs.hypertwist.app',
|
||||
release_notes_url: 'https://notes.hypertwist.app',
|
||||
corresponding_source_url: 'https://hypertwist.app/open-source/source.zip',
|
||||
open_source_repo_url: 'https://github.com/hypertwist/hypertwist',
|
||||
commerce: {
|
||||
operator_checkout_url: 'https://buy.paddle.com/operator-live',
|
||||
studio_checkout_url: 'https://buy.paddle.com/studio-live',
|
||||
plan_price_operator: '$29/mo',
|
||||
plan_price_studio: '$99/mo',
|
||||
},
|
||||
viewer: {
|
||||
authenticated: true,
|
||||
canDownload: false,
|
||||
plan: 'free',
|
||||
role: 'viewer',
|
||||
accessStatus: 'payment_required',
|
||||
},
|
||||
platforms: [
|
||||
{
|
||||
platform_key: 'windows',
|
||||
platform: 'Windows',
|
||||
subtitle: 'Primary shipping lane',
|
||||
details: 'Current packaged validation is strongest on the Windows Unreal lane.',
|
||||
configured: true,
|
||||
channel: 'candidate',
|
||||
version: '1.0.1',
|
||||
build_id: 'win64-1001',
|
||||
published_at: '2026-06-23T00:00:00.000Z',
|
||||
file_name: 'HyperTwist-Windows.zip',
|
||||
file_size_bytes: 1048576,
|
||||
checksum_sha256: 'abc124',
|
||||
download_url: null,
|
||||
download_available: false,
|
||||
validation_summary: {
|
||||
lane: 'Windows Unreal packaged validation',
|
||||
result: 'passed',
|
||||
generated_at: '2026-06-23T01:43:08.7625247Z',
|
||||
configuration: 'Development',
|
||||
skip_build: true,
|
||||
smoke_map_count: 2,
|
||||
smoke_maps: [
|
||||
{
|
||||
map_url: '/Game/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining',
|
||||
label: 'Magic120Cell dedicated-family training map',
|
||||
result: 'passed',
|
||||
},
|
||||
{
|
||||
map_url: '/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube5DTraining',
|
||||
label: 'MagicCube5D dedicated-family training map',
|
||||
result: 'passed',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
renderPage(<AccountPage />, '/app/account')
|
||||
|
||||
expect(await screen.findByText('Current release action')).toBeTruthy()
|
||||
expect(await screen.findByText('Desktop package delivery still requires billing or operator provisioning.')).toBeTruthy()
|
||||
expect((await screen.findByRole('link', { name: 'Open operator checkout' })).getAttribute('href')).toBe('https://buy.paddle.com/operator-live')
|
||||
expect(screen.getByRole('link', { name: 'Open studio checkout' }).getAttribute('href')).toBe('https://buy.paddle.com/studio-live')
|
||||
expect(screen.getByRole('link', { name: 'Open pricing' }).getAttribute('href')).toBe('/pricing')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -10,7 +10,13 @@ import { ReleaseValidationSummary } from '../components/ui/ReleaseValidationSumm
|
|||
import { resolvePublicLaunchStatusSummary } from '../public-launch'
|
||||
import { downloadTargets, launchReadiness, mplSourceUrl, openSourceRepoUrl, publicDocsUrl, releaseNotesUrl } from '../site-config'
|
||||
import { buildReleaseMetadataItems, resolveReleaseManifestView, type ReleaseManifestView } from '../release-manifest'
|
||||
import { buildProtectedDownloadPath, buildSupportPath, getDownloadPlatformLabel, normalizeDownloadPlatform } from '../site-routes'
|
||||
import {
|
||||
buildProtectedDownloadPath,
|
||||
buildSupportPath,
|
||||
getDownloadPlatformLabel,
|
||||
isExternalHref,
|
||||
normalizeDownloadPlatform,
|
||||
} from '../site-routes'
|
||||
import {
|
||||
browserDesktopRealityCards,
|
||||
controlProfileRosterCards,
|
||||
|
|
@ -219,6 +225,21 @@ type ProtectedSupportTopicGuide = (typeof supportTopicDirectory)[number] & {
|
|||
actions: readonly ProtectedSupportTopicRoute[]
|
||||
}
|
||||
|
||||
type ProtectedReleaseAction = {
|
||||
label: string
|
||||
href: string
|
||||
primary?: boolean
|
||||
}
|
||||
|
||||
type ProtectedReleaseActionSurface = {
|
||||
badge: string
|
||||
tone: 'success' | 'info'
|
||||
title: string
|
||||
summary: string
|
||||
items: readonly string[]
|
||||
actions: readonly ProtectedReleaseAction[]
|
||||
}
|
||||
|
||||
function buildProtectedSupportTopicActions(topicKey: string): readonly ProtectedSupportTopicRoute[] {
|
||||
switch (topicKey) {
|
||||
case 'launch-readiness':
|
||||
|
|
@ -249,6 +270,260 @@ const protectedSupportTopicGuides: readonly ProtectedSupportTopicGuide[] = suppo
|
|||
actions: buildProtectedSupportTopicActions(topic.topicKey),
|
||||
}))
|
||||
|
||||
function ProtectedActionLink({
|
||||
action,
|
||||
}: {
|
||||
action: ProtectedReleaseAction
|
||||
}) {
|
||||
const className = action.primary ? 'button button--primary' : 'button button--ghost'
|
||||
|
||||
if (isExternalHref(action.href)) {
|
||||
return (
|
||||
<a className={className} href={action.href} target="_blank" rel="noreferrer">
|
||||
{action.label}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Link className={className} to={action.href}>
|
||||
{action.label}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
function describeReleaseTarget(
|
||||
platform: ReleaseManifestView['platforms'][number] | null | undefined,
|
||||
) {
|
||||
if (!platform) {
|
||||
return 'No desktop target selected yet'
|
||||
}
|
||||
|
||||
const metadata: string[] = []
|
||||
if (platform.version) {
|
||||
metadata.push(platform.version)
|
||||
}
|
||||
if (platform.channel) {
|
||||
metadata.push(platform.channel)
|
||||
}
|
||||
|
||||
return metadata.length > 0
|
||||
? `${platform.platform} (${metadata.join(', ')})`
|
||||
: platform.platform
|
||||
}
|
||||
|
||||
function buildProtectedCheckoutActions(releaseManifest: ReleaseManifestView) {
|
||||
const operatorCheckoutUrl = releaseManifest.commerce?.operator_checkout_url || null
|
||||
const studioCheckoutUrl = releaseManifest.commerce?.studio_checkout_url || null
|
||||
const preferredCheckoutUrl = releaseManifest.viewer.plan === 'studio'
|
||||
? (studioCheckoutUrl || operatorCheckoutUrl)
|
||||
: (operatorCheckoutUrl || studioCheckoutUrl)
|
||||
|
||||
const preferredCheckoutLabel = preferredCheckoutUrl === studioCheckoutUrl
|
||||
? 'Open studio checkout'
|
||||
: 'Open operator checkout'
|
||||
|
||||
const secondaryCheckoutUrl = [operatorCheckoutUrl, studioCheckoutUrl]
|
||||
.find((candidate) => candidate && candidate !== preferredCheckoutUrl) || null
|
||||
|
||||
const secondaryCheckoutLabel = secondaryCheckoutUrl === studioCheckoutUrl
|
||||
? 'Open studio checkout'
|
||||
: 'Open operator checkout'
|
||||
|
||||
return [
|
||||
preferredCheckoutUrl ? {
|
||||
label: preferredCheckoutLabel,
|
||||
href: preferredCheckoutUrl,
|
||||
primary: true,
|
||||
} : null,
|
||||
secondaryCheckoutUrl ? {
|
||||
label: secondaryCheckoutLabel,
|
||||
href: secondaryCheckoutUrl,
|
||||
} : null,
|
||||
].filter((action): action is ProtectedReleaseAction => action != null)
|
||||
}
|
||||
|
||||
function buildProtectedReleaseActionSurface({
|
||||
releaseManifest,
|
||||
requestedPlatform,
|
||||
releaseAuthorityUnavailable,
|
||||
}: {
|
||||
releaseManifest: ReleaseManifestView
|
||||
requestedPlatform?: string | null
|
||||
releaseAuthorityUnavailable: boolean
|
||||
}): ProtectedReleaseActionSurface {
|
||||
const configuredTargets = releaseManifest.platforms.filter((platform) => platform.configured)
|
||||
const requestedTarget = requestedPlatform
|
||||
? (releaseManifest.platforms.find((platform) => platform.platform_key === requestedPlatform) || null)
|
||||
: null
|
||||
const downloadableTargets = releaseManifest.platforms.filter((platform) => Boolean(platform.download_url))
|
||||
const primaryDownloadTarget = downloadableTargets.find((platform) => (
|
||||
requestedTarget ? platform.platform_key === requestedTarget.platform_key : platform.platform_key === 'windows'
|
||||
)) || downloadableTargets[0] || null
|
||||
const supportEmail = releaseManifest.support_email || 'hello@hypertwist.app'
|
||||
const manifestPlan = releaseManifest.viewer.plan || 'unresolved'
|
||||
const manifestAccessStatus = releaseManifest.viewer.accessStatus || 'not resolved yet'
|
||||
const configuredTargetsLabel = `${configuredTargets.length}/${releaseManifest.platforms.length} configured`
|
||||
|
||||
if (releaseAuthorityUnavailable) {
|
||||
if (releaseManifest.viewer.canDownload) {
|
||||
return {
|
||||
badge: 'Fallback release posture',
|
||||
tone: 'info',
|
||||
title: 'Live release authority must recover before package delivery resumes.',
|
||||
summary:
|
||||
'Your signed-in account still resolves to desktop access, but this protected lane intentionally withholds direct downloads until the auth server returns.',
|
||||
items: [
|
||||
`Manifest viewer plan: ${manifestPlan}`,
|
||||
`Manifest viewer access: ${manifestAccessStatus}`,
|
||||
`Protected target posture: ${requestedTarget ? describeReleaseTarget(requestedTarget) : 'Windows default'}`,
|
||||
`Configured release targets: ${configuredTargetsLabel}`,
|
||||
`Support contact: ${supportEmail}`,
|
||||
],
|
||||
actions: [
|
||||
{ label: 'Open downloads', href: buildProtectedDownloadPath('windows'), primary: true },
|
||||
{ label: 'Review notices', href: '/app/notices' },
|
||||
{ label: 'Get help', href: buildSupportPath('operator-access') },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
badge: 'Release authority degraded',
|
||||
tone: 'info',
|
||||
title: 'Wait for live release authority before trusting upgrade or delivery posture.',
|
||||
summary:
|
||||
'This protected lane is still useful for orientation, but checkout, entitlement, and package-delivery decisions should wait until the auth server is back.',
|
||||
items: [
|
||||
`Manifest viewer plan: ${manifestPlan}`,
|
||||
`Manifest viewer access: ${manifestAccessStatus}`,
|
||||
`Configured release targets: ${configuredTargetsLabel}`,
|
||||
`Support contact: ${supportEmail}`,
|
||||
],
|
||||
actions: [
|
||||
{ label: 'Open pricing', href: '/pricing', primary: true },
|
||||
{ label: 'Review notices', href: '/app/notices' },
|
||||
{ label: 'Get help', href: buildSupportPath('operator-access') },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
if (releaseManifest.viewer.canDownload && primaryDownloadTarget?.download_url) {
|
||||
const primaryActionTarget = requestedTarget && requestedTarget.configured === false
|
||||
? primaryDownloadTarget
|
||||
: (requestedTarget || primaryDownloadTarget)
|
||||
|
||||
return {
|
||||
badge: 'Download ready',
|
||||
tone: 'success',
|
||||
title: requestedTarget && requestedTarget.configured === false
|
||||
? `${requestedTarget.platform} is not published yet, but another entitled package is ready.`
|
||||
: `${primaryActionTarget.platform} package is ready for this account.`,
|
||||
summary: requestedTarget && requestedTarget.configured === false
|
||||
? `Your requested ${requestedTarget.platform} lane is not configured yet, but the protected release lane can still deliver ${describeReleaseTarget(primaryDownloadTarget)} right now.`
|
||||
: 'The protected release lane now has both live entitlement and a current package target, so download and release references can stay attached to the same operator workflow.',
|
||||
items: [
|
||||
`Manifest viewer plan: ${manifestPlan}`,
|
||||
`Manifest viewer access: ${manifestAccessStatus}`,
|
||||
`Primary release target: ${describeReleaseTarget(primaryActionTarget)}`,
|
||||
`Configured release targets: ${configuredTargetsLabel}`,
|
||||
`Support contact: ${supportEmail}`,
|
||||
],
|
||||
actions: [
|
||||
{ label: `Download ${primaryActionTarget.platform} package`, href: primaryDownloadTarget.download_url, primary: true },
|
||||
{ label: 'Review notices', href: '/app/notices' },
|
||||
{ label: 'Review release notes', href: '/changelog' },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
if (configuredTargets.length === 0) {
|
||||
return {
|
||||
badge: 'Awaiting packaged release target',
|
||||
tone: 'info',
|
||||
title: 'Desktop access is visible, but no packaged target is published yet.',
|
||||
summary:
|
||||
'This protected lane can still show account and release posture, but it cannot deliver a desktop package until at least one target is configured.',
|
||||
items: [
|
||||
`Manifest viewer plan: ${manifestPlan}`,
|
||||
`Manifest viewer access: ${manifestAccessStatus}`,
|
||||
`Configured release targets: ${configuredTargetsLabel}`,
|
||||
`Support contact: ${supportEmail}`,
|
||||
],
|
||||
actions: [
|
||||
{ label: 'Review release notes', href: '/changelog', primary: true },
|
||||
{ label: 'Review notices', href: '/app/notices' },
|
||||
{ label: 'Get help', href: buildSupportPath('launch-readiness') },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
if (!releaseManifest.viewer.canDownload) {
|
||||
const checkoutActions = buildProtectedCheckoutActions(releaseManifest)
|
||||
const statusItems = [
|
||||
`Manifest viewer plan: ${manifestPlan}`,
|
||||
`Manifest viewer access: ${manifestAccessStatus}`,
|
||||
`Configured release targets: ${configuredTargetsLabel}`,
|
||||
`Operator checkout target: ${releaseManifest.commerce?.operator_checkout_url ? 'configured' : 'missing'}`,
|
||||
`Studio checkout target: ${releaseManifest.commerce?.studio_checkout_url ? 'configured' : 'missing'}`,
|
||||
]
|
||||
|
||||
if (checkoutActions.length > 0) {
|
||||
return {
|
||||
badge: 'Upgrade or provisioning required',
|
||||
tone: 'info',
|
||||
title: 'Desktop package delivery still requires billing or operator provisioning.',
|
||||
summary:
|
||||
'The protected release lane can show live release posture now, but it still keeps actual package delivery behind the current checkout and entitlement rules.',
|
||||
items: [
|
||||
...statusItems,
|
||||
`Support contact: ${supportEmail}`,
|
||||
],
|
||||
actions: [
|
||||
...checkoutActions,
|
||||
{ label: 'Open pricing', href: '/pricing' },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
badge: 'Checkout not live yet',
|
||||
tone: 'info',
|
||||
title: 'Protected release posture is ready, but live checkout is not configured.',
|
||||
summary:
|
||||
'This deployment can show the signed-in release lane, yet commercial access still depends on manual operator help because live checkout targets are not configured.',
|
||||
items: [
|
||||
...statusItems,
|
||||
`Support contact: ${supportEmail}`,
|
||||
],
|
||||
actions: [
|
||||
{ label: 'Open pricing', href: '/pricing', primary: true },
|
||||
{ label: 'Review notices', href: '/app/notices' },
|
||||
{ label: 'Get help', href: buildSupportPath('operator-access') },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
badge: 'Release lane ready for review',
|
||||
tone: 'info',
|
||||
title: 'Protected release posture is available for operator follow-through.',
|
||||
summary:
|
||||
'The protected lane is carrying live release metadata, packaged proof, and sign-in continuity even though a direct package action is not available from this exact state.',
|
||||
items: [
|
||||
`Manifest viewer plan: ${manifestPlan}`,
|
||||
`Manifest viewer access: ${manifestAccessStatus}`,
|
||||
`Configured release targets: ${configuredTargetsLabel}`,
|
||||
`Support contact: ${supportEmail}`,
|
||||
],
|
||||
actions: [
|
||||
{ label: 'Open downloads', href: buildProtectedDownloadPath('windows'), primary: true },
|
||||
{ label: 'Review notices', href: '/app/notices' },
|
||||
{ label: 'Get help', href: buildSupportPath('operator-access') },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
function ProtectedReleaseAuthorityNotice({
|
||||
badge,
|
||||
title,
|
||||
|
|
@ -297,6 +572,46 @@ function ProtectedReleaseAuthorityNotice({
|
|||
)
|
||||
}
|
||||
|
||||
function ProtectedReleaseActionPanel({
|
||||
title,
|
||||
kicker,
|
||||
releaseManifest,
|
||||
requestedPlatform,
|
||||
releaseAuthorityUnavailable = false,
|
||||
}: {
|
||||
title: string
|
||||
kicker: string
|
||||
releaseManifest: ReleaseManifestView
|
||||
requestedPlatform?: string | null
|
||||
releaseAuthorityUnavailable?: boolean
|
||||
}) {
|
||||
const surface = buildProtectedReleaseActionSurface({
|
||||
releaseManifest,
|
||||
requestedPlatform,
|
||||
releaseAuthorityUnavailable,
|
||||
})
|
||||
|
||||
return (
|
||||
<Panel title={title} kicker={kicker}>
|
||||
<p className={`status-pill${surface.tone === 'success' ? ' status-pill--success' : ' status-pill--info'}`}>
|
||||
{surface.badge}
|
||||
</p>
|
||||
<p><strong>{surface.title}</strong></p>
|
||||
<p>{surface.summary}</p>
|
||||
<ul className="list top-gap">
|
||||
{surface.items.map((item) => (
|
||||
<li key={item}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="button-row top-gap">
|
||||
{surface.actions.map((action) => (
|
||||
<ProtectedActionLink key={`${surface.badge}-${action.label}`} action={action} />
|
||||
))}
|
||||
</div>
|
||||
</Panel>
|
||||
)
|
||||
}
|
||||
|
||||
function PackagedValidationPanel({
|
||||
title,
|
||||
kicker,
|
||||
|
|
@ -724,6 +1039,13 @@ export function DashboardOverviewPage() {
|
|||
</ul>
|
||||
</Panel>
|
||||
|
||||
<ProtectedReleaseActionPanel
|
||||
title="Current release action"
|
||||
kicker="Protected access guidance"
|
||||
releaseManifest={releaseManifest}
|
||||
releaseAuthorityUnavailable={releaseManifestQuery.isError}
|
||||
/>
|
||||
|
||||
<Panel title="Launch readiness" kicker="Public release configuration">
|
||||
<p className={`status-pill${launchStatus.ready ? ' status-pill--success' : ''}`}>
|
||||
{launchStatus.ready ? 'Launch-ready posture' : 'Preview posture'}
|
||||
|
|
@ -871,6 +1193,14 @@ export function DownloadCenterPage() {
|
|||
</Panel>
|
||||
) : null}
|
||||
|
||||
<ProtectedReleaseActionPanel
|
||||
title="Current release action"
|
||||
kicker="Protected access guidance"
|
||||
releaseManifest={releaseManifest}
|
||||
requestedPlatform={requestedPlatform}
|
||||
releaseAuthorityUnavailable={releaseAuthorityUnavailable}
|
||||
/>
|
||||
|
||||
<Panel title="Release targets" kicker="Desktop distribution">
|
||||
{!viewerCanDownload ? (
|
||||
<p className="form-error">
|
||||
|
|
@ -1216,6 +1546,14 @@ export function AccountPage() {
|
|||
</ul>
|
||||
) : null}
|
||||
</Panel>
|
||||
|
||||
<ProtectedReleaseActionPanel
|
||||
title="Current release action"
|
||||
kicker="Protected access guidance"
|
||||
releaseManifest={releaseManifest}
|
||||
releaseAuthorityUnavailable={releaseManifestQuery.isError}
|
||||
/>
|
||||
|
||||
<PackagedValidationPanel
|
||||
title={protectedPackagedDesktopProofTitle}
|
||||
kicker="Release evidence"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue