From e68842c268afa9b1d38875fb8489313a52d164de Mon Sep 17 00:00:00 2001
From: Etienne <45695613+etiennejouan@users.noreply.github.com>
Date: Mon, 20 Apr 2026 10:43:02 +0200
Subject: [PATCH] Billing - fixes (#19867)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Uniformize credit formating : In UI, 1$=1credit. In BE 1 UI credit =
1_000_000 BE "crédits"
- Add crédit rollover information + Link to documentation +
Documentation update
- Enable direct top-up (or subscription if in trial) from AI chat
- Inform users if credit limit is reached - Banner
---
packages/twenty-docs/docs.json | 34 ++--
.../{workflow-credits.mdx => credits.mdx} | 0
.../{workflow-credits.mdx => credits.mdx} | 0
.../{workflow-credits.mdx => credits.mdx} | 0
.../{workflow-credits.mdx => credits.mdx} | 0
.../{workflow-credits.mdx => credits.mdx} | 0
.../{workflow-credits.mdx => credits.mdx} | 0
.../{workflow-credits.mdx => credits.mdx} | 0
.../billing/how-tos/billing-faq.mdx | 4 +-
.../{workflow-credits.mdx => credits.mdx} | 0
.../{workflow-credits.mdx => credits.mdx} | 0
.../{workflow-credits.mdx => credits.mdx} | 0
.../{workflow-credits.mdx => credits.mdx} | 0
.../{workflow-credits.mdx => credits.mdx} | 0
.../{workflow-credits.mdx => credits.mdx} | 0
.../navigation/base-structure.json | 2 +-
.../navigation/navigation-schema.json | 2 +-
.../billing/capabilities/credits.mdx | 55 +++++++
.../billing/capabilities/workflow-credits.mdx | 48 ------
.../billing/how-tos/billing-faq.mdx | 4 +-
.../capabilities/workflow-credits.mdx | 6 +-
.../AIChatNoMoreBillingCreditsBanner.tsx | 149 ++++++++++++++++++
.../modules/ai/components/AiChatBanner.tsx | 1 +
.../ai/components/AiChatEditorSection.tsx | 11 +-
.../ai/components/RoutingDebugDisplay.tsx | 9 +-
.../internal/AiChatContextUsageButton.tsx | 9 +-
.../components/InformationBannerWrapper.tsx | 17 +-
.../InformationBannerNoMoreCredits.tsx | 30 ++++
.../SettingsBillingCreditsSection.tsx | 36 ++++-
.../SettingsBillingLabelValueItem.tsx | 34 +++-
.../hooks/useGetNextMeteredBillingPrice.ts | 60 +++++++
.../components/EnterprisePlanModal.tsx | 4 +-
.../components/UsageDailyChartSection.tsx | 5 +
.../usage/components/UsagePieChart.tsx | 7 +-
.../usage/hooks/useUsageValueFormatter.ts | 11 +-
.../useIsSomeMeteredProductCapReached.ts | 15 --
...sReachedCurrentBillingPeriodCapSelector.ts | 18 +++
...tabase-product-to-graphql-dto.util.spec.ts | 4 +-
.../logic-function-executor.service.ts | 22 +++
.../twenty-config/config-variables.ts | 8 +-
.../usage/enums/usage-resource-type.enum.ts | 1 +
.../usage/services/usage-analytics.service.ts | 11 +-
.../core-modules/usage/usage.resolver.ts | 44 ++++--
.../usage/utils/to-display-credits.util.ts | 7 +-
.../src/constants/DocumentationPaths.ts | 4 +-
45 files changed, 521 insertions(+), 151 deletions(-)
rename packages/twenty-docs/l/ar/user-guide/billing/capabilities/{workflow-credits.mdx => credits.mdx} (100%)
rename packages/twenty-docs/l/cs/user-guide/billing/capabilities/{workflow-credits.mdx => credits.mdx} (100%)
rename packages/twenty-docs/l/de/user-guide/billing/capabilities/{workflow-credits.mdx => credits.mdx} (100%)
rename packages/twenty-docs/l/es/user-guide/billing/capabilities/{workflow-credits.mdx => credits.mdx} (100%)
rename packages/twenty-docs/l/fr/user-guide/billing/capabilities/{workflow-credits.mdx => credits.mdx} (100%)
rename packages/twenty-docs/l/it/user-guide/billing/capabilities/{workflow-credits.mdx => credits.mdx} (100%)
rename packages/twenty-docs/l/ja/user-guide/billing/capabilities/{workflow-credits.mdx => credits.mdx} (100%)
rename packages/twenty-docs/l/ko/user-guide/billing/capabilities/{workflow-credits.mdx => credits.mdx} (100%)
rename packages/twenty-docs/l/pt/user-guide/billing/capabilities/{workflow-credits.mdx => credits.mdx} (100%)
rename packages/twenty-docs/l/ro/user-guide/billing/capabilities/{workflow-credits.mdx => credits.mdx} (100%)
rename packages/twenty-docs/l/ru/user-guide/billing/capabilities/{workflow-credits.mdx => credits.mdx} (100%)
rename packages/twenty-docs/l/tr/user-guide/billing/capabilities/{workflow-credits.mdx => credits.mdx} (100%)
rename packages/twenty-docs/l/zh/user-guide/billing/capabilities/{workflow-credits.mdx => credits.mdx} (100%)
create mode 100644 packages/twenty-docs/user-guide/billing/capabilities/credits.mdx
delete mode 100644 packages/twenty-docs/user-guide/billing/capabilities/workflow-credits.mdx
create mode 100644 packages/twenty-front/src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
create mode 100644 packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerNoMoreCredits.tsx
create mode 100644 packages/twenty-front/src/modules/settings/billing/hooks/useGetNextMeteredBillingPrice.ts
delete mode 100644 packages/twenty-front/src/modules/workspace/hooks/useIsSomeMeteredProductCapReached.ts
create mode 100644 packages/twenty-front/src/modules/workspace/states/hasReachedCurrentBillingPeriodCapSelector.ts
diff --git a/packages/twenty-docs/docs.json b/packages/twenty-docs/docs.json
index b3ff28b945..48cc9415ac 100644
--- a/packages/twenty-docs/docs.json
+++ b/packages/twenty-docs/docs.json
@@ -309,7 +309,7 @@
"group": "Capabilities",
"pages": [
"user-guide/billing/capabilities/pricing-plans",
- "user-guide/billing/capabilities/workflow-credits"
+ "user-guide/billing/capabilities/credits"
]
},
{
@@ -756,7 +756,7 @@
"group": "Capacités",
"pages": [
"l/fr/user-guide/billing/capabilities/pricing-plans",
- "l/fr/user-guide/billing/capabilities/workflow-credits"
+ "l/fr/user-guide/billing/capabilities/credits"
]
},
{
@@ -1203,7 +1203,7 @@
"group": "القدرات",
"pages": [
"l/ar/user-guide/billing/capabilities/pricing-plans",
- "l/ar/user-guide/billing/capabilities/workflow-credits"
+ "l/ar/user-guide/billing/capabilities/credits"
]
},
{
@@ -1650,7 +1650,7 @@
"group": "Možnosti",
"pages": [
"l/cs/user-guide/billing/capabilities/pricing-plans",
- "l/cs/user-guide/billing/capabilities/workflow-credits"
+ "l/cs/user-guide/billing/capabilities/credits"
]
},
{
@@ -2097,7 +2097,7 @@
"group": "Funktionen",
"pages": [
"l/de/user-guide/billing/capabilities/pricing-plans",
- "l/de/user-guide/billing/capabilities/workflow-credits"
+ "l/de/user-guide/billing/capabilities/credits"
]
},
{
@@ -2544,7 +2544,7 @@
"group": "Capacidades",
"pages": [
"l/es/user-guide/billing/capabilities/pricing-plans",
- "l/es/user-guide/billing/capabilities/workflow-credits"
+ "l/es/user-guide/billing/capabilities/credits"
]
},
{
@@ -2991,7 +2991,7 @@
"group": "Funzionalità",
"pages": [
"l/it/user-guide/billing/capabilities/pricing-plans",
- "l/it/user-guide/billing/capabilities/workflow-credits"
+ "l/it/user-guide/billing/capabilities/credits"
]
},
{
@@ -3438,7 +3438,7 @@
"group": "機能",
"pages": [
"l/ja/user-guide/billing/capabilities/pricing-plans",
- "l/ja/user-guide/billing/capabilities/workflow-credits"
+ "l/ja/user-guide/billing/capabilities/credits"
]
},
{
@@ -3885,7 +3885,7 @@
"group": "기능",
"pages": [
"l/ko/user-guide/billing/capabilities/pricing-plans",
- "l/ko/user-guide/billing/capabilities/workflow-credits"
+ "l/ko/user-guide/billing/capabilities/credits"
]
},
{
@@ -4332,7 +4332,7 @@
"group": "Capabilities",
"pages": [
"l/pt/user-guide/billing/capabilities/pricing-plans",
- "l/pt/user-guide/billing/capabilities/workflow-credits"
+ "l/pt/user-guide/billing/capabilities/credits"
]
},
{
@@ -4779,7 +4779,7 @@
"group": "Capabilities",
"pages": [
"l/ro/user-guide/billing/capabilities/pricing-plans",
- "l/ro/user-guide/billing/capabilities/workflow-credits"
+ "l/ro/user-guide/billing/capabilities/credits"
]
},
{
@@ -5226,7 +5226,7 @@
"group": "Возможности",
"pages": [
"l/ru/user-guide/billing/capabilities/pricing-plans",
- "l/ru/user-guide/billing/capabilities/workflow-credits"
+ "l/ru/user-guide/billing/capabilities/credits"
]
},
{
@@ -5673,7 +5673,7 @@
"group": "Yetkinlikler",
"pages": [
"l/tr/user-guide/billing/capabilities/pricing-plans",
- "l/tr/user-guide/billing/capabilities/workflow-credits"
+ "l/tr/user-guide/billing/capabilities/credits"
]
},
{
@@ -6120,7 +6120,7 @@
"group": "功能",
"pages": [
"l/zh/user-guide/billing/capabilities/pricing-plans",
- "l/zh/user-guide/billing/capabilities/workflow-credits"
+ "l/zh/user-guide/billing/capabilities/credits"
]
},
{
@@ -6734,7 +6734,11 @@
},
{
"source": "/user-guide/billing/workflow-credits",
- "destination": "/user-guide/billing/capabilities/workflow-credits"
+ "destination": "/user-guide/billing/capabilities/credits"
+ },
+ {
+ "source": "/user-guide/billing/capabilities/workflow-credits",
+ "destination": "/user-guide/billing/capabilities/credits"
},
{
"source": "/user-guide/billing/billing-faq",
diff --git a/packages/twenty-docs/l/ar/user-guide/billing/capabilities/workflow-credits.mdx b/packages/twenty-docs/l/ar/user-guide/billing/capabilities/credits.mdx
similarity index 100%
rename from packages/twenty-docs/l/ar/user-guide/billing/capabilities/workflow-credits.mdx
rename to packages/twenty-docs/l/ar/user-guide/billing/capabilities/credits.mdx
diff --git a/packages/twenty-docs/l/cs/user-guide/billing/capabilities/workflow-credits.mdx b/packages/twenty-docs/l/cs/user-guide/billing/capabilities/credits.mdx
similarity index 100%
rename from packages/twenty-docs/l/cs/user-guide/billing/capabilities/workflow-credits.mdx
rename to packages/twenty-docs/l/cs/user-guide/billing/capabilities/credits.mdx
diff --git a/packages/twenty-docs/l/de/user-guide/billing/capabilities/workflow-credits.mdx b/packages/twenty-docs/l/de/user-guide/billing/capabilities/credits.mdx
similarity index 100%
rename from packages/twenty-docs/l/de/user-guide/billing/capabilities/workflow-credits.mdx
rename to packages/twenty-docs/l/de/user-guide/billing/capabilities/credits.mdx
diff --git a/packages/twenty-docs/l/es/user-guide/billing/capabilities/workflow-credits.mdx b/packages/twenty-docs/l/es/user-guide/billing/capabilities/credits.mdx
similarity index 100%
rename from packages/twenty-docs/l/es/user-guide/billing/capabilities/workflow-credits.mdx
rename to packages/twenty-docs/l/es/user-guide/billing/capabilities/credits.mdx
diff --git a/packages/twenty-docs/l/fr/user-guide/billing/capabilities/workflow-credits.mdx b/packages/twenty-docs/l/fr/user-guide/billing/capabilities/credits.mdx
similarity index 100%
rename from packages/twenty-docs/l/fr/user-guide/billing/capabilities/workflow-credits.mdx
rename to packages/twenty-docs/l/fr/user-guide/billing/capabilities/credits.mdx
diff --git a/packages/twenty-docs/l/it/user-guide/billing/capabilities/workflow-credits.mdx b/packages/twenty-docs/l/it/user-guide/billing/capabilities/credits.mdx
similarity index 100%
rename from packages/twenty-docs/l/it/user-guide/billing/capabilities/workflow-credits.mdx
rename to packages/twenty-docs/l/it/user-guide/billing/capabilities/credits.mdx
diff --git a/packages/twenty-docs/l/ja/user-guide/billing/capabilities/workflow-credits.mdx b/packages/twenty-docs/l/ja/user-guide/billing/capabilities/credits.mdx
similarity index 100%
rename from packages/twenty-docs/l/ja/user-guide/billing/capabilities/workflow-credits.mdx
rename to packages/twenty-docs/l/ja/user-guide/billing/capabilities/credits.mdx
diff --git a/packages/twenty-docs/l/ja/user-guide/billing/how-tos/billing-faq.mdx b/packages/twenty-docs/l/ja/user-guide/billing/how-tos/billing-faq.mdx
index 5904e5ef27..89b0cdec33 100644
--- a/packages/twenty-docs/l/ja/user-guide/billing/how-tos/billing-faq.mdx
+++ b/packages/twenty-docs/l/ja/user-guide/billing/how-tos/billing-faq.mdx
@@ -46,8 +46,8 @@ description: Frequently asked questions about Twenty pricing and billing.
The number of credits depends on your billing cycle, not your plan:
- * **Monthly subscriptions**: 5 million credits per month
- * **Yearly subscriptions**: 50 million credits per year
+ * **Monthly subscriptions**: 5 credits per month
+ * **Yearly subscriptions**: 50 credits per year
diff --git a/packages/twenty-docs/l/ko/user-guide/billing/capabilities/workflow-credits.mdx b/packages/twenty-docs/l/ko/user-guide/billing/capabilities/credits.mdx
similarity index 100%
rename from packages/twenty-docs/l/ko/user-guide/billing/capabilities/workflow-credits.mdx
rename to packages/twenty-docs/l/ko/user-guide/billing/capabilities/credits.mdx
diff --git a/packages/twenty-docs/l/pt/user-guide/billing/capabilities/workflow-credits.mdx b/packages/twenty-docs/l/pt/user-guide/billing/capabilities/credits.mdx
similarity index 100%
rename from packages/twenty-docs/l/pt/user-guide/billing/capabilities/workflow-credits.mdx
rename to packages/twenty-docs/l/pt/user-guide/billing/capabilities/credits.mdx
diff --git a/packages/twenty-docs/l/ro/user-guide/billing/capabilities/workflow-credits.mdx b/packages/twenty-docs/l/ro/user-guide/billing/capabilities/credits.mdx
similarity index 100%
rename from packages/twenty-docs/l/ro/user-guide/billing/capabilities/workflow-credits.mdx
rename to packages/twenty-docs/l/ro/user-guide/billing/capabilities/credits.mdx
diff --git a/packages/twenty-docs/l/ru/user-guide/billing/capabilities/workflow-credits.mdx b/packages/twenty-docs/l/ru/user-guide/billing/capabilities/credits.mdx
similarity index 100%
rename from packages/twenty-docs/l/ru/user-guide/billing/capabilities/workflow-credits.mdx
rename to packages/twenty-docs/l/ru/user-guide/billing/capabilities/credits.mdx
diff --git a/packages/twenty-docs/l/tr/user-guide/billing/capabilities/workflow-credits.mdx b/packages/twenty-docs/l/tr/user-guide/billing/capabilities/credits.mdx
similarity index 100%
rename from packages/twenty-docs/l/tr/user-guide/billing/capabilities/workflow-credits.mdx
rename to packages/twenty-docs/l/tr/user-guide/billing/capabilities/credits.mdx
diff --git a/packages/twenty-docs/l/zh/user-guide/billing/capabilities/workflow-credits.mdx b/packages/twenty-docs/l/zh/user-guide/billing/capabilities/credits.mdx
similarity index 100%
rename from packages/twenty-docs/l/zh/user-guide/billing/capabilities/workflow-credits.mdx
rename to packages/twenty-docs/l/zh/user-guide/billing/capabilities/credits.mdx
diff --git a/packages/twenty-docs/navigation/base-structure.json b/packages/twenty-docs/navigation/base-structure.json
index fe476658cb..138da0db2a 100644
--- a/packages/twenty-docs/navigation/base-structure.json
+++ b/packages/twenty-docs/navigation/base-structure.json
@@ -308,7 +308,7 @@
"label": "Capabilities",
"pages": [
"user-guide/billing/capabilities/pricing-plans",
- "user-guide/billing/capabilities/workflow-credits"
+ "user-guide/billing/capabilities/credits"
]
},
{
diff --git a/packages/twenty-docs/navigation/navigation-schema.json b/packages/twenty-docs/navigation/navigation-schema.json
index 2ad19a5db1..5ac78aba20 100644
--- a/packages/twenty-docs/navigation/navigation-schema.json
+++ b/packages/twenty-docs/navigation/navigation-schema.json
@@ -307,7 +307,7 @@
"label": "Capabilities",
"pages": [
"user-guide/billing/capabilities/pricing-plans",
- "user-guide/billing/capabilities/workflow-credits"
+ "user-guide/billing/capabilities/credits"
]
},
{
diff --git a/packages/twenty-docs/user-guide/billing/capabilities/credits.mdx b/packages/twenty-docs/user-guide/billing/capabilities/credits.mdx
new file mode 100644
index 0000000000..8eb58a6be4
--- /dev/null
+++ b/packages/twenty-docs/user-guide/billing/capabilities/credits.mdx
@@ -0,0 +1,55 @@
+---
+title: Credits
+description: Understanding how credits power workflows, AI agents, and the AI chatbot — and how to manage your balance.
+---
+
+## Overview
+
+Credits power workflow automations and AI capabilities. They are consumed when workflow actions execute, when AI agents process tasks inside workflows, and whithin the AI chat.
+
+## Credit Allocation
+
+Credits are based on your billing cycle, not your plan:
+
+| Billing Cycle | Credits |
+|---------------|---------|
+| Monthly | 5/month |
+| Yearly | 50/year |
+
+
+The 5 monthly credits are designed to empower you to run automations without worrying about costs. For most workflows using standard actions, this is more than enough. You'll only need additional credits when running advanced code nodes or AI-powered features.
+
+
+## Credit Rollover
+
+Unused credits at the end of a billing period are automatically carried over to the next period.
+
+- **Cap**: The rollover amount is capped at one period's full allocation, so you can never carry over more credits than your plan provides per period.
+- **Visibility**: When rollover credits are available, they appear as a separate **Rollover Credits** line in **Settings → Billing**, alongside a **Total Available** balance.
+
+## Credit Consumption
+
+Different actions consume different amounts of credits:
+
+| Action Type | Credit Usage |
+|-------------|--------------|
+| **Basic operations** (search, update, create records) | Minimal |
+| **Complex operations** (code nodes, external API calls) | More credits |
+| **AI prompt & AI chats** | Variable based on usage |
+
+Credits are deducted in real-time when workflows execute.
+
+## Monitoring Usage
+
+Track your credit consumption:
+1. Go to **Settings → Billing**
+2. View your current usage and remaining credits
+3. Monitor trends to plan for additional credits if needed
+
+## Purchasing Additional Credits
+
+Need more credits?
+1. Go to **Settings → Billing**
+2. Click on the option to purchase additional credit packs
+3. Select the amount you need
+
diff --git a/packages/twenty-docs/user-guide/billing/capabilities/workflow-credits.mdx b/packages/twenty-docs/user-guide/billing/capabilities/workflow-credits.mdx
deleted file mode 100644
index 83129ce029..0000000000
--- a/packages/twenty-docs/user-guide/billing/capabilities/workflow-credits.mdx
+++ /dev/null
@@ -1,48 +0,0 @@
----
-title: Workflow Credits
-description: Understanding workflow credits, consumption, and how to purchase more.
----
-
-## Overview
-
-Credits power your workflow automations in Twenty. Every workflow action consumes credits based on its complexity.
-
-## Credit Allocation
-
-Credits are based on your billing cycle, not your plan:
-
-| Billing Cycle | Credits |
-|---------------|---------|
-| Monthly | 5 million/month |
-| Yearly | 50 million/year |
-
-
-The 5 million monthly credits are designed to empower you to run automations without worrying about costs. For most workflows using standard actions, this is more than enough. You'll only need additional credits when running advanced code nodes or AI-powered features.
-
-
-## Credit Consumption
-
-Different actions consume different amounts of credits:
-
-| Action Type | Credit Usage |
-|-------------|--------------|
-| **Basic operations** (search, update, create records) | Minimal |
-| **Complex operations** (code nodes, external API calls) | More credits |
-| **AI prompts** (coming soon) | Variable based on usage |
-
-Credits are deducted in real-time when workflows execute.
-
-## Monitoring Usage
-
-Track your credit consumption:
-1. Go to **Settings → Billing**
-2. View your current usage and remaining credits
-3. Monitor trends to plan for additional credits if needed
-
-## Purchasing Additional Credits
-
-Need more credits?
-1. Go to **Settings → Billing**
-2. Click on the option to purchase additional credit packs
-3. Select the amount you need
-
diff --git a/packages/twenty-docs/user-guide/billing/how-tos/billing-faq.mdx b/packages/twenty-docs/user-guide/billing/how-tos/billing-faq.mdx
index e019844ee0..19892c6c21 100644
--- a/packages/twenty-docs/user-guide/billing/how-tos/billing-faq.mdx
+++ b/packages/twenty-docs/user-guide/billing/how-tos/billing-faq.mdx
@@ -44,8 +44,8 @@ You will find this under `Settings → Billing`.
The number of credits depends on your billing cycle, not your plan:
-- **Monthly subscriptions**: 5 million credits per month
-- **Yearly subscriptions**: 50 million credits per year
+- **Monthly subscriptions**: 5 credits per month
+- **Yearly subscriptions**: 50 credits per year
diff --git a/packages/twenty-docs/user-guide/workflows/capabilities/workflow-credits.mdx b/packages/twenty-docs/user-guide/workflows/capabilities/workflow-credits.mdx
index 7e85cd1269..cc88fd18d4 100644
--- a/packages/twenty-docs/user-guide/workflows/capabilities/workflow-credits.mdx
+++ b/packages/twenty-docs/user-guide/workflows/capabilities/workflow-credits.mdx
@@ -11,11 +11,11 @@ Workflow credits are allocated based on your billing cycle, not your plan tier:
| Billing Cycle | Credits |
|---------------|---------|
-| **Monthly subscription** | 5 million credits per month |
-| **Yearly subscription** | 50 million credits per year |
+| **Monthly subscription** | 5 credits per month |
+| **Yearly subscription** | 50 credits per year |
-5 million monthly credits are generous for standard automations. Most teams won't exceed this limit with typical workflow usage. Additional credits are primarily needed for advanced Code actions and AI-powered workflows.
+5 monthly credits are generous for standard automations. Most teams won't exceed this limit with typical workflow usage. Additional credits are primarily needed for advanced Code actions and AI-powered workflows.
## How Credit Consumption Works
diff --git a/packages/twenty-front/src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx b/packages/twenty-front/src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
new file mode 100644
index 0000000000..abf02e0c87
--- /dev/null
+++ b/packages/twenty-front/src/modules/ai/components/AIChatNoMoreBillingCreditsBanner.tsx
@@ -0,0 +1,149 @@
+import { AiChatBanner } from '@/ai/components/AiChatBanner';
+import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
+import { useNumberFormat } from '@/localization/hooks/useNumberFormat';
+import { useEndSubscriptionTrialPeriod } from '@/settings/billing/hooks/useEndSubscriptionTrialPeriod';
+import { useGetNextMeteredBillingPrice } from '@/settings/billing/hooks/useGetNextMeteredBillingPrice';
+import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
+import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
+import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
+import { useModal } from '@/ui/layout/modal/hooks/useModal';
+import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
+import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
+import { useMutation } from '@apollo/client/react';
+import { t } from '@lingui/core/macro';
+import { isDefined } from 'twenty-shared/utils';
+import {
+ PermissionFlagType,
+ SetMeteredSubscriptionPriceDocument,
+ SubscriptionInterval,
+ SubscriptionStatus,
+} from '~/generated-metadata/graphql';
+
+const AI_CHAT_END_TRIAL_PERIOD_MODAL_ID = 'ai-chat-end-trial-period-modal';
+const AI_CHAT_UPGRADE_CREDIT_PLAN_MODAL_ID =
+ 'ai-chat-upgrade-credit-plan-modal';
+
+export const AIChatNoMoreBillingCreditsBanner = () => {
+ const subscriptionStatus = useSubscriptionStatus();
+
+ const { openModal } = useModal();
+ const { endTrialPeriod, isLoading: isEndTrialLoading } =
+ useEndSubscriptionTrialPeriod();
+ const nextMeteredBillingPrice = useGetNextMeteredBillingPrice();
+ const { formatNumber } = useNumberFormat();
+ const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
+
+ const [currentWorkspace, setCurrentWorkspace] = useAtomState(
+ currentWorkspaceState,
+ );
+
+ const [setMeteredSubscriptionPrice, { loading: isUpgrading }] = useMutation(
+ SetMeteredSubscriptionPriceDocument,
+ );
+
+ const { [PermissionFlagType.WORKSPACE]: hasPermissionToManageBilling } =
+ usePermissionFlagMap();
+
+ if (!hasPermissionToManageBilling) {
+ return null;
+ }
+
+ const isTrialing = subscriptionStatus === SubscriptionStatus.Trialing;
+
+ const nextTierCredits = isDefined(nextMeteredBillingPrice)
+ ? formatNumber(nextMeteredBillingPrice.tiers[0].upTo, {
+ abbreviate: true,
+ decimals: 2,
+ })
+ : null;
+
+ const nextTierPrice = isDefined(nextMeteredBillingPrice)
+ ? formatNumber(nextMeteredBillingPrice.tiers[0].flatAmount / 100)
+ : null;
+
+ const nextTierInterval = isDefined(nextMeteredBillingPrice)
+ ? nextMeteredBillingPrice.recurringInterval === SubscriptionInterval.Month
+ ? t`month`
+ : t`year`
+ : null;
+
+ const message = isTrialing
+ ? t`You've hit your usage limit. Subscribe for more usage.`
+ : isDefined(nextMeteredBillingPrice)
+ ? t`You've hit your usage limit. \nUpgrade to ${nextTierCredits} credits for $${nextTierPrice}/${nextTierInterval}.`
+ : t`You've hit your usage limit. \nReach to our support team to upgrade.`;
+
+ const buttonTitle = isTrialing
+ ? t`Subscribe Now`
+ : isDefined(nextMeteredBillingPrice)
+ ? t`Upgrade`
+ : undefined;
+
+ const handleButtonClick = isTrialing
+ ? () => openModal(AI_CHAT_END_TRIAL_PERIOD_MODAL_ID)
+ : isDefined(nextMeteredBillingPrice)
+ ? () => openModal(AI_CHAT_UPGRADE_CREDIT_PLAN_MODAL_ID)
+ : undefined;
+
+ const handleUpgradeConfirm = async () => {
+ if (!isDefined(nextMeteredBillingPrice)) return;
+ try {
+ const { data } = await setMeteredSubscriptionPrice({
+ variables: { priceId: nextMeteredBillingPrice.stripePriceId },
+ });
+ if (
+ isDefined(
+ data?.setMeteredSubscriptionPrice.currentBillingSubscription,
+ ) &&
+ isDefined(currentWorkspace)
+ ) {
+ setCurrentWorkspace({
+ ...currentWorkspace,
+ currentBillingSubscription:
+ data.setMeteredSubscriptionPrice.currentBillingSubscription,
+ billingSubscriptions:
+ data.setMeteredSubscriptionPrice.billingSubscriptions,
+ });
+ }
+ enqueueSuccessSnackBar({ message: t`Credit plan upgraded.` });
+ } catch {
+ enqueueErrorSnackBar({ message: t`Failed to upgrade credit plan.` });
+ }
+ };
+
+ return (
+ <>
+
+ {isTrialing && (
+
+ )}
+ {!isTrialing && (
+
+ )}
+ >
+ );
+};
diff --git a/packages/twenty-front/src/modules/ai/components/AiChatBanner.tsx b/packages/twenty-front/src/modules/ai/components/AiChatBanner.tsx
index 5b3d062ace..fbf6baa505 100644
--- a/packages/twenty-front/src/modules/ai/components/AiChatBanner.tsx
+++ b/packages/twenty-front/src/modules/ai/components/AiChatBanner.tsx
@@ -51,6 +51,7 @@ const StyledMessage = styled.p<{ variant: AiChatBannerVariant }>`
line-height: 1.4;
margin: 0;
min-width: 0;
+ white-space: pre-line;
`;
export type AiChatBannerProps = {
diff --git a/packages/twenty-front/src/modules/ai/components/AiChatEditorSection.tsx b/packages/twenty-front/src/modules/ai/components/AiChatEditorSection.tsx
index b67f8935b2..11d6408ce4 100644
--- a/packages/twenty-front/src/modules/ai/components/AiChatEditorSection.tsx
+++ b/packages/twenty-front/src/modules/ai/components/AiChatEditorSection.tsx
@@ -3,6 +3,7 @@ import { EditorContent } from '@tiptap/react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { AiChatEmptyState } from '@/ai/components/AiChatEmptyState';
+import { AIChatNoMoreBillingCreditsBanner } from '@/ai/components/AIChatNoMoreBillingCreditsBanner';
import { AiChatStandaloneError } from '@/ai/components/AiChatStandaloneError';
import { AgentChatContextPreview } from '@/ai/components/internal/AgentChatContextPreview';
import { AgentChatFileUploadButton } from '@/ai/components/internal/AgentChatFileUploadButton';
@@ -10,13 +11,15 @@ import { AiChatContextUsageButton } from '@/ai/components/internal/AiChatContext
import { AiChatEditorFocusEffect } from '@/ai/components/internal/AiChatEditorFocusEffect';
import { AiChatSkeletonLoader } from '@/ai/components/internal/AiChatSkeletonLoader';
import { SendMessageButton } from '@/ai/components/internal/SendMessageButton';
+import { useAgentChatModelId } from '@/ai/hooks/useAgentChatModelId';
import { useAiChatEditor } from '@/ai/hooks/useAiChatEditor';
import { useAiModelOptions } from '@/ai/hooks/useAiModelOptions';
-import { useAgentChatModelId } from '@/ai/hooks/useAgentChatModelId';
import { agentChatUserSelectedModelState } from '@/ai/states/agentChatUserSelectedModelState';
import { Select } from '@/ui/input/components/Select';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
+import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
+import { hasReachedCurrentBillingPeriodCapSelector } from '@/workspace/states/hasReachedCurrentBillingPeriodCapSelector';
import { type SelectOption } from 'twenty-ui/input';
const StyledInputArea = styled.div<{ isMobile: boolean }>`
@@ -106,6 +109,9 @@ const StyledRightButtonsContainer = styled.div`
export const AiChatEditorSection = () => {
const isMobile = useIsMobile();
+ const hasReachedCurrentBillingPeriodCap = useAtomStateValue(
+ hasReachedCurrentBillingPeriodCapSelector,
+ );
const { options, pinnedOption } = useAiModelOptions({
variant: 'pinned-default',
});
@@ -134,6 +140,9 @@ export const AiChatEditorSection = () => {
+ {hasReachedCurrentBillingPeriodCap && (
+
+ )}
diff --git a/packages/twenty-front/src/modules/ai/components/RoutingDebugDisplay.tsx b/packages/twenty-front/src/modules/ai/components/RoutingDebugDisplay.tsx
index 3f5da38ed0..54abfe14ad 100644
--- a/packages/twenty-front/src/modules/ai/components/RoutingDebugDisplay.tsx
+++ b/packages/twenty-front/src/modules/ai/components/RoutingDebugDisplay.tsx
@@ -9,6 +9,7 @@ import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { useLingui } from '@lingui/react/macro';
import { type DataMessagePart } from 'twenty-shared/ai';
import { type JsonValue } from 'type-fest';
+import { useUsageValueFormatter } from '@/settings/usage/hooks/useUsageValueFormatter';
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
const StyledContainer = styled.div`
@@ -157,6 +158,7 @@ type TimingTabProps = {
const TimingTab = ({ debug }: TimingTabProps) => {
const { t } = useLingui();
+ const { formatUsageValue } = useUsageValueFormatter();
const totalTime =
debug.agentExecutionStartTimeMs !== undefined
? `${debug.agentExecutionStartTimeMs + (debug.agentExecutionTimeMs || 0)}ms`
@@ -164,7 +166,7 @@ const TimingTab = ({ debug }: TimingTabProps) => {
const totalCost =
debug.totalCostInCredits !== undefined
- ? formatNumber(debug.totalCostInCredits)
+ ? formatUsageValue(debug.totalCostInCredits)
: undefined;
return (
@@ -231,10 +233,7 @@ const TimingTab = ({ debug }: TimingTabProps) => {
: undefined
}
/>
-
+
);
};
diff --git a/packages/twenty-front/src/modules/ai/components/internal/AiChatContextUsageButton.tsx b/packages/twenty-front/src/modules/ai/components/internal/AiChatContextUsageButton.tsx
index 612cb4bd49..79d749b0d4 100644
--- a/packages/twenty-front/src/modules/ai/components/internal/AiChatContextUsageButton.tsx
+++ b/packages/twenty-front/src/modules/ai/components/internal/AiChatContextUsageButton.tsx
@@ -14,8 +14,9 @@ import {
type AgentChatLastMessageUsage,
} from '@/ai/states/agentChatUsageComponentFamilyState';
import { currentAiChatThreadState } from '@/ai/states/currentAiChatThreadState';
-import { SettingsBillingLabelValueItem } from '@/settings/billing/components/internal/SettingsBillingLabelValueItem';
import { billingState } from '@/client-config/states/billingState';
+import { SettingsBillingLabelValueItem } from '@/settings/billing/components/internal/SettingsBillingLabelValueItem';
+import { useUsageValueFormatter } from '@/settings/usage/hooks/useUsageValueFormatter';
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
@@ -103,12 +104,12 @@ export const AiChatContextUsageButton = () => {
);
const billing = useAtomStateValue(billingState);
const isBillingEnabled = billing?.isBillingEnabled ?? false;
+ const { formatUsageValue } = useUsageValueFormatter();
- // Values from the streaming API arrive as display credits (micro-credits / 1000).
- // 1000 display credits = $1. Convert accordingly.
+ // Values from the streaming API arrive as display credits (micro-credits).
const formatChatCost = (displayCredits: number): string => {
if (isBillingEnabled) {
- return `${formatNumber(displayCredits, { decimals: 1 })} credits`;
+ return `${formatUsageValue(displayCredits)}`;
}
const dollars = displayCredits / 1000;
diff --git a/packages/twenty-front/src/modules/information-banner/components/InformationBannerWrapper.tsx b/packages/twenty-front/src/modules/information-banner/components/InformationBannerWrapper.tsx
index 3cd550c9eb..fb33bf0ee0 100644
--- a/packages/twenty-front/src/modules/information-banner/components/InformationBannerWrapper.tsx
+++ b/packages/twenty-front/src/modules/information-banner/components/InformationBannerWrapper.tsx
@@ -11,10 +11,12 @@ import { InformationBannerMaintenance } from '@/information-banner/components/ma
import { InformationBannerReconnectAccountEmailAliases } from '@/information-banner/components/reconnect-account/InformationBannerReconnectAccountEmailAliases';
import { InformationBannerReconnectAccountInsufficientPermissions } from '@/information-banner/components/reconnect-account/InformationBannerReconnectAccountInsufficientPermissions';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
-import { useIsSomeMeteredProductCapReached } from '@/workspace/hooks/useIsSomeMeteredProductCapReached';
+import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useIsWorkspaceActivationStatusEqualsTo } from '@/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo';
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
+import { hasReachedCurrentBillingPeriodCapSelector } from '@/workspace/states/hasReachedCurrentBillingPeriodCapSelector';
+import { InformationBannerNoMoreCredits } from '@/information-banner/components/billing/InformationBannerNoMoreCredits';
import {
PermissionFlagType,
SubscriptionStatus,
@@ -36,7 +38,9 @@ export const InformationBannerWrapper = () => {
const isWorkspaceSuspended = useIsWorkspaceActivationStatusEqualsTo(
WorkspaceActivationStatus.SUSPENDED,
);
- const isSomeMeteredProductCapReached = useIsSomeMeteredProductCapReached();
+ const hasReachedCurrentBillingPeriodCap = useAtomStateValue(
+ hasReachedCurrentBillingPeriodCapSelector,
+ );
const displayBillingSubscriptionPausedBanner =
isWorkspaceSuspended && subscriptionStatus === SubscriptionStatus.Paused;
@@ -49,9 +53,15 @@ export const InformationBannerWrapper = () => {
subscriptionStatus === SubscriptionStatus.Unpaid;
const displayEndTrialPeriodBanner =
- isSomeMeteredProductCapReached &&
+ hasReachedCurrentBillingPeriodCap &&
subscriptionStatus === SubscriptionStatus.Trialing;
+ const displayNoMoreCreditsBanner =
+ !isWorkspaceSuspended &&
+ !displayFailPaymentInfoBanner &&
+ !displayEndTrialPeriodBanner &&
+ hasReachedCurrentBillingPeriodCap;
+
return (
@@ -70,6 +80,7 @@ export const InformationBannerWrapper = () => {
)}
{displayFailPaymentInfoBanner && }
{displayEndTrialPeriodBanner && }
+ {displayNoMoreCreditsBanner && }
);
};
diff --git a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerNoMoreCredits.tsx b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerNoMoreCredits.tsx
new file mode 100644
index 0000000000..b1a18cf0da
--- /dev/null
+++ b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerNoMoreCredits.tsx
@@ -0,0 +1,30 @@
+import { InformationBanner } from '@/information-banner/components/InformationBanner';
+import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
+import { useLingui } from '@lingui/react/macro';
+import { SettingsPath } from 'twenty-shared/types';
+import { PermissionFlagType } from '~/generated-metadata/graphql';
+import { useNavigateSettings } from '~/hooks/useNavigateSettings';
+
+export const InformationBannerNoMoreCredits = () => {
+ const { t } = useLingui();
+
+ const { [PermissionFlagType.WORKSPACE]: hasPermissionToUpdateCreditPlan } =
+ usePermissionFlagMap();
+
+ const navigateSettings = useNavigateSettings();
+
+ return (
+ navigateSettings(SettingsPath.Billing)}
+ />
+ );
+};
diff --git a/packages/twenty-front/src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx b/packages/twenty-front/src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
index 8631fed813..7e008fe074 100644
--- a/packages/twenty-front/src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
+++ b/packages/twenty-front/src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
@@ -7,13 +7,20 @@ import { useBillingWording } from '@/settings/billing/hooks/useBillingWording';
import { useCurrentBillingFlags } from '@/settings/billing/hooks/useCurrentBillingFlags';
import { useCurrentMetered } from '@/settings/billing/hooks/useCurrentMetered';
import { useGetWorkflowNodeExecutionUsage } from '@/settings/billing/hooks/useGetWorkflowNodeExecutionUsage';
+import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl';
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { useContext } from 'react';
+import { DOCUMENTATION_PATHS } from 'twenty-shared/constants';
import { SettingsPath } from 'twenty-shared/types';
import { formatToShortNumber, getSettingsPath } from 'twenty-shared/utils';
-import { H2Title, HorizontalSeparator, IconChartBar } from 'twenty-ui/display';
+import {
+ H2Title,
+ HorizontalSeparator,
+ IconChartBar,
+ IconExternalLink,
+} from 'twenty-ui/display';
import { ProgressBar } from 'twenty-ui/feedback';
import { Button } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
@@ -22,6 +29,8 @@ import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { SubscriptionStatus } from '~/generated-metadata/graphql';
const StyledCreditUsageFooterActions = styled.div`
+ display: flex;
+ gap: ${themeCssVariables.spacing[2]};
margin-top: ${themeCssVariables.spacing[4]};
`;
@@ -49,9 +58,9 @@ export const SettingsBillingCreditsSection = ({
const {
usedCredits,
grantedCredits,
- rolloverCredits,
totalGrantedCredits,
unitPriceCents,
+ rolloverCredits,
} = getWorkflowNodeExecutionUsage();
const progressBarValue = (usedCredits / totalGrantedCredits) * 100;
@@ -60,7 +69,7 @@ export const SettingsBillingCreditsSection = ({
const extraCreditsUsed = Math.max(0, usedCredits - totalGrantedCredits);
- const costPer1kExtraCredits = (unitPriceCents / 100) * 1000;
+ const costPerExtraCredits = unitPriceCents / 100;
const costExtraCredits = (extraCreditsUsed * unitPriceCents) / 100;
@@ -78,7 +87,7 @@ export const SettingsBillingCreditsSection = ({
)}
{rolloverCredits > 0 && (
@@ -123,8 +134,8 @@ export const SettingsBillingCreditsSection = ({
value={`${formatToShortNumber(extraCreditsUsed)}`}
/>
+