Spaces:
Sleeping
Sleeping
| import { useEffect, useState } from 'react'; | |
| import { supabase } from '../supabaseClient'; | |
| import { BookOpen, Map, Flame, LogOut, RefreshCw, Trophy, User } from 'lucide-react'; | |
| interface SidebarProps { | |
| activeTab: 'queue' | 'journey'; | |
| setActiveTab: (tab: 'queue' | 'journey') => void; | |
| user: any; | |
| } | |
| export default function Sidebar({ activeTab, setActiveTab, user }: SidebarProps) { | |
| const [streak, setStreak] = useState<any>(null); | |
| const [syncing, setSyncing] = useState(false); | |
| const [syncResult, setSyncResult] = useState<string | null>(null); | |
| const fetchStreak = async () => { | |
| try { | |
| const { data: { session } } = await supabase.auth.getSession(); | |
| if (!session) return; | |
| const res = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/streak`, { | |
| headers: { | |
| 'Authorization': `Bearer ${session.access_token}` | |
| } | |
| }); | |
| if (res.ok) { | |
| const data = await res.json(); | |
| setStreak(data); | |
| } | |
| } catch (err) { | |
| console.error('Error fetching streak:', err); | |
| } | |
| }; | |
| useEffect(() => { | |
| fetchStreak(); | |
| }, [user]); | |
| const handleSync = async () => { | |
| setSyncing(true); | |
| setSyncResult(null); | |
| try { | |
| const { data: { session } } = await supabase.auth.getSession(); | |
| if (!session) return; | |
| const res = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/sync`, { | |
| method: 'POST', | |
| headers: { | |
| 'Authorization': `Bearer ${session.access_token}` | |
| } | |
| }); | |
| if (res.ok) { | |
| const data = await res.json(); | |
| setSyncResult(data.message || 'Sync complete!'); | |
| // Refresh streak and state | |
| fetchStreak(); | |
| } else { | |
| setSyncResult('Sync failed. Check backend logs.'); | |
| } | |
| } catch (err) { | |
| setSyncResult('Sync failed. Server unreachable.'); | |
| } finally { | |
| setSyncing(false); | |
| } | |
| }; | |
| const handleSignOut = async () => { | |
| await supabase.auth.signOut(); | |
| }; | |
| return ( | |
| <aside className="w-80 h-screen bg-bg-sidebar border-r border-slate-900 flex flex-col justify-between p-6 shrink-0 z-10 relative"> | |
| <div className="space-y-8"> | |
| {/* Brand */} | |
| <div className="flex items-center gap-3"> | |
| <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-purple to-accent-pink flex items-center justify-center shadow-[0_0_15px_rgba(157,78,221,0.3)]"> | |
| <Trophy className="w-5 h-5 text-white" /> | |
| </div> | |
| <div> | |
| <h1 className="text-xl font-bold tracking-tight text-white m-0">AlgoSpaced</h1> | |
| <span className="text-xs text-slate-500">DSA Repetition</span> | |
| </div> | |
| </div> | |
| {/* Navigation */} | |
| <nav className="space-y-2"> | |
| <button | |
| onClick={() => setActiveTab('queue')} | |
| className={`w-full flex items-center gap-3.5 px-4 py-3 rounded-xl text-sm font-semibold tracking-wide transition-all duration-200 cursor-pointer ${ | |
| activeTab === 'queue' | |
| ? 'bg-primary-purple text-white shadow-[0_0_15px_rgba(157,78,221,0.25)]' | |
| : 'text-slate-400 hover:text-white hover:bg-slate-950' | |
| }`} | |
| > | |
| <BookOpen className="w-5 h-5" /> | |
| Daily Review Queue | |
| </button> | |
| <button | |
| onClick={() => setActiveTab('journey')} | |
| className={`w-full flex items-center gap-3.5 px-4 py-3 rounded-xl text-sm font-semibold tracking-wide transition-all duration-200 cursor-pointer ${ | |
| activeTab === 'journey' | |
| ? 'bg-primary-purple text-white shadow-[0_0_15px_rgba(157,78,221,0.25)]' | |
| : 'text-slate-400 hover:text-white hover:bg-slate-950' | |
| }`} | |
| > | |
| <Map className="w-5 h-5" /> | |
| Journey Path | |
| </button> | |
| </nav> | |
| {/* Streak Component */} | |
| <div className="glass-panel rounded-2xl p-5 border border-slate-900"> | |
| <div className="flex items-center gap-3 mb-4"> | |
| <div className="p-2 rounded-lg bg-orange-500/10 text-orange-400"> | |
| <Flame className="w-5 h-5" /> | |
| </div> | |
| <div> | |
| <h3 className="text-xs font-semibold text-slate-400 uppercase tracking-wider">Active Streak</h3> | |
| <span className="text-2xl font-bold text-white tracking-tight">{streak?.current_streak ?? 0} days</span> | |
| </div> | |
| </div> | |
| <div className="flex justify-between items-center text-xs text-slate-500 border-t border-slate-900/60 pt-3"> | |
| <span>Longest: {streak?.longest_streak ?? 0} days</span> | |
| {streak?.last_active_date && ( | |
| <span>Last active: {new Date(streak.last_active_date).toLocaleDateString()}</span> | |
| )} | |
| </div> | |
| </div> | |
| {/* Sync Google Drive */} | |
| <div className="space-y-2"> | |
| <button | |
| onClick={handleSync} | |
| disabled={syncing} | |
| className="w-full bg-slate-950 hover:bg-slate-900 text-slate-300 font-semibold py-3 px-4 rounded-xl flex items-center justify-center gap-2 border border-slate-800 transition-all text-sm cursor-pointer disabled:opacity-50" | |
| > | |
| <RefreshCw className={`w-4 h-4 text-accent-cyan ${syncing ? 'animate-spin' : ''}`} /> | |
| {syncing ? 'Syncing...' : 'Sync Google Drive'} | |
| </button> | |
| {syncResult && ( | |
| <p className="text-[11px] text-center text-accent-cyan leading-normal max-h-16 overflow-y-auto px-2"> | |
| {syncResult} | |
| </p> | |
| )} | |
| </div> | |
| </div> | |
| {/* User Session Info / Sign Out */} | |
| <div className="border-t border-slate-900 pt-5 space-y-4"> | |
| <div className="flex items-center gap-3 px-2"> | |
| <div className="w-8 h-8 rounded-full bg-slate-900 border border-slate-800 flex items-center justify-center text-slate-400"> | |
| <User className="w-4 h-4" /> | |
| </div> | |
| <div className="overflow-hidden"> | |
| <p className="text-xs font-semibold text-white truncate m-0">{user.email}</p> | |
| <p className="text-[10px] text-slate-500 truncate m-0">Verified User</p> | |
| </div> | |
| </div> | |
| <button | |
| onClick={handleSignOut} | |
| className="w-full flex items-center justify-center gap-2 py-2.5 rounded-lg text-xs font-semibold text-rose-400 hover:text-rose-300 bg-rose-500/5 hover:bg-rose-500/10 transition-all cursor-pointer" | |
| > | |
| <LogOut className="w-4 h-4" /> | |
| Sign Out | |
| </button> | |
| </div> | |
| </aside> | |
| ); | |
| } | |