diff --git a/backend/tests/uploadPhoto.test.js b/backend/tests/uploadPhoto.test.js
index 5d4cc9dd..50b55625 100644
--- a/backend/tests/uploadPhoto.test.js
+++ b/backend/tests/uploadPhoto.test.js
@@ -11,17 +11,22 @@ import { errorHandler } from "../middlewares/errorHandler.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
-const profilesUploadDir = path.resolve(__dirname, "../uploads/profiles");
+const profilesUploadDir = path.resolve(__dirname, "../../uploads/profiles");
// ── Supabase stub (requireAuth fast-path won't reach it, but the import needs it) ──
// Extended with a storage stub so the /api/upload suite below (uploadController.js)
// can assert on the path passed to storage.upload().
const { storageUploadMock, storageFromMock } = vi.hoisted(() => {
- const storageUploadMock = vi.fn(async (_filePath, fileStream) => {
- const finished = once(fileStream, "end");
- fileStream.resume();
- await finished;
- return { data: { path: "mock-path" }, error: null };
+ const storageUploadMock = vi.fn((path, stream) => {
+ if (stream) {
+ if (typeof stream.on === 'function') {
+ stream.on('error', () => {}); // swallow unhandled stream errors in mock
+ }
+ if (typeof stream.destroy === 'function') {
+ stream.destroy();
+ }
+ }
+ return Promise.resolve({ data: { path: "mock-path" }, error: null });
});
const storageFromMock = vi.fn(() => ({
upload: storageUploadMock,
diff --git a/src/App.tsx b/src/App.tsx
index ef7a46e9..2eea7ff2 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,6 +1,6 @@
-import React, { useEffect, Suspense, useState, useRef } from "react";
+import React from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import { BrowserRouter, Routes, Route, Navigate, Router, useLocation } from "react-router-dom";
+import { RouterProvider } from "react-router-dom";
import { Toaster } from "@/components/ui/toaster";
import { Toaster as Sonner } from "@/components/ui/sonner";
@@ -10,9 +10,6 @@ import { AuthProvider } from "@/contexts/AuthContext";
import { CookieConsentProvider } from "@/contexts/CookieConsentContext";
import { RoleProvider } from "@/contexts/RoleContext";
import { ThemeProvider } from "@/contexts/ThemeContext";
-import AdminRoute from "@/components/AdminRoute";
-import ProtectedRoute from "@/components/ProtectedRoute";
-import ProtectedMentorRoute from "@/components/ProtectedMentorRoute";
// Global layout components - rendered on every page, keep static
import Navbar from "./components/Navbar/Navbar";
@@ -402,15 +399,13 @@ function App() {
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/src/hooks/useResources.ts b/src/hooks/useResources.ts
index 443e0761..b64d9b5c 100644
--- a/src/hooks/useResources.ts
+++ b/src/hooks/useResources.ts
@@ -63,10 +63,10 @@ export const useResources = (filters?: ResourceFilters) => {
return;
}
- const savedData = await safeSupabaseCall(
+ const savedData = await safeSupabaseCall(
() => (supabase as any).from("saved_resources").select("resource_id").eq("user_id", user.id).abortSignal(controller.signal)
);
-
+
savedResourceIds =
(savedData as SavedResource[] | null)?.map(
(item) => item.resource_id
diff --git a/src/hooks/useSkillEndorsements.ts b/src/hooks/useSkillEndorsements.ts
index af8f000b..9d40ad61 100644
--- a/src/hooks/useSkillEndorsements.ts
+++ b/src/hooks/useSkillEndorsements.ts
@@ -64,7 +64,7 @@ export function useSkillEndorsements({
}
try {
- const { data, error } = await supabase
+ const { data, error } = await (supabase as any)
.from("skill_endorsements")
.select("skill, endorser_id")
.eq("endorsed_user_id", profileUserId)
@@ -133,7 +133,7 @@ export function useSkillEndorsements({
try {
if (isRemoving) {
- const { error } = await supabase
+ const { error } = await (supabase as any)
.from("skill_endorsements")
.delete()
.match({
@@ -143,7 +143,7 @@ export function useSkillEndorsements({
});
if (error) throw error;
} else {
- const { error } = await supabase
+ const { error } = await (supabase as any)
.from("skill_endorsements")
.insert({
skill,
diff --git a/src/layouts/AdminLayout.tsx b/src/layouts/AdminLayout.tsx
new file mode 100644
index 00000000..d76b2208
--- /dev/null
+++ b/src/layouts/AdminLayout.tsx
@@ -0,0 +1,11 @@
+import React from "react";
+import AdminRoute from "@/components/AdminRoute";
+import MainLayout from "./MainLayout";
+
+export default function AdminLayout() {
+ return (
+
+
+
+ );
+}
diff --git a/src/layouts/MainLayout.tsx b/src/layouts/MainLayout.tsx
new file mode 100644
index 00000000..fb536b81
--- /dev/null
+++ b/src/layouts/MainLayout.tsx
@@ -0,0 +1,16 @@
+import React from "react";
+import { Outlet } from "react-router-dom";
+import Navbar from "@/components/Navbar/Navbar";
+import StreakBadge from "@/components/StreakBadge";
+import { useAuth } from "@/contexts/useAuth";
+
+export default function MainLayout() {
+ const { user } = useAuth();
+ return (
+ <>
+
+ {user && }
+
+ >
+ );
+}
diff --git a/src/layouts/ProtectedLayout.tsx b/src/layouts/ProtectedLayout.tsx
new file mode 100644
index 00000000..ab2f2c94
--- /dev/null
+++ b/src/layouts/ProtectedLayout.tsx
@@ -0,0 +1,11 @@
+import React from "react";
+import ProtectedRoute from "@/components/ProtectedRoute";
+import MainLayout from "./MainLayout";
+
+export default function ProtectedLayout() {
+ return (
+
+
+
+ );
+}
diff --git a/src/layouts/ProtectedMentorLayout.tsx b/src/layouts/ProtectedMentorLayout.tsx
new file mode 100644
index 00000000..b5507c3a
--- /dev/null
+++ b/src/layouts/ProtectedMentorLayout.tsx
@@ -0,0 +1,11 @@
+import React from "react";
+import ProtectedMentorRoute from "@/components/ProtectedMentorRoute";
+import MainLayout from "./MainLayout";
+
+export default function ProtectedMentorLayout() {
+ return (
+
+
+
+ );
+}
diff --git a/src/layouts/RootLayout.tsx b/src/layouts/RootLayout.tsx
new file mode 100644
index 00000000..4d94a018
--- /dev/null
+++ b/src/layouts/RootLayout.tsx
@@ -0,0 +1,50 @@
+import React, { useState, useEffect, Suspense } from "react";
+import { Outlet } from "react-router-dom";
+import { useAuth } from "@/contexts/useAuth";
+
+import SplashScreen from "@/components/SplashScreen";
+import MouseSparkles from "@/components/MouseSparkles";
+import CookieConsentBanner from "@/components/CookieConsentBanner";
+import Chatbot from "@/components/Chatbot/Chatbot";
+import FloatingAI from "@/components/FloatingAI";
+import BackToTop from "@/components/BackToTop";
+
+export default function RootLayout() {
+ const { user } = useAuth();
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ const timer = setTimeout(() => setLoading(false), 2500);
+ return () => clearTimeout(timer);
+ }, []);
+
+ if (loading) {
+ return ;
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+ }
+ >
+
+
+
+ {user && (
+ <>
+
+
+ >
+ )}
+
+
+ >
+ );
+}
diff --git a/src/pages/Contact.test.tsx b/src/pages/Contact.test.tsx
index ca4459e0..07fa0899 100644
--- a/src/pages/Contact.test.tsx
+++ b/src/pages/Contact.test.tsx
@@ -29,7 +29,9 @@ describe("Contact", () => {
beforeEach(() => {
vi.clearAllMocks();
- localStorage?.clear();
+ if (typeof window !== "undefined" && window.localStorage && typeof window.localStorage.clear === "function") {
+ window.localStorage.clear();
+ }
(useToast as any).mockReturnValue({ toast });
(supabase.from as any).mockReturnValue({ insert });
diff --git a/src/router/admin.routes.tsx b/src/router/admin.routes.tsx
new file mode 100644
index 00000000..bcf53a48
--- /dev/null
+++ b/src/router/admin.routes.tsx
@@ -0,0 +1,14 @@
+import React from "react";
+import { RouteObject } from "react-router-dom";
+import AdminLayout from "@/layouts/AdminLayout";
+
+const Admin = React.lazy(() => import("@/pages/Admin"));
+
+export const adminRoutes: RouteObject[] = [
+ {
+ element: ,
+ children: [
+ { path: "/admin", element: },
+ ],
+ },
+];
diff --git a/src/router/auth.routes.tsx b/src/router/auth.routes.tsx
new file mode 100644
index 00000000..9cc45f71
--- /dev/null
+++ b/src/router/auth.routes.tsx
@@ -0,0 +1,22 @@
+import React from "react";
+import { RouteObject } from "react-router-dom";
+
+const Login = React.lazy(() => import("@/pages/Login"));
+const Signup = React.lazy(() => import("@/pages/Signup"));
+const ForgotPassword = React.lazy(() => import("@/pages/ForgotPassword"));
+const ResetPassword = React.lazy(() => import("@/pages/ResetPassword"));
+const AuthCallback = React.lazy(() => import("@/pages/AuthCallback"));
+const Onboarding = React.lazy(() => import("@/pages/Onboarding"));
+const PublicPortfolio = React.lazy(() => import("@/pages/PublicPortfolio"));
+const BecomeMentor = React.lazy(() => import("@/pages/BecomeMentor"));
+
+export const authRoutes: RouteObject[] = [
+ { path: "/login", element: },
+ { path: "/signup", element: },
+ { path: "/forgot-password", element: },
+ { path: "/reset-password", element: },
+ { path: "/auth/callback", element: },
+ { path: "/onboarding", element: },
+ { path: "/portfolio/:slug", element: },
+ { path: "/become-mentor", element: },
+];
diff --git a/src/router/index.tsx b/src/router/index.tsx
new file mode 100644
index 00000000..0c59fa49
--- /dev/null
+++ b/src/router/index.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+import { createBrowserRouter } from "react-router-dom";
+import RootLayout from "@/layouts/RootLayout";
+import { publicRoutes } from "./public.routes";
+import { authRoutes } from "./auth.routes";
+import { protectedRoutes } from "./protected.routes";
+import { settingsRoutes } from "./settings.routes";
+import { mentorRoutes } from "./mentor.routes";
+import { adminRoutes } from "./admin.routes";
+
+export const router = createBrowserRouter([
+ {
+ element: ,
+ children: [
+ ...publicRoutes,
+ ...authRoutes,
+ ...protectedRoutes,
+ ...settingsRoutes,
+ ...mentorRoutes,
+ ...adminRoutes,
+ ],
+ },
+]);
diff --git a/src/router/mentor.routes.tsx b/src/router/mentor.routes.tsx
new file mode 100644
index 00000000..018d9a7d
--- /dev/null
+++ b/src/router/mentor.routes.tsx
@@ -0,0 +1,14 @@
+import React from "react";
+import { RouteObject } from "react-router-dom";
+import ProtectedMentorLayout from "@/layouts/ProtectedMentorLayout";
+
+const MentorDashboard = React.lazy(() => import("@/pages/MentorDashboard"));
+
+export const mentorRoutes: RouteObject[] = [
+ {
+ element: ,
+ children: [
+ { path: "/mentor-dashboard", element: },
+ ],
+ },
+];
diff --git a/src/router/protected.routes.tsx b/src/router/protected.routes.tsx
new file mode 100644
index 00000000..466fc3e4
--- /dev/null
+++ b/src/router/protected.routes.tsx
@@ -0,0 +1,56 @@
+import React from "react";
+import { RouteObject } from "react-router-dom";
+import ProtectedLayout from "@/layouts/ProtectedLayout";
+import { useAuth } from "@/contexts/useAuth";
+
+const Dashboard = React.lazy(() => import("@/pages/Dashboard"));
+const LearnerDashboard = React.lazy(() => import("@/pages/LearnerDashboard"));
+const Discover = React.lazy(() => import("@/pages/Discover"));
+const Sessions = React.lazy(() => import("@/pages/Sessions"));
+const Messages = React.lazy(() => import("@/pages/Messages"));
+const Chat = React.lazy(() => import("@/pages/Chat"));
+const Notifications = React.lazy(() => import("@/pages/Notifications"));
+const Leaderboard = React.lazy(() => import("@/pages/Leaderboard"));
+const ResourceHub = React.lazy(() => import("@/pages/ResourceHub"));
+const Portfolio = React.lazy(() => import("@/pages/Portfolio"));
+const PeerReviewDashboard = React.lazy(() => import("@/pages/PeerReviewDashboard"));
+const SubmitForReview = React.lazy(() => import("@/pages/SubmitForReview"));
+const ReviewSubmission = React.lazy(() => import("@/pages/ReviewSubmission"));
+const MockInterview = React.lazy(() => import("@/pages/MockInterview"));
+const AnonymousDoubts = React.lazy(() => import("@/pages/AnonymousDoubts"));
+const ContributorDashboard = React.lazy(() => import("@/pages/ContributorDashboard"));
+const AIPage = React.lazy(() => import("@/pages/aipage"));
+const StudyRooms = React.lazy(() => import("@/components/StudyRooms"));
+const Room = React.lazy(() => import("@/components/Room/Room"));
+
+const MessagesRoute = () => {
+ const { user } = useAuth();
+ return ;
+};
+
+export const protectedRoutes: RouteObject[] = [
+ {
+ element: ,
+ children: [
+ { path: "/dashboard", element: },
+ { path: "/learner-dashboard", element: },
+ { path: "/discover", element: },
+ { path: "/sessions", element: },
+ { path: "/messages", element: },
+ { path: "/chat", element: },
+ { path: "/notifications", element: },
+ { path: "/leaderboard", element: },
+ { path: "/resources", element: },
+ { path: "/portfolio", element: },
+ { path: "/peer-review", element: },
+ { path: "/peer-review/new", element: },
+ { path: "/peer-review/:id", element: },
+ { path: "/mock-interview", element: },
+ { path: "/anonymous-doubts", element: },
+ { path: "/contributor-dashboard", element: },
+ { path: "/ai", element: },
+ { path: "/rooms", element: },
+ { path: "/rooms/:id", element: },
+ ],
+ },
+];
diff --git a/src/router/public.routes.tsx b/src/router/public.routes.tsx
new file mode 100644
index 00000000..92b9769f
--- /dev/null
+++ b/src/router/public.routes.tsx
@@ -0,0 +1,33 @@
+import React from "react";
+import { Navigate, RouteObject } from "react-router-dom";
+import MainLayout from "@/layouts/MainLayout";
+import { useAuth } from "@/contexts/useAuth";
+
+const Index = React.lazy(() => import("@/pages/Index"));
+const Contact = React.lazy(() => import("@/pages/Contact"));
+const PrivacyPolicy = React.lazy(() => import("@/pages/privacy"));
+const CookiesPolicy = React.lazy(() => import("@/pages/cookies-policy"));
+const TermsAndConditions = React.lazy(() => import("@/pages/TermsAndConditions"));
+const NotFound = React.lazy(() => import("@/pages/NotFound"));
+
+const IndexRoute = () => {
+ const { user } = useAuth();
+ return user ? : ;
+};
+
+export const publicRoutes: RouteObject[] = [
+ {
+ element: ,
+ children: [
+ { path: "/", element: },
+ { path: "/contact", element: },
+ { path: "/privacy-policy", element: },
+ { path: "/cookies-policy", element: },
+ { path: "/terms-and-conditions", element: },
+ ],
+ },
+ {
+ path: "*",
+ element: ,
+ },
+];
diff --git a/src/router/settings.routes.tsx b/src/router/settings.routes.tsx
new file mode 100644
index 00000000..62c55e4f
--- /dev/null
+++ b/src/router/settings.routes.tsx
@@ -0,0 +1,25 @@
+import React from "react";
+import { RouteObject } from "react-router-dom";
+import ProtectedRoute from "@/components/ProtectedRoute";
+
+const Profile = React.lazy(() => import("@/pages/Profile"));
+const EditProfile = React.lazy(() => import("@/pages/EditProfile"));
+
+export const settingsRoutes: RouteObject[] = [
+ {
+ path: "/profile",
+ element: (
+
+
+
+ ),
+ },
+ {
+ path: "/edit-profile",
+ element: (
+
+
+
+ ),
+ },
+];
diff --git a/supabase/migrations/20260617000000_consolidate_rls_policies.sql b/supabase/migrations/20260617000000_consolidate_rls_policies.sql
index bfc73b7a..ba63581b 100644
--- a/supabase/migrations/20260617000000_consolidate_rls_policies.sql
+++ b/supabase/migrations/20260617000000_consolidate_rls_policies.sql
@@ -339,6 +339,7 @@ CREATE POLICY "Users can delete peer connections"
ON public.peer_connections FOR DELETE USING (auth.uid() = sender_id OR auth.uid() = receiver_id);
-- peer_submissions
+ALTER TABLE public.peer_submissions ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view submissions"
ON public.peer_submissions FOR SELECT USING (true);
@@ -364,6 +365,7 @@ CREATE POLICY "Users can delete own submissions"
ON public.peer_submissions FOR DELETE USING (user_id = auth.uid());
-- peer_reviews
+ALTER TABLE public.peer_reviews ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view reviews"
ON public.peer_reviews FOR SELECT USING (true);