@taskforcehq/taskforce 0.3.303 → 0.3.304
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Taskforce.module.css +102 -5
- package/dist/TaskforceCore.js +514 -212
- package/dist/TaskforceCore.test.js +1172 -133
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/TaskSettings.js +58 -38
- package/dist/components/features/TaskSettings.test.js +139 -40
- 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 +2 -1
- package/dist/components/task/TaskForm.js +22 -4
- package/dist/components/task/TaskForm.test.js +46 -0
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +31 -160
- 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 +4 -2
- package/dist/components/views/PlanComparisonPage.test.js +4 -0
- package/dist/components/views/PlansPage.d.ts +5 -1
- package/dist/components/views/PlansPage.js +39 -12
- package/dist/components/views/PlansPage.test.js +59 -2
- package/dist/components/views/StandaloneLayout.js +113 -38
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/PlanEntitlementService.d.ts +3 -1
- package/dist/core/PlanEntitlementService.js +61 -11
- package/dist/core/SignupMonetizationSettings.test.js +37 -2
- 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/Taskforce.d.ts +15 -11
- package/dist/core/Taskforce.js +43 -2
- package/dist/core/WorkspacePlanMode.test.js +65 -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 +61 -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 +122 -0
- package/dist/hooks/sync/transfers.js +431 -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 +5 -4
- package/dist/hooks/useSyncOrchestrator.js +314 -873
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +568 -3
- package/dist/hooks/useTaskMutations.js +45 -6
- package/dist/hooks/useTaskMutations.test.js +147 -0
- package/dist/hooks/useTaskforce.d.ts +8 -2
- package/dist/hooks/useTaskforce.js +285 -812
- package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.js +22 -10
- 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/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/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +174 -476
- package/dist/mcp/runtime.test.js +81 -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/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.js +13 -19
- package/dist/server/routes/auth.js +37 -2
- package/dist/server/routes/billing.js +18 -67
- package/dist/server/routes/billing.test.js +43 -0
- package/dist/server/routes/shared.d.ts +30 -0
- package/dist/server/routes/shared.js +26 -0
- package/dist/server/routes/sync.integration.test.js +20 -0
- package/dist/server/routes/sync.js +110 -237
- package/dist/server/routes/tasks.js +49 -7
- package/dist/server/routes.js +8 -19
- package/dist/server/routes.test.js +243 -5
- 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 +12 -10
- 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 +377 -0
- package/dist/sync/syncService.d.ts +8 -1
- package/dist/sync/syncService.js +36 -2
- package/dist/sync/syncService.test.js +48 -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 +22 -1
- package/dist/sync/workspaceSyncModel.js +65 -0
- package/dist/sync/workspaceSyncModel.test.js +119 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-C3GyzrWm.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-BjR6iBf9.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
- package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-DnFqdUME.js} +1 -1
- package/dist/ui/assets/index-DXUdtH1B.js +6 -0
- package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
- package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-I7ZwG1z_.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +3 -1
- package/scripts/playwright-auth.sh +4 -0
- package/dist/ui/assets/AgentsModule-CpsTmrIW.js +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, 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,14 @@ 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);
|
|
83
95
|
const fallbackSetupLibraryPacks = useMemo(() => listBuiltInTaxonomyLibraryPacks(), []);
|
|
84
96
|
const [setupLibraryPacks, setSetupLibraryPacks] = useState(fallbackSetupLibraryPacks);
|
|
85
97
|
const [selectedSetupPackId, setSelectedSetupPackId] = useState(() => (fallbackSetupLibraryPacks.find((pack) => pack.isDefaultStarter)?.id || fallbackSetupLibraryPacks[0]?.id || ''));
|
|
@@ -88,6 +100,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
88
100
|
const [setupLibraryError, setSetupLibraryError] = useState(null);
|
|
89
101
|
const [bootstrapRetryBusy, setBootstrapRetryBusy] = useState(false);
|
|
90
102
|
const [accountAccessGate, setAccountAccessGate] = useState(null);
|
|
103
|
+
const [accountAccessGateResolved, setAccountAccessGateResolved] = useState(false);
|
|
91
104
|
// Initialize the hook
|
|
92
105
|
const taskforceState = useTaskforce({
|
|
93
106
|
config,
|
|
@@ -95,7 +108,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
95
108
|
onTaskCountChange,
|
|
96
109
|
onClose
|
|
97
110
|
});
|
|
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;
|
|
111
|
+
const { runtimeMode, workspaceSwitchingEnabled, currentWorkspaceId, configLoaded, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, hasBetaAccess, authSessionResolved, workspaceBootstrapPending, bootstrapPhase, bootstrapError, setupState, runtimeCapabilities, refreshSetupContext, retryBootstrapChecks, createWorkspace, saveWorkspaceProfile, fetchWorkspaces, applyWorkspaceSyncStateSnapshot, workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceSyncSummary, workspaceSyncRecommendedAction, workspaceSyncBusy, workspaceLastErrorMessage, retryWorkspaceCloudSync, settingsModel, fetchTasks, loginWithCredentials, registerWithCredentials, requestEmailVerification, confirmEmailVerification, requestPasswordReset, confirmPasswordReset, inspectInviteAcceptance, acceptInviteWithToken, joinInviteWithToken, authUserEmail, currentTheme, logout } = taskforceState;
|
|
99
112
|
const selectedSetupPack = useMemo(() => {
|
|
100
113
|
return setupLibraryPacks.find((pack) => pack.id === selectedSetupPackId)
|
|
101
114
|
|| setupLibraryPacks.find((pack) => pack.isDefaultStarter)
|
|
@@ -118,6 +131,46 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
118
131
|
const isLoginRoute = location.pathname === '/login';
|
|
119
132
|
const isSetupRoute = location.pathname === '/setup';
|
|
120
133
|
const isComingSoonRoute = location.pathname === '/coming-soon';
|
|
134
|
+
const wantsCloudWorkspaceSetup = !isCloudRuntime && workspaceSetupSource === 'cloud';
|
|
135
|
+
const isCloudWorkspaceSetupFlow = wantsCloudWorkspaceSetup && canUseCloudWorkspaceSetup;
|
|
136
|
+
const cloudWorkspaceImportMatchesCurrentWorkspace = Boolean(pendingCloudWorkspaceImport
|
|
137
|
+
&& currentWorkspaceId === pendingCloudWorkspaceImport.workspaceId);
|
|
138
|
+
const isCloudWorkspaceImportErrored = Boolean(pendingCloudWorkspaceImport
|
|
139
|
+
&& cloudWorkspaceImportMatchesCurrentWorkspace
|
|
140
|
+
&& workspaceSyncPhase === 'error');
|
|
141
|
+
const isCloudWorkspaceImportReady = Boolean(pendingCloudWorkspaceImport
|
|
142
|
+
&& cloudWorkspaceImportMatchesCurrentWorkspace
|
|
143
|
+
&& workspaceCloudSyncEnabled
|
|
144
|
+
&& workspaceSyncPhase === 'active'
|
|
145
|
+
&& !workspaceSyncBusy);
|
|
146
|
+
const cloudWorkspaceImportProgressLines = useMemo(() => {
|
|
147
|
+
if (!pendingCloudWorkspaceImport)
|
|
148
|
+
return [];
|
|
149
|
+
const connectingState = setupBusy ? 'In progress' : 'Complete';
|
|
150
|
+
const importingState = isCloudWorkspaceImportErrored
|
|
151
|
+
? 'Needs attention'
|
|
152
|
+
: (workspaceSyncBusy || workspaceSyncPhase === 'attach-cloud')
|
|
153
|
+
? 'In progress'
|
|
154
|
+
: (workspaceSyncPhase === 'active' ? 'Complete' : 'Waiting');
|
|
155
|
+
const finishingState = cloudWorkspaceImportFinalizing
|
|
156
|
+
? 'In progress'
|
|
157
|
+
: isCloudWorkspaceImportReady
|
|
158
|
+
? 'Ready'
|
|
159
|
+
: 'Waiting';
|
|
160
|
+
return [
|
|
161
|
+
`Connect workspace: ${connectingState}`,
|
|
162
|
+
`Import cloud data: ${importingState}`,
|
|
163
|
+
`Open local workspace: ${finishingState}`
|
|
164
|
+
];
|
|
165
|
+
}, [
|
|
166
|
+
pendingCloudWorkspaceImport,
|
|
167
|
+
setupBusy,
|
|
168
|
+
isCloudWorkspaceImportErrored,
|
|
169
|
+
workspaceSyncBusy,
|
|
170
|
+
workspaceSyncPhase,
|
|
171
|
+
cloudWorkspaceImportFinalizing,
|
|
172
|
+
isCloudWorkspaceImportReady
|
|
173
|
+
]);
|
|
121
174
|
const shouldForceLogin = isLikelyCloudAppHost
|
|
122
175
|
? ((!isAuthenticated || authBlocked) && !isPricingRoute && !isPlansRoute && !isPlansScreen)
|
|
123
176
|
: (((isCloudAuthRequired && !isAuthenticated) || shouldForceForAuthBlocked) && !isPricingRoute && !isPlansRoute && !isPlansScreen);
|
|
@@ -131,6 +184,23 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
131
184
|
&& !isPlansRoute
|
|
132
185
|
&& !isPlansScreen;
|
|
133
186
|
const directRegistrationEnabled = cloudAuthConfigured;
|
|
187
|
+
const authPageTitle = useMemo(() => {
|
|
188
|
+
switch (authMode) {
|
|
189
|
+
case 'register':
|
|
190
|
+
return 'Create Account | Taskforce';
|
|
191
|
+
case 'verify':
|
|
192
|
+
return 'Verify Email | Taskforce';
|
|
193
|
+
case 'forgot':
|
|
194
|
+
return 'Reset Password | Taskforce';
|
|
195
|
+
case 'reset':
|
|
196
|
+
return 'Set New Password | Taskforce';
|
|
197
|
+
case 'invite':
|
|
198
|
+
return 'Accept Invite | Taskforce';
|
|
199
|
+
case 'login':
|
|
200
|
+
default:
|
|
201
|
+
return 'Sign In | Taskforce';
|
|
202
|
+
}
|
|
203
|
+
}, [authMode]);
|
|
134
204
|
const setupStep = useMemo(() => String(new URLSearchParams(location.search).get('step') || '').trim().toLowerCase(), [location.search]);
|
|
135
205
|
const setupIntent = useMemo(() => String(new URLSearchParams(location.search).get('intent') || '').trim().toLowerCase(), [location.search]);
|
|
136
206
|
const isCreateWorkspaceIntent = isSetupRoute && setupStep === 'workspace' && setupIntent === 'create-workspace';
|
|
@@ -198,9 +268,11 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
198
268
|
useEffect(() => {
|
|
199
269
|
if (runtimeMode !== 'cloud' || !isAuthenticated || !authSessionResolved) {
|
|
200
270
|
setAccountAccessGate(null);
|
|
271
|
+
setAccountAccessGateResolved(false);
|
|
201
272
|
return;
|
|
202
273
|
}
|
|
203
274
|
setAccountAccessGate(null);
|
|
275
|
+
setAccountAccessGateResolved(false);
|
|
204
276
|
let cancelled = false;
|
|
205
277
|
(async () => {
|
|
206
278
|
try {
|
|
@@ -209,11 +281,17 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
209
281
|
credentials: 'include'
|
|
210
282
|
});
|
|
211
283
|
const payload = await res.json().catch(() => ({}));
|
|
212
|
-
if (cancelled
|
|
284
|
+
if (cancelled)
|
|
285
|
+
return;
|
|
286
|
+
if (!res.ok) {
|
|
287
|
+
setAccountAccessGate(null);
|
|
288
|
+
setAccountAccessGateResolved(true);
|
|
213
289
|
return;
|
|
290
|
+
}
|
|
214
291
|
const gate = String(payload?.gate || '').trim().toLowerCase();
|
|
215
292
|
if (gate !== 'ok' && gate !== 'plan_selection_required' && gate !== 'checkout_pending' && gate !== 'misconfigured' && gate !== 'missing_entitlement') {
|
|
216
293
|
setAccountAccessGate(null);
|
|
294
|
+
setAccountAccessGateResolved(true);
|
|
217
295
|
return;
|
|
218
296
|
}
|
|
219
297
|
setAccountAccessGate({
|
|
@@ -222,10 +300,13 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
222
300
|
canAccessPlans: payload?.canAccessPlans !== false,
|
|
223
301
|
message: typeof payload?.message === 'string' ? payload.message : null
|
|
224
302
|
});
|
|
303
|
+
setAccountAccessGateResolved(true);
|
|
225
304
|
}
|
|
226
305
|
catch {
|
|
227
|
-
if (!cancelled)
|
|
306
|
+
if (!cancelled) {
|
|
228
307
|
setAccountAccessGate(null);
|
|
308
|
+
setAccountAccessGateResolved(true);
|
|
309
|
+
}
|
|
229
310
|
}
|
|
230
311
|
})();
|
|
231
312
|
return () => {
|
|
@@ -258,6 +339,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
258
339
|
useEffect(() => {
|
|
259
340
|
if (!isSetupRoute || setupStep !== 'workspace')
|
|
260
341
|
return;
|
|
342
|
+
setWorkspaceSetupPage('details');
|
|
261
343
|
const source = String(new URLSearchParams(location.search).get('source') || '').trim().toLowerCase();
|
|
262
344
|
if (source === 'cloud' && canUseCloudWorkspaceSetup) {
|
|
263
345
|
setWorkspaceSetupSource('cloud');
|
|
@@ -355,6 +437,63 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
355
437
|
useEffect(() => {
|
|
356
438
|
setSetupTaxonomySections(defaultSetupTaxonomySections(selectedSetupPack));
|
|
357
439
|
}, [selectedSetupPackId, selectedSetupPack]);
|
|
440
|
+
useEffect(() => {
|
|
441
|
+
if (workspaceSetupSource === 'cloud') {
|
|
442
|
+
setWorkspaceSetupPage('details');
|
|
443
|
+
}
|
|
444
|
+
}, [workspaceSetupSource]);
|
|
445
|
+
useEffect(() => {
|
|
446
|
+
if (!isCloudWorkspaceSetupFlow) {
|
|
447
|
+
setPendingCloudWorkspaceImport(null);
|
|
448
|
+
setCloudWorkspaceImportFinalizing(false);
|
|
449
|
+
}
|
|
450
|
+
}, [isCloudWorkspaceSetupFlow]);
|
|
451
|
+
useEffect(() => {
|
|
452
|
+
if (!isCloudWorkspaceImportErrored || !pendingCloudWorkspaceImport)
|
|
453
|
+
return;
|
|
454
|
+
setSetupNotice(null);
|
|
455
|
+
setSetupError(workspaceLastErrorMessage
|
|
456
|
+
|| `Failed to import "${pendingCloudWorkspaceImport.name}" from cloud. Retry import to continue.`);
|
|
457
|
+
}, [isCloudWorkspaceImportErrored, pendingCloudWorkspaceImport, workspaceLastErrorMessage]);
|
|
458
|
+
useEffect(() => {
|
|
459
|
+
if (!isCloudWorkspaceImportReady || !pendingCloudWorkspaceImport || cloudWorkspaceImportFinalizing)
|
|
460
|
+
return;
|
|
461
|
+
let cancelled = false;
|
|
462
|
+
setCloudWorkspaceImportFinalizing(true);
|
|
463
|
+
setSetupError(null);
|
|
464
|
+
setSetupNotice(`Workspace import complete. Opening ${pendingCloudWorkspaceImport.name}...`);
|
|
465
|
+
void (async () => {
|
|
466
|
+
try {
|
|
467
|
+
await refreshSetupContext();
|
|
468
|
+
await Promise.all([
|
|
469
|
+
fetchWorkspaces(),
|
|
470
|
+
fetchTasks(true)
|
|
471
|
+
]);
|
|
472
|
+
if (cancelled)
|
|
473
|
+
return;
|
|
474
|
+
setPendingCloudWorkspaceImport(null);
|
|
475
|
+
navigate('/', { replace: true });
|
|
476
|
+
}
|
|
477
|
+
catch {
|
|
478
|
+
if (cancelled)
|
|
479
|
+
return;
|
|
480
|
+
setSetupNotice(null);
|
|
481
|
+
setSetupError(`Imported "${pendingCloudWorkspaceImport.name}" but failed to finish opening it.`);
|
|
482
|
+
setCloudWorkspaceImportFinalizing(false);
|
|
483
|
+
}
|
|
484
|
+
})();
|
|
485
|
+
return () => {
|
|
486
|
+
cancelled = true;
|
|
487
|
+
};
|
|
488
|
+
}, [
|
|
489
|
+
cloudWorkspaceImportFinalizing,
|
|
490
|
+
fetchTasks,
|
|
491
|
+
fetchWorkspaces,
|
|
492
|
+
isCloudWorkspaceImportReady,
|
|
493
|
+
navigate,
|
|
494
|
+
pendingCloudWorkspaceImport,
|
|
495
|
+
refreshSetupContext
|
|
496
|
+
]);
|
|
358
497
|
const nextPath = useMemo(() => {
|
|
359
498
|
const raw = new URLSearchParams(location.search).get('next') || '/';
|
|
360
499
|
if (!raw.startsWith('/'))
|
|
@@ -363,7 +502,29 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
363
502
|
return '/';
|
|
364
503
|
return raw;
|
|
365
504
|
}, [location.search]);
|
|
505
|
+
const cloudWorkspaceSelectionPath = '/setup?step=workspace&source=cloud&postAuth=select-cloud-workspace';
|
|
506
|
+
const shouldReturnToCloudWorkspaceSelection = useMemo(() => {
|
|
507
|
+
try {
|
|
508
|
+
const parsed = new URL(nextPath, 'https://taskforce.local');
|
|
509
|
+
return parsed.pathname === '/setup'
|
|
510
|
+
&& parsed.searchParams.get('step') === 'workspace'
|
|
511
|
+
&& parsed.searchParams.get('source') === 'cloud'
|
|
512
|
+
&& parsed.searchParams.get('postAuth') === 'select-cloud-workspace';
|
|
513
|
+
}
|
|
514
|
+
catch {
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
517
|
+
}, [nextPath]);
|
|
518
|
+
const resolveWorkspaceSetupPath = useCallback(() => (shouldReturnToCloudWorkspaceSelection
|
|
519
|
+
? cloudWorkspaceSelectionPath
|
|
520
|
+
: '/setup?step=workspace'), [shouldReturnToCloudWorkspaceSelection]);
|
|
366
521
|
const resolvedSignupInterval = selectedSignupInterval === 'year' ? 'year' : 'month';
|
|
522
|
+
const hasCommercialOnboardingGate = useCallback((input) => {
|
|
523
|
+
return Boolean(input?.planSelectionRequired
|
|
524
|
+
|| input?.checkoutPending
|
|
525
|
+
|| input?.commercialState === 'pending_plan_selection'
|
|
526
|
+
|| input?.commercialState === 'checkout_pending');
|
|
527
|
+
}, []);
|
|
367
528
|
const resolvePostRegistrationPath = useCallback((input) => {
|
|
368
529
|
if (input?.planSelectionRequired || input?.commercialState === 'pending_plan_selection') {
|
|
369
530
|
return planSelectionPath;
|
|
@@ -408,26 +569,6 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
408
569
|
const query = params.toString();
|
|
409
570
|
navigate(`/login${query ? `?${query}` : ''}`, { replace: true });
|
|
410
571
|
}, [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
572
|
useEffect(() => {
|
|
432
573
|
if (!isLoginRoute)
|
|
433
574
|
return;
|
|
@@ -445,8 +586,40 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
445
586
|
if (isLoginRoute && loginRouteMode === 'verify')
|
|
446
587
|
return;
|
|
447
588
|
setPendingVerificationWorkspaceSetupRequired(false);
|
|
589
|
+
setPendingVerificationCommercialState(null);
|
|
448
590
|
setPendingVerificationRedirectPath(null);
|
|
449
591
|
}, [authMode, isLoginRoute, loginRouteMode]);
|
|
592
|
+
useEffect(() => {
|
|
593
|
+
if (!isLoginRoute || !pendingPostAuthRedirect || !authSessionResolved || !isAuthenticated)
|
|
594
|
+
return;
|
|
595
|
+
if (runtimeMode === 'cloud' && !pendingPostAuthRedirect.commercialOnboardingGate && !accountAccessGateResolved) {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
if (!pendingPostAuthRedirect.commercialOnboardingGate && pendingPostAuthRedirect.workspaceSetupRequired) {
|
|
599
|
+
navigate(resolveWorkspaceSetupPath(), { replace: true });
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
navigate(pendingPostAuthRedirect.path, { replace: true });
|
|
603
|
+
}
|
|
604
|
+
setLoginEmail('');
|
|
605
|
+
setLoginDisplayName('');
|
|
606
|
+
setLoginPassword('');
|
|
607
|
+
setPendingPostAuthRedirect(null);
|
|
608
|
+
}, [
|
|
609
|
+
accountAccessGateResolved,
|
|
610
|
+
authSessionResolved,
|
|
611
|
+
isAuthenticated,
|
|
612
|
+
isLoginRoute,
|
|
613
|
+
navigate,
|
|
614
|
+
pendingPostAuthRedirect,
|
|
615
|
+
resolveWorkspaceSetupPath,
|
|
616
|
+
runtimeMode
|
|
617
|
+
]);
|
|
618
|
+
useEffect(() => {
|
|
619
|
+
if (!isLoginRoute)
|
|
620
|
+
return;
|
|
621
|
+
document.title = authPageTitle;
|
|
622
|
+
}, [authPageTitle, isLoginRoute]);
|
|
450
623
|
useEffect(() => {
|
|
451
624
|
if (!isLoginRoute || authMode !== 'invite' || !inviteToken.trim())
|
|
452
625
|
return;
|
|
@@ -533,6 +706,17 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
533
706
|
const shouldStayOnInviteRoute = isLoginRoute
|
|
534
707
|
&& authMode === 'invite'
|
|
535
708
|
&& inviteToken.trim().length > 0;
|
|
709
|
+
const shouldDelayAuthenticatedLoginRedirect = isLoginRoute
|
|
710
|
+
&& isAuthenticated
|
|
711
|
+
&& !shouldStayOnInviteRoute
|
|
712
|
+
&& (Boolean(pendingPostAuthRedirect)
|
|
713
|
+
|| workspaceBootstrapPending
|
|
714
|
+
|| authMode === 'register'
|
|
715
|
+
|| authMode === 'verify'
|
|
716
|
+
|| (runtimeMode === 'cloud' && (!configLoaded || !accountAccessGateResolved)));
|
|
717
|
+
if (shouldDelayAuthenticatedLoginRedirect) {
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
536
720
|
if (isLoginRoute && isAuthenticated && !shouldStayOnInviteRoute)
|
|
537
721
|
navigate(nextPath, { replace: true });
|
|
538
722
|
}
|
|
@@ -544,6 +728,9 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
544
728
|
}
|
|
545
729
|
return;
|
|
546
730
|
}
|
|
731
|
+
if (isLoginRoute && !isAuthenticated) {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
547
734
|
if (shouldForceAccountAccessGate) {
|
|
548
735
|
if (!isPlansScreen) {
|
|
549
736
|
const gate = encodeURIComponent(String(accountAccessGate?.gate || 'plan_selection_required'));
|
|
@@ -551,6 +738,9 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
551
738
|
}
|
|
552
739
|
return;
|
|
553
740
|
}
|
|
741
|
+
if (isSetupRoute && pendingCloudWorkspaceImport) {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
554
744
|
if (setupGateActive) {
|
|
555
745
|
if (hasSetupReadError) {
|
|
556
746
|
if (!isSetupRoute || setupStep !== 'error') {
|
|
@@ -566,7 +756,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
566
756
|
}
|
|
567
757
|
if (needsWorkspaceSetup) {
|
|
568
758
|
if (!isSetupRoute || setupStep !== 'workspace') {
|
|
569
|
-
navigate(
|
|
759
|
+
navigate(resolveWorkspaceSetupPath(), { replace: true });
|
|
570
760
|
}
|
|
571
761
|
return;
|
|
572
762
|
}
|
|
@@ -589,19 +779,26 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
589
779
|
isPlansRoute,
|
|
590
780
|
isPlansScreen,
|
|
591
781
|
accountAccessGate?.gate,
|
|
782
|
+
accountAccessGateResolved,
|
|
783
|
+
authMode,
|
|
592
784
|
setupGateActive,
|
|
593
785
|
setupState,
|
|
594
786
|
hasSetupReadError,
|
|
595
787
|
isCreateWorkspaceIntent,
|
|
788
|
+
pendingCloudWorkspaceImport,
|
|
596
789
|
needsGlobalSetup,
|
|
597
790
|
needsWorkspaceSetup,
|
|
598
791
|
setupStep,
|
|
599
792
|
workspaceBootstrapPending,
|
|
793
|
+
configLoaded,
|
|
794
|
+
runtimeMode,
|
|
795
|
+
inviteToken,
|
|
600
796
|
location.pathname,
|
|
601
797
|
location.search,
|
|
602
798
|
location.hash,
|
|
603
799
|
navigate,
|
|
604
|
-
nextPath
|
|
800
|
+
nextPath,
|
|
801
|
+
pendingPostAuthRedirect
|
|
605
802
|
]);
|
|
606
803
|
useEffect(() => {
|
|
607
804
|
if (mode === 'widget')
|
|
@@ -653,18 +850,26 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
653
850
|
if (isLoginRoute) {
|
|
654
851
|
return (_jsx("div", { className: shellStyles.standaloneWrapper, "data-theme": currentTheme, children: _jsx("div", { className: authStyles.loginView, style: {
|
|
655
852
|
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'
|
|
853
|
+
}, 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
854
|
? `You've been invited to join ${inviteWorkspaceId || 'this workspace'}.`
|
|
658
855
|
: inviteResolution === 'existing_user_signed_out'
|
|
659
856
|
? 'Sign in to join this workspace.'
|
|
660
|
-
: 'Create your account to join this workspace.')] }), (authMode === 'login'
|
|
857
|
+
: 'Create your account to join this workspace.')] }), (authMode === 'login'
|
|
858
|
+
|| authMode === 'register'
|
|
859
|
+
|| authMode === 'verify'
|
|
860
|
+
|| authMode === 'forgot'
|
|
861
|
+
|| (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
862
|
|| authMode === 'register'
|
|
662
863
|
|| authMode === 'reset'
|
|
663
|
-
|| (authMode === 'invite' && (invitePasswordRequired || inviteResolution === 'existing_user_signed_out'))) && (
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
864
|
+
|| (authMode === 'invite' && (invitePasswordRequired || inviteResolution === 'existing_user_signed_out'))) && (_jsxs("label", { className: authStyles.loginField, children: [_jsx("span", { className: authStyles.loginFieldLabel, children: authMode === 'reset'
|
|
865
|
+
? 'New password'
|
|
866
|
+
: (authMode === 'invite' && invitePasswordRequired ? 'Create password' : 'Password') }), _jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "password", value: loginPassword, placeholder: authMode === 'reset'
|
|
867
|
+
? 'New password'
|
|
868
|
+
: (authMode === 'invite'
|
|
869
|
+
? (invitePasswordRequired ? 'Create password' : 'Password')
|
|
870
|
+
: 'Password'), autoComplete: authMode === 'register' || authMode === 'reset' || (authMode === 'invite' && invitePasswordRequired)
|
|
871
|
+
? 'new-password'
|
|
872
|
+
: '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
873
|
setLoginBusy(true);
|
|
669
874
|
setLoginNotice(null);
|
|
670
875
|
const sent = await requestEmailVerification(loginEmail);
|
|
@@ -681,180 +886,206 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
681
886
|
setLoginError(formatAuthError(sent));
|
|
682
887
|
}
|
|
683
888
|
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') {
|
|
889
|
+
}, 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 () => {
|
|
890
|
+
setLoginBusy(true);
|
|
891
|
+
setLoginNotice(null);
|
|
892
|
+
setLoginError(null);
|
|
893
|
+
setLoginErrorCode(null);
|
|
894
|
+
if (authMode === 'register' && !loginDisplayName.trim()) {
|
|
895
|
+
setLoginError('Display name is required.');
|
|
896
|
+
setLoginBusy(false);
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
let result = { success: false };
|
|
900
|
+
if (authMode === 'login') {
|
|
717
901
|
result = await loginWithCredentials(loginEmail, loginPassword);
|
|
718
902
|
}
|
|
719
|
-
else {
|
|
720
|
-
result =
|
|
721
|
-
|
|
722
|
-
:
|
|
903
|
+
else if (authMode === 'register') {
|
|
904
|
+
result = await registerWithCredentials(loginEmail, loginPassword, {
|
|
905
|
+
displayName: loginDisplayName,
|
|
906
|
+
planId: selectedSignupPlanId || undefined,
|
|
907
|
+
planVersionId: selectedSignupPlanVersionId || undefined,
|
|
908
|
+
interval: selectedSignupInterval || undefined
|
|
909
|
+
});
|
|
723
910
|
}
|
|
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;
|
|
911
|
+
else if (authMode === 'verify') {
|
|
912
|
+
result = await confirmEmailVerification(verificationToken);
|
|
733
913
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
return;
|
|
914
|
+
else if (authMode === 'forgot') {
|
|
915
|
+
result = await requestPasswordReset(loginEmail);
|
|
916
|
+
}
|
|
917
|
+
else if (authMode === 'reset') {
|
|
918
|
+
result = await confirmPasswordReset(resetToken, loginPassword);
|
|
740
919
|
}
|
|
741
|
-
if (authMode === '
|
|
742
|
-
|
|
743
|
-
|
|
920
|
+
else if (authMode === 'invite') {
|
|
921
|
+
if (inviteResolution === 'existing_user_signed_out') {
|
|
922
|
+
result = await loginWithCredentials(loginEmail, loginPassword);
|
|
923
|
+
}
|
|
924
|
+
else {
|
|
925
|
+
result = invitePasswordRequired
|
|
926
|
+
? await acceptInviteWithToken(inviteToken, loginPassword)
|
|
927
|
+
: await joinInviteWithToken(inviteToken);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
if (!result.success) {
|
|
931
|
+
if (authMode === 'register'
|
|
932
|
+
&& (result.code === 'SIGNUP_PLAN_NOT_ENABLED'
|
|
933
|
+
|| result.code === 'SIGNUP_PLAN_VERSION_NOT_FOUND'
|
|
934
|
+
|| result.code === 'SIGNUP_PLAN_VERSION_REQUIRED')) {
|
|
935
|
+
navigate(planSelectionPath, { replace: true });
|
|
936
|
+
setLoginBusy(false);
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
setLoginError(formatAuthError(result));
|
|
940
|
+
setLoginErrorCode(result.code || null);
|
|
941
|
+
if (authMode === 'login' && (result.code === 'WORKSPACE_NOT_FOUND' || result.code === 'WORKSPACE_ID_REQUIRED')) {
|
|
942
|
+
navigate('/setup?step=workspace', { replace: true });
|
|
943
|
+
setLoginBusy(false);
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
if (authMode === 'login' && result.code === 'EMAIL_NOT_VERIFIED' && loginEmail.trim()) {
|
|
947
|
+
const sent = await requestEmailVerification(loginEmail);
|
|
948
|
+
if (sent.success) {
|
|
949
|
+
setVerifyCooldownUntil(Date.now() + 30_000);
|
|
950
|
+
setLoginMode('verify');
|
|
951
|
+
if (sent.verificationToken)
|
|
952
|
+
setVerificationToken(sent.verificationToken);
|
|
953
|
+
setLoginNotice('Email not verified. A verification token was sent.');
|
|
954
|
+
setLoginError(null);
|
|
955
|
+
setLoginErrorCode(null);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
if (authMode === 'register' && result.code === 'EMAIL_ALREADY_EXISTS_UNVERIFIED' && loginEmail.trim()) {
|
|
744
959
|
setVerifyCooldownUntil(Date.now() + 30_000);
|
|
745
960
|
setLoginMode('verify');
|
|
746
|
-
if (
|
|
747
|
-
setVerificationToken(
|
|
748
|
-
setLoginNotice(
|
|
961
|
+
if (result.verificationToken)
|
|
962
|
+
setVerificationToken(result.verificationToken);
|
|
963
|
+
setLoginNotice(result.emailSent === false
|
|
964
|
+
? `This email already has an unverified account, but verification email delivery failed. ${result.emailError || 'Try Resend Verification again.'}`
|
|
965
|
+
: 'This email already has an unverified account. Check your email for verification instructions.');
|
|
749
966
|
setLoginError(null);
|
|
750
967
|
setLoginErrorCode(null);
|
|
751
968
|
}
|
|
752
969
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
970
|
+
else if (authMode === 'register') {
|
|
971
|
+
const postRegistrationPath = resolvePostRegistrationPath(result);
|
|
972
|
+
const commercialOnboardingGate = hasCommercialOnboardingGate(result);
|
|
973
|
+
if (result.verificationRequired) {
|
|
974
|
+
setPendingVerificationWorkspaceSetupRequired(Boolean(result.workspaceSetupRequired));
|
|
975
|
+
setPendingVerificationCommercialState(commercialOnboardingGate
|
|
976
|
+
? String(result.commercialState || '').trim() || (result.planSelectionRequired ? 'pending_plan_selection' : 'checkout_pending')
|
|
977
|
+
: null);
|
|
978
|
+
setPendingVerificationRedirectPath(postRegistrationPath);
|
|
979
|
+
if (result.verificationToken)
|
|
980
|
+
setVerificationToken(result.verificationToken);
|
|
981
|
+
setVerifyCooldownUntil(Date.now() + 30_000);
|
|
982
|
+
setLoginMode('verify');
|
|
983
|
+
setLoginNotice(result.emailSent === false
|
|
984
|
+
? `Account created, but verification email failed to send. ${result.emailError || 'Try Resend Verification again.'}`
|
|
985
|
+
: 'Account created. Check your email for verification instructions.');
|
|
986
|
+
}
|
|
987
|
+
else {
|
|
988
|
+
setPendingPostAuthRedirect({
|
|
989
|
+
path: postRegistrationPath,
|
|
990
|
+
workspaceSetupRequired: Boolean(result.workspaceSetupRequired),
|
|
991
|
+
commercialOnboardingGate
|
|
992
|
+
});
|
|
993
|
+
}
|
|
766
994
|
}
|
|
767
|
-
else {
|
|
995
|
+
else if (authMode === 'verify') {
|
|
768
996
|
await retryBootstrapChecks();
|
|
769
|
-
|
|
997
|
+
const commercialOnboardingGate = pendingVerificationCommercialState === 'pending_plan_selection'
|
|
998
|
+
|| pendingVerificationCommercialState === 'checkout_pending';
|
|
999
|
+
setPendingPostAuthRedirect({
|
|
1000
|
+
path: pendingVerificationRedirectPath || nextPath,
|
|
1001
|
+
workspaceSetupRequired: pendingVerificationWorkspaceSetupRequired,
|
|
1002
|
+
commercialOnboardingGate
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
else if (authMode === 'forgot') {
|
|
1006
|
+
if (result.resetToken)
|
|
1007
|
+
setResetToken(result.resetToken);
|
|
1008
|
+
setResetCooldownUntil(Date.now() + 30_000);
|
|
1009
|
+
setLoginMode('reset');
|
|
1010
|
+
setLoginNotice(result.emailSent === false
|
|
1011
|
+
? `Password reset email failed to send. ${result.emailError || 'Try again in a minute.'}`
|
|
1012
|
+
: 'Password reset instructions sent.');
|
|
1013
|
+
}
|
|
1014
|
+
else if (authMode === 'reset') {
|
|
1015
|
+
setLoginMode('login');
|
|
1016
|
+
setLoginNotice('Password updated. Sign in with your new password.');
|
|
1017
|
+
}
|
|
1018
|
+
else if (authMode === 'invite') {
|
|
1019
|
+
if (inviteResolution === 'existing_user_signed_out') {
|
|
1020
|
+
setAuthMode('invite');
|
|
1021
|
+
setLoginPassword('');
|
|
1022
|
+
setLoginNotice('Signed in. Review the invite details to continue.');
|
|
1023
|
+
setLoginBusy(false);
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
if (result.workspaceSetupRequired) {
|
|
1027
|
+
navigate(resolveWorkspaceSetupPath(), { replace: true });
|
|
1028
|
+
setLoginBusy(false);
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
navigate(nextPath, { replace: true });
|
|
1032
|
+
}
|
|
1033
|
+
else if (authMode === 'login') {
|
|
1034
|
+
if (inviteToken.trim()) {
|
|
1035
|
+
setAuthMode('invite');
|
|
1036
|
+
setLoginPassword('');
|
|
1037
|
+
setLoginNotice('Signed in. Review the invite details to continue.');
|
|
1038
|
+
setLoginBusy(false);
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
if (result.workspaceSetupRequired) {
|
|
1042
|
+
navigate(resolveWorkspaceSetupPath(), { replace: true });
|
|
770
1043
|
setLoginBusy(false);
|
|
771
1044
|
return;
|
|
772
1045
|
}
|
|
773
1046
|
setLoginEmail('');
|
|
774
1047
|
setLoginDisplayName('');
|
|
775
1048
|
setLoginPassword('');
|
|
776
|
-
navigate(
|
|
1049
|
+
navigate(nextPath, { replace: true });
|
|
777
1050
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
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;
|
|
809
|
-
}
|
|
810
|
-
if (result.workspaceSetupRequired) {
|
|
811
|
-
navigate('/setup?step=workspace', { replace: true });
|
|
812
|
-
setLoginBusy(false);
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
|
-
navigate(nextPath, { replace: true });
|
|
816
|
-
}
|
|
817
|
-
else if (authMode === 'login') {
|
|
818
|
-
if (inviteToken.trim()) {
|
|
819
|
-
setAuthMode('invite');
|
|
1051
|
+
else {
|
|
1052
|
+
setLoginEmail('');
|
|
1053
|
+
setLoginDisplayName('');
|
|
820
1054
|
setLoginPassword('');
|
|
821
|
-
|
|
822
|
-
setLoginBusy(false);
|
|
823
|
-
return;
|
|
824
|
-
}
|
|
825
|
-
if (result.workspaceSetupRequired) {
|
|
826
|
-
navigate('/setup?step=workspace', { replace: true });
|
|
827
|
-
setLoginBusy(false);
|
|
828
|
-
return;
|
|
1055
|
+
navigate(nextPath, { replace: true });
|
|
829
1056
|
}
|
|
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
|
-
|
|
1057
|
+
setLoginBusy(false);
|
|
1058
|
+
}, children: loginBusy ? 'Working...' : (authMode === 'login' ? 'Sign In'
|
|
1059
|
+
: authMode === 'register' ? 'Create Account'
|
|
1060
|
+
: authMode === 'verify' ? 'Verify Email'
|
|
1061
|
+
: authMode === 'forgot'
|
|
1062
|
+
? (resetCooldownActive ? `Retry in ${resetCooldownSeconds}s` : 'Send Reset Link')
|
|
1063
|
+
: authMode === 'reset'
|
|
1064
|
+
? 'Reset Password'
|
|
1065
|
+
: (inviteResolution === 'existing_user_signed_out'
|
|
1066
|
+
? 'Sign In to Continue'
|
|
1067
|
+
: (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: () => {
|
|
1068
|
+
setLoginError(null);
|
|
1069
|
+
setLoginErrorCode(null);
|
|
1070
|
+
setLoginNotice(null);
|
|
1071
|
+
setLoginMode(authMode === 'login' ? 'register' : 'login');
|
|
1072
|
+
}, 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 () => {
|
|
1073
|
+
setLoginBusy(true);
|
|
1074
|
+
setLoginNotice(null);
|
|
1075
|
+
setLoginError(null);
|
|
1076
|
+
setLoginErrorCode(null);
|
|
1077
|
+
const sent = await requestEmailVerification(loginEmail);
|
|
1078
|
+
if (sent.success) {
|
|
1079
|
+
setVerifyCooldownUntil(Date.now() + 30_000);
|
|
1080
|
+
if (sent.verificationToken)
|
|
1081
|
+
setVerificationToken(sent.verificationToken);
|
|
1082
|
+
setLoginNotice('Verification email resent.');
|
|
1083
|
+
}
|
|
1084
|
+
else {
|
|
1085
|
+
setLoginError(formatAuthError(sent));
|
|
1086
|
+
}
|
|
1087
|
+
setLoginBusy(false);
|
|
1088
|
+
}, 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
1089
|
}
|
|
859
1090
|
if (shouldForceLogin && !isLoginRoute) {
|
|
860
1091
|
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 +1096,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
865
1096
|
}, 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
1097
|
}
|
|
867
1098
|
const showGuidedWorkspaceSetup = isCreateWorkspaceIntent
|
|
1099
|
+
|| (isSetupRoute && Boolean(pendingCloudWorkspaceImport))
|
|
868
1100
|
|| (isSetupRoute && setupGateActive && (hasSetupReadError || needsGlobalSetup || needsWorkspaceSetup));
|
|
869
1101
|
if (showGuidedWorkspaceSetup) {
|
|
870
1102
|
const isError = !isCreateWorkspaceIntent && (hasSetupReadError || setupStep === 'error');
|
|
@@ -873,25 +1105,52 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
873
1105
|
? t('setup.headingCheckFailed')
|
|
874
1106
|
: (isGlobal
|
|
875
1107
|
? t('setup.headingGlobalRequired')
|
|
876
|
-
:
|
|
1108
|
+
: t('setup.headingWorkspaceRequired'));
|
|
877
1109
|
const subtitle = isError
|
|
878
1110
|
? t('setup.subtitleUnreadable')
|
|
879
1111
|
: isGlobal
|
|
880
1112
|
? t('setup.subtitleGlobalRequired')
|
|
881
|
-
:
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
const
|
|
885
|
-
|
|
886
|
-
|
|
1113
|
+
: null;
|
|
1114
|
+
const workspaceLabel = 'Workspace';
|
|
1115
|
+
const isLocalWorkspaceSetup = isCloudRuntime || workspaceSetupSource !== 'cloud';
|
|
1116
|
+
const isWorkspaceDetailsPage = isLocalWorkspaceSetup && workspaceSetupPage === 'details';
|
|
1117
|
+
const isWorkspaceStarterPage = isLocalWorkspaceSetup && workspaceSetupPage === 'starter';
|
|
1118
|
+
const showWorkspaceSourceSelector = !isCloudRuntime
|
|
1119
|
+
&& canUseCloudWorkspaceSetup
|
|
1120
|
+
&& isWorkspaceDetailsPage
|
|
1121
|
+
&& !pendingCloudWorkspaceImport;
|
|
1122
|
+
const runtimeIcon = runtimeCapabilities?.runtimeMode === 'cloud' ? Cloud : LaptopMinimal;
|
|
1123
|
+
const starterCategoryItems = selectedSetupPack ? formatStarterSectionItems(selectedSetupPack.categories) : '';
|
|
1124
|
+
const starterTypeItems = selectedSetupPack ? formatStarterSectionItems(selectedSetupPack.types) : '';
|
|
1125
|
+
const starterPriorityItems = selectedSetupPack ? formatStarterSectionItems(selectedSetupPack.priorities) : '';
|
|
1126
|
+
const setupPrimaryActionLabel = setupBusy
|
|
1127
|
+
? t('setup.saving')
|
|
1128
|
+
: wantsCloudWorkspaceSetup
|
|
1129
|
+
? (pendingCloudWorkspaceImport
|
|
1130
|
+
? (isCloudWorkspaceImportErrored ? 'Retry Import' : 'Importing Workspace...')
|
|
1131
|
+
: 'Connect Workspace')
|
|
1132
|
+
: isWorkspaceDetailsPage
|
|
1133
|
+
? 'Continue'
|
|
1134
|
+
: isWorkspaceStarterPage
|
|
1135
|
+
? 'Create Workspace'
|
|
1136
|
+
: 'Connect Workspace';
|
|
887
1137
|
return (_jsx("div", { className: shellStyles.standaloneWrapper, "data-theme": currentTheme, children: _jsx("div", { className: authStyles.loginView, style: {
|
|
888
1138
|
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
|
-
|
|
1139
|
+
}, 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: [!pendingCloudWorkspaceImport && !canUseCloudWorkspaceSetup && (_jsx("p", { className: authStyles.loginError, children: "Cloud workspace sync is unavailable right now. Switch back to local setup or refresh runtime configuration." })), !pendingCloudWorkspaceImport && 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: () => {
|
|
1140
|
+
navigate(`/login?mode=login&next=${encodeURIComponent(cloudWorkspaceSelectionPath)}`, { replace: true });
|
|
1141
|
+
}, children: "Sign In" }), directRegistrationEnabled && (_jsx("button", { className: styles.submitBtn, disabled: setupBusy, onClick: () => {
|
|
1142
|
+
navigate(`/login?mode=register&next=${encodeURIComponent(cloudWorkspaceSelectionPath)}`, { replace: true });
|
|
1143
|
+
}, children: "Register" }))] })] })), !pendingCloudWorkspaceImport && 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 })] })), pendingCloudWorkspaceImport && (_jsxs(_Fragment, { children: [_jsxs("p", { className: authStyles.inlineHint, children: ["Importing \"", pendingCloudWorkspaceImport.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
|
|
1144
|
+
? 'Finishing setup and opening your workspace.'
|
|
1145
|
+
: (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 })] })), isWorkspaceStarterPage && (_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 starter options...' : 'No starter 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 Starter 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 starter 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: [isWorkspaceStarterPage ? (_jsx("button", { className: styles.cancelBtn, onClick: () => {
|
|
1146
|
+
setSetupError(null);
|
|
1147
|
+
setSetupNotice(null);
|
|
1148
|
+
setWorkspaceSetupPage('details');
|
|
1149
|
+
}, children: "Back" })) : isCreateWorkspaceIntent ? (_jsx("button", { className: styles.cancelBtn, onClick: () => {
|
|
1150
|
+
setSetupError(null);
|
|
1151
|
+
setSetupNotice(null);
|
|
1152
|
+
navigate('/', { replace: true });
|
|
1153
|
+
}, 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(pendingCloudWorkspaceImport) && !isCloudWorkspaceImportErrored), onClick: async () => {
|
|
895
1154
|
setSetupBusy(true);
|
|
896
1155
|
setSetupNotice(null);
|
|
897
1156
|
setSetupError(null);
|
|
@@ -917,11 +1176,32 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
917
1176
|
finally {
|
|
918
1177
|
setSetupBusy(false);
|
|
919
1178
|
}
|
|
920
|
-
}, children: setupBusy ? t('setup.saving') : t('setup.saveGlobalSetup') })) : !isError ? (_jsx("button", { className: styles.submitBtn, disabled: setupBusy
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1179
|
+
}, children: setupBusy ? t('setup.saving') : t('setup.saveGlobalSetup') })) : !isError ? (_jsx("button", { className: styles.submitBtn, disabled: setupBusy
|
|
1180
|
+
|| (isCloudWorkspaceSetupFlow && !pendingCloudWorkspaceImport && !isAuthenticated), onClick: async () => {
|
|
1181
|
+
if (isCloudWorkspaceSetupFlow && pendingCloudWorkspaceImport) {
|
|
1182
|
+
if (!isCloudWorkspaceImportErrored)
|
|
1183
|
+
return;
|
|
1184
|
+
setSetupBusy(true);
|
|
1185
|
+
setSetupNotice(`Retrying import for ${pendingCloudWorkspaceImport.name}...`);
|
|
1186
|
+
setSetupError(null);
|
|
1187
|
+
try {
|
|
1188
|
+
await retryWorkspaceCloudSync();
|
|
1189
|
+
}
|
|
1190
|
+
catch {
|
|
1191
|
+
setSetupNotice(null);
|
|
1192
|
+
setSetupError(`Failed to retry import for "${pendingCloudWorkspaceImport.name}".`);
|
|
1193
|
+
}
|
|
1194
|
+
finally {
|
|
1195
|
+
setSetupBusy(false);
|
|
1196
|
+
}
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
if (wantsCloudWorkspaceSetup) {
|
|
1200
|
+
if (!canUseCloudWorkspaceSetup) {
|
|
1201
|
+
setSetupError('Cloud workspace sync is unavailable right now.');
|
|
1202
|
+
setSetupBusy(false);
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
925
1205
|
if (!isAuthenticated) {
|
|
926
1206
|
setSetupError('Sign in is required before syncing a cloud project.');
|
|
927
1207
|
setSetupBusy(false);
|
|
@@ -979,14 +1259,40 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
979
1259
|
pullCursor: null
|
|
980
1260
|
});
|
|
981
1261
|
await refreshSetupContext();
|
|
982
|
-
|
|
1262
|
+
setPendingCloudWorkspaceImport({
|
|
1263
|
+
workspaceId: workspaceIdForSync,
|
|
1264
|
+
name: selectedWorkspace.name || selectedWorkspace.id
|
|
1265
|
+
});
|
|
1266
|
+
setCloudWorkspaceImportFinalizing(false);
|
|
1267
|
+
setSetupNotice(`Importing ${selectedWorkspace.name || selectedWorkspace.id} from cloud...`);
|
|
1268
|
+
setSetupBusy(false);
|
|
1269
|
+
return;
|
|
983
1270
|
}
|
|
984
|
-
|
|
1271
|
+
if (isWorkspaceDetailsPage) {
|
|
1272
|
+
const normalizedWorkspaceName = workspaceNameInput.trim();
|
|
1273
|
+
if (!normalizedWorkspaceName) {
|
|
1274
|
+
setSetupError('Workspace name is required before continuing.');
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
setSetupError(null);
|
|
1278
|
+
setSetupNotice(null);
|
|
1279
|
+
setWorkspaceSetupPage('starter');
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
if (isWorkspaceStarterPage) {
|
|
1283
|
+
// fall through to final create/save behavior below
|
|
1284
|
+
}
|
|
1285
|
+
setSetupBusy(true);
|
|
1286
|
+
setSetupNotice(null);
|
|
1287
|
+
setSetupError(null);
|
|
1288
|
+
{
|
|
985
1289
|
const creatingNewWorkspace = isCreateWorkspaceIntent || setupState?.workspaceSetupState === 'missing';
|
|
986
|
-
const
|
|
1290
|
+
const shouldCreateWorkspace = isCreateWorkspaceIntent
|
|
1291
|
+
|| (workspaceSwitchingEnabled && creatingNewWorkspace);
|
|
1292
|
+
const createResult = shouldCreateWorkspace
|
|
987
1293
|
? await createWorkspace(workspaceNameInput, workspaceDescriptionInput || undefined)
|
|
988
1294
|
: null;
|
|
989
|
-
const saveResult =
|
|
1295
|
+
const saveResult = shouldCreateWorkspace
|
|
990
1296
|
? null
|
|
991
1297
|
: await saveWorkspaceProfile({
|
|
992
1298
|
workspaceId: creatingNewWorkspace ? undefined : setupState?.workspaceId,
|
|
@@ -1068,11 +1374,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
|
|
|
1068
1374
|
}
|
|
1069
1375
|
}
|
|
1070
1376
|
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] })] }) }) }));
|
|
1377
|
+
}, children: setupPrimaryActionLabel })) : (_jsx("button", { className: styles.submitBtn, disabled: setupBusy, onClick: refreshSetupContext, children: t('setup.retrySetupCheck') }))] })] }) }) }));
|
|
1076
1378
|
}
|
|
1077
1379
|
return (_jsx(StandaloneLayout, { ...taskforceState, onHeaderMouseDown: onHeaderMouseDown, isDragging: isDragging, onClose: onClose }));
|
|
1078
1380
|
}
|