@streamoid/settings 0.12.3 → 0.13.0

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
@@ -673,7 +673,9 @@ interface SharedPoolBalance {
673
673
  remaining: number;
674
674
  /** When the sponsor's billing cycle ends and the allowance starts again. */
675
675
  cycleEnd: string;
676
- /** The member's own credits: still its own, but paused while it is a member. */
676
+ /** When the share stops on its own (ISO); null or absent when it runs until removed. */
677
+ endsAt?: string | null;
678
+ /** The member's own credits, spent before the pool's. */
677
679
  ownCredits?: number;
678
680
  }
679
681
  interface AvailableCreditsResponse {
@@ -894,6 +896,8 @@ interface CreditPoolMember {
894
896
  end: string;
895
897
  base: number;
896
898
  };
899
+ /** When the share stops on its own (ISO); null or absent when it runs until removed. */
900
+ endsAt?: string | null;
897
901
  note?: string;
898
902
  requestedBy?: string;
899
903
  createdBy?: string;
@@ -926,10 +930,14 @@ interface AddCreditPoolMemberInput {
926
930
  value: number;
927
931
  /** Why — required, at most 500 characters. */
928
932
  note: string;
933
+ /** When the share stops on its own (an ISO date in the future); null or absent for never. */
934
+ endsAt?: string | null;
929
935
  }
930
936
  interface UpdateCreditPoolMemberInput {
931
937
  mode: CreditPoolMode;
932
938
  value: number;
939
+ /** A new end date (ISO, in the future), or null to clear it; absent leaves it as it is. */
940
+ endsAt?: string | null;
933
941
  }
934
942
  /** A workspace credits can be shared with (another workspace of the organization). */
935
943
  interface CreditPoolCandidate {
@@ -2082,18 +2090,63 @@ declare function formatPoolSplit(split: {
2082
2090
  }): string;
2083
2091
  /** "20% · 200 credits this cycle", or "200 credits this cycle" for a fixed share. */
2084
2092
  declare function describeAllowance(member: Pick<CreditPoolMember, "mode" | "value" | "allowance">): string;
2093
+ /**
2094
+ * When a share stops on its own, as the Ends field picks it. `never` is
2095
+ * today's behaviour: the share renews every cycle until it is removed.
2096
+ */
2097
+ type PoolExpiryChoice = {
2098
+ kind: "never";
2099
+ } | {
2100
+ kind: "month";
2101
+ } | {
2102
+ kind: "months";
2103
+ months: number;
2104
+ } | {
2105
+ kind: "year";
2106
+ }
2107
+ /** A local calendar day, `YYYY-MM-DD` (what `<input type="date">` gives). */
2108
+ | {
2109
+ kind: "date";
2110
+ date: string;
2111
+ };
2112
+ declare const POOL_EXPIRY_MAX_MONTHS = 120;
2113
+ /** A local calendar day as `YYYY-MM-DD`. */
2114
+ declare function toLocalDateInput(date: Date): string;
2115
+ /** The earliest day the date picker allows: tomorrow, in local time. */
2116
+ declare function minPoolEndDate(now?: Date): string;
2117
+ type PoolEndsAtResult = {
2118
+ ok: true;
2119
+ value: string | null;
2120
+ } | {
2121
+ ok: false;
2122
+ reason: string;
2123
+ };
2124
+ /**
2125
+ * The `endsAt` to send for a choice, as an ISO string, or `null` for Never.
2126
+ * Months and a year count from `now`; a date is the start of that day in the
2127
+ * admin's local time and must be after today.
2128
+ */
2129
+ declare function poolEndsAtFrom(choice: PoolExpiryChoice, now?: Date): PoolEndsAtResult;
2130
+ /** The Ends field's starting choice for a share: Never, or its end date's day. */
2131
+ declare function expiryChoiceFor(member: Pick<CreditPoolMember, "endsAt"> | null | undefined): PoolExpiryChoice;
2132
+ /** When a share ends: "Mar 1, 2027", or "Never" when it runs until removed. */
2133
+ declare function formatPoolEndsAt(endsAt: string | null | undefined): string;
2134
+ /** The member banner's line on when sharing stops: "Sharing ends on Mar 1, 2027." or "Sharing ends: Never." */
2135
+ declare function sharedPoolEndsText(pool: Pick<SharedPoolBalance, "endsAt">): string;
2136
+ /** Whether the allowance starts again at `cycleEnd`: not when the share has ended by then. */
2137
+ declare function poolRenewsAtCycleEnd(pool: Pick<SharedPoolBalance, "endsAt" | "cycleEnd">): boolean;
2085
2138
  /** The other workspace's name, or a stand-in when it could not be resolved. */
2086
2139
  declare function poolWorkspaceName(name: string | null | undefined): string;
2087
2140
  /** The member's Billing banner: "Credits shared by Agency: 80 of 200 left this cycle". */
2088
2141
  declare function sharedPoolBannerText(pool: Pick<SharedPoolBalance, "sponsorName" | "allowance" | "remaining">): string;
2089
2142
  /**
2090
- * The line under the member's banner when its own credits could not be spent
2091
- * with the pool's. Billing now spends them first (see `ownCreditsFirstText`);
2092
- * kept for hosts that still show it.
2143
+ * Was the line saying a member's own credits were paused while it used the
2144
+ * pool's. Billing spends them first, so it now answers `ownCreditsFirstText`
2145
+ * for hosts that still call it.
2093
2146
  *
2094
2147
  * @deprecated Use `ownCreditsFirstText`.
2095
2148
  */
2096
- declare function pausedOwnCreditsText(pool: Pick<SharedPoolBalance, "ownCredits">): string;
2149
+ declare function pausedOwnCreditsText(_pool?: Pick<SharedPoolBalance, "ownCredits">): string;
2097
2150
  /** The line under the member's banner: whose credits a job spends, in order. */
2098
2151
  declare function ownCreditsFirstText(): string;
2099
2152
  interface CreditsMeter {
@@ -2278,4 +2331,4 @@ interface AppSettingsRowProps {
2278
2331
  }
2279
2332
  declare function AppSettingsRow({ title, description, summary, href, onClick, disabled, disabledReason, ariaLabel, trailing }: AppSettingsRowProps): react.JSX.Element;
2280
2333
 
2281
- export { APP_KEYS, type AddCreditPoolMemberInput, AdministrationOverview, type AdministrationOverviewProps, type AdministrativeNavigationOptions, type ApiToken, type AppKey, AppSettingsHeader, type AppSettingsHeaderProps, AppSettingsPage, type AppSettingsPageProps, AppSettingsRow, type AppSettingsRowProps, AppSettingsSection, type AppSettingsSectionProps, type AvailableCreditsResponse, BillingContent, type BillingEventPayload, type BillingProduct, type BillingSubscription, BuyCreditsModal, type BuyCreditsPayload, type BuyCreditsResponse, ConfirmDeleteModal, ConfirmLeaveModal, type ConnectedApp, ConnectedAppsContent, type CreatedToken, type CreditCycleUnit, type CreditLimitValidation, type CreditPoolCandidate, type CreditPoolFormerMember, type CreditPoolMember, CreditPoolMemberModal, type CreditPoolMemberModalProps, type CreditPoolMemberStatus, type CreditPoolMode, type CreditPoolResponse, type CreditsMeter, type CustomCreditInfo, type CustomCreditResponse, DEFAULT_PERMISSIONS, DEFAULT_USER_LANGUAGE, ExportDateModal, InviteUpdateModal, type InvoicePdfResponse, type MemberCreditLimit, MemberCreditLimitModal, type MemberCreditLimitRow, type MemberCreditLimitSource, type MemberCreditLimitsResponse, MemberListFrame, type MemberListFrameProps, type MemberListTab, MemberListToolbar, type MemberListToolbarProps, type MemberPermissionsPayload, OrganizationContent, type OrganizationData, type OrganizationDetails, OrganizationIntegrationsContent, type OrganizationInvite, type OrganizationJoinRequest, type OrganizationJoinableWorkspace, type OrganizationMember, OrganizationMemberModal, type OrganizationMemberModalProps, type OrganizationMemberRole, OrganizationModal, OrganizationPicker, type OrganizationPickerItem, type OrganizationPickerProps, type OrganizationPickerType, type OrganizationView, type OrganizationWorkspaceItem, POOL_ERROR_CODES, POOL_NOTE_MAX_LENGTH, PerUserLimitsView, type PoolErrorCode, type PoolValueValidation, type Price, type PriceMetadata, type ProductsResponse, ProfileAvatar, ProfileTab, REMOVE_REASON_MAX_LENGTH, ReferralTab, RemovePoolMemberModal, type RemovePoolMemberModalProps, SCOPE_LABELS, ScopeHeader, type ScopeHeaderProps, type ServiceItem, type SettingsAdapters, SettingsAdaptersProvider, type SettingsApiAdapter, type SettingsAuthAdapter, type SettingsAuthUser, SettingsContent, type SettingsContentProps, type SettingsImageUploadAdapter, type SettingsIntegrationsAdapter, type SettingsNavigationAdapter, type SettingsOrganizationAdapter, type SettingsOrganizationCreateOptions, type SettingsOrganizationCurrent, type SettingsOrganizationJoinableItem, type SettingsOrganizationListItem, type SettingsOrganizationType, type SettingsScope, type SettingsTab, type SettingsToastAdapter, SettingsTooltip, type SettingsTooltipPlacement, type SettingsTooltipProps, type SettingsWorkspaceAdapter, type SettingsWorkspaceCurrent, type SettingsWorkspaceListItem, SharedCreditsView, type SharedPoolBalance, 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 UpdateCreditPoolMemberInput, 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 WorkspacePickerGroup, type WorkspacePickerItem, type WorkspacePickerProps, WorkspaceTab, type WorkspaceUsageResponse, belowUsageReason, canShowSharedCredits, cleanCreditLimitInput, cleanPoolValueInput, countLabel, creditCycleUnit, creditsMeter, deriveWorkspaceInvitePermission, describeAllowance, describeDefaultLimit, describeMemberLimit, findBillingError, findPoolError, formatCreditLimit, formatCredits, formatPoolSplit, groupWorkspaceList, isLimitBelowUsage, joinMembersWithLimits, lastUsedLabel, limitBelowUsageUsed, limitSourceLabel, loadWorkspaceOrganization, memberRoleUpdate, minimumCreditLimit, ownCreditsFirstText, pausedOwnCreditsText, poolErrorMessage, poolWorkspaceName, readSettingsCaller, rememberSettingsCaller, resolveSettingsTabFromPath, roundCredits, settingsReturnTarget, sharedPoolBannerText, useSettingsAdapters, useWorkspaceInvitePermission, useWorkspaceOrganization, usedByNamesFrom, usedByWorkspaceKey, userLanguageLabel, validateCreditLimitInput, validateDefaultCreditLimitInput, validatePoolText, validatePoolValue };
2334
+ export { APP_KEYS, type AddCreditPoolMemberInput, AdministrationOverview, type AdministrationOverviewProps, type AdministrativeNavigationOptions, type ApiToken, type AppKey, AppSettingsHeader, type AppSettingsHeaderProps, AppSettingsPage, type AppSettingsPageProps, AppSettingsRow, type AppSettingsRowProps, AppSettingsSection, type AppSettingsSectionProps, type AvailableCreditsResponse, BillingContent, type BillingEventPayload, type BillingProduct, type BillingSubscription, BuyCreditsModal, type BuyCreditsPayload, type BuyCreditsResponse, ConfirmDeleteModal, ConfirmLeaveModal, type ConnectedApp, ConnectedAppsContent, type CreatedToken, type CreditCycleUnit, type CreditLimitValidation, type CreditPoolCandidate, type CreditPoolFormerMember, type CreditPoolMember, CreditPoolMemberModal, type CreditPoolMemberModalProps, type CreditPoolMemberStatus, type CreditPoolMode, type CreditPoolResponse, type CreditsMeter, type CustomCreditInfo, type CustomCreditResponse, DEFAULT_PERMISSIONS, DEFAULT_USER_LANGUAGE, ExportDateModal, InviteUpdateModal, type InvoicePdfResponse, type MemberCreditLimit, MemberCreditLimitModal, type MemberCreditLimitRow, type MemberCreditLimitSource, type MemberCreditLimitsResponse, MemberListFrame, type MemberListFrameProps, type MemberListTab, MemberListToolbar, type MemberListToolbarProps, type MemberPermissionsPayload, OrganizationContent, type OrganizationData, type OrganizationDetails, OrganizationIntegrationsContent, type OrganizationInvite, type OrganizationJoinRequest, type OrganizationJoinableWorkspace, type OrganizationMember, OrganizationMemberModal, type OrganizationMemberModalProps, type OrganizationMemberRole, OrganizationModal, OrganizationPicker, type OrganizationPickerItem, type OrganizationPickerProps, type OrganizationPickerType, type OrganizationView, type OrganizationWorkspaceItem, POOL_ERROR_CODES, POOL_EXPIRY_MAX_MONTHS, POOL_NOTE_MAX_LENGTH, PerUserLimitsView, type PoolEndsAtResult, type PoolErrorCode, type PoolExpiryChoice, type PoolValueValidation, type Price, type PriceMetadata, type ProductsResponse, ProfileAvatar, ProfileTab, REMOVE_REASON_MAX_LENGTH, ReferralTab, RemovePoolMemberModal, type RemovePoolMemberModalProps, SCOPE_LABELS, ScopeHeader, type ScopeHeaderProps, type ServiceItem, type SettingsAdapters, SettingsAdaptersProvider, type SettingsApiAdapter, type SettingsAuthAdapter, type SettingsAuthUser, SettingsContent, type SettingsContentProps, type SettingsImageUploadAdapter, type SettingsIntegrationsAdapter, type SettingsNavigationAdapter, type SettingsOrganizationAdapter, type SettingsOrganizationCreateOptions, type SettingsOrganizationCurrent, type SettingsOrganizationJoinableItem, type SettingsOrganizationListItem, type SettingsOrganizationType, type SettingsScope, type SettingsTab, type SettingsToastAdapter, SettingsTooltip, type SettingsTooltipPlacement, type SettingsTooltipProps, type SettingsWorkspaceAdapter, type SettingsWorkspaceCurrent, type SettingsWorkspaceListItem, SharedCreditsView, type SharedPoolBalance, 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 UpdateCreditPoolMemberInput, 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 WorkspacePickerGroup, type WorkspacePickerItem, type WorkspacePickerProps, WorkspaceTab, type WorkspaceUsageResponse, belowUsageReason, canShowSharedCredits, cleanCreditLimitInput, cleanPoolValueInput, countLabel, creditCycleUnit, creditsMeter, deriveWorkspaceInvitePermission, describeAllowance, describeDefaultLimit, describeMemberLimit, expiryChoiceFor, findBillingError, findPoolError, formatCreditLimit, formatCredits, formatPoolEndsAt, formatPoolSplit, groupWorkspaceList, isLimitBelowUsage, joinMembersWithLimits, lastUsedLabel, limitBelowUsageUsed, limitSourceLabel, loadWorkspaceOrganization, memberRoleUpdate, minPoolEndDate, minimumCreditLimit, ownCreditsFirstText, pausedOwnCreditsText, poolEndsAtFrom, poolErrorMessage, poolRenewsAtCycleEnd, poolWorkspaceName, readSettingsCaller, rememberSettingsCaller, resolveSettingsTabFromPath, roundCredits, settingsReturnTarget, sharedPoolBannerText, sharedPoolEndsText, toLocalDateInput, useSettingsAdapters, useWorkspaceInvitePermission, useWorkspaceOrganization, usedByNamesFrom, usedByWorkspaceKey, userLanguageLabel, validateCreditLimitInput, validateDefaultCreditLimitInput, validatePoolText, validatePoolValue };
package/dist/index.d.ts CHANGED
@@ -673,7 +673,9 @@ interface SharedPoolBalance {
673
673
  remaining: number;
674
674
  /** When the sponsor's billing cycle ends and the allowance starts again. */
675
675
  cycleEnd: string;
676
- /** The member's own credits: still its own, but paused while it is a member. */
676
+ /** When the share stops on its own (ISO); null or absent when it runs until removed. */
677
+ endsAt?: string | null;
678
+ /** The member's own credits, spent before the pool's. */
677
679
  ownCredits?: number;
678
680
  }
679
681
  interface AvailableCreditsResponse {
@@ -894,6 +896,8 @@ interface CreditPoolMember {
894
896
  end: string;
895
897
  base: number;
896
898
  };
899
+ /** When the share stops on its own (ISO); null or absent when it runs until removed. */
900
+ endsAt?: string | null;
897
901
  note?: string;
898
902
  requestedBy?: string;
899
903
  createdBy?: string;
@@ -926,10 +930,14 @@ interface AddCreditPoolMemberInput {
926
930
  value: number;
927
931
  /** Why — required, at most 500 characters. */
928
932
  note: string;
933
+ /** When the share stops on its own (an ISO date in the future); null or absent for never. */
934
+ endsAt?: string | null;
929
935
  }
930
936
  interface UpdateCreditPoolMemberInput {
931
937
  mode: CreditPoolMode;
932
938
  value: number;
939
+ /** A new end date (ISO, in the future), or null to clear it; absent leaves it as it is. */
940
+ endsAt?: string | null;
933
941
  }
934
942
  /** A workspace credits can be shared with (another workspace of the organization). */
935
943
  interface CreditPoolCandidate {
@@ -2082,18 +2090,63 @@ declare function formatPoolSplit(split: {
2082
2090
  }): string;
2083
2091
  /** "20% · 200 credits this cycle", or "200 credits this cycle" for a fixed share. */
2084
2092
  declare function describeAllowance(member: Pick<CreditPoolMember, "mode" | "value" | "allowance">): string;
2093
+ /**
2094
+ * When a share stops on its own, as the Ends field picks it. `never` is
2095
+ * today's behaviour: the share renews every cycle until it is removed.
2096
+ */
2097
+ type PoolExpiryChoice = {
2098
+ kind: "never";
2099
+ } | {
2100
+ kind: "month";
2101
+ } | {
2102
+ kind: "months";
2103
+ months: number;
2104
+ } | {
2105
+ kind: "year";
2106
+ }
2107
+ /** A local calendar day, `YYYY-MM-DD` (what `<input type="date">` gives). */
2108
+ | {
2109
+ kind: "date";
2110
+ date: string;
2111
+ };
2112
+ declare const POOL_EXPIRY_MAX_MONTHS = 120;
2113
+ /** A local calendar day as `YYYY-MM-DD`. */
2114
+ declare function toLocalDateInput(date: Date): string;
2115
+ /** The earliest day the date picker allows: tomorrow, in local time. */
2116
+ declare function minPoolEndDate(now?: Date): string;
2117
+ type PoolEndsAtResult = {
2118
+ ok: true;
2119
+ value: string | null;
2120
+ } | {
2121
+ ok: false;
2122
+ reason: string;
2123
+ };
2124
+ /**
2125
+ * The `endsAt` to send for a choice, as an ISO string, or `null` for Never.
2126
+ * Months and a year count from `now`; a date is the start of that day in the
2127
+ * admin's local time and must be after today.
2128
+ */
2129
+ declare function poolEndsAtFrom(choice: PoolExpiryChoice, now?: Date): PoolEndsAtResult;
2130
+ /** The Ends field's starting choice for a share: Never, or its end date's day. */
2131
+ declare function expiryChoiceFor(member: Pick<CreditPoolMember, "endsAt"> | null | undefined): PoolExpiryChoice;
2132
+ /** When a share ends: "Mar 1, 2027", or "Never" when it runs until removed. */
2133
+ declare function formatPoolEndsAt(endsAt: string | null | undefined): string;
2134
+ /** The member banner's line on when sharing stops: "Sharing ends on Mar 1, 2027." or "Sharing ends: Never." */
2135
+ declare function sharedPoolEndsText(pool: Pick<SharedPoolBalance, "endsAt">): string;
2136
+ /** Whether the allowance starts again at `cycleEnd`: not when the share has ended by then. */
2137
+ declare function poolRenewsAtCycleEnd(pool: Pick<SharedPoolBalance, "endsAt" | "cycleEnd">): boolean;
2085
2138
  /** The other workspace's name, or a stand-in when it could not be resolved. */
2086
2139
  declare function poolWorkspaceName(name: string | null | undefined): string;
2087
2140
  /** The member's Billing banner: "Credits shared by Agency: 80 of 200 left this cycle". */
2088
2141
  declare function sharedPoolBannerText(pool: Pick<SharedPoolBalance, "sponsorName" | "allowance" | "remaining">): string;
2089
2142
  /**
2090
- * The line under the member's banner when its own credits could not be spent
2091
- * with the pool's. Billing now spends them first (see `ownCreditsFirstText`);
2092
- * kept for hosts that still show it.
2143
+ * Was the line saying a member's own credits were paused while it used the
2144
+ * pool's. Billing spends them first, so it now answers `ownCreditsFirstText`
2145
+ * for hosts that still call it.
2093
2146
  *
2094
2147
  * @deprecated Use `ownCreditsFirstText`.
2095
2148
  */
2096
- declare function pausedOwnCreditsText(pool: Pick<SharedPoolBalance, "ownCredits">): string;
2149
+ declare function pausedOwnCreditsText(_pool?: Pick<SharedPoolBalance, "ownCredits">): string;
2097
2150
  /** The line under the member's banner: whose credits a job spends, in order. */
2098
2151
  declare function ownCreditsFirstText(): string;
2099
2152
  interface CreditsMeter {
@@ -2278,4 +2331,4 @@ interface AppSettingsRowProps {
2278
2331
  }
2279
2332
  declare function AppSettingsRow({ title, description, summary, href, onClick, disabled, disabledReason, ariaLabel, trailing }: AppSettingsRowProps): react.JSX.Element;
2280
2333
 
2281
- export { APP_KEYS, type AddCreditPoolMemberInput, AdministrationOverview, type AdministrationOverviewProps, type AdministrativeNavigationOptions, type ApiToken, type AppKey, AppSettingsHeader, type AppSettingsHeaderProps, AppSettingsPage, type AppSettingsPageProps, AppSettingsRow, type AppSettingsRowProps, AppSettingsSection, type AppSettingsSectionProps, type AvailableCreditsResponse, BillingContent, type BillingEventPayload, type BillingProduct, type BillingSubscription, BuyCreditsModal, type BuyCreditsPayload, type BuyCreditsResponse, ConfirmDeleteModal, ConfirmLeaveModal, type ConnectedApp, ConnectedAppsContent, type CreatedToken, type CreditCycleUnit, type CreditLimitValidation, type CreditPoolCandidate, type CreditPoolFormerMember, type CreditPoolMember, CreditPoolMemberModal, type CreditPoolMemberModalProps, type CreditPoolMemberStatus, type CreditPoolMode, type CreditPoolResponse, type CreditsMeter, type CustomCreditInfo, type CustomCreditResponse, DEFAULT_PERMISSIONS, DEFAULT_USER_LANGUAGE, ExportDateModal, InviteUpdateModal, type InvoicePdfResponse, type MemberCreditLimit, MemberCreditLimitModal, type MemberCreditLimitRow, type MemberCreditLimitSource, type MemberCreditLimitsResponse, MemberListFrame, type MemberListFrameProps, type MemberListTab, MemberListToolbar, type MemberListToolbarProps, type MemberPermissionsPayload, OrganizationContent, type OrganizationData, type OrganizationDetails, OrganizationIntegrationsContent, type OrganizationInvite, type OrganizationJoinRequest, type OrganizationJoinableWorkspace, type OrganizationMember, OrganizationMemberModal, type OrganizationMemberModalProps, type OrganizationMemberRole, OrganizationModal, OrganizationPicker, type OrganizationPickerItem, type OrganizationPickerProps, type OrganizationPickerType, type OrganizationView, type OrganizationWorkspaceItem, POOL_ERROR_CODES, POOL_NOTE_MAX_LENGTH, PerUserLimitsView, type PoolErrorCode, type PoolValueValidation, type Price, type PriceMetadata, type ProductsResponse, ProfileAvatar, ProfileTab, REMOVE_REASON_MAX_LENGTH, ReferralTab, RemovePoolMemberModal, type RemovePoolMemberModalProps, SCOPE_LABELS, ScopeHeader, type ScopeHeaderProps, type ServiceItem, type SettingsAdapters, SettingsAdaptersProvider, type SettingsApiAdapter, type SettingsAuthAdapter, type SettingsAuthUser, SettingsContent, type SettingsContentProps, type SettingsImageUploadAdapter, type SettingsIntegrationsAdapter, type SettingsNavigationAdapter, type SettingsOrganizationAdapter, type SettingsOrganizationCreateOptions, type SettingsOrganizationCurrent, type SettingsOrganizationJoinableItem, type SettingsOrganizationListItem, type SettingsOrganizationType, type SettingsScope, type SettingsTab, type SettingsToastAdapter, SettingsTooltip, type SettingsTooltipPlacement, type SettingsTooltipProps, type SettingsWorkspaceAdapter, type SettingsWorkspaceCurrent, type SettingsWorkspaceListItem, SharedCreditsView, type SharedPoolBalance, 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 UpdateCreditPoolMemberInput, 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 WorkspacePickerGroup, type WorkspacePickerItem, type WorkspacePickerProps, WorkspaceTab, type WorkspaceUsageResponse, belowUsageReason, canShowSharedCredits, cleanCreditLimitInput, cleanPoolValueInput, countLabel, creditCycleUnit, creditsMeter, deriveWorkspaceInvitePermission, describeAllowance, describeDefaultLimit, describeMemberLimit, findBillingError, findPoolError, formatCreditLimit, formatCredits, formatPoolSplit, groupWorkspaceList, isLimitBelowUsage, joinMembersWithLimits, lastUsedLabel, limitBelowUsageUsed, limitSourceLabel, loadWorkspaceOrganization, memberRoleUpdate, minimumCreditLimit, ownCreditsFirstText, pausedOwnCreditsText, poolErrorMessage, poolWorkspaceName, readSettingsCaller, rememberSettingsCaller, resolveSettingsTabFromPath, roundCredits, settingsReturnTarget, sharedPoolBannerText, useSettingsAdapters, useWorkspaceInvitePermission, useWorkspaceOrganization, usedByNamesFrom, usedByWorkspaceKey, userLanguageLabel, validateCreditLimitInput, validateDefaultCreditLimitInput, validatePoolText, validatePoolValue };
2334
+ export { APP_KEYS, type AddCreditPoolMemberInput, AdministrationOverview, type AdministrationOverviewProps, type AdministrativeNavigationOptions, type ApiToken, type AppKey, AppSettingsHeader, type AppSettingsHeaderProps, AppSettingsPage, type AppSettingsPageProps, AppSettingsRow, type AppSettingsRowProps, AppSettingsSection, type AppSettingsSectionProps, type AvailableCreditsResponse, BillingContent, type BillingEventPayload, type BillingProduct, type BillingSubscription, BuyCreditsModal, type BuyCreditsPayload, type BuyCreditsResponse, ConfirmDeleteModal, ConfirmLeaveModal, type ConnectedApp, ConnectedAppsContent, type CreatedToken, type CreditCycleUnit, type CreditLimitValidation, type CreditPoolCandidate, type CreditPoolFormerMember, type CreditPoolMember, CreditPoolMemberModal, type CreditPoolMemberModalProps, type CreditPoolMemberStatus, type CreditPoolMode, type CreditPoolResponse, type CreditsMeter, type CustomCreditInfo, type CustomCreditResponse, DEFAULT_PERMISSIONS, DEFAULT_USER_LANGUAGE, ExportDateModal, InviteUpdateModal, type InvoicePdfResponse, type MemberCreditLimit, MemberCreditLimitModal, type MemberCreditLimitRow, type MemberCreditLimitSource, type MemberCreditLimitsResponse, MemberListFrame, type MemberListFrameProps, type MemberListTab, MemberListToolbar, type MemberListToolbarProps, type MemberPermissionsPayload, OrganizationContent, type OrganizationData, type OrganizationDetails, OrganizationIntegrationsContent, type OrganizationInvite, type OrganizationJoinRequest, type OrganizationJoinableWorkspace, type OrganizationMember, OrganizationMemberModal, type OrganizationMemberModalProps, type OrganizationMemberRole, OrganizationModal, OrganizationPicker, type OrganizationPickerItem, type OrganizationPickerProps, type OrganizationPickerType, type OrganizationView, type OrganizationWorkspaceItem, POOL_ERROR_CODES, POOL_EXPIRY_MAX_MONTHS, POOL_NOTE_MAX_LENGTH, PerUserLimitsView, type PoolEndsAtResult, type PoolErrorCode, type PoolExpiryChoice, type PoolValueValidation, type Price, type PriceMetadata, type ProductsResponse, ProfileAvatar, ProfileTab, REMOVE_REASON_MAX_LENGTH, ReferralTab, RemovePoolMemberModal, type RemovePoolMemberModalProps, SCOPE_LABELS, ScopeHeader, type ScopeHeaderProps, type ServiceItem, type SettingsAdapters, SettingsAdaptersProvider, type SettingsApiAdapter, type SettingsAuthAdapter, type SettingsAuthUser, SettingsContent, type SettingsContentProps, type SettingsImageUploadAdapter, type SettingsIntegrationsAdapter, type SettingsNavigationAdapter, type SettingsOrganizationAdapter, type SettingsOrganizationCreateOptions, type SettingsOrganizationCurrent, type SettingsOrganizationJoinableItem, type SettingsOrganizationListItem, type SettingsOrganizationType, type SettingsScope, type SettingsTab, type SettingsToastAdapter, SettingsTooltip, type SettingsTooltipPlacement, type SettingsTooltipProps, type SettingsWorkspaceAdapter, type SettingsWorkspaceCurrent, type SettingsWorkspaceListItem, SharedCreditsView, type SharedPoolBalance, 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 UpdateCreditPoolMemberInput, 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 WorkspacePickerGroup, type WorkspacePickerItem, type WorkspacePickerProps, WorkspaceTab, type WorkspaceUsageResponse, belowUsageReason, canShowSharedCredits, cleanCreditLimitInput, cleanPoolValueInput, countLabel, creditCycleUnit, creditsMeter, deriveWorkspaceInvitePermission, describeAllowance, describeDefaultLimit, describeMemberLimit, expiryChoiceFor, findBillingError, findPoolError, formatCreditLimit, formatCredits, formatPoolEndsAt, formatPoolSplit, groupWorkspaceList, isLimitBelowUsage, joinMembersWithLimits, lastUsedLabel, limitBelowUsageUsed, limitSourceLabel, loadWorkspaceOrganization, memberRoleUpdate, minPoolEndDate, minimumCreditLimit, ownCreditsFirstText, pausedOwnCreditsText, poolEndsAtFrom, poolErrorMessage, poolRenewsAtCycleEnd, poolWorkspaceName, readSettingsCaller, rememberSettingsCaller, resolveSettingsTabFromPath, roundCredits, settingsReturnTarget, sharedPoolBannerText, sharedPoolEndsText, toLocalDateInput, useSettingsAdapters, useWorkspaceInvitePermission, useWorkspaceOrganization, usedByNamesFrom, usedByWorkspaceKey, userLanguageLabel, validateCreditLimitInput, validateDefaultCreditLimitInput, validatePoolText, validatePoolValue };
package/dist/index.js CHANGED
@@ -45,6 +45,7 @@ __export(index_exports, {
45
45
  OrganizationModal: () => OrganizationModal,
46
46
  OrganizationPicker: () => OrganizationPicker,
47
47
  POOL_ERROR_CODES: () => POOL_ERROR_CODES,
48
+ POOL_EXPIRY_MAX_MONTHS: () => POOL_EXPIRY_MAX_MONTHS,
48
49
  POOL_NOTE_MAX_LENGTH: () => POOL_NOTE_MAX_LENGTH,
49
50
  PerUserLimitsView: () => PerUserLimitsView,
50
51
  ProfileAvatar: () => ProfileAvatar,
@@ -84,10 +85,12 @@ __export(index_exports, {
84
85
  describeAllowance: () => describeAllowance,
85
86
  describeDefaultLimit: () => describeDefaultLimit,
86
87
  describeMemberLimit: () => describeMemberLimit,
88
+ expiryChoiceFor: () => expiryChoiceFor,
87
89
  findBillingError: () => findBillingError,
88
90
  findPoolError: () => findPoolError,
89
91
  formatCreditLimit: () => formatCreditLimit,
90
92
  formatCredits: () => formatCredits,
93
+ formatPoolEndsAt: () => formatPoolEndsAt,
91
94
  formatPoolSplit: () => formatPoolSplit,
92
95
  groupWorkspaceList: () => groupWorkspaceList,
93
96
  isLimitBelowUsage: () => isLimitBelowUsage,
@@ -97,10 +100,13 @@ __export(index_exports, {
97
100
  limitSourceLabel: () => limitSourceLabel,
98
101
  loadWorkspaceOrganization: () => loadWorkspaceOrganization,
99
102
  memberRoleUpdate: () => memberRoleUpdate,
103
+ minPoolEndDate: () => minPoolEndDate,
100
104
  minimumCreditLimit: () => minimumCreditLimit,
101
105
  ownCreditsFirstText: () => ownCreditsFirstText,
102
106
  pausedOwnCreditsText: () => pausedOwnCreditsText,
107
+ poolEndsAtFrom: () => poolEndsAtFrom,
103
108
  poolErrorMessage: () => poolErrorMessage,
109
+ poolRenewsAtCycleEnd: () => poolRenewsAtCycleEnd,
104
110
  poolWorkspaceName: () => poolWorkspaceName,
105
111
  readSettingsCaller: () => readSettingsCaller,
106
112
  rememberSettingsCaller: () => rememberSettingsCaller,
@@ -108,6 +114,8 @@ __export(index_exports, {
108
114
  roundCredits: () => roundCredits,
109
115
  settingsReturnTarget: () => settingsReturnTarget,
110
116
  sharedPoolBannerText: () => sharedPoolBannerText,
117
+ sharedPoolEndsText: () => sharedPoolEndsText,
118
+ toLocalDateInput: () => toLocalDateInput,
111
119
  useSettingsAdapters: () => useSettingsAdapters,
112
120
  useWorkspaceInvitePermission: () => useWorkspaceInvitePermission,
113
121
  useWorkspaceOrganization: () => useWorkspaceOrganization,
@@ -9655,6 +9663,76 @@ function describeAllowance(member) {
9655
9663
  const allowance = `${credits(member.allowance)} this cycle`;
9656
9664
  return member.mode === "percent" ? `${formatPoolSplit(member)} \xB7 ${allowance}` : allowance;
9657
9665
  }
9666
+ var POOL_EXPIRY_MAX_MONTHS = 120;
9667
+ function addCalendarMonths(date, months) {
9668
+ const target = new Date(date.getFullYear(), date.getMonth() + months, 1);
9669
+ const lastDay = new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate();
9670
+ return new Date(
9671
+ target.getFullYear(),
9672
+ target.getMonth(),
9673
+ Math.min(date.getDate(), lastDay),
9674
+ date.getHours(),
9675
+ date.getMinutes(),
9676
+ date.getSeconds(),
9677
+ date.getMilliseconds()
9678
+ );
9679
+ }
9680
+ var pad2 = (n) => String(n).padStart(2, "0");
9681
+ function toLocalDateInput(date) {
9682
+ return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
9683
+ }
9684
+ function minPoolEndDate(now = /* @__PURE__ */ new Date()) {
9685
+ return toLocalDateInput(new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1));
9686
+ }
9687
+ function parseLocalDate(value) {
9688
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
9689
+ if (!match) return null;
9690
+ const [year, month, day] = [Number(match[1]), Number(match[2]) - 1, Number(match[3])];
9691
+ const date = new Date(year, month, day);
9692
+ return date.getFullYear() === year && date.getMonth() === month && date.getDate() === day ? date : null;
9693
+ }
9694
+ function poolEndsAtFrom(choice, now = /* @__PURE__ */ new Date()) {
9695
+ switch (choice.kind) {
9696
+ case "never":
9697
+ return { ok: true, value: null };
9698
+ case "month":
9699
+ return { ok: true, value: addCalendarMonths(now, 1).toISOString() };
9700
+ case "year":
9701
+ return { ok: true, value: addCalendarMonths(now, 12).toISOString() };
9702
+ case "months": {
9703
+ const { months } = choice;
9704
+ if (!Number.isInteger(months) || months < 1 || months > POOL_EXPIRY_MAX_MONTHS) {
9705
+ return { ok: false, reason: `Enter a whole number of months from 1 to ${POOL_EXPIRY_MAX_MONTHS}` };
9706
+ }
9707
+ return { ok: true, value: addCalendarMonths(now, months).toISOString() };
9708
+ }
9709
+ case "date": {
9710
+ if (!choice.date) return { ok: false, reason: "Choose a date" };
9711
+ const date = parseLocalDate(choice.date);
9712
+ if (!date) return { ok: false, reason: "Choose a valid date" };
9713
+ if (choice.date < minPoolEndDate(now)) return { ok: false, reason: "Choose a date after today" };
9714
+ return { ok: true, value: date.toISOString() };
9715
+ }
9716
+ }
9717
+ }
9718
+ function expiryChoiceFor(member) {
9719
+ const endsAt = member?.endsAt ? new Date(member.endsAt) : null;
9720
+ if (!endsAt || !Number.isFinite(endsAt.getTime())) return { kind: "never" };
9721
+ return { kind: "date", date: toLocalDateInput(endsAt) };
9722
+ }
9723
+ function formatPoolEndsAt(endsAt) {
9724
+ const date = endsAt ? new Date(endsAt) : null;
9725
+ if (!date || !Number.isFinite(date.getTime())) return "Never";
9726
+ return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
9727
+ }
9728
+ function sharedPoolEndsText(pool) {
9729
+ return pool.endsAt ? `Sharing ends on ${formatPoolEndsAt(pool.endsAt)}.` : "Sharing ends: Never.";
9730
+ }
9731
+ function poolRenewsAtCycleEnd(pool) {
9732
+ if (!pool.cycleEnd) return false;
9733
+ if (!pool.endsAt) return true;
9734
+ return new Date(pool.endsAt).getTime() > new Date(pool.cycleEnd).getTime();
9735
+ }
9658
9736
  function poolWorkspaceName(name) {
9659
9737
  return name?.trim() || "Unknown workspace";
9660
9738
  }
@@ -9662,12 +9740,8 @@ function sharedPoolBannerText(pool) {
9662
9740
  const sponsor = pool.sponsorName?.trim() || "another workspace";
9663
9741
  return `Credits shared by ${sponsor}: ${formatCredits(pool.remaining)} of ${formatCredits(pool.allowance)} left this cycle`;
9664
9742
  }
9665
- function pausedOwnCreditsText(pool) {
9666
- const own = pool.ownCredits;
9667
- if (typeof own !== "number" || !Number.isFinite(own)) {
9668
- return "This workspace spends these shared credits, not its own; credits bought here can't be used while it does.";
9669
- }
9670
- return `This workspace's own ${credits(own)} ${own === 1 ? "is" : "are"} paused while it uses shared credits. They stay with it and can be used again once sharing stops.`;
9743
+ function pausedOwnCreditsText(_pool) {
9744
+ return ownCreditsFirstText();
9671
9745
  }
9672
9746
  function ownCreditsFirstText() {
9673
9747
  return "This workspace uses its own credits first, then these shared credits.";
@@ -9862,25 +9936,36 @@ function PoolModalShell({
9862
9936
  }
9863
9937
  );
9864
9938
  }
9865
- function PoolWarning({ children }) {
9866
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
9867
- "div",
9868
- {
9869
- role: "alert",
9870
- className: "w-full",
9871
- style: {
9872
- ...modalBodyText,
9873
- color: T16.primary,
9874
- background: T16.warningFill,
9875
- border: `1px solid ${T16.warningBorder}`,
9876
- borderRadius: T16.r3xl,
9877
- padding: `${T16.spXl} ${T16.sp3xl}`
9878
- },
9879
- children
9880
- }
9881
- );
9882
- }
9883
9939
  var fieldLabel = { ...modalBodyText, fontWeight: 500, color: T16.primary };
9940
+ var fieldBox = {
9941
+ background: T16.fillPlus,
9942
+ height: 48,
9943
+ borderRadius: T16.r3xl,
9944
+ padding: `${T16.spXl} ${T16.sp3xl}`,
9945
+ gap: T16.spXl
9946
+ };
9947
+ var bareInput = {
9948
+ ...modalBodyText,
9949
+ background: "transparent",
9950
+ border: "none",
9951
+ outline: "none",
9952
+ color: T16.primary,
9953
+ padding: 0
9954
+ };
9955
+ var EXPIRY_OPTIONS = [
9956
+ { id: "never", label: "Never" },
9957
+ { id: "month", label: "In 1 month" },
9958
+ { id: "months", label: "In a number of months" },
9959
+ { id: "year", label: "In 1 year" },
9960
+ { id: "date", label: "On a date" }
9961
+ ];
9962
+ var isExpiryKind = (id) => EXPIRY_OPTIONS.some((o) => o.id === id);
9963
+ var endsText = (member) => member.endsAt ? `, until ${formatPoolEndsAt(member.endsAt)}` : "";
9964
+ function endsAtHelp(kind, endsAt) {
9965
+ if (kind === "never") return "Shares every billing cycle until it is removed.";
9966
+ const stop = "After that, the other workspace spends only its own credits.";
9967
+ return endsAt ? `Sharing stops on ${formatPoolEndsAt(endsAt)}. ${stop}` : stop;
9968
+ }
9884
9969
  function CreditPoolMemberModal(props) {
9885
9970
  const { toast } = useSettingsAdapters();
9886
9971
  const editing = props.kind === "edit" ? props.member : null;
@@ -9891,17 +9976,28 @@ function CreditPoolMemberModal(props) {
9891
9976
  const [touched, setTouched] = (0, import_react20.useState)(false);
9892
9977
  const [saving, setSaving] = (0, import_react20.useState)(false);
9893
9978
  const [serverError, setServerError] = (0, import_react20.useState)(null);
9979
+ const initialExpiry = expiryChoiceFor(editing);
9980
+ const [expiryKind, setExpiryKind] = (0, import_react20.useState)(initialExpiry.kind);
9981
+ const [monthsRaw, setMonthsRaw] = (0, import_react20.useState)("3");
9982
+ const [endDate, setEndDate] = (0, import_react20.useState)(initialExpiry.kind === "date" ? initialExpiry.date : "");
9894
9983
  (0, import_react20.useEffect)(() => {
9895
9984
  setServerError(null);
9896
- }, [memberId, mode, raw, note]);
9985
+ }, [memberId, mode, raw, note, expiryKind, monthsRaw, endDate]);
9897
9986
  const value = validatePoolValue(mode, raw);
9898
9987
  const noteCheck = validatePoolText(note, POOL_NOTE_MAX_LENGTH);
9899
- const changed = !editing || mode !== editing.mode || !value.ok || value.value !== editing.value;
9900
- const valid = value.ok && (props.kind === "edit" || Boolean(memberId) && noteCheck.ok);
9988
+ const expiry = expiryKind === "months" ? { kind: "months", months: monthsRaw.trim() ? Number(monthsRaw) : NaN } : expiryKind === "date" ? { kind: "date", date: endDate } : { kind: expiryKind };
9989
+ const endsAtUnchanged = Boolean(editing) && expiry.kind === initialExpiry.kind && (expiry.kind !== "date" || initialExpiry.kind === "date" && expiry.date === initialExpiry.date);
9990
+ const endsAt = endsAtUnchanged ? null : poolEndsAtFrom(expiry);
9991
+ const splitChanged = !editing || mode !== editing.mode || !value.ok || value.value !== editing.value;
9992
+ const changed = splitChanged || !endsAtUnchanged;
9993
+ const valid = value.ok && (endsAt === null || endsAt.ok) && (props.kind === "edit" || Boolean(memberId) && noteCheck.ok);
9901
9994
  const canSave = !saving && changed && valid;
9902
9995
  const valueHint = touched && !value.ok ? value.reason : null;
9996
+ const expiryHint = endsAt && !endsAt.ok && !(expiry.kind === "date" && !expiry.date) ? endsAt.reason : null;
9903
9997
  const handleSave = async () => {
9904
9998
  if (!canSave || !value.ok) return;
9999
+ const sendEndsAt = endsAtUnchanged ? null : poolEndsAtFrom(expiry);
10000
+ if (sendEndsAt && !sendEndsAt.ok) return;
9905
10001
  setSaving(true);
9906
10002
  try {
9907
10003
  if (props.kind === "add") {
@@ -9909,12 +10005,17 @@ function CreditPoolMemberModal(props) {
9909
10005
  memberWorkspaceId: memberId,
9910
10006
  mode,
9911
10007
  value: value.value,
9912
- note: noteCheck.ok ? noteCheck.value : ""
10008
+ note: noteCheck.ok ? noteCheck.value : "",
10009
+ ...sendEndsAt?.value ? { endsAt: sendEndsAt.value } : {}
9913
10010
  });
9914
- toast.success(`Credits shared. ${describeAllowance(added)}.`);
10011
+ toast.success(`Credits shared. ${describeAllowance(added)}${endsText(added)}.`);
9915
10012
  } else {
9916
- const updated = await props.onSave({ mode, value: value.value });
9917
- toast.success(`Share updated. ${describeAllowance(updated)}.`);
10013
+ const updated = await props.onSave({
10014
+ mode,
10015
+ value: value.value,
10016
+ ...sendEndsAt ? { endsAt: sendEndsAt.value } : {}
10017
+ });
10018
+ toast.success(`Share updated. ${describeAllowance(updated)}${endsText(updated)}.`);
9918
10019
  }
9919
10020
  props.onSaved();
9920
10021
  props.onClose();
@@ -9999,11 +10100,7 @@ function CreditPoolMemberModal(props) {
9999
10100
  {
10000
10101
  className: "flex items-center w-full",
10001
10102
  style: {
10002
- background: T16.fillPlus,
10003
- height: 48,
10004
- borderRadius: T16.r3xl,
10005
- padding: `${T16.spXl} ${T16.sp3xl}`,
10006
- gap: T16.spXl,
10103
+ ...fieldBox,
10007
10104
  border: valueHint ? `1px solid ${T16.error}` : "1px solid transparent"
10008
10105
  },
10009
10106
  children: [
@@ -10021,14 +10118,7 @@ function CreditPoolMemberModal(props) {
10021
10118
  setTouched(true);
10022
10119
  },
10023
10120
  className: "flex-1 min-w-0",
10024
- style: {
10025
- ...modalBodyText,
10026
- background: "transparent",
10027
- border: "none",
10028
- outline: "none",
10029
- color: T16.primary,
10030
- padding: 0
10031
- }
10121
+ style: bareInput
10032
10122
  }
10033
10123
  ),
10034
10124
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "shrink-0", style: { ...modalBodyText, color: T16.muted }, children: mode === "percent" ? "%" : "credits" })
@@ -10036,10 +10126,76 @@ function CreditPoolMemberModal(props) {
10036
10126
  }
10037
10127
  ),
10038
10128
  valueHint ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { role: "alert", style: { ...modalBodyText, color: T16.error }, children: valueHint }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("span", { style: { ...modalBodyText, color: T16.tertiary }, children: [
10039
- mode === "percent" ? "A share of this workspace's plan credits for each billing cycle. Credits stay with this workspace; the other workspace spends them up to this share." : "Credits the other workspace may spend each billing cycle. Credits stay with this workspace until spent.",
10129
+ mode === "percent" ? "A share of this workspace's plan credits for each billing cycle. Credits stay with this workspace; the other workspace spends them up to this share, after its own credits." : "Credits the other workspace may spend each billing cycle, after its own credits. Credits stay with this workspace until spent.",
10040
10130
  editing ? " Applies immediately." : ""
10041
10131
  ] })
10042
10132
  ] }),
10133
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex flex-col w-full", style: { gap: T16.spMd }, children: [
10134
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { style: fieldLabel, children: "Ends" }),
10135
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
10136
+ import_ui14.ScSelect,
10137
+ {
10138
+ value: expiryKind,
10139
+ onChange: (id) => {
10140
+ if (isExpiryKind(id)) setExpiryKind(id);
10141
+ },
10142
+ options: EXPIRY_OPTIONS,
10143
+ disabled: saving
10144
+ }
10145
+ ),
10146
+ expiryKind === "months" && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
10147
+ "div",
10148
+ {
10149
+ className: "flex items-center w-full",
10150
+ style: {
10151
+ ...fieldBox,
10152
+ border: expiryHint ? `1px solid ${T16.error}` : "1px solid transparent"
10153
+ },
10154
+ children: [
10155
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
10156
+ "input",
10157
+ {
10158
+ inputMode: "numeric",
10159
+ "aria-label": "Number of months",
10160
+ "aria-invalid": expiryHint ? true : void 0,
10161
+ placeholder: "Enter a number of months",
10162
+ value: monthsRaw,
10163
+ disabled: saving,
10164
+ onChange: (e) => setMonthsRaw(e.target.value.replace(/\D/g, "").slice(0, 3)),
10165
+ className: "flex-1 min-w-0",
10166
+ style: bareInput
10167
+ }
10168
+ ),
10169
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "shrink-0", style: { ...modalBodyText, color: T16.muted }, children: "months" })
10170
+ ]
10171
+ }
10172
+ ),
10173
+ expiryKind === "date" && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
10174
+ "div",
10175
+ {
10176
+ className: "flex items-center w-full",
10177
+ style: {
10178
+ ...fieldBox,
10179
+ border: expiryHint ? `1px solid ${T16.error}` : "1px solid transparent"
10180
+ },
10181
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
10182
+ "input",
10183
+ {
10184
+ type: "date",
10185
+ "aria-label": "End date",
10186
+ "aria-invalid": expiryHint ? true : void 0,
10187
+ min: minPoolEndDate(),
10188
+ value: endDate,
10189
+ disabled: saving,
10190
+ onChange: (e) => setEndDate(e.target.value),
10191
+ className: "flex-1 min-w-0",
10192
+ style: { ...bareInput, colorScheme: "light dark" }
10193
+ }
10194
+ )
10195
+ }
10196
+ ),
10197
+ expiryHint ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { role: "alert", style: { ...modalBodyText, color: T16.error }, children: expiryHint }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { style: { ...modalBodyText, color: T16.tertiary }, children: endsAtHelp(expiryKind, endsAtUnchanged ? editing?.endsAt ?? null : endsAt?.ok ? endsAt.value : null) })
10198
+ ] }),
10043
10199
  props.kind === "add" && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex flex-col w-full", style: { gap: T16.spXs }, children: [
10044
10200
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { style: fieldLabel, children: "Note" }),
10045
10201
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
@@ -10061,7 +10217,6 @@ function CreditPoolMemberModal(props) {
10061
10217
  POOL_NOTE_MAX_LENGTH
10062
10218
  ] })
10063
10219
  ] }),
10064
- props.kind === "add" && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(PoolWarning, { children: "While it spends shared credits, the other workspace uses only this workspace's credits, not its own." }),
10065
10220
  serverError && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { role: "alert", style: { ...modalBodyText, color: T16.error }, children: serverError })
10066
10221
  ]
10067
10222
  }
@@ -10182,7 +10337,9 @@ var T17 = {
10182
10337
  r3xl: "var(--radius-3xl)",
10183
10338
  rFull: "var(--radius-full)"
10184
10339
  };
10185
- var MEMBER_COLUMNS2 = "minmax(220px, 1fr) 220px 110px 110px 150px";
10340
+ var MEMBER_COLUMNS2 = "minmax(180px, 1fr) 150px 80px 90px 110px 150px";
10341
+ var MEMBER_GAP = 16;
10342
+ var MEMBER_MIN_WIDTH = 880;
10186
10343
  var cellText2 = {
10187
10344
  fontFamily: T17.font,
10188
10345
  fontSize: T17.fsSm,
@@ -10195,7 +10352,7 @@ var tableHeader = (columns, labels) => /* @__PURE__ */ (0, import_jsx_runtime28.
10195
10352
  style: {
10196
10353
  display: "grid",
10197
10354
  gridTemplateColumns: columns,
10198
- gap: 24,
10355
+ gap: MEMBER_GAP,
10199
10356
  padding: "8px 16px",
10200
10357
  fontSize: 11,
10201
10358
  color: T17.muted,
@@ -10219,17 +10376,21 @@ function MemberRow2({
10219
10376
  gridTemplateColumns: MEMBER_COLUMNS2,
10220
10377
  alignItems: "center",
10221
10378
  padding: `10px ${T17.sp3xl}`,
10222
- gap: 24
10379
+ gap: MEMBER_GAP
10223
10380
  },
10224
10381
  children: [
10225
10382
  /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex flex-col min-w-0", style: { gap: 2 }, children: [
10226
10383
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "truncate", style: { ...cellText2, fontWeight: 500 }, children: poolWorkspaceName(member.memberWorkspaceName) }),
10227
10384
  member.note && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "truncate", title: member.note, style: { ...cellText2, color: T17.tertiary }, children: member.note })
10228
10385
  ] }),
10229
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { style: cellText2, children: describeAllowance(member) }),
10386
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex flex-col min-w-0", style: { gap: 2 }, children: [
10387
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { style: cellText2, children: formatPoolSplit({ mode: "fixed", value: member.allowance }) }),
10388
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { style: { ...cellText2, color: T17.tertiary }, children: member.mode === "percent" ? `${formatPoolSplit(member)} \xB7 this cycle` : "this cycle" })
10389
+ ] }),
10230
10390
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { style: cellText2, children: formatCredits(member.used) }),
10231
10391
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { style: { ...cellText2, color: spent ? T17.warning : T17.primary }, children: formatCredits(member.remaining) }),
10232
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex items-center", style: { gap: 8 }, children: [
10392
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { style: { ...cellText2, color: member.endsAt ? T17.primary : T17.muted }, children: formatPoolEndsAt(member.endsAt) }),
10393
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex items-center whitespace-nowrap", style: { gap: 8 }, children: [
10233
10394
  onEdit && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui16.ScButton, { text: "Edit", variant: "secondary", size: "sm", onClick: onEdit }),
10234
10395
  onRemove && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui16.ScButton, { text: "Remove", variant: "secondary", size: "sm", onClick: onRemove })
10235
10396
  ] })
@@ -10355,11 +10516,13 @@ function SharedCreditsView() {
10355
10516
  remaining: sponsor.remaining
10356
10517
  }) }),
10357
10518
  /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { style: { ...cellText2, color: T17.muted }, children: [
10358
- "This workspace spends ",
10519
+ "This workspace uses its own credits first, then",
10520
+ " ",
10359
10521
  poolWorkspaceName(sponsor.sponsorWorkspaceName),
10360
- "'s credits, not its own",
10361
- sponsor.cycle?.end ? `, until its allowance starts again on ${formatDate2(sponsor.cycle.end)}` : "",
10362
- "."
10522
+ "'s, up to its allowance",
10523
+ sponsor.cycle?.end && poolRenewsAtCycleEnd({ cycleEnd: sponsor.cycle.end, endsAt: sponsor.endsAt }) ? `, which starts again on ${formatDate2(sponsor.cycle.end)}` : "",
10524
+ ". ",
10525
+ sharedPoolEndsText(sponsor)
10363
10526
  ] })
10364
10527
  ]
10365
10528
  }
@@ -10371,8 +10534,8 @@ function SharedCreditsView() {
10371
10534
  {
10372
10535
  grow: "cap",
10373
10536
  maxHeight: 420,
10374
- contentMinWidth: 860,
10375
- header: tableHeader(MEMBER_COLUMNS2, ["Workspace", "Allowance", "Used", "Remaining", "Actions"]),
10537
+ contentMinWidth: MEMBER_MIN_WIDTH,
10538
+ header: tableHeader(MEMBER_COLUMNS2, ["Workspace", "Allowance", "Used", "Remaining", "Ends", "Actions"]),
10376
10539
  isEmpty: failed || members.length === 0,
10377
10540
  emptyText: failed ? "Couldn't load shared credits. Try again later." : "This workspace doesn't share its credits with any other workspace.",
10378
10541
  children: members.map((member) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
@@ -12188,7 +12351,8 @@ function BillingContent({
12188
12351
  },
12189
12352
  children: [
12190
12353
  ownCreditsFirstText(),
12191
- sharedPool.cycleEnd ? ` The allowance starts again on ${formatDate3(sharedPool.cycleEnd)}.` : ""
12354
+ poolRenewsAtCycleEnd(sharedPool) ? ` The allowance starts again on ${formatDate3(sharedPool.cycleEnd)}.` : "",
12355
+ ` ${sharedPoolEndsText(sharedPool)}`
12192
12356
  ]
12193
12357
  }
12194
12358
  )
@@ -12436,7 +12600,7 @@ var import_react24 = require("react");
12436
12600
  var import_ui18 = require("@streamoid/ui");
12437
12601
  var import_icons16 = require("@streamoid/icons");
12438
12602
  var import_jsx_runtime30 = require("react/jsx-runtime");
12439
- var EXPIRY_OPTIONS = [
12603
+ var EXPIRY_OPTIONS2 = [
12440
12604
  { label: "30 days", value: "30d" },
12441
12605
  { label: "60 days", value: "60d" },
12442
12606
  { label: "90 days", value: "90d" },
@@ -12627,7 +12791,7 @@ function CreateTokenModal({ onClose, onCreated }) {
12627
12791
  children: "Expiry"
12628
12792
  }
12629
12793
  ) }),
12630
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { style: { display: "flex", gap: "var(--spacing-md)", flexWrap: "wrap" }, children: EXPIRY_OPTIONS.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
12794
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { style: { display: "flex", gap: "var(--spacing-md)", flexWrap: "wrap" }, children: EXPIRY_OPTIONS2.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
12631
12795
  import_ui18.ScButton,
12632
12796
  {
12633
12797
  text: opt.label,
@@ -15220,7 +15384,7 @@ function SettingsContentInner({
15220
15384
  const organizationScopeName = organization.current?.name ?? null;
15221
15385
  const hasOrganization = !!organization.current;
15222
15386
  const activeScope = TAB_SCOPE[activeTab];
15223
- const scopeCrumb = activeScope === "workspace" ? workspaceScopeName ? overviewDetail ? `Workspace \xB7 ${workspaceScopeName}` : workspaceScopeName : SCOPE_LABELS.workspace : activeScope === "organization" && hasOrganization ? organizationScopeName ? overviewDetail ? `Organization \xB7 ${organizationScopeName}` : organizationScopeName : SCOPE_LABELS.organization : null;
15387
+ const scopeCrumb = activeScope === "workspace" ? workspaceScopeName ? overviewDetail ? `Workspace \xB7 ${workspaceScopeName}` : workspaceScopeName : SCOPE_LABELS.workspace : activeScope === "organization" && hasOrganization ? organizationScopeName ? overviewDetail ? `Organization \xB7 ${organizationScopeName}` : organizationScopeName : SCOPE_LABELS.organization : activeScope === "account" && overviewDetail ? SCOPE_LABELS.account : null;
15224
15388
  const leafLabel = activeScope === "organization" && !hasOrganization ? SCOPE_LABELS.organization : TAB_LABELS[activeTab];
15225
15389
  async function handleSaveCurrentWorkspace(name, imageFile, imageRemoved) {
15226
15390
  if (!currentWorkspaceData || updatingWorkspace) return false;
@@ -15466,9 +15630,10 @@ function SettingsContentInner({
15466
15630
  /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "flex items-center flex-1 min-w-0", style: { gap: 8, paddingLeft: 20 }, children: [
15467
15631
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_icons18.SiconSettings, { className: "w-6 h-6 shrink-0", color: "var(--alias-text---icons-primary)" }),
15468
15632
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
15469
- "span",
15633
+ "h1",
15470
15634
  {
15471
15635
  style: {
15636
+ margin: 0,
15472
15637
  fontFamily: "var(--font-family-font-family-body)",
15473
15638
  fontSize: "var(--font-size-font-size-xl)",
15474
15639
  fontWeight: 500,
@@ -15478,7 +15643,7 @@ function SettingsContentInner({
15478
15643
  children: overviewDetail ? "Account and workspace settings" : "Settings"
15479
15644
  }
15480
15645
  ),
15481
- !isOverview && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
15646
+ !isOverview && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("nav", { "aria-label": "Breadcrumb", className: "flex items-center min-w-0", style: { gap: 8 }, children: [
15482
15647
  scopeCrumb && /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
15483
15648
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_icons18.SiconRight, { className: "w-6 h-6 shrink-0", color: "var(--alias-text---icons-primary)" }),
15484
15649
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
package/dist/index.mjs CHANGED
@@ -9601,6 +9601,76 @@ function describeAllowance(member) {
9601
9601
  const allowance = `${credits(member.allowance)} this cycle`;
9602
9602
  return member.mode === "percent" ? `${formatPoolSplit(member)} \xB7 ${allowance}` : allowance;
9603
9603
  }
9604
+ var POOL_EXPIRY_MAX_MONTHS = 120;
9605
+ function addCalendarMonths(date, months) {
9606
+ const target = new Date(date.getFullYear(), date.getMonth() + months, 1);
9607
+ const lastDay = new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate();
9608
+ return new Date(
9609
+ target.getFullYear(),
9610
+ target.getMonth(),
9611
+ Math.min(date.getDate(), lastDay),
9612
+ date.getHours(),
9613
+ date.getMinutes(),
9614
+ date.getSeconds(),
9615
+ date.getMilliseconds()
9616
+ );
9617
+ }
9618
+ var pad2 = (n) => String(n).padStart(2, "0");
9619
+ function toLocalDateInput(date) {
9620
+ return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
9621
+ }
9622
+ function minPoolEndDate(now = /* @__PURE__ */ new Date()) {
9623
+ return toLocalDateInput(new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1));
9624
+ }
9625
+ function parseLocalDate(value) {
9626
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
9627
+ if (!match) return null;
9628
+ const [year, month, day] = [Number(match[1]), Number(match[2]) - 1, Number(match[3])];
9629
+ const date = new Date(year, month, day);
9630
+ return date.getFullYear() === year && date.getMonth() === month && date.getDate() === day ? date : null;
9631
+ }
9632
+ function poolEndsAtFrom(choice, now = /* @__PURE__ */ new Date()) {
9633
+ switch (choice.kind) {
9634
+ case "never":
9635
+ return { ok: true, value: null };
9636
+ case "month":
9637
+ return { ok: true, value: addCalendarMonths(now, 1).toISOString() };
9638
+ case "year":
9639
+ return { ok: true, value: addCalendarMonths(now, 12).toISOString() };
9640
+ case "months": {
9641
+ const { months } = choice;
9642
+ if (!Number.isInteger(months) || months < 1 || months > POOL_EXPIRY_MAX_MONTHS) {
9643
+ return { ok: false, reason: `Enter a whole number of months from 1 to ${POOL_EXPIRY_MAX_MONTHS}` };
9644
+ }
9645
+ return { ok: true, value: addCalendarMonths(now, months).toISOString() };
9646
+ }
9647
+ case "date": {
9648
+ if (!choice.date) return { ok: false, reason: "Choose a date" };
9649
+ const date = parseLocalDate(choice.date);
9650
+ if (!date) return { ok: false, reason: "Choose a valid date" };
9651
+ if (choice.date < minPoolEndDate(now)) return { ok: false, reason: "Choose a date after today" };
9652
+ return { ok: true, value: date.toISOString() };
9653
+ }
9654
+ }
9655
+ }
9656
+ function expiryChoiceFor(member) {
9657
+ const endsAt = member?.endsAt ? new Date(member.endsAt) : null;
9658
+ if (!endsAt || !Number.isFinite(endsAt.getTime())) return { kind: "never" };
9659
+ return { kind: "date", date: toLocalDateInput(endsAt) };
9660
+ }
9661
+ function formatPoolEndsAt(endsAt) {
9662
+ const date = endsAt ? new Date(endsAt) : null;
9663
+ if (!date || !Number.isFinite(date.getTime())) return "Never";
9664
+ return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
9665
+ }
9666
+ function sharedPoolEndsText(pool) {
9667
+ return pool.endsAt ? `Sharing ends on ${formatPoolEndsAt(pool.endsAt)}.` : "Sharing ends: Never.";
9668
+ }
9669
+ function poolRenewsAtCycleEnd(pool) {
9670
+ if (!pool.cycleEnd) return false;
9671
+ if (!pool.endsAt) return true;
9672
+ return new Date(pool.endsAt).getTime() > new Date(pool.cycleEnd).getTime();
9673
+ }
9604
9674
  function poolWorkspaceName(name) {
9605
9675
  return name?.trim() || "Unknown workspace";
9606
9676
  }
@@ -9608,12 +9678,8 @@ function sharedPoolBannerText(pool) {
9608
9678
  const sponsor = pool.sponsorName?.trim() || "another workspace";
9609
9679
  return `Credits shared by ${sponsor}: ${formatCredits(pool.remaining)} of ${formatCredits(pool.allowance)} left this cycle`;
9610
9680
  }
9611
- function pausedOwnCreditsText(pool) {
9612
- const own = pool.ownCredits;
9613
- if (typeof own !== "number" || !Number.isFinite(own)) {
9614
- return "This workspace spends these shared credits, not its own; credits bought here can't be used while it does.";
9615
- }
9616
- return `This workspace's own ${credits(own)} ${own === 1 ? "is" : "are"} paused while it uses shared credits. They stay with it and can be used again once sharing stops.`;
9681
+ function pausedOwnCreditsText(_pool) {
9682
+ return ownCreditsFirstText();
9617
9683
  }
9618
9684
  function ownCreditsFirstText() {
9619
9685
  return "This workspace uses its own credits first, then these shared credits.";
@@ -9808,25 +9874,36 @@ function PoolModalShell({
9808
9874
  }
9809
9875
  );
9810
9876
  }
9811
- function PoolWarning({ children }) {
9812
- return /* @__PURE__ */ jsx26(
9813
- "div",
9814
- {
9815
- role: "alert",
9816
- className: "w-full",
9817
- style: {
9818
- ...modalBodyText,
9819
- color: T16.primary,
9820
- background: T16.warningFill,
9821
- border: `1px solid ${T16.warningBorder}`,
9822
- borderRadius: T16.r3xl,
9823
- padding: `${T16.spXl} ${T16.sp3xl}`
9824
- },
9825
- children
9826
- }
9827
- );
9828
- }
9829
9877
  var fieldLabel = { ...modalBodyText, fontWeight: 500, color: T16.primary };
9878
+ var fieldBox = {
9879
+ background: T16.fillPlus,
9880
+ height: 48,
9881
+ borderRadius: T16.r3xl,
9882
+ padding: `${T16.spXl} ${T16.sp3xl}`,
9883
+ gap: T16.spXl
9884
+ };
9885
+ var bareInput = {
9886
+ ...modalBodyText,
9887
+ background: "transparent",
9888
+ border: "none",
9889
+ outline: "none",
9890
+ color: T16.primary,
9891
+ padding: 0
9892
+ };
9893
+ var EXPIRY_OPTIONS = [
9894
+ { id: "never", label: "Never" },
9895
+ { id: "month", label: "In 1 month" },
9896
+ { id: "months", label: "In a number of months" },
9897
+ { id: "year", label: "In 1 year" },
9898
+ { id: "date", label: "On a date" }
9899
+ ];
9900
+ var isExpiryKind = (id) => EXPIRY_OPTIONS.some((o) => o.id === id);
9901
+ var endsText = (member) => member.endsAt ? `, until ${formatPoolEndsAt(member.endsAt)}` : "";
9902
+ function endsAtHelp(kind, endsAt) {
9903
+ if (kind === "never") return "Shares every billing cycle until it is removed.";
9904
+ const stop = "After that, the other workspace spends only its own credits.";
9905
+ return endsAt ? `Sharing stops on ${formatPoolEndsAt(endsAt)}. ${stop}` : stop;
9906
+ }
9830
9907
  function CreditPoolMemberModal(props) {
9831
9908
  const { toast } = useSettingsAdapters();
9832
9909
  const editing = props.kind === "edit" ? props.member : null;
@@ -9837,17 +9914,28 @@ function CreditPoolMemberModal(props) {
9837
9914
  const [touched, setTouched] = useState18(false);
9838
9915
  const [saving, setSaving] = useState18(false);
9839
9916
  const [serverError, setServerError] = useState18(null);
9917
+ const initialExpiry = expiryChoiceFor(editing);
9918
+ const [expiryKind, setExpiryKind] = useState18(initialExpiry.kind);
9919
+ const [monthsRaw, setMonthsRaw] = useState18("3");
9920
+ const [endDate, setEndDate] = useState18(initialExpiry.kind === "date" ? initialExpiry.date : "");
9840
9921
  useEffect16(() => {
9841
9922
  setServerError(null);
9842
- }, [memberId, mode, raw, note]);
9923
+ }, [memberId, mode, raw, note, expiryKind, monthsRaw, endDate]);
9843
9924
  const value = validatePoolValue(mode, raw);
9844
9925
  const noteCheck = validatePoolText(note, POOL_NOTE_MAX_LENGTH);
9845
- const changed = !editing || mode !== editing.mode || !value.ok || value.value !== editing.value;
9846
- const valid = value.ok && (props.kind === "edit" || Boolean(memberId) && noteCheck.ok);
9926
+ const expiry = expiryKind === "months" ? { kind: "months", months: monthsRaw.trim() ? Number(monthsRaw) : NaN } : expiryKind === "date" ? { kind: "date", date: endDate } : { kind: expiryKind };
9927
+ const endsAtUnchanged = Boolean(editing) && expiry.kind === initialExpiry.kind && (expiry.kind !== "date" || initialExpiry.kind === "date" && expiry.date === initialExpiry.date);
9928
+ const endsAt = endsAtUnchanged ? null : poolEndsAtFrom(expiry);
9929
+ const splitChanged = !editing || mode !== editing.mode || !value.ok || value.value !== editing.value;
9930
+ const changed = splitChanged || !endsAtUnchanged;
9931
+ const valid = value.ok && (endsAt === null || endsAt.ok) && (props.kind === "edit" || Boolean(memberId) && noteCheck.ok);
9847
9932
  const canSave = !saving && changed && valid;
9848
9933
  const valueHint = touched && !value.ok ? value.reason : null;
9934
+ const expiryHint = endsAt && !endsAt.ok && !(expiry.kind === "date" && !expiry.date) ? endsAt.reason : null;
9849
9935
  const handleSave = async () => {
9850
9936
  if (!canSave || !value.ok) return;
9937
+ const sendEndsAt = endsAtUnchanged ? null : poolEndsAtFrom(expiry);
9938
+ if (sendEndsAt && !sendEndsAt.ok) return;
9851
9939
  setSaving(true);
9852
9940
  try {
9853
9941
  if (props.kind === "add") {
@@ -9855,12 +9943,17 @@ function CreditPoolMemberModal(props) {
9855
9943
  memberWorkspaceId: memberId,
9856
9944
  mode,
9857
9945
  value: value.value,
9858
- note: noteCheck.ok ? noteCheck.value : ""
9946
+ note: noteCheck.ok ? noteCheck.value : "",
9947
+ ...sendEndsAt?.value ? { endsAt: sendEndsAt.value } : {}
9859
9948
  });
9860
- toast.success(`Credits shared. ${describeAllowance(added)}.`);
9949
+ toast.success(`Credits shared. ${describeAllowance(added)}${endsText(added)}.`);
9861
9950
  } else {
9862
- const updated = await props.onSave({ mode, value: value.value });
9863
- toast.success(`Share updated. ${describeAllowance(updated)}.`);
9951
+ const updated = await props.onSave({
9952
+ mode,
9953
+ value: value.value,
9954
+ ...sendEndsAt ? { endsAt: sendEndsAt.value } : {}
9955
+ });
9956
+ toast.success(`Share updated. ${describeAllowance(updated)}${endsText(updated)}.`);
9864
9957
  }
9865
9958
  props.onSaved();
9866
9959
  props.onClose();
@@ -9945,11 +10038,7 @@ function CreditPoolMemberModal(props) {
9945
10038
  {
9946
10039
  className: "flex items-center w-full",
9947
10040
  style: {
9948
- background: T16.fillPlus,
9949
- height: 48,
9950
- borderRadius: T16.r3xl,
9951
- padding: `${T16.spXl} ${T16.sp3xl}`,
9952
- gap: T16.spXl,
10041
+ ...fieldBox,
9953
10042
  border: valueHint ? `1px solid ${T16.error}` : "1px solid transparent"
9954
10043
  },
9955
10044
  children: [
@@ -9967,14 +10056,7 @@ function CreditPoolMemberModal(props) {
9967
10056
  setTouched(true);
9968
10057
  },
9969
10058
  className: "flex-1 min-w-0",
9970
- style: {
9971
- ...modalBodyText,
9972
- background: "transparent",
9973
- border: "none",
9974
- outline: "none",
9975
- color: T16.primary,
9976
- padding: 0
9977
- }
10059
+ style: bareInput
9978
10060
  }
9979
10061
  ),
9980
10062
  /* @__PURE__ */ jsx26("span", { className: "shrink-0", style: { ...modalBodyText, color: T16.muted }, children: mode === "percent" ? "%" : "credits" })
@@ -9982,10 +10064,76 @@ function CreditPoolMemberModal(props) {
9982
10064
  }
9983
10065
  ),
9984
10066
  valueHint ? /* @__PURE__ */ jsx26("span", { role: "alert", style: { ...modalBodyText, color: T16.error }, children: valueHint }) : /* @__PURE__ */ jsxs23("span", { style: { ...modalBodyText, color: T16.tertiary }, children: [
9985
- mode === "percent" ? "A share of this workspace's plan credits for each billing cycle. Credits stay with this workspace; the other workspace spends them up to this share." : "Credits the other workspace may spend each billing cycle. Credits stay with this workspace until spent.",
10067
+ mode === "percent" ? "A share of this workspace's plan credits for each billing cycle. Credits stay with this workspace; the other workspace spends them up to this share, after its own credits." : "Credits the other workspace may spend each billing cycle, after its own credits. Credits stay with this workspace until spent.",
9986
10068
  editing ? " Applies immediately." : ""
9987
10069
  ] })
9988
10070
  ] }),
10071
+ /* @__PURE__ */ jsxs23("div", { className: "flex flex-col w-full", style: { gap: T16.spMd }, children: [
10072
+ /* @__PURE__ */ jsx26("span", { style: fieldLabel, children: "Ends" }),
10073
+ /* @__PURE__ */ jsx26(
10074
+ ScSelect,
10075
+ {
10076
+ value: expiryKind,
10077
+ onChange: (id) => {
10078
+ if (isExpiryKind(id)) setExpiryKind(id);
10079
+ },
10080
+ options: EXPIRY_OPTIONS,
10081
+ disabled: saving
10082
+ }
10083
+ ),
10084
+ expiryKind === "months" && /* @__PURE__ */ jsxs23(
10085
+ "div",
10086
+ {
10087
+ className: "flex items-center w-full",
10088
+ style: {
10089
+ ...fieldBox,
10090
+ border: expiryHint ? `1px solid ${T16.error}` : "1px solid transparent"
10091
+ },
10092
+ children: [
10093
+ /* @__PURE__ */ jsx26(
10094
+ "input",
10095
+ {
10096
+ inputMode: "numeric",
10097
+ "aria-label": "Number of months",
10098
+ "aria-invalid": expiryHint ? true : void 0,
10099
+ placeholder: "Enter a number of months",
10100
+ value: monthsRaw,
10101
+ disabled: saving,
10102
+ onChange: (e) => setMonthsRaw(e.target.value.replace(/\D/g, "").slice(0, 3)),
10103
+ className: "flex-1 min-w-0",
10104
+ style: bareInput
10105
+ }
10106
+ ),
10107
+ /* @__PURE__ */ jsx26("span", { className: "shrink-0", style: { ...modalBodyText, color: T16.muted }, children: "months" })
10108
+ ]
10109
+ }
10110
+ ),
10111
+ expiryKind === "date" && /* @__PURE__ */ jsx26(
10112
+ "div",
10113
+ {
10114
+ className: "flex items-center w-full",
10115
+ style: {
10116
+ ...fieldBox,
10117
+ border: expiryHint ? `1px solid ${T16.error}` : "1px solid transparent"
10118
+ },
10119
+ children: /* @__PURE__ */ jsx26(
10120
+ "input",
10121
+ {
10122
+ type: "date",
10123
+ "aria-label": "End date",
10124
+ "aria-invalid": expiryHint ? true : void 0,
10125
+ min: minPoolEndDate(),
10126
+ value: endDate,
10127
+ disabled: saving,
10128
+ onChange: (e) => setEndDate(e.target.value),
10129
+ className: "flex-1 min-w-0",
10130
+ style: { ...bareInput, colorScheme: "light dark" }
10131
+ }
10132
+ )
10133
+ }
10134
+ ),
10135
+ expiryHint ? /* @__PURE__ */ jsx26("span", { role: "alert", style: { ...modalBodyText, color: T16.error }, children: expiryHint }) : /* @__PURE__ */ jsx26("span", { style: { ...modalBodyText, color: T16.tertiary }, children: endsAtHelp(expiryKind, endsAtUnchanged ? editing?.endsAt ?? null : endsAt?.ok ? endsAt.value : null) })
10136
+ ] }),
9989
10137
  props.kind === "add" && /* @__PURE__ */ jsxs23("div", { className: "flex flex-col w-full", style: { gap: T16.spXs }, children: [
9990
10138
  /* @__PURE__ */ jsx26("span", { style: fieldLabel, children: "Note" }),
9991
10139
  /* @__PURE__ */ jsx26(
@@ -10007,7 +10155,6 @@ function CreditPoolMemberModal(props) {
10007
10155
  POOL_NOTE_MAX_LENGTH
10008
10156
  ] })
10009
10157
  ] }),
10010
- props.kind === "add" && /* @__PURE__ */ jsx26(PoolWarning, { children: "While it spends shared credits, the other workspace uses only this workspace's credits, not its own." }),
10011
10158
  serverError && /* @__PURE__ */ jsx26("span", { role: "alert", style: { ...modalBodyText, color: T16.error }, children: serverError })
10012
10159
  ]
10013
10160
  }
@@ -10128,7 +10275,9 @@ var T17 = {
10128
10275
  r3xl: "var(--radius-3xl)",
10129
10276
  rFull: "var(--radius-full)"
10130
10277
  };
10131
- var MEMBER_COLUMNS2 = "minmax(220px, 1fr) 220px 110px 110px 150px";
10278
+ var MEMBER_COLUMNS2 = "minmax(180px, 1fr) 150px 80px 90px 110px 150px";
10279
+ var MEMBER_GAP = 16;
10280
+ var MEMBER_MIN_WIDTH = 880;
10132
10281
  var cellText2 = {
10133
10282
  fontFamily: T17.font,
10134
10283
  fontSize: T17.fsSm,
@@ -10141,7 +10290,7 @@ var tableHeader = (columns, labels) => /* @__PURE__ */ jsx28(
10141
10290
  style: {
10142
10291
  display: "grid",
10143
10292
  gridTemplateColumns: columns,
10144
- gap: 24,
10293
+ gap: MEMBER_GAP,
10145
10294
  padding: "8px 16px",
10146
10295
  fontSize: 11,
10147
10296
  color: T17.muted,
@@ -10165,17 +10314,21 @@ function MemberRow2({
10165
10314
  gridTemplateColumns: MEMBER_COLUMNS2,
10166
10315
  alignItems: "center",
10167
10316
  padding: `10px ${T17.sp3xl}`,
10168
- gap: 24
10317
+ gap: MEMBER_GAP
10169
10318
  },
10170
10319
  children: [
10171
10320
  /* @__PURE__ */ jsxs25("div", { className: "flex flex-col min-w-0", style: { gap: 2 }, children: [
10172
10321
  /* @__PURE__ */ jsx28("span", { className: "truncate", style: { ...cellText2, fontWeight: 500 }, children: poolWorkspaceName(member.memberWorkspaceName) }),
10173
10322
  member.note && /* @__PURE__ */ jsx28("span", { className: "truncate", title: member.note, style: { ...cellText2, color: T17.tertiary }, children: member.note })
10174
10323
  ] }),
10175
- /* @__PURE__ */ jsx28("span", { style: cellText2, children: describeAllowance(member) }),
10324
+ /* @__PURE__ */ jsxs25("div", { className: "flex flex-col min-w-0", style: { gap: 2 }, children: [
10325
+ /* @__PURE__ */ jsx28("span", { style: cellText2, children: formatPoolSplit({ mode: "fixed", value: member.allowance }) }),
10326
+ /* @__PURE__ */ jsx28("span", { style: { ...cellText2, color: T17.tertiary }, children: member.mode === "percent" ? `${formatPoolSplit(member)} \xB7 this cycle` : "this cycle" })
10327
+ ] }),
10176
10328
  /* @__PURE__ */ jsx28("span", { style: cellText2, children: formatCredits(member.used) }),
10177
10329
  /* @__PURE__ */ jsx28("span", { style: { ...cellText2, color: spent ? T17.warning : T17.primary }, children: formatCredits(member.remaining) }),
10178
- /* @__PURE__ */ jsxs25("div", { className: "flex items-center", style: { gap: 8 }, children: [
10330
+ /* @__PURE__ */ jsx28("span", { style: { ...cellText2, color: member.endsAt ? T17.primary : T17.muted }, children: formatPoolEndsAt(member.endsAt) }),
10331
+ /* @__PURE__ */ jsxs25("div", { className: "flex items-center whitespace-nowrap", style: { gap: 8 }, children: [
10179
10332
  onEdit && /* @__PURE__ */ jsx28(ScButton13, { text: "Edit", variant: "secondary", size: "sm", onClick: onEdit }),
10180
10333
  onRemove && /* @__PURE__ */ jsx28(ScButton13, { text: "Remove", variant: "secondary", size: "sm", onClick: onRemove })
10181
10334
  ] })
@@ -10301,11 +10454,13 @@ function SharedCreditsView() {
10301
10454
  remaining: sponsor.remaining
10302
10455
  }) }),
10303
10456
  /* @__PURE__ */ jsxs25("span", { style: { ...cellText2, color: T17.muted }, children: [
10304
- "This workspace spends ",
10457
+ "This workspace uses its own credits first, then",
10458
+ " ",
10305
10459
  poolWorkspaceName(sponsor.sponsorWorkspaceName),
10306
- "'s credits, not its own",
10307
- sponsor.cycle?.end ? `, until its allowance starts again on ${formatDate2(sponsor.cycle.end)}` : "",
10308
- "."
10460
+ "'s, up to its allowance",
10461
+ sponsor.cycle?.end && poolRenewsAtCycleEnd({ cycleEnd: sponsor.cycle.end, endsAt: sponsor.endsAt }) ? `, which starts again on ${formatDate2(sponsor.cycle.end)}` : "",
10462
+ ". ",
10463
+ sharedPoolEndsText(sponsor)
10309
10464
  ] })
10310
10465
  ]
10311
10466
  }
@@ -10317,8 +10472,8 @@ function SharedCreditsView() {
10317
10472
  {
10318
10473
  grow: "cap",
10319
10474
  maxHeight: 420,
10320
- contentMinWidth: 860,
10321
- header: tableHeader(MEMBER_COLUMNS2, ["Workspace", "Allowance", "Used", "Remaining", "Actions"]),
10475
+ contentMinWidth: MEMBER_MIN_WIDTH,
10476
+ header: tableHeader(MEMBER_COLUMNS2, ["Workspace", "Allowance", "Used", "Remaining", "Ends", "Actions"]),
10322
10477
  isEmpty: failed || members.length === 0,
10323
10478
  emptyText: failed ? "Couldn't load shared credits. Try again later." : "This workspace doesn't share its credits with any other workspace.",
10324
10479
  children: members.map((member) => /* @__PURE__ */ jsx28(
@@ -12134,7 +12289,8 @@ function BillingContent({
12134
12289
  },
12135
12290
  children: [
12136
12291
  ownCreditsFirstText(),
12137
- sharedPool.cycleEnd ? ` The allowance starts again on ${formatDate3(sharedPool.cycleEnd)}.` : ""
12292
+ poolRenewsAtCycleEnd(sharedPool) ? ` The allowance starts again on ${formatDate3(sharedPool.cycleEnd)}.` : "",
12293
+ ` ${sharedPoolEndsText(sharedPool)}`
12138
12294
  ]
12139
12295
  }
12140
12296
  )
@@ -12388,7 +12544,7 @@ import {
12388
12544
  SiconClose as SiconClose10
12389
12545
  } from "@streamoid/icons";
12390
12546
  import { Fragment as Fragment12, jsx as jsx30, jsxs as jsxs27 } from "react/jsx-runtime";
12391
- var EXPIRY_OPTIONS = [
12547
+ var EXPIRY_OPTIONS2 = [
12392
12548
  { label: "30 days", value: "30d" },
12393
12549
  { label: "60 days", value: "60d" },
12394
12550
  { label: "90 days", value: "90d" },
@@ -12579,7 +12735,7 @@ function CreateTokenModal({ onClose, onCreated }) {
12579
12735
  children: "Expiry"
12580
12736
  }
12581
12737
  ) }),
12582
- /* @__PURE__ */ jsx30("div", { style: { display: "flex", gap: "var(--spacing-md)", flexWrap: "wrap" }, children: EXPIRY_OPTIONS.map((opt) => /* @__PURE__ */ jsx30(
12738
+ /* @__PURE__ */ jsx30("div", { style: { display: "flex", gap: "var(--spacing-md)", flexWrap: "wrap" }, children: EXPIRY_OPTIONS2.map((opt) => /* @__PURE__ */ jsx30(
12583
12739
  ScButton15,
12584
12740
  {
12585
12741
  text: opt.label,
@@ -15172,7 +15328,7 @@ function SettingsContentInner({
15172
15328
  const organizationScopeName = organization.current?.name ?? null;
15173
15329
  const hasOrganization = !!organization.current;
15174
15330
  const activeScope = TAB_SCOPE[activeTab];
15175
- const scopeCrumb = activeScope === "workspace" ? workspaceScopeName ? overviewDetail ? `Workspace \xB7 ${workspaceScopeName}` : workspaceScopeName : SCOPE_LABELS.workspace : activeScope === "organization" && hasOrganization ? organizationScopeName ? overviewDetail ? `Organization \xB7 ${organizationScopeName}` : organizationScopeName : SCOPE_LABELS.organization : null;
15331
+ const scopeCrumb = activeScope === "workspace" ? workspaceScopeName ? overviewDetail ? `Workspace \xB7 ${workspaceScopeName}` : workspaceScopeName : SCOPE_LABELS.workspace : activeScope === "organization" && hasOrganization ? organizationScopeName ? overviewDetail ? `Organization \xB7 ${organizationScopeName}` : organizationScopeName : SCOPE_LABELS.organization : activeScope === "account" && overviewDetail ? SCOPE_LABELS.account : null;
15176
15332
  const leafLabel = activeScope === "organization" && !hasOrganization ? SCOPE_LABELS.organization : TAB_LABELS[activeTab];
15177
15333
  async function handleSaveCurrentWorkspace(name, imageFile, imageRemoved) {
15178
15334
  if (!currentWorkspaceData || updatingWorkspace) return false;
@@ -15418,9 +15574,10 @@ function SettingsContentInner({
15418
15574
  /* @__PURE__ */ jsxs30("div", { className: "flex items-center flex-1 min-w-0", style: { gap: 8, paddingLeft: 20 }, children: [
15419
15575
  /* @__PURE__ */ jsx33(SiconSettings2, { className: "w-6 h-6 shrink-0", color: "var(--alias-text---icons-primary)" }),
15420
15576
  /* @__PURE__ */ jsx33(
15421
- "span",
15577
+ "h1",
15422
15578
  {
15423
15579
  style: {
15580
+ margin: 0,
15424
15581
  fontFamily: "var(--font-family-font-family-body)",
15425
15582
  fontSize: "var(--font-size-font-size-xl)",
15426
15583
  fontWeight: 500,
@@ -15430,7 +15587,7 @@ function SettingsContentInner({
15430
15587
  children: overviewDetail ? "Account and workspace settings" : "Settings"
15431
15588
  }
15432
15589
  ),
15433
- !isOverview && /* @__PURE__ */ jsxs30(Fragment16, { children: [
15590
+ !isOverview && /* @__PURE__ */ jsxs30("nav", { "aria-label": "Breadcrumb", className: "flex items-center min-w-0", style: { gap: 8 }, children: [
15434
15591
  scopeCrumb && /* @__PURE__ */ jsxs30(Fragment16, { children: [
15435
15592
  /* @__PURE__ */ jsx33(SiconRight3, { className: "w-6 h-6 shrink-0", color: "var(--alias-text---icons-primary)" }),
15436
15593
  /* @__PURE__ */ jsx33(
@@ -16676,6 +16833,7 @@ export {
16676
16833
  OrganizationModal,
16677
16834
  OrganizationPicker,
16678
16835
  POOL_ERROR_CODES,
16836
+ POOL_EXPIRY_MAX_MONTHS,
16679
16837
  POOL_NOTE_MAX_LENGTH,
16680
16838
  PerUserLimitsView,
16681
16839
  ProfileAvatar,
@@ -16715,10 +16873,12 @@ export {
16715
16873
  describeAllowance,
16716
16874
  describeDefaultLimit,
16717
16875
  describeMemberLimit,
16876
+ expiryChoiceFor,
16718
16877
  findBillingError,
16719
16878
  findPoolError,
16720
16879
  formatCreditLimit,
16721
16880
  formatCredits,
16881
+ formatPoolEndsAt,
16722
16882
  formatPoolSplit,
16723
16883
  groupWorkspaceList,
16724
16884
  isLimitBelowUsage,
@@ -16728,10 +16888,13 @@ export {
16728
16888
  limitSourceLabel,
16729
16889
  loadWorkspaceOrganization,
16730
16890
  memberRoleUpdate,
16891
+ minPoolEndDate,
16731
16892
  minimumCreditLimit,
16732
16893
  ownCreditsFirstText,
16733
16894
  pausedOwnCreditsText,
16895
+ poolEndsAtFrom,
16734
16896
  poolErrorMessage,
16897
+ poolRenewsAtCycleEnd,
16735
16898
  poolWorkspaceName,
16736
16899
  readSettingsCaller,
16737
16900
  rememberSettingsCaller,
@@ -16739,6 +16902,8 @@ export {
16739
16902
  roundCredits,
16740
16903
  settingsReturnTarget,
16741
16904
  sharedPoolBannerText,
16905
+ sharedPoolEndsText,
16906
+ toLocalDateInput,
16742
16907
  useSettingsAdapters,
16743
16908
  useWorkspaceInvitePermission,
16744
16909
  useWorkspaceOrganization,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamoid/settings",
3
- "version": "0.12.3",
3
+ "version": "0.13.0",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",