@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.
- package/README.md +27 -0
- package/lib/api/DatalayerApi.d.ts +5 -1
- package/lib/api/DatalayerApi.js +55 -8
- package/lib/api/iam/oauth2.d.ts +3 -3
- package/lib/api/iam/oauth2.js +2 -2
- package/lib/api/iam/profile.d.ts +9 -1
- package/lib/api/iam/profile.js +15 -0
- package/lib/api/runtimes/checkpoints.js +1 -1
- package/lib/api/runtimes/snapshots.d.ts +7 -7
- package/lib/api/runtimes/snapshots.js +9 -9
- package/lib/client/auth/storage.js +103 -12
- package/lib/client/index.d.ts +7 -7
- package/lib/client/index.js +1 -1
- package/lib/client/mixins/RuntimesMixin.d.ts +6 -6
- package/lib/client/mixins/RuntimesMixin.js +5 -5
- package/lib/client/mixins/SpacerMixin.js +22 -1
- package/lib/collaboration/DatalayerCollaborationProvider.d.ts +4 -3
- package/lib/components/avatars/DLAvatar.d.ts +24 -0
- package/lib/components/avatars/DLAvatar.js +26 -0
- package/lib/components/avatars/UserProfileAvatar.js +4 -4
- package/lib/components/avatars/index.d.ts +1 -0
- package/lib/components/avatars/index.js +1 -0
- package/lib/components/billing/BillableAccountSelect.d.ts +35 -0
- package/lib/components/billing/BillableAccountSelect.js +340 -0
- package/lib/components/billing/index.d.ts +1 -0
- package/lib/components/billing/index.js +5 -0
- package/lib/components/checkout/StripeCheckout.d.ts +24 -3
- package/lib/components/checkout/StripeCheckout.js +1116 -55
- package/lib/components/display/DatalayerBox.js +6 -1
- package/lib/components/index.d.ts +2 -0
- package/lib/components/index.js +2 -0
- package/lib/components/principal/Principal.d.ts +37 -0
- package/lib/components/principal/Principal.js +54 -0
- package/lib/components/principal/PrincipalAvatar.d.ts +10 -0
- package/lib/components/principal/PrincipalAvatar.js +55 -0
- package/lib/components/principal/PrincipalBadge.d.ts +20 -0
- package/lib/components/principal/PrincipalBadge.js +208 -0
- package/lib/components/principal/PrincipalBanner.d.ts +7 -0
- package/lib/components/principal/PrincipalBanner.js +84 -0
- package/lib/components/principal/PrincipalDetailsOverlay.d.ts +23 -0
- package/lib/components/principal/PrincipalDetailsOverlay.js +86 -0
- package/lib/components/principal/PrincipalSwitcherMenu.d.ts +14 -0
- package/lib/components/principal/PrincipalSwitcherMenu.js +287 -0
- package/lib/components/principal/index.d.ts +7 -0
- package/lib/components/principal/index.js +10 -0
- package/lib/components/runtimes/RuntimeLauncherDialog.d.ts +2 -2
- package/lib/components/sharing/ShareAccessComponent.d.ts +18 -0
- package/lib/components/sharing/ShareAccessComponent.js +1368 -0
- package/lib/components/sharing/SharingEditor.d.ts +20 -0
- package/lib/components/sharing/SharingEditor.js +65 -0
- package/lib/components/sharing/index.d.ts +9 -0
- package/lib/components/sharing/index.js +12 -0
- package/lib/components/snapshots/{RuntimeSnapshotMenu.d.ts → SandboxSnapshotMenu.d.ts} +4 -4
- package/lib/components/snapshots/{RuntimeSnapshotMenu.js → SandboxSnapshotMenu.js} +33 -33
- package/lib/components/snapshots/index.d.ts +1 -1
- package/lib/components/snapshots/index.js +1 -1
- package/lib/components/storage/ContentsBrowser.js +2 -2
- package/lib/components/tokens/SpaceVariantToken.d.ts +1 -1
- package/lib/components/tokens/SpaceVariantToken.js +7 -4
- package/lib/components/users/PeerIndicator.js +5 -4
- package/lib/hooks/index.d.ts +5 -0
- package/lib/hooks/index.js +5 -0
- package/lib/hooks/useBillableAccountStore.d.ts +31 -0
- package/lib/hooks/useBillableAccountStore.js +53 -0
- package/lib/hooks/useCache.d.ts +125 -18
- package/lib/hooks/useCache.js +893 -120
- package/lib/hooks/useDatalayer.js +31 -3
- package/lib/hooks/useIAM.js +1 -8
- package/lib/hooks/usePrincipalStore.d.ts +41 -0
- package/lib/hooks/usePrincipalStore.js +101 -0
- package/lib/hooks/useProjects.d.ts +44 -0
- package/lib/hooks/useProjects.js +26 -2
- package/lib/hooks/useSelectedBillableAccount.d.ts +14 -0
- package/lib/hooks/useSelectedBillableAccount.js +36 -0
- package/lib/hooks/useSelectedPrincipal.d.ts +17 -0
- package/lib/hooks/useSelectedPrincipal.js +29 -0
- package/lib/hooks/useUsageRefreshStore.d.ts +8 -0
- package/lib/hooks/useUsageRefreshStore.js +16 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +1 -1
- package/lib/mocks/components/FlashMock.js +8 -2
- package/lib/models/{RuntimeSnapshot.d.ts → CodeSandboxSnapshot.d.ts} +5 -5
- package/lib/models/{RuntimeSnapshot.js → CodeSandboxSnapshot.js} +1 -1
- package/lib/models/{RuntimeSnapshotDTO.d.ts → CodeSandboxSnapshotDTO.d.ts} +30 -30
- package/lib/models/{RuntimeSnapshotDTO.js → CodeSandboxSnapshotDTO.js} +6 -6
- package/lib/models/Environment.d.ts +4 -0
- package/lib/models/IAM.d.ts +66 -0
- package/lib/models/Organization.d.ts +1 -0
- package/lib/models/Organization.js +1 -0
- package/lib/models/Page.d.ts +2 -2
- package/lib/models/Page.js +2 -2
- package/lib/models/Profile.d.ts +1 -1
- package/lib/models/RuntimeDTO.d.ts +2 -2
- package/lib/models/Space.d.ts +1 -0
- package/lib/models/Space.js +29 -5
- package/lib/models/SpaceDTO.d.ts +4 -0
- package/lib/models/SpaceDTO.js +5 -0
- package/lib/models/Usage.d.ts +15 -0
- package/lib/models/Usage.js +9 -0
- package/lib/models/User.d.ts +21 -0
- package/lib/models/User.js +6 -2
- package/lib/models/index.d.ts +2 -2
- package/lib/models/index.js +2 -2
- package/lib/otel/auth.js +0 -4
- package/lib/services/index.d.ts +1 -1
- package/lib/services/index.js +1 -1
- package/lib/state/substates/IAMState.js +0 -7
- package/lib/state/substates/RuntimesState.d.ts +9 -9
- package/lib/state/substates/RuntimesState.js +3 -3
- package/lib/stateful/runtimes/actions.d.ts +17 -17
- package/lib/stateful/runtimes/actions.js +35 -55
- package/lib/stateful/runtimes/apis.d.ts +3 -3
- package/lib/stateful/runtimes/snapshots.d.ts +2 -2
- package/lib/stateful/runtimes/snapshots.js +7 -7
- package/lib/utils/Format.d.ts +6 -0
- package/lib/utils/Format.js +17 -0
- package/lib/utils/Handles.d.ts +3 -0
- package/lib/utils/Handles.js +53 -0
- package/lib/utils/Snapshot.d.ts +1 -1
- package/lib/utils/Snapshot.js +1 -1
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +1 -0
- package/lib/views/datasources/DatasourceDetail.d.ts +7 -1
- package/lib/views/datasources/DatasourceDetail.js +61 -23
- package/lib/views/datasources/DatasourceNew.d.ts +8 -1
- package/lib/views/datasources/DatasourceNew.js +23 -10
- package/lib/views/datasources/Datasources.d.ts +5 -1
- package/lib/views/datasources/Datasources.js +38 -30
- package/lib/views/iam-tokens/IAMTokenNew.d.ts +3 -1
- package/lib/views/iam-tokens/IAMTokenNew.js +2 -2
- package/lib/views/iam-tokens/IAMTokens.d.ts +3 -1
- package/lib/views/iam-tokens/IAMTokens.js +2 -2
- package/lib/views/otel/OtelHeader.d.ts +5 -0
- package/lib/views/otel/OtelHeader.js +3 -3
- package/lib/views/profile/UserBadge.d.ts +7 -2
- package/lib/views/profile/UserBadge.js +11 -2
- package/lib/views/secrets/Secrets.js +21 -21
- package/package.json +7 -8
package/lib/hooks/useCache.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
*
|
|
4607
|
+
* Create Stripe top-up payment intent client secret
|
|
4315
4608
|
*/
|
|
4316
|
-
const
|
|
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: ['
|
|
4629
|
+
queryKey: ['subscription', 'plans', scope?.accountUid ?? 'self'],
|
|
4319
4630
|
queryFn: async () => {
|
|
4320
4631
|
const resp = await requestDatalayer({
|
|
4321
|
-
url: `${configuration.iamRunUrl}/api/iam/
|
|
4632
|
+
url: withAccountUidQuery(`${configuration.iamRunUrl}/api/iam/v1/plans/catalog`, scope?.accountUid),
|
|
4322
4633
|
method: 'GET',
|
|
4323
4634
|
});
|
|
4324
|
-
return resp.
|
|
4635
|
+
return resp.plans || [];
|
|
4325
4636
|
},
|
|
4326
4637
|
...options,
|
|
4327
4638
|
});
|
|
4328
4639
|
};
|
|
4329
4640
|
/**
|
|
4330
|
-
* Create Stripe
|
|
4641
|
+
* Create Stripe monthly subscription payment intent client secret.
|
|
4331
4642
|
*/
|
|
4332
|
-
const
|
|
4643
|
+
const useCreateSubscriptionPaymentIntent = (scope) => {
|
|
4333
4644
|
return useMutation({
|
|
4334
|
-
mutationFn: async ({
|
|
4645
|
+
mutationFn: async ({ plan, }) => {
|
|
4335
4646
|
const resp = await requestDatalayer({
|
|
4336
|
-
url: `${configuration.iamRunUrl}/api/iam/stripe/v1/
|
|
4647
|
+
url: `${configuration.iamRunUrl}/api/iam/stripe/v1/subscription/payment-intent`,
|
|
4337
4648
|
method: 'POST',
|
|
4338
4649
|
body: {
|
|
4339
|
-
price_id:
|
|
4340
|
-
|
|
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(
|
|
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
|
-
...
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|