From 83e78e431f804a091ac687ad535483c8c7504b5d Mon Sep 17 00:00:00 2001 From: TNTHNGVDYNND Date: Wed, 11 Feb 2026 11:54:39 +0100 Subject: [PATCH 01/19] feat(routing): Add ProtectedRoute component for authenticated routes - Implements route guard for user-only routes like /profile - Checks for valid access token before rendering protected content - Redirects to login if authentication is missing --- .../src/components/routing/ProtectedRoute.jsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 frontend/src/components/routing/ProtectedRoute.jsx diff --git a/frontend/src/components/routing/ProtectedRoute.jsx b/frontend/src/components/routing/ProtectedRoute.jsx new file mode 100644 index 0000000..042981e --- /dev/null +++ b/frontend/src/components/routing/ProtectedRoute.jsx @@ -0,0 +1,18 @@ +import { Navigate, useLocation } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import { isLoggedIn } from '../utils/auth'; + +export default function ProtectedRoute({ children }) { + const location = useLocation(); + const isAuthenticated = isLoggedIn(); + + if (!isAuthenticated) { + return ; + } + + return children; +} + +ProtectedRoute.propTypes = { + children: PropTypes.node.isRequired, +}; From e355ce65d1402545da053916e13c640c30e34086 Mon Sep 17 00:00:00 2001 From: TNTHNGVDYNND Date: Wed, 11 Feb 2026 11:54:39 +0100 Subject: [PATCH 02/19] feat(routing): Add AdminRoute component for admin-only routes - Implements route guard for admin-only routes like /admin, /characters/edit/:id, /characters/new - Validates both authentication and admin role - Redirects unauthorized users appropriately --- .../src/components/routing/AdminRoute.jsx | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 frontend/src/components/routing/AdminRoute.jsx diff --git a/frontend/src/components/routing/AdminRoute.jsx b/frontend/src/components/routing/AdminRoute.jsx new file mode 100644 index 0000000..5a9d2e2 --- /dev/null +++ b/frontend/src/components/routing/AdminRoute.jsx @@ -0,0 +1,23 @@ +import { Navigate, useLocation } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import { isLoggedIn, getUserRole } from '../utils/auth'; + +export default function AdminRoute({ children }) { + const location = useLocation(); + const isAuthenticated = isLoggedIn(); + const userRole = getUserRole(); + + if (!isAuthenticated) { + return ; + } + + if (userRole !== 'admin') { + return ; + } + + return children; +} + +AdminRoute.propTypes = { + children: PropTypes.node.isRequired, +}; From c6f12e1c8a476027a85afc39ed91443ec907785e Mon Sep 17 00:00:00 2001 From: TNTHNGVDYNND Date: Wed, 11 Feb 2026 11:54:40 +0100 Subject: [PATCH 03/19] feat(layout): Add Layout component with persistent Navigation - Creates persistent navigation wrapper for all routes - Replaces old view-based routing with React Router structure - Integrates Navigation component as fixed header --- frontend/src/components/layout/Layout.jsx | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 frontend/src/components/layout/Layout.jsx diff --git a/frontend/src/components/layout/Layout.jsx b/frontend/src/components/layout/Layout.jsx new file mode 100644 index 0000000..37bfbc8 --- /dev/null +++ b/frontend/src/components/layout/Layout.jsx @@ -0,0 +1,32 @@ +import { Outlet, useLocation } from 'react-router-dom'; +import Navigation from './Navigation'; +import starWarsNeonLogo from '../../assets/star-wars-neon.svg'; + +export default function Layout() { + const location = useLocation(); + + console.log('Current route:', location.pathname); + + return ( +
+ + + {/* Header with Logo */} +
+ Star Wars Logo +

+ Character Database API +

+
+ + {/* Main Content */} +
+ +
+
+ ); +} From a1c5e595e169f4765d9f7797f1ee48568b9e82a1 Mon Sep 17 00:00:00 2001 From: TNTHNGVDYNND Date: Wed, 11 Feb 2026 11:54:41 +0100 Subject: [PATCH 04/19] feat(layout): Add Navigation component with persistent nav bar - Implements fixed top navigation bar with glassmorphism effect - Shows different nav items based on authentication state - Includes Logo, Characters link, User menu (Profile, Admin, Logout), and Auth buttons --- frontend/src/components/layout/Navigation.jsx | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 frontend/src/components/layout/Navigation.jsx diff --git a/frontend/src/components/layout/Navigation.jsx b/frontend/src/components/layout/Navigation.jsx new file mode 100644 index 0000000..7095517 --- /dev/null +++ b/frontend/src/components/layout/Navigation.jsx @@ -0,0 +1,70 @@ +import { Link, useNavigate } from 'react-router-dom'; +import { useApp } from '../../context/AppContext'; +import SpaceBtn from '../buttons/SpaceBtn'; +import Button from '../buttons/Button'; + +export default function Navigation() { + const { user, handleLogout } = useApp(); + const navigate = useNavigate(); + + const onLogout = () => { + handleLogout(); + navigate('/'); + }; + + return ( + + ); +} From 4b5e3ddcaeb8de6dfd557929e4333b35175084dd Mon Sep 17 00:00:00 2001 From: TNTHNGVDYNND Date: Wed, 11 Feb 2026 11:54:41 +0100 Subject: [PATCH 05/19] docs(buttons): Add ButtonStyleGuide component for design reference - Documents standardized button usage across the application - Shows primary (SpaceBtn), secondary (Button), and destructive button patterns - Part of Phase 1 design system standardization --- .../src/components/buttons/ButtonStyleGuide.jsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 frontend/src/components/buttons/ButtonStyleGuide.jsx diff --git a/frontend/src/components/buttons/ButtonStyleGuide.jsx b/frontend/src/components/buttons/ButtonStyleGuide.jsx new file mode 100644 index 0000000..e97cc3f --- /dev/null +++ b/frontend/src/components/buttons/ButtonStyleGuide.jsx @@ -0,0 +1,17 @@ +/** + * BUTTON STYLE GUIDE + * + * PRIMARY: SpaceBtn with white text prop + * Text + * Use for: Main CTAs, important actions + * + * SECONDARY: Button component (default) + * + * Use for: Navigation, secondary actions + * + * DESTRUCTIVE: Button with red styling + * + * Use for: Delete, remove, dangerous actions (always with confirmation) + */ From 1cfa963ba162a98c171e426a34d08d473a8b5666 Mon Sep 17 00:00:00 2001 From: TNTHNGVDYNND Date: Wed, 11 Feb 2026 11:54:55 +0100 Subject: [PATCH 06/19] refactor(routing): Migrate from view-based to React Router v6 - Replaced ViewRouter with React Router Routes - Added route guards (ProtectedRoute, AdminRoute) - Implemented routes: /, /characters, /characters/:id, /login, /register, /profile, /admin - Added nested routing with Layout component --- frontend/src/App.jsx | 79 ++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index e0e56ad..d14d2b5 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,45 +1,52 @@ +import { Routes, Route, Navigate } from 'react-router-dom'; +import { AppProvider } from './context/AppContext'; +import Layout from './components/layout/Layout'; +import ProtectedRoute from './components/routing/ProtectedRoute'; +import AdminRoute from './components/routing/AdminRoute'; import NebulaCanvas from './components/spaceAtmos/NebulaCanvas'; -import starWarsNeonLogo from './assets/star-wars-neon.svg'; -import ViewRouter from './components/ViewRouter'; -import { AppProvider, useApp } from './context/AppContext'; -import './App.css'; -function Layout() { - const { background } = useApp(); +// Pages +import InfoPage from './components/views/InfoPage'; +import Characters from './components/Characters'; +import CharacterDetail from './components/CharacterDetail'; +import CharactersForm from './components/CharactersForm'; +import LoginForm from './components/reg-auth/LoginForm'; +import RegisterForm from './components/reg-auth/RegisterForm'; +import UserProfile from './components/views/UserProfile'; - return ( - <> - -
-
- Star Wars Logo -

- Character Database API -

-
- -
- - ); -} +const AdminDashboard = () => ( +
+

Admin Dashboard

+

Coming soon...

+
+); -export default function App() { +function App() { return ( - +
+ +
+ + }> + } /> + } /> + } /> + } /> + } /> + + } /> + } /> + } /> + } /> + + } /> + + +
+
); } + +export default App; From c533404444553e5071f85d31d05ece19833d22d1 Mon Sep 17 00:00:00 2001 From: TNTHNGVDYNND Date: Wed, 11 Feb 2026 11:54:55 +0100 Subject: [PATCH 07/19] refactor(context): Update AppContext for React Router integration - Removed view-based routing state (setView, view) - Updated handleLogout to use window.location for navigation - Simplified context to focus on auth state management --- frontend/src/context/AppContext.jsx | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/frontend/src/context/AppContext.jsx b/frontend/src/context/AppContext.jsx index 9888823..18e1ffc 100644 --- a/frontend/src/context/AppContext.jsx +++ b/frontend/src/context/AppContext.jsx @@ -6,11 +6,6 @@ import { getStoredAuth, clearStoredAuth } from '../components/utils/auth'; const AppContext = createContext(); function AppProvider({ children }) { - const [background] = useState( - 'https://www.transparenttextures.com/patterns/stardust.png' - ); - const [selectedCharacterId, setSelectedCharacterId] = useState(null); - const [view, setView] = useState('info'); const [user, setUser] = useState(() => { const storedData = getStoredAuth(); return storedData ? storedData.user : null; @@ -19,37 +14,20 @@ function AppProvider({ children }) { const handleLogout = () => { setUser(null); clearStoredAuth(); - setView('info'); - alert('You have successfully logged out.'); }; return ( - + {children} ); } -AppProvider.propTypes = { - children: PropTypes.node.isRequired, -}; +AppProvider.propTypes = { children: PropTypes.node.isRequired }; function useApp() { const context = useContext(AppContext); - if (!context) { - throw new Error('useApp must be used within an AppProvider'); - } + if (!context) throw new Error('useApp must be used within an AppProvider'); return context; } From cc6a04f06331358dfb2442c963c41034c58feacf Mon Sep 17 00:00:00 2001 From: TNTHNGVDYNND Date: Wed, 11 Feb 2026 11:54:55 +0100 Subject: [PATCH 08/19] feat(buttons): Enhance SpaceBtn with React Router navigation support - Added useNavigate and useLocation hooks for client-side routing - Implemented href prop handling with smooth scroll for hash links - Fixed navigation to prevent full page reloads --- frontend/src/components/buttons/SpaceBtn.jsx | 47 +++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/buttons/SpaceBtn.jsx b/frontend/src/components/buttons/SpaceBtn.jsx index 81e41ad..0326f06 100644 --- a/frontend/src/components/buttons/SpaceBtn.jsx +++ b/frontend/src/components/buttons/SpaceBtn.jsx @@ -1,3 +1,4 @@ +import { useNavigate, useLocation } from "react-router-dom"; import SpaceBtnSvg from "./SpaceBtnSvg"; //It is the shape, is a custom SVG component that renders the SpaceBtn.jsx's SVG graphics. /** @@ -8,7 +9,7 @@ import SpaceBtnSvg from "./SpaceBtnSvg"; //It is the shape, is a custom SVG comp * * Props: * - className: Additional CSS classes to apply to the button/link. - * - href: If provided, the component renders a link instead of a button. + * - href: If provided, the component handles navigation or smooth scrolling. * - onClick: Click event handler for the button. * - children: Content to be displayed inside the button/link. * - px: Padding-x class to apply to the button/link (default is "px-7"). @@ -28,6 +29,9 @@ const SpaceBtn = ({ white, type = "button", }) => { + const navigate = useNavigate(); + const location = useLocation(); + // Construct the CSS classes for the button/link const classes = `button relative inline-flex items-center justify-center py-[.5rem] font-bold transition-colors duration-1000 cursor-pointer hover:text-red-600 ${px} ${ white ? "text-neutral-800" : "text-neutral-100/5" @@ -36,24 +40,43 @@ const SpaceBtn = ({ // CSS classes for the span inside the button/link const spanClasses = "relative z-10"; + const handleClick = (e) => { + // adding PageHash to the href to check if it is a hash link Then scroll or navigate to the target route! + if (href) { + const isSamePageHash = href.startsWith("#") && location.pathname === "/"; + + if (isSamePageHash) { + // Scroll to section within the same page + const targetId = href.slice(1); // Remove the "#" symbol + const targetElement = document.getElementById(targetId); + if (targetElement) { + targetElement.scrollIntoView({ behavior: "smooth" }); + } + } else if (location.pathname === href) { + // Smooth scroll to the top if already on the target route + window.scrollTo({ + top: 0, + behavior: "smooth", + }); + } else { + // Navigate to the target route + navigate(href); + } + } else if (onClick) { + onClick(); + } + }; + // Function to render a button element const renderButton = () => ( - ); - // Function to render a link element - const renderLink = () => ( - - {children} - {SpaceBtnSvg(white)} - - ); - - // Render a link if `href` is provided, otherwise render a button - return href ? renderLink() : renderButton(); + // Render a button (handles both navigation and regular clicks) + return renderButton(); }; export default SpaceBtn; From 31f28fc44edbe4e5e3a38c12cee036622e38d4b3 Mon Sep 17 00:00:00 2001 From: TNTHNGVDYNND Date: Wed, 11 Feb 2026 11:54:55 +0100 Subject: [PATCH 09/19] refactor(views): Update InfoPage for React Router navigation - Replaced Link wrappers with direct Button/SpaceBtn href props - Updated navigation buttons to use href prop for routing - Removed unused Link import --- frontend/src/components/views/InfoPage.jsx | 66 ++++++---------------- 1 file changed, 16 insertions(+), 50 deletions(-) diff --git a/frontend/src/components/views/InfoPage.jsx b/frontend/src/components/views/InfoPage.jsx index 1c2d8ab..3ac7d8e 100644 --- a/frontend/src/components/views/InfoPage.jsx +++ b/frontend/src/components/views/InfoPage.jsx @@ -2,70 +2,36 @@ import { useApp } from '../../context/AppContext'; import Button from '../buttons/Button'; import SpaceBtn from '../buttons/SpaceBtn'; -import ButtonGradient from '../buttons/ButtonGradient'; -import BtnNeonGradient from '../buttons/BtnNeonGradient'; export default function InfoPage() { - const { user, setView, handleLogout } = useApp(); + const { user } = useApp(); return ( -
-

- Welcome to{' '} - Star Wars Admin Dashboard. Built - with role-based access and full-stack CRUD power! -

-

- Register or login to access the database, or{' '} - - contact me - {' '} - for admin access. +

+

+ Welcome to Star Wars Admin Dashboard.

{user ? ( -
-

- Welcome back,{' '} - {user.name && {user.name}} - {user.email && ( - ({user.email}) - )} - ! - +
+

+ Welcome back, {user.name && {user.name}}

-
- - - - Logout +
+ + View Profile
) : ( -
- - - - - +
+ + + Register
)} - - - DEAD STAR! - + + DEAD STAR!

); From 3a0936633f42529e01a9f09b38a9481fced4dc05 Mon Sep 17 00:00:00 2001 From: TNTHNGVDYNND Date: Wed, 11 Feb 2026 11:54:56 +0100 Subject: [PATCH 10/19] refactor(views): Update UserProfile for React Router navigation - Fixed navigation buttons to use href props instead of Link wrappers - Ensured compatibility with updated Button and SpaceBtn components --- frontend/src/components/views/UserProfile.jsx | 103 ++++++------------ 1 file changed, 36 insertions(+), 67 deletions(-) diff --git a/frontend/src/components/views/UserProfile.jsx b/frontend/src/components/views/UserProfile.jsx index 36e6771..d93122c 100644 --- a/frontend/src/components/views/UserProfile.jsx +++ b/frontend/src/components/views/UserProfile.jsx @@ -1,23 +1,15 @@ import { useState } from 'react'; import { useUserProfileFetcher } from '../hooks/userProfileFetcher'; -import PropTypes from 'prop-types'; +import { Link } from 'react-router-dom'; import { apiRequest } from '../utils/api'; - import SpaceBtn from '../buttons/SpaceBtn'; -import BtnNeoGradient from '../buttons/BtnNeonGradient'; import Button from '../buttons/Button'; -import ButtonGradient from '../buttons/ButtonGradient'; -const UserProfile = ({ returnToInfo, onUpdate }) => { - const { profile, setProfile, loading, message, setMessage, refetch } = +const UserProfile = () => { + const { profile, setProfile, loading, message, setMessage } = useUserProfileFetcher(); const [saving, setSaving] = useState(false); - const { name, bio, location, avatar } = profile; - - const isUnchanged = - name === '' && bio === '' && location === '' && avatar === ''; - const handleChange = (e) => { setProfile({ ...profile, [e.target.name]: e.target.value }); }; @@ -27,27 +19,13 @@ const UserProfile = ({ returnToInfo, onUpdate }) => { setSaving(true); try { const payload = { - name: name.trim(), - bio: bio.trim(), - location: location.trim(), - avatar: avatar.trim(), + name: profile.name.trim(), + bio: profile.bio.trim(), + location: profile.location.trim(), + avatar: profile.avatar.trim(), }; - await apiRequest('PATCH', '/users/profile', payload); // Update profile on the backend - setProfile(payload); // Update local state + await apiRequest('PATCH', '/users/profile', payload); setMessage('Profile updated!'); - console.log('Profile updated successfully:', name); - - await refetch(); // Refetch the profile data to ensure it's up-to-date - - if (onUpdate) { - onUpdate({ - name: payload.name, - bio: payload.bio, - location: payload.location, - avatar: payload.avatar, - }); - console.log('onUpdate callback called with:', payload); - } } catch (err) { setMessage(err.message); } finally { @@ -55,35 +33,37 @@ const UserProfile = ({ returnToInfo, onUpdate }) => { } }; - if (loading) { - return

Loading profile...

; - } + if (loading) + return ( +

Loading profile...

+ ); return ( -
+

- {name || 'User Profile'} + {profile.name || 'User Profile'}

+
- {avatar && ( + {profile.avatar && (
Avatar Preview
)} + @@ -91,10 +71,9 @@ const UserProfile = ({ returnToInfo, onUpdate }) => { Bio: