diff --git a/admin.php b/admin.php new file mode 100644 index 0000000..87d4fdc --- /dev/null +++ b/admin.php @@ -0,0 +1,117 @@ +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()); +} + +?> + + + + + + Admin Board + + + + +
+ Logout +

Webinar Attendees

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDWebinarNameEmailConsentedRegistered AtTimezoneUTM SourceUTM MediumUTM CampaignReferrerGCLIDFBCLID
No attendees yet.
+
+
+ + diff --git a/api/pexels.php b/api/pexels.php new file mode 100644 index 0000000..9cc0c60 --- /dev/null +++ b/api/pexels.php @@ -0,0 +1,29 @@ + '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); + ?> \ No newline at end of file diff --git a/assets/images/pexels/22601971.jpg b/assets/images/pexels/22601971.jpg new file mode 100644 index 0000000..7f67267 Binary files /dev/null and b/assets/images/pexels/22601971.jpg differ diff --git a/assets/images/pexels/34238049.jpg b/assets/images/pexels/34238049.jpg new file mode 100644 index 0000000..dec0503 Binary files /dev/null and b/assets/images/pexels/34238049.jpg differ diff --git a/assets/images/pexels/34285016.jpg b/assets/images/pexels/34285016.jpg new file mode 100644 index 0000000..8448f03 Binary files /dev/null and b/assets/images/pexels/34285016.jpg differ diff --git a/assets/pasted-20251016-160256-cf9071d4.jpg b/assets/pasted-20251016-160256-cf9071d4.jpg new file mode 100644 index 0000000..05da89c Binary files /dev/null and b/assets/pasted-20251016-160256-cf9071d4.jpg differ diff --git a/assets/pasted-20251016-161033-a327c220.png b/assets/pasted-20251016-161033-a327c220.png new file mode 100644 index 0000000..57d7074 Binary files /dev/null and b/assets/pasted-20251016-161033-a327c220.png differ diff --git a/assets/vm-shot-2025-10-16T16-02-18-334Z.jpg b/assets/vm-shot-2025-10-16T16-02-18-334Z.jpg new file mode 100644 index 0000000..05da89c Binary files /dev/null and b/assets/vm-shot-2025-10-16T16-02-18-334Z.jpg differ diff --git a/dashboard.php b/dashboard.php new file mode 100644 index 0000000..9e87d2e --- /dev/null +++ b/dashboard.php @@ -0,0 +1,126 @@ +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.'; +} + +?> + + + + + + Your Webinar Dashboard + + + +
+
+

Your Dashboard

+ +
+ +

Hello, !

+

You are registered for the following webinar:

+
+
+ format('l, F j, Y \a\t g:i A T'); + ?> +
+ Join Webinar + +
Could not find your registration details.
+ +
+ +
+ + diff --git a/db/migrate.php b/db/migrate.php index b922e8f..31962db 100644 --- a/db/migrate.php +++ b/db/migrate.php @@ -1,26 +1,49 @@ - 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); - $pdo->exec($sql); - echo "Executed migration: $migration" . PHP_EOL; + // 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); + + // 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()); -} +} \ No newline at end of file diff --git a/db/migrations/004_add_tracking_to_attendees.sql b/db/migrations/004_add_tracking_to_attendees.sql new file mode 100644 index 0000000..d054cc2 --- /dev/null +++ b/db/migrations/004_add_tracking_to_attendees.sql @@ -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; diff --git a/db/migrations/005_add_password_to_attendees.sql b/db/migrations/005_add_password_to_attendees.sql new file mode 100644 index 0000000..4ac011a --- /dev/null +++ b/db/migrations/005_add_password_to_attendees.sql @@ -0,0 +1 @@ +ALTER TABLE `attendees` ADD `password` VARCHAR(255) NOT NULL; \ No newline at end of file diff --git a/includes/pexels.php b/includes/pexels.php new file mode 100644 index 0000000..c2b95ff --- /dev/null +++ b/includes/pexels.php @@ -0,0 +1,26 @@ + 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; + } + ?> \ No newline at end of file diff --git a/index.php b/index.php index 30caa1e..7ae8f1b 100644 --- a/index.php +++ b/index.php @@ -1,9 +1,27 @@ +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'; + +?> - Professional Vibe-Coding Webinar + <?= htmlspecialchars($webinar_title) ?> + + +
+

Welcome, !

+

You are now joining the webinar:

+

The webinar is scheduled for: format('l, F j, Y \a\t g:i A T') ?>

+
+ + diff --git a/login.php b/login.php new file mode 100644 index 0000000..6fe1559 --- /dev/null +++ b/login.php @@ -0,0 +1,199 @@ +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 register for the webinar?'; + } + } catch (PDOException $e) { + error_log($e->getMessage()); + $error = 'A database error occurred. Please try again later.'; + } +} +?> + + + + + + Login - Webinar Platform + + + + +
+ +
+

Welcome Back

+

Login to access your dashboard

+ +
+
+ +
+
+ + +
+
+ + +
+ +
+ + +
+ +
+ + \ No newline at end of file diff --git a/register.php b/register.php index ddf5093..13bc79c 100644 --- a/register.php +++ b/register.php @@ -1,191 +1,306 @@ 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 (!$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.']; - } + + 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); + + try { + // 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 = "

You're in!

" + . "

Thanks for registering for our webinar: {$webinar['title']}.

" + . "

It will take place on " . $webinar_date->format('l, F j, Y \a\t g:i A T') . ".

" + . "

You can now log in to your dashboard to see the details.

"; + + 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; +} ?> - + - - - Register for Webinar - - - - + + + Register for <?= htmlspecialchars($webinar['title']) ?> + -
-
- -

Registration Successful!

- - ← Back to Webinars - -

Register for Webinar

-

You are registering for:

- - - - +
+ Back to Home +
+
+
+

+
Loading date...
+

+
+

Speakers

+
Philip Daineka Philip Daineka
+
Alex Rubanau Alex Rubanau
+
Alexey Vertel Alexey Vertel
+
+
-
- -
- - -
-
- - -
- -
- ← Cancel - -

Webinar Not Found

-

The requested webinar could not be found.

- ← Back to Webinars - +
+
+
+

Register & Get Calendar Invite

+
+ + +

We’ll send the join link & calendar invite.

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

No spam. Unsubscribe anytime.

+ + + + + + + + + + + + +
+
+
+
-
+ + \ No newline at end of file