From 9b7ccbdd3d39e4858a7636cac94145d42c178667 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 18 Feb 2026 16:23:45 +0000 Subject: [PATCH] versionone --- admin/order.php | 65 ++++++++ admin/orders.php | 109 +++++++++++++ assets/css/custom.css | 90 +++++++++++ assets/js/main.js | 9 ++ cart.php | 100 ++++++++++++ checkout.php | 97 ++++++++++++ includes/bootstrap.php | 26 +++ includes/layout.php | 74 +++++++++ includes/store.php | 349 +++++++++++++++++++++++++++++++++++++++++ index.php | 225 ++++++++++---------------- order.php | 46 ++++++ product.php | 40 +++++ shop.php | 63 ++++++++ track.php | 64 ++++++++ 14 files changed, 1213 insertions(+), 144 deletions(-) create mode 100644 admin/order.php create mode 100644 admin/orders.php create mode 100644 assets/css/custom.css create mode 100644 assets/js/main.js create mode 100644 cart.php create mode 100644 checkout.php create mode 100644 includes/bootstrap.php create mode 100644 includes/layout.php create mode 100644 includes/store.php create mode 100644 order.php create mode 100644 product.php create mode 100644 shop.php create mode 100644 track.php diff --git a/admin/order.php b/admin/order.php new file mode 100644 index 0000000..5327bc6 --- /dev/null +++ b/admin/order.php @@ -0,0 +1,65 @@ + 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'); +?> +
+ Back to orders +

Order detail

+ + +
Order not found.
+ +
+
+
+
Order number
+
+
Customer
+
+
+
+
Status
+
+
+
+
+
+
Items
+ +
No items recorded.
+ +
    + +
  • + x + +
  • + +
+ +
+ Total + +
+
+
+
+ +
+ diff --git a/admin/orders.php b/admin/orders.php new file mode 100644 index 0000000..0ac57aa --- /dev/null +++ b/admin/orders.php @@ -0,0 +1,109 @@ + 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'); +?> +
+
+
+

Admin orders

+

Update order statuses and monitor totals.

+
+
Demo admin area. No authentication yet.
+
+ +
+
+
+
Total orders
+
+
+
+
+
+
Total revenue
+
+ + +
+
+
+
+
+
Latest status
+
+
+
+
+ + +
No orders yet. Place a demo order to see entries.
+ +
+ + + + + + + + + + + + + + + + + + + + + +
OrderCustomerTotalStatusUpdate
+
+
+
+
+
+
+ + +
+ + + + View +
+
+
+ +
+ diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..5eeb534 --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,90 @@ +: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); +} diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..48c5292 --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,9 @@ +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(); + } + }); +}); diff --git a/cart.php b/cart.php new file mode 100644 index 0000000..6cf6c38 --- /dev/null +++ b/cart.php @@ -0,0 +1,100 @@ + 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'); +?> +
+

Your cart

+ + +
Your cart is empty. Browse products to get started.
+ Go to shop + +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
ProductPriceQtyTotal
+
+
+
+
+ + + + +
+
+
+ + + + +
+
+
+
+
+
+
+ Subtotal + +
+
+ Shipping + $0.00 +
+
+ Total + +
+ Proceed to checkout +
+
+
+ +
+ diff --git a/checkout.php b/checkout.php new file mode 100644 index 0000000..d0db103 --- /dev/null +++ b/checkout.php @@ -0,0 +1,97 @@ + $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'); +?> +
+

Checkout

+ + +
+ + +
+
+
+

Customer details

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

Order summary

+ +

Add items to cart to continue.

+ +
    + +
  • + x + +
  • + +
+
+ Total + +
+ +
+
+
+
+ diff --git a/includes/bootstrap.php b/includes/bootstrap.php new file mode 100644 index 0000000..b4a9fbf --- /dev/null +++ b/includes/bootstrap.php @@ -0,0 +1,26 @@ + $type, 'message' => $message]; +} + +function flash_get(): ?array { + if (empty($_SESSION['flash'])) { + return null; + } + $flash = $_SESSION['flash']; + unset($_SESSION['flash']); + return $flash; +} diff --git a/includes/layout.php b/includes/layout.php new file mode 100644 index 0000000..4e727a2 --- /dev/null +++ b/includes/layout.php @@ -0,0 +1,74 @@ + $active === $key ? 'active' : ''; + echo "\n"; + echo "\n"; + echo "\n"; + echo " \n"; + echo " \n"; + echo " " . e($title) . "\n"; + if ($projectDescription) { + echo " \n"; + echo " \n"; + echo " \n"; + } + if ($projectImageUrl) { + echo " \n"; + echo " \n"; + } + echo " \n"; + echo " \n"; + echo " \n"; + echo " \n"; + echo " \n"; + echo "\n"; + echo "\n"; + echo "\n"; +} + +function render_footer(): void { + $flash = flash_get(); + echo "\n"; + echo "\n"; + echo "\n"; + + if ($flash) { + $type = $flash['type'] === 'success' ? 'success' : 'warning'; + echo "
\n"; + echo "
\n"; + echo "
\n"; + echo "
" . e($flash['message']) . "
\n"; + echo " \n"; + echo "
\n"; + echo "
\n"; + echo "
\n"; + } + + echo "\n\n"; +} diff --git a/includes/store.php b/includes/store.php new file mode 100644 index 0000000..5dcedc8 --- /dev/null +++ b/includes/store.php @@ -0,0 +1,349 @@ +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 = "" . $text . ""; + 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]); +} diff --git a/index.php b/index.php index 7205f3d..15dd489 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,87 @@ - - - - - - New Style - - - - - - - - - - - - - - - - - - - - - -
-
-

Analyzing your requirements and generating your website…

-
- Loading… +
+
+
+
+ Professional ecommerce MVP +

E-SO9 curated storefront with fast checkout and admin tracking.

+

Browse products, add to cart, place a demo order, and track status. Admins can review orders and update fulfillment.

+ +
+
+
+
+
+
Categories
+
+
+
+
Active products
+
6
+
+
+
Checkout status
+
Demo payments enabled
+
+
+
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

-
-
- Page updated: (UTC) -
- - + + +
+
+

Browse by category

+ See all products +
+
+ +
+
+
Category
+
+ Explore +
+
+ +
+
+ +
+
+

Featured products

+ Hand-picked for the launch +
+
+ +
+
+ <?= e($product['name']) ?> +
+
+

+
+ + View +
+
+
+
+ +
+
+
+ diff --git a/order.php b/order.php new file mode 100644 index 0000000..fab70e1 --- /dev/null +++ b/order.php @@ -0,0 +1,46 @@ + +
+

Order details

+ + +
Order not found. Check the number and try again.
+ Track another order + +
+
+
+
+
+
Order number
+
+
+ +
+
+
Customer
+
+
+
+
+
+
+
+
+
Total paid
+
+
Placed
+
+
+
+ +
+ diff --git a/product.php b/product.php new file mode 100644 index 0000000..9d87d56 --- /dev/null +++ b/product.php @@ -0,0 +1,40 @@ + +
+
+
+ <?= e($product['name']) ?> +
+
+
+

+

+
+ + Rating + Stock +
+
+ + + +
+
Demo checkout only. Orders are stored locally for the admin view.
+
+
+
+ diff --git a/shop.php b/shop.php new file mode 100644 index 0000000..c2031e0 --- /dev/null +++ b/shop.php @@ -0,0 +1,63 @@ + +
+
+
+

Shop all products

+

Search, filter, and add items to your cart.

+
+
+ + + + +
+
+ + +
No products matched your filters.
+ + +
+ +
+
+ <?= e($product['name']) ?> +
+
+

+

+
+ + View +
+
+
+
+ +
+
+ diff --git a/track.php b/track.php new file mode 100644 index 0000000..524e17b --- /dev/null +++ b/track.php @@ -0,0 +1,64 @@ + +
+

Track your order

+
+
+
+
+
+ + +
+
+ + +
+ +
+ +
+ +
+
+
+ +
+
Status
+
+
Order total
+
+ View full order +
+ +
Place a demo order to see tracking updates here.
+ +
+
+
+