feat: about me page

This commit is contained in:
2025-06-14 11:22:00 -04:00
parent 8db73f113c
commit 2c73c3ba4c
5 changed files with 237 additions and 48 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
/backups
# Python
/__pycache__/*
__pycache__
# Logs
logs
*.log
Binary file not shown.
+97 -1
View File
@@ -8,11 +8,14 @@
"name": "blogs-app",
"version": "0.0.0",
"dependencies": {
"@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@tailwindcss/vite": "^4.1.7",
"fs": "^0.0.1-security",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-hook-form": "^7.57.0",
"react-icons": "^5.5.0",
"react-markdown": "^10.1.0",
"react-router-dom": "^7.6.0"
},
@@ -881,6 +884,53 @@
"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": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -3076,7 +3126,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/js-yaml": {
@@ -3424,6 +3473,18 @@
"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": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -4159,6 +4220,15 @@
"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": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -4331,6 +4401,17 @@
"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": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
@@ -4409,6 +4490,21 @@
"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": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz",
+3
View File
@@ -10,11 +10,14 @@
"preview": "vite preview"
},
"dependencies": {
"@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@tailwindcss/vite": "^4.1.7",
"fs": "^0.0.1-security",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-hook-form": "^7.57.0",
"react-icons": "^5.5.0",
"react-markdown": "^10.1.0",
"react-router-dom": "^7.6.0"
},
+116 -26
View File
@@ -1,3 +1,10 @@
import React, {
createContext,
useContext,
useState,
useEffect,
useRef,
} from "react";
import {
BrowserRouter as Router,
Routes,
@@ -5,15 +12,22 @@ import {
Link,
useNavigate,
} from "react-router-dom";
import { useState, useContext, useEffect, createContext, useRef } from "react";
import { useForm } from "react-hook-form";
import { BlogViewer } from "./utils/BlogViewer";
import { BlogList } from "./utils/BlogList";
import { RequireAdmin } from "./utils/RouteGuard";
import { AdminPage } from "./utils/AdminPage";
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";
// Auth Context
@@ -59,14 +73,30 @@ const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
};
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 (
<Router>
<AuthProvider>
<div className={darkMode ? "dark" : ""}>
<div className="min-h-screen bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100">
<AppBar toggleDarkMode={() => setDarkMode(!darkMode)} />
<div className="min-h-screen bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors duration-300">
<AppBar
darkMode={darkMode}
toggleDarkMode={() => setDarkMode((prev) => !prev)}
/>
<div className="p-4">
<Routes>
<Route path="/" element={<LandingPage />} />
@@ -89,13 +119,18 @@ function App() {
</Routes>
</div>
</div>
</div>
</AuthProvider>
</Router>
);
}
function AppBar({ toggleDarkMode }: { toggleDarkMode: () => void }) {
function AppBar({
darkMode,
toggleDarkMode,
}: {
darkMode: boolean;
toggleDarkMode: () => void;
}) {
const { isAuthenticated, logout } = useAuth();
const [menuOpen, setMenuOpen] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
@@ -111,9 +146,7 @@ function AppBar({ toggleDarkMode }: { toggleDarkMode: () => void }) {
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [menuOpen]);
return (
@@ -159,7 +192,7 @@ function AppBar({ toggleDarkMode }: { toggleDarkMode: () => void }) {
<div className="relative" ref={menuRef}>
<button
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>
</button>
@@ -183,15 +216,16 @@ function AppBar({ toggleDarkMode }: { toggleDarkMode: () => void }) {
)}
<button
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>
</div>
</nav>
);
}
// Pages and forms
function LandingPage() {
return (
<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() {
return (
<div className="text-center py-10">
<h2 className="text-2xl font-bold mb-4">About Me</h2>
<p>About page content coming soon.</p>
<div className="py-10">
<div className="flex justify-center items-end space-x-4">
<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>
);
}
@@ -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
interface RegisterForm {
username: string;