diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..fe38e7a --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,203 @@ +/* General Body Styles */ +body { + font-family: 'Roboto', sans-serif; + color: #4F4F4F; + background-color: #FFFFFF; + margin: 0; + line-height: 1.6; +} + +.container { + width: 90%; + max-width: 1100px; + margin: 0 auto; +} + +/* Typography */ +h1, h2, h3 { + font-family: 'Poppins', sans-serif; + font-weight: 600; + color: #333; +} + +h1 { font-size: 3rem; margin-bottom: 1rem; } +h2 { font-size: 2.2rem; margin-bottom: 1rem; text-align: center;} + +/* Header and Navbar */ +.navbar { + background: #FFFFFF; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + padding: 1rem 0; + position: sticky; + top: 0; + z-index: 1000; +} + +.navbar .container { + display: flex; + justify-content: space-between; + align-items: center; +} + +.logo { + font-family: 'Poppins', sans-serif; + font-size: 1.5rem; + font-weight: 700; + color: #2F80ED; + text-decoration: none; +} + +.nav-links { + list-style: none; + margin: 0; + padding: 0; + display: flex; + align-items: center; +} + +.nav-links li { + margin-left: 20px; +} + +.nav-links a { + text-decoration: none; + color: #4F4F4F; + font-weight: 500; + transition: color 0.3s ease; +} + +.nav-links a:hover { + color: #2F80ED; +} + +/* Buttons */ +.btn { + padding: 10px 20px; + border-radius: 8px; + text-decoration: none; + font-weight: 500; + transition: all 0.3s ease; + display: inline-block; + border: none; + cursor: pointer; +} + +.btn-primary { + background-color: #2F80ED; + color: #FFFFFF; +} + +.btn-primary:hover { + background-color: #2D67B2; +} + +.btn-secondary { + background-color: #F2F2F2; + color: #333; +} + +.btn-secondary:hover { + background-color: #E0E0E0; +} + +.btn-lg { + padding: 15px 30px; + font-size: 1.1rem; +} + +/* Hero Section */ +.hero-section { + background: linear-gradient(to right, #2F80ED, #56CCF2); + color: #FFFFFF; + text-align: center; + padding: 100px 0; +} + +.hero-section h1 { + color: #FFFFFF; + font-weight: 700; +} + +.hero-section p { + font-size: 1.2rem; + margin-bottom: 2rem; +} + +/* Content Sections */ +.content-section { + padding: 60px 0; +} + +.bg-light { + background-color: #F2F2F2; +} + +/* Service Boxes */ +.service-boxes { + display: flex; + justify-content: space-around; + gap: 20px; + margin-top: 40px; +} + +.service-box { + background: #FFFFFF; + padding: 30px; + border-radius: 8px; + box-shadow: 0 4px 6px rgba(0,0,0,0.1); + text-align: center; + width: 30%; +} + +.service-box h3 { + font-size: 1.5rem; + margin-bottom: 1rem; +} + +/* Contact Form */ +.contact-form { + max-width: 600px; + margin: 40px auto 0; +} + +.form-group { + margin-bottom: 1.5rem; +} + +.form-control { + width: 100%; + padding: 12px; + border: 1px solid #BDBDBD; + border-radius: 8px; + font-size: 1rem; +} + +.form-control:focus { + outline: none; + border-color: #2F80ED; +} + +/* Footer */ +.footer { + background: #333; + color: #FFFFFF; + text-align: center; + padding: 20px 0; +} + +/* Form Messages */ +#form-messages .success { + color: #27AE60; + background: #E9F7EF; + padding: 10px; + border-radius: 8px; + margin-bottom: 1rem; +} + +#form-messages .error { + color: #EB5757; + background: #FDEEEE; + padding: 10px; + border-radius: 8px; + margin-bottom: 1rem; +} \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..50e340f --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,30 @@ +document.addEventListener('DOMContentLoaded', function() { + const contactForm = document.getElementById('contact-form'); + const formMessages = document.getElementById('form-messages'); + + if (contactForm) { + contactForm.addEventListener('submit', function(e) { + e.preventDefault(); + + const formData = new FormData(contactForm); + + fetch('handle_contact.php', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + formMessages.innerHTML = `
${data.message}
`; + contactForm.reset(); + } else { + formMessages.innerHTML = `
${data.message}
`; + } + }) + .catch(error => { + formMessages.innerHTML = `
An error occurred while sending your message. Please try again.
`; + console.error('Error:', error); + }); + }); + } +}); \ No newline at end of file diff --git a/contact.php b/contact.php new file mode 100644 index 0000000..a7efceb --- /dev/null +++ b/contact.php @@ -0,0 +1,47 @@ + false, 'error' => 'Please fill out all fields.']); + exit; +} + +if (!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) { + echo json_encode(['success' => false, 'error' => 'Invalid email format.']); + exit; +} + +$name = $_POST['name']; +$email = $_POST['email']; +$message = $_POST['message']; + +try { + // 1. Save to database + $pdo = db(); + $stmt = $pdo->prepare("INSERT INTO contact_submissions (name, email, message) VALUES (?, ?, ?)"); + $stmt->execute([$name, $email, $message]); + + // 2. Send email notification + // The recipient can be configured via the MAIL_TO environment variable. + $mailResult = MailService::sendContactMessage($name, $email, $message, null, 'New Miralok Africa Inquiry'); + + if ($mailResult['success']) { + echo json_encode(['success' => true]); + } else { + // Log error, but don't expose it to the client for security. + error_log('MailService Error: ' . ($mailResult['error'] ?? 'Unknown error')); + // Still return success to the user as the main action (saving the submission) was successful. + echo json_encode(['success' => true, 'warning' => 'Could not send email notification.']); + } + +} catch (PDOException $e) { + error_log('Database Error: ' . $e->getMessage()); + echo json_encode(['success' => false, 'error' => 'A server error occurred. Please try again later.']); +} catch (Exception $e) { + error_log('General Error: ' . $e->getMessage()); + echo json_encode(['success' => false, 'error' => 'A server error occurred. Please try again later.']); +} diff --git a/dashboard.php b/dashboard.php new file mode 100644 index 0000000..bfa2e01 --- /dev/null +++ b/dashboard.php @@ -0,0 +1,52 @@ + + + + + + + Dashboard - Miralok Africa + + + + + + + + + + + + + +
+
+
+

Welcome, !

+

This is your dashboard. From here you will be able to manage your profile, vehicles, and applications.

+

Your role is:

+
+
+ +
+
+

Quick Actions

+

More content and features will be added here soon.

+
+
+
+ + + + + diff --git a/db/config.php b/db/config.php index cc9229f..2aa6380 100644 --- a/db/config.php +++ b/db/config.php @@ -1,5 +1,4 @@ exec("CREATE TABLE IF NOT EXISTS contact_submissions ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL, + message TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + );"); + + // Organizations Table + $pdo->exec("CREATE TABLE IF NOT EXISTS Organization ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + type ENUM('Insurer','Financier','OEM','FleetOperator','Partner') NOT NULL, + contactEmail VARCHAR(255), + phone VARCHAR(255), + address TEXT, + website VARCHAR(255), + approved BOOLEAN DEFAULT false, + createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP + );"); + + // Users Table + $pdo->exec("CREATE TABLE IF NOT EXISTS User ( + id INT AUTO_INCREMENT PRIMARY KEY, + firstName VARCHAR(255) NOT NULL, + lastName VARCHAR(255), + email VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + phone VARCHAR(255), + role ENUM('Rider','FleetOperator','Insurer','Financier','OEM','Partner','Support','Admin','Public') NOT NULL, + organizationId INT, + verified BOOLEAN DEFAULT false, + createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (organizationId) REFERENCES Organization(id) ON DELETE SET NULL + );"); + + // Fleets Table + $pdo->exec("CREATE TABLE IF NOT EXISTS Fleet ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + organizationId INT, + managerUserId INT, + notes TEXT, + createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (organizationId) REFERENCES Organization(id) ON DELETE CASCADE, + FOREIGN KEY (managerUserId) REFERENCES User(id) ON DELETE SET NULL + );"); + + // Vehicles Table + $pdo->exec("CREATE TABLE IF NOT EXISTS Vehicle ( + id INT AUTO_INCREMENT PRIMARY KEY, + vin VARCHAR(255) UNIQUE, + model VARCHAR(255) NOT NULL, + licensePlate VARCHAR(255), + ownerUserId INT, + fleetId INT, + createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (ownerUserId) REFERENCES User(id) ON DELETE SET NULL, + FOREIGN KEY (fleetId) REFERENCES Fleet(id) ON DELETE SET NULL + );"); + + } catch (PDOException $e) { + die("Database initialization failed: " . $e->getMessage()); + } +} + +// Run the initialization function to ensure tables exist. +db_init(); diff --git a/db/migrations/001_create_contact_submissions_table.sql b/db/migrations/001_create_contact_submissions_table.sql new file mode 100644 index 0000000..541e784 --- /dev/null +++ b/db/migrations/001_create_contact_submissions_table.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `contact_submissions` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `name` VARCHAR(255) NOT NULL, + `email` VARCHAR(255) NOT NULL, + `message` TEXT NOT NULL, + `submitted_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/handle_contact.php b/handle_contact.php new file mode 100644 index 0000000..398c0e1 --- /dev/null +++ b/handle_contact.php @@ -0,0 +1,61 @@ + false, 'message' => 'An unknown error occurred.']; + +if ($_SERVER["REQUEST_METHOD"] == "POST") { + $name = trim($_POST['name'] ?? ''); + $email = trim($_POST['email'] ?? ''); + $message = trim($_POST['message'] ?? ''); + + if (empty($name) || empty($email) || empty($message)) { + $response['message'] = 'Please fill in all fields.'; + echo json_encode($response); + exit; + } + + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + $response['message'] = 'Invalid email format.'; + echo json_encode($response); + exit; + } + + try { + $pdo = db(); + $sql = "INSERT INTO contact_submissions (name, email, message) VALUES (?, ?, ?)"; + $stmt = $pdo->prepare($sql); + + if ($stmt->execute([$name, $email, $message])) { + // Database insert was successful, now send email + $mailResult = MailService::sendContactMessage($name, $email, $message); + + if (!empty($mailResult['success'])) { + $response['success'] = true; + $response['message'] = 'Thank you for your message. We will get back to you shortly.'; + } else { + // Email failed, but data is saved. This might be a configuration issue. + $response['success'] = true; // Still a success from user's perspective + $response['message'] = 'Thank you for your message. It has been received.'; + // Log the email error if possible, e.g., error_log("MailService Error: " . $mailResult['error']); + } + } else { + $response['message'] = 'Error: Could not save your message.'; + } + } catch (PDOException $e) { + // Log database error, don't show specific SQL errors to user + error_log($e->getMessage()); + $response['message'] = 'A server error occurred. Please try again later.'; + } catch (Exception $e) { + error_log($e->getMessage()); + $response['message'] = 'An unexpected error occurred. Please try again later.'; + } + +} else { + $response['message'] = 'Invalid request method.'; +} + +echo json_encode($response); +?> \ No newline at end of file diff --git a/handle_login.php b/handle_login.php new file mode 100644 index 0000000..794ed95 --- /dev/null +++ b/handle_login.php @@ -0,0 +1,40 @@ + false, 'error' => 'Email and password are required.']); + exit; +} + +$email = $_POST['email']; +$password = $_POST['password']; + +try { + $pdo = db(); + + // 2. Fetch user by email + $stmt = $pdo->prepare("SELECT id, firstName, role, password FROM User WHERE email = ?"); + $stmt->execute([$email]); + $user = $stmt->fetch(); + + // 3. Verify password + if ($user && password_verify($password, $user['password'])) { + // 4. Set session variables + $_SESSION['user_id'] = $user['id']; + $_SESSION['user_name'] = $user['firstName']; + $_SESSION['user_role'] = $user['role']; + + echo json_encode(['success' => true, 'redirect' => 'dashboard.php']); + } else { + echo json_encode(['success' => false, 'error' => 'Invalid email or password.']); + } + +} catch (PDOException $e) { + error_log('Login Error: ' . $e->getMessage()); + echo json_encode(['success' => false, 'error' => 'A server error occurred. Please try again later.']); +} diff --git a/handle_register.php b/handle_register.php new file mode 100644 index 0000000..28bf5e3 --- /dev/null +++ b/handle_register.php @@ -0,0 +1,52 @@ + false, 'error' => implode(' ', $errors)]); + exit; +} + +$firstName = $_POST['firstName']; +$lastName = $_POST['lastName']; +$email = $_POST['email']; +$password = $_POST['password']; + +try { + $pdo = db(); + + // 2. Check if user already exists + $stmt = $pdo->prepare("SELECT id FROM User WHERE email = ?"); + $stmt->execute([$email]); + if ($stmt->fetch()) { + echo json_encode(['success' => false, 'error' => 'An account with this email already exists.']); + exit; + } + + // 3. Hash password + $hashedPassword = password_hash($password, PASSWORD_DEFAULT); + + // 4. Insert new user with 'Rider' role + $stmt = $pdo->prepare( + "INSERT INTO User (firstName, lastName, email, password, role) VALUES (?, ?, ?, ?, ?)" + ); + $stmt->execute([$firstName, $lastName, $email, $hashedPassword, 'Rider']); + + // 5. Return success + echo json_encode(['success' => true]); + +} catch (PDOException $e) { + error_log('Registration Error: ' . $e->getMessage()); + echo json_encode(['success' => false, 'error' => 'A server error occurred during registration. Please try again later.']); +} diff --git a/index.php b/index.php index 7205f3d..a69db6a 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,61 @@ - -$phpVersion = PHP_VERSION; -$now = date('Y-m-d H:i:s'); -?> - - - - - - New Style - - - - - - - - - - - - - - - - - - - - - -
-
-

Analyzing your requirements and generating your website…

-
- Loading… -
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

+
+
+

Connecting the Future of African Mobility

+

A unified online ecosystem for electric vehicle adoption, financing, and insurance.

+ Get Started Today
-
- - - + + +
+
+

About Miralok Africa

+

Miralok Africa provides online business services that connect riders, fleet operators, insurers, and financiers. We serve as a continental web portal offering verified information, strategic partnerships, and digital workflows that simplify how Africans access, finance, and protect electric mobility assets.

+
+
+ +
+
+

Our Services

+
+
+

Educate

+

We educate the public on EV technology, financing models, and cost-of-ownership.

+
+
+

Connect

+

We connect insurers, OEMs, and fleet managers for transparent collaboration.

+
+
+

Host

+

We host digital information, forms, and partner dashboards to reduce time and cost of adoption.

+
+
+
+
+ +
+
+

Contact Us

+

Have a question or want to partner with us? Send us a message.

+
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ + \ No newline at end of file diff --git a/login.php b/login.php new file mode 100644 index 0000000..3588e78 --- /dev/null +++ b/login.php @@ -0,0 +1,118 @@ + + + + + + + Login - Miralok Africa + + + + + + + + + + + +
+

Log In

+ + +
Registration successful! Please log in.
+ + +
+
+ +
Registration successful! Please log in.
+ + +
Please log in to access that page.
+ + +
+ + +
+
+ + +
+
+ +
+

Don't have an account? Register

+
+

Back to Home

+
+ + + + + + diff --git a/logout.php b/logout.php new file mode 100644 index 0000000..ea4deb3 --- /dev/null +++ b/logout.php @@ -0,0 +1,22 @@ + + + + + + Register - Miralok Africa + + + + + + + + + + + +
+

Create Your Rider Account

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

Already have an account? Log in

+
+

Back to Home

+
+ + + + + + diff --git a/shared/footer.php b/shared/footer.php new file mode 100644 index 0000000..11c50bd --- /dev/null +++ b/shared/footer.php @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/shared/header.php b/shared/header.php new file mode 100644 index 0000000..f96e04b --- /dev/null +++ b/shared/header.php @@ -0,0 +1,28 @@ + + + + + + Miralok Africa + + + + + + + + +