This commit is contained in:
Flatlogic Bot 2025-10-12 11:03:22 +00:00
parent 222ac63dcc
commit 9660f06ca5
19 changed files with 677 additions and 273 deletions

View File

@ -8,10 +8,7 @@ if (!isset($_GET['id'])) {
$pdo = db(); $pdo = db();
// We should also delete related orders to maintain data integrity // The ON DELETE CASCADE constraint on the orders table will automatically delete related orders.
$stmt = $pdo->prepare('DELETE FROM orders WHERE customer_id = ?');
$stmt->execute([$_GET['id']]);
$stmt = $pdo->prepare('DELETE FROM customers WHERE id = ?'); $stmt = $pdo->prepare('DELETE FROM customers WHERE id = ?');
$stmt->execute([$_GET['id']]); $stmt->execute([$_GET['id']]);

View File

@ -7,7 +7,7 @@ $customer = [
'id' => '', 'id' => '',
'name' => '', 'name' => '',
'email' => '', 'email' => '',
'address' => '' 'service_address' => ''
]; ];
$page_title = 'Edit Customer'; $page_title = 'Edit Customer';
@ -30,12 +30,12 @@ if (!$customer) {
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? ''; $name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? ''; $email = $_POST['email'] ?? '';
$address = $_POST['address'] ?? ''; $service_address = $_POST['service_address'] ?? '';
$customer_id = $_POST['id'] ?? null; $customer_id = $_POST['id'] ?? null;
if ($customer_id) { if ($customer_id) {
$stmt = $pdo->prepare('UPDATE customers SET name = ?, email = ?, address = ? WHERE id = ?'); $stmt = $pdo->prepare('UPDATE customers SET name = ?, email = ?, service_address = ? WHERE id = ?');
$stmt->execute([$name, $email, $address, $customer_id]); $stmt->execute([$name, $email, $service_address, $customer_id]);
header('Location: customers.php'); header('Location: customers.php');
exit; exit;
} }
@ -55,8 +55,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<input type="email" class="form-control" id="email" name="email" value="<?php echo htmlspecialchars($customer['email']); ?>" required> <input type="email" class="form-control" id="email" name="email" value="<?php echo htmlspecialchars($customer['email']); ?>" required>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="address" class="form-label">Address</label> <label for="service_address" class="form-label">Service Address</label>
<input type="text" class="form-control" id="address" name="address" value="<?php echo htmlspecialchars($customer['address']); ?>" required> <input type="text" class="form-control" id="service_address" name="service_address" value="<?php echo htmlspecialchars($customer['service_address']); ?>" required>
</div> </div>
<button type="submit" class="btn btn-primary">Save Customer</button> <button type="submit" class="btn btn-primary">Save Customer</button>
<a href="customers.php" class="btn btn-secondary">Cancel</a> <a href="customers.php" class="btn btn-secondary">Cancel</a>

View File

@ -17,7 +17,7 @@ $customers = $stmt->fetchAll();
<th>ID</th> <th>ID</th>
<th>Name</th> <th>Name</th>
<th>Email</th> <th>Email</th>
<th>Address</th> <th>Service Address</th>
<th>Joined On</th> <th>Joined On</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
@ -33,7 +33,7 @@ $customers = $stmt->fetchAll();
<td><?php echo htmlspecialchars($customer['id']); ?></td> <td><?php echo htmlspecialchars($customer['id']); ?></td>
<td><?php echo htmlspecialchars($customer['name']); ?></td> <td><?php echo htmlspecialchars($customer['name']); ?></td>
<td><?php echo htmlspecialchars($customer['email']); ?></td> <td><?php echo htmlspecialchars($customer['email']); ?></td>
<td><?php echo htmlspecialchars($customer['address']); ?></td> <td><?php echo htmlspecialchars($customer['service_address']); ?></td>
<td><?php echo htmlspecialchars(date('Y-m-d', strtotime($customer['created_at']))); ?></td> <td><?php echo htmlspecialchars(date('Y-m-d', strtotime($customer['created_at']))); ?></td>
<td> <td>
<a href="customer_edit.php?id=<?php echo $customer['id']; ?>" class="btn btn-sm btn-primary">Edit</a> <a href="customer_edit.php?id=<?php echo $customer['id']; ?>" class="btn btn-sm btn-primary">Edit</a>

View File

@ -43,6 +43,7 @@
<a class="nav-link" href="/admin/customers.php"><i class="fa fa-users"></i>Customers</a> <a class="nav-link" href="/admin/customers.php"><i class="fa fa-users"></i>Customers</a>
<a class="nav-link" href="/admin/orders.php"><i class="fa fa-box-open"></i>Orders</a> <a class="nav-link" href="/admin/orders.php"><i class="fa fa-box-open"></i>Orders</a>
<a class="nav-link" href="/admin/plans.php"><i class="fa fa-list-alt"></i>Plans</a> <a class="nav-link" href="/admin/plans.php"><i class="fa fa-list-alt"></i>Plans</a>
<a class="nav-link" href="/admin/pages.php"><i class="fa fa-file-alt"></i>Pages</a>
</nav> </nav>
</div> </div>
<div class="content"> <div class="content">

View File

@ -3,8 +3,23 @@ include __DIR__ . '/../db/config.php';
include 'header.php'; include 'header.php';
$pdo = db(); $pdo = db();
$stmt = $pdo->query('SELECT o.id, c.name as customer_name, p.name as plan_name, o.status, o.created_at FROM orders o JOIN customers c ON o.customer_id = c.id JOIN plans p ON o.plan_id = p.id ORDER BY o.created_at DESC'); $stmt = $pdo->query('SELECT o.id, c.name as customer_name, p.name as plan_name, o.order_status, o.amount, o.created_at FROM orders o JOIN customers c ON o.customer_id = c.id JOIN plans p ON o.plan_id = p.id ORDER BY o.created_at DESC');
$orders = $stmt->fetchAll(); $orders = $stmt->fetchAll();
function get_status_badge($status) {
switch (strtolower($status)) {
case 'completed':
case 'active':
return 'bg-success';
case 'pending':
return 'bg-warning';
case 'failed':
case 'cancelled':
return 'bg-danger';
default:
return 'bg-secondary';
}
}
?> ?>
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
@ -17,6 +32,7 @@ $orders = $stmt->fetchAll();
<th>Order ID</th> <th>Order ID</th>
<th>Customer</th> <th>Customer</th>
<th>Plan</th> <th>Plan</th>
<th>Amount</th>
<th>Status</th> <th>Status</th>
<th>Order Date</th> <th>Order Date</th>
</tr> </tr>
@ -24,7 +40,7 @@ $orders = $stmt->fetchAll();
<tbody> <tbody>
<?php if (empty($orders)): ?> <?php if (empty($orders)): ?>
<tr> <tr>
<td colspan="5" class="text-center">No orders found.</td> <td colspan="6" class="text-center">No orders found.</td>
</tr> </tr>
<?php else: ?> <?php else: ?>
<?php foreach ($orders as $order): ?> <?php foreach ($orders as $order): ?>
@ -32,8 +48,9 @@ $orders = $stmt->fetchAll();
<td><?php echo htmlspecialchars($order['id']); ?></td> <td><?php echo htmlspecialchars($order['id']); ?></td>
<td><?php echo htmlspecialchars($order['customer_name']); ?></td> <td><?php echo htmlspecialchars($order['customer_name']); ?></td>
<td><?php echo htmlspecialchars($order['plan_name']); ?></td> <td><?php echo htmlspecialchars($order['plan_name']); ?></td>
<td><span class="badge bg-success"><?php echo htmlspecialchars($order['status']); ?></span></td> <td>$<?php echo htmlspecialchars(number_format($order['amount'], 2)); ?></td>
<td><?php echo htmlspecialchars(date('Y-m-d', strtotime($order['created_at']))); ?></td> <td><span class="badge <?php echo get_status_badge($order['order_status']); ?>"><?php echo htmlspecialchars(ucfirst($order['order_status'])); ?></span></td>
<td><?php echo htmlspecialchars(date('Y-m-d H:i', strtotime($order['created_at']))); ?></td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
<?php endif; ?> <?php endif; ?>

12
admin/page_delete.php Normal file
View File

@ -0,0 +1,12 @@
<?php
require_once '../db/config.php';
$id = $_GET['id'] ?? null;
if ($id) {
$stmt = db()->prepare('DELETE FROM pages WHERE id = ?');
$stmt->execute([$id]);
}
header('Location: pages.php');
exit;

55
admin/page_edit.php Normal file
View File

@ -0,0 +1,55 @@
<?php
require_once '../db/config.php';
$id = $_GET['id'] ?? null;
$page = [];
$title = 'Create Page';
if ($id) {
$stmt = db()->prepare('SELECT * FROM pages WHERE id = ?');
$stmt->execute([$id]);
$page = $stmt->fetch();
$title = 'Edit Page';
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$slug = $_POST['slug'];
$page_title = $_POST['title'];
$content = $_POST['content'];
if ($id) {
$stmt = db()->prepare('UPDATE pages SET slug = ?, title = ?, content = ? WHERE id = ?');
$stmt->execute([$slug, $page_title, $content, $id]);
} else {
$stmt = db()->prepare('INSERT INTO pages (slug, title, content) VALUES (?, ?, ?)');
$stmt->execute([$slug, $page_title, $content]);
}
header('Location: pages.php');
exit;
}
include 'header.php';
?>
<h1><?php echo $title; ?></h1>
<form method="POST">
<div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" class="form-control" id="title" name="title" value="<?php echo htmlspecialchars($page['title'] ?? ''); ?>" required>
</div>
<div class="mb-3">
<label for="slug" class="form-label">Slug</label>
<input type="text" class="form-control" id="slug" name="slug" value="<?php echo htmlspecialchars($page['slug'] ?? ''); ?>" required>
<div class="form-text">The slug is the URL-friendly version of the name. It is usually all lowercase and contains only letters, numbers, and hyphens.</div>
</div>
<div class="mb-3">
<label for="content" class="form-label">Content</label>
<textarea class="form-control" id="content" name="content" rows="10" required><?php echo htmlspecialchars($page['content'] ?? ''); ?></textarea>
</div>
<button type="submit" class="btn btn-primary">Save</button>
<a href="pages.php" class="btn btn-secondary">Cancel</a>
</form>
<?php include 'footer.php'; ?>

37
admin/pages.php Normal file
View File

@ -0,0 +1,37 @@
<?php
require_once '../db/config.php';
$pages = db()->query('SELECT * FROM pages ORDER BY title ASC')->fetchAll();
include 'header.php';
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Pages</h1>
<a href="page_edit.php" class="btn btn-primary">Create Page</a>
</div>
<table class="table table-striped">
<thead>
<tr>
<th>Title</th>
<th>Slug</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($pages as $page): ?>
<tr>
<td><?php echo htmlspecialchars($page['title']); ?></td>
<td><?php echo htmlspecialchars($page['slug']); ?></td>
<td>
<a href="page_edit.php?id=<?php echo $page['id']; ?>" class="btn btn-sm btn-outline-primary">Edit</a>
<a href="page_delete.php?id=<?php echo $page['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure?')">Delete</a>
<a href="/page.php?slug=<?php echo htmlspecialchars($page['slug']); ?>" class="btn btn-sm btn-outline-secondary" target="_blank">View</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php include 'footer.php'; ?>

75
api/ai_support.php Normal file
View File

@ -0,0 +1,75 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../db/config.php';
// 1. Fetch plans from the database to create a knowledge base.
$knowledge_base = "";
try {
$pdo = db();
$stmt = $pdo->query("SELECT name, speed_mbps, price_monthly, contract_length_months FROM plans ORDER BY price_monthly ASC");
$plans = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($plans) {
$knowledge_base .= "Here is a list of our current internet plans:\n\n";
foreach ($plans as $plan) {
$knowledge_base .= "- Plan Name: " . $plan['name'] . "\n";
$knowledge_base .= " - Speed: " . $plan['speed_mbps'] . " Mbps\n";
$knowledge_base .= " - Price: $" . $plan['price_monthly'] . " per month\n";
$knowledge_base .= " - Contract: " . $plan['contract_length_months'] . " months\n\n";
}
$knowledge_base .= "You are a helpful and friendly customer support assistant for a telecommunications company. Your goal is to answer customer questions based ONLY on the information provided in this knowledge base. Do not invent or assume any details. If the answer is not in the knowledge base, say 'I'm sorry, I don't have that information, but I can connect you with a human agent.' Keep your answers concise and to the point.";
}
} catch (PDOException $e) {
// In a real app, you'd log this error.
// For now, we'll proceed with an empty knowledge base on failure.
}
// 2. Get the user's question
$data = json_decode(file_get_contents('php://input'), true);
$user_question = $data['question'] ?? '';
if (empty($user_question)) {
echo json_encode(['error' => 'No question provided.']);
exit;
}
// 3. Prepare the data for the Gemini API
// IMPORTANT: You must replace getenv('GEMINI_API_KEY') with your actual API key.
$api_key = getenv('GEMINI_API_KEY');
$api_url = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=' . $api_key;
$payload = [
'contents' => [
[
'parts' => [
['text' => $knowledge_base . "\n\nCustomer Question: " . $user_question]
]
]
]
];
// 4. Use cURL to make the API call
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $api_url);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // For local development only
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// 5. Process and return the response
if ($http_code === 200) {
$result = json_decode($response, true);
$ai_response = $result['candidates'][0]['content']['parts'][0]['text'] ?? 'I am unable to answer at this time.';
echo json_encode(['reply' => $ai_response]);
} else {
// Log the error response for debugging
error_log("Gemini API Error: HTTP " . $http_code . " - " . $response);
echo json_encode(['error' => 'Failed to get a response from the AI assistant. Please try again later.']);
}

77
api/process_signup.php Normal file
View File

@ -0,0 +1,77 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../db/config.php';
\Stripe\Stripe::setApiKey('sk_test_51Hh9Y2L9s5P2Q8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4');
header('Content-Type: application/json');
$json_str = file_get_contents('php://input');
$json_obj = json_decode($json_str);
$pdo = db();
try {
// 1. Validation
if (empty($json_obj->plan_id) || empty($json_obj->name) || empty($json_obj->email) || empty($json_obj->address)) {
throw new Exception('Incomplete data provided.');
}
// 2. Fetch Plan
$stmt = $pdo->prepare("SELECT * FROM plans WHERE id = ?");
$stmt->execute([$json_obj->plan_id]);
$plan = $stmt->fetch(PDO::FETCH_OBJ);
if (!$plan) {
throw new Exception('Plan not found.');
}
$order_amount = $plan->price_monthly; // Amount in dollars
// 3. Create Stripe Customer
$stripe_customer = \Stripe\Customer::create([
'name' => $json_obj->name,
'email' => $json_obj->email,
'address' => [
'line1' => $json_obj->address
],
]);
// 4. Create Local Customer
// For now, using a placeholder for the password. In a real app, this should be properly hashed.
$password_placeholder = password_hash('password123', PASSWORD_DEFAULT);
$stmt = $pdo->prepare("INSERT INTO customers (name, email, password, service_address, stripe_customer_id) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$json_obj->name, $json_obj->email, $password_placeholder, $json_obj->address, $stripe_customer->id]);
$customer_id = $pdo->lastInsertId();
// 5. Create Local Order
$stmt = $pdo->prepare("INSERT INTO orders (customer_id, plan_id, order_status, amount) VALUES (?, ?, 'pending', ?)");
$stmt->execute([$customer_id, $plan->id, $order_amount]);
$order_id = $pdo->lastInsertId();
// 6. Create Stripe Payment Intent
$paymentIntent = \Stripe\PaymentIntent::create([
'customer' => $stripe_customer->id,
'amount' => round($order_amount * 100), // Amount in cents
'currency' => 'aud',
'automatic_payment_methods' => [
'enabled' => true,
],
'metadata' => [
'order_id' => $order_id,
'customer_id' => $customer_id,
'plan_id' => $plan->id
]
]);
// 7. Update Local Order with Payment Intent ID
$stmt = $pdo->prepare("UPDATE orders SET stripe_payment_intent_id = ? WHERE id = ?");
$stmt->execute([$paymentIntent->id, $order_id]);
// 8. Return Client Secret
echo json_encode([
'clientSecret' => $paymentIntent->client_secret,
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}

View File

@ -1,36 +1,21 @@
// This is your test publishable key. Don't hardcode this in a real app.
const stripe = Stripe('pk_test_51Hh9Y2L9s5P2Q8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4'); const stripe = Stripe('pk_test_51Hh9Y2L9s5P2Q8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4');
let elements; const form = document.querySelector("#signup-form");
let clientSecret; form.addEventListener("submit", handleSubmit);
initialize(); // Initially, disable the submit button until the form is filled
document.querySelector("#submit").disabled = true;
document
.querySelector("#signup-form")
.addEventListener("submit", handleSubmit);
// Fetches a payment intent and captures the client secret
async function initialize() {
const urlParams = new URLSearchParams(window.location.search);
const planId = urlParams.get('plan_id');
const response = await fetch("/api/create_payment_intent.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ plan_id: planId }),
});
const data = await response.json();
clientSecret = data.clientSecret;
const appearance = {
theme: 'stripe',
};
elements = stripe.elements({ appearance, clientSecret });
// Create and mount the Payment Element
const elements = stripe.elements();
const paymentElement = elements.create("payment"); const paymentElement = elements.create("payment");
paymentElement.mount("#payment-element"); paymentElement.mount("#payment-element");
}
// Re-enable the submit button when the Payment Element is ready
paymentElement.on('ready', () => {
document.querySelector("#submit").disabled = false;
});
async function handleSubmit(e) { async function handleSubmit(e) {
e.preventDefault(); e.preventDefault();
@ -39,69 +24,64 @@ async function handleSubmit(e) {
const name = document.getElementById('name').value; const name = document.getElementById('name').value;
const email = document.getElementById('email').value; const email = document.getElementById('email').value;
const address = document.getElementById('address').value; const address = document.getElementById('address').value;
const planId = form.dataset.planId;
if (!name || !email || !address) { if (!name || !email || !address || !planId) {
showMessage("Please fill out all fields."); showMessage("Please fill out all fields.");
setLoading(false); setLoading(false);
return; return;
} }
const payment_intent_id = clientSecret.split('_secret')[0]; // Create customer, order, and payment intent on the server.
const response = await fetch("/api/process_signup.php", {
// Update the payment intent with customer details
await fetch("/api/update_payment_intent.php", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body: JSON.stringify({
payment_intent_id: payment_intent_id, plan_id: planId,
name: name, name: name,
email: email, email: email,
address: address address: address
}), }),
}); });
const { error } = await stripe.confirmPayment({ const { clientSecret, error } = await response.json();
if (error) {
showMessage(error);
setLoading(false);
return;
}
// Confirm the payment with the client secret.
const { error: stripeError } = await stripe.confirmPayment({
elements, elements,
clientSecret,
confirmParams: { confirmParams: {
// Make sure to change this to your payment completion page
return_url: window.location.origin + "/order_confirmation.php", return_url: window.location.origin + "/order_confirmation.php",
receipt_email: email, receipt_email: email,
}, },
}); });
// This point will only be reached if there is an immediate error when if (stripeError) {
// confirming the payment. Otherwise, your customer will be redirected to showMessage(stripeError.message);
// your `return_url`. For some payment methods like iDEAL, your customer will
// be redirected to an intermediate site first to authorize the payment, then
// redirected to the `return_url`.
if (error.type === "card_error" || error.type === "validation_error") {
showMessage(error.message);
} else {
showMessage("An unexpected error occurred.");
} }
setLoading(false); setLoading(false);
} }
// ------- UI helpers ------- // ------- UI helpers -------
function showMessage(messageText) { function showMessage(messageText) {
const messageContainer = document.querySelector("#payment-message"); const messageContainer = document.querySelector("#payment-message");
messageContainer.style.display = "block"; messageContainer.style.display = "block";
messageContainer.textContent = messageText; messageContainer.textContent = messageText;
setTimeout(function () { setTimeout(function () {
messageContainer.style.display = "none"; messageContainer.style.display = "none";
messageContainer.textContent = ""; messageContainer.textContent = "";
}, 4000); }, 5000);
} }
// Show a spinner on payment submission
function setLoading(isLoading) { function setLoading(isLoading) {
if (isLoading) { if (isLoading) {
// Disable the button and show a spinner
document.querySelector("#submit").disabled = true; document.querySelector("#submit").disabled = true;
document.querySelector("#spinner").style.display = "inline"; document.querySelector("#spinner").style.display = "inline";
document.querySelector("#button-text").style.display = "none"; document.querySelector("#button-text").style.display = "none";

View File

@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS pages (
id INT AUTO_INCREMENT PRIMARY KEY,
slug VARCHAR(255) NOT NULL UNIQUE,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

13
footer.php Normal file
View File

@ -0,0 +1,13 @@
</main>
<footer class="py-4 bg-dark text-white text-center">
<div class="container">
<p>&copy; <?php echo date('Y'); ?> Australia Broadband Internet. All Rights Reserved.</p>
<p><a href="privacy.php" class="text-white">Privacy Policy</a></p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body>
</html>

35
header.php Normal file
View File

@ -0,0 +1,35 @@
<?php session_start(); ?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Australia Broadband Internet</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/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;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<header class="navbar navbar-expand-lg navbar-light bg-light fixed-top">
<div class="container">
<a class="navbar-brand" href="index.php">ABI</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#hero">Home</a></li>
<li class="nav-item"><a class="nav-link" href="index.php#plans">Plans</a></li>
<li class="nav-item"><a class="nav-link" href="index.php#about">About</a></li>
<li class="nav-item"><a class="nav-link" href="index.php#contact">Contact</a></li>
<li class="nav-item"><a class="btn btn-primary ms-lg-3" href="index.php#hero">Check Availability</a></li>
</ul>
</div>
</div>
</header>
<main class="py-5 mt-5">

View File

@ -1,36 +1,4 @@
<!DOCTYPE html> <?php include 'header.php'; ?>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Australia Broadband Internet</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<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;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<header class="navbar navbar-expand-lg navbar-light bg-light fixed-top">
<div class="container">
<a class="navbar-brand" href="#">ABI</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="#hero">Home</a></li>
<li class="nav-item"><a class="nav-link" href="#plans">Plans</a></li>
<li class="nav-item"><a class="nav-link" href="#about">About</a></li>
<li class="nav-item"><a class="nav-link" href="#contact">Contact</a></li>
<li class="nav-item"><a class="btn btn-primary ms-lg-3" href="#hero">Check Availability</a></li>
</ul>
</div>
</div>
</header>
<main>
<section id="hero" class="hero text-center"> <section id="hero" class="hero text-center">
<div class="container"> <div class="container">
<h1>Fast, Reliable Internet for Your Home</h1> <h1>Fast, Reliable Internet for Your Home</h1>
@ -49,7 +17,7 @@
</div> </div>
</section> </section>
<section id="plans" class="py-5"> <section id="plans" class="py-5 bg-light">
<div class="container"> <div class="container">
<h2 class="text-center mb-5">Our Plans</h2> <h2 class="text-center mb-5">Our Plans</h2>
<div class="row"> <div class="row">
@ -57,11 +25,9 @@
require_once __DIR__ . '/db/config.php'; require_once __DIR__ . '/db/config.php';
try { try {
$pdo = db(); $pdo = db();
$stmt = $pdo->query("SELECT * FROM plans WHERE is_active = 1 ORDER BY price"); $stmt = $pdo->query("SELECT * FROM plans ORDER BY price_monthly");
$plans = $stmt->fetchAll(); $plans = $stmt->fetchAll();
} catch (PDOException $e) { } catch (PDOException $e) {
// For the public page, we might not want to show a detailed error.
// We can log the error and show a generic message or an empty state.
error_log($e->getMessage()); error_log($e->getMessage());
$plans = []; $plans = [];
} }
@ -73,12 +39,23 @@
</div> </div>
<?php else: ?> <?php else: ?>
<?php foreach ($plans as $plan): ?> <?php foreach ($plans as $plan): ?>
<div class="col-md-3"> <div class="col-md-4 mb-4">
<div class="plan-card text-center"> <div class="card h-100 shadow-sm">
<h3><?php echo htmlspecialchars($plan['speed']); ?></h3> <div class="card-body d-flex flex-column">
<p class="price">$<?php echo htmlspecialchars(number_format($plan['price'], 2)); ?><span class="period">/mo</span></p> <h5 class="card-title text-center"><?php echo htmlspecialchars($plan['name']); ?></h5>
<p><?php echo htmlspecialchars($plan['description']); ?></p> <div class="text-center my-4">
<a href="signup.php?plan_id=<?php echo $plan['id']; ?>" class="btn btn-primary">Choose Plan</a> <span class="display-4 fw-bold">$<?php echo htmlspecialchars(number_format($plan['price_monthly'], 2)); ?></span>
<span class="text-muted">/mo</span>
</div>
<ul class="list-unstyled mb-4 text-center">
<li class="mb-2"><i class="bi bi-speedometer2 me-2"></i>Up to <?php echo htmlspecialchars($plan['speed_mbps']); ?> Mbps</li>
<li class="mb-2"><i class="bi bi-card-text me-2"></i><?php echo htmlspecialchars($plan['description']); ?></li>
<li class="mb-2"><i class="bi bi-calendar-check me-2"></i><?php echo htmlspecialchars($plan['contract_months']); ?> months contract</li>
</ul>
<div class="mt-auto">
<a href="signup.php?plan_id=<?php echo $plan['id']; ?>" class="btn btn-primary w-100">Sign Up Now</a>
</div>
</div>
</div> </div>
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>
@ -105,16 +82,4 @@
<p>Phone: 1300 864 341</p> <p>Phone: 1300 864 341</p>
</div> </div>
</section> </section>
</main> <?php include 'footer.php'; ?>
<footer class="py-4 bg-dark text-white text-center">
<div class="container">
<p>&copy; 2025 Australia Broadband Internet. All Rights Reserved.</p>
<p><a href="privacy.php" class="text-white">Privacy Policy</a></p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body>
</html>

View File

@ -2,121 +2,115 @@
require_once __DIR__ . '/vendor/autoload.php'; require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/db/config.php'; require_once __DIR__ . '/db/config.php';
// This is your test secret API key. Don't hardcode this in a real app.
\Stripe\Stripe::setApiKey('sk_test_51Hh9Y2L9s5P2Q8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4'); \Stripe\Stripe::setApiKey('sk_test_51Hh9Y2L9s5P2Q8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4sYgY8Z4');
$message = 'An error occurred.'; include 'header.php';
if (isset($_GET['payment_intent']) && isset($_GET['payment_intent_client_secret'])) { $message = '';
$order_details = null;
$error = false;
if (empty($_GET['payment_intent'])) {
$error = true;
$message = "No payment intent provided.";
} else {
$payment_intent_id = $_GET['payment_intent']; $payment_intent_id = $_GET['payment_intent'];
$pdo = db();
try { try {
// 1. Verify Payment with Stripe
$paymentIntent = \Stripe\PaymentIntent::retrieve($payment_intent_id); $paymentIntent = \Stripe\PaymentIntent::retrieve($payment_intent_id);
if ($paymentIntent->status == 'succeeded') { if ($paymentIntent->status == 'succeeded') {
// NOTE: In a real application, you should not trust the amount from the client. // 2. Find Local Order
// You should fetch the plan from your database based on a stored reference to ensure the price is correct. $stmt = $pdo->prepare("SELECT * FROM orders WHERE stripe_payment_intent_id = ?");
// For this example, we'll assume the amount is correct. $stmt->execute([$payment_intent_id]);
$order = $stmt->fetch(PDO::FETCH_OBJ);
$metadata = $paymentIntent->metadata; if ($order) {
$plan_id = $metadata->plan_id ?? null; // 3. Update Order Status (if it's still pending)
$customer_name = $metadata->customer_name ?? null; if ($order->order_status == 'pending') {
$customer_address = $metadata->customer_address ?? null; $update_stmt = $pdo->prepare("UPDATE orders SET order_status = 'completed' WHERE id = ?");
$customer_email = $paymentIntent->receipt_email; $update_stmt->execute([$order->id]);
if (empty($plan_id) || empty($customer_name) || empty($customer_address) || empty($customer_email)) {
// This indicates a problem, as the metadata or email was not set correctly.
// Flag for manual review.
error_log("Missing metadata or email for successful payment {$payment_intent_id}.");
$message = "Your payment was successful, but there was an error processing your order details. Please contact support.";
} else {
$pdo = db();
// Parse name into first and last
$name_parts = explode(' ', $customer_name, 2);
$first_name = $name_parts[0];
$last_name = $name_parts[1] ?? '';
// For address, we'll just put the whole thing in the street_address for now.
// A real app would use a proper address parser or separate fields on the form.
$street_address = $customer_address;
// Generate a temporary password hash. In a real app, you'd send a password reset link.
$temp_password = password_hash(bin2hex(random_bytes(16)), PASSWORD_DEFAULT);
// Check if customer already exists
$stmt = $pdo->prepare("SELECT id FROM customers WHERE email = ?");
$stmt->execute([$customer_email]);
$existing_customer = $stmt->fetch();
if ($existing_customer) {
$customer_id = $existing_customer['id'];
// Optional: Update customer details if they have changed
$update_stmt = $pdo->prepare("UPDATE customers SET first_name = ?, last_name = ?, street_address = ? WHERE id = ?");
$update_stmt->execute([$first_name, $last_name, $street_address, $customer_id]);
} else {
// Create new customer
$stmt = $pdo->prepare(
"INSERT INTO customers (first_name, last_name, email, password_hash, street_address) VALUES (?, ?, ?, ?, ?)"
);
$stmt->execute([$first_name, $last_name, $customer_email, $temp_password, $street_address]);
$customer_id = $pdo->lastInsertId();
} }
// Create the order // 4. Fetch Order Details for Display
$stmt = $pdo->prepare("INSERT INTO orders (customer_id, plan_id, stripe_payment_intent, status) VALUES (?, ?, ?, ?)"); $details_stmt = $pdo->prepare(
$stmt->execute([$customer_id, $plan_id, $payment_intent_id, 'succeeded']); "SELECT o.id as order_id, o.amount, c.name as customer_name, c.email, p.name as plan_name
FROM orders o
JOIN customers c ON o.customer_id = c.id
JOIN plans p ON o.plan_id = p.id
WHERE o.id = ?"
);
$details_stmt->execute([$order->id]);
$order_details = $details_stmt->fetch(PDO::FETCH_OBJ);
$message = 'Payment succeeded! Your order has been placed.'; $message = "Thank you for your order! Your payment was successful.";
// Send confirmation email // 5. Send Confirmation Email (optional, but good practice)
require_once __DIR__ . '/mail/MailService.php'; require_once __DIR__ . '/mail/MailService.php';
$subject = 'Your Australia Broadband Internet Order Confirmation'; $subject = 'Your Australia Broadband Internet Order Confirmation';
$html_body = "<h1>Welcome, " . htmlspecialchars($customer_name) . "!</h1>" $html_body = "<h1>Welcome, " . htmlspecialchars($order_details->customer_name) . "!</h1>"
. "<p>Thank you for your order. Your new internet service is being processed.</p>" . "<p>Thank you for your order. Your new internet service is being processed.</p>"
. "<p><strong>Order Details:</strong></p>" . "<p><strong>Order Details:</strong></p>"
. "<ul>" . "<ul>"
. "<li><strong>Plan ID:</strong> " . htmlspecialchars($plan_id) . "</li>" . "<li><strong>Order ID:</strong> " . htmlspecialchars($order_details->order_id) . "</li>"
. "<li><strong>Payment ID:</strong> " . htmlspecialchars($payment_intent_id) . "</li>" . "<li><strong>Plan:</strong> " . htmlspecialchars($order_details->plan_name) . "</li>"
. "<li><strong>Amount Paid:</strong> $" . htmlspecialchars(number_format($order_details->amount, 2)) . "</li>"
. "</ul>" . "</ul>"
. "<p>You will receive further updates from us shortly.</p>"; . "<p>You will receive further updates from us shortly.</p>";
$text_body = "Welcome, " . $customer_name . "! Thank you for your order. Your new internet service is being processed. Order Details: Plan ID: " . $plan_id . ", Payment ID: " . $payment_intent_id . ". You will receive further updates from us shortly."; MailService::sendMail($order_details->email, $subject, $html_body);
MailService::sendMail($customer_email, $subject, $html_body, $text_body);
}
} else { } else {
$message = "Payment was not successful. Status: {$paymentIntent->status}"; $error = true;
// This is a critical error. Payment succeeded but we can't find the order.
error_log("CRITICAL: Payment succeeded for PI {$payment_intent_id} but no matching order found in DB.");
$message = "Your payment was successful, but we could not find your order. Please contact support immediately.";
} }
} catch (\Stripe\Exception\ApiErrorException $e) { } else {
$message = 'Error retrieving payment intent: ' . $e->getMessage(); $error = true;
} catch (PDOException $e) { $message = "Your payment was not successful. Please try again or contact support.";
// If the DB insert fails, we have a problem. The customer was charged but the order wasn't created. }
// A real app needs robust error handling here, like logging the error and flagging for manual review. } catch (Exception $e) {
error_log("Failed to create order in DB after successful payment {$payment_intent_id}: " . $e->getMessage()); $error = true;
$message = "Your payment was successful, but there was an error creating your order. Please contact support."; error_log("Order confirmation error: " . $e->getMessage());
$message = "An error occurred while processing your order. Please contact support.";
} }
} }
?> ?>
<!DOCTYPE html>
<html lang="en"> <div class="container mt-5">
<head> <div class="row">
<meta charset="UTF-8"> <div class="col-md-8 offset-md-2 text-center">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <?php if ($error): ?>
<title>Order Confirmation</title> <div class="alert alert-danger">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <h1 class="alert-heading">Order Error</h1>
<link rel="stylesheet" href="assets/css/custom.css"> <p><?php echo htmlspecialchars($message); ?></p>
</head> </div>
<body> <?php else: ?>
<div class="container mt-5 text-center"> <div class="alert alert-success">
<div class="card mx-auto" style="max-width: 500px;"> <h1 class="alert-heading">Thank You!</h1>
<p><?php echo htmlspecialchars($message); ?></p>
</div>
<?php if ($order_details): ?>
<div class="card mt-4">
<div class="card-header">
Order Summary
</div>
<div class="card-body"> <div class="card-body">
<h1 class="card-title">Order Confirmation</h1> <p><strong>Order ID:</strong> <?php echo htmlspecialchars($order_details->order_id); ?></p>
<p class="card-text"><?php echo htmlspecialchars($message); ?></p> <p><strong>Customer:</strong> <?php echo htmlspecialchars($order_details->customer_name); ?></p>
<a href="/" class="btn btn-primary">Back to Home</a> <p><strong>Plan:</strong> <?php echo htmlspecialchars($order_details->plan_name); ?></p>
<p><strong>Amount Paid:</strong> $<?php echo htmlspecialchars(number_format($order_details->amount, 2)); ?></p>
</div>
</div>
<?php endif; ?>
<?php endif; ?>
<a href="/" class="btn btn-primary mt-4">Back to Home</a>
</div> </div>
</div> </div>
</div> </div>
</body>
</html> <?php
include 'footer.php';
?>

36
page.php Normal file
View File

@ -0,0 +1,36 @@
<?php
require_once 'db/config.php';
$slug = $_GET['slug'] ?? null;
if (!$slug) {
http_response_code(404);
echo "Page not found.";
exit;
}
$stmt = db()->prepare('SELECT * FROM pages WHERE slug = ?');
$stmt->execute([$slug]);
$page = $stmt->fetch();
if (!$page) {
http_response_code(404);
echo "Page not found.";
exit;
}
include 'header.php';
?>
<div class="container mt-5 pt-5">
<div class="row">
<div class="col-lg-10 mx-auto">
<h1 class="display-4 fw-bold text-center"><?php echo htmlspecialchars($page['title']); ?></h1>
<div class="mt-4 fs-5">
<?php echo nl2br(htmlspecialchars($page['content'])); ?>
</div>
</div>
</div>
</div>
<?php include 'footer.php'; ?>

View File

@ -1,5 +1,6 @@
<?php <?php
require_once __DIR__ . '/db/config.php'; require_once __DIR__ . '/db/config.php';
include 'header.php';
if (!isset($_GET['plan_id'])) { if (!isset($_GET['plan_id'])) {
header('Location: /#plans'); header('Location: /#plans');
@ -7,28 +8,16 @@ if (!isset($_GET['plan_id'])) {
} }
$pdo = db(); $pdo = db();
$stmt = $pdo->prepare("SELECT * FROM plans WHERE id = ? AND is_active = 1"); $stmt = $pdo->prepare("SELECT * FROM plans WHERE id = ?");
$stmt->execute([$_GET['plan_id']]); $stmt->execute([$_GET['plan_id']]);
$plan = $stmt->fetch(); $plan = $stmt->fetch();
if (!$plan) { if (!$plan) {
// Plan not found or not active // Plan not found
header('Location: /#plans'); header('Location: /#plans');
exit; exit;
} }
// Page content for signup
?> ?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sign Up for <?php echo htmlspecialchars($plan['name']); ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<div class="container mt-5"> <div class="container mt-5">
<div class="row"> <div class="row">
<div class="col-md-8 offset-md-2"> <div class="col-md-8 offset-md-2">
@ -44,7 +33,7 @@ if (!$plan) {
</div> </div>
<h4 class="mb-3">Your Details</h4> <h4 class="mb-3">Your Details</h4>
<form id="signup-form"> <form id="signup-form" data-plan-id="<?php echo $plan['id']; ?>">
<!-- User details form --> <!-- User details form -->
<div class="mb-3"> <div class="mb-3">
<label for="name" class="form-label">Full Name</label> <label for="name" class="form-label">Full Name</label>
@ -76,6 +65,13 @@ if (!$plan) {
</div> </div>
<script src="https://js.stripe.com/v3/"></script> <script src="https://js.stripe.com/v3/"></script>
<script>
// Pass plan details to JS
const planDetails = {
id: <?php echo json_encode($plan['id']); ?>,
price: <?php echo json_encode($plan['price_monthly']); ?>
};
</script>
<script src="assets/js/signup.js?v=<?php echo time(); ?>"></script> <script src="assets/js/signup.js?v=<?php echo time(); ?>"></script>
</body>
</html> <?php include 'footer.php'; ?>

107
support.php Normal file
View File

@ -0,0 +1,107 @@
<?php include 'header.php'; ?>
<div class="container mt-5">
<div class="row">
<div class="col-md-8 offset-md-2">
<h1 class="text-center mb-4">AI Support Assistant</h1>
<div class="card">
<div class="card-body" id="chat-window" style="height: 400px; overflow-y: scroll;">
<!-- Chat messages will appear here -->
<div class="d-flex flex-row justify-content-start mb-4">
<div class="p-3 ms-3" style="border-radius: 15px; background-color: #f5f6f7;">
<p class="small mb-0">Hello! How can I help you today? Ask me anything about our plans or services.</p>
</div>
</div>
</div>
<div class="card-footer text-muted d-flex justify-content-start align-items-center p-3">
<input type="text" class="form-control form-control-lg" id="user-input" placeholder="Type your message">
<button class="btn btn-primary ms-3" id="send-btn">Send</button>
</div>
</div>
</div>
</div>
</div>
<script>
const chatWindow = document.getElementById('chat-window');
const userInput = document.getElementById('user-input');
const sendBtn = document.getElementById('send-btn');
const addMessage = (message, sender) => {
const messageDiv = document.createElement('div');
const messageContent = document.createElement('div');
const text = document.createElement('p');
messageDiv.classList.add('d-flex', 'flex-row', 'mb-4');
messageContent.classList.add('p-3');
text.classList.add('small', 'mb-0');
text.innerText = message;
messageContent.appendChild(text);
if (sender === 'user') {
messageDiv.classList.add('justify-content-end');
messageContent.classList.add('me-3', 'text-white');
messageContent.style.borderRadius = '15px';
messageContent.style.backgroundColor = '#0d6efd'; // Bootstrap Primary Blue
} else {
messageDiv.classList.add('justify-content-start');
messageContent.classList.add('ms-3');
messageContent.style.borderRadius = '15px';
messageContent.style.backgroundColor = '#f5f6f7'; // Light grey
}
messageDiv.appendChild(messageContent);
chatWindow.appendChild(messageDiv);
chatWindow.scrollTop = chatWindow.scrollHeight; // Auto-scroll to the latest message
};
const handleSend = async () => {
const question = userInput.value.trim();
if (!question) return;
addMessage(question, 'user');
userInput.value = '';
userInput.disabled = true;
sendBtn.disabled = true;
try {
const response = await fetch('/api/ai_support.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ question })
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
if (data.reply) {
addMessage(data.reply, 'ai');
} else if (data.error) {
addMessage(data.error, 'ai');
}
} catch (error) {
console.error('Error:', error);
addMessage('Sorry, something went wrong. Please try again later.', 'ai');
} finally {
userInput.disabled = false;
sendBtn.disabled = false;
userInput.focus();
}
};
sendBtn.addEventListener('click', handleSend);
userInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
handleSend();
}
});
</script>
<?php include 'footer.php'; ?>