diff --git a/client/src/App.tsx b/client/src/App.tsx
index 9a3f249..def52dc 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -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() {
-
-
-
-
-
-
-
-
-
-
-
+ {() => }
+ {() => }
+ {() => }
+ {() => }
+ {() => }
+ {() => }
+ {() => }
+ {() => }
+ {() => }
+ {() => }
+ {() => }
diff --git a/client/src/components/ProtectedRoute.tsx b/client/src/components/ProtectedRoute.tsx
new file mode 100644
index 0000000..adc163c
--- /dev/null
+++ b/client/src/components/ProtectedRoute.tsx
@@ -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 (
+
+ );
+ }
+
+ if (!isAuthenticated) {
+ return null;
+ }
+
+ return <>{children}>;
+}
diff --git a/client/src/lib/iconMap.tsx b/client/src/lib/iconMap.tsx
new file mode 100644
index 0000000..10dc147
--- /dev/null
+++ b/client/src/lib/iconMap.tsx
@@ -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> = {
+ "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 ;
+ }
+
+ if (iconName.length <= 4) {
+ return iconName;
+ }
+
+ return "🏆";
+}
diff --git a/client/src/pages/admin-achievements.tsx b/client/src/pages/admin-achievements.tsx
index c54c0f3..8f44a8d 100644
--- a/client/src/pages/admin-achievements.tsx
+++ b/client/src/pages/admin-achievements.tsx
@@ -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 (
-
- );
- }
-
const handleLogout = async () => {
await logout();
setLocation("/");
@@ -84,7 +66,7 @@ export default function AdminAchievements() {
>
- {achievement.icon || '🏆'}
+ {getIcon(achievement.icon)}
{achievement.name}
diff --git a/client/src/pages/admin-activity.tsx b/client/src/pages/admin-activity.tsx
index 1cc4b92..f418b3b 100644
--- a/client/src/pages/admin-activity.tsx
+++ b/client/src/pages/admin-activity.tsx
@@ -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
([]);
const [lastRefresh, setLastRefresh] = useState(new Date());
const [seenEventIds, setSeenEventIds] = useState>(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 (
-
- );
- }
-
const handleLogout = async () => {
await logout();
setLocation("/");
diff --git a/client/src/pages/admin-aegis.tsx b/client/src/pages/admin-aegis.tsx
index 59e9e27..20e0d00 100644
--- a/client/src/pages/admin-aegis.tsx
+++ b/client/src/pages/admin-aegis.tsx
@@ -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 (
-
- );
- }
-
const handleLogout = async () => {
await logout();
setLocation("/");
diff --git a/client/src/pages/admin-applications.tsx b/client/src/pages/admin-applications.tsx
index e63fede..e9417a4 100644
--- a/client/src/pages/admin-applications.tsx
+++ b/client/src/pages/admin-applications.tsx
@@ -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(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 (
-
- );
- }
-
const handleLogout = async () => {
await logout();
setLocation("/");
diff --git a/client/src/pages/admin-architects.tsx b/client/src/pages/admin-architects.tsx
index ff8d5cc..f41a1f0 100644
--- a/client/src/pages/admin-architects.tsx
+++ b/client/src/pages/admin-architects.tsx
@@ -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(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 (
-
- );
- }
-
const filteredProfiles = profiles?.filter((p: any) =>
p.username?.toLowerCase().includes(searchQuery.toLowerCase()) ||
p.email?.toLowerCase().includes(searchQuery.toLowerCase())
diff --git a/client/src/pages/admin-credentials.tsx b/client/src/pages/admin-credentials.tsx
index 42cafda..7bdeba9 100644
--- a/client/src/pages/admin-credentials.tsx
+++ b/client/src/pages/admin-credentials.tsx
@@ -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 (
-
- );
- }
-
const handleLogout = async () => {
await logout();
setLocation("/");
diff --git a/client/src/pages/admin-logs.tsx b/client/src/pages/admin-logs.tsx
index 23ada3c..41b9fab 100644
--- a/client/src/pages/admin-logs.tsx
+++ b/client/src/pages/admin-logs.tsx
@@ -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 (
-
- );
- }
-
const handleLogout = async () => {
await logout();
setLocation("/");
diff --git a/client/src/pages/admin-notifications.tsx b/client/src/pages/admin-notifications.tsx
index 3252f11..ad65246 100644
--- a/client/src/pages/admin-notifications.tsx
+++ b/client/src/pages/admin-notifications.tsx
@@ -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(() => {
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 (
-
- );
- }
-
const handleLogout = async () => {
await logout();
setLocation("/");
diff --git a/client/src/pages/admin-projects.tsx b/client/src/pages/admin-projects.tsx
index 849a11f..86eb837 100644
--- a/client/src/pages/admin-projects.tsx
+++ b/client/src/pages/admin-projects.tsx
@@ -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 (
-
- );
- }
-
const handleLogout = async () => {
await logout();
setLocation("/");
diff --git a/client/src/pages/admin-sites.tsx b/client/src/pages/admin-sites.tsx
index 9adb82b..6a6cb47 100644
--- a/client/src/pages/admin-sites.tsx
+++ b/client/src/pages/admin-sites.tsx
@@ -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 (
-
- );
- }
-
const handleLogout = async () => {
await logout();
setLocation("/");
diff --git a/client/src/pages/admin.tsx b/client/src/pages/admin.tsx
index f378036..697ae98 100644
--- a/client/src/pages/admin.tsx
+++ b/client/src/pages/admin.tsx
@@ -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 (
-
- );
- }
-
- if (!isAuthenticated) {
- return null;
- }
-
const handleLogout = async () => {
await logout();
setLocation("/");