From 1cc0e441187c62651a77a5f3e8d0e9e4b51f572c Mon Sep 17 00:00:00 2001 From: agabrielcorujo Date: Tue, 10 Feb 2026 16:43:07 -0500 Subject: [PATCH 1/2] test commit --- infra/CloudFormation/test.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 infra/CloudFormation/test.txt diff --git a/infra/CloudFormation/test.txt b/infra/CloudFormation/test.txt new file mode 100644 index 0000000..e69de29 From 3829175fcb0533db62ed94aae925c0f8b897d3e1 Mon Sep 17 00:00:00 2001 From: agabrielcorujo Date: Tue, 10 Feb 2026 16:47:35 -0500 Subject: [PATCH 2/2] base frontend added --- .DS_Store | Bin 6148 -> 6148 bytes infra/CloudFormation/test.txt | 0 src/.DS_Store | Bin 0 -> 6148 bytes src/client/.DS_Store | Bin 0 -> 6148 bytes src/client/certstack/.gitignore | 53 +- src/client/certstack/app/dashboard/page.tsx | 37 + src/client/certstack/app/exams/page.tsx | 305 + src/client/certstack/app/favicon.ico | Bin 25931 -> 0 bytes src/client/certstack/app/globals.css | 125 +- src/client/certstack/app/help/page.tsx | 106 + src/client/certstack/app/layout.tsx | 33 +- src/client/certstack/app/login/page.tsx | 186 + src/client/certstack/app/materials/page.tsx | 179 + src/client/certstack/app/page.tsx | 66 +- src/client/certstack/app/practice/page.tsx | 148 + src/client/certstack/app/profile/page.tsx | 280 + src/client/certstack/app/settings/page.tsx | 5 + src/client/certstack/app/signup/page.tsx | 243 + src/client/certstack/components.json | 21 + .../certstack/components/app-header.tsx | 53 + .../certstack/components/app-layout.tsx | 16 + .../certstack/components/app-sidebar.tsx | 99 + .../components/dashboard/progress-chart.tsx | 111 + .../components/dashboard/recent-activity.tsx | 122 + .../components/dashboard/stat-cards.tsx | 109 + .../components/dashboard/subject-progress.tsx | 93 + .../components/dashboard/upcoming-exams.tsx | 91 + .../components/practice/question-card.tsx | 179 + .../practice/question-navigator.tsx | 92 + .../certstack/components/theme-provider.tsx | 11 + .../certstack/components/ui/accordion.tsx | 58 + .../certstack/components/ui/alert-dialog.tsx | 141 + src/client/certstack/components/ui/alert.tsx | 59 + .../certstack/components/ui/aspect-ratio.tsx | 7 + src/client/certstack/components/ui/avatar.tsx | 50 + src/client/certstack/components/ui/badge.tsx | 37 + .../certstack/components/ui/breadcrumb.tsx | 115 + src/client/certstack/components/ui/button.tsx | 57 + .../certstack/components/ui/calendar.tsx | 66 + src/client/certstack/components/ui/card.tsx | 79 + .../certstack/components/ui/carousel.tsx | 262 + src/client/certstack/components/ui/chart.tsx | 365 + .../certstack/components/ui/checkbox.tsx | 30 + .../certstack/components/ui/collapsible.tsx | 11 + .../certstack/components/ui/command.tsx | 153 + .../certstack/components/ui/context-menu.tsx | 200 + src/client/certstack/components/ui/dialog.tsx | 122 + src/client/certstack/components/ui/drawer.tsx | 118 + .../certstack/components/ui/dropdown-menu.tsx | 200 + src/client/certstack/components/ui/form.tsx | 178 + .../certstack/components/ui/hover-card.tsx | 29 + .../certstack/components/ui/input-otp.tsx | 71 + src/client/certstack/components/ui/input.tsx | 22 + src/client/certstack/components/ui/label.tsx | 26 + .../certstack/components/ui/menubar.tsx | 236 + .../components/ui/navigation-menu.tsx | 128 + .../certstack/components/ui/pagination.tsx | 117 + .../certstack/components/ui/popover.tsx | 31 + .../certstack/components/ui/progress.tsx | 28 + .../certstack/components/ui/radio-group.tsx | 44 + .../certstack/components/ui/resizable.tsx | 45 + .../certstack/components/ui/scroll-area.tsx | 48 + src/client/certstack/components/ui/select.tsx | 160 + .../certstack/components/ui/separator.tsx | 31 + src/client/certstack/components/ui/sheet.tsx | 141 + .../certstack/components/ui/sidebar.tsx | 771 ++ .../certstack/components/ui/skeleton.tsx | 15 + src/client/certstack/components/ui/slider.tsx | 28 + src/client/certstack/components/ui/sonner.tsx | 31 + src/client/certstack/components/ui/switch.tsx | 29 + src/client/certstack/components/ui/table.tsx | 117 + src/client/certstack/components/ui/tabs.tsx | 55 + .../certstack/components/ui/textarea.tsx | 22 + src/client/certstack/components/ui/toast.tsx | 129 + .../certstack/components/ui/toaster.tsx | 35 + .../certstack/components/ui/toggle-group.tsx | 61 + src/client/certstack/components/ui/toggle.tsx | 45 + .../certstack/components/ui/tooltip.tsx | 30 + .../certstack/components/ui/use-mobile.tsx | 19 + .../certstack/components/ui/use-toast.ts | 191 + src/client/certstack/eslint.config.mjs | 18 - src/client/certstack/hooks/use-mobile.tsx | 19 + src/client/certstack/hooks/use-toast.ts | 191 + src/client/certstack/lib/utils.ts | 6 + src/client/certstack/next.config.mjs | 11 + src/client/certstack/next.config.ts | 7 - src/client/certstack/package-lock.json | 6549 ----------------- src/client/certstack/package.json | 67 +- src/client/certstack/postcss.config.mjs | 7 +- src/client/certstack/public/file.svg | 1 - src/client/certstack/public/globe.svg | 1 - src/client/certstack/public/next.svg | 1 - .../certstack/public/placeholder-logo.svg | 1 + src/client/certstack/public/placeholder.svg | 1 + src/client/certstack/public/vercel.svg | 1 - src/client/certstack/public/window.svg | 1 - src/client/certstack/styles/globals.css | 94 + src/client/certstack/tailwind.config.ts | 113 + src/client/certstack/tsconfig.json | 5 +- 99 files changed, 8159 insertions(+), 6741 deletions(-) delete mode 100644 infra/CloudFormation/test.txt create mode 100644 src/.DS_Store create mode 100644 src/client/.DS_Store create mode 100644 src/client/certstack/app/dashboard/page.tsx create mode 100644 src/client/certstack/app/exams/page.tsx delete mode 100644 src/client/certstack/app/favicon.ico create mode 100644 src/client/certstack/app/help/page.tsx create mode 100644 src/client/certstack/app/login/page.tsx create mode 100644 src/client/certstack/app/materials/page.tsx create mode 100644 src/client/certstack/app/practice/page.tsx create mode 100644 src/client/certstack/app/profile/page.tsx create mode 100644 src/client/certstack/app/settings/page.tsx create mode 100644 src/client/certstack/app/signup/page.tsx create mode 100644 src/client/certstack/components.json create mode 100644 src/client/certstack/components/app-header.tsx create mode 100644 src/client/certstack/components/app-layout.tsx create mode 100644 src/client/certstack/components/app-sidebar.tsx create mode 100644 src/client/certstack/components/dashboard/progress-chart.tsx create mode 100644 src/client/certstack/components/dashboard/recent-activity.tsx create mode 100644 src/client/certstack/components/dashboard/stat-cards.tsx create mode 100644 src/client/certstack/components/dashboard/subject-progress.tsx create mode 100644 src/client/certstack/components/dashboard/upcoming-exams.tsx create mode 100644 src/client/certstack/components/practice/question-card.tsx create mode 100644 src/client/certstack/components/practice/question-navigator.tsx create mode 100644 src/client/certstack/components/theme-provider.tsx create mode 100644 src/client/certstack/components/ui/accordion.tsx create mode 100644 src/client/certstack/components/ui/alert-dialog.tsx create mode 100644 src/client/certstack/components/ui/alert.tsx create mode 100644 src/client/certstack/components/ui/aspect-ratio.tsx create mode 100644 src/client/certstack/components/ui/avatar.tsx create mode 100644 src/client/certstack/components/ui/badge.tsx create mode 100644 src/client/certstack/components/ui/breadcrumb.tsx create mode 100644 src/client/certstack/components/ui/button.tsx create mode 100644 src/client/certstack/components/ui/calendar.tsx create mode 100644 src/client/certstack/components/ui/card.tsx create mode 100644 src/client/certstack/components/ui/carousel.tsx create mode 100644 src/client/certstack/components/ui/chart.tsx create mode 100644 src/client/certstack/components/ui/checkbox.tsx create mode 100644 src/client/certstack/components/ui/collapsible.tsx create mode 100644 src/client/certstack/components/ui/command.tsx create mode 100644 src/client/certstack/components/ui/context-menu.tsx create mode 100644 src/client/certstack/components/ui/dialog.tsx create mode 100644 src/client/certstack/components/ui/drawer.tsx create mode 100644 src/client/certstack/components/ui/dropdown-menu.tsx create mode 100644 src/client/certstack/components/ui/form.tsx create mode 100644 src/client/certstack/components/ui/hover-card.tsx create mode 100644 src/client/certstack/components/ui/input-otp.tsx create mode 100644 src/client/certstack/components/ui/input.tsx create mode 100644 src/client/certstack/components/ui/label.tsx create mode 100644 src/client/certstack/components/ui/menubar.tsx create mode 100644 src/client/certstack/components/ui/navigation-menu.tsx create mode 100644 src/client/certstack/components/ui/pagination.tsx create mode 100644 src/client/certstack/components/ui/popover.tsx create mode 100644 src/client/certstack/components/ui/progress.tsx create mode 100644 src/client/certstack/components/ui/radio-group.tsx create mode 100644 src/client/certstack/components/ui/resizable.tsx create mode 100644 src/client/certstack/components/ui/scroll-area.tsx create mode 100644 src/client/certstack/components/ui/select.tsx create mode 100644 src/client/certstack/components/ui/separator.tsx create mode 100644 src/client/certstack/components/ui/sheet.tsx create mode 100644 src/client/certstack/components/ui/sidebar.tsx create mode 100644 src/client/certstack/components/ui/skeleton.tsx create mode 100644 src/client/certstack/components/ui/slider.tsx create mode 100644 src/client/certstack/components/ui/sonner.tsx create mode 100644 src/client/certstack/components/ui/switch.tsx create mode 100644 src/client/certstack/components/ui/table.tsx create mode 100644 src/client/certstack/components/ui/tabs.tsx create mode 100644 src/client/certstack/components/ui/textarea.tsx create mode 100644 src/client/certstack/components/ui/toast.tsx create mode 100644 src/client/certstack/components/ui/toaster.tsx create mode 100644 src/client/certstack/components/ui/toggle-group.tsx create mode 100644 src/client/certstack/components/ui/toggle.tsx create mode 100644 src/client/certstack/components/ui/tooltip.tsx create mode 100644 src/client/certstack/components/ui/use-mobile.tsx create mode 100644 src/client/certstack/components/ui/use-toast.ts delete mode 100644 src/client/certstack/eslint.config.mjs create mode 100644 src/client/certstack/hooks/use-mobile.tsx create mode 100644 src/client/certstack/hooks/use-toast.ts create mode 100644 src/client/certstack/lib/utils.ts create mode 100644 src/client/certstack/next.config.mjs delete mode 100644 src/client/certstack/next.config.ts delete mode 100644 src/client/certstack/package-lock.json delete mode 100644 src/client/certstack/public/file.svg delete mode 100644 src/client/certstack/public/globe.svg delete mode 100644 src/client/certstack/public/next.svg create mode 100644 src/client/certstack/public/placeholder-logo.svg create mode 100644 src/client/certstack/public/placeholder.svg delete mode 100644 src/client/certstack/public/vercel.svg delete mode 100644 src/client/certstack/public/window.svg create mode 100644 src/client/certstack/styles/globals.css create mode 100644 src/client/certstack/tailwind.config.ts diff --git a/.DS_Store b/.DS_Store index 4076ea698a799cb97e87b6b823cd91737c73543e..153624f1b620dc1f7c80c96ecd9cb0dfadfde595 100644 GIT binary patch delta 85 zcmZoMXfc=&!Bbq2l#`#tz`(F0sbDfA)AGp%A}s9848;sZ49Sz_7#lbYO$>DuOpJ^s jFJrV9hDwwL7v<&T=cNO+F>YpLVq=@wAh?;G<1aq|=)V?f delta 98 zcmZoMXfc=&!NZWskjIdcR9;+=lr#AwS5Z-NTyQK&@DD)WcTClA}5HBIt7cim+m718K!I&*gY7eE5v%Zi|;`2DO zyEy~{-bCyS?0&QJvzz%K`@tC_WTCez%H(`ca*;xK}c+v_+D<;s=IG|W`4ryZgzx-+Z4UXLcDfjt?IHv@Zp zG9H3{biCPg#s0zJ>Ba0hc}eA)CXxf^N_GvF@D56)rdMx~CNg~ldzn+l5)uQ%05L!e zY##&WED*izE1fDP28e+l7{L9(hKA@EEHtXE13J7uqrZiS0y@4W5N(5w!9pW=K)6l? z)T!J&F}O|#yKUkegM~(&&bV3`<}oXmj~A|12fMAp8Fw^NPYe(PRR)@RXyN&P4!=z8 zBfpwLBVvFU_-738*3_H2P?R}azmS5Z-O8O(;SR3OxqA7Hn$~#7l_v1&ruHr6wk5Xv~%*wTDv3SzpK}@p+ut z-H4?XJc-yDnEhtwC(C{t_J=XX-D%ik%wmigpoldSnl*xP)D@{|4bjTO9tv1Vq^UX~^zeSm0H zSyd1c1H=F^uqh1a{m*D^%7kg2!~ikyV+L@4ut5k2nbiDfU1=1Ck9vL;J0<2qp?(|${Cj + +
+
+ {/* Stat Cards */} + + + {/* Charts + Activity Row */} +
+
+ +
+
+ +
+
+ + {/* Activity + Upcoming Row */} +
+ + +
+
+
+ + ) +} diff --git a/src/client/certstack/app/exams/page.tsx b/src/client/certstack/app/exams/page.tsx new file mode 100644 index 0000000..620e798 --- /dev/null +++ b/src/client/certstack/app/exams/page.tsx @@ -0,0 +1,305 @@ +"use client" + +import { useState } from "react" +import { AppLayout } from "@/components/app-layout" +import { AppHeader } from "@/components/app-header" +import { + Search, + Filter, + Clock, + FileText, + CheckCircle, + Play, + ChevronDown, + Calendar, +} from "lucide-react" +import { cn } from "@/lib/utils" + +type ExamStatus = "all" | "available" | "completed" | "upcoming" + +interface Exam { + id: number + name: string + subject: string + questions: number + duration: string + difficulty: "Easy" | "Medium" | "Hard" + status: "available" | "completed" | "upcoming" + score?: number + date?: string + attempts?: number +} + +const exams: Exam[] = [ + { + id: 1, + name: "Anatomy Comprehensive Final", + subject: "Anatomy", + questions: 150, + duration: "3 hours", + difficulty: "Hard", + status: "available", + attempts: 0, + }, + { + id: 2, + name: "Pharmacology Midterm Mock", + subject: "Pharmacology", + questions: 100, + duration: "2 hours", + difficulty: "Medium", + status: "completed", + score: 85, + date: "Jan 28, 2026", + attempts: 2, + }, + { + id: 3, + name: "Biochemistry Unit 3 Test", + subject: "Biochemistry", + questions: 50, + duration: "1 hour", + difficulty: "Easy", + status: "completed", + score: 92, + date: "Jan 20, 2026", + attempts: 1, + }, + { + id: 4, + name: "Pathology Practice Exam", + subject: "Pathology", + questions: 120, + duration: "2.5 hours", + difficulty: "Hard", + status: "available", + attempts: 0, + }, + { + id: 5, + name: "Microbiology Final Review", + subject: "Microbiology", + questions: 80, + duration: "1.5 hours", + difficulty: "Medium", + status: "upcoming", + date: "Mar 5, 2026", + }, + { + id: 6, + name: "Physiology Systems Exam", + subject: "Physiology", + questions: 100, + duration: "2 hours", + difficulty: "Medium", + status: "completed", + score: 73, + date: "Jan 15, 2026", + attempts: 1, + }, + { + id: 7, + name: "Histology Lab Practical", + subject: "Histology", + questions: 60, + duration: "1 hour", + difficulty: "Easy", + status: "available", + attempts: 0, + }, + { + id: 8, + name: "Genetics Comprehensive", + subject: "Genetics", + questions: 90, + duration: "2 hours", + difficulty: "Hard", + status: "upcoming", + date: "Mar 12, 2026", + }, +] + +const difficultyConfig = { + Easy: { bg: "#ECFDF5", text: "#047857" }, + Medium: { bg: "#FEF3C7", text: "#B45309" }, + Hard: { bg: "#FEE2E2", text: "#B91C1C" }, +} + +const statusConfig = { + available: { label: "Start Exam", icon: Play, color: "#4A7FFF" }, + completed: { label: "Review", icon: CheckCircle, color: "#10B981" }, + upcoming: { label: "Upcoming", icon: Calendar, color: "#F59E0B" }, +} + +const tabs: { label: string; value: ExamStatus }[] = [ + { label: "All Exams", value: "all" }, + { label: "Available", value: "available" }, + { label: "Completed", value: "completed" }, + { label: "Upcoming", value: "upcoming" }, +] + +export default function ExamsPage() { + const [activeTab, setActiveTab] = useState("all") + const [searchQuery, setSearchQuery] = useState("") + + const filteredExams = exams.filter((exam) => { + const matchesTab = activeTab === "all" || exam.status === activeTab + const matchesSearch = + exam.name.toLowerCase().includes(searchQuery.toLowerCase()) || + exam.subject.toLowerCase().includes(searchQuery.toLowerCase()) + return matchesTab && matchesSearch + }) + + return ( + + +
+
+ {/* Filters Row */} +
+ {/* Tabs */} +
+ {tabs.map((tab) => ( + + ))} +
+ + {/* Search + Filter */} +
+
+ + setSearchQuery(e.target.value)} + className="h-10 w-64 rounded-xl border border-transparent bg-[hsl(var(--surface))] pl-10 pr-4 text-sm text-[hsl(var(--text-primary))] placeholder:text-[hsl(var(--text-tertiary))] transition-all duration-200 focus:border-[hsl(var(--border))] focus:bg-[hsl(var(--surface-elevated))] focus:outline-none focus:ring-2 focus:ring-[#DBEAFE]" + /> +
+ +
+
+ + {/* Exam Table */} +
+ {/* Table Header */} +
+ + Exam Name + + + Subject + + + Questions + + + Duration + + + Difficulty + + + Action + +
+ + {/* Table Rows */} + {filteredExams.map((exam) => { + const difficulty = difficultyConfig[exam.difficulty] + const status = statusConfig[exam.status] + const StatusIcon = status.icon + return ( +
+ {/* Name */} +
+

{exam.name}

+ {exam.status === "completed" && exam.score !== undefined && ( +

+ Score: {exam.score}% | {exam.attempts} attempt{exam.attempts !== 1 ? "s" : ""} +

+ )} + {exam.status === "upcoming" && exam.date && ( +

+ Available: {exam.date} +

+ )} +
+ + {/* Subject */} + {exam.subject} + + {/* Questions */} +
+ + {exam.questions} +
+ + {/* Duration */} +
+ + {exam.duration} +
+ + {/* Difficulty */} + + {exam.difficulty} + + + {/* Action */} + +
+ ) + })} + + {filteredExams.length === 0 && ( +
+ +

+ No exams found +

+

+ Try adjusting your search or filter +

+
+ )} +
+
+
+
+ ) +} diff --git a/src/client/certstack/app/favicon.ico b/src/client/certstack/app/favicon.ico deleted file mode 100644 index 718d6fea4835ec2d246af9800eddb7ffb276240c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25931 zcmeHv30#a{`}aL_*G&7qml|y<+KVaDM2m#dVr!KsA!#An?kSQM(q<_dDNCpjEux83 zLb9Z^XxbDl(w>%i@8hT6>)&Gu{h#Oeyszu?xtw#Zb1mO{pgX9699l+Qppw7jXaYf~-84xW z)w4x8?=youko|}Vr~(D$UXIbiXABHh`p1?nn8Po~fxRJv}|0e(BPs|G`(TT%kKVJAdg5*Z|x0leQq0 zkdUBvb#>9F()jo|T~kx@OM8$9wzs~t2l;K=woNssA3l6|sx2r3+kdfVW@e^8e*E}v zA1y5{bRi+3Z`uD3{F7LgFJDdvm;nJilkzDku>BwXH(8ItVCXk*-lSJnR?-2UN%hJ){&rlvg`CDTj z)Bzo!3v7Ou#83zEDEFcKt(f1E0~=rqeEbTnMvWR#{+9pg%7G8y>u1OVRUSoox-ovF z2Ydma(;=YuBY(eI|04{hXzZD6_f(v~H;C~y5=DhAC{MMS>2fm~1H_t2$56pc$NH8( z5bH|<)71dV-_oCHIrzrT`2s-5w_+2CM0$95I6X8p^r!gHp+j_gd;9O<1~CEQQGS8) zS9Qh3#p&JM-G8rHekNmKVewU;pJRcTAog68KYo^dRo}(M>36U4Us zfgYWSiHZL3;lpWT=zNAW>Dh#mB!_@Lg%$ms8N-;aPqMn+C2HqZgz&9~Eu z4|Kp<`$q)Uw1R?y(~S>ePdonHxpV1#eSP1B;Ogo+-Pk}6#0GsZZ5!||ev2MGdh}_m z{DeR7?0-1^zVs&`AV6Vt;r3`I`OI_wgs*w=eO%_#7Kepl{B@xiyCANc(l zzIyd4y|c6PXWq9-|KM8(zIk8LPk(>a)zyFWjhT!$HJ$qX1vo@d25W<fvZQ2zUz5WRc(UnFMKHwe1| zWmlB1qdbiA(C0jmnV<}GfbKtmcu^2*P^O?MBLZKt|As~ge8&AAO~2K@zbXelK|4T<{|y4`raF{=72kC2Kn(L4YyenWgrPiv z@^mr$t{#X5VuIMeL!7Ab6_kG$&#&5p*Z{+?5U|TZ`B!7llpVmp@skYz&n^8QfPJzL z0G6K_OJM9x+Wu2gfN45phANGt{7=C>i34CV{Xqlx(fWpeAoj^N0Biu`w+MVcCUyU* zDZuzO0>4Z6fbu^T_arWW5n!E45vX8N=bxTVeFoep_G#VmNlQzAI_KTIc{6>c+04vr zx@W}zE5JNSU>!THJ{J=cqjz+4{L4A{Ob9$ZJ*S1?Ggg3klFp!+Y1@K+pK1DqI|_gq z5ZDXVpge8-cs!o|;K73#YXZ3AShj50wBvuq3NTOZ`M&qtjj#GOFfgExjg8Gn8>Vq5 z`85n+9|!iLCZF5$HJ$Iu($dm?8~-ofu}tEc+-pyke=3!im#6pk_Wo8IA|fJwD&~~F zc16osQ)EBo58U7XDuMexaPRjU@h8tXe%S{fA0NH3vGJFhuyyO!Uyl2^&EOpX{9As0 zWj+P>{@}jxH)8|r;2HdupP!vie{sJ28b&bo!8`D^x}TE$%zXNb^X1p@0PJ86`dZyj z%ce7*{^oo+6%&~I!8hQy-vQ7E)0t0ybH4l%KltWOo~8cO`T=157JqL(oq_rC%ea&4 z2NcTJe-HgFjNg-gZ$6!Y`SMHrlj}Etf7?r!zQTPPSv}{so2e>Fjs1{gzk~LGeesX%r(Lh6rbhSo_n)@@G-FTQy93;l#E)hgP@d_SGvyCp0~o(Y;Ee8{ zdVUDbHm5`2taPUOY^MAGOw*>=s7=Gst=D+p+2yON!0%Hk` zz5mAhyT4lS*T3LS^WSxUy86q&GnoHxzQ6vm8)VS}_zuqG?+3td68_x;etQAdu@sc6 zQJ&5|4(I?~3d-QOAODHpZ=hlSg(lBZ!JZWCtHHSj`0Wh93-Uk)_S%zsJ~aD>{`A0~ z9{AG(e|q3g5B%wYKRxiL2Y$8(4w6bzchKuloQW#e&S3n+P- z8!ds-%f;TJ1>)v)##>gd{PdS2Oc3VaR`fr=`O8QIO(6(N!A?pr5C#6fc~Ge@N%Vvu zaoAX2&(a6eWy_q&UwOhU)|P3J0Qc%OdhzW=F4D|pt0E4osw;%<%Dn58hAWD^XnZD= z>9~H(3bmLtxpF?a7su6J7M*x1By7YSUbxGi)Ot0P77`}P3{)&5Un{KD?`-e?r21!4vTTnN(4Y6Lin?UkSM z`MXCTC1@4A4~mvz%Rh2&EwY))LeoT=*`tMoqcEXI>TZU9WTP#l?uFv+@Dn~b(>xh2 z;>B?;Tz2SR&KVb>vGiBSB`@U7VIWFSo=LDSb9F{GF^DbmWAfpms8Sx9OX4CnBJca3 zlj9(x!dIjN?OG1X4l*imJNvRCk}F%!?SOfiOq5y^mZW)jFL@a|r-@d#f7 z2gmU8L3IZq0ynIws=}~m^#@&C%J6QFo~Mo4V`>v7MI-_!EBMMtb%_M&kvAaN)@ZVw z+`toz&WG#HkWDjnZE!6nk{e-oFdL^$YnbOCN}JC&{$#$O27@|Tn-skXr)2ml2~O!5 zX+gYoxhoc7qoU?C^3~&!U?kRFtnSEecWuH0B0OvLodgUAi}8p1 zrO6RSXHH}DMc$&|?D004DiOVMHV8kXCP@7NKB zgaZq^^O<7PoKEp72kby@W0Z!Y*Ay{&vfg#C&gG@YVR9g?FEocMUi1gSN$+V+ayF45{a zuDZDTN}mS|;BO%gEf}pjBfN2-gIrU#G5~cucA;dokXW89%>AyXJJI z9X4UlIWA|ZYHgbI z5?oFk@A=Ik7lrEQPDH!H+b`7_Y~aDb_qa=B2^Y&Ow41cU=4WDd40dp5(QS-WMN-=Y z9g;6_-JdNU;|6cPwf$ak*aJIcwL@1n$#l~zi{c{EW?T;DaW*E8DYq?Umtz{nJ&w-M zEMyTDrC&9K$d|kZe2#ws6)L=7K+{ zQw{XnV6UC$6-rW0emqm8wJoeZK)wJIcV?dST}Z;G0Arq{dVDu0&4kd%N!3F1*;*pW zR&qUiFzK=@44#QGw7k1`3t_d8&*kBV->O##t|tonFc2YWrL7_eqg+=+k;!F-`^b8> z#KWCE8%u4k@EprxqiV$VmmtiWxDLgnGu$Vs<8rppV5EajBXL4nyyZM$SWVm!wnCj-B!Wjqj5-5dNXukI2$$|Bu3Lrw}z65Lc=1G z^-#WuQOj$hwNGG?*CM_TO8Bg-1+qc>J7k5c51U8g?ZU5n?HYor;~JIjoWH-G>AoUP ztrWWLbRNqIjW#RT*WqZgPJXU7C)VaW5}MiijYbABmzoru6EmQ*N8cVK7a3|aOB#O& zBl8JY2WKfmj;h#Q!pN%9o@VNLv{OUL?rixHwOZuvX7{IJ{(EdPpuVFoQqIOa7giLVkBOKL@^smUA!tZ1CKRK}#SSM)iQHk)*R~?M!qkCruaS!#oIL1c z?J;U~&FfH#*98^G?i}pA{ z9Jg36t4=%6mhY(quYq*vSxptes9qy|7xSlH?G=S@>u>Ebe;|LVhs~@+06N<4CViBk zUiY$thvX;>Tby6z9Y1edAMQaiH zm^r3v#$Q#2T=X>bsY#D%s!bhs^M9PMAcHbCc0FMHV{u-dwlL;a1eJ63v5U*?Q_8JO zT#50!RD619#j_Uf))0ooADz~*9&lN!bBDRUgE>Vud-i5ck%vT=r^yD*^?Mp@Q^v+V zG#-?gKlr}Eeqifb{|So?HM&g91P8|av8hQoCmQXkd?7wIJwb z_^v8bbg`SAn{I*4bH$u(RZ6*xUhuA~hc=8czK8SHEKTzSxgbwi~9(OqJB&gwb^l4+m`k*Q;_?>Y-APi1{k zAHQ)P)G)f|AyjSgcCFps)Fh6Bca*Xznq36!pV6Az&m{O8$wGFD? zY&O*3*J0;_EqM#jh6^gMQKpXV?#1?>$ml1xvh8nSN>-?H=V;nJIwB07YX$e6vLxH( zqYwQ>qxwR(i4f)DLd)-$P>T-no_c!LsN@)8`e;W@)-Hj0>nJ-}Kla4-ZdPJzI&Mce zv)V_j;(3ERN3_@I$N<^|4Lf`B;8n+bX@bHbcZTopEmDI*Jfl)-pFDvo6svPRoo@(x z);_{lY<;);XzT`dBFpRmGrr}z5u1=pC^S-{ce6iXQlLGcItwJ^mZx{m$&DA_oEZ)B{_bYPq-HA zcH8WGoBG(aBU_j)vEy+_71T34@4dmSg!|M8Vf92Zj6WH7Q7t#OHQqWgFE3ARt+%!T z?oLovLVlnf?2c7pTc)~cc^($_8nyKwsN`RA-23ed3sdj(ys%pjjM+9JrctL;dy8a( z@en&CQmnV(()bu|Y%G1-4a(6x{aLytn$T-;(&{QIJB9vMox11U-1HpD@d(QkaJdEb zG{)+6Dos_L+O3NpWo^=gR?evp|CqEG?L&Ut#D*KLaRFOgOEK(Kq1@!EGcTfo+%A&I z=dLbB+d$u{sh?u)xP{PF8L%;YPPW53+@{>5W=Jt#wQpN;0_HYdw1{ksf_XhO4#2F= zyPx6Lx2<92L-;L5PD`zn6zwIH`Jk($?Qw({erA$^bC;q33hv!d!>%wRhj# zal^hk+WGNg;rJtb-EB(?czvOM=H7dl=vblBwAv>}%1@{}mnpUznfq1cE^sgsL0*4I zJ##!*B?=vI_OEVis5o+_IwMIRrpQyT_Sq~ZU%oY7c5JMIADzpD!Upz9h@iWg_>>~j zOLS;wp^i$-E?4<_cp?RiS%Rd?i;f*mOz=~(&3lo<=@(nR!_Rqiprh@weZlL!t#NCc zO!QTcInq|%#>OVgobj{~ixEUec`E25zJ~*DofsQdzIa@5^nOXj2T;8O`l--(QyU^$t?TGY^7#&FQ+2SS3B#qK*k3`ye?8jUYSajE5iBbJls75CCc(m3dk{t?- zopcER9{Z?TC)mk~gpi^kbbu>b-+a{m#8-y2^p$ka4n60w;Sc2}HMf<8JUvhCL0B&Btk)T`ctE$*qNW8L$`7!r^9T+>=<=2qaq-;ll2{`{Rg zc5a0ZUI$oG&j-qVOuKa=*v4aY#IsoM+1|c4Z)<}lEDvy;5huB@1RJPquU2U*U-;gu z=En2m+qjBzR#DEJDO`WU)hdd{Vj%^0V*KoyZ|5lzV87&g_j~NCjwv0uQVqXOb*QrQ zy|Qn`hxx(58c70$E;L(X0uZZ72M1!6oeg)(cdKO ze0gDaTz+ohR-#d)NbAH4x{I(21yjwvBQfmpLu$)|m{XolbgF!pmsqJ#D}(ylp6uC> z{bqtcI#hT#HW=wl7>p!38sKsJ`r8}lt-q%Keqy%u(xk=yiIJiUw6|5IvkS+#?JTBl z8H5(Q?l#wzazujH!8o>1xtn8#_w+397*_cy8!pQGP%K(Ga3pAjsaTbbXJlQF_+m+-UpUUent@xM zg%jqLUExj~o^vQ3Gl*>wh=_gOr2*|U64_iXb+-111aH}$TjeajM+I20xw(((>fej-@CIz4S1pi$(#}P7`4({6QS2CaQS4NPENDp>sAqD z$bH4KGzXGffkJ7R>V>)>tC)uax{UsN*dbeNC*v}#8Y#OWYwL4t$ePR?VTyIs!wea+ z5Urmc)X|^`MG~*dS6pGSbU+gPJoq*^a=_>$n4|P^w$sMBBy@f*Z^Jg6?n5?oId6f{ z$LW4M|4m502z0t7g<#Bx%X;9<=)smFolV&(V^(7Cv2-sxbxopQ!)*#ZRhTBpx1)Fc zNm1T%bONzv6@#|dz(w02AH8OXe>kQ#1FMCzO}2J_mST)+ExmBr9cva-@?;wnmWMOk z{3_~EX_xadgJGv&H@zK_8{(x84`}+c?oSBX*Ge3VdfTt&F}yCpFP?CpW+BE^cWY0^ zb&uBN!Ja3UzYHK-CTyA5=L zEMW{l3Usky#ly=7px648W31UNV@K)&Ub&zP1c7%)`{);I4b0Q<)B}3;NMG2JH=X$U zfIW4)4n9ZM`-yRj67I)YSLDK)qfUJ_ij}a#aZN~9EXrh8eZY2&=uY%2N0UFF7<~%M zsB8=erOWZ>Ct_#^tHZ|*q`H;A)5;ycw*IcmVxi8_0Xk}aJA^ath+E;xg!x+As(M#0=)3!NJR6H&9+zd#iP(m0PIW8$ z1Y^VX`>jm`W!=WpF*{ioM?C9`yOR>@0q=u7o>BP-eSHqCgMDj!2anwH?s%i2p+Q7D zzszIf5XJpE)IG4;d_(La-xenmF(tgAxK`Y4sQ}BSJEPs6N_U2vI{8=0C_F?@7<(G; zo$~G=8p+076G;`}>{MQ>t>7cm=zGtfbdDXm6||jUU|?X?CaE?(<6bKDYKeHlz}DA8 zXT={X=yp_R;HfJ9h%?eWvQ!dRgz&Su*JfNt!Wu>|XfU&68iRikRrHRW|ZxzRR^`eIGt zIeiDgVS>IeExKVRWW8-=A=yA`}`)ZkWBrZD`hpWIxBGkh&f#ijr449~m`j6{4jiJ*C!oVA8ZC?$1RM#K(_b zL9TW)kN*Y4%^-qPpMP7d4)o?Nk#>aoYHT(*g)qmRUb?**F@pnNiy6Fv9rEiUqD(^O zzyS?nBrX63BTRYduaG(0VVG2yJRe%o&rVrLjbxTaAFTd8s;<<@Qs>u(<193R8>}2_ zuwp{7;H2a*X7_jryzriZXMg?bTuegABb^87@SsKkr2)0Gyiax8KQWstw^v#ix45EVrcEhr>!NMhprl$InQMzjSFH54x5k9qHc`@9uKQzvL4ihcq{^B zPrVR=o_ic%Y>6&rMN)hTZsI7I<3&`#(nl+3y3ys9A~&^=4?PL&nd8)`OfG#n zwAMN$1&>K++c{^|7<4P=2y(B{jJsQ0a#U;HTo4ZmWZYvI{+s;Td{Yzem%0*k#)vjpB zia;J&>}ICate44SFYY3vEelqStQWFihx%^vQ@Do(sOy7yR2@WNv7Y9I^yL=nZr3mb zXKV5t@=?-Sk|b{XMhA7ZGB@2hqsx}4xwCW!in#C zI@}scZlr3-NFJ@NFaJlhyfcw{k^vvtGl`N9xSo**rDW4S}i zM9{fMPWo%4wYDG~BZ18BD+}h|GQKc-g^{++3MY>}W_uq7jGHx{mwE9fZiPCoxN$+7 zrODGGJrOkcPQUB(FD5aoS4g~7#6NR^ma7-!>mHuJfY5kTe6PpNNKC9GGRiu^L31uG z$7v`*JknQHsYB!Tm_W{a32TM099djW%5e+j0Ve_ct}IM>XLF1Ap+YvcrLV=|CKo6S zb+9Nl3_YdKP6%Cxy@6TxZ>;4&nTneadr z_ES90ydCev)LV!dN=#(*f}|ZORFdvkYBni^aLbUk>BajeWIOcmHP#8S)*2U~QKI%S zyrLmtPqb&TphJ;>yAxri#;{uyk`JJqODDw%(Z=2`1uc}br^V%>j!gS)D*q*f_-qf8&D;W1dJgQMlaH5er zN2U<%Smb7==vE}dDI8K7cKz!vs^73o9f>2sgiTzWcwY|BMYHH5%Vn7#kiw&eItCqa zIkR2~Q}>X=Ar8W|^Ms41Fm8o6IB2_j60eOeBB1Br!boW7JnoeX6Gs)?7rW0^5psc- zjS16yb>dFn>KPOF;imD}e!enuIniFzv}n$m2#gCCv4jM#ArwlzZ$7@9&XkFxZ4n!V zj3dyiwW4Ki2QG{@i>yuZXQizw_OkZI^-3otXC{!(lUpJF33gI60ak;Uqitp74|B6I zgg{b=Iz}WkhCGj1M=hu4#Aw173YxIVbISaoc z-nLZC*6Tgivd5V`K%GxhBsp@SUU60-rfc$=wb>zdJzXS&-5(NRRodFk;Kxk!S(O(a0e7oY=E( zAyS;Ow?6Q&XA+cnkCb{28_1N8H#?J!*$MmIwLq^*T_9-z^&UE@A(z9oGYtFy6EZef LrJugUA?W`A8`#=m diff --git a/src/client/certstack/app/globals.css b/src/client/certstack/app/globals.css index a2dc41e..7ff6b3f 100644 --- a/src/client/certstack/app/globals.css +++ b/src/client/certstack/app/globals.css @@ -1,26 +1,111 @@ -@import "tailwindcss"; +@tailwind base; +@tailwind components; +@tailwind utilities; -:root { - --background: #ffffff; - --foreground: #171717; -} +@layer base { + :root { + /* Soft Periwinkle Blue - Primary */ + --primary-50: 224 100% 97%; + --primary-100: 223 100% 94%; + --primary-200: 223 96% 89%; + --primary-300: 223 95% 82%; + --primary-400: 223 93% 76%; + --primary-500: 223 100% 71%; /* Main brand - #6B8DFF */ + --primary-600: 223 89% 66%; + --primary-700: 223 77% 58%; + + /* Backgrounds - Warmer, creamier */ + --background: 220 40% 99%; /* Soft blue tint */ + --background-app: 220 35% 99%; + --background-card: 0 0% 100%; + --background-surface: 220 25% 98%; + --background-hover: 220 30% 96%; + + /* Pastel Accent Colors - for icon circles */ + --pastel-blue: 223 100% 96%; /* #E8F1FF */ + --pastel-mint: 160 80% 93%; /* #E6F9F5 */ + --pastel-yellow: 48 100% 95%; /* #FFF9E6 */ + --pastel-lavender: 250 100% 97%; /* #F3F0FF */ + --pastel-peach: 25 100% 96%; /* #FFF0E8 */ + --pastel-pink: 340 100% 96%; /* #FFE8F0 */ + + /* Subject Colors - Soft and Desaturated */ + --subject-anatomy: 223 100% 71%; /* Soft blue #6B8DFF */ + --subject-pharma: 165 65% 55%; /* Soft teal #4FD1B8 */ + --subject-biochem: 38 100% 65%; /* Soft orange #FFB84D */ + --subject-pathology: 255 70% 80%; /* Soft purple #B8A0FF */ + --subject-micro: 350 100% 76%; /* Soft coral #FF8FA3 */ + + /* Text Colors - Warmer grays with blue tint */ + --text-primary: 215 25% 24%; /* #2E3A4D */ + --text-secondary: 215 15% 50%; /* #6F7B8A */ + --text-tertiary: 215 12% 66%; /* #A0A8B4 */ + --text-disabled: 220 13% 82%; + + /* Borders - Very subtle */ + --border-light: 220 15% 95%; /* #F0F1F4 */ + --border: 220 15% 93%; + --border-medium: 220 13% 91%; + + /* Semantic Colors - Softer versions */ + --success: 160 65% 50%; /* #4FD1B8 soft teal */ + --success-light: 160 80% 93%; /* #E6F9F5 */ + --warning: 38 92% 65%; /* #FFB84D */ + --warning-light: 48 100% 95%; /* #FFF9E6 */ + --error: 350 85% 65%; /* #FF8FA3 */ + --error-light: 350 100% 96%; /* #FFE8F0 */ + --info: 223 100% 71%; + --info-light: 223 100% 96%; + + /* shadcn/ui compatibility */ + --foreground: 215 25% 24%; + --card: 0 0% 100%; + --card-foreground: 215 25% 24%; + --popover: 0 0% 100%; + --popover-foreground: 215 25% 24%; + --primary: 223 100% 71%; + --primary-foreground: 0 0% 100%; + --secondary: 220 25% 98%; + --secondary-foreground: 215 25% 24%; + --muted: 220 25% 98%; + --muted-foreground: 215 15% 50%; + --accent: 220 25% 98%; + --accent-foreground: 215 25% 24%; + --destructive: 350 85% 65%; + --destructive-foreground: 0 0% 100%; + --border: 220 15% 93%; + --input: 220 15% 93%; + --ring: 223 100% 71%; + --radius: 1.25rem; /* 20px - rounder than before */ -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); -} + /* Chart colors - desaturated */ + --chart-1: 350 100% 76%; /* Coral */ + --chart-2: 223 100% 71%; /* Blue */ + --chart-3: 165 65% 55%; /* Teal */ + --chart-4: 255 70% 80%; /* Purple */ + --chart-5: 38 100% 65%; /* Amber */ -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; + /* Sidebar */ + --sidebar-background: 0 0% 100%; + --sidebar-foreground: 215 25% 24%; + --sidebar-primary: 223 100% 71%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 223 100% 96%; + --sidebar-accent-foreground: 223 89% 66%; + --sidebar-border: 220 15% 95%; + --sidebar-ring: 223 100% 71%; + + /* Additional tokens */ + --surface: 220 25% 98%; + --surface-elevated: 0 0% 100%; + } + + * { + @apply border-border; } -} -body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; + body { + @apply bg-background text-foreground antialiased; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + } } diff --git a/src/client/certstack/app/help/page.tsx b/src/client/certstack/app/help/page.tsx new file mode 100644 index 0000000..598283f --- /dev/null +++ b/src/client/certstack/app/help/page.tsx @@ -0,0 +1,106 @@ +"use client" + +import { AppLayout } from "@/components/app-layout" +import { AppHeader } from "@/components/app-header" +import { Search, BookOpen, MessageCircle, Mail, ChevronRight } from "lucide-react" +import { useState } from "react" + +const faqs = [ + { + question: "How do I start a practice session?", + answer: "Navigate to the Practice page from the sidebar, select your subject and topic, then click 'Start Practice' to begin answering questions.", + }, + { + question: "Can I retake an exam?", + answer: "Yes, you can retake any available exam as many times as you want. Your best score will be recorded in your profile.", + }, + { + question: "How is my score calculated?", + answer: "Your score is calculated as the percentage of correct answers. Each question carries equal weight unless specified otherwise.", + }, + { + question: "Can I download study materials for offline use?", + answer: "Yes, you can download most study materials from the Materials page. Click the download icon on any resource card.", + }, + { + question: "How do I reset my progress?", + answer: "Go to Settings > Study Preferences and click 'Reset Progress'. Note that this action is irreversible.", + }, +] + +export default function HelpPage() { + const [openFaq, setOpenFaq] = useState(null) + + return ( + + +
+
+ {/* Search */} +
+ + +
+ + {/* Quick Actions */} +
+
+
+ +
+ Documentation + Browse guides +
+
+
+ +
+ Live Chat + Talk to support +
+
+
+ +
+ Email Us + Get in touch +
+
+ + {/* FAQs */} +
+
+

Frequently Asked Questions

+
+
+
+ {faqs.map((faq, idx) => ( +
+ + {openFaq === idx && ( +

+ {faq.answer} +

+ )} +
+ ))} +
+
+
+
+
+
+ ) +} diff --git a/src/client/certstack/app/layout.tsx b/src/client/certstack/app/layout.tsx index f7fa87e..9b7d1e8 100644 --- a/src/client/certstack/app/layout.tsx +++ b/src/client/certstack/app/layout.tsx @@ -1,34 +1,25 @@ -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import "./globals.css"; +import React from "react" +import type { Metadata } from 'next' +import { Geist, Geist_Mono } from 'next/font/google' -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); +import './globals.css' -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); +const _geist = Geist({ subsets: ['latin'] }) +const _geistMono = Geist_Mono({ subsets: ['latin'] }) export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; + title: 'ExamPrep - Ace Your Exams With Confidence', + description: 'Practice with thousands of questions, track your progress, and master every subject before exam day.', +} export default function RootLayout({ children, }: Readonly<{ - children: React.ReactNode; + children: React.ReactNode }>) { return ( - - {children} - + {children} - ); + ) } diff --git a/src/client/certstack/app/login/page.tsx b/src/client/certstack/app/login/page.tsx new file mode 100644 index 0000000..024666d --- /dev/null +++ b/src/client/certstack/app/login/page.tsx @@ -0,0 +1,186 @@ +"use client" + +import { useState } from "react" +import Link from "next/link" +import { BookOpen, Eye, EyeOff, Mail, Lock } from "lucide-react" + +export default function LoginPage() { + const [showPassword, setShowPassword] = useState(false) + + return ( +
+ {/* Left Panel - Branding */} +
+
+
+ +
+ ExamPrep +
+ +
+

+ Ace your exams with +
+ confidence +

+

+ Practice with thousands of questions, track your progress, and master every subject before exam day. +

+
+ +
+
+

50K+

+

Questions

+
+
+

12K+

+

Students

+
+
+

95%

+

Pass Rate

+
+
+
+ + {/* Right Panel - Form */} +
+
+ {/* Mobile Logo */} +
+
+ +
+ ExamPrep +
+ +

Welcome back

+

+ Sign in to continue your study journey +

+ +
e.preventDefault()}> + {/* Email */} +
+ +
+ + +
+
+ + {/* Password */} +
+
+ + +
+
+ + + +
+
+ + {/* Remember Me */} +
+ + +
+ + {/* Submit */} + + + {/* Divider */} +
+
+ or continue with +
+
+ + {/* Social Buttons */} +
+ + +
+ + +

+ {"Don't have an account? "} + + Sign up for free + +

+
+
+
+ ) +} diff --git a/src/client/certstack/app/materials/page.tsx b/src/client/certstack/app/materials/page.tsx new file mode 100644 index 0000000..ef2b0ea --- /dev/null +++ b/src/client/certstack/app/materials/page.tsx @@ -0,0 +1,179 @@ +"use client" + +import { AppLayout } from "@/components/app-layout" +import { AppHeader } from "@/components/app-header" +import { BookOpen, Download, FileText, Video, Headphones, Search, Filter, ChevronDown } from "lucide-react" +import { useState } from "react" +import { cn } from "@/lib/utils" + +const categories = ["All", "Textbooks", "Videos", "Audio", "Notes"] + +const materials = [ + { + id: 1, + title: "Gray's Anatomy - Chapter Review Notes", + type: "notes" as const, + subject: "Anatomy", + size: "2.4 MB", + date: "Jan 15, 2026", + downloads: 1240, + }, + { + id: 2, + title: "Pharmacology Lecture Series - Drug Interactions", + type: "video" as const, + subject: "Pharmacology", + size: "850 MB", + date: "Jan 22, 2026", + downloads: 890, + }, + { + id: 3, + title: "Biochemistry Metabolic Pathways Quick Guide", + type: "notes" as const, + subject: "Biochemistry", + size: "1.8 MB", + date: "Feb 1, 2026", + downloads: 2100, + }, + { + id: 4, + title: "Pathology Audio Lectures - Inflammation", + type: "audio" as const, + subject: "Pathology", + size: "120 MB", + date: "Jan 28, 2026", + downloads: 560, + }, + { + id: 5, + title: "Robbins Basic Pathology - Condensed Notes", + type: "textbook" as const, + subject: "Pathology", + size: "15 MB", + date: "Dec 20, 2025", + downloads: 3200, + }, + { + id: 6, + title: "Microbiology Lab Techniques Video", + type: "video" as const, + subject: "Microbiology", + size: "420 MB", + date: "Feb 3, 2026", + downloads: 710, + }, +] + +const typeConfig = { + notes: { icon: FileText, color: "#4A7FFF", bg: "#EFF6FF" }, + video: { icon: Video, color: "#10B981", bg: "#ECFDF5" }, + audio: { icon: Headphones, color: "#A78BFA", bg: "#F3F0FF" }, + textbook: { icon: BookOpen, color: "#F59E0B", bg: "#FEF3C7" }, +} + +export default function MaterialsPage() { + const [activeCategory, setActiveCategory] = useState("All") + const [searchQuery, setSearchQuery] = useState("") + + const filtered = materials.filter((m) => { + const matchesCategory = + activeCategory === "All" || + (activeCategory === "Textbooks" && m.type === "textbook") || + (activeCategory === "Videos" && m.type === "video") || + (activeCategory === "Audio" && m.type === "audio") || + (activeCategory === "Notes" && m.type === "notes") + const matchesSearch = + m.title.toLowerCase().includes(searchQuery.toLowerCase()) || + m.subject.toLowerCase().includes(searchQuery.toLowerCase()) + return matchesCategory && matchesSearch + }) + + return ( + + +
+
+ {/* Filters */} +
+
+ {categories.map((cat) => ( + + ))} +
+
+ + setSearchQuery(e.target.value)} + className="h-10 w-64 rounded-xl border border-transparent bg-[hsl(var(--surface))] pl-10 pr-4 text-sm text-[hsl(var(--text-primary))] placeholder:text-[hsl(var(--text-tertiary))] transition-all duration-200 focus:border-[hsl(var(--border))] focus:bg-[hsl(var(--surface-elevated))] focus:outline-none focus:ring-2 focus:ring-[#DBEAFE]" + /> +
+
+ + {/* Materials Grid */} +
+ {filtered.map((material) => { + const config = typeConfig[material.type] + const TypeIcon = config.icon + return ( +
+
+
+ +
+
+

+ {material.title} +

+

{material.subject}

+
+
+
+
+ {material.size} + {material.downloads.toLocaleString()} downloads +
+ +
+
+ ) + })} +
+ + {filtered.length === 0 && ( +
+ +

No materials found

+

Try adjusting your search or filter

+
+ )} +
+
+
+ ) +} diff --git a/src/client/certstack/app/page.tsx b/src/client/certstack/app/page.tsx index 295f8fd..2c1db70 100644 --- a/src/client/certstack/app/page.tsx +++ b/src/client/certstack/app/page.tsx @@ -1,65 +1,5 @@ -import Image from "next/image"; +import { redirect } from "next/navigation" -export default function Home() { - return ( -
-
- Next.js logo -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

-
- -
-
- ); +export default function Page() { + redirect("/dashboard") } diff --git a/src/client/certstack/app/practice/page.tsx b/src/client/certstack/app/practice/page.tsx new file mode 100644 index 0000000..181880b --- /dev/null +++ b/src/client/certstack/app/practice/page.tsx @@ -0,0 +1,148 @@ +"use client" + +import { useState } from "react" +import { AppLayout } from "@/components/app-layout" +import { AppHeader } from "@/components/app-header" +import { QuestionCard } from "@/components/practice/question-card" +import { QuestionNavigator } from "@/components/practice/question-navigator" +import { Flag, ChevronLeft, ChevronRight } from "lucide-react" + +const questions = [ + { + id: 1, + question: + "Which of the following structures passes through the foramen ovale of the sphenoid bone?", + options: [ + { id: "a", text: "Maxillary nerve (V2)" }, + { id: "b", text: "Mandibular nerve (V3)" }, + { id: "c", text: "Middle meningeal artery" }, + { id: "d", text: "Ophthalmic nerve (V1)" }, + ], + correctAnswer: "b", + explanation: + "The mandibular nerve (V3) is the third branch of the trigeminal nerve that passes through the foramen ovale. The maxillary nerve passes through the foramen rotundum, the middle meningeal artery passes through the foramen spinosum, and the ophthalmic nerve passes through the superior orbital fissure.", + }, + { + id: 2, + question: + "A patient presents with inability to abduct the arm beyond 15 degrees. Which muscle is most likely affected?", + options: [ + { id: "a", text: "Supraspinatus" }, + { id: "b", text: "Deltoid" }, + { id: "c", text: "Infraspinatus" }, + { id: "d", text: "Teres minor" }, + ], + correctAnswer: "a", + explanation: + "The supraspinatus muscle initiates abduction of the arm (first 15 degrees). Damage to this muscle or the suprascapular nerve would impair the initial phase of arm abduction. The deltoid takes over abduction from 15-90 degrees.", + }, + { + id: 3, + question: "Which enzyme is the rate-limiting step in cholesterol synthesis?", + options: [ + { id: "a", text: "Acetyl-CoA carboxylase" }, + { id: "b", text: "HMG-CoA reductase" }, + { id: "c", text: "HMG-CoA synthase" }, + { id: "d", text: "Squalene synthase" }, + ], + correctAnswer: "b", + explanation: + "HMG-CoA reductase is the rate-limiting enzyme in the mevalonate pathway for cholesterol synthesis. It converts HMG-CoA to mevalonate. Statins work by inhibiting this enzyme, reducing cholesterol synthesis.", + }, +] + +export default function PracticePage() { + const [currentQuestion, setCurrentQuestion] = useState(1) + const [answeredQuestions, setAnsweredQuestions] = useState([]) + const [flaggedQuestions, setFlaggedQuestions] = useState([2]) + + const currentQ = questions[currentQuestion - 1] + + function handlePrev() { + if (currentQuestion > 1) setCurrentQuestion(currentQuestion - 1) + } + + function handleNext() { + if (currentQuestion < questions.length) { + if (!answeredQuestions.includes(currentQuestion)) { + setAnsweredQuestions([...answeredQuestions, currentQuestion]) + } + setCurrentQuestion(currentQuestion + 1) + } + } + + function handleFlag() { + if (flaggedQuestions.includes(currentQuestion)) { + setFlaggedQuestions(flaggedQuestions.filter((q) => q !== currentQuestion)) + } else { + setFlaggedQuestions([...flaggedQuestions, currentQuestion]) + } + } + + return ( + + +
+
+
+ {/* Main Question Area */} +
+ + + {/* Navigation Buttons */} +
+ + + + + +
+
+ + {/* Sidebar Navigator */} +
+ +
+
+
+
+
+ ) +} diff --git a/src/client/certstack/app/profile/page.tsx b/src/client/certstack/app/profile/page.tsx new file mode 100644 index 0000000..2f7c4a7 --- /dev/null +++ b/src/client/certstack/app/profile/page.tsx @@ -0,0 +1,280 @@ +"use client" + +import { useState } from "react" +import { AppLayout } from "@/components/app-layout" +import { AppHeader } from "@/components/app-header" +import { Camera, Mail, Phone, MapPin, GraduationCap, Calendar } from "lucide-react" +import { cn } from "@/lib/utils" + +const tabs = [ + { label: "Profile", value: "profile" }, + { label: "Settings", value: "settings" }, + { label: "Notifications", value: "notifications" }, +] + +export default function ProfilePage() { + const [activeTab, setActiveTab] = useState("profile") + + return ( + + +
+
+ {/* Profile Header Card */} +
+
+ {/* Avatar */} +
+
+ JD +
+ +
+ + {/* Info */} +
+

John Doe

+

Medical Student - Year 3

+
+ + john.doe@university.edu + + + New York, USA + + + Joined Sep 2024 + +
+
+ + {/* Edit Button */} + +
+
+ + {/* Tabs */} +
+ {tabs.map((tab) => ( + + ))} +
+ + {/* Tab Content */} + {activeTab === "profile" && } + {activeTab === "settings" && } + {activeTab === "notifications" && } +
+
+
+ ) +} + +function ProfileTab() { + return ( +
+ {/* Personal Info */} +
+

Personal Information

+
+ + + + +
+
+ + {/* Academic Info */} +
+

Academic Information

+
+ + + + +
+
+ + {/* Study Stats */} +
+

Study Statistics

+
+ + + + +
+
+
+ ) +} + +function SettingsTab() { + return ( +
+ {/* Account Settings */} +
+

Account Settings

+
+ + +
+ + +
+
+
+ + +
+
+ + {/* Study Preferences */} +
+

Study Preferences

+
+ + + + +
+
+ + {/* Danger Zone */} +
+

Danger Zone

+

+ These actions are irreversible. Please proceed with caution. +

+
+ + +
+
+
+ ) +} + +function NotificationsTab() { + return ( +
+
+

Notification Preferences

+
+ + + + + +
+
+
+ ) +} + +function FormField({ label, value }: { label: string; value: string }) { + return ( +
+ {label} + {value} +
+ ) +} + +function StatItem({ label, value }: { label: string; value: string }) { + return ( +
+

{value}

+

{label}

+
+ ) +} + +function InputField({ + label, + type, + defaultValue, +}: { + label: string + type: string + defaultValue: string +}) { + return ( +
+ + +
+ ) +} + +function ToggleRow({ + label, + description, + defaultChecked, +}: { + label: string + description: string + defaultChecked: boolean +}) { + const [checked, setChecked] = useState(defaultChecked) + return ( +
+
+

{label}

+

{description}

+
+ +
+ ) +} diff --git a/src/client/certstack/app/settings/page.tsx b/src/client/certstack/app/settings/page.tsx new file mode 100644 index 0000000..70c23b3 --- /dev/null +++ b/src/client/certstack/app/settings/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation" + +export default function SettingsPage() { + redirect("/profile") +} diff --git a/src/client/certstack/app/signup/page.tsx b/src/client/certstack/app/signup/page.tsx new file mode 100644 index 0000000..ac3d84c --- /dev/null +++ b/src/client/certstack/app/signup/page.tsx @@ -0,0 +1,243 @@ +"use client" + +import { useState } from "react" +import Link from "next/link" +import { BookOpen, Eye, EyeOff, Mail, Lock, User, GraduationCap } from "lucide-react" + +export default function SignupPage() { + const [showPassword, setShowPassword] = useState(false) + + return ( +
+ {/* Left Panel - Branding */} +
+
+
+ +
+ ExamPrep +
+ +
+

+ Start your journey +
+ to success +

+

+ Join thousands of students who use ExamPrep to prepare for their exams and achieve their academic goals. +

+
+
+
+ + + +
+ Access to 50,000+ practice questions +
+
+
+ + + +
+ Detailed performance analytics +
+
+
+ + + +
+ Personalized study plans +
+
+
+ +

Trusted by 12,000+ students worldwide

+
+ + {/* Right Panel - Form */} +
+
+ {/* Mobile Logo */} +
+
+ +
+ ExamPrep +
+ +

Create your account

+

+ Get started with your free account today +

+ +
e.preventDefault()}> + {/* Name Row */} +
+
+ +
+ + +
+
+
+ + +
+
+ + {/* Email */} +
+ +
+ + +
+
+ + {/* Institution */} +
+ +
+ + +
+
+ + {/* Password */} +
+ +
+ + + +
+

+ Must be at least 8 characters with a number and special character +

+
+ + {/* Terms */} +
+ + +
+ + {/* Submit */} + + + {/* Divider */} +
+
+ or sign up with +
+
+ + {/* Social Buttons */} +
+ + +
+ + +

+ Already have an account?{" "} + + Sign in + +

+
+
+
+ ) +} diff --git a/src/client/certstack/components.json b/src/client/certstack/components.json new file mode 100644 index 0000000..13f24bf --- /dev/null +++ b/src/client/certstack/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/src/client/certstack/components/app-header.tsx b/src/client/certstack/components/app-header.tsx new file mode 100644 index 0000000..09545fa --- /dev/null +++ b/src/client/certstack/components/app-header.tsx @@ -0,0 +1,53 @@ +"use client" + +import { Bell, Search } from "lucide-react" + +interface AppHeaderProps { + title: string + subtitle?: string +} + +export function AppHeader({ title, subtitle }: AppHeaderProps) { + return ( +
+
+

{title}

+ {subtitle && ( +

{subtitle}

+ )} +
+ +
+ {/* Search */} +
+ + +
+ + {/* Notifications */} + + + {/* Avatar */} +
+
+ JD +
+
+

John Doe

+

Student

+
+
+
+
+ ) +} diff --git a/src/client/certstack/components/app-layout.tsx b/src/client/certstack/components/app-layout.tsx new file mode 100644 index 0000000..17394bb --- /dev/null +++ b/src/client/certstack/components/app-layout.tsx @@ -0,0 +1,16 @@ +"use client" + +import React from "react" + +import { AppSidebar } from "@/components/app-sidebar" + +export function AppLayout({ children }: { children: React.ReactNode }) { + return ( +
+ +
+ {children} +
+
+ ) +} diff --git a/src/client/certstack/components/app-sidebar.tsx b/src/client/certstack/components/app-sidebar.tsx new file mode 100644 index 0000000..790644f --- /dev/null +++ b/src/client/certstack/components/app-sidebar.tsx @@ -0,0 +1,99 @@ +"use client" + +import { usePathname } from "next/navigation" +import Link from "next/link" +import { + LayoutDashboard, + FileText, + ClipboardList, + User, + Settings, + BookOpen, + LogOut, + HelpCircle, +} from "lucide-react" +import { cn } from "@/lib/utils" + +const mainNavItems = [ + { label: "Dashboard", href: "/dashboard", icon: LayoutDashboard }, + { label: "Practice", href: "/practice", icon: FileText }, + { label: "Exams", href: "/exams", icon: ClipboardList }, + { label: "Study Materials", href: "/materials", icon: BookOpen }, +] + +const bottomNavItems = [ + { label: "Profile", href: "/profile", icon: User }, + { label: "Settings", href: "/settings", icon: Settings }, + { label: "Help", href: "/help", icon: HelpCircle }, +] + +export function AppSidebar() { + const pathname = usePathname() + + return ( + + ) +} diff --git a/src/client/certstack/components/dashboard/progress-chart.tsx b/src/client/certstack/components/dashboard/progress-chart.tsx new file mode 100644 index 0000000..c6346d7 --- /dev/null +++ b/src/client/certstack/components/dashboard/progress-chart.tsx @@ -0,0 +1,111 @@ +"use client" + +import { + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, +} from "recharts" + +const data = [ + { day: "Mon", score: 65 }, + { day: "Tue", score: 72 }, + { day: "Wed", score: 68 }, + { day: "Thu", score: 78 }, + { day: "Fri", score: 82 }, + { day: "Sat", score: 75 }, + { day: "Sun", score: 85 }, +] + +export function ProgressChart() { + return ( +
+
+
+

Weekly Progress

+

Your score trend this week

+
+ {/* Tab pills - grouped with soft background */} +
+ + + +
+
+
+ + + + + + + + + {/* Softer grid - no dashes, no vertical lines */} + + + + + {/* Thicker line with dots */} + + + +
+
+ ) +} diff --git a/src/client/certstack/components/dashboard/recent-activity.tsx b/src/client/certstack/components/dashboard/recent-activity.tsx new file mode 100644 index 0000000..82e5a4e --- /dev/null +++ b/src/client/certstack/components/dashboard/recent-activity.tsx @@ -0,0 +1,122 @@ +"use client" + +import { CheckCircle, Clock, XCircle } from "lucide-react" + +const activities = [ + { + id: 1, + title: "Anatomy - Chapter 5 Quiz", + status: "completed" as const, + score: "92%", + time: "2 hours ago", + questions: 25, + }, + { + id: 2, + title: "Pharmacology Practice Set", + status: "in-progress" as const, + score: "15/30", + time: "5 hours ago", + questions: 30, + }, + { + id: 3, + title: "Biochemistry Final Mock", + status: "completed" as const, + score: "78%", + time: "1 day ago", + questions: 50, + }, + { + id: 4, + title: "Pathology - Unit 3 Review", + status: "failed" as const, + score: "45%", + time: "2 days ago", + questions: 40, + }, + { + id: 5, + title: "Microbiology Quick Test", + status: "completed" as const, + score: "88%", + time: "3 days ago", + questions: 20, + }, +] + +const statusConfig = { + completed: { + icon: CheckCircle, + color: "hsl(var(--success))", + bg: "hsl(var(--success-light))", + label: "Completed", + }, + "in-progress": { + icon: Clock, + color: "hsl(var(--warning))", + bg: "hsl(var(--warning-light))", + label: "In Progress", + }, + failed: { + icon: XCircle, + color: "hsl(var(--error))", + bg: "hsl(var(--error-light))", + label: "Needs Review", + }, +} + +export function RecentActivity() { + return ( +
+
+

Recent Activity

+ +
+
+ {activities.map((activity) => { + const config = statusConfig[activity.status] + const StatusIcon = config.icon + return ( +
+ {/* Icon circle - larger with better styling */} +
+ +
+ + {/* Content */} +
+

+ {activity.title} +

+

+ {activity.questions} questions +

+
+ + {/* Score - larger and bolder */} +
+

+ {activity.score} +

+

{activity.time}

+
+
+ ) + })} +
+
+ ) +} diff --git a/src/client/certstack/components/dashboard/stat-cards.tsx b/src/client/certstack/components/dashboard/stat-cards.tsx new file mode 100644 index 0000000..1683978 --- /dev/null +++ b/src/client/certstack/components/dashboard/stat-cards.tsx @@ -0,0 +1,109 @@ +"use client" + +import { BookOpen, CheckCircle, Clock, TrendingUp } from "lucide-react" + +const stats = [ + { + label: "Total Questions", + value: "2,847", + change: "+12%", + changeType: "positive" as const, + icon: BookOpen, + iconBg: "hsl(var(--pastel-blue))", + iconColor: "hsl(var(--primary-500))", + blobFrom: "hsl(var(--pastel-blue))", + blobTo: "transparent", + }, + { + label: "Completed", + value: "1,234", + change: "+8%", + changeType: "positive" as const, + icon: CheckCircle, + iconBg: "hsl(var(--pastel-mint))", + iconColor: "hsl(var(--success))", + blobFrom: "hsl(var(--pastel-mint))", + blobTo: "transparent", + }, + { + label: "Avg. Score", + value: "78%", + change: "+5%", + changeType: "positive" as const, + icon: TrendingUp, + iconBg: "hsl(var(--pastel-yellow))", + iconColor: "hsl(var(--warning))", + blobFrom: "hsl(var(--pastel-yellow))", + blobTo: "transparent", + }, + { + label: "Study Time", + value: "45h", + change: "This month", + changeType: "neutral" as const, + icon: Clock, + iconBg: "hsl(var(--pastel-lavender))", + iconColor: "hsl(var(--subject-pathology))", + blobFrom: "hsl(var(--pastel-lavender))", + blobTo: "transparent", + }, +] + +export function StatCards() { + return ( +
+ {stats.map((stat) => ( +
+ {/* Decorative gradient blob */} +
+ +
+
+ {/* Icon circle - larger and softer */} +
+ +
+ + {/* Change badge - softer style */} + {stat.changeType === "positive" && ( + + {stat.change} + + )} + {stat.changeType === "neutral" && ( + + {stat.change} + + )} +
+ + {/* Value and label - improved hierarchy */} +
+

+ {stat.value} +

+

+ {stat.label} +

+
+
+
+ ))} +
+ ) +} diff --git a/src/client/certstack/components/dashboard/subject-progress.tsx b/src/client/certstack/components/dashboard/subject-progress.tsx new file mode 100644 index 0000000..f85d222 --- /dev/null +++ b/src/client/certstack/components/dashboard/subject-progress.tsx @@ -0,0 +1,93 @@ +"use client" + +const subjects = [ + { + name: "Anatomy", + progress: 85, + total: 500, + completed: 425, + colorFrom: "hsl(var(--subject-anatomy))", + colorTo: "hsl(223 100% 76%)", + }, + { + name: "Pharmacology", + progress: 62, + total: 400, + completed: 248, + colorFrom: "hsl(var(--subject-pharma))", + colorTo: "hsl(165 75% 60%)", + }, + { + name: "Biochemistry", + progress: 45, + total: 350, + completed: 157, + colorFrom: "hsl(var(--subject-biochem))", + colorTo: "hsl(38 100% 70%)", + }, + { + name: "Pathology", + progress: 30, + total: 450, + completed: 135, + colorFrom: "hsl(var(--subject-pathology))", + colorTo: "hsl(255 80% 85%)", + }, + { + name: "Microbiology", + progress: 72, + total: 300, + completed: 216, + colorFrom: "hsl(var(--subject-micro))", + colorTo: "hsl(350 100% 80%)", + }, +] + +export function SubjectProgress() { + return ( +
+
+

Subject Progress

+ +
+
+ {subjects.map((subject) => ( +
+ {/* Header row */} +
+ + {subject.name} + + + {subject.completed}/{subject.total} + +
+ + {/* Progress bar - thicker with gradient */} +
+
+
+ + {/* Percentage - larger and colored */} +
+ + {subject.progress}% + +
+
+ ))} +
+
+ ) +} diff --git a/src/client/certstack/components/dashboard/upcoming-exams.tsx b/src/client/certstack/components/dashboard/upcoming-exams.tsx new file mode 100644 index 0000000..2f8c4db --- /dev/null +++ b/src/client/certstack/components/dashboard/upcoming-exams.tsx @@ -0,0 +1,91 @@ +"use client" + +import { Calendar, ArrowRight } from "lucide-react" + +const exams = [ + { + id: 1, + name: "Anatomy Midterm", + date: "Feb 15, 2026", + daysLeft: 8, + questions: 100, + duration: "2 hours", + }, + { + id: 2, + name: "Pharmacology Final", + date: "Feb 22, 2026", + daysLeft: 15, + questions: 150, + duration: "3 hours", + }, + { + id: 3, + name: "Biochemistry Quiz", + date: "Mar 1, 2026", + daysLeft: 22, + questions: 50, + duration: "1 hour", + }, +] + +function getDaysLeftColor(days: number) { + if (days <= 7) return { bg: "hsl(var(--error-light))", text: "hsl(var(--error))" } + if (days <= 14) return { bg: "hsl(var(--warning-light))", text: "hsl(var(--warning))" } + return { bg: "hsl(var(--success-light))", text: "hsl(var(--success))" } +} + +export function UpcomingExams() { + return ( +
+
+

Upcoming Exams

+ +
+
+ {exams.map((exam) => { + const daysColor = getDaysLeftColor(exam.daysLeft) + return ( +
+ {/* Calendar icon */} +
+ +
+ + {/* Content */} +
+

{exam.name}

+

+ {exam.questions} questions / {exam.duration} +

+
+ + {/* Days left badge and arrow */} +
+ + {exam.daysLeft}d left + + +
+
+ ) + })} +
+
+ ) +} diff --git a/src/client/certstack/components/practice/question-card.tsx b/src/client/certstack/components/practice/question-card.tsx new file mode 100644 index 0000000..6bd52aa --- /dev/null +++ b/src/client/certstack/components/practice/question-card.tsx @@ -0,0 +1,179 @@ +"use client" + +import { useState } from "react" +import { cn } from "@/lib/utils" +import { CheckCircle, XCircle, Lightbulb } from "lucide-react" + +interface Option { + id: string + text: string +} + +interface QuestionCardProps { + questionNumber: number + totalQuestions: number + question: string + options: Option[] + correctAnswer: string + explanation: string +} + +export function QuestionCard({ + questionNumber, + totalQuestions, + question, + options, + correctAnswer, + explanation, +}: QuestionCardProps) { + const [selectedAnswer, setSelectedAnswer] = useState(null) + const [showResult, setShowResult] = useState(false) + const [showExplanation, setShowExplanation] = useState(false) + + const isCorrect = selectedAnswer === correctAnswer + + function handleSelect(optionId: string) { + if (showResult) return + setSelectedAnswer(optionId) + } + + function handleSubmit() { + if (!selectedAnswer) return + setShowResult(true) + } + + function handleShowExplanation() { + setShowExplanation(!showExplanation) + } + + function getOptionStyle(optionId: string) { + if (!showResult) { + return optionId === selectedAnswer + ? "border-[#4A7FFF] bg-[#EFF6FF] ring-2 ring-[#DBEAFE]" + : "border-[hsl(var(--border))] hover:border-[#BFDBFE] hover:bg-[hsl(var(--surface))]" + } + if (optionId === correctAnswer) { + return "border-[#10B981] bg-[#ECFDF5]" + } + if (optionId === selectedAnswer && !isCorrect) { + return "border-[#EF4444] bg-[#FEF2F2]" + } + return "border-[hsl(var(--border))] opacity-50" + } + + return ( +
+ {/* Progress */} +
+ + Question {questionNumber} of {totalQuestions} + +
+
+
+
+ + {Math.round((questionNumber / totalQuestions) * 100)}% + +
+
+ + {/* Question */} +

+ {question} +

+ + {/* Options */} +
+ {options.map((option) => ( + + ))} +
+ + {/* Actions */} +
+ {!showResult ? ( + + ) : ( + <> + + + )} +
+ + {/* Result Banner */} + {showResult && ( +
+ {isCorrect ? ( + <> + + Correct! Well done. + + ) : ( + <> + + + {"Incorrect. The correct answer is "}{options.find(o => o.id === correctAnswer)?.text}. + + + )} +
+ )} + + {/* Explanation */} + {showExplanation && ( +
+
+ + Explanation +
+

{explanation}

+
+ )} +
+ ) +} diff --git a/src/client/certstack/components/practice/question-navigator.tsx b/src/client/certstack/components/practice/question-navigator.tsx new file mode 100644 index 0000000..4e5a188 --- /dev/null +++ b/src/client/certstack/components/practice/question-navigator.tsx @@ -0,0 +1,92 @@ +"use client" + +import { cn } from "@/lib/utils" +import { Clock, Flag } from "lucide-react" + +interface QuestionNavigatorProps { + totalQuestions: number + currentQuestion: number + answeredQuestions: number[] + flaggedQuestions: number[] + onSelect: (index: number) => void +} + +export function QuestionNavigator({ + totalQuestions, + currentQuestion, + answeredQuestions, + flaggedQuestions, + onSelect, +}: QuestionNavigatorProps) { + return ( +
+ {/* Timer */} +
+ +
+

Time Remaining

+

45:32

+
+
+ + {/* Question Grid */} +

Questions

+
+ {Array.from({ length: totalQuestions }, (_, i) => { + const num = i + 1 + const isCurrent = num === currentQuestion + const isAnswered = answeredQuestions.includes(num) + const isFlagged = flaggedQuestions.includes(num) + + return ( + + ) + })} +
+ + {/* Legend */} +
+
+
+ Current +
+
+
+ Answered +
+
+
+ Unanswered +
+
+ + {/* Stats */} +
+
+ Answered + {answeredQuestions.length}/{totalQuestions} +
+
+ Flagged + {flaggedQuestions.length} +
+
+
+ ) +} diff --git a/src/client/certstack/components/theme-provider.tsx b/src/client/certstack/components/theme-provider.tsx new file mode 100644 index 0000000..55c2f6e --- /dev/null +++ b/src/client/certstack/components/theme-provider.tsx @@ -0,0 +1,11 @@ +'use client' + +import * as React from 'react' +import { + ThemeProvider as NextThemesProvider, + type ThemeProviderProps, +} from 'next-themes' + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children} +} diff --git a/src/client/certstack/components/ui/accordion.tsx b/src/client/certstack/components/ui/accordion.tsx new file mode 100644 index 0000000..b69428b --- /dev/null +++ b/src/client/certstack/components/ui/accordion.tsx @@ -0,0 +1,58 @@ +'use client' + +import * as React from 'react' +import * as AccordionPrimitive from '@radix-ui/react-accordion' +import { ChevronDown } from 'lucide-react' + +import { cn } from '@/lib/utils' + +const Accordion = AccordionPrimitive.Root + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AccordionItem.displayName = 'AccordionItem' + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180', + className, + )} + {...props} + > + {children} + + + +)) +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)) + +AccordionContent.displayName = AccordionPrimitive.Content.displayName + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/src/client/certstack/components/ui/alert-dialog.tsx b/src/client/certstack/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..e58f90a --- /dev/null +++ b/src/client/certstack/components/ui/alert-dialog.tsx @@ -0,0 +1,141 @@ +'use client' + +import * as React from 'react' +import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' + +import { cn } from '@/lib/utils' +import { buttonVariants } from '@/components/ui/button' + +const AlertDialog = AlertDialogPrimitive.Root + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger + +const AlertDialogPortal = AlertDialogPrimitive.Portal + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)) +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogHeader.displayName = 'AlertDialogHeader' + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogFooter.displayName = 'AlertDialogFooter' + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/src/client/certstack/components/ui/alert.tsx b/src/client/certstack/components/ui/alert.tsx new file mode 100644 index 0000000..2b2ced8 --- /dev/null +++ b/src/client/certstack/components/ui/alert.tsx @@ -0,0 +1,59 @@ +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const alertVariants = cva( + 'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground', + { + variants: { + variant: { + default: 'bg-background text-foreground', + destructive: + 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)) +Alert.displayName = 'Alert' + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertTitle.displayName = 'AlertTitle' + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertDescription.displayName = 'AlertDescription' + +export { Alert, AlertTitle, AlertDescription } diff --git a/src/client/certstack/components/ui/aspect-ratio.tsx b/src/client/certstack/components/ui/aspect-ratio.tsx new file mode 100644 index 0000000..794c6f4 --- /dev/null +++ b/src/client/certstack/components/ui/aspect-ratio.tsx @@ -0,0 +1,7 @@ +'use client' + +import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio' + +const AspectRatio = AspectRatioPrimitive.Root + +export { AspectRatio } diff --git a/src/client/certstack/components/ui/avatar.tsx b/src/client/certstack/components/ui/avatar.tsx new file mode 100644 index 0000000..77fde46 --- /dev/null +++ b/src/client/certstack/components/ui/avatar.tsx @@ -0,0 +1,50 @@ +'use client' + +import * as React from 'react' +import * as AvatarPrimitive from '@radix-ui/react-avatar' + +import { cn } from '@/lib/utils' + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/src/client/certstack/components/ui/badge.tsx b/src/client/certstack/components/ui/badge.tsx new file mode 100644 index 0000000..eb4ccad --- /dev/null +++ b/src/client/certstack/components/ui/badge.tsx @@ -0,0 +1,37 @@ +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const badgeVariants = cva( + 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', + { + variants: { + variant: { + default: + 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80', + secondary: + 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', + destructive: + 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80', + outline: 'text-foreground', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) + +export interface BadgeProps + extends + React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/src/client/certstack/components/ui/breadcrumb.tsx b/src/client/certstack/components/ui/breadcrumb.tsx new file mode 100644 index 0000000..d731202 --- /dev/null +++ b/src/client/certstack/components/ui/breadcrumb.tsx @@ -0,0 +1,115 @@ +import * as React from 'react' +import { Slot } from '@radix-ui/react-slot' +import { ChevronRight, MoreHorizontal } from 'lucide-react' + +import { cn } from '@/lib/utils' + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<'nav'> & { + separator?: React.ReactNode + } +>(({ ...props }, ref) =>