+
-
-
-
-
-
-
-
-
-
-
-
-
- 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"
+?>
+
+
+
+
+
+
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 Name |
- Email |
- Status |
- Onboarding Progress |
- Actions |
-
-
-
-
-
- |
- 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 Name |
+ Email |
+ Status |
+ Onboarding Progress |
+ Actions |
+
+
+
+
+
+ |
+ 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';
+?>
+
+
+
+
+
+
+
This template has no steps yet.
+
Start by adding a new page to your onboarding flow.
+
+
+
+
+
+ $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
-
\ 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.
+
+
-
Total Customers
-
+
+
Total Clients
-
Active Onboarding
-
+
+
In Onboarding
-
-
Open Support Tickets
-
+
+
Active Managed
-
-
-
-
-
Quick Actions
-
-
- Onboard New Customer
-
-
-
+
+
-
-
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';
+?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | MSP Name |
+ Current Plan |
+ Subdomain |
+ Custom Domain |
+ Actions |
+
+
+
+
+
+ |
+ |
+ |
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 "
";
- 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';
+?>
+
+
+
+
+
+
+
+
+
+
Recent MSP Registrations
+
Manage All MSPs
+
+
+
+
+ | MSP Name |
+ Plan |
+ Subdomain |
+ Created At |
+ Action |
+
+
+
+
+
+ |
+ |
+ |
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+
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();
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
No Templates Found
+
You haven't created any onboarding templates yet. Click the button above to start.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+