From ec0164691b3ccd93285df604fb3ac85fd878e759 Mon Sep 17 00:00:00 2001 From: oguz ozturk Date: Sat, 10 Jan 2026 16:23:40 +0300 Subject: [PATCH] Add frontend authentication - Landing page, Dashboard, Auth context --- frontend/package.json | 3 +- frontend/src/App.jsx | 124 +++++--------- frontend/src/context/AuthContext.jsx | 109 ++++++++++++ frontend/src/index.css | 50 +++--- frontend/src/pages/Dashboard.jsx | 231 ++++++++++++++++++++++++++ frontend/src/pages/Landing.jsx | 237 +++++++++++++++++++++++++++ frontend/src/services/api.js | 36 ++++ frontend/tailwind.config.js | 44 ++++- 8 files changed, 722 insertions(+), 112 deletions(-) create mode 100644 frontend/src/context/AuthContext.jsx create mode 100644 frontend/src/pages/Dashboard.jsx create mode 100644 frontend/src/pages/Landing.jsx 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 ( +
+
+
+

Loading...

+
+
+ ) + } + + 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 */} -
-
-
-

© 2024 Hosting Platform. Powered by Cloudflare.

-

Automated DNS & SSL Management

-
-
-
-
+ + + + } /> + + + + } + /> + } /> + + + ) } 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' && ( +
+

Account 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 */} +
+ ARGE ICT +

Hosting Platform

+

+ Professional WordPress hosting with container infrastructure +

+
+ + {/* Form Card */} +
+ {/* Tabs */} +
+ + +
+ + {/* Error message */} + {error && ( +
+ {error} +
+ )} + + {/* Form */} +
+ {!isLogin && ( + <> +
+ + +
+ +
+ + +
+ + )} + +
+ + +
+ +
+ + +
+ + {!isLogin && ( +
+ + +
+ )} + + +
+
+ + {/* 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: [], }