mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-09-03 14:22:41 +02:00
Merge branch 'feat/questions-chat' of github.com:kamranahmedse/developer-roadmap into feat/questions-chat
This commit is contained in:
@@ -12,6 +12,10 @@ import { GenerateAIRoadmap } from './GenerateAIRoadmap';
|
|||||||
import { AIRoadmapContent, type RoadmapNodeDetails } from './AIRoadmapContent';
|
import { AIRoadmapContent, type RoadmapNodeDetails } from './AIRoadmapContent';
|
||||||
import { AIRoadmapChat } from './AIRoadmapChat';
|
import { AIRoadmapChat } from './AIRoadmapChat';
|
||||||
import { AlertCircleIcon } from 'lucide-react';
|
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 = {
|
export type AIRoadmapChatActions = {
|
||||||
handleNodeClick: (node: RoadmapNodeDetails) => void;
|
handleNodeClick: (node: RoadmapNodeDetails) => void;
|
||||||
@@ -42,7 +46,29 @@ export function AIRoadmap(props: AIRoadmapProps) {
|
|||||||
error: aiRoadmapError,
|
error: aiRoadmapError,
|
||||||
} = useQuery(aiRoadmapOptions(roadmapSlug), queryClient);
|
} = 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) => {
|
const handleRegenerate = async (prompt?: string) => {
|
||||||
|
if (!isLoggedIn()) {
|
||||||
|
showLoginPopup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPaidUser && isLimitExceeded) {
|
||||||
|
setShowUpgradeModal(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
flushSync(() => {
|
flushSync(() => {
|
||||||
setIsRegenerating(true);
|
setIsRegenerating(true);
|
||||||
setRegeneratedSvgHtml(null);
|
setRegeneratedSvgHtml(null);
|
||||||
@@ -76,12 +102,17 @@ export function AIRoadmap(props: AIRoadmapProps) {
|
|||||||
},
|
},
|
||||||
onFinish: () => {
|
onFinish: () => {
|
||||||
setIsRegenerating(false);
|
setIsRegenerating(false);
|
||||||
|
refetchTokenUsage();
|
||||||
queryClient.invalidateQueries(aiRoadmapOptions(roadmapSlug));
|
queryClient.invalidateQueries(aiRoadmapOptions(roadmapSlug));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLoading = isLoadingBySlug || isRegenerating;
|
const isLoading =
|
||||||
|
isLoadingBySlug ||
|
||||||
|
isRegenerating ||
|
||||||
|
isTokenUsageLoading ||
|
||||||
|
isBillingDetailsLoading;
|
||||||
|
|
||||||
const handleNodeClick = useCallback(
|
const handleNodeClick = useCallback(
|
||||||
(node: RoadmapNodeDetails) => {
|
(node: RoadmapNodeDetails) => {
|
||||||
|
@@ -124,6 +124,10 @@ export function AIRoadmapChat(props: AIRoadmapChatProps) {
|
|||||||
});
|
});
|
||||||
sendMessages(newMessages);
|
sendMessages(newMessages);
|
||||||
setInputValue('');
|
setInputValue('');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
scrollToBottom('smooth');
|
||||||
|
}, 0);
|
||||||
},
|
},
|
||||||
[inputValue, isStreamingMessage, messages, sendMessages, setMessages],
|
[inputValue, isStreamingMessage, messages, sendMessages, setMessages],
|
||||||
);
|
);
|
||||||
@@ -174,11 +178,7 @@ export function AIRoadmapChat(props: AIRoadmapChatProps) {
|
|||||||
|
|
||||||
useImperativeHandle(aiChatActionsRef, () => ({
|
useImperativeHandle(aiChatActionsRef, () => ({
|
||||||
handleNodeClick: (node: RoadmapNodeDetails) => {
|
handleNodeClick: (node: RoadmapNodeDetails) => {
|
||||||
flushSync(() => {
|
handleSubmitInput(`Explain what is "${node.nodeTitle}" topic in detail.`);
|
||||||
setInputValue(`Explain what is ${node.nodeTitle} topic in detail.`);
|
|
||||||
});
|
|
||||||
|
|
||||||
inputRef.current?.focus();
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -304,6 +304,24 @@ export function AIRoadmapChat(props: AIRoadmapChatProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!isLoggedIn() && (
|
||||||
|
<div className="absolute inset-0 z-10 flex items-center justify-center gap-2 bg-black text-white">
|
||||||
|
<LockIcon
|
||||||
|
className="size-4 cursor-not-allowed"
|
||||||
|
strokeWidth={2.5}
|
||||||
|
/>
|
||||||
|
<p className="cursor-not-allowed">Please login to continue</p>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
showLoginPopup();
|
||||||
|
}}
|
||||||
|
className="rounded-md bg-white px-2 py-1 text-xs font-medium text-black hover:bg-gray-300"
|
||||||
|
>
|
||||||
|
Login / Register
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
type="text"
|
type="text"
|
||||||
|
@@ -19,6 +19,9 @@ import { aiRoadmapOptions } from '../../queries/ai-roadmap';
|
|||||||
import { UpdatePreferences } from '../GenerateGuide/UpdatePreferences';
|
import { UpdatePreferences } from '../GenerateGuide/UpdatePreferences';
|
||||||
import { generateAIRoadmapFromText } from '@roadmapsh/editor';
|
import { generateAIRoadmapFromText } from '@roadmapsh/editor';
|
||||||
import { useToast } from '../../hooks/use-toast';
|
import { useToast } from '../../hooks/use-toast';
|
||||||
|
import { showLoginPopup } from '../../lib/popup';
|
||||||
|
import { isLoggedIn } from '../../lib/jwt';
|
||||||
|
import { useAuth } from '../../hooks/use-auth';
|
||||||
|
|
||||||
type AIRoadmapRegenerateProps = {
|
type AIRoadmapRegenerateProps = {
|
||||||
onRegenerate: (prompt?: string) => void;
|
onRegenerate: (prompt?: string) => void;
|
||||||
@@ -34,6 +37,7 @@ export function AIRoadmapRegenerate(props: AIRoadmapRegenerateProps) {
|
|||||||
const [showPromptModal, setShowPromptModal] = useState(false);
|
const [showPromptModal, setShowPromptModal] = useState(false);
|
||||||
const [showUpdatePreferencesModal, setShowUpdatePreferencesModal] =
|
const [showUpdatePreferencesModal, setShowUpdatePreferencesModal] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
const currentUser = useAuth();
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -132,8 +136,11 @@ export function AIRoadmapRegenerate(props: AIRoadmapRegenerateProps) {
|
|||||||
queryClient,
|
queryClient,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isCurrentUserCreator = currentUser?.id === aiRoadmap?.userId;
|
||||||
const showUpdatePreferences =
|
const showUpdatePreferences =
|
||||||
aiRoadmap?.questionAndAnswers && aiRoadmap.questionAndAnswers.length > 0;
|
aiRoadmap?.questionAndAnswers &&
|
||||||
|
aiRoadmap.questionAndAnswers.length > 0 &&
|
||||||
|
isCurrentUserCreator;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -180,45 +187,78 @@ export function AIRoadmapRegenerate(props: AIRoadmapRegenerateProps) {
|
|||||||
</button>
|
</button>
|
||||||
{isDropdownVisible && (
|
{isDropdownVisible && (
|
||||||
<div className="absolute top-full right-0 min-w-[190px] translate-y-1 overflow-hidden rounded-md border border-gray-200 bg-white shadow-md">
|
<div className="absolute top-full right-0 min-w-[190px] translate-y-1 overflow-hidden rounded-md border border-gray-200 bg-white shadow-md">
|
||||||
{showUpdatePreferences && (
|
{isCurrentUserCreator && (
|
||||||
<ActionButton
|
<>
|
||||||
onClick={() => {
|
{showUpdatePreferences && (
|
||||||
setIsDropdownVisible(false);
|
<ActionButton
|
||||||
setShowUpdatePreferencesModal(true);
|
onClick={() => {
|
||||||
}}
|
if (!isLoggedIn()) {
|
||||||
icon={SettingsIcon}
|
showLoginPopup();
|
||||||
label="Update Preferences"
|
return;
|
||||||
/>
|
}
|
||||||
|
|
||||||
|
setIsDropdownVisible(false);
|
||||||
|
setShowUpdatePreferencesModal(true);
|
||||||
|
}}
|
||||||
|
icon={SettingsIcon}
|
||||||
|
label="Update Preferences"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ActionButton
|
||||||
|
onClick={() => {
|
||||||
|
if (!isLoggedIn()) {
|
||||||
|
showLoginPopup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsDropdownVisible(false);
|
||||||
|
onRegenerate();
|
||||||
|
}}
|
||||||
|
icon={RefreshCcw}
|
||||||
|
label="Regenerate"
|
||||||
|
/>
|
||||||
|
<ActionButton
|
||||||
|
onClick={() => {
|
||||||
|
if (!isLoggedIn()) {
|
||||||
|
showLoginPopup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsDropdownVisible(false);
|
||||||
|
setShowPromptModal(true);
|
||||||
|
}}
|
||||||
|
icon={PenSquare}
|
||||||
|
label="Modify Prompt"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<hr className="my-1 border-gray-200" />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsDropdownVisible(false);
|
if (!isLoggedIn()) {
|
||||||
onRegenerate();
|
showLoginPopup();
|
||||||
}}
|
return;
|
||||||
icon={RefreshCcw}
|
}
|
||||||
label="Regenerate"
|
|
||||||
/>
|
|
||||||
<ActionButton
|
|
||||||
onClick={() => {
|
|
||||||
setIsDropdownVisible(false);
|
|
||||||
setShowPromptModal(true);
|
|
||||||
}}
|
|
||||||
icon={PenSquare}
|
|
||||||
label="Modify Prompt"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<hr className="my-1 border-gray-200" />
|
saveAIRoadmap();
|
||||||
|
}}
|
||||||
<ActionButton
|
|
||||||
onClick={saveAIRoadmap}
|
|
||||||
icon={SaveIcon}
|
icon={SaveIcon}
|
||||||
label="Start Learning"
|
label="Start Learning"
|
||||||
isLoading={isSavingAIRoadmap}
|
isLoading={isSavingAIRoadmap}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
onClick={editAIRoadmap}
|
onClick={() => {
|
||||||
|
if (!isLoggedIn()) {
|
||||||
|
showLoginPopup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
editAIRoadmap();
|
||||||
|
}}
|
||||||
icon={PenSquare}
|
icon={PenSquare}
|
||||||
label="Edit in Editor"
|
label="Edit in Editor"
|
||||||
isLoading={isEditingAIRoadmap}
|
isLoading={isEditingAIRoadmap}
|
||||||
|
@@ -59,7 +59,6 @@ export function GetAICourse(props: GetAICourseProps) {
|
|||||||
|
|
||||||
await generateCourse({
|
await generateCourse({
|
||||||
term: aiCourse.keyword,
|
term: aiCourse.keyword,
|
||||||
difficulty: aiCourse.difficulty,
|
|
||||||
slug: courseSlug,
|
slug: courseSlug,
|
||||||
prompt,
|
prompt,
|
||||||
onCourseChange: (course, rawData) => {
|
onCourseChange: (course, rawData) => {
|
||||||
@@ -68,7 +67,6 @@ export function GetAICourse(props: GetAICourseProps) {
|
|||||||
{
|
{
|
||||||
...aiCourse,
|
...aiCourse,
|
||||||
title: course.title,
|
title: course.title,
|
||||||
difficulty: course.difficulty,
|
|
||||||
modules: course.modules,
|
modules: course.modules,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -89,7 +87,6 @@ export function GetAICourse(props: GetAICourseProps) {
|
|||||||
course={{
|
course={{
|
||||||
title: aiCourse?.title || '',
|
title: aiCourse?.title || '',
|
||||||
modules: aiCourse?.modules || [],
|
modules: aiCourse?.modules || [],
|
||||||
difficulty: aiCourse?.difficulty || 'Easy',
|
|
||||||
done: aiCourse?.done || [],
|
done: aiCourse?.done || [],
|
||||||
}}
|
}}
|
||||||
isLoading={isLoading || isRegenerating}
|
isLoading={isLoading || isRegenerating}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { ExternalLink } from 'lucide-react';
|
import { AlertCircleIcon, ExternalLink } from 'lucide-react';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { flushSync } from 'react-dom';
|
import { flushSync } from 'react-dom';
|
||||||
import { generateGuide } from '../../helper/generate-ai-guide';
|
import { generateGuide } from '../../helper/generate-ai-guide';
|
||||||
@@ -16,6 +16,9 @@ import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
|
|||||||
import { AIGuideChat } from './AIGuideChat';
|
import { AIGuideChat } from './AIGuideChat';
|
||||||
import { AIGuideContent } from './AIGuideContent';
|
import { AIGuideContent } from './AIGuideContent';
|
||||||
import { GenerateAIGuide } from './GenerateAIGuide';
|
import { GenerateAIGuide } from './GenerateAIGuide';
|
||||||
|
import { getAiCourseLimitOptions } from '../../queries/ai-course';
|
||||||
|
import { billingDetailsOptions } from '../../queries/billing';
|
||||||
|
import { showLoginPopup } from '../../lib/popup';
|
||||||
|
|
||||||
type AIGuideProps = {
|
type AIGuideProps = {
|
||||||
guideSlug?: string;
|
guideSlug?: string;
|
||||||
@@ -32,10 +35,23 @@ export function AIGuide(props: AIGuideProps) {
|
|||||||
|
|
||||||
// only fetch the guide if the guideSlug is provided
|
// only fetch the guide if the guideSlug is provided
|
||||||
// otherwise we are still generating the guide
|
// otherwise we are still generating the guide
|
||||||
const { data: aiGuide, isLoading: isLoadingBySlug } = useQuery(
|
const {
|
||||||
getAiGuideOptions(guideSlug),
|
data: aiGuide,
|
||||||
queryClient,
|
isLoading: isLoadingBySlug,
|
||||||
);
|
error: aiGuideError,
|
||||||
|
} = useQuery(getAiGuideOptions(guideSlug), 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 } =
|
const { data: aiGuideSuggestions, isLoading: isAiGuideSuggestionsLoading } =
|
||||||
useQuery(
|
useQuery(
|
||||||
@@ -57,6 +73,16 @@ export function AIGuide(props: AIGuideProps) {
|
|||||||
}, [aiGuideSuggestions]);
|
}, [aiGuideSuggestions]);
|
||||||
|
|
||||||
const handleRegenerate = async (prompt?: string) => {
|
const handleRegenerate = async (prompt?: string) => {
|
||||||
|
if (!isLoggedIn()) {
|
||||||
|
showLoginPopup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPaidUser && isLimitExceeded) {
|
||||||
|
setShowUpgradeModal(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
flushSync(() => {
|
flushSync(() => {
|
||||||
setIsRegenerating(true);
|
setIsRegenerating(true);
|
||||||
setRegeneratedHtml(null);
|
setRegeneratedHtml(null);
|
||||||
@@ -92,25 +118,44 @@ export function AIGuide(props: AIGuideProps) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isLoading =
|
||||||
|
isLoadingBySlug ||
|
||||||
|
isRegenerating ||
|
||||||
|
isTokenUsageLoading ||
|
||||||
|
isBillingDetailsLoading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AITutorLayout
|
<AITutorLayout
|
||||||
wrapperClassName="flex-row p-0 lg:p-0 overflow-hidden bg-white"
|
wrapperClassName="flex-row p-0 lg:p-0 relative overflow-hidden bg-white"
|
||||||
containerClassName="h-[calc(100vh-49px)] overflow-hidden relative"
|
containerClassName="h-[calc(100vh-49px)] overflow-hidden relative"
|
||||||
>
|
>
|
||||||
{showUpgradeModal && (
|
{showUpgradeModal && (
|
||||||
<UpgradeAccountModal onClose={() => setShowUpgradeModal(false)} />
|
<UpgradeAccountModal onClose={() => setShowUpgradeModal(false)} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!isLoading && aiGuideError && (
|
||||||
|
<div className="absolute inset-0 z-10 flex h-full flex-col items-center justify-center bg-white">
|
||||||
|
<div className="flex flex-col items-center justify-center gap-2">
|
||||||
|
<AlertCircleIcon className="size-10 text-gray-500" />
|
||||||
|
<p className="text-center">
|
||||||
|
{aiGuideError?.message || 'Something went wrong'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="grow overflow-y-auto p-4 pt-0">
|
<div className="grow overflow-y-auto p-4 pt-0">
|
||||||
{guideSlug && (
|
{guideSlug && !aiGuideError && (
|
||||||
<AIGuideContent
|
<AIGuideContent
|
||||||
html={regeneratedHtml || aiGuide?.html || ''}
|
html={regeneratedHtml || aiGuide?.html || ''}
|
||||||
onRegenerate={handleRegenerate}
|
onRegenerate={handleRegenerate}
|
||||||
isLoading={isLoadingBySlug || isRegenerating}
|
isLoading={isLoading}
|
||||||
guideSlug={guideSlug}
|
guideSlug={guideSlug}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!guideSlug && <GenerateAIGuide onGuideSlugChange={setGuideSlug} />}
|
{!guideSlug && !aiGuideError && (
|
||||||
|
<GenerateAIGuide onGuideSlugChange={setGuideSlug} />
|
||||||
|
)}
|
||||||
|
|
||||||
{aiGuide && !isRegenerating && (
|
{aiGuide && !isRegenerating && (
|
||||||
<div className="mx-auto mt-12 mb-12 max-w-4xl">
|
<div className="mx-auto mt-12 mb-12 max-w-4xl">
|
||||||
|
@@ -60,11 +60,8 @@ export function AIGuideChat(props: AIGuideChatProps) {
|
|||||||
refetch: refetchTokenUsage,
|
refetch: refetchTokenUsage,
|
||||||
} = useQuery(getAiCourseLimitOptions(), queryClient);
|
} = useQuery(getAiCourseLimitOptions(), queryClient);
|
||||||
|
|
||||||
const {
|
const { data: userBillingDetails, isLoading: isBillingDetailsLoading } =
|
||||||
data: userBillingDetails,
|
useQuery(billingDetailsOptions(), queryClient);
|
||||||
isLoading: isBillingDetailsLoading,
|
|
||||||
refetch: refetchBillingDetails,
|
|
||||||
} = useQuery(billingDetailsOptions(), queryClient);
|
|
||||||
|
|
||||||
const isLimitExceeded = (tokenUsage?.used || 0) >= (tokenUsage?.limit || 0);
|
const isLimitExceeded = (tokenUsage?.used || 0) >= (tokenUsage?.limit || 0);
|
||||||
const isPaidUser = userBillingDetails?.status === 'active';
|
const isPaidUser = userBillingDetails?.status === 'active';
|
||||||
@@ -127,6 +124,10 @@ export function AIGuideChat(props: AIGuideChatProps) {
|
|||||||
});
|
});
|
||||||
sendMessages(newMessages);
|
sendMessages(newMessages);
|
||||||
setInputValue('');
|
setInputValue('');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
scrollToBottom('smooth');
|
||||||
|
}, 0);
|
||||||
},
|
},
|
||||||
[inputValue, isStreamingMessage, messages, sendMessages, setMessages],
|
[inputValue, isStreamingMessage, messages, sendMessages, setMessages],
|
||||||
);
|
);
|
||||||
@@ -332,6 +333,24 @@ export function AIGuideChat(props: AIGuideChatProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!isLoggedIn() && (
|
||||||
|
<div className="absolute inset-0 z-10 flex items-center justify-center gap-2 bg-black text-white">
|
||||||
|
<LockIcon
|
||||||
|
className="size-4 cursor-not-allowed"
|
||||||
|
strokeWidth={2.5}
|
||||||
|
/>
|
||||||
|
<p className="cursor-not-allowed">Please login to continue</p>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
showLoginPopup();
|
||||||
|
}}
|
||||||
|
className="rounded-md bg-white px-2 py-1 text-xs font-medium text-black hover:bg-gray-300"
|
||||||
|
>
|
||||||
|
Login / Register
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
type="text"
|
type="text"
|
||||||
|
@@ -10,6 +10,9 @@ import { useMutation, useQuery } from '@tanstack/react-query';
|
|||||||
import { getAiGuideOptions } from '../../queries/ai-guide';
|
import { getAiGuideOptions } from '../../queries/ai-guide';
|
||||||
import { queryClient } from '../../stores/query-client';
|
import { queryClient } from '../../stores/query-client';
|
||||||
import { httpPost } from '../../lib/query-http';
|
import { httpPost } from '../../lib/query-http';
|
||||||
|
import { useAuth } from '../../hooks/use-auth';
|
||||||
|
import { showLoginPopup } from '../../lib/popup';
|
||||||
|
import { isLoggedIn } from '../../lib/jwt';
|
||||||
|
|
||||||
type AIGuideRegenerateProps = {
|
type AIGuideRegenerateProps = {
|
||||||
onRegenerate: (prompt?: string) => void;
|
onRegenerate: (prompt?: string) => void;
|
||||||
@@ -24,6 +27,7 @@ export function AIGuideRegenerate(props: AIGuideRegenerateProps) {
|
|||||||
const [showPromptModal, setShowPromptModal] = useState(false);
|
const [showPromptModal, setShowPromptModal] = useState(false);
|
||||||
const [showUpdatePreferencesModal, setShowUpdatePreferencesModal] =
|
const [showUpdatePreferencesModal, setShowUpdatePreferencesModal] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
const currentUser = useAuth();
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -61,7 +65,9 @@ export function AIGuideRegenerate(props: AIGuideRegenerateProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const showUpdatePreferences =
|
const showUpdatePreferences =
|
||||||
aiGuide?.questionAndAnswers && aiGuide.questionAndAnswers.length > 0;
|
aiGuide?.questionAndAnswers &&
|
||||||
|
aiGuide.questionAndAnswers.length > 0 &&
|
||||||
|
currentUser?.id === aiGuide.userId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -127,6 +133,11 @@ export function AIGuideRegenerate(props: AIGuideRegenerateProps) {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
if (!isLoggedIn()) {
|
||||||
|
showLoginPopup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setIsDropdownVisible(false);
|
setIsDropdownVisible(false);
|
||||||
onRegenerate();
|
onRegenerate();
|
||||||
}}
|
}}
|
||||||
@@ -141,6 +152,11 @@ export function AIGuideRegenerate(props: AIGuideRegenerateProps) {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
if (!isLoggedIn()) {
|
||||||
|
showLoginPopup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setIsDropdownVisible(false);
|
setIsDropdownVisible(false);
|
||||||
setShowPromptModal(true);
|
setShowPromptModal(true);
|
||||||
}}
|
}}
|
||||||
|
@@ -48,7 +48,6 @@ export function getAiGuideOptions(guideSlug?: string) {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
enabled: !!guideSlug,
|
enabled: !!guideSlug,
|
||||||
refetchOnMount: false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user