From 13a32b607d2b1abc6ae9124fd565615d481fd9a7 Mon Sep 17 00:00:00 2001 From: TanCodeX Date: Sun, 5 Jul 2026 21:42:06 +0530 Subject: [PATCH 1/7] refactor: modularize application routing and implement layout-based architecture using React Router --- src/App.tsx | 392 +------------------------- src/layouts/AdminLayout.tsx | 11 + src/layouts/MainLayout.tsx | 16 ++ src/layouts/ProtectedLayout.tsx | 11 + src/layouts/ProtectedMentorLayout.tsx | 11 + 5 files changed, 59 insertions(+), 382 deletions(-) create mode 100644 src/layouts/AdminLayout.tsx create mode 100644 src/layouts/MainLayout.tsx create mode 100644 src/layouts/ProtectedLayout.tsx create mode 100644 src/layouts/ProtectedMentorLayout.tsx diff --git a/src/App.tsx b/src/App.tsx index 88a588d4..39a431c3 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 } 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,380 +10,11 @@ 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"; -import Chatbot from "./components/Chatbot/Chatbot"; -import StreakBadge from "./components/StreakBadge"; -import CookieConsentBanner from "./components/CookieConsentBanner"; -import FloatingAI from "./components/FloatingAI"; -import MouseSparkles from "./components/MouseSparkles"; -import BackToTop from "./components/BackToTop"; // ? ADDED THIS LINE -import { useAuth } from "@/contexts/useAuth"; -import SplashScreen from "./components/SplashScreen"; - - - - -// Lazy-loaded page & route-specific components (code-split per route) -const Landing = React.lazy(() => import("./pages/Landing")); -const Index = React.lazy(() => import("./pages/Index")); -const NotFound = React.lazy(() => import("./pages/NotFound")); -const Dashboard = React.lazy(() => import("./pages/Dashboard")); -const MentorDashboard = React.lazy(() => import("./pages/MentorDashboard")); -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 Login = React.lazy(() => import("./pages/Login")); -const Signup = React.lazy(() => import("./pages/Signup")); -const Onboarding = React.lazy(() => import("./pages/Onboarding")); -const Profile = React.lazy(() => import("./pages/Profile")); -const EditProfile = React.lazy(() => import("./pages/EditProfile")); -const Notifications = React.lazy(() => import("./pages/Notifications")); -const Leaderboard = React.lazy(() => import("./pages/Leaderboard")); -const Admin = React.lazy(() => import("./pages/Admin")); -const ForgotPassword = React.lazy(() => import("./pages/ForgotPassword")); -const ResetPassword = React.lazy(() => import("./pages/ResetPassword")); -const AnonymousDoubts = React.lazy(() => import("./pages/AnonymousDoubts")); -const AIPage = React.lazy(() => import("./pages/aipage")); -const ContributorDashboard = React.lazy(() => import("./pages/ContributorDashboard")); -const BecomeMentor = React.lazy(() => import("./pages/BecomeMentor")); -const Portfolio = React.lazy(() => import("./pages/Portfolio")); -const AuthCallback = React.lazy(() => import("./pages/AuthCallback")); -const PublicPortfolio = React.lazy(() => import("./pages/PublicPortfolio")); -const ResourceHub = React.lazy(() => import("@/pages/ResourceHub")); -const StudyRooms = React.lazy(() => import("./components/StudyRooms")); -const Room = React.lazy(() => import("./components/Room/Room")); -const Contact = React.lazy(() => import("./pages/Contact")); -const PrivacyPolicy = React.lazy(() => import("./pages/privacy")); -const CookiesPolicy = React.lazy(() => import("./pages/cookies-policy")); -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 TermsAndConditions = React.lazy( - () => import("./pages/TermsAndConditions") -); +import { router } from "@/router"; const queryClient = new QueryClient(); -const WithNav = ({ children }: { children: React.ReactNode }) => { - const { user } = useAuth(); - return ( - <> - - {user && } - {children} - - ); -}; - -function AppContent() { - const { user } = useAuth(); - - const [loading, setLoading] = useState(true); - - useEffect(() => { - const timer = setTimeout(() => setLoading(false), 2500); - return () => clearTimeout(timer); - }, []); - - if (loading) { - return ; - } - - return ( - <> - - - -
}> - - : } - /> - - } /> - } /> - } /> - - - - - - } - /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - - - - - } - /> - - - - - - } - /> - - - - - - - } - /> - - - - - } - /> - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - } /> - -
- - {user && ( - <> - - - - )} - - {/* ? ADDED THIS LINE */} - - ); -} - function App() { return ( @@ -392,15 +23,13 @@ function App() { - - - - - - - - - + + + + + + + @@ -408,4 +37,3 @@ function App() { } export default App; -// fix/error-boundaries 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 ( + + + + ); +} From 1506b23f65edfd5939509bfefd137df67d67b39a Mon Sep 17 00:00:00 2001 From: TanCodeX Date: Sun, 5 Jul 2026 21:42:18 +0530 Subject: [PATCH 2/7] feat: implement modular react-router-dom configuration with a persistent root layout --- src/layouts/RootLayout.tsx | 50 +++++++++++++++++++++++++++++++++++++ src/router/admin.routes.tsx | 14 +++++++++++ src/router/auth.routes.tsx | 22 ++++++++++++++++ src/router/index.tsx | 23 +++++++++++++++++ 4 files changed, 109 insertions(+) create mode 100644 src/layouts/RootLayout.tsx create mode 100644 src/router/admin.routes.tsx create mode 100644 src/router/auth.routes.tsx create mode 100644 src/router/index.tsx 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/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, + ], + }, +]); From f7e30cf359002b75b5ef19d902ba4163e97ec2eb Mon Sep 17 00:00:00 2001 From: TanCodeX Date: Sun, 5 Jul 2026 21:42:33 +0530 Subject: [PATCH 3/7] feat: implement modularized application routing for public, protected, mentor, and settings pages --- src/router/mentor.routes.tsx | 14 +++++++++ src/router/protected.routes.tsx | 56 +++++++++++++++++++++++++++++++++ src/router/public.routes.tsx | 33 +++++++++++++++++++ src/router/settings.routes.tsx | 25 +++++++++++++++ 4 files changed, 128 insertions(+) create mode 100644 src/router/mentor.routes.tsx create mode 100644 src/router/protected.routes.tsx create mode 100644 src/router/public.routes.tsx create mode 100644 src/router/settings.routes.tsx 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: ( + + + + ), + }, +]; From 4ddd2ba3516bcc265f4ea8aa4fefeb7994411a7e Mon Sep 17 00:00:00 2001 From: TanCodeX Date: Thu, 16 Jul 2026 10:17:39 +0530 Subject: [PATCH 4/7] fix: resolve local storage access, update test paths, and adjust supabase query patterns across hooks and tests --- backend/tests/uploadPhoto.test.js | 2 +- src/hooks/useResources.ts | 4 +--- src/hooks/useSkillEndorsements.ts | 6 +++--- src/pages/Contact.test.tsx | 4 +++- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/tests/uploadPhoto.test.js b/backend/tests/uploadPhoto.test.js index 5ba2ebe3..7eca172d 100644 --- a/backend/tests/uploadPhoto.test.js +++ b/backend/tests/uploadPhoto.test.js @@ -10,7 +10,7 @@ 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) ── vi.mock("../utils/supabase.js", () => ({ diff --git a/src/hooks/useResources.ts b/src/hooks/useResources.ts index 02fb255e..6e27f0bf 100644 --- a/src/hooks/useResources.ts +++ b/src/hooks/useResources.ts @@ -63,12 +63,10 @@ export const useResources = (filters?: ResourceFilters) => { return; } - const { data: savedData, error: savedError } = await safeSupabaseCall( + const savedData = await safeSupabaseCall( () => (supabase as any).from("saved_resources").select("resource_id").eq("user_id", user.id).abortSignal(controller.signal) ); - if (savedError) throw savedError; - savedResourceIds = (savedData as SavedResource[] | null)?.map( (item) => item.resource_id diff --git a/src/hooks/useSkillEndorsements.ts b/src/hooks/useSkillEndorsements.ts index 0da3b5e9..ab745458 100644 --- a/src/hooks/useSkillEndorsements.ts +++ b/src/hooks/useSkillEndorsements.ts @@ -47,7 +47,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) @@ -115,7 +115,7 @@ export function useSkillEndorsements({ try { if (isRemoving) { - const { error } = await supabase + const { error } = await (supabase as any) .from("skill_endorsements") .delete() .match({ @@ -125,7 +125,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/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 }); From bdc55137840626edca3d83ffde3f2c727ebd9ec1 Mon Sep 17 00:00:00 2001 From: TanCodeX Date: Thu, 16 Jul 2026 11:12:24 +0530 Subject: [PATCH 5/7] feat: enable RLS for peer_submissions and peer_reviews tables --- supabase/migrations/20260617000000_consolidate_rls_policies.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/supabase/migrations/20260617000000_consolidate_rls_policies.sql b/supabase/migrations/20260617000000_consolidate_rls_policies.sql index a3dcc635..bcf6417e 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); From a862c2b7879fa3486bc5b3ab20a8c34f4e71e2b0 Mon Sep 17 00:00:00 2001 From: TanCodeX Date: Thu, 16 Jul 2026 11:16:22 +0530 Subject: [PATCH 6/7] test: update storageUploadMock to destroy streams during upload testing --- backend/tests/uploadPhoto.test.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/tests/uploadPhoto.test.js b/backend/tests/uploadPhoto.test.js index 42e121f5..7e86e2b3 100644 --- a/backend/tests/uploadPhoto.test.js +++ b/backend/tests/uploadPhoto.test.js @@ -16,9 +16,12 @@ const profilesUploadDir = path.resolve(__dirname, "../../uploads/profiles"); // 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(() => - Promise.resolve({ data: { path: "mock-path" }, error: null }) - ); + const storageUploadMock = vi.fn((path, stream) => { + if (stream && typeof stream.destroy === 'function') { + stream.destroy(); + } + return Promise.resolve({ data: { path: "mock-path" }, error: null }); + }); const storageFromMock = vi.fn(() => ({ upload: storageUploadMock, getPublicUrl: (filePath) => ({ From ae441cc8c50dbdd97ac51217a22fad4a580c81ef Mon Sep 17 00:00:00 2001 From: TanCodeX Date: Sat, 18 Jul 2026 20:38:28 +0530 Subject: [PATCH 7/7] fix: handle potential ReadStream errors during file uploads and update test mocks accordingly --- backend/controllers/uploadController.js | 5 +++++ backend/tests/uploadPhoto.test.js | 9 +++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/backend/controllers/uploadController.js b/backend/controllers/uploadController.js index 421ac893..b9887fc6 100644 --- a/backend/controllers/uploadController.js +++ b/backend/controllers/uploadController.js @@ -126,6 +126,11 @@ export const handleUpload = async (req, res, next) => { // Upload to Supabase Storage using a ReadStream const fileStream = fs.createReadStream(file.path); + + // Prevent unhandled stream errors if the file is deleted or fails to read + fileStream.on("error", (err) => { + console.error("ReadStream error:", err); + }); const { data, error } = await supabaseAdmin.storage .from(folder) diff --git a/backend/tests/uploadPhoto.test.js b/backend/tests/uploadPhoto.test.js index 9757cdcb..50b55625 100644 --- a/backend/tests/uploadPhoto.test.js +++ b/backend/tests/uploadPhoto.test.js @@ -18,8 +18,13 @@ const profilesUploadDir = path.resolve(__dirname, "../../uploads/profiles"); // can assert on the path passed to storage.upload(). const { storageUploadMock, storageFromMock } = vi.hoisted(() => { const storageUploadMock = vi.fn((path, stream) => { - if (stream && typeof stream.destroy === 'function') { - stream.destroy(); + 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 }); });