Initial version
This commit is contained in:
commit
31312e505e
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
*/node_modules/
|
||||
*/build/
|
||||
18
.htaccess
Normal file
18
.htaccess
Normal file
@ -0,0 +1,18 @@
|
||||
DirectoryIndex index.php index.html
|
||||
Options -Indexes
|
||||
Options -MultiViews
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
# 0) Serve existing files/directories as-is
|
||||
RewriteCond %{REQUEST_FILENAME} -f [OR]
|
||||
RewriteCond %{REQUEST_FILENAME} -d
|
||||
RewriteRule ^ - [L]
|
||||
|
||||
# 1) Internal map: /page or /page/ -> /page.php (if such PHP file exists)
|
||||
RewriteCond %{REQUEST_FILENAME}.php -f
|
||||
RewriteRule ^(.+?)/?$ $1.php [L]
|
||||
|
||||
# 2) Optional: strip trailing slash for non-directories (keeps .php links working)
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^(.+)/$ $1 [R=301,L]
|
||||
0
.perm_test_apache
Normal file
0
.perm_test_apache
Normal file
0
.perm_test_exec
Normal file
0
.perm_test_exec
Normal file
27
admin.php
Normal file
27
admin.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
require_once 'templates/header.php';
|
||||
|
||||
// Protect this page
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("Location: login.php");
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
<main class="container my-5">
|
||||
|
||||
<section class="py-5">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="feature-card p-4">
|
||||
<h2 class="text-center mb-4">Admin Panel</h2>
|
||||
<p class="text-center">Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?>!</p>
|
||||
<p class="text-center">This is your admin dashboard. You can add content and manage your site from here.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
<?php require_once 'templates/footer.php'; ?>
|
||||
88
assets/css/custom.css
Normal file
88
assets/css/custom.css
Normal file
@ -0,0 +1,88 @@
|
||||
:root {
|
||||
--primary-color: #0D6EFD;
|
||||
--secondary-color: #6C757D;
|
||||
--background-color: #F8F9FA;
|
||||
--surface-color: #FFFFFF;
|
||||
--primary-font: 'Poppins', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--primary-font);
|
||||
background: linear-gradient(270deg, #a1c4fd, #c2e9fb);
|
||||
background-size: 400% 400%;
|
||||
animation: moveGradient 15s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes moveGradient {
|
||||
0%{background-position:0% 50%}
|
||||
50%{background-position:100% 50%}
|
||||
100%{background-position:0% 50%}
|
||||
}
|
||||
|
||||
.hero-section {
|
||||
background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('https://picsum.photos/seed/nimbus-hero/1600/900');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
color: white;
|
||||
padding: 10rem 0;
|
||||
}
|
||||
|
||||
.navbar.scrolled {
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
||||
border-color: #86b7fe;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
font-size: 2.5rem;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px); /* For Safari */
|
||||
border-radius: 15px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.hero-section .btn-primary {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
padding: 12px 30px;
|
||||
font-weight: 600;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.hero-section .btn-primary:hover {
|
||||
background-color: #0b5ed7;
|
||||
}
|
||||
|
||||
.hero-section .btn-outline-secondary {
|
||||
color: white;
|
||||
border-color: white;
|
||||
padding: 12px 30px;
|
||||
font-weight: 600;
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
.hero-section .btn-outline-secondary:hover {
|
||||
background-color: white;
|
||||
color: black;
|
||||
}
|
||||
39
assets/js/main.js
Normal file
39
assets/js/main.js
Normal file
@ -0,0 +1,39 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Navbar shrink on scroll
|
||||
const navbar = document.querySelector('.navbar');
|
||||
if (navbar) {
|
||||
window.addEventListener('scroll', () => {
|
||||
if (window.scrollY > 50) {
|
||||
navbar.classList.add('scrolled');
|
||||
} else {
|
||||
navbar.classList.remove('scrolled');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Smooth scroll for anchor links
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Pricing toggle
|
||||
const priceToggle = document.getElementById('priceToggle');
|
||||
if (priceToggle) {
|
||||
priceToggle.addEventListener('change', function() {
|
||||
const isYearly = this.checked;
|
||||
document.querySelectorAll('[data-monthly]').forEach(priceEl => {
|
||||
const monthly = priceEl.getAttribute('data-monthly');
|
||||
const yearly = priceEl.getAttribute('data-yearly');
|
||||
priceEl.textContent = isYearly ? yearly : monthly;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
17
db/config.php
Normal file
17
db/config.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
// Generated by setup_mariadb_project.sh — edit as needed.
|
||||
define('DB_HOST', '127.0.0.1');
|
||||
define('DB_NAME', 'app_33962');
|
||||
define('DB_USER', 'app_33962');
|
||||
define('DB_PASS', '6e30d396-9b31-413f-bcba-4f83f8ec7cd3');
|
||||
|
||||
function db() {
|
||||
static $pdo;
|
||||
if (!$pdo) {
|
||||
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]);
|
||||
}
|
||||
return $pdo;
|
||||
}
|
||||
29
db/migrate.php
Normal file
29
db/migrate.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
// Simple migration runner
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$migrationsDir = __DIR__ . '/migrations';
|
||||
$files = glob($migrationsDir . '/*.sql');
|
||||
|
||||
if (empty($files)) {
|
||||
echo "No migration files found.\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
sort($files);
|
||||
|
||||
foreach ($files as $file) {
|
||||
echo "Running migration: " . basename($file) . "...\n";
|
||||
$sql = file_get_contents($file);
|
||||
$pdo->exec($sql);
|
||||
echo "Success.\n";
|
||||
}
|
||||
|
||||
echo "\nAll migrations completed successfully.\n";
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("Database error: " . $e->getMessage() . "\n");
|
||||
}
|
||||
|
||||
7
db/migrations/001_create_users_table.sql
Normal file
7
db/migrations/001_create_users_table.sql
Normal file
@ -0,0 +1,7 @@
|
||||
CREATE TABLE IF NOT EXISTS `users` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`username` VARCHAR(50) NOT NULL UNIQUE,
|
||||
`email` VARCHAR(100) NOT NULL UNIQUE,
|
||||
`password_hash` VARCHAR(255) NOT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
288
index.php
Normal file
288
index.php
Normal file
@ -0,0 +1,288 @@
|
||||
<?php
|
||||
session_start();
|
||||
$message_sent = null;
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['form_submitted'])) {
|
||||
require_once __DIR__ . '/mail/MailService.php';
|
||||
|
||||
$name = filter_var(trim($_POST['name']), FILTER_SANITIZE_STRING);
|
||||
$email = filter_var(trim($_POST['email']), FILTER_SANITIZE_EMAIL);
|
||||
$message_text = filter_var(trim($_POST['message']), FILTER_SANITIZE_STRING);
|
||||
|
||||
if (filter_var($email, FILTER_VALIDATE_EMAIL) && !empty($name) && !empty($message_text)) {
|
||||
$to = getenv('MAIL_TO') ?: 'support@yourdomain.com'; // Fallback recipient
|
||||
$subject = 'New Lead from Nimbus Landing Page';
|
||||
|
||||
$res = MailService::sendContactMessage($name, $email, $message_text, $to, $subject);
|
||||
|
||||
if (!empty($res['success'])) {
|
||||
$_SESSION['message_sent'] = 'success';
|
||||
} else {
|
||||
$_SESSION['message_sent'] = 'error';
|
||||
// Log the detailed error
|
||||
$log_message = date('[Y-m-d H:i:s]') . ' Mailer Error: ' . ($res['error'] ?? 'Unknown error') . PHP_EOL;
|
||||
file_put_contents('mail_log.txt', $log_message, FILE_APPEND);
|
||||
}
|
||||
} else {
|
||||
$_SESSION['message_sent'] = 'validation_error';
|
||||
}
|
||||
|
||||
// Redirect to avoid form resubmission
|
||||
header("Location: " . $_SERVER['PHP_SELF'] . "#contact");
|
||||
exit();
|
||||
}
|
||||
|
||||
if (isset($_SESSION['message_sent'])) {
|
||||
$message_sent = $_SESSION['message_sent'];
|
||||
unset($_SESSION['message_sent']);
|
||||
}
|
||||
?>
|
||||
<?php require_once 'templates/header.php'; ?>
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="hero-section text-center py-5">
|
||||
<div class="container py-5">
|
||||
<h1 class="display-3 fw-bold">Your Cloud, Simplified</h1>
|
||||
<p class="lead mb-5">Nimbus is the single pane of glass for your entire cloud infrastructure. Monitor, optimize, and automate with ease.</p>
|
||||
<a href="#contact" class="btn btn-primary btn-lg">Start Your Free Trial</a>
|
||||
<a href="#features" class="btn btn-outline-secondary btn-lg">Explore Features</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Social Proof -->
|
||||
<section class="py-5">
|
||||
<div class="container text-center">
|
||||
<h6 class="text-muted mb-4">TRUSTED BY LEADING COMPANIES WORLDWIDE</h6>
|
||||
<div class="d-flex flex-wrap justify-content-center align-items-center">
|
||||
<i class="bi bi-cloud-haze2 fs-1 text-muted mx-4"></i>
|
||||
<i class="bi bi-bar-chart-line fs-1 text-muted mx-4"></i>
|
||||
<i class="bi bi-shield-lock fs-1 text-muted mx-4"></i>
|
||||
<i class="bi bi-hdd-stack fs-1 text-muted mx-4"></i>
|
||||
<i class="bi bi-globe2 fs-1 text-muted mx-4"></i>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section id="features" class="py-5">
|
||||
<div class="container">
|
||||
<div class="text-center mb-5">
|
||||
<h2>Core Features</h2>
|
||||
<p class="lead">Everything you need in one platform.</p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="feature-card text-center p-4 h-100">
|
||||
<i class="bi bi-graph-up-arrow section-icon mb-3"></i>
|
||||
<h4>Unified Monitoring</h4>
|
||||
<p>Get a bird's-eye view of your entire infrastructure in one place.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="feature-card text-center p-4 h-100">
|
||||
<i class="bi bi-piggy-bank section-icon mb-3"></i>
|
||||
<h4>Cost Optimization</h4>
|
||||
<p>Identify and eliminate wasted cloud spend with smart recommendations.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="feature-card text-center p-4 h-100">
|
||||
<i class="bi bi-shield-check section-icon mb-3"></i>
|
||||
<h4>Automated Security</h4>
|
||||
<p>Proactively secure your cloud environment against threats.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Product Screenshot -->
|
||||
<section class="py-5">
|
||||
<div class="container">
|
||||
<div class="text-center mb-5">
|
||||
<h2>See Nimbus in Action</h2>
|
||||
</div>
|
||||
<img src="https://picsum.photos/seed/nimbus-dashboard/1200/800" class="img-fluid rounded shadow" alt="A mock screenshot of the Nimbus dashboard showing key performance metrics.">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Testimonials -->
|
||||
<section id="testimonials" class="py-5">
|
||||
<div class="container">
|
||||
<div class="text-center mb-5">
|
||||
<h2>What Our Customers Say</h2>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="feature-card p-4 h-100">
|
||||
<p class="fst-italic">"Nimbus has been a game-changer for us. We've saved over 30% on our cloud bills."</p>
|
||||
<div class="d-flex align-items-center mt-4">
|
||||
<img src="https://picsum.photos/seed/avatar1/96/96" class="rounded-circle me-3" alt="Avatar of a satisfied Nimbus customer.">
|
||||
<div>
|
||||
<h6 class="m-0">Jane Doe</h6>
|
||||
<small class="text-muted">CTO, TechCorp</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="feature-card p-4 h-100">
|
||||
<p class="fst-italic">"The unified dashboard is incredibly intuitive. Our team was up and running in minutes."</p>
|
||||
<div class="d-flex align-items-center mt-4">
|
||||
<img src="https://picsum.photos/seed/avatar2/96/96" class="rounded-circle me-3" alt="Avatar of a satisfied Nimbus customer.">
|
||||
<div>
|
||||
<h6 class="m-0">John Smith</h6>
|
||||
<small class="text-muted">CEO, Innovate LLC</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="feature-card p-4 h-100">
|
||||
<p class="fst-italic">"I can't imagine managing our infrastructure without Nimbus. It's essential."</p>
|
||||
<div class="d-flex align-items-center mt-4">
|
||||
<img src="https://picsum.photos/seed/avatar3/96/96" class="rounded-circle me-3" alt="Avatar of a satisfied Nimbus customer.">
|
||||
<div>
|
||||
<h6 class="m-0">Sam Wilson</h6>
|
||||
<small class="text-muted">DevOps Lead, Future Solutions</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Pricing -->
|
||||
<section id="pricing" class="py-5">
|
||||
<div class="container">
|
||||
<div class="text-center mb-5">
|
||||
<h2>Simple, Transparent Pricing</h2>
|
||||
<div class="form-check form-switch d-inline-block">
|
||||
<input class="form-check-input" type="checkbox" id="priceToggle">
|
||||
<label class="form-check-label" for="priceToggle">Yearly (Save 20%)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<!-- Pricing Tier 1 -->
|
||||
<div class="col-lg-4 mb-4">
|
||||
<div class="feature-card text-center p-4 h-100">
|
||||
<h4>Starter</h4>
|
||||
<h5 class="card-title my-4">
|
||||
<span data-monthly="$49" data-yearly="$39">$49</span>/mo
|
||||
</h5>
|
||||
<ul class="list-unstyled my-4">
|
||||
<li>10 Projects</li>
|
||||
<li>Basic Analytics</li>
|
||||
<li>Email Support</li>
|
||||
</ul>
|
||||
<button class="btn btn-outline-light">Choose Plan</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Pricing Tier 2 -->
|
||||
<div class="col-lg-4 mb-4">
|
||||
<div class="feature-card text-center p-4 h-100">
|
||||
<h4>Pro</h4>
|
||||
<h5 class="card-title my-4">
|
||||
<span data-monthly="$99" data-yearly="$79">$99</span>/mo
|
||||
</h5>
|
||||
<ul class="list-unstyled my-4">
|
||||
<li>Unlimited Projects</li>
|
||||
<li>Advanced Analytics</li>
|
||||
<li>Priority Support</li>
|
||||
</ul>
|
||||
<button class="btn btn-light">Choose Plan</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Pricing Tier 3 -->
|
||||
<div class="col-lg-4 mb-4">
|
||||
<div class="feature-card text-center p-4 h-100">
|
||||
<h4>Enterprise</h4>
|
||||
<h5 class="card-title my-4">Contact Us</h5>
|
||||
<ul class="list-unstyled my-4">
|
||||
<li>Dedicated Infrastructure</li>
|
||||
<li>Custom Integrations</li>
|
||||
<li>24/7 Support</li>
|
||||
</ul>
|
||||
<button class="btn btn-outline-light">Contact Sales</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FAQ -->
|
||||
<section id="faq" class="py-5">
|
||||
<div class="container">
|
||||
<div class="text-center mb-5">
|
||||
<h2>Frequently Asked Questions</h2>
|
||||
</div>
|
||||
<div class="accordion" id="faqAccordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header"><button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#faq1">What is Nimbus?</button></h2>
|
||||
<div id="faq1" class="accordion-collapse collapse show" data-bs-parent="#faqAccordion">
|
||||
<div class="accordion-body">Nimbus is a B2B SaaS platform that helps businesses manage their cloud infrastructure.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header"><button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#faq2">What cloud providers do you support?</button></h2>
|
||||
<div id="faq2" class="accordion-collapse collapse" data-bs-parent="#faqAccordion">
|
||||
<div class="accordion-body">We support AWS, Google Cloud, and Azure.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header"><button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#faq3">Can I cancel my subscription?</button></h2>
|
||||
<div id="faq3" class="accordion-collapse collapse" data-bs-parent="#faqAccordion">
|
||||
<div class="accordion-body">Yes, you can cancel your subscription at any time.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Contact Form -->
|
||||
<section id="contact" class="py-5">
|
||||
<div class="container">
|
||||
<div class="text-center mb-5">
|
||||
<h2>Get in Touch</h2>
|
||||
<p class="lead">We'd love to hear from you.</p>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-6">
|
||||
<?php if ($message_sent === 'success'): ?>
|
||||
<div class="alert alert-success" role="alert">
|
||||
<strong>Thank you!</strong> Your message has been sent successfully.
|
||||
</div>
|
||||
<?php elseif ($message_sent === 'error'): ?>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<strong>Error!</strong> There was a problem sending your message. Please try again later.
|
||||
</div>
|
||||
<?php elseif ($message_sent === 'validation_error'): ?>
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<strong>Oops!</strong> Please fill out all fields correctly.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form id="leadForm" method="POST" action="#contact" novalidate>
|
||||
<input type="hidden" name="form_submitted" value="1">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Name</label>
|
||||
<input type="text" class="form-control" id="name" name="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input type="email" class="form-control" id="email" name="email" required>
|
||||
<div class="invalid-feedback">Please provide a valid email.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="message" class="form-label">Message</label>
|
||||
<textarea class="form-control" id="message" name="message" rows="4" required></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Send Message</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php require_once 'templates/footer.php'; ?>
|
||||
86
login.php
Normal file
86
login.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
require_once 'templates/header.php';
|
||||
|
||||
$errors = [];
|
||||
$email = '';
|
||||
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
header("Location: admin.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$email = trim($_POST['email']);
|
||||
$password = $_POST['password'];
|
||||
|
||||
if (empty($email)) {
|
||||
$errors[] = 'Email is required';
|
||||
}
|
||||
if (empty($password)) {
|
||||
$errors[] = 'Password is required';
|
||||
}
|
||||
|
||||
if (empty($errors)) {
|
||||
$stmt = db()->prepare("SELECT id, username, password_hash FROM users WHERE email = ?");
|
||||
$stmt->execute([$email]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($user && password_verify($password, $user['password_hash'])) {
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['username'] = $user['username'];
|
||||
header("Location: admin.php");
|
||||
exit();
|
||||
} else {
|
||||
$errors[] = 'Invalid email or password';
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<main class="container my-5">
|
||||
|
||||
<section class="py-5">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-6">
|
||||
<div class="feature-card p-4">
|
||||
<h2 class="text-center mb-4">Login</h2>
|
||||
|
||||
<?php if (isset($_GET['registration']) && $_GET['registration'] === 'success'): ?>
|
||||
<div class="alert alert-success">
|
||||
Registration successful! You can now log in.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($errors)): ?>
|
||||
<div class="alert alert-danger">
|
||||
<?php foreach ($errors as $error): ?>
|
||||
<p class="m-0"><?php echo $error; ?></p>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST" action="login.php">
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input type="email" class="form-control" id="email" name="email" value="<?php echo htmlspecialchars($email); ?>" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary">Login</button>
|
||||
</div>
|
||||
</form>
|
||||
<p class="text-center mt-3">
|
||||
Don't have an account? <a href="register.php">Register here</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
<?php require_once 'templates/footer.php'; ?>
|
||||
7
logout.php
Normal file
7
logout.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
session_start();
|
||||
session_unset();
|
||||
session_destroy();
|
||||
header("Location: login.php");
|
||||
exit();
|
||||
?>
|
||||
235
mail/MailService.php
Normal file
235
mail/MailService.php
Normal file
@ -0,0 +1,235 @@
|
||||
<?php
|
||||
// Minimal mail service for the workspace app (VM).
|
||||
// Usage:
|
||||
// require_once __DIR__ . '/MailService.php';
|
||||
// // Generic:
|
||||
// MailService::sendMail($to, $subject, $htmlBody, $textBody = null, $opts = []);
|
||||
// // Contact form helper:
|
||||
// MailService::sendContactMessage($name, $email, $message, $to = null, $subject = 'New contact form');
|
||||
|
||||
class MailService
|
||||
{
|
||||
// Universal mail sender (no attachments by design)
|
||||
public static function sendMail($to, string $subject, string $htmlBody, ?string $textBody = null, array $opts = [])
|
||||
{
|
||||
$cfg = self::loadConfig();
|
||||
|
||||
$autoload = __DIR__ . '/../vendor/autoload.php';
|
||||
if (file_exists($autoload)) {
|
||||
require_once $autoload;
|
||||
}
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'libphp-phpmailer/autoload.php';
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'libphp-phpmailer/src/Exception.php';
|
||||
@require_once 'libphp-phpmailer/src/SMTP.php';
|
||||
@require_once 'libphp-phpmailer/src/PHPMailer.php';
|
||||
}
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'PHPMailer/src/Exception.php';
|
||||
@require_once 'PHPMailer/src/SMTP.php';
|
||||
@require_once 'PHPMailer/src/PHPMailer.php';
|
||||
}
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'PHPMailer/Exception.php';
|
||||
@require_once 'PHPMailer/SMTP.php';
|
||||
@require_once 'PHPMailer/PHPMailer.php';
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
return [ 'success' => false, 'error' => 'PHPMailer not available' ];
|
||||
}
|
||||
|
||||
$mail = new PHPMailer\PHPMailer\PHPMailer(true);
|
||||
try {
|
||||
$mail->isSMTP();
|
||||
$mail->Host = $cfg['smtp_host'] ?? '';
|
||||
$mail->Port = (int)($cfg['smtp_port'] ?? 587);
|
||||
$secure = $cfg['smtp_secure'] ?? 'tls';
|
||||
if ($secure === 'ssl') $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_SMTPS;
|
||||
elseif ($secure === 'tls') $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS;
|
||||
else $mail->SMTPSecure = false;
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = $cfg['smtp_user'] ?? '';
|
||||
$mail->Password = $cfg['smtp_pass'] ?? '';
|
||||
|
||||
$fromEmail = $opts['from_email'] ?? ($cfg['from_email'] ?? 'no-reply@localhost');
|
||||
$fromName = $opts['from_name'] ?? ($cfg['from_name'] ?? 'App');
|
||||
$mail->setFrom($fromEmail, $fromName);
|
||||
if (!empty($opts['reply_to']) && filter_var($opts['reply_to'], FILTER_VALIDATE_EMAIL)) {
|
||||
$mail->addReplyTo($opts['reply_to']);
|
||||
} elseif (!empty($cfg['reply_to'])) {
|
||||
$mail->addReplyTo($cfg['reply_to']);
|
||||
}
|
||||
|
||||
// Recipients
|
||||
$toList = [];
|
||||
if ($to) {
|
||||
if (is_string($to)) $toList = array_map('trim', explode(',', $to));
|
||||
elseif (is_array($to)) $toList = $to;
|
||||
} elseif (!empty(getenv('MAIL_TO'))) {
|
||||
$toList = array_map('trim', explode(',', getenv('MAIL_TO')));
|
||||
}
|
||||
$added = 0;
|
||||
foreach ($toList as $addr) {
|
||||
if (filter_var($addr, FILTER_VALIDATE_EMAIL)) { $mail->addAddress($addr); $added++; }
|
||||
}
|
||||
if ($added === 0) {
|
||||
return [ 'success' => false, 'error' => 'No recipients defined (set MAIL_TO or pass $to)' ];
|
||||
}
|
||||
|
||||
foreach ((array)($opts['cc'] ?? []) as $cc) { if (filter_var($cc, FILTER_VALIDATE_EMAIL)) $mail->addCC($cc); }
|
||||
foreach ((array)($opts['bcc'] ?? []) as $bcc){ if (filter_var($bcc, FILTER_VALIDATE_EMAIL)) $mail->addBCC($bcc); }
|
||||
|
||||
// Optional DKIM
|
||||
if (!empty($cfg['dkim_domain']) && !empty($cfg['dkim_selector']) && !empty($cfg['dkim_private_key_path'])) {
|
||||
$mail->DKIM_domain = $cfg['dkim_domain'];
|
||||
$mail->DKIM_selector = $cfg['dkim_selector'];
|
||||
$mail->DKIM_private = $cfg['dkim_private_key_path'];
|
||||
}
|
||||
|
||||
$mail->isHTML(true);
|
||||
$mail->Subject = $subject;
|
||||
$mail->Body = $htmlBody;
|
||||
$mail->AltBody = $textBody ?? strip_tags($htmlBody);
|
||||
$ok = $mail->send();
|
||||
return [ 'success' => $ok ];
|
||||
} catch (\Throwable $e) {
|
||||
return [ 'success' => false, 'error' => 'PHPMailer error: ' . $e->getMessage() ];
|
||||
}
|
||||
}
|
||||
private static function loadConfig(): array
|
||||
{
|
||||
$configPath = __DIR__ . '/config.php';
|
||||
if (!file_exists($configPath)) {
|
||||
throw new \RuntimeException('Mail config not found. Copy mail/config.sample.php to mail/config.php and fill in credentials.');
|
||||
}
|
||||
$cfg = require $configPath;
|
||||
if (!is_array($cfg)) {
|
||||
throw new \RuntimeException('Invalid mail config format: expected array');
|
||||
}
|
||||
return $cfg;
|
||||
}
|
||||
|
||||
// Send a contact message
|
||||
// $to can be: a single email string, a comma-separated list, an array of emails, or null (fallback to MAIL_TO/MAIL_FROM)
|
||||
public static function sendContactMessage(string $name, string $email, string $message, $to = null, string $subject = 'New contact form')
|
||||
{
|
||||
$cfg = self::loadConfig();
|
||||
|
||||
// Try Composer autoload if available (for PHPMailer)
|
||||
$autoload = __DIR__ . '/../vendor/autoload.php';
|
||||
if (file_exists($autoload)) {
|
||||
require_once $autoload;
|
||||
}
|
||||
// Fallback to system-wide PHPMailer (installed via apt: libphp-phpmailer)
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
// Debian/Ubuntu package layout (libphp-phpmailer)
|
||||
@require_once 'libphp-phpmailer/autoload.php';
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'libphp-phpmailer/src/Exception.php';
|
||||
@require_once 'libphp-phpmailer/src/SMTP.php';
|
||||
@require_once 'libphp-phpmailer/src/PHPMailer.php';
|
||||
}
|
||||
// Alternative layout (older PHPMailer package names)
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'PHPMailer/src/Exception.php';
|
||||
@require_once 'PHPMailer/src/SMTP.php';
|
||||
@require_once 'PHPMailer/src/PHPMailer.php';
|
||||
}
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'PHPMailer/Exception.php';
|
||||
@require_once 'PHPMailer/SMTP.php';
|
||||
@require_once 'PHPMailer/PHPMailer.php';
|
||||
}
|
||||
}
|
||||
|
||||
$transport = $cfg['transport'] ?? 'smtp';
|
||||
if ($transport === 'smtp' && class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
return self::sendViaPHPMailer($cfg, $name, $email, $message, $to, $subject);
|
||||
}
|
||||
|
||||
// Fallback: attempt native mail() — works only if MTA is configured on the VM
|
||||
return self::sendViaNativeMail($cfg, $name, $email, $message, $to, $subject);
|
||||
}
|
||||
|
||||
private static function sendViaPHPMailer(array $cfg, string $name, string $email, string $body, $to, string $subject)
|
||||
{
|
||||
$mail = new PHPMailer\PHPMailer\PHPMailer(true);
|
||||
try {
|
||||
$mail->isSMTP();
|
||||
$mail->Host = $cfg['smtp_host'] ?? '';
|
||||
$mail->Port = (int)($cfg['smtp_port'] ?? 587);
|
||||
$secure = $cfg['smtp_secure'] ?? 'tls';
|
||||
if ($secure === 'ssl') $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_SMTPS;
|
||||
elseif ($secure === 'tls') $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS;
|
||||
else $mail->SMTPSecure = false;
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = $cfg['smtp_user'] ?? '';
|
||||
$mail->Password = $cfg['smtp_pass'] ?? '';
|
||||
|
||||
$fromEmail = $cfg['from_email'] ?? 'no-reply@localhost';
|
||||
$fromName = $cfg['from_name'] ?? 'App';
|
||||
$mail->setFrom($fromEmail, $fromName);
|
||||
|
||||
// Use Reply-To for the user's email to avoid spoofing From
|
||||
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$mail->addReplyTo($email, $name ?: $email);
|
||||
}
|
||||
if (!empty($cfg['reply_to'])) {
|
||||
$mail->addReplyTo($cfg['reply_to']);
|
||||
}
|
||||
|
||||
// Destination: prefer dynamic recipients ($to), fallback to MAIL_TO; no silent FROM fallback
|
||||
$toList = [];
|
||||
if ($to) {
|
||||
if (is_string($to)) {
|
||||
// allow comma-separated list
|
||||
$toList = array_map('trim', explode(',', $to));
|
||||
} elseif (is_array($to)) {
|
||||
$toList = $to;
|
||||
}
|
||||
} elseif (!empty(getenv('MAIL_TO'))) {
|
||||
$toList = array_map('trim', explode(',', getenv('MAIL_TO')));
|
||||
}
|
||||
$added = 0;
|
||||
foreach ($toList as $addr) {
|
||||
if (filter_var($addr, FILTER_VALIDATE_EMAIL)) {
|
||||
$mail->addAddress($addr);
|
||||
$added++;
|
||||
}
|
||||
}
|
||||
if ($added === 0) {
|
||||
return [ 'success' => false, 'error' => 'No recipients defined (set MAIL_TO or pass $to)' ];
|
||||
}
|
||||
|
||||
// DKIM (optional)
|
||||
if (!empty($cfg['dkim_domain']) && !empty($cfg['dkim_selector']) && !empty($cfg['dkim_private_key_path'])) {
|
||||
$mail->DKIM_domain = $cfg['dkim_domain'];
|
||||
$mail->DKIM_selector = $cfg['dkim_selector'];
|
||||
$mail->DKIM_private = $cfg['dkim_private_key_path'];
|
||||
}
|
||||
|
||||
$mail->isHTML(true);
|
||||
$mail->Subject = $subject;
|
||||
$safeName = htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
$safeEmail = htmlspecialchars($email, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
$safeBody = nl2br(htmlspecialchars($body, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
|
||||
$mail->Body = "<p><strong>Name:</strong> {$safeName}</p><p><strong>Email:</strong> {$safeEmail}</p><hr>{$safeBody}";
|
||||
$mail->AltBody = "Name: {$name}\nEmail: {$email}\n\n{$body}";
|
||||
|
||||
$ok = $mail->send();
|
||||
return [ 'success' => $ok ];
|
||||
} catch (\Throwable $e) {
|
||||
return [ 'success' => false, 'error' => 'PHPMailer error: ' . $e->getMessage() ];
|
||||
}
|
||||
}
|
||||
|
||||
private static function sendViaNativeMail(array $cfg, string $name, string $email, string $body, $to, string $subject)
|
||||
{
|
||||
$opts = ['reply_to' => $email];
|
||||
$html = nl2br(htmlspecialchars($body, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
|
||||
return self::sendMail($to, $subject, $html, $body, $opts);
|
||||
}
|
||||
}
|
||||
76
mail/config.php
Normal file
76
mail/config.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
// Mail configuration sourced from environment variables.
|
||||
// No secrets are stored here; the file just maps env -> config array for MailService.
|
||||
|
||||
function env_val(string $key, $default = null) {
|
||||
$v = getenv($key);
|
||||
return ($v === false || $v === null || $v === '') ? $default : $v;
|
||||
}
|
||||
|
||||
// Fallback: if critical vars are missing from process env, try to parse executor/.env
|
||||
// This helps in web/Apache contexts where .env is not exported.
|
||||
// Supports simple KEY=VALUE lines; ignores quotes and comments.
|
||||
function load_dotenv_if_needed(array $keys): void {
|
||||
$missing = array_filter($keys, fn($k) => getenv($k) === false || getenv($k) === '');
|
||||
if (empty($missing)) return;
|
||||
static $loaded = false;
|
||||
if ($loaded) return;
|
||||
$envPath = realpath(__DIR__ . '/../../.env'); // executor/.env
|
||||
if ($envPath && is_readable($envPath)) {
|
||||
$lines = @file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
|
||||
foreach ($lines as $line) {
|
||||
if ($line[0] === '#' || trim($line) === '') continue;
|
||||
if (!str_contains($line, '=')) continue;
|
||||
[$k, $v] = array_map('trim', explode('=', $line, 2));
|
||||
// Strip potential surrounding quotes
|
||||
$v = trim($v, "\"' ");
|
||||
// Do not override existing env
|
||||
if ($k !== '' && (getenv($k) === false || getenv($k) === '')) {
|
||||
putenv("{$k}={$v}");
|
||||
}
|
||||
}
|
||||
$loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
load_dotenv_if_needed([
|
||||
'MAIL_TRANSPORT','SMTP_HOST','SMTP_PORT','SMTP_SECURE','SMTP_USER','SMTP_PASS',
|
||||
'MAIL_FROM','MAIL_FROM_NAME','MAIL_REPLY_TO','MAIL_TO',
|
||||
'DKIM_DOMAIN','DKIM_SELECTOR','DKIM_PRIVATE_KEY_PATH'
|
||||
]);
|
||||
|
||||
$transport = env_val('MAIL_TRANSPORT', 'smtp');
|
||||
$smtp_host = env_val('SMTP_HOST');
|
||||
$smtp_port = (int) env_val('SMTP_PORT', 587);
|
||||
$smtp_secure = env_val('SMTP_SECURE', 'tls'); // tls | ssl | null
|
||||
$smtp_user = env_val('SMTP_USER');
|
||||
$smtp_pass = env_val('SMTP_PASS');
|
||||
|
||||
$from_email = env_val('MAIL_FROM', 'no-reply@localhost');
|
||||
$from_name = env_val('MAIL_FROM_NAME', 'App');
|
||||
$reply_to = env_val('MAIL_REPLY_TO');
|
||||
|
||||
$dkim_domain = env_val('DKIM_DOMAIN');
|
||||
$dkim_selector = env_val('DKIM_SELECTOR');
|
||||
$dkim_private_key_path = env_val('DKIM_PRIVATE_KEY_PATH');
|
||||
|
||||
return [
|
||||
'transport' => $transport,
|
||||
|
||||
// SMTP
|
||||
'smtp_host' => $smtp_host,
|
||||
'smtp_port' => $smtp_port,
|
||||
'smtp_secure' => $smtp_secure,
|
||||
'smtp_user' => $smtp_user,
|
||||
'smtp_pass' => $smtp_pass,
|
||||
|
||||
// From / Reply-To
|
||||
'from_email' => $from_email,
|
||||
'from_name' => $from_name,
|
||||
'reply_to' => $reply_to,
|
||||
|
||||
// DKIM (optional)
|
||||
'dkim_domain' => $dkim_domain,
|
||||
'dkim_selector' => $dkim_selector,
|
||||
'dkim_private_key_path' => $dkim_private_key_path,
|
||||
];
|
||||
224
mail_log.txt
Normal file
224
mail_log.txt
Normal file
@ -0,0 +1,224 @@
|
||||
SERVER -> CLIENT: 220 email-smtp.amazonaws.com ESMTP SimpleEmailService-d-UJAP95IYE gQZPHeokgr2tbwK3Dh8W
|
||||
|
||||
CLIENT -> SERVER: EHLO vm-30855.dev.flatlogic.app
|
||||
|
||||
SERVER -> CLIENT: 250-email-smtp.amazonaws.com
|
||||
250-8BITMIME
|
||||
250-STARTTLS
|
||||
250-AUTH PLAIN LOGIN
|
||||
250 Ok
|
||||
|
||||
CLIENT -> SERVER: STARTTLS
|
||||
|
||||
SERVER -> CLIENT: 220 Ready to start TLS
|
||||
|
||||
CLIENT -> SERVER: EHLO vm-30855.dev.flatlogic.app
|
||||
|
||||
SERVER -> CLIENT: 250-email-smtp.amazonaws.com
|
||||
250-8BITMIME
|
||||
250-STARTTLS
|
||||
250-AUTH PLAIN LOGIN
|
||||
250 Ok
|
||||
|
||||
CLIENT -> SERVER: AUTH LOGIN
|
||||
|
||||
SERVER -> CLIENT: 334 VXNlcm5hbWU6
|
||||
|
||||
CLIENT -> SERVER: [credentials hidden]
|
||||
SERVER -> CLIENT: 334 UGFzc3dvcmQ6
|
||||
|
||||
CLIENT -> SERVER: [credentials hidden]
|
||||
SERVER -> CLIENT: 235 Authentication successful.
|
||||
|
||||
CLIENT -> SERVER: MAIL FROM:<app@flatlogic.app>
|
||||
|
||||
SERVER -> CLIENT: 250 Ok
|
||||
|
||||
CLIENT -> SERVER: RCPT TO:<support@yourdomain.com>
|
||||
|
||||
SERVER -> CLIENT: 250 Ok
|
||||
|
||||
CLIENT -> SERVER: DATA
|
||||
|
||||
SERVER -> CLIENT: 354 End data with <CR><LF>.<CR><LF>
|
||||
|
||||
CLIENT -> SERVER: Date: Wed, 10 Sep 2025 13:14:43 +0000
|
||||
|
||||
CLIENT -> SERVER: To: support@yourdomain.com
|
||||
|
||||
CLIENT -> SERVER: From: 10 <app@flatlogic.app>
|
||||
|
||||
CLIENT -> SERVER: Reply-To: Administrator <Blarior@gmail.com>, app@flatlogic.app
|
||||
|
||||
CLIENT -> SERVER: Subject: New Lead from Nimbus Landing Page
|
||||
|
||||
CLIENT -> SERVER: Message-ID: <RGGz281O0f5ZTbjFzdr3xDUogbl4E1VbKQwy0wGzlc@vm-30855.dev.flatlogic.app>
|
||||
|
||||
CLIENT -> SERVER: X-Mailer: PHPMailer 6.6.3 (https://github.com/PHPMailer/PHPMailer)
|
||||
|
||||
CLIENT -> SERVER: MIME-Version: 1.0
|
||||
|
||||
CLIENT -> SERVER: Content-Type: multipart/alternative;
|
||||
|
||||
CLIENT -> SERVER: boundary="b1_RGGz281O0f5ZTbjFzdr3xDUogbl4E1VbKQwy0wGzlc"
|
||||
|
||||
CLIENT -> SERVER: Content-Transfer-Encoding: 8bit
|
||||
|
||||
CLIENT -> SERVER:
|
||||
|
||||
CLIENT -> SERVER: This is a multi-part message in MIME format.
|
||||
|
||||
CLIENT -> SERVER:
|
||||
|
||||
CLIENT -> SERVER: --b1_RGGz281O0f5ZTbjFzdr3xDUogbl4E1VbKQwy0wGzlc
|
||||
|
||||
CLIENT -> SERVER: Content-Type: text/plain; charset=us-ascii
|
||||
|
||||
CLIENT -> SERVER:
|
||||
|
||||
CLIENT -> SERVER: Name: Administrator
|
||||
|
||||
CLIENT -> SERVER: Email: Blarior@gmail.com
|
||||
|
||||
CLIENT -> SERVER:
|
||||
|
||||
CLIENT -> SERVER: 123123
|
||||
|
||||
CLIENT -> SERVER:
|
||||
|
||||
CLIENT -> SERVER: --b1_RGGz281O0f5ZTbjFzdr3xDUogbl4E1VbKQwy0wGzlc
|
||||
|
||||
CLIENT -> SERVER: Content-Type: text/html; charset=us-ascii
|
||||
|
||||
CLIENT -> SERVER:
|
||||
|
||||
CLIENT -> SERVER: <p><strong>Name:</strong> Administrator</p><p><strong>Email:</strong> Blarior@gmail.com</p><hr>123123
|
||||
|
||||
CLIENT -> SERVER:
|
||||
|
||||
CLIENT -> SERVER:
|
||||
|
||||
CLIENT -> SERVER: --b1_RGGz281O0f5ZTbjFzdr3xDUogbl4E1VbKQwy0wGzlc--
|
||||
|
||||
CLIENT -> SERVER:
|
||||
|
||||
CLIENT -> SERVER: .
|
||||
|
||||
SERVER -> CLIENT: 250 Ok 0100019933c3a725-dee3f92c-89a8-43b8-b180-c9a6a6fc68db-000000
|
||||
|
||||
CLIENT -> SERVER: QUIT
|
||||
|
||||
SERVER -> CLIENT: 221 Bye
|
||||
|
||||
SERVER -> CLIENT: 220 email-smtp.amazonaws.com ESMTP SimpleEmailService-d-UJAP95IYE 6w6jayOyd9KI2UxK7oOd
|
||||
|
||||
CLIENT -> SERVER: EHLO vm-30855.dev.flatlogic.app
|
||||
|
||||
SERVER -> CLIENT: 250-email-smtp.amazonaws.com
|
||||
250-8BITMIME
|
||||
250-STARTTLS
|
||||
250-AUTH PLAIN LOGIN
|
||||
250 Ok
|
||||
|
||||
CLIENT -> SERVER: STARTTLS
|
||||
|
||||
SERVER -> CLIENT: 220 Ready to start TLS
|
||||
|
||||
CLIENT -> SERVER: EHLO vm-30855.dev.flatlogic.app
|
||||
|
||||
SERVER -> CLIENT: 250-email-smtp.amazonaws.com
|
||||
250-8BITMIME
|
||||
250-STARTTLS
|
||||
250-AUTH PLAIN LOGIN
|
||||
250 Ok
|
||||
|
||||
CLIENT -> SERVER: AUTH LOGIN
|
||||
|
||||
SERVER -> CLIENT: 334 VXNlcm5hbWU6
|
||||
|
||||
CLIENT -> SERVER: [credentials hidden]
|
||||
SERVER -> CLIENT: 334 UGFzc3dvcmQ6
|
||||
|
||||
CLIENT -> SERVER: [credentials hidden]
|
||||
SERVER -> CLIENT: 235 Authentication successful.
|
||||
|
||||
CLIENT -> SERVER: MAIL FROM:<app@flatlogic.app>
|
||||
|
||||
SERVER -> CLIENT: 250 Ok
|
||||
|
||||
CLIENT -> SERVER: RCPT TO:<support@yourdomain.com>
|
||||
|
||||
SERVER -> CLIENT: 250 Ok
|
||||
|
||||
CLIENT -> SERVER: DATA
|
||||
|
||||
SERVER -> CLIENT: 354 End data with <CR><LF>.<CR><LF>
|
||||
|
||||
CLIENT -> SERVER: Date: Wed, 10 Sep 2025 13:14:44 +0000
|
||||
|
||||
CLIENT -> SERVER: To: support@yourdomain.com
|
||||
|
||||
CLIENT -> SERVER: From: 10 <app@flatlogic.app>
|
||||
|
||||
CLIENT -> SERVER: Reply-To: Administrator <Blarior@gmail.com>, app@flatlogic.app
|
||||
|
||||
CLIENT -> SERVER: Subject: New Lead from Nimbus Landing Page
|
||||
|
||||
CLIENT -> SERVER: Message-ID: <cFHV6zdk7XiFa9a6KOQI4Pr2H941RS0y1Dmc5CeJU@vm-30855.dev.flatlogic.app>
|
||||
|
||||
CLIENT -> SERVER: X-Mailer: PHPMailer 6.6.3 (https://github.com/PHPMailer/PHPMailer)
|
||||
|
||||
CLIENT -> SERVER: MIME-Version: 1.0
|
||||
|
||||
CLIENT -> SERVER: Content-Type: multipart/alternative;
|
||||
|
||||
CLIENT -> SERVER: boundary="b1_cFHV6zdk7XiFa9a6KOQI4Pr2H941RS0y1Dmc5CeJU"
|
||||
|
||||
CLIENT -> SERVER: Content-Transfer-Encoding: 8bit
|
||||
|
||||
CLIENT -> SERVER:
|
||||
|
||||
CLIENT -> SERVER: This is a multi-part message in MIME format.
|
||||
|
||||
CLIENT -> SERVER:
|
||||
|
||||
CLIENT -> SERVER: --b1_cFHV6zdk7XiFa9a6KOQI4Pr2H941RS0y1Dmc5CeJU
|
||||
|
||||
CLIENT -> SERVER: Content-Type: text/plain; charset=us-ascii
|
||||
|
||||
CLIENT -> SERVER:
|
||||
|
||||
CLIENT -> SERVER: Name: Administrator
|
||||
|
||||
CLIENT -> SERVER: Email: Blarior@gmail.com
|
||||
|
||||
CLIENT -> SERVER:
|
||||
|
||||
CLIENT -> SERVER: 123123
|
||||
|
||||
CLIENT -> SERVER:
|
||||
|
||||
CLIENT -> SERVER: --b1_cFHV6zdk7XiFa9a6KOQI4Pr2H941RS0y1Dmc5CeJU
|
||||
|
||||
CLIENT -> SERVER: Content-Type: text/html; charset=us-ascii
|
||||
|
||||
CLIENT -> SERVER:
|
||||
|
||||
CLIENT -> SERVER: <p><strong>Name:</strong> Administrator</p><p><strong>Email:</strong> Blarior@gmail.com</p><hr>123123
|
||||
|
||||
CLIENT -> SERVER:
|
||||
|
||||
CLIENT -> SERVER:
|
||||
|
||||
CLIENT -> SERVER: --b1_cFHV6zdk7XiFa9a6KOQI4Pr2H941RS0y1Dmc5CeJU--
|
||||
|
||||
CLIENT -> SERVER:
|
||||
|
||||
CLIENT -> SERVER: .
|
||||
|
||||
SERVER -> CLIENT: 250 Ok 0100019933c3aa2d-e6f1f09d-c150-43d9-9c5a-d8f926c41ff2-000000
|
||||
|
||||
CLIENT -> SERVER: QUIT
|
||||
|
||||
SERVER -> CLIENT: 221 Bye
|
||||
|
||||
102
register.php
Normal file
102
register.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
require_once 'templates/header.php';
|
||||
|
||||
$errors = [];
|
||||
$username = '';
|
||||
$email = '';
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$username = trim($_POST['username']);
|
||||
$email = trim($_POST['email']);
|
||||
$password = $_POST['password'];
|
||||
$password_confirm = $_POST['password_confirm'];
|
||||
|
||||
// Validation
|
||||
if (empty($username)) {
|
||||
$errors[] = 'Username is required';
|
||||
}
|
||||
if (empty($email)) {
|
||||
$errors[] = 'Email is required';
|
||||
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$errors[] = 'Invalid email format';
|
||||
}
|
||||
if (empty($password)) {
|
||||
$errors[] = 'Password is required';
|
||||
}
|
||||
if ($password !== $password_confirm) {
|
||||
$errors[] = 'Passwords do not match';
|
||||
}
|
||||
|
||||
// Check if user already exists
|
||||
if (empty($errors)) {
|
||||
$stmt = db()->prepare("SELECT id FROM users WHERE username = ? OR email = ?");
|
||||
$stmt->execute([$username, $email]);
|
||||
if ($stmt->fetch()) {
|
||||
$errors[] = 'Username or email already taken';
|
||||
}
|
||||
}
|
||||
|
||||
// Insert user if no errors
|
||||
if (empty($errors)) {
|
||||
$password_hash = password_hash($password, PASSWORD_DEFAULT);
|
||||
$stmt = db()->prepare("INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)");
|
||||
if ($stmt->execute([$username, $email, $password_hash])) {
|
||||
// Redirect to login page
|
||||
header("Location: login.php?registration=success");
|
||||
exit();
|
||||
} else {
|
||||
$errors[] = 'Something went wrong. Please try again.';
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<main class="container my-5">
|
||||
|
||||
<section class="py-5">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-6">
|
||||
<div class="feature-card p-4">
|
||||
<h2 class="text-center mb-4">Create an Account</h2>
|
||||
|
||||
<?php if (!empty($errors)): ?>
|
||||
<div class="alert alert-danger">
|
||||
<?php foreach ($errors as $error): ?>
|
||||
<p class="m-0"><?php echo $error; ?></p>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST" action="register.php">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="username" name="username" value="<?php echo htmlspecialchars($username); ?>" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input type="email" class="form-control" id="email" name="email" value="<?php echo htmlspecialchars($email); ?>" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password_confirm" class="form-label">Confirm Password</label>
|
||||
<input type="password" class="form-control" id="password_confirm" name="password_confirm" required>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary">Register</button>
|
||||
</div>
|
||||
</form>
|
||||
<p class="text-center mt-3">
|
||||
Already have an account? <a href="login.php">Login here</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
<?php require_once 'templates/footer.php'; ?>
|
||||
12
templates/footer.php
Normal file
12
templates/footer.php
Normal file
@ -0,0 +1,12 @@
|
||||
<!-- Footer -->
|
||||
<footer class="py-4 bg-dark text-white text-center">
|
||||
<div class="container">
|
||||
<p class="m-0">© <?php echo date("Y"); ?> Nimbus. All Rights Reserved.</p>
|
||||
<p class="m-0"><a href="#" class="text-white">Privacy Policy</a> · <a href="#" class="text-white">Terms of Service</a></p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
40
templates/header.php
Normal file
40
templates/header.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php if (session_status() === PHP_SESSION_NONE) { session_start(); } ?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nimbus - Effortless Cloud Management</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<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">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark sticky-top">
|
||||
<div class="container">
|
||||
<a class="navbar-brand fw-bold" href="index.php">Nimbus</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="index.php#features">Features</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="index.php#testimonials">Testimonials</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="index.php#pricing">Pricing</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="index.php#faq">FAQ</a></li>
|
||||
<?php if (isset($_SESSION['user_id'])): ?>
|
||||
<li class="nav-item"><a class="nav-link" href="admin.php">Admin</a></li>
|
||||
<li class="nav-item"><a class="btn btn-primary ms-lg-2" href="logout.php">Logout</a></li>
|
||||
<?php else: ?>
|
||||
<li class="nav-item"><a class="nav-link" href="login.php">Login</a></li>
|
||||
<li class="nav-item"><a class="btn btn-primary ms-lg-2" href="register.php">Register</a></li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
Loading…
x
Reference in New Issue
Block a user