diff --git a/add_customer.php b/add_customer.php index 1bf9981..1d06587 100644 --- a/add_customer.php +++ b/add_customer.php @@ -1,84 +1,126 @@ prepare("SELECT * FROM onboarding_templates WHERE msp_id = ? ORDER BY name ASC"); +$stmt->execute([$msp_id]); +$templates = $stmt->fetchAll(); + +$success = ''; $error = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $name = $_POST['name'] ?? ''; $email = $_POST['email'] ?? ''; + $template_id = $_POST['template_id'] ?: null; - if (empty($name)) { - $error = "Customer name is required."; - } else { + if ($name && $email) { try { - $stmt = db()->prepare("INSERT INTO customers (name, contact_email, status) VALUES (?, ?, 'onboarding')"); - $stmt->execute([$name, $email]); + db()->beginTransaction(); + + $stmt = db()->prepare("INSERT INTO customers (msp_id, template_id, name, contact_email, status) VALUES (?, ?, ?, ?, 'onboarding')"); + $stmt->execute([$msp_id, $template_id, $name, $email]); $customer_id = db()->lastInsertId(); - // Seed onboarding tasks - $tasks = [ - 'Initial Kick-off Meeting', - 'Discovery & Asset Inventory', - 'Agent Deployment (RMM)', - 'Network Security Audit', - 'Backup & Disaster Recovery Setup', - 'Microsoft 365 Tenant Review', - 'Final Configuration Handover' - ]; - - $stmt_task = db()->prepare("INSERT INTO onboarding_tasks (customer_id, task_name) VALUES (?, ?)"); - foreach ($tasks as $task) { - $stmt_task->execute([$customer_id, $task]); + if ($template_id) { + // Seed tasks from template steps + $stmt = db()->prepare("SELECT title FROM onboarding_template_steps WHERE template_id = ? ORDER BY order_index ASC"); + $stmt->execute([$template_id]); + $steps = $stmt->fetchAll(); + + foreach ($steps as $step) { + $stmt = db()->prepare("INSERT INTO onboarding_tasks (customer_id, task_name) VALUES (?, ?)"); + $stmt->execute([$customer_id, "Complete Step: " . $step['title']]); + } + } else { + // Default tasks if no template + $default_tasks = ['Initial Call', 'Service Agreement signed', 'Access credentials received', 'Network audit completed']; + foreach ($default_tasks as $task) { + $stmt = db()->prepare("INSERT INTO onboarding_tasks (customer_id, task_name) VALUES (?, ?)"); + $stmt->execute([$customer_id, $task]); + } } - - $message = "Customer added successfully! View Onboarding Checklist"; + + db()->commit(); + $success = "Customer added successfully!"; } catch (PDOException $e) { + db()->rollBack(); $error = "Error adding customer: " . $e->getMessage(); } + } else { + $error = "Please fill in all required fields."; } } + +include 'header.php'; ?> -
-
-

Add New Customer

-

Enter details to start the onboarding process.

+
+
+

Add New Customer

+

Register a new client and start the onboarding process.

- -
- +
+
+
+
+ + + + + +
+ +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
Choosing a template will automatically generate onboarding tasks based on that template.
+
+
+
+ + Cancel +
+
+
+
+
+
- - - -
- -
- - -
-
-
- - -
-
- - -
-
- - Cancel -
-
-
- -
- - Adding a customer will automatically generate a standard 7-step onboarding checklist to track progress.
- + diff --git a/customer_view.php b/customer_view.php new file mode 100644 index 0000000..e80e5c1 --- /dev/null +++ b/customer_view.php @@ -0,0 +1,128 @@ +prepare("SELECT c.*, m.name as msp_name FROM customers c JOIN msps m ON c.msp_id = m.id WHERE c.id = ?"); +$stmt->execute([$customer_id]); +$customer = $stmt->fetch(); + +if (!$customer || !$customer['template_id']) { + die("No onboarding template assigned to this customer."); +} + +// Fetch steps +$stmt = db()->prepare("SELECT * FROM onboarding_template_steps WHERE template_id = ? ORDER BY order_index ASC"); +$stmt->execute([$customer['template_id']]); +$steps = $stmt->fetchAll(); + +if (empty($steps)) { + die("This onboarding flow has no steps."); +} + +$current_step = $steps[$step_index] ?? null; +if (!$current_step) { + $step_index = 0; + $current_step = $steps[0]; +} + +// Fetch fields for current step +$stmt = db()->prepare("SELECT * FROM onboarding_template_fields WHERE step_id = ? ORDER BY order_index ASC"); +$stmt->execute([$current_step['id']]); +$fields = $stmt->fetchAll(); + +$is_last_step = ($step_index === count($steps) - 1); + +include 'header.php'; // We use the same header for simplicity, but we could make a "customer header" +?> + +
+
+
+
+
Welcome to
+

Customer Onboarding

+
+ $step): ?> +
+
+
+ +
+
+ +
+
+

+

+ +
+
+ +
+ +
+ +
+ + + > + + + + + + + +
+ +
+
+ Please download and review this document. +
+ Download +
+ + + > + +
+ +
+ +
+ 0): ?> + Back + +
+ + + + + + + +
+
+
+
+
+
+
+ + + + diff --git a/customers.php b/customers.php index 9d5a566..f489edc 100644 --- a/customers.php +++ b/customers.php @@ -1,83 +1,97 @@ query("SELECT * FROM customers ORDER BY name ASC"); + $stmt = db()->prepare("SELECT * FROM customers WHERE msp_id = ? ORDER BY name ASC"); + $stmt->execute([$msp_id]); $customers = $stmt->fetchAll(); } catch (PDOException $e) { // Handle error } ?> -
-
+
+
-

Managed Customers

-

List of all customers and their current status.

+

Managed Customers

+

List of all customers and their current status.

- Add Customer + Add Customer
-
- - - - - - - - - - - - - - - - - +
+
+
Customer NameEmailStatusOnboarding ProgressActions
- No customers found. Add your first customer. -
+ - - - - - + + + + + + + + + + - - - -
- prepare("SELECT COUNT(*) as total, SUM(CASE WHEN is_completed = 1 THEN 1 ELSE 0 END) as completed FROM onboarding_tasks WHERE customer_id = ?"); - $stmt->execute([$customer['id']]); - $row = $stmt->fetch(); - if ($row && $row['total'] > 0) { - $progress = round(($row['completed'] / $row['total']) * 100); - } - } catch (PDOException $e) {} - ?> -
-
-
- % -
- + Customer NameEmailStatusOnboarding ProgressActions
+ No customers found. Add your first customer.
+ + + + + + + + + + + + prepare("SELECT COUNT(*) as total, SUM(CASE WHEN is_completed = 1 THEN 1 ELSE 0 END) as completed FROM onboarding_tasks WHERE customer_id = ?"); + $stmt->execute([$customer['id']]); + $row = $stmt->fetch(); + if ($row && $row['total'] > 0) { + $progress = round(($row['completed'] / $row['total']) * 100); + } + } catch (PDOException $e) {} + ?> +
+
+
+ % + + + + + + + + + +
- + \ No newline at end of file diff --git a/db/migrations_002.sql b/db/migrations_002.sql new file mode 100644 index 0000000..b9241c7 --- /dev/null +++ b/db/migrations_002.sql @@ -0,0 +1,79 @@ +-- Plans table +CREATE TABLE IF NOT EXISTS plans ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + price DECIMAL(10, 2) DEFAULT 0.00, + max_customers INT DEFAULT 10, + can_custom_domain BOOLEAN DEFAULT FALSE, + is_trial BOOLEAN DEFAULT FALSE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- Seed default plans +INSERT INTO plans (name, price, max_customers, can_custom_domain, is_trial) VALUES +('Free Trial', 0.00, 5, FALSE, TRUE), +('Pro', 49.00, 50, TRUE, FALSE), +('Enterprise', 199.00, 1000, TRUE, FALSE); + +-- MSPs table (Tenants) +CREATE TABLE IF NOT EXISTS msps ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + plan_id INT NOT NULL, + subdomain VARCHAR(255) UNIQUE, + custom_domain VARCHAR(255) UNIQUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (plan_id) REFERENCES plans(id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- Users table +CREATE TABLE IF NOT EXISTS users ( + id INT AUTO_INCREMENT PRIMARY KEY, + msp_id INT NULL, -- NULL for superadmins + name VARCHAR(255) NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL, + password_hash VARCHAR(255) NOT NULL, + role ENUM('superadmin', 'msp_admin', 'msp_staff') NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (msp_id) REFERENCES msps(id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- Onboarding Templates +CREATE TABLE IF NOT EXISTS onboarding_templates ( + id INT AUTO_INCREMENT PRIMARY KEY, + msp_id INT NOT NULL, + name VARCHAR(255) NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (msp_id) REFERENCES msps(id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- Onboarding Template Steps (Pages) +CREATE TABLE IF NOT EXISTS onboarding_template_steps ( + id INT AUTO_INCREMENT PRIMARY KEY, + template_id INT NOT NULL, + title VARCHAR(255) NOT NULL, + description TEXT, + order_index INT DEFAULT 0, + FOREIGN KEY (template_id) REFERENCES onboarding_templates(id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- Onboarding Template Fields/Content +CREATE TABLE IF NOT EXISTS onboarding_template_fields ( + id INT AUTO_INCREMENT PRIMARY KEY, + step_id INT NOT NULL, + type ENUM('text', 'form_input', 'form_textarea', 'form_select', 'download', 'upload') NOT NULL, + label VARCHAR(255) NOT NULL, + content TEXT, -- Used for 'text' or 'download' URL or 'options' for select + is_required BOOLEAN DEFAULT FALSE, + order_index INT DEFAULT 0, + FOREIGN KEY (step_id) REFERENCES onboarding_template_steps(id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- Update customers table to link to MSP +ALTER TABLE customers ADD COLUMN msp_id INT NOT NULL AFTER id; +ALTER TABLE customers ADD CONSTRAINT fk_customer_msp FOREIGN KEY (msp_id) REFERENCES msps(id) ON DELETE CASCADE; + +-- Create a default superadmin (password: admin123) +-- In a real app, this would be handled via a setup script or CLI. +INSERT INTO users (name, email, password_hash, role) VALUES +('Super Admin', 'admin@msp-portal.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'superadmin'); diff --git a/db/setup.php b/db/setup.php index f1c2b05..b40216f 100644 --- a/db/setup.php +++ b/db/setup.php @@ -2,9 +2,14 @@ require_once __DIR__ . '/config.php'; try { - $sql = file_get_contents(__DIR__ . '/migrations_001.sql'); - db()->exec($sql); + $migrations = glob(__DIR__ . '/migrations_*.sql'); + sort($migrations); + foreach ($migrations as $migration) { + $sql = file_get_contents($migration); + db()->exec($sql); + echo "Executed migration: " . basename($migration) . "\n"; + } echo "Database setup successful."; } catch (PDOException $e) { echo "Error setting up database: " . $e->getMessage(); -} +} \ No newline at end of file diff --git a/edit_template.php b/edit_template.php new file mode 100644 index 0000000..20176d4 --- /dev/null +++ b/edit_template.php @@ -0,0 +1,249 @@ +prepare("SELECT * FROM onboarding_templates WHERE id = ? AND msp_id = ?"); +$stmt->execute([$template_id, $msp_id]); +$template = $stmt->fetch(); + +if (!$template) { + header('Location: templates.php'); + exit; +} + +// Handle Form Submissions +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { + if ($_POST['action'] === 'add_step') { + $title = $_POST['title']; + $description = $_POST['description']; + + $stmt = db()->prepare("SELECT MAX(order_index) FROM onboarding_template_steps WHERE template_id = ?"); + $stmt->execute([$template_id]); + $max_order = $stmt->fetchColumn() ?: 0; + + $stmt = db()->prepare("INSERT INTO onboarding_template_steps (template_id, title, description, order_index) VALUES (?, ?, ?, ?)"); + $stmt->execute([$template_id, $title, $description, $max_order + 1]); + header("Location: edit_template.php?id=$template_id"); + exit; + } + + if ($_POST['action'] === 'add_field') { + $step_id = $_POST['step_id']; + $type = $_POST['type']; + $label = $_POST['label']; + $content = $_POST['content'] ?? ''; + $is_required = isset($_POST['is_required']) ? 1 : 0; + + $stmt = db()->prepare("SELECT MAX(order_index) FROM onboarding_template_fields WHERE step_id = ?"); + $stmt->execute([$step_id]); + $max_order = $stmt->fetchColumn() ?: 0; + + $stmt = db()->prepare("INSERT INTO onboarding_template_fields (step_id, type, label, content, is_required, order_index) VALUES (?, ?, ?, ?, ?, ?)"); + $stmt->execute([$step_id, $type, $label, $content, $is_required, $max_order + 1]); + header("Location: edit_template.php?id=$template_id"); + exit; + } + + if ($_POST['action'] === 'delete_step') { + $step_id = $_POST['step_id']; + $stmt = db()->prepare("DELETE FROM onboarding_template_steps WHERE id = ? AND template_id = ?"); + $stmt->execute([$step_id, $template_id]); + header("Location: edit_template.php?id=$template_id"); + exit; + } +} + +// Fetch all steps and their fields +$stmt = db()->prepare("SELECT * FROM onboarding_template_steps WHERE template_id = ? ORDER BY order_index ASC"); +$stmt->execute([$template_id]); +$steps = $stmt->fetchAll(); + +foreach ($steps as &$step) { + $stmt = db()->prepare("SELECT * FROM onboarding_template_fields WHERE step_id = ? ORDER BY order_index ASC"); + $stmt->execute([$step['id']]); + $step['fields'] = $stmt->fetchAll(); +} + +include 'header.php'; +?> + +
+
+ Back to Templates +
+
+

Editing Template:

+

+
+ +
+
+ + +
+

This template has no steps yet.

+

Start by adding a new page to your onboarding flow.

+ +
+ + +
+ $step): ?> +
+
+

Step :

+
+ +
+ + + +
+
+
+
+

+ +
+ +
+
+
+ + + + * + +
+
+ + +
+
+ +
+ + + +
+ + + +
+ No items added to this step yet. +
+ +
+
+
+ + + + +
+
+ + + + + + + diff --git a/header.php b/header.php index ee876a0..10eafad 100644 --- a/header.php +++ b/header.php @@ -1,7 +1,17 @@ MSP Connect | Customer Success Platform - - - - - @@ -32,7 +37,6 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; - @@ -41,14 +45,29 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; MSP Connect
- Admin - + + + + () + + + + Login + Register MSP +
- \ No newline at end of file + diff --git a/index.php b/index.php index a0d7d17..4063881 100644 --- a/index.php +++ b/index.php @@ -1,138 +1,109 @@ 0, - 'active_onboarding' => 0, - 'qbrs_scheduled' => 4, - 'open_tickets' => 12 -]; - -try { - $stmt = db()->query("SELECT COUNT(*) FROM customers"); - $stats['total_customers'] = $stmt->fetchColumn(); - - $stmt = db()->query("SELECT COUNT(*) FROM customers WHERE status = 'onboarding'"); - $stats['active_onboarding'] = $stmt->fetchColumn(); -} catch (PDOException $e) { - // Handle error gracefully +session_start(); +if (!isset($_SESSION['user_id'])) { + header('Location: login.php'); + exit; } -// Fetch recent customers -$recent_customers = []; -try { - $stmt = db()->query("SELECT * FROM customers ORDER BY created_at DESC LIMIT 5"); - $recent_customers = $stmt->fetchAll(); -} catch (PDOException $e) { - // Handle error +require_once 'db/config.php'; + +// If superadmin, redirect to superadmin dashboard +if ($_SESSION['role'] === 'superadmin') { + header('Location: superadmin_dashboard.php'); + exit; } + +$msp_id = $_SESSION['msp_id']; + +// Stats for current MSP +$total_customers = db()->prepare("SELECT COUNT(*) FROM customers WHERE msp_id = ?"); +$total_customers->execute([$msp_id]); +$total_customers = $total_customers->fetchColumn(); + +$onboarding_count = db()->prepare("SELECT COUNT(*) FROM customers WHERE msp_id = ? AND status = 'onboarding'"); +$onboarding_count->execute([$msp_id]); +$onboarding_count = $onboarding_count->fetchColumn(); + +$active_count = db()->prepare("SELECT COUNT(*) FROM customers WHERE msp_id = ? AND status = 'active'"); +$active_count->execute([$msp_id]); +$active_count = $active_count->fetchColumn(); + +// Recent Customers +$stmt = db()->prepare("SELECT * FROM customers WHERE msp_id = ? ORDER BY created_at DESC LIMIT 5"); +$stmt->execute([$msp_id]); +$recent_customers = $stmt->fetchAll(); + +include 'header.php'; ?> -
-
-

MSP Operations Dashboard

-

Welcome back, Admin. Here's what's happening today.

+
+
+

Dashboard

+

Welcome back, . Here's what's happening with your clients.

-
Total Customers
-
+
+
Total Clients
-
Active Onboarding
-
+
+
In Onboarding
-
QBRs This Quarter
-
-
-
-
Open Support Tickets
-
+
+
Active Managed
-
-
- Recent Customers - View All -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Customer NameEmail AddressStatusAdded OnAction
- - No customers added yet. Add your first customer. -
- - - Onboarding - - - Details - -
-
- -
-
-
Quick Actions
-
- - Onboard New Customer - - - +
+
+
+
+
+

Recent Customers

+ View All +
+
+ + +
+
+ +
+ + + +
+ + +

No customers yet. Add your first one!

+ +
+
-
-
Integration Status
-
-
- ConnectWise PSA - Connected +
+
+
+

Onboarding Help

+

Need to streamline your client intake? Use our template builder to create custom onboarding flows.

+ Manage Templates
-
- Microsoft 365 - Connected -
-
- Datto RMM - Configure +
+ +
+
+

System Updates

+
+
v2.0 - Multi-tenancy & Templates Mar 1
+
v1.5 - QBR Support Feb 20
+
- \ No newline at end of file + diff --git a/login.php b/login.php new file mode 100644 index 0000000..609c3de --- /dev/null +++ b/login.php @@ -0,0 +1,72 @@ +prepare("SELECT * FROM users WHERE email = ?"); + $stmt->execute([$email]); + $user = $stmt->fetch(); + + if ($user && password_verify($password, $user['password_hash'])) { + $_SESSION['user_id'] = $user['id']; + $_SESSION['msp_id'] = $user['msp_id']; + $_SESSION['role'] = $user['role']; + $_SESSION['user_name'] = $user['name']; + + header('Location: index.php'); + exit; + } else { + $error = 'Invalid email or password.'; + } +} + +include 'header.php'; +?> + +
+
+
+
+
+
+

MSP Portal Login

+

Welcome back! Please enter your details.

+
+ + +
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ +
+

Don't have an account? Register your MSP

+
+
+
+
+
+
+ + diff --git a/logout.php b/logout.php new file mode 100644 index 0000000..37bc5ab --- /dev/null +++ b/logout.php @@ -0,0 +1,5 @@ +prepare("UPDATE msps SET subdomain = ?, custom_domain = ?, plan_id = ? WHERE id = ?"); + $stmt->execute([$subdomain ?: null, $custom_domain ?: null, $plan_id, $msp_id]); + $success = "MSP configuration updated successfully."; + } catch (PDOException $e) { + $error = "Error updating MSP: " . $e->getMessage(); + } + } +} + +$stmt = db()->query("SELECT m.*, p.name as plan_name FROM msps m JOIN plans p ON m.plan_id = p.id ORDER BY m.name ASC"); +$msps = $stmt->fetchAll(); + +$stmt = db()->query("SELECT * FROM plans"); +$plans = $stmt->fetchAll(); + +include 'header.php'; +?> + +
+
+

Manage MSPs

+

Manage tenant subdomains, custom domains, and subscription plans.

+
+ + +
+ + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
MSP NameCurrent PlanSubdomainCustom DomainActions
+ +
+
+
+
+ + + diff --git a/onboarding.php b/onboarding.php index 177e2fd..fc36bf0 100644 --- a/onboarding.php +++ b/onboarding.php @@ -1,163 +1,107 @@

Customer Not Found

Back to Customer List
"; - require_once __DIR__ . '/footer.php'; + header('Location: customers.php'); exit; } -// Fetch customer -$customer = null; -try { - $stmt = db()->prepare("SELECT * FROM customers WHERE id = ?"); - $stmt->execute([$customer_id]); - $customer = $stmt->fetch(); -} catch (PDOException $e) {} +// Security: Check if customer belongs to the current MSP +$stmt = db()->prepare("SELECT * FROM customers WHERE id = ? AND msp_id = ?"); +$stmt->execute([$customer_id, $msp_id]); +$customer = $stmt->fetch(); if (!$customer) { - echo "

Customer Not Found

Back to Customer List
"; - require_once __DIR__ . '/footer.php'; + header('Location: customers.php'); exit; } -// Fetch tasks -$tasks = []; -try { - $stmt = db()->prepare("SELECT * FROM onboarding_tasks WHERE customer_id = ? ORDER BY id ASC"); - $stmt->execute([$customer_id]); - $tasks = $stmt->fetchAll(); -} catch (PDOException $e) {} +// Fetch tasks for the customer +$stmt = db()->prepare("SELECT * FROM onboarding_tasks WHERE customer_id = ? ORDER BY id ASC"); +$stmt->execute([$customer_id]); +$tasks = $stmt->fetchAll(); -// Calculate progress -$total_tasks = count($tasks); -$completed_tasks = count(array_filter($tasks, function($t) { return $t['is_completed']; })); -$progress = $total_tasks > 0 ? round(($completed_tasks / $total_tasks) * 100) : 0; +include 'header.php'; ?> -
-
+
+
-

Onboarding Checklist:

-

Manage and track setup tasks for this customer.

+

Onboarding:

+

Track and manage the setup process for this client.

- - Back to List - -
- -
-
-
-
Overall Onboarding Progress
-
%
-
-
-
-
-
-
- -
-
Tasks & Milestones
-
- -
- data-task-id=""> - - - - -
- -
-
- -
- - - - +
+
+ +
+
+
+
+

Onboarding Checklist

+
+ +
+
+ + onchange="updateTask(, this.checked)"> + +
+
+ + + +
+ +

No tasks assigned to this customer.

+
+ +
+
+
+
+ +
+
+
+
Customer Information
+
+ + +
+
+ + + + +
+
+ + +
+
+
+
- - - + + \ No newline at end of file diff --git a/register.php b/register.php new file mode 100644 index 0000000..963b1f9 --- /dev/null +++ b/register.php @@ -0,0 +1,131 @@ +query("SELECT * FROM plans"); +$plans = $stmt->fetchAll(); + +$error = ''; +$success = ''; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $msp_name = $_POST['msp_name'] ?? ''; + $plan_id = $_POST['plan_id'] ?? ''; + $user_name = $_POST['user_name'] ?? ''; + $email = $_POST['email'] ?? ''; + $password = $_POST['password'] ?? ''; + $confirm_password = $_POST['confirm_password'] ?? ''; + + if ($password !== $confirm_password) { + $error = 'Passwords do not match.'; + } else { + try { + db()->beginTransaction(); + + $stmt = db()->prepare("INSERT INTO msps (name, plan_id) VALUES (?, ?)"); + $stmt->execute([$msp_name, $plan_id]); + $msp_id = db()->lastInsertId(); + + $password_hash = password_hash($password, PASSWORD_DEFAULT); + $stmt = db()->prepare("INSERT INTO users (msp_id, name, email, password_hash, role) VALUES (?, ?, ?, ?, 'msp_admin')"); + $stmt->execute([$msp_id, $user_name, $email, $password_hash]); + + db()->commit(); + + $_SESSION['user_id'] = db()->lastInsertId(); + $_SESSION['msp_id'] = $msp_id; + $_SESSION['role'] = 'msp_admin'; + $_SESSION['user_name'] = $user_name; + + header('Location: index.php'); + exit; + } catch (PDOException $e) { + db()->rollBack(); + if ($e->getCode() == 23000) { + $error = 'Email already registered.'; + } else { + $error = 'Error during registration: ' . $e->getMessage(); + } + } + } +} + +include 'header.php'; +?> + +
+
+
+
+
+
+

MSP Registration

+

Start managing your customer onboarding today.

+
+ + +
+ + +
+
+
+ + +
+
+ + +
+
+ +
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ +
+
+ +
+

Already have an account? Sign In

+
+
+
+
+
+
+ + diff --git a/superadmin_dashboard.php b/superadmin_dashboard.php new file mode 100644 index 0000000..9a12c77 --- /dev/null +++ b/superadmin_dashboard.php @@ -0,0 +1,77 @@ +query("SELECT COUNT(*) FROM msps")->fetchColumn(); +$total_customers = db()->query("SELECT COUNT(*) FROM customers")->fetchColumn(); +$total_users = db()->query("SELECT COUNT(*) FROM users")->fetchColumn(); + +// Recent MSPs +$stmt = db()->query("SELECT m.*, p.name as plan_name FROM msps m JOIN plans p ON m.plan_id = p.id ORDER BY m.created_at DESC LIMIT 5"); +$recent_msps = $stmt->fetchAll(); + +include 'header.php'; +?> + +
+
+

Superadmin Dashboard

+

Overview of all MSPs and system health.

+
+ +
+
+
+
Total MSPs
+
+
+
+
Total Customers
+
+
+
+
Total Users
+
+
+ +
+
+
+

Recent MSP Registrations

+ Manage All MSPs +
+ + + + + + + + + + + + + + + + + + + + + +
MSP NamePlanSubdomainCreated AtAction
+ +
+
+
+
+ + diff --git a/templates.php b/templates.php new file mode 100644 index 0000000..fc51d47 --- /dev/null +++ b/templates.php @@ -0,0 +1,107 @@ +prepare("INSERT INTO onboarding_templates (msp_id, name, description) VALUES (?, ?, ?)"); + $stmt->execute([$msp_id, $name, $description]); + $template_id = db()->lastInsertId(); + header("Location: edit_template.php?id=$template_id"); + exit; + } +} + +$stmt = db()->prepare("SELECT * FROM onboarding_templates WHERE msp_id = ? ORDER BY created_at DESC"); +$stmt->execute([$msp_id]); +$templates = $stmt->fetchAll(); + +include 'header.php'; +?> + +
+
+
+

Onboarding Templates

+

Create multi-page onboarding flows for your customers.

+
+ +
+ +
+ +
+
+
+

+

+ + prepare("SELECT COUNT(*) FROM onboarding_template_steps WHERE template_id = ?"); + $stmt->execute([$template['id']]); + $steps_count = $stmt->fetchColumn(); + ?> + +
+ Steps + Manage Template +
+
+
+
+ + + +
+
+ +

No Templates Found

+

You haven't created any onboarding templates yet. Click the button above to start.

+
+
+ +
+
+ + + + + +