diff --git a/src/components/AIRoadmap/AIRoadmap.tsx b/src/components/AIRoadmap/AIRoadmap.tsx
index 615287808..5c11b3eba 100644
--- a/src/components/AIRoadmap/AIRoadmap.tsx
+++ b/src/components/AIRoadmap/AIRoadmap.tsx
@@ -12,6 +12,10 @@ import { GenerateAIRoadmap } from './GenerateAIRoadmap';
import { AIRoadmapContent, type RoadmapNodeDetails } from './AIRoadmapContent';
import { AIRoadmapChat } from './AIRoadmapChat';
import { AlertCircleIcon } from 'lucide-react';
+import { isLoggedIn } from '../../lib/jwt';
+import { showLoginPopup } from '../../lib/popup';
+import { getAiCourseLimitOptions } from '../../queries/ai-course';
+import { billingDetailsOptions } from '../../queries/billing';
export type AIRoadmapChatActions = {
handleNodeClick: (node: RoadmapNodeDetails) => void;
@@ -42,7 +46,29 @@ export function AIRoadmap(props: AIRoadmapProps) {
error: aiRoadmapError,
} = useQuery(aiRoadmapOptions(roadmapSlug), queryClient);
+ const {
+ data: tokenUsage,
+ isLoading: isTokenUsageLoading,
+ refetch: refetchTokenUsage,
+ } = useQuery(getAiCourseLimitOptions(), queryClient);
+
+ const { data: userBillingDetails, isLoading: isBillingDetailsLoading } =
+ useQuery(billingDetailsOptions(), queryClient);
+
+ const isLimitExceeded = (tokenUsage?.used || 0) >= (tokenUsage?.limit || 0);
+ const isPaidUser = userBillingDetails?.status === 'active';
+
const handleRegenerate = async (prompt?: string) => {
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ if (!isPaidUser && isLimitExceeded) {
+ setShowUpgradeModal(true);
+ return;
+ }
+
flushSync(() => {
setIsRegenerating(true);
setRegeneratedSvgHtml(null);
@@ -76,12 +102,17 @@ export function AIRoadmap(props: AIRoadmapProps) {
},
onFinish: () => {
setIsRegenerating(false);
+ refetchTokenUsage();
queryClient.invalidateQueries(aiRoadmapOptions(roadmapSlug));
},
});
};
- const isLoading = isLoadingBySlug || isRegenerating;
+ const isLoading =
+ isLoadingBySlug ||
+ isRegenerating ||
+ isTokenUsageLoading ||
+ isBillingDetailsLoading;
const handleNodeClick = useCallback(
(node: RoadmapNodeDetails) => {
diff --git a/src/components/AIRoadmap/AIRoadmapChat.tsx b/src/components/AIRoadmap/AIRoadmapChat.tsx
index ca36feb2e..b865471b0 100644
--- a/src/components/AIRoadmap/AIRoadmapChat.tsx
+++ b/src/components/AIRoadmap/AIRoadmapChat.tsx
@@ -124,6 +124,10 @@ export function AIRoadmapChat(props: AIRoadmapChatProps) {
});
sendMessages(newMessages);
setInputValue('');
+
+ setTimeout(() => {
+ scrollToBottom('smooth');
+ }, 0);
},
[inputValue, isStreamingMessage, messages, sendMessages, setMessages],
);
@@ -174,11 +178,7 @@ export function AIRoadmapChat(props: AIRoadmapChatProps) {
useImperativeHandle(aiChatActionsRef, () => ({
handleNodeClick: (node: RoadmapNodeDetails) => {
- flushSync(() => {
- setInputValue(`Explain what is ${node.nodeTitle} topic in detail.`);
- });
-
- inputRef.current?.focus();
+ handleSubmitInput(`Explain what is "${node.nodeTitle}" topic in detail.`);
},
}));
@@ -304,6 +304,24 @@ export function AIRoadmapChat(props: AIRoadmapChatProps) {
)}
+ {!isLoggedIn() && (
+
+
+
Please login to continue
+
+
+ )}
+
void;
@@ -34,6 +37,7 @@ export function AIRoadmapRegenerate(props: AIRoadmapRegenerateProps) {
const [showPromptModal, setShowPromptModal] = useState(false);
const [showUpdatePreferencesModal, setShowUpdatePreferencesModal] =
useState(false);
+ const currentUser = useAuth();
const ref = useRef(null);
@@ -133,7 +137,9 @@ export function AIRoadmapRegenerate(props: AIRoadmapRegenerateProps) {
);
const showUpdatePreferences =
- aiRoadmap?.questionAndAnswers && aiRoadmap.questionAndAnswers.length > 0;
+ aiRoadmap?.questionAndAnswers &&
+ aiRoadmap.questionAndAnswers.length > 0 &&
+ currentUser?.id === aiRoadmap.userId;
return (
<>
@@ -183,6 +189,11 @@ export function AIRoadmapRegenerate(props: AIRoadmapRegenerateProps) {
{showUpdatePreferences && (
{
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
setIsDropdownVisible(false);
setShowUpdatePreferencesModal(true);
}}
@@ -193,6 +204,11 @@ export function AIRoadmapRegenerate(props: AIRoadmapRegenerateProps) {
{
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
setIsDropdownVisible(false);
onRegenerate();
}}
@@ -201,6 +217,11 @@ export function AIRoadmapRegenerate(props: AIRoadmapRegenerateProps) {
/>
{
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
setIsDropdownVisible(false);
setShowPromptModal(true);
}}
@@ -211,14 +232,28 @@ export function AIRoadmapRegenerate(props: AIRoadmapRegenerateProps) {
{
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ saveAIRoadmap();
+ }}
icon={SaveIcon}
label="Start Learning"
isLoading={isSavingAIRoadmap}
/>
{
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ editAIRoadmap();
+ }}
icon={PenSquare}
label="Edit in Editor"
isLoading={isEditingAIRoadmap}
diff --git a/src/components/GenerateCourse/GetAICourse.tsx b/src/components/GenerateCourse/GetAICourse.tsx
index 072b80689..dba0f8183 100644
--- a/src/components/GenerateCourse/GetAICourse.tsx
+++ b/src/components/GenerateCourse/GetAICourse.tsx
@@ -59,7 +59,6 @@ export function GetAICourse(props: GetAICourseProps) {
await generateCourse({
term: aiCourse.keyword,
- difficulty: aiCourse.difficulty,
slug: courseSlug,
prompt,
onCourseChange: (course, rawData) => {
@@ -68,7 +67,6 @@ export function GetAICourse(props: GetAICourseProps) {
{
...aiCourse,
title: course.title,
- difficulty: course.difficulty,
modules: course.modules,
},
);
@@ -89,7 +87,6 @@ export function GetAICourse(props: GetAICourseProps) {
course={{
title: aiCourse?.title || '',
modules: aiCourse?.modules || [],
- difficulty: aiCourse?.difficulty || 'Easy',
done: aiCourse?.done || [],
}}
isLoading={isLoading || isRegenerating}
diff --git a/src/components/GenerateGuide/AIGuide.tsx b/src/components/GenerateGuide/AIGuide.tsx
index d5a653fed..a733e69d7 100644
--- a/src/components/GenerateGuide/AIGuide.tsx
+++ b/src/components/GenerateGuide/AIGuide.tsx
@@ -16,6 +16,9 @@ import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
import { AIGuideChat } from './AIGuideChat';
import { AIGuideContent } from './AIGuideContent';
import { GenerateAIGuide } from './GenerateAIGuide';
+import { getAiCourseLimitOptions } from '../../queries/ai-course';
+import { billingDetailsOptions } from '../../queries/billing';
+import { showLoginPopup } from '../../lib/popup';
type AIGuideProps = {
guideSlug?: string;
@@ -37,6 +40,18 @@ export function AIGuide(props: AIGuideProps) {
queryClient,
);
+ const {
+ data: tokenUsage,
+ isLoading: isTokenUsageLoading,
+ refetch: refetchTokenUsage,
+ } = useQuery(getAiCourseLimitOptions(), queryClient);
+
+ const { data: userBillingDetails, isLoading: isBillingDetailsLoading } =
+ useQuery(billingDetailsOptions(), queryClient);
+
+ const isLimitExceeded = (tokenUsage?.used || 0) >= (tokenUsage?.limit || 0);
+ const isPaidUser = userBillingDetails?.status === 'active';
+
const { data: aiGuideSuggestions, isLoading: isAiGuideSuggestionsLoading } =
useQuery(
{
@@ -57,6 +72,16 @@ export function AIGuide(props: AIGuideProps) {
}, [aiGuideSuggestions]);
const handleRegenerate = async (prompt?: string) => {
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ if (!isPaidUser && isLimitExceeded) {
+ setShowUpgradeModal(true);
+ return;
+ }
+
flushSync(() => {
setIsRegenerating(true);
setRegeneratedHtml(null);
@@ -92,6 +117,12 @@ export function AIGuide(props: AIGuideProps) {
});
};
+ const isLoading =
+ isLoadingBySlug ||
+ isRegenerating ||
+ isTokenUsageLoading ||
+ isBillingDetailsLoading;
+
return (
)}
diff --git a/src/components/GenerateGuide/AIGuideChat.tsx b/src/components/GenerateGuide/AIGuideChat.tsx
index 407ddc00d..90c549e32 100644
--- a/src/components/GenerateGuide/AIGuideChat.tsx
+++ b/src/components/GenerateGuide/AIGuideChat.tsx
@@ -60,11 +60,8 @@ export function AIGuideChat(props: AIGuideChatProps) {
refetch: refetchTokenUsage,
} = useQuery(getAiCourseLimitOptions(), queryClient);
- const {
- data: userBillingDetails,
- isLoading: isBillingDetailsLoading,
- refetch: refetchBillingDetails,
- } = useQuery(billingDetailsOptions(), queryClient);
+ const { data: userBillingDetails, isLoading: isBillingDetailsLoading } =
+ useQuery(billingDetailsOptions(), queryClient);
const isLimitExceeded = (tokenUsage?.used || 0) >= (tokenUsage?.limit || 0);
const isPaidUser = userBillingDetails?.status === 'active';
@@ -332,6 +329,24 @@ export function AIGuideChat(props: AIGuideChatProps) {
)}
+ {!isLoggedIn() && (
+
+
+
Please login to continue
+
+
+ )}
+
void;
@@ -24,6 +27,7 @@ export function AIGuideRegenerate(props: AIGuideRegenerateProps) {
const [showPromptModal, setShowPromptModal] = useState(false);
const [showUpdatePreferencesModal, setShowUpdatePreferencesModal] =
useState(false);
+ const currentUser = useAuth();
const ref = useRef(null);
@@ -61,7 +65,9 @@ export function AIGuideRegenerate(props: AIGuideRegenerateProps) {
);
const showUpdatePreferences =
- aiGuide?.questionAndAnswers && aiGuide.questionAndAnswers.length > 0;
+ aiGuide?.questionAndAnswers &&
+ aiGuide.questionAndAnswers.length > 0 &&
+ currentUser?.id === aiGuide.userId;
return (
<>
@@ -127,6 +133,11 @@ export function AIGuideRegenerate(props: AIGuideRegenerateProps) {