@datalayer/core 1.1.43 → 1.1.45

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 (30) hide show
  1. package/lib/components/billing/BillingEntitySelect.d.ts +35 -0
  2. package/lib/components/billing/{BillableAccountSelect.js → BillingEntitySelect.js} +39 -41
  3. package/lib/components/billing/index.d.ts +1 -1
  4. package/lib/components/billing/index.js +1 -1
  5. package/lib/components/checkout/StripeCheckout.js +5 -5
  6. package/lib/components/display/VisuallyHidden.d.ts +1 -1
  7. package/lib/components/display/index.d.ts +0 -1
  8. package/lib/components/display/index.js +0 -1
  9. package/lib/components/principal/Principal.js +4 -2
  10. package/lib/components/principal/PrincipalBadge.js +21 -0
  11. package/lib/components/principal/PrincipalDetailsOverlay.js +72 -37
  12. package/lib/components/principal/PrincipalSwitcherMenu.d.ts +3 -3
  13. package/lib/components/principal/PrincipalSwitcherMenu.js +130 -111
  14. package/lib/hooks/index.d.ts +2 -2
  15. package/lib/hooks/index.js +2 -2
  16. package/lib/hooks/useBillingEntityStore.d.ts +31 -0
  17. package/lib/hooks/useBillingEntityStore.js +53 -0
  18. package/lib/hooks/useCache.d.ts +34 -33
  19. package/lib/hooks/useCache.js +32 -16
  20. package/lib/hooks/useSelectedBillingEntity.d.ts +14 -0
  21. package/lib/hooks/{useSelectedBillableAccount.js → useSelectedBillingEntity.js} +14 -14
  22. package/lib/hooks/useSelectedPrincipal.d.ts +1 -1
  23. package/lib/hooks/useSelectedPrincipal.js +1 -1
  24. package/package.json +4 -4
  25. package/lib/components/billing/BillableAccountSelect.d.ts +0 -35
  26. package/lib/components/display/LiveRelativeTime.d.ts +0 -1
  27. package/lib/components/display/LiveRelativeTime.js +0 -10
  28. package/lib/hooks/useBillableAccountStore.d.ts +0 -31
  29. package/lib/hooks/useBillableAccountStore.js +0 -53
  30. package/lib/hooks/useSelectedBillableAccount.d.ts +0 -14
@@ -4,14 +4,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
4
  * Distributed under the terms of the Modified BSD License.
5
5
  */
6
6
  import { useEffect, useMemo, useState } from 'react';
7
- import { ActionList, ActionMenu, Box, Button, Label, Text } from '@primer/react';
7
+ import { ActionList, ActionMenu, Box, Button, Label, Text, ThemeProvider, } from '@primer/react';
8
8
  import { OrganizationIcon, PeopleIcon, PersonIcon, TriangleDownIcon, } from '@primer/octicons-react';
9
9
  import { useCache, useAuthorization } from '../../hooks/index.js';
10
10
  import { useCoreStore } from '../../state/index.js';
11
11
  import { useIAMStore } from '../../state/substates/index.js';
12
12
  import { memberships as fetchMemberships } from '../../api/iam/profile.js';
13
13
  import { usePrincipalStore } from '../../hooks/usePrincipalStore.js';
14
- import { useBillableAccountStore } from '../../hooks/useBillableAccountStore.js';
14
+ import { useBillingEntityStore } from '../../hooks/useBillingEntityStore.js';
15
15
  import { useSelectedPrincipal } from '../../hooks/useSelectedPrincipal.js';
16
16
  import { DisplayHandle, displayHandleText } from '../display/DisplayHandle.js';
17
17
  function truncatePrincipalLabel(label, maxChars) {
@@ -26,10 +26,10 @@ function truncatePrincipalLabel(label, maxChars) {
26
26
  }
27
27
  /**
28
28
  * PrincipalSwitcherMenu — the *only* component allowed to write to the
29
- * principal store and the billable account store. It keeps both stores in
29
+ * principal store and the billing entity store. It keeps both stores in
30
30
  * sync per the rule:
31
- * - selecting a user/org principal → billable account = same user/org
32
- * - selecting a team principal → billable account = the team's parent org
31
+ * - selecting a user/org principal → billing entity = same user/org
32
+ * - selecting a team principal → billing entity = the team's parent org
33
33
  */
34
34
  export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, showClosedBorder = true, }) {
35
35
  const { user, token, iamUrl } = useIAMStore();
@@ -45,17 +45,17 @@ export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, sh
45
45
  const selectPersonalPrincipal = usePrincipalStore(state => state.selectPersonalPrincipal);
46
46
  const selectOrganizationPrincipal = usePrincipalStore(state => state.selectOrganizationPrincipal);
47
47
  const selectTeamPrincipal = usePrincipalStore(state => state.selectTeamPrincipal);
48
- const setBillableAccount = useBillableAccountStore(state => state.setBillableAccount);
48
+ const setBillingEntity = useBillingEntityStore(state => state.setBillingEntity);
49
49
  const { selectedPrincipalKind, selectedPrincipalUid, selectedPrincipalHandle, selectedTeamParentOrganizationHandle, } = useSelectedPrincipal();
50
50
  const personalUid = user?.uid || user?.id || '';
51
51
  const personalHandle = user?.handle || '';
52
52
  const selectUser = (uid, handle) => {
53
53
  selectPersonalPrincipal(uid, handle);
54
- setBillableAccount({ kind: 'user', uid, handle });
54
+ setBillingEntity({ kind: 'user', uid, handle });
55
55
  };
56
56
  const selectOrganization = (uid, handle) => {
57
57
  selectOrganizationPrincipal(uid, handle);
58
- setBillableAccount({ kind: 'organization', uid, handle });
58
+ setBillingEntity({ kind: 'organization', uid, handle });
59
59
  };
60
60
  const selectTeam = (team, orgHandle) => {
61
61
  if (!team.organizationUid) {
@@ -67,7 +67,7 @@ export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, sh
67
67
  organizationUid: team.organizationUid,
68
68
  organizationHandle: orgHandle,
69
69
  });
70
- setBillableAccount({
70
+ setBillingEntity({
71
71
  kind: 'organization',
72
72
  uid: team.organizationUid,
73
73
  handle: orgHandle,
@@ -95,6 +95,8 @@ export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, sh
95
95
  .map((membership) => ({
96
96
  uid: String(membership?.uid || membership?.id || '').trim(),
97
97
  handle: String(membership?.handle || '').trim(),
98
+ name: String(membership?.name || '').trim() || undefined,
99
+ displayName: String(membership?.display_name || membership?.displayName || '').trim() || undefined,
98
100
  organizationUid: String(membership?.organization_uid || '').trim() || undefined,
99
101
  organizationHandle: String(membership?.organization_handle ||
100
102
  membership?.organization?.handle ||
@@ -132,7 +134,8 @@ export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, sh
132
134
  // Keep explicit organization/team selections stable across route transitions,
133
135
  // even while memberships/organizations are still loading or temporarily
134
136
  // unavailable for a given view.
135
- if (selectedPrincipalKind === 'organization' || selectedPrincipalKind === 'team') {
137
+ if (selectedPrincipalKind === 'organization' ||
138
+ selectedPrincipalKind === 'team') {
136
139
  return;
137
140
  }
138
141
  if (selectedPrincipalKind === 'personal' &&
@@ -203,122 +206,138 @@ export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, sh
203
206
  textTransform: 'lowercase',
204
207
  lineHeight: 1.2,
205
208
  };
206
- return (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: showClosedBorder ? (_jsxs(Box, { as: "button", type: "button", "aria-label": "Switch principal", sx: {
207
- width: fullWidth ? '100%' : 'auto',
208
- boxSizing: 'border-box',
209
- px: 2,
210
- py: 2,
211
- fontSize: 0,
212
- display: 'flex',
213
- alignItems: 'center',
214
- justifyContent: 'space-between',
215
- gap: 2,
216
- bg: 'canvas.subtle',
217
- border: '1px solid',
218
- borderColor: 'border.default',
219
- borderRadius: 2,
220
- cursor: 'pointer',
221
- textAlign: 'left',
222
- transition: 'background-color 120ms ease',
223
- ':hover': {
224
- bg: 'canvas.subtle',
225
- },
226
- ':focus-visible': {
227
- bg: 'canvas.subtle',
228
- outline: '2px solid',
229
- outlineColor: 'accent.emphasis',
230
- outlineOffset: '2px',
231
- },
232
- }, children: [_jsx(Box, { sx: {
233
- display: 'inline-flex',
234
- alignItems: 'center',
235
- color: 'fg.muted',
236
- flexShrink: 0,
237
- }, children: selectedPrincipalKind === 'organization' ? (_jsx(OrganizationIcon, { size: 16 })) : selectedPrincipalKind === 'team' ? (_jsx(PeopleIcon, { size: 16 })) : (_jsx(PersonIcon, { size: 16 })) }), _jsx(Box, { sx: { minWidth: 0, flex: 1 }, children: _jsx(Text, { sx: {
238
- display: 'block',
239
- color: 'accent.fg',
240
- fontWeight: 'semibold',
241
- fontSize: 'inherit',
242
- whiteSpace: 'nowrap',
243
- overflow: 'hidden',
244
- textOverflow: 'ellipsis',
245
- maxWidth: '100%',
246
- }, children: selectedPrincipalLabelClosed }) }), isPlatformAdmin && isCurrentUserPrincipal ? (_jsx(Box, { sx: {
247
- flexShrink: 0,
248
- display: 'inline-flex',
249
- alignItems: 'center',
250
- }, children: _jsx(Label, { variant: "secondary", size: "small", sx: adminBadgeSx, children: "admin" }) })) : null, _jsx(Box, { sx: {
251
- display: 'inline-flex',
252
- alignItems: 'center',
253
- color: 'fg.muted',
254
- flexShrink: 0,
255
- ml: isPlatformAdmin && isCurrentUserPrincipal ? 1 : 0,
256
- }, children: _jsx(TriangleDownIcon, { size: 12 }) })] })) : (_jsx(Button, { variant: "invisible", size: "small", "aria-label": "Switch principal", trailingVisual: TriangleDownIcon, sx: {
257
- maxWidth: fullWidth ? '100%' : ['200px', '260px', '360px'],
258
- width: fullWidth ? '100%' : 'auto',
259
- }, children: _jsxs(Box, { sx: {
260
- display: 'inline-flex',
209
+ return (_jsx(ThemeProvider, { children: _jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: showClosedBorder ? (_jsxs(Box, { as: "button", type: "button", "aria-label": "Switch principal", sx: {
210
+ width: fullWidth ? '100%' : 'auto',
211
+ boxSizing: 'border-box',
212
+ px: 2,
213
+ py: 2,
214
+ fontSize: 0,
215
+ display: 'flex',
261
216
  alignItems: 'center',
262
- gap: 1,
263
- minWidth: 0,
217
+ justifyContent: 'space-between',
218
+ gap: 2,
219
+ bg: 'canvas.subtle',
220
+ border: '1px solid',
221
+ borderColor: 'border.default',
222
+ borderRadius: 2,
223
+ cursor: 'pointer',
224
+ textAlign: 'left',
225
+ transition: 'background-color 120ms ease',
226
+ ':hover': {
227
+ bg: 'canvas.subtle',
228
+ },
229
+ ':focus-visible': {
230
+ bg: 'canvas.subtle',
231
+ outline: '2px solid',
232
+ outlineColor: 'accent.emphasis',
233
+ outlineOffset: '2px',
234
+ },
264
235
  }, children: [_jsx(Box, { sx: {
265
236
  display: 'inline-flex',
266
237
  alignItems: 'center',
267
238
  color: 'fg.muted',
268
239
  flexShrink: 0,
269
- }, children: selectedPrincipalKind === 'organization' ? (_jsx(OrganizationIcon, { size: 16 })) : selectedPrincipalKind === 'team' ? (_jsx(PeopleIcon, { size: 16 })) : (_jsx(PersonIcon, { size: 16 })) }), _jsx(Box, { sx: {
270
- minWidth: 0,
271
- overflow: 'hidden',
272
- textOverflow: 'ellipsis',
273
- whiteSpace: 'nowrap',
274
- }, children: _jsx(Text, { sx: {
240
+ }, children: selectedPrincipalKind === 'organization' ? (_jsx(PeopleIcon, { size: 16 })) : selectedPrincipalKind === 'team' ? (_jsx(OrganizationIcon, { size: 16 })) : (_jsx(PersonIcon, { size: 16 })) }), _jsx(Box, { sx: { minWidth: 0, flex: 1 }, children: _jsx(Text, { sx: {
241
+ display: 'block',
275
242
  color: 'accent.fg',
276
243
  fontWeight: 'semibold',
277
- }, children: selectedPrincipalLabelClosed }) }), isPlatformAdmin && isCurrentUserPrincipal ? (_jsx(Label, { variant: "secondary", size: "small", sx: adminBadgeSx, children: "admin" })) : null] }) })) }), _jsx(ActionMenu.Overlay, { width: "medium", children: _jsxs(ActionList, { children: [_jsxs(ActionList.Group, { children: [_jsx(ActionList.GroupHeading, { children: "Personal" }), _jsxs(ActionList.Item, { disabled: isCurrentUserPrincipal, selected: isCurrentUserPrincipal, sx: isCurrentUserPrincipal ? selectedItemSx : undefined, onSelect: () => {
278
- if (isCurrentUserPrincipal) {
279
- return;
280
- }
281
- if (personalUid && personalHandle) {
282
- selectUser(personalUid, personalHandle);
283
- }
284
- }, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(PersonIcon, {}) }), _jsx(DisplayHandle, { handle: personalHandle || 'me' }), isPlatformAdmin ? (_jsx(ActionList.TrailingVisual, { children: _jsx(Label, { variant: "secondary", size: "small", sx: adminBadgeSx, children: "admin" }) })) : null] })] }), _jsxs(ActionList.Group, { children: [_jsx(ActionList.GroupHeading, { children: "Organizations" }), organizations.length === 0 ? (_jsx(ActionList.Item, { disabled: true, children: "No organizations" })) : (organizations.map((organization) => {
285
- const organizationUid = getOrganizationUid(organization);
286
- const isCurrentOrganizationPrincipal = selectedPrincipalKind === 'organization' &&
287
- selectedPrincipalUid === organizationUid;
288
- return (_jsxs(ActionList.Item, { disabled: isCurrentOrganizationPrincipal, selected: isCurrentOrganizationPrincipal, sx: isCurrentOrganizationPrincipal
289
- ? selectedItemSx
290
- : undefined, onSelect: () => {
291
- if (isCurrentOrganizationPrincipal) {
244
+ fontSize: 'inherit',
245
+ whiteSpace: 'nowrap',
246
+ overflow: 'hidden',
247
+ textOverflow: 'ellipsis',
248
+ maxWidth: '100%',
249
+ }, children: selectedPrincipalLabelClosed }) }), isPlatformAdmin && isCurrentUserPrincipal ? (_jsx(Box, { sx: {
250
+ flexShrink: 0,
251
+ display: 'inline-flex',
252
+ alignItems: 'center',
253
+ }, children: _jsx(Label, { variant: "secondary", size: "small", sx: adminBadgeSx, children: "admin" }) })) : null, _jsx(Box, { sx: {
254
+ display: 'inline-flex',
255
+ alignItems: 'center',
256
+ color: 'fg.muted',
257
+ flexShrink: 0,
258
+ ml: isPlatformAdmin && isCurrentUserPrincipal ? 1 : 0,
259
+ }, children: _jsx(TriangleDownIcon, { size: 12 }) })] })) : (_jsx(Button, { variant: "invisible", size: "small", "aria-label": "Switch principal", trailingVisual: TriangleDownIcon, sx: {
260
+ maxWidth: fullWidth ? '100%' : ['200px', '260px', '360px'],
261
+ width: fullWidth ? '100%' : 'auto',
262
+ }, children: _jsxs(Box, { sx: {
263
+ display: 'inline-flex',
264
+ alignItems: 'center',
265
+ gap: 1,
266
+ minWidth: 0,
267
+ }, children: [_jsx(Box, { sx: {
268
+ display: 'inline-flex',
269
+ alignItems: 'center',
270
+ color: 'fg.muted',
271
+ flexShrink: 0,
272
+ }, children: selectedPrincipalKind === 'organization' ? (_jsx(PeopleIcon, { size: 16 })) : selectedPrincipalKind === 'team' ? (_jsx(OrganizationIcon, { size: 16 })) : (_jsx(PersonIcon, { size: 16 })) }), _jsx(Box, { sx: {
273
+ minWidth: 0,
274
+ overflow: 'hidden',
275
+ textOverflow: 'ellipsis',
276
+ whiteSpace: 'nowrap',
277
+ }, children: _jsx(Text, { sx: {
278
+ color: 'accent.fg',
279
+ fontWeight: 'semibold',
280
+ }, children: selectedPrincipalLabelClosed }) }), isPlatformAdmin && isCurrentUserPrincipal ? (_jsx(Label, { variant: "secondary", size: "small", sx: adminBadgeSx, children: "admin" })) : null] }) })) }), _jsx(ActionMenu.Overlay, { width: "large", children: _jsxs(ActionList, { children: [_jsxs(ActionList.Group, { children: [_jsx(ActionList.GroupHeading, { children: "Personal" }), _jsxs(ActionList.Item, { disabled: isCurrentUserPrincipal, selected: isCurrentUserPrincipal, sx: isCurrentUserPrincipal ? selectedItemSx : undefined, onSelect: () => {
281
+ if (isCurrentUserPrincipal) {
292
282
  return;
293
283
  }
294
- if (organizationUid && organization.handle) {
295
- selectOrganization(organizationUid, organization.handle);
296
- }
297
- }, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(OrganizationIcon, {}) }), _jsx(Box, { as: "span", title: displayHandleText(organization.handle), sx: {
298
- display: 'block',
299
- minWidth: 0,
300
- overflow: 'hidden',
301
- textOverflow: 'ellipsis',
302
- whiteSpace: 'nowrap',
303
- }, children: _jsx(DisplayHandle, { handle: organization.handle }) })] }, organizationUid));
304
- }))] }), _jsxs(ActionList.Group, { children: [_jsx(ActionList.GroupHeading, { children: "Teams" }), teams.length === 0 ? (_jsx(ActionList.Item, { disabled: true, children: "No teams" })) : (teams.map(team => {
305
- const isCurrentTeamPrincipal = selectedPrincipalKind === 'team' &&
306
- selectedPrincipalUid === team.uid;
307
- const orgHandle = resolveTeamOrganizationHandle(team) ||
308
- personalHandle ||
309
- 'organization';
310
- return (_jsxs(ActionList.Item, { disabled: isCurrentTeamPrincipal, selected: isCurrentTeamPrincipal, sx: isCurrentTeamPrincipal ? selectedItemSx : undefined, onSelect: () => {
311
- if (isCurrentTeamPrincipal) {
312
- return;
284
+ if (personalUid && personalHandle) {
285
+ selectUser(personalUid, personalHandle);
313
286
  }
314
- selectTeam(team, orgHandle);
315
- }, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(PeopleIcon, {}) }), _jsxs(Box, { as: "span", title: `${displayHandleText(orgHandle)}/${displayHandleText(team.handle, { withAt: false })}`, sx: {
287
+ }, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(PersonIcon, {}) }), _jsxs(Box, { as: "span", sx: {
316
288
  display: 'block',
317
289
  minWidth: 0,
318
290
  overflow: 'hidden',
319
291
  textOverflow: 'ellipsis',
320
292
  whiteSpace: 'nowrap',
321
- }, children: [_jsx(DisplayHandle, { handle: orgHandle }), "/", _jsx(DisplayHandle, { handle: team.handle, withAt: false })] })] }, team.uid));
322
- }))] })] }) })] }));
293
+ }, children: [String(user?.friendlyName ||
294
+ user?.displayName ||
295
+ personalHandle ||
296
+ 'Me').trim(), _jsxs(Box, { as: "span", sx: { color: 'fg.muted' }, children: [' ', "\u00B7 ", _jsx(DisplayHandle, { handle: personalHandle || 'me' })] })] }), isPlatformAdmin ? (_jsx(ActionList.TrailingVisual, { children: _jsx(Label, { variant: "secondary", size: "small", sx: adminBadgeSx, children: "admin" }) })) : null] })] }), _jsxs(ActionList.Group, { children: [_jsx(ActionList.GroupHeading, { children: "Organizations" }), organizations.length === 0 ? (_jsx(ActionList.Item, { disabled: true, children: "No organizations" })) : (organizations.map((organization) => {
297
+ const organizationUid = getOrganizationUid(organization);
298
+ const isCurrentOrganizationPrincipal = selectedPrincipalKind === 'organization' &&
299
+ selectedPrincipalUid === organizationUid;
300
+ return (_jsxs(ActionList.Item, { disabled: isCurrentOrganizationPrincipal, selected: isCurrentOrganizationPrincipal, sx: isCurrentOrganizationPrincipal
301
+ ? selectedItemSx
302
+ : undefined, onSelect: () => {
303
+ if (isCurrentOrganizationPrincipal) {
304
+ return;
305
+ }
306
+ if (organizationUid && organization.handle) {
307
+ selectOrganization(organizationUid, organization.handle);
308
+ }
309
+ }, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(PeopleIcon, {}) }), _jsxs(Box, { as: "span", title: displayHandleText(organization.handle), sx: {
310
+ display: 'block',
311
+ minWidth: 0,
312
+ overflow: 'hidden',
313
+ textOverflow: 'ellipsis',
314
+ whiteSpace: 'nowrap',
315
+ }, children: [String(organization?.display_name ||
316
+ organization?.displayName ||
317
+ organization?.name ||
318
+ organization?.handle ||
319
+ 'Organization').trim(), _jsxs(Box, { as: "span", sx: { color: 'fg.muted' }, children: [' ', "\u00B7 ", _jsx(DisplayHandle, { handle: organization.handle })] })] })] }, organizationUid));
320
+ }))] }), _jsxs(ActionList.Group, { children: [_jsx(ActionList.GroupHeading, { children: "Teams" }), teams.length === 0 ? (_jsx(ActionList.Item, { disabled: true, children: "No teams" })) : (teams.map(team => {
321
+ const isCurrentTeamPrincipal = selectedPrincipalKind === 'team' &&
322
+ selectedPrincipalUid === team.uid;
323
+ const orgHandle = resolveTeamOrganizationHandle(team) ||
324
+ personalHandle ||
325
+ 'organization';
326
+ return (_jsxs(ActionList.Item, { disabled: isCurrentTeamPrincipal, selected: isCurrentTeamPrincipal, sx: isCurrentTeamPrincipal ? selectedItemSx : undefined, onSelect: () => {
327
+ if (isCurrentTeamPrincipal) {
328
+ return;
329
+ }
330
+ selectTeam(team, orgHandle);
331
+ }, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(OrganizationIcon, {}) }), _jsxs(Box, { as: "span", title: `${displayHandleText(orgHandle)}/${displayHandleText(team.handle, { withAt: false })}`, sx: {
332
+ display: 'block',
333
+ minWidth: 0,
334
+ overflow: 'hidden',
335
+ textOverflow: 'ellipsis',
336
+ whiteSpace: 'nowrap',
337
+ }, children: [String(team.displayName ||
338
+ team.name ||
339
+ team.handle ||
340
+ 'Team').trim(), _jsxs(Box, { as: "span", sx: { color: 'fg.muted' }, children: [' ', "\u00B7 ", _jsx(DisplayHandle, { handle: orgHandle }), "/", _jsx(DisplayHandle, { handle: team.handle, withAt: false })] })] })] }, team.uid));
341
+ }))] })] }) })] }) }));
323
342
  }
324
343
  export default PrincipalSwitcherMenu;
@@ -28,8 +28,8 @@ export * from './useProjectStore';
28
28
  export * from './useMobile';
29
29
  export * from './useUsageRefreshStore';
30
30
  export * from './usePrincipalStore';
31
- export * from './useBillableAccountStore';
31
+ export * from './useBillingEntityStore';
32
32
  export * from './useSelectedPrincipal';
33
- export * from './useSelectedBillableAccount';
33
+ export * from './useSelectedBillingEntity';
34
34
  export * from './useVisibilityObserver';
35
35
  export * from './useWindowSize';
@@ -33,8 +33,8 @@ export * from './useProjectStore.js';
33
33
  export * from './useMobile.js';
34
34
  export * from './useUsageRefreshStore.js';
35
35
  export * from './usePrincipalStore.js';
36
- export * from './useBillableAccountStore.js';
36
+ export * from './useBillingEntityStore.js';
37
37
  export * from './useSelectedPrincipal.js';
38
- export * from './useSelectedBillableAccount.js';
38
+ export * from './useSelectedBillingEntity.js';
39
39
  export * from './useVisibilityObserver.js';
40
40
  export * from './useWindowSize.js';
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The kind of billing entity currently in scope.
3
+ *
4
+ * An account is the *billing* principal — only users and organizations
5
+ * are accounts. Teams are NOT accounts; when a team is the selected
6
+ * principal, the billing entity is the team's parent organization.
7
+ */
8
+ export type BillingEntityKind = 'user' | 'organization';
9
+ export type BillingEntityState = {
10
+ billingEntityKind: BillingEntityKind;
11
+ billingEntityUid?: string;
12
+ billingEntityHandle?: string;
13
+ setBillingEntity: (args: {
14
+ kind: BillingEntityKind;
15
+ uid: string;
16
+ handle: string;
17
+ }) => void;
18
+ resetBillingEntity: () => void;
19
+ };
20
+ export declare const useBillingEntityStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<BillingEntityState>, "persist"> & {
21
+ persist: {
22
+ setOptions: (options: Partial<import("zustand/middleware").PersistOptions<BillingEntityState, unknown>>) => void;
23
+ clearStorage: () => void;
24
+ rehydrate: () => Promise<void> | void;
25
+ hasHydrated: () => boolean;
26
+ onHydrate: (fn: (state: BillingEntityState) => void) => () => void;
27
+ onFinishHydration: (fn: (state: BillingEntityState) => void) => () => void;
28
+ getOptions: () => Partial<import("zustand/middleware").PersistOptions<BillingEntityState, unknown>>;
29
+ };
30
+ }>;
31
+ export default useBillingEntityStore;
@@ -0,0 +1,53 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { create } from 'zustand';
6
+ import { createJSONStorage, persist } from 'zustand/middleware';
7
+ import { useUsageRefreshStore } from './useUsageRefreshStore.js';
8
+ export const useBillingEntityStore = create()(persist(set => ({
9
+ billingEntityKind: 'user',
10
+ billingEntityUid: undefined,
11
+ billingEntityHandle: undefined,
12
+ setBillingEntity: ({ kind, uid, handle }) => set(state => {
13
+ const unchanged = state.billingEntityKind === kind &&
14
+ state.billingEntityUid === uid &&
15
+ state.billingEntityHandle === handle;
16
+ if (unchanged) {
17
+ return state;
18
+ }
19
+ useUsageRefreshStore
20
+ .getState()
21
+ .requestUsageRefresh('billing-entity-changed');
22
+ return {
23
+ billingEntityKind: kind,
24
+ billingEntityUid: uid,
25
+ billingEntityHandle: handle,
26
+ };
27
+ }),
28
+ resetBillingEntity: () => set(state => {
29
+ const unchanged = state.billingEntityKind === 'user' &&
30
+ state.billingEntityUid === undefined &&
31
+ state.billingEntityHandle === undefined;
32
+ if (unchanged) {
33
+ return state;
34
+ }
35
+ useUsageRefreshStore
36
+ .getState()
37
+ .requestUsageRefresh('billing-entity-reset');
38
+ return {
39
+ billingEntityKind: 'user',
40
+ billingEntityUid: undefined,
41
+ billingEntityHandle: undefined,
42
+ };
43
+ }),
44
+ }), {
45
+ name: 'datalayer-billing-entity',
46
+ storage: createJSONStorage(() => localStorage),
47
+ partialize: state => ({
48
+ billingEntityKind: state.billingEntityKind,
49
+ billingEntityUid: state.billingEntityUid,
50
+ billingEntityHandle: state.billingEntityHandle,
51
+ }),
52
+ }));
53
+ export default useBillingEntityStore;