@streamoid/settings 0.6.1 → 0.6.3

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 CHANGED
@@ -155,3 +155,19 @@ See [`src/adapters.ts`](src/adapters.ts) for full type definitions.
155
155
 
156
156
  - **0.1.0** — Adapter surface and package scaffold. Tab content lands in
157
157
  subsequent releases as files migrate from `cxo-dashboard` into the package.
158
+
159
+ ## Workspace owner in the sidebar
160
+
161
+ `useWorkspaceOrganization({ workspaceId, request, enabled, onOpenOrganization })`
162
+ returns organization props for `ScWorkspaceAccountMenu` from `@streamoid/ui`.
163
+ The host supplies a stable authenticated GET transport returning the unified
164
+ API envelope. The adapter reads `/workspace`, finds the exact selected workspace,
165
+ and resolves only its `organizationId` through `/organization/:id` and `/role`.
166
+ It never chooses an organization from the user's membership list. An unowned
167
+ workspace, a loading request, and an unavailable owner remain distinct states.
168
+
169
+ Enable it while the menu is open. Requests are aborted on close/switch and stale
170
+ responses cannot update the next workspace. Organization roles come from the
171
+ organization API, never the workspace role. Guests may see an authorized owner
172
+ name, but only members/admins get the optional navigation callback. The host
173
+ owns routing; apps without organization settings omit the callback.
package/dist/index.d.mts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import react__default, { SVGProps, ReactNode } from 'react';
3
+ import { WorkspaceUsage, ScWorkspaceAccountMenuProps } from '@streamoid/ui';
3
4
 
4
5
  /**
5
6
  * Settings tab identifiers and URL mapping helpers.
@@ -141,6 +142,13 @@ interface SettingsWorkspaceCurrent {
141
142
  /** Optional reactive credit summary if the host tracks it. */
142
143
  availableCredits?: number | null;
143
144
  totalCredits?: number | null;
145
+ /**
146
+ * Storage & bandwidth against the plan allowance, as the HOST has already
147
+ * fetched and evaluated it (`evaluateWorkspaceUsage` from `@streamoid/ui`).
148
+ * This package never requests it. When present, the Billing tab renders a
149
+ * usage meter for each kind; absent or null renders none.
150
+ */
151
+ usage?: WorkspaceUsage | null;
144
152
  /** ISO country code for the workspace; drives plan currency selection. */
145
153
  country?: string;
146
154
  /**
@@ -512,6 +520,8 @@ interface MemberPermissionsPayload {
512
520
  serviceIds: string[];
513
521
  storeIds: string[];
514
522
  }
523
+ /** `SettingsWorkspaceCurrent.usage`; re-exported from `@streamoid/ui`. */
524
+ type WorkspaceUsageResponse = WorkspaceUsage;
515
525
  interface AvailableCreditsResponse {
516
526
  availableCredits: number;
517
527
  }
@@ -1384,4 +1394,20 @@ declare function SettingsAdaptersProvider({ adapters, children, }: {
1384
1394
  */
1385
1395
  declare function useSettingsAdapters(): SettingsAdapters;
1386
1396
 
1387
- export { APP_KEYS, type ApiToken, type AppKey, type AvailableCreditsResponse, BillingContent, type BillingEventPayload, type BillingProduct, type BillingSubscription, BuyCreditsModal, type BuyCreditsPayload, type BuyCreditsResponse, ConfirmDeleteModal, ConfirmLeaveModal, type CreatedToken, type CustomCreditInfo, type CustomCreditResponse, DEFAULT_PERMISSIONS, DEFAULT_USER_LANGUAGE, ExportDateModal, InviteUpdateModal, type InvoicePdfResponse, MemberListFrame, type MemberListFrameProps, type MemberListTab, MemberListToolbar, type MemberListToolbarProps, type MemberPermissionsPayload, OrganizationContent, type OrganizationData, type OrganizationInvite, OrganizationInviteModal, type OrganizationInviteModalProps, type OrganizationJoinRequest, type OrganizationMember, OrganizationModal, type OrganizationView, type OrganizationWorkspaceItem, type Price, type PriceMetadata, type ProductsResponse, ProfileAvatar, ProfileTab, ReferralTab, SCOPE_LABELS, ScopeHeader, type ScopeHeaderProps, type ServiceItem, type SettingsAdapters, SettingsAdaptersProvider, type SettingsApiAdapter, type SettingsAuthAdapter, type SettingsAuthUser, SettingsContent, type SettingsContentProps, type SettingsImageUploadAdapter, type SettingsNavigationAdapter, type SettingsOrganizationAdapter, type SettingsOrganizationCurrent, type SettingsOrganizationListItem, type SettingsScope, type SettingsTab, type SettingsToastAdapter, SettingsTooltip, type SettingsTooltipPlacement, type SettingsTooltipProps, type SettingsWorkspaceAdapter, type SettingsWorkspaceCurrent, type SettingsWorkspaceListItem, SiconKey, type StoreItem, type SubscriptionPlan, type SubscriptionResponse, TAB_DESCRIPTIONS, TAB_LABELS, TAB_SCOPE, TAB_TO_URL, type TeamMember, TeamsContent, type TokenExpiry, TokensContent, URL_TO_TAB, USER_LANGUAGES, type UpdateSubscriptionPayload, type UpdateSubscriptionResponse, type UsageHistoryItem, type UsageHistoryResponse, type UserLanguage, type UserPermissions, type WorkspaceData, WorkspaceGeneralTab, type WorkspaceGeneralTabProps, type WorkspaceInvite, WorkspaceListTab, type WorkspaceMember, WorkspaceModal, WorkspaceTab, countLabel, resolveSettingsTabFromPath, useSettingsAdapters, userLanguageLabel };
1397
+ /** Host transport: authenticated GET returning the unified API envelope. */
1398
+ type WorkspaceOrganizationRequest = (path: string, signal: AbortSignal) => Promise<unknown>;
1399
+ interface WorkspaceOrganization {
1400
+ _id: string;
1401
+ name: string;
1402
+ role?: string;
1403
+ }
1404
+ /** Resolve only the selected workspace's owner, never a user's first org. */
1405
+ declare function loadWorkspaceOrganization(workspaceId: string, request: WorkspaceOrganizationRequest, signal: AbortSignal): Promise<WorkspaceOrganization | null>;
1406
+ declare function useWorkspaceOrganization({ workspaceId, request, enabled, onOpenOrganization, }: {
1407
+ workspaceId?: string | null;
1408
+ request: WorkspaceOrganizationRequest;
1409
+ enabled?: boolean;
1410
+ onOpenOrganization?: (organizationId: string) => void;
1411
+ }): Pick<ScWorkspaceAccountMenuProps, "organizationName" | "organizationRole" | "organizationStatus" | "onOrganizationClick">;
1412
+
1413
+ export { APP_KEYS, type ApiToken, type AppKey, type AvailableCreditsResponse, BillingContent, type BillingEventPayload, type BillingProduct, type BillingSubscription, BuyCreditsModal, type BuyCreditsPayload, type BuyCreditsResponse, ConfirmDeleteModal, ConfirmLeaveModal, type CreatedToken, type CustomCreditInfo, type CustomCreditResponse, DEFAULT_PERMISSIONS, DEFAULT_USER_LANGUAGE, ExportDateModal, InviteUpdateModal, type InvoicePdfResponse, MemberListFrame, type MemberListFrameProps, type MemberListTab, MemberListToolbar, type MemberListToolbarProps, type MemberPermissionsPayload, OrganizationContent, type OrganizationData, type OrganizationInvite, OrganizationInviteModal, type OrganizationInviteModalProps, type OrganizationJoinRequest, type OrganizationMember, OrganizationModal, type OrganizationView, type OrganizationWorkspaceItem, type Price, type PriceMetadata, type ProductsResponse, ProfileAvatar, ProfileTab, ReferralTab, SCOPE_LABELS, ScopeHeader, type ScopeHeaderProps, type ServiceItem, type SettingsAdapters, SettingsAdaptersProvider, type SettingsApiAdapter, type SettingsAuthAdapter, type SettingsAuthUser, SettingsContent, type SettingsContentProps, type SettingsImageUploadAdapter, type SettingsNavigationAdapter, type SettingsOrganizationAdapter, type SettingsOrganizationCurrent, type SettingsOrganizationListItem, type SettingsScope, type SettingsTab, type SettingsToastAdapter, SettingsTooltip, type SettingsTooltipPlacement, type SettingsTooltipProps, type SettingsWorkspaceAdapter, type SettingsWorkspaceCurrent, type SettingsWorkspaceListItem, SiconKey, type StoreItem, type SubscriptionPlan, type SubscriptionResponse, TAB_DESCRIPTIONS, TAB_LABELS, TAB_SCOPE, TAB_TO_URL, type TeamMember, TeamsContent, type TokenExpiry, TokensContent, URL_TO_TAB, USER_LANGUAGES, type UpdateSubscriptionPayload, type UpdateSubscriptionResponse, type UsageHistoryItem, type UsageHistoryResponse, type UserLanguage, type UserPermissions, type WorkspaceData, WorkspaceGeneralTab, type WorkspaceGeneralTabProps, type WorkspaceInvite, WorkspaceListTab, type WorkspaceMember, WorkspaceModal, type WorkspaceOrganization, type WorkspaceOrganizationRequest, WorkspaceTab, type WorkspaceUsageResponse, countLabel, loadWorkspaceOrganization, resolveSettingsTabFromPath, useSettingsAdapters, useWorkspaceOrganization, userLanguageLabel };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import react__default, { SVGProps, ReactNode } from 'react';
3
+ import { WorkspaceUsage, ScWorkspaceAccountMenuProps } from '@streamoid/ui';
3
4
 
4
5
  /**
5
6
  * Settings tab identifiers and URL mapping helpers.
@@ -141,6 +142,13 @@ interface SettingsWorkspaceCurrent {
141
142
  /** Optional reactive credit summary if the host tracks it. */
142
143
  availableCredits?: number | null;
143
144
  totalCredits?: number | null;
145
+ /**
146
+ * Storage & bandwidth against the plan allowance, as the HOST has already
147
+ * fetched and evaluated it (`evaluateWorkspaceUsage` from `@streamoid/ui`).
148
+ * This package never requests it. When present, the Billing tab renders a
149
+ * usage meter for each kind; absent or null renders none.
150
+ */
151
+ usage?: WorkspaceUsage | null;
144
152
  /** ISO country code for the workspace; drives plan currency selection. */
145
153
  country?: string;
146
154
  /**
@@ -512,6 +520,8 @@ interface MemberPermissionsPayload {
512
520
  serviceIds: string[];
513
521
  storeIds: string[];
514
522
  }
523
+ /** `SettingsWorkspaceCurrent.usage`; re-exported from `@streamoid/ui`. */
524
+ type WorkspaceUsageResponse = WorkspaceUsage;
515
525
  interface AvailableCreditsResponse {
516
526
  availableCredits: number;
517
527
  }
@@ -1384,4 +1394,20 @@ declare function SettingsAdaptersProvider({ adapters, children, }: {
1384
1394
  */
1385
1395
  declare function useSettingsAdapters(): SettingsAdapters;
1386
1396
 
1387
- export { APP_KEYS, type ApiToken, type AppKey, type AvailableCreditsResponse, BillingContent, type BillingEventPayload, type BillingProduct, type BillingSubscription, BuyCreditsModal, type BuyCreditsPayload, type BuyCreditsResponse, ConfirmDeleteModal, ConfirmLeaveModal, type CreatedToken, type CustomCreditInfo, type CustomCreditResponse, DEFAULT_PERMISSIONS, DEFAULT_USER_LANGUAGE, ExportDateModal, InviteUpdateModal, type InvoicePdfResponse, MemberListFrame, type MemberListFrameProps, type MemberListTab, MemberListToolbar, type MemberListToolbarProps, type MemberPermissionsPayload, OrganizationContent, type OrganizationData, type OrganizationInvite, OrganizationInviteModal, type OrganizationInviteModalProps, type OrganizationJoinRequest, type OrganizationMember, OrganizationModal, type OrganizationView, type OrganizationWorkspaceItem, type Price, type PriceMetadata, type ProductsResponse, ProfileAvatar, ProfileTab, ReferralTab, SCOPE_LABELS, ScopeHeader, type ScopeHeaderProps, type ServiceItem, type SettingsAdapters, SettingsAdaptersProvider, type SettingsApiAdapter, type SettingsAuthAdapter, type SettingsAuthUser, SettingsContent, type SettingsContentProps, type SettingsImageUploadAdapter, type SettingsNavigationAdapter, type SettingsOrganizationAdapter, type SettingsOrganizationCurrent, type SettingsOrganizationListItem, type SettingsScope, type SettingsTab, type SettingsToastAdapter, SettingsTooltip, type SettingsTooltipPlacement, type SettingsTooltipProps, type SettingsWorkspaceAdapter, type SettingsWorkspaceCurrent, type SettingsWorkspaceListItem, SiconKey, type StoreItem, type SubscriptionPlan, type SubscriptionResponse, TAB_DESCRIPTIONS, TAB_LABELS, TAB_SCOPE, TAB_TO_URL, type TeamMember, TeamsContent, type TokenExpiry, TokensContent, URL_TO_TAB, USER_LANGUAGES, type UpdateSubscriptionPayload, type UpdateSubscriptionResponse, type UsageHistoryItem, type UsageHistoryResponse, type UserLanguage, type UserPermissions, type WorkspaceData, WorkspaceGeneralTab, type WorkspaceGeneralTabProps, type WorkspaceInvite, WorkspaceListTab, type WorkspaceMember, WorkspaceModal, WorkspaceTab, countLabel, resolveSettingsTabFromPath, useSettingsAdapters, userLanguageLabel };
1397
+ /** Host transport: authenticated GET returning the unified API envelope. */
1398
+ type WorkspaceOrganizationRequest = (path: string, signal: AbortSignal) => Promise<unknown>;
1399
+ interface WorkspaceOrganization {
1400
+ _id: string;
1401
+ name: string;
1402
+ role?: string;
1403
+ }
1404
+ /** Resolve only the selected workspace's owner, never a user's first org. */
1405
+ declare function loadWorkspaceOrganization(workspaceId: string, request: WorkspaceOrganizationRequest, signal: AbortSignal): Promise<WorkspaceOrganization | null>;
1406
+ declare function useWorkspaceOrganization({ workspaceId, request, enabled, onOpenOrganization, }: {
1407
+ workspaceId?: string | null;
1408
+ request: WorkspaceOrganizationRequest;
1409
+ enabled?: boolean;
1410
+ onOpenOrganization?: (organizationId: string) => void;
1411
+ }): Pick<ScWorkspaceAccountMenuProps, "organizationName" | "organizationRole" | "organizationStatus" | "onOrganizationClick">;
1412
+
1413
+ export { APP_KEYS, type ApiToken, type AppKey, type AvailableCreditsResponse, BillingContent, type BillingEventPayload, type BillingProduct, type BillingSubscription, BuyCreditsModal, type BuyCreditsPayload, type BuyCreditsResponse, ConfirmDeleteModal, ConfirmLeaveModal, type CreatedToken, type CustomCreditInfo, type CustomCreditResponse, DEFAULT_PERMISSIONS, DEFAULT_USER_LANGUAGE, ExportDateModal, InviteUpdateModal, type InvoicePdfResponse, MemberListFrame, type MemberListFrameProps, type MemberListTab, MemberListToolbar, type MemberListToolbarProps, type MemberPermissionsPayload, OrganizationContent, type OrganizationData, type OrganizationInvite, OrganizationInviteModal, type OrganizationInviteModalProps, type OrganizationJoinRequest, type OrganizationMember, OrganizationModal, type OrganizationView, type OrganizationWorkspaceItem, type Price, type PriceMetadata, type ProductsResponse, ProfileAvatar, ProfileTab, ReferralTab, SCOPE_LABELS, ScopeHeader, type ScopeHeaderProps, type ServiceItem, type SettingsAdapters, SettingsAdaptersProvider, type SettingsApiAdapter, type SettingsAuthAdapter, type SettingsAuthUser, SettingsContent, type SettingsContentProps, type SettingsImageUploadAdapter, type SettingsNavigationAdapter, type SettingsOrganizationAdapter, type SettingsOrganizationCurrent, type SettingsOrganizationListItem, type SettingsScope, type SettingsTab, type SettingsToastAdapter, SettingsTooltip, type SettingsTooltipPlacement, type SettingsTooltipProps, type SettingsWorkspaceAdapter, type SettingsWorkspaceCurrent, type SettingsWorkspaceListItem, SiconKey, type StoreItem, type SubscriptionPlan, type SubscriptionResponse, TAB_DESCRIPTIONS, TAB_LABELS, TAB_SCOPE, TAB_TO_URL, type TeamMember, TeamsContent, type TokenExpiry, TokensContent, URL_TO_TAB, USER_LANGUAGES, type UpdateSubscriptionPayload, type UpdateSubscriptionResponse, type UsageHistoryItem, type UsageHistoryResponse, type UserLanguage, type UserPermissions, type WorkspaceData, WorkspaceGeneralTab, type WorkspaceGeneralTabProps, type WorkspaceInvite, WorkspaceListTab, type WorkspaceMember, WorkspaceModal, type WorkspaceOrganization, type WorkspaceOrganizationRequest, WorkspaceTab, type WorkspaceUsageResponse, countLabel, loadWorkspaceOrganization, resolveSettingsTabFromPath, useSettingsAdapters, useWorkspaceOrganization, userLanguageLabel };
package/dist/index.js CHANGED
@@ -56,8 +56,10 @@ __export(index_exports, {
56
56
  WorkspaceModal: () => WorkspaceModal,
57
57
  WorkspaceTab: () => WorkspaceTab,
58
58
  countLabel: () => countLabel,
59
+ loadWorkspaceOrganization: () => loadWorkspaceOrganization,
59
60
  resolveSettingsTabFromPath: () => resolveSettingsTabFromPath,
60
61
  useSettingsAdapters: () => useSettingsAdapters,
62
+ useWorkspaceOrganization: () => useWorkspaceOrganization,
61
63
  userLanguageLabel: () => userLanguageLabel
62
64
  });
63
65
  module.exports = __toCommonJS(index_exports);
@@ -9175,6 +9177,7 @@ function BillingContent({
9175
9177
  fetchBillingOverview();
9176
9178
  fetchUserMap();
9177
9179
  }, [fetchBillingOverview, fetchUserMap]);
9180
+ const usage = workspace.current?.usage ?? null;
9178
9181
  (0, import_react14.useEffect)(() => {
9179
9182
  void logBillingEvent("billing_page_viewed", "Billing page viewed", {
9180
9183
  action: "view"
@@ -9482,6 +9485,28 @@ function BillingContent({
9482
9485
  ]
9483
9486
  }
9484
9487
  ),
9488
+ usage ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
9489
+ "div",
9490
+ {
9491
+ className: "grid shrink-0 w-full",
9492
+ style: {
9493
+ gap: "var(--spacing-6xl)",
9494
+ gridTemplateColumns: "repeat(auto-fit, minmax(18rem, 1fr))"
9495
+ },
9496
+ children: [
9497
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_ui9.ScUsageMeterCard, { kind: "storage", plan: usage.plan, status: usage.storage }),
9498
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
9499
+ import_ui9.ScUsageMeterCard,
9500
+ {
9501
+ kind: "bandwidth",
9502
+ plan: usage.plan,
9503
+ status: usage.bandwidth,
9504
+ footnote: `This billing month \xB7 resets ${new Date(usage.period.to).toLocaleDateString(void 0, { day: "numeric", month: "short" })}`
9505
+ }
9506
+ )
9507
+ ]
9508
+ }
9509
+ ) : null,
9485
9510
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center justify-between shrink-0 w-full", children: [
9486
9511
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(BillingTabBar, { activeTab, onTabChange: setActiveTab }),
9487
9512
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
@@ -12309,3 +12334,73 @@ function SettingsContentInner({
12309
12334
  }
12310
12335
  );
12311
12336
  }
12337
+
12338
+ // src/workspace-organization.ts
12339
+ var import_react17 = require("react");
12340
+ function dataOf(value) {
12341
+ if (!value || typeof value !== "object") throw new Error("Invalid response");
12342
+ const envelope = value;
12343
+ if (envelope.status?.code != null && envelope.status.code !== 0) {
12344
+ throw new Error("Organization request failed");
12345
+ }
12346
+ return "data" in envelope ? envelope.data : envelope;
12347
+ }
12348
+ async function loadWorkspaceOrganization(workspaceId, request, signal) {
12349
+ const rows = dataOf(await request("/workspace", signal));
12350
+ if (!Array.isArray(rows)) throw new Error("Invalid workspace list");
12351
+ const row = rows.find((item) => (item.workspace?._id ?? item._id) === workspaceId);
12352
+ if (!row) throw new Error("Workspace not found");
12353
+ const workspace = row.workspace ?? row;
12354
+ const owner = workspace.organization !== void 0 ? workspace.organization : row.organization;
12355
+ const ownerRef = workspace.organizationId !== void 0 ? workspace.organizationId : row.organizationId;
12356
+ if (ownerRef === null || owner === null) return null;
12357
+ const id = typeof ownerRef === "string" ? ownerRef : ownerRef?._id ?? owner?._id;
12358
+ if (!id) return null;
12359
+ const path = `/organization/${encodeURIComponent(id)}`;
12360
+ const [detail, role] = await Promise.allSettled([
12361
+ owner?.name && owner._id === id ? Promise.resolve(owner) : request(path, signal).then(dataOf),
12362
+ request(`${path}/role`, signal).then(dataOf)
12363
+ ]);
12364
+ if (signal.aborted) throw new Error("Request aborted");
12365
+ if (detail.status !== "fulfilled" || !detail.value?.name || detail.value._id !== id) {
12366
+ throw new Error("Organization unavailable");
12367
+ }
12368
+ return {
12369
+ _id: id,
12370
+ name: detail.value.name,
12371
+ // A workspace role is never an organization permission.
12372
+ role: role.status === "fulfilled" ? role.value?.role : void 0
12373
+ };
12374
+ }
12375
+ function useWorkspaceOrganization({
12376
+ workspaceId,
12377
+ request,
12378
+ enabled = true,
12379
+ onOpenOrganization
12380
+ }) {
12381
+ const [result, setResult] = (0, import_react17.useState)();
12382
+ (0, import_react17.useEffect)(() => {
12383
+ if (!enabled || !workspaceId) return;
12384
+ const controller = new AbortController();
12385
+ setResult(void 0);
12386
+ loadWorkspaceOrganization(workspaceId, request, controller.signal).then(
12387
+ (owner2) => {
12388
+ if (!controller.signal.aborted) setResult({ workspaceId, owner: owner2 });
12389
+ },
12390
+ () => {
12391
+ if (!controller.signal.aborted) setResult({ workspaceId, error: true });
12392
+ }
12393
+ );
12394
+ return () => controller.abort();
12395
+ }, [workspaceId, request, enabled]);
12396
+ if (!workspaceId || !enabled) return {};
12397
+ if (result?.workspaceId !== workspaceId) return { organizationStatus: "loading" };
12398
+ if (result.error) return { organizationStatus: "error" };
12399
+ const owner = result.owner;
12400
+ const canOpen = owner?.role === "admin" || owner?.role === "member";
12401
+ return {
12402
+ organizationName: owner?.name ?? null,
12403
+ organizationRole: owner?.role,
12404
+ onOrganizationClick: owner && canOpen && onOpenOrganization ? () => onOpenOrganization(owner._id) : void 0
12405
+ };
12406
+ }
package/dist/index.mjs CHANGED
@@ -6956,7 +6956,8 @@ import {
6956
6956
  ScBillingHistoryTableList,
6957
6957
  ScHeader,
6958
6958
  ScPlanCard,
6959
- ScPlanComparison
6959
+ ScPlanComparison,
6960
+ ScUsageMeterCard
6960
6961
  } from "@streamoid/ui";
6961
6962
  import {
6962
6963
  SiconDownload,
@@ -9177,6 +9178,7 @@ function BillingContent({
9177
9178
  fetchBillingOverview();
9178
9179
  fetchUserMap();
9179
9180
  }, [fetchBillingOverview, fetchUserMap]);
9181
+ const usage = workspace.current?.usage ?? null;
9180
9182
  useEffect10(() => {
9181
9183
  void logBillingEvent("billing_page_viewed", "Billing page viewed", {
9182
9184
  action: "view"
@@ -9484,6 +9486,28 @@ function BillingContent({
9484
9486
  ]
9485
9487
  }
9486
9488
  ),
9489
+ usage ? /* @__PURE__ */ jsxs16(
9490
+ "div",
9491
+ {
9492
+ className: "grid shrink-0 w-full",
9493
+ style: {
9494
+ gap: "var(--spacing-6xl)",
9495
+ gridTemplateColumns: "repeat(auto-fit, minmax(18rem, 1fr))"
9496
+ },
9497
+ children: [
9498
+ /* @__PURE__ */ jsx18(ScUsageMeterCard, { kind: "storage", plan: usage.plan, status: usage.storage }),
9499
+ /* @__PURE__ */ jsx18(
9500
+ ScUsageMeterCard,
9501
+ {
9502
+ kind: "bandwidth",
9503
+ plan: usage.plan,
9504
+ status: usage.bandwidth,
9505
+ footnote: `This billing month \xB7 resets ${new Date(usage.period.to).toLocaleDateString(void 0, { day: "numeric", month: "short" })}`
9506
+ }
9507
+ )
9508
+ ]
9509
+ }
9510
+ ) : null,
9487
9511
  /* @__PURE__ */ jsxs16("div", { className: "flex items-center justify-between shrink-0 w-full", children: [
9488
9512
  /* @__PURE__ */ jsx18(BillingTabBar, { activeTab, onTabChange: setActiveTab }),
9489
9513
  /* @__PURE__ */ jsxs16(
@@ -12317,6 +12341,76 @@ function SettingsContentInner({
12317
12341
  }
12318
12342
  );
12319
12343
  }
12344
+
12345
+ // src/workspace-organization.ts
12346
+ import { useEffect as useEffect13, useState as useState15 } from "react";
12347
+ function dataOf(value) {
12348
+ if (!value || typeof value !== "object") throw new Error("Invalid response");
12349
+ const envelope = value;
12350
+ if (envelope.status?.code != null && envelope.status.code !== 0) {
12351
+ throw new Error("Organization request failed");
12352
+ }
12353
+ return "data" in envelope ? envelope.data : envelope;
12354
+ }
12355
+ async function loadWorkspaceOrganization(workspaceId, request, signal) {
12356
+ const rows = dataOf(await request("/workspace", signal));
12357
+ if (!Array.isArray(rows)) throw new Error("Invalid workspace list");
12358
+ const row = rows.find((item) => (item.workspace?._id ?? item._id) === workspaceId);
12359
+ if (!row) throw new Error("Workspace not found");
12360
+ const workspace = row.workspace ?? row;
12361
+ const owner = workspace.organization !== void 0 ? workspace.organization : row.organization;
12362
+ const ownerRef = workspace.organizationId !== void 0 ? workspace.organizationId : row.organizationId;
12363
+ if (ownerRef === null || owner === null) return null;
12364
+ const id = typeof ownerRef === "string" ? ownerRef : ownerRef?._id ?? owner?._id;
12365
+ if (!id) return null;
12366
+ const path = `/organization/${encodeURIComponent(id)}`;
12367
+ const [detail, role] = await Promise.allSettled([
12368
+ owner?.name && owner._id === id ? Promise.resolve(owner) : request(path, signal).then(dataOf),
12369
+ request(`${path}/role`, signal).then(dataOf)
12370
+ ]);
12371
+ if (signal.aborted) throw new Error("Request aborted");
12372
+ if (detail.status !== "fulfilled" || !detail.value?.name || detail.value._id !== id) {
12373
+ throw new Error("Organization unavailable");
12374
+ }
12375
+ return {
12376
+ _id: id,
12377
+ name: detail.value.name,
12378
+ // A workspace role is never an organization permission.
12379
+ role: role.status === "fulfilled" ? role.value?.role : void 0
12380
+ };
12381
+ }
12382
+ function useWorkspaceOrganization({
12383
+ workspaceId,
12384
+ request,
12385
+ enabled = true,
12386
+ onOpenOrganization
12387
+ }) {
12388
+ const [result, setResult] = useState15();
12389
+ useEffect13(() => {
12390
+ if (!enabled || !workspaceId) return;
12391
+ const controller = new AbortController();
12392
+ setResult(void 0);
12393
+ loadWorkspaceOrganization(workspaceId, request, controller.signal).then(
12394
+ (owner2) => {
12395
+ if (!controller.signal.aborted) setResult({ workspaceId, owner: owner2 });
12396
+ },
12397
+ () => {
12398
+ if (!controller.signal.aborted) setResult({ workspaceId, error: true });
12399
+ }
12400
+ );
12401
+ return () => controller.abort();
12402
+ }, [workspaceId, request, enabled]);
12403
+ if (!workspaceId || !enabled) return {};
12404
+ if (result?.workspaceId !== workspaceId) return { organizationStatus: "loading" };
12405
+ if (result.error) return { organizationStatus: "error" };
12406
+ const owner = result.owner;
12407
+ const canOpen = owner?.role === "admin" || owner?.role === "member";
12408
+ return {
12409
+ organizationName: owner?.name ?? null,
12410
+ organizationRole: owner?.role,
12411
+ onOrganizationClick: owner && canOpen && onOpenOrganization ? () => onOpenOrganization(owner._id) : void 0
12412
+ };
12413
+ }
12320
12414
  export {
12321
12415
  APP_KEYS,
12322
12416
  BillingContent,
@@ -12354,7 +12448,9 @@ export {
12354
12448
  WorkspaceModal,
12355
12449
  WorkspaceTab,
12356
12450
  countLabel,
12451
+ loadWorkspaceOrganization,
12357
12452
  resolveSettingsTabFromPath,
12358
12453
  useSettingsAdapters,
12454
+ useWorkspaceOrganization,
12359
12455
  userLanguageLabel
12360
12456
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamoid/settings",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -24,7 +24,7 @@
24
24
  "peerDependencies": {
25
25
  "react": "^18.0.0 || ^19.0.0",
26
26
  "react-dom": "^18.0.0 || ^19.0.0",
27
- "@streamoid/ui": "*",
27
+ "@streamoid/ui": ">=0.6.74",
28
28
  "@streamoid/icons": "*"
29
29
  },
30
30
  "devDependencies": {