diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
new file mode 100644
index 0000000..06b8e2c
--- /dev/null
+++ b/frontend/src/App.jsx
@@ -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 (
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ } />
+
+ }>
+ }>
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/frontend/src/components/Footer.jsx b/frontend/src/components/Footer.jsx
new file mode 100644
index 0000000..c47f7fa
--- /dev/null
+++ b/frontend/src/components/Footer.jsx
@@ -0,0 +1,29 @@
+export default function Footer() {
+ return (
+
+ );
+}
diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx
new file mode 100644
index 0000000..98d0ecb
--- /dev/null
+++ b/frontend/src/components/Layout.jsx
@@ -0,0 +1,14 @@
+import Navbar from './Navbar';
+import Footer from './Footer';
+
+export default function Layout({ children }) {
+ return (
+
+ );
+}
diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx
new file mode 100644
index 0000000..6ebad4a
--- /dev/null
+++ b/frontend/src/components/Navbar.jsx
@@ -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 (
+
+ );
+}
diff --git a/frontend/src/components/ProtectedRoute.jsx b/frontend/src/components/ProtectedRoute.jsx
new file mode 100644
index 0000000..7e8b071
--- /dev/null
+++ b/frontend/src/components/ProtectedRoute.jsx
@@ -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 ;
+ }
+
+ return ;
+}
diff --git a/frontend/src/index.css b/frontend/src/index.css
new file mode 100644
index 0000000..c0adf52
--- /dev/null
+++ b/frontend/src/index.css
@@ -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; }
+}
diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx
new file mode 100644
index 0000000..5eaaa2f
--- /dev/null
+++ b/frontend/src/main.jsx
@@ -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(
+
+
+
+
+ ,
+)