mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-08-22 17:02:58 +02:00
Improve AI Tutor landing page, and Sidebar (#8969)
* Add tree to AI tutor sidebar * Update featured card design * Featured card changes * Improve feature cards * Active item bug in ai tutor sidebar * Add demo button on premium page
This commit is contained in:
1
.astro/types.d.ts
vendored
1
.astro/types.d.ts
vendored
@@ -1,2 +1 @@
|
||||
/// <reference types="astro/client" />
|
||||
/// <reference path="content.d.ts" />
|
@@ -1,26 +1,29 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
BookOpen,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Compass,
|
||||
FileText,
|
||||
Map,
|
||||
MessageCircle,
|
||||
Plus,
|
||||
Star,
|
||||
Swords,
|
||||
X,
|
||||
Zap
|
||||
} from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { isLoggedIn } from '../../lib/jwt';
|
||||
import { useIsPaidUser } from '../../queries/billing';
|
||||
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
|
||||
import { AITutorLogo } from '../ReactIcons/AITutorLogo';
|
||||
import { queryClient } from '../../stores/query-client';
|
||||
import { aiLimitOptions } from '../../queries/ai-course';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getPercentage } from '../../lib/number';
|
||||
import { AILimitsPopup } from '../GenerateCourse/AILimitsPopup';
|
||||
import { getUrlParams } from '../../lib/browser';
|
||||
import { cn } from '../../lib/classname';
|
||||
import { UserDropdown } from './UserDropdown';
|
||||
import { isLoggedIn } from '../../lib/jwt';
|
||||
import { aiLimitOptions } from '../../queries/ai-course';
|
||||
import { useIsPaidUser } from '../../queries/billing';
|
||||
import { queryClient } from '../../stores/query-client';
|
||||
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
|
||||
import { AILimitsPopup } from '../GenerateCourse/AILimitsPopup';
|
||||
import { AITutorLogo } from '../ReactIcons/AITutorLogo';
|
||||
import { UpgradeSidebarCard } from './UpgradeSidebarCard';
|
||||
import { UserDropdown } from './UserDropdown';
|
||||
|
||||
type AITutorSidebarProps = {
|
||||
isFloating: boolean;
|
||||
@@ -33,14 +36,34 @@ const sidebarItems = [
|
||||
key: 'new',
|
||||
label: 'Create with AI',
|
||||
href: '/ai',
|
||||
icon: Plus,
|
||||
icon: Zap,
|
||||
children: [
|
||||
{
|
||||
key: 'create-course',
|
||||
label: 'Course',
|
||||
href: '/ai?format=course',
|
||||
icon: BookOpen,
|
||||
},
|
||||
{
|
||||
key: 'create-guide',
|
||||
label: 'Guide',
|
||||
href: '/ai?format=guide',
|
||||
icon: FileText,
|
||||
},
|
||||
{
|
||||
key: 'create-roadmap',
|
||||
label: 'Roadmap',
|
||||
href: '/ai?format=roadmap',
|
||||
icon: Map,
|
||||
},
|
||||
{
|
||||
key: 'quiz',
|
||||
label: 'Test my Skills',
|
||||
label: 'Quiz',
|
||||
href: '/ai/quiz',
|
||||
icon: Swords,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'chat',
|
||||
label: 'Ask AI Tutor',
|
||||
@@ -78,7 +101,11 @@ export type AITutorTab = (typeof sidebarItems)[number]['key'];
|
||||
export function AITutorSidebar(props: AITutorSidebarProps) {
|
||||
const { activeTab, isFloating, onClose } = props;
|
||||
|
||||
const [format, setFormat] = useState('');
|
||||
const [isInitialLoad, setIsInitialLoad] = useState(true);
|
||||
const [expandedItems, setExpandedItems] = useState<Record<string, boolean>>({
|
||||
new: true, // Keep "Create with AI" expanded by default
|
||||
});
|
||||
|
||||
const [showAILimitsPopup, setShowAILimitsPopup] = useState(false);
|
||||
const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false);
|
||||
@@ -89,15 +116,21 @@ export function AITutorSidebar(props: AITutorSidebarProps) {
|
||||
queryClient,
|
||||
);
|
||||
|
||||
const { used, limit } = limits ?? { used: 0, limit: 0 };
|
||||
const totalPercentage = getPercentage(used, limit);
|
||||
|
||||
useEffect(() => {
|
||||
const { format } = getUrlParams();
|
||||
setFormat(format || 'course');
|
||||
setIsInitialLoad(false);
|
||||
}, []);
|
||||
|
||||
const isLoading = isPaidUserLoading || isLimitsLoading;
|
||||
|
||||
const toggleExpanded = (key: string) => {
|
||||
setExpandedItems((prev) => ({
|
||||
...prev,
|
||||
[key]: !prev[key],
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isUpgradeModalOpen && (
|
||||
@@ -156,7 +189,27 @@ export function AITutorSidebar(props: AITutorSidebarProps) {
|
||||
<AITutorSidebarItem
|
||||
item={item}
|
||||
isActive={activeTab === item.key}
|
||||
isExpanded={expandedItems[item.key] || false}
|
||||
onToggleExpanded={() => toggleExpanded(item.key)}
|
||||
/>
|
||||
{item.children && expandedItems[item.key] && (
|
||||
<ul className="relative list-none">
|
||||
<div className="absolute top-0 bottom-0 left-7 w-px bg-gray-200" />
|
||||
{item.children.map((child) => (
|
||||
<li key={child.key}>
|
||||
<AITutorSidebarItem
|
||||
item={child}
|
||||
isActive={
|
||||
(activeTab === item.key &&
|
||||
`create-${format}` === child.key) ||
|
||||
activeTab === child.key
|
||||
}
|
||||
isChild={true}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
|
||||
@@ -179,35 +232,83 @@ export function AITutorSidebar(props: AITutorSidebarProps) {
|
||||
);
|
||||
}
|
||||
|
||||
type SidebarItem = {
|
||||
key: string;
|
||||
label: string;
|
||||
href: string;
|
||||
icon: any;
|
||||
children?: {
|
||||
key: string;
|
||||
label: string;
|
||||
href: string;
|
||||
icon: any;
|
||||
}[];
|
||||
};
|
||||
|
||||
type ChildItem = {
|
||||
key: string;
|
||||
label: string;
|
||||
href: string;
|
||||
icon: any;
|
||||
};
|
||||
|
||||
type AITutorSidebarItemProps = {
|
||||
item: (typeof sidebarItems)[number];
|
||||
item: SidebarItem | ChildItem;
|
||||
as?: 'a' | 'button';
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
isActive?: boolean;
|
||||
isExpanded?: boolean;
|
||||
onToggleExpanded?: () => void;
|
||||
isChild?: boolean;
|
||||
};
|
||||
|
||||
function AITutorSidebarItem(props: AITutorSidebarItemProps) {
|
||||
const { item, as = 'a', onClick, className, isActive } = props;
|
||||
const {
|
||||
item,
|
||||
as = 'a',
|
||||
onClick,
|
||||
className,
|
||||
isActive,
|
||||
isExpanded,
|
||||
onToggleExpanded,
|
||||
isChild,
|
||||
} = props;
|
||||
|
||||
const Component = as;
|
||||
const hasChildren = 'children' in item && item.children;
|
||||
const Component = hasChildren ? 'button' : as;
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...(as === 'a' ? { href: item.href } : {})}
|
||||
{...(as === 'button' ? { onClick } : {})}
|
||||
{...(Component === 'a' && !hasChildren ? { href: item.href } : {})}
|
||||
{...(Component === 'button'
|
||||
? { onClick: hasChildren ? onToggleExpanded : onClick }
|
||||
: {})}
|
||||
className={cn(
|
||||
'font-regular flex w-full items-center border-r-2 px-5 py-2 text-sm transition-all',
|
||||
isActive
|
||||
isActive && !hasChildren
|
||||
? 'border-r-black bg-gray-100 text-black'
|
||||
: 'border-r-transparent text-gray-500 hover:border-r-gray-300',
|
||||
: 'border-r-transparent text-gray-500',
|
||||
!isActive && !hasChildren && 'hover:bg-gray-50 hover:text-gray-700',
|
||||
!isActive && hasChildren && 'hover:text-gray-700',
|
||||
isChild && 'border-r-transparent py-1.5 pl-11',
|
||||
isChild && isActive && 'border-r-black border-r-2 bg-gray-100 text-black',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<span className="flex grow items-center">
|
||||
<item.icon className="mr-2 size-4" />
|
||||
{!isChild && <item.icon className="mr-2 size-4" />}
|
||||
{item.label}
|
||||
</span>
|
||||
{hasChildren && (
|
||||
<span className="ml-auto">
|
||||
{isExpanded ? (
|
||||
<ChevronDown className="size-4" />
|
||||
) : (
|
||||
<ChevronRight className="size-4" />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@ import {
|
||||
} from './QuestionAnswerChat';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
import { cn } from '../../lib/classname';
|
||||
import { getUrlParams } from '../../lib/browser';
|
||||
import { getUrlParams, setUrlParams } from '../../lib/browser';
|
||||
import { useParams } from '../../hooks/use-params';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { aiLimitOptions } from '../../queries/ai-course';
|
||||
@@ -205,7 +205,10 @@ export function ContentGenerator() {
|
||||
<FormatItem
|
||||
key={format.value}
|
||||
label={format.label}
|
||||
onClick={() => setSelectedFormat(format.value)}
|
||||
onClick={() => {
|
||||
setSelectedFormat(format.value);
|
||||
setUrlParams({ format: format.value });
|
||||
}}
|
||||
icon={format.icon}
|
||||
isSelected={isSelected}
|
||||
/>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Lock, Play } from 'lucide-react';
|
||||
import { markdownToHtml } from '../../lib/markdown';
|
||||
|
||||
interface FeatureCardProps {
|
||||
title: string;
|
||||
@@ -42,29 +43,32 @@ export function FeatureCard(props: FeatureCardProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="group relative overflow-hidden rounded-lg border border-slate-700 bg-[#151F33] p-4 hover:border-blue-400">
|
||||
<button
|
||||
onClick={onClick}
|
||||
className="group relative overflow-hidden rounded-lg border border-slate-700 bg-[#151F33] p-4 text-left hover:border-blue-400"
|
||||
className="relative block aspect-video w-full cursor-pointer overflow-hidden rounded-lg bg-slate-900/50"
|
||||
>
|
||||
<span className="group relative block aspect-video w-full cursor-pointer overflow-hidden rounded-lg bg-slate-900/50">
|
||||
<img
|
||||
src={thumbnail}
|
||||
alt={title}
|
||||
className="absolute inset-0 h-full w-full object-cover"
|
||||
/>
|
||||
</span>
|
||||
<span className="absolute inset-0 flex items-center justify-center bg-black/30 opacity-0 group-hover:opacity-100">
|
||||
<span className="absolute inset-0 -m-4 flex items-center justify-center bg-black/30 opacity-0 group-hover:opacity-100">
|
||||
<span className="flex h-12 w-12 items-center justify-center rounded-full bg-white/10 backdrop-blur-sm">
|
||||
<Play className="h-6 w-6 fill-current text-white" strokeWidth={2} />
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<span className="absolute top-1 right-1 rounded bg-black/60 px-2 py-1 text-xs text-white backdrop-blur-sm">
|
||||
{duration}
|
||||
</span>
|
||||
<div className="mt-4">
|
||||
<h3 className="mb-1 text-sm font-medium text-white">{title}</h3>
|
||||
<p className="text-xs leading-relaxed text-slate-400">{description}</p>
|
||||
<p
|
||||
className="text-xs [&_a]:text-blue-400 [&_a]:underline-offset-2 [&_a]:underline leading-relaxed text-slate-400"
|
||||
dangerouslySetInnerHTML={{ __html: markdownToHtml(description) }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
@@ -3,6 +3,7 @@ import {
|
||||
Bot,
|
||||
CheckCircle,
|
||||
Clock,
|
||||
MousePointerClick,
|
||||
PartyPopper,
|
||||
Users2,
|
||||
Wand2,
|
||||
@@ -161,10 +162,10 @@ export function PremiumPage() {
|
||||
Generate unlimited courses about any topic, get career guidance
|
||||
and instant answers from AI, test your skills and more
|
||||
</p>
|
||||
<div className="flex justify-center">
|
||||
<div className="flex flex-col items-center justify-center gap-4 sm:flex-row">
|
||||
<a
|
||||
href="#pricing"
|
||||
className="group mx-auto block rounded-2xl bg-gradient-to-b from-indigo-600 to-indigo-700 px-8 py-4 shadow-lg transition-all hover:-translate-y-0.5 hover:shadow-xl hover:shadow-indigo-500/25"
|
||||
className="group block rounded-2xl bg-gradient-to-b from-indigo-600 to-indigo-700 px-8 py-4 shadow-sm transition-all hover:-translate-y-0.5"
|
||||
>
|
||||
<span className="flex items-center justify-center gap-3 text-lg text-white sm:hidden">
|
||||
Upgrade now
|
||||
@@ -179,6 +180,21 @@ export function PremiumPage() {
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
<button
|
||||
data-demo-button="true"
|
||||
onClick={() => {
|
||||
if (isLoggedIn()) {
|
||||
window.location.href = '/ai';
|
||||
} else {
|
||||
showLoginPopup();
|
||||
}
|
||||
}}
|
||||
className="group relative flex items-center justify-center gap-2 overflow-hidden rounded-2xl border border-purple-500/30 bg-transparent px-6 py-4 text-base font-medium text-purple-500 shadow-sm transition-all hover:-translate-y-0.5 hover:bg-purple-500/10 hover:text-purple-400 hover:border-purple-300/30 focus:outline-none active:ring-0"
|
||||
>
|
||||
<MousePointerClick className="h-5 w-5" aria-hidden="true" />
|
||||
<span className="lg:hidden">Try Demo</span>
|
||||
<span className="hidden lg:inline">Try for Free</span>
|
||||
</button>
|
||||
</div>
|
||||
<p className="mt-5 flex items-center justify-center gap-2 text-sm">
|
||||
<Clock className="h-3 w-3 text-white" />
|
||||
|
@@ -10,18 +10,10 @@ export const paidFeaturesList = [
|
||||
];
|
||||
|
||||
export const features = [
|
||||
{
|
||||
title: 'Chat with Roadmaps',
|
||||
description:
|
||||
'Ask questions and get instant answers on any roadmap through AI',
|
||||
videoId: 'fq0UgNcj3Ek',
|
||||
thumbnail: 'https://assets.roadmap.sh/guest/chat-with-roadmaps-ew2l9.png',
|
||||
duration: '2:17',
|
||||
},
|
||||
{
|
||||
title: 'Unlimited AI Courses',
|
||||
description:
|
||||
'No more paying for expensive courses, create unlimited courses with AI',
|
||||
'No more paying for expensive courses, [create unlimited courses](/ai) with AI',
|
||||
videoId: 'uCcQNhdVUVQ',
|
||||
thumbnail: 'https://assets.roadmap.sh/guest/ai-courses-m07ra.png',
|
||||
duration: '3:07',
|
||||
@@ -29,15 +21,32 @@ export const features = [
|
||||
{
|
||||
title: 'In-depth Guides',
|
||||
description:
|
||||
'Create unlimited personalized in-depth learning guides with AI',
|
||||
'Create unlimited personalized [in-depth learning guides](/ai?format=guide) with AI',
|
||||
videoId: '5kwYjCg2Lu4',
|
||||
thumbnail: 'https://assets.roadmap.sh/guest/ai-guides-s4bjj.png',
|
||||
duration: '1:33',
|
||||
},
|
||||
{
|
||||
title: 'Learn Roadmap Topics',
|
||||
description:
|
||||
'Learn [roadmap topics directly](/backend) from within the nodes without leaving the roadmap',
|
||||
videoId: '0jZ1Lse1Y3E',
|
||||
thumbnail: 'https://assets.roadmap.sh/guest/smarter-roadmaps-f46ku.png',
|
||||
duration: '3:11',
|
||||
startTime: '5',
|
||||
},
|
||||
{
|
||||
title: 'Chat with Roadmaps',
|
||||
description:
|
||||
'Ask questions and [get instant answers](/frontend) on any roadmap through AI',
|
||||
videoId: 'fq0UgNcj3Ek',
|
||||
thumbnail: 'https://assets.roadmap.sh/guest/chat-with-roadmaps-ew2l9.png',
|
||||
duration: '2:17',
|
||||
},
|
||||
{
|
||||
title: 'AI as Learning Companion',
|
||||
description:
|
||||
'Use AI-powered learning companion to accelerate your roadmap progress',
|
||||
'Use [AI-powered learning companion](/ai/roadmap-chat) to accelerate your roadmap progress',
|
||||
videoId: 'Jso_HRviEOE',
|
||||
thumbnail: 'https://assets.roadmap.sh/guest/roadmap-ai-tools-adhqq.png',
|
||||
duration: '2:45',
|
||||
@@ -46,24 +55,16 @@ export const features = [
|
||||
{
|
||||
title: 'Your Personal Coach',
|
||||
description:
|
||||
'Get career guidance, roadmap and personalized growth suggestions',
|
||||
'Get career guidance, personalized growth suggestions using [AI Tutor Chat](/ai/chat)',
|
||||
videoId: '77VwAeFmoIw',
|
||||
thumbnail: 'https://assets.roadmap.sh/guest/career-guidance-t2mpu.png',
|
||||
duration: '3:45',
|
||||
startTime: '4',
|
||||
},
|
||||
{
|
||||
title: 'Learn Roadmap Topics',
|
||||
description:
|
||||
'Learn roadmap topics directly from within the nodes without leaving the roadmap',
|
||||
videoId: '0jZ1Lse1Y3E',
|
||||
thumbnail: 'https://assets.roadmap.sh/guest/smarter-roadmaps-f46ku.png',
|
||||
duration: '3:11',
|
||||
startTime: '5'
|
||||
},
|
||||
{
|
||||
title: 'Test Yourself',
|
||||
description: 'Test your knowledge and prepare for interviews with AI',
|
||||
description:
|
||||
'Let AI help you [test your knowledge](/ai/quiz) and prepare for interviews',
|
||||
videoId: 'ScruGC8uzjo',
|
||||
thumbnail: 'https://assets.roadmap.sh/guest/test-yourself-uwzqo.png',
|
||||
duration: '2:15',
|
||||
@@ -71,7 +72,7 @@ export const features = [
|
||||
{
|
||||
title: 'Powerful Roadmap Editor',
|
||||
description:
|
||||
'Create and edit roadmaps with ease using our powerful roadmap editor',
|
||||
'[Create and edit roadmaps](/account/roadmaps) with ease using our powerful roadmap editor',
|
||||
videoId: 'L2HZIHIgwOI',
|
||||
thumbnail:
|
||||
'https://assets.roadmap.sh/guest/ai-based-roadmap-editor-ochm8.png',
|
||||
|
Reference in New Issue
Block a user