168 lines
6.1 KiB
PHP
168 lines
6.1 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
function e(?string $value): string
|
|
{
|
|
return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
|
|
}
|
|
|
|
function project_name(): string
|
|
{
|
|
$name = $_SERVER['PROJECT_NAME'] ?? $_SERVER['APP_NAME'] ?? getenv('PROJECT_NAME') ?: '';
|
|
return $name !== '' ? (string)$name : 'LaunchPage';
|
|
}
|
|
|
|
function short_text(?string $value, int $limit = 54): string
|
|
{
|
|
$text = trim((string)$value);
|
|
if (strlen($text) <= $limit) {
|
|
return $text;
|
|
}
|
|
return rtrim(substr($text, 0, max(0, $limit - 3))) . '…';
|
|
}
|
|
|
|
function project_description(): string
|
|
{
|
|
$description = $_SERVER['PROJECT_DESCRIPTION'] ?? getenv('PROJECT_DESCRIPTION') ?: '';
|
|
return $description !== '' ? $description : 'A focused landing page with a fast, secure lead capture workflow.';
|
|
}
|
|
|
|
function ensure_leads_table(): void
|
|
{
|
|
static $ready = false;
|
|
if ($ready) {
|
|
return;
|
|
}
|
|
|
|
require_once __DIR__ . '/../db/config.php';
|
|
$sql = "CREATE TABLE IF NOT EXISTS leads (
|
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
public_token CHAR(32) NOT NULL UNIQUE,
|
|
name VARCHAR(120) NOT NULL,
|
|
email VARCHAR(190) NOT NULL,
|
|
company VARCHAR(160) NULL,
|
|
budget VARCHAR(80) NULL,
|
|
message TEXT NOT NULL,
|
|
source VARCHAR(120) NULL,
|
|
status ENUM('new','contacted','closed') NOT NULL DEFAULT 'new',
|
|
email_sent TINYINT(1) NOT NULL DEFAULT 0,
|
|
ip_address VARCHAR(45) NULL,
|
|
user_agent VARCHAR(255) NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
INDEX idx_created_at (created_at),
|
|
INDEX idx_status (status)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
|
|
db()->exec($sql);
|
|
$ready = true;
|
|
}
|
|
|
|
function lead_count(): int
|
|
{
|
|
ensure_leads_table();
|
|
$stmt = db()->query('SELECT COUNT(*) AS total FROM leads');
|
|
return (int)($stmt->fetch()['total'] ?? 0);
|
|
}
|
|
|
|
function latest_leads(int $limit = 8): array
|
|
{
|
|
ensure_leads_table();
|
|
$stmt = db()->prepare('SELECT id, public_token, name, email, company, budget, message, status, email_sent, created_at FROM leads ORDER BY created_at DESC LIMIT :limit');
|
|
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return $stmt->fetchAll();
|
|
}
|
|
|
|
function fetch_lead_by_token(string $token): ?array
|
|
{
|
|
ensure_leads_table();
|
|
$stmt = db()->prepare('SELECT * FROM leads WHERE public_token = :token LIMIT 1');
|
|
$stmt->bindValue(':token', $token, PDO::PARAM_STR);
|
|
$stmt->execute();
|
|
$lead = $stmt->fetch();
|
|
return $lead ?: null;
|
|
}
|
|
|
|
function fetch_lead_by_id(int $id): ?array
|
|
{
|
|
ensure_leads_table();
|
|
$stmt = db()->prepare('SELECT * FROM leads WHERE id = :id LIMIT 1');
|
|
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
$lead = $stmt->fetch();
|
|
return $lead ?: null;
|
|
}
|
|
|
|
function page_head(string $title, string $description = ''): void
|
|
{
|
|
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? getenv('PROJECT_DESCRIPTION') ?: '';
|
|
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? getenv('PROJECT_IMAGE_URL') ?: '';
|
|
$metaDescription = $description !== '' ? $description : ($projectDescription !== '' ? $projectDescription : project_description());
|
|
?>
|
|
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title><?= e($title) ?></title>
|
|
<meta name="description" content="<?= e($metaDescription) ?>">
|
|
<?php if ($projectDescription): ?>
|
|
<!-- Meta description from project environment -->
|
|
<meta property="og:description" content="<?= e($projectDescription) ?>">
|
|
<meta property="twitter:description" content="<?= e($projectDescription) ?>">
|
|
<?php endif; ?>
|
|
<?php if ($projectImageUrl): ?>
|
|
<!-- Platform-managed preview image -->
|
|
<meta property="og:image" content="<?= e($projectImageUrl) ?>">
|
|
<meta property="twitter:image" content="<?= e($projectImageUrl) ?>">
|
|
<?php endif; ?>
|
|
<meta property="og:title" content="<?= e($title) ?>">
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<link rel="preconnect" href="https://cdn.jsdelivr.net">
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link rel="stylesheet" href="assets/css/custom.css?v=2026052901">
|
|
</head>
|
|
<body>
|
|
<?php
|
|
}
|
|
|
|
function page_nav(string $active = 'home'): void
|
|
{
|
|
?>
|
|
<header class="site-header sticky-top">
|
|
<nav class="navbar navbar-expand-lg" aria-label="Primary navigation">
|
|
<div class="container">
|
|
<a class="navbar-brand" href="index.php" aria-label="<?= e(project_name()) ?> home"><?= e(project_name()) ?></a>
|
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav" aria-controls="mainNav" aria-expanded="false" aria-label="Toggle navigation">
|
|
<span class="navbar-toggler-icon"></span>
|
|
</button>
|
|
<div class="collapse navbar-collapse" id="mainNav">
|
|
<ul class="navbar-nav ms-auto align-items-lg-center gap-lg-1">
|
|
<li class="nav-item"><a class="nav-link <?= $active === 'home' ? 'active' : '' ?>" href="index.php#offer">Offer</a></li>
|
|
<li class="nav-item"><a class="nav-link <?= $active === 'process' ? 'active' : '' ?>" href="index.php#process">Process</a></li>
|
|
<li class="nav-item"><a class="nav-link <?= $active === 'leads' ? 'active' : '' ?>" href="leads.php">Leads</a></li>
|
|
<li class="nav-item"><a class="btn btn-dark btn-sm ms-lg-2" href="index.php#lead-form">Request info</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
</header>
|
|
<?php
|
|
}
|
|
|
|
function page_footer(): void
|
|
{
|
|
$year = date('Y');
|
|
?>
|
|
<footer class="site-footer">
|
|
<div class="container d-flex flex-column flex-md-row justify-content-between gap-2">
|
|
<p class="mb-0">© <?= e((string)$year) ?> <?= e(project_name()) ?>. Built for fast lead capture.</p>
|
|
<p class="mb-0"><a href="leads.php">View leads</a> <span aria-hidden="true">·</span> <a href="/healthz">Health</a></p>
|
|
</div>
|
|
</footer>
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
<script src="assets/js/main.js?v=2026052901" defer></script>
|
|
</body>
|
|
</html>
|
|
<?php
|
|
}
|