@taskforcehq/taskforce 0.3.303 → 0.3.305
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +140 -247
- package/dist/Taskforce.module.css +102 -5
- package/dist/TaskforceCore.js +635 -238
- package/dist/TaskforceCore.test.js +2243 -495
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/compat/workspaceSyncCompat.js +6 -0
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/DocumentIndex.d.ts +1 -1
- package/dist/components/features/DocumentIndex.js +1 -1
- package/dist/components/features/DocumentViewer.d.ts +1 -1
- package/dist/components/features/DocumentViewer.js +32 -9
- package/dist/components/features/DocumentWorkspace.d.ts +5 -22
- package/dist/components/features/DocumentWorkspace.js +12 -155
- package/dist/components/features/DocumentWorkspace.test.js +193 -6
- package/dist/components/features/TaskSettings.js +87 -41
- package/dist/components/features/TaskSettings.test.js +180 -40
- package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
- package/dist/components/features/documentWorkspaceModel.js +57 -0
- package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
- package/dist/components/features/useDocumentWorkspaceController.js +113 -0
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +3 -3
- package/dist/components/task/TaskActionHeader.test.js +8 -0
- package/dist/components/task/TaskCard.js +16 -1
- package/dist/components/task/TaskCard.test.js +31 -0
- package/dist/components/task/TaskForm.d.ts +9 -8
- package/dist/components/task/TaskForm.js +374 -219
- package/dist/components/task/TaskForm.test.js +482 -361
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +33 -161
- package/dist/components/task/TaskStatusActions.d.ts +6 -1
- package/dist/components/task/TaskStatusActions.js +8 -3
- package/dist/components/task/TaskStatusActions.test.js +9 -0
- package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
- package/dist/components/task/taskKanbanDropRules.js +187 -0
- package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
- package/dist/components/task/taskKanbanDropRules.test.js +114 -0
- package/dist/components/ui/Modal.d.ts +1 -1
- package/dist/components/ui/Modal.js +1 -0
- package/dist/components/views/AppShellHeader.js +5 -1
- package/dist/components/views/AppShellHeader.test.js +4 -0
- package/dist/components/views/PlanComparisonPage.d.ts +3 -2
- package/dist/components/views/PlanComparisonPage.js +9 -10
- package/dist/components/views/PlanComparisonPage.test.js +55 -41
- package/dist/components/views/PlansPage.d.ts +5 -1
- package/dist/components/views/PlansPage.js +150 -81
- package/dist/components/views/PlansPage.test.js +367 -41
- package/dist/components/views/StandaloneLayout.js +135 -54
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/panels/FilterToolbar.test.js +6 -4
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
- package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/config/envSchema.js +1 -1
- package/dist/core/AiProfileService.d.ts +6 -1
- package/dist/core/AiProfileService.js +161 -16
- package/dist/core/AiProfileService.test.js +3 -1
- package/dist/core/AiProfiles.test.js +200 -0
- package/dist/core/AuthTokenService.test.d.ts +1 -0
- package/dist/core/AuthTokenService.test.js +78 -0
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/EntitlementsPolicy.test.js +75 -13
- package/dist/core/PlanEntitlementService.d.ts +9 -1
- package/dist/core/PlanEntitlementService.js +144 -19
- package/dist/core/PlanVersionPolicy.test.js +19 -26
- package/dist/core/SignupMonetizationSettings.test.js +75 -15
- package/dist/core/SystemAdmin.test.js +212 -56
- package/dist/core/TaskActivityService.d.ts +4 -1
- package/dist/core/TaskActivityService.js +45 -0
- package/dist/core/TaskAuditTimeline.test.js +52 -1
- package/dist/core/TaskTaxonomyValidation.test.js +53 -0
- package/dist/core/Taskforce.d.ts +18 -11
- package/dist/core/Taskforce.js +93 -12
- package/dist/core/WorkspacePlanMode.test.js +73 -8
- package/dist/core/shared.d.ts +2 -0
- package/dist/core/shared.js +11 -0
- package/dist/documentReviews/service.d.ts +1 -1
- package/dist/documentReviews/types.d.ts +1 -1
- package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
- package/dist/hooks/sync/auth.d.ts +37 -0
- package/dist/hooks/sync/auth.js +63 -0
- package/dist/hooks/sync/bootstrap.d.ts +34 -0
- package/dist/hooks/sync/bootstrap.js +52 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
- package/dist/hooks/sync/orchestratorShared.js +209 -0
- package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
- package/dist/hooks/sync/orchestratorShared.test.js +49 -0
- package/dist/hooks/sync/realtime.d.ts +25 -0
- package/dist/hooks/sync/realtime.js +60 -0
- package/dist/hooks/sync/recovery.d.ts +69 -0
- package/dist/hooks/sync/recovery.js +118 -0
- package/dist/hooks/sync/transfers.d.ts +123 -0
- package/dist/hooks/sync/transfers.js +435 -0
- package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
- package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
- package/dist/hooks/ui/useResourceExport.d.ts +3 -1
- package/dist/hooks/ui/useResourceExport.js +5 -2
- package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
- package/dist/hooks/useRealtimeSync.js +2 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +12 -8
- package/dist/hooks/useSyncOrchestrator.js +388 -879
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +641 -6
- package/dist/hooks/useTaskMutations.d.ts +2 -1
- package/dist/hooks/useTaskMutations.js +57 -7
- package/dist/hooks/useTaskMutations.test.js +176 -0
- package/dist/hooks/useTaskforce.d.ts +13 -4
- package/dist/hooks/useTaskforce.js +302 -817
- package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
- package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
- package/dist/hooks/useWorkspaceSyncController.js +33 -11
- package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
- package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
- package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
- package/dist/hooks/workspace/workspaceLocalState.js +100 -0
- package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
- package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
- package/dist/localization/locales/en-US.js +2 -2
- package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
- package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
- package/dist/mcp/canonicalAssetHelpers.js +259 -0
- package/dist/mcp/clientIntegrations.d.ts +12 -1
- package/dist/mcp/clientIntegrations.js +161 -6
- package/dist/mcp/clientIntegrations.test.js +76 -4
- package/dist/mcp/collaborationRegistrar.d.ts +2 -0
- package/dist/mcp/collaborationRegistrar.js +71 -0
- package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
- package/dist/mcp/documentAssetRegistrar.js +346 -0
- package/dist/mcp/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +2455 -3925
- package/dist/mcp/runtime.test.js +240 -8
- package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
- package/dist/mcp/taskAttachmentHelpers.js +60 -0
- package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
- package/dist/mcp/taskAttachmentTypes.js +1 -0
- package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
- package/dist/mcp/taskPlanningRegistrar.js +418 -0
- package/dist/mcp/toolCatalog.d.ts +35 -0
- package/dist/mcp/toolCatalog.js +3 -0
- package/dist/plugins/vite.js +1 -1
- package/dist/runtime/appGateDefinitions.d.ts +1 -1
- package/dist/runtime/appGateDefinitions.js +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +46 -3
- package/dist/server/index.test.js +12 -1
- package/dist/server/routes/admin.d.ts +1 -5
- package/dist/server/routes/admin.js +151 -80
- package/dist/server/routes/annotatedAttachments.d.ts +1 -1
- package/dist/server/routes/annotatedAttachments.js +1 -1
- package/dist/server/routes/auth.d.ts +1 -4
- package/dist/server/routes/auth.js +59 -64
- package/dist/server/routes/authSupport.d.ts +30 -0
- package/dist/server/routes/authSupport.js +81 -0
- package/dist/server/routes/billing.d.ts +1 -1
- package/dist/server/routes/billing.js +122 -255
- package/dist/server/routes/billing.test.js +257 -86
- package/dist/server/routes/documentReviews.d.ts +1 -1
- package/dist/server/routes/documentReviews.js +1 -1
- package/dist/server/routes/documents.d.ts +4 -2
- package/dist/server/routes/documents.js +73 -7
- package/dist/server/routes/primitives.d.ts +11 -0
- package/dist/server/routes/primitives.js +73 -0
- package/dist/server/routes/resources.d.ts +1 -1
- package/dist/server/routes/resources.js +1 -1
- package/dist/server/routes/shared.d.ts +31 -1
- package/dist/server/routes/shared.js +26 -0
- package/dist/server/routes/sync.d.ts +1 -1
- package/dist/server/routes/sync.integration.test.js +20 -0
- package/dist/server/routes/sync.js +111 -238
- package/dist/server/routes/tasks.d.ts +1 -1
- package/dist/server/routes/tasks.js +50 -8
- package/dist/server/routes/workspaces.d.ts +1 -1
- package/dist/server/routes/workspaces.js +1 -1
- package/dist/server/routes.d.ts +2 -9
- package/dist/server/routes.js +84 -181
- package/dist/server/routes.test.js +912 -32
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/collaborationSyncPayload.js +4 -0
- package/dist/sync/collaborationSyncPayload.test.js +8 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -1
- package/dist/sync/collaborationSyncState.js +19 -0
- package/dist/sync/contentSyncState.js +28 -17
- package/dist/sync/syncApplyHandlers.d.ts +87 -1
- package/dist/sync/syncApplyHandlers.js +370 -8
- package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
- package/dist/sync/syncApplyHandlers.test.js +379 -0
- package/dist/sync/syncService.d.ts +9 -1
- package/dist/sync/syncService.js +44 -2
- package/dist/sync/syncService.test.js +62 -0
- package/dist/sync/workspacePullFeed.d.ts +1 -0
- package/dist/sync/workspacePullFeed.js +66 -59
- package/dist/sync/workspacePullFeed.test.js +72 -4
- package/dist/sync/workspaceSyncModel.d.ts +25 -1
- package/dist/sync/workspaceSyncModel.js +77 -3
- package/dist/sync/workspaceSyncModel.test.js +153 -0
- package/dist/sync/workspaceSyncState.d.ts +2 -0
- package/dist/sync/workspaceSyncState.js +1 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-N2MnQBZk.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
- package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
- package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-Dhm7M8e7.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
- package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-BcS4afRn.js} +1 -1
- package/dist/ui/assets/index-C2-OXZV7.css +1 -0
- package/dist/ui/assets/index-DcSI5F86.js +6 -0
- package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-BSUaRwF8.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
- package/dist/utils/commercialLifecyclePresentation.js +196 -0
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +10 -1
- package/scripts/check-cloud-mcp.mjs +83 -0
- package/scripts/playwright-auth.sh +9 -1
- package/scripts/run-billing-performance-smoke.sh +24 -0
- package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
- package/dist/ui/assets/DocumentWorkspace-C3GyzrWm.js +0 -250
- package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
- package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
- package/dist/ui/assets/index-CLIMgR6g.js +0 -6
- package/dist/ui/assets/index-DneETxcu.css +0 -1
package/dist/TaskforceCore.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { MemoryRouter, Navigate, useInRouterContext, useLocation, useNavigate } from 'react-router-dom';
|
|
4
4
|
import { useTaskforce } from './hooks/useTaskforce';
|
|
5
5
|
import { WidgetView } from './components/views/WidgetView';
|
|
@@ -14,6 +14,7 @@ import logoLight from './assets/branding/logos/logo_light.svg';
|
|
|
14
14
|
import logoDark from './assets/branding/logos/logo_dark.svg';
|
|
15
15
|
import taskforceBg from './assets/images/taskforce.png';
|
|
16
16
|
import { isLightThemeFamily } from './utils/theme';
|
|
17
|
+
import { Cloud, LaptopMinimal } from 'lucide-react';
|
|
17
18
|
function isDebugModeEnabled() {
|
|
18
19
|
const runtimeOverride = globalThis.__DEBUG_MODE__;
|
|
19
20
|
if (typeof runtimeOverride === 'boolean')
|
|
@@ -27,6 +28,12 @@ function defaultSetupTaxonomySections(pack) {
|
|
|
27
28
|
priorities: Boolean(pack?.priorities?.length),
|
|
28
29
|
};
|
|
29
30
|
}
|
|
31
|
+
function formatStarterSectionItems(values) {
|
|
32
|
+
return (Array.isArray(values) ? values : [])
|
|
33
|
+
.map((entry) => String(entry.label || entry.value || '').trim())
|
|
34
|
+
.filter(Boolean)
|
|
35
|
+
.join(', ');
|
|
36
|
+
}
|
|
30
37
|
function buildPlansPath(params = {}) {
|
|
31
38
|
const query = new URLSearchParams();
|
|
32
39
|
query.set('screen', 'plans');
|
|
@@ -54,7 +61,9 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
54
61
|
const [authMode, setAuthMode] = useState('login');
|
|
55
62
|
const [verificationToken, setVerificationToken] = useState('');
|
|
56
63
|
const [pendingVerificationWorkspaceSetupRequired, setPendingVerificationWorkspaceSetupRequired] = useState(false);
|
|
64
|
+
const [pendingVerificationCommercialState, setPendingVerificationCommercialState] = useState(null);
|
|
57
65
|
const [pendingVerificationRedirectPath, setPendingVerificationRedirectPath] = useState(null);
|
|
66
|
+
const [pendingPostAuthRedirect, setPendingPostAuthRedirect] = useState(null);
|
|
58
67
|
const [resetToken, setResetToken] = useState('');
|
|
59
68
|
const [inviteToken, setInviteToken] = useState('');
|
|
60
69
|
const [inviteEmail, setInviteEmail] = useState(null);
|
|
@@ -75,11 +84,15 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
75
84
|
const [workspaceNameInput, setWorkspaceNameInput] = useState('');
|
|
76
85
|
const [workspaceDescriptionInput, setWorkspaceDescriptionInput] = useState('');
|
|
77
86
|
const [workspaceSetupSource, setWorkspaceSetupSource] = useState('local');
|
|
87
|
+
const [workspaceSetupPage, setWorkspaceSetupPage] = useState('details');
|
|
78
88
|
const [cloudWorkspaceOptions, setCloudWorkspaceOptions] = useState([]);
|
|
79
89
|
const [cloudWorkspaceSelection, setCloudWorkspaceSelection] = useState('');
|
|
80
90
|
const [cloudWorkspaceLoading, setCloudWorkspaceLoading] = useState(false);
|
|
81
91
|
const [cloudWorkspaceError, setCloudWorkspaceError] = useState(null);
|
|
82
92
|
const [syncNewLocalWorkspaceToCloud, setSyncNewLocalWorkspaceToCloud] = useState(false);
|
|
93
|
+
const [pendingCloudWorkspaceImport, setPendingCloudWorkspaceImport] = useState(null);
|
|
94
|
+
const [cloudWorkspaceImportFinalizing, setCloudWorkspaceImportFinalizing] = useState(false);
|
|
95
|
+
const cloudWorkspaceImportFinalizingRef = useRef(false);
|
|
83
96
|
const fallbackSetupLibraryPacks = useMemo(() => listBuiltInTaxonomyLibraryPacks(), []);
|
|
84
97
|
const [setupLibraryPacks, setSetupLibraryPacks] = useState(fallbackSetupLibraryPacks);
|
|
85
98
|
const [selectedSetupPackId, setSelectedSetupPackId] = useState(() => (fallbackSetupLibraryPacks.find((pack) => pack.isDefaultStarter)?.id || fallbackSetupLibraryPacks[0]?.id || ''));
|
|
@@ -88,6 +101,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
88
101
|
const [setupLibraryError, setSetupLibraryError] = useState(null);
|
|
89
102
|
const [bootstrapRetryBusy, setBootstrapRetryBusy] = useState(false);
|
|
90
103
|
const [accountAccessGate, setAccountAccessGate] = useState(null);
|
|
104
|
+
const [accountAccessGateResolved, setAccountAccessGateResolved] = useState(false);
|
|
91
105
|
// Initialize the hook
|
|
92
106
|
const taskforceState = useTaskforce({
|
|
93
107
|
config,
|
|
@@ -95,7 +109,24 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
95
109
|
onTaskCountChange,
|
|
96
110
|
onClose
|
|
97
111
|
});
|
|
98
|
-
const { runtimeMode, workspaceSwitchingEnabled, currentWorkspaceId, configLoaded, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, hasBetaAccess, authSessionResolved, workspaceBootstrapPending, bootstrapPhase, bootstrapError, setupState, runtimeCapabilities, refreshSetupContext, retryBootstrapChecks, createWorkspace, saveWorkspaceProfile, fetchWorkspaces, applyWorkspaceSyncStateSnapshot, settingsModel, fetchTasks, loginWithCredentials, registerWithCredentials, requestEmailVerification, confirmEmailVerification, requestPasswordReset, confirmPasswordReset, inspectInviteAcceptance, acceptInviteWithToken, joinInviteWithToken, authUserEmail, currentTheme, logout } = taskforceState;
|
|
112
|
+
const { runtimeMode, workspaceSwitchingEnabled, currentWorkspaceId, configLoaded, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, hasBetaAccess, authSessionResolved, workspaceBootstrapPending, bootstrapPhase, bootstrapError, setupState, runtimeCapabilities, refreshSetupContext, retryBootstrapChecks, createWorkspace, saveWorkspaceProfile, fetchWorkspaces, applyWorkspaceSyncStateSnapshot, workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceSyncSetupIntent, workspaceSyncSummary, workspaceSyncRecommendedAction, workspaceSyncBusy, workspaceLastErrorMessage, retryWorkspaceCloudSync, settingsModel, fetchTasks, loginWithCredentials, registerWithCredentials, requestEmailVerification, confirmEmailVerification, requestPasswordReset, confirmPasswordReset, inspectInviteAcceptance, acceptInviteWithToken, joinInviteWithToken, authUserEmail, currentTheme, logout } = taskforceState;
|
|
113
|
+
const persistWorkspaceSyncStatePatch = useCallback(async (workspaceId, patch) => {
|
|
114
|
+
const response = await fetch('/api/taskforce/ui-state', {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: { 'Content-Type': 'application/json' },
|
|
117
|
+
credentials: 'include',
|
|
118
|
+
body: JSON.stringify({
|
|
119
|
+
workspaceId,
|
|
120
|
+
stateKey: 'workspace-sync',
|
|
121
|
+
patch
|
|
122
|
+
})
|
|
123
|
+
});
|
|
124
|
+
const payload = await response.json().catch(() => ({}));
|
|
125
|
+
if (!response.ok || payload?.success === false) {
|
|
126
|
+
throw new Error(payload?.error || `Failed to configure workspace sync (${response.status})`);
|
|
127
|
+
}
|
|
128
|
+
return payload;
|
|
129
|
+
}, []);
|
|
99
130
|
const selectedSetupPack = useMemo(() => {
|
|
100
131
|
return setupLibraryPacks.find((pack) => pack.id === selectedSetupPackId)
|
|
101
132
|
|| setupLibraryPacks.find((pack) => pack.isDefaultStarter)
|
|
@@ -118,6 +149,61 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
118
149
|
const isLoginRoute = location.pathname === '/login';
|
|
119
150
|
const isSetupRoute = location.pathname === '/setup';
|
|
120
151
|
const isComingSoonRoute = location.pathname === '/coming-soon';
|
|
152
|
+
const cloudWorkspaceSelectionPath = '/setup?step=workspace&source=cloud&postAuth=select-cloud-workspace';
|
|
153
|
+
const wantsCloudWorkspaceSetup = !isCloudRuntime && workspaceSetupSource === 'cloud';
|
|
154
|
+
const isCloudWorkspaceSetupFlow = wantsCloudWorkspaceSetup && canUseCloudWorkspaceSetup;
|
|
155
|
+
const activeCloudWorkspaceImport = useMemo(() => {
|
|
156
|
+
if (pendingCloudWorkspaceImport)
|
|
157
|
+
return pendingCloudWorkspaceImport;
|
|
158
|
+
if (workspaceSyncSetupIntent !== 'attach-cloud-import')
|
|
159
|
+
return null;
|
|
160
|
+
const workspaceId = String(currentWorkspaceId || '').trim();
|
|
161
|
+
if (!workspaceId || workspaceId.toLowerCase() === 'default')
|
|
162
|
+
return null;
|
|
163
|
+
const matchingWorkspace = cloudWorkspaceOptions.find((workspace) => workspace.id === workspaceId);
|
|
164
|
+
return {
|
|
165
|
+
workspaceId,
|
|
166
|
+
name: matchingWorkspace?.name || workspaceId
|
|
167
|
+
};
|
|
168
|
+
}, [cloudWorkspaceOptions, currentWorkspaceId, pendingCloudWorkspaceImport, workspaceSyncSetupIntent]);
|
|
169
|
+
const cloudWorkspaceImportMatchesCurrentWorkspace = Boolean(activeCloudWorkspaceImport
|
|
170
|
+
&& currentWorkspaceId === activeCloudWorkspaceImport.workspaceId);
|
|
171
|
+
const isCloudWorkspaceImportErrored = Boolean(activeCloudWorkspaceImport
|
|
172
|
+
&& cloudWorkspaceImportMatchesCurrentWorkspace
|
|
173
|
+
&& workspaceSyncPhase === 'error');
|
|
174
|
+
const isCloudWorkspaceImportReady = Boolean(activeCloudWorkspaceImport
|
|
175
|
+
&& cloudWorkspaceImportMatchesCurrentWorkspace
|
|
176
|
+
&& workspaceCloudSyncEnabled
|
|
177
|
+
&& workspaceSyncPhase === 'active'
|
|
178
|
+
&& !workspaceSyncBusy);
|
|
179
|
+
const cloudWorkspaceImportProgressLines = useMemo(() => {
|
|
180
|
+
if (!activeCloudWorkspaceImport)
|
|
181
|
+
return [];
|
|
182
|
+
const connectingState = setupBusy ? 'In progress' : 'Complete';
|
|
183
|
+
const importingState = isCloudWorkspaceImportErrored
|
|
184
|
+
? 'Needs attention'
|
|
185
|
+
: (workspaceSyncBusy || workspaceSyncPhase === 'attach-cloud')
|
|
186
|
+
? 'In progress'
|
|
187
|
+
: (workspaceSyncPhase === 'active' ? 'Complete' : 'Waiting');
|
|
188
|
+
const finishingState = cloudWorkspaceImportFinalizing
|
|
189
|
+
? 'In progress'
|
|
190
|
+
: isCloudWorkspaceImportReady
|
|
191
|
+
? 'Ready'
|
|
192
|
+
: 'Waiting';
|
|
193
|
+
return [
|
|
194
|
+
`Connect workspace: ${connectingState}`,
|
|
195
|
+
`Import cloud data: ${importingState}`,
|
|
196
|
+
`Open local workspace: ${finishingState}`
|
|
197
|
+
];
|
|
198
|
+
}, [
|
|
199
|
+
activeCloudWorkspaceImport,
|
|
200
|
+
setupBusy,
|
|
201
|
+
isCloudWorkspaceImportErrored,
|
|
202
|
+
workspaceSyncBusy,
|
|
203
|
+
workspaceSyncPhase,
|
|
204
|
+
cloudWorkspaceImportFinalizing,
|
|
205
|
+
isCloudWorkspaceImportReady
|
|
206
|
+
]);
|
|
121
207
|
const shouldForceLogin = isLikelyCloudAppHost
|
|
122
208
|
? ((!isAuthenticated || authBlocked) && !isPricingRoute && !isPlansRoute && !isPlansScreen)
|
|
123
209
|
: (((isCloudAuthRequired && !isAuthenticated) || shouldForceForAuthBlocked) && !isPricingRoute && !isPlansRoute && !isPlansScreen);
|
|
@@ -131,6 +217,23 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
131
217
|
&& !isPlansRoute
|
|
132
218
|
&& !isPlansScreen;
|
|
133
219
|
const directRegistrationEnabled = cloudAuthConfigured;
|
|
220
|
+
const authPageTitle = useMemo(() => {
|
|
221
|
+
switch (authMode) {
|
|
222
|
+
case 'register':
|
|
223
|
+
return 'Create Account | Taskforce';
|
|
224
|
+
case 'verify':
|
|
225
|
+
return 'Verify Email | Taskforce';
|
|
226
|
+
case 'forgot':
|
|
227
|
+
return 'Reset Password | Taskforce';
|
|
228
|
+
case 'reset':
|
|
229
|
+
return 'Set New Password | Taskforce';
|
|
230
|
+
case 'invite':
|
|
231
|
+
return 'Accept Invite | Taskforce';
|
|
232
|
+
case 'login':
|
|
233
|
+
default:
|
|
234
|
+
return 'Sign In | Taskforce';
|
|
235
|
+
}
|
|
236
|
+
}, [authMode]);
|
|
134
237
|
const setupStep = useMemo(() => String(new URLSearchParams(location.search).get('step') || '').trim().toLowerCase(), [location.search]);
|
|
135
238
|
const setupIntent = useMemo(() => String(new URLSearchParams(location.search).get('intent') || '').trim().toLowerCase(), [location.search]);
|
|
136
239
|
const isCreateWorkspaceIntent = isSetupRoute && setupStep === 'workspace' && setupIntent === 'create-workspace';
|
|
@@ -198,9 +301,11 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
198
301
|
useEffect(() => {
|
|
199
302
|
if (runtimeMode !== 'cloud' || !isAuthenticated || !authSessionResolved) {
|
|
200
303
|
setAccountAccessGate(null);
|
|
304
|
+
setAccountAccessGateResolved(false);
|
|
201
305
|
return;
|
|
202
306
|
}
|
|
203
307
|
setAccountAccessGate(null);
|
|
308
|
+
setAccountAccessGateResolved(false);
|
|
204
309
|
let cancelled = false;
|
|
205
310
|
(async () => {
|
|
206
311
|
try {
|
|
@@ -209,11 +314,17 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
209
314
|
credentials: 'include'
|
|
210
315
|
});
|
|
211
316
|
const payload = await res.json().catch(() => ({}));
|
|
212
|
-
if (cancelled
|
|
317
|
+
if (cancelled)
|
|
318
|
+
return;
|
|
319
|
+
if (!res.ok) {
|
|
320
|
+
setAccountAccessGate(null);
|
|
321
|
+
setAccountAccessGateResolved(true);
|
|
213
322
|
return;
|
|
323
|
+
}
|
|
214
324
|
const gate = String(payload?.gate || '').trim().toLowerCase();
|
|
215
325
|
if (gate !== 'ok' && gate !== 'plan_selection_required' && gate !== 'checkout_pending' && gate !== 'misconfigured' && gate !== 'missing_entitlement') {
|
|
216
326
|
setAccountAccessGate(null);
|
|
327
|
+
setAccountAccessGateResolved(true);
|
|
217
328
|
return;
|
|
218
329
|
}
|
|
219
330
|
setAccountAccessGate({
|
|
@@ -222,10 +333,13 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
222
333
|
canAccessPlans: payload?.canAccessPlans !== false,
|
|
223
334
|
message: typeof payload?.message === 'string' ? payload.message : null
|
|
224
335
|
});
|
|
336
|
+
setAccountAccessGateResolved(true);
|
|
225
337
|
}
|
|
226
338
|
catch {
|
|
227
|
-
if (!cancelled)
|
|
339
|
+
if (!cancelled) {
|
|
228
340
|
setAccountAccessGate(null);
|
|
341
|
+
setAccountAccessGateResolved(true);
|
|
342
|
+
}
|
|
229
343
|
}
|
|
230
344
|
})();
|
|
231
345
|
return () => {
|
|
@@ -258,6 +372,12 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
258
372
|
useEffect(() => {
|
|
259
373
|
if (!isSetupRoute || setupStep !== 'workspace')
|
|
260
374
|
return;
|
|
375
|
+
setWorkspaceSetupPage('details');
|
|
376
|
+
if (activeCloudWorkspaceImport && canUseCloudWorkspaceSetup) {
|
|
377
|
+
setWorkspaceSetupSource('cloud');
|
|
378
|
+
setSyncNewLocalWorkspaceToCloud(false);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
261
381
|
const source = String(new URLSearchParams(location.search).get('source') || '').trim().toLowerCase();
|
|
262
382
|
if (source === 'cloud' && canUseCloudWorkspaceSetup) {
|
|
263
383
|
setWorkspaceSetupSource('cloud');
|
|
@@ -265,7 +385,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
265
385
|
return;
|
|
266
386
|
}
|
|
267
387
|
setWorkspaceSetupSource('local');
|
|
268
|
-
}, [canUseCloudWorkspaceSetup, isSetupRoute, location.search, setupStep]);
|
|
388
|
+
}, [activeCloudWorkspaceImport, canUseCloudWorkspaceSetup, isSetupRoute, location.search, setupStep]);
|
|
269
389
|
const fetchCloudWorkspaceOptions = useCallback(async () => {
|
|
270
390
|
if (!canUseCloudWorkspaceSetup || !isAuthenticated) {
|
|
271
391
|
setCloudWorkspaceOptions([]);
|
|
@@ -328,7 +448,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
328
448
|
})
|
|
329
449
|
.then(async (res) => {
|
|
330
450
|
if (!res.ok)
|
|
331
|
-
throw new Error(`Failed to load
|
|
451
|
+
throw new Error(`Failed to load setup libraries (${res.status})`);
|
|
332
452
|
const payload = await res.json().catch(() => ({}));
|
|
333
453
|
const nextPacks = Array.isArray(payload?.packs) ? payload.packs : [];
|
|
334
454
|
if (cancelled || nextPacks.length === 0)
|
|
@@ -342,7 +462,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
342
462
|
if (cancelled)
|
|
343
463
|
return;
|
|
344
464
|
setSetupLibraryPacks(fallbackSetupLibraryPacks);
|
|
345
|
-
setSetupLibraryError(error instanceof Error ? error.message : 'Failed to load
|
|
465
|
+
setSetupLibraryError(error instanceof Error ? error.message : 'Failed to load setup libraries.');
|
|
346
466
|
})
|
|
347
467
|
.finally(() => {
|
|
348
468
|
if (!cancelled)
|
|
@@ -355,6 +475,73 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
355
475
|
useEffect(() => {
|
|
356
476
|
setSetupTaxonomySections(defaultSetupTaxonomySections(selectedSetupPack));
|
|
357
477
|
}, [selectedSetupPackId, selectedSetupPack]);
|
|
478
|
+
useEffect(() => {
|
|
479
|
+
if (workspaceSetupSource === 'cloud') {
|
|
480
|
+
setWorkspaceSetupPage('details');
|
|
481
|
+
}
|
|
482
|
+
}, [workspaceSetupSource]);
|
|
483
|
+
useEffect(() => {
|
|
484
|
+
if (!isCloudWorkspaceSetupFlow) {
|
|
485
|
+
setPendingCloudWorkspaceImport(null);
|
|
486
|
+
setCloudWorkspaceImportFinalizing(false);
|
|
487
|
+
cloudWorkspaceImportFinalizingRef.current = false;
|
|
488
|
+
}
|
|
489
|
+
}, [isCloudWorkspaceSetupFlow]);
|
|
490
|
+
useEffect(() => {
|
|
491
|
+
if (!isCloudWorkspaceImportErrored || !activeCloudWorkspaceImport)
|
|
492
|
+
return;
|
|
493
|
+
setSetupNotice(null);
|
|
494
|
+
setSetupError(workspaceLastErrorMessage
|
|
495
|
+
|| `Failed to import "${activeCloudWorkspaceImport.name}" from cloud. Retry import to continue.`);
|
|
496
|
+
}, [activeCloudWorkspaceImport, isCloudWorkspaceImportErrored, workspaceLastErrorMessage]);
|
|
497
|
+
useEffect(() => {
|
|
498
|
+
if (!isCloudWorkspaceImportReady || !activeCloudWorkspaceImport || cloudWorkspaceImportFinalizingRef.current)
|
|
499
|
+
return;
|
|
500
|
+
let cancelled = false;
|
|
501
|
+
cloudWorkspaceImportFinalizingRef.current = true;
|
|
502
|
+
setCloudWorkspaceImportFinalizing(true);
|
|
503
|
+
setSetupError(null);
|
|
504
|
+
setSetupNotice(`Workspace import complete. Opening ${activeCloudWorkspaceImport.name}...`);
|
|
505
|
+
void (async () => {
|
|
506
|
+
try {
|
|
507
|
+
await refreshSetupContext();
|
|
508
|
+
await Promise.all([
|
|
509
|
+
fetchWorkspaces(),
|
|
510
|
+
fetchTasks(true)
|
|
511
|
+
]);
|
|
512
|
+
if (cancelled)
|
|
513
|
+
return;
|
|
514
|
+
await persistWorkspaceSyncStatePatch(activeCloudWorkspaceImport.workspaceId, { setupIntent: null });
|
|
515
|
+
applyWorkspaceSyncStateSnapshot({
|
|
516
|
+
enabled: workspaceCloudSyncEnabled,
|
|
517
|
+
phase: workspaceSyncPhase,
|
|
518
|
+
setupIntent: null
|
|
519
|
+
});
|
|
520
|
+
setPendingCloudWorkspaceImport(null);
|
|
521
|
+
navigate('/', { replace: true });
|
|
522
|
+
}
|
|
523
|
+
catch {
|
|
524
|
+
if (cancelled)
|
|
525
|
+
return;
|
|
526
|
+
setSetupNotice(null);
|
|
527
|
+
setSetupError(`Imported "${activeCloudWorkspaceImport.name}" but failed to finish opening it.`);
|
|
528
|
+
setCloudWorkspaceImportFinalizing(false);
|
|
529
|
+
cloudWorkspaceImportFinalizingRef.current = false;
|
|
530
|
+
}
|
|
531
|
+
})();
|
|
532
|
+
return () => {
|
|
533
|
+
cancelled = true;
|
|
534
|
+
};
|
|
535
|
+
}, [
|
|
536
|
+
activeCloudWorkspaceImport,
|
|
537
|
+
applyWorkspaceSyncStateSnapshot,
|
|
538
|
+
fetchTasks,
|
|
539
|
+
fetchWorkspaces,
|
|
540
|
+
isCloudWorkspaceImportReady,
|
|
541
|
+
navigate,
|
|
542
|
+
persistWorkspaceSyncStatePatch,
|
|
543
|
+
refreshSetupContext
|
|
544
|
+
]);
|
|
358
545
|
const nextPath = useMemo(() => {
|
|
359
546
|
const raw = new URLSearchParams(location.search).get('next') || '/';
|
|
360
547
|
if (!raw.startsWith('/'))
|
|
@@ -363,7 +550,28 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
363
550
|
return '/';
|
|
364
551
|
return raw;
|
|
365
552
|
}, [location.search]);
|
|
553
|
+
const shouldReturnToCloudWorkspaceSelection = useMemo(() => {
|
|
554
|
+
try {
|
|
555
|
+
const parsed = new URL(nextPath, 'https://taskforce.local');
|
|
556
|
+
return parsed.pathname === '/setup'
|
|
557
|
+
&& parsed.searchParams.get('step') === 'workspace'
|
|
558
|
+
&& parsed.searchParams.get('source') === 'cloud'
|
|
559
|
+
&& parsed.searchParams.get('postAuth') === 'select-cloud-workspace';
|
|
560
|
+
}
|
|
561
|
+
catch {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
}, [nextPath]);
|
|
565
|
+
const resolveWorkspaceSetupPath = useCallback(() => (shouldReturnToCloudWorkspaceSelection
|
|
566
|
+
? cloudWorkspaceSelectionPath
|
|
567
|
+
: '/setup?step=workspace'), [shouldReturnToCloudWorkspaceSelection]);
|
|
366
568
|
const resolvedSignupInterval = selectedSignupInterval === 'year' ? 'year' : 'month';
|
|
569
|
+
const hasCommercialOnboardingGate = useCallback((input) => {
|
|
570
|
+
return Boolean(input?.planSelectionRequired
|
|
571
|
+
|| input?.checkoutPending
|
|
572
|
+
|| input?.commercialState === 'pending_plan_selection'
|
|
573
|
+
|| input?.commercialState === 'checkout_pending');
|
|
574
|
+
}, []);
|
|
367
575
|
const resolvePostRegistrationPath = useCallback((input) => {
|
|
368
576
|
if (input?.planSelectionRequired || input?.commercialState === 'pending_plan_selection') {
|
|
369
577
|
return planSelectionPath;
|
|
@@ -408,26 +616,6 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
408
616
|
const query = params.toString();
|
|
409
617
|
navigate(`/login${query ? `?${query}` : ''}`, { replace: true });
|
|
410
618
|
}, [directRegistrationEnabled, location.search, navigate]);
|
|
411
|
-
const routeToWorkspaceSetupIfNeeded = useCallback(async (hintWorkspaceSetupRequired) => {
|
|
412
|
-
if (!workspaceSwitchingEnabled)
|
|
413
|
-
return false;
|
|
414
|
-
const workspacesResult = await fetchWorkspaces();
|
|
415
|
-
if (workspacesResult.success) {
|
|
416
|
-
const workspaceCount = Array.isArray(workspacesResult.workspaces)
|
|
417
|
-
? workspacesResult.workspaces.length
|
|
418
|
-
: 0;
|
|
419
|
-
if (workspaceCount === 0) {
|
|
420
|
-
navigate('/setup?step=workspace', { replace: true });
|
|
421
|
-
return true;
|
|
422
|
-
}
|
|
423
|
-
return false;
|
|
424
|
-
}
|
|
425
|
-
if (hintWorkspaceSetupRequired) {
|
|
426
|
-
navigate('/setup?step=workspace', { replace: true });
|
|
427
|
-
return true;
|
|
428
|
-
}
|
|
429
|
-
return false;
|
|
430
|
-
}, [fetchWorkspaces, navigate, workspaceSwitchingEnabled]);
|
|
431
619
|
useEffect(() => {
|
|
432
620
|
if (!isLoginRoute)
|
|
433
621
|
return;
|
|
@@ -445,8 +633,40 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
445
633
|
if (isLoginRoute && loginRouteMode === 'verify')
|
|
446
634
|
return;
|
|
447
635
|
setPendingVerificationWorkspaceSetupRequired(false);
|
|
636
|
+
setPendingVerificationCommercialState(null);
|
|
448
637
|
setPendingVerificationRedirectPath(null);
|
|
449
638
|
}, [authMode, isLoginRoute, loginRouteMode]);
|
|
639
|
+
useEffect(() => {
|
|
640
|
+
if (!isLoginRoute || !pendingPostAuthRedirect || !authSessionResolved || !isAuthenticated)
|
|
641
|
+
return;
|
|
642
|
+
if (runtimeMode === 'cloud' && !pendingPostAuthRedirect.commercialOnboardingGate && !accountAccessGateResolved) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
if (!pendingPostAuthRedirect.commercialOnboardingGate && pendingPostAuthRedirect.workspaceSetupRequired) {
|
|
646
|
+
navigate(resolveWorkspaceSetupPath(), { replace: true });
|
|
647
|
+
}
|
|
648
|
+
else {
|
|
649
|
+
navigate(pendingPostAuthRedirect.path, { replace: true });
|
|
650
|
+
}
|
|
651
|
+
setLoginEmail('');
|
|
652
|
+
setLoginDisplayName('');
|
|
653
|
+
setLoginPassword('');
|
|
654
|
+
setPendingPostAuthRedirect(null);
|
|
655
|
+
}, [
|
|
656
|
+
accountAccessGateResolved,
|
|
657
|
+
authSessionResolved,
|
|
658
|
+
isAuthenticated,
|
|
659
|
+
isLoginRoute,
|
|
660
|
+
navigate,
|
|
661
|
+
pendingPostAuthRedirect,
|
|
662
|
+
resolveWorkspaceSetupPath,
|
|
663
|
+
runtimeMode
|
|
664
|
+
]);
|
|
665
|
+
useEffect(() => {
|
|
666
|
+
if (!isLoginRoute)
|
|
667
|
+
return;
|
|
668
|
+
document.title = authPageTitle;
|
|
669
|
+
}, [authPageTitle, isLoginRoute]);
|
|
450
670
|
useEffect(() => {
|
|
451
671
|
if (!isLoginRoute || authMode !== 'invite' || !inviteToken.trim())
|
|
452
672
|
return;
|
|
@@ -515,6 +735,24 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
515
735
|
const code = result.code || result.state;
|
|
516
736
|
return code ? `[${code}] ${message}` : message;
|
|
517
737
|
};
|
|
738
|
+
const buildVerificationRequestNotice = (input) => {
|
|
739
|
+
if (input?.deliveryAttempted === false) {
|
|
740
|
+
return input?.intro || 'If that account exists and still needs verification, we sent an email.';
|
|
741
|
+
}
|
|
742
|
+
if (input?.emailSent === false) {
|
|
743
|
+
return `Verification email failed to send. ${input?.emailError || 'Try Resend Verification again.'}`;
|
|
744
|
+
}
|
|
745
|
+
return 'Verification email resent.';
|
|
746
|
+
};
|
|
747
|
+
const buildPasswordResetNotice = (input) => {
|
|
748
|
+
if (input?.deliveryAttempted === false) {
|
|
749
|
+
return 'If that account exists, we sent password reset instructions.';
|
|
750
|
+
}
|
|
751
|
+
if (input?.emailSent === false) {
|
|
752
|
+
return `Password reset email failed to send. ${input?.emailError || 'Try again in a minute.'}`;
|
|
753
|
+
}
|
|
754
|
+
return 'Password reset instructions sent.';
|
|
755
|
+
};
|
|
518
756
|
useEffect(() => {
|
|
519
757
|
if (mode === 'widget')
|
|
520
758
|
return;
|
|
@@ -533,6 +771,17 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
533
771
|
const shouldStayOnInviteRoute = isLoginRoute
|
|
534
772
|
&& authMode === 'invite'
|
|
535
773
|
&& inviteToken.trim().length > 0;
|
|
774
|
+
const shouldDelayAuthenticatedLoginRedirect = isLoginRoute
|
|
775
|
+
&& isAuthenticated
|
|
776
|
+
&& !shouldStayOnInviteRoute
|
|
777
|
+
&& (Boolean(pendingPostAuthRedirect)
|
|
778
|
+
|| workspaceBootstrapPending
|
|
779
|
+
|| authMode === 'register'
|
|
780
|
+
|| authMode === 'verify'
|
|
781
|
+
|| (runtimeMode === 'cloud' && (!configLoaded || !accountAccessGateResolved)));
|
|
782
|
+
if (shouldDelayAuthenticatedLoginRedirect) {
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
536
785
|
if (isLoginRoute && isAuthenticated && !shouldStayOnInviteRoute)
|
|
537
786
|
navigate(nextPath, { replace: true });
|
|
538
787
|
}
|
|
@@ -544,6 +793,9 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
544
793
|
}
|
|
545
794
|
return;
|
|
546
795
|
}
|
|
796
|
+
if (isLoginRoute && !isAuthenticated) {
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
547
799
|
if (shouldForceAccountAccessGate) {
|
|
548
800
|
if (!isPlansScreen) {
|
|
549
801
|
const gate = encodeURIComponent(String(accountAccessGate?.gate || 'plan_selection_required'));
|
|
@@ -551,6 +803,15 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
551
803
|
}
|
|
552
804
|
return;
|
|
553
805
|
}
|
|
806
|
+
if (activeCloudWorkspaceImport) {
|
|
807
|
+
const isCloudImportRoute = isSetupRoute
|
|
808
|
+
&& setupStep === 'workspace'
|
|
809
|
+
&& new URLSearchParams(location.search).get('source') === 'cloud';
|
|
810
|
+
if (!isCloudImportRoute) {
|
|
811
|
+
navigate(cloudWorkspaceSelectionPath, { replace: true });
|
|
812
|
+
}
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
554
815
|
if (setupGateActive) {
|
|
555
816
|
if (hasSetupReadError) {
|
|
556
817
|
if (!isSetupRoute || setupStep !== 'error') {
|
|
@@ -566,7 +827,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
566
827
|
}
|
|
567
828
|
if (needsWorkspaceSetup) {
|
|
568
829
|
if (!isSetupRoute || setupStep !== 'workspace') {
|
|
569
|
-
navigate(
|
|
830
|
+
navigate(resolveWorkspaceSetupPath(), { replace: true });
|
|
570
831
|
}
|
|
571
832
|
return;
|
|
572
833
|
}
|
|
@@ -589,19 +850,27 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
589
850
|
isPlansRoute,
|
|
590
851
|
isPlansScreen,
|
|
591
852
|
accountAccessGate?.gate,
|
|
853
|
+
accountAccessGateResolved,
|
|
854
|
+
authMode,
|
|
592
855
|
setupGateActive,
|
|
593
856
|
setupState,
|
|
594
857
|
hasSetupReadError,
|
|
595
858
|
isCreateWorkspaceIntent,
|
|
859
|
+
activeCloudWorkspaceImport,
|
|
596
860
|
needsGlobalSetup,
|
|
597
861
|
needsWorkspaceSetup,
|
|
598
862
|
setupStep,
|
|
599
863
|
workspaceBootstrapPending,
|
|
864
|
+
configLoaded,
|
|
865
|
+
runtimeMode,
|
|
866
|
+
inviteToken,
|
|
600
867
|
location.pathname,
|
|
601
868
|
location.search,
|
|
602
869
|
location.hash,
|
|
603
870
|
navigate,
|
|
604
|
-
nextPath
|
|
871
|
+
nextPath,
|
|
872
|
+
pendingPostAuthRedirect,
|
|
873
|
+
cloudWorkspaceSelectionPath
|
|
605
874
|
]);
|
|
606
875
|
useEffect(() => {
|
|
607
876
|
if (mode === 'widget')
|
|
@@ -653,18 +922,26 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
653
922
|
if (isLoginRoute) {
|
|
654
923
|
return (_jsx("div", { className: shellStyles.standaloneWrapper, "data-theme": currentTheme, children: _jsx("div", { className: authStyles.loginView, style: {
|
|
655
924
|
backgroundImage: `linear-gradient(rgba(15, 15, 26, 0.88), rgba(15, 15, 26, 0.9)), url(${taskforceBg})`
|
|
656
|
-
}, children: _jsxs("div", { className: authStyles.loginCard, children: [!isCloudRuntime && !shouldForceLogin && (_jsx("button", { className: authStyles.loginCloseBtn, "aria-label": "Close sign in", onClick: () => navigate(nextPath, { replace: true }), children: "\u00D7" })), _jsxs("div", { className: authStyles.authBrandRow, children: [_jsx("img", { src: authBrandLogo, alt: "Taskforce", className: authStyles.loginLogo }), _jsxs("h1", { className: authStyles.loginTitle, children: ["TASKFORCE ", _jsx("span", { className: authStyles.loginTitleAccent, children: "HQ" })] })] }), _jsxs("p", { className: authStyles.loginSubtitle, children: [authMode === 'login' && 'Sign in with your account credentials.', authMode === 'register' && 'Create your Taskforce account.', authMode === 'verify' && 'Enter your verification token.', authMode === 'forgot' && 'Request a password reset link.', authMode === 'reset' && 'Set a new password using your reset token.', authMode === 'invite' && (inviteResolution === 'existing_user_ready'
|
|
925
|
+
}, children: _jsxs("div", { className: authStyles.loginCard, children: [!isCloudRuntime && !shouldForceLogin && (_jsx("button", { className: authStyles.loginCloseBtn, "aria-label": "Close sign in", "data-testid": "auth-close-button", onClick: () => navigate(nextPath, { replace: true }), children: "\u00D7" })), _jsxs("div", { className: authStyles.authBrandRow, children: [_jsx("img", { src: authBrandLogo, alt: "Taskforce", className: authStyles.loginLogo }), _jsxs("h1", { className: authStyles.loginTitle, children: ["TASKFORCE ", _jsx("span", { className: authStyles.loginTitleAccent, children: "HQ" })] })] }), _jsxs("p", { className: authStyles.loginSubtitle, children: [authMode === 'login' && 'Sign in with your account credentials.', authMode === 'register' && 'Create your Taskforce account.', authMode === 'verify' && 'Enter your verification token.', authMode === 'forgot' && 'Request a password reset link.', authMode === 'reset' && 'Set a new password using your reset token.', authMode === 'invite' && (inviteResolution === 'existing_user_ready'
|
|
657
926
|
? `You've been invited to join ${inviteWorkspaceId || 'this workspace'}.`
|
|
658
927
|
: inviteResolution === 'existing_user_signed_out'
|
|
659
928
|
? 'Sign in to join this workspace.'
|
|
660
|
-
: 'Create your account to join this workspace.')] }), (authMode === 'login'
|
|
929
|
+
: 'Create your account to join this workspace.')] }), (authMode === 'login'
|
|
930
|
+
|| authMode === 'register'
|
|
931
|
+
|| authMode === 'verify'
|
|
932
|
+
|| authMode === 'forgot'
|
|
933
|
+
|| (authMode === 'invite' && inviteResolution === 'existing_user_signed_out')) && (_jsxs("label", { className: authStyles.loginField, children: [_jsx("span", { className: authStyles.loginFieldLabel, children: "Email" }), _jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "email", value: loginEmail, placeholder: "Email", autoComplete: "email", "data-testid": "auth-email-input", onChange: (event) => setLoginEmail(event.target.value) })] })), authMode === 'register' && (_jsxs("label", { className: authStyles.loginField, children: [_jsx("span", { className: authStyles.loginFieldLabel, children: "Display name" }), _jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "text", value: loginDisplayName, placeholder: "Display name", autoComplete: "nickname", "data-testid": "auth-display-name-input", onChange: (event) => setLoginDisplayName(event.target.value) })] })), (authMode === 'login'
|
|
661
934
|
|| authMode === 'register'
|
|
662
935
|
|| authMode === 'reset'
|
|
663
|
-
|| (authMode === 'invite' && (invitePasswordRequired || inviteResolution === 'existing_user_signed_out'))) && (
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
936
|
+
|| (authMode === 'invite' && (invitePasswordRequired || inviteResolution === 'existing_user_signed_out'))) && (_jsxs("label", { className: authStyles.loginField, children: [_jsx("span", { className: authStyles.loginFieldLabel, children: authMode === 'reset'
|
|
937
|
+
? 'New password'
|
|
938
|
+
: (authMode === 'invite' && invitePasswordRequired ? 'Create password' : 'Password') }), _jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "password", value: loginPassword, placeholder: authMode === 'reset'
|
|
939
|
+
? 'New password'
|
|
940
|
+
: (authMode === 'invite'
|
|
941
|
+
? (invitePasswordRequired ? 'Create password' : 'Password')
|
|
942
|
+
: 'Password'), autoComplete: authMode === 'register' || authMode === 'reset' || (authMode === 'invite' && invitePasswordRequired)
|
|
943
|
+
? 'new-password'
|
|
944
|
+
: 'current-password', "data-testid": "auth-password-input", onChange: (event) => setLoginPassword(event.target.value) })] })), authMode === 'verify' && (_jsxs("label", { className: authStyles.loginField, children: [_jsx("span", { className: authStyles.loginFieldLabel, children: "Verification token" }), _jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "text", value: verificationToken, placeholder: "Verification token", autoComplete: "one-time-code", "data-testid": "auth-verification-token-input", onChange: (event) => setVerificationToken(event.target.value) })] })), authMode === 'reset' && (_jsxs("label", { className: authStyles.loginField, children: [_jsx("span", { className: authStyles.loginFieldLabel, children: "Reset token" }), _jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "text", value: resetToken, placeholder: "Reset token", autoComplete: "one-time-code", "data-testid": "auth-reset-token-input", onChange: (event) => setResetToken(event.target.value) })] })), authMode === 'invite' && (_jsxs(_Fragment, { children: [_jsxs("label", { className: authStyles.loginField, children: [_jsx("span", { className: authStyles.loginFieldLabel, children: "Invite token" }), _jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "text", value: inviteToken, placeholder: "Invite token", "data-testid": "auth-invite-token-input", onChange: (event) => setInviteToken(event.target.value) })] }), inviteEmail && (_jsxs("p", { className: authStyles.loginSubtitle, children: ["Invite for: ", _jsx("strong", { children: inviteEmail }), inviteWorkspaceId ? ` · Workspace: ${inviteWorkspaceId}` : ''] }))] })), loginNotice && _jsx("p", { className: authStyles.loginSubtitle, children: loginNotice }), loginError && _jsx("p", { className: authStyles.loginError, children: loginError }), authMode === 'login' && loginErrorCode === 'EMAIL_NOT_VERIFIED' && loginEmail.trim() && (_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || verifyCooldownActive, onClick: async () => {
|
|
668
945
|
setLoginBusy(true);
|
|
669
946
|
setLoginNotice(null);
|
|
670
947
|
const sent = await requestEmailVerification(loginEmail);
|
|
@@ -673,7 +950,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
673
950
|
setLoginMode('verify');
|
|
674
951
|
if (sent.verificationToken)
|
|
675
952
|
setVerificationToken(sent.verificationToken);
|
|
676
|
-
setLoginNotice(
|
|
953
|
+
setLoginNotice(buildVerificationRequestNotice(sent));
|
|
677
954
|
setLoginError(null);
|
|
678
955
|
setLoginErrorCode(null);
|
|
679
956
|
}
|
|
@@ -681,180 +958,232 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
681
958
|
setLoginError(formatAuthError(sent));
|
|
682
959
|
}
|
|
683
960
|
setLoginBusy(false);
|
|
684
|
-
}, children: verifyCooldownActive ? `Resend in ${verifyCooldownSeconds}s` : 'Resend Verification Email' })), _jsx("button", { className: `${styles.submitBtn} ${authStyles.loginPrimaryBtn}`, disabled: loginBusy || (authMode === '
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
result = await loginWithCredentials(loginEmail, loginPassword);
|
|
697
|
-
}
|
|
698
|
-
else if (authMode === 'register') {
|
|
699
|
-
result = await registerWithCredentials(loginEmail, loginPassword, {
|
|
700
|
-
displayName: loginDisplayName,
|
|
701
|
-
planId: selectedSignupPlanId || undefined,
|
|
702
|
-
planVersionId: selectedSignupPlanVersionId || undefined,
|
|
703
|
-
interval: selectedSignupInterval || undefined
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
else if (authMode === 'verify') {
|
|
707
|
-
result = await confirmEmailVerification(verificationToken);
|
|
708
|
-
}
|
|
709
|
-
else if (authMode === 'forgot') {
|
|
710
|
-
result = await requestPasswordReset(loginEmail);
|
|
711
|
-
}
|
|
712
|
-
else if (authMode === 'reset') {
|
|
713
|
-
result = await confirmPasswordReset(resetToken, loginPassword);
|
|
714
|
-
}
|
|
715
|
-
else if (authMode === 'invite') {
|
|
716
|
-
if (inviteResolution === 'existing_user_signed_out') {
|
|
961
|
+
}, children: verifyCooldownActive ? `Resend in ${verifyCooldownSeconds}s` : 'Resend Verification Email' })), _jsx("div", { className: authStyles.authPrimaryActions, role: "group", "aria-label": "Primary authentication action", children: _jsx("button", { className: `${styles.submitBtn} ${authStyles.loginPrimaryBtn}`, disabled: loginBusy || (authMode === 'forgot' && resetCooldownActive), "data-testid": "auth-submit-button", onClick: async () => {
|
|
962
|
+
setLoginBusy(true);
|
|
963
|
+
setLoginNotice(null);
|
|
964
|
+
setLoginError(null);
|
|
965
|
+
setLoginErrorCode(null);
|
|
966
|
+
if (authMode === 'register' && !loginDisplayName.trim()) {
|
|
967
|
+
setLoginError('Display name is required.');
|
|
968
|
+
setLoginBusy(false);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
let result = { success: false };
|
|
972
|
+
if (authMode === 'login') {
|
|
717
973
|
result = await loginWithCredentials(loginEmail, loginPassword);
|
|
718
974
|
}
|
|
719
|
-
else {
|
|
720
|
-
result =
|
|
721
|
-
|
|
722
|
-
:
|
|
975
|
+
else if (authMode === 'register') {
|
|
976
|
+
result = await registerWithCredentials(loginEmail, loginPassword, {
|
|
977
|
+
displayName: loginDisplayName,
|
|
978
|
+
planId: selectedSignupPlanId || undefined,
|
|
979
|
+
planVersionId: selectedSignupPlanVersionId || undefined,
|
|
980
|
+
interval: selectedSignupInterval || undefined
|
|
981
|
+
});
|
|
723
982
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
if (authMode === 'register'
|
|
727
|
-
&& (result.code === 'SIGNUP_PLAN_NOT_ENABLED'
|
|
728
|
-
|| result.code === 'SIGNUP_PLAN_VERSION_NOT_FOUND'
|
|
729
|
-
|| result.code === 'SIGNUP_PLAN_VERSION_REQUIRED')) {
|
|
730
|
-
navigate(planSelectionPath, { replace: true });
|
|
731
|
-
setLoginBusy(false);
|
|
732
|
-
return;
|
|
983
|
+
else if (authMode === 'verify') {
|
|
984
|
+
result = await confirmEmailVerification(verificationToken);
|
|
733
985
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
986
|
+
else if (authMode === 'forgot') {
|
|
987
|
+
result = await requestPasswordReset(loginEmail);
|
|
988
|
+
}
|
|
989
|
+
else if (authMode === 'reset') {
|
|
990
|
+
result = await confirmPasswordReset(resetToken, loginPassword);
|
|
991
|
+
}
|
|
992
|
+
else if (authMode === 'invite') {
|
|
993
|
+
if (inviteResolution === 'existing_user_signed_out') {
|
|
994
|
+
result = await loginWithCredentials(loginEmail, loginPassword);
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
result = invitePasswordRequired
|
|
998
|
+
? await acceptInviteWithToken(inviteToken, loginPassword)
|
|
999
|
+
: await joinInviteWithToken(inviteToken);
|
|
1000
|
+
}
|
|
740
1001
|
}
|
|
741
|
-
if (
|
|
742
|
-
|
|
743
|
-
|
|
1002
|
+
if (!result.success) {
|
|
1003
|
+
if (authMode === 'register'
|
|
1004
|
+
&& (result.code === 'SIGNUP_PLAN_NOT_ENABLED'
|
|
1005
|
+
|| result.code === 'SIGNUP_PLAN_VERSION_NOT_FOUND'
|
|
1006
|
+
|| result.code === 'SIGNUP_PLAN_VERSION_REQUIRED')) {
|
|
1007
|
+
navigate(planSelectionPath, { replace: true });
|
|
1008
|
+
setLoginBusy(false);
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
setLoginError(formatAuthError(result));
|
|
1012
|
+
setLoginErrorCode(result.code || null);
|
|
1013
|
+
if (authMode === 'login' && (result.code === 'WORKSPACE_NOT_FOUND' || result.code === 'WORKSPACE_ID_REQUIRED')) {
|
|
1014
|
+
navigate('/setup?step=workspace', { replace: true });
|
|
1015
|
+
setLoginBusy(false);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
if (authMode === 'login' && result.code === 'EMAIL_NOT_VERIFIED' && loginEmail.trim()) {
|
|
1019
|
+
const sent = await requestEmailVerification(loginEmail);
|
|
1020
|
+
if (sent.success) {
|
|
1021
|
+
setVerifyCooldownUntil(Date.now() + 30_000);
|
|
1022
|
+
setLoginMode('verify');
|
|
1023
|
+
if (sent.verificationToken)
|
|
1024
|
+
setVerificationToken(sent.verificationToken);
|
|
1025
|
+
setLoginNotice(buildVerificationRequestNotice({
|
|
1026
|
+
...sent,
|
|
1027
|
+
intro: 'If that account exists and still needs verification, we sent an email.'
|
|
1028
|
+
}));
|
|
1029
|
+
setLoginError(null);
|
|
1030
|
+
setLoginErrorCode(null);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
if (authMode === 'register' && result.code === 'EMAIL_ALREADY_EXISTS_UNVERIFIED' && loginEmail.trim()) {
|
|
744
1034
|
setVerifyCooldownUntil(Date.now() + 30_000);
|
|
745
1035
|
setLoginMode('verify');
|
|
746
|
-
if (
|
|
747
|
-
setVerificationToken(
|
|
748
|
-
setLoginNotice(
|
|
1036
|
+
if (result.verificationToken)
|
|
1037
|
+
setVerificationToken(result.verificationToken);
|
|
1038
|
+
setLoginNotice(result.emailSent === false
|
|
1039
|
+
? `This email already has an unverified account, but verification email delivery failed. ${result.emailError || 'Try Resend Verification again.'}`
|
|
1040
|
+
: 'This email already has an unverified account. Check your email for verification instructions.');
|
|
749
1041
|
setLoginError(null);
|
|
750
1042
|
setLoginErrorCode(null);
|
|
751
1043
|
}
|
|
752
1044
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
1045
|
+
else if (authMode === 'register') {
|
|
1046
|
+
const postRegistrationPath = resolvePostRegistrationPath(result);
|
|
1047
|
+
const commercialOnboardingGate = hasCommercialOnboardingGate(result);
|
|
1048
|
+
if (result.verificationRequired) {
|
|
1049
|
+
setPendingVerificationWorkspaceSetupRequired(Boolean(result.workspaceSetupRequired));
|
|
1050
|
+
setPendingVerificationCommercialState(commercialOnboardingGate
|
|
1051
|
+
? String(result.commercialState || '').trim() || (result.planSelectionRequired ? 'pending_plan_selection' : 'checkout_pending')
|
|
1052
|
+
: null);
|
|
1053
|
+
setPendingVerificationRedirectPath(postRegistrationPath);
|
|
1054
|
+
if (result.verificationToken)
|
|
1055
|
+
setVerificationToken(result.verificationToken);
|
|
1056
|
+
setVerifyCooldownUntil(Date.now() + 30_000);
|
|
1057
|
+
setLoginMode('verify');
|
|
1058
|
+
setLoginNotice(result.emailSent === false
|
|
1059
|
+
? `Account created, but verification email failed to send. ${result.emailError || 'Try Resend Verification again.'}`
|
|
1060
|
+
: 'Account created. Check your email for verification instructions.');
|
|
1061
|
+
}
|
|
1062
|
+
else {
|
|
1063
|
+
setPendingPostAuthRedirect({
|
|
1064
|
+
path: postRegistrationPath,
|
|
1065
|
+
workspaceSetupRequired: Boolean(result.workspaceSetupRequired),
|
|
1066
|
+
commercialOnboardingGate
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
766
1069
|
}
|
|
767
|
-
else {
|
|
1070
|
+
else if (authMode === 'verify') {
|
|
768
1071
|
await retryBootstrapChecks();
|
|
769
|
-
|
|
1072
|
+
const commercialOnboardingGate = pendingVerificationCommercialState === 'pending_plan_selection'
|
|
1073
|
+
|| pendingVerificationCommercialState === 'checkout_pending';
|
|
1074
|
+
setPendingPostAuthRedirect({
|
|
1075
|
+
path: pendingVerificationRedirectPath || nextPath,
|
|
1076
|
+
workspaceSetupRequired: pendingVerificationWorkspaceSetupRequired,
|
|
1077
|
+
commercialOnboardingGate
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
else if (authMode === 'forgot') {
|
|
1081
|
+
const passwordResetDeliveryFailed = result.deliveryAttempted === true && result.emailSent === false;
|
|
1082
|
+
if (result.resetToken)
|
|
1083
|
+
setResetToken(result.resetToken);
|
|
1084
|
+
setLoginNotice(buildPasswordResetNotice(result));
|
|
1085
|
+
if (!passwordResetDeliveryFailed) {
|
|
1086
|
+
setResetCooldownUntil(Date.now() + 30_000);
|
|
1087
|
+
setLoginMode('reset');
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
else if (authMode === 'reset') {
|
|
1091
|
+
setLoginMode('login');
|
|
1092
|
+
setLoginNotice('Password updated. Sign in with your new password.');
|
|
1093
|
+
}
|
|
1094
|
+
else if (authMode === 'invite') {
|
|
1095
|
+
if (inviteResolution === 'existing_user_signed_out') {
|
|
1096
|
+
const normalizedInviteEmail = String(inviteEmail || '').trim().toLowerCase();
|
|
1097
|
+
const normalizedLoginEmail = loginEmail.trim().toLowerCase();
|
|
1098
|
+
// Make the invite flow deterministically join-ready as soon as
|
|
1099
|
+
// the invited account signs in successfully, instead of waiting
|
|
1100
|
+
// for a later invite inspection effect to catch up.
|
|
1101
|
+
if (normalizedInviteEmail && normalizedLoginEmail === normalizedInviteEmail) {
|
|
1102
|
+
setAuthMode('invite');
|
|
1103
|
+
setInviteResolution('existing_user_ready');
|
|
1104
|
+
setLoginPassword('');
|
|
1105
|
+
setLoginNotice('Signed in. Review the invite details to continue.');
|
|
1106
|
+
setLoginError(null);
|
|
1107
|
+
setLoginErrorCode(null);
|
|
1108
|
+
setLoginBusy(false);
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
if (normalizedInviteEmail) {
|
|
1112
|
+
setLoginEmail(inviteEmail || '');
|
|
1113
|
+
}
|
|
1114
|
+
setInviteResolution('existing_user_signed_out');
|
|
1115
|
+
setLoginPassword('');
|
|
1116
|
+
setLoginNotice(normalizedInviteEmail
|
|
1117
|
+
? `Signed in, but this invite is for ${inviteEmail}. Sign in with that account to continue.`
|
|
1118
|
+
: 'Signed in, but this invite requires the invited account. Sign in with that account to continue.');
|
|
1119
|
+
setLoginError('Sign in with the invited account to join this workspace.');
|
|
1120
|
+
setLoginErrorCode('INVITE_ACCOUNT_MISMATCH');
|
|
1121
|
+
setLoginBusy(false);
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
if (result.workspaceSetupRequired) {
|
|
1125
|
+
navigate(resolveWorkspaceSetupPath(), { replace: true });
|
|
1126
|
+
setLoginBusy(false);
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
navigate(nextPath, { replace: true });
|
|
1130
|
+
}
|
|
1131
|
+
else if (authMode === 'login') {
|
|
1132
|
+
if (inviteToken.trim()) {
|
|
1133
|
+
setAuthMode('invite');
|
|
1134
|
+
setLoginPassword('');
|
|
1135
|
+
setLoginNotice('Signed in. Review the invite details to continue.');
|
|
1136
|
+
setLoginBusy(false);
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
if (result.workspaceSetupRequired) {
|
|
1140
|
+
navigate(resolveWorkspaceSetupPath(), { replace: true });
|
|
770
1141
|
setLoginBusy(false);
|
|
771
1142
|
return;
|
|
772
1143
|
}
|
|
773
1144
|
setLoginEmail('');
|
|
774
1145
|
setLoginDisplayName('');
|
|
775
1146
|
setLoginPassword('');
|
|
776
|
-
navigate(
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
else if (authMode === 'verify') {
|
|
780
|
-
await retryBootstrapChecks();
|
|
781
|
-
if (await routeToWorkspaceSetupIfNeeded(pendingVerificationWorkspaceSetupRequired)) {
|
|
782
|
-
setLoginBusy(false);
|
|
783
|
-
return;
|
|
784
|
-
}
|
|
785
|
-
setLoginEmail('');
|
|
786
|
-
setLoginPassword('');
|
|
787
|
-
navigate(pendingVerificationRedirectPath || nextPath, { replace: true });
|
|
788
|
-
}
|
|
789
|
-
else if (authMode === 'forgot') {
|
|
790
|
-
if (result.resetToken)
|
|
791
|
-
setResetToken(result.resetToken);
|
|
792
|
-
setResetCooldownUntil(Date.now() + 30_000);
|
|
793
|
-
setLoginMode('reset');
|
|
794
|
-
setLoginNotice(result.emailSent === false
|
|
795
|
-
? `Password reset email failed to send. ${result.emailError || 'Try again in a minute.'}`
|
|
796
|
-
: 'Password reset instructions sent.');
|
|
797
|
-
}
|
|
798
|
-
else if (authMode === 'reset') {
|
|
799
|
-
setLoginMode('login');
|
|
800
|
-
setLoginNotice('Password updated. Sign in with your new password.');
|
|
801
|
-
}
|
|
802
|
-
else if (authMode === 'invite') {
|
|
803
|
-
if (inviteResolution === 'existing_user_signed_out') {
|
|
804
|
-
setAuthMode('invite');
|
|
805
|
-
setLoginPassword('');
|
|
806
|
-
setLoginNotice('Signed in. Review the invite details to continue.');
|
|
807
|
-
setLoginBusy(false);
|
|
808
|
-
return;
|
|
1147
|
+
navigate(nextPath, { replace: true });
|
|
809
1148
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
|
-
navigate(nextPath, { replace: true });
|
|
816
|
-
}
|
|
817
|
-
else if (authMode === 'login') {
|
|
818
|
-
if (inviteToken.trim()) {
|
|
819
|
-
setAuthMode('invite');
|
|
1149
|
+
else {
|
|
1150
|
+
setLoginEmail('');
|
|
1151
|
+
setLoginDisplayName('');
|
|
820
1152
|
setLoginPassword('');
|
|
821
|
-
|
|
822
|
-
setLoginBusy(false);
|
|
823
|
-
return;
|
|
1153
|
+
navigate(nextPath, { replace: true });
|
|
824
1154
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
}, children: authMode === 'login' ? 'Register' : 'Sign In' })] })), _jsxs("div", { className: authStyles.authModeLinks, children: [(authMode === 'login' || authMode === 'register') && (_jsxs(_Fragment, { children: [_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || resetCooldownActive, onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('forgot'); }, children: resetCooldownActive ? `Forgot Password (${resetCooldownSeconds}s)` : 'Forgot Password' }), _jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || verifyCooldownActive, onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('verify'); }, children: verifyCooldownActive ? `Resend Verification (${verifyCooldownSeconds}s)` : 'Resend Verification' }), _jsx("button", { className: authStyles.authModeLink, disabled: loginBusy, onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('invite'); }, children: "Accept Invite" })] })), (authMode !== 'login' && authMode !== 'register') && (_jsxs(_Fragment, { children: [_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy, onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('login'); }, children: "Sign In" }), directRegistrationEnabled && (_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy, onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('register'); }, children: "Register" })), authMode === 'reset' && (_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || resetCooldownActive, onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('forgot'); }, children: resetCooldownActive ? `Forgot Password (${resetCooldownSeconds}s)` : 'Forgot Password' }))] }))] })] }) }) }));
|
|
1155
|
+
setLoginBusy(false);
|
|
1156
|
+
}, children: loginBusy ? 'Working...' : (authMode === 'login' ? 'Sign In'
|
|
1157
|
+
: authMode === 'register' ? 'Create Account'
|
|
1158
|
+
: authMode === 'verify' ? 'Verify Email'
|
|
1159
|
+
: authMode === 'forgot'
|
|
1160
|
+
? (resetCooldownActive ? `Retry in ${resetCooldownSeconds}s` : 'Send Reset Link')
|
|
1161
|
+
: authMode === 'reset'
|
|
1162
|
+
? 'Reset Password'
|
|
1163
|
+
: (inviteResolution === 'existing_user_signed_out'
|
|
1164
|
+
? 'Sign In to Continue'
|
|
1165
|
+
: (invitePasswordRequired ? 'Create Account and Join' : 'Join Workspace'))) }) }), authMode === 'register' && (_jsxs("p", { className: authStyles.registerConsent, children: ["By creating an account, you agree to the", ' ', _jsx("a", { className: authStyles.registerConsentLink, href: "https://taskforcehq.com/legal/terms", target: "_blank", rel: "noopener noreferrer", children: "Terms of Service" }), ' ', "and acknowledge the", ' ', _jsx("a", { className: authStyles.registerConsentLink, href: "https://taskforcehq.com/legal/privacy", target: "_blank", rel: "noopener noreferrer", children: "Privacy Policy" }), "."] })), _jsxs("div", { className: authStyles.authSecondaryActions, role: "group", "aria-label": "Authentication navigation", children: [directRegistrationEnabled && (authMode === 'login' || authMode === 'register') && (_jsxs("p", { className: authStyles.authModeSwitch, children: [authMode === 'login' ? 'Need an account?' : 'Already have an account?', ' ', _jsx("button", { className: authStyles.authModeSwitchLink, disabled: loginBusy, "data-testid": "auth-switch-mode-button", onClick: () => {
|
|
1166
|
+
setLoginError(null);
|
|
1167
|
+
setLoginErrorCode(null);
|
|
1168
|
+
setLoginNotice(null);
|
|
1169
|
+
setLoginMode(authMode === 'login' ? 'register' : 'login');
|
|
1170
|
+
}, children: authMode === 'login' ? 'Register' : 'Sign In' })] })), _jsxs("div", { className: authStyles.authModeLinks, children: [(authMode === 'login' || authMode === 'register') && (_jsxs(_Fragment, { children: [_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || resetCooldownActive, "data-testid": "auth-forgot-password-button", onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('forgot'); }, children: resetCooldownActive ? `Forgot Password (${resetCooldownSeconds}s)` : 'Forgot Password' }), _jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || verifyCooldownActive, "data-testid": "auth-resend-verification-button", onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('verify'); }, children: verifyCooldownActive ? `Resend Verification (${verifyCooldownSeconds}s)` : 'Resend Verification' }), _jsx("button", { className: authStyles.authModeLink, disabled: loginBusy, "data-testid": "auth-accept-invite-button", onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('invite'); }, children: "Accept Invite" })] })), (authMode !== 'login' && authMode !== 'register') && (_jsxs(_Fragment, { children: [authMode === 'verify' && (_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || verifyCooldownActive || !loginEmail.trim(), "data-testid": "auth-resend-verification-button", onClick: async () => {
|
|
1171
|
+
setLoginBusy(true);
|
|
1172
|
+
setLoginNotice(null);
|
|
1173
|
+
setLoginError(null);
|
|
1174
|
+
setLoginErrorCode(null);
|
|
1175
|
+
const sent = await requestEmailVerification(loginEmail);
|
|
1176
|
+
if (sent.success) {
|
|
1177
|
+
setVerifyCooldownUntil(Date.now() + 30_000);
|
|
1178
|
+
if (sent.verificationToken)
|
|
1179
|
+
setVerificationToken(sent.verificationToken);
|
|
1180
|
+
setLoginNotice(buildVerificationRequestNotice(sent));
|
|
1181
|
+
}
|
|
1182
|
+
else {
|
|
1183
|
+
setLoginError(formatAuthError(sent));
|
|
1184
|
+
}
|
|
1185
|
+
setLoginBusy(false);
|
|
1186
|
+
}, children: verifyCooldownActive ? `Resend Verification (${verifyCooldownSeconds}s)` : 'Resend Verification' })), _jsx("button", { className: authStyles.authModeLink, disabled: loginBusy, "data-testid": "auth-return-sign-in-button", onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('login'); }, children: authMode === 'verify' ? 'Back to Sign In' : 'Sign In' }), directRegistrationEnabled && authMode !== 'verify' && (_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy, "data-testid": "auth-return-register-button", onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('register'); }, children: "Register" })), authMode === 'reset' && (_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || resetCooldownActive, "data-testid": "auth-return-forgot-password-button", onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('forgot'); }, children: resetCooldownActive ? `Forgot Password (${resetCooldownSeconds}s)` : 'Forgot Password' }))] }))] })] })] }) }) }));
|
|
858
1187
|
}
|
|
859
1188
|
if (shouldForceLogin && !isLoginRoute) {
|
|
860
1189
|
return (_jsx("div", { className: shellStyles.standaloneWrapper, "data-theme": currentTheme, children: _jsx("div", { className: authStyles.loginView, children: _jsxs("div", { className: authStyles.loginCard, children: [_jsxs("div", { className: authStyles.authBrandRow, children: [_jsx("img", { src: authBrandLogo, alt: "Taskforce", className: authStyles.loginLogo }), _jsx("h1", { className: authStyles.loginTitle, children: "Loading Taskforce" })] }), _jsx("p", { className: authStyles.loginSubtitle, children: "Redirecting to sign in..." })] }) }) }));
|
|
@@ -865,6 +1194,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
865
1194
|
}, children: _jsxs("div", { className: authStyles.loginCard, children: [_jsxs("div", { className: authStyles.authBrandRow, children: [_jsx("img", { src: authBrandLogo, alt: "Taskforce", className: authStyles.loginLogo }), _jsx("h1", { className: authStyles.loginTitle, children: "Private Beta" })] }), _jsx("p", { className: authStyles.loginSubtitle, children: "You are signed in, but your account is not in the beta allowlist yet." }), _jsx("p", { className: authStyles.loginSubtitle, children: "You will see the full app as soon as beta access is enabled." }), _jsx("button", { className: styles.cancelBtn, onClick: async () => { await taskforceState.logout(); navigate('/login', { replace: true }); }, children: "Sign Out" })] }) }) }));
|
|
866
1195
|
}
|
|
867
1196
|
const showGuidedWorkspaceSetup = isCreateWorkspaceIntent
|
|
1197
|
+
|| (isSetupRoute && Boolean(activeCloudWorkspaceImport))
|
|
868
1198
|
|| (isSetupRoute && setupGateActive && (hasSetupReadError || needsGlobalSetup || needsWorkspaceSetup));
|
|
869
1199
|
if (showGuidedWorkspaceSetup) {
|
|
870
1200
|
const isError = !isCreateWorkspaceIntent && (hasSetupReadError || setupStep === 'error');
|
|
@@ -873,25 +1203,52 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
873
1203
|
? t('setup.headingCheckFailed')
|
|
874
1204
|
: (isGlobal
|
|
875
1205
|
? t('setup.headingGlobalRequired')
|
|
876
|
-
:
|
|
1206
|
+
: t('setup.headingWorkspaceRequired'));
|
|
877
1207
|
const subtitle = isError
|
|
878
1208
|
? t('setup.subtitleUnreadable')
|
|
879
1209
|
: isGlobal
|
|
880
1210
|
? t('setup.subtitleGlobalRequired')
|
|
881
|
-
:
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
const
|
|
885
|
-
|
|
886
|
-
|
|
1211
|
+
: null;
|
|
1212
|
+
const workspaceLabel = 'Workspace';
|
|
1213
|
+
const isLocalWorkspaceSetup = isCloudRuntime || workspaceSetupSource !== 'cloud';
|
|
1214
|
+
const isWorkspaceDetailsPage = isLocalWorkspaceSetup && workspaceSetupPage === 'details';
|
|
1215
|
+
const isWorkspaceLibraryPage = isLocalWorkspaceSetup && workspaceSetupPage === 'library';
|
|
1216
|
+
const showWorkspaceSourceSelector = !isCloudRuntime
|
|
1217
|
+
&& canUseCloudWorkspaceSetup
|
|
1218
|
+
&& isWorkspaceDetailsPage
|
|
1219
|
+
&& !activeCloudWorkspaceImport;
|
|
1220
|
+
const runtimeIcon = runtimeCapabilities?.runtimeMode === 'cloud' ? Cloud : LaptopMinimal;
|
|
1221
|
+
const starterCategoryItems = selectedSetupPack ? formatStarterSectionItems(selectedSetupPack.categories) : '';
|
|
1222
|
+
const starterTypeItems = selectedSetupPack ? formatStarterSectionItems(selectedSetupPack.types) : '';
|
|
1223
|
+
const starterPriorityItems = selectedSetupPack ? formatStarterSectionItems(selectedSetupPack.priorities) : '';
|
|
1224
|
+
const setupPrimaryActionLabel = setupBusy
|
|
1225
|
+
? t('setup.saving')
|
|
1226
|
+
: wantsCloudWorkspaceSetup
|
|
1227
|
+
? (activeCloudWorkspaceImport
|
|
1228
|
+
? (isCloudWorkspaceImportErrored ? 'Retry Import' : 'Importing Workspace...')
|
|
1229
|
+
: 'Connect Workspace')
|
|
1230
|
+
: isWorkspaceDetailsPage
|
|
1231
|
+
? 'Continue'
|
|
1232
|
+
: isWorkspaceLibraryPage
|
|
1233
|
+
? 'Create Workspace'
|
|
1234
|
+
: 'Connect Workspace';
|
|
887
1235
|
return (_jsx("div", { className: shellStyles.standaloneWrapper, "data-theme": currentTheme, children: _jsx("div", { className: authStyles.loginView, style: {
|
|
888
1236
|
backgroundImage: `linear-gradient(rgba(15, 15, 26, 0.88), rgba(15, 15, 26, 0.9)), url(${taskforceBg})`
|
|
889
|
-
}, children: _jsxs("div", { className: authStyles.loginCard, children: [_jsxs("div", { className: authStyles.authBrandRow, children: [_jsx("img", { src: authBrandLogo, alt: "Taskforce", className: authStyles.loginLogo }), _jsxs("h1", { className: authStyles.loginTitle, children: ["TASKFORCE ", _jsx("span", { className: authStyles.loginTitleAccent, children: "HQ" })] })] }), _jsx("
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
1237
|
+
}, children: _jsxs("div", { className: authStyles.loginCard, children: [_jsxs("div", { className: authStyles.setupHeaderRow, children: [_jsxs("div", { className: authStyles.authBrandRow, children: [_jsx("img", { src: authBrandLogo, alt: "Taskforce", className: authStyles.loginLogo }), _jsxs("h1", { className: authStyles.loginTitle, children: ["TASKFORCE ", _jsx("span", { className: authStyles.loginTitleAccent, children: "HQ" })] })] }), runtimeCapabilities && (_jsx("span", { className: authStyles.runtimeIconBadge, title: runtimeCapabilities.runtimeMode === 'cloud' ? 'Cloud runtime' : 'Local runtime', "aria-label": runtimeCapabilities.runtimeMode === 'cloud' ? 'Cloud runtime' : 'Local runtime', children: React.createElement(runtimeIcon, { size: 16, 'aria-hidden': true }) }))] }), _jsx("p", { className: authStyles.loginSubtitle, children: _jsx("strong", { children: heading }) }), subtitle ? _jsx("p", { className: authStyles.loginSubtitle, children: subtitle }) : null, setupNotice && _jsx("p", { className: authStyles.loginSubtitle, children: setupNotice }), setupError && _jsx("p", { className: authStyles.loginError, children: setupError }), isGlobal && (_jsxs("div", { className: authStyles.optionGrid, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "setup-mode", value: "core", checked: setupModeChoice === 'core', onChange: () => setSetupModeChoice('core'), disabled: setupBusy }), _jsx("span", { children: t('setup.coreModeOption', { workspaceLabel }) })] }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "setup-mode", value: "operations", checked: setupModeChoice === 'operations', onChange: () => setSetupModeChoice('operations'), disabled: setupBusy }), _jsx("span", { children: t('setup.operationsModeOption') })] })] })), !isError && !isGlobal && (_jsxs("div", { className: authStyles.optionGrid, children: [showWorkspaceSourceSelector && (_jsxs("div", { className: authStyles.optionGroup, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "workspace-setup-source", value: "local", checked: workspaceSetupSource === 'local', onChange: () => setWorkspaceSetupSource('local'), disabled: setupBusy }), _jsxs("span", { children: ["Create new local ", workspaceLabel.toLowerCase()] })] }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "workspace-setup-source", value: "cloud", checked: workspaceSetupSource === 'cloud', onChange: () => setWorkspaceSetupSource('cloud'), disabled: setupBusy }), _jsxs("span", { children: ["Sync existing cloud ", workspaceLabel.toLowerCase()] })] })] })), wantsCloudWorkspaceSetup ? (_jsxs("div", { className: authStyles.optionGroup, children: [!activeCloudWorkspaceImport && !canUseCloudWorkspaceSetup && (_jsx("p", { className: authStyles.loginError, children: "Cloud workspace sync is unavailable right now. Switch back to local setup or refresh runtime configuration." })), !activeCloudWorkspaceImport && canUseCloudWorkspaceSetup && !isAuthenticated && (_jsxs(_Fragment, { children: [_jsx("p", { className: authStyles.inlineHint, children: "Sign in to choose one of your cloud projects." }), _jsxs("div", { className: authStyles.actionRowEnd, children: [_jsx("button", { className: styles.cancelBtn, disabled: setupBusy, onClick: () => {
|
|
1238
|
+
navigate(`/login?mode=login&next=${encodeURIComponent(cloudWorkspaceSelectionPath)}`, { replace: true });
|
|
1239
|
+
}, children: "Sign In" }), directRegistrationEnabled && (_jsx("button", { className: styles.submitBtn, disabled: setupBusy, onClick: () => {
|
|
1240
|
+
navigate(`/login?mode=register&next=${encodeURIComponent(cloudWorkspaceSelectionPath)}`, { replace: true });
|
|
1241
|
+
}, children: "Register" }))] })] })), !activeCloudWorkspaceImport && canUseCloudWorkspaceSetup && isAuthenticated && (_jsxs(_Fragment, { children: [_jsxs("select", { className: styles.input, value: cloudWorkspaceSelection, onChange: (event) => setCloudWorkspaceSelection(event.target.value), disabled: setupBusy || cloudWorkspaceLoading || cloudWorkspaceOptions.length === 0, children: [cloudWorkspaceOptions.length === 0 && (_jsx("option", { value: "", children: cloudWorkspaceLoading ? 'Loading cloud projects...' : 'No cloud projects found' })), cloudWorkspaceOptions.map((workspace) => (_jsxs("option", { value: workspace.id, children: [workspace.name, " (", workspace.id, ")"] }, workspace.id)))] }), _jsx("div", { className: authStyles.actionRowEnd, children: _jsx("button", { className: styles.cancelBtn, disabled: setupBusy || cloudWorkspaceLoading, onClick: () => void fetchCloudWorkspaceOptions(), children: "Refresh Cloud Projects" }) }), cloudWorkspaceError && _jsx("p", { className: authStyles.loginError, children: cloudWorkspaceError })] })), activeCloudWorkspaceImport && (_jsxs(_Fragment, { children: [_jsxs("p", { className: authStyles.inlineHint, children: ["Importing \"", activeCloudWorkspaceImport.name, "\" from cloud into local."] }), _jsx("div", { className: authStyles.optionGroup, children: cloudWorkspaceImportProgressLines.map((line) => (_jsx("p", { className: authStyles.inlineHint, children: line }, line))) }), _jsx("p", { className: authStyles.inlineHint, children: cloudWorkspaceImportFinalizing
|
|
1242
|
+
? 'Finishing setup and opening your workspace.'
|
|
1243
|
+
: (workspaceSyncSummary || 'Pulling tasks, taxonomies, and settings into local.') }), !cloudWorkspaceImportFinalizing && workspaceSyncRecommendedAction && (_jsx("p", { className: authStyles.inlineHint, children: workspaceSyncRecommendedAction }))] }))] })) : (_jsxs(_Fragment, { children: [isWorkspaceDetailsPage && (_jsxs(_Fragment, { children: [_jsx("p", { className: authStyles.loginSubtitle, children: "Workspace name" }), _jsx("input", { className: styles.input, type: "text", value: workspaceNameInput, placeholder: "Workspace name", onChange: (event) => setWorkspaceNameInput(event.target.value), disabled: setupBusy }), _jsx("p", { className: authStyles.loginSubtitle, children: "Workspace description" }), _jsx("textarea", { className: styles.textarea, value: workspaceDescriptionInput, placeholder: "Workspace description (optional)", onChange: (event) => setWorkspaceDescriptionInput(event.target.value), disabled: setupBusy, rows: 4 })] })), isWorkspaceLibraryPage && (_jsxs("div", { className: authStyles.optionGroup, children: [_jsx("p", { className: authStyles.loginSubtitle, children: "What are you working on?" }), _jsxs("select", { className: styles.input, value: selectedSetupPackId, onChange: (event) => setSelectedSetupPackId(event.target.value), disabled: setupBusy || setupLibraryLoading || setupLibraryPacks.length === 0, children: [setupLibraryPacks.length === 0 && (_jsx("option", { value: "", children: setupLibraryLoading ? 'Loading setup options...' : 'No setup options available' })), setupLibraryPacks.map((pack) => (_jsx("option", { value: pack.id, children: pack.label }, pack.id)))] }), selectedSetupPack ? (_jsx(_Fragment, { children: selectedSetupPack.description ? (_jsx("p", { className: authStyles.inlineHint, children: selectedSetupPack.description })) : null })) : null, _jsxs("div", { className: authStyles.optionGroup, children: [_jsx("p", { className: authStyles.loginSubtitle, children: "Apply Setup Sections" }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: setupTaxonomySections.categories, onChange: () => setSetupTaxonomySections((current) => ({ ...current, categories: !current.categories })), disabled: setupBusy || !selectedSetupPack?.categories.length }), _jsx("span", { children: "Categories" })] }), starterCategoryItems ? (_jsx("p", { className: authStyles.inlineHint, children: starterCategoryItems })) : null, _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: setupTaxonomySections.types, onChange: () => setSetupTaxonomySections((current) => ({ ...current, types: !current.types })), disabled: setupBusy || !selectedSetupPack?.types.length }), _jsx("span", { children: "Task Types" })] }), starterTypeItems ? (_jsx("p", { className: authStyles.inlineHint, children: starterTypeItems })) : null, _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: setupTaxonomySections.priorities, onChange: () => setSetupTaxonomySections((current) => ({ ...current, priorities: !current.priorities })), disabled: setupBusy || !selectedSetupPack?.priorities.length }), _jsx("span", { children: "Priorities" })] }), starterPriorityItems ? (_jsx("p", { className: authStyles.inlineHint, children: starterPriorityItems })) : null] }), setupLibraryLoading ? (_jsx("p", { className: authStyles.inlineHint, children: "Loading setup options\u2026" })) : null, setupLibraryError ? (_jsx("p", { className: authStyles.loginError, children: setupLibraryError })) : null] })), !isCloudRuntime && canUseCloudWorkspaceSetup && isAuthenticated && isWorkspaceDetailsPage && (_jsxs("div", { className: authStyles.optionGroup, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: syncNewLocalWorkspaceToCloud, onChange: (event) => setSyncNewLocalWorkspaceToCloud(event.target.checked), disabled: setupBusy }), _jsx("span", { children: "Sync to cloud after setup" })] }), _jsx("p", { className: authStyles.inlineHint, children: "When enabled, this project will connect to cloud sync as soon as setup finishes." })] }))] }))] })), _jsxs("div", { className: authStyles.authModeLinks, children: [isWorkspaceLibraryPage ? (_jsx("button", { className: styles.cancelBtn, onClick: () => {
|
|
1244
|
+
setSetupError(null);
|
|
1245
|
+
setSetupNotice(null);
|
|
1246
|
+
setWorkspaceSetupPage('details');
|
|
1247
|
+
}, children: "Back" })) : isCreateWorkspaceIntent ? (_jsx("button", { className: styles.cancelBtn, onClick: () => {
|
|
1248
|
+
setSetupError(null);
|
|
1249
|
+
setSetupNotice(null);
|
|
1250
|
+
navigate('/', { replace: true });
|
|
1251
|
+
}, children: "Cancel" })) : runtimeMode === 'cloud' ? (_jsx("button", { className: styles.cancelBtn, onClick: async () => { await taskforceState.logout(); navigate('/login', { replace: true }); }, children: t('setup.signOut') })) : null, isGlobal ? (_jsx("button", { className: styles.submitBtn, disabled: setupBusy || (isCloudWorkspaceSetupFlow && Boolean(activeCloudWorkspaceImport) && !isCloudWorkspaceImportErrored), onClick: async () => {
|
|
895
1252
|
setSetupBusy(true);
|
|
896
1253
|
setSetupNotice(null);
|
|
897
1254
|
setSetupError(null);
|
|
@@ -917,11 +1274,32 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
917
1274
|
finally {
|
|
918
1275
|
setSetupBusy(false);
|
|
919
1276
|
}
|
|
920
|
-
}, children: setupBusy ? t('setup.saving') : t('setup.saveGlobalSetup') })) : !isError ? (_jsx("button", { className: styles.submitBtn, disabled: setupBusy
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1277
|
+
}, children: setupBusy ? t('setup.saving') : t('setup.saveGlobalSetup') })) : !isError ? (_jsx("button", { className: styles.submitBtn, disabled: setupBusy
|
|
1278
|
+
|| (isCloudWorkspaceSetupFlow && !activeCloudWorkspaceImport && !isAuthenticated), onClick: async () => {
|
|
1279
|
+
if (isCloudWorkspaceSetupFlow && activeCloudWorkspaceImport) {
|
|
1280
|
+
if (!isCloudWorkspaceImportErrored)
|
|
1281
|
+
return;
|
|
1282
|
+
setSetupBusy(true);
|
|
1283
|
+
setSetupNotice(`Retrying import for ${activeCloudWorkspaceImport.name}...`);
|
|
1284
|
+
setSetupError(null);
|
|
1285
|
+
try {
|
|
1286
|
+
await retryWorkspaceCloudSync();
|
|
1287
|
+
}
|
|
1288
|
+
catch {
|
|
1289
|
+
setSetupNotice(null);
|
|
1290
|
+
setSetupError(`Failed to retry import for "${activeCloudWorkspaceImport.name}".`);
|
|
1291
|
+
}
|
|
1292
|
+
finally {
|
|
1293
|
+
setSetupBusy(false);
|
|
1294
|
+
}
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
if (wantsCloudWorkspaceSetup) {
|
|
1298
|
+
if (!canUseCloudWorkspaceSetup) {
|
|
1299
|
+
setSetupError('Cloud workspace sync is unavailable right now.');
|
|
1300
|
+
setSetupBusy(false);
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
925
1303
|
if (!isAuthenticated) {
|
|
926
1304
|
setSetupError('Sign in is required before syncing a cloud project.');
|
|
927
1305
|
setSetupBusy(false);
|
|
@@ -949,44 +1327,65 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
949
1327
|
setSetupBusy(false);
|
|
950
1328
|
return;
|
|
951
1329
|
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
}
|
|
968
|
-
})
|
|
969
|
-
});
|
|
970
|
-
const statePayload = await stateRes.json().catch(() => ({}));
|
|
971
|
-
if (!stateRes.ok || statePayload?.success === false) {
|
|
972
|
-
setSetupError(statePayload?.error || `Failed to configure workspace sync (${stateRes.status})`);
|
|
1330
|
+
try {
|
|
1331
|
+
await persistWorkspaceSyncStatePatch(workspaceIdForSync, {
|
|
1332
|
+
version: 2,
|
|
1333
|
+
enabled: true,
|
|
1334
|
+
phase: 'attach-cloud',
|
|
1335
|
+
setupIntent: 'attach-cloud-import',
|
|
1336
|
+
pullCursor: null,
|
|
1337
|
+
lastPullAt: null,
|
|
1338
|
+
lastPushAt: null,
|
|
1339
|
+
lastSyncedAt: null,
|
|
1340
|
+
lastErrorMessage: null
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
catch (error) {
|
|
1344
|
+
setSetupError(error instanceof Error ? error.message : 'Failed to configure workspace sync.');
|
|
973
1345
|
setSetupBusy(false);
|
|
974
1346
|
return;
|
|
975
1347
|
}
|
|
976
1348
|
applyWorkspaceSyncStateSnapshot({
|
|
977
1349
|
enabled: true,
|
|
978
1350
|
phase: 'attach-cloud',
|
|
1351
|
+
setupIntent: 'attach-cloud-import',
|
|
979
1352
|
pullCursor: null
|
|
980
1353
|
});
|
|
981
1354
|
await refreshSetupContext();
|
|
982
|
-
|
|
1355
|
+
setPendingCloudWorkspaceImport({
|
|
1356
|
+
workspaceId: workspaceIdForSync,
|
|
1357
|
+
name: selectedWorkspace.name || selectedWorkspace.id
|
|
1358
|
+
});
|
|
1359
|
+
setCloudWorkspaceImportFinalizing(false);
|
|
1360
|
+
setSetupNotice(`Importing ${selectedWorkspace.name || selectedWorkspace.id} from cloud...`);
|
|
1361
|
+
setSetupBusy(false);
|
|
1362
|
+
return;
|
|
983
1363
|
}
|
|
984
|
-
|
|
1364
|
+
if (isWorkspaceDetailsPage) {
|
|
1365
|
+
const normalizedWorkspaceName = workspaceNameInput.trim();
|
|
1366
|
+
if (!normalizedWorkspaceName) {
|
|
1367
|
+
setSetupError('Workspace name is required before continuing.');
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
setSetupError(null);
|
|
1371
|
+
setSetupNotice(null);
|
|
1372
|
+
setWorkspaceSetupPage('library');
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
if (isWorkspaceLibraryPage) {
|
|
1376
|
+
// fall through to final create/save behavior below
|
|
1377
|
+
}
|
|
1378
|
+
setSetupBusy(true);
|
|
1379
|
+
setSetupNotice(null);
|
|
1380
|
+
setSetupError(null);
|
|
1381
|
+
{
|
|
985
1382
|
const creatingNewWorkspace = isCreateWorkspaceIntent || setupState?.workspaceSetupState === 'missing';
|
|
986
|
-
const
|
|
1383
|
+
const shouldCreateWorkspace = isCreateWorkspaceIntent
|
|
1384
|
+
|| (workspaceSwitchingEnabled && creatingNewWorkspace);
|
|
1385
|
+
const createResult = shouldCreateWorkspace
|
|
987
1386
|
? await createWorkspace(workspaceNameInput, workspaceDescriptionInput || undefined)
|
|
988
1387
|
: null;
|
|
989
|
-
const saveResult =
|
|
1388
|
+
const saveResult = shouldCreateWorkspace
|
|
990
1389
|
? null
|
|
991
1390
|
: await saveWorkspaceProfile({
|
|
992
1391
|
workspaceId: creatingNewWorkspace ? undefined : setupState?.workspaceId,
|
|
@@ -1018,7 +1417,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
1018
1417
|
workspaceIdOverride: workspaceIdForSetup
|
|
1019
1418
|
});
|
|
1020
1419
|
if (!applyResult?.success) {
|
|
1021
|
-
setSetupError(applyResult?.error || 'Failed to apply
|
|
1420
|
+
setSetupError(applyResult?.error || 'Failed to apply setup library.');
|
|
1022
1421
|
setSetupBusy(false);
|
|
1023
1422
|
return;
|
|
1024
1423
|
}
|
|
@@ -1036,6 +1435,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
1036
1435
|
version: 2,
|
|
1037
1436
|
enabled: shouldEnableCloudSync,
|
|
1038
1437
|
phase: shouldEnableCloudSync ? 'provision-local' : 'idle',
|
|
1438
|
+
setupIntent: null,
|
|
1039
1439
|
pullCursor: null,
|
|
1040
1440
|
lastPullAt: null,
|
|
1041
1441
|
lastPushAt: null,
|
|
@@ -1052,6 +1452,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
1052
1452
|
applyWorkspaceSyncStateSnapshot({
|
|
1053
1453
|
enabled: shouldEnableCloudSync,
|
|
1054
1454
|
phase: shouldEnableCloudSync ? 'provision-local' : 'idle',
|
|
1455
|
+
setupIntent: null,
|
|
1055
1456
|
pullCursor: null
|
|
1056
1457
|
});
|
|
1057
1458
|
if (shouldEnableCloudSync) {
|
|
@@ -1068,11 +1469,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
1068
1469
|
}
|
|
1069
1470
|
}
|
|
1070
1471
|
setSetupBusy(false);
|
|
1071
|
-
}, children:
|
|
1072
|
-
setSetupError(null);
|
|
1073
|
-
setSetupNotice(null);
|
|
1074
|
-
navigate('/', { replace: true });
|
|
1075
|
-
}, children: "Cancel" })) : runtimeMode === 'cloud' ? (_jsx("button", { className: styles.cancelBtn, onClick: async () => { await taskforceState.logout(); navigate('/login', { replace: true }); }, children: t('setup.signOut') })) : null] })] }) }) }));
|
|
1472
|
+
}, children: setupPrimaryActionLabel })) : (_jsx("button", { className: styles.submitBtn, disabled: setupBusy, onClick: refreshSetupContext, children: t('setup.retrySetupCheck') }))] })] }) }) }));
|
|
1076
1473
|
}
|
|
1077
1474
|
return (_jsx(StandaloneLayout, { ...taskforceState, onHeaderMouseDown: onHeaderMouseDown, isDragging: isDragging, onClose: onClose }));
|
|
1078
1475
|
}
|