@datalayer/core 1.0.20 → 1.0.22

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 (138) hide show
  1. package/README.md +27 -0
  2. package/lib/api/DatalayerApi.d.ts +5 -1
  3. package/lib/api/DatalayerApi.js +55 -8
  4. package/lib/api/iam/oauth2.d.ts +3 -3
  5. package/lib/api/iam/oauth2.js +2 -2
  6. package/lib/api/iam/profile.d.ts +9 -1
  7. package/lib/api/iam/profile.js +15 -0
  8. package/lib/api/runtimes/checkpoints.js +1 -1
  9. package/lib/api/runtimes/snapshots.d.ts +7 -7
  10. package/lib/api/runtimes/snapshots.js +9 -9
  11. package/lib/client/auth/storage.js +103 -12
  12. package/lib/client/index.d.ts +7 -7
  13. package/lib/client/index.js +1 -1
  14. package/lib/client/mixins/RuntimesMixin.d.ts +6 -6
  15. package/lib/client/mixins/RuntimesMixin.js +5 -5
  16. package/lib/client/mixins/SpacerMixin.js +22 -1
  17. package/lib/collaboration/DatalayerCollaborationProvider.d.ts +4 -3
  18. package/lib/components/avatars/DLAvatar.d.ts +24 -0
  19. package/lib/components/avatars/DLAvatar.js +26 -0
  20. package/lib/components/avatars/UserProfileAvatar.js +4 -4
  21. package/lib/components/avatars/index.d.ts +1 -0
  22. package/lib/components/avatars/index.js +1 -0
  23. package/lib/components/billing/BillableAccountSelect.d.ts +35 -0
  24. package/lib/components/billing/BillableAccountSelect.js +340 -0
  25. package/lib/components/billing/index.d.ts +1 -0
  26. package/lib/components/billing/index.js +5 -0
  27. package/lib/components/checkout/StripeCheckout.d.ts +24 -3
  28. package/lib/components/checkout/StripeCheckout.js +1116 -55
  29. package/lib/components/display/DatalayerBox.js +6 -1
  30. package/lib/components/index.d.ts +2 -0
  31. package/lib/components/index.js +2 -0
  32. package/lib/components/principal/Principal.d.ts +37 -0
  33. package/lib/components/principal/Principal.js +54 -0
  34. package/lib/components/principal/PrincipalAvatar.d.ts +10 -0
  35. package/lib/components/principal/PrincipalAvatar.js +55 -0
  36. package/lib/components/principal/PrincipalBadge.d.ts +20 -0
  37. package/lib/components/principal/PrincipalBadge.js +208 -0
  38. package/lib/components/principal/PrincipalBanner.d.ts +7 -0
  39. package/lib/components/principal/PrincipalBanner.js +84 -0
  40. package/lib/components/principal/PrincipalDetailsOverlay.d.ts +23 -0
  41. package/lib/components/principal/PrincipalDetailsOverlay.js +86 -0
  42. package/lib/components/principal/PrincipalSwitcherMenu.d.ts +14 -0
  43. package/lib/components/principal/PrincipalSwitcherMenu.js +287 -0
  44. package/lib/components/principal/index.d.ts +7 -0
  45. package/lib/components/principal/index.js +10 -0
  46. package/lib/components/runtimes/RuntimeLauncherDialog.d.ts +2 -2
  47. package/lib/components/sharing/ShareAccessComponent.d.ts +18 -0
  48. package/lib/components/sharing/ShareAccessComponent.js +1368 -0
  49. package/lib/components/sharing/SharingEditor.d.ts +20 -0
  50. package/lib/components/sharing/SharingEditor.js +65 -0
  51. package/lib/components/sharing/index.d.ts +9 -0
  52. package/lib/components/sharing/index.js +12 -0
  53. package/lib/components/snapshots/{RuntimeSnapshotMenu.d.ts → SandboxSnapshotMenu.d.ts} +4 -4
  54. package/lib/components/snapshots/{RuntimeSnapshotMenu.js → SandboxSnapshotMenu.js} +33 -33
  55. package/lib/components/snapshots/index.d.ts +1 -1
  56. package/lib/components/snapshots/index.js +1 -1
  57. package/lib/components/storage/ContentsBrowser.js +2 -2
  58. package/lib/components/tokens/SpaceVariantToken.d.ts +1 -1
  59. package/lib/components/tokens/SpaceVariantToken.js +7 -4
  60. package/lib/components/users/PeerIndicator.js +5 -4
  61. package/lib/hooks/index.d.ts +5 -0
  62. package/lib/hooks/index.js +5 -0
  63. package/lib/hooks/useBillableAccountStore.d.ts +31 -0
  64. package/lib/hooks/useBillableAccountStore.js +53 -0
  65. package/lib/hooks/useCache.d.ts +125 -18
  66. package/lib/hooks/useCache.js +893 -120
  67. package/lib/hooks/useDatalayer.js +31 -3
  68. package/lib/hooks/useIAM.js +1 -8
  69. package/lib/hooks/usePrincipalStore.d.ts +41 -0
  70. package/lib/hooks/usePrincipalStore.js +101 -0
  71. package/lib/hooks/useProjects.d.ts +44 -0
  72. package/lib/hooks/useProjects.js +26 -2
  73. package/lib/hooks/useSelectedBillableAccount.d.ts +14 -0
  74. package/lib/hooks/useSelectedBillableAccount.js +36 -0
  75. package/lib/hooks/useSelectedPrincipal.d.ts +17 -0
  76. package/lib/hooks/useSelectedPrincipal.js +29 -0
  77. package/lib/hooks/useUsageRefreshStore.d.ts +8 -0
  78. package/lib/hooks/useUsageRefreshStore.js +16 -0
  79. package/lib/index.d.ts +2 -2
  80. package/lib/index.js +1 -1
  81. package/lib/mocks/components/FlashMock.js +8 -2
  82. package/lib/models/{RuntimeSnapshot.d.ts → CodeSandboxSnapshot.d.ts} +5 -5
  83. package/lib/models/{RuntimeSnapshot.js → CodeSandboxSnapshot.js} +1 -1
  84. package/lib/models/{RuntimeSnapshotDTO.d.ts → CodeSandboxSnapshotDTO.d.ts} +30 -30
  85. package/lib/models/{RuntimeSnapshotDTO.js → CodeSandboxSnapshotDTO.js} +6 -6
  86. package/lib/models/Environment.d.ts +4 -0
  87. package/lib/models/IAM.d.ts +66 -0
  88. package/lib/models/Organization.d.ts +1 -0
  89. package/lib/models/Organization.js +1 -0
  90. package/lib/models/Page.d.ts +2 -2
  91. package/lib/models/Page.js +2 -2
  92. package/lib/models/Profile.d.ts +1 -1
  93. package/lib/models/RuntimeDTO.d.ts +2 -2
  94. package/lib/models/Space.d.ts +1 -0
  95. package/lib/models/Space.js +29 -5
  96. package/lib/models/SpaceDTO.d.ts +4 -0
  97. package/lib/models/SpaceDTO.js +5 -0
  98. package/lib/models/Usage.d.ts +15 -0
  99. package/lib/models/Usage.js +9 -0
  100. package/lib/models/User.d.ts +21 -0
  101. package/lib/models/User.js +6 -2
  102. package/lib/models/index.d.ts +2 -2
  103. package/lib/models/index.js +2 -2
  104. package/lib/otel/auth.js +0 -4
  105. package/lib/services/index.d.ts +1 -1
  106. package/lib/services/index.js +1 -1
  107. package/lib/state/substates/IAMState.js +0 -7
  108. package/lib/state/substates/RuntimesState.d.ts +9 -9
  109. package/lib/state/substates/RuntimesState.js +3 -3
  110. package/lib/stateful/runtimes/actions.d.ts +17 -17
  111. package/lib/stateful/runtimes/actions.js +35 -55
  112. package/lib/stateful/runtimes/apis.d.ts +3 -3
  113. package/lib/stateful/runtimes/snapshots.d.ts +2 -2
  114. package/lib/stateful/runtimes/snapshots.js +7 -7
  115. package/lib/utils/Format.d.ts +6 -0
  116. package/lib/utils/Format.js +17 -0
  117. package/lib/utils/Handles.d.ts +3 -0
  118. package/lib/utils/Handles.js +53 -0
  119. package/lib/utils/Snapshot.d.ts +1 -1
  120. package/lib/utils/Snapshot.js +1 -1
  121. package/lib/utils/index.d.ts +1 -0
  122. package/lib/utils/index.js +1 -0
  123. package/lib/views/datasources/DatasourceDetail.d.ts +7 -1
  124. package/lib/views/datasources/DatasourceDetail.js +61 -23
  125. package/lib/views/datasources/DatasourceNew.d.ts +8 -1
  126. package/lib/views/datasources/DatasourceNew.js +23 -10
  127. package/lib/views/datasources/Datasources.d.ts +5 -1
  128. package/lib/views/datasources/Datasources.js +38 -30
  129. package/lib/views/iam-tokens/IAMTokenNew.d.ts +3 -1
  130. package/lib/views/iam-tokens/IAMTokenNew.js +2 -2
  131. package/lib/views/iam-tokens/IAMTokens.d.ts +3 -1
  132. package/lib/views/iam-tokens/IAMTokens.js +2 -2
  133. package/lib/views/otel/OtelHeader.d.ts +5 -0
  134. package/lib/views/otel/OtelHeader.js +3 -3
  135. package/lib/views/profile/UserBadge.d.ts +7 -2
  136. package/lib/views/profile/UserBadge.js +11 -2
  137. package/lib/views/secrets/Secrets.js +21 -21
  138. package/package.json +7 -8
@@ -4,11 +4,9 @@
4
4
  */
5
5
  /**
6
6
  * TanStack Query-based cache hook for Datalayer API
7
+ * for automatic cache management, background refetching,
8
+ * and optimistic updates.
7
9
  *
8
- * This is a modernized replacement for useCache.tsx that leverages TanStack Query
9
- * for automatic cache management, background refetching, and optimistic updates.
10
- *
11
- * Key improvements over useCache:
12
10
  * - Automatic cache management (no manual Map objects)
13
11
  * - Built-in loading/error states
14
12
  * - Automatic refetching and deduplication
@@ -42,7 +40,6 @@ import { useQuery, useMutation, useQueryClient, } from '@tanstack/react-query';
42
40
  import { BOOTSTRAP_USER_ONBOARDING, asContact, asDatasource, asInvite, asOrganization, asPage, asSecret, asSpace, asTeam, asToken, asUser, } from '../models/index.js';
43
41
  import { useCoreStore, useIAMStore } from '../state/index.js';
44
42
  import { asDisplayName, namesAsInitials, asArray } from '../utils/index.js';
45
- import { newUserMock } from './../mocks/index.js';
46
43
  import { useDatalayer } from './useDatalayer.js';
47
44
  import { useAuthorization } from './useAuthorization.js';
48
45
  import { useUploadForm } from './useUpload.js';
@@ -81,7 +78,9 @@ export const queryKeys = {
81
78
  details: () => [...queryKeys.users.all(), 'detail'],
82
79
  detail: (id) => [...queryKeys.users.details(), id],
83
80
  byHandle: (handle) => [...queryKeys.users.all(), 'handle', handle],
81
+ publicProfileByHandle: (handle) => [...queryKeys.users.all(), 'public-profile', handle],
84
82
  search: (pattern) => [...queryKeys.users.all(), 'search', pattern],
83
+ bulk: (userIdsKey) => [...queryKeys.users.all(), 'bulk', userIdsKey],
85
84
  settings: (userId) => [...queryKeys.users.detail(userId), 'settings'],
86
85
  onboarding: (userId) => [...queryKeys.users.detail(userId), 'onboarding'],
87
86
  surveys: (userId) => [...queryKeys.users.detail(userId), 'surveys'],
@@ -94,6 +93,7 @@ export const queryKeys = {
94
93
  details: () => [...queryKeys.organizations.all(), 'detail'],
95
94
  detail: (id) => [...queryKeys.organizations.details(), id],
96
95
  byHandle: (handle) => [...queryKeys.organizations.all(), 'handle', handle],
96
+ publicProfileByHandle: (handle) => [...queryKeys.organizations.all(), 'public-profile', handle],
97
97
  userOrgs: () => [...queryKeys.organizations.all(), 'user'],
98
98
  members: (orgId) => [...queryKeys.organizations.detail(orgId), 'members'],
99
99
  },
@@ -284,6 +284,19 @@ export const queryKeys = {
284
284
  userById: (userId) => ['usages', 'user', userId],
285
285
  platform: () => ['usages', 'platform'],
286
286
  },
287
+ // Credits allocations
288
+ credits: {
289
+ organizationOverview: (organizationUid) => ['credits', 'allocations', 'organization', organizationUid],
290
+ organizationHistory: (organizationUid) => [
291
+ 'credits',
292
+ 'allocations',
293
+ 'organization',
294
+ organizationUid,
295
+ 'history',
296
+ ],
297
+ teamOverview: (teamUid) => ['credits', 'allocations', 'team', teamUid],
298
+ teamHistory: (teamUid) => ['credits', 'allocations', 'team', teamUid, 'history'],
299
+ },
287
300
  // Prices
288
301
  prices: {
289
302
  stripe: () => ['prices', 'stripe'],
@@ -368,10 +381,45 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
368
381
  const toTeam = (org, organizationId) => {
369
382
  return asTeam(org, organizationId);
370
383
  };
384
+ /**
385
+ * Build a minimal {@link IUser} for an item's owner/creator from the
386
+ * raw fields returned by spacer (`creator_uid_s`/`creator_handle_s`
387
+ * and friends). Returns an empty-but-typed user when no creator info
388
+ * is available so the UI hydration hooks (e.g. `useUsersByUids`) can
389
+ * still fill in display data downstream.
390
+ */
391
+ const toItemOwner = (raw) => {
392
+ const uid = String(raw?.creator_uid_s ??
393
+ raw?.creator_uid ??
394
+ raw?.owner_uid_s ??
395
+ raw?.owner_uid ??
396
+ '').trim();
397
+ const handle = String(raw?.creator_handle_s ?? raw?.owner_handle_s ?? '').trim();
398
+ const firstName = String(raw?.creator_first_name_t ?? raw?.first_name_t ?? '').trim();
399
+ const lastName = String(raw?.creator_last_name_t ?? raw?.last_name_t ?? '').trim();
400
+ const email = String(raw?.creator_email_s ?? raw?.email_s ?? '').trim();
401
+ const displayName = firstName || lastName ? asDisplayName(firstName, lastName) : handle;
402
+ return {
403
+ id: uid,
404
+ handle,
405
+ email,
406
+ firstName,
407
+ lastName,
408
+ initials: namesAsInitials(firstName, lastName),
409
+ displayName,
410
+ roles: [],
411
+ iamProviders: [],
412
+ setRoles: () => { },
413
+ unsubscribedFromOutbounds: false,
414
+ onboarding: BOOTSTRAP_USER_ONBOARDING,
415
+ events: [],
416
+ settings: {},
417
+ };
418
+ };
371
419
  // Kept for potential future use
372
420
  // Kept for potential future use
373
421
  const toDataset = (raw_dataset) => {
374
- const owner = newUserMock();
422
+ const owner = toItemOwner(raw_dataset);
375
423
  return {
376
424
  id: raw_dataset.uid,
377
425
  type: 'dataset',
@@ -394,12 +442,13 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
394
442
  handle: raw_dataset.handle_s,
395
443
  },
396
444
  organization: {
397
- handle: raw_dataset.handle_s,
445
+ id: raw_dataset.organization_uid_s,
446
+ handle: raw_dataset.organization_handle_s,
398
447
  },
399
448
  };
400
449
  };
401
450
  const toCell = (cl) => {
402
- const owner = newUserMock();
451
+ const owner = toItemOwner(cl);
403
452
  return {
404
453
  id: cl.uid,
405
454
  type: 'cell',
@@ -418,12 +467,13 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
418
467
  handle: cl.handle_s,
419
468
  },
420
469
  organization: {
421
- handle: cl.handle_s,
470
+ id: cl.organization_uid_s,
471
+ handle: cl.organization_handle_s,
422
472
  },
423
473
  };
424
474
  };
425
475
  const toNotebook = (raw_notebook) => {
426
- const owner = newUserMock();
476
+ const owner = toItemOwner(raw_notebook);
427
477
  return {
428
478
  id: raw_notebook.uid,
429
479
  type: 'notebook',
@@ -446,12 +496,13 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
446
496
  handle: raw_notebook.handle_s,
447
497
  },
448
498
  organization: {
449
- handle: raw_notebook.handle_s,
499
+ id: raw_notebook.organization_uid_s,
500
+ handle: raw_notebook.organization_handle_s,
450
501
  },
451
502
  };
452
503
  };
453
504
  const toDocument = (doc) => {
454
- const owner = newUserMock();
505
+ const owner = toItemOwner(doc);
455
506
  return {
456
507
  id: doc.uid,
457
508
  type: 'document',
@@ -471,12 +522,13 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
471
522
  handle: doc.handle_s,
472
523
  },
473
524
  organization: {
474
- handle: doc.handle_s,
525
+ id: doc.organization_uid_s,
526
+ handle: doc.organization_handle_s,
475
527
  },
476
528
  };
477
529
  };
478
530
  const toLesson = (raw_lesson) => {
479
- const owner = newUserMock();
531
+ const owner = toItemOwner(raw_lesson);
480
532
  return {
481
533
  id: raw_lesson.uid,
482
534
  type: 'lesson',
@@ -496,13 +548,14 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
496
548
  handle: raw_lesson.handle_s,
497
549
  },
498
550
  organization: {
499
- handle: raw_lesson.handle_s,
551
+ id: raw_lesson.organization_uid_s,
552
+ handle: raw_lesson.organization_handle_s,
500
553
  },
501
554
  datasets: [],
502
555
  };
503
556
  };
504
557
  const toExercise = (ex) => {
505
- const owner = newUserMock();
558
+ const owner = toItemOwner(ex);
506
559
  return {
507
560
  id: ex.uid,
508
561
  type: 'exercise',
@@ -526,13 +579,14 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
526
579
  handle: ex.handle_s,
527
580
  },
528
581
  organization: {
529
- handle: ex.handle_s,
582
+ id: ex.organization_uid_s,
583
+ handle: ex.organization_handle_s,
530
584
  },
531
585
  datasets: [],
532
586
  };
533
587
  };
534
588
  const toAssignment = (raw_assignment) => {
535
- const owner = newUserMock();
589
+ const owner = toItemOwner(raw_assignment);
536
590
  let studentItem = undefined;
537
591
  if (raw_assignment.student_items) {
538
592
  raw_assignment.student_items.forEach((student_item) => {
@@ -570,12 +624,13 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
570
624
  handle: raw_assignment.handle_s,
571
625
  },
572
626
  organization: {
573
- handle: raw_assignment.handle_s,
627
+ id: raw_assignment.organization_uid_s,
628
+ handle: raw_assignment.organization_handle_s,
574
629
  },
575
630
  };
576
631
  };
577
632
  const toEnvironment = (env) => {
578
- const owner = newUserMock();
633
+ const owner = toItemOwner(env);
579
634
  return {
580
635
  id: env.uid,
581
636
  type: 'environment',
@@ -591,7 +646,8 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
591
646
  handle: env.handle_s,
592
647
  },
593
648
  organization: {
594
- handle: env.handle_s,
649
+ id: env.organization_uid_s,
650
+ handle: env.organization_handle_s,
595
651
  },
596
652
  };
597
653
  };
@@ -622,7 +678,7 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
622
678
  }
623
679
  };
624
680
  const toCourse = (raw_course) => {
625
- const owner = newUserMock();
681
+ const owner = toItemOwner(raw_course);
626
682
  let instructor = undefined;
627
683
  if (raw_course.members) {
628
684
  let raw_instructor = raw_course.members;
@@ -860,6 +916,46 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
860
916
  enabled: !!handle,
861
917
  });
862
918
  };
919
+ /**
920
+ * Get enriched public user profile by handle.
921
+ */
922
+ const useUserPublicProfileByHandle = (handle) => {
923
+ return useQuery({
924
+ queryKey: queryKeys.users.publicProfileByHandle(handle),
925
+ queryFn: async () => {
926
+ const normalizedHandle = String(handle || '').trim();
927
+ if (!normalizedHandle) {
928
+ return null;
929
+ }
930
+ const resp = await requestDatalayer({
931
+ url: `${configuration.iamRunUrl}/api/iam/v1/users/public/${encodeURIComponent(normalizedHandle)}`,
932
+ method: 'GET',
933
+ });
934
+ if (resp.success) {
935
+ const profile = resp.profile || null;
936
+ if (profile?.uid && profile?.handle) {
937
+ const mappedUser = toUser({
938
+ uid: profile.uid,
939
+ id: profile.id,
940
+ handle_s: profile.handle,
941
+ first_name_t: profile.first_name,
942
+ last_name_t: profile.last_name,
943
+ avatar_url_s: profile.avatar_url,
944
+ origin_s: profile.origin,
945
+ });
946
+ if (mappedUser) {
947
+ queryClient.setQueryData(queryKeys.users.detail(mappedUser.id), mappedUser);
948
+ queryClient.setQueryData(queryKeys.users.byHandle(mappedUser.handle), mappedUser);
949
+ }
950
+ }
951
+ return profile;
952
+ }
953
+ return null;
954
+ },
955
+ ...DEFAULT_QUERY_OPTIONS,
956
+ enabled: !!handle,
957
+ });
958
+ };
863
959
  /**
864
960
  * Search users by naming pattern
865
961
  */
@@ -890,6 +986,44 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
890
986
  enabled: namingPattern !== undefined && namingPattern !== null,
891
987
  });
892
988
  };
989
+ /**
990
+ * Get multiple users by UIDs in one request.
991
+ */
992
+ const useUsersByUids = (userIds) => {
993
+ const normalizedUserIds = Array.from(new Set((userIds || [])
994
+ .map(userId => String(userId || '').trim())
995
+ .filter(Boolean)));
996
+ const idsKey = normalizedUserIds.join(',');
997
+ return useQuery({
998
+ queryKey: queryKeys.users.bulk(idsKey),
999
+ queryFn: async () => {
1000
+ if (normalizedUserIds.length === 0) {
1001
+ return [];
1002
+ }
1003
+ const resp = await requestDatalayer({
1004
+ url: `${configuration.iamRunUrl}/api/iam/v1/users/bulk`,
1005
+ method: 'POST',
1006
+ body: { userIds: normalizedUserIds },
1007
+ });
1008
+ if (resp.success && resp.users) {
1009
+ const users = resp.users
1010
+ .map((u) => {
1011
+ const user = toUser(u);
1012
+ if (user) {
1013
+ queryClient.setQueryData(queryKeys.users.detail(user.id), user);
1014
+ queryClient.setQueryData(queryKeys.users.byHandle(user.handle), user);
1015
+ }
1016
+ return user;
1017
+ })
1018
+ .filter(Boolean);
1019
+ return users;
1020
+ }
1021
+ return [];
1022
+ },
1023
+ ...DEFAULT_QUERY_OPTIONS,
1024
+ enabled: normalizedUserIds.length > 0,
1025
+ });
1026
+ };
893
1027
  /**
894
1028
  * Update user onboarding
895
1029
  */
@@ -979,6 +1113,30 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
979
1113
  enabled: !!handle,
980
1114
  });
981
1115
  };
1116
+ /**
1117
+ * Get enriched public organization profile by handle.
1118
+ */
1119
+ const useOrganizationPublicProfileByHandle = (handle) => {
1120
+ return useQuery({
1121
+ queryKey: queryKeys.organizations.publicProfileByHandle(handle),
1122
+ queryFn: async () => {
1123
+ const normalizedHandle = String(handle || '').trim();
1124
+ if (!normalizedHandle) {
1125
+ return null;
1126
+ }
1127
+ const resp = await requestDatalayer({
1128
+ url: `${configuration.iamRunUrl}/api/iam/v1/organizations/public/${encodeURIComponent(normalizedHandle)}`,
1129
+ method: 'GET',
1130
+ });
1131
+ if (resp.success) {
1132
+ return resp.organization || null;
1133
+ }
1134
+ return null;
1135
+ },
1136
+ ...DEFAULT_QUERY_OPTIONS,
1137
+ enabled: !!handle,
1138
+ });
1139
+ };
982
1140
  /**
983
1141
  * Get user's organizations
984
1142
  */
@@ -1192,6 +1350,9 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1192
1350
  queryClient.setQueryData(queryKeys.spaces.byHandle(space.handle), space);
1193
1351
  return space;
1194
1352
  }
1353
+ if (resp?.code === 'FORBIDDEN') {
1354
+ throw new Error(resp.message || 'Not authorized to access this space');
1355
+ }
1195
1356
  throw new Error(resp.message || 'Failed to fetch space');
1196
1357
  },
1197
1358
  ...DEFAULT_QUERY_OPTIONS,
@@ -1212,6 +1373,9 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1212
1373
  if (resp.success && resp.space) {
1213
1374
  return toSpace(resp.space);
1214
1375
  }
1376
+ if (resp?.code === 'FORBIDDEN') {
1377
+ throw new Error(resp.message || 'Not authorized to access this space');
1378
+ }
1215
1379
  throw new Error(resp.message || 'Failed to fetch space');
1216
1380
  },
1217
1381
  ...DEFAULT_QUERY_OPTIONS,
@@ -1246,12 +1410,24 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1246
1410
  /**
1247
1411
  * Get user spaces
1248
1412
  */
1249
- const useUserSpaces = () => {
1413
+ const useUserSpaces = (scope) => {
1250
1414
  return useQuery({
1251
- queryKey: queryKeys.spaces.userSpaces(),
1415
+ queryKey: [
1416
+ ...queryKeys.spaces.userSpaces(),
1417
+ scope?.selectedPrincipalUid || '',
1418
+ scope?.selectedPrincipalKind || '',
1419
+ ],
1252
1420
  queryFn: async () => {
1421
+ const params = new URLSearchParams();
1422
+ if (scope?.selectedPrincipalUid) {
1423
+ params.set('selected_principal_uid', scope.selectedPrincipalUid);
1424
+ }
1425
+ if (scope?.selectedPrincipalKind) {
1426
+ params.set('selected_principal_kind', scope.selectedPrincipalKind);
1427
+ }
1428
+ const query = params.toString();
1253
1429
  const resp = await requestDatalayer({
1254
- url: `${configuration.spacerRunUrl}/api/spacer/v1/spaces/users/me`,
1430
+ url: `${configuration.spacerRunUrl}/api/spacer/v1/spaces/users/me${query ? `?${query}` : ''}`,
1255
1431
  method: 'GET',
1256
1432
  });
1257
1433
  if (resp.success && resp.spaces) {
@@ -1274,11 +1450,11 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1274
1450
  */
1275
1451
  const useCreateSpace = () => {
1276
1452
  return useMutation({
1277
- mutationFn: async ({ space, organization, }) => {
1453
+ mutationFn: async ({ space, organization, team, }) => {
1278
1454
  const seedSpaceId = space.variant === 'course'
1279
1455
  ? space.seedSpace?.id
1280
1456
  : undefined;
1281
- return requestDatalayer({
1457
+ const resp = await requestDatalayer({
1282
1458
  url: `${configuration.spacerRunUrl}/api/spacer/v1/spaces`,
1283
1459
  method: 'POST',
1284
1460
  body: {
@@ -1288,9 +1464,20 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1288
1464
  public: space.public,
1289
1465
  spaceHandle: space.handle,
1290
1466
  organizationId: organization?.id,
1467
+ organizationHandle: organization?.handle,
1468
+ teamId: team?.id,
1469
+ teamHandle: team?.handle,
1291
1470
  seedSpaceId,
1292
1471
  },
1293
1472
  });
1473
+ if (resp?.success === false ||
1474
+ resp?.sucess === false) {
1475
+ throw new Error(resp?.message || 'Failed to create space');
1476
+ }
1477
+ if (!resp?.space) {
1478
+ throw new Error(resp?.message || 'Failed to create space');
1479
+ }
1480
+ return resp;
1294
1481
  },
1295
1482
  onSuccess: (resp, _variables) => {
1296
1483
  if (resp.space) {
@@ -1314,7 +1501,9 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1314
1501
  mutationFn: async (space) => {
1315
1502
  const { id, name, description, ...extraFields } = space;
1316
1503
  return requestDatalayer({
1317
- url: `${configuration.spacerRunUrl}/api/spacer/v1/spaces/${id}/users/${user?.id}`,
1504
+ // Use the canonical space update route. This keeps project runtime
1505
+ // assignment cleanup resilient when stale runtime links must be cleared.
1506
+ url: `${configuration.spacerRunUrl}/api/spacer/v1/spaces/${id}`,
1318
1507
  method: 'PUT',
1319
1508
  body: {
1320
1509
  name,
@@ -1372,6 +1561,9 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1372
1561
  if (resp.success && resp.notebook) {
1373
1562
  return toNotebook(resp.notebook);
1374
1563
  }
1564
+ if (resp?.code === 'FORBIDDEN') {
1565
+ throw new Error(resp.message || 'Not authorized to access this notebook');
1566
+ }
1375
1567
  throw new Error(resp.message || 'Failed to fetch notebook');
1376
1568
  },
1377
1569
  ...DEFAULT_QUERY_OPTIONS,
@@ -1529,6 +1721,9 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1529
1721
  if (resp.success && resp.document) {
1530
1722
  return toDocument(resp.document);
1531
1723
  }
1724
+ if (resp?.code === 'FORBIDDEN') {
1725
+ throw new Error(resp.message || 'Not authorized to access this document');
1726
+ }
1532
1727
  throw new Error(resp.message || 'Failed to fetch document');
1533
1728
  },
1534
1729
  ...DEFAULT_QUERY_OPTIONS,
@@ -1695,12 +1890,24 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1695
1890
  /**
1696
1891
  * Get all pages
1697
1892
  */
1698
- const usePages = () => {
1893
+ const usePages = (scope) => {
1699
1894
  return useQuery({
1700
- queryKey: queryKeys.pages.all(),
1895
+ queryKey: [
1896
+ ...queryKeys.pages.all(),
1897
+ scope?.selectedPrincipalUid || '',
1898
+ scope?.selectedPrincipalKind || '',
1899
+ ],
1701
1900
  queryFn: async () => {
1901
+ const params = new URLSearchParams();
1902
+ if (scope?.selectedPrincipalUid) {
1903
+ params.set('selected_principal_uid', scope.selectedPrincipalUid);
1904
+ }
1905
+ if (scope?.selectedPrincipalKind) {
1906
+ params.set('selected_principal_kind', scope.selectedPrincipalKind);
1907
+ }
1908
+ const query = params.toString();
1702
1909
  const resp = await requestDatalayer({
1703
- url: `${configuration.libraryRunUrl}/api/library/v1/pages`,
1910
+ url: `${configuration.libraryRunUrl}/api/library/v1/pages${query ? `?${query}` : ''}`,
1704
1911
  method: 'GET',
1705
1912
  });
1706
1913
  if (resp.success && resp.pages) {
@@ -1797,18 +2004,17 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1797
2004
  },
1798
2005
  });
1799
2006
  };
1800
- // ============================================================================
1801
- // Datasource, Secret, Token Hooks
1802
- // ============================================================================
1803
2007
  /**
1804
2008
  * Get all datasources
1805
2009
  */
1806
- const useDatasources = () => {
2010
+ const useDatasources = (options) => {
2011
+ const principalUid = options?.principalUid;
2012
+ const principalKind = options?.principalKind;
1807
2013
  return useQuery({
1808
- queryKey: queryKeys.datasources.all(),
2014
+ queryKey: [...queryKeys.datasources.all(), principalUid || 'self', principalKind || ''],
1809
2015
  queryFn: async () => {
1810
2016
  const resp = await requestDatalayer({
1811
- url: `${configuration.iamRunUrl}/api/iam/v1/datasources`,
2017
+ url: withAccountUidQuery(`${configuration.iamRunUrl}/api/iam/v1/datasources`, principalUid, principalKind),
1812
2018
  method: 'GET',
1813
2019
  });
1814
2020
  if (resp.success && resp.datasources) {
@@ -1831,11 +2037,13 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1831
2037
  /**
1832
2038
  * Create datasource
1833
2039
  */
1834
- const useCreateDatasource = () => {
2040
+ const useCreateDatasource = (options) => {
2041
+ const principalUid = options?.principalUid;
2042
+ const principalKind = options?.principalKind;
1835
2043
  return useMutation({
1836
2044
  mutationFn: async (datasource) => {
1837
2045
  return requestDatalayer({
1838
- url: `${configuration.iamRunUrl}/api/iam/v1/datasources`,
2046
+ url: withAccountUidQuery(`${configuration.iamRunUrl}/api/iam/v1/datasources`, principalUid, principalKind),
1839
2047
  method: 'POST',
1840
2048
  body: { ...datasource },
1841
2049
  });
@@ -1860,12 +2068,14 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1860
2068
  // but this prevented useSecret from fetching fresh data (e.g., the value field).
1861
2069
  // Consider re-adding cache pre-population if the list endpoint returns full secret data,
1862
2070
  // or use a different query key pattern for partial vs full secret data.
1863
- const useSecrets = () => {
2071
+ const useSecrets = (options) => {
2072
+ const principalUid = options?.principalUid;
2073
+ const principalKind = options?.principalKind;
1864
2074
  return useQuery({
1865
- queryKey: queryKeys.secrets.all(),
2075
+ queryKey: [...queryKeys.secrets.all(), principalUid || 'self', principalKind || ''],
1866
2076
  queryFn: async () => {
1867
2077
  const resp = await requestDatalayer({
1868
- url: `${configuration.iamRunUrl}/api/iam/v1/secrets`,
2078
+ url: withAccountUidQuery(`${configuration.iamRunUrl}/api/iam/v1/secrets`, principalUid, principalKind),
1869
2079
  method: 'GET',
1870
2080
  });
1871
2081
  if (resp.success && resp.secrets) {
@@ -1883,11 +2093,13 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1883
2093
  /**
1884
2094
  * Create secret
1885
2095
  */
1886
- const useCreateSecret = () => {
2096
+ const useCreateSecret = (options) => {
2097
+ const principalUid = options?.principalUid;
2098
+ const principalKind = options?.principalKind;
1887
2099
  return useMutation({
1888
2100
  mutationFn: async (secret) => {
1889
2101
  return requestDatalayer({
1890
- url: `${configuration.iamRunUrl}/api/iam/v1/secrets`,
2102
+ url: withAccountUidQuery(`${configuration.iamRunUrl}/api/iam/v1/secrets`, principalUid, principalKind),
1891
2103
  method: 'POST',
1892
2104
  body: { ...secret },
1893
2105
  });
@@ -1906,11 +2118,13 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1906
2118
  /**
1907
2119
  * Delete secret
1908
2120
  */
1909
- const useDeleteSecret = () => {
2121
+ const useDeleteSecret = (options) => {
2122
+ const principalUid = options?.principalUid;
2123
+ const principalKind = options?.principalKind;
1910
2124
  return useMutation({
1911
2125
  mutationFn: async (secretId) => {
1912
2126
  return requestDatalayer({
1913
- url: `${configuration.iamRunUrl}/api/iam/v1/secrets/${secretId}`,
2127
+ url: withAccountUidQuery(`${configuration.iamRunUrl}/api/iam/v1/secrets/${secretId}`, principalUid, principalKind),
1914
2128
  method: 'DELETE',
1915
2129
  });
1916
2130
  },
@@ -2167,12 +2381,14 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
2167
2381
  /**
2168
2382
  * Get single datasource by ID
2169
2383
  */
2170
- const useDatasource = (datasourceId) => {
2384
+ const useDatasource = (datasourceId, options) => {
2385
+ const principalUid = options?.principalUid;
2386
+ const principalKind = options?.principalKind;
2171
2387
  return useQuery({
2172
- queryKey: queryKeys.datasources.detail(datasourceId),
2388
+ queryKey: [...queryKeys.datasources.detail(datasourceId), principalUid || 'self', principalKind || ''],
2173
2389
  queryFn: async () => {
2174
2390
  const resp = await requestDatalayer({
2175
- url: `${configuration.iamRunUrl}/api/iam/v1/datasources/${datasourceId}`,
2391
+ url: withAccountUidQuery(`${configuration.iamRunUrl}/api/iam/v1/datasources/${datasourceId}`, principalUid, principalKind),
2176
2392
  method: 'GET',
2177
2393
  });
2178
2394
  if (resp.success && resp.datasource) {
@@ -2187,11 +2403,13 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
2187
2403
  /**
2188
2404
  * Update datasource
2189
2405
  */
2190
- const useUpdateDatasource = () => {
2406
+ const useUpdateDatasource = (options) => {
2407
+ const principalUid = options?.principalUid;
2408
+ const principalKind = options?.principalKind;
2191
2409
  return useMutation({
2192
2410
  mutationFn: async (datasource) => {
2193
2411
  return requestDatalayer({
2194
- url: `${configuration.iamRunUrl}/api/iam/v1/datasources/${datasource.id}`,
2412
+ url: withAccountUidQuery(`${configuration.iamRunUrl}/api/iam/v1/datasources/${datasource.id}`, principalUid, principalKind),
2195
2413
  method: 'PUT',
2196
2414
  body: { ...datasource },
2197
2415
  });
@@ -2213,11 +2431,13 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
2213
2431
  * Get single secret by ID
2214
2432
  */
2215
2433
  const useSecret = (secretId, options) => {
2434
+ const principalUid = options?.principalUid;
2435
+ const principalKind = options?.principalKind;
2216
2436
  return useQuery({
2217
- queryKey: queryKeys.secrets.detail(secretId),
2437
+ queryKey: [...queryKeys.secrets.detail(secretId), principalUid || 'self', principalKind || ''],
2218
2438
  queryFn: async () => {
2219
2439
  const resp = await requestDatalayer({
2220
- url: `${configuration.iamRunUrl}/api/iam/v1/secrets/${secretId}`,
2440
+ url: withAccountUidQuery(`${configuration.iamRunUrl}/api/iam/v1/secrets/${secretId}`, principalUid, principalKind),
2221
2441
  method: 'GET',
2222
2442
  });
2223
2443
  if (resp.success && resp.secret) {
@@ -2235,11 +2455,13 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
2235
2455
  /**
2236
2456
  * Update secret
2237
2457
  */
2238
- const useUpdateSecret = () => {
2458
+ const useUpdateSecret = (options) => {
2459
+ const principalUid = options?.principalUid;
2460
+ const principalKind = options?.principalKind;
2239
2461
  return useMutation({
2240
2462
  mutationFn: async (secret) => {
2241
2463
  return requestDatalayer({
2242
- url: `${configuration.iamRunUrl}/api/iam/v1/secrets/${secret.id}`,
2464
+ url: withAccountUidQuery(`${configuration.iamRunUrl}/api/iam/v1/secrets/${secret.id}`, principalUid, principalKind),
2243
2465
  method: 'PUT',
2244
2466
  body: { ...secret },
2245
2467
  });
@@ -2383,6 +2605,9 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
2383
2605
  }
2384
2606
  return space;
2385
2607
  }
2608
+ if (resp?.code === 'FORBIDDEN') {
2609
+ throw new Error(resp.message || 'Not authorized to access this space');
2610
+ }
2386
2611
  return null;
2387
2612
  },
2388
2613
  ...DEFAULT_QUERY_OPTIONS,
@@ -2441,6 +2666,9 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
2441
2666
  });
2442
2667
  return targetSpace;
2443
2668
  }
2669
+ if (resp?.code === 'FORBIDDEN') {
2670
+ throw new Error(resp.message || 'Not authorized to access this space');
2671
+ }
2444
2672
  return null;
2445
2673
  },
2446
2674
  ...DEFAULT_QUERY_OPTIONS,
@@ -3384,6 +3612,9 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
3384
3612
  if (resp.success && resp.item) {
3385
3613
  return toCell(resp.item);
3386
3614
  }
3615
+ if (resp?.code === 'FORBIDDEN') {
3616
+ throw new Error(resp.message || 'Not authorized to access this cell');
3617
+ }
3387
3618
  return undefined;
3388
3619
  },
3389
3620
  enabled: !!cellId,
@@ -4307,43 +4538,265 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
4307
4538
  },
4308
4539
  });
4309
4540
  };
4310
- // ============================================================================
4311
- // Checkout & Credits
4312
- // ============================================================================
4541
+ const withAccountUidQuery = (url, accountUid, accountKind) => {
4542
+ if (!accountUid) {
4543
+ return url;
4544
+ }
4545
+ const separator = url.includes('?') ? '&' : '?';
4546
+ const parts = [`billable_account_uid=${encodeURIComponent(accountUid)}`];
4547
+ if (accountKind) {
4548
+ parts.push(`billable_account_kind=${encodeURIComponent(accountKind)}`);
4549
+ }
4550
+ return `${url}${separator}${parts.join('&')}`;
4551
+ };
4552
+ const useTopUpPrices = (options) => {
4553
+ return useQuery({
4554
+ queryKey: ['stripe', 'plans', 'prices'],
4555
+ queryFn: async () => {
4556
+ const normalizeTopUpPrices = (raw) => {
4557
+ const prices = raw.map((item) => ({
4558
+ ...item,
4559
+ default: item?.default === true,
4560
+ }));
4561
+ const explicitDefaultIndex = prices.findIndex(item => item.default === true);
4562
+ if (explicitDefaultIndex >= 0) {
4563
+ return prices.map((item, index) => ({
4564
+ ...item,
4565
+ default: index === explicitDefaultIndex,
4566
+ }));
4567
+ }
4568
+ if (prices.length === 0) {
4569
+ return prices;
4570
+ }
4571
+ let fallbackDefaultIndex = 0;
4572
+ let fallbackDefaultAmount = Number(prices[0]?.amount || 0);
4573
+ for (let index = 1; index < prices.length; index += 1) {
4574
+ const amount = Number(prices[index]?.amount || 0);
4575
+ if (amount > fallbackDefaultAmount) {
4576
+ fallbackDefaultAmount = amount;
4577
+ fallbackDefaultIndex = index;
4578
+ }
4579
+ }
4580
+ return prices.map((item, index) => ({
4581
+ ...item,
4582
+ default: index === fallbackDefaultIndex,
4583
+ }));
4584
+ };
4585
+ const resp = await requestDatalayer({
4586
+ url: `${configuration.iamRunUrl}/api/iam/stripe/v1/plans/prices`,
4587
+ method: 'GET',
4588
+ });
4589
+ if (resp?.success === false) {
4590
+ throw new Error(resp?.message || 'Unable to fetch available top-up products.');
4591
+ }
4592
+ if (Array.isArray(resp?.prices)) {
4593
+ return normalizeTopUpPrices(resp.prices);
4594
+ }
4595
+ if (Array.isArray(resp)) {
4596
+ return normalizeTopUpPrices(resp);
4597
+ }
4598
+ if (resp && Object.keys(resp).length === 0) {
4599
+ return [];
4600
+ }
4601
+ throw new Error('Unable to fetch available top-up products.');
4602
+ },
4603
+ ...options,
4604
+ });
4605
+ };
4313
4606
  /**
4314
- * Get Stripe pricing information
4607
+ * Create Stripe top-up payment intent client secret
4315
4608
  */
4316
- const useStripePrices = (options) => {
4609
+ const useCreateTopUpPaymentIntent = (scope) => {
4610
+ return useMutation({
4611
+ mutationFn: async ({ product, }) => {
4612
+ const resp = await requestDatalayer({
4613
+ url: `${configuration.iamRunUrl}/api/iam/stripe/v1/topup/payment-intent`,
4614
+ method: 'POST',
4615
+ body: {
4616
+ price_id: product?.id,
4617
+ ...(scope?.accountUid ? { account_uid: scope.accountUid } : {}),
4618
+ },
4619
+ });
4620
+ return resp.client_secret;
4621
+ },
4622
+ });
4623
+ };
4624
+ /**
4625
+ * Get available monthly subscription plans.
4626
+ */
4627
+ const useSubscriptionPlans = (scope, options) => {
4317
4628
  return useQuery({
4318
- queryKey: ['stripe', 'prices'],
4629
+ queryKey: ['subscription', 'plans', scope?.accountUid ?? 'self'],
4319
4630
  queryFn: async () => {
4320
4631
  const resp = await requestDatalayer({
4321
- url: `${configuration.iamRunUrl}/api/iam/stripe/v1/prices`,
4632
+ url: withAccountUidQuery(`${configuration.iamRunUrl}/api/iam/v1/plans/catalog`, scope?.accountUid),
4322
4633
  method: 'GET',
4323
4634
  });
4324
- return resp.prices || [];
4635
+ return resp.plans || [];
4325
4636
  },
4326
4637
  ...options,
4327
4638
  });
4328
4639
  };
4329
4640
  /**
4330
- * Create Stripe checkout session
4641
+ * Create Stripe monthly subscription payment intent client secret.
4331
4642
  */
4332
- const useCreateCheckoutSession = () => {
4643
+ const useCreateSubscriptionPaymentIntent = (scope) => {
4333
4644
  return useMutation({
4334
- mutationFn: async ({ product, location, }) => {
4645
+ mutationFn: async ({ plan, }) => {
4335
4646
  const resp = await requestDatalayer({
4336
- url: `${configuration.iamRunUrl}/api/iam/stripe/v1/checkout/session`,
4647
+ url: `${configuration.iamRunUrl}/api/iam/stripe/v1/subscription/payment-intent`,
4337
4648
  method: 'POST',
4338
4649
  body: {
4339
- price_id: product?.id,
4340
- return_url: `${location.protocol}//${location.host}${location.pathname.split('/').slice(0, -1).join('/')}`,
4650
+ price_id: plan?.id,
4651
+ ...(scope?.accountUid ? { account_uid: scope.accountUid } : {}),
4341
4652
  },
4342
4653
  });
4343
4654
  return resp.client_secret;
4344
4655
  },
4345
4656
  });
4346
4657
  };
4658
+ /**
4659
+ * Create Stripe SetupIntent client secret to update card before resuming.
4660
+ */
4661
+ const useCreateResumeSetupIntent = (scope) => {
4662
+ return useMutation({
4663
+ mutationFn: async () => {
4664
+ const resp = await requestDatalayer({
4665
+ url: `${configuration.iamRunUrl}/api/iam/stripe/v1/subscription/resume/setup-intent`,
4666
+ method: 'POST',
4667
+ body: scope?.accountUid ? { account_uid: scope.accountUid } : {},
4668
+ });
4669
+ return resp.client_secret;
4670
+ },
4671
+ });
4672
+ };
4673
+ /**
4674
+ * Get authenticated user subscription details.
4675
+ */
4676
+ const useSubscriptionStatus = (scope, options) => {
4677
+ return useQuery({
4678
+ queryKey: ['subscription', 'status', scope?.accountUid ?? 'self'],
4679
+ queryFn: async () => {
4680
+ return requestDatalayer({
4681
+ url: withAccountUidQuery(`${configuration.iamRunUrl}/api/iam/v1/plans`, scope?.accountUid),
4682
+ method: 'GET',
4683
+ });
4684
+ },
4685
+ ...options,
4686
+ });
4687
+ };
4688
+ /**
4689
+ * Get accounts (personal + organizations) eligible for subscription-backed workloads.
4690
+ */
4691
+ const useEligibleSubscriptionAccounts = (options) => {
4692
+ return useQuery({
4693
+ queryKey: ['subscription', 'eligible-accounts'],
4694
+ queryFn: async () => {
4695
+ const resp = await requestDatalayer({
4696
+ url: `${configuration.iamRunUrl}/api/iam/v1/plans/eligible-accounts`,
4697
+ method: 'GET',
4698
+ });
4699
+ return resp.accounts || [];
4700
+ },
4701
+ ...options,
4702
+ });
4703
+ };
4704
+ /**
4705
+ * Get subscription details + eligibility for a batch of account UIDs.
4706
+ */
4707
+ const useSubscriptionAccountsDetails = (accountUids, options) => {
4708
+ const normalizedAccountUids = Array.from(new Set((accountUids || [])
4709
+ .map(uid => String(uid || '').trim())
4710
+ .filter(Boolean)));
4711
+ return useQuery({
4712
+ queryKey: ['subscription', 'accounts-details', normalizedAccountUids],
4713
+ queryFn: async () => {
4714
+ const resp = await requestDatalayer({
4715
+ url: `${configuration.iamRunUrl}/api/iam/v1/plans/accounts/details`,
4716
+ method: 'POST',
4717
+ body: {
4718
+ account_uids: normalizedAccountUids,
4719
+ },
4720
+ });
4721
+ return resp.accounts || [];
4722
+ },
4723
+ enabled: normalizedAccountUids.length > 0,
4724
+ ...options,
4725
+ });
4726
+ };
4727
+ /**
4728
+ * Request cancellation portal for the current subscription.
4729
+ */
4730
+ const useCancelSubscription = (scope) => {
4731
+ const queryClient = useQueryClient();
4732
+ return useMutation({
4733
+ mutationFn: async () => {
4734
+ return requestDatalayer({
4735
+ url: withAccountUidQuery(`${configuration.iamRunUrl}/api/iam/v1/plans/cancel`, scope?.accountUid),
4736
+ method: 'POST',
4737
+ });
4738
+ },
4739
+ onSuccess: () => {
4740
+ queryClient.invalidateQueries({
4741
+ queryKey: ['subscription', 'status', scope?.accountUid ?? 'self'],
4742
+ });
4743
+ },
4744
+ });
4745
+ };
4746
+ /**
4747
+ * Resume a subscription already scheduled for cancellation.
4748
+ */
4749
+ const useResumeSubscription = (scope) => {
4750
+ const queryClient = useQueryClient();
4751
+ return useMutation({
4752
+ mutationFn: async () => {
4753
+ return requestDatalayer({
4754
+ url: withAccountUidQuery(`${configuration.iamRunUrl}/api/iam/v1/plans/resume`, scope?.accountUid),
4755
+ method: 'POST',
4756
+ });
4757
+ },
4758
+ onSuccess: () => {
4759
+ queryClient.invalidateQueries({
4760
+ queryKey: ['subscription', 'status', scope?.accountUid ?? 'self'],
4761
+ });
4762
+ },
4763
+ });
4764
+ };
4765
+ /**
4766
+ * Get subscription details for an arbitrary user (platform_admin only).
4767
+ */
4768
+ const useUserSubscription = (userId, options) => {
4769
+ return useQuery({
4770
+ queryKey: ['subscription', 'admin', userId],
4771
+ queryFn: async () => {
4772
+ return requestDatalayer({
4773
+ url: `${configuration.iamRunUrl}/api/iam/v1/plans/admin/${userId}`,
4774
+ method: 'GET',
4775
+ });
4776
+ },
4777
+ enabled: Boolean(userId),
4778
+ ...options,
4779
+ });
4780
+ };
4781
+ /**
4782
+ * Force-reset a user's subscription (platform_admin only).
4783
+ */
4784
+ const useResetUserSubscription = () => {
4785
+ const queryClient = useQueryClient();
4786
+ return useMutation({
4787
+ mutationFn: async (userId) => {
4788
+ return requestDatalayer({
4789
+ url: `${configuration.iamRunUrl}/api/iam/v1/plans/admin/${userId}/reset`,
4790
+ method: 'POST',
4791
+ });
4792
+ },
4793
+ onSuccess: (_data, userId) => {
4794
+ queryClient.invalidateQueries({
4795
+ queryKey: ['subscription', 'admin', userId],
4796
+ });
4797
+ },
4798
+ });
4799
+ };
4347
4800
  /**
4348
4801
  * Burn user credits (deduct from balance)
4349
4802
  */
@@ -4733,6 +5186,174 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
4733
5186
  // ============================================================================
4734
5187
  // Credits Quota & Usage
4735
5188
  // ============================================================================
5189
+ /**
5190
+ * Get organization/team balances for allocation workflows
5191
+ */
5192
+ const useOrganizationAllocationOverview = (organizationUid) => {
5193
+ return useQuery({
5194
+ queryKey: queryKeys.credits.organizationOverview(organizationUid),
5195
+ queryFn: async () => {
5196
+ const resp = await requestDatalayer({
5197
+ url: `${configuration.iamRunUrl}/api/iam/v1/usage/credits/allocations/organizations/${organizationUid}/overview`,
5198
+ method: 'GET',
5199
+ });
5200
+ return resp.overview ?? { organization: null, teams: [] };
5201
+ },
5202
+ ...DEFAULT_QUERY_OPTIONS,
5203
+ enabled: !!organizationUid,
5204
+ });
5205
+ };
5206
+ /**
5207
+ * Get team/member balances for allocation workflows
5208
+ */
5209
+ const useTeamAllocationOverview = (teamUid) => {
5210
+ return useQuery({
5211
+ queryKey: queryKeys.credits.teamOverview(teamUid),
5212
+ queryFn: async () => {
5213
+ const resp = await requestDatalayer({
5214
+ url: `${configuration.iamRunUrl}/api/iam/v1/usage/credits/allocations/teams/${teamUid}/overview`,
5215
+ method: 'GET',
5216
+ });
5217
+ return resp.overview ?? { team: null, members: [] };
5218
+ },
5219
+ ...DEFAULT_QUERY_OPTIONS,
5220
+ enabled: !!teamUid,
5221
+ });
5222
+ };
5223
+ /**
5224
+ * Get organization allocation history
5225
+ */
5226
+ const useOrganizationAllocationHistory = (organizationUid) => {
5227
+ return useQuery({
5228
+ queryKey: queryKeys.credits.organizationHistory(organizationUid),
5229
+ queryFn: async () => {
5230
+ const resp = await requestDatalayer({
5231
+ url: `${configuration.iamRunUrl}/api/iam/v1/usage/credits/allocations/organizations/${organizationUid}/history`,
5232
+ method: 'GET',
5233
+ });
5234
+ return (resp.history ?? { organization_uid: organizationUid, events: [] });
5235
+ },
5236
+ ...DEFAULT_QUERY_OPTIONS,
5237
+ enabled: !!organizationUid,
5238
+ });
5239
+ };
5240
+ /**
5241
+ * Get team allocation history
5242
+ */
5243
+ const useTeamAllocationHistory = (teamUid) => {
5244
+ return useQuery({
5245
+ queryKey: queryKeys.credits.teamHistory(teamUid),
5246
+ queryFn: async () => {
5247
+ const resp = await requestDatalayer({
5248
+ url: `${configuration.iamRunUrl}/api/iam/v1/usage/credits/allocations/teams/${teamUid}/history`,
5249
+ method: 'GET',
5250
+ });
5251
+ return resp.history ?? { team_uid: teamUid, events: [] };
5252
+ },
5253
+ ...DEFAULT_QUERY_OPTIONS,
5254
+ enabled: !!teamUid,
5255
+ });
5256
+ };
5257
+ /**
5258
+ * Allocate credits from organization to team
5259
+ */
5260
+ const useAllocateOrganizationCreditsToTeam = () => {
5261
+ return useMutation({
5262
+ mutationFn: async ({ organizationUid, teamUid, amount, }) => {
5263
+ return requestDatalayer({
5264
+ url: `${configuration.iamRunUrl}/api/iam/v1/usage/credits/allocations/organizations/${organizationUid}/teams/${teamUid}`,
5265
+ method: 'POST',
5266
+ body: { amount },
5267
+ });
5268
+ },
5269
+ onSuccess: (_, { organizationUid, teamUid }) => {
5270
+ queryClient.invalidateQueries({
5271
+ queryKey: queryKeys.credits.organizationOverview(organizationUid),
5272
+ });
5273
+ queryClient.invalidateQueries({
5274
+ queryKey: queryKeys.credits.organizationHistory(organizationUid),
5275
+ });
5276
+ queryClient.invalidateQueries({
5277
+ queryKey: queryKeys.credits.teamOverview(teamUid),
5278
+ });
5279
+ queryClient.invalidateQueries({
5280
+ queryKey: queryKeys.credits.teamHistory(teamUid),
5281
+ });
5282
+ },
5283
+ });
5284
+ };
5285
+ /**
5286
+ * Allocate credits from team to member
5287
+ */
5288
+ const useAllocateTeamCreditsToMember = () => {
5289
+ return useMutation({
5290
+ mutationFn: async ({ teamUid, memberUid, amount, }) => {
5291
+ return requestDatalayer({
5292
+ url: `${configuration.iamRunUrl}/api/iam/v1/usage/credits/allocations/teams/${teamUid}/members/${memberUid}`,
5293
+ method: 'POST',
5294
+ body: { amount },
5295
+ });
5296
+ },
5297
+ onSuccess: (_, { teamUid }) => {
5298
+ queryClient.invalidateQueries({
5299
+ queryKey: queryKeys.credits.teamOverview(teamUid),
5300
+ });
5301
+ queryClient.invalidateQueries({
5302
+ queryKey: queryKeys.credits.teamHistory(teamUid),
5303
+ });
5304
+ },
5305
+ });
5306
+ };
5307
+ /**
5308
+ * Revoke credits from team back to organization
5309
+ */
5310
+ const useRevokeOrganizationCreditsFromTeam = () => {
5311
+ return useMutation({
5312
+ mutationFn: async ({ organizationUid, teamUid, amount, }) => {
5313
+ return requestDatalayer({
5314
+ url: `${configuration.iamRunUrl}/api/iam/v1/usage/credits/allocations/organizations/${organizationUid}/teams/${teamUid}/revoke`,
5315
+ method: 'POST',
5316
+ body: { amount },
5317
+ });
5318
+ },
5319
+ onSuccess: (_, { organizationUid, teamUid }) => {
5320
+ queryClient.invalidateQueries({
5321
+ queryKey: queryKeys.credits.organizationOverview(organizationUid),
5322
+ });
5323
+ queryClient.invalidateQueries({
5324
+ queryKey: queryKeys.credits.organizationHistory(organizationUid),
5325
+ });
5326
+ queryClient.invalidateQueries({
5327
+ queryKey: queryKeys.credits.teamOverview(teamUid),
5328
+ });
5329
+ queryClient.invalidateQueries({
5330
+ queryKey: queryKeys.credits.teamHistory(teamUid),
5331
+ });
5332
+ },
5333
+ });
5334
+ };
5335
+ /**
5336
+ * Revoke credits from member back to team
5337
+ */
5338
+ const useRevokeTeamCreditsFromMember = () => {
5339
+ return useMutation({
5340
+ mutationFn: async ({ teamUid, memberUid, amount, }) => {
5341
+ return requestDatalayer({
5342
+ url: `${configuration.iamRunUrl}/api/iam/v1/usage/credits/allocations/teams/${teamUid}/members/${memberUid}/revoke`,
5343
+ method: 'POST',
5344
+ body: { amount },
5345
+ });
5346
+ },
5347
+ onSuccess: (_, { teamUid }) => {
5348
+ queryClient.invalidateQueries({
5349
+ queryKey: queryKeys.credits.teamOverview(teamUid),
5350
+ });
5351
+ queryClient.invalidateQueries({
5352
+ queryKey: queryKeys.credits.teamHistory(teamUid),
5353
+ });
5354
+ },
5355
+ });
5356
+ };
4736
5357
  /**
4737
5358
  * Update user credits quota
4738
5359
  */
@@ -4760,33 +5381,77 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
4760
5381
  /**
4761
5382
  * Get current user's usage data
4762
5383
  */
4763
- const useUsages = (options) => {
5384
+ const useUsages = (scopeOrOptions, options) => {
5385
+ const hasScope = !!scopeOrOptions &&
5386
+ typeof scopeOrOptions === 'object' &&
5387
+ 'accountUid' in scopeOrOptions;
5388
+ const scope = (hasScope ? scopeOrOptions : undefined);
5389
+ const queryOptions = (hasScope ? options : scopeOrOptions);
4764
5390
  return useQuery({
4765
- queryKey: ['usage', 'me'],
5391
+ queryKey: ['usage', 'me', scope?.accountUid ?? 'self'],
4766
5392
  queryFn: async () => {
5393
+ const parseUsageDate = (value) => {
5394
+ if (!value) {
5395
+ return undefined;
5396
+ }
5397
+ const date = value instanceof Date ? value : new Date(value);
5398
+ return Number.isNaN(date.getTime()) ? undefined : date;
5399
+ };
5400
+ const asUsage = (u) => {
5401
+ const metadata = new Map(Object.entries(u.metadata || {}));
5402
+ const usagePeriodKey = u.usage_period_key;
5403
+ const usagePeriodLabel = u.usage_period_label;
5404
+ const usagePeriodStart = u.usage_period_start;
5405
+ const usagePeriodEnd = u.usage_period_end;
5406
+ if (usagePeriodKey) {
5407
+ metadata.set('usage_period_key', String(usagePeriodKey));
5408
+ }
5409
+ if (usagePeriodLabel) {
5410
+ metadata.set('usage_period_label', String(usagePeriodLabel));
5411
+ }
5412
+ if (usagePeriodStart) {
5413
+ metadata.set('usage_period_start', String(usagePeriodStart));
5414
+ }
5415
+ if (usagePeriodEnd) {
5416
+ metadata.set('usage_period_end', String(usagePeriodEnd));
5417
+ }
5418
+ const endDate = parseUsageDate(u.end_date);
5419
+ const updatedAt = parseUsageDate(u.updated_at) || endDate || new Date();
5420
+ const rawStartDate = parseUsageDate(u.start_date);
5421
+ let startDate = rawStartDate || endDate || updatedAt;
5422
+ if (endDate && startDate && startDate.getTime() > endDate.getTime()) {
5423
+ startDate =
5424
+ updatedAt.getTime() <= endDate.getTime() ? updatedAt : endDate;
5425
+ }
5426
+ return {
5427
+ id: u.resource_uid,
5428
+ accountId: u.account_uid,
5429
+ type: u.resource_type,
5430
+ burningRate: u.burning_rate,
5431
+ credits: u.credits,
5432
+ creditsLimit: u.credits_limit,
5433
+ startDate,
5434
+ updatedAt,
5435
+ endDate,
5436
+ givenName: u.given_name || u.resource_given_name || '',
5437
+ resourceState: u.resource_state,
5438
+ resources: u.pod_resources,
5439
+ usagePeriodKey,
5440
+ usagePeriodLabel,
5441
+ usagePeriodStart: parseUsageDate(usagePeriodStart),
5442
+ usagePeriodEnd: parseUsageDate(usagePeriodEnd),
5443
+ metadata,
5444
+ };
5445
+ };
4767
5446
  const resp = await requestDatalayer({
4768
- url: `${configuration.iamRunUrl}/api/iam/v1/usage/user`,
5447
+ url: withAccountUidQuery(`${configuration.iamRunUrl}/api/iam/v1/usage/user`, scope?.accountUid),
4769
5448
  method: 'GET',
4770
5449
  });
4771
5450
  // Transform snake_case API response to camelCase IUsage interface
4772
- const usages = (resp.usages || []).map((u) => ({
4773
- id: u.resource_uid,
4774
- accountId: u.account_uid,
4775
- type: u.resource_type,
4776
- burningRate: u.burning_rate,
4777
- credits: u.credits,
4778
- creditsLimit: u.credits_limit,
4779
- startDate: u.start_date ? new Date(u.start_date) : new Date(),
4780
- updatedAt: u.updated_at ? new Date(u.updated_at) : new Date(),
4781
- endDate: u.end_date ? new Date(u.end_date) : undefined,
4782
- givenName: u.given_name || u.resource_given_name || '',
4783
- resourceState: u.resource_state,
4784
- resources: u.pod_resources,
4785
- metadata: new Map(Object.entries(u.metadata || {})),
4786
- }));
5451
+ const usages = (resp.usages || []).map(asUsage);
4787
5452
  return usages;
4788
5453
  },
4789
- ...options,
5454
+ ...queryOptions,
4790
5455
  });
4791
5456
  };
4792
5457
  /**
@@ -4796,26 +5461,61 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
4796
5461
  return useQuery({
4797
5462
  queryKey: ['usage', 'user', userId],
4798
5463
  queryFn: async () => {
5464
+ const parseUsageDate = (value) => {
5465
+ if (!value) {
5466
+ return undefined;
5467
+ }
5468
+ const date = value instanceof Date ? value : new Date(value);
5469
+ return Number.isNaN(date.getTime()) ? undefined : date;
5470
+ };
5471
+ const asUsage = (u) => {
5472
+ const metadata = new Map(Object.entries(u.metadata || {}));
5473
+ if (u.usage_period_key) {
5474
+ metadata.set('usage_period_key', String(u.usage_period_key));
5475
+ }
5476
+ if (u.usage_period_label) {
5477
+ metadata.set('usage_period_label', String(u.usage_period_label));
5478
+ }
5479
+ if (u.usage_period_start) {
5480
+ metadata.set('usage_period_start', String(u.usage_period_start));
5481
+ }
5482
+ if (u.usage_period_end) {
5483
+ metadata.set('usage_period_end', String(u.usage_period_end));
5484
+ }
5485
+ const endDate = parseUsageDate(u.end_date);
5486
+ const updatedAt = parseUsageDate(u.updated_at) || endDate || new Date();
5487
+ const rawStartDate = parseUsageDate(u.start_date);
5488
+ let startDate = rawStartDate || endDate || updatedAt;
5489
+ if (endDate && startDate && startDate.getTime() > endDate.getTime()) {
5490
+ startDate =
5491
+ updatedAt.getTime() <= endDate.getTime() ? updatedAt : endDate;
5492
+ }
5493
+ return {
5494
+ id: u.resource_uid,
5495
+ accountId: u.account_uid,
5496
+ type: u.resource_type,
5497
+ burningRate: u.burning_rate,
5498
+ credits: u.credits,
5499
+ creditsLimit: u.credits_limit,
5500
+ startDate,
5501
+ updatedAt,
5502
+ endDate,
5503
+ givenName: u.given_name || u.resource_given_name || '',
5504
+ resourceState: u.resource_state,
5505
+ resources: u.pod_resources,
5506
+ usagePeriodKey: u.usage_period_key,
5507
+ usagePeriodLabel: u.usage_period_label,
5508
+ usagePeriodStart: parseUsageDate(u.usage_period_start),
5509
+ usagePeriodEnd: parseUsageDate(u.usage_period_end),
5510
+ metadata,
5511
+ };
5512
+ };
4799
5513
  const resp = await requestDatalayer({
4800
5514
  url: `${configuration.iamRunUrl}/api/iam/v1/usage/users/${userId}`,
4801
5515
  method: 'GET',
4802
5516
  });
4803
5517
  // Transform snake_case API response to camelCase IUsage interface
4804
- const usages = (resp.usages || []).map((u) => ({
4805
- id: u.resource_uid,
4806
- accountId: u.account_uid,
4807
- type: u.resource_type,
4808
- burningRate: u.burning_rate,
4809
- credits: u.credits,
4810
- creditsLimit: u.credits_limit,
4811
- startDate: u.start_date ? new Date(u.start_date) : new Date(),
4812
- updatedAt: u.updated_at ? new Date(u.updated_at) : new Date(),
4813
- endDate: u.end_date ? new Date(u.end_date) : undefined,
4814
- givenName: u.given_name || u.resource_given_name || '',
4815
- resourceState: u.resource_state,
4816
- resources: u.pod_resources,
4817
- metadata: new Map(Object.entries(u.metadata || {})),
4818
- }));
5518
+ const usages = (resp.usages || []).map(asUsage);
4819
5519
  return usages;
4820
5520
  },
4821
5521
  enabled: !!userId,
@@ -4829,26 +5529,61 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
4829
5529
  return useQuery({
4830
5530
  queryKey: ['usage', 'platform'],
4831
5531
  queryFn: async () => {
5532
+ const parseUsageDate = (value) => {
5533
+ if (!value) {
5534
+ return undefined;
5535
+ }
5536
+ const date = value instanceof Date ? value : new Date(value);
5537
+ return Number.isNaN(date.getTime()) ? undefined : date;
5538
+ };
5539
+ const asUsage = (u) => {
5540
+ const metadata = new Map(Object.entries(u.metadata || {}));
5541
+ if (u.usage_period_key) {
5542
+ metadata.set('usage_period_key', String(u.usage_period_key));
5543
+ }
5544
+ if (u.usage_period_label) {
5545
+ metadata.set('usage_period_label', String(u.usage_period_label));
5546
+ }
5547
+ if (u.usage_period_start) {
5548
+ metadata.set('usage_period_start', String(u.usage_period_start));
5549
+ }
5550
+ if (u.usage_period_end) {
5551
+ metadata.set('usage_period_end', String(u.usage_period_end));
5552
+ }
5553
+ const endDate = parseUsageDate(u.end_date);
5554
+ const updatedAt = parseUsageDate(u.updated_at) || endDate || new Date();
5555
+ const rawStartDate = parseUsageDate(u.start_date);
5556
+ let startDate = rawStartDate || endDate || updatedAt;
5557
+ if (endDate && startDate && startDate.getTime() > endDate.getTime()) {
5558
+ startDate =
5559
+ updatedAt.getTime() <= endDate.getTime() ? updatedAt : endDate;
5560
+ }
5561
+ return {
5562
+ id: u.resource_uid,
5563
+ accountId: u.account_uid,
5564
+ type: u.resource_type,
5565
+ burningRate: u.burning_rate,
5566
+ credits: u.credits,
5567
+ creditsLimit: u.credits_limit,
5568
+ startDate,
5569
+ updatedAt,
5570
+ endDate,
5571
+ givenName: u.given_name || u.resource_given_name || '',
5572
+ resourceState: u.resource_state,
5573
+ resources: u.pod_resources,
5574
+ usagePeriodKey: u.usage_period_key,
5575
+ usagePeriodLabel: u.usage_period_label,
5576
+ usagePeriodStart: parseUsageDate(u.usage_period_start),
5577
+ usagePeriodEnd: parseUsageDate(u.usage_period_end),
5578
+ metadata,
5579
+ };
5580
+ };
4832
5581
  const resp = await requestDatalayer({
4833
5582
  url: `${configuration.iamRunUrl}/api/iam/v1/usage/platform`,
4834
5583
  method: 'GET',
4835
5584
  });
4836
5585
  // Transform snake_case API response to camelCase IUsage interface
4837
- const usages = (resp.usages || []).map((u) => ({
4838
- id: u.resource_uid,
4839
- accountId: u.account_uid,
4840
- type: u.resource_type,
4841
- burningRate: u.burning_rate,
4842
- credits: u.credits,
4843
- creditsLimit: u.credits_limit,
4844
- startDate: u.start_date ? new Date(u.start_date) : new Date(),
4845
- updatedAt: u.updated_at ? new Date(u.updated_at) : new Date(),
4846
- endDate: u.end_date ? new Date(u.end_date) : undefined,
4847
- givenName: u.given_name || u.resource_given_name || '',
4848
- resourceState: u.resource_state,
4849
- resources: u.pod_resources,
4850
- metadata: new Map(Object.entries(u.metadata || {})),
4851
- }));
5586
+ const usages = (resp.usages || []).map(asUsage);
4852
5587
  return usages;
4853
5588
  },
4854
5589
  ...options,
@@ -5169,6 +5904,22 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
5169
5904
  ...options,
5170
5905
  });
5171
5906
  };
5907
+ /**
5908
+ * Get growth contacts KPIs.
5909
+ */
5910
+ const useGrowthContactsKPI = (options) => {
5911
+ return useQuery({
5912
+ queryKey: ['growth', 'contacts-kpi'],
5913
+ queryFn: async () => {
5914
+ const resp = await requestDatalayer({
5915
+ url: `${configuration.growthRunUrl}/api/growth/v1/kpis/contacts`,
5916
+ method: 'GET',
5917
+ });
5918
+ return resp;
5919
+ },
5920
+ ...options,
5921
+ });
5922
+ };
5172
5923
  // ============================================================================
5173
5924
  // Refresh Operations & Additional Methods
5174
5925
  // ============================================================================
@@ -6088,7 +6839,9 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
6088
6839
  // Users
6089
6840
  useUser,
6090
6841
  useUserByHandle,
6842
+ useUserPublicProfileByHandle,
6091
6843
  useSearchUsers,
6844
+ useUsersByUids,
6092
6845
  useUpdateUserOnboarding,
6093
6846
  useUpdateUserSettings,
6094
6847
  useRefreshUser,
@@ -6096,6 +6849,14 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
6096
6849
  useUpdateUserCredits,
6097
6850
  useAssignRoleToUser,
6098
6851
  useUnassignRoleFromUser,
6852
+ useOrganizationAllocationOverview,
6853
+ useOrganizationAllocationHistory,
6854
+ useTeamAllocationOverview,
6855
+ useTeamAllocationHistory,
6856
+ useAllocateOrganizationCreditsToTeam,
6857
+ useAllocateTeamCreditsToMember,
6858
+ useRevokeOrganizationCreditsFromTeam,
6859
+ useRevokeTeamCreditsFromMember,
6099
6860
  useUpdateUserCreditsQuota,
6100
6861
  useUsages,
6101
6862
  useUsagesForUser,
@@ -6103,6 +6864,7 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
6103
6864
  // Organizations
6104
6865
  useOrganization,
6105
6866
  useOrganizationByHandle,
6867
+ useOrganizationPublicProfileByHandle,
6106
6868
  useUserOrganizations,
6107
6869
  useCreateOrganization,
6108
6870
  useUpdateOrganization,
@@ -6312,14 +7074,25 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
6312
7074
  useDisableUserMFA,
6313
7075
  useValidateUserMFACode,
6314
7076
  // Checkout & Credits
6315
- useCreateCheckoutSession,
7077
+ useCreateTopUpPaymentIntent,
7078
+ useCreateSubscriptionPaymentIntent,
7079
+ useCreateResumeSetupIntent,
7080
+ useSubscriptionStatus,
7081
+ useSubscriptionPlans,
7082
+ useEligibleSubscriptionAccounts,
7083
+ useSubscriptionAccountsDetails,
7084
+ useCancelSubscription,
7085
+ useResumeSubscription,
7086
+ useUserSubscription,
7087
+ useResetUserSubscription,
6316
7088
  useBurnCredit,
6317
- useStripePrices,
7089
+ useTopUpPrices,
6318
7090
  // Support & Growth
6319
7091
  useRequestPlatformSupport,
6320
7092
  useRequestPlatformSupport2,
6321
7093
  useUserSurveys,
6322
7094
  useGrowthKPI,
7095
+ useGrowthContactsKPI,
6323
7096
  // Query keys for manual operations
6324
7097
  queryKeys,
6325
7098
  // Upload