@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.
Files changed (77) hide show
  1. package/dist/Taskforce.module.css +11 -0
  2. package/dist/TaskforceCore.js +59 -9
  3. package/dist/TaskforceCore.test.js +717 -14
  4. package/dist/components/task/TaskKanban.test.js +17 -0
  5. package/dist/components/views/PlansPage.d.ts +3 -1
  6. package/dist/components/views/PlansPage.js +110 -51
  7. package/dist/components/views/PlansPage.test.d.ts +1 -0
  8. package/dist/components/views/PlansPage.test.js +100 -0
  9. package/dist/components/views/StandaloneLayout.d.ts +1 -0
  10. package/dist/components/views/StandaloneLayout.js +106 -151
  11. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +8 -3
  12. package/dist/components/views/standalone/modals/AccountHubModal.js +16 -2
  13. package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
  14. package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
  15. package/dist/core/EntitlementsPolicy.test.js +2 -2
  16. package/dist/core/GlobalSettingsService.js +78 -18
  17. package/dist/core/PlanEntitlementService.d.ts +29 -5
  18. package/dist/core/PlanEntitlementService.js +297 -219
  19. package/dist/core/SignupMonetizationSettings.test.js +173 -49
  20. package/dist/core/SystemAdmin.test.js +186 -96
  21. package/dist/core/Taskforce.d.ts +51 -10
  22. package/dist/core/Taskforce.js +218 -49
  23. package/dist/core/Taskforce.mcpAuth.test.js +0 -2
  24. package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
  25. package/dist/core/types.d.ts +11 -12
  26. package/dist/hooks/useSyncOrchestrator.js +17 -3
  27. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +97 -5
  28. package/dist/hooks/useTaskforce.d.ts +3 -0
  29. package/dist/hooks/useTaskforce.js +9 -4
  30. package/dist/mcp/runtime.js +90 -29
  31. package/dist/mcp/runtime.test.js +272 -81
  32. package/dist/migrations/billingSchemaParity.test.js +3 -0
  33. package/dist/migrations/taskSchemaMigrations.js +15 -0
  34. package/dist/server/index.cookieProxy.test.js +1 -0
  35. package/dist/server/index.js +2 -0
  36. package/dist/server/index.test.js +2 -1
  37. package/dist/server/mockStripe.d.ts +1 -0
  38. package/dist/server/mockStripe.js +2 -0
  39. package/dist/server/routes/admin.js +80 -23
  40. package/dist/server/routes/auth.d.ts +1 -0
  41. package/dist/server/routes/auth.js +194 -53
  42. package/dist/server/routes/billing.js +506 -23
  43. package/dist/server/routes/billing.test.js +937 -45
  44. package/dist/server/routes/shared.js +5 -3
  45. package/dist/server/routes/sync.integration.test.js +160 -0
  46. package/dist/server/routes/sync.js +15 -5
  47. package/dist/server/routes.js +3 -3
  48. package/dist/server/routes.test.js +287 -76
  49. package/dist/sync/cloudSyncApi.d.ts +1 -0
  50. package/dist/sync/contentSyncState.d.ts +10 -0
  51. package/dist/sync/contentSyncState.js +277 -16
  52. package/dist/sync/syncService.d.ts +1 -0
  53. package/dist/sync/syncService.js +21 -8
  54. package/dist/sync/workspacePullFeed.d.ts +10 -0
  55. package/dist/sync/workspacePullFeed.js +111 -63
  56. package/dist/sync/workspacePullFeed.test.js +112 -0
  57. package/dist/ui/assets/{AgentsModule-JG5jc3he.js → AgentsModule-CpsTmrIW.js} +1 -1
  58. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-C_TmXZwA.js} +1 -1
  59. package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-C3GyzrWm.js} +1 -1
  60. package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-4-Rh2K2n.js} +1 -1
  61. package/dist/ui/assets/PlansPage-BP7AYOqr.js +1 -0
  62. package/dist/ui/assets/{TaskSettings-Bqd4cCzW.js → TaskSettings-BOC5F6Ag.js} +1 -1
  63. package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-CGk9s3NJ.js} +1 -1
  64. package/dist/ui/assets/index-CLIMgR6g.js +6 -0
  65. package/dist/ui/assets/index-DneETxcu.css +1 -0
  66. package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-pWocEipT.js} +1 -1
  67. package/dist/ui/index.html +3 -3
  68. package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
  69. package/dist/utils/accountProfileSummaryCache.js +97 -0
  70. package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
  71. package/dist/utils/accountProfileSummaryCache.test.js +63 -0
  72. package/package.json +3 -3
  73. package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
  74. package/dist/ui/assets/index-MXeWoebC.css +0 -1
  75. package/dist/ui/assets/index-YJ4k5QNa.js +0 -6
  76. package/dist/utils/billingStatusCache.d.ts +0 -21
  77. package/dist/utils/billingStatusCache.js +0 -70
@@ -8,13 +8,13 @@
8
8
  <link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png" />
9
9
  <link rel="apple-touch-icon" href="/taskforce/favicon/apple-touch-icon.png" />
10
10
  <title>Taskforce Dashboard</title>
11
- <script type="module" crossorigin src="/taskforce/assets/index-YJ4k5QNa.js"></script>
11
+ <script type="module" crossorigin src="/taskforce/assets/index-CLIMgR6g.js"></script>
12
12
  <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-react-CKJs5o3c.js">
13
- <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-icons-C8EED3Oh.js">
13
+ <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-icons-pWocEipT.js">
14
14
  <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-markdown-BUxTU7dS.js">
15
15
  <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-dnd-DRzYolkg.js">
16
16
  <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-router-BbWMxlnO.js">
17
- <link rel="stylesheet" crossorigin href="/taskforce/assets/index-MXeWoebC.css">
17
+ <link rel="stylesheet" crossorigin href="/taskforce/assets/index-DneETxcu.css">
18
18
  </head>
19
19
  <body>
20
20
  <div id="root"></div>
@@ -0,0 +1,31 @@
1
+ export declare const ACCOUNT_PROFILE_SUMMARY_PATH = "/api/taskforce/account/profile-summary";
2
+ export type AccountProfileSummary = {
3
+ stripeStatus?: string | null;
4
+ stripeCustomerId?: string | null;
5
+ stripeSubscriptionId?: string | null;
6
+ stripePriceId?: string | null;
7
+ billingInterval?: 'month' | 'year' | null;
8
+ trialEnd?: string | null;
9
+ currentPeriodEnd?: string | null;
10
+ effectiveUntil?: string | null;
11
+ entitlementState?: string;
12
+ planVersionId?: string;
13
+ planId?: string;
14
+ planName?: string | null;
15
+ gate?: string | null;
16
+ message?: string | null;
17
+ billingConflictCode?: 'CHECKOUT_SESSION_EXPIRED' | 'CHECKOUT_SESSION_SUPERSEDED' | null;
18
+ planSelectionRequired?: boolean;
19
+ workspaceId?: string | null;
20
+ workspaceRole?: 'owner' | 'admin' | 'member' | 'read-only' | null;
21
+ canManageWorkspace?: boolean;
22
+ teamPlanMode?: 'personal' | 'team' | 'unknown';
23
+ teamManagementAllowed?: boolean;
24
+ };
25
+ export declare function invalidateAccountProfileSummaryCache(rawUrl: string, options?: {
26
+ identityKey?: string | null;
27
+ }): void;
28
+ export declare function loadAccountProfileSummaryCached(rawUrl: string, options?: {
29
+ force?: boolean;
30
+ identityKey?: string | null;
31
+ }): Promise<AccountProfileSummary | null>;
@@ -0,0 +1,97 @@
1
+ import { logBootstrapDebug } from '../hooks/workspace/workspaceLocalState';
2
+ export const ACCOUNT_PROFILE_SUMMARY_PATH = '/api/taskforce/account/profile-summary';
3
+ const ACCOUNT_PROFILE_SUMMARY_CACHE_TTL_MS = 90_000;
4
+ const accountProfileSummaryCache = new Map();
5
+ function getTimingNow() {
6
+ return typeof performance !== 'undefined' ? performance.now() : Date.now();
7
+ }
8
+ function normalizeAccountProfileSummaryCacheKey(rawUrl, identityKey) {
9
+ const url = String(rawUrl || '').trim();
10
+ if (!url)
11
+ return '';
12
+ const normalizedIdentityKey = String(identityKey || '').trim();
13
+ return normalizedIdentityKey ? `${url}::${normalizedIdentityKey}` : url;
14
+ }
15
+ export function invalidateAccountProfileSummaryCache(rawUrl, options) {
16
+ accountProfileSummaryCache.delete(normalizeAccountProfileSummaryCacheKey(rawUrl, options?.identityKey));
17
+ }
18
+ export async function loadAccountProfileSummaryCached(rawUrl, options) {
19
+ const url = String(rawUrl || '').trim();
20
+ const cacheKey = normalizeAccountProfileSummaryCacheKey(rawUrl, options?.identityKey);
21
+ if (!cacheKey || !url)
22
+ return null;
23
+ const force = options?.force === true;
24
+ const now = Date.now();
25
+ const cached = accountProfileSummaryCache.get(cacheKey);
26
+ if (!force && cached?.payload !== undefined && (now - cached.fetchedAt) < ACCOUNT_PROFILE_SUMMARY_CACHE_TTL_MS) {
27
+ logBootstrapDebug('account_profile_summary_cache_hit', {
28
+ url,
29
+ cacheKey,
30
+ ageMs: now - cached.fetchedAt
31
+ });
32
+ return cached.payload;
33
+ }
34
+ if (!force && cached?.promise) {
35
+ logBootstrapDebug('account_profile_summary_request_reused', {
36
+ url,
37
+ cacheKey
38
+ });
39
+ return cached.promise;
40
+ }
41
+ const startedAt = getTimingNow();
42
+ const request = (async () => {
43
+ const res = await fetch(url, {
44
+ method: 'GET',
45
+ credentials: 'include'
46
+ });
47
+ if (!res.ok) {
48
+ throw new Error(String((await res.json().catch(() => ({})))?.error || 'Failed to load account summary.'));
49
+ }
50
+ const payload = await res.json().catch(() => ({}));
51
+ const normalized = (payload || null);
52
+ accountProfileSummaryCache.set(cacheKey, {
53
+ payload: normalized,
54
+ fetchedAt: Date.now(),
55
+ promise: null
56
+ });
57
+ logBootstrapDebug('account_profile_summary_loaded', {
58
+ url,
59
+ cacheKey,
60
+ durationMs: Math.round(getTimingNow() - startedAt),
61
+ fromCache: false
62
+ });
63
+ return normalized;
64
+ })().catch((error) => {
65
+ const stalePayload = cached?.payload ?? null;
66
+ if (stalePayload) {
67
+ accountProfileSummaryCache.set(cacheKey, {
68
+ payload: stalePayload,
69
+ fetchedAt: cached?.fetchedAt ?? Date.now(),
70
+ promise: null
71
+ });
72
+ logBootstrapDebug('account_profile_summary_failed', {
73
+ url,
74
+ cacheKey,
75
+ durationMs: Math.round(getTimingNow() - startedAt),
76
+ error: error instanceof Error ? error.message : String(error || 'Unknown error'),
77
+ returnedStale: true
78
+ });
79
+ return stalePayload;
80
+ }
81
+ accountProfileSummaryCache.delete(cacheKey);
82
+ logBootstrapDebug('account_profile_summary_failed', {
83
+ url,
84
+ cacheKey,
85
+ durationMs: Math.round(getTimingNow() - startedAt),
86
+ error: error instanceof Error ? error.message : String(error || 'Unknown error'),
87
+ returnedStale: false
88
+ });
89
+ throw error;
90
+ });
91
+ accountProfileSummaryCache.set(cacheKey, {
92
+ payload: cached?.payload ?? null,
93
+ fetchedAt: cached?.fetchedAt ?? 0,
94
+ promise: request
95
+ });
96
+ return request;
97
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,63 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { ACCOUNT_PROFILE_SUMMARY_PATH, invalidateAccountProfileSummaryCache, loadAccountProfileSummaryCached } from './accountProfileSummaryCache';
3
+ describe('accountProfileSummaryCache', () => {
4
+ afterEach(() => {
5
+ vi.restoreAllMocks();
6
+ vi.useRealTimers();
7
+ invalidateAccountProfileSummaryCache(ACCOUNT_PROFILE_SUMMARY_PATH, { identityKey: 'user-a' });
8
+ invalidateAccountProfileSummaryCache(ACCOUNT_PROFILE_SUMMARY_PATH, { identityKey: 'user-b' });
9
+ });
10
+ it('keeps account summaries isolated per authenticated user', async () => {
11
+ const fetchMock = vi.fn()
12
+ .mockResolvedValueOnce({
13
+ ok: true,
14
+ json: async () => ({ planId: 'team-5', planVersionId: 'team-5-v1' })
15
+ })
16
+ .mockResolvedValueOnce({
17
+ ok: true,
18
+ json: async () => ({ planId: 'starter', planVersionId: 'starter-v1' })
19
+ });
20
+ vi.stubGlobal('fetch', fetchMock);
21
+ const first = await loadAccountProfileSummaryCached(ACCOUNT_PROFILE_SUMMARY_PATH, { identityKey: 'user-a' });
22
+ const second = await loadAccountProfileSummaryCached(ACCOUNT_PROFILE_SUMMARY_PATH, { identityKey: 'user-b' });
23
+ expect(first?.planId).toBe('team-5');
24
+ expect(second?.planId).toBe('starter');
25
+ expect(fetchMock).toHaveBeenCalledTimes(2);
26
+ expect(fetchMock).toHaveBeenNthCalledWith(1, ACCOUNT_PROFILE_SUMMARY_PATH, expect.any(Object));
27
+ expect(fetchMock).toHaveBeenNthCalledWith(2, ACCOUNT_PROFILE_SUMMARY_PATH, expect.any(Object));
28
+ });
29
+ it('reuses the cached summary inside the cache window', async () => {
30
+ vi.useFakeTimers();
31
+ const fetchMock = vi.fn().mockResolvedValue({
32
+ ok: true,
33
+ json: async () => ({ planId: 'team-5', planVersionId: 'team-5-v1' })
34
+ });
35
+ vi.stubGlobal('fetch', fetchMock);
36
+ const first = await loadAccountProfileSummaryCached(ACCOUNT_PROFILE_SUMMARY_PATH, { identityKey: 'user-a' });
37
+ vi.advanceTimersByTime(30_000);
38
+ const second = await loadAccountProfileSummaryCached(ACCOUNT_PROFILE_SUMMARY_PATH, { identityKey: 'user-a' });
39
+ expect(first?.planId).toBe('team-5');
40
+ expect(second?.planId).toBe('team-5');
41
+ expect(fetchMock).toHaveBeenCalledTimes(1);
42
+ });
43
+ it('returns the last successful summary when a forced refresh fails', async () => {
44
+ const fetchMock = vi.fn()
45
+ .mockResolvedValueOnce({
46
+ ok: true,
47
+ json: async () => ({ planId: 'team-5', planVersionId: 'team-5-v1', teamManagementAllowed: true })
48
+ })
49
+ .mockResolvedValueOnce({
50
+ ok: false,
51
+ json: async () => ({ error: 'Stripe unavailable' })
52
+ });
53
+ vi.stubGlobal('fetch', fetchMock);
54
+ const warm = await loadAccountProfileSummaryCached(ACCOUNT_PROFILE_SUMMARY_PATH, { identityKey: 'user-a' });
55
+ const refreshed = await loadAccountProfileSummaryCached(ACCOUNT_PROFILE_SUMMARY_PATH, {
56
+ identityKey: 'user-a',
57
+ force: true
58
+ });
59
+ expect(warm?.planId).toBe('team-5');
60
+ expect(refreshed).toEqual(warm);
61
+ expect(fetchMock).toHaveBeenCalledTimes(2);
62
+ });
63
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taskforcehq/taskforce",
3
- "version": "0.3.302",
3
+ "version": "0.3.303",
4
4
  "description": "Mission Command center for AI-powered task management (beta - under active development)",
5
5
  "author": "Taskforce HQ <hello@taskforcehq.ai> (https://taskforcehq.ai)",
6
6
  "license": "MIT",
@@ -37,6 +37,8 @@
37
37
  "perf": "sh scripts/run-dev-proxy.sh performance",
38
38
  "stage": "sh scripts/run-dev-proxy.sh staging",
39
39
  "prod": "TASKFORCE_RUNTIME_MODE=local TASKFORCE_CLOUD_ENVIRONMENT=production TASKFORCE_CLOUD_PROXY_BASE_URL=https://app.taskforcehq.ai VITE_TASKFORCE_BASE_URL=https://app.taskforcehq.ai npm run dev:runtime",
40
+ "deploy:staging": "bash scripts/deploy-staging.sh",
41
+ "deploy:prod": "bash scripts/deploy-prod.sh",
40
42
  "dev:runtime": "concurrently \"npm run dev:app\" \"TASKFORCE_RUNTIME_MODE=local sh scripts/with-esbuild.sh node --import tsx src/cli.ts open 5174\"",
41
43
  "stage:direct": "TASKFORCE_RUNTIME_MODE=local TASKFORCE_CLOUD_ENVIRONMENT=staging TASKFORCE_CLOUD_PROXY_BASE_URL=https://staging-app.taskforcehq.ai VITE_TASKFORCE_BASE_URL=https://staging-app.taskforcehq.ai npm run dev:runtime",
42
44
  "perf:direct": "TASKFORCE_RUNTIME_MODE=local TASKFORCE_CLOUD_ENVIRONMENT=performance TASKFORCE_CLOUD_PROXY_BASE_URL=https://performance-app.taskforcehq.ai VITE_TASKFORCE_BASE_URL=https://performance-app.taskforcehq.ai npm run dev:runtime",
@@ -60,8 +62,6 @@
60
62
  "check:localization-strings": "node scripts/check-localization-strings.mjs",
61
63
  "version:next": "node scripts/release-version.mjs --dry-run",
62
64
  "version:bump": "node scripts/release-version.mjs",
63
- "deploy:staging": "bash scripts/deploy-staging.sh",
64
- "deploy:prod": "bash scripts/deploy-prod.sh",
65
65
  "test:localization": "vitest run src/__tests__/localizationCatalogs.test.ts",
66
66
  "check:runtime-boundaries": "node scripts/check-runtime-boundaries.mjs",
67
67
  "test:workflow-readiness": "npm run check:workflows && vitest src/__tests__/workflowSimulation.test.ts src/__tests__/workflowExportMatrix.test.ts src/__tests__/workflowExportSnapshots.test.ts src/__tests__/workflowDocumentFixtures.test.ts src/__tests__/structuredCommentValidation.test.ts src/__tests__/structuredDocValidation.test.ts src/__tests__/workflowV2Policy.test.ts src/__tests__/workflowGeneration.test.ts src/__tests__/templates.test.ts",
@@ -1 +0,0 @@
1
- import{r as a,j as e}from"./vendor-react-CKJs5o3c.js";import{t as ue,y as de,z,B as pe,C as me}from"./index-YJ4k5QNa.js";import{aI as ge,h as fe,U as he}from"./vendor-icons-C8EED3Oh.js";import{u as ye,a as _e}from"./vendor-router-BbWMxlnO.js";import"./vendor-markdown-BUxTU7dS.js";import"./vendor-dnd-DRzYolkg.js";const be="_plansWrapper_b8yvs_1",ve="_contentFrame_b8yvs_28",Se="_shellOwnsScroll_b8yvs_36",Ce="_withChrome_b8yvs_42",ke="_siteVariant_b8yvs_46",xe="_title_b8yvs_54",we="_subtitle_b8yvs_63",Ie="_header_b8yvs_67",je="_planCard_b8yvs_79",Ne="_grid_b8yvs_92",Te="_planName_b8yvs_98",Re="_featureText_b8yvs_102",Pe="_currentBadge_b8yvs_106",Be="_navAction_b8yvs_110",$e="_backBtn_b8yvs_111",Ee="_priceRows_b8yvs_122",Ue="_headerBar_b8yvs_139",Ae="_headerActions_b8yvs_148",Fe="_debugMeta_b8yvs_175",Le="_clickablePlanCard_b8yvs_204",Me="_activePlanCard_b8yvs_219",Ve="_callToAction_b8yvs_272",Oe="_primaryCta_b8yvs_284",Ye="_secondaryCta_b8yvs_295",qe="_currentCta_b8yvs_307",ze="_featuresList_b8yvs_321",De="_priceRow_b8yvs_122",Ge="_priceRowAmount_b8yvs_346",He="_priceRowInterval_b8yvs_352",We="_ctaRow_b8yvs_357",Qe="_featureItem_b8yvs_363",Ke="_featureIcon_b8yvs_369",Je="_emptyState_b8yvs_383",Xe="_marketingEmptyState_b8yvs_392",Ze="_emptyBadge_b8yvs_420",et="_emptyHighlights_b8yvs_435",tt="_loader_b8yvs_457",nt="_spinner_b8yvs_467",rt="_statusBanner_b8yvs_520",st="_successBanner_b8yvs_528",at="_errorBanner_b8yvs_534",n={plansWrapper:be,contentFrame:ve,shellOwnsScroll:Se,withChrome:Ce,siteVariant:ke,title:xe,subtitle:we,header:Ie,planCard:je,grid:Ne,planName:Te,featureText:Re,currentBadge:Pe,navAction:Be,backBtn:$e,priceRows:Ee,headerBar:Ue,headerActions:Ae,debugMeta:Fe,clickablePlanCard:Le,activePlanCard:Me,callToAction:Ve,primaryCta:Oe,secondaryCta:Ye,currentCta:qe,featuresList:ze,priceRow:De,priceRowAmount:Ge,priceRowInterval:He,ctaRow:We,featureItem:Qe,featureIcon:Ke,emptyState:Je,marketingEmptyState:Xe,emptyBadge:Ze,emptyHighlights:et,loader:tt,spinner:nt,statusBanner:rt,successBanner:st,errorBanner:at};function it(){const r=globalThis.__DEBUG_MODE__;return typeof r=="boolean"?r:!1}function ot(r){const i=String(r?.environment||"").trim(),_=String(r?.runtimeMode||"").trim();return!i&&!_?null:i&&_?`${i} (${_})`:i||_||null}function ct(r,i){if(r==null||!i)return"Contact Sales";try{return new Intl.NumberFormat("en-US",{style:"currency",currency:i.toUpperCase(),minimumFractionDigits:0}).format(r/100)}catch{return`${i.toUpperCase()} ${r/100}`}}function lt(){return"$0.00"}function ie(r){return r?r.pricingType==="free"?!0:r.unitAmount!=null&&r.currency!=null:!1}function oe(r){return r?r.pricingType==="free"?lt():ct(r.unitAmount,r.currency):"Unavailable"}function ce(r){const i=[r.pricing.month,r.pricing.year].filter(Boolean);if(i.some(m=>m.pricingType==="free"))return 0;const _=i.map(m=>m.pricingType==="stripe"&&typeof m.unitAmount=="number"?m.unitAmount:null).filter(m=>m!=null);return _.length>0?Math.min(..._):Number.POSITIVE_INFINITY}function ut(r){const i=String(r||"").trim().toLowerCase();return i==="active"||i==="trialing"||i==="grace"}function dt({heading:r,subtitle:i,variant:_="app",chrome:m,footer:P,isAuthenticated:B,authSessionResolved:D=!0,currentPlanId:V,currentPlanVersionId:C,currentEntitlementState:X,canManageCurrentPlan:d=!0,pricingEndpoint:f,statusBanner:b,onBack:G,backLabel:H,topActions:$,shellOwnsScroll:W=!1,showPageBackButton:c=!0,fallbackPricingSource:U=null,onSelectPlan:S}){const[w,E]=a.useState([]),[I,Q]=a.useState(null),[A,O]=a.useState(!0),[F,k]=a.useState(null),[K,Z]=a.useState(0);a.useEffect(()=>{let s=!0;return(async()=>{const y=String(f||"/api/taskforce/public/pricing").trim()||"/api/taskforce/public/pricing";s&&(O(!0),k(null));try{const l=await fetch(y,{method:"GET",credentials:"include"}),u=await l.json().catch(()=>({}));if(l.ok){s&&(E(Array.isArray(u?.plans)?u.plans:[]),Q(u?.pricingSource&&typeof u.pricingSource=="object"?u.pricingSource:null));return}throw new Error(String(u?.error||`Failed to load pricing (${l.status})`))}catch(l){if(s){const u=l instanceof Error?l.message:"Failed to load pricing";k(u||"Failed to load pricing"),Q(null)}}finally{s&&O(!1)}})(),()=>{s=!1}},[f,K]);const h=a.useMemo(()=>[...w].sort((s,g)=>{const y=ce(s),l=ce(g);if(y!==l)return y-l;const u=s.defaultVersion?.versionNumber||0,p=g.defaultVersion?.versionNumber||0;return u!==p?u-p:String(s.displayName||s.planId).localeCompare(String(g.displayName||g.planId))}),[w]),Y=a.useMemo(()=>ot(I||U),[U,I]),q=`${n.plansWrapper} ${W?n.shellOwnsScroll:"tf-scrollbar"} ${_==="site"?n.siteVariant:""} ${m?n.withChrome:""}`.trim(),j=s=>W?e.jsxs(e.Fragment,{children:[m,s,P]}):e.jsxs("div",{className:ue.standaloneWrapper,"data-theme":"dark",children:[m,s,P]});return j(A||!D?e.jsx(e.Fragment,{children:e.jsx("div",{className:q,children:e.jsx("div",{className:n.contentFrame,children:e.jsxs("div",{className:n.loader,children:[e.jsx(fe,{size:48,className:n.spinner}),e.jsx("p",{children:"Loading plans..."})]})})})}):F?e.jsx(e.Fragment,{children:e.jsx("div",{className:q,children:e.jsx("div",{className:n.contentFrame,children:e.jsxs("div",{className:n.emptyState,children:[e.jsx("h2",{children:"Connecting to plan pricing..."}),e.jsx("p",{children:F}),e.jsx("button",{className:n.backBtn,onClick:()=>Z(s=>s+1),children:"Retry"})]})})})}):e.jsx(e.Fragment,{children:e.jsx("div",{className:q,children:e.jsxs("div",{className:n.contentFrame,children:[e.jsxs("div",{className:n.header,children:[!m&&(c||$)&&e.jsxs("div",{className:n.headerBar,children:[e.jsx("div",{children:c?e.jsx("button",{className:n.backBtn,onClick:G,children:H}):null}),e.jsx("div",{className:n.headerActions,children:$})]}),e.jsx("h1",{className:n.title,children:r}),i&&e.jsx("p",{className:n.subtitle,children:i}),it()&&Y?e.jsxs("p",{className:n.debugMeta,children:["Pricing source: ",Y]}):null,b&&e.jsx("div",{className:`${n.statusBanner} ${b.type==="success"?n.successBanner:n.errorBanner}`,children:b.message})]}),h.length===0?e.jsxs("div",{className:`${n.emptyState} ${n.marketingEmptyState}`,children:[e.jsx("span",{className:n.emptyBadge,children:"Coming Soon"}),e.jsx("h2",{children:"Paid plans are getting their final polish."}),e.jsx("p",{children:"Taskforce pricing is on the way with flexible options for solo operators, teams, and larger rollouts. Check back soon for launch tiers, feature bundles, and early access details."}),e.jsxs("div",{className:n.emptyHighlights,"aria-label":"Upcoming pricing highlights",children:[e.jsx("span",{children:"Launch-ready tiers"}),e.jsx("span",{children:"Team billing controls"}),e.jsx("span",{children:"Feature-based packaging"})]})]}):e.jsx("div",{className:n.grid,children:h.map(s=>{const g=s.pricing.month,y=s.pricing.year,l=ie(g),u=ie(y),p=String(s.defaultVersion?.planVersionId||"").trim(),x=!!(p&&C&&C===p&&ut(X))||!C&&!!(V&&V===s.planId),N=l?"month":u?"year":null,L=N==="year"?y:g,T=!!p&&(x?d:!!N),J=()=>{if(p){if(x){if(!d)return;S({planId:s.planId,planVersionId:p,interval:N||"month",pricingType:L?.pricingType||"stripe",isCurrentPlan:!0});return}N&&S({planId:s.planId,planVersionId:p,interval:N,pricingType:L?.pricingType||"stripe",isCurrentPlan:!1})}};return e.jsxs("div",{className:`${n.planCard} ${x?n.activePlanCard:""} ${T?n.clickablePlanCard:""}`,"data-testid":`plan-card-${s.planId}`,role:T?"button":void 0,tabIndex:T?0:void 0,onClick:T?J:void 0,onKeyDown:T?t=>{(t.key==="Enter"||t.key===" ")&&(t.preventDefault(),J())}:void 0,children:[x&&e.jsx("span",{className:n.currentBadge,children:"Current Plan"}),e.jsx("h3",{className:n.planName,children:s.displayName}),e.jsx("div",{className:n.featuresList,children:Array.isArray(s.features)&&s.features.length>0?s.features.map((t,o)=>e.jsxs("div",{className:n.featureItem,children:[e.jsx(ge,{className:n.featureIcon}),e.jsx("span",{className:n.featureText,children:String(t.featureKey||"").replace(/[_\-.]+/g," ").replace(/\b\w/g,v=>v.toUpperCase())})]},`${t.featureKey}-${o}`)):e.jsx("span",{className:n.featureText,children:"No additional features included"})}),e.jsxs("div",{className:n.priceRows,children:[e.jsxs("div",{className:n.priceRow,children:[e.jsx("span",{className:n.priceRowAmount,children:oe(g)}),e.jsx("span",{className:n.priceRowInterval,children:"/ month"})]}),e.jsxs("div",{className:n.priceRow,children:[e.jsx("span",{className:n.priceRowAmount,children:oe(y)}),e.jsx("span",{className:n.priceRowInterval,children:"/ year"})]})]}),x?e.jsx("button",{className:`${n.callToAction} ${n.currentCta}`,"data-testid":`plan-manage-${s.planId}`,onClick:t=>{t.stopPropagation(),d&&S({planId:s.planId,planVersionId:p,interval:N||"month",pricingType:L?.pricingType||"stripe",isCurrentPlan:!0})},disabled:!p||!d,children:B&&d?"Manage Plan":"Current Plan"}):e.jsxs("div",{className:n.ctaRow,children:[e.jsx("button",{className:`${n.callToAction} ${n.secondaryCta}`,"data-testid":`plan-select-${s.planId}-month`,onClick:t=>{t.stopPropagation(),S({planId:s.planId,planVersionId:p,interval:"month",pricingType:g?.pricingType||"stripe",isCurrentPlan:!1})},disabled:!p||!l,children:g?.pricingType==="free"?B?"Select Free":"Get Started Free":B?"Choose Monthly":"Get Started Monthly"}),e.jsx("button",{className:`${n.callToAction} ${n.primaryCta}`,"data-testid":`plan-select-${s.planId}-year`,onClick:t=>{t.stopPropagation(),S({planId:s.planId,planVersionId:p,interval:"year",pricingType:y?.pricingType||"stripe",isCurrentPlan:!1})},disabled:!p||!u,children:y?.pricingType==="free"?B?"Select Free":"Get Started Free":B?"Choose Yearly":"Get Started Yearly"})]})]},s.planId)})})]})})}))}const pt=1600,mt=new Set(["active","trialing","grace"]),M={planSelectionRequired:"Choose a plan to continue.",misconfigured:"Signup monetization is misconfigured. Contact support or a system administrator.",missingEntitlement:"No entitlement is linked to this account."};function gt(r){return r==="year"?"year":"month"}function le(r){const i=String(r||"").trim();return i?i.replace(/\/+$/,""):""}function ne(r={}){const i=new URLSearchParams;i.set("screen","plans");for(const[_,m]of Object.entries(r)){const P=String(m||"").trim();P&&i.set(_,P)}return`/?${i.toString()}`}function St({isAuthenticated:r,runtimeMode:i,authSessionResolved:_,currentTheme:m="dark",projectName:P,currentWorkspaceId:B,apiBaseUrl:D,connectedEnvironmentSource:V,embedded:C=!1,shellOwnsScroll:X=!1}){const d=ye(),f=_e(),[b,G]=a.useState(null),[H,$]=a.useState(!1),[W,c]=a.useState(null),U=a.useRef(!1),S=a.useRef(null),w=a.useMemo(()=>new URLSearchParams(f.search),[f.search]),E=String(w.get("checkout")||"").trim().toLowerCase(),I=String(w.get("gate")||"").trim().toLowerCase(),Q=String(w.get("planId")||"").trim(),A=String(w.get("planVersionId")||"").trim(),O=gt(w.get("interval")),F=String(w.get("session_id")||"").trim(),k=a.useMemo(()=>le(D),[D]),K=a.useMemo(()=>le(V),[V]),Z=a.useMemo(()=>({environment:de(K||k),runtimeMode:i==="cloud"?"cloud":"local"}),[k,K,i]),h=a.useCallback(t=>{const o=String(t||"").trim();return k?`${k}${o.startsWith("/")?o:`/${o}`}`:o},[k]),Y=a.useCallback(()=>{const t=new URLSearchParams(f.search);t.delete("screen"),t.delete("gate"),t.delete("checkout"),t.delete("planId"),t.delete("planVersionId"),t.delete("interval");const o=t.toString();d(`/${o?`?${o}`:""}${f.hash||""}`)},[f.hash,f.search,d]),q=a.useMemo(()=>[k?`${k}/api/taskforce/public/pricing`:"","/api/taskforce/public/pricing"].map(o=>String(o||"").trim()).filter((o,v,R)=>o&&R.indexOf(o)===v),[k]),j=a.useMemo(()=>{const t=String(b?.entitlementState||"").trim().toLowerCase();return mt.has(t)},[b?.entitlementState]),s=a.useMemo(()=>j?null:I==="plan_selection_required"?M.planSelectionRequired:I==="misconfigured"?M.misconfigured:I==="missing_entitlement"?M.missingEntitlement:null,[I,j]),g=a.useCallback(()=>{const t=new URLSearchParams(f.search);t.delete("checkout"),t.delete("session_id"),t.delete("gate"),t.delete("planId"),t.delete("planVersionId"),t.delete("interval");const o=t.toString();d(`${f.pathname}${o?`?${o}`:""}${f.hash||""}`,{replace:!0})},[f.hash,f.pathname,f.search,d]),y=a.useCallback(()=>{S.current!=null&&window.clearTimeout(S.current),S.current=window.setTimeout(()=>{g(),S.current=null},pt)},[g]),l=a.useCallback(async()=>{const t=h("/api/taskforce/billing/status");if(!r){G(null),z(t);return}try{const o=await pe(t);G(o)}catch{}},[h,r]),u=a.useCallback(async(t,o)=>{if(!t){c({type:"error",message:"Selected plan is missing a default plan version."});return}$(!0),c(null);try{const v=await fetch(h("/api/taskforce/billing/checkout-session"),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({planVersionId:t,interval:o})}),R=await v.json().catch(()=>({}));if(!v.ok){c({type:"error",message:String(R?.error||"Unable to start checkout for selected plan.")});return}const ee=String(R?.checkoutState||"").trim().toLowerCase();if(ee==="free"){c({type:"success",message:"Plan selected. Your access is ready."}),z(h("/api/taskforce/billing/status")),await l();return}if(ee==="updated"){c({type:"success",message:String(R?.message||"Your subscription was updated successfully.")}),z(h("/api/taskforce/billing/status")),await l();return}if(ee==="already_current_plan"){c({type:"success",message:String(R?.message||"You are already on this plan.")}),z(h("/api/taskforce/billing/status")),await l();return}const re=String(R?.returnBaseUrl||"").trim();if(i==="local"&&re)try{const te=window.location.origin,ae=new URL(re,te).origin;if(ae!==te){c({type:"error",message:`Checkout return is misconfigured for this runtime. Expected ${te} but got ${ae}.`});return}}catch{c({type:"error",message:"Checkout return URL is invalid for this runtime."});return}const se=String(R?.url||"").trim();if(!se){c({type:"error",message:"Checkout did not return a redirect URL."});return}window.location.assign(se)}catch{c({type:"error",message:"Unable to start checkout for selected plan."})}finally{$(!1)}},[h,l]),p=a.useCallback(async t=>{if(!t)return!1;try{const o=await fetch(h("/api/taskforce/billing/checkout-session/confirm"),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({sessionId:t})}),v=await o.json().catch(()=>({}));return o.ok?(z(h("/api/taskforce/billing/status")),await l(),!0):(c({type:"error",message:String(v?.error||"Unable to confirm checkout completion.")}),!1)}catch{return c({type:"error",message:"Unable to confirm checkout completion."}),!1}},[h,l]),x=a.useCallback(async()=>{if(!r){d(`/login?mode=login&next=${encodeURIComponent(ne())}`);return}$(!0),c(null);try{const t=await fetch(h("/api/taskforce/billing/portal-session"),{method:"POST",credentials:"include"}),o=await t.json().catch(()=>({}));if(!t.ok){c({type:"error",message:String(o?.error||"Failed to open billing portal.")});return}const v=String(o?.url||"").trim();if(!v){c({type:"error",message:"Portal session did not return a redirect URL."});return}window.location.assign(v)}catch{c({type:"error",message:"Failed to open billing portal."})}finally{$(!1)}},[h,r,d]);a.useEffect(()=>{l()},[l]),a.useEffect(()=>{j&&I==="missing_entitlement"&&g()},[g,I,j]),a.useEffect(()=>()=>{S.current!=null&&window.clearTimeout(S.current)},[]),a.useEffect(()=>{if(E==="success"){U.current=!0,c({type:"success",message:"Checkout completed successfully. Finalizing your plan..."}),(async()=>(F&&await p(F)||await l(),c({type:"success",message:"Checkout completed successfully."}),y()))();return}if(E==="cancel"){c({type:"error",message:"Checkout was canceled. No charge was made."}),y();return}if((E==="start"||E==="free")&&r&&A&&!U.current){U.current=!0,u(A,O);return}if(s){c({type:"error",message:s});return}c(t=>!t||t.type!=="error"?t:t.message===M.planSelectionRequired||t.message===M.misconfigured||t.message===M.missingEntitlement?null:t)},[E,p,s,r,l,O,A,F,y,u]);const N=a.useCallback(async t=>{if(!r){const o=t.pricingType==="free"?"free":"start",v=ne({checkout:o,planId:t.planId,planVersionId:t.planVersionId,interval:t.interval});d(`/login?mode=register&planId=${encodeURIComponent(t.planId)}&planVersionId=${encodeURIComponent(t.planVersionId)}&interval=${encodeURIComponent(t.interval)}&next=${encodeURIComponent(v)}`);return}if(t.isCurrentPlan){if(!b?.stripeCustomerId){c({type:"success",message:"Your current plan does not use Stripe billing."});return}await x();return}if(t.pricingType==="free"){await u(t.planVersionId,t.interval);return}await u(t.planVersionId,t.interval)},[r,d,x,u]),L=a.useCallback(()=>{if(C){Y();return}if(window.history.length>1){d(-1);return}d("/")},[Y,C,d]),T=C?e.jsx(e.Fragment,{children:r&&b?.stripeCustomerId&&e.jsx("button",{className:n.navAction,onClick:()=>{x()},disabled:H,children:"Manage billing"})}):e.jsxs(e.Fragment,{children:[e.jsx("button",{className:n.navAction,onClick:L,children:"Back"}),r&&b?.stripeCustomerId&&e.jsx("button",{className:n.navAction,onClick:()=>{x()},disabled:H,children:"Manage billing"}),!r&&e.jsxs("button",{className:n.navAction,onClick:()=>d(`/login?mode=login&next=${encodeURIComponent(ne())}`),children:[e.jsx(he,{size:16,style:{marginRight:"8px",verticalAlign:"text-bottom"}}),"Sign In"]})]}),J=C?null:e.jsx(me,{projectName:P,currentWorkspaceId:B,runtimeMode:i==="cloud"?"cloud":"local",theme:m,actions:T});return e.jsx(dt,{chrome:J,heading:"Subscription Plans",subtitle:"Manage your subscription and explore available features. Your current plan is highlighted below.",isAuthenticated:r,authSessionResolved:_,currentPlanId:String(b?.planId||Q||"").trim()||null,currentPlanVersionId:String(b?.planVersionId||A||"").trim()||null,currentEntitlementState:String(b?.entitlementState||"").trim()||null,canManageCurrentPlan:!!b?.stripeCustomerId,pricingEndpoint:q[0],statusBanner:W,onBack:L,backLabel:C?"Back to Board":"Back to App",topActions:C?T:null,shellOwnsScroll:X,showPageBackButton:!C,fallbackPricingSource:Z,onSelectPlan:N})}export{St as PlansPage};