feat: add social features, leaderboard, auth enhancements, and admin fixes
continuous-integration/drone/push Build is passing

- Implement Social Features: Shared Tasks, Global Access, Privacy Settings (Leaderboard/Searchable).
- Add Leaderboard Page and API.
- Enhance Auth: Support Email/Username login, explicit duplicate registration errors.
- Fix: Admin login password hash regression.
- Refactor: Move to wouter for routing, add Admin Dashboard and User Management.
- Add Setup Wizard.
- Update UI with Sidebar and Gamification elements.
This commit is contained in:
2025-12-10 14:04:26 +01:00
parent d5b045158a
commit ccfb674318
52 changed files with 9206 additions and 1505 deletions
+120
View File
@@ -0,0 +1,120 @@
import { useTranslation } from 'react-i18next';
import { Home, Calendar, List, LayoutGrid, Settings, CheckSquare, Target, Trophy, LogOut, Award } from 'lucide-react';
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarMenu,
SidebarMenuItem,
SidebarMenuButton,
SidebarRail,
useSidebar,
SidebarTrigger,
} from "@/components/ui/sidebar"
import { useQueryClient, useMutation } from '@tanstack/react-query';
import { useToast } from "@/hooks/use-toast";
import { User } from "@shared/schema";
import ThemeToggle from './ThemeToggle';
import { useLocation } from "wouter";
import { GamificationBar } from './GamificationBar';
interface AppSidebarProps extends React.ComponentProps<typeof Sidebar> {
user: User | undefined;
}
export function AppSidebar({ user, ...props }: AppSidebarProps) {
const { t } = useTranslation();
const { state } = useSidebar();
const { toast } = useToast();
const queryClient = useQueryClient();
const [location, setLocation] = useLocation();
const logoutMutation = useMutation({
mutationFn: async () => {
await fetch("/api/logout", { method: "POST" });
},
onSuccess: () => {
queryClient.setQueryData(["/api/user"], null);
toast({ title: "Logged out successfully" });
},
});
const items = [
{ title: t('navigation.focus'), id: 'focus', path: '/', icon: Target, color: 'text-red-500' },
{ title: t('navigation.tasks'), id: 'tasks', path: '/tasks', icon: Home, color: 'text-blue-500' },
{ title: t('navigation.calendar'), id: 'calendar', path: '/calendar', icon: Calendar, color: 'text-violet-500' },
{ title: t('navigation.weekList'), id: 'weeklist', path: '/weeklist', icon: List, color: 'text-pink-500' },
{ title: t('navigation.kanban'), id: 'kanban', path: '/kanban', icon: LayoutGrid, color: 'text-orange-500' },
{ title: t('navigation.achievements'), id: 'achievements', path: '/achievements', icon: Trophy, color: 'text-yellow-500' },
{ title: t('navigation.leaderboard'), id: 'leaderboard', path: '/leaderboard', icon: Award, color: 'text-yellow-500' },
{ title: t('navigation.settings'), id: 'settings', path: '/settings', icon: Settings, color: 'text-gray-500' },
]
return (
<Sidebar collapsible="icon" {...props}>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground h-14">
<div className="flex aspect-square size-9 items-center justify-center rounded-xl bg-gradient-to-br from-violet-600 to-indigo-600 text-white shadow-lg">
<CheckSquare className="size-5" />
</div>
{state !== 'collapsed' && (
<div className="grid flex-1 text-left text-sm leading-tight ml-2">
<span className="truncate font-bold text-base">{t('app.title')}</span>
<span className="truncate text-xs opacity-70">Personal</span>
</div>
)}
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarMenu className="gap-2 px-2">
{items.map((item) => (
<SidebarMenuItem key={item.id}>
<SidebarMenuButton
isActive={location === item.path || (item.path !== '/' && location.startsWith(item.path))}
onClick={() => setLocation(item.path)}
tooltip={item.title}
className={`h-12 transition-all duration-200 ${location === item.path || (item.path !== '/' && location.startsWith(item.path))
? 'bg-gradient-to-r from-violet-600 to-indigo-600 text-white shadow-md hover:from-violet-500 hover:to-indigo-500 hover:text-white'
: 'hover:bg-sidebar-accent hover:pl-4'}`}
>
<item.icon className={`transition-all duration-200 ${state === 'collapsed' ? 'size-7' : 'size-5'} ${location === item.path || (item.path !== '/' && location.startsWith(item.path)) ? 'text-white' : item.color}`} />
{state !== 'collapsed' && (
<span className="font-medium text-base ml-2">{item.title}</span>
)}
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarContent>
<SidebarFooter>
{state !== 'collapsed' && user && (
<GamificationBar xp={user.xp} level={user.level} streak={user.currentStreak} />
)}
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton
onClick={() => logoutMutation.mutate()}
disabled={logoutMutation.isPending}
className="text-red-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-950/20"
>
<LogOut className={state === 'collapsed' ? 'size-5' : 'size-4'} />
{state !== 'collapsed' && <span className="font-medium ml-2">Logout</span>}
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
<div className={`p-4 flex items-center ${state === 'collapsed' ? 'justify-center flex-col gap-4' : 'justify-between'}`}>
<ThemeToggle />
<SidebarTrigger className={state === 'collapsed' ? '' : 'ml-auto'} />
</div>
</SidebarFooter>
<SidebarRail />
</Sidebar>
)
}