@streamoid/settings 0.7.3 → 0.7.5

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/dist/index.d.mts CHANGED
@@ -182,6 +182,9 @@ interface SettingsWorkspaceListItem {
182
182
  /** The organization this workspace belongs to, if any. My workspaces groups
183
183
  * the list under each organization (named from `organization.list()`). */
184
184
  organizationId?: string;
185
+ /** The organization's name, when the host has it (used by `listPage()`
186
+ * rows whose organization isn't in `organization.list()`). */
187
+ organizationName?: string;
185
188
  }
186
189
  interface SettingsWorkspaceAdapter {
187
190
  /** Same authenticated transport used by the sidebar owner lookup. */
@@ -240,6 +243,20 @@ interface SettingsWorkspaceAdapter {
240
243
  * When omitted, the plan on each `list()` item is used as before.
241
244
  */
242
245
  fetchPlans?: (workspaceIds: string[]) => Promise<Record<string, string>>;
246
+ /**
247
+ * Optional: one page of the workspace list, from the API
248
+ * (`GET /workspace?limit=&offset=`). When given, My workspaces asks for each
249
+ * page as it is shown and renders exactly what comes back — `total` is every
250
+ * workspace the user can see. When omitted (or when a page request fails),
251
+ * My workspaces pages through `list()` in the browser as before.
252
+ */
253
+ listPage?: (params: {
254
+ limit: number;
255
+ offset: number;
256
+ }) => Promise<{
257
+ items: SettingsWorkspaceListItem[];
258
+ total: number;
259
+ }>;
243
260
  /**
244
261
  * Optional: host-controlled refresh after the package mutates workspace
245
262
  * state. If provided, the package will call this instead of (or in addition
@@ -349,6 +366,14 @@ interface OrganizationWorkspaceItem {
349
366
  imageUrl?: string;
350
367
  orgVisibility: "private" | "shared";
351
368
  }
369
+ /** A shared workspace in the organization that the viewer isn't in yet. */
370
+ interface OrganizationJoinableWorkspace {
371
+ _id: string;
372
+ name: string;
373
+ imageUrl?: string;
374
+ /** Who created it, shown under the name. */
375
+ ownerName?: string;
376
+ }
352
377
  interface OrganizationJoinRequest {
353
378
  _id: string;
354
379
  workspace: {
@@ -438,6 +463,15 @@ interface SettingsOrganizationAdapter {
438
463
  canClaimEmailDomain?: boolean;
439
464
  }>;
440
465
  fetchWorkspaces: () => Promise<OrganizationWorkspaceItem[]>;
466
+ /**
467
+ * Optional: the organization's shared workspaces the viewer isn't in yet —
468
+ * the workspace picker's list. When given (with `joinWorkspaces`), org
469
+ * members see them under Organization › Workspaces and can join any of them
470
+ * as a member at any time, not only at first sign-in.
471
+ */
472
+ fetchJoinableWorkspaces?: () => Promise<OrganizationJoinableWorkspace[]>;
473
+ /** Optional: join those workspaces as a member. Resolves to the ids joined. */
474
+ joinWorkspaces?: (workspaceIds: string[]) => Promise<string[]>;
441
475
  fetchJoinRequests: () => Promise<OrganizationJoinRequest[]>;
442
476
  approveJoinRequest: (requestId: string) => Promise<void>;
443
477
  rejectJoinRequest: (requestId: string) => Promise<void>;
@@ -961,7 +995,7 @@ interface WorkspaceListGroup {
961
995
  * last. Groups keep the order the organizations came in.
962
996
  */
963
997
  declare function groupWorkspaceList(workspaces: WorkspaceInfo[], organizations?: WorkspaceListOrganization[]): WorkspaceListGroup[];
964
- declare function WorkspaceListTab({ workspaces, loading, onCreateClick: _onCreateClick, onEditClick, onSwitchClick, onLeaveClick, fetchPlans, organizations, pageSize, }: {
998
+ declare function WorkspaceListTab({ workspaces, loading, onCreateClick: _onCreateClick, onEditClick, onSwitchClick, onLeaveClick, fetchPlans, organizations, pageSize, listPage, reloadKey, }: {
965
999
  workspaces: WorkspaceInfo[];
966
1000
  loading: boolean;
967
1001
  onCreateClick: () => void;
@@ -973,6 +1007,18 @@ declare function WorkspaceListTab({ workspaces, loading, onCreateClick: _onCreat
973
1007
  /** The viewer's organizations, to group the list under. */
974
1008
  organizations?: WorkspaceListOrganization[];
975
1009
  pageSize?: number;
1010
+ /** One page from the API (see SettingsWorkspaceAdapter.listPage). When
1011
+ * given, each page is fetched as it is shown and rendered as returned. */
1012
+ listPage?: (params: {
1013
+ limit: number;
1014
+ offset: number;
1015
+ }) => Promise<{
1016
+ items: WorkspaceInfo[];
1017
+ total: number;
1018
+ }>;
1019
+ /** Changes whenever the host's list changes (a leave, a rename…), so the
1020
+ * page on screen is fetched again. */
1021
+ reloadKey?: string;
976
1022
  }): react.JSX.Element;
977
1023
  /**
978
1024
  * @deprecated Renamed to `WorkspaceListTab` in 0.4.0 — the tab is a workspace
@@ -1439,4 +1485,4 @@ declare function SettingsAdaptersProvider({ adapters, children, }: {
1439
1485
  */
1440
1486
  declare function useSettingsAdapters(): SettingsAdapters;
1441
1487
 
1442
- 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, type OrganizationJoinRequest, type OrganizationMember, OrganizationMemberModal, type OrganizationMemberModalProps, 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, WORKSPACE_LIST_PAGE_SIZE, type WorkspaceData, WorkspaceGeneralTab, type WorkspaceGeneralTabProps, type WorkspaceInvite, type WorkspaceListOrganization, WorkspaceListTab, type WorkspaceMember, WorkspaceModal, type WorkspaceOrganization, type WorkspaceOrganizationRequest, WorkspacePicker, type WorkspacePickerItem, type WorkspacePickerProps, WorkspaceTab, type WorkspaceUsageResponse, countLabel, groupWorkspaceList, loadWorkspaceOrganization, resolveSettingsTabFromPath, useSettingsAdapters, useWorkspaceOrganization, userLanguageLabel };
1488
+ 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, type OrganizationJoinRequest, type OrganizationJoinableWorkspace, type OrganizationMember, OrganizationMemberModal, type OrganizationMemberModalProps, 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, WORKSPACE_LIST_PAGE_SIZE, type WorkspaceData, WorkspaceGeneralTab, type WorkspaceGeneralTabProps, type WorkspaceInvite, type WorkspaceListOrganization, WorkspaceListTab, type WorkspaceMember, WorkspaceModal, type WorkspaceOrganization, type WorkspaceOrganizationRequest, WorkspacePicker, type WorkspacePickerItem, type WorkspacePickerProps, WorkspaceTab, type WorkspaceUsageResponse, countLabel, groupWorkspaceList, loadWorkspaceOrganization, resolveSettingsTabFromPath, useSettingsAdapters, useWorkspaceOrganization, userLanguageLabel };
package/dist/index.d.ts CHANGED
@@ -182,6 +182,9 @@ interface SettingsWorkspaceListItem {
182
182
  /** The organization this workspace belongs to, if any. My workspaces groups
183
183
  * the list under each organization (named from `organization.list()`). */
184
184
  organizationId?: string;
185
+ /** The organization's name, when the host has it (used by `listPage()`
186
+ * rows whose organization isn't in `organization.list()`). */
187
+ organizationName?: string;
185
188
  }
186
189
  interface SettingsWorkspaceAdapter {
187
190
  /** Same authenticated transport used by the sidebar owner lookup. */
@@ -240,6 +243,20 @@ interface SettingsWorkspaceAdapter {
240
243
  * When omitted, the plan on each `list()` item is used as before.
241
244
  */
242
245
  fetchPlans?: (workspaceIds: string[]) => Promise<Record<string, string>>;
246
+ /**
247
+ * Optional: one page of the workspace list, from the API
248
+ * (`GET /workspace?limit=&offset=`). When given, My workspaces asks for each
249
+ * page as it is shown and renders exactly what comes back — `total` is every
250
+ * workspace the user can see. When omitted (or when a page request fails),
251
+ * My workspaces pages through `list()` in the browser as before.
252
+ */
253
+ listPage?: (params: {
254
+ limit: number;
255
+ offset: number;
256
+ }) => Promise<{
257
+ items: SettingsWorkspaceListItem[];
258
+ total: number;
259
+ }>;
243
260
  /**
244
261
  * Optional: host-controlled refresh after the package mutates workspace
245
262
  * state. If provided, the package will call this instead of (or in addition
@@ -349,6 +366,14 @@ interface OrganizationWorkspaceItem {
349
366
  imageUrl?: string;
350
367
  orgVisibility: "private" | "shared";
351
368
  }
369
+ /** A shared workspace in the organization that the viewer isn't in yet. */
370
+ interface OrganizationJoinableWorkspace {
371
+ _id: string;
372
+ name: string;
373
+ imageUrl?: string;
374
+ /** Who created it, shown under the name. */
375
+ ownerName?: string;
376
+ }
352
377
  interface OrganizationJoinRequest {
353
378
  _id: string;
354
379
  workspace: {
@@ -438,6 +463,15 @@ interface SettingsOrganizationAdapter {
438
463
  canClaimEmailDomain?: boolean;
439
464
  }>;
440
465
  fetchWorkspaces: () => Promise<OrganizationWorkspaceItem[]>;
466
+ /**
467
+ * Optional: the organization's shared workspaces the viewer isn't in yet —
468
+ * the workspace picker's list. When given (with `joinWorkspaces`), org
469
+ * members see them under Organization › Workspaces and can join any of them
470
+ * as a member at any time, not only at first sign-in.
471
+ */
472
+ fetchJoinableWorkspaces?: () => Promise<OrganizationJoinableWorkspace[]>;
473
+ /** Optional: join those workspaces as a member. Resolves to the ids joined. */
474
+ joinWorkspaces?: (workspaceIds: string[]) => Promise<string[]>;
441
475
  fetchJoinRequests: () => Promise<OrganizationJoinRequest[]>;
442
476
  approveJoinRequest: (requestId: string) => Promise<void>;
443
477
  rejectJoinRequest: (requestId: string) => Promise<void>;
@@ -961,7 +995,7 @@ interface WorkspaceListGroup {
961
995
  * last. Groups keep the order the organizations came in.
962
996
  */
963
997
  declare function groupWorkspaceList(workspaces: WorkspaceInfo[], organizations?: WorkspaceListOrganization[]): WorkspaceListGroup[];
964
- declare function WorkspaceListTab({ workspaces, loading, onCreateClick: _onCreateClick, onEditClick, onSwitchClick, onLeaveClick, fetchPlans, organizations, pageSize, }: {
998
+ declare function WorkspaceListTab({ workspaces, loading, onCreateClick: _onCreateClick, onEditClick, onSwitchClick, onLeaveClick, fetchPlans, organizations, pageSize, listPage, reloadKey, }: {
965
999
  workspaces: WorkspaceInfo[];
966
1000
  loading: boolean;
967
1001
  onCreateClick: () => void;
@@ -973,6 +1007,18 @@ declare function WorkspaceListTab({ workspaces, loading, onCreateClick: _onCreat
973
1007
  /** The viewer's organizations, to group the list under. */
974
1008
  organizations?: WorkspaceListOrganization[];
975
1009
  pageSize?: number;
1010
+ /** One page from the API (see SettingsWorkspaceAdapter.listPage). When
1011
+ * given, each page is fetched as it is shown and rendered as returned. */
1012
+ listPage?: (params: {
1013
+ limit: number;
1014
+ offset: number;
1015
+ }) => Promise<{
1016
+ items: WorkspaceInfo[];
1017
+ total: number;
1018
+ }>;
1019
+ /** Changes whenever the host's list changes (a leave, a rename…), so the
1020
+ * page on screen is fetched again. */
1021
+ reloadKey?: string;
976
1022
  }): react.JSX.Element;
977
1023
  /**
978
1024
  * @deprecated Renamed to `WorkspaceListTab` in 0.4.0 — the tab is a workspace
@@ -1439,4 +1485,4 @@ declare function SettingsAdaptersProvider({ adapters, children, }: {
1439
1485
  */
1440
1486
  declare function useSettingsAdapters(): SettingsAdapters;
1441
1487
 
1442
- 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, type OrganizationJoinRequest, type OrganizationMember, OrganizationMemberModal, type OrganizationMemberModalProps, 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, WORKSPACE_LIST_PAGE_SIZE, type WorkspaceData, WorkspaceGeneralTab, type WorkspaceGeneralTabProps, type WorkspaceInvite, type WorkspaceListOrganization, WorkspaceListTab, type WorkspaceMember, WorkspaceModal, type WorkspaceOrganization, type WorkspaceOrganizationRequest, WorkspacePicker, type WorkspacePickerItem, type WorkspacePickerProps, WorkspaceTab, type WorkspaceUsageResponse, countLabel, groupWorkspaceList, loadWorkspaceOrganization, resolveSettingsTabFromPath, useSettingsAdapters, useWorkspaceOrganization, userLanguageLabel };
1488
+ 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, type OrganizationJoinRequest, type OrganizationJoinableWorkspace, type OrganizationMember, OrganizationMemberModal, type OrganizationMemberModalProps, 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, WORKSPACE_LIST_PAGE_SIZE, type WorkspaceData, WorkspaceGeneralTab, type WorkspaceGeneralTabProps, type WorkspaceInvite, type WorkspaceListOrganization, WorkspaceListTab, type WorkspaceMember, WorkspaceModal, type WorkspaceOrganization, type WorkspaceOrganizationRequest, WorkspacePicker, type WorkspacePickerItem, type WorkspacePickerProps, WorkspaceTab, type WorkspaceUsageResponse, countLabel, groupWorkspaceList, loadWorkspaceOrganization, resolveSettingsTabFromPath, useSettingsAdapters, useWorkspaceOrganization, userLanguageLabel };
package/dist/index.js CHANGED
@@ -6174,6 +6174,79 @@ function WorkspaceRow({ workspace }) {
6174
6174
  }
6175
6175
  );
6176
6176
  }
6177
+ function JoinableWorkspaceRow({
6178
+ workspace,
6179
+ joining,
6180
+ onJoin
6181
+ }) {
6182
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
6183
+ "div",
6184
+ {
6185
+ className: "flex items-center self-stretch shrink-0",
6186
+ style: {
6187
+ borderBottom: `1px solid ${T11.borderSubtle}`,
6188
+ padding: `${T11.spXl} ${T11.sp3xl}`,
6189
+ gap: T11.sp6xl
6190
+ },
6191
+ children: [
6192
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center min-w-0", style: { flex: 1, gap: T11.spMd }, children: [
6193
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6194
+ ProfileAvatar,
6195
+ {
6196
+ name: workspace.name,
6197
+ profileImage: workspace.imageUrl,
6198
+ size: 28,
6199
+ borderRadius: "var(--radius-lg)"
6200
+ }
6201
+ ),
6202
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex flex-col min-w-0", children: [
6203
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6204
+ "span",
6205
+ {
6206
+ className: "truncate",
6207
+ style: {
6208
+ fontFamily: T11.font,
6209
+ fontSize: T11.fsSm,
6210
+ fontWeight: 500,
6211
+ lineHeight: T11.lhSm,
6212
+ color: T11.primary
6213
+ },
6214
+ children: workspace.name
6215
+ }
6216
+ ),
6217
+ workspace.ownerName && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
6218
+ "span",
6219
+ {
6220
+ className: "truncate",
6221
+ style: {
6222
+ fontFamily: T11.font,
6223
+ fontSize: T11.fsXs,
6224
+ fontWeight: 400,
6225
+ lineHeight: T11.lhXs,
6226
+ color: T11.muted
6227
+ },
6228
+ children: [
6229
+ "Created by ",
6230
+ workspace.ownerName
6231
+ ]
6232
+ }
6233
+ )
6234
+ ] })
6235
+ ] }),
6236
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6237
+ import_ui8.ScButton,
6238
+ {
6239
+ text: joining ? "Joining\u2026" : "Join",
6240
+ variant: "secondary",
6241
+ size: "sm",
6242
+ state: joining ? "disabled" : "default",
6243
+ onClick: joining ? void 0 : onJoin
6244
+ }
6245
+ )
6246
+ ]
6247
+ }
6248
+ );
6249
+ }
6177
6250
  function JoinRequestRow({
6178
6251
  request,
6179
6252
  onApprove,
@@ -6293,10 +6366,13 @@ function OrganizationContent({
6293
6366
  embedded = false,
6294
6367
  view = "people"
6295
6368
  } = {}) {
6296
- const { organization, toast } = useSettingsAdapters();
6369
+ const { organization, workspace: workspaceAdapter, toast } = useSettingsAdapters();
6297
6370
  const current = organization.current;
6298
6371
  const [members, setMembers] = (0, import_react12.useState)([]);
6299
6372
  const [workspaces, setWorkspaces] = (0, import_react12.useState)([]);
6373
+ const canJoinShared = current?.role === "member" && !!organization.fetchJoinableWorkspaces && !!organization.joinWorkspaces;
6374
+ const [joinable, setJoinable] = (0, import_react12.useState)([]);
6375
+ const [joiningId, setJoiningId] = (0, import_react12.useState)(null);
6300
6376
  const [joinRequests, setJoinRequests] = (0, import_react12.useState)([]);
6301
6377
  const [highestRole, setHighestRole] = (0, import_react12.useState)("guest");
6302
6378
  const [searchQuery, setSearchQuery] = (0, import_react12.useState)("");
@@ -6306,12 +6382,20 @@ function OrganizationContent({
6306
6382
  if (!organization.current) return;
6307
6383
  setLoadingData(true);
6308
6384
  try {
6309
- const [membersRes, workspacesRes, joinRequestsRes, highestRoleRes] = await Promise.all([
6385
+ const [
6386
+ membersRes,
6387
+ workspacesRes,
6388
+ joinRequestsRes,
6389
+ highestRoleRes,
6390
+ joinableRes
6391
+ ] = await Promise.all([
6310
6392
  organization.fetchMembers().catch(() => null),
6311
6393
  organization.fetchWorkspaces().catch(() => null),
6312
6394
  organization.fetchJoinRequests().catch(() => null),
6313
- organization.getHighestRole?.().catch(() => null) ?? Promise.resolve(null)
6395
+ organization.getHighestRole?.().catch(() => null) ?? Promise.resolve(null),
6396
+ canJoinShared ? organization.fetchJoinableWorkspaces().catch(() => null) : Promise.resolve(null)
6314
6397
  ]);
6398
+ if (joinableRes) setJoinable(joinableRes);
6315
6399
  if (highestRoleRes?.role) setHighestRole(highestRoleRes.role);
6316
6400
  if (membersRes) setMembers(membersRes.filter((m) => m?.user));
6317
6401
  if (workspacesRes) setWorkspaces(workspacesRes);
@@ -6320,10 +6404,30 @@ function OrganizationContent({
6320
6404
  } finally {
6321
6405
  setLoadingData(false);
6322
6406
  }
6323
- }, [organization]);
6407
+ }, [organization, canJoinShared]);
6324
6408
  (0, import_react12.useEffect)(() => {
6325
6409
  fetchData();
6326
6410
  }, [fetchData]);
6411
+ const handleJoinWorkspace = async (target) => {
6412
+ if (!organization.joinWorkspaces) return;
6413
+ setJoiningId(target._id);
6414
+ try {
6415
+ const joined = await organization.joinWorkspaces([target._id]);
6416
+ if (!joined.includes(target._id)) {
6417
+ toast.error(`Couldn't join ${target.name}. Try again.`);
6418
+ return;
6419
+ }
6420
+ toast.success(`Joined ${target.name}`);
6421
+ await workspaceAdapter.refresh?.().catch(() => void 0);
6422
+ await fetchData();
6423
+ } catch (err) {
6424
+ toast.error(
6425
+ err instanceof Error ? err.message : `Couldn't join ${target.name}`
6426
+ );
6427
+ } finally {
6428
+ setJoiningId(null);
6429
+ }
6430
+ };
6327
6431
  const [manageModalOpen, setManageModalOpen] = (0, import_react12.useState)(false);
6328
6432
  const handleUpdateOrganization = async (name, imageFile) => {
6329
6433
  if (!current) return false;
@@ -6552,6 +6656,23 @@ function OrganizationContent({
6552
6656
  children: workspaces.map((ws) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(WorkspaceRow, { workspace: ws }, ws._id))
6553
6657
  }
6554
6658
  ),
6659
+ canJoinShared && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6660
+ Section,
6661
+ {
6662
+ title: "Shared workspaces you can join",
6663
+ empty: joinable.length === 0,
6664
+ emptyText: "You're in every shared workspace in this organization",
6665
+ children: joinable.map((ws) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6666
+ JoinableWorkspaceRow,
6667
+ {
6668
+ workspace: ws,
6669
+ joining: joiningId === ws._id,
6670
+ onJoin: () => void handleJoinWorkspace(ws)
6671
+ },
6672
+ ws._id
6673
+ ))
6674
+ }
6675
+ ),
6555
6676
  (isOrgAdmin || joinRequests.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
6556
6677
  Section,
6557
6678
  {
@@ -10938,26 +11059,59 @@ function WorkspaceListTab({
10938
11059
  onLeaveClick,
10939
11060
  fetchPlans,
10940
11061
  organizations = [],
10941
- pageSize = WORKSPACE_LIST_PAGE_SIZE
11062
+ pageSize = WORKSPACE_LIST_PAGE_SIZE,
11063
+ listPage,
11064
+ reloadKey
10942
11065
  }) {
10943
11066
  const currentWorkspace = workspaces.find((ws) => ws.isCurrent);
10944
- const groups = groupWorkspaceList(
11067
+ const currentOrganization = organizations.find(
11068
+ (org) => org._id && org._id === currentWorkspace?.organizationId
11069
+ );
11070
+ const [page, setPage] = (0, import_react17.useState)(0);
11071
+ const [serverPage, setServerPage] = (0, import_react17.useState)(null);
11072
+ const [serverFailed, setServerFailed] = (0, import_react17.useState)(false);
11073
+ const serverMode = !!listPage && !serverFailed;
11074
+ (0, import_react17.useEffect)(() => {
11075
+ if (!listPage || serverFailed) return;
11076
+ let cancelled = false;
11077
+ listPage({ limit: pageSize, offset: page * pageSize }).then((result) => {
11078
+ if (cancelled) return;
11079
+ const lastPage = Math.max(0, Math.ceil(result.total / pageSize) - 1);
11080
+ if (page > lastPage) {
11081
+ setPage(lastPage);
11082
+ return;
11083
+ }
11084
+ setServerPage({ page, items: result.items, total: result.total });
11085
+ }).catch(() => {
11086
+ if (!cancelled) setServerFailed(true);
11087
+ });
11088
+ return () => {
11089
+ cancelled = true;
11090
+ };
11091
+ }, [listPage, serverFailed, page, pageSize, reloadKey]);
11092
+ const organizationLabel = (org) => org.role === "guest" ? `${org.name} \xB7 Guest` : org.name;
11093
+ const serverGroupOf = (ws) => {
11094
+ const org = organizations.find((o) => o._id === ws.organizationId);
11095
+ if (org) return { key: org._id, label: organizationLabel(org), workspaces: [] };
11096
+ if (ws.organizationId && ws.organizationName) {
11097
+ return { key: ws.organizationId, label: ws.organizationName, workspaces: [] };
11098
+ }
11099
+ return { key: "__no_org__", label: "No organization", workspaces: [] };
11100
+ };
11101
+ const groups = serverMode ? [] : groupWorkspaceList(
10945
11102
  workspaces.filter((ws) => !ws.isCurrent),
10946
11103
  organizations
10947
11104
  );
10948
11105
  const rows = groups.flatMap(
10949
11106
  (group) => group.workspaces.map((ws) => ({ ws, group }))
10950
11107
  );
10951
- const otherWorkspaces = rows.map((row) => row.ws);
10952
- const currentOrganization = organizations.find(
10953
- (org) => org._id && org._id === currentWorkspace?.organizationId
10954
- );
10955
- const pageCount = Math.max(1, Math.ceil(otherWorkspaces.length / pageSize));
10956
- const [page, setPage] = (0, import_react17.useState)(0);
10957
- const safePage = Math.min(page, pageCount - 1);
11108
+ const listTotal = serverMode ? serverPage?.total ?? 0 : rows.length;
11109
+ const pageCount = Math.max(1, Math.ceil(listTotal / pageSize));
11110
+ const safePage = serverMode ? serverPage?.page ?? 0 : Math.min(page, pageCount - 1);
10958
11111
  const pageStart = safePage * pageSize;
10959
- const pageRows = rows.slice(pageStart, pageStart + pageSize);
11112
+ const pageRows = serverMode ? (serverPage?.items ?? []).map((ws) => ({ ws, group: serverGroupOf(ws) })) : rows.slice(pageStart, pageStart + pageSize);
10960
11113
  const pageWorkspaces = pageRows.map((row) => row.ws);
11114
+ const listLoading = loading || serverMode && !serverPage;
10961
11115
  const [plans, setPlans] = (0, import_react17.useState)({});
10962
11116
  const requestedPlans = (0, import_react17.useRef)(/* @__PURE__ */ new Set());
10963
11117
  const visibleIds = [
@@ -10973,7 +11127,7 @@ function WorkspaceListTab({
10973
11127
  };
10974
11128
  }, []);
10975
11129
  (0, import_react17.useEffect)(() => {
10976
- if (!fetchPlans || loading) return;
11130
+ if (!fetchPlans || listLoading) return;
10977
11131
  const missing = visibleKey.split(",").filter((id) => id && !requestedPlans.current.has(id));
10978
11132
  if (!missing.length) return;
10979
11133
  missing.forEach((id) => requestedPlans.current.add(id));
@@ -10981,7 +11135,7 @@ function WorkspaceListTab({
10981
11135
  if (mounted.current) setPlans((prev) => ({ ...prev, ...found }));
10982
11136
  }).catch(() => {
10983
11137
  });
10984
- }, [fetchPlans, loading, visibleKey]);
11138
+ }, [fetchPlans, listLoading, visibleKey]);
10985
11139
  const planOf = (ws) => fetchPlans ? plans[ws._id] ?? "" : ws.plan ?? "";
10986
11140
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
10987
11141
  "div",
@@ -11002,7 +11156,7 @@ function WorkspaceListTab({
11002
11156
  children: "Switching changes which workspace the rest of Settings acts on."
11003
11157
  }
11004
11158
  ),
11005
- loading && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
11159
+ listLoading && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
11006
11160
  "p",
11007
11161
  {
11008
11162
  style: {
@@ -11013,7 +11167,7 @@ function WorkspaceListTab({
11013
11167
  children: "Loading workspaces\u2026"
11014
11168
  }
11015
11169
  ),
11016
- !loading && currentWorkspace && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
11170
+ !listLoading && currentWorkspace && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
11017
11171
  "div",
11018
11172
  {
11019
11173
  className: "flex flex-col items-start shrink-0 w-full",
@@ -11053,7 +11207,7 @@ function WorkspaceListTab({
11053
11207
  ]
11054
11208
  }
11055
11209
  ),
11056
- !loading && currentWorkspace && otherWorkspaces.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
11210
+ !listLoading && currentWorkspace && listTotal > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
11057
11211
  "div",
11058
11212
  {
11059
11213
  className: "shrink-0 w-full",
@@ -11063,7 +11217,7 @@ function WorkspaceListTab({
11063
11217
  }
11064
11218
  }
11065
11219
  ),
11066
- !loading && otherWorkspaces.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
11220
+ !listLoading && listTotal > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
11067
11221
  "div",
11068
11222
  {
11069
11223
  className: "flex flex-col items-start flex-1 min-h-0 w-full",
@@ -11099,8 +11253,8 @@ function WorkspaceListTab({
11099
11253
  role: ws.role,
11100
11254
  plan: planOf(ws),
11101
11255
  ownerId: ws.ownerEmail,
11102
- type: "otherWS",
11103
- onSwitch: () => onSwitchClick(ws),
11256
+ type: ws.isCurrent ? "currentWS" : "otherWS",
11257
+ onSwitch: ws.isCurrent ? void 0 : () => onSwitchClick(ws),
11104
11258
  onSettings: () => onEditClick(ws),
11105
11259
  onLeave: () => onLeaveClick(ws)
11106
11260
  }
@@ -11129,7 +11283,7 @@ function WorkspaceListTab({
11129
11283
  pageStart + pageWorkspaces.length,
11130
11284
  " of",
11131
11285
  " ",
11132
- otherWorkspaces.length
11286
+ listTotal
11133
11287
  ]
11134
11288
  }
11135
11289
  ),
@@ -11303,6 +11457,24 @@ function SettingsContentInner({
11303
11457
  (0, import_react17.useEffect)(() => {
11304
11458
  void refreshWorkspaceList();
11305
11459
  }, [refreshWorkspaceList]);
11460
+ const hostListPage = workspace.listPage;
11461
+ const listWorkspacePage = (0, import_react17.useMemo)(
11462
+ () => hostListPage ? async (params) => {
11463
+ const result = await hostListPage(params);
11464
+ return {
11465
+ total: result.total,
11466
+ items: result.items.map(
11467
+ (it) => ({
11468
+ ...it,
11469
+ isCurrent: it.isCurrent ?? it._id === workspaceId,
11470
+ imageUrl: it.imageUrl ?? it.image,
11471
+ initials: deriveInitials(it.name)
11472
+ })
11473
+ )
11474
+ };
11475
+ } : void 0,
11476
+ [hostListPage, workspaceId]
11477
+ );
11306
11478
  const [listOrganizations, setListOrganizations] = (0, import_react17.useState)([]);
11307
11479
  (0, import_react17.useEffect)(() => {
11308
11480
  if (activeTab !== "workspaceList" || typeof organization?.list !== "function") {
@@ -11976,6 +12148,8 @@ function SettingsContentInner({
11976
12148
  loading: workspaceListLoading && workspaceList.length === 0,
11977
12149
  fetchPlans: workspace.fetchPlans,
11978
12150
  organizations: listOrganizations,
12151
+ listPage: listWorkspacePage,
12152
+ reloadKey: displayWorkspaces.map((ws) => `${ws._id}:${ws.name}:${ws.role}`).join(","),
11979
12153
  onCreateClick: handleCreateClick,
11980
12154
  onEditClick: handleEditClick,
11981
12155
  onSwitchClick: async (ws) => {
package/dist/index.mjs CHANGED
@@ -167,7 +167,8 @@ import {
167
167
  useState as useState15,
168
168
  useEffect as useEffect13,
169
169
  useRef as useRef7,
170
- useCallback as useCallback7
170
+ useCallback as useCallback7,
171
+ useMemo as useMemo3
171
172
  } from "react";
172
173
  import {
173
174
  ScSidebarMenu,
@@ -6150,6 +6151,79 @@ function WorkspaceRow({ workspace }) {
6150
6151
  }
6151
6152
  );
6152
6153
  }
6154
+ function JoinableWorkspaceRow({
6155
+ workspace,
6156
+ joining,
6157
+ onJoin
6158
+ }) {
6159
+ return /* @__PURE__ */ jsxs14(
6160
+ "div",
6161
+ {
6162
+ className: "flex items-center self-stretch shrink-0",
6163
+ style: {
6164
+ borderBottom: `1px solid ${T11.borderSubtle}`,
6165
+ padding: `${T11.spXl} ${T11.sp3xl}`,
6166
+ gap: T11.sp6xl
6167
+ },
6168
+ children: [
6169
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-center min-w-0", style: { flex: 1, gap: T11.spMd }, children: [
6170
+ /* @__PURE__ */ jsx17(
6171
+ ProfileAvatar,
6172
+ {
6173
+ name: workspace.name,
6174
+ profileImage: workspace.imageUrl,
6175
+ size: 28,
6176
+ borderRadius: "var(--radius-lg)"
6177
+ }
6178
+ ),
6179
+ /* @__PURE__ */ jsxs14("div", { className: "flex flex-col min-w-0", children: [
6180
+ /* @__PURE__ */ jsx17(
6181
+ "span",
6182
+ {
6183
+ className: "truncate",
6184
+ style: {
6185
+ fontFamily: T11.font,
6186
+ fontSize: T11.fsSm,
6187
+ fontWeight: 500,
6188
+ lineHeight: T11.lhSm,
6189
+ color: T11.primary
6190
+ },
6191
+ children: workspace.name
6192
+ }
6193
+ ),
6194
+ workspace.ownerName && /* @__PURE__ */ jsxs14(
6195
+ "span",
6196
+ {
6197
+ className: "truncate",
6198
+ style: {
6199
+ fontFamily: T11.font,
6200
+ fontSize: T11.fsXs,
6201
+ fontWeight: 400,
6202
+ lineHeight: T11.lhXs,
6203
+ color: T11.muted
6204
+ },
6205
+ children: [
6206
+ "Created by ",
6207
+ workspace.ownerName
6208
+ ]
6209
+ }
6210
+ )
6211
+ ] })
6212
+ ] }),
6213
+ /* @__PURE__ */ jsx17(
6214
+ ScButton5,
6215
+ {
6216
+ text: joining ? "Joining\u2026" : "Join",
6217
+ variant: "secondary",
6218
+ size: "sm",
6219
+ state: joining ? "disabled" : "default",
6220
+ onClick: joining ? void 0 : onJoin
6221
+ }
6222
+ )
6223
+ ]
6224
+ }
6225
+ );
6226
+ }
6153
6227
  function JoinRequestRow({
6154
6228
  request,
6155
6229
  onApprove,
@@ -6269,10 +6343,13 @@ function OrganizationContent({
6269
6343
  embedded = false,
6270
6344
  view = "people"
6271
6345
  } = {}) {
6272
- const { organization, toast } = useSettingsAdapters();
6346
+ const { organization, workspace: workspaceAdapter, toast } = useSettingsAdapters();
6273
6347
  const current = organization.current;
6274
6348
  const [members, setMembers] = useState10([]);
6275
6349
  const [workspaces, setWorkspaces] = useState10([]);
6350
+ const canJoinShared = current?.role === "member" && !!organization.fetchJoinableWorkspaces && !!organization.joinWorkspaces;
6351
+ const [joinable, setJoinable] = useState10([]);
6352
+ const [joiningId, setJoiningId] = useState10(null);
6276
6353
  const [joinRequests, setJoinRequests] = useState10([]);
6277
6354
  const [highestRole, setHighestRole] = useState10("guest");
6278
6355
  const [searchQuery, setSearchQuery] = useState10("");
@@ -6282,12 +6359,20 @@ function OrganizationContent({
6282
6359
  if (!organization.current) return;
6283
6360
  setLoadingData(true);
6284
6361
  try {
6285
- const [membersRes, workspacesRes, joinRequestsRes, highestRoleRes] = await Promise.all([
6362
+ const [
6363
+ membersRes,
6364
+ workspacesRes,
6365
+ joinRequestsRes,
6366
+ highestRoleRes,
6367
+ joinableRes
6368
+ ] = await Promise.all([
6286
6369
  organization.fetchMembers().catch(() => null),
6287
6370
  organization.fetchWorkspaces().catch(() => null),
6288
6371
  organization.fetchJoinRequests().catch(() => null),
6289
- organization.getHighestRole?.().catch(() => null) ?? Promise.resolve(null)
6372
+ organization.getHighestRole?.().catch(() => null) ?? Promise.resolve(null),
6373
+ canJoinShared ? organization.fetchJoinableWorkspaces().catch(() => null) : Promise.resolve(null)
6290
6374
  ]);
6375
+ if (joinableRes) setJoinable(joinableRes);
6291
6376
  if (highestRoleRes?.role) setHighestRole(highestRoleRes.role);
6292
6377
  if (membersRes) setMembers(membersRes.filter((m) => m?.user));
6293
6378
  if (workspacesRes) setWorkspaces(workspacesRes);
@@ -6296,10 +6381,30 @@ function OrganizationContent({
6296
6381
  } finally {
6297
6382
  setLoadingData(false);
6298
6383
  }
6299
- }, [organization]);
6384
+ }, [organization, canJoinShared]);
6300
6385
  useEffect10(() => {
6301
6386
  fetchData();
6302
6387
  }, [fetchData]);
6388
+ const handleJoinWorkspace = async (target) => {
6389
+ if (!organization.joinWorkspaces) return;
6390
+ setJoiningId(target._id);
6391
+ try {
6392
+ const joined = await organization.joinWorkspaces([target._id]);
6393
+ if (!joined.includes(target._id)) {
6394
+ toast.error(`Couldn't join ${target.name}. Try again.`);
6395
+ return;
6396
+ }
6397
+ toast.success(`Joined ${target.name}`);
6398
+ await workspaceAdapter.refresh?.().catch(() => void 0);
6399
+ await fetchData();
6400
+ } catch (err) {
6401
+ toast.error(
6402
+ err instanceof Error ? err.message : `Couldn't join ${target.name}`
6403
+ );
6404
+ } finally {
6405
+ setJoiningId(null);
6406
+ }
6407
+ };
6303
6408
  const [manageModalOpen, setManageModalOpen] = useState10(false);
6304
6409
  const handleUpdateOrganization = async (name, imageFile) => {
6305
6410
  if (!current) return false;
@@ -6528,6 +6633,23 @@ function OrganizationContent({
6528
6633
  children: workspaces.map((ws) => /* @__PURE__ */ jsx17(WorkspaceRow, { workspace: ws }, ws._id))
6529
6634
  }
6530
6635
  ),
6636
+ canJoinShared && /* @__PURE__ */ jsx17(
6637
+ Section,
6638
+ {
6639
+ title: "Shared workspaces you can join",
6640
+ empty: joinable.length === 0,
6641
+ emptyText: "You're in every shared workspace in this organization",
6642
+ children: joinable.map((ws) => /* @__PURE__ */ jsx17(
6643
+ JoinableWorkspaceRow,
6644
+ {
6645
+ workspace: ws,
6646
+ joining: joiningId === ws._id,
6647
+ onJoin: () => void handleJoinWorkspace(ws)
6648
+ },
6649
+ ws._id
6650
+ ))
6651
+ }
6652
+ ),
6531
6653
  (isOrgAdmin || joinRequests.length > 0) && /* @__PURE__ */ jsx17(
6532
6654
  Section,
6533
6655
  {
@@ -10941,26 +11063,59 @@ function WorkspaceListTab({
10941
11063
  onLeaveClick,
10942
11064
  fetchPlans,
10943
11065
  organizations = [],
10944
- pageSize = WORKSPACE_LIST_PAGE_SIZE
11066
+ pageSize = WORKSPACE_LIST_PAGE_SIZE,
11067
+ listPage,
11068
+ reloadKey
10945
11069
  }) {
10946
11070
  const currentWorkspace = workspaces.find((ws) => ws.isCurrent);
10947
- const groups = groupWorkspaceList(
11071
+ const currentOrganization = organizations.find(
11072
+ (org) => org._id && org._id === currentWorkspace?.organizationId
11073
+ );
11074
+ const [page, setPage] = useState15(0);
11075
+ const [serverPage, setServerPage] = useState15(null);
11076
+ const [serverFailed, setServerFailed] = useState15(false);
11077
+ const serverMode = !!listPage && !serverFailed;
11078
+ useEffect13(() => {
11079
+ if (!listPage || serverFailed) return;
11080
+ let cancelled = false;
11081
+ listPage({ limit: pageSize, offset: page * pageSize }).then((result) => {
11082
+ if (cancelled) return;
11083
+ const lastPage = Math.max(0, Math.ceil(result.total / pageSize) - 1);
11084
+ if (page > lastPage) {
11085
+ setPage(lastPage);
11086
+ return;
11087
+ }
11088
+ setServerPage({ page, items: result.items, total: result.total });
11089
+ }).catch(() => {
11090
+ if (!cancelled) setServerFailed(true);
11091
+ });
11092
+ return () => {
11093
+ cancelled = true;
11094
+ };
11095
+ }, [listPage, serverFailed, page, pageSize, reloadKey]);
11096
+ const organizationLabel = (org) => org.role === "guest" ? `${org.name} \xB7 Guest` : org.name;
11097
+ const serverGroupOf = (ws) => {
11098
+ const org = organizations.find((o) => o._id === ws.organizationId);
11099
+ if (org) return { key: org._id, label: organizationLabel(org), workspaces: [] };
11100
+ if (ws.organizationId && ws.organizationName) {
11101
+ return { key: ws.organizationId, label: ws.organizationName, workspaces: [] };
11102
+ }
11103
+ return { key: "__no_org__", label: "No organization", workspaces: [] };
11104
+ };
11105
+ const groups = serverMode ? [] : groupWorkspaceList(
10948
11106
  workspaces.filter((ws) => !ws.isCurrent),
10949
11107
  organizations
10950
11108
  );
10951
11109
  const rows = groups.flatMap(
10952
11110
  (group) => group.workspaces.map((ws) => ({ ws, group }))
10953
11111
  );
10954
- const otherWorkspaces = rows.map((row) => row.ws);
10955
- const currentOrganization = organizations.find(
10956
- (org) => org._id && org._id === currentWorkspace?.organizationId
10957
- );
10958
- const pageCount = Math.max(1, Math.ceil(otherWorkspaces.length / pageSize));
10959
- const [page, setPage] = useState15(0);
10960
- const safePage = Math.min(page, pageCount - 1);
11112
+ const listTotal = serverMode ? serverPage?.total ?? 0 : rows.length;
11113
+ const pageCount = Math.max(1, Math.ceil(listTotal / pageSize));
11114
+ const safePage = serverMode ? serverPage?.page ?? 0 : Math.min(page, pageCount - 1);
10961
11115
  const pageStart = safePage * pageSize;
10962
- const pageRows = rows.slice(pageStart, pageStart + pageSize);
11116
+ const pageRows = serverMode ? (serverPage?.items ?? []).map((ws) => ({ ws, group: serverGroupOf(ws) })) : rows.slice(pageStart, pageStart + pageSize);
10963
11117
  const pageWorkspaces = pageRows.map((row) => row.ws);
11118
+ const listLoading = loading || serverMode && !serverPage;
10964
11119
  const [plans, setPlans] = useState15({});
10965
11120
  const requestedPlans = useRef7(/* @__PURE__ */ new Set());
10966
11121
  const visibleIds = [
@@ -10976,7 +11131,7 @@ function WorkspaceListTab({
10976
11131
  };
10977
11132
  }, []);
10978
11133
  useEffect13(() => {
10979
- if (!fetchPlans || loading) return;
11134
+ if (!fetchPlans || listLoading) return;
10980
11135
  const missing = visibleKey.split(",").filter((id) => id && !requestedPlans.current.has(id));
10981
11136
  if (!missing.length) return;
10982
11137
  missing.forEach((id) => requestedPlans.current.add(id));
@@ -10984,7 +11139,7 @@ function WorkspaceListTab({
10984
11139
  if (mounted.current) setPlans((prev) => ({ ...prev, ...found }));
10985
11140
  }).catch(() => {
10986
11141
  });
10987
- }, [fetchPlans, loading, visibleKey]);
11142
+ }, [fetchPlans, listLoading, visibleKey]);
10988
11143
  const planOf = (ws) => fetchPlans ? plans[ws._id] ?? "" : ws.plan ?? "";
10989
11144
  return /* @__PURE__ */ jsxs19(
10990
11145
  "div",
@@ -11005,7 +11160,7 @@ function WorkspaceListTab({
11005
11160
  children: "Switching changes which workspace the rest of Settings acts on."
11006
11161
  }
11007
11162
  ),
11008
- loading && /* @__PURE__ */ jsx22(
11163
+ listLoading && /* @__PURE__ */ jsx22(
11009
11164
  "p",
11010
11165
  {
11011
11166
  style: {
@@ -11016,7 +11171,7 @@ function WorkspaceListTab({
11016
11171
  children: "Loading workspaces\u2026"
11017
11172
  }
11018
11173
  ),
11019
- !loading && currentWorkspace && /* @__PURE__ */ jsxs19(
11174
+ !listLoading && currentWorkspace && /* @__PURE__ */ jsxs19(
11020
11175
  "div",
11021
11176
  {
11022
11177
  className: "flex flex-col items-start shrink-0 w-full",
@@ -11056,7 +11211,7 @@ function WorkspaceListTab({
11056
11211
  ]
11057
11212
  }
11058
11213
  ),
11059
- !loading && currentWorkspace && otherWorkspaces.length > 0 && /* @__PURE__ */ jsx22(
11214
+ !listLoading && currentWorkspace && listTotal > 0 && /* @__PURE__ */ jsx22(
11060
11215
  "div",
11061
11216
  {
11062
11217
  className: "shrink-0 w-full",
@@ -11066,7 +11221,7 @@ function WorkspaceListTab({
11066
11221
  }
11067
11222
  }
11068
11223
  ),
11069
- !loading && otherWorkspaces.length > 0 && /* @__PURE__ */ jsxs19(
11224
+ !listLoading && listTotal > 0 && /* @__PURE__ */ jsxs19(
11070
11225
  "div",
11071
11226
  {
11072
11227
  className: "flex flex-col items-start flex-1 min-h-0 w-full",
@@ -11102,8 +11257,8 @@ function WorkspaceListTab({
11102
11257
  role: ws.role,
11103
11258
  plan: planOf(ws),
11104
11259
  ownerId: ws.ownerEmail,
11105
- type: "otherWS",
11106
- onSwitch: () => onSwitchClick(ws),
11260
+ type: ws.isCurrent ? "currentWS" : "otherWS",
11261
+ onSwitch: ws.isCurrent ? void 0 : () => onSwitchClick(ws),
11107
11262
  onSettings: () => onEditClick(ws),
11108
11263
  onLeave: () => onLeaveClick(ws)
11109
11264
  }
@@ -11132,7 +11287,7 @@ function WorkspaceListTab({
11132
11287
  pageStart + pageWorkspaces.length,
11133
11288
  " of",
11134
11289
  " ",
11135
- otherWorkspaces.length
11290
+ listTotal
11136
11291
  ]
11137
11292
  }
11138
11293
  ),
@@ -11306,6 +11461,24 @@ function SettingsContentInner({
11306
11461
  useEffect13(() => {
11307
11462
  void refreshWorkspaceList();
11308
11463
  }, [refreshWorkspaceList]);
11464
+ const hostListPage = workspace.listPage;
11465
+ const listWorkspacePage = useMemo3(
11466
+ () => hostListPage ? async (params) => {
11467
+ const result = await hostListPage(params);
11468
+ return {
11469
+ total: result.total,
11470
+ items: result.items.map(
11471
+ (it) => ({
11472
+ ...it,
11473
+ isCurrent: it.isCurrent ?? it._id === workspaceId,
11474
+ imageUrl: it.imageUrl ?? it.image,
11475
+ initials: deriveInitials(it.name)
11476
+ })
11477
+ )
11478
+ };
11479
+ } : void 0,
11480
+ [hostListPage, workspaceId]
11481
+ );
11309
11482
  const [listOrganizations, setListOrganizations] = useState15([]);
11310
11483
  useEffect13(() => {
11311
11484
  if (activeTab !== "workspaceList" || typeof organization?.list !== "function") {
@@ -11979,6 +12152,8 @@ function SettingsContentInner({
11979
12152
  loading: workspaceListLoading && workspaceList.length === 0,
11980
12153
  fetchPlans: workspace.fetchPlans,
11981
12154
  organizations: listOrganizations,
12155
+ listPage: listWorkspacePage,
12156
+ reloadKey: displayWorkspaces.map((ws) => `${ws._id}:${ws.name}:${ws.role}`).join(","),
11982
12157
  onCreateClick: handleCreateClick,
11983
12158
  onEditClick: handleEditClick,
11984
12159
  onSwitchClick: async (ws) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamoid/settings",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",