594 lines
22 KiB
TypeScript
594 lines
22 KiB
TypeScript
import {
|
||
SidebarContent,
|
||
SidebarHeader,
|
||
SidebarFooter,
|
||
SidebarGroup,
|
||
SidebarGroupLabel,
|
||
SidebarMenu,
|
||
SidebarMenuItem,
|
||
SidebarMenuButton,
|
||
SidebarMenuSub,
|
||
} from "../components/ui/sidebar";
|
||
|
||
import {
|
||
ChevronDown,
|
||
BookOpen,
|
||
Home,
|
||
Target,
|
||
Zap,
|
||
Trophy,
|
||
Map,
|
||
SquareLibrary,
|
||
ListIcon,
|
||
} from "lucide-react";
|
||
|
||
import { useState } from "react";
|
||
import logo from "../assets/ed_logo1.png";
|
||
import { NavLink, useNavigate, useLocation } from "react-router-dom";
|
||
import { useAuthStore } from "../stores/authStore";
|
||
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
|
||
|
||
export function AppSidebar() {
|
||
const [open, setOpen] = useState(false);
|
||
const user = useAuthStore((s) => s.user);
|
||
const navigate = useNavigate();
|
||
const location = useLocation();
|
||
const isQuestPage = location.pathname.startsWith("/student/quests");
|
||
|
||
const STYLES = `
|
||
/* ══ DEFAULT sidebar (cream frosted glass) ══ */
|
||
.as-sidebar-container {
|
||
position: fixed;
|
||
top: 0.5rem;
|
||
bottom: 0.5rem;
|
||
left: 0.5rem;
|
||
width: 16rem;
|
||
z-index: 10;
|
||
border-radius: 1.75rem;
|
||
overflow: hidden;
|
||
pointer-events: auto;
|
||
background: rgba(255,251,244,0.72);
|
||
backdrop-filter: blur(24px) saturate(180%);
|
||
-webkit-backdrop-filter: blur(24px) saturate(180%);
|
||
border: 1.5px solid rgba(255,255,255,0.7);
|
||
box-shadow:
|
||
0 8px 32px rgba(0,0,0,0.12),
|
||
0 2px 8px rgba(0,0,0,0.06),
|
||
inset 0 1px 0 rgba(255,255,255,0.8);
|
||
transition:
|
||
background 0.4s ease,
|
||
border-color 0.4s ease,
|
||
box-shadow 0.4s ease,
|
||
z-index 0.3s ease;
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: calc(100vh - 1rem);
|
||
}
|
||
|
||
/* ══ QUEST mode sidebar (dark navy pirate + gold) ══ */
|
||
.as-sidebar-container.quest-mode {
|
||
background: linear-gradient(
|
||
180deg,
|
||
rgba(251,191,36,0.12) 0%,
|
||
rgba(251,191,36,0.08) 50%,
|
||
rgba(251,191,36,0.1) 100%
|
||
);
|
||
background-size: 100% 200%;
|
||
animation: asSweepDown 3s linear infinite;
|
||
border-color: rgba(251,191,36,0.28);
|
||
box-shadow:
|
||
0 8px 32px rgba(0,0,0,0.25),
|
||
0 2px 8px rgba(0,0,0,0.15),
|
||
inset 0 1px 0 rgba(255,255,255,0.08);
|
||
}
|
||
|
||
/* Shimmer animation from top to bottom */
|
||
@keyframes asSweepDown {
|
||
0% { background-position: 0% 200%; }
|
||
100% { background-position: 0% -200%; }
|
||
}
|
||
|
||
/* On quest page, sidebar stays visible above content */
|
||
.as-sidebar-container.quest-mode {
|
||
z-index: 40;
|
||
}
|
||
|
||
.as-sidebar-inner {
|
||
position: relative;
|
||
z-index: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
}
|
||
|
||
.as-gradient-overlay {
|
||
position: absolute;
|
||
inset: 0;
|
||
pointer-events: none;
|
||
z-index: 0;
|
||
}
|
||
|
||
.as-gradient-overlay.default {
|
||
background: radial-gradient(
|
||
circle at top,
|
||
rgba(168,85,247,0.18),
|
||
transparent 55%
|
||
),
|
||
radial-gradient(
|
||
circle at bottom,
|
||
rgba(249,115,22,0.1),
|
||
transparent 55%
|
||
);
|
||
}
|
||
|
||
.as-gradient-overlay.quest {
|
||
background: transparent;
|
||
}
|
||
|
||
/* Ensure Sidebar sub-components are transparent */
|
||
.as-sidebar-inner > * {
|
||
background: transparent;
|
||
}
|
||
|
||
.as-sidebar-inner [class*="SidebarHeader"],
|
||
.as-sidebar-inner [class*="SidebarContent"],
|
||
.as-sidebar-inner [class*="SidebarFooter"],
|
||
.as-sidebar-inner [class*="SidebarGroup"],
|
||
.as-sidebar-inner [class*="SidebarMenu"] {
|
||
background: transparent;
|
||
}
|
||
|
||
/* Quest mode text visibility */
|
||
.as-sidebar-container.quest-mode [class*="SidebarGroupLabel"] {
|
||
color: rgba(255, 255, 255, 0.4) !important;
|
||
}
|
||
|
||
.as-sidebar-container.quest-mode a {
|
||
color: rgba(255, 255, 255, 0.6) !important;
|
||
background: transparent !important;
|
||
}
|
||
|
||
.as-sidebar-container.quest-mode a:hover {
|
||
color: rgba(255, 255, 255, 0.85) !important;
|
||
background: rgba(255, 255, 255, 0.08) !important;
|
||
}
|
||
|
||
.as-sidebar-container.quest-mode a.active {
|
||
color: #fbbf24 !important;
|
||
}
|
||
|
||
.as-sidebar-container.quest-mode span {
|
||
color: inherit;
|
||
}
|
||
|
||
/* Quest header text */
|
||
.as-sidebar-container.quest-mode .text-slate-900 {
|
||
color: rgba(255, 255, 255, 0.9) !important;
|
||
}
|
||
|
||
.as-sidebar-container.quest-mode .text-slate-400 {
|
||
color: rgba(255, 255, 255, 0.4) !important;
|
||
}
|
||
|
||
/* Quest mode removes white hover background from menu buttons */
|
||
.as-sidebar-container.quest-mode [class*="SidebarMenuButton"]:hover {
|
||
background: transparent !important;
|
||
}
|
||
|
||
.as-sidebar-container.quest-mode [class*="SidebarMenuButton"] {
|
||
background: transparent !important;
|
||
}
|
||
|
||
/* Prevent group-hover from adding white background in quest mode */
|
||
.as-sidebar-container.quest-mode .group:hover {
|
||
background: transparent !important;
|
||
}
|
||
|
||
/* Quest mode footer button styling */
|
||
.as-sidebar-container.quest-mode [class*="SidebarFooter"] button {
|
||
background: rgba(255, 255, 255, 0.08) !important;
|
||
--tw-ring-color: rgba(255, 255, 255, 0.05) !important;
|
||
border-color: rgba(255, 255, 255, 0.05) !important;
|
||
}
|
||
|
||
.as-sidebar-container.quest-mode [class*="SidebarFooter"] button:hover {
|
||
background: rgba(255, 255, 255, 0.12) !important;
|
||
}
|
||
|
||
/* Override Tailwind bg-white and ring classes for quest mode */
|
||
.as-sidebar-container.quest-mode button[class*="bg-white"] {
|
||
background-color: rgba(255, 255, 255, 0.08) !important;
|
||
}
|
||
|
||
.as-sidebar-container.quest-mode button[class*="ring-white"]:not(:hover) {
|
||
--tw-ring-color: rgba(255, 255, 255, 0.05) !important;
|
||
border-color: rgba(255, 255, 255, 0.05) !important;
|
||
}
|
||
|
||
.as-sidebar-container.quest-mode button:hover[class*="hover:bg-white"] {
|
||
background-color: rgba(255, 255, 255, 0.12) !important;
|
||
}
|
||
|
||
.as-sidebar-container.quest-mode a:hover {
|
||
--tw-ring-color: rgba(255, 255, 255, 0.05) !important;
|
||
}
|
||
`;
|
||
|
||
return (
|
||
<>
|
||
<style>{STYLES}</style>
|
||
<div
|
||
className={`as-sidebar-container${isQuestPage ? " quest-mode" : ""}`}
|
||
>
|
||
<div
|
||
className={`as-gradient-overlay ${isQuestPage ? "quest" : "default"}`}
|
||
/>
|
||
|
||
<div className="as-sidebar-inner">
|
||
{/* HEADER */}
|
||
<SidebarHeader className="px-3 pb-4 pt-1">
|
||
<div className="flex items-center justify-start gap-2">
|
||
<div className="flex items-center gap-3 rounded-2xl px-2 py-2">
|
||
<div className="flex h-10 w-10 items-center justify-center overflow-hidden rounded-full bg-linear-to-br from-purple-400 to-purple-500 shadow-[0_6px_18px_rgba(168,85,247,0.55)]">
|
||
<img
|
||
src={logo}
|
||
className="h-full w-full object-cover object-left"
|
||
alt="Logo"
|
||
/>
|
||
</div>
|
||
<div className="flex flex-col text-sm">
|
||
<span className="font-satoshi-medium text-slate-900">
|
||
Edbridge Scholars
|
||
</span>
|
||
<span className="font-satoshi text-xs text-slate-400">
|
||
Student
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</SidebarHeader>
|
||
|
||
{/* CONTENT */}
|
||
<SidebarContent className="px-1">
|
||
<SidebarGroup>
|
||
<SidebarGroupLabel className="px-2 text-[0.7rem] font-satoshi tracking-[0.16em] text-slate-400">
|
||
PLATFORM
|
||
</SidebarGroupLabel>
|
||
|
||
<SidebarMenu className="mt-1 space-y-1.5">
|
||
{/* HOME */}
|
||
<SidebarMenuItem>
|
||
<SidebarMenuButton
|
||
asChild
|
||
className="group cursor-pointer px-2 py-2.5 transition-colors duration-200"
|
||
>
|
||
<NavLink
|
||
to="/student/home"
|
||
className={({ isActive }) =>
|
||
`flex items-center gap-2.5 text-sm font-satoshi rounded-2xl px-2 py-2.5 transition-all duration-200 ${
|
||
isActive
|
||
? "text-slate-900"
|
||
: "text-slate-500 group-hover:text-slate-900"
|
||
}`
|
||
}
|
||
>
|
||
{({ isActive }) => (
|
||
<>
|
||
<Home
|
||
size={18}
|
||
strokeWidth={3}
|
||
className={
|
||
isActive ? "text-orange-400" : "text-slate-400"
|
||
}
|
||
/>
|
||
<span
|
||
className={
|
||
isActive
|
||
? "text-orange-400 font-extrabold"
|
||
: "text-slate-400 font-bold"
|
||
}
|
||
>
|
||
Home
|
||
</span>
|
||
</>
|
||
)}
|
||
</NavLink>
|
||
</SidebarMenuButton>
|
||
</SidebarMenuItem>
|
||
|
||
{/* PRACTICE */}
|
||
<SidebarMenuItem
|
||
onMouseEnter={() => setOpen(true)}
|
||
onMouseLeave={() => setOpen(false)}
|
||
>
|
||
<SidebarMenuButton
|
||
className="group cursor-pointer px-2 py-2.5 transition-colors duration-200"
|
||
asChild
|
||
>
|
||
<NavLink
|
||
to="/student/practice"
|
||
className={({ isActive }) =>
|
||
`flex items-center gap-2.5 text-sm font-satoshi rounded-2xl px-2 py-2.5 transition-all duration-200 ${
|
||
isActive
|
||
? "text-slate-900"
|
||
: "text-slate-500 group-hover:text-slate-900"
|
||
}`
|
||
}
|
||
>
|
||
{({ isActive }) => (
|
||
<>
|
||
<BookOpen
|
||
size={18}
|
||
strokeWidth={3}
|
||
className={
|
||
isActive ? "text-purple-500" : "text-slate-400"
|
||
}
|
||
/>
|
||
<span
|
||
className={
|
||
isActive
|
||
? "text-purple-500 font-extrabold"
|
||
: "text-slate-400 font-bold"
|
||
}
|
||
>
|
||
Practice
|
||
</span>
|
||
<ChevronDown
|
||
size={16}
|
||
strokeWidth={3}
|
||
className={`ml-auto text-slate-400 transition-transform ${
|
||
open ? "rotate-180" : ""
|
||
}`}
|
||
/>
|
||
</>
|
||
)}
|
||
</NavLink>
|
||
</SidebarMenuButton>
|
||
{open && (
|
||
<SidebarMenuSub className="mt-2 space-y-1.5 pl-3">
|
||
<NavLink
|
||
to="/student/practice/targeted-practice"
|
||
className={({ isActive }) =>
|
||
`flex items-center gap-2.5 rounded-2xl px-2 py-2 text-sm font-satoshi transition-colors duration-200 ${
|
||
isActive
|
||
? "bg-white text-slate-900"
|
||
: "text-slate-500 hover:bg-white hover:text-slate-900"
|
||
}`
|
||
}
|
||
>
|
||
<Target
|
||
size={18}
|
||
strokeWidth={3}
|
||
className="text-slate-400"
|
||
/>
|
||
<span>Targeted Practice</span>
|
||
</NavLink>
|
||
|
||
<NavLink
|
||
to="/student/practice/drills"
|
||
className={({ isActive }) =>
|
||
`flex items-center gap-2.5 rounded-2xl px-2 py-2 text-sm font-satoshi transition-colors duration-200 ${
|
||
isActive
|
||
? "bg-white text-slate-900"
|
||
: "text-slate-500 hover:bg-white hover:text-slate-900"
|
||
}`
|
||
}
|
||
>
|
||
<Zap
|
||
size={18}
|
||
strokeWidth={3}
|
||
className="text-slate-400"
|
||
/>
|
||
<span>Drills</span>
|
||
</NavLink>
|
||
<NavLink
|
||
to="/student/practice/hard-test-modules"
|
||
className={({ isActive }) =>
|
||
`flex items-center gap-2.5 rounded-2xl px-2 py-2 text-sm font-satoshi transition-colors duration-200 ${
|
||
isActive
|
||
? "bg-white text-slate-900"
|
||
: "text-slate-500 hover:bg-white hover:text-slate-900"
|
||
}`
|
||
}
|
||
>
|
||
<Trophy
|
||
size={18}
|
||
strokeWidth={3}
|
||
className="text-slate-400"
|
||
/>
|
||
<span>Hard Test Modules</span>
|
||
</NavLink>
|
||
<NavLink
|
||
to="/student/practice/practice-sheet"
|
||
className={({ isActive }) =>
|
||
`flex items-center gap-2.5 rounded-2xl px-2 py-2 text-sm font-satoshi transition-colors duration-200 ${
|
||
isActive
|
||
? "bg-white text-slate-900"
|
||
: "text-slate-500 hover:bg-white hover:text-slate-900"
|
||
}`
|
||
}
|
||
>
|
||
<ListIcon
|
||
size={18}
|
||
strokeWidth={3}
|
||
className="text-slate-400"
|
||
/>
|
||
<span>Practice Sheet</span>
|
||
</NavLink>
|
||
</SidebarMenuSub>
|
||
)}
|
||
</SidebarMenuItem>
|
||
|
||
{/* QUESTS */}
|
||
<SidebarMenuItem>
|
||
<SidebarMenuButton
|
||
asChild
|
||
className="group cursor-pointer px-2 py-2.5 transition-colors duration-200"
|
||
>
|
||
<NavLink
|
||
to="/student/quests"
|
||
className={({ isActive }) => {
|
||
if (isActive && isQuestPage) {
|
||
return "flex items-center gap-2.5 text-sm rounded-2xl px-2 py-2.5 transition-all duration-200";
|
||
}
|
||
if (isActive) {
|
||
return "flex items-center gap-2.5 text-sm font-satoshi rounded-2xl px-2 py-2.5 transition-all duration-200 text-slate-900";
|
||
}
|
||
return "flex items-center gap-2.5 text-sm font-satoshi rounded-2xl px-2 py-2.5 transition-all duration-200 text-slate-500 group-hover:text-slate-900";
|
||
}}
|
||
>
|
||
{({ isActive }) => (
|
||
<>
|
||
<Map
|
||
size={18}
|
||
strokeWidth={3}
|
||
className={
|
||
isActive && isQuestPage
|
||
? "text-amber-400"
|
||
: isActive
|
||
? "text-blue-500"
|
||
: "text-slate-400"
|
||
}
|
||
/>
|
||
<span
|
||
className={
|
||
isActive && isQuestPage
|
||
? ""
|
||
: isActive
|
||
? "text-blue-500 font-extrabold"
|
||
: "text-slate-400 font-bold"
|
||
}
|
||
style={
|
||
isActive && isQuestPage
|
||
? {
|
||
fontFamily: "'Sorts Mill Goudy', serif",
|
||
fontSize: "0.95rem",
|
||
fontWeight: 900,
|
||
letterSpacing: "0.05em",
|
||
color: "#fbbf24",
|
||
textShadow: "0 0 12px rgba(251,191,36,0.5)",
|
||
}
|
||
: {}
|
||
}
|
||
>
|
||
Quests
|
||
</span>
|
||
</>
|
||
)}
|
||
</NavLink>
|
||
</SidebarMenuButton>
|
||
</SidebarMenuItem>
|
||
|
||
{/* LESSONS */}
|
||
<SidebarMenuItem>
|
||
<SidebarMenuButton
|
||
asChild
|
||
className="group cursor-pointer px-2 py-2.5 transition-colors duration-200"
|
||
>
|
||
<NavLink
|
||
to="/student/lessons"
|
||
className={({ isActive }) =>
|
||
`flex items-center gap-2.5 text-sm font-satoshi rounded-2xl px-2 py-2.5 transition-all duration-200 ${
|
||
isActive
|
||
? "text-slate-900"
|
||
: "text-slate-500 group-hover:text-slate-900"
|
||
}`
|
||
}
|
||
>
|
||
{({ isActive }) => (
|
||
<>
|
||
<SquareLibrary
|
||
size={18}
|
||
strokeWidth={3}
|
||
className={
|
||
isActive ? "text-cyan-500" : "text-slate-400"
|
||
}
|
||
/>
|
||
<span
|
||
className={
|
||
isActive
|
||
? "text-cyan-500 font-extrabold"
|
||
: "text-slate-400 font-bold"
|
||
}
|
||
>
|
||
Lessons
|
||
</span>
|
||
</>
|
||
)}
|
||
</NavLink>
|
||
</SidebarMenuButton>
|
||
</SidebarMenuItem>
|
||
|
||
{/* REWARDS */}
|
||
<SidebarMenuItem>
|
||
<SidebarMenuButton
|
||
asChild
|
||
className="group cursor-pointer px-2 py-2.5 transition-colors duration-200"
|
||
>
|
||
<NavLink
|
||
to="/student/rewards"
|
||
className={({ isActive }) =>
|
||
`flex items-center gap-2.5 text-sm font-satoshi rounded-2xl px-2 py-2.5 transition-all duration-200 ${
|
||
isActive
|
||
? "text-slate-900"
|
||
: "text-slate-500 group-hover:text-slate-900"
|
||
}`
|
||
}
|
||
>
|
||
{({ isActive }) => (
|
||
<>
|
||
<Trophy
|
||
size={18}
|
||
strokeWidth={3}
|
||
className={
|
||
isActive ? "text-emerald-500" : "text-slate-400"
|
||
}
|
||
/>
|
||
<span
|
||
className={
|
||
isActive
|
||
? "text-emerald-500 font-extrabold"
|
||
: "text-slate-400 font-bold"
|
||
}
|
||
>
|
||
Rewards
|
||
</span>
|
||
</>
|
||
)}
|
||
</NavLink>
|
||
</SidebarMenuButton>
|
||
</SidebarMenuItem>
|
||
</SidebarMenu>
|
||
</SidebarGroup>
|
||
</SidebarContent>
|
||
|
||
{/* FOOTER – links to profile */}
|
||
<SidebarFooter className="mt-auto px-3 pb-3 pt-4">
|
||
<button
|
||
type="button"
|
||
onClick={() => navigate("/student/profile")}
|
||
className="flex w-full items-center gap-3 rounded-2xl bg-white/60 px-3 py-2 text-left shadow-sm ring-1 ring-white/80 hover:bg-white"
|
||
>
|
||
<Avatar>
|
||
<AvatarImage src={user?.avatar_url} />
|
||
<AvatarFallback className="bg-linear-to-br from-purple-400 to-purple-500 font-satoshi-bold uppercase text-white">
|
||
{user?.name.slice(0, 1)}
|
||
</AvatarFallback>
|
||
</Avatar>
|
||
<div className="flex flex-col text-sm">
|
||
<span className="font-medium text-slate-900">{user?.name}</span>
|
||
<span className="text-xs text-slate-400">{user?.email}</span>
|
||
</div>
|
||
<ChevronDown
|
||
size={16}
|
||
strokeWidth={3}
|
||
className="ml-auto text-slate-400"
|
||
/>
|
||
</button>
|
||
</SidebarFooter>
|
||
</div>
|
||
</div>
|
||
</>
|
||
);
|
||
}
|