mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-09-25 08:35:42 +02:00
feat: add tab navigation for chat and topic details
This commit is contained in:
@@ -65,6 +65,8 @@ export type RoamdapAIChatHistoryType = {
|
||||
jsx?: React.ReactNode;
|
||||
};
|
||||
|
||||
export type RoadmapAIChatTab = 'chat' | 'topic';
|
||||
|
||||
type RoadmapAIChatProps = {
|
||||
roadmapId: string;
|
||||
};
|
||||
@@ -82,6 +84,7 @@ export function RoadmapAIChat(props: RoadmapAIChatProps) {
|
||||
const [selectedTopicTitle, setSelectedTopicTitle] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [activeTab, setActiveTab] = useState<RoadmapAIChatTab>('chat');
|
||||
|
||||
const [aiChatHistory, setAiChatHistory] = useState<
|
||||
RoamdapAIChatHistoryType[]
|
||||
@@ -178,6 +181,7 @@ export function RoadmapAIChat(props: RoadmapAIChatProps) {
|
||||
flushSync(() => {
|
||||
setSelectedTopicId(topicId);
|
||||
setSelectedTopicTitle(topicTitle);
|
||||
setActiveTab('topic');
|
||||
});
|
||||
|
||||
const topicWithSlug = slugify(topicTitle) + '@' + topicId;
|
||||
@@ -389,41 +393,6 @@ export function RoadmapAIChat(props: RoadmapAIChatProps) {
|
||||
</div>
|
||||
|
||||
<div className="relative flex h-full max-w-[40%] flex-grow flex-col border-l border-gray-200 bg-white">
|
||||
{selectedTopicId && (
|
||||
<>
|
||||
<TopicDetail
|
||||
resourceId={selectedTopicId}
|
||||
resourceType="roadmap"
|
||||
renderer="editor"
|
||||
canSubmitContribution={false}
|
||||
wrapperClassName="static mx-auto sm:pt-14 pt-14"
|
||||
overlayClassName="hidden"
|
||||
onClose={() => setSelectedTopicId(null)}
|
||||
shouldCloseOnBackdropClick={false}
|
||||
shouldCloseOnEscape={false}
|
||||
/>
|
||||
<div className="absolute top-0 left-0 z-99 flex w-full items-center justify-between gap-2 bg-gray-100 p-1">
|
||||
<div className="flex items-center gap-2 px-4 py-2 text-sm">
|
||||
AI Tutor{' '}
|
||||
<ChevronRightIcon className="size-4 shrink-0 stroke-[2.5] text-gray-500" />{' '}
|
||||
{selectedTopicTitle}
|
||||
</div>
|
||||
<button
|
||||
className="flex cursor-pointer items-center gap-2 rounded-lg p-2 text-black hover:bg-gray-200"
|
||||
onClick={() => setSelectedTopicId(null)}
|
||||
>
|
||||
<XIcon className="size-3 shrink-0" strokeWidth={2.5} />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!selectedTopicId && (
|
||||
<>
|
||||
{showUpgradeModal && (
|
||||
<UpgradeAccountModal onClose={() => setShowUpgradeModal(false)} />
|
||||
)}
|
||||
|
||||
<RoadmapAIChatHeader
|
||||
isLoading={isDataLoading}
|
||||
hasChatHistory={hasChatHistory}
|
||||
@@ -434,8 +403,46 @@ export function RoadmapAIChat(props: RoadmapAIChatProps) {
|
||||
onUpgrade={() => {
|
||||
setShowUpgradeModal(true);
|
||||
}}
|
||||
activeTab={activeTab}
|
||||
onTabChange={(tab) => {
|
||||
setActiveTab(tab);
|
||||
if (tab === 'topic' && selectedTopicId && selectedTopicTitle) {
|
||||
handleSelectTopic(selectedTopicId, selectedTopicTitle);
|
||||
}
|
||||
}}
|
||||
onCloseTopic={() => {
|
||||
setSelectedTopicId(null);
|
||||
setSelectedTopicTitle(null);
|
||||
setActiveTab('chat');
|
||||
}}
|
||||
selectedTopicId={selectedTopicId}
|
||||
/>
|
||||
|
||||
{activeTab === 'topic' && selectedTopicId && (
|
||||
<TopicDetail
|
||||
resourceId={selectedTopicId}
|
||||
resourceType="roadmap"
|
||||
renderer="editor"
|
||||
canSubmitContribution={false}
|
||||
wrapperClassName="static mx-auto h-auto grow"
|
||||
overlayClassName="hidden"
|
||||
closeButtonClassName="hidden"
|
||||
onClose={() => {
|
||||
setSelectedTopicId(null);
|
||||
setSelectedTopicTitle(null);
|
||||
setActiveTab('chat');
|
||||
}}
|
||||
shouldCloseOnBackdropClick={false}
|
||||
shouldCloseOnEscape={false}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === 'chat' && (
|
||||
<>
|
||||
{showUpgradeModal && (
|
||||
<UpgradeAccountModal onClose={() => setShowUpgradeModal(false)} />
|
||||
)}
|
||||
|
||||
<div className="relative grow overflow-y-auto" ref={scrollareaRef}>
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 flex h-full w-full items-center justify-center">
|
||||
|
@@ -3,12 +3,16 @@ import { getAiCourseLimitOptions } from '../../queries/ai-course';
|
||||
import { queryClient } from '../../stores/query-client';
|
||||
import { billingDetailsOptions } from '../../queries/billing';
|
||||
import { isLoggedIn } from '../../lib/jwt';
|
||||
import { BotIcon, GiftIcon, Trash2Icon } from 'lucide-react';
|
||||
import type { RoamdapAIChatHistoryType } from './RoadmapAIChat';
|
||||
import { BookIcon, BotIcon, GiftIcon, Trash2Icon, XIcon } from 'lucide-react';
|
||||
import type {
|
||||
RoadmapAIChatTab,
|
||||
RoamdapAIChatHistoryType,
|
||||
} from './RoadmapAIChat';
|
||||
import { useState } from 'react';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
import { getPercentage } from '../../lib/number';
|
||||
import { AILimitsPopup } from '../GenerateCourse/AILimitsPopup';
|
||||
import { cn } from '../../lib/classname';
|
||||
|
||||
type RoadmapAIChatHeaderProps = {
|
||||
isLoading: boolean;
|
||||
@@ -18,6 +22,11 @@ type RoadmapAIChatHeaderProps = {
|
||||
|
||||
onLogin: () => void;
|
||||
onUpgrade: () => void;
|
||||
|
||||
activeTab: RoadmapAIChatTab;
|
||||
onTabChange: (tab: RoadmapAIChatTab) => void;
|
||||
onCloseTopic: () => void;
|
||||
selectedTopicId: string | null;
|
||||
};
|
||||
|
||||
export function RoadmapAIChatHeader(props: RoadmapAIChatHeaderProps) {
|
||||
@@ -27,6 +36,11 @@ export function RoadmapAIChatHeader(props: RoadmapAIChatHeaderProps) {
|
||||
onLogin,
|
||||
onUpgrade,
|
||||
isLoading: isDataLoading,
|
||||
|
||||
activeTab,
|
||||
onTabChange,
|
||||
onCloseTopic,
|
||||
selectedTopicId,
|
||||
} = props;
|
||||
|
||||
const toast = useToast();
|
||||
@@ -58,11 +72,43 @@ export function RoadmapAIChatHeader(props: RoadmapAIChatHeaderProps) {
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex min-h-[46px] items-center justify-between gap-2 border-b border-gray-200 px-3 py-2 text-sm">
|
||||
<span className="flex items-center gap-2 text-sm">
|
||||
<div className="flex h-[46px] items-center justify-between gap-2 border-b border-gray-200 text-sm">
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
className={cn(
|
||||
'flex items-center gap-2 px-2 py-3 text-sm',
|
||||
activeTab === 'chat' && selectedTopicId && 'bg-gray-100',
|
||||
)}
|
||||
onClick={() => onTabChange('chat')}
|
||||
>
|
||||
<BotIcon className="size-4 shrink-0 text-black" />
|
||||
<span>AI Chat</span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{(activeTab === 'topic' || selectedTopicId) && (
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 border-l border-gray-200 px-2 py-3 text-sm',
|
||||
activeTab === 'topic' && selectedTopicId && 'bg-gray-100',
|
||||
)}
|
||||
>
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => onTabChange('topic')}
|
||||
>
|
||||
<BookIcon className="size-4 shrink-0 text-black" />
|
||||
<span>Topic Details</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="ml-auto rounded-lg p-1 text-gray-500 hover:bg-gray-200"
|
||||
onClick={onCloseTopic}
|
||||
>
|
||||
<XIcon className="size-4 shrink-0" strokeWidth={2.5} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!isDataLoading && (
|
||||
<div className="flex gap-1.5">
|
||||
|
@@ -93,6 +93,7 @@ type TopicDetailProps = {
|
||||
|
||||
wrapperClassName?: string;
|
||||
overlayClassName?: string;
|
||||
closeButtonClassName?: string;
|
||||
onClose?: () => void;
|
||||
shouldCloseOnBackdropClick?: boolean;
|
||||
shouldCloseOnEscape?: boolean;
|
||||
@@ -106,6 +107,7 @@ export function TopicDetail(props: TopicDetailProps) {
|
||||
renderer = 'balsamiq',
|
||||
wrapperClassName,
|
||||
overlayClassName,
|
||||
closeButtonClassName,
|
||||
onClose,
|
||||
shouldCloseOnBackdropClick = true,
|
||||
shouldCloseOnEscape = true,
|
||||
@@ -449,7 +451,10 @@ export function TopicDetail(props: TopicDetailProps) {
|
||||
<button
|
||||
type="button"
|
||||
id="close-topic"
|
||||
className="flex items-center gap-1.5 rounded-lg bg-gray-200 px-1.5 py-1 text-xs text-black hover:bg-gray-300 hover:text-gray-900"
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 rounded-lg bg-gray-200 px-1.5 py-1 text-xs text-black hover:bg-gray-300 hover:text-gray-900',
|
||||
closeButtonClassName,
|
||||
)}
|
||||
onClick={handleClose}
|
||||
>
|
||||
<X className="size-4" />
|
||||
|
Reference in New Issue
Block a user