diff --git a/frontend/package.json b/frontend/package.json
index c201e6b..bba8541 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -13,7 +13,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"axios": "^1.6.2",
- "react-router-dom": "^6.20.1"
+ "react-router-dom": "^6.20.1",
+ "@heroicons/react": "^2.1.1"
},
"devDependencies": {
"@types/react": "^18.2.43",
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 3d341c2..de1abdb 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -1,93 +1,45 @@
-import { useState } from 'react'
-import DomainSetup from './pages/DomainSetup'
-import DomainSetupNew from './pages/DomainSetupNew'
-import DomainList from './pages/DomainList'
-import AdminCFAccounts from './pages/AdminCFAccounts'
+import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
+import { AuthProvider, useAuth } from './context/AuthContext'
+import Landing from './pages/Landing'
+import Dashboard from './pages/Dashboard'
import './App.css'
+// Protected route wrapper
+const ProtectedRoute = ({ children }) => {
+ const { isAuthenticated, loading } = useAuth()
+
+ if (loading) {
+ return (
+
+ )
+ }
+
+ return isAuthenticated ? children :
+}
+
function App() {
- const [currentPage, setCurrentPage] = useState('setup-new')
-
return (
-
- {/* Header */}
-
-
-
-
-
- H
-
-
-
Hosting Platform
-
DNS & SSL Management
-
-
-
-
-
-
-
-
- {/* Main Content */}
-
- {currentPage === 'setup-new' && }
- {currentPage === 'setup' && }
- {currentPage === 'list' && }
- {currentPage === 'admin' && }
-
-
- {/* Footer */}
-
-
+
+
+
+ } />
+
+
+
+ }
+ />
+ } />
+
+
+
)
}
diff --git a/frontend/src/context/AuthContext.jsx b/frontend/src/context/AuthContext.jsx
new file mode 100644
index 0000000..b0f6420
--- /dev/null
+++ b/frontend/src/context/AuthContext.jsx
@@ -0,0 +1,109 @@
+/**
+ * Auth Context - Global authentication state management
+ */
+import { createContext, useContext, useState, useEffect } from 'react';
+import { authAPI } from '../services/api';
+
+const AuthContext = createContext(null);
+
+export const useAuth = () => {
+ const context = useContext(AuthContext);
+ if (!context) {
+ throw new Error('useAuth must be used within AuthProvider');
+ }
+ return context;
+};
+
+export const AuthProvider = ({ children }) => {
+ const [user, setUser] = useState(null);
+ const [customer, setCustomer] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
+
+ // Check if user is logged in on mount
+ useEffect(() => {
+ const checkAuth = async () => {
+ const token = localStorage.getItem('auth_token');
+ if (token) {
+ try {
+ const response = await authAPI.getProfile();
+ setUser(response.data.user);
+ setCustomer(response.data.customer);
+ setIsAuthenticated(true);
+ } catch (error) {
+ console.error('Auth check failed:', error);
+ localStorage.removeItem('auth_token');
+ localStorage.removeItem('user');
+ }
+ }
+ setLoading(false);
+ };
+
+ checkAuth();
+ }, []);
+
+ const login = async (email, password) => {
+ try {
+ const response = await authAPI.login({ email, password });
+ const { token, user: userData, customer: customerData } = response.data;
+
+ localStorage.setItem('auth_token', token);
+ localStorage.setItem('user', JSON.stringify(userData));
+
+ setUser(userData);
+ setCustomer(customerData);
+ setIsAuthenticated(true);
+
+ return { success: true, data: response.data };
+ } catch (error) {
+ console.error('Login failed:', error);
+ return {
+ success: false,
+ error: error.response?.data?.message || 'Login failed',
+ };
+ }
+ };
+
+ const register = async (data) => {
+ try {
+ const response = await authAPI.register(data);
+ const { token, user: userData, customer: customerData } = response.data;
+
+ localStorage.setItem('auth_token', token);
+ localStorage.setItem('user', JSON.stringify(userData));
+
+ setUser(userData);
+ setCustomer(customerData);
+ setIsAuthenticated(true);
+
+ return { success: true, data: response.data };
+ } catch (error) {
+ console.error('Registration failed:', error);
+ return {
+ success: false,
+ error: error.response?.data?.message || 'Registration failed',
+ };
+ }
+ };
+
+ const logout = () => {
+ localStorage.removeItem('auth_token');
+ localStorage.removeItem('user');
+ setUser(null);
+ setCustomer(null);
+ setIsAuthenticated(false);
+ };
+
+ const value = {
+ user,
+ customer,
+ loading,
+ isAuthenticated,
+ login,
+ register,
+ logout,
+ };
+
+ return {children};
+};
+
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 10c0aec..880e301 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -1,33 +1,35 @@
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
+
@tailwind base;
@tailwind components;
@tailwind utilities;
-:root {
- font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
- line-height: 1.5;
- font-weight: 400;
+@layer base {
+ * {
+ @apply border-gray-200;
+ }
- color-scheme: light dark;
- color: rgba(255, 255, 255, 0.87);
- background-color: #242424;
-
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
+ body {
+ @apply bg-gray-50 text-gray-900 font-sans;
+ }
}
-body {
- margin: 0;
- display: flex;
- place-items: center;
- min-width: 320px;
- min-height: 100vh;
-}
-
-#root {
- width: 100%;
- margin: 0 auto;
- text-align: center;
+@layer components {
+ /* Custom component styles */
+ .btn-primary {
+ @apply bg-primary-500 hover:bg-primary-600 text-white font-medium px-4 py-2 rounded-lg transition-colors;
+ }
+
+ .btn-secondary {
+ @apply bg-secondary-500 hover:bg-secondary-600 text-white font-medium px-4 py-2 rounded-lg transition-colors;
+ }
+
+ .input-field {
+ @apply w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent outline-none transition-all;
+ }
+
+ .card {
+ @apply bg-white rounded-xl shadow-sm border border-gray-200 p-6;
+ }
}
diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx
new file mode 100644
index 0000000..23a7a8b
--- /dev/null
+++ b/frontend/src/pages/Dashboard.jsx
@@ -0,0 +1,231 @@
+/**
+ * Customer Dashboard - Main dashboard with sidebar navigation
+ */
+import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useAuth } from '../context/AuthContext';
+import {
+ HomeIcon,
+ GlobeAltIcon,
+ ServerIcon,
+ WifiIcon,
+ ShieldCheckIcon,
+ Cog6ToothIcon,
+ ArrowRightOnRectangleIcon,
+} from '@heroicons/react/24/outline';
+
+const Dashboard = () => {
+ const { user, customer, logout } = useAuth();
+ const navigate = useNavigate();
+ const [activeTab, setActiveTab] = useState('overview');
+
+ const handleLogout = () => {
+ logout();
+ navigate('/');
+ };
+
+ const menuItems = [
+ { id: 'overview', name: 'Overview', icon: HomeIcon },
+ { id: 'dns', name: 'DNS Management', icon: GlobeAltIcon },
+ { id: 'containers', name: 'Containers', icon: ServerIcon },
+ { id: 'network', name: 'Network', icon: WifiIcon },
+ { id: 'security', name: 'Security', icon: ShieldCheckIcon },
+ { id: 'settings', name: 'Settings', icon: Cog6ToothIcon },
+ ];
+
+ return (
+
+ {/* Sidebar */}
+
+
+ {/* Main content */}
+
+
+ {/* Header */}
+
+
+ {menuItems.find((item) => item.id === activeTab)?.name}
+
+
+ Welcome back, {user?.full_name}!
+
+
+
+ {/* Content based on active tab */}
+ {activeTab === 'overview' && (
+
+ {/* Stats cards */}
+
+
+
+
Domains
+
0
+
+ of {customer?.max_domains} limit
+
+
+
+
+
+
+
+
+
+
Containers
+
0
+
+ of {customer?.max_containers} limit
+
+
+
+
+
+
+
+
+
+
Status
+
Active
+
All systems operational
+
+
+
+
+
+ )}
+
+ {activeTab === 'dns' && (
+
+
DNS Management module coming soon...
+
+ )}
+
+ {activeTab === 'containers' && (
+
+
Container management module coming soon...
+
+ )}
+
+ {activeTab === 'network' && (
+
+
Network configuration module coming soon...
+
+ )}
+
+ {activeTab === 'security' && (
+
+
Security settings module coming soon...
+
+ )}
+
+ {activeTab === 'settings' && (
+
+ )}
+
+
+
+ );
+};
+
+export default Dashboard;
+
diff --git a/frontend/src/pages/Landing.jsx b/frontend/src/pages/Landing.jsx
new file mode 100644
index 0000000..f475f88
--- /dev/null
+++ b/frontend/src/pages/Landing.jsx
@@ -0,0 +1,237 @@
+/**
+ * Landing Page - Register/Login with animations
+ */
+import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useAuth } from '../context/AuthContext';
+
+const Landing = () => {
+ const [isLogin, setIsLogin] = useState(true);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState('');
+ const navigate = useNavigate();
+ const { login, register } = useAuth();
+
+ // Form states
+ const [formData, setFormData] = useState({
+ email: '',
+ password: '',
+ password_confirm: '',
+ full_name: '',
+ company_name: '',
+ });
+
+ const handleChange = (e) => {
+ setFormData({ ...formData, [e.target.name]: e.target.value });
+ setError('');
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setLoading(true);
+ setError('');
+
+ try {
+ let result;
+ if (isLogin) {
+ result = await login(formData.email, formData.password);
+ } else {
+ result = await register(formData);
+ }
+
+ if (result.success) {
+ navigate('/dashboard');
+ } else {
+ setError(result.error);
+ }
+ } catch (err) {
+ setError('An unexpected error occurred');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+ {/* Background decoration */}
+
+
+
+ {/* Logo */}
+
+

+
Hosting Platform
+
+ Professional WordPress hosting with container infrastructure
+
+
+
+ {/* Form Card */}
+
+ {/* Tabs */}
+
+
+
+
+
+ {/* Error message */}
+ {error && (
+
+ {error}
+
+ )}
+
+ {/* Form */}
+
+
+
+ {/* Footer */}
+
+ © 2026 ARGE ICT. All rights reserved.
+
+
+
+
+
+ );
+};
+
+export default Landing;
+
diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js
index ec79117..a1fdb94 100644
--- a/frontend/src/services/api.js
+++ b/frontend/src/services/api.js
@@ -9,6 +9,42 @@ const api = axios.create({
},
})
+// Request interceptor - Add auth token
+api.interceptors.request.use(
+ (config) => {
+ const token = localStorage.getItem('auth_token')
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`
+ }
+ return config
+ },
+ (error) => {
+ return Promise.reject(error)
+ }
+)
+
+// Response interceptor - Handle errors
+api.interceptors.response.use(
+ (response) => response,
+ (error) => {
+ if (error.response?.status === 401) {
+ // Token expired or invalid
+ localStorage.removeItem('auth_token')
+ localStorage.removeItem('user')
+ window.location.href = '/'
+ }
+ return Promise.reject(error)
+ }
+)
+
+// Auth API
+export const authAPI = {
+ register: (data) => api.post('/api/auth/register', data),
+ login: (data) => api.post('/api/auth/login', data),
+ getProfile: () => api.get('/api/auth/me'),
+ verifyToken: (token) => api.post('/api/auth/verify-token', { token }),
+}
+
export const dnsAPI = {
// Health check
health: () => api.get('/health'),
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js
index d37737f..c500c66 100644
--- a/frontend/tailwind.config.js
+++ b/frontend/tailwind.config.js
@@ -5,7 +5,49 @@ export default {
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
- extend: {},
+ extend: {
+ colors: {
+ // Brand colors from ARGE ICT logo
+ brand: {
+ green: {
+ DEFAULT: '#159052',
+ dark: '#046D3F',
+ light: '#53BA6F',
+ },
+ orange: '#F69036',
+ blue: '#0F578B',
+ red: '#B42832',
+ },
+ // Semantic colors
+ primary: {
+ 50: '#e6f7ef',
+ 100: '#b3e6d0',
+ 200: '#80d5b1',
+ 300: '#4dc492',
+ 400: '#1ab373',
+ 500: '#159052', // Main brand green
+ 600: '#117342',
+ 700: '#0d5631',
+ 800: '#093921',
+ 900: '#051c10',
+ },
+ secondary: {
+ 50: '#fff3e6',
+ 100: '#ffdbb3',
+ 200: '#ffc380',
+ 300: '#ffab4d',
+ 400: '#ff931a',
+ 500: '#F69036', // Brand orange
+ 600: '#c5722b',
+ 700: '#945520',
+ 800: '#633816',
+ 900: '#321c0b',
+ },
+ },
+ fontFamily: {
+ sans: ['Inter', 'system-ui', 'sans-serif'],
+ },
+ },
},
plugins: [],
}