diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9f634f0..c37e9db 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,6 +11,7 @@ "@tailwindcss/vite": "^4.1.18", "react": "^19.2.0", "react-dom": "^19.2.0", + "react-router-dom": "^7.13.0", "tailwindcss": "^4.1.18" }, "devDependencies": { @@ -2155,6 +2156,19 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3351,6 +3365,44 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz", + "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz", + "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==", + "license": "MIT", + "dependencies": { + "react-router": "7.13.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3421,6 +3473,12 @@ "semver": "bin/semver.js" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7089900..aedf76a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ "@tailwindcss/vite": "^4.1.18", "react": "^19.2.0", "react-dom": "^19.2.0", + "react-router-dom": "^7.13.0", "tailwindcss": "^4.1.18" }, "devDependencies": { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2c984b4..74cee14 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,18 +1,33 @@ import "./App.css"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; import Navigation from "./components/Navigation.tsx"; import Footer from "./components/Footer.tsx"; import Home from "./pages/Home.tsx"; import About from "./components/Skills.tsx"; +import Login from "./pages/Login.tsx"; + function App() { return ( -
- -
- - -
-
+ +
+ +
+ + + + + + } + /> + } /> + +
+
+
); } diff --git a/frontend/src/components/Navigation.tsx b/frontend/src/components/Navigation.tsx index d00cd1c..8baa456 100644 --- a/frontend/src/components/Navigation.tsx +++ b/frontend/src/components/Navigation.tsx @@ -1,12 +1,100 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; + +interface User { + name?: string; + email?: string; + avatar?: string; +} export default function Navigation() { const [isOpen, setIsOpen] = useState(false); + const [user, setUser] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + checkAuth(); + }, []); + + const checkAuth = async () => { + try { + const response = await fetch("/api/auth/session"); + if (response.ok) { + const data = await response.json(); + setUser(data.user); + } + } catch (error) { + console.error("Auth check failed:", error); + } finally { + setIsLoading(false); + } + }; + + const getInitials = (user: User): string => { + if (user.name) { + return user.name.substring(0, 2).toUpperCase(); + } + if (user.email) { + return user.email.substring(0, 2).toUpperCase(); + } + return "?"; + }; + + const AccountAvatar = () => { + if (isLoading) { + return ( +
+ ); + } + + return ( + <> + {user ? ( +
+ {user.avatar ? ( + {user.name + ) : ( +
+ {getInitials(user)} +
+ )} +
+ ) : ( + + + + + + )} + + ); + }; return ( <> + {/* Account Avatar - Fixed position, synced with nav */} +
+ +
+
{/* Mobile Menu */} @@ -88,6 +179,22 @@ export default function Navigation() { > Guestbook + + {user && ( + <> +
+
+ {user.name || user.email} +
+ setIsOpen(false)} + > + Logout + + + )} diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx new file mode 100644 index 0000000..2fa10a4 --- /dev/null +++ b/frontend/src/pages/Login.tsx @@ -0,0 +1,79 @@ +export default function Login() { + const handleGitHubLogin = () => { + window.location.href = "/api/v1/auth/github"; + }; + + return ( +
+
+ {/* ASCII Art Header */} +
+          {`
+    ██╗      ██████╗  ██████╗ ██╗███╗   ██╗
+    ██║     ██╔═══██╗██╔════╝ ██║████╗  ██║
+    ██║     ██║   ██║██║  ███╗██║██╔██╗ ██║
+    ██║     ██║   ██║██║   ██║██║██║╚██╗██║
+    ███████╗╚██████╔╝╚██████╔╝██║██║ ╚████║
+    ╚══════╝ ╚═════╝  ╚═════╝ ╚═╝╚═╝  ╚═══╝
+
+    ╔════════════════════════════════════╗
+    ║     Secure GitHub Authentication   ║
+    ╚════════════════════════════════════╝
+`}
+        
+ + {/* Login Card */} +
+

+ Welcome Back +

+

+ Sign in to continue to your account +

+ + {/* GitHub Login Button */} + + + {/* Divider */} +
+
+
+
+
+ + secure authentication + +
+
+ + {/* Footer note */} +

+ By signing in, you agree to our{" "} + + terms of service + +

+
+
+
+ ); +}