diff --git a/.gitignore b/.gitignore index 620c125..3cf52cc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ /backups # Python -/__pycache__/* +__pycache__ # Logs logs *.log diff --git a/backend/__pycache__/main.cpython-313.pyc b/backend/__pycache__/main.cpython-313.pyc deleted file mode 100644 index 6ffcfb4..0000000 Binary files a/backend/__pycache__/main.cpython-313.pyc and /dev/null differ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 54ec7b5..56adbf0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -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", diff --git a/frontend/package.json b/frontend/package.json index ac62763..c33f105 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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" }, diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 34b6ab1..a79eeb8 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -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,35 +73,50 @@ 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(() => { + return localStorage.getItem("theme") === "dark"; + }); + + // Sync dark mode state with 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 ( -
-
- setDarkMode(!darkMode)} /> -
- - } /> - } /> - } /> - } /> - } /> - - - - } - /> - } /> - } /> - } /> - } /> - -
+
+ setDarkMode((prev) => !prev)} + /> +
+ + } /> + } /> + } /> + } /> + } /> + + + + } + /> + } /> + } /> + } /> + } /> +
@@ -95,7 +124,13 @@ function App() { ); } -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(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 }) {
@@ -183,15 +216,16 @@ function AppBar({ toggleDarkMode }: { toggleDarkMode: () => void }) { )}
); } +// Pages and forms function LandingPage() { return (
@@ -203,20 +237,67 @@ function LandingPage() { ); } -// Profile placeholder -function Profile() { - return ( -
-

Profile

-

Profile page coming soon.

-
- ); -} function About() { return ( -
-

About Me

-

About page content coming soon.

+
+
+ Photo 1 + Photo 2 + Photo 3 +
+
+

About Me

+

+ 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. I’m comfortable in many tech + stacks and learn new ones quickly—never afraid to jump in the deep end + and adapt on the fly. +

+
+
); } @@ -230,6 +311,15 @@ function Contact() { ); } +function Profile() { + return ( +
+

Profile

+

Profile page coming soon.

+
+ ); +} + // Registration form with API integration interface RegisterForm { username: string;