Version 2

This commit is contained in:
Flatlogic Bot 2025-10-21 23:39:12 +00:00
parent e7d7877d01
commit 23be5a5236
30 changed files with 972 additions and 12 deletions

29
api/pexels.php Normal file
View File

@ -0,0 +1,29 @@
<?php
header('Content-Type: application/json');
require_once __DIR__.'/../includes/pexels.php';
$qs = isset($_GET['queries']) ? explode(',', $_GET['queries']) : ['home','apple','pizza','mountains','cat'];
$out = [];
foreach ($qs as $q) {
$u = 'https://api.pexels.com/v1/search?query=' . urlencode(trim($q)) . '&orientation=landscape&per_page=1&page=1';
$d = pexels_get($u);
if ($d && !empty($d['photos'])) {
$p = $d['photos'][0];
$src = $p['src']['large2x'] ?? ($p['src']['large'] ?? $p['src']['original']);
$dest = __DIR__.'/../assets/images/pexels/'.$p['id'].'.jpg';
if ($src && !file_exists($dest)) download_to($src, $dest);
$out[] = [
'src' => 'assets/images/pexels/'.$p['id'].'.jpg',
'photographer' => $p['photographer'] ?? 'Unknown',
'photographer_url' => $p['photographer_url'] ?? '',
];
} else {
// Fallback: Picsum
$out[] = [
'src' => 'https://picsum.photos/1200/800?random='.rand(),
'photographer' => 'Random Picsum',
'photographer_url' => 'https://picsum.photos/'
];
}
}
echo json_encode($out);
?>

View File

@ -1,4 +1,3 @@
/* General Body Styles */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
@ -59,8 +58,7 @@ h2 {
/* Hero Section */
#hero {
background: linear-gradient(180deg, #FFFFFF 0%, #F2F2F7 100%);
padding: 120px 0;
padding: 0;
}
/* Card Styles */
@ -92,3 +90,72 @@ footer {
background-color: #FFFFFF;
padding: 40px 0;
}
/* Pricing Table */
#pricing .card {
border: 1px solid #e5e5e5;
transition: all .2s;
}
#pricing .card:hover {
transform: scale(1.02);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.1);
}
#pricing .card-popular {
border-color: #007AFF;
border-width: 2px;
}
#pricing .card-price {
font-size: 3rem;
font-weight: 700;
}
#pricing .period {
font-size: 0.8rem;
font-weight: normal;
color: #6c757d;
}
#pricing .fa-ul {
list-style: none;
padding-left: 0;
margin-bottom: 2rem;
}
#pricing .fa-li {
position: relative;
left: 0;
margin-bottom: 0.75rem;
padding-left: 2em; /* Add padding to align text */
}
#pricing .fa-li .fas {
position: absolute;
left: 0;
width: 2em;
text-align: center;
color: #007AFF;
}
#pricing .text-muted .fa-li .fas {
color: #6c757d;
}
/* Hero Overlay */
#hero {
position: relative;
background-size: cover;
background-position: center;
color: white;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
}
.hero-overlay {
position: relative;
z-index: 1;
background-color: rgba(0, 0, 0, 0.5);
padding: 120px 0;
}

37
assets/css/portal.css Normal file
View File

@ -0,0 +1,37 @@
body {
background-color: #f8f9fa;
}
.portal-nav {
background-color: #fff;
border-bottom: 1px solid #dee2e6;
}
.portal-main {
min-height: calc(100vh - 56px);
display: flex;
}
.sidebar {
width: 250px;
background-color: #fff;
border-right: 1px solid #dee2e6;
}
.sidebar .nav-link {
color: #333;
padding: 1rem;
border-left: 3px solid transparent;
}
.sidebar .nav-link.active,
.sidebar .nav-link:hover {
color: #007bff;
background-color: #e9ecef;
border-left-color: #007bff;
}
.content {
flex-grow: 1;
padding: 2rem;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

72
auth_handler.php Normal file
View File

@ -0,0 +1,72 @@
<?php
require_once 'includes/auth.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: index.php');
exit();
}
$action = $_POST['action'] ?? '';
if ($action === 'register') {
$name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
if (empty($name) || empty($email) || empty($password)) {
header('Location: register.php?error=missing_fields');
exit();
}
$db = db();
$stmt = $db->prepare('SELECT id FROM users WHERE email = ?');
$stmt->execute([$email]);
if ($stmt->fetch()) {
header('Location: register.php?error=email_taken');
exit();
}
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
// Default new registrations to the 'Client' role
$stmt = $db->prepare("SELECT id FROM roles WHERE name = 'Client'");
$stmt->execute();
$client_role = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $db->prepare('INSERT INTO users (name, email, password, role_id) VALUES (?, ?, ?, ?)');
if ($stmt->execute([$name, $email, $hashed_password, $client_role['id'] ?? null])) {
$user_id = $db->lastInsertId();
$_SESSION['user_id'] = $user_id;
header('Location: portal/');
exit();
} else {
header('Location: register.php?error=registration_failed');
exit();
}
} elseif ($action === 'login') {
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
if (empty($email) || empty($password)) {
header('Location: login.php?error=missing_fields');
exit();
}
$db = db();
$stmt = $db->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
header('Location: portal/');
exit();
} else {
header('Location: login.php?error=invalid_credentials');
exit();
}
} else {
header('Location: index.php');
exit();
}

View File

@ -0,0 +1,26 @@
CREATE TABLE IF NOT EXISTS `roles` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(50) NOT NULL UNIQUE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Insert default roles
INSERT IGNORE INTO `roles` (`name`) VALUES
('Super Admin'),
('Agency Admin'),
('Account Manager'),
('Client'),
('Content Creator'),
('Analyst'),
('Billing'),
('Support');
CREATE TABLE IF NOT EXISTS `users` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(100) NOT NULL,
`email` VARCHAR(100) NOT NULL UNIQUE,
`password` VARCHAR(255) NOT NULL,
`role_id` INT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -0,0 +1,61 @@
-- Projects table to store project information
CREATE TABLE IF NOT EXISTS `projects` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(255) NOT NULL,
`client_id` INT NOT NULL,
`status` VARCHAR(50) DEFAULT 'Pending',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`client_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Assets table for creative assets linked to projects
CREATE TABLE IF NOT EXISTS `assets` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`project_id` INT NOT NULL,
`name` VARCHAR(255) NOT NULL,
`file_path` VARCHAR(255) NOT NULL,
`status` VARCHAR(50) DEFAULT 'Pending Approval',
`uploaded_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Comments on assets
CREATE TABLE IF NOT EXISTS `asset_comments` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`asset_id` INT NOT NULL,
`user_id` INT NOT NULL,
`comment` TEXT NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`asset_id`) REFERENCES `assets`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Courses available for purchase
CREATE TABLE IF NOT EXISTS `courses` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(255) NOT NULL,
`description` TEXT,
`price` DECIMAL(10, 2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Link table for users who have access to courses
CREATE TABLE IF NOT EXISTS `user_courses` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`user_id` INT NOT NULL,
`course_id` INT NOT NULL,
`purchased_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`course_id`) REFERENCES `courses`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Invoices for billing
CREATE TABLE IF NOT EXISTS `invoices` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`client_id` INT NOT NULL,
`amount` DECIMAL(10, 2) NOT NULL,
`status` VARCHAR(50) DEFAULT 'Unpaid',
`due_date` DATE,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`client_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -0,0 +1,63 @@
-- Messaging system tied to projects
CREATE TABLE IF NOT EXISTS `messages` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`project_id` INT NOT NULL,
`user_id` INT NOT NULL,
`message` TEXT NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Calendar events
CREATE TABLE IF NOT EXISTS `calendar_events` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`title` VARCHAR(255) NOT NULL,
`start_event` DATETIME NOT NULL,
`end_event` DATETIME NOT NULL,
`project_id` INT,
`user_id` INT,
`description` TEXT,
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Approvals for assets
CREATE TABLE IF NOT EXISTS `approvals` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`asset_id` INT NOT NULL,
`user_id` INT NOT NULL,
`status` ENUM('Pending', 'Approved', 'Rejected') NOT NULL DEFAULT 'Pending',
`comments` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (`asset_id`) REFERENCES `assets`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Metrics from ad platforms
CREATE TABLE IF NOT EXISTS `metrics` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`project_id` INT NOT NULL,
`platform` VARCHAR(50) NOT NULL, -- e.g., 'Facebook', 'Google Ads'
`date` DATE NOT NULL,
`impressions` INT,
`clicks` INT,
`conversions` INT,
`spend` DECIMAL(10, 2),
`engagement` INT,
`reach` INT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY `project_platform_date` (`project_id`, `platform`, `date`),
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Secure storage for API tokens
CREATE TABLE IF NOT EXISTS `api_tokens` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`service` VARCHAR(50) NOT NULL UNIQUE, -- e.g., 'meta_ads', 'google_ads'
`access_token` TEXT NOT NULL,
`refresh_token` TEXT,
`expires_at` TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

102
includes/auth.php Normal file
View File

@ -0,0 +1,102 @@
<?php
session_start();
require_once __DIR__ . '/../db/config.php';
// Function to check if a user is logged in
function is_logged_in() {
return isset($_SESSION['user_id']);
}
// Function to require a user to be logged in to access a page
function require_login() {
if (!is_logged_in()) {
header('Location: /login.php');
exit();
}
}
// Function to check if the current user is an admin
function is_admin() {
if (!is_logged_in()) {
return false;
}
// If impersonating, the original user must be an admin
$user_id = $_SESSION['original_user_id'] ?? $_SESSION['user_id'];
$db = db();
$stmt = $db->prepare('SELECT roles.name FROM users JOIN roles ON users.role_id = roles.id WHERE users.id = ?');
$stmt->execute([$user_id]);
$role = $stmt->fetchColumn();
return in_array($role, ['Super Admin', 'Agency Admin']);
}
// Function to require admin privileges
function require_admin() {
if (!is_admin()) {
// Redirect to portal index if not an admin
header('Location: /portal');
exit();
}
}
// Function to start impersonating a user
function impersonate_user($user_id_to_impersonate) {
if (!is_admin()) {
return false; // Only admins can impersonate
}
// Prevent impersonating another admin for security
$db = db();
$stmt = $db->prepare('SELECT roles.name FROM users JOIN roles ON users.role_id = roles.id WHERE users.id = ?');
$stmt->execute([$user_id_to_impersonate]);
$role_to_impersonate = $stmt->fetchColumn();
if (in_array($role_to_impersonate, ['Super Admin', 'Agency Admin']) && $_SESSION['user_id'] != $user_id_to_impersonate) {
// Allow admins to view their own profile without triggering this rule
return false;
}
if (!isset($_SESSION['original_user_id'])) {
$_SESSION['original_user_id'] = $_SESSION['user_id'];
}
$_SESSION['user_id'] = $user_id_to_impersonate;
return true;
}
// Function to stop impersonating
function stop_impersonating() {
if (isset($_SESSION['original_user_id'])) {
$_SESSION['user_id'] = $_SESSION['original_user_id'];
unset($_SESSION['original_user_id']);
return true;
}
return false;
}
// Function to check if currently impersonating
function is_impersonating() {
return isset($_SESSION['original_user_id']);
}
// Function to get the current user's data (handles impersonation)
function current_user() {
if (!is_logged_in()) {
return null;
}
$db = db();
$stmt = $db->prepare('SELECT users.*, roles.name AS role_name FROM users LEFT JOIN roles ON users.role_id = roles.id WHERE users.id = ?');
$stmt->execute([$_SESSION['user_id']]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
// Function to get the original admin user if impersonating
function original_user() {
if (!is_impersonating()) {
return null;
}
$db = db();
$stmt = $db->prepare('SELECT users.*, roles.name AS role_name FROM users LEFT JOIN roles ON users.role_id = roles.id WHERE users.id = ?');
$stmt->execute([$_SESSION['original_user_id']]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}

26
includes/pexels.php Normal file
View File

@ -0,0 +1,26 @@
<?php
function pexels_key() {
$k = getenv('PEXELS_KEY');
return $k && strlen($k) > 0 ? $k : 'Vc99rnmOhHhJAbgGQoKLZtsaIVfkeownoQNbTj78VemUjKh08ZYRbf18';
}
function pexels_get($url) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [ 'Authorization: '. pexels_key() ],
CURLOPT_TIMEOUT => 15,
]);
$resp = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code >= 200 && $code < 300 && $resp) return json_decode($resp, true);
return null;
}
function download_to($srcUrl, $destPath) {
$data = file_get_contents($srcUrl);
if ($data === false) return false;
if (!is_dir(dirname($destPath))) mkdir(dirname($destPath), 0775, true);
return file_put_contents($destPath, $data) !== false;
}
?>

132
index.php
View File

@ -1,3 +1,22 @@
<?php
// Fetch images from Pexels API
$api_url = 'http://' . $_SERVER['HTTP_HOST'] . '/api/pexels.php?action=multiple&queries=marketing,social%20media,analytics,creative,dashboard';
$images_json = @file_get_contents($api_url);
$images = $images_json ? json_decode($images_json, true) : [];
function get_image($index, $fallback_url) {
global $images;
return htmlspecialchars($images[$index]['src'] ?? $fallback_url);
}
$hero_image = get_image(0, 'https://picsum.photos/1200/800');
$about_image = get_image(1, 'https://picsum.photos/600/400');
$portfolio_images = [
get_image(2, 'https://picsum.photos/600/400?random=1'),
get_image(3, 'https://picsum.photos/600/400?random=2'),
get_image(4, 'https://picsum.photos/600/400?random=3'),
];
?>
<!DOCTYPE html>
<html lang="en">
<head>
@ -23,10 +42,26 @@
<!-- Stylesheets -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<!-- Icons -->
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<style>
#hero {
position: relative;
background-image: url('<?php echo $hero_image; ?>');
background-size: cover;
background-position: center;
color: white;
}
.hero-overlay {
position: relative;
z-index: 1;
background-color: rgba(0, 0, 0, 0.5);
padding: 6rem 0;
}
</style>
</head>
<body>
@ -41,9 +76,14 @@
<ul class="navbar-nav ms-auto">
<li class="nav-item"><a class="nav-link" href="#about">About</a></li>
<li class="nav-item"><a class="nav-link" href="#portfolio">Portfolio</a></li>
<li class="nav-item"><a class="nav-link" href="#pricing">Pricing</a></li>
<li class="nav-item"><a class="nav-link" href="#testimonials">Testimonials</a></li>
<li class="nav-item"><a class="nav-link" href="#contact">Contact</a></li>
</ul>
<div class="d-flex align-items-center ms-lg-3">
<a href="/login.php" class="btn btn-outline-secondary me-2">Login</a>
<a href="/register.php" class="btn btn-primary">Register</a>
</div>
</div>
</div>
</nav>
@ -52,10 +92,12 @@
<main>
<!-- Hero Section -->
<section id="hero" class="text-center">
<div class="container">
<h1 class="display-3">Automate Your Marketing with AI</h1>
<p class="lead col-lg-8 mx-auto">We build intelligent systems to scale your social media, content creation, and client management. Focus on strategy, not busywork.</p>
<a href="#contact" class="btn btn-primary btn-lg mt-4">Get a Demo</a>
<div class="hero-overlay">
<div class="container">
<h1 class="display-3">Automate Your Marketing with AI</h1>
<p class="lead col-lg-8 mx-auto">We build intelligent systems to scale your social media, content creation, and client management. Focus on strategy, not busywork.</p>
<a href="#contact" class="btn btn-primary btn-lg mt-4">Get a Demo</a>
</div>
</div>
</section>
@ -69,8 +111,7 @@
<p>Our platform provides a unified portal for your team and clients. Manage projects, approve content, and track performance—all in one place. We integrate with the tools you already love, like Stripe, Calendly, and Meta Ads, to create seamless, automated workflows.</p>
</div>
<div class="col-lg-6 text-center">
<!-- Placeholder for an illustration or screenshot -->
<img src="https://via.placeholder.com/500x350/007AFF/FFFFFF?text=Portal+Screenshot" class="img-fluid rounded shadow-sm" alt="Marketing Portal Screenshot">
<img src="<?php echo $about_image; ?>" class="img-fluid rounded shadow-sm" alt="Marketing Professionals Collaborating">
</div>
</div>
</div>
@ -81,9 +122,82 @@
<div class="container">
<h2 class="text-center">Our Work</h2>
<div class="row g-4 mt-4">
<div class="col-md-4"><div class="card"><img src="https://via.placeholder.com/600x400/EFEFEF/1D1D1F?text=Project+One" class="card-img-top" alt="Project One"><div class="card-body"><h5 class="card-title">Client A</h5><p class="card-text">Social Media Growth</p></div></div></div>
<div class="col-md-4"><div class="card"><img src="https://via.placeholder.com/600x400/EFEFEF/1D1D1F?text=Project+Two" class="card-img-top" alt="Project Two"><div class="card-body"><h5 class="card-title">Client B</h5><p class="card-text">Content Automation</p></div></div></div>
<div class="col-md-4"><div class="card"><img src="https://via.placeholder.com/600x400/EFEFEF/1D1D1F?text=Project+Three" class="card-img-top" alt="Project Three"><div class="card-body"><h5 class="card-title">Client C</h5><p class="card-text">Ad Campaign Management</p></div></div></div>
<div class="col-md-4"><div class="card h-100"><img src="<?php echo $portfolio_images[0]; ?>" class="card-img-top" alt="Social Media Growth Project"><div class="card-body"><h5 class="card-title">Client A</h5><p class="card-text">Social Media Growth</p></div></div></div>
<div class="col-md-4"><div class="card h-100"><img src="<?php echo $portfolio_images[1]; ?>" class="card-img-top" alt="Content Automation Project"><div class="card-body"><h5 class="card-title">Client B</h5><p class="card-text">Content Automation</p></div></div></div>
<div class="col-md-4"><div class="card h-100"><img src="<?php echo $portfolio_images[2]; ?>" class="card-img-top" alt="Ad Campaign Management Project"><div class="card-body"><h5 class="card-title">Client C</h5><p class="card-text">Ad Campaign Management</p></div></div></div>
</div>
</div>
</section>
<!-- Pricing Section -->
<section id="pricing">
<div class="container">
<h2 class="text-center">Simple, Transparent Pricing</h2>
<p class="text-center text-muted mb-5">Choose the plan that's right for your business. All prices are placeholders.</p>
<div class="row">
<!-- Plan 1 -->
<div class="col-lg-4">
<div class="card mb-5 mb-lg-0 h-100">
<div class="card-body d-flex flex-column">
<h5 class="card-title text-muted text-uppercase text-center">Starter</h5>
<h6 class="card-price text-center">$99<span class="period">/month</span></h6>
<hr>
<ul class="fa-ul">
<li><span class="fa-li"><i class="fas fa-check"></i></span>1 Client Portal</li>
<li><span class="fa-li"><i class="fas fa-check"></i></span>Content Calendar</li>
<li><span class="fa-li"><i class="fas fa-check"></i></span>Basic Reporting</li>
<li class="text-muted"><span class="fa-li"><i class="fas fa-times"></i></span>AI Content Generation</li>
<li class="text-muted"><span class="fa-li"><i class="fas fa-times"></i></span>Ad Integration</li>
<li class="text-muted"><span class="fa-li"><i class="fas fa-times"></i></span>Dedicated Support</li>
</ul>
<div class="d-grid mt-auto">
<a href="#" class="btn btn-outline-primary text-uppercase">Choose Plan</a>
</div>
</div>
</div>
</div>
<!-- Plan 2 -->
<div class="col-lg-4">
<div class="card mb-5 mb-lg-0 card-popular h-100">
<div class="card-body d-flex flex-column">
<h5 class="card-title text-muted text-uppercase text-center">Pro</h5>
<h6 class="card-price text-center">$299<span class="period">/month</span></h6>
<hr>
<ul class="fa-ul">
<li><span class="fa-li"><i class="fas fa-check"></i></span>10 Client Portals</li>
<li><span class="fa-li"><i class="fas fa-check"></i></span>Content Calendar</li>
<li><span class="fa-li"><i class="fas fa-check"></i></span>Advanced Reporting</li>
<li><span class="fa-li"><i class="fas fa-check"></i></span>AI Content Generation</li>
<li><span class="fa-li"><i class="fas fa-check"></i></span>Ad Integration</li>
<li class="text-muted"><span class="fa-li"><i class="fas fa-times"></i></span>Dedicated Support</li>
</ul>
<div class="d-grid mt-auto">
<a href="#" class="btn btn-primary text-uppercase">Choose Plan</a>
</div>
</div>
</div>
</div>
<!-- Plan 3 -->
<div class="col-lg-4">
<div class="card h-100">
<div class="card-body d-flex flex-column">
<h5 class="card-title text-muted text-uppercase text-center">Enterprise</h5>
<h6 class="card-price text-center">$599+<span class="period">/month</span></h6>
<hr>
<ul class="fa-ul">
<li><span class="fa-li"><i class="fas fa-check"></i></span>Unlimited Client Portals</li>
<li><span class="fa-li"><i class="fas fa-check"></i></span>Content Calendar</li>
<li><span class="fa-li"><i class="fas fa-check"></i></span>Advanced Reporting</li>
<li><span class="fa-li"><i class="fas fa-check"></i></span>AI Content Generation</li>
<li><span class="fa-li"><i class="fas fa-check"></i></span>Ad Integration</li>
<li><span class="fa-li"><i class="fas fa-check"></i></span>Dedicated Support</li>
</ul>
<div class="d-grid mt-auto">
<a href="#" class="btn btn-outline-primary text-uppercase">Choose Plan</a>
</div>
</div>
</div>
</div>
</div>
</div>
</section>

44
login.php Normal file
View File

@ -0,0 +1,44 @@
<?php
require_once 'includes/auth.php';
if (is_logged_in()) {
header('Location: /portal');
exit();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - AI-Powered Marketing Portal</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css">
</head>
<body>
<div class="container d-flex justify-content-center align-items-center vh-100">
<div class="card shadow-sm" style="width: 100%; max-width: 400px;">
<div class="card-body p-4">
<h2 class="card-title text-center mb-4">Client Login</h2>
<form action="auth_handler.php" method="POST">
<input type="hidden" name="action" value="login">
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary w-100">Login</button>
</form>
<div class="text-center mt-3">
<p>Don't have an account? <a href="register.php">Register here</a></p>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

7
logout.php Normal file
View File

@ -0,0 +1,7 @@
<?php
require_once 'includes/auth.php';
session_destroy();
header('Location: /login.php?logged_out=true');
exit();

View File

@ -0,0 +1,28 @@
<?php
require_once __DIR__ . '/../../includes/auth.php';
// Stop impersonation if requested
if (isset($_GET['stop'])) {
stop_impersonating();
header('Location: /portal/admin/users.php');
exit();
}
// Start impersonation if user_id is provided
if (isset($_GET['user_id'])) {
require_admin(); // Ensure only admins can initiate
$user_id_to_impersonate = (int)$_GET['user_id'];
if (impersonate_user($user_id_to_impersonate)) {
header('Location: /portal'); // Redirect to the portal dashboard as the impersonated user
exit();
} else {
// Handle failed impersonation (e.g., trying to impersonate an admin)
header('Location: /portal/admin/users.php?error=impersonation_failed');
exit();
}
}
// Default redirect if accessed directly without parameters
header('Location: /portal/admin/users.php');
exit();

48
portal/admin/users.php Normal file
View File

@ -0,0 +1,48 @@
<?php
require_once __DIR__ . '/../../includes/auth.php';
require_admin(); // Only admins can access this page
$db = db();
$stmt = $db->query('SELECT users.id, users.name, users.email, roles.name AS role_name FROM users LEFT JOIN roles ON users.role_id = roles.id ORDER BY users.name');
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
$page_title = 'Manage Users';
include __DIR__ . '/../includes/header.php';
?>
<div class="container-fluid p-4">
<h1 class="h3 mb-4">Manage Users</h1>
<div class="card">
<div class="card-body">
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Role</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td><?php echo htmlspecialchars($user['name']); ?></td>
<td><?php echo htmlspecialchars($user['email']); ?></td>
<td><?php echo htmlspecialchars($user['role_name']); ?></td>
<td>
<?php if (!in_array($user['role_name'], ['Super Admin', 'Agency Admin']) || $user['id'] == ($_SESSION['original_user_id'] ?? $_SESSION['user_id'])): ?>
<a href="/portal/admin/impersonate_handler.php?user_id=<?php echo $user['id']; ?>" class="btn btn-sm btn-outline-secondary">Impersonate</a>
<?php else: ?>
<button class="btn btn-sm btn-outline-secondary" disabled>Impersonate</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php include __DIR__ . '/../includes/footer.php'; ?>

12
portal/approvals.php Normal file
View File

@ -0,0 +1,12 @@
<?php
$page_title = 'Approvals';
include __DIR__ . '/includes/header.php';
?>
<div class="container-fluid p-4">
<h1 class="h3 mb-4">Approval Queue</h1>
<p>This page will display a list of creative assets and other items pending approval. Clients and managers will be able to review and approve or reject work from this screen.</p>
<!-- Approval list will go here -->
</div>
<?php include __DIR__ . '/includes/footer.php'; ?>

14
portal/assets.php Normal file
View File

@ -0,0 +1,14 @@
<?php require_once __DIR__ . '/includes/header.php'; ?>
<div class="container-fluid">
<h1 class="h2">Creative Assets</h1>
<p>Review and approve creative assets for your projects.</p>
<div class="card">
<div class="card-body">
<p class="text-center">There are no assets available for review at this time.</p>
</div>
</div>
</div>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

14
portal/billing.php Normal file
View File

@ -0,0 +1,14 @@
<?php require_once __DIR__ . '/includes/header.php'; ?>
<div class="container-fluid">
<h1 class="h2">Billing</h1>
<p>View your invoices and manage your subscription.</p>
<div class="card">
<div class="card-body">
<p class="text-center">You have no invoices.</p>
</div>
</div>
</div>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

12
portal/calendar.php Normal file
View File

@ -0,0 +1,12 @@
<?php
$page_title = 'Calendar';
include __DIR__ . '/includes/header.php';
?>
<div class="container-fluid p-4">
<h1 class="h3 mb-4">Project Calendar</h1>
<p>This page will contain a calendar view of project milestones, deadlines, and other important events. The data will be dynamically loaded based on project and user roles.</p>
<!-- Calendar integration will go here -->
</div>
<?php include __DIR__ . '/includes/footer.php'; ?>

14
portal/courses.php Normal file
View File

@ -0,0 +1,14 @@
<?php require_once __DIR__ . '/includes/header.php'; ?>
<div class="container-fluid">
<h1 class="h2">My Courses</h1>
<p>Access your purchased courses here.</p>
<div class="card">
<div class="card-body">
<p class="text-center">You have not purchased any courses yet.</p>
</div>
</div>
</div>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

View File

@ -0,0 +1,6 @@
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@ -0,0 +1,55 @@
<?php
require_once __DIR__ . '/../../includes/auth.php';
require_login();
$current_user = current_user();
$original_user = original_user(); // Get original user if impersonating
$page = basename($_SERVER['PHP_SELF']);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Client Portal</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="/assets/css/portal.css?v=<?php echo time(); ?>">
</head>
<body>
<?php if (is_impersonating()): ?>
<div class="impersonation-banner bg-warning text-dark text-center p-2">
You are currently viewing as <strong><?php echo htmlspecialchars($current_user['name']); ?></strong>.
<a href="/portal/admin/impersonate_handler.php?stop=true" class="fw-bold">Return to your admin view</a>.
</div>
<?php endif; ?>
<header class="portal-nav p-3 d-flex justify-content-between align-items-center">
<a href="/portal" class="h4 text-dark text-decoration-none">Client Portal</a>
<div>
<span class="me-3">Welcome, <?php echo htmlspecialchars($original_user ? $original_user['name'] : $current_user['name']); ?>!</span>
<a href="/logout.php" class="btn btn-outline-primary">Logout</a>
</div>
</header>
<div class="portal-main">
<aside class="sidebar p-3">
<nav class="nav flex-column">
<a class="nav-link <?php echo ($page == 'index.php') ? 'active' : ''; ?>" href="/portal">Dashboard</a>
<a class="nav-link <?php echo ($page == 'projects.php') ? 'active' : ''; ?>" href="/portal/projects.php">Projects</a>
<a class="nav-link <?php echo ($page == 'calendar.php') ? 'active' : ''; ?>" href="/portal/calendar.php">Calendar</a>
<a class="nav-link <?php echo ($page == 'assets.php') ? 'active' : ''; ?>" href="/portal/assets.php">Creative Assets</a>
<a class="nav-link <?php echo ($page == 'approvals.php') ? 'active' : ''; ?>" href="/portal/approvals.php">Approvals</a>
<a class="nav-link <?php echo ($page == 'metrics.php') ? 'active' : ''; ?>" href="/portal/metrics.php">Metrics</a>
<a class="nav-link <?php echo ($page == 'courses.php') ? 'active' : ''; ?>" href="/portal/courses.php">Courses</a>
<a class="nav-link <?php echo ($page == 'billing.php') ? 'active' : ''; ?>" href="/portal/billing.php">Billing</a>
<a class="nav-link <?php echo ($page == 'profile.php') ? 'active' : ''; ?>" href="/portal/profile.php">Profile</a>
<?php if (is_admin()): ?>
<hr>
<h6 class="nav-link-heading text-muted">Admin</h6>
<a class="nav-link <?php echo ($page == 'users.php') ? 'active' : ''; ?>" href="/portal/admin/users.php">Manage Users</a>
<?php endif; ?>
</nav>
</aside>
<main class="content">

31
portal/index.php Normal file
View File

@ -0,0 +1,31 @@
<?php
require_once __DIR__ . '/../includes/auth.php';
<div class="container-fluid">
<h1 class="h2">Dashboard</h1>
<p>Welcome to your client portal. Here you can manage your projects, view creative assets, access your courses, and handle billing.</p>
<div class="row mt-4">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h5 class="card-title">Recent Projects</h5>
<p class="card-text">You have no active projects.</p>
<a href="/portal/projects.php" class="btn btn-primary">View Projects</a>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h5 class="card-title">My Courses</h5>
<p class="card-text">You have not enrolled in any courses.</p>
<a href="/portal/courses.php" class="btn btn-primary">View Courses</a>
</div>
</div>
</div>
</div>
</div>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

16
portal/profile.php Normal file
View File

@ -0,0 +1,16 @@
<?php require_once __DIR__ . '/includes/header.php'; ?>
<div class="container-fluid">
<h1 class="h2">My Profile</h1>
<p>Your account information.</p>
<div class="card">
<div class="card-body">
<p><strong>Name:</strong> <?php echo htmlspecialchars($current_user['name']); ?></p>
<p><strong>Email:</strong> <?php echo htmlspecialchars($current_user['email']); ?></p>
<p><strong>Role:</strong> <?php echo htmlspecialchars($current_user['role_name']); ?></p>
</div>
</div>
</div>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

14
portal/projects.php Normal file
View File

@ -0,0 +1,14 @@
<?php require_once __DIR__ . '/includes/header.php'; ?>
<div class="container-fluid">
<h1 class="h2">My Projects</h1>
<p>Here is a list of your projects and their current status.</p>
<div class="card">
<div class="card-body">
<p class="text-center">No projects have been assigned to you yet.</p>
</div>
</div>
</div>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

48
register.php Normal file
View File

@ -0,0 +1,48 @@
<?php
require_once 'includes/auth.php';
if (is_logged_in()) {
header('Location: /portal');
exit();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register - AI-Powered Marketing Portal</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css">
</head>
<body>
<div class="container d-flex justify-content-center align-items-center vh-100">
<div class="card shadow-sm" style="width: 100%; max-width: 400px;">
<div class="card-body p-4">
<h2 class="card-title text-center mb-4">Create Account</h2>
<form action="auth_handler.php" method="POST">
<input type="hidden" name="action" value="register">
<div class="mb-3">
<label for="name" class="form-label">Full Name</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary w-100">Register</button>
</form>
<div class="text-center mt-3">
<p>Already have an account? <a href="login.php">Login here</a></p>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>