@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
|
@@ -35,9 +35,10 @@ import { getLocale, t } from '../../localization';
|
|
|
35
35
|
import { resolveAnnotatedAttachmentDetail } from '../../utils/annotatedAttachments';
|
|
36
36
|
import { ACCOUNT_PROFILE_SUMMARY_PATH, invalidateAccountProfileSummaryCache, loadAccountProfileSummaryCached } from '../../utils/accountProfileSummaryCache';
|
|
37
37
|
import { fetchTaskforceApi } from '../../utils/taskforceApiClient';
|
|
38
|
+
import { resolveCommercialLifecyclePresentation } from '../../utils/commercialLifecyclePresentation';
|
|
38
39
|
import { AppShellHeader } from './AppShellHeader';
|
|
39
40
|
import { getTaskforceCloudEnvironmentConfig, parseTaskforceCloudEnvironment, resolveTaskforceCloudEnvironmentFromBaseUrl } from '../../config/cloudEnvironment';
|
|
40
|
-
import { logBootstrapDebug } from '../../hooks/workspace/workspaceLocalState';
|
|
41
|
+
import { logBootstrapDebug, persistRemoteUiState } from '../../hooks/workspace/workspaceLocalState';
|
|
41
42
|
import { FilterToolbar } from './panels/FilterToolbar';
|
|
42
43
|
import { DocumentFilterToolbar } from './panels/DocumentFilterToolbar';
|
|
43
44
|
import { PlanningDrawer, getPlanningDrawerWidthPx, } from './panels/PlanningDrawer';
|
|
@@ -192,9 +193,6 @@ export function StandaloneLayout(props) {
|
|
|
192
193
|
const nextQuery = params.toString();
|
|
193
194
|
navigate(`${location.pathname === '/' ? '/' : location.pathname}${nextQuery ? `?${nextQuery}` : ''}${location.hash || ''}`);
|
|
194
195
|
}, [location.hash, location.pathname, location.search, navigate]);
|
|
195
|
-
const handlePlansBack = useCallback(() => {
|
|
196
|
-
closePlansScreen();
|
|
197
|
-
}, [closePlansScreen]);
|
|
198
196
|
const {
|
|
199
197
|
// State
|
|
200
198
|
activeTab, setActiveTab, activeCategories, groupBy, setGroupBy, activeWorkspaceModule, setActiveWorkspaceModule, emptyColumnMode, setEmptyColumnMode, zenMode, setZenMode, filterStatus, setFilterStatus,
|
|
@@ -504,34 +502,31 @@ export function StandaloneLayout(props) {
|
|
|
504
502
|
return;
|
|
505
503
|
const timeoutId = window.setTimeout(async () => {
|
|
506
504
|
try {
|
|
507
|
-
await
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
annotatedSessionId: requestedAnnotatedSessionId
|
|
533
|
-
}
|
|
534
|
-
})
|
|
505
|
+
await persistRemoteUiState({
|
|
506
|
+
stateKey: 'layout',
|
|
507
|
+
workspaceId: currentWorkspaceId,
|
|
508
|
+
headers: workspaceUiStateHeaders,
|
|
509
|
+
patch: {
|
|
510
|
+
scheduleShowWeekends,
|
|
511
|
+
scheduleShowBacklog,
|
|
512
|
+
scheduleOnlyExpired,
|
|
513
|
+
scheduleSidebarOpen,
|
|
514
|
+
scheduleSelectedDate,
|
|
515
|
+
scheduleCalendarMonth,
|
|
516
|
+
scheduleScrollLeft,
|
|
517
|
+
showFilters,
|
|
518
|
+
documentTypeFilters,
|
|
519
|
+
documentAttachmentFilters,
|
|
520
|
+
planningDrawerOpen,
|
|
521
|
+
planningTreeCollapsed,
|
|
522
|
+
planningPrimaryCollapsed,
|
|
523
|
+
planningSecondaryCollapsed,
|
|
524
|
+
planningExpandedInitiativeIds: Array.from(expandedPlanningInitiativeIds),
|
|
525
|
+
planningDrawerDetail,
|
|
526
|
+
planningNestedWorkstreamDetailId,
|
|
527
|
+
annotatedTarget: requestedAnnotatedTarget,
|
|
528
|
+
annotatedSessionId: requestedAnnotatedSessionId
|
|
529
|
+
}
|
|
535
530
|
});
|
|
536
531
|
}
|
|
537
532
|
catch {
|
|
@@ -828,8 +823,13 @@ export function StandaloneLayout(props) {
|
|
|
828
823
|
assigneeOptions.forEach((option) => {
|
|
829
824
|
map.set(String(option.value), option.label);
|
|
830
825
|
});
|
|
826
|
+
const normalizedAuthUserId = String(authUserId || '').trim();
|
|
827
|
+
const normalizedAuthIdentityLabel = String(authUserDisplayName || authUserEmail || '').trim();
|
|
828
|
+
if (normalizedAuthUserId && normalizedAuthIdentityLabel) {
|
|
829
|
+
map.set(normalizedAuthUserId, normalizedAuthIdentityLabel);
|
|
830
|
+
}
|
|
831
831
|
return map;
|
|
832
|
-
}, [assigneeOptions]);
|
|
832
|
+
}, [assigneeOptions, authUserDisplayName, authUserEmail, authUserId]);
|
|
833
833
|
const planningStructure = useMemo(() => {
|
|
834
834
|
const planningScopedTasks = taskScope === 'archived'
|
|
835
835
|
? archivedScopeAllTasks
|
|
@@ -928,28 +928,36 @@ export function StandaloneLayout(props) {
|
|
|
928
928
|
const referenceLabel = formatInitiativeReference(initiative);
|
|
929
929
|
return {
|
|
930
930
|
value: initiative.id,
|
|
931
|
-
label: referenceLabel
|
|
932
|
-
selectedLabel: referenceLabel || initiative.title,
|
|
931
|
+
label: referenceLabel || initiative.title,
|
|
933
932
|
};
|
|
934
933
|
}), [planningStructure.initiatives]);
|
|
935
|
-
const workstreamFilterOptions = useMemo(() =>
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
label: referenceLabel ? `${referenceLabel} - ${workstream.title}` : workstream.title,
|
|
941
|
-
selectedLabel: referenceLabel || workstream.title,
|
|
942
|
-
};
|
|
943
|
-
})),
|
|
944
|
-
...planningStructure.standaloneWorkstreams.map((workstream) => {
|
|
934
|
+
const workstreamFilterOptions = useMemo(() => {
|
|
935
|
+
const visibleInitiativeWorkstreams = selectedPlanningInitiativeId
|
|
936
|
+
? (planningStructure.initiativeById.get(selectedPlanningInitiativeId)?.workstreams || [])
|
|
937
|
+
: planningStructure.initiatives.flatMap((initiative) => initiative.workstreams);
|
|
938
|
+
const initiativeWorkstreams = visibleInitiativeWorkstreams.map((workstream) => {
|
|
945
939
|
const referenceLabel = formatWorkstreamReference(workstream);
|
|
946
940
|
return {
|
|
947
941
|
value: workstream.id,
|
|
948
|
-
label: referenceLabel
|
|
949
|
-
selectedLabel: referenceLabel || workstream.title,
|
|
942
|
+
label: referenceLabel || workstream.title,
|
|
950
943
|
};
|
|
951
|
-
})
|
|
952
|
-
|
|
944
|
+
});
|
|
945
|
+
const standaloneWorkstreams = selectedPlanningInitiativeId
|
|
946
|
+
? []
|
|
947
|
+
: planningStructure.standaloneWorkstreams.map((workstream) => {
|
|
948
|
+
const referenceLabel = formatWorkstreamReference(workstream);
|
|
949
|
+
return {
|
|
950
|
+
value: workstream.id,
|
|
951
|
+
label: referenceLabel || workstream.title,
|
|
952
|
+
};
|
|
953
|
+
});
|
|
954
|
+
return [...initiativeWorkstreams, ...standaloneWorkstreams];
|
|
955
|
+
}, [
|
|
956
|
+
planningStructure.initiativeById,
|
|
957
|
+
planningStructure.initiatives,
|
|
958
|
+
planningStructure.standaloneWorkstreams,
|
|
959
|
+
selectedPlanningInitiativeId,
|
|
960
|
+
]);
|
|
953
961
|
const selectedPlanningTaskIds = useMemo(() => {
|
|
954
962
|
if (selectedPlanningWorkstreamId) {
|
|
955
963
|
return new Set(planningStructure.workstreamTaskIds.get(selectedPlanningWorkstreamId) || []);
|
|
@@ -1229,6 +1237,7 @@ export function StandaloneLayout(props) {
|
|
|
1229
1237
|
const [billingError, setBillingError] = useState(null);
|
|
1230
1238
|
const [billingActionBusy, setBillingActionBusy] = useState(false);
|
|
1231
1239
|
const [billingActionError, setBillingActionError] = useState(null);
|
|
1240
|
+
const [plansNavigationBusy, setPlansNavigationBusy] = useState(false);
|
|
1232
1241
|
const [billingNotice, setBillingNotice] = useState(null);
|
|
1233
1242
|
const [billingIntervalChoice, setBillingIntervalChoice] = useState('month');
|
|
1234
1243
|
const [profileDisplayNameDraft, setProfileDisplayNameDraft] = useState('');
|
|
@@ -1543,7 +1552,7 @@ export function StandaloneLayout(props) {
|
|
|
1543
1552
|
});
|
|
1544
1553
|
accountSurfaceOpenStartedAtRef.current = null;
|
|
1545
1554
|
}, [accountProfileSummary?.planId, canOpenTeamManagement, showAccountHub, showAccountMenu]);
|
|
1546
|
-
const loadAccountProfileSummary = useCallback(async () => {
|
|
1555
|
+
const loadAccountProfileSummary = useCallback(async (options) => {
|
|
1547
1556
|
const accountProfileSummaryUrl = resolveCloudAuthUrl(ACCOUNT_PROFILE_SUMMARY_PATH);
|
|
1548
1557
|
if (!isAuthenticated) {
|
|
1549
1558
|
setAccountProfileSummary(null);
|
|
@@ -1563,7 +1572,10 @@ export function StandaloneLayout(props) {
|
|
|
1563
1572
|
setTeamManagementAccessLoading(canAccessCloudTeamManagement);
|
|
1564
1573
|
setTeamManagementAccessError(null);
|
|
1565
1574
|
try {
|
|
1566
|
-
const payload = await loadAccountProfileSummaryCached(accountProfileSummaryUrl, {
|
|
1575
|
+
const payload = await loadAccountProfileSummaryCached(accountProfileSummaryUrl, {
|
|
1576
|
+
identityKey: authUserId,
|
|
1577
|
+
force: options?.force === true
|
|
1578
|
+
});
|
|
1567
1579
|
const nextRole = payload?.workspaceRole === 'owner'
|
|
1568
1580
|
|| payload?.workspaceRole === 'admin'
|
|
1569
1581
|
|| payload?.workspaceRole === 'member'
|
|
@@ -1611,6 +1623,23 @@ export function StandaloneLayout(props) {
|
|
|
1611
1623
|
markAccountSurfaceLoadSettled('teamManagement');
|
|
1612
1624
|
}
|
|
1613
1625
|
}, [authUserId, canAccessCloudTeamManagement, isAuthenticated, markAccountSurfaceLoadSettled, resolveCloudAuthUrl]);
|
|
1626
|
+
const applyPlansAccountProfileSummary = useCallback((payload) => {
|
|
1627
|
+
setAccountProfileSummary(payload);
|
|
1628
|
+
const nextRole = payload?.workspaceRole === 'owner'
|
|
1629
|
+
|| payload?.workspaceRole === 'admin'
|
|
1630
|
+
|| payload?.workspaceRole === 'member'
|
|
1631
|
+
|| payload?.workspaceRole === 'read-only'
|
|
1632
|
+
? payload.workspaceRole
|
|
1633
|
+
: null;
|
|
1634
|
+
const nextMode = payload?.teamPlanMode === 'team' || payload?.teamPlanMode === 'personal'
|
|
1635
|
+
? payload.teamPlanMode
|
|
1636
|
+
: 'unknown';
|
|
1637
|
+
setTeamWorkspaceRole(nextRole);
|
|
1638
|
+
setTeamPlanMode(nextMode);
|
|
1639
|
+
}, []);
|
|
1640
|
+
const hasActiveCommercialEntitlement = useMemo(() => {
|
|
1641
|
+
return resolveCommercialLifecyclePresentation(accountProfileSummary).allowReturnToApp;
|
|
1642
|
+
}, [accountProfileSummary]);
|
|
1614
1643
|
const handleStartCheckout = useCallback(async () => {
|
|
1615
1644
|
const params = new URLSearchParams();
|
|
1616
1645
|
params.set('screen', 'plans');
|
|
@@ -1798,6 +1827,46 @@ export function StandaloneLayout(props) {
|
|
|
1798
1827
|
}
|
|
1799
1828
|
navigate(`/setup?${params.toString()}`, { replace: true });
|
|
1800
1829
|
}, [navigate]);
|
|
1830
|
+
const handlePlansBack = useCallback(async () => {
|
|
1831
|
+
if (plansNavigationBusy)
|
|
1832
|
+
return;
|
|
1833
|
+
if (!isAuthenticated) {
|
|
1834
|
+
closePlansScreen();
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
const summary = await loadAccountProfileSummary({ force: true });
|
|
1838
|
+
if (!resolveCommercialLifecyclePresentation(summary).allowReturnToApp) {
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
setPlansNavigationBusy(true);
|
|
1842
|
+
try {
|
|
1843
|
+
const result = await props.continueAfterCommercialOnboarding();
|
|
1844
|
+
if (!result.success) {
|
|
1845
|
+
pushNotice(result.error || 'Unable to finish onboarding.', 'error');
|
|
1846
|
+
if (result.destination === 'login') {
|
|
1847
|
+
navigate('/login', { replace: true });
|
|
1848
|
+
}
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
if (result.destination === 'setup') {
|
|
1852
|
+
openWorkspaceSetup();
|
|
1853
|
+
return;
|
|
1854
|
+
}
|
|
1855
|
+
closePlansScreen();
|
|
1856
|
+
}
|
|
1857
|
+
finally {
|
|
1858
|
+
setPlansNavigationBusy(false);
|
|
1859
|
+
}
|
|
1860
|
+
}, [
|
|
1861
|
+
closePlansScreen,
|
|
1862
|
+
isAuthenticated,
|
|
1863
|
+
loadAccountProfileSummary,
|
|
1864
|
+
navigate,
|
|
1865
|
+
openWorkspaceSetup,
|
|
1866
|
+
plansNavigationBusy,
|
|
1867
|
+
props,
|
|
1868
|
+
pushNotice
|
|
1869
|
+
]);
|
|
1801
1870
|
const handleSwitchWorkspace = useCallback(async (workspaceId) => {
|
|
1802
1871
|
if (!workspaceId || workspaceActionBusy)
|
|
1803
1872
|
return;
|
|
@@ -2545,6 +2614,9 @@ export function StandaloneLayout(props) {
|
|
|
2545
2614
|
}, role: "menuitem", children: [_jsx(User, { size: 15 }), "Sign In / Register"] })), isAuthenticated && (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: async () => {
|
|
2546
2615
|
setShowAccountMenu(false);
|
|
2547
2616
|
await logout();
|
|
2617
|
+
if (runtimeMode === 'cloud') {
|
|
2618
|
+
navigate('/login', { replace: true });
|
|
2619
|
+
}
|
|
2548
2620
|
}, role: "menuitem", children: [_jsx(LogOut, { size: 15 }), "Sign Out"] }))] }))] }));
|
|
2549
2621
|
const cycleEmptyColumnVisibility = useCallback(() => {
|
|
2550
2622
|
setEmptyColumnMode((prev) => {
|
|
@@ -3180,7 +3252,7 @@ export function StandaloneLayout(props) {
|
|
|
3180
3252
|
display: 'inline-flex',
|
|
3181
3253
|
alignItems: 'center',
|
|
3182
3254
|
justifyContent: 'center'
|
|
3183
|
-
}, children: syncStatusMeta.icon === 'off' ? (_jsx(CloudOff, { size: 16 })) : (_jsx(Cloud, { size: 16 })) }))] })), actions: (_jsxs(_Fragment, { children: [isPlansScreen && (_jsx("button", { className: "tf-control-icon", onClick: handlePlansBack, title:
|
|
3255
|
+
}, children: syncStatusMeta.icon === 'off' ? (_jsx(CloudOff, { size: 16 })) : (_jsx(Cloud, { size: 16 })) }))] })), actions: (_jsxs(_Fragment, { children: [isPlansScreen && (_jsx("button", { className: "tf-control-icon", onClick: () => { void handlePlansBack(); }, title: isAuthenticated && !hasActiveCommercialEntitlement ? 'Choose a plan to continue.' : 'Continue to Taskforce', disabled: plansNavigationBusy || (isAuthenticated && !hasActiveCommercialEntitlement), children: hasActiveCommercialEntitlement ? 'Take Me to Taskforce' : 'Back' })), !isPlansScreen && (_jsxs(_Fragment, { children: [workspaceHeaderSections, workspacePrimaryAction] })), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalDisplayActions }), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalFilterActions }), accountMenuControl] })) }), _jsx(TopNoticeLayer, { notice: uiNotice, onDismiss: clearNotice }), authBlocked && (_jsx("div", { className: authStyles.authBlockedBanner, children: "Authentication required for this environment. Use the Account menu to sign in." })), !isPlansScreen && showFilters && workspaceFilterBar, isPlansScreen ? (_jsx("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} ${styles.appScrollbar} tf-scrollbar`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflowY: 'auto', overflowX: 'hidden', scrollbarGutter: 'stable' }, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(PlansPage, { ...props, currentTheme: currentTheme, projectName: projectName, currentWorkspaceId: currentWorkspaceId, authUserId: authUserId, apiBaseUrl: apiBaseUrl, connectedEnvironmentSource: props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '', resolveCloudAuthUrl: resolveCloudAuthUrl, embedded: true, shellOwnsScroll: true, onAccountProfileSummaryChange: applyPlansAccountProfileSummary, onContinueToTaskforce: handlePlansBack, continueBusy: plansNavigationBusy }) }) })) : resolvedWorkspaceModule === 'docs' && documentsWorkspaceEnabled ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(DocumentWorkspaceShell, { tasks: tasks, runtimeMode: runtimeMode, apiBaseUrl: props.config?.apiBaseUrl || '', cloudAuthBaseUrl: props.config?.cloudAuthBaseUrl || '', typeFilters: documentTypeFilters, attachmentFilters: documentAttachmentFilters, onTypeFiltersChange: setDocumentTypeFilters, onAttachmentFiltersChange: setDocumentAttachmentFilters, requestedDocPath: requestedDocPath, requestedDocAssetId: requestedDocAssetId, onRequestedDocHandled: () => {
|
|
3184
3256
|
setRequestedDocPath(null);
|
|
3185
3257
|
setRequestedDocAssetId(null);
|
|
3186
3258
|
}, enableTaskGeneration: true }) })] })) : resolvedWorkspaceModule === 'annotate' && annotatedAttachmentsWorkspaceEnabled ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(AnnotatedAttachmentWorkspaceShell, { runtimeMode: runtimeMode, apiBaseUrl: props.config?.apiBaseUrl || '', cloudAuthBaseUrl: props.config?.cloudAuthBaseUrl || '', workspaceId: currentWorkspaceId, sessionLoadReady: annotatedWorkspaceReady, requestedTarget: requestedAnnotatedTarget, requestedSessionId: requestedAnnotatedSessionId, requestedOpenVersion: requestedAnnotatedOpenVersion, resolveTaskReferenceLabel: resolveAnnotatedTaskReferenceLabel, resolveImageReferenceLabel: resolveAnnotatedImageReferenceLabel, onOpenTarget: handleAnnotatedAttachmentOpenTarget, onContextChange: handleAnnotatedAttachmentContextChange, onBackToTask: handleBackToTaskFromAnnotatedAttachment }) })] })) : resolvedWorkspaceModule === 'workflows' ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(WorkflowsModule, { availableWorkflows: availableWorkflows, availableEnvironments: availableEnvironments, exportEnvironment: exportEnvironment, exportWorkflowsPath: props.exportWorkflowsPath, exportingResource: exportingResource, exportResult: exportResult, onExportEnvironmentChange: props.setExportEnvironment, onExportWorkflows: onExportWorkflows, onRefreshWorkflows: fetchWorkflows, onFetchWorkflowTemplate: props.fetchWorkflowTemplate, onFetchWorkflowOverrideNames: props.fetchWorkflowOverrideNames, onSaveWorkflowTemplateDraft: props.saveWorkflowTemplateDraft, onResetWorkflowTemplateDraft: props.resetWorkflowTemplateDraft }) })] })) : resolvedWorkspaceModule === 'agents' ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(AgentsModule, { workspaceId: currentWorkspaceId }) })] })) : (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} `, style: {
|
|
@@ -3240,7 +3312,16 @@ export function StandaloneLayout(props) {
|
|
|
3240
3312
|
top: '10px',
|
|
3241
3313
|
right: '10px',
|
|
3242
3314
|
zIndex: 20,
|
|
3243
|
-
}, children: _jsx(ChevronLeft, { size: 16 }) }))] }), groupBy === 'schedule' && (_jsx(ScheduleSidebar, { open: scheduleSidebarOpen, onClose: () => setScheduleSidebarOpen(false), scheduleSelectedDate: scheduleSelectedDate, setScheduleSelectedDate: setScheduleSelectedDate, scheduleCalendarMonth: scheduleCalendarMonth, setScheduleCalendarMonth: setScheduleCalendarMonth, scheduleShowWeekends: scheduleShowWeekends, setScheduleShowWeekends: setScheduleShowWeekends, scheduleShowBacklog: scheduleShowBacklog, setScheduleShowBacklog: setScheduleShowBacklog, scheduleOnlyExpired: scheduleOnlyExpired, setScheduleOnlyExpired: setScheduleOnlyExpired, expiredScheduledCount: expiredScheduledCount, overdueDueCount: overdueDueCount, expiredLeafCandidates: expiredLeafCandidates, expiredRecoveryCandidates: expiredRecoveryCandidates, onMoveExpiredToSelectedWeek: handleMoveExpiredToSelectedWeek, onMoveExpiredToBacklog: handleMoveExpiredToBacklog, scheduleBulkBusy: scheduleBulkBusy, globalWeekStartsOn: globalWeekStartsOn, resolvedLocale: resolvedLocale, todayDateOnly: todayDateOnly, scheduleBaseTasks: scheduleBaseTasks, scheduleWeekStart: scheduleWeekStart, scheduleWeekLabel: scheduleWeekLabel }))] })), _jsxs(Modal, { isOpen: activeTab === 'add', onClose: handleCloseModal, title: editingTaskId ? (currentTask?.isDeleted ? 'Deleted Task' : 'Edit Task') : 'New Task', size: taskModalFullscreen ? 'full' : 'xl', theme: currentTheme, headerActions: (_jsx("button", { className: "tf-control-icon", onClick: () => setTaskModalFullscreen((value) => !value), title: taskModalFullscreen ? 'Exit full screen' : 'Enter full screen', "aria-label": taskModalFullscreen ? 'Exit full screen' : 'Enter full screen', children: taskModalFullscreen ? _jsx(Minimize2, { size: 18 }) : _jsx(Maximize2, { size: 18 }) })), draggable: !taskModalFullscreen, closeOnOverlayClick: false, children: [_jsx(TaskActionHeader, { editingTaskId: editingTaskId, loading: loading, autoSaveState: autoSaveState, title: title, currentTask: currentTask, currentTaskWorkstream: currentTaskWorkstream, currentTaskInitiative: currentTaskInitiative, workstreamInput: workstreamInput, onWorkstreamInputChange: setWorkstreamInput, onSetWorkstreamForCurrentTask: handleSetWorkstreamForCurrentTask, handleSubmit: handleSubmit, resetForm: resetForm, handleCopyId: handleCopyId, copiedId: copiedId, tasks: tasks, handleToggleInProgress: handleToggleInProgress, handleToggleReview: handleToggleReview, handleToggleComplete: handleToggleComplete, handleToggleCancel: handleToggleCancel, handleSetStatus: handleSetStatus, handleArchiveTask: handleArchiveTask,
|
|
3315
|
+
}, children: _jsx(ChevronLeft, { size: 16 }) }))] }), groupBy === 'schedule' && (_jsx(ScheduleSidebar, { open: scheduleSidebarOpen, onClose: () => setScheduleSidebarOpen(false), scheduleSelectedDate: scheduleSelectedDate, setScheduleSelectedDate: setScheduleSelectedDate, scheduleCalendarMonth: scheduleCalendarMonth, setScheduleCalendarMonth: setScheduleCalendarMonth, scheduleShowWeekends: scheduleShowWeekends, setScheduleShowWeekends: setScheduleShowWeekends, scheduleShowBacklog: scheduleShowBacklog, setScheduleShowBacklog: setScheduleShowBacklog, scheduleOnlyExpired: scheduleOnlyExpired, setScheduleOnlyExpired: setScheduleOnlyExpired, expiredScheduledCount: expiredScheduledCount, overdueDueCount: overdueDueCount, expiredLeafCandidates: expiredLeafCandidates, expiredRecoveryCandidates: expiredRecoveryCandidates, onMoveExpiredToSelectedWeek: handleMoveExpiredToSelectedWeek, onMoveExpiredToBacklog: handleMoveExpiredToBacklog, scheduleBulkBusy: scheduleBulkBusy, globalWeekStartsOn: globalWeekStartsOn, resolvedLocale: resolvedLocale, todayDateOnly: todayDateOnly, scheduleBaseTasks: scheduleBaseTasks, scheduleWeekStart: scheduleWeekStart, scheduleWeekLabel: scheduleWeekLabel }))] })), _jsxs(Modal, { isOpen: activeTab === 'add', onClose: handleCloseModal, title: editingTaskId ? (currentTask?.isDeleted ? 'Deleted Task' : 'Edit Task') : 'New Task', size: taskModalFullscreen ? 'full' : 'xl', theme: currentTheme, headerActions: (_jsx("button", { className: "tf-control-icon", onClick: () => setTaskModalFullscreen((value) => !value), title: taskModalFullscreen ? 'Exit full screen' : 'Enter full screen', "aria-label": taskModalFullscreen ? 'Exit full screen' : 'Enter full screen', children: taskModalFullscreen ? _jsx(Minimize2, { size: 18 }) : _jsx(Maximize2, { size: 18 }) })), draggable: !taskModalFullscreen, closeOnOverlayClick: false, children: [_jsx(TaskActionHeader, { editingTaskId: editingTaskId, loading: loading, autoSaveState: autoSaveState, title: title, currentTask: currentTask, currentTaskWorkstream: currentTaskWorkstream, currentTaskInitiative: currentTaskInitiative, workstreamInput: workstreamInput, onWorkstreamInputChange: setWorkstreamInput, onSetWorkstreamForCurrentTask: handleSetWorkstreamForCurrentTask, handleSubmit: handleSubmit, resetForm: resetForm, handleCopyId: handleCopyId, copiedId: copiedId, tasks: tasks, handleToggleInProgress: handleToggleInProgress, handleToggleReview: handleToggleReview, handleToggleComplete: handleToggleComplete, handleToggleCancel: handleToggleCancel, handleSetStatus: handleSetStatus, handleArchiveTask: handleArchiveTask, handleUnarchiveTask: (taskId) => {
|
|
3316
|
+
if (currentTask?.isDeleted) {
|
|
3317
|
+
const deletedRecord = deletedTaskRecordByTaskId.get(taskId);
|
|
3318
|
+
if (!deletedRecord)
|
|
3319
|
+
return;
|
|
3320
|
+
void handleRestoreDeletedTask(deletedRecord.id);
|
|
3321
|
+
return;
|
|
3322
|
+
}
|
|
3323
|
+
void handleUnarchive(taskId);
|
|
3324
|
+
}, handleRestoreDeletedTask: currentTask?.deletedRecordId
|
|
3244
3325
|
? (deletedRecordId) => {
|
|
3245
3326
|
void handleRestoreDeletedTask(deletedRecordId);
|
|
3246
3327
|
}
|
|
@@ -3253,7 +3334,7 @@ export function StandaloneLayout(props) {
|
|
|
3253
3334
|
resetForm();
|
|
3254
3335
|
setActiveTab('tasks');
|
|
3255
3336
|
}
|
|
3256
|
-
: undefined }), _jsx(TaskForm, { editingTaskId: editingTaskId, error: error, title: title, description: description, checklistItems: checklistItems, category: category, type: type, priority: priority, complexity: complexity, manualComplexityEnabled: manualComplexityEnabled, approach: approach, assignee: assignee, scheduledDate: scheduledDate, dueDate: dueDate, workstreamInput: workstreamInput, formTaxonomies: formTaxonomies, onTaxonomyChange: (id, val) => setFormTaxonomies(prev => ({ ...prev, [id]: val })), taxonomies: taxonomies, comments: comments, newCommentText: newCommentText, contextFiles: attachments, currentWorkspaceId: currentWorkspaceId, apiBaseUrl: apiBaseUrl, descriptionFocused: descriptionFocused, showMarkdownHelp: showMarkdownHelp, showChecklist: showChecklist, checklistEnabled: checklistDropdownEnabled, showComments: showComments, categories: activeCategories, types: activeTypes, priorities: priorities, taxonomyDisplayLabels: taxonomyDisplayLabels, assigneeOptions: assigneeOptions, workstreams: props.workstreams, copiedId: copiedId, onTitleChange: setTitle, onDescriptionChange: setDescription, onChecklistItemsChange: setChecklistItems, onCategoryChange: setCategory, onTypeChange: setType, onPriorityChange: setPriority, onComplexityChange: setComplexity, onApproachChange: (val) => { setApproach(val); setFormTaxonomies(prev => ({ ...prev, approach: val })); }, onAssigneeChange: setAssignee, onScheduledDateChange: setScheduledDate, onDueDateChange: setDueDate, onWorkstreamInputChange: setWorkstreamInput, onNewCommentTextChange: setNewCommentText, onDescriptionFocusedChange: setDescriptionFocused, onShowMarkdownHelpChange: setShowMarkdownHelp, onShowChecklistChange: setShowChecklist, onShowCommentsChange: setShowComments, onOpenSettings: (section) => {
|
|
3337
|
+
: undefined }), _jsx(TaskForm, { editingTaskId: editingTaskId, error: error, title: title, description: description, checklistItems: checklistItems, category: category, type: type, priority: priority, complexity: complexity, manualComplexityEnabled: manualComplexityEnabled, approach: approach, assignee: assignee, scheduledDate: scheduledDate, dueDate: dueDate, workstreamInput: workstreamInput, formTaxonomies: formTaxonomies, onTaxonomyChange: (id, val) => setFormTaxonomies(prev => ({ ...prev, [id]: val })), taxonomies: taxonomies, comments: comments, newCommentText: newCommentText, contextFiles: attachments, currentWorkspaceId: currentWorkspaceId, apiBaseUrl: apiBaseUrl, descriptionFocused: descriptionFocused, showMarkdownHelp: showMarkdownHelp, showChecklist: showChecklist, checklistEnabled: checklistDropdownEnabled, showComments: showComments, categories: activeCategories, types: activeTypes, priorities: priorities, taxonomyDisplayLabels: taxonomyDisplayLabels, assigneeOptions: assigneeOptions, workstreams: props.workstreams, initiatives: props.initiatives, copiedId: copiedId, onTitleChange: setTitle, onDescriptionChange: setDescription, onChecklistItemsChange: setChecklistItems, onCategoryChange: setCategory, onTypeChange: setType, onPriorityChange: setPriority, onComplexityChange: setComplexity, onApproachChange: (val) => { setApproach(val); setFormTaxonomies(prev => ({ ...prev, approach: val })); }, onAssigneeChange: setAssignee, onScheduledDateChange: setScheduledDate, onDueDateChange: setDueDate, onWorkstreamInputChange: setWorkstreamInput, onNewCommentTextChange: setNewCommentText, onDescriptionFocusedChange: setDescriptionFocused, onShowMarkdownHelpChange: setShowMarkdownHelp, onShowChecklistChange: setShowChecklist, onShowCommentsChange: setShowComments, onOpenSettings: (section) => {
|
|
3257
3338
|
setSettingsSection(section);
|
|
3258
3339
|
setActiveTab('settings');
|
|
3259
3340
|
}, onSubmit: handleSubmit, commentsEndRef: commentsEndRef, onAddComment: () => handleAddComment(newCommentText), onOpenTaskById: handleOpenTaskById, onAddContextFile: (file) => {
|
|
@@ -270,7 +270,16 @@ export function WidgetView(props) {
|
|
|
270
270
|
handleNavigation(() => {
|
|
271
271
|
setActiveTab('settings');
|
|
272
272
|
});
|
|
273
|
-
}, title: "Settings", children: _jsx(Settings, { size: 18 }) }), _jsx("button", { className: "tf-control-icon", onClick: handleXClick, title: activeTab === 'tasks' ? "Close" : "Back to Tasks", children: activeTab === 'tasks' ? _jsx(X, { size: 20 }) : _jsx(ArrowLeft, { size: 20 }) })] })] }), _jsx(TopNoticeLayer, { notice: uiNotice, onDismiss: clearNotice }), activeTab === 'add' && (_jsx(TaskActionHeader, { editingTaskId: editingTaskId, loading: loading, autoSaveState: autoSaveState, title: title, currentTask: currentTask, currentTaskWorkstream: currentTaskWorkstream, currentTaskInitiative: currentTaskInitiative, workstreamInput: workstreamInput, onWorkstreamInputChange: setWorkstreamInput, onSetWorkstreamForCurrentTask: handleSetWorkstreamForCurrentTask, handleSubmit: handleSubmit, resetForm: resetForm, handleCopyId: handleCopyId, copiedId: copiedId, tasks: tasks, handleToggleInProgress: handleToggleInProgress, handleToggleReview: handleToggleReview, handleToggleComplete: handleToggleComplete, handleToggleCancel: handleToggleCancel, handleArchiveTask: handleArchiveTask,
|
|
273
|
+
}, title: "Settings", children: _jsx(Settings, { size: 18 }) }), _jsx("button", { className: "tf-control-icon", onClick: handleXClick, title: activeTab === 'tasks' ? "Close" : "Back to Tasks", children: activeTab === 'tasks' ? _jsx(X, { size: 20 }) : _jsx(ArrowLeft, { size: 20 }) })] })] }), _jsx(TopNoticeLayer, { notice: uiNotice, onDismiss: clearNotice }), activeTab === 'add' && (_jsx(TaskActionHeader, { editingTaskId: editingTaskId, loading: loading, autoSaveState: autoSaveState, title: title, currentTask: currentTask, currentTaskWorkstream: currentTaskWorkstream, currentTaskInitiative: currentTaskInitiative, workstreamInput: workstreamInput, onWorkstreamInputChange: setWorkstreamInput, onSetWorkstreamForCurrentTask: handleSetWorkstreamForCurrentTask, handleSubmit: handleSubmit, resetForm: resetForm, handleCopyId: handleCopyId, copiedId: copiedId, tasks: tasks, handleToggleInProgress: handleToggleInProgress, handleToggleReview: handleToggleReview, handleToggleComplete: handleToggleComplete, handleToggleCancel: handleToggleCancel, handleArchiveTask: handleArchiveTask, handleUnarchiveTask: (taskId) => {
|
|
274
|
+
if (currentTask?.isDeleted) {
|
|
275
|
+
const deletedRecord = deletedRecordByTaskId.get(taskId);
|
|
276
|
+
if (deletedRecord) {
|
|
277
|
+
void handleRestoreDeletedTask(deletedRecord.id);
|
|
278
|
+
}
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
handleUnarchive(taskId);
|
|
282
|
+
}, handleRestoreDeletedTask: (deletedRecordId) => {
|
|
274
283
|
void handleRestoreDeletedTask(deletedRecordId);
|
|
275
284
|
}, handlePermanentlyDeleteDeletedTask: (deletedRecordId) => {
|
|
276
285
|
void handlePermanentlyDeleteDeletedTask(deletedRecordId);
|
|
@@ -290,7 +299,7 @@ export function WidgetView(props) {
|
|
|
290
299
|
void handleDelete(taskId);
|
|
291
300
|
}, onDeleteAllDeleted: () => {
|
|
292
301
|
void handleEmptyDeletedTasks();
|
|
293
|
-
}, onFetchArchive: fetchArchive, onAddTaskToCategory: handleAddTaskToCategory, supplementalTasks: deletedScopeTasks })), activeTab === 'add' && (_jsx(TaskForm, { editingTaskId: editingTaskId, error: error, title: title, description: description, checklistItems: checklistItems, category: category, type: type, priority: priority, complexity: complexity, manualComplexityEnabled: manualComplexityEnabled, approach: approach, assignee: assignee, scheduledDate: scheduledDate, dueDate: dueDate, workstreamInput: workstreamInput, formTaxonomies: formTaxonomies, onTaxonomyChange: onTaxonomyChange, taxonomies: taxonomies, comments: comments, newCommentText: newCommentText, contextFiles: attachments, currentWorkspaceId: currentWorkspaceId, apiBaseUrl: "", descriptionFocused: descriptionFocused, showMarkdownHelp: showMarkdownHelp, showChecklist: showChecklist, checklistEnabled: checklistDropdownEnabled, showComments: showComments, categories: activeCategories, types: activeTypes, priorities: priorities, taxonomyDisplayLabels: taxonomyDisplayLabels, assigneeOptions: assigneeOptions, workstreams: props.workstreams, copiedId: copiedId, onTitleChange: setTitle, onDescriptionChange: setDescription, onChecklistItemsChange: setChecklistItems, onCategoryChange: setCategory, onTypeChange: setType, onPriorityChange: setPriority, onComplexityChange: setComplexity, onApproachChange: (val) => { setApproach(val); setFormTaxonomies(prev => ({ ...prev, approach: val })); }, onAssigneeChange: setAssignee, onScheduledDateChange: setScheduledDate, onDueDateChange: setDueDate, onWorkstreamInputChange: setWorkstreamInput, onNewCommentTextChange: setNewCommentText, onDescriptionFocusedChange: setDescriptionFocused, onShowMarkdownHelpChange: setShowMarkdownHelp, onShowChecklistChange: setShowChecklist, onShowCommentsChange: setShowComments, onOpenSettings: (section) => {
|
|
302
|
+
}, onFetchArchive: fetchArchive, onAddTaskToCategory: handleAddTaskToCategory, supplementalTasks: deletedScopeTasks })), activeTab === 'add' && (_jsx(TaskForm, { editingTaskId: editingTaskId, error: error, title: title, description: description, checklistItems: checklistItems, category: category, type: type, priority: priority, complexity: complexity, manualComplexityEnabled: manualComplexityEnabled, approach: approach, assignee: assignee, scheduledDate: scheduledDate, dueDate: dueDate, workstreamInput: workstreamInput, formTaxonomies: formTaxonomies, onTaxonomyChange: onTaxonomyChange, taxonomies: taxonomies, comments: comments, newCommentText: newCommentText, contextFiles: attachments, currentWorkspaceId: currentWorkspaceId, apiBaseUrl: "", descriptionFocused: descriptionFocused, showMarkdownHelp: showMarkdownHelp, showChecklist: showChecklist, checklistEnabled: checklistDropdownEnabled, showComments: showComments, categories: activeCategories, types: activeTypes, priorities: priorities, taxonomyDisplayLabels: taxonomyDisplayLabels, assigneeOptions: assigneeOptions, workstreams: props.workstreams, initiatives: props.initiatives, copiedId: copiedId, onTitleChange: setTitle, onDescriptionChange: setDescription, onChecklistItemsChange: setChecklistItems, onCategoryChange: setCategory, onTypeChange: setType, onPriorityChange: setPriority, onComplexityChange: setComplexity, onApproachChange: (val) => { setApproach(val); setFormTaxonomies(prev => ({ ...prev, approach: val })); }, onAssigneeChange: setAssignee, onScheduledDateChange: setScheduledDate, onDueDateChange: setDueDate, onWorkstreamInputChange: setWorkstreamInput, onNewCommentTextChange: setNewCommentText, onDescriptionFocusedChange: setDescriptionFocused, onShowMarkdownHelpChange: setShowMarkdownHelp, onShowChecklistChange: setShowChecklist, onShowCommentsChange: setShowComments, onOpenSettings: (section) => {
|
|
294
303
|
handleNavigation(() => {
|
|
295
304
|
setSettingsSection(section);
|
|
296
305
|
setActiveTab('settings');
|
|
@@ -31,15 +31,17 @@ describe('FilterToolbar', () => {
|
|
|
31
31
|
expect(screen.getByRole('button', { name: /All Initiatives/i })).toBeInTheDocument();
|
|
32
32
|
expect(screen.getByRole('button', { name: /All Workstreams/i })).toBeInTheDocument();
|
|
33
33
|
rerender(_jsx(FilterToolbar, { categoryFilterOptions: [], typeFilterOptions: [], priorities: [], filterCategories: [], setFilterCategories: vi.fn(), filterTypes: [], setFilterTypes: vi.fn(), filterPriorities: [], setFilterPriorities: vi.fn(), filterStatus: [], setFilterStatus: vi.fn(), filterAssignees: [], setFilterAssignees: vi.fn(), assigneeOptions: [], initiativeFilterOptions: [
|
|
34
|
-
{ value: 'initiative-1', label: '
|
|
34
|
+
{ value: 'initiative-1', label: 'IN-101' },
|
|
35
35
|
], selectedInitiativeId: "", setSelectedInitiativeId: setSelectedInitiativeId, workstreamFilterOptions: [
|
|
36
|
-
{ value: 'workstream-1', label: '
|
|
36
|
+
{ value: 'workstream-1', label: 'WS-202' },
|
|
37
37
|
], selectedWorkstreamId: "", setSelectedWorkstreamId: setSelectedWorkstreamId, taskScope: "open", setTaskScope: vi.fn(), showArchive: false, setShowArchive: vi.fn(), fetchArchive: vi.fn(), clearFilters: vi.fn() }));
|
|
38
38
|
await user.click(screen.getByRole('button', { name: /All Initiatives/i }));
|
|
39
|
-
|
|
39
|
+
expect(screen.queryByText('Stability Sprint')).not.toBeInTheDocument();
|
|
40
|
+
await user.click(screen.getByRole('button', { name: 'IN-101' }));
|
|
40
41
|
expect(setSelectedInitiativeId).toHaveBeenCalledWith('initiative-1');
|
|
41
42
|
await user.click(screen.getByRole('button', { name: /All Workstreams/i }));
|
|
42
|
-
|
|
43
|
+
expect(screen.queryByText('Header polish')).not.toBeInTheDocument();
|
|
44
|
+
await user.click(screen.getByRole('button', { name: 'WS-202' }));
|
|
43
45
|
expect(setSelectedWorkstreamId).toHaveBeenCalledWith('workstream-1');
|
|
44
46
|
});
|
|
45
47
|
});
|
|
@@ -1,17 +1,5 @@
|
|
|
1
|
+
import type { AccountProfileSummary } from '../../../../utils/accountProfileSummaryCache';
|
|
1
2
|
import type { ModalRuntimeMode, ModalThemeName } from './types';
|
|
2
|
-
interface AccountProfileSummaryLike {
|
|
3
|
-
planId?: string | null;
|
|
4
|
-
planName?: string | null;
|
|
5
|
-
entitlementState?: string | null;
|
|
6
|
-
stripeStatus?: string | null;
|
|
7
|
-
effectiveUntil?: string | null;
|
|
8
|
-
stripeSubscriptionId?: string | null;
|
|
9
|
-
stripeCustomerId?: string | null;
|
|
10
|
-
gate?: string | null;
|
|
11
|
-
message?: string | null;
|
|
12
|
-
billingConflictCode?: 'CHECKOUT_SESSION_EXPIRED' | 'CHECKOUT_SESSION_SUPERSEDED' | null;
|
|
13
|
-
planSelectionRequired?: boolean;
|
|
14
|
-
}
|
|
15
3
|
interface AccountHubModalProps {
|
|
16
4
|
isOpen: boolean;
|
|
17
5
|
theme: ModalThemeName;
|
|
@@ -26,7 +14,7 @@ interface AccountHubModalProps {
|
|
|
26
14
|
billingNotice: string | null;
|
|
27
15
|
billingActionBusy: boolean;
|
|
28
16
|
billingIntervalChoice: 'month' | 'year';
|
|
29
|
-
accountProfileSummary:
|
|
17
|
+
accountProfileSummary: AccountProfileSummary | null;
|
|
30
18
|
currentWorkspaceId: string;
|
|
31
19
|
canOpenTeamManagement: boolean;
|
|
32
20
|
canManageWorkspaceSync: boolean;
|
|
@@ -5,21 +5,19 @@ import accountStyles from '../../StandaloneAccount.module.css';
|
|
|
5
5
|
import authStyles from '../../AuthShell.module.css';
|
|
6
6
|
import managementStyles from '../../StandaloneManagement.module.css';
|
|
7
7
|
import styles from '../../../../Taskforce.module.css';
|
|
8
|
+
import { resolveCommercialLifecyclePresentation } from '../../../../utils/commercialLifecyclePresentation';
|
|
8
9
|
export function AccountHubModal({ isOpen, theme, runtimeMode, authRequiredForApi, isAuthenticated, hasAuthIdentity, authIdentityLabel, billingLoading, billingError, billingActionError, billingNotice, billingActionBusy, billingIntervalChoice, accountProfileSummary, currentWorkspaceId, canOpenTeamManagement, canManageWorkspaceSync, workspaceCloudSyncEnabled, syncStatusLabel, workspaceSyncError, syncControlBusy, onClose, onOpenWorkspaceAudit, onBillingIntervalChange, onRefreshBilling, onUpdateInterval, onManageBilling, onStartCheckout, onToggleWorkspaceSync, onOpenHelp, }) {
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|| (billingConflictCode === 'CHECKOUT_SESSION_EXPIRED'
|
|
13
|
-
? 'Your latest checkout expired. Choose a plan to continue.'
|
|
14
|
-
: (billingConflictCode === 'CHECKOUT_SESSION_SUPERSEDED'
|
|
15
|
-
? 'A newer checkout is already in progress for this account. Open plans to continue.'
|
|
16
|
-
: ''));
|
|
17
|
-
const startCheckoutLabel = billingConflictCode === 'CHECKOUT_SESSION_EXPIRED'
|
|
18
|
-
|| accountProfileSummary?.planSelectionRequired === true
|
|
19
|
-
|| billingGate === 'plan_selection_required'
|
|
20
|
-
? 'Choose Plan'
|
|
21
|
-
: (billingGate === 'checkout_pending' ? 'Open Plans' : 'Start Checkout');
|
|
10
|
+
const lifecyclePresentation = resolveCommercialLifecyclePresentation(accountProfileSummary);
|
|
11
|
+
const billingRecoveryMessage = lifecyclePresentation.message;
|
|
12
|
+
const startCheckoutLabel = lifecyclePresentation.primaryLabel || 'Start Checkout';
|
|
22
13
|
const resolvedPlanLabel = String(accountProfileSummary?.planName || accountProfileSummary?.planId || 'n/a').trim() || 'n/a';
|
|
14
|
+
const primaryBillingAction = () => {
|
|
15
|
+
if (lifecyclePresentation.primaryAction === 'manage_billing') {
|
|
16
|
+
onManageBilling();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
onStartCheckout();
|
|
20
|
+
};
|
|
23
21
|
return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, title: "Account Hub", size: "md", theme: theme, draggable: true, children: _jsxs("div", { className: `${modalStyles.form} ${managementStyles.modalBody}`, children: [_jsx("p", { className: managementStyles.mutedText, children: "Manage sign in, profile, and workspace controls from one place." }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Authentication" }), _jsx("p", { className: managementStyles.sectionText, children: runtimeMode === 'cloud'
|
|
24
22
|
? (authRequiredForApi
|
|
25
23
|
? (isAuthenticated ? 'Signed in. API access is enabled.' : 'Cloud mode requires sign in before app usage.')
|
|
@@ -28,7 +26,7 @@ export function AccountHubModal({ isOpen, theme, runtimeMode, authRequiredForApi
|
|
|
28
26
|
? (hasAuthIdentity
|
|
29
27
|
? _jsxs(_Fragment, { children: ["Signed in as ", _jsx("span", { className: accountStyles.accountIdentityEmail, children: authIdentityLabel })] })
|
|
30
28
|
: 'Signed in')
|
|
31
|
-
: 'Signed in as: not signed in' }), runtimeMode === 'cloud' && authRequiredForApi && !isAuthenticated && (_jsx("p", { className: managementStyles.sectionTextTop, children: "Use the `/login` screen to sign in." }))] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Profile & Account" }), _jsx("p", { className: managementStyles.sectionText, children: "Profile editing and account preferences will live here." })] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Billing" }), !isAuthenticated ? (_jsx("p", { className: managementStyles.sectionText, children: "Sign in to manage subscription billing." })) : runtimeMode !== 'cloud' ? (_jsx("p", { className: managementStyles.sectionText, children: "Billing remains cloud-backed in local runtime and uses your connected cloud account." })) : (_jsxs(_Fragment, { children: [billingLoading && (_jsx("p", { className: managementStyles.sectionTextSpaced, children: "Loading billing status..." })), billingError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: billingError })), billingActionError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: billingActionError })), billingNotice && (_jsx("p", { className: managementStyles.errorText, children: billingNotice })), billingRecoveryMessage && (_jsx("p", { className: managementStyles.sectionText, children: billingRecoveryMessage })), _jsxs("p", { className: managementStyles.sectionTextSpaced, children: ["Plan: ", _jsx("code", { children: resolvedPlanLabel }), ' · ', "Entitlement: ", _jsx("code", { children: String(accountProfileSummary?.entitlementState || 'n/a') })] }), _jsxs("p", { className: managementStyles.sectionText, children: ["Stripe status: ", _jsx("code", { children: String(accountProfileSummary?.stripeStatus || 'n/a') }), accountProfileSummary?.effectiveUntil ? ` · Effective until ${new Date(accountProfileSummary.effectiveUntil).toLocaleString()}` : ''] }), _jsx("div", { className: managementStyles.wrapRow, children: _jsxs("label", { className: `${accountStyles.accountMenuMeta} ${managementStyles.inlineFieldLabel}`, children: ["Interval", _jsxs("select", { className: styles.input, value: billingIntervalChoice, onChange: (event) => onBillingIntervalChange(event.target.value === 'year' ? 'year' : 'month'), disabled: billingActionBusy, children: [_jsx("option", { value: "month", children: "Monthly" }), _jsx("option", { value: "year", children: "Yearly" })] })] }) }), _jsxs("div", { className: managementStyles.actionRowEnd, children: [_jsx("button", { className: styles.cancelBtn, onClick: onRefreshBilling, disabled: billingActionBusy || billingLoading, children: "Refresh Billing" }), _jsx("button", { className: styles.cancelBtn, onClick: onUpdateInterval, disabled: billingActionBusy || !accountProfileSummary?.stripeSubscriptionId, children: "Update Interval" }), _jsx("button", { className: styles.cancelBtn, onClick: onManageBilling, disabled: billingActionBusy || !accountProfileSummary?.stripeCustomerId, children: "Manage Billing" }), _jsx("button", { className: styles.submitBtn, onClick:
|
|
29
|
+
: 'Signed in as: not signed in' }), runtimeMode === 'cloud' && authRequiredForApi && !isAuthenticated && (_jsx("p", { className: managementStyles.sectionTextTop, children: "Use the `/login` screen to sign in." }))] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Profile & Account" }), _jsx("p", { className: managementStyles.sectionText, children: "Profile editing and account preferences will live here." })] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Billing" }), !isAuthenticated ? (_jsx("p", { className: managementStyles.sectionText, children: "Sign in to manage subscription billing." })) : runtimeMode !== 'cloud' ? (_jsx("p", { className: managementStyles.sectionText, children: "Billing remains cloud-backed in local runtime and uses your connected cloud account." })) : (_jsxs(_Fragment, { children: [billingLoading && (_jsx("p", { className: managementStyles.sectionTextSpaced, children: "Loading billing status..." })), billingError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: billingError })), billingActionError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: billingActionError })), billingNotice && (_jsx("p", { className: managementStyles.errorText, children: billingNotice })), billingRecoveryMessage && (_jsx("p", { className: managementStyles.sectionText, children: billingRecoveryMessage })), _jsxs("p", { className: managementStyles.sectionTextSpaced, children: ["Plan: ", _jsx("code", { children: resolvedPlanLabel }), ' · ', "Entitlement: ", _jsx("code", { children: String(accountProfileSummary?.entitlementState || 'n/a') })] }), _jsxs("p", { className: managementStyles.sectionText, children: ["Stripe status: ", _jsx("code", { children: String(accountProfileSummary?.stripeStatus || 'n/a') }), accountProfileSummary?.effectiveUntil ? ` · Effective until ${new Date(accountProfileSummary.effectiveUntil).toLocaleString()}` : ''] }), _jsx("div", { className: managementStyles.wrapRow, children: _jsxs("label", { className: `${accountStyles.accountMenuMeta} ${managementStyles.inlineFieldLabel}`, children: ["Interval", _jsxs("select", { className: styles.input, value: billingIntervalChoice, onChange: (event) => onBillingIntervalChange(event.target.value === 'year' ? 'year' : 'month'), disabled: billingActionBusy, children: [_jsx("option", { value: "month", children: "Monthly" }), _jsx("option", { value: "year", children: "Yearly" })] })] }) }), _jsxs("div", { className: managementStyles.actionRowEnd, children: [_jsx("button", { className: styles.cancelBtn, onClick: onRefreshBilling, disabled: billingActionBusy || billingLoading, children: "Refresh Billing" }), _jsx("button", { className: styles.cancelBtn, onClick: onUpdateInterval, disabled: billingActionBusy || !accountProfileSummary?.stripeSubscriptionId, children: "Update Interval" }), _jsx("button", { className: styles.cancelBtn, onClick: onManageBilling, disabled: billingActionBusy || !accountProfileSummary?.stripeCustomerId, children: "Manage Billing" }), _jsx("button", { className: styles.submitBtn, onClick: primaryBillingAction, disabled: billingActionBusy || (lifecyclePresentation.primaryAction === 'manage_billing' && !accountProfileSummary?.stripeCustomerId), children: billingActionBusy ? 'Working...' : startCheckoutLabel })] })] }))] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Workspace Audit Log" }), _jsx("p", { className: managementStyles.sectionText, children: "Audit entries are workspace-scoped for the active workspace, not global account history." }), _jsxs("p", { className: managementStyles.sectionText, children: ["Active workspace: ", _jsx("code", { children: currentWorkspaceId })] }), _jsx("div", { className: managementStyles.actionRowEnd, children: _jsx("button", { className: styles.cancelBtn, onClick: onOpenWorkspaceAudit, disabled: !canOpenTeamManagement, children: "Open Workspace Audit" }) })] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Workspace Cloud Sync" }), _jsx("p", { className: managementStyles.sectionText, children: canManageWorkspaceSync
|
|
32
30
|
? (workspaceCloudSyncEnabled
|
|
33
31
|
? `Enabled · ${syncStatusLabel}`
|
|
34
32
|
: 'Disabled')
|
|
@@ -17,10 +17,33 @@ describe('AccountHubModal', () => {
|
|
|
17
17
|
billingConflictCode: 'CHECKOUT_SESSION_EXPIRED',
|
|
18
18
|
planSelectionRequired: true
|
|
19
19
|
}, currentWorkspaceId: "workspace-1", canOpenTeamManagement: false, canManageWorkspaceSync: false, workspaceCloudSyncEnabled: false, syncStatusLabel: "Disabled", workspaceSyncError: null, syncControlBusy: false, onClose: () => { }, onOpenWorkspaceAudit: () => { }, onBillingIntervalChange: () => { }, onRefreshBilling: () => { }, onUpdateInterval: () => { }, onManageBilling: () => { }, onStartCheckout: onStartCheckout, onToggleWorkspaceSync: () => { }, onOpenHelp: () => { } }));
|
|
20
|
-
expect(screen.getByText('Your
|
|
20
|
+
expect(screen.getByText('Your previous checkout expired. Choose a plan to continue.')).toBeInTheDocument();
|
|
21
21
|
expect(screen.getByText('Pro Plan')).toBeInTheDocument();
|
|
22
22
|
const cta = screen.getByRole('button', { name: 'Choose Plan' });
|
|
23
23
|
fireEvent.click(cta);
|
|
24
24
|
expect(onStartCheckout).toHaveBeenCalledTimes(1);
|
|
25
25
|
});
|
|
26
|
+
it('makes billing recovery the primary action for suspended accounts', () => {
|
|
27
|
+
const onManageBilling = vi.fn();
|
|
28
|
+
render(_jsx(AccountHubModal, { isOpen: true, theme: "dark", runtimeMode: "cloud", authRequiredForApi: true, isAuthenticated: true, hasAuthIdentity: true, authIdentityLabel: "owner@example.com", billingLoading: false, billingError: null, billingActionError: null, billingNotice: null, billingActionBusy: false, billingIntervalChoice: "month", accountProfileSummary: {
|
|
29
|
+
planId: 'pro',
|
|
30
|
+
planName: 'Pro Plan',
|
|
31
|
+
entitlementState: 'suspended',
|
|
32
|
+
stripeCustomerId: 'cus_123'
|
|
33
|
+
}, currentWorkspaceId: "workspace-1", canOpenTeamManagement: false, canManageWorkspaceSync: false, workspaceCloudSyncEnabled: false, syncStatusLabel: "Disabled", workspaceSyncError: null, syncControlBusy: false, onClose: () => { }, onOpenWorkspaceAudit: () => { }, onBillingIntervalChange: () => { }, onRefreshBilling: () => { }, onUpdateInterval: () => { }, onManageBilling: onManageBilling, onStartCheckout: () => { }, onToggleWorkspaceSync: () => { }, onOpenHelp: () => { } }));
|
|
34
|
+
expect(screen.getByText('Your access is suspended because billing needs attention. Update billing to restore access.')).toBeInTheDocument();
|
|
35
|
+
fireEvent.click(screen.getAllByRole('button', { name: 'Manage Billing' })[1]);
|
|
36
|
+
expect(onManageBilling).toHaveBeenCalledTimes(1);
|
|
37
|
+
});
|
|
38
|
+
it('shows trialing accounts as active without blocked recovery copy', () => {
|
|
39
|
+
render(_jsx(AccountHubModal, { isOpen: true, theme: "dark", runtimeMode: "cloud", authRequiredForApi: true, isAuthenticated: true, hasAuthIdentity: true, authIdentityLabel: "owner@example.com", billingLoading: false, billingError: null, billingActionError: null, billingNotice: null, billingActionBusy: false, billingIntervalChoice: "month", accountProfileSummary: {
|
|
40
|
+
planId: 'starter',
|
|
41
|
+
planName: 'Starter',
|
|
42
|
+
entitlementState: 'trialing',
|
|
43
|
+
stripeCustomerId: 'cus_123',
|
|
44
|
+
trialEnd: '2026-05-01T12:00:00.000Z'
|
|
45
|
+
}, currentWorkspaceId: "workspace-1", canOpenTeamManagement: false, canManageWorkspaceSync: false, workspaceCloudSyncEnabled: false, syncStatusLabel: "Disabled", workspaceSyncError: null, syncControlBusy: false, onClose: () => { }, onOpenWorkspaceAudit: () => { }, onBillingIntervalChange: () => { }, onRefreshBilling: () => { }, onUpdateInterval: () => { }, onManageBilling: () => { }, onStartCheckout: () => { }, onToggleWorkspaceSync: () => { }, onOpenHelp: () => { } }));
|
|
46
|
+
expect(screen.getByText(/Your trial is active through/)).toBeInTheDocument();
|
|
47
|
+
expect(screen.queryByText('Your access is suspended because billing needs attention. Update billing to restore access.')).not.toBeInTheDocument();
|
|
48
|
+
});
|
|
26
49
|
});
|
|
@@ -5,7 +5,7 @@ import modalStyles from '../../../ui/Modal.module.css';
|
|
|
5
5
|
import syncStatusStyles from '../../SyncStatusModal.module.css';
|
|
6
6
|
import styles from '../../../../Taskforce.module.css';
|
|
7
7
|
export function SyncStatusModal({ isOpen, theme, currentWorkspaceLabel, syncStatusMeta, workspaceCloudSyncEnabled, syncControlBusy, canManageWorkspaceSync, workspaceSyncSummary, workspaceSyncRepairBusy, referenceMismatchCount, syncStageLabel, workspaceSyncPendingChanges, formattedLastSyncTime, formattedLastPullTime, formattedLastPushTime, syncLastError, workspaceSyncDiagnostics, activeReferenceMismatchSummaries, syncDiagnosticsSummary, syncEventRows, syncEventsListRef, workspaceSyncRepairQueued, workspaceSyncBusy, workspaceSyncCopied, runtimeMode, isAuthenticated, onClose, onToggleWorkspaceSync, onRepairSync, onCopyReport, onOpenLogin, onRetrySync, }) {
|
|
8
|
-
return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, title: "Sync Manager", size: "
|
|
8
|
+
return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, title: "Sync Manager", size: "mdWide", theme: theme, draggable: true, children: _jsxs("div", { className: `${modalStyles.form} ${syncStatusStyles.syncStatusModal}`, style: { gap: '10px' }, children: [_jsxs("div", { className: syncStatusStyles.syncStatusTop, children: [_jsx("div", { className: syncStatusStyles.syncStatusHeaderInfo, children: _jsxs("div", { className: syncStatusStyles.syncStatusMetaHeader, children: [_jsx("div", { className: syncStatusStyles.syncStatusBadge, style: {
|
|
9
9
|
borderColor: syncStatusMeta.border,
|
|
10
10
|
background: syncStatusMeta.background,
|
|
11
11
|
color: syncStatusMeta.color
|
package/dist/config/envSchema.js
CHANGED
|
@@ -26,7 +26,7 @@ const SPECS = [
|
|
|
26
26
|
{ key: 'TASKFORCE_BOOTSTRAP_ADMIN_EMAIL', description: 'Bootstrap admin email used by login fallback.', category: 'Auth', targets: ['server', 'all'] },
|
|
27
27
|
{ key: 'TASKFORCE_BOOTSTRAP_ADMIN_PASSWORD', description: 'Bootstrap admin password used by login fallback.', category: 'Auth', secret: true, targets: ['server', 'all'] },
|
|
28
28
|
{ key: 'TASKFORCE_AUTH_PUBLIC_BASE_URL', description: 'Public auth URL used in email links.', category: 'Auth', example: 'https://performance-app.taskforcehq.ai', targets: ['server', 'all'] },
|
|
29
|
-
{ key: 'TASKFORCE_AUTH_EXPOSE_DEV_TOKENS', description: '
|
|
29
|
+
{ key: 'TASKFORCE_AUTH_EXPOSE_DEV_TOKENS', description: 'Explicitly expose auth tokens in non-production responses for test automation.', category: 'Auth', defaultValue: 'false', targets: ['server', 'all'] },
|
|
30
30
|
{ key: 'TASKFORCE_CLOUD_PROXY_BASE_URL', description: 'Optional upstream cloud base URL for local proxying of auth/sync API calls.', category: 'Auth', example: 'https://performance-app.taskforcehq.ai', targets: ['server', 'all'] },
|
|
31
31
|
{ key: 'TASKFORCE_CF_ACCESS_CLIENT_ID', description: 'Optional Cloudflare Access service token client id for local cloud proxy.', category: 'Cloudflare', secret: true, targets: ['server', 'all'] },
|
|
32
32
|
{ key: 'TASKFORCE_CF_ACCESS_CLIENT_SECRET', description: 'Optional Cloudflare Access service token client secret for local cloud proxy.', category: 'Cloudflare', secret: true, targets: ['server', 'all'] },
|
|
@@ -50,7 +50,12 @@ export interface AiProfileServiceDeps {
|
|
|
50
50
|
aiProfileRowToRecord: (row: Record<string, unknown>) => AiProfileRecordLike;
|
|
51
51
|
generateEntityId: (prefix: string) => string;
|
|
52
52
|
issueProfileToken: () => string;
|
|
53
|
-
|
|
53
|
+
countAiProfileUsage: (workspaceId: string) => {
|
|
54
|
+
used: number;
|
|
55
|
+
limit: number | null;
|
|
56
|
+
remaining: number | null;
|
|
57
|
+
};
|
|
58
|
+
createRuleError: (message: string, statusCode?: number, code?: string, details?: unknown) => Error;
|
|
54
59
|
}
|
|
55
60
|
export declare function listAiProfilesService(deps: AiProfileServiceDeps, workspaceId: string): AiProfileRecordLike[];
|
|
56
61
|
export declare function getAiProfileByTokenService(deps: AiProfileServiceDeps, input: {
|