From 163c483584ac68728b64b5e9e90a5bbf128bb777 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Tue, 28 Oct 2025 01:29:21 +0000 Subject: [PATCH] atual --- README.md | 62 +++++++ app/Controllers/AdminController.php | 0 app/Controllers/AuthController.php | 50 ++++++ app/Controllers/CartController.php | 108 ++++++++++++ app/Controllers/CategoryController.php | 68 ++++++++ app/Controllers/HomeController.php | 0 app/Controllers/OrderController.php | 97 +++++++++++ app/Controllers/ProductController.php | 153 +++++++++++++++++ app/Models/Category.php | 0 app/Models/Order.php | 27 +++ app/Models/Product.php | 116 +++++++++++++ app/Models/User.php | 0 app/Views/admin/footer.php | 10 ++ app/Views/admin/header.php | 72 ++++++++ app/Views/public/footer.php | 19 +++ app/Views/public/header.php | 80 +++++++++ config/config.php | 19 +++ database/schema.sql | 88 ++++++++++ helpers/slugify.php | 28 +++ public_html/.htaccess | 18 ++ public_html/admin/categories.php | 147 ++++++++++++++++ public_html/admin/index.php | 47 +++++ public_html/admin/login.php | 59 +++++++ public_html/admin/logout.php | 9 + public_html/admin/orders.php | 53 ++++++ public_html/admin/products.php | 228 +++++++++++++++++++++++++ public_html/assets/css/custom.css | 185 ++++++++++++++++++++ public_html/assets/js/main.js | 0 public_html/cart.php | 94 ++++++++++ public_html/catalog.php | 62 +++++++ public_html/checkout.php | 79 +++++++++ public_html/contact.php | 153 +++++++++++++++++ public_html/index.php | 120 +++++++++++++ public_html/product.php | 65 +++++++ public_html/thank-you.php | 23 +++ scripts/seed_admin.php | 0 36 files changed, 2339 insertions(+) create mode 100644 README.md create mode 100644 app/Controllers/AdminController.php create mode 100644 app/Controllers/AuthController.php create mode 100644 app/Controllers/CartController.php create mode 100644 app/Controllers/CategoryController.php create mode 100644 app/Controllers/HomeController.php create mode 100644 app/Controllers/OrderController.php create mode 100644 app/Controllers/ProductController.php create mode 100644 app/Models/Category.php create mode 100644 app/Models/Order.php create mode 100644 app/Models/Product.php create mode 100644 app/Models/User.php create mode 100644 app/Views/admin/footer.php create mode 100644 app/Views/admin/header.php create mode 100644 app/Views/public/footer.php create mode 100644 app/Views/public/header.php create mode 100644 config/config.php create mode 100644 database/schema.sql create mode 100644 helpers/slugify.php create mode 100644 public_html/.htaccess create mode 100644 public_html/admin/categories.php create mode 100644 public_html/admin/index.php create mode 100644 public_html/admin/login.php create mode 100644 public_html/admin/logout.php create mode 100644 public_html/admin/orders.php create mode 100644 public_html/admin/products.php create mode 100644 public_html/assets/css/custom.css create mode 100644 public_html/assets/js/main.js create mode 100644 public_html/cart.php create mode 100644 public_html/catalog.php create mode 100644 public_html/checkout.php create mode 100644 public_html/contact.php create mode 100644 public_html/index.php create mode 100644 public_html/product.php create mode 100644 public_html/thank-you.php create mode 100644 scripts/seed_admin.php diff --git a/README.md b/README.md new file mode 100644 index 0000000..bc342e8 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# Catálogo de Cestas - beni cestas + +Este é um projeto de site de catálogo de cestas e presentes desenvolvido em PHP puro, com estrutura MVC-like, pronto para ser implantado em servidores de hospedagem como o InfinityFree. + +## Estrutura de Pastas + +``` +/ +├─ /app (Lógica da aplicação, fora da raiz pública) +│ ├─ /Controllers +│ ├─ /Models +│ └─ /Views +├─ /config (Configuração, fora da raiz pública) +├─ /database (Schema e seeds do banco de dados) +├─ /public_html (Raiz pública do site, o que vai para a pasta htdocs) +│ ├─ index.php (Front-controller) +│ ├─ .htaccess +│ ├─ /assets (CSS, JS, Imagens) +│ └─ /uploads (Imagens dos produtos) +├─ /scripts (Scripts de utilidade) +└─ README.md +``` + +## Requisitos + +* PHP 8.x +* MySQL / MariaDB +* Servidor web com suporte a `mod_rewrite` (Apache) + +## Instruções de Deploy no InfinityFree + +1. **Crie sua conta:** Acesse [InfinityFree.net](https://www.infinityfree.net/) e crie uma nova conta de hospedagem. +2. **Crie o Banco de Dados:** + * No painel de controle (cPanel), vá para a seção "MySQL Databases". + * Crie um novo banco de dados. Anote o nome do banco de dados (`if0_..._dbname`), o nome de usuário (`if0_...`) e a senha. O host do banco de dados (`sqlXXX.epizy.com`) também será exibido. +3. **Configure o projeto:** + * Abra o arquivo `config/config.php`. + * Altere os valores de `DB_HOST`, `DB_NAME`, `DB_USER`, e `DB_PASS` com as credenciais que você anotou no passo anterior. + * Altere o `BASE_URL` para a URL do seu site no InfinityFree (ex: `http://seusite.epizy.com`). +4. **Faça o Upload dos Arquivos:** + * Use um cliente FTP como o FileZilla para se conectar ao seu servidor InfinityFree (as credenciais de FTP estão no painel de controle). + * **IMPORTANTE:** Envie o **conteúdo** da pasta `public_html` do projeto para a pasta `htdocs` no seu servidor. + * Envie as pastas `app`, `config`, e `vendor` (se existir) para a raiz do seu servidor (fora da pasta `htdocs`). O InfinityFree permite criar diretórios fora de `htdocs`, o que é mais seguro. +5. **Importe o Banco de Dados:** + * No painel de controle do InfinityFree, abra o `phpMyAdmin`. + * Selecione o banco de dados que você criou. + * Vá para a aba "Importar". + * Faça o upload do arquivo `database/schema.sql` e execute a importação. +6. **Acesse o site:** Seu site já deve estar funcionando! + +## Acesso ao Admin + +* **URL:** `http://seusite.epizy.com/admin` (será implementado) +* **Usuário:** `admin@exemplo.com` +* **Senha:** `ChangeMe123!` + +**IMPORTANTE:** A senha no `schema.sql` é um placeholder. Você precisará trocá-la. Um script para criar o admin com uma senha segura (`scripts/seed_admin.php`) será fornecido. + +## Observações + +* A pasta `uploads` precisa de permissão de escrita para que o upload de imagens de produtos funcione. Você pode precisar ajustar as permissões via FTP (geralmente para 755). +* Este projeto foi desenvolvido com uma estrutura simples para fins educacionais e de portfólio. Para um ambiente de produção real, considere usar um framework PHP robusto como Laravel ou Symfony. diff --git a/app/Controllers/AdminController.php b/app/Controllers/AdminController.php new file mode 100644 index 0000000..e69de29 diff --git a/app/Controllers/AuthController.php b/app/Controllers/AuthController.php new file mode 100644 index 0000000..8a8544f --- /dev/null +++ b/app/Controllers/AuthController.php @@ -0,0 +1,50 @@ +prepare("SELECT * FROM users WHERE email = ? AND (role = 'admin' OR role = 'editor')"); + $stmt->execute([$email]); + $user = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($user && password_verify($password, $user['password'])) { + session_start(); + $_SESSION['user_id'] = $user['id']; + $_SESSION['user_name'] = $user['name']; + $_SESSION['role'] = $user['role']; + return ['success' => true]; + } else { + // For security, we use a generic error message. + // Note: The default password 'ChangeMe123!' from the initial seed needs to be hashed correctly. + // Use scripts/seed_admin.php or a manual hash generation to set it up. + return ['success' => false, 'message' => 'Credenciais inválidas ou usuário não autorizado.']; + } + } + + public function logout() { + session_start(); + session_unset(); + session_destroy(); + } + + public static function checkAuth() { + if (session_status() === PHP_SESSION_NONE) { + session_start(); + } + if (!isset($_SESSION['user_id'])) { + header("Location: " . BASE_URL . "admin/login.php"); + exit(); + } + } + + public static function isAdmin() { + self::checkAuth(); + if ($_SESSION['role'] !== 'admin') { + // Redirect to a less privileged page or show an error + header("Location: " . BASE_URL . "admin/index.php?error=unauthorized"); + exit(); + } + } +} diff --git a/app/Controllers/CartController.php b/app/Controllers/CartController.php new file mode 100644 index 0000000..4965467 --- /dev/null +++ b/app/Controllers/CartController.php @@ -0,0 +1,108 @@ + $productId, + 'name' => $product['name'], + 'price' => $product['price'], + 'image' => $product['image'], + 'slug' => $product['slug'], + 'quantity' => $quantity, + ]; + } + } + header('Location: /cart.php'); + exit(); + } + } + + public function update() { + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['product_id'])) { + $productId = (int)$_POST['product_id']; + $quantity = isset($_POST['quantity']) ? (int)$_POST['quantity'] : 0; + + if ($quantity > 0 && isset($_SESSION['cart'][$productId])) { + $_SESSION['cart'][$productId]['quantity'] = $quantity; + } else { + unset($_SESSION['cart'][$productId]); + } + } + header('Location: /cart.php'); + exit(); + } + + public function remove() { + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['product_id'])) { + $productId = (int)$_POST['product_id']; + unset($_SESSION['cart'][$productId]); + } + header('Location: /cart.php'); + exit(); + } + + public static function getCartContents() { + return $_SESSION['cart'] ?? []; + } + + public static function getCartItemCount() { + $count = 0; + if (isset($_SESSION['cart'])) { + foreach ($_SESSION['cart'] as $item) { + $count += $item['quantity']; + } + } + return $count; + } + + public static function getCartTotal() { + $total = 0; + if (isset($_SESSION['cart'])) { + foreach ($_SESSION['cart'] as $item) { + $total += $item['price'] * $item['quantity']; + } + } + return $total; + } +} + +// Handle cart actions +if (isset($_POST['action'])) { + $cartController = new CartController(); + switch ($_POST['action']) { + case 'add': + $cartController->add(); + break; + case 'update': + $cartController->update(); + break; + case 'remove': + $cartController->remove(); + break; + } +} +?> \ No newline at end of file diff --git a/app/Controllers/CategoryController.php b/app/Controllers/CategoryController.php new file mode 100644 index 0000000..49ec7d1 --- /dev/null +++ b/app/Controllers/CategoryController.php @@ -0,0 +1,68 @@ +query("SELECT * FROM categories ORDER BY name"); + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } + + public function store($name) { + $slug = $this->slugify($name); + $pdo = db(); + try { + $stmt = $pdo->prepare("INSERT INTO categories (name, slug) VALUES (?, ?)"); + $stmt->execute([$name, $slug]); + return ['success' => true]; + } catch (PDOException $e) { + if ($e->errorInfo[1] == 1062) { // Duplicate entry + return ['success' => false, 'message' => 'Uma categoria com este nome ou slug já existe.']; + } + return ['success' => false, 'message' => 'Erro ao criar categoria.']; + } + } + + public function show($id) { + $pdo = db(); + $stmt = $pdo->prepare("SELECT * FROM categories WHERE id = ?"); + $stmt->execute([$id]); + return $stmt->fetch(PDO::FETCH_ASSOC); + } + + public function update($id, $name) { + $slug = $this->slugify($name); + $pdo = db(); + try { + $stmt = $pdo->prepare("UPDATE categories SET name = ?, slug = ? WHERE id = ?"); + $stmt->execute([$name, $slug, $id]); + return ['success' => true]; + } catch (PDOException $e) { + if ($e->errorInfo[1] == 1062) { + return ['success' => false, 'message' => 'Uma categoria com este nome ou slug já existe.']; + } + return ['success' => false, 'message' => 'Erro ao atualizar categoria.']; + } + } + + public function destroy($id) { + $pdo = db(); + $stmt = $pdo->prepare("DELETE FROM categories WHERE id = ?"); + $stmt->execute([$id]); + return ['success' => true]; + } + + private function slugify($text) { + $text = preg_replace('~[\pL\d]+~u', '-', $text); + $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text); + $text = preg_replace('~[^\w]+~u', '-', $text); + $text = trim($text, '-'); + $text = preg_replace('~-~', '-', $text); + $text = strtolower($text); + if (empty($text)) { + return 'n-a'; + } + return $text; + } +} diff --git a/app/Controllers/HomeController.php b/app/Controllers/HomeController.php new file mode 100644 index 0000000..e69de29 diff --git a/app/Controllers/OrderController.php b/app/Controllers/OrderController.php new file mode 100644 index 0000000..f58f419 --- /dev/null +++ b/app/Controllers/OrderController.php @@ -0,0 +1,97 @@ +sendConfirmationEmails($orderId, $customerName, $customerEmail, $cartItems, $cartTotal); + + // Redirect to a success page + header('Location: /thank-you.php?order_id=' . $orderId); + exit(); + } + + private function sendConfirmationEmails($orderId, $customerName, $customerEmail, $cartItems, $cartTotal) { + // Email to Customer + $customerSubject = "Confirmação do seu Pedido #{$orderId}"; + $customerHtml = "

Obrigado pela sua compra, {$customerName}!

"; + $customerHtml .= "

Seu pedido #{$orderId} foi recebido e está sendo processado.

"; + $customerHtml .= $this->formatOrderForEmail($cartItems, $cartTotal); + MailService::sendMail($customerEmail, $customerSubject, $customerHtml); + + // Email to Admin + $adminEmail = getenv('MAIL_TO') ?: getenv('MAIL_FROM'); + if ($adminEmail) { + $adminSubject = "Novo Pedido Recebido #{$orderId}"; + $adminHtml = "

Novo Pedido de {$customerName} ({$customerEmail})

"; + $adminHtml .= $this->formatOrderForEmail($cartItems, $cartTotal); + MailService::sendMail($adminEmail, $adminSubject, $adminHtml); + } + } + + private function formatOrderForEmail($items, $total) { + $html = ''; + $html .= ''; + $html .= ''; + foreach ($items as $item) { + $html .= sprintf('', + htmlspecialchars($item['name']), + $item['quantity'], + $item['price'], + $item['price'] * $item['quantity'] + ); + } + $html .= ''; + $html .= sprintf('', $total); + $html .= '
ProdutoQtdPreçoSubtotal
%s%dR$ %.2fR$ %.2f
Total:R$ %.2f
'; + return $html; + } +} + +if (isset($_POST['action']) && $_POST['action'] === 'checkout') { + $orderController = new OrderController(); + $orderController->checkout(); +} +?> \ No newline at end of file diff --git a/app/Controllers/ProductController.php b/app/Controllers/ProductController.php new file mode 100644 index 0000000..5090f21 --- /dev/null +++ b/app/Controllers/ProductController.php @@ -0,0 +1,153 @@ +handleImageUpload(); + + $data = [ + 'category_id' => $_POST['category_id'], + 'name' => $name, + 'slug' => $slug, + 'description' => $_POST['description'] ?? '', + 'price' => $price, + 'stock' => $stock, + 'active' => isset($_POST['active']) ? 1 : 0, + 'image' => $image_filename + ]; + + if (Product::create($data)) { + $_SESSION['success_message'] = 'Product created successfully.'; + return true; + } else { + $_SESSION['error_message'] = 'Failed to create product.'; + return false; + } + } + return false; + } + + public function update() { + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $id = $_POST['id']; + $name = trim($_POST['name'] ?? ''); + $price = trim($_POST['price'] ?? '0'); + $stock = trim($_POST['stock'] ?? '0'); + + if (empty($name) || empty($price) || empty($stock)) { + $_SESSION['error_message'] = 'Name, price and stock are required.'; + return false; + } + + $product = Product::find($id); + if (!$product) { + $_SESSION['error_message'] = 'Product not found.'; + return false; + } + + $slug = slugify($name); + $image_filename = $this->handleImageUpload($product['image']); + + $data = [ + 'category_id' => $_POST['category_id'], + 'name' => $name, + 'slug' => $slug, + 'description' => $_POST['description'] ?? '', + 'price' => $price, + 'stock' => $stock, + 'active' => isset($_POST['active']) ? 1 : 0, + 'image' => $image_filename + ]; + + if (Product::update($id, $data)) { + $_SESSION['success_message'] = 'Product updated successfully.'; + return true; + } else { + $_SESSION['error_message'] = 'Failed to update product.'; + return false; + } + } + return false; + } + + public function destroy() { + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $id = $_POST['delete_id']; + $image = Product::getProductImage($id); + + if (Product::delete($id)) { + if ($image) { + $this->deleteImage($image); + } + $_SESSION['success_message'] = 'Product deleted successfully.'; + return true; + } else { + $_SESSION['error_message'] = 'Failed to delete product.'; + return false; + } + } + return false; + } + + private function handleImageUpload($existing_image = null) { + if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) { + $upload_dir = __DIR__ . '/../../public_html/uploads/products/'; + if (!is_dir($upload_dir)) { + mkdir($upload_dir, 0777, true); + } + + $tmp_name = $_FILES['image']['tmp_name']; + $original_name = basename($_FILES['image']['name']); + $file_extension = pathinfo($original_name, PATHINFO_EXTENSION); + $safe_filename = uniqid('product_', true) . '.' . $file_extension; + $destination = $upload_dir . $safe_filename; + + if (move_uploaded_file($tmp_name, $destination)) { + // Delete old image if a new one is uploaded + if ($existing_image) { + $this->deleteImage($existing_image); + } + return $safe_filename; + } + } + return $existing_image; // Return old image if no new one was uploaded + } + + private function deleteImage($filename) { + $file_path = __DIR__ . '/../../public_html/uploads/products/' . $filename; + if (file_exists($file_path)) { + unlink($file_path); + } + } + + public function getCategories() { + return Category::getAll(); + } + + public function show($slug) { + return Product::findBySlug($slug); + } + + public function search($term) { + return Product::search($term); + } +} diff --git a/app/Models/Category.php b/app/Models/Category.php new file mode 100644 index 0000000..e69de29 diff --git a/app/Models/Order.php b/app/Models/Order.php new file mode 100644 index 0000000..ae0bea8 --- /dev/null +++ b/app/Models/Order.php @@ -0,0 +1,27 @@ +prepare($sql); + $stmt->execute([$customerName, $customerEmail, $total]); + return $pdo->lastInsertId(); + } + + public static function createOrderItem($orderId, $productId, $quantity, $price) { + $pdo = db(); + $sql = "INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)"; + $stmt = $pdo->prepare($sql); + $stmt->execute([$orderId, $productId, $quantity, $price]); + } + + public static function getAll() { + $pdo = db(); + $sql = "SELECT * FROM orders ORDER BY created_at DESC"; + $stmt = $pdo->query($sql); + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } +} +?> \ No newline at end of file diff --git a/app/Models/Product.php b/app/Models/Product.php new file mode 100644 index 0000000..5b96fcf --- /dev/null +++ b/app/Models/Product.php @@ -0,0 +1,116 @@ +query('SELECT p.*, c.name as category_name FROM products p JOIN categories c ON p.category_id = c.id ORDER BY p.created_at DESC'); + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } + + /** + * Get a single product by its ID. + */ + public static function find($id) { + $pdo = db(); + $stmt = $pdo->prepare('SELECT * FROM products WHERE id = ?'); + $stmt->execute([$id]); + return $stmt->fetch(PDO::FETCH_ASSOC); + } + + /** + * Get a single product by its slug. + */ + public static function findBySlug($slug) { + $pdo = db(); + $stmt = $pdo->prepare('SELECT p.*, c.name as category_name FROM products p JOIN categories c ON p.category_id = c.id WHERE p.slug = ? AND p.active = 1'); + $stmt->execute([$slug]); + return $stmt->fetch(PDO::FETCH_ASSOC); + } + + /** + * Create a new product. + */ + public static function create($data) { + $pdo = db(); + $sql = "INSERT INTO products (category_id, name, slug, description, price, stock, active, image) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; + $stmt = $pdo->prepare($sql); + return $stmt->execute([ + $data['category_id'], + $data['name'], + $data['slug'], + $data['description'], + $data['price'], + $data['stock'], + $data['active'], + $data['image'] + ]); + } + + /** + * Update an existing product. + */ + public static function update($id, $data) { + $pdo = db(); + $sql = "UPDATE products SET category_id = ?, name = ?, slug = ?, description = ?, price = ?, stock = ?, active = ?, image = ? WHERE id = ?"; + $stmt = $pdo->prepare($sql); + + $params = [ + $data['category_id'], + $data['name'], + $data['slug'], + $data['description'], + $data['price'], + $data['stock'], + $data['active'], + $data['image'], + $id + ]; + + return $stmt->execute($params); + } + + + /** + * Delete a product by its ID. + */ + public static function delete($id) { + $pdo = db(); + $stmt = $pdo->prepare('DELETE FROM products WHERE id = ?'); + return $stmt->execute([$id]); + } + + /** + * Get the image filename for a product. + */ + public static function getProductImage($id) { + $pdo = db(); + $stmt = $pdo->prepare('SELECT image FROM products WHERE id = ?'); + $stmt->execute([$id]); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + return $result ? $result['image'] : null; + } + + /** + * Search for products by a given term. + */ + public static function search($term) { + $pdo = db(); + $searchTerm = '%' . $term . '%'; + $stmt = $pdo->prepare( + 'SELECT p.*, c.name as category_name + FROM products p + JOIN categories c ON p.category_id = c.id + WHERE (p.name LIKE ? OR p.description LIKE ?) AND p.active = 1 + ORDER BY p.created_at DESC' + ); + $stmt->execute([$searchTerm, $searchTerm]); + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } +} \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php new file mode 100644 index 0000000..e69de29 diff --git a/app/Views/admin/footer.php b/app/Views/admin/footer.php new file mode 100644 index 0000000..77a2c7d --- /dev/null +++ b/app/Views/admin/footer.php @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/app/Views/admin/header.php b/app/Views/admin/header.php new file mode 100644 index 0000000..2d9b2df --- /dev/null +++ b/app/Views/admin/header.php @@ -0,0 +1,72 @@ + + + + + + + Admin - Beni Cestas + + + + + + + +
+ +
diff --git a/app/Views/public/footer.php b/app/Views/public/footer.php new file mode 100644 index 0000000..ead5b55 --- /dev/null +++ b/app/Views/public/footer.php @@ -0,0 +1,19 @@ + +
+
+

© 2025 beni cestas. Todos os direitos reservados.

+

Built with ❤️ by Flatlogic

+
+
+ + + + + + + + + + diff --git a/app/Views/public/header.php b/app/Views/public/header.php new file mode 100644 index 0000000..458f319 --- /dev/null +++ b/app/Views/public/header.php @@ -0,0 +1,80 @@ + + + + + + + beni cestas - Cestas de Café da Manhã e Presentes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/config.php b/config/config.php new file mode 100644 index 0000000..7c80e3b --- /dev/null +++ b/config/config.php @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/database/schema.sql b/database/schema.sql new file mode 100644 index 0000000..f9b9274 --- /dev/null +++ b/database/schema.sql @@ -0,0 +1,88 @@ +CREATE TABLE IF NOT EXISTS `users` ( + `id` int NOT NULL AUTO_INCREMENT, + `name` varchar(150) NOT NULL, + `email` varchar(150) NOT NULL, + `password` varchar(255) NOT NULL, + `role` enum('admin','editor') DEFAULT 'editor', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `email` (`email`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +CREATE TABLE IF NOT EXISTS `categories` ( + `id` int NOT NULL AUTO_INCREMENT, + `name` varchar(150) NOT NULL, + `slug` varchar(150) NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `slug` (`slug`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +CREATE TABLE IF NOT EXISTS `products` ( + `id` int NOT NULL AUTO_INCREMENT, + `category_id` int NOT NULL, + `name` varchar(255) NOT NULL, + `slug` varchar(255) NOT NULL, + `description` text, + `price` decimal(10,2) NOT NULL, + `image` varchar(255) DEFAULT NULL, + `stock` int DEFAULT '0', + `active` tinyint(1) DEFAULT '1', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `slug` (`slug`), + KEY `category_id` (`category_id`), + CONSTRAINT `products_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +CREATE TABLE IF NOT EXISTS `orders` ( + `id` int NOT NULL AUTO_INCREMENT, + `customer_name` varchar(255) DEFAULT NULL, + `customer_email` varchar(255) DEFAULT NULL, + `total` decimal(10,2) DEFAULT NULL, + `status` enum('pending','paid','sent','cancelled') DEFAULT 'pending', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +CREATE TABLE IF NOT EXISTS `order_items` ( + `id` int NOT NULL AUTO_INCREMENT, + `order_id` int NOT NULL, + `product_id` int NOT NULL, + `quantity` int NOT NULL, + `price` decimal(10,2) NOT NULL, + PRIMARY KEY (`id`), + KEY `order_id` (`order_id`), + KEY `product_id` (`product_id`), + CONSTRAINT `order_items_ibfk_1` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE, + CONSTRAINT `order_items_ibfk_2` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +CREATE TABLE IF NOT EXISTS `messages` ( + `id` int NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT NULL, + `email` varchar(255) DEFAULT NULL, + `subject` varchar(255) DEFAULT NULL, + `message` text, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +-- SEED DATA -- +INSERT INTO `categories` (`name`, `slug`) VALUES +('Café da Manhã', 'cafe-da-manha'), +('Lanche', 'lanche'), +('Caixa Box', 'caixa-box'); + +INSERT INTO `products` (`category_id`, `name`, `slug`, `description`, `price`, `stock`, `active`) VALUES +(1, 'Cesta de Café da Manhã Clássica', 'cesta-cafe-da-manha-classica', 'Uma cesta completa com pães, frutas, frios e muito mais.', 120.00, 10, 1), +(1, 'Cesta de Café da Manhã Premium', 'cesta-cafe-da-manha-premium', 'Uma experiência de café da manhã inesquecível com itens selecionados.', 180.00, 5, 1), +(2, 'Kit Lanche da Tarde', 'kit-lanche-da-tarde', 'Uma seleção de salgados e doces para uma pausa deliciosa.', 80.00, 15, 1), +(2, 'Caixa de Brigadeiros Gourmet', 'caixa-brigadeiros-gourmet', '20 brigadeiros de sabores variados.', 50.00, 20, 1), +(3, 'Box Happy Hour', 'box-happy-hour', 'Cervejas artesanais, amendoins e outros petiscos.', 150.00, 8, 1), +(3, 'Box Presente Romântico', 'box-presente-romantico', 'Vinho, taças, chocolates e um toque de romance.', 220.00, 4, 1); + +-- Admin user: admin@exemplo.com / ChangeMe123! +-- The password hash is a placeholder and needs to be generated with password_hash() +INSERT INTO `users` (`name`, `email`, `password`, `role`) VALUES +('Admin', 'admin@exemplo.com', '$2y$10$3s.G5s1JSs5s/S.d5s.d5u/zY8A.L9zJ.X5s.d5s.d5u/zY8A.L9zJ.', 'admin'); diff --git a/helpers/slugify.php b/helpers/slugify.php new file mode 100644 index 0000000..578d719 --- /dev/null +++ b/helpers/slugify.php @@ -0,0 +1,28 @@ + + RewriteEngine on + RewriteRule ^uploads/.*\.(php|php5|php7)$ - [F,L,NC] + + +# Protege arquivos de configuração e outros arquivos sensíveis + + Require all denied + diff --git a/public_html/admin/categories.php b/public_html/admin/categories.php new file mode 100644 index 0000000..972d84f --- /dev/null +++ b/public_html/admin/categories.php @@ -0,0 +1,147 @@ +store($_POST['name']); + $alert = ['type' => $result['success'] ? 'success' : 'danger', 'message' => $result['success'] ? 'Categoria criada com sucesso!' : $result['message']]; + break; + case 'update': + $result = $categoryController->update($_POST['id'], $_POST['name']); + $alert = ['type' => $result['success'] ? 'success' : 'danger', 'message' => $result['success'] ? 'Categoria atualizada com sucesso!' : $result['message']]; + break; + case 'delete': + $result = $categoryController->destroy($_POST['id']); + $alert = ['type' => 'success', 'message' => 'Categoria excluída com sucesso!']; + break; + } + } +} + +$categories = $categoryController->index(); + +require_once __DIR__ . '/../../app/Views/admin/header.php'; +?> + +
+

Gerenciar Categorias

+ + + + + +
+
+
+
+

Lista de Categorias

+
+
+
+ + + + + + + + + + + + + + + +
NomeAções
+ +
+ + + +
+
+
+
+
+
+
+
+
+

Adicionar Nova Categoria

+
+
+
+ +
+ + +
+ +
+
+
+
+
+
+ + + + + + + diff --git a/public_html/admin/index.php b/public_html/admin/index.php new file mode 100644 index 0000000..fd70ebf --- /dev/null +++ b/public_html/admin/index.php @@ -0,0 +1,47 @@ + + +
+
+

Dashboard

+
+ + +
Você não tem permissão para acessar essa página.
+ + +
+
+
+
+
+
+
Pedidos (Mensal)
+
40
+
+
+
+
+
+
+
+
+
+
+
+
Faturamento (Mensal)
+
R$215,000
+
+
+
+
+
+
+
+
+ + diff --git a/public_html/admin/login.php b/public_html/admin/login.php new file mode 100644 index 0000000..7a1b0a7 --- /dev/null +++ b/public_html/admin/login.php @@ -0,0 +1,59 @@ +login($_POST['email'], $_POST['password']); + if ($result['success']) { + header("Location: " . BASE_URL . "admin/index.php"); + exit(); + } else { + $error = $result['message']; + } +} +?> + + + + + + Admin Login - Beni Cestas + + + + +
+
+
+

Admin Login

+ +
+ +
+
+ + +
+
+ + +
+
+ +
+
+
+
+
+ + + diff --git a/public_html/admin/logout.php b/public_html/admin/logout.php new file mode 100644 index 0000000..2e14438 --- /dev/null +++ b/public_html/admin/logout.php @@ -0,0 +1,9 @@ +logout(); + +header("Location: " . BASE_URL . "admin/login.php"); +exit(); diff --git a/public_html/admin/orders.php b/public_html/admin/orders.php new file mode 100644 index 0000000..26ff21e --- /dev/null +++ b/public_html/admin/orders.php @@ -0,0 +1,53 @@ +index(); + +require_once __DIR__ . '/../../app/Views/admin/header.php'; +?> + +
+

Pedidos

+ +
+
+
Todos os Pedidos
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
ID do PedidoClienteEmailTotalStatusData
R$
+
+
+
+
+ + \ No newline at end of file diff --git a/public_html/admin/products.php b/public_html/admin/products.php new file mode 100644 index 0000000..5589e53 --- /dev/null +++ b/public_html/admin/products.php @@ -0,0 +1,228 @@ +store(); + } elseif (isset($_POST['update_product'])) { + $productController->update(); + } elseif (isset($_POST['delete_id'])) { + $productController->destroy(); + } + header("Location: products.php"); + exit; +} + +$products = $productController->index(); +$categories = $productController->getCategories(); + +$pageTitle = 'Manage Products'; +include __DIR__ . '/../../app/Views/admin/header.php'; +?> + +
+

Manage Products

+ + +
+ +
+ + +
+ +
+ + + +
+
+ + Add New Product +
+
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ +
+
+
+ + +
+
+ + Existing Products +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
ImageNameCategoryPriceStockActiveActions
+ + <?php echo htmlspecialchars($product['name']); ?> + + + +
+ + +
+
+
+
+
+
+ + + + + + + diff --git a/public_html/assets/css/custom.css b/public_html/assets/css/custom.css new file mode 100644 index 0000000..004c2c9 --- /dev/null +++ b/public_html/assets/css/custom.css @@ -0,0 +1,185 @@ +/* Google Fonts */ +@import url('https://fonts.googleapis.com/css2?family=Lato:wght@400;700&family=Playfair+Display:wght@700&display=swap'); + +/* Custom Properties */ +:root { + --primary-color: #D4A373; + --secondary-color: #FAEDCD; + --accent-color: #EAB676; + --text-color: #333333; + --bg-surface: #FEFDF8; + --border-radius: 0.5rem; + --font-heading: 'Playfair Display', serif; + --font-body: 'Lato', sans-serif; +} + +body { + font-family: var(--font-body); + background-color: var(--bg-surface); + color: var(--text-color); + padding-top: 70px; /* Offset for fixed navbar */ +} + +h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { + font-family: var(--font-heading); + color: var(--primary-color); +} + +.navbar { + background-color: rgba(254, 253, 248, 0.85); + backdrop-filter: blur(10px); + box-shadow: 0 2px 4px rgba(0,0,0,0.05); +} + +.navbar-brand { + font-family: var(--font-heading); + font-weight: 700; + font-size: 1.5rem; + color: var(--primary-color) !important; +} + +.nav-link { + color: var(--text-color) !important; + transition: color 0.3s; + font-weight: 700; +} + +.nav-link:hover, .nav-link.active { + color: var(--primary-color) !important; +} + +.carousel-item { + height: 75vh; + min-height: 400px; + background: no-repeat center center scroll; + background-size: cover; +} + +.carousel-caption { + bottom: 25%; + background-color: rgba(0,0,0,0.5); + padding: 1.5rem 2.5rem; + border-radius: var(--border-radius); +} + +.carousel-caption h5 { + font-size: 3rem; + color: #fff; + font-weight: 700; +} + +.product-card { + border: none; + border-radius: var(--border-radius); + transition: transform 0.3s, box-shadow 0.3s; + background-color: #fff; + overflow: hidden; + box-shadow: 0 4px 15px rgba(0,0,0,0.05); +} + +.product-card:hover { + transform: translateY(-10px); + box-shadow: 0 12px 25px rgba(0,0,0,0.1); +} + +.product-card img { + aspect-ratio: 4 / 3; + object-fit: cover; +} + +.product-card .card-body { + text-align: center; +} + +.product-card .card-title { + font-size: 1.2rem; + font-family: var(--font-body); + font-weight: 700; + color: var(--text-color); +} + +.product-card .card-price { + font-size: 1.3rem; + font-weight: 700; + color: var(--primary-color); +} + +.btn-primary { + background: linear-gradient(to right, var(--accent-color), var(--primary-color)); + border: none; + padding: 0.75rem 1.5rem; + border-radius: var(--border-radius); + transition: transform 0.2s, box-shadow 0.2s; + font-weight: 700; + color: #fff; +} + +.btn-primary:hover { + transform: scale(1.05); + box-shadow: 0 4px 15px rgba(212, 163, 115, 0.4); +} + +footer { + background-color: var(--secondary-color); +} + +/* Contact Page Styles */ +.contact-header { + padding-top: 2rem; + padding-bottom: 2rem; +} + +.contact-form { + background-color: #FFFFFF; + padding: 2.5rem; + border-radius: var(--border-radius); + box-shadow: 0 4px 15px rgba(0,0,0,0.05); +} + +.form-control { + min-height: 48px; + border-radius: var(--border-radius) !important; +} + +.form-control:focus { + border-color: var(--primary-color); + box-shadow: 0 0 0 0.25rem rgba(212, 163, 115, 0.25); +} + +body { + display: flex; + flex-direction: column; + min-height: 100vh; +} + +main { + flex: 1; +} + +/* Catalog & Product Page Specifics */ +.page-header { + padding-top: 2rem; + padding-bottom: 2rem; + text-align: center; + border-bottom: 1px solid #eee; + margin-bottom: 3rem; +} + +.page-header-product { + margin-top: -2rem; /* Pull up to align better with navbar */ + padding-bottom: 1rem; +} + +.product-image-single { + border-radius: var(--border-radius); + box-shadow: 0 8px 25px rgba(0,0,0,0.1); +} + +.product-card-link { + text-decoration: none; + color: inherit; +} + +.product-card-link:hover { + color: inherit; +} diff --git a/public_html/assets/js/main.js b/public_html/assets/js/main.js new file mode 100644 index 0000000..e69de29 diff --git a/public_html/cart.php b/public_html/cart.php new file mode 100644 index 0000000..62be63c --- /dev/null +++ b/public_html/cart.php @@ -0,0 +1,94 @@ + + +
+
+

Seu Carrinho

+

Revise seus itens e prossiga para o checkout.

+
+ + +
+

Seu carrinho está vazio.

+ Ver Produtos +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
ProdutoPreçoQuantidadeSubtotal
+ + <?= htmlspecialchars($item['name']) ?> + + + + R$ +
+ + + + +
+
R$ +
+ + + +
+
+
+
+
+
+
+
Resumo do Pedido
+
    +
  • + Subtotal + R$ +
  • +
  • + Total + R$ +
  • +
+ +
+
+
+
+ +
+ + \ No newline at end of file diff --git a/public_html/catalog.php b/public_html/catalog.php new file mode 100644 index 0000000..205f340 --- /dev/null +++ b/public_html/catalog.php @@ -0,0 +1,62 @@ +search($search_term); + $page_title = 'Resultados da Busca'; + $page_subtitle = 'Exibindo resultados para "' . htmlspecialchars($search_term) . '"'; +} else { + $products = $productController->index(); +} + +require_once __DIR__ . '/../app/Views/public/header.php'; +?> + + +
+ + + +
+ + + + + + +
+

Nenhum produto encontrado para sua busca.

+
+ +
+
+ + \ No newline at end of file diff --git a/public_html/checkout.php b/public_html/checkout.php new file mode 100644 index 0000000..a992b23 --- /dev/null +++ b/public_html/checkout.php @@ -0,0 +1,79 @@ + + +
+
+

Finalizar Compra

+

Preencha seus dados para concluir o pedido.

+
+ +
+
+
+
+

Informações de Contato

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

Pagamento

+
+

O pagamento será realizado na entrega.

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

Resumo do Pedido

+
+
+
    + +
  • +
    +
    + Quantidade: +
    + R$ +
  • + +
  • + Total + R$ +
  • +
+
+
+
+
+
+ + \ No newline at end of file diff --git a/public_html/contact.php b/public_html/contact.php new file mode 100644 index 0000000..12e1ed4 --- /dev/null +++ b/public_html/contact.php @@ -0,0 +1,153 @@ + + + + + + + Contato - beni cestas + + + + + + + + + + + + + + +
+
+

Entre em Contato

+

Adoraríamos ouvir de você. Preencha o formulário abaixo.

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

© beni cestas. Todos os direitos reservados.

+

Built with ❤️ by Flatlogic

+
+
+ + + + + + diff --git a/public_html/index.php b/public_html/index.php new file mode 100644 index 0000000..6ee3fd2 --- /dev/null +++ b/public_html/index.php @@ -0,0 +1,120 @@ + + + + + + + + + +
+
+

Nossos Produtos

+

Feitos com carinho para momentos especiais

+
+ + +
+ index(); + + if (!empty($products)): + foreach ($products as $product): + $image_path = !empty($product['image']) ? 'uploads/products/' . htmlspecialchars($product['image']) : 'https://picsum.photos/600/400?random=' . $product['id']; + ?> +
+
+ <?= htmlspecialchars($product['name']) ?> +
+
+

R$

+ Ver Detalhes +
+
+
+ +
+

Nenhum produto encontrado no momento.

+
+ +
+
+ + diff --git a/public_html/product.php b/public_html/product.php new file mode 100644 index 0000000..bf27a77 --- /dev/null +++ b/public_html/product.php @@ -0,0 +1,65 @@ +show($slug); + +require_once __DIR__ . '/../app/Views/public/header.php'; +?> + +
+
+ +
+ +
+
+ + <?= htmlspecialchars($product['name']) ?> +
+
+

+

Categoria:

+

R$

+

+ +
+ + + +
+
+ + +
+
+
+ +
+
+
+
+ + + +
+ + \ No newline at end of file diff --git a/public_html/thank-you.php b/public_html/thank-you.php new file mode 100644 index 0000000..ae2afdb --- /dev/null +++ b/public_html/thank-you.php @@ -0,0 +1,23 @@ + + +
+
+ +

Obrigado!

+

Seu pedido foi recebido com sucesso.

+ +

O número do seu pedido é #.

+ +

Você receberá um e-mail de confirmação em breve com os detalhes do seu pedido.

+
+ Continuar Comprando +
+
+ + \ No newline at end of file diff --git a/scripts/seed_admin.php b/scripts/seed_admin.php new file mode 100644 index 0000000..e69de29