d99e479be8
Today, when a trialing user hits their AI usage cap inside the Ask AI chat, ending the trial bounces them to the Stripe billing portal (and, for card-less users, loses their place in the conversation). This PR makes activating a paid plan / topping up credits feel seamless from within the chat: Trial users with a card on file activate their subscription in place, without leaving the app. Trial users without a card are sent to the Stripe payment-method portal and, on return, the trial is ended automatically and they're dropped back into the exact Ask AI thread they came from. Credit-exhaustion and trial banners now reflect whether a payment method exists (Add Credit Card vs Subscribe Now / End Trial Period) and upgrade inline via a confirmation modal instead of redirecting to Settings. Uploading Screen Recording 2026-06-16 at 07.51.12.mov… https://github.com/user-attachments/assets/4ea77273-da63-4b32-b6f1-5ac9e9560651 <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21645?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
40 lines
1.4 KiB
TypeScript
40 lines
1.4 KiB
TypeScript
import { agentChatDraftsByThreadIdState } from '@/ai/states/agentChatDraftsByThreadIdState';
|
|
import { agentChatInputState } from '@/ai/states/agentChatInputState';
|
|
import { currentAiChatThreadState } from '@/ai/states/currentAiChatThreadState';
|
|
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
|
|
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
|
|
import { useStore } from 'jotai';
|
|
import { isDefined } from 'twenty-shared/utils';
|
|
|
|
export const useSwitchAgentChatThreadWithDraft = () => {
|
|
const [currentAiChatThread, setCurrentAiChatThread] = useAtomState(
|
|
currentAiChatThreadState,
|
|
);
|
|
const setAgentChatInput = useSetAtomState(agentChatInputState);
|
|
const setAgentChatDraftsByThreadId = useSetAtomState(
|
|
agentChatDraftsByThreadIdState,
|
|
);
|
|
const store = useStore();
|
|
|
|
const switchThreadWithDraft = (toThreadId: string) => {
|
|
const isSameThread = toThreadId === currentAiChatThread;
|
|
|
|
if (isDefined(currentAiChatThread)) {
|
|
setAgentChatDraftsByThreadId((prev) => ({
|
|
...prev,
|
|
[currentAiChatThread]: store.get(agentChatInputState.atom),
|
|
}));
|
|
}
|
|
|
|
setCurrentAiChatThread(toThreadId);
|
|
|
|
if (!isSameThread) {
|
|
const destinationDraft =
|
|
store.get(agentChatDraftsByThreadIdState.atom)[toThreadId] ?? '';
|
|
setAgentChatInput(destinationDraft);
|
|
}
|
|
};
|
|
|
|
return { switchThreadWithDraft };
|
|
};
|