Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
163c483584 |
62
README.md
Normal file
62
README.md
Normal 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.
|
||||||
0
app/Controllers/AdminController.php
Normal file
0
app/Controllers/AdminController.php
Normal file
50
app/Controllers/AuthController.php
Normal file
50
app/Controllers/AuthController.php
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
108
app/Controllers/CartController.php
Normal file
108
app/Controllers/CartController.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
68
app/Controllers/CategoryController.php
Normal file
68
app/Controllers/CategoryController.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
0
app/Controllers/HomeController.php
Normal file
0
app/Controllers/HomeController.php
Normal file
97
app/Controllers/OrderController.php
Normal file
97
app/Controllers/OrderController.php
Normal 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();
|
||||||
|
}
|
||||||
|
?>
|
||||||
153
app/Controllers/ProductController.php
Normal file
153
app/Controllers/ProductController.php
Normal 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
0
app/Models/Category.php
Normal file
27
app/Models/Order.php
Normal file
27
app/Models/Order.php
Normal 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
116
app/Models/Product.php
Normal 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
0
app/Models/User.php
Normal file
10
app/Views/admin/footer.php
Normal file
10
app/Views/admin/footer.php
Normal 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>
|
||||||
72
app/Views/admin/header.php
Normal file
72
app/Views/admin/header.php
Normal 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">
|
||||||
19
app/Views/public/footer.php
Normal file
19
app/Views/public/footer.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<!-- Footer -->
|
||||||
|
<footer class="py-4 mt-5">
|
||||||
|
<div class="container text-center">
|
||||||
|
<p class="mb-0">© 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>
|
||||||
80
app/Views/public/header.php
Normal file
80
app/Views/public/header.php
Normal 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
19
config/config.php
Normal 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
88
database/schema.sql
Normal 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
28
helpers/slugify.php
Normal 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
18
public_html/.htaccess
Normal 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>
|
||||||
147
public_html/admin/categories.php
Normal file
147
public_html/admin/categories.php
Normal 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>
|
||||||
47
public_html/admin/index.php
Normal file
47
public_html/admin/index.php
Normal 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';
|
||||||
|
?>
|
||||||
59
public_html/admin/login.php
Normal file
59
public_html/admin/login.php
Normal 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>
|
||||||
9
public_html/admin/logout.php
Normal file
9
public_html/admin/logout.php
Normal 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();
|
||||||
53
public_html/admin/orders.php
Normal file
53
public_html/admin/orders.php
Normal 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';
|
||||||
|
?>
|
||||||
228
public_html/admin/products.php
Normal file
228
public_html/admin/products.php
Normal 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>
|
||||||
185
public_html/assets/css/custom.css
Normal file
185
public_html/assets/css/custom.css
Normal 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;
|
||||||
|
}
|
||||||
0
public_html/assets/js/main.js
Normal file
0
public_html/assets/js/main.js
Normal file
94
public_html/cart.php
Normal file
94
public_html/cart.php
Normal 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
62
public_html/catalog.php
Normal 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
79
public_html/checkout.php
Normal 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
153
public_html/contact.php
Normal 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">© <?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
120
public_html/index.php
Normal 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
65
public_html/product.php
Normal 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
23
public_html/thank-you.php
Normal 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
0
scripts/seed_admin.php
Normal file
Loading…
x
Reference in New Issue
Block a user