Check if saas before blocking credit insufficiencies (#5929)

fixes #5926
This commit is contained in:
ConnorYoh
2026-03-13 10:28:39 +00:00
committed by GitHub
parent 9969fe5a6d
commit 44e036da5a
4 changed files with 18 additions and 9 deletions
+1 -1
View File
@@ -78,7 +78,7 @@ springBoot {
allprojects { allprojects {
group = 'stirling.software' group = 'stirling.software'
version = '2.7.1' version = '2.7.2'
configurations.configureEach { configurations.configureEach {
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat" exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useSaaSBilling } from '@app/contexts/SaasBillingContext'; import { useSaaSBilling } from '@app/contexts/SaasBillingContext';
import { useSaaSMode } from '@app/hooks/useSaaSMode';
import { BILLING_CONFIG } from '@app/config/billing'; import { BILLING_CONFIG } from '@app/config/billing';
import { CreditExhaustedModal } from '@app/components/shared/modals/CreditExhaustedModal'; import { CreditExhaustedModal } from '@app/components/shared/modals/CreditExhaustedModal';
import { InsufficientCreditsModal } from '@app/components/shared/modals/InsufficientCreditsModal'; import { InsufficientCreditsModal } from '@app/components/shared/modals/InsufficientCreditsModal';
@@ -19,16 +20,17 @@ export function CreditModalBootstrap() {
requiredCredits?: number; requiredCredits?: number;
}>({}); }>({});
const isSaaSMode = useSaaSMode();
const { creditBalance, isManagedTeamMember, lastFetchTime, plansLastFetchTime, refreshPlans } = useSaaSBilling(); const { creditBalance, isManagedTeamMember, lastFetchTime, plansLastFetchTime, refreshPlans } = useSaaSBilling();
// Preload plan pricing when billing confirms credits are low. // Preload plan pricing when billing confirms credits are low.
// Fires once: only when billing has loaded (lastFetchTime set) and plans haven't been // Fires once: only when in SaaS mode, billing has loaded (lastFetchTime set) and plans haven't been
// fetched yet (plansLastFetchTime null). This way the modal shows real prices instantly. // fetched yet (plansLastFetchTime null). This way the modal shows real prices instantly.
useEffect(() => { useEffect(() => {
if (lastFetchTime !== null && plansLastFetchTime === null && creditBalance < BILLING_CONFIG.PLAN_PRICING_PRELOAD_THRESHOLD && !isManagedTeamMember) { if (isSaaSMode && lastFetchTime !== null && plansLastFetchTime === null && creditBalance < BILLING_CONFIG.PLAN_PRICING_PRELOAD_THRESHOLD && !isManagedTeamMember) {
refreshPlans(); refreshPlans();
} }
}, [lastFetchTime, plansLastFetchTime, creditBalance, isManagedTeamMember, refreshPlans]); }, [isSaaSMode, lastFetchTime, plansLastFetchTime, creditBalance, isManagedTeamMember, refreshPlans]);
// Monitor credit balance and dispatch events // Monitor credit balance and dispatch events
useCreditEvents(); useCreditEvents();
+4 -1
View File
@@ -1,6 +1,7 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSaaSBilling } from '@app/contexts/SaasBillingContext'; import { useSaaSBilling } from '@app/contexts/SaasBillingContext';
import { useSaaSMode } from '@app/hooks/useSaaSMode';
import { getToolCreditCost } from '@app/utils/creditCosts'; import { getToolCreditCost } from '@app/utils/creditCosts';
import { CREDIT_EVENTS } from '@app/constants/creditEvents'; import { CREDIT_EVENTS } from '@app/constants/creditEvents';
import type { ToolId } from '@app/types/toolId'; import type { ToolId } from '@app/types/toolId';
@@ -16,9 +17,11 @@ import type { ToolId } from '@app/types/toolId';
*/ */
export function useCreditCheck(operationType?: string) { export function useCreditCheck(operationType?: string) {
const billing = useSaaSBilling(); const billing = useSaaSBilling();
const isSaaSMode = useSaaSMode();
const { t } = useTranslation(); const { t } = useTranslation();
const checkCredits = useCallback(async (): Promise<string | null> => { const checkCredits = useCallback(async (): Promise<string | null> => {
if (!isSaaSMode) return null; // Credits only apply in SaaS mode, not self-hosted
if (!billing) return null; if (!billing) return null;
const { creditBalance, loading } = billing; const { creditBalance, loading } = billing;
@@ -41,7 +44,7 @@ export function useCreditCheck(operationType?: string) {
} }
return null; return null;
}, [billing, operationType, t]); }, [billing, isSaaSMode, operationType, t]);
return { checkCredits }; return { checkCredits };
} }
@@ -1,20 +1,24 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { useSaaSBilling } from '@app/contexts/SaasBillingContext'; import { useSaaSBilling } from '@app/contexts/SaasBillingContext';
import { useSaaSMode } from '@app/hooks/useSaaSMode';
import { CREDIT_EVENTS } from '@app/constants/creditEvents'; import { CREDIT_EVENTS } from '@app/constants/creditEvents';
/** /**
* Desktop hook that monitors credit balance and dispatches events * Desktop hook that monitors credit balance and dispatches events
* when credits are exhausted or low * when credits are exhausted or low.
* Only active in SaaS mode — self-hosted users have no credit balance.
*/ */
export function useCreditEvents() { export function useCreditEvents() {
const isSaaSMode = useSaaSMode();
const { creditBalance } = useSaaSBilling(); const { creditBalance } = useSaaSBilling();
const prevBalanceRef = useRef(creditBalance); const prevBalanceRef = useRef(creditBalance);
useEffect(() => { useEffect(() => {
const prevBalance = prevBalanceRef.current; const prevBalance = prevBalanceRef.current;
// Dispatch exhausted event when credits reach 0 from positive balance // Dispatch exhausted event when credits reach 0 from positive balance.
if (creditBalance <= 0 && prevBalance > 0) { // Skip entirely in self-hosted mode — creditBalance defaults to 0 there.
if (isSaaSMode && creditBalance <= 0 && prevBalance > 0) {
window.dispatchEvent( window.dispatchEvent(
new CustomEvent(CREDIT_EVENTS.EXHAUSTED, { new CustomEvent(CREDIT_EVENTS.EXHAUSTED, {
detail: { previousBalance: prevBalance, currentBalance: creditBalance }, detail: { previousBalance: prevBalance, currentBalance: creditBalance },
@@ -24,5 +28,5 @@ export function useCreditEvents() {
// Update ref for next comparison // Update ref for next comparison
prevBalanceRef.current = creditBalance; prevBalanceRef.current = creditBalance;
}, [creditBalance]); }, [isSaaSMode, creditBalance]);
} }