feat: about me page

This commit is contained in:
2025-06-14 11:22:00 -04:00
parent 8db73f113c
commit 87a0910b32
4 changed files with 237 additions and 48 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
/backups /backups
# Python # Python
/__pycache__/* __pycache__
# Logs # Logs
logs logs
*.log *.log
+97 -1
View File
@@ -8,11 +8,14 @@
"name": "blogs-app", "name": "blogs-app",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@tailwindcss/vite": "^4.1.7", "@tailwindcss/vite": "^4.1.7",
"fs": "^0.0.1-security", "fs": "^0.0.1-security",
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
"react-hook-form": "^7.57.0", "react-hook-form": "^7.57.0",
"react-icons": "^5.5.0",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-router-dom": "^7.6.0" "react-router-dom": "^7.6.0"
}, },
@@ -881,6 +884,53 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@fortawesome/fontawesome-common-types": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz",
"integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/fontawesome-svg-core": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz",
"integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.7.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-brands-svg-icons": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.7.2.tgz",
"integrity": "sha512-zu0evbcRTgjKfrr77/2XX+bU+kuGfjm0LbajJHVIgBWNIDzrhpRxiCPNT8DW5AdmSsq7Mcf9D1bH0aSeSUSM+Q==",
"license": "(CC-BY-4.0 AND MIT)",
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.7.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/react-fontawesome": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz",
"integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==",
"license": "MIT",
"dependencies": {
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@fortawesome/fontawesome-svg-core": "~1 || ~6",
"react": ">=16.3"
}
},
"node_modules/@humanfs/core": { "node_modules/@humanfs/core": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -3076,7 +3126,6 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
@@ -3424,6 +3473,18 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -4159,6 +4220,15 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/optionator": { "node_modules/optionator": {
"version": "0.9.4", "version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -4331,6 +4401,17 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"node_modules/property-information": { "node_modules/property-information": {
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
@@ -4409,6 +4490,21 @@
"react": "^16.8.0 || ^17 || ^18 || ^19" "react": "^16.8.0 || ^17 || ^18 || ^19"
} }
}, },
"node_modules/react-icons": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
"integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
"license": "MIT",
"peerDependencies": {
"react": "*"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
"node_modules/react-markdown": { "node_modules/react-markdown": {
"version": "10.1.0", "version": "10.1.0",
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz",
+3
View File
@@ -10,11 +10,14 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@tailwindcss/vite": "^4.1.7", "@tailwindcss/vite": "^4.1.7",
"fs": "^0.0.1-security", "fs": "^0.0.1-security",
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
"react-hook-form": "^7.57.0", "react-hook-form": "^7.57.0",
"react-icons": "^5.5.0",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-router-dom": "^7.6.0" "react-router-dom": "^7.6.0"
}, },
+136 -46
View File
@@ -1,3 +1,10 @@
import React, {
createContext,
useContext,
useState,
useEffect,
useRef,
} from "react";
import { import {
BrowserRouter as Router, BrowserRouter as Router,
Routes, Routes,
@@ -5,15 +12,22 @@ import {
Link, Link,
useNavigate, useNavigate,
} from "react-router-dom"; } from "react-router-dom";
import { useState, useContext, useEffect, createContext, useRef } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { BlogViewer } from "./utils/BlogViewer"; import { BlogViewer } from "./utils/BlogViewer";
import { BlogList } from "./utils/BlogList"; import { BlogList } from "./utils/BlogList";
import { RequireAdmin } from "./utils/RouteGuard"; import { RequireAdmin } from "./utils/RouteGuard";
import { AdminPage } from "./utils/AdminPage"; import { AdminPage } from "./utils/AdminPage";
import Unauthorized from "./utils/UnauthorizedPage"; import Unauthorized from "./utils/UnauthorizedPage";
import {
FaGithub,
FaGitAlt,
FaLinkedin,
FaTwitter,
FaSun,
FaMoon,
} from "react-icons/fa";
// Use Vite environment variable for API base URL // Base API URL from env
const API_URL = import.meta.env.VITE_API_URL || "http://localhost:8000"; const API_URL = import.meta.env.VITE_API_URL || "http://localhost:8000";
// Auth Context // Auth Context
@@ -59,35 +73,50 @@ const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
}; };
function App() { function App() {
const [darkMode, setDarkMode] = useState(true); // Initialize theme from localStorage or default to light
const [darkMode, setDarkMode] = useState<boolean>(() => {
return localStorage.getItem("theme") === "dark";
});
// Sync dark mode state with <html> class and localStorage
useEffect(() => {
if (darkMode) {
document.documentElement.classList.add("dark");
localStorage.setItem("theme", "dark");
} else {
document.documentElement.classList.remove("dark");
localStorage.setItem("theme", "light");
}
}, [darkMode]);
return ( return (
<Router> <Router>
<AuthProvider> <AuthProvider>
<div className={darkMode ? "dark" : ""}> <div className="min-h-screen bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors duration-300">
<div className="min-h-screen bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100"> <AppBar
<AppBar toggleDarkMode={() => setDarkMode(!darkMode)} /> darkMode={darkMode}
<div className="p-4"> toggleDarkMode={() => setDarkMode((prev) => !prev)}
<Routes> />
<Route path="/" element={<LandingPage />} /> <div className="p-4">
<Route path="/blog" element={<BlogList />} /> <Routes>
<Route path="/blog/:slug" element={<BlogViewer />} /> <Route path="/" element={<LandingPage />} />
<Route path="/about" element={<About />} /> <Route path="/blog" element={<BlogList />} />
<Route path="/contact" element={<Contact />} /> <Route path="/blog/:slug" element={<BlogViewer />} />
<Route <Route path="/about" element={<About />} />
path="/admin" <Route path="/contact" element={<Contact />} />
element={ <Route
<RequireAdmin> path="/admin"
<AdminPage /> element={
</RequireAdmin> <RequireAdmin>
} <AdminPage />
/> </RequireAdmin>
<Route path="/unauthorized" element={<Unauthorized />} /> }
<Route path="/register" element={<Register />} /> />
<Route path="/signin" element={<SignIn />} /> <Route path="/unauthorized" element={<Unauthorized />} />
<Route path="/profile" element={<Profile />} /> <Route path="/register" element={<Register />} />
</Routes> <Route path="/signin" element={<SignIn />} />
</div> <Route path="/profile" element={<Profile />} />
</Routes>
</div> </div>
</div> </div>
</AuthProvider> </AuthProvider>
@@ -95,7 +124,13 @@ function App() {
); );
} }
function AppBar({ toggleDarkMode }: { toggleDarkMode: () => void }) { function AppBar({
darkMode,
toggleDarkMode,
}: {
darkMode: boolean;
toggleDarkMode: () => void;
}) {
const { isAuthenticated, logout } = useAuth(); const { isAuthenticated, logout } = useAuth();
const [menuOpen, setMenuOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false);
const menuRef = useRef<HTMLDivElement>(null); const menuRef = useRef<HTMLDivElement>(null);
@@ -111,9 +146,7 @@ function AppBar({ toggleDarkMode }: { toggleDarkMode: () => void }) {
} }
}; };
document.addEventListener("mousedown", handleClickOutside); document.addEventListener("mousedown", handleClickOutside);
return () => { return () => document.removeEventListener("mousedown", handleClickOutside);
document.removeEventListener("mousedown", handleClickOutside);
};
}, [menuOpen]); }, [menuOpen]);
return ( return (
@@ -159,7 +192,7 @@ function AppBar({ toggleDarkMode }: { toggleDarkMode: () => void }) {
<div className="relative" ref={menuRef}> <div className="relative" ref={menuRef}>
<button <button
onClick={() => setMenuOpen(!menuOpen)} onClick={() => setMenuOpen(!menuOpen)}
className="w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center" className="w-8 h-8 rounded-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center"
> >
<span className="text-gray-700 dark:text-gray-200">U</span> <span className="text-gray-700 dark:text-gray-200">U</span>
</button> </button>
@@ -183,15 +216,16 @@ function AppBar({ toggleDarkMode }: { toggleDarkMode: () => void }) {
)} )}
<button <button
onClick={toggleDarkMode} onClick={toggleDarkMode}
className="text-sm bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded" className="text-xl p-1 bg-gray-200 dark:bg-gray-700 rounded-full"
> >
Toggle Dark Mode {darkMode ? <FaMoon /> : <FaSun />}
</button> </button>
</div> </div>
</nav> </nav>
); );
} }
// Pages and forms
function LandingPage() { function LandingPage() {
return ( return (
<div className="text-center py-10"> <div className="text-center py-10">
@@ -203,20 +237,67 @@ function LandingPage() {
); );
} }
// Profile placeholder
function Profile() {
return (
<div className="text-center py-10">
<h2 className="text-2xl font-bold mb-4">Profile</h2>
<p>Profile page coming soon.</p>
</div>
);
}
function About() { function About() {
return ( return (
<div className="text-center py-10"> <div className="py-10">
<h2 className="text-2xl font-bold mb-4">About Me</h2> <div className="flex justify-center items-end space-x-4">
<p>About page content coming soon.</p> <img
src="/images/photo1.jpg"
alt="Photo 1"
className="w-24 h-24 rounded-full"
/>
<img
src="/images/photo2.jpg"
alt="Photo 2"
className="w-24 h-24 rounded-full transform translate-y-4"
/>
<img
src="/images/photo3.jpg"
alt="Photo 3"
className="w-24 h-24 rounded-full"
/>
</div>
<div className="text-center mt-8 px-4">
<h2 className="text-3xl font-bold mb-4">About Me</h2>
<p className="text-lg text-gray-700 dark:text-gray-300 max-w-2xl mx-auto">
I am a software engineer at Whisker who designs and implements
whatever is highest priority. I work with every team in the
engineering organization to coordinate mission-critical projects
across backend, mobile, and firmware. Im comfortable in many tech
stacks and learn new ones quicklynever afraid to jump in the deep end
and adapt on the fly.
</p>
</div>
<div className="mt-8 flex flex-col items-center space-y-2">
<a
href="https://github.com/amuszyn"
className="flex items-center space-x-2 text-blue-600 hover:underline"
>
<FaGithub />
<span>github.com/amuszyn</span>
</a>
<a
href="https://gitea.muszyn.dev/"
className="flex items-center space-x-2 text-blue-600 hover:underline"
>
<FaGitAlt />
<span>gitea.muszyn.dev/</span>
</a>
<a
href="https://www.linkedin.com/in/almuszynski/"
className="flex items-center space-x-2 text-blue-600 hover:underline"
>
<FaLinkedin />
<span>linkedin.com/in/almuszynski</span>
</a>
<a
href="https://x.com/Muszynlol"
className="flex items-center space-x-2 text-blue-600 hover:underline"
>
<FaTwitter />
<span>x.com/Muszynlol</span>
</a>
</div>
</div> </div>
); );
} }
@@ -230,6 +311,15 @@ function Contact() {
); );
} }
function Profile() {
return (
<div className="text-center py-10">
<h2 className="text-2xl font-bold mb-4">Profile</h2>
<p>Profile page coming soon.</p>
</div>
);
}
// Registration form with API integration // Registration form with API integration
interface RegisterForm { interface RegisterForm {
username: string; username: string;