Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import Layout from './components/Layout';
import Home from './pages/Home';
import Projects from './pages/Projects';
import Experience from './pages/Experience';
import Education from './pages/Education';
import Contact from './pages/Contact';
import Login from './pages/Login';
import ProtectedRoute from './components/ProtectedRoute';

// Admin
import AdminLayout from './pages/Admin/AdminLayout';
import Dashboard from './pages/Admin/Dashboard';
import ManageMessages from './pages/Admin/ManageMessages';
import ManageStats from './pages/Admin/ManageStats';
import ManageProjects from './pages/Admin/ManageProjects';
import ManageExperience from './pages/Admin/ManageExperience';
import ManageEducation from './pages/Admin/ManageEducation';
import ManageTechStack from './pages/Admin/ManageTechStack';
import ManageContent from './pages/Admin/ManageContent';

function App() {
return (
<Router>
<Layout>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/projects" element={<Projects />} />
<Route path="/experience" element={<Experience />} />
<Route path="/education" element={<Education />} />
<Route path="/contact" element={<Contact />} />

<Route path="/login" element={<Login />} />

<Route path="/admin" element={<ProtectedRoute />}>
<Route element={<AdminLayout />}>
<Route index element={<Navigate to="dashboard" replace />} />
<Route path="dashboard" element={<Dashboard />} />
<Route path="messages" element={<ManageMessages />} />
<Route path="stats" element={<ManageStats />} />
<Route path="projects" element={<ManageProjects />} />
<Route path="experience" element={<ManageExperience />} />
<Route path="education" element={<ManageEducation />} />
<Route path="tech-stack" element={<ManageTechStack />} />
<Route path="content" element={<ManageContent />} />
</Route>
</Route>
</Routes>
</Layout>
</Router>
);
}

export default App;
29 changes: 29 additions & 0 deletions frontend/src/components/Footer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export default function Footer() {
return (
<footer className="w-full py-8 bg-slate-950 dark:bg-slate-950 border-t border-slate-800 mt-auto">
<div className="flex flex-col md:flex-row justify-between items-center px-12 max-w-screen-2xl mx-auto gap-4">
<div className="font-mono text-[10px] uppercase tracking-widest text-[#00FF41]">
&copy; {new Date().getFullYear()} // ALL RIGHTS RESERVED
</div>
<div className="flex gap-6 font-mono text-[10px] uppercase tracking-widest text-slate-600">
<a className="hover:text-white transition-opacity duration-300 relative group" href="#">
GitHub
<span className="absolute -top-6 left-0 hidden group-hover:block bg-surface border border-outline-variant px-2 py-1 text-primary-container">coord: [45.1, -12.3]</span>
</a>
<a className="hover:text-white transition-opacity duration-300 relative group" href="#">
LinkedIn
<span className="absolute -top-6 left-0 hidden group-hover:block bg-surface border border-outline-variant px-2 py-1 text-primary-container">coord: [88.2, 14.7]</span>
</a>
<a className="hover:text-white transition-opacity duration-300 relative group" href="#">
Email
<span className="absolute -top-6 left-0 hidden group-hover:block bg-surface border border-outline-variant px-2 py-1 text-primary-container">coord: [0.0, 0.0]</span>
</a>
<span className="hover:text-white transition-opacity duration-300 border-l border-slate-800 pl-6 flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-[#00FF41]"></div>
Status: Operational
</span>
</div>
</div>
</footer>
);
}
14 changes: 14 additions & 0 deletions frontend/src/components/Layout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Navbar from './Navbar';
import Footer from './Footer';

export default function Layout({ children }) {
return (
<div className="min-h-screen flex flex-col w-full">
<Navbar />
<div className="flex-grow flex flex-col">
{children}
</div>
<Footer />
</div>
);
}
48 changes: 48 additions & 0 deletions frontend/src/components/Navbar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Link, useLocation } from 'react-router-dom';

export default function Navbar() {
const location = useLocation();
const currentPath = location.pathname;

const links = [
{ name: 'Home', path: '/' },
{ name: 'Projects', path: '/projects' },
{ name: 'Experience', path: '/experience' },
{ name: 'Education', path: '/education' },
{ name: 'Contact', path: '/contact' },
];

return (
<nav className="fixed top-0 w-full z-50 bg-slate-950/80 dark:bg-slate-950/80 backdrop-blur-xl border-b border-slate-800 flat no shadows">
<div className="flex justify-between items-center px-12 h-16 w-full max-w-[1440px] mx-auto">
<div className="text-xl font-black text-[#00FF41] tracking-widest font-mono">
<img src="../../public/logo.svg" alt="logo" />
</div>
<div className="hidden md:flex gap-8 items-center font-mono uppercase tracking-tighter text-sm">
{links.map((link) => {
const isActive = currentPath === link.path || (link.path === '/admin' && currentPath.startsWith('/admin'));
return (
<Link
key={link.name}
to={link.path}
className={
isActive
? "text-[#00FF41] border-b border-[#00FF41] pb-1 glow-border-sm duration-150 px-2 py-1"
: "text-slate-500 hover:text-[#00FF41] hover:bg-slate-900/50 transition-all duration-200 px-2 py-1"
}
>
{link.name}
</Link>
);
})}
</div>
<Link to="/contact" className="hidden md:block font-mono text-sm border border-outline-variant px-4 py-2 text-on-surface hover:border-[#00FF41] hover:text-[#00FF41] transition-colors duration-200 bg-surface-container">
[ Execute_Contact ]
</Link>
<button className="md:hidden text-[#00FF41]">
<span className="material-symbols-outlined">menu</span>
</button>
</div>
</nav>
);
}
31 changes: 31 additions & 0 deletions frontend/src/components/ProtectedRoute.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Navigate, Outlet } from 'react-router-dom';
import { jwtDecode } from 'jwt-decode';

export default function ProtectedRoute() {
const token = localStorage.getItem('token');
let isValid = false;

if (token) {
try {
const decoded = jwtDecode(token);
const currentTime = Date.now() / 1000;

// Quarkus/SmallRye JWT usually puts roles in 'groups' claim
const roles = decoded.groups || [];
if (decoded.exp > currentTime && (roles.includes('OWNER') || decoded.role === 'OWNER')) {
isValid = true;
}
} catch (err) {
console.error('Invalid token format');
}
}

if (!isValid) {
// Clear any invalid state
localStorage.removeItem('isAuthenticated');
localStorage.removeItem('token');
return <Navigate to="/login" replace />;
}

return <Outlet />;
}
48 changes: 48 additions & 0 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=JetBrains+Mono:wght@400;500;800&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap');

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
body {
@apply bg-background text-on-background min-h-screen flex flex-col font-body-base text-body-base selection:bg-primary-container selection:text-on-primary-container grid-bg;
}
}

@layer utilities {
.grid-bg {
background-image:
linear-gradient(to right, rgba(132, 150, 126, 0.05) 1px, transparent 1px),
linear-gradient(to bottom, rgba(132, 150, 126, 0.05) 1px, transparent 1px);
background-size: 64px 64px;
}

.glow-border {
box-shadow: 0 0 8px 0 theme('colors.primary-container');
}

.glow-border-sm {
box-shadow: 0 0 4px 0 theme('colors.primary-container');
}

.glow-border-active {
box-shadow: 0 0 8px 0 theme('colors.primary-container');
border-color: theme('colors.primary-container');
}

.glow-border-hover:hover {
box-shadow: 0 0 8px 0 theme('colors.primary-container');
border-color: theme('colors.primary-container');
}

.cursor-blink {
animation: blink 1s step-end infinite;
}
}

@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
15 changes: 15 additions & 0 deletions frontend/src/main.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

import { Provider } from 'react-redux';
import { store } from './store';

createRoot(document.getElementById('root')).render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
)