Compare commits

..

1 Commits

Author SHA1 Message Date
Flatlogic Bot
163c483584 atual 2025-10-28 01:29:21 +00:00
36 changed files with 2339 additions and 0 deletions

62
README.md Normal file
View File

@ -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.

View File

View File

@ -0,0 +1,50 @@
<?php
require_once __DIR__ . '/../../db/config.php';
class AuthController {
public function login($email, $password) {
$pdo = db();
$stmt = $pdo->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();
}
}
}

View File

@ -0,0 +1,108 @@
<?php
session_start();
require_once __DIR__ . '/../Models/Product.php';
class CartController {
public function __construct() {
if (!isset($_SESSION['cart'])) {
$_SESSION['cart'] = [];
}
}
public function add() {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['product_id'])) {
$productId = (int)$_POST['product_id'];
$quantity = isset($_POST['quantity']) ? (int)$_POST['quantity'] : 1;
if ($quantity <= 0) {
header('Location: /product.php?slug=' . $_POST['product_slug'] . '&error=Invalid quantity');
exit();
}
$product = Product::findById($productId);
if ($product) {
if (isset($_SESSION['cart'][$productId])) {
$_SESSION['cart'][$productId]['quantity'] += $quantity;
} else {
$_SESSION['cart'][$productId] = [
'product_id' => $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;
}
}
?>

View File

@ -0,0 +1,68 @@
<?php
require_once __DIR__ . '/../../db/config.php';
class CategoryController {
public function index() {
$pdo = db();
$stmt = $pdo->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;
}
}

View File

View File

@ -0,0 +1,97 @@
<?php
session_start();
require_once __DIR__ . '/../Models/Order.php';
require_once __DIR__ . '/CartController.php';
require_once __DIR__ . '/../../mail/MailService.php';
class OrderController {
public function index() {
return Order::getAll();
}
public function checkout() {
public function checkout() {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: /cart.php');
exit();
}
$customerName = trim($_POST['name']);
$customerEmail = trim($_POST['email']);
if (empty($customerName) || empty($customerEmail) || !filter_var($customerEmail, FILTER_VALIDATE_EMAIL)) {
// Basic validation
header('Location: /checkout.php?error=Invalid data');
exit();
}
$cartItems = CartController::getCartContents();
$cartTotal = CartController::getCartTotal();
if (empty($cartItems)) {
header('Location: /cart.php');
exit();
}
// Create Order
$orderId = Order::create($customerName, $customerEmail, $cartTotal);
// Create Order Items
foreach ($cartItems as $item) {
Order::createOrderItem($orderId, $item['product_id'], $item['quantity'], $item['price']);
}
// Clear the cart
$_SESSION['cart'] = [];
// Send confirmation emails
$this->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 = "<h1>Obrigado pela sua compra, {$customerName}!</h1>";
$customerHtml .= "<p>Seu pedido #{$orderId} foi recebido e está sendo processado.</p>";
$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 = "<h1>Novo Pedido de {$customerName} ({$customerEmail})</h1>";
$adminHtml .= $this->formatOrderForEmail($cartItems, $cartTotal);
MailService::sendMail($adminEmail, $adminSubject, $adminHtml);
}
}
private function formatOrderForEmail($items, $total) {
$html = '<table border="1" cellpadding="10" cellspacing="0" width="100%">';
$html .= '<thead><tr><th>Produto</th><th>Qtd</th><th>Preço</th><th>Subtotal</th></tr></thead>';
$html .= '<tbody>';
foreach ($items as $item) {
$html .= sprintf('<tr><td>%s</td><td>%d</td><td>R$ %.2f</td><td>R$ %.2f</td></tr>',
htmlspecialchars($item['name']),
$item['quantity'],
$item['price'],
$item['price'] * $item['quantity']
);
}
$html .= '</tbody>';
$html .= sprintf('<tfoot><tr><th colspan="3" align="right">Total:</th><th>R$ %.2f</th></tr></tfoot>', $total);
$html .= '</table>';
return $html;
}
}
if (isset($_POST['action']) && $_POST['action'] === 'checkout') {
$orderController = new OrderController();
$orderController->checkout();
}
?>

View File

@ -0,0 +1,153 @@
<?php
require_once __DIR__ . '/../Models/Product.php';
require_once __DIR__ . '/../Models/Category.php';
require_once __DIR__ . '/../../helpers/slugify.php';
class ProductController {
public function index() {
return Product::getAll();
}
public function store() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$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;
}
$slug = slugify($name);
$image_filename = $this->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);
}
}

0
app/Models/Category.php Normal file
View File

27
app/Models/Order.php Normal file
View File

@ -0,0 +1,27 @@
<?php
require_once __DIR__ . '/../../db/config.php';
class Order {
public static function create($customerName, $customerEmail, $total) {
$pdo = db();
$sql = "INSERT INTO orders (customer_name, customer_email, total) VALUES (?, ?, ?)";
$stmt = $pdo->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);
}
}
?>

116
app/Models/Product.php Normal file
View File

@ -0,0 +1,116 @@
<?php
require_once __DIR__ . '/../../config/config.php';
require_once __DIR__ . '/../../db/config.php';
class Product {
/**
* Get all products with their category names.
*/
public static function getAll() {
$pdo = db();
$stmt = $pdo->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);
}
}

0
app/Models/User.php Normal file
View File

View File

@ -0,0 +1,10 @@
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script>
feather.replace();
</script>
</body>
</html>

View File

@ -0,0 +1,72 @@
<?php
require_once __DIR__ . '/../../../app/Controllers/AuthController.php';
AuthController::checkAuth();
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin - Beni Cestas</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.css" rel="stylesheet">
<link rel="stylesheet" href="<?php echo BASE_URL; ?>assets/css/custom.css">
<style>
body {
display: flex;
min-height: 100vh;
flex-direction: column;
}
.main-container {
display: flex;
flex: 1;
}
.sidebar {
width: 250px;
background-color: #f8f9fa;
}
.content {
flex: 1;
padding: 2rem;
}
</style>
</head>
<body>
<div class="main-container">
<nav class="sidebar p-3">
<h4 class="mb-4">Beni Cestas</h4>
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" href="<?php echo BASE_URL; ?>admin/index.php">
<i data-feather="home"></i> Dashboard
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?php echo BASE_URL; ?>admin/categories.php">
<i data-feather="tag"></i> Categorias
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="products.php">Produtos</a>
</li>
<li class="nav-item">
<a class="nav-link" href="orders.php">Pedidos</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<i data-feather="shopping-cart"></i> Pedidos
</a>
</li>
</ul>
<hr>
<div class="dropdown">
<a href="#" class="d-flex align-items-center text-decoration-none dropdown-toggle" id="dropdownUser" data-bs-toggle="dropdown" aria-expanded="false">
<strong><?php echo htmlspecialchars($_SESSION['user_name']); ?></strong>
</a>
<ul class="dropdown-menu" aria-labelledby="dropdownUser">
<li><a class="dropdown-item" href="<?php echo BASE_URL; ?>admin/logout.php">Sair</a></li>
</ul>
</div>
</nav>
<main class="content">

View File

@ -0,0 +1,19 @@
<!-- Footer -->
<footer class="py-4 mt-5">
<div class="container text-center">
<p class="mb-0">&copy; 2025 beni cestas. Todos os direitos reservados.</p>
<p class="text-muted small">Built with ❤️ by Flatlogic</p>
</div>
</footer>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<!-- Custom JS -->
<script src="assets/js/main.js"></script>
<script>
feather.replace();
</script>
</body>
</html>

View File

@ -0,0 +1,80 @@
<?php
require_once __DIR__ . '/../../app/Controllers/CartController.php';
$cart_item_count = CartController::getCartItemCount();
$page = basename($_SERVER['PHP_SELF']);
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>beni cestas - Cestas de Café da Manhã e Presentes</title>
<!-- SEO Meta Tags -->
<meta name="description" content="As melhores cestas de café da manhã, boxes e presentes para todas as ocasiões. Surpreenda quem você ama com a beni cestas.">
<meta name="keywords" content="cestas de café da manhã, presentes, caixas box, kits, beni cestas, cestas personalizadas, Built with Flatlogic Generator">
<meta name="author" content="beni cestas">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:title" content="beni cestas - Cestas e Presentes Especiais">
<meta property="og:description" content="Descubra nossas cestas de café da manhã, boxes e presentes criativos para momentos inesquecíveis.">
<meta property="og:image" content="">
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:title" content="beni cestas - Cestas e Presentes Especiais">
<meta property="twitter:description" content="Descubra nossas cestas de café da manhã, boxes e presentes criativos para momentos inesquecíveis.">
<meta property="twitter:image" content="">
<!-- Favicon -->
<!-- <link rel="icon" href="/favicon.ico"> -->
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom CSS -->
<link rel="stylesheet" href="assets/css/custom.css">
<!-- Feather Icons -->
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
</head>
<body>
<!-- Navbar -->
<nav class="navbar navbar-expand-lg fixed-top">
<div class="container">
<a class="navbar-brand" href="index.php">beni cestas</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<!-- Search Form -->
<form class="d-flex ms-auto me-3" action="catalog.php" method="GET" role="search">
<input class="form-control me-2" type="search" name="q" placeholder="Buscar produtos..." aria-label="Search">
<button class="btn btn-outline-primary" type="submit"><i data-feather="search"></i></button>
</form>
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link <?php echo ($page == 'index.php') ? 'active' : ''; ?>" href="index.php">Home</a>
</li>
<li class="nav-item">
<a class="nav-link <?php echo ($page == 'catalog.php') ? 'active' : ''; ?>" href="catalog.php">Catálogo</a>
</li>
<li class="nav-item">
<a class="nav-link <?php echo ($page == 'about.php') ? 'active' : ''; ?>" href="#">Sobre</a>
</li>
<li class="nav-item">
<a class="nav-link <?php echo ($page == 'contact.php') ? 'active' : ''; ?>" href="contact.php">Contato</a>
</li>
<li class="nav-item">
<a class="nav-link <?php echo ($page == 'cart.php') ? 'active' : ''; ?>" href="cart.php">
<i data-feather="shopping-cart"></i>
<span class="badge bg-primary rounded-pill ms-1"><?= $cart_item_count ?></span>
</a>
</li>
</ul>
</div>
</div>
</nav>

19
config/config.php Normal file
View File

@ -0,0 +1,19 @@
<?php
// Configuração do Banco de Dados
define('DB_HOST', 'sql.infinityfree.com'); // Ou o host fornecido pelo InfinityFree
define('DB_NAME', 'if0_35555555_beni_cestas'); // Substitua pelo seu
define('DB_USER', 'if0_35555555'); // Substitua pelo seu
define('DB_PASS', 'your_password'); // Substitua pelo seu
// URL Base do Site
define('BASE_URL', 'http://your-domain.com'); // Substitua pelo seu domínio
// Email do Administrador
define('EMAIL_ADMIN', 'your-email@example.com');
// Configurações de Segurança
define('CSRF_TOKEN_SECRET', 'your-long-random-secret-string-here');
// Outras Configurações
define('SITE_NAME', 'beni cestas');
?>

88
database/schema.sql Normal file
View File

@ -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');

28
helpers/slugify.php Normal file
View File

@ -0,0 +1,28 @@
<?php
function slugify($text) {
// replace non letter or digits by -
$text = preg_replace('~[\pL\d]+'u, '-', $text);
// transliterate
$text = iconv('utf-8', 'us-ascii//TRANSLIT', $text);
// remove unwanted characters
$text = preg_replace('~[^\-\w]+', '', $text);
// trim
$text = trim($text, '-');
// remove duplicate -
$text = preg_replace('~-+', '-', $text);
// lowercase
$text = strtolower($text);
if (empty($text)) {
return 'n-a';
}
return $text;
}

18
public_html/.htaccess Normal file
View File

@ -0,0 +1,18 @@
RewriteEngine On
RewriteBase /
# Redireciona todas as requisições para arquivos que não existem para o index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
# Protege a pasta de uploads contra execução de scripts
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule ^uploads/.*\.(php|php5|php7)$ - [F,L,NC]
</IfModule>
# Protege arquivos de configuração e outros arquivos sensíveis
<FilesMatch "\.(env|config\.php|md|sql|log)$">
Require all denied
</FilesMatch>

View File

@ -0,0 +1,147 @@
<?php
require_once __DIR__ . '/../../config/config.php';
require_once __DIR__ . '/../../app/Controllers/AuthController.php';
require_once __DIR__ . '/../../app/Controllers/CategoryController.php';
AuthController::isAdmin();
$categoryController = new CategoryController();
$alert = null;
// Handle POST requests
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action'])) {
switch ($_POST['action']) {
case 'create':
$result = $categoryController->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';
?>
<div class="container-fluid">
<h3 class="text-dark mb-4">Gerenciar Categorias</h3>
<?php if ($alert): ?>
<div class="alert alert-<?php echo $alert['type']; ?> alert-dismissible fade show" role="alert">
<?php echo htmlspecialchars($alert['message']); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<div class="row">
<div class="col-md-8">
<div class="card shadow">
<div class="card-header py-3">
<p class="text-primary m-0 fw-bold">Lista de Categorias</p>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered" id="dataTable">
<thead>
<tr>
<th>Nome</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
<?php foreach ($categories as $category): ?>
<tr>
<td><?php echo htmlspecialchars($category['name']); ?></td>
<td>
<button class="btn btn-sm btn-primary edit-btn" data-id="<?php echo $category['id']; ?>" data-name="<?php echo htmlspecialchars($category['name']); ?>" data-bs-toggle="modal" data-bs-target="#editModal">Editar</button>
<form action="" method="POST" class="d-inline" onsubmit="return confirm('Tem certeza que deseja excluir esta categoria?');">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?php echo $category['id']; ?>">
<button type="submit" class="btn btn-sm btn-danger">Excluir</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card shadow">
<div class="card-header py-3">
<p class="text-primary m-0 fw-bold">Adicionar Nova Categoria</p>
</div>
<div class="card-body">
<form action="" method="POST">
<input type="hidden" name="action" value="create">
<div class="mb-3">
<label for="name" class="form-label">Nome da Categoria</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<button type="submit" class="btn btn-primary">Adicionar</button>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Edit Modal -->
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editModalLabel">Editar Categoria</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="" method="POST">
<div class="modal-body">
<input type="hidden" name="action" value="update">
<input type="hidden" id="edit-id" name="id">
<div class="mb-3">
<label for="edit-name" class="form-label">Nome da Categoria</label>
<input type="text" class="form-control" id="edit-name" name="name" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fechar</button>
<button type="submit" class="btn btn-primary">Salvar Alterações</button>
</div>
</form>
</div>
</div>
</div>
<?php
require_once __DIR__ . '/../../app/Views/admin/footer.php';
?>
<script>
document.addEventListener('DOMContentLoaded', function () {
var editModal = document.getElementById('editModal');
editModal.addEventListener('show.bs.modal', function (event) {
var button = event.relatedTarget;
var id = button.getAttribute('data-id');
var name = button.getAttribute('data-name');
var modalTitle = editModal.querySelector('.modal-title');
var modalBodyInputId = editModal.querySelector('#edit-id');
var modalBodyInputName = editModal.querySelector('#edit-name');
modalBodyInputId.value = id;
modalBodyInputName.value = name;
});
});
</script>

View File

@ -0,0 +1,47 @@
<?php
require_once __DIR__ . '/../../config/config.php';
require_once __DIR__ . '/../../app/Views/admin/header.php';
?>
<div class="container-fluid">
<div class="d-sm-flex justify-content-between align-items-center mb-4">
<h3 class="text-dark mb-0">Dashboard</h3>
</div>
<?php if(isset($_GET['error']) && $_GET['error'] === 'unauthorized'): ?>
<div class="alert alert-danger">Você não tem permissão para acessar essa página.</div>
<?php endif; ?>
<div class="row">
<div class="col-md-6 col-xl-3 mb-4">
<div class="card shadow border-start-primary py-2">
<div class="card-body">
<div class="row align-items-center no-gutters">
<div class="col me-2">
<div class="text-uppercase text-primary fw-bold text-xs mb-1"><span>Pedidos (Mensal)</span></div>
<div class="text-dark fw-bold h5 mb-0"><span>40</span></div>
</div>
<div class="col-auto"><i class="fas fa-calendar fa-2x text-gray-300"></i></div>
</div>
</div>
</div>
</div>
<div class="col-md-6 col-xl-3 mb-4">
<div class="card shadow border-start-success py-2">
<div class="card-body">
<div class="row align-items-center no-gutters">
<div class="col me-2">
<div class="text-uppercase text-success fw-bold text-xs mb-1"><span>Faturamento (Mensal)</span></div>
<div class="text-dark fw-bold h5 mb-0"><span>R$215,000</span></div>
</div>
<div class="col-auto"><i class="fas fa-dollar-sign fa-2x text-gray-300"></i></div>
</div>
</div>
</div>
</div>
</div>
</div>
<?php
require_once __DIR__ . '/../../app/Views/admin/footer.php';
?>

View File

@ -0,0 +1,59 @@
<?php
session_start();
require_once __DIR__ . '/../../config/config.php';
// If user is already logged in, redirect to admin dashboard
if (isset($_SESSION['user_id']) && isset($_SESSION['role']) && $_SESSION['role'] === 'admin') {
header("Location: " . BASE_URL . "admin/index.php");
exit();
}
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
require_once __DIR__ . '/../../app/Controllers/AuthController.php';
$authController = new AuthController();
$result = $authController->login($_POST['email'], $_POST['password']);
if ($result['success']) {
header("Location: " . BASE_URL . "admin/index.php");
exit();
} else {
$error = $result['message'];
}
}
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Login - Beni Cestas</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="<?php echo BASE_URL; ?>assets/css/custom.css">
</head>
<body>
<div class="container vh-100 d-flex justify-content-center align-items-center">
<div class="card shadow" style="width: 22rem;">
<div class="card-body">
<h3 class="card-title text-center mb-4">Admin Login</h3>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<form action="login.php" method="POST">
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Senha</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Login</button>
</div>
</form>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
<?php
require_once __DIR__ . '/../../config/config.php';
require_once __DIR__ . '/../../app/Controllers/AuthController.php';
$auth = new AuthController();
$auth->logout();
header("Location: " . BASE_URL . "admin/login.php");
exit();

View File

@ -0,0 +1,53 @@
<?php
require_once __DIR__ . '/../../app/Controllers/AuthController.php';
require_once __DIR__ . '/../../app/Controllers/OrderController.php';
AuthController::isAdmin();
$orderController = new OrderController();
$orders = $orderController->index();
require_once __DIR__ . '/../../app/Views/admin/header.php';
?>
<div class="container-fluid">
<h1 class="h3 mb-4 text-gray-800">Pedidos</h1>
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Todos os Pedidos</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered" id="dataTable" width="100%" cellspacing="0">
<thead>
<tr>
<th>ID do Pedido</th>
<th>Cliente</th>
<th>Email</th>
<th>Total</th>
<th>Status</th>
<th>Data</th>
</tr>
</thead>
<tbody>
<?php foreach ($orders as $order): ?>
<tr>
<td><?= $order['id'] ?></td>
<td><?= htmlspecialchars($order['customer_name']) ?></td>
<td><?= htmlspecialchars($order['customer_email']) ?></td>
<td>R$ <?= number_format($order['total'], 2, ',', '.') ?></td>
<td><span class="badge bg-secondary"><?= htmlspecialchars($order['status']) ?></span></td>
<td><?= date('d/m/Y H:i', strtotime($order['created_at'])) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?php
require_once __DIR__ . '/../../app/Views/admin/footer.php';
?>

View File

@ -0,0 +1,228 @@
<?php
require_once __DIR__ . '/../../app/Controllers/AuthController.php';
require_once __DIR__ . '/../../app/Controllers/ProductController.php';
AuthController::isAdmin();
$productController = new ProductController();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['add_product'])) {
$productController->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';
?>
<div class="container-fluid">
<h1 class="mt-4">Manage Products</h1>
<?php if (isset($_SESSION['success_message'])): ?>
<div class="alert alert-success">
<?php echo $_SESSION['success_message']; unset($_SESSION['success_message']); ?>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['error_message'])): ?>
<div class="alert alert-danger">
<?php echo $_SESSION['error_message']; unset($_SESSION['error_message']); ?>
</div>
<?php endif; ?>
<!-- Add Product Form -->
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-plus-circle me-1"></i>
Add New Product
</div>
<div class="card-body">
<form action="products.php" method="POST" enctype="multipart/form-data">
<div class="row mb-3">
<div class="col-md-6">
<label for="name" class="form-label">Product Name</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="col-md-6">
<label for="category_id" class="form-label">Category</label>
<select class="form-select" id="category_id" name="category_id" required>
<?php foreach ($categories as $category): ?>
<option value="<?php echo $category['id']; ?>"><?php echo htmlspecialchars($category['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" name="description" rows="3"></textarea>
</div>
<div class="row mb-3">
<div class="col-md-4">
<label for="price" class="form-label">Price</label>
<input type="number" step="0.01" class="form-control" id="price" name="price" required>
</div>
<div class="col-md-4">
<label for="stock" class="form-label">Stock</label>
<input type="number" class="form-control" id="stock" name="stock" required>
</div>
<div class="col-md-4">
<label for="image" class="form-label">Image</label>
<input type="file" class="form-control" id="image" name="image">
</div>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" value="1" id="active" name="active" checked>
<label class="form-check-label" for="active">
Active
</label>
</div>
<button type="submit" name="add_product" class="btn btn-primary">Add Product</button>
</form>
</div>
</div>
<!-- Products Table -->
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-list me-1"></i>
Existing Products
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered" id="productsTable" width="100%" cellspacing="0">
<thead>
<tr>
<th>Image</th>
<th>Name</th>
<th>Category</th>
<th>Price</th>
<th>Stock</th>
<th>Active</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($products as $product): ?>
<tr>
<td>
<?php if ($product['image']): ?>
<img src="../uploads/products/<?php echo htmlspecialchars($product['image']); ?>" alt="<?php echo htmlspecialchars($product['name']); ?>" width="50">
<?php endif; ?>
</td>
<td><?php echo htmlspecialchars($product['name']); ?></td>
<td><?php echo htmlspecialchars($product['category_name']); ?></td>
<td><?php echo htmlspecialchars($product['price']); ?></td>
<td><?php echo htmlspecialchars($product['stock']); ?></td>
<td><?php echo $product['active'] ? 'Yes' : 'No'; ?></td>
<td>
<button class="btn btn-sm btn-primary edit-btn" data-bs-toggle="modal" data-bs-target="#editProductModal"
data-id="<?php echo $product['id']; ?>"
data-name="<?php echo htmlspecialchars($product['name']); ?>"
data-category_id="<?php echo $product['category_id']; ?>"
data-description="<?php echo htmlspecialchars($product['description']); ?>"
data-price="<?php echo $product['price']; ?>"
data-stock="<?php echo $product['stock']; ?>"
data-active="<?php echo $product['active']; ?>">
<i class="fas fa-edit"></i>
</button>
<form action="products.php" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this product?');">
<input type="hidden" name="delete_id" value="<?php echo $product['id']; ?>">
<button type="submit" class="btn btn-sm btn-danger"><i class="fas fa-trash"></i></button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Edit Product Modal -->
<div class="modal fade" id="editProductModal" tabindex="-1" aria-labelledby="editProductModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form action="products.php" method="POST" enctype="multipart/form-data">
<div class="modal-header">
<h5 class="modal-title" id="editProductModalLabel">Edit Product</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<input type="hidden" name="id" id="edit_id">
<div class="row mb-3">
<div class="col-md-6">
<label for="edit_name" class="form-label">Product Name</label>
<input type="text" class="form-control" id="edit_name" name="name" required>
</div>
<div class="col-md-6">
<label for="edit_category_id" class="form-label">Category</label>
<select class="form-select" id="edit_category_id" name="category_id" required>
<?php foreach ($categories as $category): ?>
<option value="<?php echo $category['id']; ?>"><?php echo htmlspecialchars($category['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="mb-3">
<label for="edit_description" class="form-label">Description</label>
<textarea class="form-control" id="edit_description" name="description" rows="3"></textarea>
</div>
<div class="row mb-3">
<div class="col-md-4">
<label for="edit_price" class="form-label">Price</label>
<input type="number" step="0.01" class="form-control" id="edit_price" name="price" required>
</div>
<div class="col-md-4">
<label for="edit_stock" class="form-label">Stock</label>
<input type="number" class="form-control" id="edit_stock" name="stock" required>
</div>
<div class="col-md-4">
<label for="edit_image" class="form-label">New Image (optional)</label>
<input type="file" class="form-control" id="edit_image" name="image">
</div>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" value="1" id="edit_active" name="active">
<label class="form-check-label" for="edit_active">
Active
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="submit" name="update_product" class="btn btn-primary">Save changes</button>
</div>
</form>
</div>
</div>
</div>
<?php include __DIR__ . '/../../app/Views/admin/footer.php'; ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
const editProductModal = document.getElementById('editProductModal');
editProductModal.addEventListener('show.bs.modal', function (event) {
const button = event.relatedTarget;
const modal = this;
modal.querySelector('#edit_id').value = button.dataset.id;
modal.querySelector('#edit_name').value = button.dataset.name;
modal.querySelector('#edit_category_id').value = button.dataset.category_id;
modal.querySelector('#edit_description').value = button.dataset.description;
modal.querySelector('#edit_price').value = button.dataset.price;
modal.querySelector('#edit_stock').value = button.dataset.stock;
modal.querySelector('#edit_active').checked = button.dataset.active == 1;
});
});
</script>

View File

@ -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;
}

View File

94
public_html/cart.php Normal file
View File

@ -0,0 +1,94 @@
<?php
require_once __DIR__ . '/../app/Controllers/CartController.php';
require_once __DIR__ . '/../app/Views/public/header.php';
$cart_items = CartController::getCartContents();
$cart_total = CartController::getCartTotal();
?>
<main class="container my-5">
<div class="page-header-cart">
<h1 class="display-4">Seu Carrinho</h1>
<p class="lead">Revise seus itens e prossiga para o checkout.</p>
</div>
<?php if (empty($cart_items)): ?>
<div class="alert alert-info text-center">
<p>Seu carrinho está vazio.</p>
<a href="catalog.php" class="btn btn-primary">Ver Produtos</a>
</div>
<?php else: ?>
<div class="row">
<div class="col-lg-8">
<div class="table-responsive">
<table class="table align-middle">
<thead>
<tr>
<th scope="col" colspan="2">Produto</th>
<th scope="col">Preço</th>
<th scope="col">Quantidade</th>
<th scope="col">Subtotal</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<?php foreach ($cart_items as $item): ?>
<tr>
<td style="width: 100px;">
<a href="product.php?slug=<?= htmlspecialchars($item['slug']) ?>">
<img src="uploads/products/<?= htmlspecialchars($item['image']) ?>" alt="<?= htmlspecialchars($item['name']) ?>" class="img-fluid rounded" style="max-height: 75px;">
</a>
</td>
<td>
<a href="product.php?slug=<?= htmlspecialchars($item['slug']) ?>" class="text-decoration-none text-dark fw-bold"><?= htmlspecialchars($item['name']) ?></a>
</td>
<td>R$ <?= number_format($item['price'], 2, ',', '.') ?></td>
<td>
<form action="/app/Controllers/CartController.php" method="POST" class="d-flex">
<input type="hidden" name="action" value="update">
<input type="hidden" name="product_id" value="<?= $item['product_id'] ?>">
<input type="number" name="quantity" class="form-control form-control-sm" value="<?= $item['quantity'] ?>" min="1" style="width: 70px;">
<button type="submit" class="btn btn-outline-secondary btn-sm ms-2">Atualizar</button>
</form>
</td>
<td>R$ <?= number_format($item['price'] * $item['quantity'], 2, ',', '.') ?></td>
<td>
<form action="/app/Controllers/CartController.php" method="POST">
<input type="hidden" name="action" value="remove">
<input type="hidden" name="product_id" value="<?= $item['product_id'] ?>">
<button type="submit" class="btn btn-danger btn-sm"><i data-feather="trash-2"></i></button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<div class="col-lg-4">
<div class="card shadow-sm">
<div class="card-body">
<h5 class="card-title">Resumo do Pedido</h5>
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-center">
Subtotal
<span>R$ <?= number_format($cart_total, 2, ',', '.') ?></span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center fw-bold">
Total
<span>R$ <?= number_format($cart_total, 2, ',', '.') ?></span>
</li>
</ul>
<div class="d-grid mt-3">
<a href="checkout.php" class="btn btn-primary">Finalizar Compra</a>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
</main>
<?php
require_once __DIR__ . '/../app/Views/public/footer.php';
?>

62
public_html/catalog.php Normal file
View File

@ -0,0 +1,62 @@
<?php
require_once __DIR__ . '/../app/Controllers/ProductController.php';
$productController = new ProductController();
$search_term = $_GET['q'] ?? null;
$page_title = 'Catálogo de Produtos';
$page_subtitle = 'Explore nossa coleção completa de cestas e presentes.';
if ($search_term) {
$products = $productController->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';
?>
<!-- Main Content -->
<main class="container my-5">
<div class="page-header">
<h1 class="display-4"><?= $page_title ?></h1>
<p class="lead text-muted"><?= $page_subtitle ?></p>
</div>
<!-- Products Grid -->
<section class="row mt-5">
<?php if (!empty($products)): ?>
<?php foreach ($products as $product): ?>
<?php
$image_path = !empty($product['image'])
? 'uploads/products/' . htmlspecialchars($product['image'])
: 'https://picsum.photos/600/400?random=' . $product['id'];
?>
<div class="col-lg-4 col-md-6 mb-4">
<div class="product-card">
<a href="product.php?slug=<?= htmlspecialchars($product['slug']) ?>" class="product-card-link">
<img src="<?= $image_path ?>" class="card-img-top" alt="<?= htmlspecialchars($product['name']) ?>">
<div class="card-body p-4">
<h5 class="card-title"><?= htmlspecialchars($product['name']) ?></h5>
<p class="card-price mb-3">R$ <?= number_format($product['price'], 2, ',', '.') ?></p>
<div class="d-grid">
<span class="btn btn-primary">Ver Detalhes</span>
</div>
</div>
</a>
</div>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="col-12">
<p class="text-center text-muted">Nenhum produto encontrado para sua busca.</p>
</div>
<?php endif; ?>
</section>
</main>
<?php
require_once __DIR__ . '/../app/Views/public/footer.php';
?>

79
public_html/checkout.php Normal file
View File

@ -0,0 +1,79 @@
<?php
require_once __DIR__ . '/../app/Controllers/CartController.php';
require_once __DIR__ . '/../app/Views/public/header.php';
$cart_items = CartController::getCartContents();
$cart_total = CartController::getCartTotal();
if (empty($cart_items)) {
header('Location: /cart.php');
exit();
}
?>
<main class="container my-5">
<div class="page-header-checkout">
<h1 class="display-4">Finalizar Compra</h1>
<p class="lead">Preencha seus dados para concluir o pedido.</p>
</div>
<div class="row">
<div class="col-lg-7">
<div class="card shadow-sm mb-4">
<div class="card-header">
<h4>Informações de Contato</h4>
</div>
<div class="card-body">
<form action="/app/Controllers/OrderController.php" method="POST">
<input type="hidden" name="action" value="checkout">
<div class="mb-3">
<label for="name" class="form-label">Nome Completo</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">E-mail</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<hr class="my-4">
<h4 class="mb-3">Pagamento</h4>
<div class="my-3">
<p class="text-muted">O pagamento será realizado na entrega.</p>
</div>
<hr class="my-4">
<div class="d-grid">
<button class="btn btn-primary btn-lg" type="submit">Finalizar Pedido</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-lg-5">
<div class="card shadow-sm">
<div class="card-header">
<h4>Resumo do Pedido</h4>
</div>
<div class="card-body">
<ul class="list-group list-group-flush">
<?php foreach ($cart_items as $item): ?>
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
<h6 class="my-0"><?= htmlspecialchars($item['name']) ?></h6>
<small class="text-muted">Quantidade: <?= $item['quantity'] ?></small>
</div>
<span class="text-muted">R$ <?= number_format($item['price'] * $item['quantity'], 2, ',', '.') ?></span>
</li>
<?php endforeach; ?>
<li class="list-group-item d-flex justify-content-between fw-bold">
<span>Total</span>
<strong>R$ <?= number_format($cart_total, 2, ',', '.') ?></strong>
</li>
</ul>
</div>
</div>
</div>
</div>
</main>
<?php
require_once __DIR__ . '/../app/Views/public/footer.php';
?>

153
public_html/contact.php Normal file
View File

@ -0,0 +1,153 @@
<?php
session_start();
require_once __DIR__ . '/../mail/MailService.php';
require_once __DIR__ . '/../config/config.php';
$feedback = [];
$success = false;
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$name = trim(filter_input(INPUT_POST, 'name', FILTER_SANITIZE_STRING));
$email = trim(filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL));
$message = trim(filter_input(INPUT_POST, 'message', FILTER_SANITIZE_STRING));
if (empty($name)) {
$feedback['name'] = 'Por favor, preencha seu nome.';
}
if (!$email) {
$feedback['email'] = 'Por favor, insira um e-mail válido.';
}
if (empty($message)) {
$feedback['message'] = 'Por favor, escreva sua mensagem.';
}
if (empty($feedback)) {
$to = defined('EMAIL_ADMIN') ? EMAIL_ADMIN : 'admin@example.com';
$subject = "Nova mensagem do formulário de contato - beni cestas";
$result = MailService::sendContactMessage($name, $email, $message, $to, $subject);
if ($result['success']) {
$success = true;
} else {
$feedback['general'] = "Ocorreu um erro ao enviar sua mensagem. Tente novamente mais tarde. Detalhe: " . htmlspecialchars($result['error']);
}
}
}
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contato - beni cestas</title>
<meta name="description" content="Entre em contato com a beni cestas para dúvidas, sugestões ou encomendas. Estamos prontos para atender você.">
<meta name="keywords" content="contato, beni cestas, encomendas, dúvidas, sugestões, Built with Flatlogic Generator">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css">
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
</head>
<body>
<!-- Navbar -->
<nav class="navbar navbar-expand-lg fixed-top">
<div class="container">
<a class="navbar-brand" href="index.php">beni cestas</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="index.php">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Catálogo</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Sobre</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="contact.php">Contato</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<i data-feather="shopping-cart"></i>
<span class="badge bg-primary rounded-pill ms-1">0</span>
</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- Main Content -->
<main class="container my-5 pt-5">
<div class="contact-header text-center">
<h1 class="display-5">Entre em Contato</h1>
<p class="lead text-muted">Adoraríamos ouvir de você. Preencha o formulário abaixo.</p>
</div>
<div class="row justify-content-center mt-5">
<div class="col-lg-8">
<?php if ($success): ?>
<div class="alert alert-success" role="alert">
<strong>Obrigado!</strong> Sua mensagem foi enviada com sucesso. Responderemos em breve.
</div>
<?php endif; ?>
<?php if (!empty($feedback['general'])): ?>
<div class="alert alert-danger" role="alert">
<?php echo $feedback['general']; ?>
</div>
<?php endif; ?>
<?php if (!$success): ?>
<form action="contact.php" method="POST" class="contact-form">
<div class="mb-3">
<label for="name" class="form-label">Nome</label>
<input type="text" class="form-control <?php echo isset($feedback['name']) ? 'is-invalid' : ''; ?>" id="name" name="name" value="<?php echo isset($_POST['name']) ? htmlspecialchars($_POST['name']) : ''; ?>" required>
<?php if (isset($feedback['name'])): ?>
<div class="invalid-feedback"><?php echo $feedback['name']; ?></div>
<?php endif; ?>
</div>
<div class="mb-3">
<label for="email" class="form-label">E-mail</label>
<input type="email" class="form-control <?php echo isset($feedback['email']) ? 'is-invalid' : ''; ?>" id="email" name="email" value="<?php echo isset($_POST['email']) ? htmlspecialchars($_POST['email']) : ''; ?>" required>
<?php if (isset($feedback['email'])): ?>
<div class="invalid-feedback"><?php echo $feedback['email']; ?></div>
<?php endif; ?>
</div>
<div class="mb-3">
<label for="message" class="form-label">Mensagem</label>
<textarea class="form-control <?php echo isset($feedback['message']) ? 'is-invalid' : ''; ?>" id="message" name="message" rows="5" required><?php echo isset($_POST['message']) ? htmlspecialchars($_POST['message']) : ''; ?></textarea>
<?php if (isset($feedback['message'])): ?>
<div class="invalid-feedback"><?php echo $feedback['message']; ?></div>
<?php endif; ?>
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary btn-lg">Enviar Mensagem</button>
</div>
</form>
<?php endif; ?>
</div>
</div>
</main>
<!-- Footer -->
<footer class="py-4 mt-auto">
<div class="container text-center">
<p class="mb-0">&copy; <?php echo date("Y"); ?> beni cestas. Todos os direitos reservados.</p>
<p class="text-muted small">Built with ❤️ by Flatlogic</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js"></script>
<script>
feather.replace();
</script>
</body>
</html>

120
public_html/index.php Normal file
View File

@ -0,0 +1,120 @@
<?php require_once __DIR__ . '/../app/Views/public/header.php'; ?>
<!-- Header Carousel -->
<header id="heroCarousel" class="carousel slide" data-bs-ride="carousel">
<div class="carousel-indicators">
<button type="button" data-bs-target="#heroCarousel" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Slide 1"></button>
<button type="button" data-bs-target="#heroCarousel" data-bs-slide-to="1" aria-label="Slide 2"></button>
<button type="button" data-bs-target="#heroCarousel" data-bs-slide-to="2" aria-label="Slide 3"></button>
</div>
<div class="carousel-inner">
<div class="carousel-item active" style="background-image: url('https://picsum.photos/1920/1080?random=1&blur=1')">
<div class="carousel-caption d-none d-md-block">
<h5>Presentes que Criam Memórias</h5>
<p>As mais belas cestas para surpreender em qualquer ocasião.</p>
</div>
</div>
<div class="carousel-item" style="background-image: url('https://picsum.photos/1920/1080?random=2&blur=1')">
<div class="carousel-caption d-none d-md-block">
<h5>Café da Manhã Especiais</h5>
<p>Comece o dia de quem você ama com um sorriso.</p>
</div>
</div>
<div class="carousel-item" style="background-image: url('https://picsum.photos/1920/1080?random=3&blur=1')">
<div class="carousel-caption d-none d-md-block">
<h5>Boxes e Kits Personalizados</h5>
<p>Monte um presente único e com a sua cara.</p>
</div>
</div>
</div>
<button class="carousel-control-prev" type="button" data-bs-target="#heroCarousel" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#heroCarousel" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
</header>
<!-- Header Carousel -->
<header id="heroCarousel" class="carousel slide" data-bs-ride="carousel">
<div class="carousel-indicators">
<button type="button" data-bs-target="#heroCarousel" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Slide 1"></button>
<button type="button" data-bs-target="#heroCarousel" data-bs-slide-to="1" aria-label="Slide 2"></button>
<button type="button" data-bs-target="#heroCarousel" data-bs-slide-to="2" aria-label="Slide 3"></button>
</div>
<div class="carousel-inner">
<div class="carousel-item active" style="background-image: url('https://picsum.photos/1920/1080?random=1&blur=1')">
<div class="carousel-caption d-none d-md-block">
<h5>Presentes que Criam Memórias</h5>
<p>As mais belas cestas para surpreender em qualquer ocasião.</p>
</div>
</div>
<div class="carousel-item" style="background-image: url('https://picsum.photos/1920/1080?random=2&blur=1')">
<div class="carousel-caption d-none d-md-block">
<h5>Café da Manhã Especiais</h5>
<p>Comece o dia de quem você ama com um sorriso.</p>
</div>
</div>
<div class="carousel-item" style="background-image: url('https://picsum.photos/1920/1080?random=3&blur=1')">
<div class="carousel-caption d-none d-md-block">
<h5>Boxes e Kits Personalizados</h5>
<p>Monte um presente único e com a sua cara.</p>
</div>
</div>
</div>
<button class="carousel-control-prev" type="button" data-bs-target="#heroCarousel" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#heroCarousel" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
</header>
<!-- Main Content -->
<main class="container my-5">
<section class="text-center mb-5">
<h2 class="display-5">Nossos Produtos</h2>
<p class="lead text-muted">Feitos com carinho para momentos especiais</p>
</section>
<!-- Products Grid -->
<section class="row">
<?php
if (!isset($productController)) {
require_once __DIR__ . '/../app/Controllers/ProductController.php';
$productController = new ProductController();
}
$products = $productController->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'];
?>
<div class="col-lg-4 col-md-6 mb-4">
<div class="product-card">
<img src="<?= $image_path ?>" class="card-img-top" alt="<?= htmlspecialchars($product['name']) ?>">
<div class="card-body p-4">
<h5 class="card-title"><?= htmlspecialchars($product['name']) ?></h5>
<p class="card-price mb-3">R$ <?= number_format($product['price'], 2, ',', '.') ?></p>
<a href="product.php?slug=<?= htmlspecialchars($product['slug']) ?>" class="btn btn-primary">Ver Detalhes</a>
</div>
</div>
</div>
<?php
endforeach;
else:
?>
<div class="col-12">
<p class="text-center text-muted">Nenhum produto encontrado no momento.</p>
</div>
<?php
endif;
?>
</section>
</main>
<?php require_once __DIR__ . '/../app/Views/public/footer.php'; ?>

65
public_html/product.php Normal file
View File

@ -0,0 +1,65 @@
<?php
require_once __DIR__ . '/../app/Controllers/ProductController.php';
$slug = $_GET['slug'] ?? null;
if (!$slug) {
header("Location: index.php");
exit();
}
$pc = new ProductController();
$product = $pc->show($slug);
require_once __DIR__ . '/../app/Views/public/header.php';
?>
<main class="container my-5">
<div class="page-header-product">
<!-- Breadcrumb could go here -->
</div>
<?php if ($product): ?>
<section class="row">
<div class="col-md-6">
<?php
$image_path = !empty($product['image'])
? 'uploads/products/' . htmlspecialchars($product['image'])
: 'https://picsum.photos/800/600?random=' . $product['id'];
?>
<img src="<?= $image_path ?>" class="img-fluid rounded product-image-single" alt="<?= htmlspecialchars($product['name']) ?>">
</div>
<div class="col-md-6">
<h1 class="display-5"><?= htmlspecialchars($product['name']) ?></h1>
<p class="text-muted">Categoria: <?= htmlspecialchars($product['category_name']) ?></p>
<p class="card-price display-6 mb-3">R$ <?= number_format($product['price'], 2, ',', '.') ?></p>
<p class="lead"><?= nl2br(htmlspecialchars($product['description'])) ?></p>
<form action="/app/Controllers/CartController.php" method="POST">
<input type="hidden" name="action" value="add">
<input type="hidden" name="product_id" value="<?= $product['id'] ?>">
<input type="hidden" name="product_slug" value="<?= $product['slug'] ?>">
<div class="row">
<div class="col-md-4">
<label for="quantity" class="form-label">Quantidade</label>
<input type="number" name="quantity" id="quantity" class="form-control" value="1" min="1">
</div>
</div>
<div class="d-grid gap-2 d-md-block mt-4">
<button class="btn btn-primary btn-lg" type="submit">Adicionar ao Carrinho</button>
</div>
</form>
</div>
</section>
<?php else: ?>
<div class="alert alert-danger text-center" role="alert">
<h4 class="alert-heading">Oops!</h4>
<p>O produto que você está procurando não foi encontrado.</p>
<hr>
<a href="catalog.php" class="btn btn-secondary">Voltar ao Catálogo</a>
</div>
<?php endif; ?>
</main>
<?php
require_once __DIR__ . '/../app/Views/public/footer.php';
?>

23
public_html/thank-you.php Normal file
View File

@ -0,0 +1,23 @@
<?php
require_once __DIR__ . '/../app/Views/public/header.php';
$order_id = $_GET['order_id'] ?? null;
?>
<main class="container my-5 text-center">
<div class="py-5">
<i data-feather="check-circle" class="text-success" style="width: 100px; height: 100px;"></i>
<h1 class="display-4 mt-4">Obrigado!</h1>
<p class="lead">Seu pedido foi recebido com sucesso.</p>
<?php if ($order_id): ?>
<p>O número do seu pedido é <strong>#<?= htmlspecialchars($order_id) ?></strong>.</p>
<?php endif; ?>
<p>Você receberá um e-mail de confirmação em breve com os detalhes do seu pedido.</p>
<hr class="my-4">
<a href="catalog.php" class="btn btn-primary">Continuar Comprando</a>
</div>
</main>
<?php
require_once __DIR__ . '/../app/Views/public/footer.php';
?>

0
scripts/seed_admin.php Normal file
View File