Compare commits
No commits in common. "ai-dev" and "master" have entirely different histories.
@ -1,65 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../includes/bootstrap.php';
|
||||
require_once __DIR__ . '/../includes/layout.php';
|
||||
|
||||
$orderId = isset($_GET['id']) ? (int) $_GET['id'] : 0;
|
||||
$order = null;
|
||||
$items = [];
|
||||
|
||||
if ($orderId > 0) {
|
||||
$stmt = db()->prepare('SELECT * FROM orders WHERE id = :id');
|
||||
$stmt->execute([':id' => $orderId]);
|
||||
$order = $stmt->fetch();
|
||||
if ($order) {
|
||||
$items = get_order_items((int) $order['id']);
|
||||
}
|
||||
}
|
||||
|
||||
render_header('Order Detail - E-SO9', 'admin');
|
||||
?>
|
||||
<main class="container my-5">
|
||||
<a href="/admin/orders.php" class="link-secondary">Back to orders</a>
|
||||
<h1 class="h3 mb-3">Order detail</h1>
|
||||
|
||||
<?php if (!$order): ?>
|
||||
<div class="alert alert-warning">Order not found.</div>
|
||||
<?php else: ?>
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-5">
|
||||
<div class="stat-card">
|
||||
<div class="text-muted small">Order number</div>
|
||||
<div class="h5 mb-2"><?= e($order['order_number']) ?></div>
|
||||
<div class="text-muted small">Customer</div>
|
||||
<div class="fw-semibold"><?= e($order['customer_name']) ?></div>
|
||||
<div class="text-muted small"><?= e($order['customer_email']) ?></div>
|
||||
<div class="text-muted small"><?= e($order['customer_address']) ?></div>
|
||||
<div class="text-muted small mt-3">Status</div>
|
||||
<div class="fw-semibold"><?= e($order['status']) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-7">
|
||||
<div class="stat-card">
|
||||
<div class="text-muted small mb-2">Items</div>
|
||||
<?php if (!$items): ?>
|
||||
<div class="text-muted">No items recorded.</div>
|
||||
<?php else: ?>
|
||||
<ul class="list-unstyled">
|
||||
<?php foreach ($items as $item): ?>
|
||||
<li class="d-flex justify-content-between mb-2">
|
||||
<span><?= e($item['name']) ?> x <?= e((string) $item['quantity']) ?></span>
|
||||
<span><?= e(format_price((float) $item['price'])) ?></span>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
<div class="d-flex justify-content-between border-top pt-2">
|
||||
<span class="fw-semibold">Total</span>
|
||||
<span class="fw-semibold"><?= e(format_price((float) $order['total_price'])) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
<?php render_footer(); ?>
|
||||
109
admin/orders.php
109
admin/orders.php
@ -1,109 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../includes/bootstrap.php';
|
||||
require_once __DIR__ . '/../includes/layout.php';
|
||||
|
||||
$statuses = ['Processing', 'Shipped', 'Delivered'];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$orderId = isset($_POST['order_id']) ? (int) $_POST['order_id'] : 0;
|
||||
$status = isset($_POST['status']) ? (string) $_POST['status'] : '';
|
||||
if ($orderId > 0 && in_array($status, $statuses, true)) {
|
||||
update_order_status($orderId, $status);
|
||||
flash_set('success', 'Order status updated.');
|
||||
}
|
||||
header('Location: /admin/orders.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$orders = get_orders();
|
||||
|
||||
render_header('Admin Orders - E-SO9', 'admin');
|
||||
?>
|
||||
<main class="container my-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h1 class="h3 mb-1">Admin orders</h1>
|
||||
<p class="text-muted mb-0">Update order statuses and monitor totals.</p>
|
||||
</div>
|
||||
<div class="admin-note">Demo admin area. No authentication yet.</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="stat-card">
|
||||
<div class="text-muted small">Total orders</div>
|
||||
<div class="h4 mb-0"><?= e((string) count($orders)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="stat-card">
|
||||
<div class="text-muted small">Total revenue</div>
|
||||
<div class="h4 mb-0">
|
||||
<?php
|
||||
$revenue = 0.0;
|
||||
foreach ($orders as $order) {
|
||||
$revenue += (float) $order['total_price'];
|
||||
}
|
||||
?>
|
||||
<?= e(format_price($revenue)) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="stat-card">
|
||||
<div class="text-muted small">Latest status</div>
|
||||
<div class="h4 mb-0"><?= $orders ? e($orders[0]['status']) : 'N/A' ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!$orders): ?>
|
||||
<div class="alert alert-light border">No orders yet. Place a demo order to see entries.</div>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Order</th>
|
||||
<th>Customer</th>
|
||||
<th>Total</th>
|
||||
<th>Status</th>
|
||||
<th>Update</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($orders as $order): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-semibold"><?= e($order['order_number']) ?></div>
|
||||
<div class="text-muted small"><?= e($order['created_at']) ?></div>
|
||||
</td>
|
||||
<td>
|
||||
<div><?= e($order['customer_name']) ?></div>
|
||||
<div class="text-muted small"><?= e($order['customer_email']) ?></div>
|
||||
</td>
|
||||
<td><?= e(format_price((float) $order['total_price'])) ?></td>
|
||||
<td>
|
||||
<span class="badge text-bg-dark"><?= e($order['status']) ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<form method="post" action="/admin/orders.php" class="d-flex gap-2">
|
||||
<input type="hidden" name="order_id" value="<?= e((string) $order['id']) ?>" />
|
||||
<select name="status" class="form-select form-select-sm">
|
||||
<?php foreach ($statuses as $status): ?>
|
||||
<option value="<?= e($status) ?>" <?= $order['status'] === $status ? 'selected' : '' ?>><?= e($status) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<button class="btn btn-outline-secondary btn-sm" type="submit">Save</button>
|
||||
<a class="btn btn-link" href="/admin/order.php?id=<?= e((string) $order['id']) ?>">View</a>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
<?php render_footer(); ?>
|
||||
@ -1,90 +0,0 @@
|
||||
:root {
|
||||
--bg: #f6f7f9;
|
||||
--surface: #ffffff;
|
||||
--surface-muted: #f1f3f6;
|
||||
--border: #e2e5ea;
|
||||
--text: #111827;
|
||||
--muted: #6b7280;
|
||||
--accent: #0f766e;
|
||||
--accent-strong: #0b5f59;
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 10px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.navbar {
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.hero {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 2.5rem 2rem;
|
||||
}
|
||||
|
||||
.badge-soft {
|
||||
background: var(--surface-muted);
|
||||
color: var(--muted);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.product-card {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--surface);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.product-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.product-card img {
|
||||
border-bottom: 1px solid var(--border);
|
||||
border-top-left-radius: var(--radius-md);
|
||||
border-top-right-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.btn-primary:hover,
|
||||
.btn-primary:focus {
|
||||
background: var(--accent-strong);
|
||||
border-color: var(--accent-strong);
|
||||
}
|
||||
|
||||
.form-control,
|
||||
.form-select {
|
||||
border-radius: var(--radius-sm);
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.table {
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
.admin-note {
|
||||
background: var(--surface-muted);
|
||||
border: 1px dashed var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const toastEls = document.querySelectorAll('[data-toast="auto"]');
|
||||
toastEls.forEach((el) => {
|
||||
if (window.bootstrap && bootstrap.Toast) {
|
||||
const toast = new bootstrap.Toast(el, { delay: 3500 });
|
||||
toast.show();
|
||||
}
|
||||
});
|
||||
});
|
||||
100
cart.php
100
cart.php
@ -1,100 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/includes/bootstrap.php';
|
||||
require_once __DIR__ . '/includes/layout.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$productId = isset($_POST['product_id']) ? (int) $_POST['product_id'] : 0;
|
||||
$quantity = isset($_POST['quantity']) ? (int) $_POST['quantity'] : 1;
|
||||
$action = isset($_POST['action']) ? (string) $_POST['action'] : 'add';
|
||||
|
||||
if ($productId > 0) {
|
||||
if ($action === 'update') {
|
||||
cart_update($productId, $quantity);
|
||||
flash_set('success', 'Cart updated.');
|
||||
} else {
|
||||
cart_add($productId, $quantity);
|
||||
flash_set('success', 'Item added to cart.');
|
||||
}
|
||||
}
|
||||
header('Location: /cart.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$items = cart_items();
|
||||
$total = cart_total();
|
||||
|
||||
render_header('Cart - E-SO9', 'cart');
|
||||
?>
|
||||
<main class="container my-5">
|
||||
<h1 class="h3 mb-3">Your cart</h1>
|
||||
|
||||
<?php if (!$items): ?>
|
||||
<div class="alert alert-light border">Your cart is empty. Browse products to get started.</div>
|
||||
<a href="/shop.php" class="btn btn-outline-secondary">Go to shop</a>
|
||||
<?php else: ?>
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-8">
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<th>Price</th>
|
||||
<th>Qty</th>
|
||||
<th>Total</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($items as $item): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-semibold"><?= e($item['product']['name']) ?></div>
|
||||
<div class="text-muted small"><?= e($item['product']['category_name'] ?? 'General') ?></div>
|
||||
</td>
|
||||
<td><?= e(format_price((float) $item['product']['price'])) ?></td>
|
||||
<td>
|
||||
<form method="post" action="/cart.php" class="d-flex gap-2">
|
||||
<input type="hidden" name="action" value="update" />
|
||||
<input type="hidden" name="product_id" value="<?= e((string) $item['product']['id']) ?>" />
|
||||
<input type="number" name="quantity" class="form-control form-control-sm" min="1" value="<?= e((string) $item['quantity']) ?>" />
|
||||
<button class="btn btn-outline-secondary btn-sm" type="submit">Update</button>
|
||||
</form>
|
||||
</td>
|
||||
<td><?= e(format_price((float) $item['line_total'])) ?></td>
|
||||
<td>
|
||||
<form method="post" action="/cart.php">
|
||||
<input type="hidden" name="action" value="update" />
|
||||
<input type="hidden" name="product_id" value="<?= e((string) $item['product']['id']) ?>" />
|
||||
<input type="hidden" name="quantity" value="0" />
|
||||
<button class="btn btn-link text-danger" type="submit">Remove</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="stat-card">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-muted">Subtotal</span>
|
||||
<span class="fw-semibold"><?= e(format_price((float) $total)) ?></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-muted">Shipping</span>
|
||||
<span class="fw-semibold">$0.00</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between border-top pt-2">
|
||||
<span class="fw-semibold">Total</span>
|
||||
<span class="fw-semibold"><?= e(format_price((float) $total)) ?></span>
|
||||
</div>
|
||||
<a href="/checkout.php" class="btn btn-primary w-100 mt-3">Proceed to checkout</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
<?php render_footer(); ?>
|
||||
97
checkout.php
97
checkout.php
@ -1,97 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/includes/bootstrap.php';
|
||||
require_once __DIR__ . '/includes/layout.php';
|
||||
|
||||
$items = cart_items();
|
||||
$errors = [];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$name = trim((string) ($_POST['name'] ?? ''));
|
||||
$email = trim((string) ($_POST['email'] ?? ''));
|
||||
$address = trim((string) ($_POST['address'] ?? ''));
|
||||
|
||||
if ($name === '') {
|
||||
$errors[] = 'Name is required.';
|
||||
}
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$errors[] = 'Valid email is required.';
|
||||
}
|
||||
if ($address === '') {
|
||||
$errors[] = 'Address is required.';
|
||||
}
|
||||
if (!$items) {
|
||||
$errors[] = 'Your cart is empty.';
|
||||
}
|
||||
|
||||
if (!$errors) {
|
||||
$orderNumber = create_order([
|
||||
'name' => $name,
|
||||
'email' => $email,
|
||||
'address' => $address
|
||||
], $items);
|
||||
|
||||
if ($orderNumber) {
|
||||
cart_clear();
|
||||
flash_set('success', 'Order placed successfully.');
|
||||
header('Location: /order.php?order=' . urlencode($orderNumber));
|
||||
exit;
|
||||
}
|
||||
$errors[] = 'Unable to place the order. Try again.';
|
||||
}
|
||||
}
|
||||
|
||||
render_header('Checkout - E-SO9', 'cart');
|
||||
?>
|
||||
<main class="container my-5">
|
||||
<h1 class="h3 mb-3">Checkout</h1>
|
||||
|
||||
<?php foreach ($errors as $error): ?>
|
||||
<div class="alert alert-warning"><?= e($error) ?></div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-7">
|
||||
<div class="stat-card">
|
||||
<h2 class="h6 mb-3">Customer details</h2>
|
||||
<form method="post" action="/checkout.php">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Full name</label>
|
||||
<input type="text" name="name" class="form-control" value="<?= e((string) ($_POST['name'] ?? '')) ?>" required />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Email</label>
|
||||
<input type="email" name="email" class="form-control" value="<?= e((string) ($_POST['email'] ?? '')) ?>" required />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Shipping address</label>
|
||||
<textarea name="address" class="form-control" rows="3" required><?= e((string) ($_POST['address'] ?? '')) ?></textarea>
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit">Place demo order</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<div class="stat-card">
|
||||
<h2 class="h6 mb-3">Order summary</h2>
|
||||
<?php if (!$items): ?>
|
||||
<p class="text-muted">Add items to cart to continue.</p>
|
||||
<?php else: ?>
|
||||
<ul class="list-unstyled mb-3">
|
||||
<?php foreach ($items as $item): ?>
|
||||
<li class="d-flex justify-content-between mb-2">
|
||||
<span><?= e($item['product']['name']) ?> x <?= e((string) $item['quantity']) ?></span>
|
||||
<span><?= e(format_price((float) $item['line_total'])) ?></span>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<div class="d-flex justify-content-between border-top pt-2">
|
||||
<span class="fw-semibold">Total</span>
|
||||
<span class="fw-semibold"><?= e(format_price((float) cart_total())) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<?php render_footer(); ?>
|
||||
@ -1,26 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/store.php';
|
||||
|
||||
session_start();
|
||||
|
||||
init_store();
|
||||
|
||||
function e(string $value): string {
|
||||
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
function flash_set(string $type, string $message): void {
|
||||
$_SESSION['flash'] = ['type' => $type, 'message' => $message];
|
||||
}
|
||||
|
||||
function flash_get(): ?array {
|
||||
if (empty($_SESSION['flash'])) {
|
||||
return null;
|
||||
}
|
||||
$flash = $_SESSION['flash'];
|
||||
unset($_SESSION['flash']);
|
||||
return $flash;
|
||||
}
|
||||
@ -1,74 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
function render_header(string $title, string $active = ''): void {
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
$cartCount = cart_count();
|
||||
$isActive = fn(string $key) => $active === $key ? 'active' : '';
|
||||
echo "<!doctype html>\n";
|
||||
echo "<html lang=\"en\">\n";
|
||||
echo "<head>\n";
|
||||
echo " <meta charset=\"utf-8\" />\n";
|
||||
echo " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n";
|
||||
echo " <title>" . e($title) . "</title>\n";
|
||||
if ($projectDescription) {
|
||||
echo " <meta name=\"description\" content=\"" . e($projectDescription) . "\" />\n";
|
||||
echo " <meta property=\"og:description\" content=\"" . e($projectDescription) . "\" />\n";
|
||||
echo " <meta property=\"twitter:description\" content=\"" . e($projectDescription) . "\" />\n";
|
||||
}
|
||||
if ($projectImageUrl) {
|
||||
echo " <meta property=\"og:image\" content=\"" . e($projectImageUrl) . "\" />\n";
|
||||
echo " <meta property=\"twitter:image\" content=\"" . e($projectImageUrl) . "\" />\n";
|
||||
}
|
||||
echo " <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n";
|
||||
echo " <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n";
|
||||
echo " <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap\" rel=\"stylesheet\">\n";
|
||||
echo " <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n";
|
||||
echo " <link href=\"/assets/css/custom.css?v=" . time() . "\" rel=\"stylesheet\">\n";
|
||||
echo "</head>\n";
|
||||
echo "<body>\n";
|
||||
echo "<nav class=\"navbar navbar-expand-lg sticky-top border-bottom bg-white\">\n";
|
||||
echo " <div class=\"container\">\n";
|
||||
echo " <a class=\"navbar-brand fw-semibold\" href=\"/index.php\">E-SO9</a>\n";
|
||||
echo " <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#siteNav\">\n";
|
||||
echo " <span class=\"navbar-toggler-icon\"></span>\n";
|
||||
echo " </button>\n";
|
||||
echo " <div class=\"collapse navbar-collapse\" id=\"siteNav\">\n";
|
||||
echo " <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n";
|
||||
echo " <li class=\"nav-item\"><a class=\"nav-link " . $isActive('home') . "\" href=\"/index.php\">Home</a></li>\n";
|
||||
echo " <li class=\"nav-item\"><a class=\"nav-link " . $isActive('shop') . "\" href=\"/shop.php\">Shop</a></li>\n";
|
||||
echo " <li class=\"nav-item\"><a class=\"nav-link " . $isActive('track') . "\" href=\"/track.php\">Track Order</a></li>\n";
|
||||
echo " <li class=\"nav-item\"><a class=\"nav-link " . $isActive('admin') . "\" href=\"/admin/orders.php\">Admin</a></li>\n";
|
||||
echo " </ul>\n";
|
||||
echo " <a class=\"btn btn-outline-secondary btn-sm\" href=\"/cart.php\">Cart <span class=\"badge text-bg-dark ms-1\">" . $cartCount . "</span></a>\n";
|
||||
echo " </div>\n";
|
||||
echo " </div>\n";
|
||||
echo "</nav>\n";
|
||||
}
|
||||
|
||||
function render_footer(): void {
|
||||
$flash = flash_get();
|
||||
echo "<footer class=\"border-top py-4 mt-5\">\n";
|
||||
echo " <div class=\"container d-flex flex-column flex-md-row justify-content-between gap-2\">\n";
|
||||
echo " <div class=\"text-muted small\">E-SO9 demo storefront and admin panel.</div>\n";
|
||||
echo " <div class=\"text-muted small\">Payments run in demo mode.</div>\n";
|
||||
echo " </div>\n";
|
||||
echo "</footer>\n";
|
||||
echo "<script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js\"></script>\n";
|
||||
echo "<script src=\"/assets/js/main.js?v=" . time() . "\"></script>\n";
|
||||
|
||||
if ($flash) {
|
||||
$type = $flash['type'] === 'success' ? 'success' : 'warning';
|
||||
echo "<div class=\"toast-container position-fixed bottom-0 end-0 p-3\">\n";
|
||||
echo " <div class=\"toast align-items-center text-bg-" . $type . " border-0\" role=\"alert\" data-toast=\"auto\">\n";
|
||||
echo " <div class=\"d-flex\">\n";
|
||||
echo " <div class=\"toast-body\">" . e($flash['message']) . "</div>\n";
|
||||
echo " <button type=\"button\" class=\"btn-close btn-close-white me-2 m-auto\" data-bs-dismiss=\"toast\"></button>\n";
|
||||
echo " </div>\n";
|
||||
echo " </div>\n";
|
||||
echo "</div>\n";
|
||||
}
|
||||
|
||||
echo "</body>\n</html>\n";
|
||||
}
|
||||
@ -1,349 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
function init_store(): void {
|
||||
ensure_schema();
|
||||
seed_data();
|
||||
}
|
||||
|
||||
function ensure_schema(): void {
|
||||
$pdo = db();
|
||||
$pdo->exec(
|
||||
'CREATE TABLE IF NOT EXISTS categories (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(120) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'
|
||||
);
|
||||
|
||||
$pdo->exec(
|
||||
'CREATE TABLE IF NOT EXISTS products (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(160) NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
price DECIMAL(10,2) NOT NULL,
|
||||
stock INT NOT NULL DEFAULT 0,
|
||||
rating DECIMAL(3,2) NOT NULL DEFAULT 0.00,
|
||||
category_id INT NULL,
|
||||
image_url VARCHAR(255) NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX (category_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'
|
||||
);
|
||||
|
||||
$pdo->exec(
|
||||
'CREATE TABLE IF NOT EXISTS orders (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
order_number VARCHAR(20) NOT NULL,
|
||||
customer_name VARCHAR(120) NOT NULL,
|
||||
customer_email VARCHAR(160) NOT NULL,
|
||||
customer_address VARCHAR(255) NOT NULL,
|
||||
status VARCHAR(30) NOT NULL,
|
||||
total_price DECIMAL(10,2) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY (order_number)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'
|
||||
);
|
||||
|
||||
$pdo->exec(
|
||||
'CREATE TABLE IF NOT EXISTS order_items (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
order_id INT NOT NULL,
|
||||
product_id INT NOT NULL,
|
||||
quantity INT NOT NULL,
|
||||
price DECIMAL(10,2) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX (order_id),
|
||||
INDEX (product_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'
|
||||
);
|
||||
}
|
||||
|
||||
function seed_data(): void {
|
||||
$pdo = db();
|
||||
$count = (int) $pdo->query('SELECT COUNT(*) FROM products')->fetchColumn();
|
||||
if ($count > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$categories = [
|
||||
'Apparel',
|
||||
'Electronics',
|
||||
'Moroccan Goods'
|
||||
];
|
||||
|
||||
$categoryStmt = $pdo->prepare('INSERT INTO categories (name) VALUES (:name)');
|
||||
$categoryIds = [];
|
||||
foreach ($categories as $name) {
|
||||
$categoryStmt->execute([':name' => $name]);
|
||||
$categoryIds[$name] = (int) $pdo->lastInsertId();
|
||||
}
|
||||
|
||||
$products = [
|
||||
[
|
||||
'name' => 'Atlas Hoodie',
|
||||
'description' => 'Midweight cotton hoodie with brushed interior and clean stitch detailing.',
|
||||
'price' => 79.00,
|
||||
'stock' => 24,
|
||||
'rating' => 4.6,
|
||||
'category' => 'Apparel'
|
||||
],
|
||||
[
|
||||
'name' => 'Casablanca Sneakers',
|
||||
'description' => 'Everyday sneaker with cushioned sole and minimalist profile.',
|
||||
'price' => 98.00,
|
||||
'stock' => 18,
|
||||
'rating' => 4.4,
|
||||
'category' => 'Apparel'
|
||||
],
|
||||
[
|
||||
'name' => 'Rabat Smartwatch',
|
||||
'description' => 'Slim fitness watch with sleep tracking and a 7-day battery.',
|
||||
'price' => 149.00,
|
||||
'stock' => 12,
|
||||
'rating' => 4.2,
|
||||
'category' => 'Electronics'
|
||||
],
|
||||
[
|
||||
'name' => 'Sahara Bluetooth Speaker',
|
||||
'description' => 'Portable speaker with clean bass and long-lasting battery.',
|
||||
'price' => 129.00,
|
||||
'stock' => 10,
|
||||
'rating' => 4.3,
|
||||
'category' => 'Electronics'
|
||||
],
|
||||
[
|
||||
'name' => 'Fez Copper Lamp',
|
||||
'description' => 'Hand-finished lamp with soft perforated light pattern.',
|
||||
'price' => 119.00,
|
||||
'stock' => 7,
|
||||
'rating' => 4.8,
|
||||
'category' => 'Moroccan Goods'
|
||||
],
|
||||
[
|
||||
'name' => 'Agadir Ceramic Tagine',
|
||||
'description' => 'Glazed ceramic tagine for slow cooking and elegant serving.',
|
||||
'price' => 84.00,
|
||||
'stock' => 9,
|
||||
'rating' => 4.7,
|
||||
'category' => 'Moroccan Goods'
|
||||
]
|
||||
];
|
||||
|
||||
$productStmt = $pdo->prepare(
|
||||
'INSERT INTO products (name, description, price, stock, rating, category_id) VALUES (:name, :description, :price, :stock, :rating, :category_id)'
|
||||
);
|
||||
|
||||
foreach ($products as $product) {
|
||||
$productStmt->execute([
|
||||
':name' => $product['name'],
|
||||
':description' => $product['description'],
|
||||
':price' => $product['price'],
|
||||
':stock' => $product['stock'],
|
||||
':rating' => $product['rating'],
|
||||
':category_id' => $categoryIds[$product['category']] ?? null
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
function get_categories(): array {
|
||||
$stmt = db()->query('SELECT id, name FROM categories ORDER BY name ASC');
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
function get_products(?string $search = null, ?int $categoryId = null, ?string $sort = null): array {
|
||||
$sql = 'SELECT p.*, c.name AS category_name FROM products p LEFT JOIN categories c ON c.id = p.category_id WHERE 1=1';
|
||||
$params = [];
|
||||
|
||||
if ($search) {
|
||||
$sql .= ' AND (p.name LIKE :search OR p.description LIKE :search)';
|
||||
$params[':search'] = '%' . $search . '%';
|
||||
}
|
||||
|
||||
if ($categoryId) {
|
||||
$sql .= ' AND p.category_id = :category_id';
|
||||
$params[':category_id'] = $categoryId;
|
||||
}
|
||||
|
||||
switch ($sort) {
|
||||
case 'price_asc':
|
||||
$sql .= ' ORDER BY p.price ASC';
|
||||
break;
|
||||
case 'price_desc':
|
||||
$sql .= ' ORDER BY p.price DESC';
|
||||
break;
|
||||
default:
|
||||
$sql .= ' ORDER BY p.created_at DESC';
|
||||
break;
|
||||
}
|
||||
|
||||
$stmt = db()->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
function get_product(int $id): ?array {
|
||||
$stmt = db()->prepare('SELECT p.*, c.name AS category_name FROM products p LEFT JOIN categories c ON c.id = p.category_id WHERE p.id = :id');
|
||||
$stmt->execute([':id' => $id]);
|
||||
$product = $stmt->fetch();
|
||||
return $product ?: null;
|
||||
}
|
||||
|
||||
function format_price(float $price): string {
|
||||
return '$' . number_format($price, 2);
|
||||
}
|
||||
|
||||
function product_image_data(string $label): string {
|
||||
$clean = preg_replace('/[^A-Za-z0-9]/', '', $label);
|
||||
$text = strtoupper(substr($clean, 0, 2));
|
||||
if ($text === '') {
|
||||
$text = 'ES';
|
||||
}
|
||||
$svg = "<svg xmlns='http://www.w3.org/2000/svg' width='600' height='400' viewBox='0 0 600 400'><rect width='600' height='400' fill='#f1f3f6'/><text x='50%' y='50%' dominant-baseline='middle' text-anchor='middle' font-family='Inter, Arial, sans-serif' font-size='96' fill='#111827'>" . $text . "</text></svg>";
|
||||
return 'data:image/svg+xml;utf8,' . rawurlencode($svg);
|
||||
}
|
||||
|
||||
function cart_count(): int {
|
||||
$cart = $_SESSION['cart'] ?? [];
|
||||
return array_sum($cart);
|
||||
}
|
||||
|
||||
function cart_items(): array {
|
||||
$cart = $_SESSION['cart'] ?? [];
|
||||
if (!$cart) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$ids = array_keys($cart);
|
||||
$placeholders = implode(',', array_fill(0, count($ids), '?'));
|
||||
$stmt = db()->prepare(
|
||||
"SELECT p.*, c.name AS category_name FROM products p LEFT JOIN categories c ON c.id = p.category_id WHERE p.id IN ($placeholders)"
|
||||
);
|
||||
$stmt->execute($ids);
|
||||
$products = $stmt->fetchAll();
|
||||
$indexed = [];
|
||||
foreach ($products as $product) {
|
||||
$indexed[(int) $product['id']] = $product;
|
||||
}
|
||||
|
||||
$items = [];
|
||||
foreach ($cart as $productId => $qty) {
|
||||
$product = $indexed[(int) $productId] ?? null;
|
||||
if (!$product) {
|
||||
continue;
|
||||
}
|
||||
$lineTotal = ((float) $product['price']) * $qty;
|
||||
$items[] = [
|
||||
'product' => $product,
|
||||
'quantity' => $qty,
|
||||
'line_total' => $lineTotal
|
||||
];
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
function cart_total(): float {
|
||||
$total = 0.0;
|
||||
foreach (cart_items() as $item) {
|
||||
$total += $item['line_total'];
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
|
||||
function cart_add(int $productId, int $quantity): void {
|
||||
if ($quantity < 1) {
|
||||
return;
|
||||
}
|
||||
$cart = $_SESSION['cart'] ?? [];
|
||||
$cart[$productId] = ($cart[$productId] ?? 0) + $quantity;
|
||||
$_SESSION['cart'] = $cart;
|
||||
}
|
||||
|
||||
function cart_update(int $productId, int $quantity): void {
|
||||
$cart = $_SESSION['cart'] ?? [];
|
||||
if ($quantity < 1) {
|
||||
unset($cart[$productId]);
|
||||
} else {
|
||||
$cart[$productId] = $quantity;
|
||||
}
|
||||
$_SESSION['cart'] = $cart;
|
||||
}
|
||||
|
||||
function cart_clear(): void {
|
||||
unset($_SESSION['cart']);
|
||||
}
|
||||
|
||||
function create_order(array $customer, array $items): ?string {
|
||||
if (!$items) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
$orderNumber = 'ES9-' . strtoupper(bin2hex(random_bytes(3)));
|
||||
$total = 0.0;
|
||||
foreach ($items as $item) {
|
||||
$total += $item['line_total'];
|
||||
}
|
||||
|
||||
$orderStmt = $pdo->prepare(
|
||||
'INSERT INTO orders (order_number, customer_name, customer_email, customer_address, status, total_price) VALUES (:order_number, :customer_name, :customer_email, :customer_address, :status, :total_price)'
|
||||
);
|
||||
$orderStmt->execute([
|
||||
':order_number' => $orderNumber,
|
||||
':customer_name' => $customer['name'],
|
||||
':customer_email' => $customer['email'],
|
||||
':customer_address' => $customer['address'],
|
||||
':status' => 'Processing',
|
||||
':total_price' => $total
|
||||
]);
|
||||
|
||||
$orderId = (int) $pdo->lastInsertId();
|
||||
|
||||
$itemStmt = $pdo->prepare(
|
||||
'INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (:order_id, :product_id, :quantity, :price)'
|
||||
);
|
||||
|
||||
foreach ($items as $item) {
|
||||
$itemStmt->execute([
|
||||
':order_id' => $orderId,
|
||||
':product_id' => $item['product']['id'],
|
||||
':quantity' => $item['quantity'],
|
||||
':price' => $item['product']['price']
|
||||
]);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
return $orderNumber;
|
||||
} catch (Throwable $e) {
|
||||
$pdo->rollBack();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function get_order_by_number(string $orderNumber): ?array {
|
||||
$stmt = db()->prepare('SELECT * FROM orders WHERE order_number = :order_number');
|
||||
$stmt->execute([':order_number' => $orderNumber]);
|
||||
$order = $stmt->fetch();
|
||||
return $order ?: null;
|
||||
}
|
||||
|
||||
function get_order_items(int $orderId): array {
|
||||
$stmt = db()->prepare(
|
||||
'SELECT oi.*, p.name, p.description FROM order_items oi LEFT JOIN products p ON p.id = oi.product_id WHERE oi.order_id = :order_id'
|
||||
);
|
||||
$stmt->execute([':order_id' => $orderId]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
function get_orders(): array {
|
||||
$stmt = db()->query('SELECT * FROM orders ORDER BY created_at DESC');
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
function update_order_status(int $orderId, string $status): void {
|
||||
$stmt = db()->prepare('UPDATE orders SET status = :status WHERE id = :id');
|
||||
$stmt->execute([':status' => $status, ':id' => $orderId]);
|
||||
}
|
||||
225
index.php
225
index.php
@ -1,87 +1,150 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/includes/bootstrap.php';
|
||||
require_once __DIR__ . '/includes/layout.php';
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
|
||||
$categories = get_categories();
|
||||
$featured = get_products(null, null, 'price_desc');
|
||||
$featured = array_slice($featured, 0, 3);
|
||||
|
||||
render_header('E-SO9 Storefront', 'home');
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
?>
|
||||
<main class="container my-5">
|
||||
<section class="hero mb-5">
|
||||
<div class="row align-items-center g-4">
|
||||
<div class="col-lg-7">
|
||||
<span class="badge badge-soft mb-3">Professional ecommerce MVP</span>
|
||||
<h1 class="display-6 fw-semibold mb-3">E-SO9 curated storefront with fast checkout and admin tracking.</h1>
|
||||
<p class="text-muted mb-4">Browse products, add to cart, place a demo order, and track status. Admins can review orders and update fulfillment.</p>
|
||||
<div class="d-flex gap-3">
|
||||
<a href="/shop.php" class="btn btn-primary">Start shopping</a>
|
||||
<a href="/track.php" class="btn btn-outline-secondary">Track an order</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<div class="stat-card">
|
||||
<div class="row g-3">
|
||||
<div class="col-6">
|
||||
<div class="text-muted small">Categories</div>
|
||||
<div class="h4 mb-0"><?= e((string) count($categories)) ?></div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="text-muted small">Active products</div>
|
||||
<div class="h4 mb-0">6</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="text-muted small">Checkout status</div>
|
||||
<div class="fw-semibold">Demo payments enabled</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</title>
|
||||
<?php
|
||||
// Read project preview data from environment
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<?php if ($projectDescription): ?>
|
||||
<!-- Meta description -->
|
||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||
<!-- Open Graph meta tags -->
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<!-- Twitter meta tags -->
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<!-- Open Graph image -->
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<!-- Twitter image -->
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<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=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
@keyframes bg-pan {
|
||||
0% { background-position: 0% 0%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.loader {
|
||||
margin: 1.25rem auto 1.25rem;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.hint {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
code {
|
||||
background: rgba(0,0,0,0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
</div>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mb-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2 class="h5 mb-0">Browse by category</h2>
|
||||
<a href="/shop.php" class="link-secondary">See all products</a>
|
||||
</div>
|
||||
<div class="row g-3">
|
||||
<?php foreach ($categories as $category): ?>
|
||||
<div class="col-md-4">
|
||||
<div class="stat-card h-100">
|
||||
<div class="text-muted small">Category</div>
|
||||
<div class="h5 mb-2"><?= e($category['name']) ?></div>
|
||||
<a href="/shop.php?category=<?= e((string) $category['id']) ?>" class="btn btn-outline-secondary btn-sm">Explore</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2 class="h5 mb-0">Featured products</h2>
|
||||
<span class="text-muted small">Hand-picked for the launch</span>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<?php foreach ($featured as $product): ?>
|
||||
<div class="col-md-4">
|
||||
<div class="product-card h-100">
|
||||
<img src="<?= e($product['image_url'] ?: product_image_data($product['name'])) ?>" class="img-fluid" alt="<?= e($product['name']) ?>" width="600" height="400" />
|
||||
<div class="p-3">
|
||||
<div class="text-muted small mb-1"><?= e($product['category_name'] ?? 'General') ?></div>
|
||||
<h3 class="h6 mb-2"><?= e($product['name']) ?></h3>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="fw-semibold"><?= e(format_price((float) $product['price'])) ?></span>
|
||||
<a href="/product.php?id=<?= e((string) $product['id']) ?>" class="btn btn-outline-secondary btn-sm">View</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<?php render_footer(); ?>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
46
order.php
46
order.php
@ -1,46 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/includes/bootstrap.php';
|
||||
require_once __DIR__ . '/includes/layout.php';
|
||||
|
||||
$orderNumber = isset($_GET['order']) ? (string) $_GET['order'] : '';
|
||||
$order = $orderNumber ? get_order_by_number($orderNumber) : null;
|
||||
|
||||
render_header('Order - E-SO9', 'track');
|
||||
?>
|
||||
<main class="container my-5">
|
||||
<h1 class="h3 mb-3">Order details</h1>
|
||||
|
||||
<?php if (!$order): ?>
|
||||
<div class="alert alert-warning">Order not found. Check the number and try again.</div>
|
||||
<a href="/track.php" class="btn btn-outline-secondary">Track another order</a>
|
||||
<?php else: ?>
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-7">
|
||||
<div class="stat-card">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<div class="text-muted small">Order number</div>
|
||||
<div class="h5 mb-1"><?= e($order['order_number']) ?></div>
|
||||
</div>
|
||||
<span class="badge text-bg-dark align-self-start"><?= e($order['status']) ?></span>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<div class="text-muted small">Customer</div>
|
||||
<div class="fw-semibold"><?= e($order['customer_name']) ?></div>
|
||||
<div class="text-muted small"><?= e($order['customer_email']) ?></div>
|
||||
<div class="text-muted small"><?= e($order['customer_address']) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<div class="stat-card">
|
||||
<div class="text-muted small">Total paid</div>
|
||||
<div class="h4 mb-0"><?= e(format_price((float) $order['total_price'])) ?></div>
|
||||
<div class="text-muted small mt-2">Placed <?= e($order['created_at']) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
<?php render_footer(); ?>
|
||||
40
product.php
40
product.php
@ -1,40 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/includes/bootstrap.php';
|
||||
require_once __DIR__ . '/includes/layout.php';
|
||||
|
||||
$id = isset($_GET['id']) ? (int) $_GET['id'] : 0;
|
||||
$product = $id ? get_product($id) : null;
|
||||
|
||||
if (!$product) {
|
||||
flash_set('warning', 'Product not found.');
|
||||
header('Location: /shop.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
render_header($product['name'] . ' - E-SO9', 'shop');
|
||||
?>
|
||||
<main class="container my-5">
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-6">
|
||||
<img src="<?= e($product['image_url'] ?: product_image_data($product['name'])) ?>" class="img-fluid rounded" alt="<?= e($product['name']) ?>" width="800" height="600" />
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="text-muted small mb-2"><?= e($product['category_name'] ?? 'General') ?></div>
|
||||
<h1 class="h3 mb-3"><?= e($product['name']) ?></h1>
|
||||
<p class="text-muted mb-3"><?= e($product['description']) ?></p>
|
||||
<div class="d-flex align-items-center gap-3 mb-4">
|
||||
<span class="h4 mb-0"><?= e(format_price((float) $product['price'])) ?></span>
|
||||
<span class="badge badge-soft">Rating <?= e((string) $product['rating']) ?></span>
|
||||
<span class="text-muted small">Stock <?= e((string) $product['stock']) ?></span>
|
||||
</div>
|
||||
<form method="post" action="/cart.php" class="d-flex gap-2">
|
||||
<input type="hidden" name="product_id" value="<?= e((string) $product['id']) ?>" />
|
||||
<input type="number" name="quantity" class="form-control" min="1" max="<?= e((string) $product['stock']) ?>" value="1" />
|
||||
<button class="btn btn-primary" type="submit">Add to cart</button>
|
||||
</form>
|
||||
<div class="admin-note mt-4">Demo checkout only. Orders are stored locally for the admin view.</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<?php render_footer(); ?>
|
||||
63
shop.php
63
shop.php
@ -1,63 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/includes/bootstrap.php';
|
||||
require_once __DIR__ . '/includes/layout.php';
|
||||
|
||||
$search = isset($_GET['search']) ? trim((string) $_GET['search']) : null;
|
||||
$categoryId = isset($_GET['category']) ? (int) $_GET['category'] : null;
|
||||
$sort = isset($_GET['sort']) ? (string) $_GET['sort'] : null;
|
||||
|
||||
$categories = get_categories();
|
||||
$products = get_products($search, $categoryId, $sort);
|
||||
|
||||
render_header('Shop - E-SO9', 'shop');
|
||||
?>
|
||||
<main class="container my-5">
|
||||
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-start align-items-lg-center gap-3 mb-4">
|
||||
<div>
|
||||
<h1 class="h3 mb-1">Shop all products</h1>
|
||||
<p class="text-muted mb-0">Search, filter, and add items to your cart.</p>
|
||||
</div>
|
||||
<form class="d-flex gap-2" method="get" action="/shop.php">
|
||||
<input type="search" name="search" class="form-control" placeholder="Search products" value="<?= e($search ?? '') ?>" />
|
||||
<select name="category" class="form-select">
|
||||
<option value="">All categories</option>
|
||||
<?php foreach ($categories as $category): ?>
|
||||
<option value="<?= e((string) $category['id']) ?>" <?= $categoryId === (int) $category['id'] ? 'selected' : '' ?>>
|
||||
<?= e($category['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<select name="sort" class="form-select">
|
||||
<option value="">Newest</option>
|
||||
<option value="price_asc" <?= $sort === 'price_asc' ? 'selected' : '' ?>>Price low-high</option>
|
||||
<option value="price_desc" <?= $sort === 'price_desc' ? 'selected' : '' ?>>Price high-low</option>
|
||||
</select>
|
||||
<button class="btn btn-outline-secondary" type="submit">Apply</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php if (!$products): ?>
|
||||
<div class="alert alert-light border">No products matched your filters.</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="row g-4">
|
||||
<?php foreach ($products as $product): ?>
|
||||
<div class="col-md-4">
|
||||
<div class="product-card h-100">
|
||||
<img src="<?= e($product['image_url'] ?: product_image_data($product['name'])) ?>" class="img-fluid" alt="<?= e($product['name']) ?>" width="600" height="400" />
|
||||
<div class="p-3 d-flex flex-column h-100">
|
||||
<div class="text-muted small mb-1"><?= e($product['category_name'] ?? 'General') ?></div>
|
||||
<h2 class="h6 mb-2"><?= e($product['name']) ?></h2>
|
||||
<p class="text-muted small flex-grow-1"><?= e($product['description']) ?></p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="fw-semibold"><?= e(format_price((float) $product['price'])) ?></span>
|
||||
<a href="/product.php?id=<?= e((string) $product['id']) ?>" class="btn btn-outline-secondary btn-sm">View</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</main>
|
||||
<?php render_footer(); ?>
|
||||
64
track.php
64
track.php
@ -1,64 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/includes/bootstrap.php';
|
||||
require_once __DIR__ . '/includes/layout.php';
|
||||
|
||||
$orderNumber = '';
|
||||
$email = '';
|
||||
$order = null;
|
||||
$error = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$orderNumber = trim((string) ($_POST['order_number'] ?? ''));
|
||||
$email = trim((string) ($_POST['email'] ?? ''));
|
||||
|
||||
if ($orderNumber === '' || $email === '') {
|
||||
$error = 'Both fields are required.';
|
||||
} else {
|
||||
$order = get_order_by_number($orderNumber);
|
||||
if (!$order || strtolower((string) $order['customer_email']) !== strtolower($email)) {
|
||||
$error = 'No matching order found.';
|
||||
$order = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render_header('Track Order - E-SO9', 'track');
|
||||
?>
|
||||
<main class="container my-5">
|
||||
<h1 class="h3 mb-3">Track your order</h1>
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-6">
|
||||
<div class="stat-card">
|
||||
<form method="post" action="/track.php">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Order number</label>
|
||||
<input type="text" name="order_number" class="form-control" value="<?= e($orderNumber) ?>" required />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Email</label>
|
||||
<input type="email" name="email" class="form-control" value="<?= e($email) ?>" required />
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit">Check status</button>
|
||||
</form>
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-warning mt-3"><?= e($error) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<?php if ($order): ?>
|
||||
<div class="stat-card">
|
||||
<div class="text-muted small">Status</div>
|
||||
<div class="h4 mb-2"><?= e($order['status']) ?></div>
|
||||
<div class="text-muted small">Order total</div>
|
||||
<div class="fw-semibold mb-2"><?= e(format_price((float) $order['total_price'])) ?></div>
|
||||
<a href="/order.php?order=<?= e($order['order_number']) ?>" class="btn btn-outline-secondary btn-sm">View full order</a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="admin-note">Place a demo order to see tracking updates here.</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<?php render_footer(); ?>
|
||||
Loading…
x
Reference in New Issue
Block a user