@taskforcehq/taskforce 0.3.301 → 0.3.303
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/dist/Taskforce.module.css +37 -2
- package/dist/TaskforceCore.filter-persistence.test.js +1 -1
- package/dist/TaskforceCore.js +72 -33
- package/dist/TaskforceCore.test.js +848 -130
- package/dist/components/features/TaskSettings.js +14 -82
- package/dist/components/features/TaskSettings.test.js +33 -70
- package/dist/components/features/settings/TaxonomyLibraryManager.js +8 -2
- package/dist/components/features/settings/TaxonomyLibraryManager.test.js +1 -1
- package/dist/components/task/TaskActionHeader.js +6 -3
- package/dist/components/task/TaskCard.js +6 -3
- package/dist/components/task/TaskForm.js +2 -2
- package/dist/components/task/TaskForm.test.js +41 -2
- package/dist/components/task/TaskKanban.test.js +17 -0
- package/dist/components/ui/ReferenceBadgeButton.js +1 -1
- package/dist/components/views/PlansPage.d.ts +3 -1
- package/dist/components/views/PlansPage.js +109 -49
- package/dist/components/views/PlansPage.test.d.ts +1 -0
- package/dist/components/views/PlansPage.test.js +100 -0
- package/dist/components/views/StandaloneLayout.d.ts +1 -0
- package/dist/components/views/StandaloneLayout.js +473 -256
- package/dist/components/views/panels/PlanningDrawer.js +18 -1
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +48 -0
- package/dist/components/views/standalone/modals/AccountHubModal.js +36 -0
- package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
- package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.js +8 -0
- package/dist/components/views/standalone/modals/EditProfileModal.d.ts +26 -0
- package/dist/components/views/standalone/modals/EditProfileModal.js +10 -0
- package/dist/components/views/standalone/modals/HelpModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/HelpModal.js +8 -0
- package/dist/components/views/standalone/modals/ScheduleWarningModal.d.ts +13 -0
- package/dist/components/views/standalone/modals/ScheduleWarningModal.js +6 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +61 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.js +38 -0
- package/dist/components/views/standalone/modals/TeamManagementModal.d.ts +64 -0
- package/dist/components/views/standalone/modals/TeamManagementModal.js +10 -0
- package/dist/components/views/standalone/modals/types.d.ts +2 -0
- package/dist/components/views/standalone/modals/types.js +1 -0
- package/dist/components/views/teamManagementAccess.d.ts +1 -1
- package/dist/components/views/teamManagementAccess.js +2 -2
- package/dist/components/views/teamManagementAccess.test.js +3 -3
- package/dist/config/deployment.js +2 -2
- package/dist/config/deployment.test.js +5 -3
- package/dist/core/AiProfileService.js +7 -2
- package/dist/core/AiProfiles.test.js +19 -0
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +78 -18
- package/dist/core/PlanEntitlementService.d.ts +29 -5
- package/dist/core/PlanEntitlementService.js +297 -219
- package/dist/core/SignupMonetizationSettings.test.js +173 -49
- package/dist/core/SyncReconciliationService.d.ts +2 -0
- package/dist/core/SyncReconciliationService.js +3 -0
- package/dist/core/SystemAdmin.test.js +186 -96
- package/dist/core/TaskAuditTimeline.test.js +40 -0
- package/dist/core/Taskforce.d.ts +100 -10
- package/dist/core/Taskforce.ids.test.js +133 -0
- package/dist/core/Taskforce.js +459 -127
- package/dist/core/Taskforce.mcpAuth.test.js +0 -2
- package/dist/core/UserProfileAvatarDrafts.test.d.ts +1 -0
- package/dist/core/UserProfileAvatarDrafts.test.js +111 -0
- package/dist/core/taskReferencePolicy.d.ts +28 -0
- package/dist/core/taskReferencePolicy.js +189 -0
- package/dist/core/types.d.ts +13 -12
- package/dist/hooks/tasks/useTaskFormActions.js +3 -0
- package/dist/hooks/useSyncOrchestrator.js +17 -3
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +97 -5
- package/dist/hooks/useTaskData.js +3 -2
- package/dist/hooks/useTaskData.test.js +66 -0
- package/dist/hooks/useTaskforce.d.ts +8 -0
- package/dist/hooks/useTaskforce.js +72 -19
- package/dist/hooks/useTaskforce.sync-behavior.test.js +185 -6
- package/dist/mcp/aiProfileBootstrap.d.ts +29 -0
- package/dist/mcp/aiProfileBootstrap.js +80 -0
- package/dist/mcp/aiProfileBootstrap.test.d.ts +1 -0
- package/dist/mcp/aiProfileBootstrap.test.js +68 -0
- package/dist/mcp/clientIntegrations.d.ts +2 -8
- package/dist/mcp/clientIntegrations.js +21 -40
- package/dist/mcp/clientIntegrations.test.js +28 -2
- package/dist/mcp/clientProfileDefaults.d.ts +12 -0
- package/dist/mcp/clientProfileDefaults.js +37 -0
- package/dist/mcp/runtime.d.ts +10 -7
- package/dist/mcp/runtime.js +131 -48
- package/dist/mcp/runtime.test.js +321 -83
- package/dist/migrations/billingSchemaParity.test.js +3 -0
- package/dist/migrations/taskSchemaMigrations.js +68 -1
- package/dist/qaOpen.test.d.ts +1 -0
- package/dist/qaOpen.test.js +56 -0
- package/dist/server/index.cookieProxy.test.js +2 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +17 -1
- package/dist/server/index.test.js +15 -1
- package/dist/server/mockStripe.d.ts +1 -0
- package/dist/server/mockStripe.js +2 -0
- package/dist/server/routes/admin.js +105 -45
- package/dist/server/routes/auth.d.ts +6 -0
- package/dist/server/routes/auth.js +505 -30
- package/dist/server/routes/billing.js +517 -24
- package/dist/server/routes/billing.test.js +967 -45
- package/dist/server/routes/documents.js +6 -1
- package/dist/server/routes/shared.d.ts +1 -1
- package/dist/server/routes/shared.js +16 -6
- package/dist/server/routes/sync.integration.test.js +290 -0
- package/dist/server/routes/sync.js +234 -1922
- package/dist/server/routes/tasks.js +18 -4
- package/dist/server/routes.js +29 -22
- package/dist/server/routes.test.js +639 -82
- package/dist/shared/runtimeContract.d.ts +1 -1
- package/dist/sync/cloudSyncApi.d.ts +1 -0
- package/dist/sync/collaborationSyncPayload.d.ts +17 -0
- package/dist/sync/collaborationSyncPayload.js +131 -0
- package/dist/sync/collaborationSyncPayload.test.d.ts +1 -0
- package/dist/sync/collaborationSyncPayload.test.js +104 -0
- package/dist/sync/collaborationSyncState.d.ts +15 -0
- package/dist/sync/collaborationSyncState.js +60 -0
- package/dist/sync/contentSyncPayload.d.ts +45 -0
- package/dist/sync/contentSyncPayload.js +104 -0
- package/dist/sync/contentSyncPayload.test.d.ts +1 -0
- package/dist/sync/contentSyncPayload.test.js +104 -0
- package/dist/sync/contentSyncState.d.ts +98 -0
- package/dist/sync/contentSyncState.js +1004 -0
- package/dist/sync/planningSyncPayload.d.ts +9 -0
- package/dist/sync/planningSyncPayload.js +82 -0
- package/dist/sync/planningSyncPayload.test.d.ts +1 -0
- package/dist/sync/planningSyncPayload.test.js +68 -0
- package/dist/sync/syncApplyHandlers.d.ts +83 -0
- package/dist/sync/syncApplyHandlers.js +445 -0
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +21 -8
- package/dist/sync/taskSyncPayload.d.ts +33 -0
- package/dist/sync/taskSyncPayload.js +126 -0
- package/dist/sync/taskSyncPayload.test.d.ts +1 -0
- package/dist/sync/taskSyncPayload.test.js +54 -0
- package/dist/sync/workspacePullFeed.d.ts +38 -0
- package/dist/sync/workspacePullFeed.js +349 -0
- package/dist/sync/workspacePullFeed.test.d.ts +1 -0
- package/dist/sync/workspacePullFeed.test.js +278 -0
- package/dist/sync/workspaceSyncModel.d.ts +4 -1
- package/dist/sync/workspaceSyncModel.js +9 -2
- package/dist/sync/workspaceSyncModel.test.js +29 -0
- package/dist/test/setup.js +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/ui/assets/{AgentsModule-Bdq3T1Ts.js → AgentsModule-CpsTmrIW.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-jKUVo-PJ.js → AnnotatedAttachmentWorkspace-C_TmXZwA.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-DuQa-uIC.js → DocumentWorkspace-C3GyzrWm.js} +2 -2
- package/dist/ui/assets/{InitiativesModule-C8Pi2VTk.js → InitiativesModule-4-Rh2K2n.js} +1 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +1 -0
- package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +8 -0
- package/dist/ui/assets/{TaskSettings-BwB9NtmJ.css → TaskSettings-CnIBL_Eb.css} +1 -1
- package/dist/ui/assets/{WorkflowsModule-b6TLuHwJ.js → WorkflowsModule-CGk9s3NJ.js} +1 -1
- package/dist/ui/assets/index-CLIMgR6g.js +6 -0
- package/dist/ui/assets/index-DneETxcu.css +1 -0
- package/dist/ui/assets/{vendor-icons-QZyhEwgT.js → vendor-icons-pWocEipT.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
- package/dist/utils/accountProfileSummaryCache.js +97 -0
- package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
- package/dist/utils/accountProfileSummaryCache.test.js +63 -0
- package/dist/utils/taskActivity.js +17 -4
- package/dist/utils/taskActivity.test.js +27 -0
- package/dist/utils/taskNormalization.js +10 -2
- package/dist/utils/taskReferences.d.ts +11 -1
- package/dist/utils/taskReferences.js +33 -1
- package/package.json +4 -3
- package/scripts/qa-open-lib.mjs +175 -0
- package/scripts/qa-open-lib.mjs.d.ts +49 -0
- package/scripts/qa-open.mjs +352 -0
- package/dist/ui/assets/PlansPage-B6BDECSi.js +0 -1
- package/dist/ui/assets/TaskSettings-DUJgRTPU.js +0 -8
- package/dist/ui/assets/index-BiiF1cLQ.css +0 -1
- package/dist/ui/assets/index-xBWLPOEb.js +0 -6
|
@@ -2,16 +2,21 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import React, { useMemo, useCallback, useState, useEffect, useRef, useLayoutEffect, Suspense } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
import { useLocation, useNavigate } from 'react-router-dom';
|
|
5
|
-
import { Settings, Plus, Filter, Layers, User, Maximize2, Minimize2, Check, ChevronLeft, ArrowUp, ArrowDown, ArrowUpDown, AlertTriangle, Save, Loader2, Folder, FileText, LogOut, NotebookTabs, ClipboardCheck, Cloud, CloudOff,
|
|
5
|
+
import { Settings, Plus, Filter, Layers, User, Maximize2, Minimize2, Check, ChevronLeft, ArrowUp, ArrowDown, ArrowUpDown, AlertTriangle, Save, Loader2, Folder, FileText, LogOut, NotebookTabs, ClipboardCheck, Cloud, CloudOff, Users, ClockAlert, ChevronsUp, ChevronsDown, EyeOff, Zap, HelpCircle, Bot, Image, Workflow } from 'lucide-react';
|
|
6
6
|
import { STATUS_OPTIONS, getStatusDefinition } from '../../utils/constants';
|
|
7
7
|
import styles from '../../Taskforce.module.css';
|
|
8
8
|
import shellStyles from './WorkspaceShell.module.css';
|
|
9
9
|
import accountStyles from './StandaloneAccount.module.css';
|
|
10
10
|
import authStyles from './AuthShell.module.css';
|
|
11
11
|
import folderBrowserStyles from './FolderBrowser.module.css';
|
|
12
|
-
import managementStyles from './StandaloneManagement.module.css';
|
|
13
|
-
import syncStatusStyles from './SyncStatusModal.module.css';
|
|
14
12
|
import modalStyles from '../ui/Modal.module.css';
|
|
13
|
+
import { CreateWorkspaceConfirmModal } from './standalone/modals/CreateWorkspaceConfirmModal';
|
|
14
|
+
import { AccountHubModal } from './standalone/modals/AccountHubModal';
|
|
15
|
+
import { EditProfileModal } from './standalone/modals/EditProfileModal';
|
|
16
|
+
import { HelpModal } from './standalone/modals/HelpModal';
|
|
17
|
+
import { ScheduleWarningModal } from './standalone/modals/ScheduleWarningModal';
|
|
18
|
+
import { SyncStatusModal } from './standalone/modals/SyncStatusModal';
|
|
19
|
+
import { TeamManagementModal } from './standalone/modals/TeamManagementModal';
|
|
15
20
|
import { Modal } from '../ui/Modal';
|
|
16
21
|
import { TaskForm } from '../task/TaskForm';
|
|
17
22
|
import { TaskKanban } from '../task/TaskKanban';
|
|
@@ -28,8 +33,11 @@ import { getImageReferenceLabel } from '../../utils/imageReferences';
|
|
|
28
33
|
import { buildCustomTaxonomySortOptions } from '../../utils/taxonomySorting.js';
|
|
29
34
|
import { getLocale, t } from '../../localization';
|
|
30
35
|
import { resolveAnnotatedAttachmentDetail } from '../../utils/annotatedAttachments';
|
|
36
|
+
import { ACCOUNT_PROFILE_SUMMARY_PATH, invalidateAccountProfileSummaryCache, loadAccountProfileSummaryCached } from '../../utils/accountProfileSummaryCache';
|
|
37
|
+
import { fetchTaskforceApi } from '../../utils/taskforceApiClient';
|
|
31
38
|
import { AppShellHeader } from './AppShellHeader';
|
|
32
39
|
import { getTaskforceCloudEnvironmentConfig, parseTaskforceCloudEnvironment, resolveTaskforceCloudEnvironmentFromBaseUrl } from '../../config/cloudEnvironment';
|
|
40
|
+
import { logBootstrapDebug } from '../../hooks/workspace/workspaceLocalState';
|
|
33
41
|
import { FilterToolbar } from './panels/FilterToolbar';
|
|
34
42
|
import { DocumentFilterToolbar } from './panels/DocumentFilterToolbar';
|
|
35
43
|
import { PlanningDrawer, getPlanningDrawerWidthPx, } from './panels/PlanningDrawer';
|
|
@@ -37,7 +45,7 @@ import { getWorkspaceChromeDefinition, getWorkspaceModuleDefinitions, resolveWor
|
|
|
37
45
|
import { EmptyWorkspaceFilterBar, WorkspaceHeaderActions, WorkspaceHeaderDivider, WorkspaceHeaderSearch, WorkspaceHeaderSelect, WorkspacePrimaryAction, } from './chrome/WorkspaceChromeSections';
|
|
38
46
|
import { ScheduleSidebar, SCHEDULE_SIDEBAR_WIDTH_PX } from './panels/ScheduleSidebar';
|
|
39
47
|
import { DOCUMENT_ATTACHMENT_FILTER_OPTIONS, DOCUMENT_TYPE_FILTER_OPTIONS, } from '../features/documentWorkspaceFilters';
|
|
40
|
-
import { canManageTeamWorkspace as computeCanManageTeamWorkspace, canOpenTeamManagement as computeCanOpenTeamManagement
|
|
48
|
+
import { canManageTeamWorkspace as computeCanManageTeamWorkspace, canOpenTeamManagement as computeCanOpenTeamManagement } from './teamManagementAccess';
|
|
41
49
|
import { toDateOnlyLocal, parseDateOnlyLocal, startOfWeek, getDayOffsetFromWeekStart, addDays, dateOnlyToIsoWeekKey, SCHEDULE_DAY_KEYS, SCHEDULE_DAY_LABELS, } from './panels/scheduleUtils';
|
|
42
50
|
import { AGENTS_WORKSPACE_FEATURE_KEY, ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY, DOCUMENT_WORKSPACE_FEATURE_KEY, INITIATIVES_WORKSPACE_FEATURE_KEY, WORKFLOWS_WORKSPACE_FEATURE_KEY, resolveFeatureAccess, } from '../../runtime/appGates';
|
|
43
51
|
// Heavy feature modules — lazy-loaded to reduce the initial app chunk size.
|
|
@@ -48,6 +56,9 @@ const WorkflowsModule = React.lazy(() => import('../features/WorkflowsModule').t
|
|
|
48
56
|
const AgentsModule = React.lazy(() => import('../features/AgentsModule').then(m => ({ default: m.AgentsModule })));
|
|
49
57
|
const InitiativesModule = React.lazy(() => import('../features/InitiativesModule').then(m => ({ default: m.InitiativesModule })));
|
|
50
58
|
const PlansPage = React.lazy(() => import('./PlansPage').then(m => ({ default: m.PlansPage })));
|
|
59
|
+
const PROFILE_AVATAR_ACCEPT = 'image/png,image/jpeg,image/webp,image/gif';
|
|
60
|
+
const PROFILE_AVATAR_MAX_BYTES = 5 * 1024 * 1024;
|
|
61
|
+
const resolveSameOriginTaskforcePath = (path) => path;
|
|
51
62
|
function getSyncEventStableKey(event, index = 0) {
|
|
52
63
|
const explicitId = String(event.id ?? '').trim();
|
|
53
64
|
if (explicitId)
|
|
@@ -147,6 +158,9 @@ function isDebugModeEnabled() {
|
|
|
147
158
|
return runtimeOverride;
|
|
148
159
|
return typeof __DEBUG_MODE__ !== 'undefined' && __DEBUG_MODE__;
|
|
149
160
|
}
|
|
161
|
+
function getTimingNow() {
|
|
162
|
+
return typeof performance !== 'undefined' ? performance.now() : Date.now();
|
|
163
|
+
}
|
|
150
164
|
export function StandaloneLayout(props) {
|
|
151
165
|
const WORKSPACE_TOOL_RAIL_WIDTH = 56;
|
|
152
166
|
const WORKSPACE_CONTENT_GUTTER = 6;
|
|
@@ -202,7 +216,7 @@ export function StandaloneLayout(props) {
|
|
|
202
216
|
// clearFilters, // This was duplicated, removed one instance
|
|
203
217
|
// activeTypes, // This was duplicated, removed one instance
|
|
204
218
|
// Settings State & Props
|
|
205
|
-
settingsModel, configLoaded, currentTheme, setCurrentTheme, pathSaved, saveSettings, keyShortcut, setKeyShortcut, globalWeekStartsOn, locale, jsonBackupEnabled, setJsonBackupEnabled, mcpHostRoot, setMcpHostRoot, settingsSection, setSettingsSection, runtimeMode, workspaceSwitchingEnabled, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, authWorkspaceId, authUserEmail, authUserDisplayName, authSessionResolved, realtimeSyncEnabled, realtimeSyncFlagSource, workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceSyncStatus, workspaceSyncSummary, workspaceSyncRecommendedAction, workspaceSyncBusy, workspaceSyncPendingChanges, saveWorkspaceCloudSyncSettings, pushNotice, userGlobalSyncStatus, workspaceLastSuccessfulSyncAt, workspaceLastPullAt, workspaceLastPushAt, workspaceLastErrorAt, userGlobalSyncError, workspaceLastErrorMessage, retryWorkspaceCloudSync, resetWorkspaceSyncCursorAndPull, getWorkspaceSyncDiagnostics, currentWorkspaceId, currentWorkspaceRole, availableWorkspaces, switchWorkspace, updateCurrentUserProfile, logout, projectRoot, projectName, mcpScriptPath, serverHostRoot,
|
|
219
|
+
settingsModel, configLoaded, currentTheme, setCurrentTheme, pathSaved, saveSettings, keyShortcut, setKeyShortcut, globalWeekStartsOn, locale, jsonBackupEnabled, setJsonBackupEnabled, mcpHostRoot, setMcpHostRoot, settingsSection, setSettingsSection, runtimeMode, workspaceSwitchingEnabled, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, authUserId, authWorkspaceId, authUserEmail, authUserDisplayName, authUserAvatarUrl, authSessionResolved, realtimeSyncEnabled, realtimeSyncFlagSource, workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceSyncStatus, workspaceSyncSummary, workspaceSyncRecommendedAction, workspaceSyncBusy, workspaceSyncPendingChanges, saveWorkspaceCloudSyncSettings, pushNotice, userGlobalSyncStatus, workspaceLastSuccessfulSyncAt, workspaceLastPullAt, workspaceLastPushAt, workspaceLastErrorAt, userGlobalSyncError, workspaceLastErrorMessage, retryWorkspaceCloudSync, resetWorkspaceSyncCursorAndPull, getWorkspaceSyncDiagnostics, currentWorkspaceId, currentWorkspaceRole, availableWorkspaces, switchWorkspace, updateCurrentUserProfile, resolveCloudAuthUrl = resolveSameOriginTaskforcePath, logout, projectRoot, projectName, mcpScriptPath, serverHostRoot,
|
|
206
220
|
// Browser Props
|
|
207
221
|
showFolderBrowser, setShowFolderBrowser, folders, files, currentBrowsePath, fetchFolders, browserTarget, setBrowserTarget, handleSelectPath, handleAddPath, handleRemovePath,
|
|
208
222
|
// Settings Managers
|
|
@@ -430,6 +444,15 @@ export function StandaloneLayout(props) {
|
|
|
430
444
|
if (typeof state.planningDrawerOpen === 'boolean') {
|
|
431
445
|
setPlanningDrawerOpen(state.planningDrawerOpen);
|
|
432
446
|
}
|
|
447
|
+
if (typeof state.planningTreeCollapsed === 'boolean') {
|
|
448
|
+
setPlanningTreeCollapsed(state.planningTreeCollapsed);
|
|
449
|
+
}
|
|
450
|
+
if (typeof state.planningPrimaryCollapsed === 'boolean') {
|
|
451
|
+
setPlanningPrimaryCollapsed(state.planningPrimaryCollapsed);
|
|
452
|
+
}
|
|
453
|
+
if (typeof state.planningSecondaryCollapsed === 'boolean') {
|
|
454
|
+
setPlanningSecondaryCollapsed(state.planningSecondaryCollapsed);
|
|
455
|
+
}
|
|
433
456
|
if (Array.isArray(state.planningExpandedInitiativeIds)) {
|
|
434
457
|
setExpandedPlanningInitiativeIds(new Set(state.planningExpandedInitiativeIds
|
|
435
458
|
.map((value) => String(value || '').trim())
|
|
@@ -499,6 +522,9 @@ export function StandaloneLayout(props) {
|
|
|
499
522
|
documentTypeFilters,
|
|
500
523
|
documentAttachmentFilters,
|
|
501
524
|
planningDrawerOpen,
|
|
525
|
+
planningTreeCollapsed,
|
|
526
|
+
planningPrimaryCollapsed,
|
|
527
|
+
planningSecondaryCollapsed,
|
|
502
528
|
planningExpandedInitiativeIds: Array.from(expandedPlanningInitiativeIds),
|
|
503
529
|
planningDrawerDetail,
|
|
504
530
|
planningNestedWorkstreamDetailId,
|
|
@@ -526,6 +552,9 @@ export function StandaloneLayout(props) {
|
|
|
526
552
|
documentTypeFilters,
|
|
527
553
|
documentAttachmentFilters,
|
|
528
554
|
planningDrawerOpen,
|
|
555
|
+
planningTreeCollapsed,
|
|
556
|
+
planningPrimaryCollapsed,
|
|
557
|
+
planningSecondaryCollapsed,
|
|
529
558
|
expandedPlanningInitiativeIds,
|
|
530
559
|
planningDrawerDetail,
|
|
531
560
|
planningNestedWorkstreamDetailId,
|
|
@@ -776,9 +805,9 @@ export function StandaloneLayout(props) {
|
|
|
776
805
|
if (taskScope === 'deleted')
|
|
777
806
|
return deletedScopeAllTasks;
|
|
778
807
|
if (!showArchive)
|
|
779
|
-
return props.
|
|
780
|
-
return [...props.
|
|
781
|
-
}, [archivedScopeAllTasks, deletedScopeAllTasks, props.
|
|
808
|
+
return props.tasks;
|
|
809
|
+
return [...props.tasks, ...archivedScopeAllTasks];
|
|
810
|
+
}, [archivedScopeAllTasks, deletedScopeAllTasks, props.tasks, showArchive, taskScope]);
|
|
782
811
|
const hierarchyTasks = useMemo(() => {
|
|
783
812
|
const byId = new Map();
|
|
784
813
|
for (const task of archivedScopeAllTasks)
|
|
@@ -802,16 +831,11 @@ export function StandaloneLayout(props) {
|
|
|
802
831
|
return map;
|
|
803
832
|
}, [assigneeOptions]);
|
|
804
833
|
const planningStructure = useMemo(() => {
|
|
805
|
-
const
|
|
806
|
-
?
|
|
807
|
-
:
|
|
808
|
-
?
|
|
809
|
-
: !
|
|
810
|
-
const planningWorkstreams = taskScope === 'deleted'
|
|
811
|
-
? []
|
|
812
|
-
: props.workstreams.filter((workstream) => taskScope === 'archived'
|
|
813
|
-
? Boolean(workstream.isArchived)
|
|
814
|
-
: !workstream.isArchived);
|
|
834
|
+
const planningScopedTasks = taskScope === 'archived'
|
|
835
|
+
? archivedScopeAllTasks
|
|
836
|
+
: taskScope === 'deleted'
|
|
837
|
+
? deletedScopeAllTasks
|
|
838
|
+
: props.tasks.filter((task) => !task.isArchived);
|
|
815
839
|
const summarizeTasks = (scopeTasks) => {
|
|
816
840
|
const taskCount = scopeTasks.length;
|
|
817
841
|
const completedTaskCount = scopeTasks.filter((task) => task.isArchived || task.status === 'done' || task.status === 'cancelled').length;
|
|
@@ -827,7 +851,7 @@ export function StandaloneLayout(props) {
|
|
|
827
851
|
const workstreamById = new Map();
|
|
828
852
|
const initiativeById = new Map();
|
|
829
853
|
const buildWorkstreamSummary = (workstream) => {
|
|
830
|
-
const scopedTasks =
|
|
854
|
+
const scopedTasks = planningScopedTasks.filter((task) => (task.workstreamId || null) === workstream.id);
|
|
831
855
|
const taskPreviews = scopedTasks.map((task) => ({
|
|
832
856
|
id: task.id,
|
|
833
857
|
referenceNumber: task.referenceNumber ?? null,
|
|
@@ -853,10 +877,10 @@ export function StandaloneLayout(props) {
|
|
|
853
877
|
workstreamById.set(summary.id, summary);
|
|
854
878
|
return summary;
|
|
855
879
|
};
|
|
856
|
-
const
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
.
|
|
880
|
+
const allWorkstreamSummaries = props.workstreams.map(buildWorkstreamSummary);
|
|
881
|
+
const allInitiatives = props.initiatives.map((initiative) => {
|
|
882
|
+
const workstreams = allWorkstreamSummaries
|
|
883
|
+
.filter((workstream) => (workstream.initiativeId || null) === initiative.id);
|
|
860
884
|
const initiativeScopedTasks = workstreams
|
|
861
885
|
.flatMap((workstream) => workstreamTaskIds.get(workstream.id) || [])
|
|
862
886
|
.map((taskId) => hierarchyTaskById.get(taskId))
|
|
@@ -878,9 +902,18 @@ export function StandaloneLayout(props) {
|
|
|
878
902
|
initiativeById.set(initiativeSummary.id, initiativeSummary);
|
|
879
903
|
return initiativeSummary;
|
|
880
904
|
});
|
|
881
|
-
const
|
|
905
|
+
const initiatives = taskScope === 'deleted'
|
|
906
|
+
? []
|
|
907
|
+
: allInitiatives.filter((initiative) => taskScope === 'archived'
|
|
908
|
+
? Boolean(initiative.isArchived)
|
|
909
|
+
: !initiative.isArchived);
|
|
910
|
+
const standaloneWorkstreams = allWorkstreamSummaries
|
|
882
911
|
.filter((workstream) => !workstream.initiativeId)
|
|
883
|
-
.
|
|
912
|
+
.filter((workstream) => taskScope === 'deleted'
|
|
913
|
+
? false
|
|
914
|
+
: taskScope === 'archived'
|
|
915
|
+
? Boolean(workstream.isArchived)
|
|
916
|
+
: !workstream.isArchived);
|
|
884
917
|
return {
|
|
885
918
|
initiatives,
|
|
886
919
|
standaloneWorkstreams,
|
|
@@ -890,7 +923,7 @@ export function StandaloneLayout(props) {
|
|
|
890
923
|
workstreamById,
|
|
891
924
|
initiativeById,
|
|
892
925
|
};
|
|
893
|
-
}, [assigneeLabelByValue,
|
|
926
|
+
}, [archivedScopeAllTasks, assigneeLabelByValue, deletedScopeAllTasks, hierarchyTaskById, props.initiatives, props.tasks, props.workstreams, taskScope]);
|
|
894
927
|
const initiativeFilterOptions = useMemo(() => planningStructure.initiatives.map((initiative) => {
|
|
895
928
|
const referenceLabel = formatInitiativeReference(initiative);
|
|
896
929
|
return {
|
|
@@ -962,13 +995,6 @@ export function StandaloneLayout(props) {
|
|
|
962
995
|
return exactInitiative;
|
|
963
996
|
return resolveInitiativeReference(planningStructure.initiatives, planningDraftInitiativeId);
|
|
964
997
|
}, [planningDraftInitiativeId, planningStructure.initiativeById, planningStructure.initiatives]);
|
|
965
|
-
useEffect(() => {
|
|
966
|
-
if (!planningDrawerOpen)
|
|
967
|
-
return;
|
|
968
|
-
setPlanningTreeCollapsed(false);
|
|
969
|
-
setPlanningPrimaryCollapsed(false);
|
|
970
|
-
setPlanningSecondaryCollapsed(false);
|
|
971
|
-
}, [planningDrawerOpen]);
|
|
972
998
|
useEffect(() => {
|
|
973
999
|
if (selectedPlanningInitiativeId && !planningStructure.initiativeById.has(selectedPlanningInitiativeId)) {
|
|
974
1000
|
setSelectedPlanningInitiativeId('');
|
|
@@ -976,23 +1002,7 @@ export function StandaloneLayout(props) {
|
|
|
976
1002
|
if (selectedPlanningWorkstreamId && !planningStructure.workstreamById.has(selectedPlanningWorkstreamId)) {
|
|
977
1003
|
setSelectedPlanningWorkstreamId('');
|
|
978
1004
|
}
|
|
979
|
-
if (planningDrawerDetail?.type === 'initiative' && !planningStructure.initiativeById.has(planningDrawerDetail.id)) {
|
|
980
|
-
setPlanningDrawerDetail(null);
|
|
981
|
-
}
|
|
982
|
-
if (planningDrawerDetail?.type === 'workstream' && !planningStructure.workstreamById.has(planningDrawerDetail.id)) {
|
|
983
|
-
setPlanningDrawerDetail(null);
|
|
984
|
-
}
|
|
985
|
-
if (planningNestedWorkstreamDetailId && !planningStructure.workstreamById.has(planningNestedWorkstreamDetailId)) {
|
|
986
|
-
setPlanningNestedWorkstreamDetailId(null);
|
|
987
|
-
}
|
|
988
|
-
if (planningSecondaryEditor?.mode === 'edit'
|
|
989
|
-
&& !planningStructure.workstreamById.has(planningSecondaryEditor.targetId)) {
|
|
990
|
-
setPlanningSecondaryEditor(null);
|
|
991
|
-
}
|
|
992
1005
|
}, [
|
|
993
|
-
planningDrawerDetail,
|
|
994
|
-
planningNestedWorkstreamDetailId,
|
|
995
|
-
planningSecondaryEditor,
|
|
996
1006
|
planningStructure.initiativeById,
|
|
997
1007
|
planningStructure.workstreamById,
|
|
998
1008
|
selectedPlanningInitiativeId,
|
|
@@ -1027,6 +1037,11 @@ export function StandaloneLayout(props) {
|
|
|
1027
1037
|
return boardVisibleTasks;
|
|
1028
1038
|
return boardVisibleTasks.filter((task) => selectedPlanningTaskIds.has(task.id));
|
|
1029
1039
|
}, [boardVisibleTasks, selectedPlanningTaskIds]);
|
|
1040
|
+
const scopedKanbanAllTasks = useMemo(() => {
|
|
1041
|
+
if (!selectedPlanningTaskIds || selectedPlanningTaskIds.size === 0)
|
|
1042
|
+
return kanbanAllTasks;
|
|
1043
|
+
return kanbanAllTasks.filter((task) => selectedPlanningTaskIds.has(task.id));
|
|
1044
|
+
}, [kanbanAllTasks, selectedPlanningTaskIds]);
|
|
1030
1045
|
const expiredRecoveryCandidates = useMemo(() => {
|
|
1031
1046
|
if (groupBy !== 'schedule')
|
|
1032
1047
|
return [];
|
|
@@ -1059,6 +1074,29 @@ export function StandaloneLayout(props) {
|
|
|
1059
1074
|
return !!task.dueDate && task.dueDate < todayDateOnly;
|
|
1060
1075
|
}).length;
|
|
1061
1076
|
}, [groupBy, hierarchyVisibleTasks, todayDateOnly]);
|
|
1077
|
+
const normalizedAuthUserId = String(authUserId || '').trim();
|
|
1078
|
+
const assignedTaskCount = useMemo(() => {
|
|
1079
|
+
if (!normalizedAuthUserId || normalizedAuthUserId === 'anonymous')
|
|
1080
|
+
return 0;
|
|
1081
|
+
return tasks.filter((task) => {
|
|
1082
|
+
const isTerminal = task.status === 'done' || task.status === 'cancelled';
|
|
1083
|
+
if (isTerminal)
|
|
1084
|
+
return false;
|
|
1085
|
+
return String(task.assignee || '').trim() === normalizedAuthUserId;
|
|
1086
|
+
}).length;
|
|
1087
|
+
}, [normalizedAuthUserId, tasks]);
|
|
1088
|
+
const assignedOverdueTaskCount = useMemo(() => {
|
|
1089
|
+
if (!normalizedAuthUserId || normalizedAuthUserId === 'anonymous')
|
|
1090
|
+
return 0;
|
|
1091
|
+
return tasks.filter((task) => {
|
|
1092
|
+
const isTerminal = task.status === 'done' || task.status === 'cancelled';
|
|
1093
|
+
if (isTerminal)
|
|
1094
|
+
return false;
|
|
1095
|
+
if (String(task.assignee || '').trim() !== normalizedAuthUserId)
|
|
1096
|
+
return false;
|
|
1097
|
+
return !!task.dueDate && task.dueDate < todayDateOnly;
|
|
1098
|
+
}).length;
|
|
1099
|
+
}, [normalizedAuthUserId, tasks, todayDateOnly]);
|
|
1062
1100
|
const expiredColumnCandidates = useMemo(() => {
|
|
1063
1101
|
if (groupBy !== 'schedule')
|
|
1064
1102
|
return [];
|
|
@@ -1168,7 +1206,10 @@ export function StandaloneLayout(props) {
|
|
|
1168
1206
|
const [showCreateWorkspaceConfirm, setShowCreateWorkspaceConfirm] = useState(false);
|
|
1169
1207
|
const [showTeamManagementModal, setShowTeamManagementModal] = useState(false);
|
|
1170
1208
|
const [teamManagementTab, setTeamManagementTab] = useState('members');
|
|
1209
|
+
const [teamWorkspaceRole, setTeamWorkspaceRole] = useState(null);
|
|
1171
1210
|
const [teamPlanMode, setTeamPlanMode] = useState('unknown');
|
|
1211
|
+
const [teamManagementAccessLoading, setTeamManagementAccessLoading] = useState(false);
|
|
1212
|
+
const [teamManagementAccessError, setTeamManagementAccessError] = useState(null);
|
|
1172
1213
|
const [teamMgmtError, setTeamMgmtError] = useState(null);
|
|
1173
1214
|
const [teamUsersLoading, setTeamUsersLoading] = useState(false);
|
|
1174
1215
|
const [teamUsers, setTeamUsers] = useState([]);
|
|
@@ -1183,7 +1224,7 @@ export function StandaloneLayout(props) {
|
|
|
1183
1224
|
const [teamAuditEvents, setTeamAuditEvents] = useState([]);
|
|
1184
1225
|
const [teamAuditHasMore, setTeamAuditHasMore] = useState(false);
|
|
1185
1226
|
const [teamAuditPages, setTeamAuditPages] = useState(1);
|
|
1186
|
-
const [
|
|
1227
|
+
const [accountProfileSummary, setAccountProfileSummary] = useState(null);
|
|
1187
1228
|
const [billingLoading, setBillingLoading] = useState(false);
|
|
1188
1229
|
const [billingError, setBillingError] = useState(null);
|
|
1189
1230
|
const [billingActionBusy, setBillingActionBusy] = useState(false);
|
|
@@ -1191,12 +1232,25 @@ export function StandaloneLayout(props) {
|
|
|
1191
1232
|
const [billingNotice, setBillingNotice] = useState(null);
|
|
1192
1233
|
const [billingIntervalChoice, setBillingIntervalChoice] = useState('month');
|
|
1193
1234
|
const [profileDisplayNameDraft, setProfileDisplayNameDraft] = useState('');
|
|
1235
|
+
const [profileAvatarPreviewUrl, setProfileAvatarPreviewUrl] = useState('');
|
|
1236
|
+
const [profileAvatarDraftId, setProfileAvatarDraftId] = useState(null);
|
|
1237
|
+
const [profileAvatarBusy, setProfileAvatarBusy] = useState(false);
|
|
1194
1238
|
const [profileSaveBusy, setProfileSaveBusy] = useState(false);
|
|
1195
1239
|
const [profileSaveError, setProfileSaveError] = useState(null);
|
|
1196
1240
|
const [profileSaveNotice, setProfileSaveNotice] = useState(null);
|
|
1197
1241
|
const TEAM_AUDIT_PAGE_SIZE = 25;
|
|
1198
1242
|
const accountMenuRef = useRef(null);
|
|
1243
|
+
const profileAvatarInputRef = useRef(null);
|
|
1244
|
+
const accountSurfaceOpenStartedAtRef = useRef(null);
|
|
1245
|
+
const accountSurfacePendingRef = useRef({
|
|
1246
|
+
billing: false,
|
|
1247
|
+
teamManagement: false
|
|
1248
|
+
});
|
|
1249
|
+
const accountProfileSummaryRef = useRef(null);
|
|
1199
1250
|
// showSearch state removed - search always visible in actions area
|
|
1251
|
+
useEffect(() => {
|
|
1252
|
+
accountProfileSummaryRef.current = accountProfileSummary;
|
|
1253
|
+
}, [accountProfileSummary]);
|
|
1200
1254
|
useEffect(() => {
|
|
1201
1255
|
if (!showAccountMenu)
|
|
1202
1256
|
return;
|
|
@@ -1239,32 +1293,25 @@ export function StandaloneLayout(props) {
|
|
|
1239
1293
|
const fallbackId = String(currentWorkspaceId || '').trim();
|
|
1240
1294
|
return fallbackId || 'Workspace';
|
|
1241
1295
|
}, [availableWorkspaces, currentWorkspaceId, projectName, runtimeMode]);
|
|
1242
|
-
const canManageTeamWorkspace = computeCanManageTeamWorkspace(canAccessCloudTeamManagement,
|
|
1243
|
-
const canOpenTeamManagement = computeCanOpenTeamManagement(canAccessCloudTeamManagement,
|
|
1296
|
+
const canManageTeamWorkspace = computeCanManageTeamWorkspace(canAccessCloudTeamManagement, teamWorkspaceRole);
|
|
1297
|
+
const canOpenTeamManagement = computeCanOpenTeamManagement(canAccessCloudTeamManagement, teamWorkspaceRole, teamPlanMode);
|
|
1244
1298
|
const authDisplayName = String(authUserDisplayName || '').trim();
|
|
1299
|
+
const authAvatarUrl = String(authUserAvatarUrl || '').trim();
|
|
1245
1300
|
const authEmailLabel = String(authUserEmail || '').trim();
|
|
1246
1301
|
const authIdentityLabel = authDisplayName || authEmailLabel;
|
|
1247
1302
|
const hasAuthIdentity = authIdentityLabel.length > 0;
|
|
1248
1303
|
const hasAuthEmail = authEmailLabel.length > 0;
|
|
1249
|
-
const accountBadgeLabel = (authIdentityLabel || authEmailLabel || '
|
|
1250
|
-
const
|
|
1251
|
-
const
|
|
1304
|
+
const accountBadgeLabel = (authIdentityLabel || authEmailLabel || '').trim();
|
|
1305
|
+
const shouldShowResolvedAccountInitial = isAuthenticated && authSessionResolved && accountBadgeLabel.length > 0;
|
|
1306
|
+
const accountBadgeInitial = shouldShowResolvedAccountInitial ? accountBadgeLabel.charAt(0).toUpperCase() : '';
|
|
1307
|
+
const accountAvatarUrl = authAvatarUrl;
|
|
1308
|
+
const shouldShowResolvedAccountAvatar = isAuthenticated && authSessionResolved && accountAvatarUrl.length > 0;
|
|
1309
|
+
const profileAvatarDisplayUrl = String(profileAvatarPreviewUrl || authAvatarUrl).trim();
|
|
1310
|
+
const subscriptionLabel = String(accountProfileSummary?.planName || accountProfileSummary?.planId || '').trim();
|
|
1252
1311
|
const hasSubscription = subscriptionLabel.length > 0;
|
|
1253
|
-
const teamManagementWorkspaceId = String(authWorkspaceId || currentWorkspaceId || '').trim();
|
|
1312
|
+
const teamManagementWorkspaceId = String(accountProfileSummary?.workspaceId || authWorkspaceId || currentWorkspaceId || '').trim();
|
|
1254
1313
|
const runtimeLabel = runtimeMode === 'cloud' ? 'CLOUD' : 'LOCAL';
|
|
1255
1314
|
const apiBaseUrl = props.config?.apiBaseUrl || '';
|
|
1256
|
-
const billingApiBaseUrl = useMemo(() => {
|
|
1257
|
-
const preferredBase = runtimeMode === 'local'
|
|
1258
|
-
? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
|
|
1259
|
-
: (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
|
|
1260
|
-
return String(preferredBase || '').trim().replace(/\/+$/, '');
|
|
1261
|
-
}, [props.config?.apiBaseUrl, props.config?.cloudAuthBaseUrl, runtimeMode]);
|
|
1262
|
-
const buildBillingApiUrl = useCallback((path) => {
|
|
1263
|
-
const normalizedPath = String(path || '').trim();
|
|
1264
|
-
if (!billingApiBaseUrl)
|
|
1265
|
-
return normalizedPath;
|
|
1266
|
-
return `${billingApiBaseUrl}${normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`}`;
|
|
1267
|
-
}, [billingApiBaseUrl]);
|
|
1268
1315
|
const connectedEnvironmentLabel = useMemo(() => resolveConnectedEnvironmentLabel(props.config?.cloudEnvironment, [
|
|
1269
1316
|
String(props.config?.cloudBaseUrl || ''),
|
|
1270
1317
|
String(props.config?.cloudAuthBaseUrl || ''),
|
|
@@ -1282,9 +1329,141 @@ export function StandaloneLayout(props) {
|
|
|
1282
1329
|
if (!showEditProfileModal)
|
|
1283
1330
|
return;
|
|
1284
1331
|
setProfileDisplayNameDraft(authDisplayName);
|
|
1332
|
+
setProfileAvatarPreviewUrl(authAvatarUrl);
|
|
1333
|
+
setProfileAvatarDraftId(null);
|
|
1334
|
+
setProfileSaveError(null);
|
|
1335
|
+
setProfileSaveNotice(null);
|
|
1336
|
+
}, [authAvatarUrl, authDisplayName, showEditProfileModal]);
|
|
1337
|
+
const discardProfileAvatarDraft = useCallback(async (draftId) => {
|
|
1338
|
+
const normalizedDraftId = String(draftId || '').trim();
|
|
1339
|
+
if (!normalizedDraftId || !cloudAuthConfigured)
|
|
1340
|
+
return;
|
|
1341
|
+
try {
|
|
1342
|
+
const baseUrl = runtimeMode === 'local'
|
|
1343
|
+
? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
|
|
1344
|
+
: (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
|
|
1345
|
+
await fetchTaskforceApi('/api/taskforce/auth/profile/avatar/discard', {
|
|
1346
|
+
method: 'POST',
|
|
1347
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1348
|
+
credentials: 'include',
|
|
1349
|
+
body: JSON.stringify({ draftId: normalizedDraftId })
|
|
1350
|
+
}, baseUrl);
|
|
1351
|
+
}
|
|
1352
|
+
catch {
|
|
1353
|
+
// Best-effort cleanup only.
|
|
1354
|
+
}
|
|
1355
|
+
}, [cloudAuthConfigured, props.config?.apiBaseUrl, props.config?.cloudAuthBaseUrl, runtimeMode]);
|
|
1356
|
+
const closeEditProfileModal = useCallback(() => {
|
|
1357
|
+
const pendingDraftId = profileAvatarDraftId;
|
|
1358
|
+
setShowEditProfileModal(false);
|
|
1285
1359
|
setProfileSaveError(null);
|
|
1286
1360
|
setProfileSaveNotice(null);
|
|
1287
|
-
|
|
1361
|
+
setProfileAvatarDraftId(null);
|
|
1362
|
+
setProfileAvatarPreviewUrl(authAvatarUrl);
|
|
1363
|
+
if (pendingDraftId) {
|
|
1364
|
+
void discardProfileAvatarDraft(pendingDraftId);
|
|
1365
|
+
}
|
|
1366
|
+
}, [authAvatarUrl, discardProfileAvatarDraft, profileAvatarDraftId]);
|
|
1367
|
+
const handleProfileAvatarSelected = useCallback(async (file) => {
|
|
1368
|
+
if (!file || !cloudAuthConfigured)
|
|
1369
|
+
return;
|
|
1370
|
+
if (!file.type.startsWith('image/')) {
|
|
1371
|
+
setProfileSaveError('Profile photo must be an image file.');
|
|
1372
|
+
setProfileSaveNotice(null);
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
if (file.size > PROFILE_AVATAR_MAX_BYTES) {
|
|
1376
|
+
setProfileSaveError('Profile photo must be 5 MB or smaller.');
|
|
1377
|
+
setProfileSaveNotice(null);
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
setProfileAvatarBusy(true);
|
|
1381
|
+
setProfileSaveError(null);
|
|
1382
|
+
setProfileSaveNotice(null);
|
|
1383
|
+
try {
|
|
1384
|
+
const baseUrl = runtimeMode === 'local'
|
|
1385
|
+
? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
|
|
1386
|
+
: (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
|
|
1387
|
+
const initRes = await fetchTaskforceApi('/api/taskforce/auth/profile/avatar/init', {
|
|
1388
|
+
method: 'POST',
|
|
1389
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1390
|
+
credentials: 'include',
|
|
1391
|
+
body: JSON.stringify({
|
|
1392
|
+
originalName: file.name,
|
|
1393
|
+
mimeType: file.type,
|
|
1394
|
+
size: file.size
|
|
1395
|
+
})
|
|
1396
|
+
}, baseUrl);
|
|
1397
|
+
const initData = await initRes.json().catch(() => ({}));
|
|
1398
|
+
if (!initRes.ok || !initData?.success || typeof initData?.uploadUrl !== 'string' || typeof initData?.relativePath !== 'string') {
|
|
1399
|
+
throw new Error(initData?.error || 'Failed to start avatar upload.');
|
|
1400
|
+
}
|
|
1401
|
+
const uploadRes = await fetch(initData.uploadUrl, {
|
|
1402
|
+
method: String(initData.method || 'PUT'),
|
|
1403
|
+
headers: initData.headers || { 'Content-Type': file.type || 'application/octet-stream' },
|
|
1404
|
+
body: file
|
|
1405
|
+
});
|
|
1406
|
+
if (!uploadRes.ok) {
|
|
1407
|
+
throw new Error('Failed to upload avatar.');
|
|
1408
|
+
}
|
|
1409
|
+
const finalizeRes = await fetchTaskforceApi('/api/taskforce/auth/profile/avatar/finalize', {
|
|
1410
|
+
method: 'POST',
|
|
1411
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1412
|
+
credentials: 'include',
|
|
1413
|
+
body: JSON.stringify({
|
|
1414
|
+
draftId: initData.draftId,
|
|
1415
|
+
relativePath: initData.relativePath
|
|
1416
|
+
})
|
|
1417
|
+
}, baseUrl);
|
|
1418
|
+
const finalizeData = await finalizeRes.json().catch(() => ({}));
|
|
1419
|
+
if (!finalizeRes.ok || !finalizeData?.success || typeof finalizeData?.draftId !== 'string') {
|
|
1420
|
+
throw new Error(finalizeData?.error || 'Failed to finalize avatar upload.');
|
|
1421
|
+
}
|
|
1422
|
+
const previousDraftId = profileAvatarDraftId;
|
|
1423
|
+
setProfileAvatarDraftId(finalizeData.draftId);
|
|
1424
|
+
setProfileAvatarPreviewUrl(typeof finalizeData?.avatarUrl === 'string' ? finalizeData.avatarUrl : '');
|
|
1425
|
+
setProfileSaveNotice('Profile photo ready to save.');
|
|
1426
|
+
if (previousDraftId && previousDraftId !== finalizeData.draftId) {
|
|
1427
|
+
void discardProfileAvatarDraft(previousDraftId);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
catch (error) {
|
|
1431
|
+
setProfileSaveError(error instanceof Error ? error.message : 'Failed to upload profile photo.');
|
|
1432
|
+
}
|
|
1433
|
+
finally {
|
|
1434
|
+
setProfileAvatarBusy(false);
|
|
1435
|
+
if (profileAvatarInputRef.current) {
|
|
1436
|
+
profileAvatarInputRef.current.value = '';
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
}, [
|
|
1440
|
+
cloudAuthConfigured,
|
|
1441
|
+
discardProfileAvatarDraft,
|
|
1442
|
+
profileAvatarDraftId,
|
|
1443
|
+
props.config?.apiBaseUrl,
|
|
1444
|
+
props.config?.cloudAuthBaseUrl,
|
|
1445
|
+
runtimeMode
|
|
1446
|
+
]);
|
|
1447
|
+
const handleDiscardPendingProfileAvatar = useCallback(() => {
|
|
1448
|
+
if (!profileAvatarDraftId)
|
|
1449
|
+
return;
|
|
1450
|
+
const draftId = profileAvatarDraftId;
|
|
1451
|
+
setProfileAvatarDraftId(null);
|
|
1452
|
+
setProfileAvatarPreviewUrl(authAvatarUrl);
|
|
1453
|
+
setProfileSaveNotice(null);
|
|
1454
|
+
setProfileSaveError(null);
|
|
1455
|
+
void discardProfileAvatarDraft(draftId);
|
|
1456
|
+
}, [authAvatarUrl, discardProfileAvatarDraft, profileAvatarDraftId]);
|
|
1457
|
+
const handleRemoveProfileAvatar = useCallback(() => {
|
|
1458
|
+
const pendingDraftId = profileAvatarDraftId;
|
|
1459
|
+
setProfileAvatarDraftId(null);
|
|
1460
|
+
setProfileAvatarPreviewUrl('');
|
|
1461
|
+
setProfileSaveNotice(authAvatarUrl ? 'Profile photo will be removed when you save.' : null);
|
|
1462
|
+
setProfileSaveError(null);
|
|
1463
|
+
if (pendingDraftId) {
|
|
1464
|
+
void discardProfileAvatarDraft(pendingDraftId);
|
|
1465
|
+
}
|
|
1466
|
+
}, [authAvatarUrl, discardProfileAvatarDraft, profileAvatarDraftId]);
|
|
1288
1467
|
const handleSaveProfile = useCallback(async () => {
|
|
1289
1468
|
const trimmedDisplayName = profileDisplayNameDraft.trim();
|
|
1290
1469
|
if (!trimmedDisplayName) {
|
|
@@ -1295,65 +1474,161 @@ export function StandaloneLayout(props) {
|
|
|
1295
1474
|
setProfileSaveBusy(true);
|
|
1296
1475
|
setProfileSaveError(null);
|
|
1297
1476
|
setProfileSaveNotice(null);
|
|
1298
|
-
const result = await updateCurrentUserProfile({
|
|
1477
|
+
const result = await updateCurrentUserProfile({
|
|
1478
|
+
displayName: trimmedDisplayName,
|
|
1479
|
+
avatarDraftId: profileAvatarDraftId,
|
|
1480
|
+
clearAvatar: !profileAvatarDraftId && !profileAvatarPreviewUrl && Boolean(authAvatarUrl)
|
|
1481
|
+
});
|
|
1299
1482
|
if (!result.success) {
|
|
1300
1483
|
setProfileSaveError(result.error || 'Failed to update profile.');
|
|
1301
1484
|
setProfileSaveBusy(false);
|
|
1302
1485
|
return;
|
|
1303
1486
|
}
|
|
1304
1487
|
setProfileSaveNotice('Profile updated.');
|
|
1488
|
+
setProfileAvatarDraftId(null);
|
|
1489
|
+
setProfileAvatarPreviewUrl('');
|
|
1305
1490
|
setProfileSaveBusy(false);
|
|
1306
1491
|
setShowEditProfileModal(false);
|
|
1307
|
-
}, [profileDisplayNameDraft, updateCurrentUserProfile]);
|
|
1492
|
+
}, [authAvatarUrl, profileAvatarDraftId, profileAvatarPreviewUrl, profileDisplayNameDraft, updateCurrentUserProfile]);
|
|
1308
1493
|
const teamAdminHeaders = useCallback(() => {
|
|
1309
1494
|
const headers = {};
|
|
1310
1495
|
const workspaceId = String(teamManagementWorkspaceId || '').trim();
|
|
1311
1496
|
if (workspaceId)
|
|
1312
1497
|
headers['x-taskforce-workspace-id'] = workspaceId;
|
|
1498
|
+
const workspaceRole = teamWorkspaceRole || currentWorkspaceRole;
|
|
1499
|
+
if (workspaceRole === 'owner'
|
|
1500
|
+
|| workspaceRole === 'admin'
|
|
1501
|
+
|| workspaceRole === 'member'
|
|
1502
|
+
|| workspaceRole === 'read-only') {
|
|
1503
|
+
headers['x-taskforce-workspace-role'] = workspaceRole;
|
|
1504
|
+
}
|
|
1313
1505
|
return headers;
|
|
1314
|
-
}, [teamManagementWorkspaceId]);
|
|
1315
|
-
const
|
|
1506
|
+
}, [currentWorkspaceRole, teamManagementWorkspaceId, teamWorkspaceRole]);
|
|
1507
|
+
const startAccountSurfaceTiming = useCallback((surface) => {
|
|
1508
|
+
const expectsBilling = isAuthenticated;
|
|
1509
|
+
const expectsTeamManagement = canAccessCloudTeamManagement;
|
|
1510
|
+
accountSurfaceOpenStartedAtRef.current = getTimingNow();
|
|
1511
|
+
accountSurfacePendingRef.current = {
|
|
1512
|
+
billing: expectsBilling,
|
|
1513
|
+
teamManagement: expectsTeamManagement
|
|
1514
|
+
};
|
|
1515
|
+
logBootstrapDebug('account_surface_opened', {
|
|
1516
|
+
surface,
|
|
1517
|
+
expectsBilling,
|
|
1518
|
+
expectsTeamManagement
|
|
1519
|
+
});
|
|
1520
|
+
if (!expectsBilling && !expectsTeamManagement) {
|
|
1521
|
+
logBootstrapDebug('account_surface_ready', {
|
|
1522
|
+
surface,
|
|
1523
|
+
durationMs: 0,
|
|
1524
|
+
teamManagementVisible: false,
|
|
1525
|
+
billingPlanId: null
|
|
1526
|
+
});
|
|
1527
|
+
accountSurfaceOpenStartedAtRef.current = null;
|
|
1528
|
+
}
|
|
1529
|
+
}, [canAccessCloudTeamManagement, isAuthenticated]);
|
|
1530
|
+
const markAccountSurfaceLoadSettled = useCallback((kind) => {
|
|
1531
|
+
const pending = accountSurfacePendingRef.current;
|
|
1532
|
+
pending[kind] = false;
|
|
1533
|
+
if (pending.billing || pending.teamManagement)
|
|
1534
|
+
return;
|
|
1535
|
+
const startedAt = accountSurfaceOpenStartedAtRef.current;
|
|
1536
|
+
if (startedAt === null)
|
|
1537
|
+
return;
|
|
1538
|
+
logBootstrapDebug('account_surface_ready', {
|
|
1539
|
+
surface: showAccountHub ? 'account_hub' : (showAccountMenu ? 'account_menu' : 'closed'),
|
|
1540
|
+
durationMs: Math.round(getTimingNow() - startedAt),
|
|
1541
|
+
teamManagementVisible: canOpenTeamManagement,
|
|
1542
|
+
billingPlanId: String(accountProfileSummary?.planId || '').trim() || null
|
|
1543
|
+
});
|
|
1544
|
+
accountSurfaceOpenStartedAtRef.current = null;
|
|
1545
|
+
}, [accountProfileSummary?.planId, canOpenTeamManagement, showAccountHub, showAccountMenu]);
|
|
1546
|
+
const loadAccountProfileSummary = useCallback(async () => {
|
|
1547
|
+
const accountProfileSummaryUrl = resolveCloudAuthUrl(ACCOUNT_PROFILE_SUMMARY_PATH);
|
|
1316
1548
|
if (!isAuthenticated) {
|
|
1317
|
-
|
|
1549
|
+
setAccountProfileSummary(null);
|
|
1550
|
+
setBillingError(null);
|
|
1551
|
+
setTeamWorkspaceRole(null);
|
|
1552
|
+
setTeamPlanMode('unknown');
|
|
1553
|
+
setTeamManagementAccessLoading(false);
|
|
1554
|
+
setTeamManagementAccessError(null);
|
|
1555
|
+
invalidateAccountProfileSummaryCache(accountProfileSummaryUrl, { identityKey: authUserId });
|
|
1556
|
+
markAccountSurfaceLoadSettled('billing');
|
|
1557
|
+
markAccountSurfaceLoadSettled('teamManagement');
|
|
1318
1558
|
return null;
|
|
1319
1559
|
}
|
|
1560
|
+
const startedAt = getTimingNow();
|
|
1320
1561
|
setBillingLoading(true);
|
|
1321
1562
|
setBillingError(null);
|
|
1563
|
+
setTeamManagementAccessLoading(canAccessCloudTeamManagement);
|
|
1564
|
+
setTeamManagementAccessError(null);
|
|
1322
1565
|
try {
|
|
1323
|
-
const
|
|
1324
|
-
|
|
1325
|
-
|
|
1566
|
+
const payload = await loadAccountProfileSummaryCached(accountProfileSummaryUrl, { identityKey: authUserId });
|
|
1567
|
+
const nextRole = payload?.workspaceRole === 'owner'
|
|
1568
|
+
|| payload?.workspaceRole === 'admin'
|
|
1569
|
+
|| payload?.workspaceRole === 'member'
|
|
1570
|
+
|| payload?.workspaceRole === 'read-only'
|
|
1571
|
+
? payload.workspaceRole
|
|
1572
|
+
: null;
|
|
1573
|
+
const nextMode = payload?.teamPlanMode === 'team' || payload?.teamPlanMode === 'personal'
|
|
1574
|
+
? payload.teamPlanMode
|
|
1575
|
+
: 'unknown';
|
|
1576
|
+
setAccountProfileSummary(payload);
|
|
1577
|
+
setTeamWorkspaceRole(nextRole);
|
|
1578
|
+
setTeamPlanMode(nextMode);
|
|
1579
|
+
setTeamManagementAccessError(null);
|
|
1580
|
+
logBootstrapDebug('account_profile_summary_resolved', {
|
|
1581
|
+
durationMs: Math.round(getTimingNow() - startedAt),
|
|
1582
|
+
planId: String(payload?.planId || '').trim() || null,
|
|
1583
|
+
gate: String(payload?.gate || '').trim() || null,
|
|
1584
|
+
teamManagementAllowed: payload?.teamManagementAllowed === true
|
|
1326
1585
|
});
|
|
1327
|
-
const payload = await res.json().catch(() => ({}));
|
|
1328
|
-
if (!res.ok) {
|
|
1329
|
-
setBillingStatus(null);
|
|
1330
|
-
setBillingError(String(payload?.error || 'Failed to load billing status.'));
|
|
1331
|
-
return null;
|
|
1332
|
-
}
|
|
1333
|
-
setBillingStatus(payload);
|
|
1334
1586
|
return payload;
|
|
1335
1587
|
}
|
|
1336
|
-
catch {
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1588
|
+
catch (error) {
|
|
1589
|
+
const message = error instanceof Error ? error.message : 'Failed to load account summary.';
|
|
1590
|
+
const hasPreviousSummary = Boolean(accountProfileSummaryRef.current);
|
|
1591
|
+
setBillingError(message);
|
|
1592
|
+
if (!hasPreviousSummary) {
|
|
1593
|
+
setTeamWorkspaceRole(null);
|
|
1594
|
+
setTeamPlanMode('unknown');
|
|
1595
|
+
setTeamManagementAccessError(message);
|
|
1596
|
+
}
|
|
1597
|
+
else {
|
|
1598
|
+
setTeamManagementAccessError(null);
|
|
1599
|
+
}
|
|
1600
|
+
logBootstrapDebug('account_profile_summary_failed', {
|
|
1601
|
+
durationMs: Math.round(getTimingNow() - startedAt),
|
|
1602
|
+
error: message,
|
|
1603
|
+
preservedSummary: hasPreviousSummary
|
|
1604
|
+
});
|
|
1605
|
+
return accountProfileSummaryRef.current;
|
|
1340
1606
|
}
|
|
1341
1607
|
finally {
|
|
1342
1608
|
setBillingLoading(false);
|
|
1609
|
+
setTeamManagementAccessLoading(false);
|
|
1610
|
+
markAccountSurfaceLoadSettled('billing');
|
|
1611
|
+
markAccountSurfaceLoadSettled('teamManagement');
|
|
1343
1612
|
}
|
|
1344
|
-
}, [
|
|
1613
|
+
}, [authUserId, canAccessCloudTeamManagement, isAuthenticated, markAccountSurfaceLoadSettled, resolveCloudAuthUrl]);
|
|
1345
1614
|
const handleStartCheckout = useCallback(async () => {
|
|
1346
1615
|
const params = new URLSearchParams();
|
|
1347
1616
|
params.set('screen', 'plans');
|
|
1348
1617
|
params.set('interval', billingIntervalChoice);
|
|
1618
|
+
const planId = String(accountProfileSummary?.planId || '').trim();
|
|
1619
|
+
const planVersionId = String(accountProfileSummary?.planVersionId || '').trim();
|
|
1620
|
+
if (planId)
|
|
1621
|
+
params.set('planId', planId);
|
|
1622
|
+
if (planVersionId)
|
|
1623
|
+
params.set('planVersionId', planVersionId);
|
|
1349
1624
|
navigate(`/?${params.toString()}`);
|
|
1350
|
-
}, [billingIntervalChoice, navigate]);
|
|
1625
|
+
}, [accountProfileSummary?.planId, accountProfileSummary?.planVersionId, billingIntervalChoice, navigate]);
|
|
1351
1626
|
const handleOpenBillingPortal = useCallback(async () => {
|
|
1352
1627
|
setBillingActionError(null);
|
|
1353
1628
|
setBillingNotice(null);
|
|
1354
1629
|
setBillingActionBusy(true);
|
|
1355
1630
|
try {
|
|
1356
|
-
const res = await fetch(
|
|
1631
|
+
const res = await fetch(resolveCloudAuthUrl('/api/taskforce/billing/portal-session'), {
|
|
1357
1632
|
method: 'POST',
|
|
1358
1633
|
credentials: 'include'
|
|
1359
1634
|
});
|
|
@@ -1375,18 +1650,18 @@ export function StandaloneLayout(props) {
|
|
|
1375
1650
|
finally {
|
|
1376
1651
|
setBillingActionBusy(false);
|
|
1377
1652
|
}
|
|
1378
|
-
}, [
|
|
1653
|
+
}, [resolveCloudAuthUrl]);
|
|
1379
1654
|
const handleSwitchSubscriptionInterval = useCallback(async () => {
|
|
1380
1655
|
setBillingActionError(null);
|
|
1381
1656
|
setBillingNotice(null);
|
|
1382
1657
|
setBillingActionBusy(true);
|
|
1383
1658
|
try {
|
|
1384
|
-
const currentPlanVersionId = String(
|
|
1659
|
+
const currentPlanVersionId = String(accountProfileSummary?.planVersionId || '').trim();
|
|
1385
1660
|
if (!currentPlanVersionId) {
|
|
1386
1661
|
setBillingActionError('No active plan version is linked to this account.');
|
|
1387
1662
|
return;
|
|
1388
1663
|
}
|
|
1389
|
-
const res = await fetch(
|
|
1664
|
+
const res = await fetch(resolveCloudAuthUrl('/api/taskforce/billing/subscription'), {
|
|
1390
1665
|
method: 'POST',
|
|
1391
1666
|
credentials: 'include',
|
|
1392
1667
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -1401,7 +1676,8 @@ export function StandaloneLayout(props) {
|
|
|
1401
1676
|
return;
|
|
1402
1677
|
}
|
|
1403
1678
|
setBillingNotice('Subscription updated.');
|
|
1404
|
-
|
|
1679
|
+
invalidateAccountProfileSummaryCache(resolveCloudAuthUrl(ACCOUNT_PROFILE_SUMMARY_PATH), { identityKey: authUserId });
|
|
1680
|
+
await loadAccountProfileSummary();
|
|
1405
1681
|
}
|
|
1406
1682
|
catch {
|
|
1407
1683
|
setBillingActionError('Failed to update subscription interval.');
|
|
@@ -1409,51 +1685,17 @@ export function StandaloneLayout(props) {
|
|
|
1409
1685
|
finally {
|
|
1410
1686
|
setBillingActionBusy(false);
|
|
1411
1687
|
}
|
|
1412
|
-
}, [
|
|
1413
|
-
const loadTeamPlanMode = useCallback(async () => {
|
|
1414
|
-
if (!canManageTeamWorkspace) {
|
|
1415
|
-
setTeamPlanMode('unknown');
|
|
1416
|
-
return 'unknown';
|
|
1417
|
-
}
|
|
1418
|
-
try {
|
|
1419
|
-
const res = await fetch('/api/taskforce/account/entitlements', {
|
|
1420
|
-
method: 'GET',
|
|
1421
|
-
credentials: 'include',
|
|
1422
|
-
headers: teamAdminHeaders()
|
|
1423
|
-
});
|
|
1424
|
-
const payload = await res.json().catch(() => ({}));
|
|
1425
|
-
if (!res.ok) {
|
|
1426
|
-
setTeamPlanMode('unknown');
|
|
1427
|
-
return 'unknown';
|
|
1428
|
-
}
|
|
1429
|
-
const nextMode = resolveTeamPlanModeFromEntitlements(payload);
|
|
1430
|
-
setTeamPlanMode(nextMode);
|
|
1431
|
-
return nextMode;
|
|
1432
|
-
}
|
|
1433
|
-
catch {
|
|
1434
|
-
setTeamPlanMode('unknown');
|
|
1435
|
-
return 'unknown';
|
|
1436
|
-
}
|
|
1437
|
-
}, [canManageTeamWorkspace, teamAdminHeaders]);
|
|
1438
|
-
useEffect(() => {
|
|
1439
|
-
if (!showAccountMenu && !showTeamManagementModal && !showAccountHub)
|
|
1440
|
-
return;
|
|
1441
|
-
void loadTeamPlanMode();
|
|
1442
|
-
}, [showAccountHub, showAccountMenu, showTeamManagementModal, loadTeamPlanMode]);
|
|
1688
|
+
}, [accountProfileSummary?.planVersionId, authUserId, billingIntervalChoice, loadAccountProfileSummary, resolveCloudAuthUrl]);
|
|
1443
1689
|
useEffect(() => {
|
|
1444
1690
|
if (!isAuthenticated || !authSessionResolved)
|
|
1445
1691
|
return;
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
return;
|
|
1449
|
-
}
|
|
1450
|
-
void loadTeamPlanMode();
|
|
1451
|
-
}, [authSessionResolved, canManageTeamWorkspace, isAuthenticated, loadTeamPlanMode]);
|
|
1692
|
+
void loadAccountProfileSummary();
|
|
1693
|
+
}, [authSessionResolved, isAuthenticated, loadAccountProfileSummary]);
|
|
1452
1694
|
useEffect(() => {
|
|
1453
1695
|
if (!showAccountHub && !showAccountMenu)
|
|
1454
1696
|
return;
|
|
1455
|
-
void
|
|
1456
|
-
}, [
|
|
1697
|
+
void loadAccountProfileSummary();
|
|
1698
|
+
}, [loadAccountProfileSummary, showAccountHub, showAccountMenu]);
|
|
1457
1699
|
const loadTeamUsers = useCallback(async () => {
|
|
1458
1700
|
if (!canManageTeamWorkspace)
|
|
1459
1701
|
return;
|
|
@@ -1582,12 +1824,12 @@ export function StandaloneLayout(props) {
|
|
|
1582
1824
|
setShowAccountMenu(false);
|
|
1583
1825
|
setShowTeamManagementModal(true);
|
|
1584
1826
|
setTeamManagementTab('members');
|
|
1585
|
-
const
|
|
1586
|
-
if (
|
|
1827
|
+
const summary = await loadAccountProfileSummary();
|
|
1828
|
+
if (!(summary?.teamManagementAllowed ?? canOpenTeamManagement))
|
|
1587
1829
|
return;
|
|
1588
1830
|
await loadTeamUsers();
|
|
1589
1831
|
await loadTeamAuditPage(0);
|
|
1590
|
-
}, [
|
|
1832
|
+
}, [canOpenTeamManagement, loadAccountProfileSummary, loadTeamAuditPage, loadTeamUsers]);
|
|
1591
1833
|
const executeTeamUserAction = useCallback(async (userId, fn) => {
|
|
1592
1834
|
setTeamMgmtError(null);
|
|
1593
1835
|
setTeamActionBusyUserId(userId);
|
|
@@ -1891,12 +2133,17 @@ export function StandaloneLayout(props) {
|
|
|
1891
2133
|
const activeReferenceMismatchSummaries = useMemo(() => (activeReferenceMismatchEvents.map((event, index) => {
|
|
1892
2134
|
const details = event.details && typeof event.details === 'object' ? event.details : null;
|
|
1893
2135
|
const pathValue = String(details?.path || '').trim();
|
|
2136
|
+
const referenceLabelValue = String(details?.referenceLabel || '').trim();
|
|
2137
|
+
const taskTitleValue = String(details?.taskTitle || details?.existingTaskTitle || '').trim();
|
|
1894
2138
|
const existingRefValue = Number(details?.existingReferenceNumber);
|
|
1895
2139
|
const incomingRefValue = Number(details?.incomingReferenceNumber);
|
|
1896
|
-
const pathLabel = pathValue || `Mismatch ${index + 1}`;
|
|
1897
|
-
const refsLabel = (Number.isFinite(existingRefValue)
|
|
2140
|
+
const pathLabel = pathValue || taskTitleValue || referenceLabelValue || `Mismatch ${index + 1}`;
|
|
2141
|
+
const refsLabel = (Number.isFinite(existingRefValue)
|
|
2142
|
+
|| Number.isFinite(incomingRefValue))
|
|
1898
2143
|
? `Existing ${Number.isFinite(existingRefValue) ? existingRefValue : '?'} vs incoming ${Number.isFinite(incomingRefValue) ? incomingRefValue : '?'}`
|
|
1899
|
-
:
|
|
2144
|
+
: referenceLabelValue
|
|
2145
|
+
? `Both claimed ${referenceLabelValue}`
|
|
2146
|
+
: null;
|
|
1900
2147
|
return {
|
|
1901
2148
|
key: getSyncEventStableKey(event, index),
|
|
1902
2149
|
pathLabel,
|
|
@@ -2252,23 +2499,35 @@ export function StandaloneLayout(props) {
|
|
|
2252
2499
|
}, [groupBy, activeCategories, activeTypes, priorities, approaches, assigneeOptions, scheduleColumns]);
|
|
2253
2500
|
// === SECTION: AccountMenuControl ===
|
|
2254
2501
|
// Not extracted: ~25 distinct props from local state would be needed.
|
|
2255
|
-
// References: isAuthenticated, authIdentityLabel,
|
|
2502
|
+
// References: isAuthenticated, authIdentityLabel, accountProfileSummary, runtimeLabel,
|
|
2256
2503
|
// connectedEnvironmentLabel, canManageCloudWorkspaces, workspaceActionBusy,
|
|
2257
2504
|
// setWorkspacePanelOpen, canOpenTeamManagement, openTeamManagementModal, and more.
|
|
2258
|
-
const accountMenuControl = (_jsxs("div", { className: accountStyles.accountMenuWrap, ref: accountMenuRef, children: [_jsx("button", { className: `tf-control-icon ${accountStyles.avatarBtn}`, onClick: () =>
|
|
2505
|
+
const accountMenuControl = (_jsxs("div", { className: accountStyles.accountMenuWrap, ref: accountMenuRef, children: [_jsx("button", { className: `tf-control-icon ${accountStyles.avatarBtn}`, onClick: () => {
|
|
2506
|
+
setShowAccountMenu((prev) => {
|
|
2507
|
+
const next = !prev;
|
|
2508
|
+
if (next) {
|
|
2509
|
+
startAccountSurfaceTiming('account_menu');
|
|
2510
|
+
}
|
|
2511
|
+
else {
|
|
2512
|
+
accountSurfaceOpenStartedAtRef.current = null;
|
|
2513
|
+
accountSurfacePendingRef.current = { billing: false, teamManagement: false };
|
|
2514
|
+
}
|
|
2515
|
+
return next;
|
|
2516
|
+
});
|
|
2517
|
+
}, title: isAuthenticated ? 'Account' : 'Account (Not signed in)', "aria-label": isAuthenticated ? 'Account' : 'Account (Not signed in)', "aria-haspopup": "menu", "aria-expanded": showAccountMenu, children: _jsx("span", { className: accountStyles.avatarBadge, "aria-hidden": "true", children: shouldShowResolvedAccountAvatar ? (_jsx("img", { src: accountAvatarUrl, alt: "", className: accountStyles.avatarImage })) : accountBadgeInitial ? (accountBadgeInitial) : (_jsx(User, { size: 14, className: accountStyles.avatarIcon })) }) }), showAccountMenu && (_jsxs("div", { className: accountStyles.accountMenu, role: "menu", "aria-label": "Account menu", children: [_jsxs("div", { className: accountStyles.accountMenuSection, children: [_jsx("div", { className: accountStyles.accountMenuSectionLabel, children: "Account" }), _jsx("div", { className: accountStyles.accountMenuHint, children: isAuthBootstrapPending
|
|
2259
2518
|
? 'Checking sign-in status...'
|
|
2260
2519
|
: isAuthenticated
|
|
2261
2520
|
? (_jsx(_Fragment, { children: hasAuthIdentity
|
|
2262
|
-
? (_jsx(_Fragment, { children: _jsxs("span", { className: accountStyles.accountIdentityBlock, children: [_jsx("span", { className: accountStyles.accountIdentityEmail, children: authIdentityLabel }), hasAuthEmail && authDisplayName && (_jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Email" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: authEmailLabel })] })), hasSubscription
|
|
2521
|
+
? (_jsx(_Fragment, { children: _jsxs("span", { className: accountStyles.accountIdentityBlock, children: [_jsx("span", { className: accountStyles.accountIdentityEmail, children: authIdentityLabel }), hasAuthEmail && authDisplayName && (_jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Email" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: authEmailLabel })] })), billingLoading ? (_jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Subscription" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: "Loading\u2026" })] })) : hasSubscription ? (_jsxs("button", { "aria-label": `Subscription ${subscriptionLabel}`, className: `${accountStyles.accountIdentityMetaLine} ${accountStyles.accountIdentityMetaAction}`, onClick: () => {
|
|
2263
2522
|
setShowAccountMenu(false);
|
|
2264
2523
|
openPlansScreen();
|
|
2265
|
-
}, type: "button", children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Subscription" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: subscriptionLabel })] })), _jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Runtime" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: runtimeLabel })] }), _jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Environment" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: connectedEnvironmentLabel })] })] }) }))
|
|
2524
|
+
}, type: "button", children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Subscription" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: subscriptionLabel })] })) : billingError ? (_jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Subscription" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: "Unavailable" })] })) : null, _jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Runtime" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: runtimeLabel })] }), _jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Environment" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: connectedEnvironmentLabel })] })] }) }))
|
|
2266
2525
|
: 'Signed in' }))
|
|
2267
2526
|
: 'Not signed in' })] }), canManageCloudWorkspaces && (_jsxs("div", { className: accountStyles.accountMenuSection, children: [_jsx("div", { className: accountStyles.accountMenuSectionLabel, children: "Workspaces (Owned + Invited)" }), availableWorkspaces.length === 0 && (_jsx("div", { className: accountStyles.accountMenuHint, children: "No workspaces found for this account yet." })), availableWorkspaces.map((workspace) => (_jsxs("button", { className: `${accountStyles.accountMenuItem} ${workspace.id === currentWorkspaceId ? accountStyles.accountMenuItemActive : ''}`, onClick: () => handleSwitchWorkspace(workspace.id), role: "menuitem", disabled: workspaceActionBusy || workspace.id === currentWorkspaceId, title: workspace.description || workspace.name, children: [_jsx(Folder, { size: 15 }), _jsxs("span", { style: { display: 'flex', flexDirection: 'column', gap: '2px' }, children: [_jsx("span", { children: workspace.name }), _jsx("span", { className: accountStyles.accountMenuMeta, children: workspace.role })] })] }, workspace.id))), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
|
|
2268
2527
|
setWorkspaceActionError('');
|
|
2269
2528
|
setShowCreateWorkspaceConfirm(true);
|
|
2270
2529
|
setShowAccountMenu(false);
|
|
2271
|
-
}, role: "menuitem", disabled: workspaceActionBusy, children: [_jsx(Plus, { size: 15 }), "Create Workspace"] }), workspaceActionError && (_jsx("div", { className: accountStyles.accountMenuError, children: workspaceActionError }))] })), canOpenTeamManagement
|
|
2530
|
+
}, role: "menuitem", disabled: workspaceActionBusy, children: [_jsx(Plus, { size: 15 }), "Create Workspace"] }), workspaceActionError && (_jsx("div", { className: accountStyles.accountMenuError, children: workspaceActionError }))] })), teamManagementAccessLoading ? (_jsx("div", { className: accountStyles.accountMenuHint, children: "Resolving team management access..." })) : canOpenTeamManagement ? (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => { void openTeamManagementModal(); }, role: "menuitem", children: [_jsx(Users, { size: 15 }), "Team Management"] })) : teamManagementAccessError ? (_jsx("div", { className: accountStyles.accountMenuHint, children: "Team Management unavailable right now." })) : null, isAuthenticated && (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
|
|
2272
2531
|
setShowAccountMenu(false);
|
|
2273
2532
|
setShowEditProfileModal(true);
|
|
2274
2533
|
}, role: "menuitem", children: [_jsx(User, { size: 15 }), "Edit Profile"] })), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
|
|
@@ -2276,6 +2535,7 @@ export function StandaloneLayout(props) {
|
|
|
2276
2535
|
openPlansScreen();
|
|
2277
2536
|
}, role: "menuitem", children: [_jsx(Layers, { size: 15 }), "Plans"] }), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
|
|
2278
2537
|
setShowAccountMenu(false);
|
|
2538
|
+
startAccountSurfaceTiming('account_hub');
|
|
2279
2539
|
setShowAccountHub(true);
|
|
2280
2540
|
}, role: "menuitem", children: [_jsx(User, { size: 15 }), "Account Hub"] }), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
|
|
2281
2541
|
setShowAccountMenu(false);
|
|
@@ -2908,7 +3168,7 @@ export function StandaloneLayout(props) {
|
|
|
2908
3168
|
const workspacePrimaryAction = workspaceChromeDefinition.primaryAction
|
|
2909
3169
|
? primaryActionRegistry[workspaceChromeDefinition.primaryAction]
|
|
2910
3170
|
: null;
|
|
2911
|
-
return (_jsxs("div", { className: `${shellStyles.standaloneWrapper} ${zenMode ? styles.zenModeEnabled : ''}`, "data-theme": currentTheme, children: [_jsx(AppShellHeader, { projectName: projectName, currentWorkspaceId: currentWorkspaceId, runtimeMode: runtimeMode, theme: currentTheme, meta: isPlansScreen ? null : (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.taskCountBadge, title: activeCountTitle, children: activeCountLabel }), shouldShowSyncStatus && (_jsx("button", { className: "tf-control-icon", onClick: () => setShowSyncStatusModal(true), title: `Sync manager: ${syncStatusMeta.label} | Last success: ${formattedLastSyncTime}`, style: {
|
|
3171
|
+
return (_jsxs("div", { className: `${shellStyles.standaloneWrapper} ${zenMode ? styles.zenModeEnabled : ''}`, "data-theme": currentTheme, children: [_jsx(AppShellHeader, { projectName: projectName, currentWorkspaceId: currentWorkspaceId, runtimeMode: runtimeMode, theme: currentTheme, meta: isPlansScreen ? null : (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.taskCountBadge, title: activeCountTitle, children: activeCountLabel }), normalizedAuthUserId && normalizedAuthUserId !== 'anonymous' && (_jsxs(_Fragment, { children: [_jsxs("span", { className: styles.taskCountBadge, title: "Tasks assigned to me", children: [_jsx(User, { size: 13, className: styles.taskCountBadgeIcon, "aria-hidden": "true" }), assignedTaskCount] }), _jsxs("span", { className: `${styles.taskCountBadge} ${assignedOverdueTaskCount > 0 ? styles.taskCountBadgeAlert : ''}`.trim(), title: "Tasks overdue", children: [_jsx(ClockAlert, { size: 13, className: styles.taskCountBadgeIcon, "aria-hidden": "true" }), assignedOverdueTaskCount] })] })), shouldShowSyncStatus && (_jsx("button", { className: "tf-control-icon", onClick: () => setShowSyncStatusModal(true), title: `Sync manager: ${syncStatusMeta.label} | Last success: ${formattedLastSyncTime}`, style: {
|
|
2912
3172
|
marginLeft: '6px',
|
|
2913
3173
|
height: '24px',
|
|
2914
3174
|
width: '24px',
|
|
@@ -2920,7 +3180,7 @@ export function StandaloneLayout(props) {
|
|
|
2920
3180
|
display: 'inline-flex',
|
|
2921
3181
|
alignItems: 'center',
|
|
2922
3182
|
justifyContent: 'center'
|
|
2923
|
-
}, 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: "Back", children: "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, apiBaseUrl:
|
|
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: "Back", children: "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 }) }) })) : 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: () => {
|
|
2924
3184
|
setRequestedDocPath(null);
|
|
2925
3185
|
setRequestedDocAssetId(null);
|
|
2926
3186
|
}, 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: {
|
|
@@ -2939,7 +3199,7 @@ export function StandaloneLayout(props) {
|
|
|
2939
3199
|
inset: 0,
|
|
2940
3200
|
zIndex: 35,
|
|
2941
3201
|
pointerEvents: 'none',
|
|
2942
|
-
} }), _jsxs("div", { style: { position: 'relative', width: '100%', minWidth: 0, flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [props.loadingTasks && boardVisibleTasks.length > 0 && (_jsx("div", { className: styles.boardRefreshIndicator, "aria-live": "polite", "aria-label": "Refreshing tasks", children: _jsx(Loader2, { size: 14, className: styles.spinner }) })), _jsx(TaskKanban, { tasks: scopedBoardVisibleTasks, allTasks:
|
|
3202
|
+
} }), _jsxs("div", { style: { position: 'relative', width: '100%', minWidth: 0, flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [props.loadingTasks && boardVisibleTasks.length > 0 && (_jsx("div", { className: styles.boardRefreshIndicator, "aria-live": "polite", "aria-label": "Refreshing tasks", children: _jsx(Loader2, { size: 14, className: styles.spinner }) })), _jsx(TaskKanban, { tasks: scopedBoardVisibleTasks, allTasks: scopedKanbanAllTasks, columns: kanbanColumns, groupBy: groupBy, scheduleDates: scheduleDates, searchQuery: searchQuery, filterCategories: filterCategories, filterTypes: filterTypes, filterPriorities: filterPriorities, filterStatus: filterStatus, filterAssignees: filterAssignees, assigneeOptions: assigneeOptions, scheduleFilteredTaskIds: Array.from(scheduleFilteredTaskIds), copiedId: copiedId, taxonomies: taxonomies, types: activeTypes, priorities: priorities, onUpdateTask: handleUpdateTask, onTaskClick: (task) => handleEdit(task), onOpenTaskById: handleOpenTaskById, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onSetStatus: handleSetStatus, onArchiveTask: handleArchiveTask, onAddTaskToColumn: handleAddTaskToColumn, showTaskCardStatusLabel: showTaskCardStatusLabel, onScheduleDaySelected: (dateOnly) => {
|
|
2943
3203
|
setScheduleSelectedDate(dateOnly);
|
|
2944
3204
|
const parsed = parseDateOnlyLocal(dateOnly);
|
|
2945
3205
|
if (parsed) {
|
|
@@ -3041,114 +3301,71 @@ export function StandaloneLayout(props) {
|
|
|
3041
3301
|
return;
|
|
3042
3302
|
}
|
|
3043
3303
|
void handleUnarchive(taskId);
|
|
3044
|
-
}, currentTask: currentTask })] }), _jsx(Modal, { isOpen: activeTab === 'settings', onClose: handleCloseModal, title: "Settings", size: "xl", theme: currentTheme, draggable: true, isSettings: true, closeOnOverlayClick: false, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '2rem' }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(TaskSettings, { settingsModel: settingsModel, onSectionChange: setSettingsSection }) }) }), _jsx(
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
}));
|
|
3110
|
-
}, children: "Revoke Invite" })), _jsx("button", { className: styles.cancelBtn, disabled: teamActionBusyUserId === member.userId, onClick: () => {
|
|
3111
|
-
void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}?workspaceId=${encodeURIComponent(teamManagementWorkspaceId)}`, {
|
|
3112
|
-
method: 'DELETE',
|
|
3113
|
-
headers: teamAdminHeaders(),
|
|
3114
|
-
credentials: 'include'
|
|
3115
|
-
}));
|
|
3116
|
-
}, children: "Remove" })] })] }, member.userId)))] })), teamManagementTab === 'invites' && (_jsxs("div", { className: accountStyles.accountHubCard, children: [teamInviteFeedback && (_jsx("p", { className: managementStyles.sectionText, children: teamInviteFeedback })), _jsxs("div", { className: styles.pathInputGroup, children: [_jsx("label", { className: `${styles.label} ${managementStyles.labelFixed}`, children: "Email" }), _jsx("input", { className: styles.input, value: teamInviteEmail, onChange: (event) => setTeamInviteEmail(event.target.value), placeholder: "name@company.com" })] }), _jsxs("div", { className: styles.pathInputGroup, children: [_jsx("label", { className: `${styles.label} ${managementStyles.labelFixed}`, children: "Role" }), _jsxs("select", { className: `${styles.input} ${managementStyles.selectRole}`, value: teamInviteRole, onChange: (event) => setTeamInviteRole(event.target.value === 'admin' ? 'admin' : 'member'), children: [_jsx("option", { value: "member", children: "Member" }), _jsx("option", { value: "admin", children: "Admin" })] }), teamInviteRole === 'member' && (_jsxs("select", { className: `${styles.input} ${managementStyles.selectPermission}`, value: teamInvitePermissionMode, onChange: (event) => setTeamInvitePermissionMode(event.target.value === 'read-only' ? 'read-only' : 'read-write'), children: [_jsx("option", { value: "read-write", children: "Read / Write" }), _jsx("option", { value: "read-only", children: "Read Only" })] })), _jsx("button", { className: styles.submitBtn, disabled: teamInviteBusy || !teamInviteEmail.trim() || teamPlanMode !== 'team', onClick: () => { void submitTeamInvite(); }, children: teamInviteBusy ? 'Sending...' : 'Send Invite' })] }), _jsxs("div", { className: managementStyles.inviteRow, children: [_jsx("div", { className: managementStyles.listHeading, children: "Pending Invites" }), pendingInvites.length === 0 && (_jsx("p", { className: managementStyles.mutedText, children: "No pending invites." })), pendingInvites.map((invite) => (_jsxs("div", { className: managementStyles.memberRow, children: [_jsx("div", { className: managementStyles.memberTitle, children: invite.displayName || invite.email }), _jsxs("div", { className: accountStyles.accountMenuMeta, children: [invite.email, " \u00B7 ", invite.role] })] }, invite.userId)))] })] })), teamManagementTab === 'audit' && (_jsxs("div", { className: accountStyles.accountHubCard, children: [teamAuditLoading && _jsx("p", { className: managementStyles.mutedText, children: "Loading audit entries..." }), !teamAuditLoading && teamAuditEvents.length === 0 && (_jsx("p", { className: managementStyles.mutedText, children: "No audit events for this workspace." })), !teamAuditLoading && (_jsx("div", { className: managementStyles.auditList, children: teamAuditEvents.map((event) => (_jsxs("div", { className: managementStyles.memberRow, children: [_jsx("div", { className: accountStyles.accountMenuMeta, children: new Date(event.createdAt).toLocaleString() }), _jsx("div", { className: managementStyles.memberTitle, children: event.action }), _jsxs("div", { className: accountStyles.accountMenuMeta, children: [event.actorUserId, " (", event.actorRole, ")"] })] }, event.id))) })), _jsxs("div", { className: managementStyles.auditPager, children: [_jsx("button", { className: styles.cancelBtn, disabled: teamAuditLoading || teamAuditPage === 0, onClick: () => { void loadTeamAuditPage(teamAuditPage - 1); }, children: "Previous" }), _jsxs("span", { className: accountStyles.accountMenuMeta, children: ["Page ", teamAuditPage + 1, " of ", teamAuditPages] }), _jsx("button", { className: styles.cancelBtn, disabled: teamAuditLoading || !teamAuditHasMore || teamAuditPage + 1 >= teamAuditPages, onClick: () => { void loadTeamAuditPage(teamAuditPage + 1); }, children: "Next" })] })] })), _jsx("div", { className: `${modalStyles.formActions} ${managementStyles.modalActionsEnd}`, children: _jsx("button", { className: styles.cancelBtn, onClick: () => setShowTeamManagementModal(false), children: "Close" }) })] }) }), _jsx(Modal, { isOpen: showCreateWorkspaceConfirm, onClose: () => setShowCreateWorkspaceConfirm(false), title: "Create Workspace", size: "sm", theme: currentTheme, draggable: true, footer: (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: styles.secondaryButton, onClick: () => setShowCreateWorkspaceConfirm(false), children: "Cancel" }), _jsx("button", { type: "button", className: styles.primaryUpdateBtn, onClick: () => openWorkspaceSetup({ intent: 'create-workspace' }), children: "Continue to Setup" })] })), children: _jsx("div", { className: `${modalStyles.form} ${managementStyles.modalBody}`, children: _jsx("p", { className: managementStyles.sectionText, children: "Start a new workspace with the guided setup flow. You will choose the workspace name, description, and starter taxonomy library before anything is created." }) }) }), _jsx(Modal, { isOpen: showSyncStatusModal, onClose: () => {
|
|
3117
|
-
setShowSyncStatusModal(false);
|
|
3118
|
-
}, title: "Sync Manager", size: "md", theme: currentTheme, 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: {
|
|
3119
|
-
borderColor: syncStatusMeta.border,
|
|
3120
|
-
background: syncStatusMeta.background,
|
|
3121
|
-
color: syncStatusMeta.color
|
|
3122
|
-
}, children: syncStatusMeta.label }), _jsxs("span", { className: syncStatusStyles.syncStatusLabel, children: ["Workspace: ", _jsx("code", { children: currentWorkspaceLabel })] })] }) }), _jsx("label", { className: syncStatusStyles.syncToggle, children: _jsxs("span", { className: [
|
|
3123
|
-
syncStatusStyles.syncToggleControl,
|
|
3124
|
-
workspaceCloudSyncEnabled ? syncStatusStyles.syncToggleControlEnabled : syncStatusStyles.syncToggleControlDisabled,
|
|
3125
|
-
syncControlBusy ? syncStatusStyles.syncToggleControlBusy : '',
|
|
3126
|
-
!canManageWorkspaceSync ? syncStatusStyles.syncToggleControlBlocked : ''
|
|
3127
|
-
].filter(Boolean).join(' '), children: [_jsx("input", { className: syncStatusStyles.syncToggleInput, type: "checkbox", role: "switch", "aria-label": "Enable Sync", checked: workspaceCloudSyncEnabled, disabled: !canManageWorkspaceSync || syncControlBusy, onChange: (event) => {
|
|
3128
|
-
void handleWorkspaceSyncToggle(event.target.checked);
|
|
3129
|
-
} }), _jsx("span", { className: `${syncStatusStyles.syncToggleState} ${syncStatusStyles.syncToggleStateOn}`, children: "On" }), _jsx("span", { className: `${syncStatusStyles.syncToggleState} ${syncStatusStyles.syncToggleStateOff}`, children: "Off" }), _jsx("span", { className: syncStatusStyles.syncToggleThumb })] }) })] }), _jsxs("div", { className: syncStatusStyles.syncStatusSummaryCompact, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Health" }), _jsx("span", { className: syncStatusStyles.syncStatusSummaryValue, children: workspaceSyncSummary })] }), workspaceSyncRepairBusy && (_jsxs("div", { className: syncStatusStyles.syncStatusRepairBanner, children: [_jsxs("div", { className: syncStatusStyles.syncStatusRepairHeader, children: [_jsxs("div", { className: syncStatusStyles.syncStatusRepairBadge, children: [_jsx(Loader2, { size: 13, className: styles.spinner }), _jsx("span", { children: "Repair In Progress" })] }), _jsx("span", { className: syncStatusStyles.syncStatusRepairMeta, children: "Advanced recovery mode" })] }), _jsx("span", { className: syncStatusStyles.syncStatusRepairText, children: referenceMismatchCount > 0
|
|
3130
|
-
? `Taskforce is clearing the saved sync cursor and reconciling workspace state from the cloud again, including ${referenceMismatchCount} detected reference mismatch${referenceMismatchCount === 1 ? '' : 'es'}. The panel may stay busy for a while during large repairs.`
|
|
3131
|
-
: 'Taskforce is clearing the saved sync cursor and reconciling workspace state from the cloud again. The panel may stay busy for a while during large repairs.' })] })), _jsx("div", { className: syncStatusStyles.syncStatusSectionHeader, children: _jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Current Session" }) }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactGrid, children: [_jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Stage" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: syncStageLabel })] }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Local Changes" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: workspaceSyncPendingChanges })] }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Last Successful" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: formattedLastSyncTime })] })] }), _jsx("div", { className: syncStatusStyles.syncStatusSectionHeader, children: _jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Activity" }) }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactGrid, children: [_jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Last Pull" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: formattedLastPullTime })] }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Last Push" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: formattedLastPushTime })] })] }), syncLastError !== 'None' && (_jsxs("div", { className: syncStatusStyles.syncStatusPanel, style: { background: 'rgba(239, 68, 68, 0.05)', borderColor: 'rgba(239, 68, 68, 0.2)' }, children: [_jsxs("div", { className: syncStatusStyles.syncStatusSectionHeader, style: { marginTop: 0 }, children: [_jsx(AlertTriangle, { size: 12, color: "#fda4af" }), _jsxs("span", { className: syncStatusStyles.syncStatusLabel, style: { color: '#fda4af' }, children: ["Last Error (", workspaceSyncDiagnostics.lastErrorAt ? new Date(workspaceSyncDiagnostics.lastErrorAt).toLocaleTimeString() : 'Recent', ")"] })] }), _jsx("span", { className: `${syncStatusStyles.syncStatusValue} ${syncStatusStyles.syncStatusValueError}`, children: syncLastError })] })), referenceMismatchCount > 0 && (_jsxs("div", { className: syncStatusStyles.syncStatusPanel, style: { background: 'rgba(250, 204, 21, 0.08)', borderColor: 'rgba(250, 204, 21, 0.22)' }, children: [_jsxs("div", { className: syncStatusStyles.syncStatusPanelHeader, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, style: { color: '#fde68a' }, children: "Identifier Integrity" }), _jsxs("span", { className: syncStatusStyles.syncStatusPanelMeta, children: [referenceMismatchCount, " mismatch", referenceMismatchCount === 1 ? '' : 'es', " detected"] })] }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: "Document or image reference numbers disagreed during normal sync. Standard sync now preserves existing identifiers; use Repair to reconcile mismatched references deliberately." }), activeReferenceMismatchSummaries.length > 0 && (_jsx("div", { style: { marginTop: '10px', display: 'flex', flexDirection: 'column', gap: '8px' }, children: activeReferenceMismatchSummaries.map((item) => (_jsxs("div", { style: {
|
|
3132
|
-
display: 'flex',
|
|
3133
|
-
flexDirection: 'column',
|
|
3134
|
-
gap: '2px',
|
|
3135
|
-
padding: '8px 10px',
|
|
3136
|
-
borderRadius: '8px',
|
|
3137
|
-
background: 'rgba(15, 23, 42, 0.18)',
|
|
3138
|
-
border: '1px solid rgba(250, 204, 21, 0.16)'
|
|
3139
|
-
}, children: [_jsx("span", { className: syncStatusStyles.syncStatusValue, style: { fontSize: '12px', wordBreak: 'break-all' }, children: item.pathLabel }), item.refsLabel ? (_jsx("span", { className: syncStatusStyles.syncStatusPanelMeta, children: item.refsLabel })) : null] }, item.key))) }))] })), _jsxs("div", { className: syncStatusStyles.syncStatusPanel, children: [_jsxs("div", { className: syncStatusStyles.syncStatusPanelHeader, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Local Sync Diagnostics" }), _jsx("span", { className: syncStatusStyles.syncStatusPanelMeta, style: { fontSize: '10px' }, children: "AI profiles & metadata" })] }), _jsx("div", { className: syncStatusStyles.syncStatusCompactGrid, style: { gridTemplateColumns: 'repeat(2, 1fr)' }, children: syncDiagnosticsSummary.map((item) => (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: '8px' }, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, style: { fontSize: '9px', textTransform: 'capitalize' }, children: item.label }), _jsx("span", { className: syncStatusStyles.syncStatusValue, style: { fontSize: '11px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, children: item.value })] }, item.label))) })] }), _jsxs("div", { className: syncStatusStyles.syncStatusPanel, children: [_jsx("div", { className: syncStatusStyles.syncStatusPanelHeader, children: _jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Recent Events" }) }), _jsx("div", { ref: syncEventsListRef, className: syncStatusStyles.syncStatusEventsList, style: { height: '120px' }, children: syncEventRows.map((row) => (_jsx("div", { className: [
|
|
3140
|
-
syncStatusStyles.syncStatusEventItem,
|
|
3141
|
-
row.tone === 'error' ? syncStatusStyles.syncStatusEventItemError : '',
|
|
3142
|
-
row.tone === 'muted' ? syncStatusStyles.syncStatusEventItemMuted : ''
|
|
3143
|
-
].filter(Boolean).join(' '), style: { padding: '6px 8px', borderRadius: '6px' }, children: _jsx("span", { className: syncStatusStyles.syncStatusEventLine, style: { fontSize: '11px' }, children: row.text }) }, row.key))) })] }), _jsxs("div", { className: `${modalStyles.formActions} ${syncStatusStyles.syncStatusActions}`, children: [_jsxs("div", { className: syncStatusStyles.syncStatusSecondaryActions, children: [_jsx("button", { className: styles.cancelBtn, onClick: handleQueueOrRunRepairSync, disabled: workspaceSyncRepairBusy, title: workspaceSyncRepairBusy
|
|
3144
|
-
? 'Repair is running now.'
|
|
3145
|
-
: workspaceSyncRepairQueued
|
|
3146
|
-
? 'Repair is queued and will start when the current sync finishes.'
|
|
3147
|
-
: workspaceSyncBusy
|
|
3148
|
-
? 'Queue a repair to start automatically when the current sync finishes.'
|
|
3149
|
-
: 'Advanced recovery: clear the saved pull cursor and re-fetch cloud state from the beginning.', children: workspaceSyncRepairBusy ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { size: 14, className: styles.spinner }), _jsx("span", { style: { marginLeft: '6px' }, children: "Repairing..." })] })) : workspaceSyncRepairQueued ? ('Repair Queued') : ('Repair') }), _jsxs("button", { className: styles.cancelBtn, onClick: () => { void handleCopySyncDetails(); }, title: "Copy the current sync manager report for support or AI troubleshooting.", children: [_jsx(Copy, { size: 14 }), _jsx("span", { style: { marginLeft: '6px' }, children: workspaceSyncCopied ? 'Copied' : 'Copy Report' })] })] }), _jsxs("div", { className: syncStatusStyles.syncStatusPrimaryActions, children: [_jsx("div", { className: syncStatusStyles.syncStatusPrimaryActionSlot, children: runtimeMode === 'local' && !isAuthenticated ? (_jsx("button", { className: styles.submitBtn, onClick: () => {
|
|
3150
|
-
openSharedLoginRoute('login');
|
|
3151
|
-
}, children: "Sign In / Register" })) : (_jsx("div", { className: syncStatusStyles.syncStatusActionPlaceholder, "aria-hidden": "true" })) }), _jsx("div", { className: syncStatusStyles.syncStatusPrimaryActionSlot, children: _jsx("button", { className: styles.submitBtn, onClick: () => void retryWorkspaceCloudSync(), disabled: workspaceSyncBusy, children: workspaceSyncBusy ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { size: 14, className: styles.spinner }), "Syncing..."] })) : (_jsxs(_Fragment, { children: [_jsx(RefreshCw, { size: 14 }), "Sync"] })) }) })] })] })] }) }), unsavedModalOpen && createPortal(_jsx("div", { className: `${modalStyles.overlay} ${modalStyles.unsavedOverlay}`, style: { zIndex: 2000 }, children: _jsxs("div", { className: `tf-surface-modal tf-modal-shell ${modalStyles.modal} ${modalStyles.unsavedModal}`, "data-theme": currentTheme, children: [_jsx("div", { className: `tf-modal-header ${modalStyles.unsavedHeader}`, children: _jsxs("div", { className: `tf-modal-title ${modalStyles.unsavedTitle}`, children: [_jsx(AlertTriangle, { size: 20 }), "Unsaved Changes"] }) }), _jsx("div", { className: `${modalStyles.form} ${modalStyles.unsavedContent}`, children: _jsx("p", { className: modalStyles.unsavedText, children: "You have unsaved changes. Would you like to save them?" }) }), _jsxs("div", { className: `${modalStyles.formActions} ${modalStyles.unsavedActions}`, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => setUnsavedModalOpen(false), title: "Close dialog and continue editing", children: "Keep Editing" }), _jsx("button", { className: styles.destructiveBtn, onClick: handleDiscard, title: "Discard unsaved changes and leave", children: "Discard" }), _jsxs("button", { className: styles.submitBtn, onClick: handleSaveAndNavigate, disabled: loading, title: "Save changes and leave", children: [loading ? _jsx(Loader2, { size: 16, className: styles.spinner }) : _jsx(Save, { size: 16 }), "Save"] })] })] }) }), document.body), showFolderBrowser && createPortal(_jsx("div", { className: folderBrowserStyles.overlay, children: _jsxs("div", { className: folderBrowserStyles.browser, children: [_jsxs("div", { className: folderBrowserStyles.header, children: [_jsxs("div", { className: folderBrowserStyles.pathInfo, children: [_jsx(Folder, { size: 14 }), _jsx("span", { children: currentBrowsePath || 'Project Root' })] }), _jsxs("div", { className: folderBrowserStyles.actions, children: [currentBrowsePath && (_jsx("button", { className: styles.helpLink, onClick: () => {
|
|
3304
|
+
}, currentTask: currentTask })] }), _jsx(Modal, { isOpen: activeTab === 'settings', onClose: handleCloseModal, title: "Settings", size: "xl", theme: currentTheme, draggable: true, isSettings: true, closeOnOverlayClick: false, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '2rem' }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(TaskSettings, { settingsModel: settingsModel, onSectionChange: setSettingsSection }) }) }), _jsx(ScheduleWarningModal, { prompt: scheduleWarningPrompt, theme: currentTheme, onClose: cancelScheduleWarning, onConfirm: confirmScheduleWarning }), _jsx(HelpModal, { isOpen: showHelpModal, theme: currentTheme, onClose: () => setShowHelpModal(false), onOpenSettings: () => {
|
|
3305
|
+
setShowHelpModal(false);
|
|
3306
|
+
setActiveTab('settings');
|
|
3307
|
+
setSettingsSection('general');
|
|
3308
|
+
} }), _jsx(EditProfileModal, { isOpen: showEditProfileModal, theme: currentTheme, onClose: closeEditProfileModal, onSave: () => { void handleSaveProfile(); }, displayName: profileDisplayNameDraft, email: authEmailLabel, avatarDisplayUrl: profileAvatarDisplayUrl, accountBadgeInitial: accountBadgeInitial, avatarInputRef: profileAvatarInputRef, avatarAccept: PROFILE_AVATAR_ACCEPT, avatarDraftId: profileAvatarDraftId, saveBusy: profileSaveBusy, avatarBusy: profileAvatarBusy, saveError: profileSaveError, saveNotice: profileSaveNotice, onDisplayNameChange: setProfileDisplayNameDraft, onAvatarInputChange: (file) => { void handleProfileAvatarSelected(file); }, onStartAvatarUpload: () => profileAvatarInputRef.current?.click(), onDiscardUpload: handleDiscardPendingProfileAvatar, onRemovePhoto: handleRemoveProfileAvatar }), _jsx(AccountHubModal, { isOpen: showAccountHub, theme: currentTheme, runtimeMode: runtimeMode, authRequiredForApi: authRequiredForApi, isAuthenticated: isAuthenticated, hasAuthIdentity: hasAuthIdentity, authIdentityLabel: authIdentityLabel, billingLoading: billingLoading, billingError: billingError, billingActionError: billingActionError, billingNotice: billingNotice, billingActionBusy: billingActionBusy, billingIntervalChoice: billingIntervalChoice, accountProfileSummary: accountProfileSummary, currentWorkspaceId: currentWorkspaceId, canOpenTeamManagement: canOpenTeamManagement, canManageWorkspaceSync: canManageWorkspaceSync, workspaceCloudSyncEnabled: workspaceCloudSyncEnabled, syncStatusLabel: syncStatusMeta.label, workspaceSyncError: workspaceSyncError, syncControlBusy: syncControlBusy, onClose: () => setShowAccountHub(false), onOpenWorkspaceAudit: () => {
|
|
3309
|
+
setShowAccountHub(false);
|
|
3310
|
+
void openTeamManagementModal();
|
|
3311
|
+
setTeamManagementTab('audit');
|
|
3312
|
+
}, onBillingIntervalChange: setBillingIntervalChoice, onRefreshBilling: () => { void loadAccountProfileSummary(); }, onUpdateInterval: () => { void handleSwitchSubscriptionInterval(); }, onManageBilling: () => { void handleOpenBillingPortal(); }, onStartCheckout: () => { void handleStartCheckout(); }, onToggleWorkspaceSync: (enabled) => { void handleWorkspaceSyncToggle(enabled); }, onOpenHelp: () => {
|
|
3313
|
+
setShowAccountHub(false);
|
|
3314
|
+
setShowHelpModal(true);
|
|
3315
|
+
} }), _jsx(TeamManagementModal, { isOpen: showTeamManagementModal, theme: currentTheme, teamPlanMode: teamPlanMode, teamMgmtError: teamMgmtError, teamManagementTab: teamManagementTab, teamUsersLoading: teamUsersLoading, teamUsers: teamUsers, teamActionBusyUserId: teamActionBusyUserId, teamInviteFeedback: teamInviteFeedback, teamInviteEmail: teamInviteEmail, teamInviteRole: teamInviteRole, teamInvitePermissionMode: teamInvitePermissionMode, teamInviteBusy: teamInviteBusy, pendingInvites: pendingInvites, teamAuditLoading: teamAuditLoading, teamAuditEvents: teamAuditEvents, teamAuditPage: teamAuditPage, teamAuditPages: teamAuditPages, teamAuditHasMore: teamAuditHasMore, onClose: () => setShowTeamManagementModal(false), onOpenMembersTab: () => {
|
|
3316
|
+
setTeamManagementTab('members');
|
|
3317
|
+
void loadTeamUsers();
|
|
3318
|
+
}, onOpenInvitesTab: () => setTeamManagementTab('invites'), onOpenAuditTab: () => {
|
|
3319
|
+
setTeamManagementTab('audit');
|
|
3320
|
+
void loadTeamAuditPage(teamAuditPage);
|
|
3321
|
+
}, onMemberRoleChange: (userId, role) => {
|
|
3322
|
+
void executeTeamUserAction(userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(userId)}/role`, {
|
|
3323
|
+
method: 'PATCH',
|
|
3324
|
+
headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
|
|
3325
|
+
credentials: 'include',
|
|
3326
|
+
body: JSON.stringify({
|
|
3327
|
+
workspaceId: teamManagementWorkspaceId,
|
|
3328
|
+
role
|
|
3329
|
+
})
|
|
3330
|
+
}));
|
|
3331
|
+
}, onMemberPermissionChange: (userId, mode) => {
|
|
3332
|
+
void executeTeamUserAction(userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(userId)}/permission-mode`, {
|
|
3333
|
+
method: 'PATCH',
|
|
3334
|
+
headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
|
|
3335
|
+
credentials: 'include',
|
|
3336
|
+
body: JSON.stringify({
|
|
3337
|
+
workspaceId: teamManagementWorkspaceId,
|
|
3338
|
+
mode
|
|
3339
|
+
})
|
|
3340
|
+
}));
|
|
3341
|
+
}, onToggleMemberDisabled: (userId, disabled) => {
|
|
3342
|
+
void executeTeamUserAction(userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(userId)}/disable`, {
|
|
3343
|
+
method: 'PATCH',
|
|
3344
|
+
headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
|
|
3345
|
+
credentials: 'include',
|
|
3346
|
+
body: JSON.stringify({
|
|
3347
|
+
workspaceId: teamManagementWorkspaceId,
|
|
3348
|
+
disabled
|
|
3349
|
+
})
|
|
3350
|
+
}));
|
|
3351
|
+
}, onRevokeInvite: (userId) => {
|
|
3352
|
+
void executeTeamUserAction(userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(userId)}/invite/revoke`, {
|
|
3353
|
+
method: 'POST',
|
|
3354
|
+
headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
|
|
3355
|
+
credentials: 'include',
|
|
3356
|
+
body: JSON.stringify({ workspaceId: teamManagementWorkspaceId })
|
|
3357
|
+
}));
|
|
3358
|
+
}, onRemoveMember: (userId) => {
|
|
3359
|
+
void executeTeamUserAction(userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(userId)}?workspaceId=${encodeURIComponent(teamManagementWorkspaceId)}`, {
|
|
3360
|
+
method: 'DELETE',
|
|
3361
|
+
headers: teamAdminHeaders(),
|
|
3362
|
+
credentials: 'include'
|
|
3363
|
+
}));
|
|
3364
|
+
}, onInviteEmailChange: setTeamInviteEmail, onInviteRoleChange: setTeamInviteRole, onInvitePermissionModeChange: setTeamInvitePermissionMode, onSubmitInvite: () => { void submitTeamInvite(); }, onLoadAuditPrevious: () => { void loadTeamAuditPage(teamAuditPage - 1); }, onLoadAuditNext: () => { void loadTeamAuditPage(teamAuditPage + 1); } }), _jsx(CreateWorkspaceConfirmModal, { isOpen: showCreateWorkspaceConfirm, theme: currentTheme, onClose: () => setShowCreateWorkspaceConfirm(false), onConfirm: () => openWorkspaceSetup({ intent: 'create-workspace' }) }), _jsx(SyncStatusModal, { isOpen: showSyncStatusModal, theme: currentTheme, currentWorkspaceLabel: currentWorkspaceLabel, syncStatusMeta: syncStatusMeta, workspaceCloudSyncEnabled: workspaceCloudSyncEnabled, syncControlBusy: syncControlBusy, canManageWorkspaceSync: canManageWorkspaceSync, workspaceSyncSummary: workspaceSyncSummary, workspaceSyncRepairBusy: workspaceSyncRepairBusy, referenceMismatchCount: referenceMismatchCount, syncStageLabel: syncStageLabel, workspaceSyncPendingChanges: workspaceSyncPendingChanges, formattedLastSyncTime: formattedLastSyncTime, formattedLastPullTime: formattedLastPullTime, formattedLastPushTime: formattedLastPushTime, syncLastError: syncLastError, workspaceSyncDiagnostics: workspaceSyncDiagnostics, activeReferenceMismatchSummaries: activeReferenceMismatchSummaries, syncDiagnosticsSummary: syncDiagnosticsSummary, syncEventRows: syncEventRows, syncEventsListRef: syncEventsListRef, workspaceSyncRepairQueued: workspaceSyncRepairQueued, workspaceSyncBusy: workspaceSyncBusy, workspaceSyncCopied: workspaceSyncCopied, runtimeMode: runtimeMode, isAuthenticated: isAuthenticated, onClose: () => setShowSyncStatusModal(false), onToggleWorkspaceSync: (enabled) => {
|
|
3365
|
+
void handleWorkspaceSyncToggle(enabled);
|
|
3366
|
+
}, onRepairSync: handleQueueOrRunRepairSync, onCopyReport: () => { void handleCopySyncDetails(); }, onOpenLogin: () => {
|
|
3367
|
+
openSharedLoginRoute('login');
|
|
3368
|
+
}, onRetrySync: () => { void retryWorkspaceCloudSync(); } }), unsavedModalOpen && createPortal(_jsx("div", { className: `${modalStyles.overlay} ${modalStyles.unsavedOverlay}`, style: { zIndex: 2000 }, children: _jsxs("div", { className: `tf-surface-modal tf-modal-shell ${modalStyles.modal} ${modalStyles.unsavedModal}`, "data-theme": currentTheme, children: [_jsx("div", { className: `tf-modal-header ${modalStyles.unsavedHeader}`, children: _jsxs("div", { className: `tf-modal-title ${modalStyles.unsavedTitle}`, children: [_jsx(AlertTriangle, { size: 20 }), "Unsaved Changes"] }) }), _jsx("div", { className: `${modalStyles.form} ${modalStyles.unsavedContent}`, children: _jsx("p", { className: modalStyles.unsavedText, children: "You have unsaved changes. Would you like to save them?" }) }), _jsxs("div", { className: `${modalStyles.formActions} ${modalStyles.unsavedActions}`, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => setUnsavedModalOpen(false), title: "Close dialog and continue editing", children: "Keep Editing" }), _jsx("button", { className: styles.destructiveBtn, onClick: handleDiscard, title: "Discard unsaved changes and leave", children: "Discard" }), _jsxs("button", { className: styles.submitBtn, onClick: handleSaveAndNavigate, disabled: loading, title: "Save changes and leave", children: [loading ? _jsx(Loader2, { size: 16, className: styles.spinner }) : _jsx(Save, { size: 16 }), "Save"] })] })] }) }), document.body), showFolderBrowser && createPortal(_jsx("div", { className: folderBrowserStyles.overlay, children: _jsxs("div", { className: folderBrowserStyles.browser, children: [_jsxs("div", { className: folderBrowserStyles.header, children: [_jsxs("div", { className: folderBrowserStyles.pathInfo, children: [_jsx(Folder, { size: 14 }), _jsx("span", { children: currentBrowsePath || 'Project Root' })] }), _jsxs("div", { className: folderBrowserStyles.actions, children: [currentBrowsePath && (_jsx("button", { className: styles.helpLink, onClick: () => {
|
|
3152
3369
|
const parts = currentBrowsePath.split('/').filter(Boolean);
|
|
3153
3370
|
parts.pop();
|
|
3154
3371
|
fetchFolders(parts.length ? parts.join('/') + '/' : '');
|