Complete Phase 1: MVC, DB migrations, Auth, RBAC, Security, and Views

This commit is contained in:
Hamza-Ayed
2026-06-05 00:56:41 +03:00
parent 7ffbc8bafa
commit bed7624ae9
51 changed files with 3295 additions and 0 deletions

View File

@@ -0,0 +1,143 @@
<div class="dashboard-header">
<h1>Welcome, <?= $this->escape($user['name']) ?></h1>
<p>Here is your daily intelligence summary for ScoutIQ.</p>
</div>
<!-- Metrics Row -->
<div class="metrics-grid">
<div class="glass-panel metric-card">
<span class="metric-title">Total Investors</span>
<span class="metric-value">142</span>
<span class="metric-footer">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline><polyline points="17 6 23 6 23 12"></polyline></svg>
<span>+12% vs last month</span>
</span>
</div>
<div class="glass-panel metric-card">
<span class="metric-title">Total Accelerators</span>
<span class="metric-value">38</span>
<span class="metric-footer">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline><polyline points="17 6 23 6 23 12"></polyline></svg>
<span>+5% vs last month</span>
</span>
</div>
<div class="glass-panel metric-card">
<span class="metric-title">Open Opportunities</span>
<span class="metric-value">29</span>
<span class="metric-footer" style="color: var(--warning);">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
<span>4 closing this week</span>
</span>
</div>
<div class="glass-panel metric-card">
<span class="metric-title">Contacts Added Today</span>
<span class="metric-value">7</span>
<span class="metric-footer">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline><polyline points="17 6 23 6 23 12"></polyline></svg>
<span>+3 new organizations</span>
</span>
</div>
</div>
<!-- Charts Row -->
<div class="charts-grid">
<div class="glass-panel chart-card">
<span class="chart-title">Opportunities by Category</span>
<div style="flex: 1; position: relative;">
<canvas id="categoryChart"></canvas>
</div>
</div>
<div class="glass-panel chart-card">
<span class="chart-title">Monthly Growth & Ingestion</span>
<div style="flex: 1; position: relative;">
<canvas id="growthChart"></canvas>
</div>
</div>
</div>
<script>
// Category Chart (Doughnut)
const ctxCategory = document.getElementById('categoryChart').getContext('2d');
new Chart(ctxCategory, {
type: 'doughnut',
data: {
labels: ['Mobility & Logistics', 'AI & Automation', 'SaaS', 'Fintech', 'Marketplaces'],
datasets: [{
data: [35, 25, 20, 12, 8],
backgroundColor: [
'hsl(263, 90%, 60%)',
'hsl(220, 95%, 50%)',
'hsl(180, 100%, 40%)',
'hsl(142, 70%, 45%)',
'hsl(38, 92%, 50%)'
],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
color: 'hsl(215, 20%, 75%)',
font: { family: 'Inter', size: 12 }
}
}
}
}
});
// Growth Chart (Line)
const ctxGrowth = document.getElementById('growthChart').getContext('2d');
new Chart(ctxGrowth, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
label: 'Ingested Opportunities',
data: [12, 19, 32, 45, 68, 96],
borderColor: 'hsl(180, 100%, 50%)',
backgroundColor: 'rgba(0, 242, 254, 0.1)',
borderWidth: 3,
fill: true,
tension: 0.4
}, {
label: 'Interactions Logged',
data: [5, 12, 18, 25, 30, 48],
borderColor: 'hsl(263, 90%, 60%)',
backgroundColor: 'transparent',
borderWidth: 3,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
color: 'hsl(215, 20%, 75%)',
font: { family: 'Inter', size: 12 }
}
}
},
scales: {
x: {
grid: { color: 'rgba(255, 255, 255, 0.05)' },
ticks: { color: 'hsl(215, 20%, 70%)' }
},
y: {
grid: { color: 'rgba(255, 255, 255, 0.05)' },
ticks: { color: 'hsl(215, 20%, 70%)' }
}
}
}
});
</script>

View File

@@ -0,0 +1,40 @@
<div class="glass-panel" style="width: 100%; max-width: 440px; padding: 40px; display: flex; flex-direction: column; gap: 30px;">
<div style="text-align: center; display: flex; flex-direction: column; gap: 10px;">
<h1 style="font-size: 2.2rem; font-weight: 800; background: var(--primary-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">ScoutIQ</h1>
<p style="color: var(--text-muted); font-size: 0.95rem;">AI-powered Investor Intelligence</p>
</div>
<?php if ($flashError = $this->session->getFlash('error')): ?>
<div class="alert alert-error">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 10px;"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>
<span><?= $this->escape($flashError) ?></span>
</div>
<?php endif; ?>
<?php if ($flashSuccess = $this->session->getFlash('success')): ?>
<div class="alert alert-success">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 10px;"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>
<span><?= $this->escape($flashSuccess) ?></span>
</div>
<?php endif; ?>
<form action="/login" method="POST" style="display: flex; flex-direction: column; gap: 20px;">
<input type="hidden" name="_csrf" value="<?= $this->session->getCsrfToken() ?>">
<div class="form-group">
<label class="form-label" for="email">Email Address</label>
<input type="email" name="email" id="email" class="form-control" placeholder="name@domain.com" required autocomplete="username">
</div>
<div class="form-group">
<label class="form-label" for="password">Password</label>
<input type="password" name="password" id="password" class="form-control" placeholder="••••••••" required autocomplete="current-password">
</div>
<button type="submit" class="btn btn-primary" style="width: 100%; margin-top: 10px;">Sign In</button>
</form>
<div style="text-align: center; font-size: 0.9rem; color: var(--text-muted);">
Don't have an account? <a href="/register" style="font-weight: 600;">Sign Up</a>
</div>
</div>

View File

@@ -0,0 +1,38 @@
<div class="glass-panel" style="width: 100%; max-width: 440px; padding: 40px; display: flex; flex-direction: column; gap: 30px;">
<div style="text-align: center; display: flex; flex-direction: column; gap: 10px;">
<h1 style="font-size: 2.2rem; font-weight: 800; background: var(--primary-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">Create Account</h1>
<p style="color: var(--text-muted); font-size: 0.95rem;">Join ScoutIQ Investor Platform</p>
</div>
<?php if ($flashError = $this->session->getFlash('error')): ?>
<div class="alert alert-error">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 10px;"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>
<span><?= $this->escape($flashError) ?></span>
</div>
<?php endif; ?>
<form action="/register" method="POST" style="display: flex; flex-direction: column; gap: 20px;">
<input type="hidden" name="_csrf" value="<?= $this->session->getCsrfToken() ?>">
<div class="form-group">
<label class="form-label" for="name">Full Name</label>
<input type="text" name="name" id="name" class="form-control" placeholder="John Doe" required autocomplete="name">
</div>
<div class="form-group">
<label class="form-label" for="email">Email Address</label>
<input type="email" name="email" id="email" class="form-control" placeholder="name@domain.com" required autocomplete="username">
</div>
<div class="form-group">
<label class="form-label" for="password">Password</label>
<input type="password" name="password" id="password" class="form-control" placeholder="••••••••" required autocomplete="new-password">
</div>
<button type="submit" class="btn btn-primary" style="width: 100%; margin-top: 10px;">Create Account</button>
</form>
<div style="text-align: center; font-size: 0.9rem; color: var(--text-muted);">
Already have an account? <a href="/login" style="font-weight: 600;">Sign In</a>
</div>
</div>

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Access Forbidden - ScoutIQ</title>
<link rel="stylesheet" href="/assets/css/app.css">
</head>
<body style="align-items: center; justify-content: center; display: flex; padding: 20px; min-height: 100vh;">
<div class="glass-panel" style="max-width: 500px; width: 100%; padding: 50px; text-align: center; display: flex; flex-direction: column; gap: 30px;">
<span style="font-size: 5rem; font-weight: 800; background: var(--primary-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-family: 'Outfit';">403</span>
<h1 style="font-size: 1.8rem; font-weight: 700;">Access Forbidden</h1>
<p style="color: var(--text-muted); font-size: 1rem; line-height: 1.6;">You do not have the required role permissions to access this directory or perform this action.</p>
<a href="/" class="btn btn-primary" style="align-self: center;">Return Home</a>
</div>
</body>
</html>

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Page Not Found - ScoutIQ</title>
<link rel="stylesheet" href="/assets/css/app.css">
</head>
<body style="align-items: center; justify-content: center; display: flex; padding: 20px; min-height: 100vh;">
<div class="glass-panel" style="max-width: 500px; width: 100%; padding: 50px; text-align: center; display: flex; flex-direction: column; gap: 30px;">
<span style="font-size: 5rem; font-weight: 800; background: var(--primary-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-family: 'Outfit';">404</span>
<h1 style="font-size: 1.8rem; font-weight: 700;">Page Not Found</h1>
<p style="color: var(--text-muted); font-size: 1rem; line-height: 1.6;">The resource you are looking for might have been removed, had its name changed, or is temporarily unavailable.</p>
<a href="/" class="btn btn-primary" style="align-self: center;">Return Home</a>
</div>
</body>
</html>

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Server Error - ScoutIQ</title>
<link rel="stylesheet" href="/assets/css/app.css">
</head>
<body style="align-items: center; justify-content: center; display: flex; padding: 20px; min-height: 100vh;">
<div class="glass-panel" style="max-width: 500px; width: 100%; padding: 50px; text-align: center; display: flex; flex-direction: column; gap: 30px;">
<span style="font-size: 5rem; font-weight: 800; background: var(--primary-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-family: 'Outfit';">500</span>
<h1 style="font-size: 1.8rem; font-weight: 700;">System Server Error</h1>
<p style="color: var(--text-muted); font-size: 1rem; line-height: 1.6;"><?= isset($message) ? $this->escape($message) : 'A critical exception has occurred on the application server.' ?></p>
<a href="/" class="btn btn-primary" style="align-self: center;">Return Home</a>
</div>
</body>
</html>

View File

@@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $this->escape($title ?? 'Admin') ?> - ScoutIQ</title>
<link rel="stylesheet" href="/assets/css/app.css">
<link rel="stylesheet" href="/assets/css/admin.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<div class="admin-container">
<!-- Sidebar -->
<aside class="sidebar">
<div class="brand">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" style="color: var(--accent);"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg>
<span>ScoutIQ</span>
</div>
<nav style="flex: 1;">
<ul class="nav-menu">
<li class="nav-item active">
<a href="/admin/dashboard">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
<span>Dashboard</span>
</a>
</li>
<li class="nav-item">
<a href="#crm">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
<span>CRM</span>
</a>
</li>
<li class="nav-item">
<a href="#opportunities">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
<span>Opportunities</span>
</a>
</li>
</ul>
</nav>
<div class="nav-menu">
<li class="nav-item">
<a href="/logout" style="color: var(--error);">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg>
<span>Logout</span>
</a>
</li>
</div>
</aside>
<!-- Main Content -->
<main class="main-content">
<!-- Top bar -->
<header class="top-bar">
<div style="color: var(--text-muted); font-size: 0.9rem;">
Platform Mode: <span style="color: var(--accent); font-weight: 600; text-transform: uppercase;"><?= $this->escape($_ENV['APP_ENV'] ?? 'local') ?></span>
</div>
<div class="user-info">
<span style="font-weight: 500; font-size: 0.95rem;"><?= $this->escape($user['name'] ?? 'User') ?></span>
<div class="avatar">
<?= strtoupper(substr($user['name'] ?? 'U', 0, 1)) ?>
</div>
</div>
</header>
<!-- View content body -->
<div class="content-body">
<?= $content ?>
</div>
</main>
</div>
</body>
</html>

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $this->escape($title ?? 'Auth') ?> - ScoutIQ</title>
<link rel="stylesheet" href="/assets/css/app.css">
</head>
<body>
<div style="flex: 1; display: flex; align-items: center; justify-content: center; padding: 20px;">
<?= $content ?>
</div>
</body>
</html>