diff --git a/frontend/package-lock.json b/frontend/package-lock.json index fb9c8e6..b45677a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -59,7 +59,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1224,7 +1223,6 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1277,7 +1275,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1403,7 +1400,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -1857,7 +1853,6 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3000,7 +2995,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3070,7 +3064,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -3080,7 +3073,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -3100,7 +3092,6 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", - "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -3191,8 +3182,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -3476,7 +3466,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -3601,7 +3590,6 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 3d82063..7a7a1b2 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,4 +1,4 @@ -import { BrowserRouter, Routes, Route } from "react-router-dom"; +import { BrowserRouter, Routes, Route, useLocation } from "react-router-dom"; import { AuthProvider } from "./context/AuthContext"; import MainLayout from "./layouts/MainLayout"; import LandingPage from "./pages/LandingPage"; @@ -24,88 +24,102 @@ import GitHubIntelligencePage from "./pages/GitHubIntelligencePage"; import GitHubCallbackPage from "./pages/GitHubCallbackPage"; import ProtectedRoute from "./components/shared/ProtectedRoute"; import PublicRoute from "./components/shared/PublicRoute"; -import FAQSection from "./components/explore/FAQSection"; + +/** + * Inner router shell — lives inside BrowserRouter so it can call useLocation. + * Keys the animation wrapper on location.pathname so the page-fade-in CSS + * keyframe re-runs on actual page changes only (not same-path re-navigations). + */ +function AppRoutes() { + const location = useLocation(); + + return ( + +
+ + } /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + } /> + {/* GitHub OAuth callback — must be public, no auth required */} + } /> + } /> + + } /> + } /> + } /> + } /> + } /> + } /> + + + + } + /> + } /> + } /> + } /> + +
+
+ ); +} + export default function App() { return ( - - - } /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - } /> - {/* GitHub OAuth callback — must be public, no auth required */} - } /> - } /> - - } /> - } /> - } /> - } /> - } /> - } /> - - - - } - /> - } /> - } /> - } /> - - + - ); } diff --git a/frontend/src/components/codeforces/ConnectBanner.jsx b/frontend/src/components/codeforces/ConnectBanner.jsx index 0afa811..b16ca66 100644 --- a/frontend/src/components/codeforces/ConnectBanner.jsx +++ b/frontend/src/components/codeforces/ConnectBanner.jsx @@ -1,4 +1,3 @@ -import { useState } from "react"; import { Link } from "react-router-dom"; /** diff --git a/frontend/src/components/dashboard/DashboardExecutiveSummary.jsx b/frontend/src/components/dashboard/DashboardExecutiveSummary.jsx index aad2631..5918132 100644 --- a/frontend/src/components/dashboard/DashboardExecutiveSummary.jsx +++ b/frontend/src/components/dashboard/DashboardExecutiveSummary.jsx @@ -15,7 +15,7 @@ export default function DashboardExecutiveSummary() { setSummary(data.data.summary); setLoading(false); } - } catch (err) { + } catch { if (isMounted) { setError("Failed to fetch AI summary. Please try again later."); setLoading(false); diff --git a/frontend/src/components/github/GitHubComponents.jsx b/frontend/src/components/github/GitHubComponents.jsx index ffc696f..6974687 100644 --- a/frontend/src/components/github/GitHubComponents.jsx +++ b/frontend/src/components/github/GitHubComponents.jsx @@ -3,16 +3,7 @@ import { RadarChart, Radar, PolarGrid, PolarAngleAxis, Cell, PieChart, Pie, Legend, } from "recharts"; - -export const COLORS = ["#000","#222","#444","#666","#888","#aaa","#ccc"]; -export const fmt = (n) => n >= 1000 ? `${(n/1000).toFixed(1)}k` : String(n ?? 0); -export const timeAgo = (d) => { - const s = (Date.now() - new Date(d)) / 1000; - if (s < 60) return `${Math.floor(s)}s ago`; - if (s < 3600) return `${Math.floor(s/60)}m ago`; - if (s < 86400) return `${Math.floor(s/3600)}h ago`; - return `${Math.floor(s/86400)}d ago`; -}; +import { COLORS, fmt, timeAgo } from "../../utils/githubHelpers"; const TT = ({ contentStyle = {}, ...props }) => (

Favorite Languages in Starred

- {starredLangs?.slice(0, 6).map((l, i) => { + {starredLangs?.slice(0, 6).map((l) => { const max = starredLangs[0]?.count || 1; return (
diff --git a/frontend/src/components/shared/Navbar.jsx b/frontend/src/components/shared/Navbar.jsx index aa10e55..6f12806 100644 --- a/frontend/src/components/shared/Navbar.jsx +++ b/frontend/src/components/shared/Navbar.jsx @@ -265,8 +265,10 @@ export default function Navbar() { // ── Close mobile menu on route change ─────────────────────────────────── useEffect(() => { - setIsMenuOpen(false); - setMobileMegaOpen(false); + setTimeout(() => { + setIsMenuOpen(false); + setMobileMegaOpen(false); + }, 0); }, [location.pathname]); useEffect(() => { diff --git a/frontend/src/components/shared/ProtectedRoute.jsx b/frontend/src/components/shared/ProtectedRoute.jsx index 6a542c0..44d008a 100644 --- a/frontend/src/components/shared/ProtectedRoute.jsx +++ b/frontend/src/components/shared/ProtectedRoute.jsx @@ -1,11 +1,11 @@ import { Navigate } from "react-router-dom"; import { useAuth } from "../../context/AuthContext"; -import Loader from "./loaders/LoaderSwitcher"; +import { GuardSkeleton } from "./skeletons/PageSkeletons"; const ProtectedRoute = ({ children }) => { const { isAuthenticated, loading } = useAuth(); - if (loading) return ; + if (loading) return ; if (!isAuthenticated) return ; return children; diff --git a/frontend/src/components/shared/PublicRoute.jsx b/frontend/src/components/shared/PublicRoute.jsx index 1c590f8..f75a89c 100644 --- a/frontend/src/components/shared/PublicRoute.jsx +++ b/frontend/src/components/shared/PublicRoute.jsx @@ -1,11 +1,11 @@ import { Navigate } from "react-router-dom"; import { useAuth } from "../../context/AuthContext"; -import Loader from "./loaders/LoaderSwitcher"; +import { GuardSkeleton } from "./skeletons/PageSkeletons"; const PublicRoute = ({ children }) => { const { isAuthenticated, loading } = useAuth(); - if (loading) return ; + if (loading) return ; if (isAuthenticated) return ; return children; diff --git a/frontend/src/components/shared/skeletons/PageSkeletons.jsx b/frontend/src/components/shared/skeletons/PageSkeletons.jsx new file mode 100644 index 0000000..0e7081b --- /dev/null +++ b/frontend/src/components/shared/skeletons/PageSkeletons.jsx @@ -0,0 +1,73 @@ +function SkeletonBlock({ className = "" }) { + return
; +} + +/** + * Generic auth-guard loading placeholder — shown by ProtectedRoute / PublicRoute + * while authentication state is being resolved. Page-agnostic by design. + */ +export function GuardSkeleton() { + return ( +
+
+
+ + +
+
+ + + +
+
+
+ ); +} + +export function DashboardPageSkeleton() { + return ( +
+
+
+ + +
+
+ + + +
+ +
+
+ ); +} + +export function AccountCenterSkeleton() { + return ( +
+
+
+ + + +
+
+
+ +
+ + +
+
+
+
+ + + + +
+
+
+ ); +} diff --git a/frontend/src/context/AuthContext.jsx b/frontend/src/context/AuthContext.jsx index dcdc4ed..8814cc6 100644 --- a/frontend/src/context/AuthContext.jsx +++ b/frontend/src/context/AuthContext.jsx @@ -1,3 +1,4 @@ +/* eslint-disable react-refresh/only-export-components */ import { createContext, useContext, useState, useEffect } from "react"; import { getProfile } from "../services/userService"; @@ -24,7 +25,7 @@ export const AuthProvider = ({ children }) => { setToken(storedToken); const response = await getProfile(); setUser(response.data); - } catch (error) { + } catch { // Token expired or invalid localStorage.removeItem("token"); localStorage.removeItem("user"); diff --git a/frontend/src/index.css b/frontend/src/index.css index f1d8c73..f3fa2de 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1 +1,16 @@ @import "tailwindcss"; + +@keyframes page-fade-in { + from { + opacity: 0; + transform: translateY(6px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.page-fade-in { + animation: page-fade-in 260ms ease-out; +} diff --git a/frontend/src/pages/AccountCenterPage.jsx b/frontend/src/pages/AccountCenterPage.jsx index b206356..c4944b8 100644 --- a/frontend/src/pages/AccountCenterPage.jsx +++ b/frontend/src/pages/AccountCenterPage.jsx @@ -2,28 +2,12 @@ import { useAuth } from "../context/AuthContext"; import { useState, useEffect } from "react"; import { Link, useSearchParams } from "react-router-dom"; import { getProfile, deleteAccount } from "../services/userService"; +import { AccountCenterSkeleton } from "../components/shared/skeletons/PageSkeletons"; const API_BASE = import.meta.env.VITE_API_BASE_URL; // ── Helpers ────────────────────────────────────────────────────────────────── -const RANK_COLORS = { - "legendary grandmaster": "text-red-600", - "international grandmaster": "text-red-500", - grandmaster: "text-red-400", - "international master": "text-orange-500", - master: "text-orange-400", - "candidate master": "text-purple-600", - expert: "text-blue-600", - specialist: "text-cyan-600", - pupil: "text-green-600", - newbie: "text-gray-500", - unrated: "text-gray-400", -}; - -const rankColor = (rank = "") => - RANK_COLORS[(rank || "").toLowerCase()] || "text-black"; - // ── Sub-components ──────────────────────────────────────────────────────────── function SectionLabel({ text }) { @@ -273,7 +257,7 @@ function DangerZone({ onLogout }) { try { await deleteAccount(); onLogout(); // Logs the user out and redirects to login - } catch (err) { + } catch { alert("Failed to delete account. Please try again later."); setLoading(false); setConfirm(false); @@ -327,7 +311,7 @@ function DangerZone({ onLogout }) { // ── Main Page ───────────────────────────────────────────────────────────────── export default function AccountCenterPage() { - const { user, setUser, logout } = useAuth(); + const { user, setUser, logout, loading: authLoading } = useAuth(); const [searchParams] = useSearchParams(); const [banner, setBanner] = useState(""); @@ -337,15 +321,21 @@ export default function AccountCenterPage() { const username = searchParams.get("githubUsername"); if (status === "connected") { - setBanner(`GitHub account @${username || "connected"} linked successfully!`); - // Refresh user profile so the card shows the new GitHub identity - getProfile() - .then((res) => setUser(res.data)) - .catch(() => {}); + setTimeout(() => { + setBanner(`GitHub account @${username || "connected"} linked successfully!`); + // Refresh user profile so the card shows the new GitHub identity + getProfile() + .then((res) => setUser(res.data)) + .catch(() => {}); + }, 0); // Clean URL window.history.replaceState({}, "", "/account-center"); } - }, [searchParams]); + }, [searchParams, setUser]); + + if (authLoading || !user) { + return ; + } return (
diff --git a/frontend/src/pages/CodeforcesPage.jsx b/frontend/src/pages/CodeforcesPage.jsx index 2dfed82..d073839 100644 --- a/frontend/src/pages/CodeforcesPage.jsx +++ b/frontend/src/pages/CodeforcesPage.jsx @@ -57,8 +57,6 @@ function ActivityHeatmap({ dailyActivity = {} }) { return "bg-black border-black"; }; - const months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]; - return (
@@ -106,16 +104,7 @@ function RatingChart({ history = [] }) { const points = history.map((h, i) => `${toX(i)},${toY(h.newRating)}`).join(" "); - // Draw coloured band zones - const zones = [ - { maxR: 1200, color: "#e5e5e5", label: "Newbie" }, - { maxR: 1400, color: "#d4edda", label: "Pupil" }, - { maxR: 1600, color: "#cce5ff", label: "Specialist" }, - { maxR: 1900, color: "#d6d6ff", label: "Expert" }, - { maxR: 2100, color: "#ffe8cc", label: "CM" }, - { maxR: 2400, color: "#ffd6d6", label: "Master" }, - { maxR: Infinity, color: "#ffb3b3", label: "GM+" }, - ]; + // Rating history chart configuration return (
@@ -304,7 +293,6 @@ export default function CodeforcesPage() { ratingHistory, submissions, isConnected, - isPending, loading, syncing, error, diff --git a/frontend/src/pages/DashboardPage.jsx b/frontend/src/pages/DashboardPage.jsx index 59d59d5..e6354d0 100644 --- a/frontend/src/pages/DashboardPage.jsx +++ b/frontend/src/pages/DashboardPage.jsx @@ -5,7 +5,7 @@ import { useCodeforces } from "../hooks/useCodeforces"; import ConnectBanner from "../components/codeforces/ConnectBanner"; import VerifyModal from "../components/codeforces/VerifyModal"; import DashboardExecutiveSummary from "../components/dashboard/DashboardExecutiveSummary"; -import LoaderSwitcher from "../components/shared/loaders/LoaderSwitcher"; +import { DashboardPageSkeleton } from "../components/shared/skeletons/PageSkeletons"; export default function DashboardPage() { const { user, loading, logout } = useAuth(); @@ -29,7 +29,7 @@ export default function DashboardPage() { }; if (loading) { - return ; + return ; } return ( @@ -74,8 +74,11 @@ export default function DashboardPage() { {/* Codeforces Widget */} {cfLoading ? ( -
-
+
+
+
+
+
) : cfConnected && cfData ? ( -
); } diff --git a/frontend/src/pages/GitHubCallbackPage.jsx b/frontend/src/pages/GitHubCallbackPage.jsx index 3615b3b..9c5f8b4 100644 --- a/frontend/src/pages/GitHubCallbackPage.jsx +++ b/frontend/src/pages/GitHubCallbackPage.jsx @@ -23,8 +23,10 @@ export default function GitHubCallbackPage() { useEffect(() => { const error = searchParams.get("githubAuthError"); if (error) { - setStatus("error"); - setErrorMsg(decodeURIComponent(error)); + setTimeout(() => { + setStatus("error"); + setErrorMsg(decodeURIComponent(error)); + }, 0); setTimeout(() => navigate(`/login?error=${encodeURIComponent(error)}`), 2000); return; } @@ -35,8 +37,10 @@ export default function GitHubCallbackPage() { const token = params.get("token"); if (!token) { - setStatus("error"); - setErrorMsg("No token received. Please try again."); + setTimeout(() => { + setStatus("error"); + setErrorMsg("No token received. Please try again."); + }, 0); setTimeout(() => navigate("/login"), 2500); return; } @@ -54,7 +58,7 @@ export default function GitHubCallbackPage() { } navigate("/dashboard", { replace: true }); - }, []); + }, [login, navigate, searchParams]); return (
diff --git a/frontend/src/pages/GitHubIntelligencePage.jsx b/frontend/src/pages/GitHubIntelligencePage.jsx index f013d15..9d842cc 100644 --- a/frontend/src/pages/GitHubIntelligencePage.jsx +++ b/frontend/src/pages/GitHubIntelligencePage.jsx @@ -4,8 +4,9 @@ import { getGitHubDashboard } from "../services/githubService"; import { StatCard, ScoreMeter, LangBytesChart, ActivityChart, ContribHeatmap, TopReposTable, ActivityFeed, - GistsPanel, StarredInsights, OrgsPanel, PRFootprint, fmt, + GistsPanel, StarredInsights, OrgsPanel, PRFootprint, } from "../components/github/GitHubComponents"; +import { fmt } from "../utils/githubHelpers"; // ── Loading ─────────────────────────────────────────────────────────────────── function Skeleton() { @@ -42,7 +43,7 @@ function NotConnected() { } // ── Profile Hero ────────────────────────────────────────────────────────────── -function ProfileHero({ profile, orgs, totalStars, totalForks, ownedRepos, forkedRepos }) { +function ProfileHero({ profile, totalStars, totalForks, ownedRepos, forkedRepos }) { return (
@@ -175,7 +176,7 @@ export default function GitHubIntelligencePage() { if (!data) return ; const { - profile, orgs, repos, ownedRepos, forkedRepos, topByStars, + profile, orgs, ownedRepos, forkedRepos, topByStars, totalStars, totalForks, langByBytes, contributions, events, gists, starred, starredTopics, starredLangs, prs, issues, metrics, @@ -186,7 +187,7 @@ export default function GitHubIntelligencePage() {
{/* Hero */} - diff --git a/frontend/src/pages/LoginPage.jsx b/frontend/src/pages/LoginPage.jsx index 5ca7599..58dd0e2 100644 --- a/frontend/src/pages/LoginPage.jsx +++ b/frontend/src/pages/LoginPage.jsx @@ -42,7 +42,7 @@ export default function LoginPage() { navigate('/dashboard', { replace: true }); } } - }, [searchParams]); + }, [searchParams, auth, navigate]); const handleSubmit = async (e) => { e.preventDefault(); diff --git a/frontend/src/pages/SignupPage.jsx b/frontend/src/pages/SignupPage.jsx index 9ab95b2..16bcc2b 100644 --- a/frontend/src/pages/SignupPage.jsx +++ b/frontend/src/pages/SignupPage.jsx @@ -12,7 +12,6 @@ export default function SignupPage() { const [password, setPassword] = useState(""); const [otp, setOtp] = useState(""); const [error, setError] = useState(""); - const [githubMessage, setGithubMessage] = useState(""); const [loading, setLoading] = useState(false); const [cooldown, setCooldown] = useState(0); const [isEmailValid, setIsEmailValid] = useState(true); diff --git a/frontend/src/utils/githubHelpers.js b/frontend/src/utils/githubHelpers.js new file mode 100644 index 0000000..8cb0b20 --- /dev/null +++ b/frontend/src/utils/githubHelpers.js @@ -0,0 +1,11 @@ +export const COLORS = ["#000", "#222", "#444", "#666", "#888", "#aaa", "#ccc"]; + +export const fmt = (n) => (n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n ?? 0)); + +export const timeAgo = (d) => { + const s = (Date.now() - new Date(d)) / 1000; + if (s < 60) return `${Math.floor(s)}s ago`; + if (s < 3600) return `${Math.floor(s / 60)}m ago`; + if (s < 86400) return `${Math.floor(s / 3600)}h ago`; + return `${Math.floor(s / 86400)}d ago`; +}; diff --git a/server/package-lock.json b/server/package-lock.json index f315bd4..5cb2039 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -624,7 +624,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -2127,7 +2126,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/server/server.js b/server/server.js index 812011b..48922f0 100644 --- a/server/server.js +++ b/server/server.js @@ -5,10 +5,15 @@ import './config/env.js'; const PORT = process.env.PORT || 5000; const startServer = async () => { - await connectDB(); - app.listen(PORT, () => { - console.log(`Server running on port ${PORT}`); - }); + try { + await connectDB(); + app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); + }); + } catch (err) { + console.error('Failed to connect to database:', err); + process.exit(1); + } }; startServer();