Files
edbridge-scholars/src/components/AppSidebar.tsx
2026-03-13 01:24:13 +06:00

594 lines
22 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
</>
);
}