@taskforcehq/taskforce 0.3.306 → 0.3.308
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -2
- package/dist/Taskforce.module.css +317 -27
- package/dist/TaskforceCore.js +1 -0
- package/dist/TaskforceCore.test.js +178 -0
- package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
- package/dist/components/activity/EntityActivityTimeline.js +81 -0
- package/dist/components/context/ContextAttachmentManager.js +15 -3
- package/dist/components/features/AgentsModule.d.ts +5 -1
- package/dist/components/features/AgentsModule.js +142 -39
- package/dist/components/features/AgentsModule.test.js +260 -11
- package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
- package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
- package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
- package/dist/components/features/DocumentIndex.d.ts +2 -2
- package/dist/components/features/DocumentIndex.js +3 -4
- package/dist/components/features/DocumentViewer.d.ts +3 -1
- package/dist/components/features/DocumentViewer.js +6 -5
- package/dist/components/features/DocumentWorkspace.d.ts +10 -2
- package/dist/components/features/DocumentWorkspace.js +15 -4
- package/dist/components/features/DocumentWorkspace.test.js +20 -3
- package/dist/components/features/TaskSettings.d.ts +2 -0
- package/dist/components/features/TaskSettings.js +83 -78
- package/dist/components/features/TaskSettings.test.js +171 -82
- package/dist/components/task/TaskActionHeader.js +11 -5
- package/dist/components/task/TaskActionHeader.test.js +10 -0
- package/dist/components/task/TaskCard.js +6 -2
- package/dist/components/task/TaskContextUpload.test.js +3 -3
- package/dist/components/task/TaskForm.js +14 -64
- package/dist/components/task/TaskKanban.js +0 -8
- package/dist/components/task/TaskList.js +5 -1
- package/dist/components/task/TaskList.test.js +4 -0
- package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
- package/dist/components/ui/HierarchyActionBadge.js +5 -0
- package/dist/components/views/AppShellHeader.d.ts +3 -1
- package/dist/components/views/AppShellHeader.js +27 -11
- package/dist/components/views/AppShellHeader.test.js +18 -1
- package/dist/components/views/PlanComparisonPage.d.ts +20 -15
- package/dist/components/views/PlanComparisonPage.js +221 -82
- package/dist/components/views/PlanComparisonPage.test.js +268 -14
- package/dist/components/views/PlansPage.js +16 -10
- package/dist/components/views/PlansPage.test.js +4 -2
- package/dist/components/views/StandaloneLayout.js +256 -69
- package/dist/components/views/WidgetView.js +10 -8
- package/dist/components/views/panels/PlanningDrawer.d.ts +19 -6
- package/dist/components/views/panels/PlanningDrawer.js +259 -51
- package/dist/components/views/panels/PlanningDrawer.test.js +392 -4
- package/dist/config/envSchema.js +1 -0
- package/dist/core/AiProfileService.d.ts +7 -0
- package/dist/core/AiProfileService.js +460 -35
- package/dist/core/AiProfiles.test.js +648 -2
- package/dist/core/AttachmentLinkService.d.ts +3 -0
- package/dist/core/AttachmentLinkService.js +58 -13
- package/dist/core/GlobalSettingsService.js +30 -0
- package/dist/core/GlobalSettingsService.test.d.ts +1 -0
- package/dist/core/GlobalSettingsService.test.js +45 -0
- package/dist/core/McpTokenService.d.ts +12 -0
- package/dist/core/McpTokenService.js +44 -1
- package/dist/core/PlanEntitlementService.d.ts +20 -0
- package/dist/core/PlanEntitlementService.js +215 -19
- package/dist/core/PlanFeatureCatalog.test.js +55 -0
- package/dist/core/PlanningEntities.test.js +366 -4
- package/dist/core/TaskActivityService.d.ts +28 -1
- package/dist/core/TaskActivityService.js +227 -46
- package/dist/core/TaskActivitySync.test.d.ts +1 -0
- package/dist/core/TaskActivitySync.test.js +90 -0
- package/dist/core/TaskAuditTimeline.test.js +96 -0
- package/dist/core/Taskforce.d.ts +204 -26
- package/dist/core/Taskforce.ids.test.js +58 -0
- package/dist/core/Taskforce.js +781 -71
- package/dist/core/Taskforce.mcpAuth.test.js +109 -0
- package/dist/core/types.d.ts +10 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +2 -1
- package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
- package/dist/hooks/bootstrapTimeouts.js +8 -0
- package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
- package/dist/hooks/bootstrapTimeouts.test.js +12 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +3 -3
- package/dist/hooks/sync/orchestratorShared.js +61 -1
- package/dist/hooks/sync/recovery.js +8 -0
- package/dist/hooks/sync/recovery.test.js +5 -5
- package/dist/hooks/sync/transfers.d.ts +2 -1
- package/dist/hooks/sync/transfers.js +36 -9
- package/dist/hooks/sync/transfers.test.js +534 -2
- package/dist/hooks/useFilterSort.js +13 -13
- package/dist/hooks/useFilterSort.test.js +87 -1
- package/dist/hooks/useSyncOrchestrator.js +50 -5
- package/dist/hooks/useTaskforce.d.ts +85 -50
- package/dist/hooks/useTaskforce.js +44 -14
- package/dist/hooks/useTaskforce.runtime-routing.test.js +94 -0
- package/dist/hooks/useTaskforce.sync-behavior.test.js +99 -0
- package/dist/hooks/useWorkspaceSyncController.js +7 -1
- package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +14 -7
- package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
- package/dist/mcp/canonicalAssetHelpers.js +18 -10
- package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
- package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
- package/dist/mcp/clientIntegrations.d.ts +1 -0
- package/dist/mcp/clientIntegrations.js +25 -0
- package/dist/mcp/clientIntegrations.test.js +14 -3
- package/dist/mcp/clientProfileDefaults.js +3 -3
- package/dist/mcp/collaborationRegistrar.js +7 -7
- package/dist/mcp/documentAssetRegistrar.js +113 -7
- package/dist/mcp/runtime.d.ts +1 -0
- package/dist/mcp/runtime.js +969 -122
- package/dist/mcp/runtime.test.js +1083 -11
- package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
- package/dist/mcp/taskPlanningRegistrar.js +48 -0
- package/dist/mcp/toolRegistry.d.ts +56 -0
- package/dist/mcp/toolRegistry.js +8 -0
- package/dist/mcp/toolRegistry.test.js +4 -0
- package/dist/migrations/taskSchemaMigrations.js +58 -15
- package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
- package/dist/server/aiProfileAvatarAssets.js +319 -0
- package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
- package/dist/server/aiProfileAvatarAssets.test.js +213 -0
- package/dist/server/annotatedAttachmentsRoutes.test.js +66 -7
- package/dist/server/index.js +4 -0
- package/dist/server/index.test.js +6 -1
- package/dist/server/routes/admin.js +328 -9
- package/dist/server/routes/annotatedAttachments.js +51 -0
- package/dist/server/routes/auth.d.ts +11 -0
- package/dist/server/routes/auth.js +189 -27
- package/dist/server/routes/authSupport.d.ts +5 -0
- package/dist/server/routes/authSupport.js +13 -1
- package/dist/server/routes/billing.d.ts +1 -0
- package/dist/server/routes/billing.js +116 -63
- package/dist/server/routes/billing.test.js +162 -11
- package/dist/server/routes/documents.js +10 -0
- package/dist/server/routes/shared.js +0 -10
- package/dist/server/routes/sync.integration.test.js +221 -0
- package/dist/server/routes/sync.js +13 -3
- package/dist/server/routes/syncSnapshotRoutes.js +2 -0
- package/dist/server/routes/tasks.js +12 -9
- package/dist/server/routes.js +87 -8
- package/dist/server/routes.test.js +972 -23
- package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
- package/dist/shared/aiProfileSurfaceType.js +10 -8
- package/dist/storage/objectStorage.d.ts +1 -0
- package/dist/storage/objectStorage.js +2 -1
- package/dist/storage/objectStorageClient.d.ts +1 -0
- package/dist/storage/objectStorageClient.js +41 -8
- package/dist/storage/objectStorageClient.test.js +26 -1
- package/dist/storage/objectStorageConfig.js +3 -1
- package/dist/storage/objectStorageConfig.test.js +13 -1
- package/dist/sync/collaborationSyncPayload.js +2 -0
- package/dist/sync/collaborationSyncPayload.test.js +1 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -3
- package/dist/sync/collaborationSyncState.js +19 -12
- package/dist/sync/contentSyncPayload.d.ts +5 -1
- package/dist/sync/contentSyncPayload.js +9 -0
- package/dist/sync/contentSyncPayload.test.js +39 -0
- package/dist/sync/contentSyncState.d.ts +5 -1
- package/dist/sync/contentSyncState.js +231 -37
- package/dist/sync/planningSyncPayload.js +5 -0
- package/dist/sync/planningSyncPayload.test.js +24 -0
- package/dist/sync/syncApplyHandlers.d.ts +4 -4
- package/dist/sync/syncApplyHandlers.js +89 -31
- package/dist/sync/syncApplyHandlers.test.js +184 -4
- package/dist/sync/syncResourceAdapters.d.ts +2 -0
- package/dist/sync/syncResourceAdapters.js +2 -0
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +13 -1
- package/dist/sync/syncService.test.js +39 -0
- package/dist/sync/workspacePullFeed.d.ts +4 -0
- package/dist/sync/workspacePullFeed.js +129 -70
- package/dist/sync/workspacePullFeed.test.js +49 -0
- package/dist/sync/workspaceSyncModel.d.ts +28 -6
- package/dist/sync/workspaceSyncModel.js +159 -45
- package/dist/sync/workspaceSyncModel.test.js +269 -3
- package/dist/types.d.ts +33 -2
- package/dist/ui/agent-logos/ChatGPT.png +0 -0
- package/dist/ui/agent-logos/Gemini CLI.jpeg +0 -0
- package/dist/ui/agent-logos/antigravity.jpeg +0 -0
- package/dist/ui/agent-logos/claude-code.jpeg +0 -0
- package/dist/ui/agent-logos/codex.jpeg +0 -0
- package/dist/ui/agent-logos/cursor.png +0 -0
- package/dist/ui/agent-logos/openclaw.jpeg +0 -0
- package/dist/ui/agent-logos/windsurf.png +0 -0
- package/dist/ui/assets/AgentsModule-BS4Pi_nC.js +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-CCckzWYZ.js +3 -0
- package/dist/ui/assets/ContextAttachmentManager-CfG_ER7C.js +3 -0
- package/dist/ui/assets/DocumentWorkspace-BVaWPA25.js +252 -0
- package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
- package/dist/ui/assets/EntityActivityTimeline-DSj0ThaK.js +1 -0
- package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
- package/dist/ui/assets/{InitiativesModule-9E6ParrY.js → InitiativesModule-AxMkWtxH.js} +1 -1
- package/dist/ui/assets/PlansPage-B_z-D6Nh.css +1 -0
- package/dist/ui/assets/PlansPage-Ndt7mYfo.js +1 -0
- package/dist/ui/assets/TaskContextUpload-Dw4rTF4i.js +1 -0
- package/dist/ui/assets/TaskSettings-f2ga42_V.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BA7jcEpH.js → WorkflowsModule-E4UL3mov.js} +4 -4
- package/dist/ui/assets/documentReferences-BOUcJm6-.js +1 -0
- package/dist/ui/assets/index-BvAbqx5Q.css +1 -0
- package/dist/ui/assets/index-MAHKvFqp.js +4 -0
- package/dist/ui/assets/{vendor-icons-DuEd_65c.js → vendor-icons-B32hGuF2.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/ui/og-image.png +0 -0
- package/dist/ui/product/agent manager.png +0 -0
- package/dist/ui/product/agent_manager_02.png +0 -0
- package/dist/utils/aiProfileEvents.d.ts +7 -0
- package/dist/utils/aiProfileEvents.js +6 -0
- package/dist/utils/assignees.d.ts +6 -0
- package/dist/utils/assignees.js +8 -1
- package/dist/utils/assignees.test.d.ts +1 -0
- package/dist/utils/assignees.test.js +25 -0
- package/dist/utils/avatarDisplay.d.ts +1 -0
- package/dist/utils/avatarDisplay.js +15 -0
- package/dist/utils/contextFiles.d.ts +3 -1
- package/dist/utils/contextFiles.js +2 -1
- package/dist/utils/taskActivity.d.ts +7 -1
- package/dist/utils/taskActivity.js +63 -1
- package/dist/utils/taskActivity.test.js +23 -1
- package/dist/utils/taskSearch.d.ts +22 -0
- package/dist/utils/taskSearch.js +87 -0
- package/dist/utils/taskSearch.test.d.ts +1 -0
- package/dist/utils/taskSearch.test.js +69 -0
- package/dist/utils/taskUtils.js +10 -3
- package/dist/utils/taskUtils.test.d.ts +1 -0
- package/dist/utils/taskUtils.test.js +19 -0
- package/package.json +1 -1
- package/dist/ui/assets/AgentsModule-DrLawwNl.js +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-Z9_whf7F.js +0 -3
- package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +0 -252
- package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +0 -1
- package/dist/ui/assets/PlansPage-CvfnMiWq.css +0 -1
- package/dist/ui/assets/PlansPage-DRXscYxH.js +0 -1
- package/dist/ui/assets/TaskSettings-DZX4jk7e.js +0 -9
- package/dist/ui/assets/index-9eT8e0Lu.css +0 -1
- package/dist/ui/assets/index-C6k75jr8.js +0 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useState } from 'react';
|
|
3
3
|
import { CheckCircle2, Loader2 } from 'lucide-react';
|
|
4
4
|
import taskforceStyles from '../../Taskforce.module.css';
|
|
@@ -19,6 +19,60 @@ function formatPricingSource(pricingSource) {
|
|
|
19
19
|
return `${environment} (${runtimeMode})`;
|
|
20
20
|
return environment || runtimeMode || null;
|
|
21
21
|
}
|
|
22
|
+
const ALLOWED_PRICING_DESCRIPTION_TAGS = new Set(['a', 'b', 'br', 'em', 'i', 'li', 'ol', 'p', 'strong', 'u', 'ul']);
|
|
23
|
+
function escapeHtmlText(raw) {
|
|
24
|
+
return raw
|
|
25
|
+
.replace(/&/g, '&')
|
|
26
|
+
.replace(/</g, '<')
|
|
27
|
+
.replace(/>/g, '>')
|
|
28
|
+
.replace(/"/g, '"')
|
|
29
|
+
.replace(/'/g, ''');
|
|
30
|
+
}
|
|
31
|
+
function sanitizePricingDescriptionHtml(raw) {
|
|
32
|
+
const value = String(raw || '').trim();
|
|
33
|
+
if (!value)
|
|
34
|
+
return '';
|
|
35
|
+
if (typeof document === 'undefined')
|
|
36
|
+
return escapeHtmlText(value);
|
|
37
|
+
const template = document.createElement('template');
|
|
38
|
+
template.innerHTML = value;
|
|
39
|
+
const output = document.createElement('div');
|
|
40
|
+
const cleanNode = (node) => {
|
|
41
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
42
|
+
return document.createTextNode(node.textContent || '');
|
|
43
|
+
}
|
|
44
|
+
if (node.nodeType !== Node.ELEMENT_NODE)
|
|
45
|
+
return null;
|
|
46
|
+
const element = node;
|
|
47
|
+
const tagName = element.tagName.toLowerCase();
|
|
48
|
+
if (tagName === 'script' || tagName === 'style')
|
|
49
|
+
return null;
|
|
50
|
+
const children = Array.from(element.childNodes).map(cleanNode).filter((child) => Boolean(child));
|
|
51
|
+
if (!ALLOWED_PRICING_DESCRIPTION_TAGS.has(tagName)) {
|
|
52
|
+
const fragment = document.createDocumentFragment();
|
|
53
|
+
for (const child of children)
|
|
54
|
+
fragment.appendChild(child);
|
|
55
|
+
return fragment;
|
|
56
|
+
}
|
|
57
|
+
const next = document.createElement(tagName);
|
|
58
|
+
if (tagName === 'a') {
|
|
59
|
+
const href = String(element.getAttribute('href') || '').trim();
|
|
60
|
+
if (/^(https?:|mailto:|\/|#)/i.test(href)) {
|
|
61
|
+
next.setAttribute('href', href);
|
|
62
|
+
next.setAttribute('rel', 'noopener noreferrer');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const child of children)
|
|
66
|
+
next.appendChild(child);
|
|
67
|
+
return next;
|
|
68
|
+
};
|
|
69
|
+
for (const child of Array.from(template.content.childNodes)) {
|
|
70
|
+
const cleaned = cleanNode(child);
|
|
71
|
+
if (cleaned)
|
|
72
|
+
output.appendChild(cleaned);
|
|
73
|
+
}
|
|
74
|
+
return output.innerHTML;
|
|
75
|
+
}
|
|
22
76
|
function formatCurrency(amount, currency) {
|
|
23
77
|
if (amount == null || !currency)
|
|
24
78
|
return 'Contact Sales';
|
|
@@ -34,7 +88,7 @@ function formatCurrency(amount, currency) {
|
|
|
34
88
|
}
|
|
35
89
|
}
|
|
36
90
|
function renderFreePriceAmount() {
|
|
37
|
-
return '$0
|
|
91
|
+
return '$0';
|
|
38
92
|
}
|
|
39
93
|
function isSelectablePrice(price) {
|
|
40
94
|
if (!price)
|
|
@@ -50,6 +104,25 @@ function renderPriceLabel(price) {
|
|
|
50
104
|
return renderFreePriceAmount();
|
|
51
105
|
return formatCurrency(price.unitAmount, price.currency);
|
|
52
106
|
}
|
|
107
|
+
function getSelectedPricing(regularPrice, campaignPrice) {
|
|
108
|
+
return isSelectablePrice(campaignPrice)
|
|
109
|
+
? { price: campaignPrice, pricingAudience: 'campaign' }
|
|
110
|
+
: { price: regularPrice, pricingAudience: 'public' };
|
|
111
|
+
}
|
|
112
|
+
function renderPriceRows(monthPrice, yearPrice, campaignMonthPrice, campaignYearPrice) {
|
|
113
|
+
const hasMonth = isSelectablePrice(monthPrice) || isSelectablePrice(campaignMonthPrice);
|
|
114
|
+
const hasYear = isSelectablePrice(yearPrice) || isSelectablePrice(campaignYearPrice);
|
|
115
|
+
if (!hasMonth && !hasYear)
|
|
116
|
+
return null;
|
|
117
|
+
const renderRow = (price, campaignPrice, interval) => {
|
|
118
|
+
if (!isSelectablePrice(price) && !isSelectablePrice(campaignPrice))
|
|
119
|
+
return null;
|
|
120
|
+
const hasCampaignPrice = isSelectablePrice(campaignPrice);
|
|
121
|
+
const intervalLabel = interval === 'year' ? 'year' : 'month';
|
|
122
|
+
return (_jsxs("div", { className: `${styles.priceRow} ${hasCampaignPrice ? styles.priceRowWithCampaign : ''}`, children: [isSelectablePrice(price) ? (_jsxs("div", { className: styles.regularPriceLine, children: [_jsx("span", { className: `${styles.priceRowAmount} ${hasCampaignPrice ? styles.regularPriceDiscounted : ''}`, children: renderPriceLabel(price) }), _jsxs("span", { className: styles.priceRowInterval, children: ["/ ", intervalLabel] })] })) : null, hasCampaignPrice ? (_jsxs("div", { className: styles.campaignPriceLine, children: [_jsx("span", { className: styles.campaignPriceLabel, children: "Founding Offer:" }), _jsx("span", { className: styles.campaignPriceAmount, children: renderPriceLabel(campaignPrice) }), _jsxs("span", { className: styles.campaignPriceInterval, children: ["/ ", intervalLabel] })] })) : null] }, interval));
|
|
123
|
+
};
|
|
124
|
+
return (_jsxs("div", { className: styles.priceRows, children: [renderRow(monthPrice, campaignMonthPrice, 'month'), renderRow(yearPrice, campaignYearPrice, 'year')] }));
|
|
125
|
+
}
|
|
53
126
|
function getPlanSortRank(plan) {
|
|
54
127
|
const prices = [plan.pricing.month, plan.pricing.year].filter(Boolean);
|
|
55
128
|
if (prices.some((price) => price.pricingType === 'free'))
|
|
@@ -74,38 +147,72 @@ function readConfiguredFeatureLimit(config, key) {
|
|
|
74
147
|
const normalized = Math.max(1, Math.floor(numericValue));
|
|
75
148
|
return normalized;
|
|
76
149
|
}
|
|
150
|
+
function formatLimitedFeatureLabel(baseLabel, limitValue) {
|
|
151
|
+
if (limitValue !== 1)
|
|
152
|
+
return baseLabel;
|
|
153
|
+
return baseLabel
|
|
154
|
+
.replace(/\b([A-Za-z]+)ies\b$/, '$1y')
|
|
155
|
+
.replace(/\b([A-Za-z]+[^s\s])s\b$/, '$1');
|
|
156
|
+
}
|
|
157
|
+
function formatStorageLimit(storageLimitMb) {
|
|
158
|
+
if (storageLimitMb <= 1000)
|
|
159
|
+
return { value: String(storageLimitMb), unit: 'MB' };
|
|
160
|
+
return {
|
|
161
|
+
value: String(Math.round(storageLimitMb / 1000)),
|
|
162
|
+
unit: 'GB'
|
|
163
|
+
};
|
|
164
|
+
}
|
|
77
165
|
function getFeatureDisplayParts(feature, seatLimit) {
|
|
78
166
|
const baseLabel = String(feature.publicLabel || feature.label || '').trim() || formatFeatureKeyFallback(feature.featureKey);
|
|
79
167
|
const config = feature.config && typeof feature.config === 'object' ? feature.config : {};
|
|
80
168
|
if (feature.featureKey === 'context.uploads') {
|
|
81
169
|
const storageLimitMb = readConfiguredFeatureLimit(config, 'storageLimitMb');
|
|
82
170
|
if (storageLimitMb !== null) {
|
|
83
|
-
|
|
171
|
+
const storageLimit = formatStorageLimit(storageLimitMb);
|
|
172
|
+
return {
|
|
173
|
+
limitValue: storageLimit.value,
|
|
174
|
+
limitUnit: storageLimit.unit,
|
|
175
|
+
baseLabel: formatLimitedFeatureLabel(baseLabel, storageLimitMb)
|
|
176
|
+
};
|
|
84
177
|
}
|
|
85
178
|
}
|
|
86
179
|
if (feature.featureKey === 'workspace.workspaces') {
|
|
87
180
|
const maxWorkspaces = readConfiguredFeatureLimit(config, 'maxWorkspaces');
|
|
88
181
|
if (maxWorkspaces !== null) {
|
|
89
|
-
return {
|
|
182
|
+
return {
|
|
183
|
+
limitValue: String(maxWorkspaces),
|
|
184
|
+
limitUnit: null,
|
|
185
|
+
baseLabel: formatLimitedFeatureLabel(baseLabel, maxWorkspaces)
|
|
186
|
+
};
|
|
90
187
|
}
|
|
91
188
|
}
|
|
92
189
|
if (feature.featureKey === 'collaboration.ai_profiles') {
|
|
93
190
|
const maxAiProfiles = readConfiguredFeatureLimit(config, 'maxAiProfiles');
|
|
94
191
|
if (maxAiProfiles !== null) {
|
|
95
|
-
return {
|
|
192
|
+
return {
|
|
193
|
+
limitValue: String(maxAiProfiles),
|
|
194
|
+
limitUnit: null,
|
|
195
|
+
baseLabel: formatLimitedFeatureLabel(baseLabel, maxAiProfiles)
|
|
196
|
+
};
|
|
96
197
|
}
|
|
97
198
|
}
|
|
98
199
|
if (feature.featureKey === 'collaboration.team_management') {
|
|
99
200
|
const normalizedSeatLimit = Number(seatLimit);
|
|
100
201
|
if (Number.isFinite(normalizedSeatLimit) && normalizedSeatLimit > 0) {
|
|
101
|
-
|
|
202
|
+
const seatLimitValue = Math.floor(normalizedSeatLimit);
|
|
203
|
+
return {
|
|
204
|
+
limitValue: String(seatLimitValue),
|
|
205
|
+
limitUnit: null,
|
|
206
|
+
baseLabel: formatLimitedFeatureLabel(baseLabel, seatLimitValue)
|
|
207
|
+
};
|
|
102
208
|
}
|
|
103
209
|
}
|
|
104
210
|
return { limitValue: null, limitUnit: null, baseLabel };
|
|
105
211
|
}
|
|
106
|
-
export function PlanComparisonPage({ heading, subtitle, variant = 'app', chrome, footer, isAuthenticated, authSessionResolved = true, currentPlanId, currentPlanVersionId, currentEntitlementState, markCurrentPlan, canManageCurrentPlan = true, pricingEndpoint, statusBanner, onBack, backLabel, backDisabled = false, topActions, shellOwnsScroll = false, showPageBackButton = true, showHeaderBarWithChrome = false, currentPlanCardActionMode = 'manage', fallbackPricingSource = null, onSelectPlan }) {
|
|
212
|
+
export function PlanComparisonPage({ heading, subtitle, variant = 'app', theme = 'dark', chrome, footer, isAuthenticated, authSessionResolved = true, currentPlanId, currentPlanVersionId, currentEntitlementState, markCurrentPlan, canManageCurrentPlan = true, pricingEndpoint, statusBanner, onBack, backLabel, backDisabled = false, topActions, shellOwnsScroll = false, showPageBackButton = true, showHeaderBarWithChrome = false, preferPricingPageTitle = true, currentPlanCardActionMode = 'manage', fallbackPricingSource = null, onSelectPlan }) {
|
|
107
213
|
const [plans, setPlans] = useState([]);
|
|
108
214
|
const [pricingSource, setPricingSource] = useState(null);
|
|
215
|
+
const [pricingPageCopy, setPricingPageCopy] = useState({ pageTitle: null, pageDescription: null });
|
|
109
216
|
const [loading, setLoading] = useState(true);
|
|
110
217
|
const [error, setError] = useState(null);
|
|
111
218
|
const [retryNonce, setRetryNonce] = useState(0);
|
|
@@ -124,6 +231,10 @@ export function PlanComparisonPage({ heading, subtitle, variant = 'app', chrome,
|
|
|
124
231
|
if (active) {
|
|
125
232
|
setPlans(Array.isArray(body?.plans) ? body.plans : []);
|
|
126
233
|
setPricingSource(body?.pricingSource && typeof body.pricingSource === 'object' ? body.pricingSource : null);
|
|
234
|
+
setPricingPageCopy({
|
|
235
|
+
pageTitle: typeof body?.pageTitle === 'string' ? (body.pageTitle.trim() || null) : null,
|
|
236
|
+
pageDescription: typeof body?.pageDescription === 'string' ? (body.pageDescription.trim() || null) : null
|
|
237
|
+
});
|
|
127
238
|
}
|
|
128
239
|
return;
|
|
129
240
|
}
|
|
@@ -134,6 +245,7 @@ export function PlanComparisonPage({ heading, subtitle, variant = 'app', chrome,
|
|
|
134
245
|
const message = err instanceof Error ? err.message : 'Failed to load pricing';
|
|
135
246
|
setError(message || 'Failed to load pricing');
|
|
136
247
|
setPricingSource(null);
|
|
248
|
+
setPricingPageCopy({ pageTitle: null, pageDescription: null });
|
|
137
249
|
}
|
|
138
250
|
}
|
|
139
251
|
finally {
|
|
@@ -156,13 +268,16 @@ export function PlanComparisonPage({ heading, subtitle, variant = 'app', chrome,
|
|
|
156
268
|
return String(a.displayName || a.planId).localeCompare(String(b.displayName || b.planId));
|
|
157
269
|
}), [plans]);
|
|
158
270
|
const pricingSourceLabel = useMemo(() => formatPricingSource(pricingSource || fallbackPricingSource), [fallbackPricingSource, pricingSource]);
|
|
271
|
+
const resolvedHeading = String((preferPricingPageTitle ? pricingPageCopy.pageTitle : null) || heading).trim() || heading;
|
|
272
|
+
const resolvedSubtitle = String(pricingPageCopy.pageDescription || subtitle || '').trim();
|
|
273
|
+
const resolvedSubtitleHtml = useMemo(() => sanitizePricingDescriptionHtml(resolvedSubtitle), [resolvedSubtitle]);
|
|
159
274
|
const planSelectionEnabled = authSessionResolved;
|
|
160
275
|
const contentClassName = `${styles.plansWrapper} ${shellOwnsScroll ? styles.shellOwnsScroll : 'tf-scrollbar'} ${variant === 'site' ? styles.siteVariant : ''} ${chrome ? styles.withChrome : ''}`.trim();
|
|
161
276
|
const wrapPage = (content) => {
|
|
162
277
|
if (shellOwnsScroll) {
|
|
163
278
|
return (_jsxs(_Fragment, { children: [chrome, content, footer] }));
|
|
164
279
|
}
|
|
165
|
-
return (_jsxs("div", { className: taskforceStyles.standaloneWrapper, "data-theme":
|
|
280
|
+
return (_jsxs("div", { className: taskforceStyles.standaloneWrapper, "data-theme": theme, children: [chrome, content, footer] }));
|
|
166
281
|
};
|
|
167
282
|
if (loading) {
|
|
168
283
|
return wrapPage(_jsx(_Fragment, { children: _jsx("div", { className: contentClassName, children: _jsx("div", { className: styles.contentFrame, children: _jsxs("div", { className: styles.loader, children: [_jsx(Loader2, { size: 48, className: styles.spinner }), _jsx("p", { children: "Loading plans..." })] }) }) }) }));
|
|
@@ -170,82 +285,106 @@ export function PlanComparisonPage({ heading, subtitle, variant = 'app', chrome,
|
|
|
170
285
|
if (error) {
|
|
171
286
|
return wrapPage(_jsx(_Fragment, { children: _jsx("div", { className: contentClassName, children: _jsx("div", { className: styles.contentFrame, children: _jsxs("div", { className: styles.emptyState, children: [_jsx("h2", { children: "Connecting to plan pricing..." }), _jsx("p", { children: error }), _jsx("button", { className: styles.backBtn, onClick: () => setRetryNonce((value) => value + 1), children: "Retry" })] }) }) }) }));
|
|
172
287
|
}
|
|
173
|
-
return wrapPage(_jsx(_Fragment, { children: _jsx("div", { className: contentClassName, children: _jsxs("div", { className: styles.contentFrame, children: [_jsxs("div", { className: styles.header, children: [(!chrome || showHeaderBarWithChrome) && (showPageBackButton || topActions) && (_jsxs("div", { className: styles.headerBar, children: [_jsx("div", { children: showPageBackButton ? (_jsx("button", { className: styles.backBtn, onClick: onBack, disabled: backDisabled, children: backLabel })) : null }), _jsx("div", { className: styles.headerActions, children: topActions })] })), _jsx("h1", { className: styles.title, children:
|
|
288
|
+
return wrapPage(_jsx(_Fragment, { children: _jsx("div", { className: contentClassName, children: _jsxs("div", { className: styles.contentFrame, children: [_jsxs("div", { className: styles.header, children: [(!chrome || showHeaderBarWithChrome) && (showPageBackButton || topActions) && (_jsxs("div", { className: styles.headerBar, children: [_jsx("div", { children: showPageBackButton ? (_jsx("button", { className: styles.backBtn, onClick: onBack, disabled: backDisabled, children: backLabel })) : null }), _jsx("div", { className: styles.headerActions, children: topActions })] })), _jsx("h1", { className: styles.title, children: resolvedHeading }), resolvedSubtitleHtml && (_jsx("div", { className: styles.subtitle, dangerouslySetInnerHTML: { __html: resolvedSubtitleHtml } })), isDebugModeEnabled() && pricingSourceLabel ? (_jsxs("p", { className: styles.debugMeta, children: ["Pricing source: ", pricingSourceLabel] })) : null, statusBanner && (_jsx("div", { className: `${styles.statusBanner} ${statusBanner.type === 'success'
|
|
174
289
|
? styles.successBanner
|
|
175
|
-
: (statusBanner.type === 'info' ? styles.infoBanner : styles.errorBanner)}`, children: statusBanner.message }))] }), sortedPlans.length === 0 ? (_jsxs("div", { className: `${styles.emptyState} ${styles.marketingEmptyState}`, children: [_jsx("span", { className: styles.emptyBadge, children: "Coming Soon" }), _jsx("h2", { children: "Paid plans are getting their final polish." }), _jsx("p", { children: "Taskforce pricing is on the way with flexible options for solo operators, teams, and larger rollouts. Check back soon for launch tiers, feature bundles, and early access details." }), _jsxs("div", { className: styles.emptyHighlights, "aria-label": "Upcoming pricing highlights", children: [_jsx("span", { children: "Launch-ready tiers" }), _jsx("span", { children: "Team billing controls" }), _jsx("span", { children: "Feature-based packaging" })] })] })) : (_jsx("div", { className: styles.grid, children: sortedPlans.map((plan) => {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
interval: defaultInterval || 'month',
|
|
209
|
-
pricingType: defaultPrice?.pricingType || 'stripe',
|
|
210
|
-
isCurrentPlan: true
|
|
211
|
-
});
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
if (!defaultInterval)
|
|
215
|
-
return;
|
|
216
|
-
void onSelectPlan({
|
|
217
|
-
planId: plan.planId,
|
|
218
|
-
planVersionId,
|
|
219
|
-
interval: defaultInterval,
|
|
220
|
-
pricingType: defaultPrice?.pricingType || 'stripe',
|
|
221
|
-
isCurrentPlan: false
|
|
222
|
-
});
|
|
223
|
-
};
|
|
224
|
-
return (_jsxs("div", { className: `${styles.planCard} ${isCurrent ? styles.activePlanCard : ''} ${canSelectCard ? styles.clickablePlanCard : ''}`, "data-testid": `plan-card-${plan.planId}`, role: canSelectCard ? 'button' : undefined, tabIndex: canSelectCard ? 0 : undefined, onClick: canSelectCard ? handleCardSelect : undefined, onKeyDown: canSelectCard ? (event) => {
|
|
225
|
-
if (event.key === 'Enter' || event.key === ' ') {
|
|
226
|
-
event.preventDefault();
|
|
227
|
-
handleCardSelect();
|
|
228
|
-
}
|
|
229
|
-
} : undefined, children: [isCurrent && _jsx("span", { className: styles.currentBadge, children: "Current Plan" }), _jsx("h3", { className: styles.planName, children: plan.displayName }), String(plan.description || '').trim() && (_jsx("p", { className: styles.planDescription, children: String(plan.description || '').trim() })), _jsx("div", { className: styles.featuresList, children: Array.isArray(plan.features) && plan.features.length > 0 ? plan.features.map((feature, idx) => (_jsxs("div", { className: styles.featureItem, children: [_jsx(CheckCircle2, { className: styles.featureIcon }), _jsxs("div", { className: styles.featureTextBlock, children: [(() => {
|
|
230
|
-
const { limitValue, limitUnit, baseLabel } = getFeatureDisplayParts(feature, plan.defaultVersion?.seatLimit);
|
|
231
|
-
return (_jsxs("span", { className: styles.featureText, children: [limitValue && (_jsxs("span", { className: styles.featureLimitAccent, children: [_jsx("span", { children: limitValue }), limitUnit ? (_jsx("span", { className: limitUnit === 'MB' ? styles.featureLimitUnit : undefined, children: limitUnit })) : null] })), _jsx("span", { children: limitValue ? ` ${baseLabel}` : baseLabel })] }));
|
|
232
|
-
})(), String(feature.publicDescription || feature.description || '').trim() && (_jsx("span", { className: styles.featureDescription, children: String(feature.publicDescription || feature.description || '').trim() }))] })] }, `${feature.featureKey}-${idx}`))) : (_jsx("span", { className: styles.featureText, children: "No additional features included" })) }), (hasMonth || hasYear) ? (_jsxs("div", { className: styles.priceRows, children: [hasMonth ? (_jsxs("div", { className: styles.priceRow, children: [_jsx("span", { className: styles.priceRowAmount, children: renderPriceLabel(monthPrice) }), _jsx("span", { className: styles.priceRowInterval, children: "/ month" })] })) : null, hasYear ? (_jsxs("div", { className: styles.priceRow, children: [_jsx("span", { className: styles.priceRowAmount, children: renderPriceLabel(yearPrice) }), _jsx("span", { className: styles.priceRowInterval, children: "/ year" })] })) : null] })) : null, isCurrent ? (currentPlanShowsManageAction ? (_jsx("button", { className: `${styles.callToAction} ${styles.currentCta}`, "data-testid": `plan-manage-${plan.planId}`, onClick: (event) => {
|
|
233
|
-
event.stopPropagation();
|
|
290
|
+
: (statusBanner.type === 'info' ? styles.infoBanner : styles.errorBanner)}`, children: statusBanner.message }))] }), _jsxs("section", { className: styles.foundingBeta, "aria-labelledby": "founding-beta-heading", children: [_jsx("h2", { id: "founding-beta-heading", children: "Founding beta" }), _jsx("p", { children: "Taskforce is in founding beta. You may run into rough edges while we improve cloud workspaces and agent workflows. Early members get access while the product is still forming, plus a direct role in shaping what comes next." })] }), sortedPlans.length === 0 ? (_jsxs("div", { className: `${styles.emptyState} ${styles.marketingEmptyState}`, children: [_jsx("span", { className: styles.emptyBadge, children: "Coming Soon" }), _jsx("h2", { children: "Paid plans are getting their final polish." }), _jsx("p", { children: "Taskforce pricing is on the way with flexible options for solo operators, teams, and larger rollouts. Check back soon for launch tiers, feature bundles, and early access details." }), _jsxs("div", { className: styles.emptyHighlights, "aria-label": "Upcoming pricing highlights", children: [_jsx("span", { children: "Launch-ready tiers" }), _jsx("span", { children: "Team billing controls" }), _jsx("span", { children: "Feature-based packaging" })] })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.grid, children: sortedPlans.map((plan) => {
|
|
291
|
+
const monthPrice = plan.pricing.month;
|
|
292
|
+
const yearPrice = plan.pricing.year;
|
|
293
|
+
const campaignMonthPrice = plan.campaignPricing?.month || null;
|
|
294
|
+
const campaignYearPrice = plan.campaignPricing?.year || null;
|
|
295
|
+
const selectedMonthPricing = getSelectedPricing(monthPrice, campaignMonthPrice);
|
|
296
|
+
const selectedYearPricing = getSelectedPricing(yearPrice, campaignYearPrice);
|
|
297
|
+
const hasMonth = isSelectablePrice(selectedMonthPricing.price);
|
|
298
|
+
const hasYear = isSelectablePrice(selectedYearPricing.price);
|
|
299
|
+
const hasMultipleBillingOptions = hasMonth && hasYear;
|
|
300
|
+
const planVersionId = String(plan.defaultVersion?.planVersionId || '').trim();
|
|
301
|
+
const hasActiveEntitlement = isCommercialEntitlementActive(currentEntitlementState);
|
|
302
|
+
const shouldMarkCurrentPlan = markCurrentPlan ?? hasActiveEntitlement;
|
|
303
|
+
const hasKnownLinkedPlan = Boolean(planVersionId
|
|
304
|
+
&& currentPlanVersionId
|
|
305
|
+
&& currentPlanVersionId === planVersionId) || Boolean(!currentPlanVersionId
|
|
306
|
+
&& Boolean(currentPlanId && currentPlanId === plan.planId));
|
|
307
|
+
const isCurrent = shouldMarkCurrentPlan && hasKnownLinkedPlan && currentEntitlementState !== 'canceled';
|
|
308
|
+
const defaultInterval = hasMonth ? 'month' : (hasYear ? 'year' : null);
|
|
309
|
+
const defaultPricing = defaultInterval === 'year' ? selectedYearPricing : selectedMonthPricing;
|
|
310
|
+
const currentPlanShowsManageAction = currentPlanCardActionMode === 'manage';
|
|
311
|
+
const currentPlanShowsIndicator = currentPlanCardActionMode === 'indicator';
|
|
312
|
+
const canSelectCard = planSelectionEnabled
|
|
313
|
+
&& Boolean(planVersionId)
|
|
314
|
+
&& (isCurrent
|
|
315
|
+
? (currentPlanShowsManageAction && canManageCurrentPlan)
|
|
316
|
+
: Boolean(defaultInterval));
|
|
317
|
+
const handleCardSelect = () => {
|
|
318
|
+
if (!planVersionId)
|
|
319
|
+
return;
|
|
320
|
+
if (isCurrent) {
|
|
321
|
+
if (!currentPlanShowsManageAction)
|
|
322
|
+
return;
|
|
234
323
|
if (!canManageCurrentPlan)
|
|
235
324
|
return;
|
|
236
|
-
void onSelectPlan({
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
325
|
+
void onSelectPlan({
|
|
326
|
+
planId: plan.planId,
|
|
327
|
+
planVersionId,
|
|
328
|
+
interval: defaultInterval || 'month',
|
|
329
|
+
pricingType: defaultPricing.price?.pricingType || 'stripe',
|
|
330
|
+
pricingAudience: defaultPricing.pricingAudience,
|
|
331
|
+
isCurrentPlan: true
|
|
332
|
+
});
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (!defaultInterval)
|
|
336
|
+
return;
|
|
337
|
+
void onSelectPlan({
|
|
338
|
+
planId: plan.planId,
|
|
339
|
+
planVersionId,
|
|
340
|
+
interval: defaultInterval,
|
|
341
|
+
pricingType: defaultPricing.price?.pricingType || 'stripe',
|
|
342
|
+
pricingAudience: defaultPricing.pricingAudience,
|
|
343
|
+
isCurrentPlan: false
|
|
344
|
+
});
|
|
345
|
+
};
|
|
346
|
+
return (_jsxs("div", { className: `${styles.planCard} ${isCurrent ? styles.activePlanCard : ''} ${canSelectCard ? styles.clickablePlanCard : ''}`, "data-testid": `plan-card-${plan.planId}`, role: canSelectCard ? 'button' : undefined, tabIndex: canSelectCard ? 0 : undefined, onClick: canSelectCard ? handleCardSelect : undefined, onKeyDown: canSelectCard ? (event) => {
|
|
347
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
348
|
+
event.preventDefault();
|
|
349
|
+
handleCardSelect();
|
|
350
|
+
}
|
|
351
|
+
} : undefined, children: [isCurrent && _jsx("span", { className: styles.currentBadge, children: "Current Plan" }), _jsx("h3", { className: styles.planName, children: plan.displayName }), renderPriceRows(monthPrice, yearPrice, campaignMonthPrice, campaignYearPrice), String(plan.description || '').trim() && (_jsx("p", { className: styles.planDescription, children: String(plan.description || '').trim() })), _jsx("div", { className: styles.featuresList, children: Array.isArray(plan.features) && plan.features.length > 0 ? plan.features.map((feature, idx) => (_jsxs("div", { className: styles.featureItem, children: [_jsx(CheckCircle2, { className: styles.featureIcon }), _jsxs("div", { className: styles.featureTextBlock, children: [(() => {
|
|
352
|
+
const { limitValue, limitUnit, baseLabel } = getFeatureDisplayParts(feature, plan.defaultVersion?.seatLimit);
|
|
353
|
+
return (_jsxs("span", { className: styles.featureText, children: [limitValue && (_jsxs("span", { className: styles.featureLimitAccent, children: [_jsx("span", { children: limitValue }), limitUnit ? (_jsx("span", { className: styles.featureLimitUnit, children: limitUnit })) : null] })), _jsx("span", { children: limitValue ? ` ${baseLabel}` : baseLabel })] }));
|
|
354
|
+
})(), feature.publicDescriptionVisible !== false && String(feature.publicDescription || feature.description || '').trim() && (_jsx("span", { className: styles.featureDescription, children: String(feature.publicDescription || feature.description || '').trim() }))] })] }, `${feature.featureKey}-${idx}`))) : (_jsx("span", { className: styles.featureText, children: "No additional features included" })) }), isCurrent ? (currentPlanShowsManageAction ? (_jsx("button", { className: `${styles.callToAction} ${styles.currentCta}`, "data-testid": `plan-manage-${plan.planId}`, onClick: (event) => {
|
|
245
355
|
event.stopPropagation();
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
356
|
+
if (!canManageCurrentPlan)
|
|
357
|
+
return;
|
|
358
|
+
void onSelectPlan({
|
|
359
|
+
planId: plan.planId,
|
|
360
|
+
planVersionId,
|
|
361
|
+
interval: defaultInterval || 'month',
|
|
362
|
+
pricingType: defaultPricing.price?.pricingType || 'stripe',
|
|
363
|
+
pricingAudience: defaultPricing.pricingAudience,
|
|
364
|
+
isCurrentPlan: true
|
|
365
|
+
});
|
|
366
|
+
}, disabled: !planVersionId || !canManageCurrentPlan || !planSelectionEnabled, children: isAuthenticated
|
|
367
|
+
? (canManageCurrentPlan ? 'Manage Plan' : 'Current Plan')
|
|
368
|
+
: 'Current Plan' })) : (currentPlanShowsIndicator ? (_jsx("button", { className: `${styles.callToAction} ${styles.currentCta}`, "data-testid": `plan-current-${plan.planId}`, disabled: true, children: "Current Plan" })) : null)) : (_jsxs("div", { className: styles.ctaRow, children: [hasMonth ? (_jsx("button", { className: `${styles.callToAction} ${styles.secondaryCta}`, "data-testid": `plan-select-${plan.planId}-month`, onClick: (event) => {
|
|
369
|
+
event.stopPropagation();
|
|
370
|
+
void onSelectPlan({
|
|
371
|
+
planId: plan.planId,
|
|
372
|
+
planVersionId,
|
|
373
|
+
interval: 'month',
|
|
374
|
+
pricingType: selectedMonthPricing.price?.pricingType || 'stripe',
|
|
375
|
+
pricingAudience: selectedMonthPricing.pricingAudience,
|
|
376
|
+
isCurrentPlan: false
|
|
377
|
+
});
|
|
378
|
+
}, disabled: !planVersionId || !planSelectionEnabled, children: hasMultipleBillingOptions ? 'Start Monthly' : 'Get Started' })) : null, hasYear ? (_jsx("button", { className: `${styles.callToAction} ${styles.primaryCta}`, "data-testid": `plan-select-${plan.planId}-year`, onClick: (event) => {
|
|
379
|
+
event.stopPropagation();
|
|
380
|
+
void onSelectPlan({
|
|
381
|
+
planId: plan.planId,
|
|
382
|
+
planVersionId,
|
|
383
|
+
interval: 'year',
|
|
384
|
+
pricingType: selectedYearPricing.price?.pricingType || 'stripe',
|
|
385
|
+
pricingAudience: selectedYearPricing.pricingAudience,
|
|
386
|
+
isCurrentPlan: false
|
|
387
|
+
});
|
|
388
|
+
}, disabled: !planVersionId || !planSelectionEnabled, children: hasMultipleBillingOptions ? 'Start Yearly' : 'Get Started' })) : null] }))] }, plan.planId));
|
|
389
|
+
}) }), _jsxs("div", { className: styles.pricingNotes, children: [_jsx("p", { children: "Prices are listed in USD. Taxes may apply." }), _jsx("p", { children: "Local install is free on every plan. Cloud limits apply only to hosted workspaces." })] }), _jsxs("section", { className: `${styles.foundingBeta} ${styles.cloudCapacityNote}`, "aria-labelledby": "cloud-capacity-heading", children: [_jsx("h2", { id: "cloud-capacity-heading", children: "Need more cloud capacity?" }), _jsx("p", { children: "Pro users can request expanded limits for cloud workspaces, AI agents, and storage." })] }), _jsxs("div", { className: `${styles.planCard} ${styles.planNoticeCard} ${styles.teamComingSoonCard}`, children: [_jsx("h2", { className: styles.planName, children: "Team workspaces are coming soon." }), _jsx("p", { className: styles.planDescription, children: "Shared workspaces, collaborators, team agent rosters, admin controls, and larger cloud limits are in development." })] })] }))] }) }) }));
|
|
251
390
|
}
|