mirror of
https://github.com/AeThex-Corporation/AeThex-OS.git
synced 2026-04-18 22:37:21 +00:00
Secure all admin routes and fix achievement icon display
Introduces a ProtectedRoute component to secure all admin routes, centralizing authentication logic. Removes redundant individual auth checks from admin pages. Updates admin-achievements.tsx to use a new `iconMap` for consistent icon rendering, resolving display issues. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 279f1558-c0e3-40e4-8217-be7e9f4c6eca Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 5a2fcd4b-aa24-41db-8542-c8df6e959cd0 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/b984cb14-1d19-4944-922b-bc79e821ed35/279f1558-c0e3-40e4-8217-be7e9f4c6eca/ugufFZw Replit-Helium-Checkpoint-Created: true
This commit is contained in:
parent
c4e451da90
commit
cf72b31513
14 changed files with 141 additions and 234 deletions
|
|
@ -4,6 +4,7 @@ import { QueryClientProvider } from "@tanstack/react-query";
|
|||
import { Toaster } from "@/components/ui/toaster";
|
||||
import { AuthProvider } from "@/lib/auth";
|
||||
import { TutorialProvider } from "@/components/Tutorial";
|
||||
import { ProtectedRoute } from "@/components/ProtectedRoute";
|
||||
import NotFound from "@/pages/not-found";
|
||||
import Home from "@/pages/home";
|
||||
import Passport from "@/pages/passport";
|
||||
|
|
@ -34,17 +35,17 @@ function Router() {
|
|||
<Route path="/dashboard" component={Dashboard} />
|
||||
<Route path="/curriculum" component={Curriculum} />
|
||||
<Route path="/login" component={Login} />
|
||||
<Route path="/admin" component={Admin} />
|
||||
<Route path="/admin/architects" component={AdminArchitects} />
|
||||
<Route path="/admin/projects" component={AdminProjects} />
|
||||
<Route path="/admin/credentials" component={AdminCredentials} />
|
||||
<Route path="/admin/aegis" component={AdminAegis} />
|
||||
<Route path="/admin/sites" component={AdminSites} />
|
||||
<Route path="/admin/logs" component={AdminLogs} />
|
||||
<Route path="/admin/achievements" component={AdminAchievements} />
|
||||
<Route path="/admin/applications" component={AdminApplications} />
|
||||
<Route path="/admin/activity" component={AdminActivity} />
|
||||
<Route path="/admin/notifications" component={AdminNotifications} />
|
||||
<Route path="/admin">{() => <ProtectedRoute><Admin /></ProtectedRoute>}</Route>
|
||||
<Route path="/admin/architects">{() => <ProtectedRoute><AdminArchitects /></ProtectedRoute>}</Route>
|
||||
<Route path="/admin/projects">{() => <ProtectedRoute><AdminProjects /></ProtectedRoute>}</Route>
|
||||
<Route path="/admin/credentials">{() => <ProtectedRoute><AdminCredentials /></ProtectedRoute>}</Route>
|
||||
<Route path="/admin/aegis">{() => <ProtectedRoute><AdminAegis /></ProtectedRoute>}</Route>
|
||||
<Route path="/admin/sites">{() => <ProtectedRoute><AdminSites /></ProtectedRoute>}</Route>
|
||||
<Route path="/admin/logs">{() => <ProtectedRoute><AdminLogs /></ProtectedRoute>}</Route>
|
||||
<Route path="/admin/achievements">{() => <ProtectedRoute><AdminAchievements /></ProtectedRoute>}</Route>
|
||||
<Route path="/admin/applications">{() => <ProtectedRoute><AdminApplications /></ProtectedRoute>}</Route>
|
||||
<Route path="/admin/activity">{() => <ProtectedRoute><AdminActivity /></ProtectedRoute>}</Route>
|
||||
<Route path="/admin/notifications">{() => <ProtectedRoute><AdminNotifications /></ProtectedRoute>}</Route>
|
||||
<Route path="/pitch" component={Pitch} />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
|
|
|
|||
32
client/src/components/ProtectedRoute.tsx
Normal file
32
client/src/components/ProtectedRoute.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { useEffect } from "react";
|
||||
import { useLocation } from "wouter";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
|
||||
interface ProtectedRouteProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function ProtectedRoute({ children }: ProtectedRouteProps) {
|
||||
const { isAuthenticated, isLoading } = useAuth();
|
||||
const [, setLocation] = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && !isAuthenticated) {
|
||||
setLocation("/login");
|
||||
}
|
||||
}, [isLoading, isAuthenticated, setLocation]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center">
|
||||
<div className="text-primary animate-pulse">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
81
client/src/lib/iconMap.tsx
Normal file
81
client/src/lib/iconMap.tsx
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import {
|
||||
Award, Star, Trophy, Crown, Shield, Zap, Heart, ThumbsUp,
|
||||
Users, UserPlus, UserCheck, UserCog, User,
|
||||
MessageSquare, MessageCircle, MessagesSquare, Hash, Send,
|
||||
Newspaper, PenSquare, PenTool,
|
||||
Code, FolderGit2, ClipboardCheck, Ticket, CheckCircle2, BugOff,
|
||||
Rocket, Footprints, Gift, Gem, Diamond, Magnet,
|
||||
Calendar, CalendarDays, CalendarHeart,
|
||||
Video, Clapperboard, Flame,
|
||||
Globe, Network, Brain, ShieldCheck, ShieldEllipsis,
|
||||
Swords, LogIn, GraduationCap
|
||||
} from "lucide-react";
|
||||
|
||||
const iconMap: Record<string, React.ComponentType<{ className?: string }>> = {
|
||||
"award": Award,
|
||||
"star": Star,
|
||||
"trophy": Trophy,
|
||||
"crown": Crown,
|
||||
"shield": Shield,
|
||||
"zap": Zap,
|
||||
"heart": Heart,
|
||||
"thumbs-up": ThumbsUp,
|
||||
"users": Users,
|
||||
"user-plus": UserPlus,
|
||||
"user-check": UserCheck,
|
||||
"user-cog": UserCog,
|
||||
"user": User,
|
||||
"message-square": MessageSquare,
|
||||
"message-circle": MessageCircle,
|
||||
"messages-square": MessagesSquare,
|
||||
"message-square-plus": MessageSquare,
|
||||
"hash": Hash,
|
||||
"send": Send,
|
||||
"newspaper": Newspaper,
|
||||
"pen-square": PenSquare,
|
||||
"pen-tool": PenTool,
|
||||
"code": Code,
|
||||
"folder-git-2": FolderGit2,
|
||||
"clipboard-check": ClipboardCheck,
|
||||
"ticket": Ticket,
|
||||
"check-circle-2": CheckCircle2,
|
||||
"bug-off": BugOff,
|
||||
"rocket": Rocket,
|
||||
"footprints": Footprints,
|
||||
"gift": Gift,
|
||||
"gem": Gem,
|
||||
"diamond": Diamond,
|
||||
"magnet": Magnet,
|
||||
"calendar": Calendar,
|
||||
"calendar-days": CalendarDays,
|
||||
"calendar-heart": CalendarHeart,
|
||||
"video": Video,
|
||||
"clapperboard": Clapperboard,
|
||||
"flame": Flame,
|
||||
"globe": Globe,
|
||||
"network": Network,
|
||||
"brain-circuit": Brain,
|
||||
"shield-check": ShieldCheck,
|
||||
"shield-ellipsis": ShieldEllipsis,
|
||||
"swords": Swords,
|
||||
"login": LogIn,
|
||||
"logins": LogIn,
|
||||
"graduationcap": GraduationCap,
|
||||
};
|
||||
|
||||
export function getIcon(iconName: string | null | undefined): React.ReactNode {
|
||||
if (!iconName) return "🏆";
|
||||
|
||||
const normalized = iconName.toLowerCase().trim();
|
||||
const IconComponent = iconMap[normalized];
|
||||
|
||||
if (IconComponent) {
|
||||
return <IconComponent className="w-6 h-6 text-primary" />;
|
||||
}
|
||||
|
||||
if (iconName.length <= 4) {
|
||||
return iconName;
|
||||
}
|
||||
|
||||
return "🏆";
|
||||
}
|
||||
|
|
@ -1,26 +1,17 @@
|
|||
import { useEffect } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Link, useLocation } from "wouter";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
import { getIcon } from "@/lib/iconMap";
|
||||
import {
|
||||
Users, FileCode, Shield, Activity, LogOut,
|
||||
BarChart3, User, Globe, Key, Award, Star, Trophy
|
||||
} from "lucide-react";
|
||||
|
||||
export default function AdminAchievements() {
|
||||
const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth();
|
||||
const { user, logout } = useAuth();
|
||||
const [, setLocation] = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (!authLoading && !isAuthenticated) {
|
||||
setLocation("/login");
|
||||
}
|
||||
}, 200);
|
||||
return () => clearTimeout(timer);
|
||||
}, [authLoading, isAuthenticated, setLocation]);
|
||||
|
||||
const { data: achievements, isLoading } = useQuery({
|
||||
queryKey: ["achievements"],
|
||||
queryFn: async () => {
|
||||
|
|
@ -28,17 +19,8 @@ export default function AdminAchievements() {
|
|||
if (!res.ok) throw new Error("Failed to fetch achievements");
|
||||
return res.json();
|
||||
},
|
||||
enabled: isAuthenticated,
|
||||
});
|
||||
|
||||
if (authLoading || !isAuthenticated) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center">
|
||||
<div className="text-primary animate-pulse">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
setLocation("/");
|
||||
|
|
@ -84,7 +66,7 @@ export default function AdminAchievements() {
|
|||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-12 h-12 bg-black/20 rounded-lg flex items-center justify-center text-2xl">
|
||||
{achievement.icon || '🏆'}
|
||||
{getIcon(achievement.icon)}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="font-display text-white uppercase text-sm">{achievement.name}</h3>
|
||||
|
|
|
|||
|
|
@ -18,18 +18,12 @@ interface ActivityEvent {
|
|||
}
|
||||
|
||||
export default function AdminActivity() {
|
||||
const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth();
|
||||
const { user, logout } = useAuth();
|
||||
const [, setLocation] = useLocation();
|
||||
const [liveEvents, setLiveEvents] = useState<ActivityEvent[]>([]);
|
||||
const [lastRefresh, setLastRefresh] = useState(new Date());
|
||||
const [seenEventIds, setSeenEventIds] = useState<Set<string>>(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
if (!authLoading && !isAuthenticated) {
|
||||
setLocation("/login");
|
||||
}
|
||||
}, [authLoading, isAuthenticated, setLocation]);
|
||||
|
||||
const { data: profiles } = useQuery({
|
||||
queryKey: ["profiles"],
|
||||
queryFn: async () => {
|
||||
|
|
@ -37,7 +31,6 @@ export default function AdminActivity() {
|
|||
if (!res.ok) return [];
|
||||
return res.json();
|
||||
},
|
||||
enabled: isAuthenticated,
|
||||
refetchInterval: 10000,
|
||||
});
|
||||
|
||||
|
|
@ -48,7 +41,6 @@ export default function AdminActivity() {
|
|||
if (!res.ok) return [];
|
||||
return res.json();
|
||||
},
|
||||
enabled: isAuthenticated,
|
||||
refetchInterval: 5000,
|
||||
});
|
||||
|
||||
|
|
@ -89,14 +81,6 @@ export default function AdminActivity() {
|
|||
}
|
||||
}, [authLogs]);
|
||||
|
||||
if (authLoading || !isAuthenticated) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center">
|
||||
<div className="text-primary animate-pulse">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
setLocation("/");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { useEffect } from "react";
|
||||
import { Link, useLocation } from "wouter";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
|
|
@ -8,19 +7,10 @@ import {
|
|||
} from "lucide-react";
|
||||
|
||||
export default function AdminAegis() {
|
||||
const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth();
|
||||
const { user, logout } = useAuth();
|
||||
const [, setLocation] = useLocation();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (!authLoading && !isAuthenticated) {
|
||||
setLocation("/login");
|
||||
}
|
||||
}, 200);
|
||||
return () => clearTimeout(timer);
|
||||
}, [authLoading, isAuthenticated, setLocation]);
|
||||
|
||||
const { data: alerts } = useQuery({
|
||||
queryKey: ["alerts"],
|
||||
queryFn: async () => {
|
||||
|
|
@ -28,7 +18,6 @@ export default function AdminAegis() {
|
|||
if (!res.ok) return [];
|
||||
return res.json();
|
||||
},
|
||||
enabled: isAuthenticated,
|
||||
});
|
||||
|
||||
const { data: authLogs } = useQuery({
|
||||
|
|
@ -38,7 +27,6 @@ export default function AdminAegis() {
|
|||
if (!res.ok) return [];
|
||||
return res.json();
|
||||
},
|
||||
enabled: isAuthenticated,
|
||||
});
|
||||
|
||||
const resolveAlertMutation = useMutation({
|
||||
|
|
@ -56,14 +44,6 @@ export default function AdminAegis() {
|
|||
},
|
||||
});
|
||||
|
||||
if (authLoading || !isAuthenticated) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center">
|
||||
<div className="text-primary animate-pulse">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
setLocation("/");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Link, useLocation } from "wouter";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
|
@ -10,20 +10,11 @@ import {
|
|||
} from "lucide-react";
|
||||
|
||||
export default function AdminApplications() {
|
||||
const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth();
|
||||
const { user, logout } = useAuth();
|
||||
const [, setLocation] = useLocation();
|
||||
const [selectedApp, setSelectedApp] = useState<any>(null);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (!authLoading && !isAuthenticated) {
|
||||
setLocation("/login");
|
||||
}
|
||||
}, 200);
|
||||
return () => clearTimeout(timer);
|
||||
}, [authLoading, isAuthenticated, setLocation]);
|
||||
|
||||
const { data: applications, isLoading } = useQuery({
|
||||
queryKey: ["applications"],
|
||||
queryFn: async () => {
|
||||
|
|
@ -31,7 +22,6 @@ export default function AdminApplications() {
|
|||
if (!res.ok) throw new Error("Failed to fetch applications");
|
||||
return res.json();
|
||||
},
|
||||
enabled: isAuthenticated,
|
||||
});
|
||||
|
||||
const updateApplicationMutation = useMutation({
|
||||
|
|
@ -50,14 +40,6 @@ export default function AdminApplications() {
|
|||
},
|
||||
});
|
||||
|
||||
if (authLoading || !isAuthenticated) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center">
|
||||
<div className="text-primary animate-pulse">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
setLocation("/");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Link, useLocation } from "wouter";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
|
@ -10,28 +10,18 @@ import {
|
|||
} from "lucide-react";
|
||||
|
||||
export default function AdminArchitects() {
|
||||
const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth();
|
||||
const { user, logout } = useAuth();
|
||||
const [, setLocation] = useLocation();
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [selectedProfile, setSelectedProfile] = useState<any>(null);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (!authLoading && !isAuthenticated) {
|
||||
setLocation("/login");
|
||||
}
|
||||
}, 200);
|
||||
return () => clearTimeout(timer);
|
||||
}, [authLoading, isAuthenticated, setLocation]);
|
||||
|
||||
const { data: profiles, isLoading } = useQuery({
|
||||
queryKey: ["profiles"],
|
||||
queryFn: async () => {
|
||||
const res = await fetch("/api/profiles");
|
||||
return res.json();
|
||||
},
|
||||
enabled: isAuthenticated,
|
||||
});
|
||||
|
||||
const updateProfileMutation = useMutation({
|
||||
|
|
@ -50,14 +40,6 @@ export default function AdminArchitects() {
|
|||
},
|
||||
});
|
||||
|
||||
if (authLoading || !isAuthenticated) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center">
|
||||
<div className="text-primary animate-pulse">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const filteredProfiles = profiles?.filter((p: any) =>
|
||||
p.username?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
p.email?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { useEffect } from "react";
|
||||
import { Link, useLocation } from "wouter";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
import {
|
||||
|
|
@ -7,26 +6,9 @@ import {
|
|||
} from "lucide-react";
|
||||
|
||||
export default function AdminCredentials() {
|
||||
const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth();
|
||||
const { user, logout } = useAuth();
|
||||
const [, setLocation] = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (!authLoading && !isAuthenticated) {
|
||||
setLocation("/login");
|
||||
}
|
||||
}, 200);
|
||||
return () => clearTimeout(timer);
|
||||
}, [authLoading, isAuthenticated, setLocation]);
|
||||
|
||||
if (authLoading || !isAuthenticated) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center">
|
||||
<div className="text-primary animate-pulse">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
setLocation("/");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { useEffect } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Link, useLocation } from "wouter";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
|
@ -9,18 +8,9 @@ import {
|
|||
} from "lucide-react";
|
||||
|
||||
export default function AdminLogs() {
|
||||
const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth();
|
||||
const { user, logout } = useAuth();
|
||||
const [, setLocation] = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (!authLoading && !isAuthenticated) {
|
||||
setLocation("/login");
|
||||
}
|
||||
}, 200);
|
||||
return () => clearTimeout(timer);
|
||||
}, [authLoading, isAuthenticated, setLocation]);
|
||||
|
||||
const { data: logs, isLoading } = useQuery({
|
||||
queryKey: ["auth-logs"],
|
||||
queryFn: async () => {
|
||||
|
|
@ -28,17 +18,8 @@ export default function AdminLogs() {
|
|||
if (!res.ok) throw new Error("Failed to fetch logs");
|
||||
return res.json();
|
||||
},
|
||||
enabled: isAuthenticated,
|
||||
});
|
||||
|
||||
if (authLoading || !isAuthenticated) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center">
|
||||
<div className="text-primary animate-pulse">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
setLocation("/");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { Link, useLocation } from "wouter";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
import {
|
||||
|
|
@ -27,7 +27,7 @@ const defaultSettings: NotificationSetting[] = [
|
|||
];
|
||||
|
||||
export default function AdminNotifications() {
|
||||
const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth();
|
||||
const { user, logout } = useAuth();
|
||||
const [, setLocation] = useLocation();
|
||||
const [settings, setSettings] = useState<NotificationSetting[]>(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
|
|
@ -44,15 +44,6 @@ export default function AdminNotifications() {
|
|||
});
|
||||
const [saved, setSaved] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (!authLoading && !isAuthenticated) {
|
||||
setLocation("/login");
|
||||
}
|
||||
}, 200);
|
||||
return () => clearTimeout(timer);
|
||||
}, [authLoading, isAuthenticated, setLocation]);
|
||||
|
||||
const toggleSetting = (id: string) => {
|
||||
setSettings((prev) =>
|
||||
prev.map((s) => (s.id === id ? { ...s, enabled: !s.enabled } : s))
|
||||
|
|
@ -67,14 +58,6 @@ export default function AdminNotifications() {
|
|||
setTimeout(() => setSaved(false), 3000);
|
||||
};
|
||||
|
||||
if (authLoading || !isAuthenticated) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center">
|
||||
<div className="text-primary animate-pulse">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
setLocation("/");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { useEffect } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Link, useLocation } from "wouter";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
|
@ -9,35 +8,17 @@ import {
|
|||
} from "lucide-react";
|
||||
|
||||
export default function AdminProjects() {
|
||||
const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth();
|
||||
const { user, logout } = useAuth();
|
||||
const [, setLocation] = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (!authLoading && !isAuthenticated) {
|
||||
setLocation("/login");
|
||||
}
|
||||
}, 200);
|
||||
return () => clearTimeout(timer);
|
||||
}, [authLoading, isAuthenticated, setLocation]);
|
||||
|
||||
const { data: projects, isLoading } = useQuery({
|
||||
queryKey: ["projects"],
|
||||
queryFn: async () => {
|
||||
const res = await fetch("/api/projects");
|
||||
return res.json();
|
||||
},
|
||||
enabled: isAuthenticated,
|
||||
});
|
||||
|
||||
if (authLoading || !isAuthenticated) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center">
|
||||
<div className="text-primary animate-pulse">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
setLocation("/");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { useEffect } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Link, useLocation } from "wouter";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
|
@ -9,18 +8,9 @@ import {
|
|||
} from "lucide-react";
|
||||
|
||||
export default function AdminSites() {
|
||||
const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth();
|
||||
const { user, logout } = useAuth();
|
||||
const [, setLocation] = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (!authLoading && !isAuthenticated) {
|
||||
setLocation("/login");
|
||||
}
|
||||
}, 200);
|
||||
return () => clearTimeout(timer);
|
||||
}, [authLoading, isAuthenticated, setLocation]);
|
||||
|
||||
const { data: sites, isLoading } = useQuery({
|
||||
queryKey: ["sites"],
|
||||
queryFn: async () => {
|
||||
|
|
@ -28,17 +18,8 @@ export default function AdminSites() {
|
|||
if (!res.ok) throw new Error("Failed to fetch sites");
|
||||
return res.json();
|
||||
},
|
||||
enabled: isAuthenticated,
|
||||
});
|
||||
|
||||
if (authLoading || !isAuthenticated) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center">
|
||||
<div className="text-primary animate-pulse">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
setLocation("/");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { useEffect } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Link, useLocation } from "wouter";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
|
@ -10,25 +9,15 @@ import {
|
|||
import gridBg from '@assets/generated_images/dark_subtle_digital_grid_texture.png';
|
||||
|
||||
export default function Admin() {
|
||||
const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth();
|
||||
const { user, logout } = useAuth();
|
||||
const [, setLocation] = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (!authLoading && !isAuthenticated) {
|
||||
setLocation("/login");
|
||||
}
|
||||
}, 200);
|
||||
return () => clearTimeout(timer);
|
||||
}, [authLoading, isAuthenticated, setLocation]);
|
||||
|
||||
const { data: metrics } = useQuery({
|
||||
queryKey: ["metrics"],
|
||||
queryFn: async () => {
|
||||
const res = await fetch("/api/metrics");
|
||||
return res.json();
|
||||
},
|
||||
enabled: isAuthenticated,
|
||||
});
|
||||
|
||||
const { data: profiles } = useQuery({
|
||||
|
|
@ -37,7 +26,6 @@ export default function Admin() {
|
|||
const res = await fetch("/api/profiles");
|
||||
return res.json();
|
||||
},
|
||||
enabled: isAuthenticated,
|
||||
});
|
||||
|
||||
const { data: projects } = useQuery({
|
||||
|
|
@ -46,21 +34,8 @@ export default function Admin() {
|
|||
const res = await fetch("/api/projects");
|
||||
return res.json();
|
||||
},
|
||||
enabled: isAuthenticated,
|
||||
});
|
||||
|
||||
if (authLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center">
|
||||
<div className="text-primary animate-pulse">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
setLocation("/");
|
||||
|
|
|
|||
Loading…
Reference in a new issue