1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-09-03 06:12:53 +02:00

Merge branch 'feat/chat-history' into feat/roadmap-chat-history

This commit is contained in:
Arik Chakma
2025-06-12 18:36:21 +06:00
3 changed files with 78 additions and 34 deletions

View File

@@ -32,7 +32,6 @@ import {
import { RoadmapRecommendations } from '../RoadmapAIChat/RoadmapRecommendations'; import { RoadmapRecommendations } from '../RoadmapAIChat/RoadmapRecommendations';
import { AIChatCourse } from './AIChatCouse'; import { AIChatCourse } from './AIChatCouse';
import { showLoginPopup } from '../../lib/popup'; import { showLoginPopup } from '../../lib/popup';
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
import { readChatStream } from '../../lib/chat'; import { readChatStream } from '../../lib/chat';
import { chatHistoryOptions } from '../../queries/chat-history'; import { chatHistoryOptions } from '../../queries/chat-history';
import { cn } from '../../lib/classname'; import { cn } from '../../lib/classname';
@@ -51,6 +50,7 @@ type AIChatProps = {
messages?: RoadmapAIChatHistoryType[]; messages?: RoadmapAIChatHistoryType[];
chatHistoryId?: string; chatHistoryId?: string;
setChatHistoryId?: (chatHistoryId: string) => void; setChatHistoryId?: (chatHistoryId: string) => void;
onUpgrade?: () => void;
}; };
export function AIChat(props: AIChatProps) { export function AIChat(props: AIChatProps) {
@@ -58,6 +58,7 @@ export function AIChat(props: AIChatProps) {
messages: defaultMessages, messages: defaultMessages,
chatHistoryId: defaultChatHistoryId, chatHistoryId: defaultChatHistoryId,
setChatHistoryId: setDefaultChatHistoryId, setChatHistoryId: setDefaultChatHistoryId,
onUpgrade,
} = props; } = props;
const toast = useToast(); const toast = useToast();
@@ -70,7 +71,6 @@ export function AIChat(props: AIChatProps) {
RoadmapAIChatHistoryType[] RoadmapAIChatHistoryType[]
>(defaultMessages ?? []); >(defaultMessages ?? []);
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
const [isPersonalizedResponseFormOpen, setIsPersonalizedResponseFormOpen] = const [isPersonalizedResponseFormOpen, setIsPersonalizedResponseFormOpen] =
useState(false); useState(false);
const [isUploadResumeModalOpen, setIsUploadResumeModalOpen] = useState(false); const [isUploadResumeModalOpen, setIsUploadResumeModalOpen] = useState(false);
@@ -128,7 +128,7 @@ export function AIChat(props: AIChatProps) {
if (isLimitExceeded) { if (isLimitExceeded) {
if (!isPaidUser) { if (!isPaidUser) {
setShowUpgradeModal(true); onUpgrade?.();
} }
toast.error('Limit reached for today. Please wait until tomorrow.'); toast.error('Limit reached for today. Please wait until tomorrow.');
@@ -342,7 +342,7 @@ export function AIChat(props: AIChatProps) {
(index: number) => { (index: number) => {
if (isLimitExceeded) { if (isLimitExceeded) {
if (!isPaidUser) { if (!isPaidUser) {
setShowUpgradeModal(true); onUpgrade?.();
} }
toast.error('Limit reached for today. Please wait until tomorrow.'); toast.error('Limit reached for today. Please wait until tomorrow.');
@@ -429,10 +429,6 @@ export function AIChat(props: AIChatProps) {
/> />
)} )}
{showUpgradeModal && (
<UpgradeAccountModal onClose={() => setShowUpgradeModal(false)} />
)}
<div <div
className="pointer-events-none absolute right-0 bottom-0 left-0 mx-auto w-full max-w-3xl px-4" className="pointer-events-none absolute right-0 bottom-0 left-0 mx-auto w-full max-w-3xl px-4"
ref={chatContainerRef} ref={chatContainerRef}
@@ -446,7 +442,7 @@ export function AIChat(props: AIChatProps) {
</div> </div>
<button <button
type="button" type="button"
onClick={() => setShowUpgradeModal(true)} onClick={() => onUpgrade?.()}
className="shrink-0 cursor-pointer rounded-md bg-yellow-200 px-2 py-1 text-xs font-medium text-yellow-800 hover:bg-yellow-200" className="shrink-0 cursor-pointer rounded-md bg-yellow-200 px-2 py-1 text-xs font-medium text-yellow-800 hover:bg-yellow-200"
> >
Upgrade to Pro Upgrade to Pro
@@ -540,7 +536,7 @@ export function AIChat(props: AIChatProps) {
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
setShowUpgradeModal(true); onUpgrade?.();
}} }}
className="rounded-md bg-white px-2 py-1 text-xs font-medium text-black hover:bg-gray-300" className="rounded-md bg-white px-2 py-1 text-xs font-medium text-black hover:bg-gray-300"
> >

View File

@@ -9,6 +9,7 @@ import { ListChatHistory } from './ListChatHistory';
import { billingDetailsOptions } from '../../queries/billing'; import { billingDetailsOptions } from '../../queries/billing';
import { ChatHistoryError } from './ChatHistoryError'; import { ChatHistoryError } from './ChatHistoryError';
import { useClientMount } from '../../hooks/use-client-mount'; import { useClientMount } from '../../hooks/use-client-mount';
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
type AIChatHistoryProps = { type AIChatHistoryProps = {
chatHistoryId?: string; chatHistoryId?: string;
@@ -19,6 +20,7 @@ export function AIChatHistory(props: AIChatHistoryProps) {
const isClientMounted = useClientMount(); const isClientMounted = useClientMount();
const [keyTrigger, setKeyTrigger] = useState(0); const [keyTrigger, setKeyTrigger] = useState(0);
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
const [isChatHistoryLoading, setIsChatHistoryLoading] = useState(true); const [isChatHistoryLoading, setIsChatHistoryLoading] = useState(true);
const [chatHistoryId, setChatHistoryId] = useState<string | undefined>( const [chatHistoryId, setChatHistoryId] = useState<string | undefined>(
defaultChatHistoryId || undefined, defaultChatHistoryId || undefined,
@@ -116,13 +118,15 @@ export function AIChatHistory(props: AIChatHistoryProps) {
return ( return (
<AIChatLayout> <AIChatLayout>
<div className="relative flex grow"> <div className="relative flex grow">
{isPaidUser && ( <ListChatHistory
<ListChatHistory activeChatHistoryId={chatHistoryId}
activeChatHistoryId={chatHistoryId} onChatHistoryClick={handleChatHistoryClick}
onChatHistoryClick={handleChatHistoryClick} onDelete={handleDelete}
onDelete={handleDelete} isPaidUser={isPaidUser}
/> onUpgrade={() => {
)} setShowUpgradeModal(true);
}}
/>
<div className="relative flex grow"> <div className="relative flex grow">
{showLoader && ( {showLoader && (
@@ -151,10 +155,17 @@ export function AIChatHistory(props: AIChatHistoryProps) {
}, },
}); });
}} }}
onUpgrade={() => {
setShowUpgradeModal(true);
}}
/> />
)} )}
</div> </div>
</div> </div>
{showUpgradeModal && (
<UpgradeAccountModal onClose={() => setShowUpgradeModal(false)} />
)}
</AIChatLayout> </AIChatLayout>
); );
} }

View File

@@ -4,6 +4,7 @@ import { queryClient } from '../../stores/query-client';
import { ChatHistoryItem } from './ChatHistoryItem'; import { ChatHistoryItem } from './ChatHistoryItem';
import { import {
Loader2Icon, Loader2Icon,
LockIcon,
PanelLeftCloseIcon, PanelLeftCloseIcon,
PanelLeftIcon, PanelLeftIcon,
PlusIcon, PlusIcon,
@@ -24,10 +25,18 @@ type ListChatHistoryProps = {
activeChatHistoryId?: string; activeChatHistoryId?: string;
onChatHistoryClick: (chatHistoryId: string | null) => void; onChatHistoryClick: (chatHistoryId: string | null) => void;
onDelete?: (chatHistoryId: string) => void; onDelete?: (chatHistoryId: string) => void;
isPaidUser?: boolean;
onUpgrade?: () => void;
}; };
export function ListChatHistory(props: ListChatHistoryProps) { export function ListChatHistory(props: ListChatHistoryProps) {
const { activeChatHistoryId, onChatHistoryClick, onDelete } = props; const {
activeChatHistoryId,
onChatHistoryClick,
onDelete,
isPaidUser,
onUpgrade,
} = props;
const [isOpen, setIsOpen] = useState(true); const [isOpen, setIsOpen] = useState(true);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
@@ -85,14 +94,49 @@ export function ListChatHistory(props: ListChatHistoryProps) {
(group) => group.histories.length === 0, (group) => group.histories.length === 0,
); );
return ( const classNames = cn(
<div 'flex w-[255px] shrink-0 flex-col justify-start border-r border-gray-200 bg-white p-2',
className={cn( 'max-md:absolute max-md:inset-0 max-md:z-20 max-md:w-full',
'flex w-[255px] shrink-0 flex-col justify-start border-r border-gray-200 bg-white p-2', !isOpen && 'hidden',
'max-md:absolute max-md:inset-0 max-md:z-20 max-md:w-full', );
!isOpen && 'hidden',
)} const closeButton = (
<button
className="flex size-8 items-center justify-center rounded-lg p-1 text-gray-500 hover:bg-gray-100 hover:text-black"
onClick={() => {
setIsOpen(false);
}}
> >
<PanelLeftCloseIcon className="h-4.5 w-4.5" />
</button>
);
if (!isPaidUser) {
return (
<div className={cn(classNames, 'relative')}>
<div className="absolute top-2 right-2">{closeButton}</div>
<div className="flex grow flex-col items-center justify-center">
<LockIcon className="size-8 text-gray-500" />
<p className="mt-4 text-center text-sm text-balance text-gray-500">
Upgrade to Pro to keep your chat history.
</p>
<button
type="button"
className="mt-2 shrink-0 cursor-pointer rounded-md bg-yellow-200 px-2.5 py-1.5 text-sm font-medium text-yellow-800 hover:bg-yellow-200"
onClick={() => {
onUpgrade?.();
}}
>
Upgrade to Pro
</button>
</div>
</div>
);
}
return (
<div className={classNames}>
{isLoading && <ListChatHistorySkeleton />} {isLoading && <ListChatHistorySkeleton />}
{!isLoading && isError && <ChatHistoryError error={error} />} {!isLoading && isError && <ChatHistoryError error={error} />}
@@ -101,18 +145,11 @@ export function ListChatHistory(props: ListChatHistoryProps) {
<div> <div>
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<h1 className="font-medium text-gray-900">Chat History</h1> <h1 className="font-medium text-gray-900">Chat History</h1>
<button {closeButton}
className="flex size-8 items-center justify-center rounded-lg p-1 hover:bg-gray-100"
onClick={() => {
setIsOpen(false);
}}
>
<PanelLeftCloseIcon className="h-4.5 w-4.5" />
</button>
</div> </div>
<button <button
className="flex w-full items-center hover:opacity-80 justify-center gap-2 rounded-lg bg-black p-2 text-sm text-white" className="flex w-full items-center justify-center gap-2 rounded-lg bg-black p-2 text-sm text-white hover:opacity-80"
onClick={() => { onClick={() => {
if (isMobile) { if (isMobile) {
setIsOpen(false); setIsOpen(false);