@taskforcehq/taskforce 0.3.303 → 0.3.305
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 +140 -247
- package/dist/Taskforce.module.css +102 -5
- package/dist/TaskforceCore.js +635 -238
- package/dist/TaskforceCore.test.js +2243 -495
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/compat/workspaceSyncCompat.js +6 -0
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/DocumentIndex.d.ts +1 -1
- package/dist/components/features/DocumentIndex.js +1 -1
- package/dist/components/features/DocumentViewer.d.ts +1 -1
- package/dist/components/features/DocumentViewer.js +32 -9
- package/dist/components/features/DocumentWorkspace.d.ts +5 -22
- package/dist/components/features/DocumentWorkspace.js +12 -155
- package/dist/components/features/DocumentWorkspace.test.js +193 -6
- package/dist/components/features/TaskSettings.js +87 -41
- package/dist/components/features/TaskSettings.test.js +180 -40
- package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
- package/dist/components/features/documentWorkspaceModel.js +57 -0
- package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
- package/dist/components/features/useDocumentWorkspaceController.js +113 -0
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +3 -3
- package/dist/components/task/TaskActionHeader.test.js +8 -0
- package/dist/components/task/TaskCard.js +16 -1
- package/dist/components/task/TaskCard.test.js +31 -0
- package/dist/components/task/TaskForm.d.ts +9 -8
- package/dist/components/task/TaskForm.js +374 -219
- package/dist/components/task/TaskForm.test.js +482 -361
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +33 -161
- package/dist/components/task/TaskStatusActions.d.ts +6 -1
- package/dist/components/task/TaskStatusActions.js +8 -3
- package/dist/components/task/TaskStatusActions.test.js +9 -0
- package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
- package/dist/components/task/taskKanbanDropRules.js +187 -0
- package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
- package/dist/components/task/taskKanbanDropRules.test.js +114 -0
- package/dist/components/ui/Modal.d.ts +1 -1
- package/dist/components/ui/Modal.js +1 -0
- package/dist/components/views/AppShellHeader.js +5 -1
- package/dist/components/views/AppShellHeader.test.js +4 -0
- package/dist/components/views/PlanComparisonPage.d.ts +3 -2
- package/dist/components/views/PlanComparisonPage.js +9 -10
- package/dist/components/views/PlanComparisonPage.test.js +55 -41
- package/dist/components/views/PlansPage.d.ts +5 -1
- package/dist/components/views/PlansPage.js +150 -81
- package/dist/components/views/PlansPage.test.js +367 -41
- package/dist/components/views/StandaloneLayout.js +135 -54
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/panels/FilterToolbar.test.js +6 -4
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
- package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/config/envSchema.js +1 -1
- package/dist/core/AiProfileService.d.ts +6 -1
- package/dist/core/AiProfileService.js +161 -16
- package/dist/core/AiProfileService.test.js +3 -1
- package/dist/core/AiProfiles.test.js +200 -0
- package/dist/core/AuthTokenService.test.d.ts +1 -0
- package/dist/core/AuthTokenService.test.js +78 -0
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/EntitlementsPolicy.test.js +75 -13
- package/dist/core/PlanEntitlementService.d.ts +9 -1
- package/dist/core/PlanEntitlementService.js +144 -19
- package/dist/core/PlanVersionPolicy.test.js +19 -26
- package/dist/core/SignupMonetizationSettings.test.js +75 -15
- package/dist/core/SystemAdmin.test.js +212 -56
- package/dist/core/TaskActivityService.d.ts +4 -1
- package/dist/core/TaskActivityService.js +45 -0
- package/dist/core/TaskAuditTimeline.test.js +52 -1
- package/dist/core/TaskTaxonomyValidation.test.js +53 -0
- package/dist/core/Taskforce.d.ts +18 -11
- package/dist/core/Taskforce.js +93 -12
- package/dist/core/WorkspacePlanMode.test.js +73 -8
- package/dist/core/shared.d.ts +2 -0
- package/dist/core/shared.js +11 -0
- package/dist/documentReviews/service.d.ts +1 -1
- package/dist/documentReviews/types.d.ts +1 -1
- package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
- package/dist/hooks/sync/auth.d.ts +37 -0
- package/dist/hooks/sync/auth.js +63 -0
- package/dist/hooks/sync/bootstrap.d.ts +34 -0
- package/dist/hooks/sync/bootstrap.js +52 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
- package/dist/hooks/sync/orchestratorShared.js +209 -0
- package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
- package/dist/hooks/sync/orchestratorShared.test.js +49 -0
- package/dist/hooks/sync/realtime.d.ts +25 -0
- package/dist/hooks/sync/realtime.js +60 -0
- package/dist/hooks/sync/recovery.d.ts +69 -0
- package/dist/hooks/sync/recovery.js +118 -0
- package/dist/hooks/sync/transfers.d.ts +123 -0
- package/dist/hooks/sync/transfers.js +435 -0
- package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
- package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
- package/dist/hooks/ui/useResourceExport.d.ts +3 -1
- package/dist/hooks/ui/useResourceExport.js +5 -2
- package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
- package/dist/hooks/useRealtimeSync.js +2 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +12 -8
- package/dist/hooks/useSyncOrchestrator.js +388 -879
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +641 -6
- package/dist/hooks/useTaskMutations.d.ts +2 -1
- package/dist/hooks/useTaskMutations.js +57 -7
- package/dist/hooks/useTaskMutations.test.js +176 -0
- package/dist/hooks/useTaskforce.d.ts +13 -4
- package/dist/hooks/useTaskforce.js +302 -817
- package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
- package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
- package/dist/hooks/useWorkspaceSyncController.js +33 -11
- package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
- package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
- package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
- package/dist/hooks/workspace/workspaceLocalState.js +100 -0
- package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
- package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
- package/dist/localization/locales/en-US.js +2 -2
- package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
- package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
- package/dist/mcp/canonicalAssetHelpers.js +259 -0
- package/dist/mcp/clientIntegrations.d.ts +12 -1
- package/dist/mcp/clientIntegrations.js +161 -6
- package/dist/mcp/clientIntegrations.test.js +76 -4
- package/dist/mcp/collaborationRegistrar.d.ts +2 -0
- package/dist/mcp/collaborationRegistrar.js +71 -0
- package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
- package/dist/mcp/documentAssetRegistrar.js +346 -0
- package/dist/mcp/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +2455 -3925
- package/dist/mcp/runtime.test.js +240 -8
- package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
- package/dist/mcp/taskAttachmentHelpers.js +60 -0
- package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
- package/dist/mcp/taskAttachmentTypes.js +1 -0
- package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
- package/dist/mcp/taskPlanningRegistrar.js +418 -0
- package/dist/mcp/toolCatalog.d.ts +35 -0
- package/dist/mcp/toolCatalog.js +3 -0
- package/dist/plugins/vite.js +1 -1
- package/dist/runtime/appGateDefinitions.d.ts +1 -1
- package/dist/runtime/appGateDefinitions.js +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +46 -3
- package/dist/server/index.test.js +12 -1
- package/dist/server/routes/admin.d.ts +1 -5
- package/dist/server/routes/admin.js +151 -80
- package/dist/server/routes/annotatedAttachments.d.ts +1 -1
- package/dist/server/routes/annotatedAttachments.js +1 -1
- package/dist/server/routes/auth.d.ts +1 -4
- package/dist/server/routes/auth.js +59 -64
- package/dist/server/routes/authSupport.d.ts +30 -0
- package/dist/server/routes/authSupport.js +81 -0
- package/dist/server/routes/billing.d.ts +1 -1
- package/dist/server/routes/billing.js +122 -255
- package/dist/server/routes/billing.test.js +257 -86
- package/dist/server/routes/documentReviews.d.ts +1 -1
- package/dist/server/routes/documentReviews.js +1 -1
- package/dist/server/routes/documents.d.ts +4 -2
- package/dist/server/routes/documents.js +73 -7
- package/dist/server/routes/primitives.d.ts +11 -0
- package/dist/server/routes/primitives.js +73 -0
- package/dist/server/routes/resources.d.ts +1 -1
- package/dist/server/routes/resources.js +1 -1
- package/dist/server/routes/shared.d.ts +31 -1
- package/dist/server/routes/shared.js +26 -0
- package/dist/server/routes/sync.d.ts +1 -1
- package/dist/server/routes/sync.integration.test.js +20 -0
- package/dist/server/routes/sync.js +111 -238
- package/dist/server/routes/tasks.d.ts +1 -1
- package/dist/server/routes/tasks.js +50 -8
- package/dist/server/routes/workspaces.d.ts +1 -1
- package/dist/server/routes/workspaces.js +1 -1
- package/dist/server/routes.d.ts +2 -9
- package/dist/server/routes.js +84 -181
- package/dist/server/routes.test.js +912 -32
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/collaborationSyncPayload.js +4 -0
- package/dist/sync/collaborationSyncPayload.test.js +8 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -1
- package/dist/sync/collaborationSyncState.js +19 -0
- package/dist/sync/contentSyncState.js +28 -17
- package/dist/sync/syncApplyHandlers.d.ts +87 -1
- package/dist/sync/syncApplyHandlers.js +370 -8
- package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
- package/dist/sync/syncApplyHandlers.test.js +379 -0
- package/dist/sync/syncService.d.ts +9 -1
- package/dist/sync/syncService.js +44 -2
- package/dist/sync/syncService.test.js +62 -0
- package/dist/sync/workspacePullFeed.d.ts +1 -0
- package/dist/sync/workspacePullFeed.js +66 -59
- package/dist/sync/workspacePullFeed.test.js +72 -4
- package/dist/sync/workspaceSyncModel.d.ts +25 -1
- package/dist/sync/workspaceSyncModel.js +77 -3
- package/dist/sync/workspaceSyncModel.test.js +153 -0
- package/dist/sync/workspaceSyncState.d.ts +2 -0
- package/dist/sync/workspaceSyncState.js +1 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-N2MnQBZk.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
- package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
- package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-Dhm7M8e7.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
- package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-BcS4afRn.js} +1 -1
- package/dist/ui/assets/index-C2-OXZV7.css +1 -0
- package/dist/ui/assets/index-DcSI5F86.js +6 -0
- package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-BSUaRwF8.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
- package/dist/utils/commercialLifecyclePresentation.js +196 -0
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +10 -1
- package/scripts/check-cloud-mcp.mjs +83 -0
- package/scripts/playwright-auth.sh +9 -1
- package/scripts/run-billing-performance-smoke.sh +24 -0
- package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
- package/dist/ui/assets/DocumentWorkspace-C3GyzrWm.js +0 -250
- package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
- package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
- package/dist/ui/assets/index-CLIMgR6g.js +0 -6
- package/dist/ui/assets/index-DneETxcu.css +0 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { buildBacklogDropUpdates, buildScheduleDropResult, buildScheduledDayDropUpdates, buildStatusTransitionUpdates, } from './taskKanbanDropRules';
|
|
3
|
+
const makeTask = (overrides = {}) => ({
|
|
4
|
+
id: 'task-1',
|
|
5
|
+
title: 'Task',
|
|
6
|
+
description: null,
|
|
7
|
+
category: 'default',
|
|
8
|
+
type: 'feature',
|
|
9
|
+
priority: 2,
|
|
10
|
+
status: 'task',
|
|
11
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
12
|
+
...overrides,
|
|
13
|
+
});
|
|
14
|
+
const toMap = (pairs) => new Map(pairs.map((pair) => [pair.taskId, pair.updates]));
|
|
15
|
+
describe('taskKanbanDropRules', () => {
|
|
16
|
+
it('reorders backlog tasks and recalculates orderInDay', () => {
|
|
17
|
+
const activeTask = makeTask({ id: 'task-2' });
|
|
18
|
+
const result = toMap(buildBacklogDropUpdates({
|
|
19
|
+
activeTask,
|
|
20
|
+
sourceColumnId: 'backlog',
|
|
21
|
+
targetTasks: [makeTask({ id: 'task-1' }), makeTask({ id: 'task-3' })],
|
|
22
|
+
sourceTasks: [makeTask({ id: 'task-1' }), makeTask({ id: 'task-3' })],
|
|
23
|
+
insertAt: 1,
|
|
24
|
+
}));
|
|
25
|
+
expect(result.get('task-1')).toEqual({ orderInDay: 0 });
|
|
26
|
+
expect(result.get('task-2')).toEqual({ orderInDay: 1 });
|
|
27
|
+
expect(result.get('task-3')).toEqual({ orderInDay: 2 });
|
|
28
|
+
});
|
|
29
|
+
it('moving scheduled task to backlog clears scheduled fields', () => {
|
|
30
|
+
const activeTask = makeTask({ id: 'task-2', scheduledDate: '2026-04-14', scheduledWeekKey: '2026-W16' });
|
|
31
|
+
const result = toMap(buildBacklogDropUpdates({
|
|
32
|
+
activeTask,
|
|
33
|
+
sourceColumnId: 'mon',
|
|
34
|
+
targetTasks: [makeTask({ id: 'task-1' })],
|
|
35
|
+
sourceTasks: [],
|
|
36
|
+
insertAt: 1,
|
|
37
|
+
}));
|
|
38
|
+
expect(result.get('task-2')).toEqual({
|
|
39
|
+
scheduledDate: null,
|
|
40
|
+
scheduledWeekKey: null,
|
|
41
|
+
orderInDay: 1,
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
it('moving task into a scheduled day sets scheduled fields and reindexes source column', () => {
|
|
45
|
+
const activeTask = makeTask({ id: 'task-3' });
|
|
46
|
+
const result = toMap(buildScheduledDayDropUpdates({
|
|
47
|
+
activeTask,
|
|
48
|
+
sourceColumnId: 'backlog',
|
|
49
|
+
targetColumnId: 'mon',
|
|
50
|
+
targetTasks: [makeTask({ id: 'task-1' })],
|
|
51
|
+
sourceTasks: [makeTask({ id: 'task-2' })],
|
|
52
|
+
insertAt: 1,
|
|
53
|
+
scheduledDate: '2026-04-14',
|
|
54
|
+
scheduledWeekKey: '2026-W16',
|
|
55
|
+
}));
|
|
56
|
+
expect(result.get('task-3')).toEqual({
|
|
57
|
+
scheduledDate: '2026-04-14',
|
|
58
|
+
scheduledWeekKey: '2026-W16',
|
|
59
|
+
orderInDay: 1,
|
|
60
|
+
});
|
|
61
|
+
expect(result.get('task-2')).toEqual({ orderInDay: 0 });
|
|
62
|
+
});
|
|
63
|
+
it('blocks non-terminal tasks from being scheduled into the past', () => {
|
|
64
|
+
const result = buildScheduleDropResult({
|
|
65
|
+
activeTask: makeTask({ id: 'task-3', status: 'task' }),
|
|
66
|
+
sourceColumnId: 'backlog',
|
|
67
|
+
targetColumnId: 'mon',
|
|
68
|
+
targetTasks: [],
|
|
69
|
+
sourceTasks: [],
|
|
70
|
+
overTaskId: null,
|
|
71
|
+
isBelowOverItem: false,
|
|
72
|
+
scheduleDates: {
|
|
73
|
+
mon: '2026-04-13',
|
|
74
|
+
tue: '2026-04-14',
|
|
75
|
+
wed: '2026-04-15',
|
|
76
|
+
thu: '2026-04-16',
|
|
77
|
+
fri: '2026-04-17',
|
|
78
|
+
sat: '2026-04-18',
|
|
79
|
+
sun: '2026-04-19',
|
|
80
|
+
},
|
|
81
|
+
parseToIsoWeekKey: () => '2026-W16',
|
|
82
|
+
todayDate: '2026-04-14',
|
|
83
|
+
});
|
|
84
|
+
expect(result).toEqual({ kind: 'blocked', reason: 'Only completed work can be scheduled in the past.' });
|
|
85
|
+
});
|
|
86
|
+
it('preserves cancelled grouping as a no-op for completed column drops', () => {
|
|
87
|
+
const task = makeTask({ id: 'task-cancelled', status: 'cancelled', completedAt: '2026-04-01T00:00:00.000Z' });
|
|
88
|
+
expect(buildStatusTransitionUpdates(task, 'completed', '2026-04-14T07:00:00.000Z')).toBeNull();
|
|
89
|
+
});
|
|
90
|
+
it('stamps completedAt when moving into completed', () => {
|
|
91
|
+
const nowIso = '2026-04-14T07:00:00.000Z';
|
|
92
|
+
const result = buildStatusTransitionUpdates(makeTask({ id: 'task-done' }), 'completed', nowIso);
|
|
93
|
+
expect(result).toEqual({
|
|
94
|
+
status: 'done',
|
|
95
|
+
inProgress: false,
|
|
96
|
+
readyForReview: false,
|
|
97
|
+
completed: true,
|
|
98
|
+
cancelled: false,
|
|
99
|
+
completedAt: nowIso,
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
it('resets review and progress flags when moving back to reviewable workflow states', () => {
|
|
103
|
+
const nowIso = '2026-04-14T07:00:00.000Z';
|
|
104
|
+
const result = buildStatusTransitionUpdates(makeTask({ id: 'task-review', status: 'done', inProgress: true, readyForReview: true, completed: true }), 'review', nowIso);
|
|
105
|
+
expect(result).toEqual({
|
|
106
|
+
status: 'review',
|
|
107
|
+
inProgress: false,
|
|
108
|
+
readyForReview: true,
|
|
109
|
+
completed: false,
|
|
110
|
+
cancelled: false,
|
|
111
|
+
completedAt: null,
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -5,7 +5,7 @@ interface ModalProps {
|
|
|
5
5
|
title: React.ReactNode;
|
|
6
6
|
children: React.ReactNode;
|
|
7
7
|
footer?: React.ReactNode;
|
|
8
|
-
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
8
|
+
size?: 'sm' | 'md' | 'mdWide' | 'lg' | 'xl' | 'full';
|
|
9
9
|
theme?: 'light' | 'dawn' | 'dark' | 'midnight';
|
|
10
10
|
className?: string;
|
|
11
11
|
headerActions?: React.ReactNode;
|
|
@@ -36,6 +36,7 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md', t
|
|
|
36
36
|
const sizeClassMap = {
|
|
37
37
|
sm: styles.modalSizeSm,
|
|
38
38
|
md: styles.modalSizeMd,
|
|
39
|
+
mdWide: styles.modalSizeMdWide,
|
|
39
40
|
lg: styles.modalSizeLg,
|
|
40
41
|
xl: styles.modalSizeXl,
|
|
41
42
|
full: styles.modalSizeFull
|
|
@@ -7,6 +7,10 @@ import favicon from '../../assets/favicon/favicon.svg';
|
|
|
7
7
|
import { DEFAULT_TASKFORCE_THEME, isLightThemeFamily } from '../../utils/theme';
|
|
8
8
|
export function AppShellHeader({ projectName, currentWorkspaceId, brandLabel = 'TaskForce', runtimeMode = 'local', theme = DEFAULT_TASKFORCE_THEME, meta, actions }) {
|
|
9
9
|
const logo = isLightThemeFamily(theme) ? logoDark : logoLight;
|
|
10
|
+
const normalizedProjectName = String(projectName || '').trim();
|
|
11
|
+
const normalizedWorkspaceId = String(currentWorkspaceId || '').trim().toLowerCase();
|
|
12
|
+
const showProjectName = Boolean(normalizedProjectName
|
|
13
|
+
&& (runtimeMode !== 'cloud' || (normalizedWorkspaceId && normalizedWorkspaceId !== 'default')));
|
|
10
14
|
return (_jsxs("div", { className: `${styles.header} ${shellStyles.standaloneHeader}`, children: [_jsxs("div", { className: `${styles.headerTitle} ${shellStyles.standaloneTitle}`, children: [_jsx("img", { src: logo, alt: "Taskforce Logo", className: styles.brandIcon, onError: (event) => {
|
|
11
15
|
const target = event.currentTarget;
|
|
12
16
|
if (target.src !== favicon) {
|
|
@@ -15,5 +19,5 @@ export function AppShellHeader({ projectName, currentWorkspaceId, brandLabel = '
|
|
|
15
19
|
else {
|
|
16
20
|
target.style.display = 'none';
|
|
17
21
|
}
|
|
18
|
-
} }), _jsx("span", { children: brandLabel }), runtimeMode === 'cloud' && (_jsx("span", { className: styles.brandCloudSuffix, children: "HQ" })),
|
|
22
|
+
} }), _jsx("span", { children: brandLabel }), runtimeMode === 'cloud' && (_jsx("span", { className: styles.brandCloudSuffix, children: "HQ" })), showProjectName && (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.projectSlash, children: "/" }), _jsx("span", { className: styles.projectName, title: currentWorkspaceId ? `Workspace ID: ${currentWorkspaceId}` : undefined, children: normalizedProjectName })] })), meta] }), _jsx("div", { className: styles.headerActions, style: { gap: '16px' }, children: actions })] }));
|
|
19
23
|
}
|
|
@@ -18,4 +18,8 @@ describe('AppShellHeader', () => {
|
|
|
18
18
|
render(_jsx(AppShellHeader, { runtimeMode: "local", theme: "dawn" }));
|
|
19
19
|
expect(screen.getByAltText('Taskforce Logo')).toHaveAttribute('src', logoDark);
|
|
20
20
|
});
|
|
21
|
+
it('hides a stale project label while cloud runtime is still on the default workspace', () => {
|
|
22
|
+
render(_jsx(AppShellHeader, { runtimeMode: "cloud", projectName: "Old Workspace", currentWorkspaceId: "default" }));
|
|
23
|
+
expect(screen.queryByText('Old Workspace')).not.toBeInTheDocument();
|
|
24
|
+
});
|
|
21
25
|
});
|
|
@@ -58,16 +58,17 @@ interface PlanComparisonPageProps {
|
|
|
58
58
|
canManageCurrentPlan?: boolean;
|
|
59
59
|
pricingEndpoint?: string;
|
|
60
60
|
statusBanner?: {
|
|
61
|
-
type: 'success' | 'error';
|
|
61
|
+
type: 'success' | 'error' | 'info';
|
|
62
62
|
message: string;
|
|
63
63
|
} | null;
|
|
64
64
|
onBack: () => void;
|
|
65
65
|
backLabel: string;
|
|
66
|
+
backDisabled?: boolean;
|
|
66
67
|
topActions?: React.ReactNode;
|
|
67
68
|
shellOwnsScroll?: boolean;
|
|
68
69
|
showPageBackButton?: boolean;
|
|
69
70
|
fallbackPricingSource?: PricingSourceDetails | null;
|
|
70
71
|
onSelectPlan: (intent: PlanSelectionIntent) => void | Promise<void>;
|
|
71
72
|
}
|
|
72
|
-
export declare function PlanComparisonPage({ heading, subtitle, variant, chrome, footer, isAuthenticated, authSessionResolved, currentPlanId, currentPlanVersionId, currentEntitlementState, canManageCurrentPlan, pricingEndpoint, statusBanner, onBack, backLabel, topActions, shellOwnsScroll, showPageBackButton, fallbackPricingSource, onSelectPlan }: PlanComparisonPageProps): import("react/jsx-runtime").JSX.Element;
|
|
73
|
+
export declare function PlanComparisonPage({ heading, subtitle, variant, chrome, footer, isAuthenticated, authSessionResolved, currentPlanId, currentPlanVersionId, currentEntitlementState, canManageCurrentPlan, pricingEndpoint, statusBanner, onBack, backLabel, backDisabled, topActions, shellOwnsScroll, showPageBackButton, fallbackPricingSource, onSelectPlan }: PlanComparisonPageProps): import("react/jsx-runtime").JSX.Element;
|
|
73
74
|
export {};
|
|
@@ -3,6 +3,7 @@ import { useEffect, useMemo, useState } from 'react';
|
|
|
3
3
|
import { CheckCircle2, Loader2 } from 'lucide-react';
|
|
4
4
|
import taskforceStyles from '../../Taskforce.module.css';
|
|
5
5
|
import styles from './PlansPage.module.css';
|
|
6
|
+
import { isCommercialEntitlementActive } from '../../utils/commercialLifecyclePresentation';
|
|
6
7
|
function isDebugModeEnabled() {
|
|
7
8
|
const runtimeOverride = globalThis.__DEBUG_MODE__;
|
|
8
9
|
if (typeof runtimeOverride === 'boolean')
|
|
@@ -60,11 +61,7 @@ function getPlanSortRank(plan) {
|
|
|
60
61
|
return Math.min(...numericAmounts);
|
|
61
62
|
return Number.POSITIVE_INFINITY;
|
|
62
63
|
}
|
|
63
|
-
function
|
|
64
|
-
const value = String(raw || '').trim().toLowerCase();
|
|
65
|
-
return value === 'active' || value === 'trialing' || value === 'grace';
|
|
66
|
-
}
|
|
67
|
-
export function PlanComparisonPage({ heading, subtitle, variant = 'app', chrome, footer, isAuthenticated, authSessionResolved = true, currentPlanId, currentPlanVersionId, currentEntitlementState, canManageCurrentPlan = true, pricingEndpoint, statusBanner, onBack, backLabel, topActions, shellOwnsScroll = false, showPageBackButton = true, fallbackPricingSource = null, onSelectPlan }) {
|
|
64
|
+
export function PlanComparisonPage({ heading, subtitle, variant = 'app', chrome, footer, isAuthenticated, authSessionResolved = true, currentPlanId, currentPlanVersionId, currentEntitlementState, canManageCurrentPlan = true, pricingEndpoint, statusBanner, onBack, backLabel, backDisabled = false, topActions, shellOwnsScroll = false, showPageBackButton = true, fallbackPricingSource = null, onSelectPlan }) {
|
|
68
65
|
const [plans, setPlans] = useState([]);
|
|
69
66
|
const [pricingSource, setPricingSource] = useState(null);
|
|
70
67
|
const [loading, setLoading] = useState(true);
|
|
@@ -130,17 +127,19 @@ export function PlanComparisonPage({ heading, subtitle, variant = 'app', chrome,
|
|
|
130
127
|
if (error) {
|
|
131
128
|
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" })] }) }) }) }));
|
|
132
129
|
}
|
|
133
|
-
return wrapPage(_jsx(_Fragment, { children: _jsx("div", { className: contentClassName, children: _jsxs("div", { className: styles.contentFrame, children: [_jsxs("div", { className: styles.header, children: [!chrome && (showPageBackButton || topActions) && (_jsxs("div", { className: styles.headerBar, children: [_jsx("div", { children: showPageBackButton ? (_jsx("button", { className: styles.backBtn, onClick: onBack, children: backLabel })) : null }), _jsx("div", { className: styles.headerActions, children: topActions })] })), _jsx("h1", { className: styles.title, children: heading }), subtitle && _jsx("p", { className: styles.subtitle, children: subtitle }), isDebugModeEnabled() && pricingSourceLabel ? (_jsxs("p", { className: styles.debugMeta, children: ["Pricing source: ", pricingSourceLabel] })) : null, statusBanner && (_jsx("div", { className: `${styles.statusBanner} ${statusBanner.type === 'success'
|
|
130
|
+
return wrapPage(_jsx(_Fragment, { children: _jsx("div", { className: contentClassName, children: _jsxs("div", { className: styles.contentFrame, children: [_jsxs("div", { className: styles.header, children: [!chrome && (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: heading }), subtitle && _jsx("p", { className: styles.subtitle, children: subtitle }), isDebugModeEnabled() && pricingSourceLabel ? (_jsxs("p", { className: styles.debugMeta, children: ["Pricing source: ", pricingSourceLabel] })) : null, statusBanner && (_jsx("div", { className: `${styles.statusBanner} ${statusBanner.type === 'success'
|
|
131
|
+
? styles.successBanner
|
|
132
|
+
: (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) => {
|
|
134
133
|
const monthPrice = plan.pricing.month;
|
|
135
134
|
const yearPrice = plan.pricing.year;
|
|
136
135
|
const hasMonth = isSelectablePrice(monthPrice);
|
|
137
136
|
const hasYear = isSelectablePrice(yearPrice);
|
|
138
137
|
const planVersionId = String(plan.defaultVersion?.planVersionId || '').trim();
|
|
139
|
-
const
|
|
138
|
+
const hasActiveEntitlement = isCommercialEntitlementActive(currentEntitlementState);
|
|
139
|
+
const isCurrent = hasActiveEntitlement && (Boolean(planVersionId
|
|
140
140
|
&& currentPlanVersionId
|
|
141
|
-
&& currentPlanVersionId === planVersionId
|
|
142
|
-
&&
|
|
143
|
-
&& Boolean(currentPlanId && currentPlanId === plan.planId));
|
|
141
|
+
&& currentPlanVersionId === planVersionId) || (!currentPlanVersionId
|
|
142
|
+
&& Boolean(currentPlanId && currentPlanId === plan.planId)));
|
|
144
143
|
const defaultInterval = hasMonth ? 'month' : (hasYear ? 'year' : null);
|
|
145
144
|
const defaultPrice = defaultInterval === 'year' ? yearPrice : monthPrice;
|
|
146
145
|
const canSelectCard = Boolean(planVersionId) && (isCurrent ? canManageCurrentPlan : Boolean(defaultInterval));
|
|
@@ -15,11 +15,11 @@ describe('PlanComparisonPage', () => {
|
|
|
15
15
|
},
|
|
16
16
|
plans: [
|
|
17
17
|
{
|
|
18
|
-
planId: '
|
|
19
|
-
displayName: '
|
|
18
|
+
planId: 'free',
|
|
19
|
+
displayName: 'Free',
|
|
20
20
|
status: 'active',
|
|
21
21
|
defaultVersion: {
|
|
22
|
-
planVersionId: '
|
|
22
|
+
planVersionId: 'free-v1',
|
|
23
23
|
versionNumber: 1,
|
|
24
24
|
seatLimit: null,
|
|
25
25
|
metadata: {}
|
|
@@ -31,19 +31,19 @@ describe('PlanComparisonPage', () => {
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
|
-
planId: '
|
|
35
|
-
displayName: '
|
|
34
|
+
planId: 'team-5',
|
|
35
|
+
displayName: 'Team 5',
|
|
36
36
|
status: 'active',
|
|
37
37
|
defaultVersion: {
|
|
38
|
-
planVersionId: '
|
|
38
|
+
planVersionId: 'team-5-v2',
|
|
39
39
|
versionNumber: 2,
|
|
40
40
|
seatLimit: 5,
|
|
41
41
|
metadata: {}
|
|
42
42
|
},
|
|
43
43
|
features: [],
|
|
44
44
|
pricing: {
|
|
45
|
-
month: { pricingType: 'stripe', stripePriceId: '
|
|
46
|
-
year: { pricingType: 'stripe', stripePriceId: '
|
|
45
|
+
month: { pricingType: 'stripe', stripePriceId: 'price_team_5_month', unitAmount: 2000, currency: 'usd', interval: 'month' },
|
|
46
|
+
year: { pricingType: 'stripe', stripePriceId: 'price_team_5_year', unitAmount: 20000, currency: 'usd', interval: 'year' }
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
]
|
|
@@ -66,11 +66,11 @@ describe('PlanComparisonPage', () => {
|
|
|
66
66
|
json: async () => ({
|
|
67
67
|
plans: [
|
|
68
68
|
{
|
|
69
|
-
planId: '
|
|
70
|
-
displayName: '
|
|
69
|
+
planId: 'free',
|
|
70
|
+
displayName: 'Free',
|
|
71
71
|
status: 'active',
|
|
72
72
|
defaultVersion: {
|
|
73
|
-
planVersionId: '
|
|
73
|
+
planVersionId: 'free-v1',
|
|
74
74
|
versionNumber: 1,
|
|
75
75
|
seatLimit: null,
|
|
76
76
|
metadata: {}
|
|
@@ -93,24 +93,38 @@ describe('PlanComparisonPage', () => {
|
|
|
93
93
|
expect(screen.queryByText('Pricing source: production (cloud)')).not.toBeInTheDocument();
|
|
94
94
|
});
|
|
95
95
|
it('does not mark a plan current when only the plan id matches but the version does not', async () => {
|
|
96
|
-
render(_jsx(PlanComparisonPage, { heading: "Subscription Plans", isAuthenticated: true, currentPlanId: "
|
|
96
|
+
render(_jsx(PlanComparisonPage, { heading: "Subscription Plans", isAuthenticated: true, currentPlanId: "team-5", currentPlanVersionId: "team-5-v1", currentEntitlementState: "active", onBack: () => { }, backLabel: "Back", onSelectPlan: () => { } }));
|
|
97
97
|
expect(await screen.findByRole('heading', { name: 'Subscription Plans' })).toBeInTheDocument();
|
|
98
98
|
expect(screen.queryByText('Current Plan')).not.toBeInTheDocument();
|
|
99
99
|
});
|
|
100
|
+
it('marks a trialing entitlement as the current plan', async () => {
|
|
101
|
+
render(_jsx(PlanComparisonPage, { heading: "Subscription Plans", isAuthenticated: true, currentPlanId: "team-5", currentPlanVersionId: "team-5-v2", currentEntitlementState: "trialing", onBack: () => { }, backLabel: "Back", onSelectPlan: () => { } }));
|
|
102
|
+
expect(await screen.findByRole('heading', { name: 'Subscription Plans' })).toBeInTheDocument();
|
|
103
|
+
expect(screen.getByText('Current Plan')).toBeInTheDocument();
|
|
104
|
+
});
|
|
105
|
+
it('does not mark a canceled entitlement as current through the plan-id fallback path', async () => {
|
|
106
|
+
render(_jsx(PlanComparisonPage, { heading: "Subscription Plans", isAuthenticated: true, currentPlanId: "team-5", currentPlanVersionId: null, currentEntitlementState: "canceled", onBack: () => { }, backLabel: "Back", onSelectPlan: () => { } }));
|
|
107
|
+
expect(await screen.findByRole('heading', { name: 'Subscription Plans' })).toBeInTheDocument();
|
|
108
|
+
expect(screen.queryByText('Current Plan')).not.toBeInTheDocument();
|
|
109
|
+
});
|
|
110
|
+
it('can disable the page back button while onboarding remains blocked', async () => {
|
|
111
|
+
render(_jsx(PlanComparisonPage, { heading: "Subscription Plans", isAuthenticated: true, onBack: () => { }, backLabel: "Back to App", backDisabled: true, onSelectPlan: () => { } }));
|
|
112
|
+
expect(await screen.findByRole('button', { name: 'Back to App' })).toBeDisabled();
|
|
113
|
+
});
|
|
100
114
|
it('selects the whole plan card using the default available interval', async () => {
|
|
101
115
|
const onSelectPlan = vi.fn();
|
|
102
116
|
render(_jsx(PlanComparisonPage, { heading: "Subscription Plans", isAuthenticated: false, onBack: () => { }, backLabel: "Back", onSelectPlan: onSelectPlan }));
|
|
103
117
|
await screen.findByRole('heading', { name: 'Subscription Plans' });
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
expect(
|
|
107
|
-
expect(within(
|
|
108
|
-
expect(within(
|
|
109
|
-
fireEvent.click(
|
|
118
|
+
const freeHeading = screen.getByRole('heading', { name: 'Free' });
|
|
119
|
+
const freeCard = freeHeading.closest('[role="button"]');
|
|
120
|
+
expect(freeCard).toBeTruthy();
|
|
121
|
+
expect(within(freeCard).getByText('$0.00')).toBeInTheDocument();
|
|
122
|
+
expect(within(freeCard).getByText('/ month')).toBeInTheDocument();
|
|
123
|
+
fireEvent.click(freeCard);
|
|
110
124
|
await waitFor(() => {
|
|
111
125
|
expect(onSelectPlan).toHaveBeenCalledWith({
|
|
112
|
-
planId: '
|
|
113
|
-
planVersionId: '
|
|
126
|
+
planId: 'free',
|
|
127
|
+
planVersionId: 'free-v1',
|
|
114
128
|
interval: 'month',
|
|
115
129
|
pricingType: 'free',
|
|
116
130
|
isCurrentPlan: false
|
|
@@ -119,7 +133,7 @@ describe('PlanComparisonPage', () => {
|
|
|
119
133
|
});
|
|
120
134
|
it('does not trigger current-plan actions when the current plan is not Stripe-managed', async () => {
|
|
121
135
|
const onSelectPlan = vi.fn();
|
|
122
|
-
render(_jsx(PlanComparisonPage, { heading: "Subscription Plans", isAuthenticated: true, currentPlanId: "
|
|
136
|
+
render(_jsx(PlanComparisonPage, { heading: "Subscription Plans", isAuthenticated: true, currentPlanId: "free", currentPlanVersionId: "free-v1", currentEntitlementState: "active", canManageCurrentPlan: false, onBack: () => { }, backLabel: "Back", onSelectPlan: onSelectPlan }));
|
|
123
137
|
await screen.findByRole('heading', { name: 'Subscription Plans' });
|
|
124
138
|
const currentButton = screen.getByRole('button', { name: 'Current Plan' });
|
|
125
139
|
expect(currentButton).toBeDisabled();
|
|
@@ -135,11 +149,11 @@ describe('PlanComparisonPage', () => {
|
|
|
135
149
|
},
|
|
136
150
|
plans: [
|
|
137
151
|
{
|
|
138
|
-
planId: '
|
|
139
|
-
displayName: '
|
|
152
|
+
planId: 'free',
|
|
153
|
+
displayName: 'Free',
|
|
140
154
|
status: 'active',
|
|
141
155
|
defaultVersion: {
|
|
142
|
-
planVersionId: '
|
|
156
|
+
planVersionId: 'free-v1',
|
|
143
157
|
versionNumber: 1,
|
|
144
158
|
seatLimit: null,
|
|
145
159
|
metadata: {}
|
|
@@ -155,7 +169,7 @@ describe('PlanComparisonPage', () => {
|
|
|
155
169
|
});
|
|
156
170
|
vi.stubGlobal('fetch', fetchMock);
|
|
157
171
|
render(_jsx(PlanComparisonPage, { heading: "Subscription Plans", isAuthenticated: false, pricingEndpoint: "https://performance-app.example/api/taskforce/public/pricing", onBack: () => { }, backLabel: "Back", onSelectPlan: () => { } }));
|
|
158
|
-
expect(await screen.findByRole('heading', { name: '
|
|
172
|
+
expect(await screen.findByRole('heading', { name: 'Free' })).toBeInTheDocument();
|
|
159
173
|
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
160
174
|
expect(fetchMock).toHaveBeenCalledWith('https://performance-app.example/api/taskforce/public/pricing', {
|
|
161
175
|
method: 'GET',
|
|
@@ -173,27 +187,27 @@ describe('PlanComparisonPage', () => {
|
|
|
173
187
|
},
|
|
174
188
|
plans: [
|
|
175
189
|
{
|
|
176
|
-
planId: '
|
|
177
|
-
displayName: '
|
|
190
|
+
planId: 'team-5',
|
|
191
|
+
displayName: 'Team 5',
|
|
178
192
|
status: 'active',
|
|
179
193
|
defaultVersion: {
|
|
180
|
-
planVersionId: '
|
|
194
|
+
planVersionId: 'team-5-v2',
|
|
181
195
|
versionNumber: 3,
|
|
182
|
-
seatLimit:
|
|
196
|
+
seatLimit: 5,
|
|
183
197
|
metadata: {}
|
|
184
198
|
},
|
|
185
199
|
features: [],
|
|
186
200
|
pricing: {
|
|
187
|
-
month: { pricingType: 'stripe', stripePriceId: '
|
|
188
|
-
year: { pricingType: 'stripe', stripePriceId: '
|
|
201
|
+
month: { pricingType: 'stripe', stripePriceId: 'price_team_5_month', unitAmount: 12000, currency: 'usd', interval: 'month' },
|
|
202
|
+
year: { pricingType: 'stripe', stripePriceId: 'price_team_5_year', unitAmount: 120000, currency: 'usd', interval: 'year' }
|
|
189
203
|
}
|
|
190
204
|
},
|
|
191
205
|
{
|
|
192
|
-
planId: '
|
|
193
|
-
displayName: '
|
|
206
|
+
planId: 'free',
|
|
207
|
+
displayName: 'Free',
|
|
194
208
|
status: 'active',
|
|
195
209
|
defaultVersion: {
|
|
196
|
-
planVersionId: '
|
|
210
|
+
planVersionId: 'free-v1',
|
|
197
211
|
versionNumber: 1,
|
|
198
212
|
seatLimit: null,
|
|
199
213
|
metadata: {}
|
|
@@ -205,19 +219,19 @@ describe('PlanComparisonPage', () => {
|
|
|
205
219
|
}
|
|
206
220
|
},
|
|
207
221
|
{
|
|
208
|
-
planId: '
|
|
209
|
-
displayName: '
|
|
222
|
+
planId: 'basic',
|
|
223
|
+
displayName: 'Basic',
|
|
210
224
|
status: 'active',
|
|
211
225
|
defaultVersion: {
|
|
212
|
-
planVersionId: '
|
|
226
|
+
planVersionId: 'basic-v1',
|
|
213
227
|
versionNumber: 2,
|
|
214
|
-
seatLimit:
|
|
228
|
+
seatLimit: 1,
|
|
215
229
|
metadata: {}
|
|
216
230
|
},
|
|
217
231
|
features: [],
|
|
218
232
|
pricing: {
|
|
219
|
-
month: { pricingType: 'stripe', stripePriceId: '
|
|
220
|
-
year: { pricingType: 'stripe', stripePriceId: '
|
|
233
|
+
month: { pricingType: 'stripe', stripePriceId: 'price_basic_month', unitAmount: 3000, currency: 'usd', interval: 'month' },
|
|
234
|
+
year: { pricingType: 'stripe', stripePriceId: 'price_basic_year', unitAmount: 30000, currency: 'usd', interval: 'year' }
|
|
221
235
|
}
|
|
222
236
|
}
|
|
223
237
|
]
|
|
@@ -227,7 +241,7 @@ describe('PlanComparisonPage', () => {
|
|
|
227
241
|
render(_jsx(PlanComparisonPage, { heading: "Subscription Plans", isAuthenticated: false, onBack: () => { }, backLabel: "Back", onSelectPlan: () => { } }));
|
|
228
242
|
await screen.findByRole('heading', { name: 'Subscription Plans' });
|
|
229
243
|
const planNames = screen.getAllByRole('heading', { level: 3 }).map((node) => node.textContent);
|
|
230
|
-
expect(planNames).toEqual(['
|
|
244
|
+
expect(planNames).toEqual(['Free', 'Basic', 'Team 5']);
|
|
231
245
|
});
|
|
232
246
|
it('defaults to the same-origin pricing endpoint', async () => {
|
|
233
247
|
const fetchMock = vi.fn().mockResolvedValue({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AccountProfileSummary } from '../../utils/accountProfileSummaryCache';
|
|
1
2
|
interface PlansPageProps {
|
|
2
3
|
isAuthenticated: boolean;
|
|
3
4
|
authUserId?: string | null;
|
|
@@ -11,6 +12,9 @@ interface PlansPageProps {
|
|
|
11
12
|
resolveCloudAuthUrl?: (path: string) => string;
|
|
12
13
|
embedded?: boolean;
|
|
13
14
|
shellOwnsScroll?: boolean;
|
|
15
|
+
onAccountProfileSummaryChange?: (summary: AccountProfileSummary | null) => void;
|
|
16
|
+
onContinueToTaskforce?: () => void | Promise<void>;
|
|
17
|
+
continueBusy?: boolean;
|
|
14
18
|
}
|
|
15
|
-
export declare function PlansPage({ isAuthenticated, authUserId, runtimeMode, authSessionResolved, currentTheme, projectName, currentWorkspaceId, apiBaseUrl, connectedEnvironmentSource, resolveCloudAuthUrl, embedded, shellOwnsScroll }: PlansPageProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export declare function PlansPage({ isAuthenticated, authUserId, runtimeMode, authSessionResolved, currentTheme, projectName, currentWorkspaceId, apiBaseUrl, connectedEnvironmentSource, resolveCloudAuthUrl, embedded, shellOwnsScroll, onAccountProfileSummaryChange, onContinueToTaskforce, continueBusy }: PlansPageProps): import("react/jsx-runtime").JSX.Element;
|
|
16
20
|
export {};
|