diff --git a/app/layout.tsx b/app/layout.tsx index 6494e6f..8292672 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,8 +1,34 @@ import type { Metadata } from 'next'; import { Analytics } from '@vercel/analytics/react'; +import { Bebas_Neue, Inter, JetBrains_Mono, Space_Grotesk } from 'next/font/google'; import '../src/styles/globals.css'; import { ExperienceProvider } from '@/src/features/motion/ExperienceProvider'; +const inter = Inter({ + subsets: ['latin'], + variable: '--font-inter', + display: 'swap', +}); + +const spaceGrotesk = Space_Grotesk({ + subsets: ['latin'], + variable: '--font-space-grotesk', + display: 'swap', +}); + +const bebasNeue = Bebas_Neue({ + weight: '400', + subsets: ['latin'], + variable: '--font-bebas-neue', + display: 'swap', +}); + +const jetbrainsMono = JetBrains_Mono({ + subsets: ['latin'], + variable: '--font-jetbrains-mono', + display: 'swap', +}); + export const metadata: Metadata = { title: { default: 'Yantra Code Editor', @@ -19,7 +45,9 @@ export default function RootLayout({ }>) { return ( - + (null); + +export function useDashboardData() { + const context = useContext(DashboardDataContext); + + if (!context) { + throw new Error('useDashboardData must be used inside StudentDashboard.'); + } + + return context; +} diff --git a/src/features/dashboard/StudentDashboard.tsx b/src/features/dashboard/StudentDashboard.tsx index 7d7f74d..e43bf44 100644 --- a/src/features/dashboard/StudentDashboard.tsx +++ b/src/features/dashboard/StudentDashboard.tsx @@ -23,19 +23,21 @@ import { type LucideIcon, } from 'lucide-react'; import { motion, useInView } from 'motion/react'; -import { createContext, memo, useContext, useEffect, useRef, useState, type ReactNode } from 'react'; +import { memo, useEffect, useRef, useState } from 'react'; import { ChatProvider, useChatWidgetActions } from '@/src/features/chat/ChatWidget'; import { useScrollThreshold } from '@/src/features/motion/useScrollThreshold'; import GlobalSidebar from '@/src/features/navigation/GlobalSidebar'; +import { DashboardDataContext, useDashboardData } from './DashboardDataContext'; import { type DashboardRoomTextureKey, type DashboardSkillIconKey, type DashboardSkillToneKey, - type StudentDashboardCurriculumNode, type StudentDashboardData, - type StudentDashboardRoom, type StudentDashboardSkill, } from './student-dashboard-model'; +import DashboardCurriculumSection from './sections/DashboardCurriculumSection'; +import DashboardRoomsSection from './sections/DashboardRoomsSection'; +import DashboardSectionShell from './sections/DashboardSectionShell'; const YantraAmbientBackground = dynamic(() => import('./YantraAmbientBackground'), { ssr: false }); import type { DashboardCurriculumNode, @@ -397,8 +399,6 @@ function buildDashboardViewModel(data: StudentDashboardData): DashboardViewModel }; } -const DashboardDataContext = createContext(null); - const aiPrompts = [ 'Explain backpropagation simply', 'What should I learn next?', @@ -431,70 +431,6 @@ const roomTextureMap: Record = { 'radial-gradient(circle at 30% 18%, rgba(255,255,255,0.08), transparent 26%), linear-gradient(150deg, rgba(9,9,9,0.96), rgba(20,20,20,0.84) 50%, rgba(11,11,13,0.98) 100%)', }; -function buildRoomHref(roomKey: string) { - if (roomKey === 'python-room') { - return '/dashboard/rooms/python'; - } - - return null; -} - -function useDashboardData() { - const context = useContext(DashboardDataContext); - - if (!context) { - throw new Error('useDashboardData must be used inside StudentDashboard.'); - } - - return context; -} - -function SectionShell({ - id, - number, - eyebrow, - title, - description, - action, - children, -}: { - id: string; - number: string; - eyebrow: string; - title: string; - description: string; - action?: ReactNode; - children: ReactNode; -}) { - return ( - -
- {number} -
- -
-
-
{eyebrow}
-

- {title} -

-

{description}

-
- {action} -
- -
{children}
-
- ); -} - function DashboardNav() { const { profile } = useDashboardData(); const { openChat } = useChatWidgetActions(); @@ -729,12 +665,12 @@ function HeroSection({ function OverviewSection({ view }: { view: DashboardViewModel }) { const { openChat } = useChatWidgetActions(); - const { path, curriculumNodes, weeklyActivity } = useDashboardData(); + const { path, weeklyActivity } = useDashboardData(); const masteryCircumference = 2 * Math.PI * 58; const masteryOffset = masteryCircumference - (masteryCircumference * path.masteryProgress) / 100; return ( - -
-
Active Curriculum Nodes
- -
- {curriculumNodes.map((node, index) => ( - - ))} -
-
-
- ); -} - -function CurriculumNodeCard({ node, index }: { node: StudentDashboardCurriculumNode; index: number }) { - return ( - -
- - {node.moduleLabel} -
- -
-

{node.title}

-

{node.description}

-
- -
- - {node.statusLabel} - - {node.unlocked ? : } -
-
+ ); } function SkillsSection() { const { skills } = useDashboardData(); return ( - ))} - + ); } @@ -1070,98 +966,6 @@ const SkillCard = memo(function SkillCard({ skill, index }: { skill: StudentDash SkillCard.displayName = 'SkillCard'; -const RoomCard = memo(function RoomCard({ room, index }: { room: StudentDashboardRoom; index: number }) { - const { openChat } = useChatWidgetActions(); - const roomHref = buildRoomHref(room.roomKey); - - return ( - -
-
- -
-
-
- {room.statusLabel} -
- -
-

{room.title}

-

{room.description}

-
-
- -
- {roomHref ? ( - - {room.ctaLabel} - - ) : ( - - )} - -
- AI-guided - -
-
-
- - ); -}); - -RoomCard.displayName = 'RoomCard'; - -function RoomsSection() { - const { rooms } = useDashboardData(); - - return ( - -
- {rooms.map((room, index) => ( - - ))} -
-
- ); -} - function YantraAiSection({ view }: { view: DashboardViewModel }) { const { openChat } = useChatWidgetActions(); const [draft, setDraft] = useState(''); @@ -1281,8 +1085,9 @@ function DashboardExperience() {
+ - +
diff --git a/src/features/dashboard/components/CurriculumCard.tsx b/src/features/dashboard/components/CurriculumCard.tsx new file mode 100644 index 0000000..8aa09cb --- /dev/null +++ b/src/features/dashboard/components/CurriculumCard.tsx @@ -0,0 +1,41 @@ +'use client'; + +import { Lock, Sparkles } from 'lucide-react'; +import { motion } from 'motion/react'; +import type { StudentDashboardCurriculumNode } from '../student-dashboard-model'; + +type CurriculumCardProps = { + node: StudentDashboardCurriculumNode; + index: number; +}; + +export default function CurriculumCard({ node, index }: CurriculumCardProps) { + return ( + +
+ + {node.moduleLabel} +
+ +
+

{node.title}

+

{node.description}

+
+ +
+ + {node.statusLabel} + + {node.unlocked ? : } +
+
+ ); +} diff --git a/src/features/dashboard/components/RoomCard.tsx b/src/features/dashboard/components/RoomCard.tsx new file mode 100644 index 0000000..fa6a19d --- /dev/null +++ b/src/features/dashboard/components/RoomCard.tsx @@ -0,0 +1,101 @@ +'use client'; + +import Link from 'next/link'; +import { ArrowRight } from 'lucide-react'; +import { motion } from 'motion/react'; +import { memo } from 'react'; +import { useChatWidgetActions } from '@/src/features/chat/ChatWidget'; +import type { DashboardRoomTextureKey, StudentDashboardRoom } from '../student-dashboard-model'; + +const roomTextureMap: Record = { + 'python-room': + 'radial-gradient(circle at 20% 10%, rgba(255,255,255,0.12), transparent 32%), radial-gradient(circle at 78% 18%, rgba(255,255,255,0.06), transparent 24%), linear-gradient(145deg, rgba(3,10,14,0.95), rgba(9,20,24,0.82) 42%, rgba(11,11,11,0.96) 100%)', + 'neural-builder': + 'radial-gradient(circle at 80% 12%, rgba(255,255,255,0.14), transparent 30%), radial-gradient(circle at 20% 88%, rgba(255,255,255,0.06), transparent 34%), linear-gradient(145deg, rgba(19,19,22,0.98), rgba(27,27,30,0.9) 45%, rgba(9,9,11,0.98) 100%)', + 'data-explorer': + 'radial-gradient(circle at 72% 18%, rgba(255,255,255,0.08), transparent 26%), linear-gradient(160deg, rgba(10,12,14,0.95), rgba(21,21,21,0.85) 54%, rgba(8,8,8,0.98) 100%)', + 'prompt-lab': + 'radial-gradient(circle at 30% 18%, rgba(255,255,255,0.08), transparent 26%), linear-gradient(150deg, rgba(9,9,9,0.96), rgba(20,20,20,0.84) 50%, rgba(11,11,13,0.98) 100%)', +}; + +function buildRoomHref(roomKey: string) { + if (roomKey === 'python-room') { + return '/dashboard/rooms/python'; + } + + return null; +} + +const RoomCard = memo(function RoomCard({ room, index }: { room: StudentDashboardRoom; index: number }) { + const { openChat } = useChatWidgetActions(); + const roomHref = buildRoomHref(room.roomKey); + + return ( + +
+
+ +
+
+
+ {room.statusLabel} +
+ +
+

{room.title}

+

{room.description}

+
+
+ +
+ {roomHref ? ( + + {room.ctaLabel} + + ) : ( + + )} + +
+ AI-guided + +
+
+
+ + ); +}); + +RoomCard.displayName = 'RoomCard'; + +export default RoomCard; diff --git a/src/features/dashboard/sections/DashboardCurriculumSection.tsx b/src/features/dashboard/sections/DashboardCurriculumSection.tsx new file mode 100644 index 0000000..38b5915 --- /dev/null +++ b/src/features/dashboard/sections/DashboardCurriculumSection.tsx @@ -0,0 +1,20 @@ +'use client'; + +import { useDashboardData } from '../DashboardDataContext'; +import CurriculumCard from '../components/CurriculumCard'; + +export default function DashboardCurriculumSection() { + const { curriculumNodes } = useDashboardData(); + + return ( +
+
Active Curriculum Nodes
+ +
+ {curriculumNodes.map((node, index) => ( + + ))} +
+
+ ); +} diff --git a/src/features/dashboard/sections/DashboardRoomsSection.tsx b/src/features/dashboard/sections/DashboardRoomsSection.tsx new file mode 100644 index 0000000..2a89a1d --- /dev/null +++ b/src/features/dashboard/sections/DashboardRoomsSection.tsx @@ -0,0 +1,25 @@ +'use client'; + +import { useDashboardData } from '../DashboardDataContext'; +import RoomCard from '../components/RoomCard'; +import DashboardSectionShell from './DashboardSectionShell'; + +export default function DashboardRoomsSection() { + const { rooms } = useDashboardData(); + + return ( + +
+ {rooms.map((room, index) => ( + + ))} +
+
+ ); +} diff --git a/src/features/dashboard/sections/DashboardSectionShell.tsx b/src/features/dashboard/sections/DashboardSectionShell.tsx new file mode 100644 index 0000000..29ed24a --- /dev/null +++ b/src/features/dashboard/sections/DashboardSectionShell.tsx @@ -0,0 +1,50 @@ +'use client'; + +import { motion } from 'motion/react'; +import type { ReactNode } from 'react'; + +type DashboardSectionShellProps = { + id: string; + number: string; + eyebrow: string; + title: string; + description: string; + action?: ReactNode; + children: ReactNode; +}; + +export default function DashboardSectionShell({ + id, + number, + eyebrow, + title, + description, + action, + children, +}: DashboardSectionShellProps) { + return ( + +
+ {number} +
+ +
+
+
{eyebrow}
+

{title}

+

{description}

+
+ {action} +
+ +
{children}
+
+ ); +} diff --git a/src/features/docs/DocsArticlePage.tsx b/src/features/docs/DocsArticlePage.tsx index 0e3ebbd..dfa8ba3 100644 --- a/src/features/docs/DocsArticlePage.tsx +++ b/src/features/docs/DocsArticlePage.tsx @@ -147,8 +147,8 @@ export default function DocsArticlePage({ slug }: { slug: string }) { return ( diff --git a/src/features/docs/DocsHomePage.tsx b/src/features/docs/DocsHomePage.tsx index db6e50e..8c712a2 100644 --- a/src/features/docs/DocsHomePage.tsx +++ b/src/features/docs/DocsHomePage.tsx @@ -103,8 +103,8 @@ export default function DocsHomePage() {