This commit is contained in:
Flatlogic Bot 2025-10-17 09:19:56 +00:00
parent dbf75ca780
commit b7c23d9356
17 changed files with 926 additions and 181 deletions

117
admin.php Normal file
View File

@ -0,0 +1,117 @@
<?php
session_start();
// Handle logout
if (isset($_GET['logout'])) {
session_unset();
session_destroy();
header('Location: login.php');
exit;
}
// If the user is not logged in as admin, redirect to the login page.
if (!isset($_SESSION['user']) || $_SESSION['user'] !== 'admin') {
header('Location: login.php');
exit;
}
// --- Data Fetching ---
require_once 'db/config.php';
$attendees = [];
try {
$pdo = db();
// Updated to select first_name instead of name
$stmt = $pdo->query('SELECT a.id, w.title AS webinar_title, a.first_name, a.email, a.consented, a.created_at, a.timezone, a.utm_source, a.utm_medium, a.utm_campaign, a.referrer, a.gclid, a.fbclid FROM attendees a JOIN webinars w ON a.webinar_id = w.id ORDER BY a.created_at DESC');
$attendees = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
die("Could not connect to the database: " . $e->getMessage());
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Board</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
background-color: #121212;
color: #e0e0e0;
}
.container {
width: 100%;
max-width: 1400px;
padding: 2rem;
}
h1 {
color: #ffd700;
}
.table {
color: #e0e0e0;
}
.table-dark {
--bs-table-bg: #212529;
border-color: #373b3e;
}
.logout-btn {
position: absolute;
top: 1rem;
right: 1rem;
}
</style>
</head>
<body>
<div class="container">
<a href="admin.php?logout=1" class="btn btn-secondary logout-btn">Logout</a>
<h1 class="mb-4 text-center">Webinar Attendees</h1>
<div class="table-responsive">
<table class="table table-dark table-striped table-hover">
<thead>
<tr>
<th>ID</th>
<th>Webinar</th>
<th>Name</th>
<th>Email</th>
<th>Consented</th>
<th>Registered At</th>
<th>Timezone</th>
<th>UTM Source</th>
<th>UTM Medium</th>
<th>UTM Campaign</th>
<th>Referrer</th>
<th>GCLID</th>
<th>FBCLID</th>
</tr>
</thead>
<tbody>
<?php if (empty($attendees)): ?>
<tr>
<td colspan="13" class="text-center">No attendees yet.</td>
</tr>
<?php else: ?>
<?php foreach ($attendees as $attendee): ?>
<tr>
<td><?= htmlspecialchars($attendee['id']) ?></td>
<td><?= htmlspecialchars($attendee['webinar_title']) ?></td>
<td><?= htmlspecialchars($attendee['first_name']) ?></td>
<td><?= htmlspecialchars($attendee['email']) ?></td>
<td><?= $attendee['consented'] ? 'Yes' : 'No' ?></td>
<td><?= htmlspecialchars($attendee['created_at']) ?></td>
<td><?= htmlspecialchars($attendee['timezone']) ?></td>
<td><?= htmlspecialchars($attendee['utm_source']) ?></td>
<td><?= htmlspecialchars($attendee['utm_medium']) ?></td>
<td><?= htmlspecialchars($attendee['utm_campaign']) ?></td>
<td><?= htmlspecialchars($attendee['referrer']) ?></td>
<td><?= htmlspecialchars($attendee['gclid']) ?></td>
<td><?= htmlspecialchars($attendee['fbclid']) ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</body>
</html>

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']) : ['professional portrait','man smiling','woman smiling'];
$out = [];
foreach ($qs as $q) {
$u = 'https://api.pexels.com/v1/search?query=' . urlencode(trim($q)) . '&orientation=square&per_page=1&page=1';
$d = pexels_get($u);
if ($d && !empty($d['photos'])) {
$p = $d['photos'][0];
$src = $p['src']['original'] ?? null;
$dest = __DIR__.'/../assets/images/pexels/'.$p['id'].'.jpg';
if ($src) 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/600',
'photographer' => 'Random Picsum',
'photographer_url' => 'https://picsum.photos/'
];
}
}
echo json_encode($out);
?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

126
dashboard.php Normal file
View File

@ -0,0 +1,126 @@
<?php
session_start();
require_once 'db/config.php';
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
$attendee = null;
$webinar = null;
$error = '';
$user_id = $_SESSION['user_id'];
try {
// Fetch attendee details
$stmt = db()->prepare("SELECT * FROM attendees WHERE id = ?");
$stmt->execute([$user_id]);
$attendee = $stmt->fetch();
if ($attendee) {
// Fetch webinar details
$stmt = db()->prepare("SELECT * FROM webinars WHERE id = ?");
$stmt->execute([$attendee['webinar_id']]);
$webinar = $stmt->fetch();
} else {
$error = 'Could not find your registration details.';
}
} catch (PDOException $e) {
$error = 'Database error. Please try again later.';
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Your Webinar Dashboard</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #1a202c;
color: #e2e8f0;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
}
.container {
max-width: 800px;
padding: 2rem;
}
.dashboard-box {
background-color: #2d3748;
border-radius: 0.5rem;
padding: 2rem;
border: 1px solid #4a5568;
}
h1 {
font-size: 2rem;
margin-bottom: 1rem;
}
.webinar-title {
font-size: 1.5rem;
font-weight: bold;
color: #f6e05e;
margin-bottom: 0.5rem;
}
.webinar-date {
font-size: 1.125rem;
margin-bottom: 1.5rem;
}
.join-button {
display: inline-block;
background-color: #f6e05e;
color: #1a202c;
padding: 0.75rem 1.5rem;
border-radius: 0.375rem;
text-decoration: none;
font-weight: bold;
font-size: 1.125rem;
transition: background-color 0.2s;
}
.join-button:hover {
background-color: #f6d32d;
}
.message {
padding: 1rem;
margin-bottom: 1rem;
border-radius: 0.25rem;
background-color: #c53030;
color: #fff;
}
</style>
</head>
<body>
<div class="container">
<div class="dashboard-box">
<h1>Your Dashboard</h1>
<?php if ($error): ?>
<div class="message"><?= htmlspecialchars($error) ?></div>
<?php elseif ($attendee && $webinar): ?>
<p>Hello, <strong><?= htmlspecialchars($attendee['first_name']) ?></strong>!</p>
<p>You are registered for the following webinar:</p>
<div class="webinar-title"><?= htmlspecialchars($webinar['title']) ?></div>
<div class="webinar-date">
<?php
$date = new DateTime($webinar['scheduled_at']);
echo $date->format('l, F j, Y \a\t g:i A T');
?>
</div>
<a href="join.php?id=<?= htmlspecialchars($attendee['id']) ?>" class="join-button">Join Webinar</a>
<?php else: ?>
<div class="message">Could not find your registration details.</div>
<?php endif; ?>
</div>
<footer style="margin-top: 2rem; font-size: 0.9rem; color: #a0aec0;">
<a href="index.php" style="color: #a0aec0; text-decoration: none;"> Back to Home</a>
</footer>
</div>
</body>
</html>

View File

@ -1,25 +1,48 @@
<?php
require_once 'config.php';
try {
// Connect without specifying a database
// Connect without specifying a database to ensure the DB exists
$pdo = new PDO('mysql:host='.DB_HOST, DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$pdo->exec('CREATE DATABASE IF NOT EXISTS '.DB_NAME);
echo "Database created or already exists." . PHP_EOL;
// Now connect to the specific database
$pdo = db();
$migrations = glob(__DIR__ . '/migrations/*.sql');
sort($migrations);
foreach ($migrations as $migration) {
$sql = file_get_contents($migration);
// 1. Create migrations table if it doesn't exist
$pdo->exec('CREATE TABLE IF NOT EXISTS migrations (
id INT AUTO_INCREMENT PRIMARY KEY,
migration VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)');
// 2. Get all migration files
$migration_files = glob(__DIR__ . '/migrations/*.sql');
sort($migration_files);
// 3. Get already executed migrations
$executed_migrations_stmt = $pdo->query('SELECT migration FROM migrations');
$executed_migrations = $executed_migrations_stmt->fetchAll(PDO::FETCH_COLUMN);
foreach ($migration_files as $file) {
$migration_name = basename($file);
// 4. If migration has not been run, execute it
if (!in_array($migration_name, $executed_migrations)) {
$sql = file_get_contents($file);
$pdo->exec($sql);
echo "Executed migration: $migration" . PHP_EOL;
// 5. Record the migration
$stmt = $pdo->prepare('INSERT INTO migrations (migration) VALUES (?)');
$stmt->execute([$migration_name]);
echo "Executed migration: $migration_name" . PHP_EOL;
}
}
echo "All migrations have been run." . PHP_EOL;
} catch (PDOException $e) {
die("Database migration failed: " . $e->getMessage());

View File

@ -0,0 +1,10 @@
ALTER TABLE attendees
ADD COLUMN timezone VARCHAR(255) DEFAULT NULL,
ADD COLUMN utm_source VARCHAR(255) DEFAULT NULL,
ADD COLUMN utm_medium VARCHAR(255) DEFAULT NULL,
ADD COLUMN utm_campaign VARCHAR(255) DEFAULT NULL,
ADD COLUMN utm_term VARCHAR(255) DEFAULT NULL,
ADD COLUMN utm_content VARCHAR(255) DEFAULT NULL,
ADD COLUMN referrer VARCHAR(255) DEFAULT NULL,
ADD COLUMN gclid VARCHAR(255) DEFAULT NULL,
ADD COLUMN fbclid VARCHAR(255) DEFAULT NULL;

View File

@ -0,0 +1 @@
ALTER TABLE `attendees` ADD `password` VARCHAR(255) NOT NULL;

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;
}
?>

View File

@ -1,9 +1,27 @@
<?php
require_once 'db/config.php';
$webinar = null;
try {
// Fetch the first upcoming webinar
$stmt = db()->prepare("SELECT * FROM webinars WHERE scheduled_at > NOW() ORDER BY scheduled_at ASC LIMIT 1");
$stmt->execute();
$webinar = $stmt->fetch();
} catch (PDOException $e) {
// Silently fail for now
}
$webinar_id = $webinar ? $webinar['id'] : 1; // Fallback to 1 if no webinar is found
$webinar_title = $webinar ? $webinar['title'] : 'Professional Vibe-Coding Webinar';
$webinar_date_str = $webinar ? (new DateTime($webinar['scheduled_at']))->format('l, F j \\ • \\ At g.i A T') : 'Monday, November 3 • At 6.00 PM PST';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Professional Vibe-Coding Webinar</title>
<title><?= htmlspecialchars($webinar_title) ?></title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
@ -71,8 +89,8 @@
</head>
<body>
<div class="container">
<h1 style="font-size: 2.5rem; font-weight: bold; color: #f6e05e; margin-bottom: 0.5rem;">Professional vibe-coding webinar</h1>
<p style="font-size: 1.25rem; color: #a0aec0; margin-bottom: 2rem;">by Flatlogic</p>
<h1 style="font-size: 2.5rem; font-weight: bold; color: #f6e05e; margin-bottom: 0.5rem;">Professional Vibe-Coding Webinar by Flatlogic</h1>
<p style="font-size: 1.25rem; color: #a0aec0; margin-bottom: 2rem;">Professional VibeCoding: the fastest way to go from an idea to a working app you own, running on your server, with your database, using real frameworks.</p>
<div class="schedule-block">
<h2>Schedule</h2>
<ul>
@ -81,13 +99,16 @@
<li> Q&A</li>
</ul>
<div class="date-time">
Monday, November 3 At 6.00 PM PST
<?= htmlspecialchars($webinar_date_str) ?>
</div>
<a href="register.php" class="register-button">Register Now </a>
<a href="register.php?webinar_id=<?= $webinar_id ?>" class="register-button">Register Now </a>
<div class="byline">
By: Philip Daineka, Alex Rubanau, Alexey Vertel
</div>
</div>
<header style="position: absolute; top: 1rem; right: 1rem;">
<a href="login.php" style="color: #1a202c; text-decoration: none; background-color: #f6e05e; padding: 0.75rem 1.5rem; border-radius: 0.375rem; font-weight: bold; font-size: 1.125rem;">Login</a>
</header>
</div>
</body>
</html>

78
join.php Normal file
View File

@ -0,0 +1,78 @@
<?php
require_once 'db/config.php';
$attendee_id_encoded = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_STRING);
if (!$attendee_id_encoded) {
http_response_code(400);
echo "Invalid link.";
exit;
}
$attendee_id = base64_decode($attendee_id_encoded);
$attendee = null;
$webinar = null;
try {
$stmt = db()->prepare("SELECT * FROM attendees WHERE id = ?");
$stmt->execute([$attendee_id]);
$attendee = $stmt->fetch();
if ($attendee) {
$stmt = db()->prepare("SELECT * FROM webinars WHERE id = ?");
$stmt->execute([$attendee['webinar_id']]);
$webinar = $stmt->fetch();
}
} catch (PDOException $e) {
// Log error
}
if (!$attendee || !$webinar) {
http_response_code(404);
echo "Registration not found.";
exit;
}
// For now, just a welcome message. In a real scenario, this would redirect to the webinar platform.
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome to the Webinar</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #1a202c;
color: #e2e8f0;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
}
.container {
max-width: 800px;
padding: 2rem;
}
h1 {
color: #f6e05e;
font-size: 2.5rem;
}
p {
font-size: 1.25rem;
}
</style>
</head>
<body>
<div class="container">
<h1>Welcome, <?= htmlspecialchars($attendee['name']) ?>!</h1>
<p>You are now joining the webinar: <strong><?= htmlspecialchars($webinar['title']) ?></strong></p>
<p>The webinar is scheduled for: <strong><?= (new DateTime($webinar['scheduled_at']))->format('l, F j, Y \a\t g:i A T') ?></strong></p>
</div>
</body>
</html>

199
login.php Normal file
View File

@ -0,0 +1,199 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'mail/MailService.php';
$error = '';
$success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['email'])) {
$email = trim($_POST['email']);
$password = isset($_POST['password']) ? $_POST['password'] : '';
// Admin Login
if ($email === 'admin' && $password === 'password') {
$_SESSION['user'] = 'admin';
header('Location: admin.php');
exit;
}
// Participant Login
try {
$stmt = db()->prepare("SELECT * FROM attendees WHERE email = ?");
$stmt->execute([$email]);
$attendee = $stmt->fetch();
if ($attendee && password_verify($password, $attendee['password'])) {
$_SESSION['user_id'] = $attendee['id'];
header('Location: dashboard.php');
exit;
} else {
$error = 'Invalid email or password. Would you like to <a href="register.php" style="color: #ffdd57;">register for the webinar</a>?';
}
} catch (PDOException $e) {
error_log($e->getMessage());
$error = 'A database error occurred. Please try again later.';
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Webinar Platform</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root {
--primary-color: #4a90e2;
--background-color: #f4f7f6;
--card-background: #ffffff;
--text-color: #333;
--input-border: #e0e0e0;
--input-focus-border: #4a90e2;
--error-color: #d9534f;
--success-color: #5cb85c;
}
body {
font-family: 'Roboto', sans-serif;
background-color: var(--background-color);
color: var(--text-color);
margin: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.logo {
font-weight: 700;
font-size: 24px;
margin-bottom: 2rem;
color: var(--primary-color);
}
.login-container {
width: 100%;
max-width: 400px;
padding: 2rem;
}
.login-card {
background-color: var(--card-background);
border-radius: 8px;
padding: 2.5rem;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05);
border: 1px solid var(--input-border);
}
h1 {
font-size: 24px;
font-weight: 500;
margin-top: 0;
margin-bottom: 0.5rem;
text-align: center;
}
.subtitle {
text-align: center;
margin-bottom: 2rem;
color: #777;
}
.form-group {
margin-bottom: 1.5rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
font-size: 14px;
}
input[type="email"], input[type="password"] {
width: 100%;
padding: 12px;
border: 1px solid var(--input-border);
border-radius: 4px;
box-sizing: border-box;
font-size: 16px;
transition: border-color 0.2s;
}
input[type="email"]:focus, input[type="password"]:focus {
outline: none;
border-color: var(--input-focus-border);
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
}
.submit-button {
width: 100%;
padding: 14px;
border: none;
background-color: var(--primary-color);
color: white;
font-weight: 700;
font-size: 16px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.submit-button:hover {
background-color: #357abd;
}
.message {
padding: 1rem;
margin-bottom: 1.5rem;
border-radius: 4px;
text-align: center;
font-size: 14px;
}
.error {
background-color: #fbe9e7;
color: var(--error-color);
border: 1px solid var(--error-color);
}
.success {
background-color: #e8f5e9;
color: var(--success-color);
border: 1px solid var(--success-color);
}
.footer-link {
text-align: center;
margin-top: 1.5rem;
font-size: 14px;
}
.footer-link a {
color: var(--primary-color);
text-decoration: none;
font-weight: 500;
}
.footer-link a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="login-container">
<div class="logo">WebinarPlatform</div>
<div class="login-card">
<h1>Welcome Back</h1>
<p class="subtitle">Login to access your dashboard</p>
<?php if ($error): ?><div class="message error"><?= $error ?></div><?php endif; ?>
<?php if ($success): ?><div class="message success"><?= $success ?></div><?php endif; ?>
<form method="POST" action="login.php">
<div class="form-group">
<label for="email">Email Address</label>
<input type="email" id="email" name="email" required value="<?= isset($_POST['email']) ? htmlspecialchars($_POST['email']) : '' ?>">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit" class="submit-button">Login</button>
</form>
<div class="footer-link">
Don't have an account? <a href="register.php">Register for the Webinar</a>
</div>
</div>
<div class="footer-link" style="margin-top: 2rem;">
<a href="index.php"> Back to Home</a>
</div>
</div>
</body>
</html>

View File

@ -1,191 +1,306 @@
<?php
declare(strict_types=1);
require_once 'db/config.php';
require_once 'mail/MailService.php';
$pdo = db();
// --- CONFIGURATION ---
$webinar_id = filter_input(INPUT_GET, 'webinar_id', FILTER_VALIDATE_INT);
$webinar = null;
$feedback = [];
$response = [];
// Fetch webinar details
// --- WEBINAR DETAILS ---
$webinar = null;
if ($webinar_id) {
try {
$stmt = $pdo->prepare('SELECT id, title, scheduled_at FROM webinars WHERE id = ?');
$stmt = db()->prepare("SELECT * FROM webinars WHERE id = ?");
$stmt->execute([$webinar_id]);
$webinar = $stmt->fetch(PDO::FETCH_ASSOC);
$webinar = $stmt->fetch();
} catch (PDOException $e) {
error_log("Webinar fetch error: " . $e->getMessage());
// Don't expose error details to user
// Log error, but don't show to user
}
}
// Handle form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$email = filter_var(trim($_POST['email'] ?? ''), FILTER_VALIDATE_EMAIL);
$submitted_webinar_id = filter_input(INPUT_POST, 'webinar_id', FILTER_VALIDATE_INT);
// --- FORM SUBMISSION (POST REQUEST) ---
if ($_SERVER["REQUEST_METHOD"] == "POST") {
header('Content-Type: application/json');
// --- DATA CAPTURE ---
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$first_name = filter_input(INPUT_POST, 'first_name', FILTER_SANITIZE_STRING);
$password = filter_input(INPUT_POST, 'password', FILTER_UNSAFE_RAW);
$confirm_password = filter_input(INPUT_POST, 'confirm_password', FILTER_UNSAFE_RAW);
$consent = filter_input(INPUT_POST, 'consent', FILTER_VALIDATE_BOOLEAN);
$timezone = filter_input(INPUT_POST, 'timezone', FILTER_SANITIZE_STRING);
// Tracking data
$utm_source = filter_input(INPUT_POST, 'utm_source', FILTER_SANITIZE_STRING);
$utm_medium = filter_input(INPUT_POST, 'utm_medium', FILTER_SANITIZE_STRING);
$utm_campaign = filter_input(INPUT_POST, 'utm_campaign', FILTER_SANITIZE_STRING);
$utm_term = filter_input(INPUT_POST, 'utm_term', FILTER_SANITIZE_STRING);
$utm_content = filter_input(INPUT_POST, 'utm_content', FILTER_SANITIZE_STRING);
$referrer = filter_input(INPUT_POST, 'referrer', FILTER_SANITIZE_STRING);
$gclid = filter_input(INPUT_POST, 'gclid', FILTER_SANITIZE_STRING);
$fbclid = filter_input(INPUT_POST, 'fbclid', FILTER_SANITIZE_STRING);
if (!$email || !$consent || !$webinar) {
$response['error'] = 'Please provide a valid email and agree to the terms.';
echo json_encode($response);
exit;
}
if ($password !== $confirm_password) {
$response['error'] = 'Passwords do not match.';
echo json_encode($response);
exit;
}
$password_hash = password_hash($password, PASSWORD_DEFAULT);
if (!$name || !$email || !$submitted_webinar_id) {
$feedback = ['type' => 'error', 'message' => 'Please fill in all fields with valid information.'];
} else {
try {
$stmt = $pdo->prepare('INSERT INTO attendees (webinar_id, name, email) VALUES (?, ?, ?)');
$stmt->execute([$submitted_webinar_id, $name, $email]);
$feedback = ['type' => 'success', 'message' => 'Thank you for registering! A confirmation has been sent to your email.'];
// In a real app, you would trigger an email here.
} catch (PDOException $e) {
error_log("Registration error: " . $e->getMessage());
if ($e->errorInfo[1] == 1062) { // Duplicate entry
$feedback = ['type' => 'error', 'message' => 'This email address has already been registered for this webinar.'];
} else {
$feedback = ['type' => 'error', 'message' => 'An error occurred during registration. Please try again.'];
}
}
}
// Check for duplicates
$stmt = db()->prepare("SELECT id FROM attendees WHERE webinar_id = ? AND email = ?");
$stmt->execute([$webinar_id, $email]);
if ($stmt->fetch()) {
$response['error'] = 'You are already registered for this webinar.';
echo json_encode($response);
exit;
}
// Insert attendee
$sql = "INSERT INTO attendees (webinar_id, name, email, password, timezone, utm_source, utm_medium, utm_campaign, utm_term, utm_content, referrer, gclid, fbclid)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = db()->prepare($sql);
$stmt->execute([$webinar_id, $first_name, $email, $password_hash, $timezone, $utm_source, $utm_medium, $utm_campaign, $utm_term, $utm_content, $referrer, $gclid, $fbclid]);
$attendee_id = db()->lastInsertId();
// --- SEND CONFIRMATION EMAIL ---
$webinar_date = new DateTime($webinar['scheduled_at']);
$subject = "Confirmation: You're Registered for " . $webinar['title'];
$body_html = "<h1>You're in!</h1>"
. "<p>Thanks for registering for our webinar: <strong>{$webinar['title']}</strong>.</p>"
. "<p>It will take place on <strong>" . $webinar_date->format('l, F j, Y \a\t g:i A T') . "</strong>.</p>"
. "<p>You can now log in to your dashboard to see the details.</p>";
MailService::sendMail($email, $subject, $body_html);
$response['success'] = true;
$response['webinar_title'] = $webinar['title'];
$response['webinar_date_utc'] = $webinar['scheduled_at'];
} catch (PDOException $e) {
// Log error
$response['error'] = 'A database error occurred. Please try again later.';
} catch (Exception $e) {
// Log error
$response['error'] = 'An error occurred: ' . $e->getMessage();
}
echo json_encode($response);
exit;
}
// --- RENDER PAGE (GET REQUEST) ---
if (!$webinar) {
http_response_code(404);
echo "Webinar not found.";
exit;
}
?>
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Register for Webinar</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap" rel="stylesheet">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register for <?= htmlspecialchars($webinar['title']) ?></title>
<style>
:root {
--bg-color: #1A2035;
--primary-color: #F2A900;
--text-color: #FFFFFF;
--card-bg: #2A3045;
--error-color: #E57373;
--success-color: #81C784;
}
body {
margin: 0;
font-family: 'Poppins', sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
max-width: 500px;
width: 100%;
padding: 2rem;
}
.form-container {
background: var(--card-bg);
padding: 2.5rem;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
h1 {
text-align: center;
font-size: 2rem;
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--primary-color);
}
.webinar-info {
text-align: center;
margin-bottom: 2rem;
color: #E0E0E0;
}
.form-group {
margin-bottom: 1.5rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
}
input[type="text"], input[type="email"] {
width: 100%;
padding: 0.8rem;
border: 1px solid #4A5062;
border-radius: 8px;
background-color: var(--bg-color);
color: var(--text-color);
font-family: 'Poppins', sans-serif;
box-sizing: border-box;
}
.submit-btn {
width: 100%;
padding: 0.9rem;
border: none;
border-radius: 8px;
background-color: var(--primary-color);
color: var(--bg-color);
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.3s ease;
}
.submit-btn:hover {
background-color: #FFC107;
}
.feedback {
padding: 1rem;
margin-bottom: 1.5rem;
border-radius: 8px;
text-align: center;
}
.feedback.success {
background-color: var(--success-color);
color: #1B5E20;
}
.feedback.error {
background-color: var(--error-color);
color: #C62828;
}
.back-link {
display: block;
text-align: center;
margin-top: 2rem;
color: var(--primary-color);
text-decoration: none;
}
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background-color: #1a202c; color: #e2e8f0; margin: 0; padding: 2rem; }
.container { max-width: 900px; margin: auto; display: grid; grid-template-columns: 1fr 1fr; gap: 4rem; align-items: start; }
.left-pane h1 { font-size: 2.5rem; color: #f6e05e; margin-bottom: 1rem; }
.left-pane .date-time { font-size: 1.25rem; font-weight: bold; margin-bottom: 1.5rem; }
.left-pane .value-prop { font-size: 1.1rem; color: #a0aec0; line-height: 1.6; }
.speakers { margin-top: 2rem; }
.speakers h3 { font-size: 1.2rem; color: #cbd5e0; margin-bottom: 1rem; }
.speaker { display: flex; align-items: center; margin-bottom: 1rem; }
.speaker img { width: 50px; height: 50px; border-radius: 50%; margin-right: 1rem; background-color: #4a5568; }
.form-card { background-color: #2d3748; border-radius: 0.5rem; padding: 2rem; border: 1px solid #4a5568; }
.form-card h2 { margin-top: 0; font-size: 1.5rem; color: #f6e05e; text-align: center; }
.form-group { margin-bottom: 1.5rem; }
label { display: block; margin-bottom: 0.5rem; font-weight: bold; }
input[type="email"], input[type="text"] { width: 100%; padding: 0.75rem; background-color: #1a202c; border: 1px solid #4a5568; border-radius: 0.375rem; color: #e2e8f0; font-size: 1rem; box-sizing: border-box; }
.consent-group { display: flex; align-items: start; gap: 0.75rem; }
.consent-group label { font-weight: normal; font-size: 0.9rem; color: #a0aec0; }
.microcopy { font-size: 0.8rem; color: #718096; margin-top: 0.5rem; }
.submit-btn { display: block; width: 100%; background-color: #f6e05e; color: #1a202c; padding: 0.85rem; border: none; border-radius: 0.375rem; font-weight: bold; font-size: 1.125rem; cursor: pointer; transition: background-color 0.2s; }
.submit-btn:hover { background-color: #f6d32d; }
.submit-btn:disabled { background-color: #4a5568; cursor: not-allowed; }
#form-result { margin-top: 1.5rem; text-align: center; }
.success-message { background-color: #2d3748; border: 1px solid #4a5568; padding: 2rem; border-radius: 0.5rem; }
.error-message { background-color: #c53030; padding: 1rem; border-radius: 0.375rem; }
.calendar-buttons a, .copy-link-btn { display: inline-block; background-color: #4a5568; color: #e2e8f0; padding: 0.75rem 1.5rem; border-radius: 0.375rem; text-decoration: none; margin: 0.5rem; font-weight: bold; }
@media (max-width: 768px) { .container { grid-template-columns: 1fr; } }
</style>
</head>
<body>
<header style="padding: 1rem;">
<a href="index.php" style="color: #1a202c; text-decoration: none; background-color: #f6e05e; padding: 0.75rem 1.5rem; border-radius: 0.375rem; font-weight: bold; font-size: 1.125rem;">Back to Home</a>
</header>
<div class="container">
<div class="form-container">
<?php if ($feedback && $feedback['type'] === 'success'): ?>
<h1>Registration Successful!</h1>
<div class="feedback success"><?= htmlspecialchars($feedback['message']) ?></div>
<a href="index.php" class="back-link">&larr; Back to Webinars</a>
<?php elseif ($webinar): ?>
<h1>Register for Webinar</h1>
<p class="webinar-info">You are registering for: <strong><?= htmlspecialchars($webinar['title']) ?></strong></p>
<?php if ($feedback && $feedback['type'] === 'error'): ?>
<div class="feedback error"><?= htmlspecialchars($feedback['message']) ?></div>
<?php endif; ?>
<form action="register.php?webinar_id=<?= $webinar_id ?>" method="POST">
<input type="hidden" name="webinar_id" value="<?= $webinar_id ?>">
<div class="form-group">
<label for="name">Full Name</label>
<input type="text" id="name" name="name" required>
<div class="left-pane">
<h1><?= htmlspecialchars($webinar['title']) ?></h1>
<div class="date-time" id="local-date-time">Loading date...</div>
<p class="value-prop"><?= htmlspecialchars($webinar['description']) ?></p>
<div class="speakers">
<h3>Speakers</h3>
<div class="speaker"><img src="assets/images/pexels/22601971.jpg" alt="Philip Daineka"> <span>Philip Daineka</span></div>
<div class="speaker"><img src="assets/images/pexels/34285016.jpg" alt="Alex Rubanau"> <span>Alex Rubanau</span></div>
<div class="speaker"><img src="assets/images/pexels/34238049.jpg" alt="Alexey Vertel"> <span>Alexey Vertel</span></div>
</div>
</div>
<div class="right-pane">
<div id="registration-form-container">
<form id="registration-form" class="form-card" method="POST">
<h2>Register & Get Calendar Invite</h2>
<div class="form-group">
<label for="email">Email Address</label>
<label for="email">Email (required)</label>
<input type="email" id="email" name="email" required>
<p class="microcopy">Well send the join link & calendar invite.</p>
</div>
<button type="submit" class="submit-btn">Register Now</button>
<div class="form-group">
<label for="first_name">First name (optional)</label>
<input type="text" id="first_name" name="first_name">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" required>
</div>
<div class="form-group">
<label for="confirm_password">Confirm Password</label>
<input type="password" id="confirm_password" name="confirm_password" required>
</div>
<div class="form-group consent-group">
<input type="checkbox" id="consent" name="consent" required>
<label for="consent">I agree to receive info about this webinar.</label>
</div>
<p class="microcopy" style="margin-top:-1rem; margin-bottom: 1.5rem;">No spam. Unsubscribe anytime.</p>
<input type="hidden" name="timezone" id="timezone">
<input type="hidden" name="utm_source" id="utm_source">
<input type="hidden" name="utm_medium" id="utm_medium">
<input type="hidden" name="utm_campaign" id="utm_campaign">
<input type="hidden" name="utm_term" id="utm_term">
<input type="hidden" name="utm_content" id="utm_content">
<input type="hidden" name="referrer" id="referrer">
<input type="hidden" name="gclid" id="gclid">
<input type="hidden" name="fbclid" id="fbclid">
<button type="submit" class="submit-btn" id="submit-button">Register Now</button>
</form>
<a href="index.php" class="back-link">&larr; Cancel</a>
<?php else: ?>
<h1>Webinar Not Found</h1>
<p style="text-align:center">The requested webinar could not be found.</p>
<a href="index.php" class="back-link">&larr; Back to Webinars</a>
<?php endif; ?>
</div>
<div id="form-result"></div>
</div>
</div>
<script>
// --- TIMEZONE & DATE LOCALIZATION ---
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
document.getElementById('timezone').value = timezone;
const webinarDateUTC = '<?= $webinar["scheduled_at"] ?>';
const localDate = new Date(webinarDateUTC + 'Z'); // 'Z' for UTC
const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' };
document.getElementById('local-date-time').textContent = localDate.toLocaleDateString(undefined, options);
// --- TRACKING PARAMS ---
const urlParams = new URLSearchParams(window.location.search);
['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'gclid', 'fbclid'].forEach(param => {
if (urlParams.has(param)) {
document.getElementById(param).value = urlParams.get(param);
}
});
document.getElementById('referrer').value = document.referrer;
// --- AJAX FORM SUBMISSION ---
const form = document.getElementById('registration-form');
const formContainer = document.getElementById('registration-form-container');
const formResult = document.getElementById('form-result');
const submitButton = document.getElementById('submit-button');
form.addEventListener('submit', function(e) {
e.preventDefault();
submitButton.disabled = true;
submitButton.textContent = 'Processing...';
const formData = new FormData(form);
fetch(window.location.href, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
formContainer.style.display = 'none';
const webinarTitle = encodeURIComponent(data.webinar_title);
const webinarDate = new Date(data.webinar_date_utc + 'Z');
const startTime = webinarDate.toISOString().replace(/-|:|..*Z/g, '') + 'Z';
const endTime = new Date(webinarDate.getTime() + (60 * 60 * 1000)).toISOString().replace(/-|:|..*Z/g, '') + 'Z';
const googleLink = `https://www.google.com/calendar/render?action=TEMPLATE&text=${webinarTitle}&dates=${startTime.replace(/Z$/, '')}/${endTime.replace(/Z$/, '')}&details=Join+the+webinar!`;
const icsContent = [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'BEGIN:VEVENT',
`URL:${window.location.href}`,
`DTSTART:${startTime.replace(/Z$/, '')}`,
`DTEND:${endTime.replace(/Z$/, '')}`,
`SUMMARY:${data.webinar_title}`,
'DESCRIPTION:Join the webinar!',
'END:VEVENT',
'END:VCALENDAR'
].join('\n');
const outlookLink = `data:text/calendar;charset=utf8,${encodeURIComponent(icsContent)}`;
formResult.innerHTML = `
<div class="success-message">
<h3 style="color: #f6e05e; font-size: 1.75rem;">Youre in for ${data.webinar_title}!</h3>
<p style="color: #cbd5e0; font-size: 1.1rem;">Check your email for your confirmation. You can now log in to see the details.</p>
<div class="calendar-buttons">
<a href="${googleLink}" target="_blank">Add to Google Calendar</a>
<a href="${outlookLink}" download="webinar.ics">Add to Outlook (ICS)</a>
</div>
</div>
`;
} else {
formResult.innerHTML = `<div class="error-message">${data.error}</div>`;
submitButton.disabled = false;
submitButton.textContent = 'Register Now';
}
})
.catch(error => {
formResult.innerHTML = `<div class="error-message">An unexpected error occurred.</div>`;
submitButton.disabled = false;
submitButton.textContent = 'Register Now';
});
});
// --- COPY LINK ---
formResult.addEventListener('click', function(e) {
if (e.target.classList.contains('copy-link-btn')) {
e.preventDefault();
const link = e.target.dataset.link;
navigator.clipboard.writeText(link).then(() => {
e.target.textContent = 'Copied!';
setTimeout(() => {
e.target.textContent = 'Copy Join Link';
}, 2000);
});
}
});
</script>
</body>
</html>