@taskforcehq/taskforce 0.3.302 → 0.3.303
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Taskforce.module.css +11 -0
- package/dist/TaskforceCore.js +59 -9
- package/dist/TaskforceCore.test.js +717 -14
- package/dist/components/task/TaskKanban.test.js +17 -0
- package/dist/components/views/PlansPage.d.ts +3 -1
- package/dist/components/views/PlansPage.js +110 -51
- package/dist/components/views/PlansPage.test.d.ts +1 -0
- package/dist/components/views/PlansPage.test.js +100 -0
- package/dist/components/views/StandaloneLayout.d.ts +1 -0
- package/dist/components/views/StandaloneLayout.js +106 -151
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +8 -3
- package/dist/components/views/standalone/modals/AccountHubModal.js +16 -2
- package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +78 -18
- package/dist/core/PlanEntitlementService.d.ts +29 -5
- package/dist/core/PlanEntitlementService.js +297 -219
- package/dist/core/SignupMonetizationSettings.test.js +173 -49
- package/dist/core/SystemAdmin.test.js +186 -96
- package/dist/core/Taskforce.d.ts +51 -10
- package/dist/core/Taskforce.js +218 -49
- package/dist/core/Taskforce.mcpAuth.test.js +0 -2
- package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
- package/dist/core/types.d.ts +11 -12
- package/dist/hooks/useSyncOrchestrator.js +17 -3
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +97 -5
- package/dist/hooks/useTaskforce.d.ts +3 -0
- package/dist/hooks/useTaskforce.js +9 -4
- package/dist/mcp/runtime.js +90 -29
- package/dist/mcp/runtime.test.js +272 -81
- package/dist/migrations/billingSchemaParity.test.js +3 -0
- package/dist/migrations/taskSchemaMigrations.js +15 -0
- package/dist/server/index.cookieProxy.test.js +1 -0
- package/dist/server/index.js +2 -0
- package/dist/server/index.test.js +2 -1
- package/dist/server/mockStripe.d.ts +1 -0
- package/dist/server/mockStripe.js +2 -0
- package/dist/server/routes/admin.js +80 -23
- package/dist/server/routes/auth.d.ts +1 -0
- package/dist/server/routes/auth.js +194 -53
- package/dist/server/routes/billing.js +506 -23
- package/dist/server/routes/billing.test.js +937 -45
- package/dist/server/routes/shared.js +5 -3
- package/dist/server/routes/sync.integration.test.js +160 -0
- package/dist/server/routes/sync.js +15 -5
- package/dist/server/routes.js +3 -3
- package/dist/server/routes.test.js +287 -76
- package/dist/sync/cloudSyncApi.d.ts +1 -0
- package/dist/sync/contentSyncState.d.ts +10 -0
- package/dist/sync/contentSyncState.js +277 -16
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +21 -8
- package/dist/sync/workspacePullFeed.d.ts +10 -0
- package/dist/sync/workspacePullFeed.js +111 -63
- package/dist/sync/workspacePullFeed.test.js +112 -0
- package/dist/ui/assets/{AgentsModule-JG5jc3he.js → AgentsModule-CpsTmrIW.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-C_TmXZwA.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-C3GyzrWm.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-4-Rh2K2n.js} +1 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +1 -0
- package/dist/ui/assets/{TaskSettings-Bqd4cCzW.js → TaskSettings-BOC5F6Ag.js} +1 -1
- package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-CGk9s3NJ.js} +1 -1
- package/dist/ui/assets/index-CLIMgR6g.js +6 -0
- package/dist/ui/assets/index-DneETxcu.css +1 -0
- package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-pWocEipT.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
- package/dist/utils/accountProfileSummaryCache.js +97 -0
- package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
- package/dist/utils/accountProfileSummaryCache.test.js +63 -0
- package/package.json +3 -3
- package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
- package/dist/ui/assets/index-MXeWoebC.css +0 -1
- package/dist/ui/assets/index-YJ4k5QNa.js +0 -6
- package/dist/utils/billingStatusCache.d.ts +0 -21
- package/dist/utils/billingStatusCache.js +0 -70
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import React, { useMemo, useCallback, useState, useEffect, useRef, useLayoutEffect, Suspense } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
import { useLocation, useNavigate } from 'react-router-dom';
|
|
5
|
-
import { Settings, Plus, Filter, Layers, User, Maximize2, Minimize2, Check, ChevronLeft, ArrowUp, ArrowDown, ArrowUpDown, AlertTriangle, Save, Loader2, Folder, FileText, LogOut, NotebookTabs, ClipboardCheck, Cloud, CloudOff, Users, ChevronsUp, ChevronsDown, EyeOff, Zap, HelpCircle, Bot, Image, Workflow } from 'lucide-react';
|
|
5
|
+
import { Settings, Plus, Filter, Layers, User, Maximize2, Minimize2, Check, ChevronLeft, ArrowUp, ArrowDown, ArrowUpDown, AlertTriangle, Save, Loader2, Folder, FileText, LogOut, NotebookTabs, ClipboardCheck, Cloud, CloudOff, Users, ClockAlert, ChevronsUp, ChevronsDown, EyeOff, Zap, HelpCircle, Bot, Image, Workflow } from 'lucide-react';
|
|
6
6
|
import { STATUS_OPTIONS, getStatusDefinition } from '../../utils/constants';
|
|
7
7
|
import styles from '../../Taskforce.module.css';
|
|
8
8
|
import shellStyles from './WorkspaceShell.module.css';
|
|
@@ -33,7 +33,7 @@ import { getImageReferenceLabel } from '../../utils/imageReferences';
|
|
|
33
33
|
import { buildCustomTaxonomySortOptions } from '../../utils/taxonomySorting.js';
|
|
34
34
|
import { getLocale, t } from '../../localization';
|
|
35
35
|
import { resolveAnnotatedAttachmentDetail } from '../../utils/annotatedAttachments';
|
|
36
|
-
import {
|
|
36
|
+
import { ACCOUNT_PROFILE_SUMMARY_PATH, invalidateAccountProfileSummaryCache, loadAccountProfileSummaryCached } from '../../utils/accountProfileSummaryCache';
|
|
37
37
|
import { fetchTaskforceApi } from '../../utils/taskforceApiClient';
|
|
38
38
|
import { AppShellHeader } from './AppShellHeader';
|
|
39
39
|
import { getTaskforceCloudEnvironmentConfig, parseTaskforceCloudEnvironment, resolveTaskforceCloudEnvironmentFromBaseUrl } from '../../config/cloudEnvironment';
|
|
@@ -58,6 +58,7 @@ const InitiativesModule = React.lazy(() => import('../features/InitiativesModule
|
|
|
58
58
|
const PlansPage = React.lazy(() => import('./PlansPage').then(m => ({ default: m.PlansPage })));
|
|
59
59
|
const PROFILE_AVATAR_ACCEPT = 'image/png,image/jpeg,image/webp,image/gif';
|
|
60
60
|
const PROFILE_AVATAR_MAX_BYTES = 5 * 1024 * 1024;
|
|
61
|
+
const resolveSameOriginTaskforcePath = (path) => path;
|
|
61
62
|
function getSyncEventStableKey(event, index = 0) {
|
|
62
63
|
const explicitId = String(event.id ?? '').trim();
|
|
63
64
|
if (explicitId)
|
|
@@ -215,7 +216,7 @@ export function StandaloneLayout(props) {
|
|
|
215
216
|
// clearFilters, // This was duplicated, removed one instance
|
|
216
217
|
// activeTypes, // This was duplicated, removed one instance
|
|
217
218
|
// Settings State & Props
|
|
218
|
-
settingsModel, configLoaded, currentTheme, setCurrentTheme, pathSaved, saveSettings, keyShortcut, setKeyShortcut, globalWeekStartsOn, locale, jsonBackupEnabled, setJsonBackupEnabled, mcpHostRoot, setMcpHostRoot, settingsSection, setSettingsSection, runtimeMode, workspaceSwitchingEnabled, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, authUserId, authWorkspaceId, authUserEmail, authUserDisplayName, authUserAvatarUrl, authSessionResolved, realtimeSyncEnabled, realtimeSyncFlagSource, workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceSyncStatus, workspaceSyncSummary, workspaceSyncRecommendedAction, workspaceSyncBusy, workspaceSyncPendingChanges, saveWorkspaceCloudSyncSettings, pushNotice, userGlobalSyncStatus, workspaceLastSuccessfulSyncAt, workspaceLastPullAt, workspaceLastPushAt, workspaceLastErrorAt, userGlobalSyncError, workspaceLastErrorMessage, retryWorkspaceCloudSync, resetWorkspaceSyncCursorAndPull, getWorkspaceSyncDiagnostics, currentWorkspaceId, currentWorkspaceRole, availableWorkspaces, switchWorkspace, updateCurrentUserProfile, logout, projectRoot, projectName, mcpScriptPath, serverHostRoot,
|
|
219
|
+
settingsModel, configLoaded, currentTheme, setCurrentTheme, pathSaved, saveSettings, keyShortcut, setKeyShortcut, globalWeekStartsOn, locale, jsonBackupEnabled, setJsonBackupEnabled, mcpHostRoot, setMcpHostRoot, settingsSection, setSettingsSection, runtimeMode, workspaceSwitchingEnabled, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, authUserId, authWorkspaceId, authUserEmail, authUserDisplayName, authUserAvatarUrl, authSessionResolved, realtimeSyncEnabled, realtimeSyncFlagSource, workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceSyncStatus, workspaceSyncSummary, workspaceSyncRecommendedAction, workspaceSyncBusy, workspaceSyncPendingChanges, saveWorkspaceCloudSyncSettings, pushNotice, userGlobalSyncStatus, workspaceLastSuccessfulSyncAt, workspaceLastPullAt, workspaceLastPushAt, workspaceLastErrorAt, userGlobalSyncError, workspaceLastErrorMessage, retryWorkspaceCloudSync, resetWorkspaceSyncCursorAndPull, getWorkspaceSyncDiagnostics, currentWorkspaceId, currentWorkspaceRole, availableWorkspaces, switchWorkspace, updateCurrentUserProfile, resolveCloudAuthUrl = resolveSameOriginTaskforcePath, logout, projectRoot, projectName, mcpScriptPath, serverHostRoot,
|
|
219
220
|
// Browser Props
|
|
220
221
|
showFolderBrowser, setShowFolderBrowser, folders, files, currentBrowsePath, fetchFolders, browserTarget, setBrowserTarget, handleSelectPath, handleAddPath, handleRemovePath,
|
|
221
222
|
// Settings Managers
|
|
@@ -804,9 +805,9 @@ export function StandaloneLayout(props) {
|
|
|
804
805
|
if (taskScope === 'deleted')
|
|
805
806
|
return deletedScopeAllTasks;
|
|
806
807
|
if (!showArchive)
|
|
807
|
-
return props.
|
|
808
|
-
return [...props.
|
|
809
|
-
}, [archivedScopeAllTasks, deletedScopeAllTasks, props.
|
|
808
|
+
return props.tasks;
|
|
809
|
+
return [...props.tasks, ...archivedScopeAllTasks];
|
|
810
|
+
}, [archivedScopeAllTasks, deletedScopeAllTasks, props.tasks, showArchive, taskScope]);
|
|
810
811
|
const hierarchyTasks = useMemo(() => {
|
|
811
812
|
const byId = new Map();
|
|
812
813
|
for (const task of archivedScopeAllTasks)
|
|
@@ -1036,6 +1037,11 @@ export function StandaloneLayout(props) {
|
|
|
1036
1037
|
return boardVisibleTasks;
|
|
1037
1038
|
return boardVisibleTasks.filter((task) => selectedPlanningTaskIds.has(task.id));
|
|
1038
1039
|
}, [boardVisibleTasks, selectedPlanningTaskIds]);
|
|
1040
|
+
const scopedKanbanAllTasks = useMemo(() => {
|
|
1041
|
+
if (!selectedPlanningTaskIds || selectedPlanningTaskIds.size === 0)
|
|
1042
|
+
return kanbanAllTasks;
|
|
1043
|
+
return kanbanAllTasks.filter((task) => selectedPlanningTaskIds.has(task.id));
|
|
1044
|
+
}, [kanbanAllTasks, selectedPlanningTaskIds]);
|
|
1039
1045
|
const expiredRecoveryCandidates = useMemo(() => {
|
|
1040
1046
|
if (groupBy !== 'schedule')
|
|
1041
1047
|
return [];
|
|
@@ -1218,7 +1224,7 @@ export function StandaloneLayout(props) {
|
|
|
1218
1224
|
const [teamAuditEvents, setTeamAuditEvents] = useState([]);
|
|
1219
1225
|
const [teamAuditHasMore, setTeamAuditHasMore] = useState(false);
|
|
1220
1226
|
const [teamAuditPages, setTeamAuditPages] = useState(1);
|
|
1221
|
-
const [
|
|
1227
|
+
const [accountProfileSummary, setAccountProfileSummary] = useState(null);
|
|
1222
1228
|
const [billingLoading, setBillingLoading] = useState(false);
|
|
1223
1229
|
const [billingError, setBillingError] = useState(null);
|
|
1224
1230
|
const [billingActionBusy, setBillingActionBusy] = useState(false);
|
|
@@ -1240,7 +1246,11 @@ export function StandaloneLayout(props) {
|
|
|
1240
1246
|
billing: false,
|
|
1241
1247
|
teamManagement: false
|
|
1242
1248
|
});
|
|
1249
|
+
const accountProfileSummaryRef = useRef(null);
|
|
1243
1250
|
// showSearch state removed - search always visible in actions area
|
|
1251
|
+
useEffect(() => {
|
|
1252
|
+
accountProfileSummaryRef.current = accountProfileSummary;
|
|
1253
|
+
}, [accountProfileSummary]);
|
|
1244
1254
|
useEffect(() => {
|
|
1245
1255
|
if (!showAccountMenu)
|
|
1246
1256
|
return;
|
|
@@ -1291,33 +1301,17 @@ export function StandaloneLayout(props) {
|
|
|
1291
1301
|
const authIdentityLabel = authDisplayName || authEmailLabel;
|
|
1292
1302
|
const hasAuthIdentity = authIdentityLabel.length > 0;
|
|
1293
1303
|
const hasAuthEmail = authEmailLabel.length > 0;
|
|
1294
|
-
const accountBadgeLabel = (authIdentityLabel || authEmailLabel || '
|
|
1295
|
-
const
|
|
1304
|
+
const accountBadgeLabel = (authIdentityLabel || authEmailLabel || '').trim();
|
|
1305
|
+
const shouldShowResolvedAccountInitial = isAuthenticated && authSessionResolved && accountBadgeLabel.length > 0;
|
|
1306
|
+
const accountBadgeInitial = shouldShowResolvedAccountInitial ? accountBadgeLabel.charAt(0).toUpperCase() : '';
|
|
1296
1307
|
const accountAvatarUrl = authAvatarUrl;
|
|
1308
|
+
const shouldShowResolvedAccountAvatar = isAuthenticated && authSessionResolved && accountAvatarUrl.length > 0;
|
|
1297
1309
|
const profileAvatarDisplayUrl = String(profileAvatarPreviewUrl || authAvatarUrl).trim();
|
|
1298
|
-
const subscriptionLabel = String(
|
|
1310
|
+
const subscriptionLabel = String(accountProfileSummary?.planName || accountProfileSummary?.planId || '').trim();
|
|
1299
1311
|
const hasSubscription = subscriptionLabel.length > 0;
|
|
1300
|
-
const teamManagementWorkspaceId = String(authWorkspaceId || currentWorkspaceId || '').trim();
|
|
1312
|
+
const teamManagementWorkspaceId = String(accountProfileSummary?.workspaceId || authWorkspaceId || currentWorkspaceId || '').trim();
|
|
1301
1313
|
const runtimeLabel = runtimeMode === 'cloud' ? 'CLOUD' : 'LOCAL';
|
|
1302
1314
|
const apiBaseUrl = props.config?.apiBaseUrl || '';
|
|
1303
|
-
const accountCapabilityApiBaseUrl = useMemo(() => {
|
|
1304
|
-
const preferredBase = runtimeMode === 'local'
|
|
1305
|
-
? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
|
|
1306
|
-
: (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
|
|
1307
|
-
return String(preferredBase || '').trim().replace(/\/+$/, '');
|
|
1308
|
-
}, [props.config?.apiBaseUrl, props.config?.cloudAuthBaseUrl, runtimeMode]);
|
|
1309
|
-
const billingApiBaseUrl = useMemo(() => {
|
|
1310
|
-
const preferredBase = runtimeMode === 'local'
|
|
1311
|
-
? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
|
|
1312
|
-
: (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
|
|
1313
|
-
return String(preferredBase || '').trim().replace(/\/+$/, '');
|
|
1314
|
-
}, [props.config?.apiBaseUrl, props.config?.cloudAuthBaseUrl, runtimeMode]);
|
|
1315
|
-
const buildBillingApiUrl = useCallback((path) => {
|
|
1316
|
-
const normalizedPath = String(path || '').trim();
|
|
1317
|
-
if (!accountCapabilityApiBaseUrl)
|
|
1318
|
-
return normalizedPath;
|
|
1319
|
-
return `${accountCapabilityApiBaseUrl}${normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`}`;
|
|
1320
|
-
}, [accountCapabilityApiBaseUrl]);
|
|
1321
1315
|
const connectedEnvironmentLabel = useMemo(() => resolveConnectedEnvironmentLabel(props.config?.cloudEnvironment, [
|
|
1322
1316
|
String(props.config?.cloudBaseUrl || ''),
|
|
1323
1317
|
String(props.config?.cloudAuthBaseUrl || ''),
|
|
@@ -1501,14 +1495,15 @@ export function StandaloneLayout(props) {
|
|
|
1501
1495
|
const workspaceId = String(teamManagementWorkspaceId || '').trim();
|
|
1502
1496
|
if (workspaceId)
|
|
1503
1497
|
headers['x-taskforce-workspace-id'] = workspaceId;
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
||
|
|
1507
|
-
||
|
|
1508
|
-
|
|
1498
|
+
const workspaceRole = teamWorkspaceRole || currentWorkspaceRole;
|
|
1499
|
+
if (workspaceRole === 'owner'
|
|
1500
|
+
|| workspaceRole === 'admin'
|
|
1501
|
+
|| workspaceRole === 'member'
|
|
1502
|
+
|| workspaceRole === 'read-only') {
|
|
1503
|
+
headers['x-taskforce-workspace-role'] = workspaceRole;
|
|
1509
1504
|
}
|
|
1510
1505
|
return headers;
|
|
1511
|
-
}, [currentWorkspaceRole, teamManagementWorkspaceId]);
|
|
1506
|
+
}, [currentWorkspaceRole, teamManagementWorkspaceId, teamWorkspaceRole]);
|
|
1512
1507
|
const startAccountSurfaceTiming = useCallback((surface) => {
|
|
1513
1508
|
const expectsBilling = isAuthenticated;
|
|
1514
1509
|
const expectsTeamManagement = canAccessCloudTeamManagement;
|
|
@@ -1544,59 +1539,96 @@ export function StandaloneLayout(props) {
|
|
|
1544
1539
|
surface: showAccountHub ? 'account_hub' : (showAccountMenu ? 'account_menu' : 'closed'),
|
|
1545
1540
|
durationMs: Math.round(getTimingNow() - startedAt),
|
|
1546
1541
|
teamManagementVisible: canOpenTeamManagement,
|
|
1547
|
-
billingPlanId: String(
|
|
1542
|
+
billingPlanId: String(accountProfileSummary?.planId || '').trim() || null
|
|
1548
1543
|
});
|
|
1549
1544
|
accountSurfaceOpenStartedAtRef.current = null;
|
|
1550
|
-
}, [
|
|
1551
|
-
const
|
|
1552
|
-
const
|
|
1553
|
-
const billingStatusUrl = buildBillingApiUrl(billingStatusPath);
|
|
1545
|
+
}, [accountProfileSummary?.planId, canOpenTeamManagement, showAccountHub, showAccountMenu]);
|
|
1546
|
+
const loadAccountProfileSummary = useCallback(async () => {
|
|
1547
|
+
const accountProfileSummaryUrl = resolveCloudAuthUrl(ACCOUNT_PROFILE_SUMMARY_PATH);
|
|
1554
1548
|
if (!isAuthenticated) {
|
|
1555
|
-
|
|
1549
|
+
setAccountProfileSummary(null);
|
|
1556
1550
|
setBillingError(null);
|
|
1557
|
-
|
|
1551
|
+
setTeamWorkspaceRole(null);
|
|
1552
|
+
setTeamPlanMode('unknown');
|
|
1553
|
+
setTeamManagementAccessLoading(false);
|
|
1554
|
+
setTeamManagementAccessError(null);
|
|
1555
|
+
invalidateAccountProfileSummaryCache(accountProfileSummaryUrl, { identityKey: authUserId });
|
|
1558
1556
|
markAccountSurfaceLoadSettled('billing');
|
|
1557
|
+
markAccountSurfaceLoadSettled('teamManagement');
|
|
1559
1558
|
return null;
|
|
1560
1559
|
}
|
|
1561
1560
|
const startedAt = getTimingNow();
|
|
1562
1561
|
setBillingLoading(true);
|
|
1563
1562
|
setBillingError(null);
|
|
1563
|
+
setTeamManagementAccessLoading(canAccessCloudTeamManagement);
|
|
1564
|
+
setTeamManagementAccessError(null);
|
|
1564
1565
|
try {
|
|
1565
|
-
const payload = await
|
|
1566
|
-
|
|
1567
|
-
|
|
1566
|
+
const payload = await loadAccountProfileSummaryCached(accountProfileSummaryUrl, { identityKey: authUserId });
|
|
1567
|
+
const nextRole = payload?.workspaceRole === 'owner'
|
|
1568
|
+
|| payload?.workspaceRole === 'admin'
|
|
1569
|
+
|| payload?.workspaceRole === 'member'
|
|
1570
|
+
|| payload?.workspaceRole === 'read-only'
|
|
1571
|
+
? payload.workspaceRole
|
|
1572
|
+
: null;
|
|
1573
|
+
const nextMode = payload?.teamPlanMode === 'team' || payload?.teamPlanMode === 'personal'
|
|
1574
|
+
? payload.teamPlanMode
|
|
1575
|
+
: 'unknown';
|
|
1576
|
+
setAccountProfileSummary(payload);
|
|
1577
|
+
setTeamWorkspaceRole(nextRole);
|
|
1578
|
+
setTeamPlanMode(nextMode);
|
|
1579
|
+
setTeamManagementAccessError(null);
|
|
1580
|
+
logBootstrapDebug('account_profile_summary_resolved', {
|
|
1568
1581
|
durationMs: Math.round(getTimingNow() - startedAt),
|
|
1569
1582
|
planId: String(payload?.planId || '').trim() || null,
|
|
1570
|
-
gate: String(payload?.gate || '').trim() || null
|
|
1583
|
+
gate: String(payload?.gate || '').trim() || null,
|
|
1584
|
+
teamManagementAllowed: payload?.teamManagementAllowed === true
|
|
1571
1585
|
});
|
|
1572
1586
|
return payload;
|
|
1573
1587
|
}
|
|
1574
1588
|
catch (error) {
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1589
|
+
const message = error instanceof Error ? error.message : 'Failed to load account summary.';
|
|
1590
|
+
const hasPreviousSummary = Boolean(accountProfileSummaryRef.current);
|
|
1591
|
+
setBillingError(message);
|
|
1592
|
+
if (!hasPreviousSummary) {
|
|
1593
|
+
setTeamWorkspaceRole(null);
|
|
1594
|
+
setTeamPlanMode('unknown');
|
|
1595
|
+
setTeamManagementAccessError(message);
|
|
1596
|
+
}
|
|
1597
|
+
else {
|
|
1598
|
+
setTeamManagementAccessError(null);
|
|
1599
|
+
}
|
|
1600
|
+
logBootstrapDebug('account_profile_summary_failed', {
|
|
1578
1601
|
durationMs: Math.round(getTimingNow() - startedAt),
|
|
1579
|
-
error:
|
|
1602
|
+
error: message,
|
|
1603
|
+
preservedSummary: hasPreviousSummary
|
|
1580
1604
|
});
|
|
1581
|
-
return
|
|
1605
|
+
return accountProfileSummaryRef.current;
|
|
1582
1606
|
}
|
|
1583
1607
|
finally {
|
|
1584
1608
|
setBillingLoading(false);
|
|
1609
|
+
setTeamManagementAccessLoading(false);
|
|
1585
1610
|
markAccountSurfaceLoadSettled('billing');
|
|
1611
|
+
markAccountSurfaceLoadSettled('teamManagement');
|
|
1586
1612
|
}
|
|
1587
|
-
}, [
|
|
1613
|
+
}, [authUserId, canAccessCloudTeamManagement, isAuthenticated, markAccountSurfaceLoadSettled, resolveCloudAuthUrl]);
|
|
1588
1614
|
const handleStartCheckout = useCallback(async () => {
|
|
1589
1615
|
const params = new URLSearchParams();
|
|
1590
1616
|
params.set('screen', 'plans');
|
|
1591
1617
|
params.set('interval', billingIntervalChoice);
|
|
1618
|
+
const planId = String(accountProfileSummary?.planId || '').trim();
|
|
1619
|
+
const planVersionId = String(accountProfileSummary?.planVersionId || '').trim();
|
|
1620
|
+
if (planId)
|
|
1621
|
+
params.set('planId', planId);
|
|
1622
|
+
if (planVersionId)
|
|
1623
|
+
params.set('planVersionId', planVersionId);
|
|
1592
1624
|
navigate(`/?${params.toString()}`);
|
|
1593
|
-
}, [billingIntervalChoice, navigate]);
|
|
1625
|
+
}, [accountProfileSummary?.planId, accountProfileSummary?.planVersionId, billingIntervalChoice, navigate]);
|
|
1594
1626
|
const handleOpenBillingPortal = useCallback(async () => {
|
|
1595
1627
|
setBillingActionError(null);
|
|
1596
1628
|
setBillingNotice(null);
|
|
1597
1629
|
setBillingActionBusy(true);
|
|
1598
1630
|
try {
|
|
1599
|
-
const res = await fetch(
|
|
1631
|
+
const res = await fetch(resolveCloudAuthUrl('/api/taskforce/billing/portal-session'), {
|
|
1600
1632
|
method: 'POST',
|
|
1601
1633
|
credentials: 'include'
|
|
1602
1634
|
});
|
|
@@ -1618,18 +1650,18 @@ export function StandaloneLayout(props) {
|
|
|
1618
1650
|
finally {
|
|
1619
1651
|
setBillingActionBusy(false);
|
|
1620
1652
|
}
|
|
1621
|
-
}, [
|
|
1653
|
+
}, [resolveCloudAuthUrl]);
|
|
1622
1654
|
const handleSwitchSubscriptionInterval = useCallback(async () => {
|
|
1623
1655
|
setBillingActionError(null);
|
|
1624
1656
|
setBillingNotice(null);
|
|
1625
1657
|
setBillingActionBusy(true);
|
|
1626
1658
|
try {
|
|
1627
|
-
const currentPlanVersionId = String(
|
|
1659
|
+
const currentPlanVersionId = String(accountProfileSummary?.planVersionId || '').trim();
|
|
1628
1660
|
if (!currentPlanVersionId) {
|
|
1629
1661
|
setBillingActionError('No active plan version is linked to this account.');
|
|
1630
1662
|
return;
|
|
1631
1663
|
}
|
|
1632
|
-
const res = await fetch(
|
|
1664
|
+
const res = await fetch(resolveCloudAuthUrl('/api/taskforce/billing/subscription'), {
|
|
1633
1665
|
method: 'POST',
|
|
1634
1666
|
credentials: 'include',
|
|
1635
1667
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -1644,8 +1676,8 @@ export function StandaloneLayout(props) {
|
|
|
1644
1676
|
return;
|
|
1645
1677
|
}
|
|
1646
1678
|
setBillingNotice('Subscription updated.');
|
|
1647
|
-
|
|
1648
|
-
await
|
|
1679
|
+
invalidateAccountProfileSummaryCache(resolveCloudAuthUrl(ACCOUNT_PROFILE_SUMMARY_PATH), { identityKey: authUserId });
|
|
1680
|
+
await loadAccountProfileSummary();
|
|
1649
1681
|
}
|
|
1650
1682
|
catch {
|
|
1651
1683
|
setBillingActionError('Failed to update subscription interval.');
|
|
@@ -1653,94 +1685,17 @@ export function StandaloneLayout(props) {
|
|
|
1653
1685
|
finally {
|
|
1654
1686
|
setBillingActionBusy(false);
|
|
1655
1687
|
}
|
|
1656
|
-
}, [
|
|
1657
|
-
const loadTeamManagementAccess = useCallback(async () => {
|
|
1658
|
-
if (!canAccessCloudTeamManagement) {
|
|
1659
|
-
setTeamWorkspaceRole(null);
|
|
1660
|
-
setTeamPlanMode('unknown');
|
|
1661
|
-
setTeamManagementAccessLoading(false);
|
|
1662
|
-
setTeamManagementAccessError(null);
|
|
1663
|
-
markAccountSurfaceLoadSettled('teamManagement');
|
|
1664
|
-
return { role: null, teamPlanMode: 'unknown', allowed: false };
|
|
1665
|
-
}
|
|
1666
|
-
const startedAt = getTimingNow();
|
|
1667
|
-
setTeamManagementAccessLoading(true);
|
|
1668
|
-
setTeamManagementAccessError(null);
|
|
1669
|
-
try {
|
|
1670
|
-
const res = await fetchTaskforceApi('/api/taskforce/account/team-management-access', {
|
|
1671
|
-
method: 'GET',
|
|
1672
|
-
credentials: 'include',
|
|
1673
|
-
headers: teamAdminHeaders()
|
|
1674
|
-
}, accountCapabilityApiBaseUrl);
|
|
1675
|
-
const payload = await res.json().catch(() => ({}));
|
|
1676
|
-
if (!res.ok) {
|
|
1677
|
-
setTeamWorkspaceRole(null);
|
|
1678
|
-
setTeamPlanMode('unknown');
|
|
1679
|
-
setTeamManagementAccessError(String(payload?.error || 'Failed to load team management access.'));
|
|
1680
|
-
logBootstrapDebug('team_management_access_failed', {
|
|
1681
|
-
durationMs: Math.round(getTimingNow() - startedAt),
|
|
1682
|
-
status: res.status
|
|
1683
|
-
});
|
|
1684
|
-
return { role: null, teamPlanMode: 'unknown', allowed: false };
|
|
1685
|
-
}
|
|
1686
|
-
const nextRole = payload?.role === 'owner' || payload?.role === 'admin' || payload?.role === 'member' || payload?.role === 'read-only'
|
|
1687
|
-
? payload.role
|
|
1688
|
-
: null;
|
|
1689
|
-
const nextMode = payload?.teamPlanMode === 'team' || payload?.teamPlanMode === 'personal'
|
|
1690
|
-
? payload.teamPlanMode
|
|
1691
|
-
: 'unknown';
|
|
1692
|
-
const allowed = payload?.allowed === true;
|
|
1693
|
-
setTeamWorkspaceRole(nextRole);
|
|
1694
|
-
setTeamPlanMode(nextMode);
|
|
1695
|
-
setTeamManagementAccessError(null);
|
|
1696
|
-
logBootstrapDebug('team_management_access_loaded', {
|
|
1697
|
-
durationMs: Math.round(getTimingNow() - startedAt),
|
|
1698
|
-
role: nextRole,
|
|
1699
|
-
teamPlanMode: nextMode,
|
|
1700
|
-
allowed
|
|
1701
|
-
});
|
|
1702
|
-
return { role: nextRole, teamPlanMode: nextMode, allowed };
|
|
1703
|
-
}
|
|
1704
|
-
catch (error) {
|
|
1705
|
-
setTeamWorkspaceRole(null);
|
|
1706
|
-
setTeamPlanMode('unknown');
|
|
1707
|
-
setTeamManagementAccessError(error instanceof Error ? error.message : 'Failed to load team management access.');
|
|
1708
|
-
logBootstrapDebug('team_management_access_failed', {
|
|
1709
|
-
durationMs: Math.round(getTimingNow() - startedAt),
|
|
1710
|
-
error: error instanceof Error ? error.message : 'Failed to load team management access.'
|
|
1711
|
-
});
|
|
1712
|
-
return { role: null, teamPlanMode: 'unknown', allowed: false };
|
|
1713
|
-
}
|
|
1714
|
-
finally {
|
|
1715
|
-
setTeamManagementAccessLoading(false);
|
|
1716
|
-
markAccountSurfaceLoadSettled('teamManagement');
|
|
1717
|
-
}
|
|
1718
|
-
}, [accountCapabilityApiBaseUrl, canAccessCloudTeamManagement, markAccountSurfaceLoadSettled, teamAdminHeaders]);
|
|
1719
|
-
useEffect(() => {
|
|
1720
|
-
if (!showAccountMenu && !showTeamManagementModal && !showAccountHub)
|
|
1721
|
-
return;
|
|
1722
|
-
void loadTeamManagementAccess();
|
|
1723
|
-
}, [showAccountHub, showAccountMenu, showTeamManagementModal, loadTeamManagementAccess]);
|
|
1724
|
-
useEffect(() => {
|
|
1725
|
-
if (!isAuthenticated || !authSessionResolved)
|
|
1726
|
-
return;
|
|
1727
|
-
if (!canAccessCloudTeamManagement) {
|
|
1728
|
-
setTeamWorkspaceRole(null);
|
|
1729
|
-
setTeamPlanMode('unknown');
|
|
1730
|
-
return;
|
|
1731
|
-
}
|
|
1732
|
-
void loadTeamManagementAccess();
|
|
1733
|
-
}, [authSessionResolved, canAccessCloudTeamManagement, isAuthenticated, loadTeamManagementAccess]);
|
|
1688
|
+
}, [accountProfileSummary?.planVersionId, authUserId, billingIntervalChoice, loadAccountProfileSummary, resolveCloudAuthUrl]);
|
|
1734
1689
|
useEffect(() => {
|
|
1735
1690
|
if (!isAuthenticated || !authSessionResolved)
|
|
1736
1691
|
return;
|
|
1737
|
-
void
|
|
1738
|
-
}, [authSessionResolved, isAuthenticated,
|
|
1692
|
+
void loadAccountProfileSummary();
|
|
1693
|
+
}, [authSessionResolved, isAuthenticated, loadAccountProfileSummary]);
|
|
1739
1694
|
useEffect(() => {
|
|
1740
1695
|
if (!showAccountHub && !showAccountMenu)
|
|
1741
1696
|
return;
|
|
1742
|
-
void
|
|
1743
|
-
}, [
|
|
1697
|
+
void loadAccountProfileSummary();
|
|
1698
|
+
}, [loadAccountProfileSummary, showAccountHub, showAccountMenu]);
|
|
1744
1699
|
const loadTeamUsers = useCallback(async () => {
|
|
1745
1700
|
if (!canManageTeamWorkspace)
|
|
1746
1701
|
return;
|
|
@@ -1869,12 +1824,12 @@ export function StandaloneLayout(props) {
|
|
|
1869
1824
|
setShowAccountMenu(false);
|
|
1870
1825
|
setShowTeamManagementModal(true);
|
|
1871
1826
|
setTeamManagementTab('members');
|
|
1872
|
-
const
|
|
1873
|
-
if (!
|
|
1827
|
+
const summary = await loadAccountProfileSummary();
|
|
1828
|
+
if (!(summary?.teamManagementAllowed ?? canOpenTeamManagement))
|
|
1874
1829
|
return;
|
|
1875
1830
|
await loadTeamUsers();
|
|
1876
1831
|
await loadTeamAuditPage(0);
|
|
1877
|
-
}, [
|
|
1832
|
+
}, [canOpenTeamManagement, loadAccountProfileSummary, loadTeamAuditPage, loadTeamUsers]);
|
|
1878
1833
|
const executeTeamUserAction = useCallback(async (userId, fn) => {
|
|
1879
1834
|
setTeamMgmtError(null);
|
|
1880
1835
|
setTeamActionBusyUserId(userId);
|
|
@@ -2544,7 +2499,7 @@ export function StandaloneLayout(props) {
|
|
|
2544
2499
|
}, [groupBy, activeCategories, activeTypes, priorities, approaches, assigneeOptions, scheduleColumns]);
|
|
2545
2500
|
// === SECTION: AccountMenuControl ===
|
|
2546
2501
|
// Not extracted: ~25 distinct props from local state would be needed.
|
|
2547
|
-
// References: isAuthenticated, authIdentityLabel,
|
|
2502
|
+
// References: isAuthenticated, authIdentityLabel, accountProfileSummary, runtimeLabel,
|
|
2548
2503
|
// connectedEnvironmentLabel, canManageCloudWorkspaces, workspaceActionBusy,
|
|
2549
2504
|
// setWorkspacePanelOpen, canOpenTeamManagement, openTeamManagementModal, and more.
|
|
2550
2505
|
const accountMenuControl = (_jsxs("div", { className: accountStyles.accountMenuWrap, ref: accountMenuRef, children: [_jsx("button", { className: `tf-control-icon ${accountStyles.avatarBtn}`, onClick: () => {
|
|
@@ -2559,7 +2514,7 @@ export function StandaloneLayout(props) {
|
|
|
2559
2514
|
}
|
|
2560
2515
|
return next;
|
|
2561
2516
|
});
|
|
2562
|
-
}, title: isAuthenticated ? 'Account' : 'Account (Not signed in)', "aria-label": isAuthenticated ? 'Account' : 'Account (Not signed in)', "aria-haspopup": "menu", "aria-expanded": showAccountMenu, children: _jsx("span", { className: accountStyles.avatarBadge, "aria-hidden": "true", children:
|
|
2517
|
+
}, title: isAuthenticated ? 'Account' : 'Account (Not signed in)', "aria-label": isAuthenticated ? 'Account' : 'Account (Not signed in)', "aria-haspopup": "menu", "aria-expanded": showAccountMenu, children: _jsx("span", { className: accountStyles.avatarBadge, "aria-hidden": "true", children: shouldShowResolvedAccountAvatar ? (_jsx("img", { src: accountAvatarUrl, alt: "", className: accountStyles.avatarImage })) : accountBadgeInitial ? (accountBadgeInitial) : (_jsx(User, { size: 14, className: accountStyles.avatarIcon })) }) }), showAccountMenu && (_jsxs("div", { className: accountStyles.accountMenu, role: "menu", "aria-label": "Account menu", children: [_jsxs("div", { className: accountStyles.accountMenuSection, children: [_jsx("div", { className: accountStyles.accountMenuSectionLabel, children: "Account" }), _jsx("div", { className: accountStyles.accountMenuHint, children: isAuthBootstrapPending
|
|
2563
2518
|
? 'Checking sign-in status...'
|
|
2564
2519
|
: isAuthenticated
|
|
2565
2520
|
? (_jsx(_Fragment, { children: hasAuthIdentity
|
|
@@ -3213,7 +3168,7 @@ export function StandaloneLayout(props) {
|
|
|
3213
3168
|
const workspacePrimaryAction = workspaceChromeDefinition.primaryAction
|
|
3214
3169
|
? primaryActionRegistry[workspaceChromeDefinition.primaryAction]
|
|
3215
3170
|
: null;
|
|
3216
|
-
return (_jsxs("div", { className: `${shellStyles.standaloneWrapper} ${zenMode ? styles.zenModeEnabled : ''}`, "data-theme": currentTheme, children: [_jsx(AppShellHeader, { projectName: projectName, currentWorkspaceId: currentWorkspaceId, runtimeMode: runtimeMode, theme: currentTheme, meta: isPlansScreen ? null : (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.taskCountBadge, title: activeCountTitle, children: activeCountLabel }), normalizedAuthUserId && normalizedAuthUserId !== 'anonymous' && (_jsxs(_Fragment, { children: [_jsxs("span", { className: styles.taskCountBadge, title: "
|
|
3171
|
+
return (_jsxs("div", { className: `${shellStyles.standaloneWrapper} ${zenMode ? styles.zenModeEnabled : ''}`, "data-theme": currentTheme, children: [_jsx(AppShellHeader, { projectName: projectName, currentWorkspaceId: currentWorkspaceId, runtimeMode: runtimeMode, theme: currentTheme, meta: isPlansScreen ? null : (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.taskCountBadge, title: activeCountTitle, children: activeCountLabel }), normalizedAuthUserId && normalizedAuthUserId !== 'anonymous' && (_jsxs(_Fragment, { children: [_jsxs("span", { className: styles.taskCountBadge, title: "Tasks assigned to me", children: [_jsx(User, { size: 13, className: styles.taskCountBadgeIcon, "aria-hidden": "true" }), assignedTaskCount] }), _jsxs("span", { className: `${styles.taskCountBadge} ${assignedOverdueTaskCount > 0 ? styles.taskCountBadgeAlert : ''}`.trim(), title: "Tasks overdue", children: [_jsx(ClockAlert, { size: 13, className: styles.taskCountBadgeIcon, "aria-hidden": "true" }), assignedOverdueTaskCount] })] })), shouldShowSyncStatus && (_jsx("button", { className: "tf-control-icon", onClick: () => setShowSyncStatusModal(true), title: `Sync manager: ${syncStatusMeta.label} | Last success: ${formattedLastSyncTime}`, style: {
|
|
3217
3172
|
marginLeft: '6px',
|
|
3218
3173
|
height: '24px',
|
|
3219
3174
|
width: '24px',
|
|
@@ -3225,7 +3180,7 @@ export function StandaloneLayout(props) {
|
|
|
3225
3180
|
display: 'inline-flex',
|
|
3226
3181
|
alignItems: 'center',
|
|
3227
3182
|
justifyContent: 'center'
|
|
3228
|
-
}, children: syncStatusMeta.icon === 'off' ? (_jsx(CloudOff, { size: 16 })) : (_jsx(Cloud, { size: 16 })) }))] })), actions: (_jsxs(_Fragment, { children: [isPlansScreen && (_jsx("button", { className: "tf-control-icon", onClick: handlePlansBack, title: "Back", children: "Back" })), !isPlansScreen && (_jsxs(_Fragment, { children: [workspaceHeaderSections, workspacePrimaryAction] })), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalDisplayActions }), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalFilterActions }), accountMenuControl] })) }), _jsx(TopNoticeLayer, { notice: uiNotice, onDismiss: clearNotice }), authBlocked && (_jsx("div", { className: authStyles.authBlockedBanner, children: "Authentication required for this environment. Use the Account menu to sign in." })), !isPlansScreen && showFilters && workspaceFilterBar, isPlansScreen ? (_jsx("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} ${styles.appScrollbar} tf-scrollbar`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflowY: 'auto', overflowX: 'hidden', scrollbarGutter: 'stable' }, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(PlansPage, { ...props, currentTheme: currentTheme, projectName: projectName, currentWorkspaceId: currentWorkspaceId, apiBaseUrl:
|
|
3183
|
+
}, children: syncStatusMeta.icon === 'off' ? (_jsx(CloudOff, { size: 16 })) : (_jsx(Cloud, { size: 16 })) }))] })), actions: (_jsxs(_Fragment, { children: [isPlansScreen && (_jsx("button", { className: "tf-control-icon", onClick: handlePlansBack, title: "Back", children: "Back" })), !isPlansScreen && (_jsxs(_Fragment, { children: [workspaceHeaderSections, workspacePrimaryAction] })), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalDisplayActions }), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalFilterActions }), accountMenuControl] })) }), _jsx(TopNoticeLayer, { notice: uiNotice, onDismiss: clearNotice }), authBlocked && (_jsx("div", { className: authStyles.authBlockedBanner, children: "Authentication required for this environment. Use the Account menu to sign in." })), !isPlansScreen && showFilters && workspaceFilterBar, isPlansScreen ? (_jsx("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} ${styles.appScrollbar} tf-scrollbar`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflowY: 'auto', overflowX: 'hidden', scrollbarGutter: 'stable' }, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(PlansPage, { ...props, currentTheme: currentTheme, projectName: projectName, currentWorkspaceId: currentWorkspaceId, authUserId: authUserId, apiBaseUrl: apiBaseUrl, connectedEnvironmentSource: props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '', resolveCloudAuthUrl: resolveCloudAuthUrl, embedded: true, shellOwnsScroll: true }) }) })) : resolvedWorkspaceModule === 'docs' && documentsWorkspaceEnabled ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(DocumentWorkspaceShell, { tasks: tasks, runtimeMode: runtimeMode, apiBaseUrl: props.config?.apiBaseUrl || '', cloudAuthBaseUrl: props.config?.cloudAuthBaseUrl || '', typeFilters: documentTypeFilters, attachmentFilters: documentAttachmentFilters, onTypeFiltersChange: setDocumentTypeFilters, onAttachmentFiltersChange: setDocumentAttachmentFilters, requestedDocPath: requestedDocPath, requestedDocAssetId: requestedDocAssetId, onRequestedDocHandled: () => {
|
|
3229
3184
|
setRequestedDocPath(null);
|
|
3230
3185
|
setRequestedDocAssetId(null);
|
|
3231
3186
|
}, enableTaskGeneration: true }) })] })) : resolvedWorkspaceModule === 'annotate' && annotatedAttachmentsWorkspaceEnabled ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(AnnotatedAttachmentWorkspaceShell, { runtimeMode: runtimeMode, apiBaseUrl: props.config?.apiBaseUrl || '', cloudAuthBaseUrl: props.config?.cloudAuthBaseUrl || '', workspaceId: currentWorkspaceId, sessionLoadReady: annotatedWorkspaceReady, requestedTarget: requestedAnnotatedTarget, requestedSessionId: requestedAnnotatedSessionId, requestedOpenVersion: requestedAnnotatedOpenVersion, resolveTaskReferenceLabel: resolveAnnotatedTaskReferenceLabel, resolveImageReferenceLabel: resolveAnnotatedImageReferenceLabel, onOpenTarget: handleAnnotatedAttachmentOpenTarget, onContextChange: handleAnnotatedAttachmentContextChange, onBackToTask: handleBackToTaskFromAnnotatedAttachment }) })] })) : resolvedWorkspaceModule === 'workflows' ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(WorkflowsModule, { availableWorkflows: availableWorkflows, availableEnvironments: availableEnvironments, exportEnvironment: exportEnvironment, exportWorkflowsPath: props.exportWorkflowsPath, exportingResource: exportingResource, exportResult: exportResult, onExportEnvironmentChange: props.setExportEnvironment, onExportWorkflows: onExportWorkflows, onRefreshWorkflows: fetchWorkflows, onFetchWorkflowTemplate: props.fetchWorkflowTemplate, onFetchWorkflowOverrideNames: props.fetchWorkflowOverrideNames, onSaveWorkflowTemplateDraft: props.saveWorkflowTemplateDraft, onResetWorkflowTemplateDraft: props.resetWorkflowTemplateDraft }) })] })) : resolvedWorkspaceModule === 'agents' ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(AgentsModule, { workspaceId: currentWorkspaceId }) })] })) : (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} `, style: {
|
|
@@ -3244,7 +3199,7 @@ export function StandaloneLayout(props) {
|
|
|
3244
3199
|
inset: 0,
|
|
3245
3200
|
zIndex: 35,
|
|
3246
3201
|
pointerEvents: 'none',
|
|
3247
|
-
} }), _jsxs("div", { style: { position: 'relative', width: '100%', minWidth: 0, flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [props.loadingTasks && boardVisibleTasks.length > 0 && (_jsx("div", { className: styles.boardRefreshIndicator, "aria-live": "polite", "aria-label": "Refreshing tasks", children: _jsx(Loader2, { size: 14, className: styles.spinner }) })), _jsx(TaskKanban, { tasks: scopedBoardVisibleTasks, allTasks:
|
|
3202
|
+
} }), _jsxs("div", { style: { position: 'relative', width: '100%', minWidth: 0, flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [props.loadingTasks && boardVisibleTasks.length > 0 && (_jsx("div", { className: styles.boardRefreshIndicator, "aria-live": "polite", "aria-label": "Refreshing tasks", children: _jsx(Loader2, { size: 14, className: styles.spinner }) })), _jsx(TaskKanban, { tasks: scopedBoardVisibleTasks, allTasks: scopedKanbanAllTasks, columns: kanbanColumns, groupBy: groupBy, scheduleDates: scheduleDates, searchQuery: searchQuery, filterCategories: filterCategories, filterTypes: filterTypes, filterPriorities: filterPriorities, filterStatus: filterStatus, filterAssignees: filterAssignees, assigneeOptions: assigneeOptions, scheduleFilteredTaskIds: Array.from(scheduleFilteredTaskIds), copiedId: copiedId, taxonomies: taxonomies, types: activeTypes, priorities: priorities, onUpdateTask: handleUpdateTask, onTaskClick: (task) => handleEdit(task), onOpenTaskById: handleOpenTaskById, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onSetStatus: handleSetStatus, onArchiveTask: handleArchiveTask, onAddTaskToColumn: handleAddTaskToColumn, showTaskCardStatusLabel: showTaskCardStatusLabel, onScheduleDaySelected: (dateOnly) => {
|
|
3248
3203
|
setScheduleSelectedDate(dateOnly);
|
|
3249
3204
|
const parsed = parseDateOnlyLocal(dateOnly);
|
|
3250
3205
|
if (parsed) {
|
|
@@ -3350,11 +3305,11 @@ export function StandaloneLayout(props) {
|
|
|
3350
3305
|
setShowHelpModal(false);
|
|
3351
3306
|
setActiveTab('settings');
|
|
3352
3307
|
setSettingsSection('general');
|
|
3353
|
-
} }), _jsx(EditProfileModal, { isOpen: showEditProfileModal, theme: currentTheme, onClose: closeEditProfileModal, onSave: () => { void handleSaveProfile(); }, displayName: profileDisplayNameDraft, email: authEmailLabel, avatarDisplayUrl: profileAvatarDisplayUrl, accountBadgeInitial: accountBadgeInitial, avatarInputRef: profileAvatarInputRef, avatarAccept: PROFILE_AVATAR_ACCEPT, avatarDraftId: profileAvatarDraftId, saveBusy: profileSaveBusy, avatarBusy: profileAvatarBusy, saveError: profileSaveError, saveNotice: profileSaveNotice, onDisplayNameChange: setProfileDisplayNameDraft, onAvatarInputChange: (file) => { void handleProfileAvatarSelected(file); }, onStartAvatarUpload: () => profileAvatarInputRef.current?.click(), onDiscardUpload: handleDiscardPendingProfileAvatar, onRemovePhoto: handleRemoveProfileAvatar }), _jsx(AccountHubModal, { isOpen: showAccountHub, theme: currentTheme, runtimeMode: runtimeMode, authRequiredForApi: authRequiredForApi, isAuthenticated: isAuthenticated, hasAuthIdentity: hasAuthIdentity, authIdentityLabel: authIdentityLabel, billingLoading: billingLoading, billingError: billingError, billingActionError: billingActionError, billingNotice: billingNotice, billingActionBusy: billingActionBusy, billingIntervalChoice: billingIntervalChoice,
|
|
3308
|
+
} }), _jsx(EditProfileModal, { isOpen: showEditProfileModal, theme: currentTheme, onClose: closeEditProfileModal, onSave: () => { void handleSaveProfile(); }, displayName: profileDisplayNameDraft, email: authEmailLabel, avatarDisplayUrl: profileAvatarDisplayUrl, accountBadgeInitial: accountBadgeInitial, avatarInputRef: profileAvatarInputRef, avatarAccept: PROFILE_AVATAR_ACCEPT, avatarDraftId: profileAvatarDraftId, saveBusy: profileSaveBusy, avatarBusy: profileAvatarBusy, saveError: profileSaveError, saveNotice: profileSaveNotice, onDisplayNameChange: setProfileDisplayNameDraft, onAvatarInputChange: (file) => { void handleProfileAvatarSelected(file); }, onStartAvatarUpload: () => profileAvatarInputRef.current?.click(), onDiscardUpload: handleDiscardPendingProfileAvatar, onRemovePhoto: handleRemoveProfileAvatar }), _jsx(AccountHubModal, { isOpen: showAccountHub, theme: currentTheme, runtimeMode: runtimeMode, authRequiredForApi: authRequiredForApi, isAuthenticated: isAuthenticated, hasAuthIdentity: hasAuthIdentity, authIdentityLabel: authIdentityLabel, billingLoading: billingLoading, billingError: billingError, billingActionError: billingActionError, billingNotice: billingNotice, billingActionBusy: billingActionBusy, billingIntervalChoice: billingIntervalChoice, accountProfileSummary: accountProfileSummary, currentWorkspaceId: currentWorkspaceId, canOpenTeamManagement: canOpenTeamManagement, canManageWorkspaceSync: canManageWorkspaceSync, workspaceCloudSyncEnabled: workspaceCloudSyncEnabled, syncStatusLabel: syncStatusMeta.label, workspaceSyncError: workspaceSyncError, syncControlBusy: syncControlBusy, onClose: () => setShowAccountHub(false), onOpenWorkspaceAudit: () => {
|
|
3354
3309
|
setShowAccountHub(false);
|
|
3355
3310
|
void openTeamManagementModal();
|
|
3356
3311
|
setTeamManagementTab('audit');
|
|
3357
|
-
}, onBillingIntervalChange: setBillingIntervalChoice, onRefreshBilling: () => { void
|
|
3312
|
+
}, onBillingIntervalChange: setBillingIntervalChoice, onRefreshBilling: () => { void loadAccountProfileSummary(); }, onUpdateInterval: () => { void handleSwitchSubscriptionInterval(); }, onManageBilling: () => { void handleOpenBillingPortal(); }, onStartCheckout: () => { void handleStartCheckout(); }, onToggleWorkspaceSync: (enabled) => { void handleWorkspaceSyncToggle(enabled); }, onOpenHelp: () => {
|
|
3358
3313
|
setShowAccountHub(false);
|
|
3359
3314
|
setShowHelpModal(true);
|
|
3360
3315
|
} }), _jsx(TeamManagementModal, { isOpen: showTeamManagementModal, theme: currentTheme, teamPlanMode: teamPlanMode, teamMgmtError: teamMgmtError, teamManagementTab: teamManagementTab, teamUsersLoading: teamUsersLoading, teamUsers: teamUsers, teamActionBusyUserId: teamActionBusyUserId, teamInviteFeedback: teamInviteFeedback, teamInviteEmail: teamInviteEmail, teamInviteRole: teamInviteRole, teamInvitePermissionMode: teamInvitePermissionMode, teamInviteBusy: teamInviteBusy, pendingInvites: pendingInvites, teamAuditLoading: teamAuditLoading, teamAuditEvents: teamAuditEvents, teamAuditPage: teamAuditPage, teamAuditPages: teamAuditPages, teamAuditHasMore: teamAuditHasMore, onClose: () => setShowTeamManagementModal(false), onOpenMembersTab: () => {
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import type { ModalRuntimeMode, ModalThemeName } from './types';
|
|
2
|
-
interface
|
|
2
|
+
interface AccountProfileSummaryLike {
|
|
3
3
|
planId?: string | null;
|
|
4
|
+
planName?: string | null;
|
|
4
5
|
entitlementState?: string | null;
|
|
5
6
|
stripeStatus?: string | null;
|
|
6
7
|
effectiveUntil?: string | null;
|
|
7
8
|
stripeSubscriptionId?: string | null;
|
|
8
9
|
stripeCustomerId?: string | null;
|
|
10
|
+
gate?: string | null;
|
|
11
|
+
message?: string | null;
|
|
12
|
+
billingConflictCode?: 'CHECKOUT_SESSION_EXPIRED' | 'CHECKOUT_SESSION_SUPERSEDED' | null;
|
|
13
|
+
planSelectionRequired?: boolean;
|
|
9
14
|
}
|
|
10
15
|
interface AccountHubModalProps {
|
|
11
16
|
isOpen: boolean;
|
|
@@ -21,7 +26,7 @@ interface AccountHubModalProps {
|
|
|
21
26
|
billingNotice: string | null;
|
|
22
27
|
billingActionBusy: boolean;
|
|
23
28
|
billingIntervalChoice: 'month' | 'year';
|
|
24
|
-
|
|
29
|
+
accountProfileSummary: AccountProfileSummaryLike | null;
|
|
25
30
|
currentWorkspaceId: string;
|
|
26
31
|
canOpenTeamManagement: boolean;
|
|
27
32
|
canManageWorkspaceSync: boolean;
|
|
@@ -39,5 +44,5 @@ interface AccountHubModalProps {
|
|
|
39
44
|
onToggleWorkspaceSync: (enabled: boolean) => void;
|
|
40
45
|
onOpenHelp: () => void;
|
|
41
46
|
}
|
|
42
|
-
export declare function AccountHubModal({ isOpen, theme, runtimeMode, authRequiredForApi, isAuthenticated, hasAuthIdentity, authIdentityLabel, billingLoading, billingError, billingActionError, billingNotice, billingActionBusy, billingIntervalChoice,
|
|
47
|
+
export declare function AccountHubModal({ isOpen, theme, runtimeMode, authRequiredForApi, isAuthenticated, hasAuthIdentity, authIdentityLabel, billingLoading, billingError, billingActionError, billingNotice, billingActionBusy, billingIntervalChoice, accountProfileSummary, currentWorkspaceId, canOpenTeamManagement, canManageWorkspaceSync, workspaceCloudSyncEnabled, syncStatusLabel, workspaceSyncError, syncControlBusy, onClose, onOpenWorkspaceAudit, onBillingIntervalChange, onRefreshBilling, onUpdateInterval, onManageBilling, onStartCheckout, onToggleWorkspaceSync, onOpenHelp, }: AccountHubModalProps): import("react/jsx-runtime").JSX.Element;
|
|
43
48
|
export {};
|
|
@@ -5,7 +5,21 @@ import accountStyles from '../../StandaloneAccount.module.css';
|
|
|
5
5
|
import authStyles from '../../AuthShell.module.css';
|
|
6
6
|
import managementStyles from '../../StandaloneManagement.module.css';
|
|
7
7
|
import styles from '../../../../Taskforce.module.css';
|
|
8
|
-
export function AccountHubModal({ isOpen, theme, runtimeMode, authRequiredForApi, isAuthenticated, hasAuthIdentity, authIdentityLabel, billingLoading, billingError, billingActionError, billingNotice, billingActionBusy, billingIntervalChoice,
|
|
8
|
+
export function AccountHubModal({ isOpen, theme, runtimeMode, authRequiredForApi, isAuthenticated, hasAuthIdentity, authIdentityLabel, billingLoading, billingError, billingActionError, billingNotice, billingActionBusy, billingIntervalChoice, accountProfileSummary, currentWorkspaceId, canOpenTeamManagement, canManageWorkspaceSync, workspaceCloudSyncEnabled, syncStatusLabel, workspaceSyncError, syncControlBusy, onClose, onOpenWorkspaceAudit, onBillingIntervalChange, onRefreshBilling, onUpdateInterval, onManageBilling, onStartCheckout, onToggleWorkspaceSync, onOpenHelp, }) {
|
|
9
|
+
const billingGate = String(accountProfileSummary?.gate || '').trim().toLowerCase();
|
|
10
|
+
const billingConflictCode = String(accountProfileSummary?.billingConflictCode || '').trim().toUpperCase();
|
|
11
|
+
const billingRecoveryMessage = String(accountProfileSummary?.message || '').trim()
|
|
12
|
+
|| (billingConflictCode === 'CHECKOUT_SESSION_EXPIRED'
|
|
13
|
+
? 'Your latest checkout expired. Choose a plan to continue.'
|
|
14
|
+
: (billingConflictCode === 'CHECKOUT_SESSION_SUPERSEDED'
|
|
15
|
+
? 'A newer checkout is already in progress for this account. Open plans to continue.'
|
|
16
|
+
: ''));
|
|
17
|
+
const startCheckoutLabel = billingConflictCode === 'CHECKOUT_SESSION_EXPIRED'
|
|
18
|
+
|| accountProfileSummary?.planSelectionRequired === true
|
|
19
|
+
|| billingGate === 'plan_selection_required'
|
|
20
|
+
? 'Choose Plan'
|
|
21
|
+
: (billingGate === 'checkout_pending' ? 'Open Plans' : 'Start Checkout');
|
|
22
|
+
const resolvedPlanLabel = String(accountProfileSummary?.planName || accountProfileSummary?.planId || 'n/a').trim() || 'n/a';
|
|
9
23
|
return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, title: "Account Hub", size: "md", theme: theme, draggable: true, children: _jsxs("div", { className: `${modalStyles.form} ${managementStyles.modalBody}`, children: [_jsx("p", { className: managementStyles.mutedText, children: "Manage sign in, profile, and workspace controls from one place." }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Authentication" }), _jsx("p", { className: managementStyles.sectionText, children: runtimeMode === 'cloud'
|
|
10
24
|
? (authRequiredForApi
|
|
11
25
|
? (isAuthenticated ? 'Signed in. API access is enabled.' : 'Cloud mode requires sign in before app usage.')
|
|
@@ -14,7 +28,7 @@ export function AccountHubModal({ isOpen, theme, runtimeMode, authRequiredForApi
|
|
|
14
28
|
? (hasAuthIdentity
|
|
15
29
|
? _jsxs(_Fragment, { children: ["Signed in as ", _jsx("span", { className: accountStyles.accountIdentityEmail, children: authIdentityLabel })] })
|
|
16
30
|
: 'Signed in')
|
|
17
|
-
: 'Signed in as: not signed in' }), runtimeMode === 'cloud' && authRequiredForApi && !isAuthenticated && (_jsx("p", { className: managementStyles.sectionTextTop, children: "Use the `/login` screen to sign in." }))] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Profile & Account" }), _jsx("p", { className: managementStyles.sectionText, children: "Profile editing and account preferences will live here." })] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Billing" }), !isAuthenticated ? (_jsx("p", { className: managementStyles.sectionText, children: "Sign in to manage subscription billing." })) : runtimeMode !== 'cloud' ? (_jsx("p", { className: managementStyles.sectionText, children: "Billing remains cloud-backed in local runtime and uses your connected cloud account." })) : (_jsxs(_Fragment, { children: [billingLoading && (_jsx("p", { className: managementStyles.sectionTextSpaced, children: "Loading billing status..." })), billingError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: billingError })), billingActionError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: billingActionError })), billingNotice && (_jsx("p", { className: managementStyles.errorText, children: billingNotice })), _jsxs("p", { className: managementStyles.sectionTextSpaced, children: ["Plan: ", _jsx("code", { children:
|
|
31
|
+
: 'Signed in as: not signed in' }), runtimeMode === 'cloud' && authRequiredForApi && !isAuthenticated && (_jsx("p", { className: managementStyles.sectionTextTop, children: "Use the `/login` screen to sign in." }))] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Profile & Account" }), _jsx("p", { className: managementStyles.sectionText, children: "Profile editing and account preferences will live here." })] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Billing" }), !isAuthenticated ? (_jsx("p", { className: managementStyles.sectionText, children: "Sign in to manage subscription billing." })) : runtimeMode !== 'cloud' ? (_jsx("p", { className: managementStyles.sectionText, children: "Billing remains cloud-backed in local runtime and uses your connected cloud account." })) : (_jsxs(_Fragment, { children: [billingLoading && (_jsx("p", { className: managementStyles.sectionTextSpaced, children: "Loading billing status..." })), billingError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: billingError })), billingActionError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: billingActionError })), billingNotice && (_jsx("p", { className: managementStyles.errorText, children: billingNotice })), billingRecoveryMessage && (_jsx("p", { className: managementStyles.sectionText, children: billingRecoveryMessage })), _jsxs("p", { className: managementStyles.sectionTextSpaced, children: ["Plan: ", _jsx("code", { children: resolvedPlanLabel }), ' · ', "Entitlement: ", _jsx("code", { children: String(accountProfileSummary?.entitlementState || 'n/a') })] }), _jsxs("p", { className: managementStyles.sectionText, children: ["Stripe status: ", _jsx("code", { children: String(accountProfileSummary?.stripeStatus || 'n/a') }), accountProfileSummary?.effectiveUntil ? ` · Effective until ${new Date(accountProfileSummary.effectiveUntil).toLocaleString()}` : ''] }), _jsx("div", { className: managementStyles.wrapRow, children: _jsxs("label", { className: `${accountStyles.accountMenuMeta} ${managementStyles.inlineFieldLabel}`, children: ["Interval", _jsxs("select", { className: styles.input, value: billingIntervalChoice, onChange: (event) => onBillingIntervalChange(event.target.value === 'year' ? 'year' : 'month'), disabled: billingActionBusy, children: [_jsx("option", { value: "month", children: "Monthly" }), _jsx("option", { value: "year", children: "Yearly" })] })] }) }), _jsxs("div", { className: managementStyles.actionRowEnd, children: [_jsx("button", { className: styles.cancelBtn, onClick: onRefreshBilling, disabled: billingActionBusy || billingLoading, children: "Refresh Billing" }), _jsx("button", { className: styles.cancelBtn, onClick: onUpdateInterval, disabled: billingActionBusy || !accountProfileSummary?.stripeSubscriptionId, children: "Update Interval" }), _jsx("button", { className: styles.cancelBtn, onClick: onManageBilling, disabled: billingActionBusy || !accountProfileSummary?.stripeCustomerId, children: "Manage Billing" }), _jsx("button", { className: styles.submitBtn, onClick: onStartCheckout, disabled: billingActionBusy, children: billingActionBusy ? 'Working...' : startCheckoutLabel })] })] }))] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Workspace Audit Log" }), _jsx("p", { className: managementStyles.sectionText, children: "Audit entries are workspace-scoped for the active workspace, not global account history." }), _jsxs("p", { className: managementStyles.sectionText, children: ["Active workspace: ", _jsx("code", { children: currentWorkspaceId })] }), _jsx("div", { className: managementStyles.actionRowEnd, children: _jsx("button", { className: styles.cancelBtn, onClick: onOpenWorkspaceAudit, disabled: !canOpenTeamManagement, children: "Open Workspace Audit" }) })] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Workspace Cloud Sync" }), _jsx("p", { className: managementStyles.sectionText, children: canManageWorkspaceSync
|
|
18
32
|
? (workspaceCloudSyncEnabled
|
|
19
33
|
? `Enabled · ${syncStatusLabel}`
|
|
20
34
|
: 'Disabled')
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|