Spaces:
Running
Running
Upload 30 files
Browse files- DISABLE_RATE_LIMIT.txt +0 -0
- README.md +27 -10
- Unconfirmed 642953.crdownload +70 -0
- admin_login.php +66 -0
- admin_orders.php +76 -0
- auth.php +27 -0
- backend.php +324 -0
- cart.html +155 -0
- cebelarstvo_cigoj (1).db +0 -0
- cebelarstvo_cigoj.db +0 -0
- checkout.php +337 -0
- composer.json +5 -0
- composer.lock +82 -0
- config.php +80 -0
- customers.php +25 -0
- index.html +390 -19
- login.php +211 -0
- logout.php +12 -0
- orders.php +114 -0
- process_order.php +70 -0
- products.php +61 -0
- prompts.txt +10 -0
- replit.md +70 -0
- reports.php +45 -0
- require_auth.php +93 -0
- schema.sql +70 -0
- style.css +28 -28
- thank_you.php +55 -0
- tmp_update_admin_password (1).php +11 -0
- tmp_update_admin_password.php +14 -0
DISABLE_RATE_LIMIT.txt
ADDED
|
File without changes
|
README.md
CHANGED
|
@@ -1,10 +1,27 @@
|
|
| 1 |
-
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
-
sdk: static
|
| 7 |
-
pinned: false
|
| 8 |
-
---
|
| 9 |
-
|
| 10 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Aa21123a
|
| 3 |
+
emoji: 🐨
|
| 4 |
+
colorFrom: red
|
| 5 |
+
colorTo: gray
|
| 6 |
+
sdk: static
|
| 7 |
+
pinned: false
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
| 11 |
+
"# cbc"
|
| 12 |
+
|
| 13 |
+
# Čebelarstvo Cigoj Admin Panel
|
| 14 |
+
|
| 15 |
+
## Admin Usage
|
| 16 |
+
- Login: `/admin_login.php` (username: `admin`, password: your set password)
|
| 17 |
+
- Dashboard: `/backend.php` (requires login)
|
| 18 |
+
- Logout: `/logout.php` (clears session)
|
| 19 |
+
|
| 20 |
+
## Troubleshooting
|
| 21 |
+
- If you get redirected to login repeatedly, check your database for the correct admin user and role.
|
| 22 |
+
- If preview/iframe blocks navigation, use the "Open dashboard in new tab" button after login.
|
| 23 |
+
|
| 24 |
+
## Security
|
| 25 |
+
- Remove `DISABLE_RATE_LIMIT` for production.
|
| 26 |
+
- Set cookies to `secure` and `SameSite=None` for HTTPS deployments.
|
| 27 |
+
- Change the hardcoded JWT secret in `config.php` before going live.
|
Unconfirmed 642953.crdownload
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Cart Fix Script - Connects the "V košarico" buttons to cart functionality
|
| 2 |
+
document.addEventListener('DOMContentLoaded', function(){
|
| 3 |
+
function parsePrice(text){
|
| 4 |
+
const m = text && text.match(/([\d.,]+)\s*€/);
|
| 5 |
+
return m ? parseFloat(m[1].replace(/\./g,'').replace(',', '.')) : 0;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
// Do NOT generate fake product IDs. Use the real data-product-id from backend-rendered HTML.
|
| 9 |
+
|
| 10 |
+
// Fallback mapping: product name -> actual DB id
|
| 11 |
+
const PRODUCT_NAME_TO_ID = {
|
| 12 |
+
'Cvetni prah 50g': '1',
|
| 13 |
+
'Cvetni prah 100g': '1',
|
| 14 |
+
'Cvetni prah': '1',
|
| 15 |
+
'Balzam za ustnice iz čebeljega voska': '2',
|
| 16 |
+
'Balzam za ustnice': '2',
|
| 17 |
+
'Balzam': '2',
|
| 18 |
+
'Med ajdov': '3',
|
| 19 |
+
'Med': '3',
|
| 20 |
+
'Med ajdov ': '3'
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
document.addEventListener('click', function(e){
|
| 24 |
+
const btn = e.target.closest('button');
|
| 25 |
+
if (!btn || !/V košarico/i.test(btn.textContent)) return;
|
| 26 |
+
e.preventDefault();
|
| 27 |
+
|
| 28 |
+
// Find the exact product container this button belongs to
|
| 29 |
+
const card = btn.closest('.product-card, .group');
|
| 30 |
+
if (!card) {
|
| 31 |
+
console.log('No product card found');
|
| 32 |
+
return;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
const nameEl = card.querySelector('h3');
|
| 36 |
+
const priceEl = card.querySelector('p[class*="amber-700"], p[class*="font-bold"]') ||
|
| 37 |
+
Array.from(card.querySelectorAll('p')).find(p => /€/.test(p.textContent));
|
| 38 |
+
|
| 39 |
+
const name = nameEl ? nameEl.textContent.trim() : 'Izdelek';
|
| 40 |
+
const price = parsePrice(priceEl ? priceEl.textContent : '0');
|
| 41 |
+
|
| 42 |
+
// Use the real productId from the HTML attribute; if missing, fall back to name->id map
|
| 43 |
+
let id = card.getAttribute('data-product-id');
|
| 44 |
+
if (!id) {
|
| 45 |
+
id = PRODUCT_NAME_TO_ID[name] || null;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
if (!id) {
|
| 49 |
+
console.warn('No product id found for item, skipping add to cart', name);
|
| 50 |
+
return;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
console.log('Adding to cart:', {id, name, price});
|
| 54 |
+
addToCart(id, name, price);
|
| 55 |
+
updateCartCount();
|
| 56 |
+
|
| 57 |
+
// Visual feedback
|
| 58 |
+
const originalHTML = btn.innerHTML;
|
| 59 |
+
btn.innerHTML = '<i data-feather="check" class="mr-1 w-4 h-4"></i> Dodano!';
|
| 60 |
+
btn.style.backgroundColor = '#10b981';
|
| 61 |
+
btn.disabled = true;
|
| 62 |
+
|
| 63 |
+
setTimeout(() => {
|
| 64 |
+
btn.innerHTML = originalHTML;
|
| 65 |
+
btn.style.backgroundColor = '';
|
| 66 |
+
btn.disabled = false;
|
| 67 |
+
feather.replace();
|
| 68 |
+
}, 2000);
|
| 69 |
+
});
|
| 70 |
+
});
|
admin_login.php
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
// Simple admin login page that posts to login.php
|
| 3 |
+
?>
|
| 4 |
+
<!DOCTYPE html>
|
| 5 |
+
<html lang="sl">
|
| 6 |
+
<head>
|
| 7 |
+
<meta charset="utf-8">
|
| 8 |
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 9 |
+
<title>Admin prijava | Čebelarstvo Cigoj</title>
|
| 10 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 11 |
+
</head>
|
| 12 |
+
<body class="bg-amber-50 min-h-screen flex items-center justify-center">
|
| 13 |
+
<div class="max-w-md w-full bg-white rounded-lg shadow p-6">
|
| 14 |
+
<h1 class="text-2xl font-bold mb-4 text-amber-900">Administracija - Prijava</h1>
|
| 15 |
+
<!-- target="_top" attempts to replace the top-level browsing context when inside an iframe/preview -->
|
| 16 |
+
<form id="loginForm" class="space-y-4" method="post" action="login.php" target="_top">
|
| 17 |
+
<div>
|
| 18 |
+
<label class="block text-sm font-medium text-gray-700">Uporabniško ime</label>
|
| 19 |
+
<input id="username" name="username" type="text" required class="mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring-amber-500 focus:border-amber-500" />
|
| 20 |
+
</div>
|
| 21 |
+
<div>
|
| 22 |
+
<label class="block text-sm font-medium text-gray-700">Geslo</label>
|
| 23 |
+
<input id="password" name="password" type="password" required class="mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring-amber-500 focus:border-amber-500" />
|
| 24 |
+
</div>
|
| 25 |
+
<div>
|
| 26 |
+
<button type="submit" class="w-full inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-amber-700 hover:bg-amber-800">Prijava</button>
|
| 27 |
+
</div>
|
| 28 |
+
<div id="error" class="text-red-600 text-sm"></div>
|
| 29 |
+
</form>
|
| 30 |
+
<div class="mt-4">
|
| 31 |
+
<label class="block text-sm font-medium text-gray-700">Ročno prilepi žeton (če preview blokira piškotke)</label>
|
| 32 |
+
<div class="flex mt-2">
|
| 33 |
+
<input id="pasteToken" class="flex-1 rounded border-gray-300 shadow-sm p-2" placeholder="Prilepite JWT sem..." />
|
| 34 |
+
<button id="applyToken" class="ml-2 bg-amber-600 text-white px-3 py-2 rounded">Uporabi</button>
|
| 35 |
+
</div>
|
| 36 |
+
</div>
|
| 37 |
+
<div class="mt-4 text-sm text-gray-600">
|
| 38 |
+
<p>If the preview still blocks navigation, you can open the dashboard manually:</p>
|
| 39 |
+
<div class="mt-2">
|
| 40 |
+
<a href="backend.php" target="_blank" class="inline-block bg-amber-100 border border-amber-300 text-amber-700 px-3 py-2 rounded">Open dashboard in new tab</a>
|
| 41 |
+
</div>
|
| 42 |
+
</div>
|
| 43 |
+
</div>
|
| 44 |
+
|
| 45 |
+
<script>
|
| 46 |
+
// Minimal JS: keep paste-token helper and top-window redirect attempt for special previews.
|
| 47 |
+
document.getElementById('applyToken').addEventListener('click', function(e) {
|
| 48 |
+
const token = document.getElementById('pasteToken').value.trim();
|
| 49 |
+
if (!token) return;
|
| 50 |
+
const expires = new Date(Date.now() + 7*24*60*60*1000).toUTCString();
|
| 51 |
+
try {
|
| 52 |
+
document.cookie = `refresh_token=${token}; expires=${expires}; path=/; SameSite=None; Secure`;
|
| 53 |
+
} catch (e) {
|
| 54 |
+
// ignore
|
| 55 |
+
}
|
| 56 |
+
try {
|
| 57 |
+
if (window.top && window.top !== window) {
|
| 58 |
+
window.top.location.replace('backend.php');
|
| 59 |
+
return;
|
| 60 |
+
}
|
| 61 |
+
} catch (e) {}
|
| 62 |
+
window.location.replace('backend.php');
|
| 63 |
+
});
|
| 64 |
+
</script>
|
| 65 |
+
</body>
|
| 66 |
+
</html>
|
admin_orders.php
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
require_once 'require_auth.php';
|
| 3 |
+
// Fetch orders and items
|
| 4 |
+
try {
|
| 5 |
+
$stmt = $pdo->prepare("SELECT * FROM orders ORDER BY created_at DESC");
|
| 6 |
+
$stmt->execute();
|
| 7 |
+
$orders = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 8 |
+
|
| 9 |
+
foreach ($orders as &$order) {
|
| 10 |
+
$stmt = $pdo->prepare("SELECT * FROM order_items WHERE order_id = ?");
|
| 11 |
+
$stmt->execute([$order['id']]);
|
| 12 |
+
$order['items'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 13 |
+
}
|
| 14 |
+
} catch (Exception $e) {
|
| 15 |
+
$orders = [];
|
| 16 |
+
}
|
| 17 |
+
?>
|
| 18 |
+
<!DOCTYPE html>
|
| 19 |
+
<html lang="sl">
|
| 20 |
+
<head>
|
| 21 |
+
<meta charset="UTF-8">
|
| 22 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 23 |
+
<title>Admin - Naročila</title>
|
| 24 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 25 |
+
</head>
|
| 26 |
+
<body class="bg-gray-100 font-sans">
|
| 27 |
+
<div class="max-w-6xl mx-auto p-6">
|
| 28 |
+
<h1 class="text-2xl font-bold mb-4">Vsa naročila</h1>
|
| 29 |
+
|
| 30 |
+
<?php if (empty($orders)): ?>
|
| 31 |
+
<p class="text-gray-600">Ni naročil za prikaz.</p>
|
| 32 |
+
<?php else: ?>
|
| 33 |
+
<?php foreach ($orders as $order): ?>
|
| 34 |
+
<div class="bg-white shadow rounded mb-4 p-4">
|
| 35 |
+
<div class="flex justify-between items-start">
|
| 36 |
+
<div>
|
| 37 |
+
<h2 class="text-lg font-semibold">Naročilo #<?php echo htmlspecialchars($order['id']); ?></h2>
|
| 38 |
+
<p class="text-sm text-gray-600"><?php echo htmlspecialchars($order['customer_name']); ?> — <?php echo htmlspecialchars($order['customer_email']); ?></p>
|
| 39 |
+
<p class="text-sm text-gray-600">Status: <?php echo htmlspecialchars($order['status']); ?></p>
|
| 40 |
+
</div>
|
| 41 |
+
<div class="text-right">
|
| 42 |
+
<p class="text-sm text-gray-600"><?php echo htmlspecialchars($order['created_at']); ?></p>
|
| 43 |
+
<p class="text-lg font-bold"><?php echo htmlspecialchars(number_format((float)$order['total_amount'], 2, ',', '.')) . '€'; ?></p>
|
| 44 |
+
</div>
|
| 45 |
+
</div>
|
| 46 |
+
|
| 47 |
+
<?php if (!empty($order['items'])): ?>
|
| 48 |
+
<div class="mt-3">
|
| 49 |
+
<table class="w-full text-left text-sm">
|
| 50 |
+
<thead class="text-xs text-gray-500 uppercase">
|
| 51 |
+
<tr>
|
| 52 |
+
<th>Izdelek</th>
|
| 53 |
+
<th class="text-right">Količina</th>
|
| 54 |
+
<th class="text-right">Enotna cena</th>
|
| 55 |
+
<th class="text-right">Skupaj</th>
|
| 56 |
+
</tr>
|
| 57 |
+
</thead>
|
| 58 |
+
<tbody>
|
| 59 |
+
<?php foreach ($order['items'] as $item): ?>
|
| 60 |
+
<tr>
|
| 61 |
+
<td><?php echo htmlspecialchars($item['product_name'] ?? $item['name'] ?? 'Izdelek'); ?></td>
|
| 62 |
+
<td class="text-right"><?php echo htmlspecialchars($item['quantity']); ?></td>
|
| 63 |
+
<td class="text-right"><?php echo htmlspecialchars(number_format((float)($item['unit_price'] ?? $item['price'] ?? 0), 2, ',', '.')) . '€'; ?></td>
|
| 64 |
+
<td class="text-right"><?php echo htmlspecialchars(number_format((float)($item['unit_price'] * $item['quantity']), 2, ',', '.')) . '€'; ?></td>
|
| 65 |
+
</tr>
|
| 66 |
+
<?php endforeach; ?>
|
| 67 |
+
</tbody>
|
| 68 |
+
</table>
|
| 69 |
+
</div>
|
| 70 |
+
<?php endif; ?>
|
| 71 |
+
</div>
|
| 72 |
+
<?php endforeach; ?>
|
| 73 |
+
<?php endif; ?>
|
| 74 |
+
</div>
|
| 75 |
+
</body>
|
| 76 |
+
</html>
|
auth.php
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
require_once 'config.php';
|
| 3 |
+
|
| 4 |
+
// Handle admin login
|
| 5 |
+
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
| 6 |
+
$data = json_decode(file_get_contents("php://input"));
|
| 7 |
+
|
| 8 |
+
// Validate admin credentials
|
| 9 |
+
if($data->username === 'admin' && $data->password === 'admin123') {
|
| 10 |
+
$token = array(
|
| 11 |
+
"iss" => "cebelarstvo_cigoj",
|
| 12 |
+
"iat" => time(),
|
| 13 |
+
"exp" => time() + (60 * 60),
|
| 14 |
+
"data" => array(
|
| 15 |
+
"username" => "admin",
|
| 16 |
+
"role" => "admin"
|
| 17 |
+
)
|
| 18 |
+
);
|
| 19 |
+
|
| 20 |
+
$jwt = \Firebase\JWT\JWT::encode($token, JWT_SECRET, 'HS256');
|
| 21 |
+
echo json_encode(array("success" => true, "token" => $jwt));
|
| 22 |
+
} else {
|
| 23 |
+
http_response_code(401);
|
| 24 |
+
echo json_encode(array("success" => false, "message" => "Invalid credentials"));
|
| 25 |
+
}
|
| 26 |
+
}
|
| 27 |
+
?>
|
backend.php
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
require_once 'require_auth.php';
|
| 3 |
+
|
| 4 |
+
// Fetch basic stats and recent orders for the dashboard
|
| 5 |
+
try {
|
| 6 |
+
// Orders count and revenue
|
| 7 |
+
$stmt = $pdo->query("SELECT COUNT(*) as orders_count, COALESCE(SUM(total_amount),0) as revenue FROM orders");
|
| 8 |
+
$stats = $stmt->fetch(PDO::FETCH_ASSOC);
|
| 9 |
+
$orders_count = $stats['orders_count'] ?? 0;
|
| 10 |
+
$revenue = $stats['revenue'] ?? 0.00;
|
| 11 |
+
|
| 12 |
+
// Products count
|
| 13 |
+
$stmt = $pdo->query("SELECT COUNT(*) FROM products");
|
| 14 |
+
$products_count = (int)$stmt->fetchColumn();
|
| 15 |
+
|
| 16 |
+
// Customers count
|
| 17 |
+
$stmt = $pdo->query("SELECT COUNT(*) FROM customers");
|
| 18 |
+
$customers_count = (int)$stmt->fetchColumn();
|
| 19 |
+
|
| 20 |
+
// Recent orders (latest 10)
|
| 21 |
+
$stmt = $pdo->prepare("SELECT id, customer_name, status, total_amount, created_at FROM orders ORDER BY created_at DESC LIMIT 10");
|
| 22 |
+
$stmt->execute();
|
| 23 |
+
$recentOrders = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 24 |
+
} catch (Exception $e) {
|
| 25 |
+
// Graceful fallback to zeros/empty if the DB query fails
|
| 26 |
+
$orders_count = $orders_count ?? 0;
|
| 27 |
+
$revenue = $revenue ?? 0.00;
|
| 28 |
+
$products_count = $products_count ?? 0;
|
| 29 |
+
$customers_count = $customers_count ?? 0;
|
| 30 |
+
$recentOrders = [];
|
| 31 |
+
}
|
| 32 |
+
?>
|
| 33 |
+
<!DOCTYPE html>
|
| 34 |
+
<html lang="sl">
|
| 35 |
+
<head>
|
| 36 |
+
<meta charset="UTF-8">
|
| 37 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 38 |
+
<title>Administracija | Čebelarstvo Cigoj</title>
|
| 39 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 40 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 41 |
+
<style>
|
| 42 |
+
.sidebar {
|
| 43 |
+
transition: all 0.3s;
|
| 44 |
+
}
|
| 45 |
+
.sidebar-link:hover {
|
| 46 |
+
background-color: rgba(251, 191, 36, 0.1);
|
| 47 |
+
}
|
| 48 |
+
.dashboard-card {
|
| 49 |
+
transition: transform 0.2s;
|
| 50 |
+
}
|
| 51 |
+
.dashboard-card:hover {
|
| 52 |
+
transform: translateY(-2px);
|
| 53 |
+
}
|
| 54 |
+
</style>
|
| 55 |
+
</head>
|
| 56 |
+
<body class="bg-gray-100 font-sans">
|
| 57 |
+
<!-- Admin Layout -->
|
| 58 |
+
<div class="flex h-screen">
|
| 59 |
+
<!-- Sidebar -->
|
| 60 |
+
<div class="sidebar bg-amber-800 text-white w-64 flex-shrink-0">
|
| 61 |
+
<div class="p-4 border-b border-amber-700">
|
| 62 |
+
<h1 class="text-xl font-bold flex items-center">
|
| 63 |
+
<i data-feather="hexagon" class="mr-2"></i>
|
| 64 |
+
Čebelarstvo Cigoj
|
| 65 |
+
</h1>
|
| 66 |
+
<p class="text-xs text-amber-200 mt-1">Administracija</p>
|
| 67 |
+
</div>
|
| 68 |
+
<nav class="p-4">
|
| 69 |
+
<ul class="space-y-2">
|
| 70 |
+
<li>
|
| 71 |
+
<a href="#" class="sidebar-link flex items-center px-3 py-2 rounded-lg bg-amber-700 text-white">
|
| 72 |
+
<i data-feather="home" class="mr-3"></i>
|
| 73 |
+
Nadzorna plošča
|
| 74 |
+
</a>
|
| 75 |
+
</li>
|
| 76 |
+
<li>
|
| 77 |
+
<a href="products.php" class="sidebar-link flex items-center px-3 py-2 rounded-lg text-amber-200 hover:text-white">
|
| 78 |
+
<i data-feather="shopping-bag" class="mr-3"></i>
|
| 79 |
+
Izdelki
|
| 80 |
+
</a>
|
| 81 |
+
</li>
|
| 82 |
+
<li>
|
| 83 |
+
<a href="orders.php" class="sidebar-link flex items-center px-3 py-2 rounded-lg text-amber-200 hover:text-white">
|
| 84 |
+
<i data-feather="file-text" class="mr-3"></i>
|
| 85 |
+
Naročila
|
| 86 |
+
</a>
|
| 87 |
+
</li>
|
| 88 |
+
<li>
|
| 89 |
+
<a href="customers.php" class="sidebar-link flex items-center px-3 py-2 rounded-lg text-amber-200 hover:text-white">
|
| 90 |
+
<i data-feather="users" class="mr-3"></i>
|
| 91 |
+
Stranke
|
| 92 |
+
</a>
|
| 93 |
+
</li>
|
| 94 |
+
<li>
|
| 95 |
+
<a href="reports.php" class="sidebar-link flex items-center px-3 py-2 rounded-lg text-amber-200 hover:text-white">
|
| 96 |
+
<i data-feather="bar-chart-2" class="mr-3"></i>
|
| 97 |
+
Poročila
|
| 98 |
+
</a>
|
| 99 |
+
</li>
|
| 100 |
+
<li>
|
| 101 |
+
<a href="settings.php" class="sidebar-link flex items-center px-3 py-2 rounded-lg text-amber-200 hover:text-white">
|
| 102 |
+
<i data-feather="settings" class="mr-3"></i>
|
| 103 |
+
Nastavitve
|
| 104 |
+
</a>
|
| 105 |
+
</li>
|
| 106 |
+
</ul>
|
| 107 |
+
</nav>
|
| 108 |
+
</div>
|
| 109 |
+
|
| 110 |
+
<!-- Main Content -->
|
| 111 |
+
<div class="flex-1 overflow-auto">
|
| 112 |
+
<!-- Top Navigation -->
|
| 113 |
+
<header class="bg-white shadow-sm">
|
| 114 |
+
<div class="flex justify-between items-center p-4">
|
| 115 |
+
<div class="flex items-center">
|
| 116 |
+
<button class="p-2 rounded-full hover:bg-gray-100 mr-2">
|
| 117 |
+
<i data-feather="menu"></i>
|
| 118 |
+
</button>
|
| 119 |
+
<h2 class="text-lg font-semibold">Nadzorna plošča</h2>
|
| 120 |
+
</div>
|
| 121 |
+
<div class="flex items-center space-x-4">
|
| 122 |
+
<button class="p-2 rounded-full hover:bg-gray-100 relative">
|
| 123 |
+
<i data-feather="bell"></i>
|
| 124 |
+
<span class="absolute top-0 right-0 h-2 w-2 rounded-full bg-red-500"></span>
|
| 125 |
+
</button>
|
| 126 |
+
<div class="flex items-center">
|
| 127 |
+
<div class="w-8 h-8 rounded-full bg-amber-200 flex items-center justify-center mr-2">
|
| 128 |
+
<i data-feather="user"></i>
|
| 129 |
+
</div>
|
| 130 |
+
<span class="text-sm">Admin</span>
|
| 131 |
+
</div>
|
| 132 |
+
</div>
|
| 133 |
+
</div>
|
| 134 |
+
</header>
|
| 135 |
+
|
| 136 |
+
<!-- Dashboard Content -->
|
| 137 |
+
<main class="p-6">
|
| 138 |
+
<!-- Stats Cards -->
|
| 139 |
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
| 140 |
+
<div class="dashboard-card bg-white rounded-lg shadow p-6">
|
| 141 |
+
<div class="flex justify-between items-start">
|
| 142 |
+
<div>
|
| 143 |
+
<p class="text-gray-500 text-sm">Skupni prihodek</p>
|
| 144 |
+
<h3 class="text-2xl font-bold mt-1"><?php echo htmlspecialchars(number_format((float)$revenue, 2, ',', '.')) . '€'; ?></h3>
|
| 145 |
+
</div>
|
| 146 |
+
<div class="p-3 rounded-full bg-green-100 text-green-600">
|
| 147 |
+
<i data-feather="dollar-sign"></i>
|
| 148 |
+
</div>
|
| 149 |
+
</div>
|
| 150 |
+
<p class="text-green-600 text-sm mt-2 flex items-center">
|
| 151 |
+
<i data-feather="trending-up" class="mr-1"></i> V primerjavi s prejšnjim mesecem
|
| 152 |
+
</p>
|
| 153 |
+
</div>
|
| 154 |
+
|
| 155 |
+
<div class="dashboard-card bg-white rounded-lg shadow p-6">
|
| 156 |
+
<div class="flex justify-between items-start">
|
| 157 |
+
<div>
|
| 158 |
+
<p class="text-gray-500 text-sm">Naročila</p>
|
| 159 |
+
<h3 class="text-2xl font-bold mt-1"><?php echo htmlspecialchars((int)$orders_count); ?></h3>
|
| 160 |
+
</div>
|
| 161 |
+
<div class="p-3 rounded-full bg-blue-100 text-blue-600">
|
| 162 |
+
<i data-feather="shopping-cart"></i>
|
| 163 |
+
</div>
|
| 164 |
+
</div>
|
| 165 |
+
<p class="text-blue-600 text-sm mt-2 flex items-center">
|
| 166 |
+
<i data-feather="trending-up" class="mr-1"></i> Zadnjih <?php echo count($recentOrders); ?> naročil
|
| 167 |
+
</p>
|
| 168 |
+
</div>
|
| 169 |
+
|
| 170 |
+
<div class="dashboard-card bg-white rounded-lg shadow p-6">
|
| 171 |
+
<div class="flex justify-between items-start">
|
| 172 |
+
<div>
|
| 173 |
+
<p class="text-gray-500 text-sm">Izdelki</p>
|
| 174 |
+
<h3 class="text-2xl font-bold mt-1"><?php echo htmlspecialchars((int)$products_count); ?></h3>
|
| 175 |
+
</div>
|
| 176 |
+
<div class="p-3 rounded-full bg-purple-100 text-purple-600">
|
| 177 |
+
<i data-feather="package"></i>
|
| 178 |
+
</div>
|
| 179 |
+
</div>
|
| 180 |
+
<p class="text-purple-600 text-sm mt-2 flex items-center">
|
| 181 |
+
<i data-feather="alert-circle" class="mr-1"></i> Pregled zalog
|
| 182 |
+
</p>
|
| 183 |
+
</div>
|
| 184 |
+
|
| 185 |
+
<div class="dashboard-card bg-white rounded-lg shadow p-6">
|
| 186 |
+
<div class="flex justify-between items-start">
|
| 187 |
+
<div>
|
| 188 |
+
<p class="text-gray-500 text-sm">Stranke</p>
|
| 189 |
+
<h3 class="text-2xl font-bold mt-1"><?php echo htmlspecialchars((int)$customers_count); ?></h3>
|
| 190 |
+
</div>
|
| 191 |
+
<div class="p-3 rounded-full bg-amber-100 text-amber-600">
|
| 192 |
+
<i data-feather="users"></i>
|
| 193 |
+
</div>
|
| 194 |
+
</div>
|
| 195 |
+
<p class="text-amber-600 text-sm mt-2 flex items-center">
|
| 196 |
+
<i data-feather="trending-up" class="mr-1"></i> Novi uporabniki
|
| 197 |
+
</p>
|
| 198 |
+
</div>
|
| 199 |
+
</div>
|
| 200 |
+
|
| 201 |
+
<!-- Recent Orders -->
|
| 202 |
+
<div class="bg-white rounded-lg shadow overflow-hidden mb-8">
|
| 203 |
+
<div class="p-4 border-b flex justify-between items-center">
|
| 204 |
+
<h3 class="font-semibold">Zadnja naročila</h3>
|
| 205 |
+
<a href="#" class="text-sm text-amber-600 hover:text-amber-800">Prikaži vse</a>
|
| 206 |
+
</div>
|
| 207 |
+
<div class="overflow-x-auto">
|
| 208 |
+
<table class="min-w-full divide-y divide-gray-200">
|
| 209 |
+
<thead class="bg-gray-50">
|
| 210 |
+
<tr>
|
| 211 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Št. naročila</th>
|
| 212 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Kupec</th>
|
| 213 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
| 214 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Znesek</th>
|
| 215 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Datum</th>
|
| 216 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></th>
|
| 217 |
+
</tr>
|
| 218 |
+
</thead>
|
| 219 |
+
<tbody class="bg-white divide-y divide-gray-200">
|
| 220 |
+
<?php if (empty($recentOrders)): ?>
|
| 221 |
+
<tr>
|
| 222 |
+
<td colspan="6" class="px-6 py-4 text-sm text-gray-500">Ni naročil za prikaz.</td>
|
| 223 |
+
</tr>
|
| 224 |
+
<?php else: ?>
|
| 225 |
+
<?php foreach ($recentOrders as $order): ?>
|
| 226 |
+
<tr>
|
| 227 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">#<?php echo htmlspecialchars($order['id']); ?></td>
|
| 228 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"><?php echo htmlspecialchars($order['customer_name']); ?></td>
|
| 229 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
| 230 |
+
<?php
|
| 231 |
+
$status = $order['status'] ?? 'pending';
|
| 232 |
+
$badgeClass = 'bg-gray-100 text-gray-800';
|
| 233 |
+
if ($status === 'paid' || $status === 'completed') $badgeClass = 'bg-green-100 text-green-800';
|
| 234 |
+
if ($status === 'pending') $badgeClass = 'bg-yellow-100 text-yellow-800';
|
| 235 |
+
if ($status === 'shipped' || $status === 'sent') $badgeClass = 'bg-blue-100 text-blue-800';
|
| 236 |
+
?>
|
| 237 |
+
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full <?php echo $badgeClass; ?>"><?php echo htmlspecialchars(ucfirst($status)); ?></span>
|
| 238 |
+
</td>
|
| 239 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"><?php echo htmlspecialchars(number_format((float)$order['total_amount'], 2, ',', '.')) . '€'; ?></td>
|
| 240 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"><?php echo htmlspecialchars($order['created_at']); ?></td>
|
| 241 |
+
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
| 242 |
+
<a href="orders.php?id=<?php echo urlencode($order['id']); ?>" class="text-amber-600 hover:text-amber-900">Ogled</a>
|
| 243 |
+
</td>
|
| 244 |
+
</tr>
|
| 245 |
+
<?php endforeach; ?>
|
| 246 |
+
<?php endif; ?>
|
| 247 |
+
</tbody>
|
| 248 |
+
</table>
|
| 249 |
+
</div>
|
| 250 |
+
</div>
|
| 251 |
+
|
| 252 |
+
<!-- Low Stock Products -->
|
| 253 |
+
<div class="bg-white rounded-lg shadow overflow-hidden">
|
| 254 |
+
<div class="p-4 border-b flex justify-between items-center">
|
| 255 |
+
<h3 class="font-semibold">Izdelki z nizko zalogo</h3>
|
| 256 |
+
<a href="#" class="text-sm text-amber-600 hover:text-amber-800">Prikaži vse</a>
|
| 257 |
+
</div>
|
| 258 |
+
<div class="overflow-x-auto">
|
| 259 |
+
<table class="min-w-full divide-y divide-gray-200">
|
| 260 |
+
<thead class="bg-gray-50">
|
| 261 |
+
<tr>
|
| 262 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Izdelek</th>
|
| 263 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
|
| 264 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Zaloga</th>
|
| 265 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
| 266 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></th>
|
| 267 |
+
</tr>
|
| 268 |
+
</thead>
|
| 269 |
+
<tbody class="bg-white divide-y divide-gray-200">
|
| 270 |
+
<tr>
|
| 271 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
| 272 |
+
<div class="flex items-center">
|
| 273 |
+
<div class="flex-shrink-0 h-10 w-10">
|
| 274 |
+
<img class="h-10 w-10 rounded" src="https://static.photos/nature/200x200/101" alt="">
|
| 275 |
+
</div>
|
| 276 |
+
<div class="ml-4">
|
| 277 |
+
<div class="text-sm font-medium text-gray-900">Cvetni prah 50g</div>
|
| 278 |
+
<div class="text-sm text-gray-500">4,50€</div>
|
| 279 |
+
</div>
|
| 280 |
+
</div>
|
| 281 |
+
</td>
|
| 282 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">PRD-001</td>
|
| 283 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">3</td>
|
| 284 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
| 285 |
+
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">Nizka zaloga</span>
|
| 286 |
+
</td>
|
| 287 |
+
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
| 288 |
+
<a href="#" class="text-amber-600 hover:text-amber-900">Uredi</a>
|
| 289 |
+
</td>
|
| 290 |
+
</tr>
|
| 291 |
+
<tr>
|
| 292 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
| 293 |
+
<div class="flex items-center">
|
| 294 |
+
<div class="flex-shrink-0 h-10 w-10">
|
| 295 |
+
<img class="h-10 w-10 rounded" src="https://static.photos/nature/200x200/102" alt="">
|
| 296 |
+
</div>
|
| 297 |
+
<div class="ml-4">
|
| 298 |
+
<div class="text-sm font-medium text-gray-900">Balzam za ustnice</div>
|
| 299 |
+
<div class="text-sm text-gray-500">2,50€</div>
|
| 300 |
+
</div>
|
| 301 |
+
</div>
|
| 302 |
+
</td>
|
| 303 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">PRD-005</td>
|
| 304 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">5</td>
|
| 305 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
| 306 |
+
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">Opozorilo</span>
|
| 307 |
+
</td>
|
| 308 |
+
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
| 309 |
+
<a href="#" class="text-amber-600 hover:text-amber-900">Uredi</a>
|
| 310 |
+
</td>
|
| 311 |
+
</tr>
|
| 312 |
+
</tbody>
|
| 313 |
+
</table>
|
| 314 |
+
</div>
|
| 315 |
+
</div>
|
| 316 |
+
</main>
|
| 317 |
+
</div>
|
| 318 |
+
</div>
|
| 319 |
+
|
| 320 |
+
<script>
|
| 321 |
+
feather.replace();
|
| 322 |
+
</script>
|
| 323 |
+
</body>
|
| 324 |
+
</html>
|
cart.html
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="sl">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Košarica | Čebelarstvo Cigoj</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 9 |
+
</head>
|
| 10 |
+
<body class="font-sans bg-gray-50">
|
| 11 |
+
<!-- Header -->
|
| 12 |
+
<header class="bg-amber-800 text-white shadow-md">
|
| 13 |
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 14 |
+
<div class="flex justify-between items-center py-4">
|
| 15 |
+
<div class="flex items-center space-x-2">
|
| 16 |
+
<i data-feather="hexagon" class="text-amber-300"></i>
|
| 17 |
+
<h1 class="text-xl font-bold">Čebelarstvo Cigoj</h1>
|
| 18 |
+
</div>
|
| 19 |
+
<div class="flex items-center space-x-4">
|
| 20 |
+
<div class="relative">
|
| 21 |
+
<i data-feather="shopping-cart" class="text-amber-200 w-6 h-6"></i>
|
| 22 |
+
<span id="cart-count" class="absolute -top-2 -right-2 bg-amber-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center hidden">0</span>
|
| 23 |
+
</div>
|
| 24 |
+
<a href="index.html" class="text-amber-200 hover:text-white transition">
|
| 25 |
+
<i data-feather="arrow-left" class="mr-2"></i>Nazaj v trgovino
|
| 26 |
+
</a>
|
| 27 |
+
</div>
|
| 28 |
+
</div>
|
| 29 |
+
</div>
|
| 30 |
+
</header>
|
| 31 |
+
|
| 32 |
+
<!-- Cart Content -->
|
| 33 |
+
<main class="container mx-auto px-4 py-8">
|
| 34 |
+
<div class="max-w-4xl mx-auto">
|
| 35 |
+
<h2 class="text-2xl font-bold text-amber-900 mb-6">Vaša košarica</h2>
|
| 36 |
+
|
| 37 |
+
<div id="cart-content">
|
| 38 |
+
<!-- Cart items will be loaded here by JavaScript -->
|
| 39 |
+
</div>
|
| 40 |
+
|
| 41 |
+
<div id="cart-summary" class="bg-white rounded-lg shadow p-6 mt-6 hidden">
|
| 42 |
+
<div class="flex justify-between items-center text-lg font-bold mb-4">
|
| 43 |
+
<span>Skupaj:</span>
|
| 44 |
+
<span id="total-amount">0.00€</span>
|
| 45 |
+
</div>
|
| 46 |
+
<button onclick="proceedToCheckout()" class="w-full bg-amber-600 hover:bg-amber-700 text-white py-3 px-4 rounded-lg font-medium transition">
|
| 47 |
+
Nadaljuj s plačilom
|
| 48 |
+
</button>
|
| 49 |
+
</div>
|
| 50 |
+
|
| 51 |
+
<div id="empty-cart" class="bg-white rounded-lg shadow p-6 text-center hidden">
|
| 52 |
+
<p class="text-gray-600 mb-4">Vaša košarica je prazna</p>
|
| 53 |
+
<a href="index.html" class="text-amber-600 hover:text-amber-800">Nazaj v trgovino</a>
|
| 54 |
+
</div>
|
| 55 |
+
</div>
|
| 56 |
+
</main>
|
| 57 |
+
|
| 58 |
+
<script>
|
| 59 |
+
function loadCart() {
|
| 60 |
+
const cart = JSON.parse(localStorage.getItem('cart')) || [];
|
| 61 |
+
const cartContent = document.getElementById('cart-content');
|
| 62 |
+
const cartSummary = document.getElementById('cart-summary');
|
| 63 |
+
const emptyCart = document.getElementById('empty-cart');
|
| 64 |
+
|
| 65 |
+
if (cart.length === 0) {
|
| 66 |
+
emptyCart.classList.remove('hidden');
|
| 67 |
+
cartSummary.classList.add('hidden');
|
| 68 |
+
return;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
let total = 0;
|
| 72 |
+
let html = '<div class="bg-white rounded-lg shadow overflow-hidden">';
|
| 73 |
+
html += '<div class="grid grid-cols-4 gap-4 p-4 bg-gray-50 font-medium">';
|
| 74 |
+
html += '<div>Izdelek</div><div>Cena</div><div>Količina</div><div>Skupaj</div>';
|
| 75 |
+
html += '</div>';
|
| 76 |
+
|
| 77 |
+
cart.forEach((item, index) => {
|
| 78 |
+
const itemTotal = item.price * item.quantity;
|
| 79 |
+
total += itemTotal;
|
| 80 |
+
|
| 81 |
+
html += `<div class="grid grid-cols-4 gap-4 p-4 border-t border-gray-200 items-center">`;
|
| 82 |
+
html += `<div class="font-medium">${item.name}</div>`;
|
| 83 |
+
html += `<div>${item.price.toFixed(2)}€</div>`;
|
| 84 |
+
html += `<div class="flex items-center space-x-2">`;
|
| 85 |
+
html += `<button onclick="updateQuantity(${index}, -1)" class="bg-gray-200 hover:bg-gray-300 w-8 h-8 rounded flex items-center justify-center">-</button>`;
|
| 86 |
+
html += `<span class="mx-2">${item.quantity}</span>`;
|
| 87 |
+
html += `<button onclick="updateQuantity(${index}, 1)" class="bg-gray-200 hover:bg-gray-300 w-8 h-8 rounded flex items-center justify-center">+</button>`;
|
| 88 |
+
html += `</div>`;
|
| 89 |
+
html += `<div class="flex justify-between items-center">`;
|
| 90 |
+
html += `<span>${itemTotal.toFixed(2)}€</span>`;
|
| 91 |
+
html += `<button onclick="removeItem(${index})" class="text-red-600 hover:text-red-800 ml-4">`;
|
| 92 |
+
html += `<i data-feather="trash-2"></i></button>`;
|
| 93 |
+
html += `</div></div>`;
|
| 94 |
+
});
|
| 95 |
+
|
| 96 |
+
html += '</div>';
|
| 97 |
+
cartContent.innerHTML = html;
|
| 98 |
+
document.getElementById('total-amount').textContent = total.toFixed(2) + '€';
|
| 99 |
+
cartSummary.classList.remove('hidden');
|
| 100 |
+
|
| 101 |
+
feather.replace();
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
function updateQuantity(index, change) {
|
| 105 |
+
let cart = JSON.parse(localStorage.getItem('cart')) || [];
|
| 106 |
+
cart[index].quantity += change;
|
| 107 |
+
|
| 108 |
+
if (cart[index].quantity <= 0) {
|
| 109 |
+
cart.splice(index, 1);
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
localStorage.setItem('cart', JSON.stringify(cart));
|
| 113 |
+
loadCart();
|
| 114 |
+
updateCartCount();
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
function removeItem(index) {
|
| 118 |
+
let cart = JSON.parse(localStorage.getItem('cart')) || [];
|
| 119 |
+
cart.splice(index, 1);
|
| 120 |
+
localStorage.setItem('cart', JSON.stringify(cart));
|
| 121 |
+
loadCart();
|
| 122 |
+
updateCartCount();
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
function updateCartCount() {
|
| 126 |
+
const cart = JSON.parse(localStorage.getItem('cart')) || [];
|
| 127 |
+
const count = cart.reduce((total, item) => total + item.quantity, 0);
|
| 128 |
+
const cartCount = document.getElementById('cart-count');
|
| 129 |
+
|
| 130 |
+
if (cartCount) {
|
| 131 |
+
cartCount.textContent = count;
|
| 132 |
+
if(count > 0) {
|
| 133 |
+
cartCount.classList.remove('hidden');
|
| 134 |
+
} else {
|
| 135 |
+
cartCount.classList.add('hidden');
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
function proceedToCheckout() {
|
| 141 |
+
const cart = JSON.parse(localStorage.getItem('cart')) || [];
|
| 142 |
+
if (cart.length === 0) {
|
| 143 |
+
alert('Vaša košarica je prazna');
|
| 144 |
+
return;
|
| 145 |
+
}
|
| 146 |
+
// Redirect to checkout page
|
| 147 |
+
window.location.href = 'checkout.php';
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
// Load cart on page load
|
| 151 |
+
loadCart();
|
| 152 |
+
feather.replace();
|
| 153 |
+
</script>
|
| 154 |
+
</body>
|
| 155 |
+
</html>
|
cebelarstvo_cigoj (1).db
ADDED
|
File without changes
|
cebelarstvo_cigoj.db
ADDED
|
Binary file (45.1 kB). View file
|
|
|
checkout.php
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
require_once 'config.php';
|
| 3 |
+
|
| 4 |
+
// Handle checkout form submission
|
| 5 |
+
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
| 6 |
+
header('Content-Type: application/json');
|
| 7 |
+
|
| 8 |
+
$input = json_decode(file_get_contents('php://input'), true);
|
| 9 |
+
|
| 10 |
+
// Validate required fields
|
| 11 |
+
$required_fields = ['customer_name', 'customer_email', 'customer_address', 'customer_phone', 'cart_items'];
|
| 12 |
+
foreach ($required_fields as $field) {
|
| 13 |
+
if (empty($input[$field])) {
|
| 14 |
+
http_response_code(400);
|
| 15 |
+
echo json_encode(['error' => "Field $field is required"]);
|
| 16 |
+
exit;
|
| 17 |
+
}
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
$customer_name = trim($input['customer_name']);
|
| 21 |
+
$customer_email = filter_var($input['customer_email'], FILTER_VALIDATE_EMAIL);
|
| 22 |
+
$customer_address = trim($input['customer_address']);
|
| 23 |
+
$customer_phone = trim($input['customer_phone']);
|
| 24 |
+
$cart_items = $input['cart_items'];
|
| 25 |
+
$shipping_method = $input['shipping_method'] ?? 'posta_slovenije';
|
| 26 |
+
|
| 27 |
+
if (!$customer_email) {
|
| 28 |
+
http_response_code(400);
|
| 29 |
+
echo json_encode(['error' => 'Invalid email address']);
|
| 30 |
+
exit;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
// Calculate totals
|
| 34 |
+
$subtotal = 0;
|
| 35 |
+
foreach ($cart_items as $item) {
|
| 36 |
+
$subtotal += $item['price'] * $item['quantity'];
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
// Add shipping cost (Pošta Slovenije - 4.90 euro)
|
| 40 |
+
$shipping_cost = 0;
|
| 41 |
+
if ($shipping_method === 'posta_slovenije') {
|
| 42 |
+
$shipping_cost = 4.90;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
$total = $subtotal + $shipping_cost;
|
| 46 |
+
|
| 47 |
+
try {
|
| 48 |
+
$pdo->beginTransaction();
|
| 49 |
+
|
| 50 |
+
// Create order
|
| 51 |
+
$stmt = $pdo->prepare("INSERT INTO orders (customer_name, customer_email, customer_address, customer_phone, subtotal, shipping_cost, total_amount, shipping_method, status, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', CURRENT_TIMESTAMP)");
|
| 52 |
+
$stmt->execute([
|
| 53 |
+
$customer_name,
|
| 54 |
+
$customer_email,
|
| 55 |
+
$customer_address,
|
| 56 |
+
$customer_phone,
|
| 57 |
+
$subtotal,
|
| 58 |
+
$shipping_cost,
|
| 59 |
+
$total,
|
| 60 |
+
$shipping_method
|
| 61 |
+
]);
|
| 62 |
+
|
| 63 |
+
$order_id = $pdo->lastInsertId();
|
| 64 |
+
|
| 65 |
+
// Add order items
|
| 66 |
+
foreach ($cart_items as $item) {
|
| 67 |
+
$stmt = $pdo->prepare("INSERT INTO order_items (order_id, product_id, product_name, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?, ?)");
|
| 68 |
+
$stmt->execute([
|
| 69 |
+
$order_id,
|
| 70 |
+
$item['id'],
|
| 71 |
+
$item['name'],
|
| 72 |
+
$item['quantity'],
|
| 73 |
+
$item['price'],
|
| 74 |
+
$item['price'] * $item['quantity']
|
| 75 |
+
]);
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
$pdo->commit();
|
| 79 |
+
|
| 80 |
+
echo json_encode([
|
| 81 |
+
'success' => true,
|
| 82 |
+
'order_id' => $order_id,
|
| 83 |
+
'subtotal' => $subtotal,
|
| 84 |
+
'shipping_cost' => $shipping_cost,
|
| 85 |
+
'total' => $total,
|
| 86 |
+
'message' => 'Naročilo je bilo uspešno oddano!'
|
| 87 |
+
]);
|
| 88 |
+
|
| 89 |
+
} catch (Exception $e) {
|
| 90 |
+
$pdo->rollBack();
|
| 91 |
+
http_response_code(500);
|
| 92 |
+
echo json_encode(['error' => 'Napaka pri oddaji naročila: ' . $e->getMessage()]);
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
exit;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
// Show checkout form
|
| 99 |
+
?>
|
| 100 |
+
<!DOCTYPE html>
|
| 101 |
+
<html lang="sl">
|
| 102 |
+
<head>
|
| 103 |
+
<meta charset="UTF-8">
|
| 104 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 105 |
+
<title>Blagajna | Čebelarstvo Cigoj</title>
|
| 106 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 107 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 108 |
+
<style>
|
| 109 |
+
.form-input:focus {
|
| 110 |
+
outline: none;
|
| 111 |
+
border-color: #d97706;
|
| 112 |
+
box-shadow: 0 0 0 3px rgba(217, 119, 6, 0.1);
|
| 113 |
+
}
|
| 114 |
+
</style>
|
| 115 |
+
</head>
|
| 116 |
+
<body class="bg-gray-50 font-sans">
|
| 117 |
+
<!-- Navigation -->
|
| 118 |
+
<nav class="bg-amber-800 text-white p-4">
|
| 119 |
+
<div class="max-w-7xl mx-auto flex justify-between items-center">
|
| 120 |
+
<div class="flex items-center">
|
| 121 |
+
<i data-feather="hexagon" class="mr-2"></i>
|
| 122 |
+
<h1 class="text-xl font-bold">Čebelarstvo Cigoj</h1>
|
| 123 |
+
</div>
|
| 124 |
+
<a href="index.html" class="flex items-center text-amber-200 hover:text-white">
|
| 125 |
+
<i data-feather="arrow-left" class="mr-1"></i>
|
| 126 |
+
Nazaj v trgovino
|
| 127 |
+
</a>
|
| 128 |
+
</div>
|
| 129 |
+
</nav>
|
| 130 |
+
|
| 131 |
+
<!-- Checkout Form -->
|
| 132 |
+
<div class="max-w-4xl mx-auto p-6">
|
| 133 |
+
<div class="bg-white rounded-lg shadow-lg overflow-hidden">
|
| 134 |
+
<div class="bg-amber-50 px-6 py-4 border-b">
|
| 135 |
+
<h2 class="text-2xl font-bold text-amber-900">Blagajna</h2>
|
| 136 |
+
<p class="text-amber-700 mt-1">Vnesite podatke za oddajo naročila</p>
|
| 137 |
+
</div>
|
| 138 |
+
|
| 139 |
+
<div class="grid md:grid-cols-2 gap-8 p-6">
|
| 140 |
+
<!-- Customer Information -->
|
| 141 |
+
<div>
|
| 142 |
+
<h3 class="text-lg font-semibold text-gray-900 mb-4">Podatki za dostavo</h3>
|
| 143 |
+
<form id="checkout-form" class="space-y-4">
|
| 144 |
+
<div>
|
| 145 |
+
<label for="customer_name" class="block text-sm font-medium text-gray-700 mb-1">Ime in priimek *</label>
|
| 146 |
+
<input type="text" id="customer_name" name="customer_name" required
|
| 147 |
+
class="form-input w-full px-3 py-2 border border-gray-300 rounded-md">
|
| 148 |
+
</div>
|
| 149 |
+
|
| 150 |
+
<div>
|
| 151 |
+
<label for="customer_email" class="block text-sm font-medium text-gray-700 mb-1">E-mail naslov *</label>
|
| 152 |
+
<input type="email" id="customer_email" name="customer_email" required
|
| 153 |
+
class="form-input w-full px-3 py-2 border border-gray-300 rounded-md">
|
| 154 |
+
</div>
|
| 155 |
+
|
| 156 |
+
<div>
|
| 157 |
+
<label for="customer_phone" class="block text-sm font-medium text-gray-700 mb-1">Telefon *</label>
|
| 158 |
+
<input type="tel" id="customer_phone" name="customer_phone" required
|
| 159 |
+
class="form-input w-full px-3 py-2 border border-gray-300 rounded-md">
|
| 160 |
+
</div>
|
| 161 |
+
|
| 162 |
+
<div>
|
| 163 |
+
<label for="customer_address" class="block text-sm font-medium text-gray-700 mb-1">Naslov za dostavo *</label>
|
| 164 |
+
<textarea id="customer_address" name="customer_address" rows="3" required
|
| 165 |
+
class="form-input w-full px-3 py-2 border border-gray-300 rounded-md"></textarea>
|
| 166 |
+
</div>
|
| 167 |
+
|
| 168 |
+
<!-- Shipping Method -->
|
| 169 |
+
<div>
|
| 170 |
+
<label class="block text-sm font-medium text-gray-700 mb-3">Način dostave</label>
|
| 171 |
+
<div class="space-y-2">
|
| 172 |
+
<label class="flex items-center p-3 border border-amber-200 rounded-lg bg-amber-50">
|
| 173 |
+
<input type="radio" name="shipping_method" value="posta_slovenije" checked
|
| 174 |
+
class="text-amber-600 focus:ring-amber-500">
|
| 175 |
+
<div class="ml-3 flex-1">
|
| 176 |
+
<div class="flex justify-between items-center">
|
| 177 |
+
<span class="font-medium text-gray-900">Pošta Slovenije</span>
|
| 178 |
+
<span class="font-bold text-amber-700">4,90 €</span>
|
| 179 |
+
</div>
|
| 180 |
+
<p class="text-sm text-gray-600">Standardna dostava v 2-3 delovnih dneh</p>
|
| 181 |
+
</div>
|
| 182 |
+
</label>
|
| 183 |
+
</div>
|
| 184 |
+
</div>
|
| 185 |
+
</form>
|
| 186 |
+
</div>
|
| 187 |
+
|
| 188 |
+
<!-- Order Summary -->
|
| 189 |
+
<div>
|
| 190 |
+
<h3 class="text-lg font-semibold text-gray-900 mb-4">Povzetek naročila</h3>
|
| 191 |
+
<div class="bg-gray-50 rounded-lg p-4">
|
| 192 |
+
<div id="order-items" class="space-y-3 mb-4">
|
| 193 |
+
<!-- Items will be populated by JavaScript -->
|
| 194 |
+
</div>
|
| 195 |
+
|
| 196 |
+
<div class="border-t pt-4 space-y-2">
|
| 197 |
+
<div class="flex justify-between text-sm">
|
| 198 |
+
<span>Izdelki:</span>
|
| 199 |
+
<span id="subtotal">0,00 €</span>
|
| 200 |
+
</div>
|
| 201 |
+
<div class="flex justify-between text-sm">
|
| 202 |
+
<span>Dostava (Pošta Slovenije):</span>
|
| 203 |
+
<span>4,90 €</span>
|
| 204 |
+
</div>
|
| 205 |
+
<div class="flex justify-between font-bold text-lg border-t pt-2">
|
| 206 |
+
<span>Skupaj:</span>
|
| 207 |
+
<span id="total">4,90 €</span>
|
| 208 |
+
</div>
|
| 209 |
+
</div>
|
| 210 |
+
|
| 211 |
+
<button type="submit" form="checkout-form"
|
| 212 |
+
class="w-full mt-6 bg-amber-600 hover:bg-amber-700 text-white font-medium py-3 px-4 rounded-lg transition duration-200">
|
| 213 |
+
<i data-feather="credit-card" class="inline mr-2"></i>
|
| 214 |
+
Oddaj naročilo
|
| 215 |
+
</button>
|
| 216 |
+
|
| 217 |
+
<p class="text-xs text-gray-500 mt-3 text-center">
|
| 218 |
+
* Plačilo ob prevzemu (gotovina ali kartica)
|
| 219 |
+
</p>
|
| 220 |
+
</div>
|
| 221 |
+
</div>
|
| 222 |
+
</div>
|
| 223 |
+
</div>
|
| 224 |
+
</div>
|
| 225 |
+
|
| 226 |
+
<!-- Success Modal -->
|
| 227 |
+
<div id="success-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
|
| 228 |
+
<div class="bg-white rounded-lg p-8 max-w-md mx-4">
|
| 229 |
+
<div class="text-center">
|
| 230 |
+
<div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100 mb-4">
|
| 231 |
+
<i data-feather="check" class="text-green-600"></i>
|
| 232 |
+
</div>
|
| 233 |
+
<h3 class="text-lg font-medium text-gray-900 mb-2">Naročilo uspešno oddano!</h3>
|
| 234 |
+
<p class="text-gray-500 mb-6">Vaše naročilo št. <span id="order-number"></span> je bilo prejeto. Kontaktirali vas bomo v kratkem.</p>
|
| 235 |
+
<button onclick="window.location.href='index.html'"
|
| 236 |
+
class="bg-amber-600 hover:bg-amber-700 text-white px-6 py-2 rounded-lg">
|
| 237 |
+
Nazaj v trgovino
|
| 238 |
+
</button>
|
| 239 |
+
</div>
|
| 240 |
+
</div>
|
| 241 |
+
</div>
|
| 242 |
+
|
| 243 |
+
<script>
|
| 244 |
+
feather.replace();
|
| 245 |
+
|
| 246 |
+
// Load cart items from localStorage
|
| 247 |
+
function loadCartItems() {
|
| 248 |
+
const cart = JSON.parse(localStorage.getItem('cart') || '[]');
|
| 249 |
+
const orderItemsDiv = document.getElementById('order-items');
|
| 250 |
+
const subtotalSpan = document.getElementById('subtotal');
|
| 251 |
+
const totalSpan = document.getElementById('total');
|
| 252 |
+
|
| 253 |
+
if (cart.length === 0) {
|
| 254 |
+
orderItemsDiv.innerHTML = '<p class="text-gray-500 text-center">Košarica je prazna</p>';
|
| 255 |
+
return;
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
let subtotal = 0;
|
| 259 |
+
orderItemsDiv.innerHTML = '';
|
| 260 |
+
|
| 261 |
+
cart.forEach(item => {
|
| 262 |
+
const itemTotal = item.price * item.quantity;
|
| 263 |
+
subtotal += itemTotal;
|
| 264 |
+
|
| 265 |
+
const itemDiv = document.createElement('div');
|
| 266 |
+
itemDiv.className = 'flex justify-between items-center';
|
| 267 |
+
itemDiv.innerHTML = `
|
| 268 |
+
<div>
|
| 269 |
+
<span class="font-medium">${item.name}</span>
|
| 270 |
+
<span class="text-gray-500 text-sm">× ${item.quantity}</span>
|
| 271 |
+
</div>
|
| 272 |
+
<span>${itemTotal.toFixed(2)} €</span>
|
| 273 |
+
`;
|
| 274 |
+
orderItemsDiv.appendChild(itemDiv);
|
| 275 |
+
});
|
| 276 |
+
|
| 277 |
+
const shipping = 4.90;
|
| 278 |
+
const total = subtotal + shipping;
|
| 279 |
+
|
| 280 |
+
subtotalSpan.textContent = subtotal.toFixed(2) + ' €';
|
| 281 |
+
totalSpan.textContent = total.toFixed(2) + ' €';
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
// Handle form submission
|
| 285 |
+
document.getElementById('checkout-form').addEventListener('submit', async function(e) {
|
| 286 |
+
e.preventDefault();
|
| 287 |
+
|
| 288 |
+
const formData = new FormData(this);
|
| 289 |
+
const cart = JSON.parse(localStorage.getItem('cart') || '[]');
|
| 290 |
+
|
| 291 |
+
if (cart.length === 0) {
|
| 292 |
+
alert('Košarica je prazna!');
|
| 293 |
+
return;
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
const data = {
|
| 297 |
+
customer_name: formData.get('customer_name'),
|
| 298 |
+
customer_email: formData.get('customer_email'),
|
| 299 |
+
customer_phone: formData.get('customer_phone'),
|
| 300 |
+
customer_address: formData.get('customer_address'),
|
| 301 |
+
shipping_method: formData.get('shipping_method'),
|
| 302 |
+
cart_items: cart
|
| 303 |
+
};
|
| 304 |
+
|
| 305 |
+
try {
|
| 306 |
+
const response = await fetch('checkout.php', {
|
| 307 |
+
method: 'POST',
|
| 308 |
+
headers: {
|
| 309 |
+
'Content-Type': 'application/json'
|
| 310 |
+
},
|
| 311 |
+
body: JSON.stringify(data)
|
| 312 |
+
});
|
| 313 |
+
|
| 314 |
+
const result = await response.json();
|
| 315 |
+
|
| 316 |
+
if (response.ok && result.success) {
|
| 317 |
+
// Clear cart
|
| 318 |
+
localStorage.removeItem('cart');
|
| 319 |
+
|
| 320 |
+
// Show success modal
|
| 321 |
+
document.getElementById('order-number').textContent = result.order_id;
|
| 322 |
+
document.getElementById('success-modal').classList.remove('hidden');
|
| 323 |
+
document.getElementById('success-modal').classList.add('flex');
|
| 324 |
+
} else {
|
| 325 |
+
alert(result.error || 'Napaka pri oddaji naročila');
|
| 326 |
+
}
|
| 327 |
+
} catch (error) {
|
| 328 |
+
alert('Napaka pri povezavi s strežnikom');
|
| 329 |
+
console.error('Error:', error);
|
| 330 |
+
}
|
| 331 |
+
});
|
| 332 |
+
|
| 333 |
+
// Load cart items on page load
|
| 334 |
+
loadCartItems();
|
| 335 |
+
</script>
|
| 336 |
+
</body>
|
| 337 |
+
</html>
|
composer.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"require": {
|
| 3 |
+
"firebase/php-jwt": "^6.11"
|
| 4 |
+
}
|
| 5 |
+
}
|
composer.lock
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_readme": [
|
| 3 |
+
"This file locks the dependencies of your project to a known state",
|
| 4 |
+
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
| 5 |
+
"This file is @generated automatically"
|
| 6 |
+
],
|
| 7 |
+
"content-hash": "726b0a650f3a35c706f5a399d9e99662",
|
| 8 |
+
"packages": [
|
| 9 |
+
{
|
| 10 |
+
"name": "firebase/php-jwt",
|
| 11 |
+
"version": "v6.11.1",
|
| 12 |
+
"source": {
|
| 13 |
+
"type": "git",
|
| 14 |
+
"url": "https://github.com/firebase/php-jwt.git",
|
| 15 |
+
"reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66"
|
| 16 |
+
},
|
| 17 |
+
"dist": {
|
| 18 |
+
"type": "zip",
|
| 19 |
+
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
|
| 20 |
+
"reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
|
| 21 |
+
"shasum": ""
|
| 22 |
+
},
|
| 23 |
+
"require": {
|
| 24 |
+
"php": "^8.0"
|
| 25 |
+
},
|
| 26 |
+
"require-dev": {
|
| 27 |
+
"guzzlehttp/guzzle": "^7.4",
|
| 28 |
+
"phpspec/prophecy-phpunit": "^2.0",
|
| 29 |
+
"phpunit/phpunit": "^9.5",
|
| 30 |
+
"psr/cache": "^2.0||^3.0",
|
| 31 |
+
"psr/http-client": "^1.0",
|
| 32 |
+
"psr/http-factory": "^1.0"
|
| 33 |
+
},
|
| 34 |
+
"suggest": {
|
| 35 |
+
"ext-sodium": "Support EdDSA (Ed25519) signatures",
|
| 36 |
+
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
|
| 37 |
+
},
|
| 38 |
+
"type": "library",
|
| 39 |
+
"autoload": {
|
| 40 |
+
"psr-4": {
|
| 41 |
+
"Firebase\\JWT\\": "src"
|
| 42 |
+
}
|
| 43 |
+
},
|
| 44 |
+
"notification-url": "https://packagist.org/downloads/",
|
| 45 |
+
"license": [
|
| 46 |
+
"BSD-3-Clause"
|
| 47 |
+
],
|
| 48 |
+
"authors": [
|
| 49 |
+
{
|
| 50 |
+
"name": "Neuman Vong",
|
| 51 |
+
"email": "neuman+pear@twilio.com",
|
| 52 |
+
"role": "Developer"
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"name": "Anant Narayanan",
|
| 56 |
+
"email": "anant@php.net",
|
| 57 |
+
"role": "Developer"
|
| 58 |
+
}
|
| 59 |
+
],
|
| 60 |
+
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
|
| 61 |
+
"homepage": "https://github.com/firebase/php-jwt",
|
| 62 |
+
"keywords": [
|
| 63 |
+
"jwt",
|
| 64 |
+
"php"
|
| 65 |
+
],
|
| 66 |
+
"support": {
|
| 67 |
+
"issues": "https://github.com/firebase/php-jwt/issues",
|
| 68 |
+
"source": "https://github.com/firebase/php-jwt/tree/v6.11.1"
|
| 69 |
+
},
|
| 70 |
+
"time": "2025-04-09T20:32:01+00:00"
|
| 71 |
+
}
|
| 72 |
+
],
|
| 73 |
+
"packages-dev": [],
|
| 74 |
+
"aliases": [],
|
| 75 |
+
"minimum-stability": "stable",
|
| 76 |
+
"stability-flags": [],
|
| 77 |
+
"prefer-stable": false,
|
| 78 |
+
"prefer-lowest": false,
|
| 79 |
+
"platform": [],
|
| 80 |
+
"platform-dev": [],
|
| 81 |
+
"plugin-api-version": "2.6.0"
|
| 82 |
+
}
|
config.php
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
// Include Composer autoloader
|
| 3 |
+
require_once __DIR__ . '/vendor/autoload.php';
|
| 4 |
+
|
| 5 |
+
// Use Firebase JWT
|
| 6 |
+
use Firebase\JWT\JWT;
|
| 7 |
+
use Firebase\JWT\Key;
|
| 8 |
+
|
| 9 |
+
// Show errors for development (disable in production)
|
| 10 |
+
ini_set('display_errors', 1);
|
| 11 |
+
ini_set('display_startup_errors', 1);
|
| 12 |
+
error_reporting(E_ALL);
|
| 13 |
+
|
| 14 |
+
// Database configuration - using SQLite for development in Replit
|
| 15 |
+
define('DB_HOST', '');
|
| 16 |
+
define('DB_USER', '');
|
| 17 |
+
define('DB_PASS', '');
|
| 18 |
+
define('DB_NAME', 'cebelarstvo_cigoj.db');
|
| 19 |
+
|
| 20 |
+
// Establish database connection (SQLite for now)
|
| 21 |
+
try {
|
| 22 |
+
$pdo = new PDO("sqlite:" . __DIR__ . "/" . DB_NAME);
|
| 23 |
+
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
| 24 |
+
// Enable foreign key support for SQLite
|
| 25 |
+
$pdo->exec('PRAGMA foreign_keys = ON;');
|
| 26 |
+
} catch(PDOException $e) {
|
| 27 |
+
die("ERROR: Could not connect to database. " . $e->getMessage());
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
// Set headers for API (only set when needed to avoid conflicts with HTML pages)
|
| 31 |
+
if (isset($_SERVER['REQUEST_URI']) &&
|
| 32 |
+
strpos($_SERVER['REQUEST_URI'], '.php') !== false &&
|
| 33 |
+
strpos($_SERVER['REQUEST_URI'], '.html') === false &&
|
| 34 |
+
!strpos($_SERVER['REQUEST_URI'], 'backend.html') &&
|
| 35 |
+
!strpos($_SERVER['REQUEST_URI'], 'backend.php') &&
|
| 36 |
+
!strpos($_SERVER['REQUEST_URI'], 'admin_login.php') &&
|
| 37 |
+
!strpos($_SERVER['REQUEST_URI'], 'checkout.php') &&
|
| 38 |
+
!strpos($_SERVER['REQUEST_URI'], 'thank_you.php')) {
|
| 39 |
+
header("Content-Type: application/json; charset=UTF-8");
|
| 40 |
+
// More secure CORS - restrict to localhost for development
|
| 41 |
+
$allowed_origins = ['http://localhost:5000', 'https://localhost:5000'];
|
| 42 |
+
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
| 43 |
+
if (in_array($origin, $allowed_origins)) {
|
| 44 |
+
header("Access-Control-Allow-Origin: " . $origin);
|
| 45 |
+
}
|
| 46 |
+
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE");
|
| 47 |
+
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
// JWT Secret key - use environment variable for security
|
| 51 |
+
$jwt_secret = 'your_hardcoded_secret_here';
|
| 52 |
+
define('JWT_SECRET', $jwt_secret);
|
| 53 |
+
|
| 54 |
+
// JWT verification middleware function - fixed for PHP built-in server
|
| 55 |
+
function verifyToken() {
|
| 56 |
+
// Use getallheaders() with fallback for PHP built-in server
|
| 57 |
+
$headers = function_exists('getallheaders') ? getallheaders() : [];
|
| 58 |
+
|
| 59 |
+
// Fallback for PHP built-in server
|
| 60 |
+
if (empty($headers) && isset($_SERVER['HTTP_AUTHORIZATION'])) {
|
| 61 |
+
$headers['Authorization'] = $_SERVER['HTTP_AUTHORIZATION'];
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
if(!isset($headers['Authorization'])) {
|
| 65 |
+
http_response_code(401);
|
| 66 |
+
echo json_encode(array("message" => "Access Denied. No token provided."));
|
| 67 |
+
exit;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
try {
|
| 71 |
+
$token = str_replace('Bearer ', '', $headers['Authorization']);
|
| 72 |
+
$decoded = JWT::decode($token, new Key(JWT_SECRET, 'HS256'));
|
| 73 |
+
return $decoded;
|
| 74 |
+
} catch(Exception $e) {
|
| 75 |
+
http_response_code(401);
|
| 76 |
+
echo json_encode(array("message" => "Invalid token", "error" => $e->getMessage()));
|
| 77 |
+
exit;
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
?>
|
customers.php
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
<?php
|
| 3 |
+
require_once 'config.php';
|
| 4 |
+
|
| 5 |
+
// JWT verification middleware
|
| 6 |
+
require_once 'require_auth.php';
|
| 7 |
+
|
| 8 |
+
// GET all customers
|
| 9 |
+
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
| 10 |
+
$stmt = $pdo->query("SELECT * FROM customers");
|
| 11 |
+
$customers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 12 |
+
echo json_encode($customers);
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
// GET customer orders
|
| 16 |
+
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['customer_id'])) {
|
| 17 |
+
$customer_id = $_GET['customer_id'];
|
| 18 |
+
|
| 19 |
+
$stmt = $pdo->prepare("SELECT * FROM orders WHERE customer_email = (SELECT email FROM customers WHERE id = ?)");
|
| 20 |
+
$stmt->execute([$customer_id]);
|
| 21 |
+
$orders = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 22 |
+
|
| 23 |
+
echo json_encode($orders);
|
| 24 |
+
}
|
| 25 |
+
?>
|
index.html
CHANGED
|
@@ -1,19 +1,390 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="sl">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Premium Naravni Med in Čebelarski Izdelki | Čebelarstvo Cigoj</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
|
| 9 |
+
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
|
| 10 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 11 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 12 |
+
<style>
|
| 13 |
+
.aspect-w-1 {
|
| 14 |
+
position: relative;
|
| 15 |
+
padding-bottom: 100%;
|
| 16 |
+
}
|
| 17 |
+
.aspect-h-1 {
|
| 18 |
+
position: relative;
|
| 19 |
+
padding-bottom: 100%;
|
| 20 |
+
}
|
| 21 |
+
.aspect-w-1 > * {
|
| 22 |
+
position: absolute;
|
| 23 |
+
top: 0;
|
| 24 |
+
left: 0;
|
| 25 |
+
width: 100%;
|
| 26 |
+
height: 100%;
|
| 27 |
+
}
|
| 28 |
+
.group:hover .group-hover\:opacity-75 {
|
| 29 |
+
opacity: 0.75;
|
| 30 |
+
}
|
| 31 |
+
</style>
|
| 32 |
+
</head>
|
| 33 |
+
<body class="font-sans bg-gray-50">
|
| 34 |
+
<!-- Header -->
|
| 35 |
+
<header class="bg-amber-800 text-white shadow-md sticky top-0 z-50">
|
| 36 |
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 37 |
+
<div class="flex justify-between items-center py-4 md:justify-start md:space-x-10">
|
| 38 |
+
<div class="flex justify-start lg:w-0 lg:flex-1">
|
| 39 |
+
<div class="flex items-center space-x-2">
|
| 40 |
+
<i data-feather="hexagon" class="text-amber-300"></i>
|
| 41 |
+
<h1 class="text-xl font-bold">Čebelarstvo Cigoj</h1>
|
| 42 |
+
</div>
|
| 43 |
+
</div>
|
| 44 |
+
<nav class="hidden md:flex space-x-10">
|
| 45 |
+
<a href="#" class="text-base font-medium text-white hover:text-amber-200">Domov</a>
|
| 46 |
+
<a href="#" class="text-base font-medium text-white hover:text-amber-200">Izdelki</a>
|
| 47 |
+
<a href="#" class="text-base font-medium text-white hover:text-amber-200">O nas</a>
|
| 48 |
+
<a href="#" class="text-base font-medium text-white hover:text-amber-200">Kontakt</a>
|
| 49 |
+
</nav>
|
| 50 |
+
<div class="flex items-center justify-end md:flex-1 lg:w-0 space-x-6">
|
| 51 |
+
<a href="cart.html" class="relative p-2 rounded-full hover:bg-amber-700 transition" id="cart-button">
|
| 52 |
+
<i data-feather="shopping-cart"></i>
|
| 53 |
+
<span id="cart-count" class="hidden absolute -top-1 -right-1 bg-red-500 text-white text-xs font-bold rounded-full h-5 w-5 flex items-center justify-center">0</span>
|
| 54 |
+
</a>
|
| 55 |
+
<a href="admin_login.php" class="text-white hover:text-amber-200 px-3 py-2 rounded">Admin</a>
|
| 56 |
+
<button class="md:hidden p-2 rounded-full hover:bg-amber-700 transition">
|
| 57 |
+
<i data-feather="menu"></i>
|
| 58 |
+
</button>
|
| 59 |
+
</div>
|
| 60 |
+
</div>
|
| 61 |
+
</div>
|
| 62 |
+
</header>
|
| 63 |
+
|
| 64 |
+
<!-- Hero Banner -->
|
| 65 |
+
<section class="bg-gradient-to-br from-amber-50 to-amber-100 py-16">
|
| 66 |
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 67 |
+
<div class="lg:grid lg:grid-cols-12 lg:gap-8 items-center">
|
| 68 |
+
<div class="sm:text-center md:max-w-2xl md:mx-auto lg:col-span-6 lg:text-left">
|
| 69 |
+
<h2 class="text-4xl tracking-tight font-extrabold text-amber-900 sm:text-5xl md:text-6xl">
|
| 70 |
+
<span class="block">Naravni cvetni prahovi</span>
|
| 71 |
+
</h2>
|
| 72 |
+
<p class="mt-3 text-lg text-amber-800 sm:mt-5 sm:text-xl sm:max-w-xl sm:mx-auto md:mt-5 md:text-xl lg:mx-0">
|
| 73 |
+
Izjemna superhrana direktno iz narave, bogata z vitamini in minerali.
|
| 74 |
+
</p>
|
| 75 |
+
<p class="mt-3 text-2xl font-bold text-amber-700">Že od 5€</p>
|
| 76 |
+
<div class="mt-8 sm:max-w-lg sm:mx-auto sm:text-center lg:text-left lg:mx-0">
|
| 77 |
+
<a href="#" class="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-lg shadow-sm text-white bg-amber-600 hover:bg-amber-700 transition">
|
| 78 |
+
Oglejte si ponudbo
|
| 79 |
+
<i data-feather="arrow-right" class="ml-2"></i>
|
| 80 |
+
</a>
|
| 81 |
+
</div>
|
| 82 |
+
</div>
|
| 83 |
+
<div class="mt-12 relative sm:max-w-lg sm:mx-auto lg:mt-0 lg:max-w-none lg:mx-0 lg:col-span-6 lg:flex lg:items-center">
|
| 84 |
+
<div class="relative mx-auto w-full rounded-lg shadow-lg lg:max-w-md">
|
| 85 |
+
<img src="https://static.photos/nature/640x360/42" alt="Cvetni prah" class="object-cover rounded-lg w-full">
|
| 86 |
+
</div>
|
| 87 |
+
</div>
|
| 88 |
+
</div>
|
| 89 |
+
</div>
|
| 90 |
+
</section>
|
| 91 |
+
|
| 92 |
+
<!-- Featured Products -->
|
| 93 |
+
<section class="py-16 bg-white">
|
| 94 |
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 95 |
+
<h2 class="text-3xl font-bold text-center text-amber-900 mb-12" data-aos="fade-up">Priljubljeni izdelki</h2>
|
| 96 |
+
|
| 97 |
+
<div class="grid grid-cols-1 gap-y-10 gap-x-6 sm:grid-cols-2 lg:grid-cols-4 xl:gap-x-8">
|
| 98 |
+
<!-- Product 1 -->
|
| 99 |
+
<div class="product-card bg-white rounded-lg overflow-hidden shadow-md relative transition duration-300" data-aos="fade-up" data-aos-delay="100">
|
| 100 |
+
<div class="product-card bg-white rounded-lg overflow-hidden shadow-md relative transition duration-300" data-aos="fade-up" data-aos-delay="100" data-product-id="1">
|
| 101 |
+
<div class="w-full min-h-80 bg-gray-200 aspect-w-1 aspect-h-1 rounded-md overflow-hidden group-hover:opacity-75 lg:h-80 lg:aspect-none">
|
| 102 |
+
<img src="https://static.photos/nature/320x240/101" alt="Cvetni prah 50g" class="w-full h-full object-center object-cover lg:w-full lg:h-full">
|
| 103 |
+
</div>
|
| 104 |
+
<div class="mt-4 flex justify-between">
|
| 105 |
+
<div>
|
| 106 |
+
<h3 class="text-lg text-amber-900 font-bold">
|
| 107 |
+
<a href="#">
|
| 108 |
+
<span aria-hidden="true" class="absolute inset-0"></span>
|
| 109 |
+
Cvetni prah 50g
|
| 110 |
+
</a>
|
| 111 |
+
</h3>
|
| 112 |
+
<p class="mt-1 text-sm text-gray-500">100g</p>
|
| 113 |
+
</div>
|
| 114 |
+
<p class="text-lg font-bold text-amber-700">4,50€</p>
|
| 115 |
+
</div>
|
| 116 |
+
<div class="mt-4 grid grid-cols-2 gap-2">
|
| 117 |
+
<button class="w-full bg-amber-100 hover:bg-amber-200 text-amber-800 py-2 px-2 rounded text-sm flex items-center justify-center transition">
|
| 118 |
+
<i data-feather="eye" class="mr-1 w-4 h-4"></i> <span>Ogled</span>
|
| 119 |
+
</button>
|
| 120 |
+
<button class="w-full bg-amber-600 hover:bg-amber-700 text-white py-2 px-2 rounded text-sm flex items-center justify-center transition">
|
| 121 |
+
<i data-feather="shopping-cart" class="mr-1 w-4 h-4"></i> <span>V košarico</span>
|
| 122 |
+
</button>
|
| 123 |
+
</div>
|
| 124 |
+
<div class="absolute top-2 right-2 bg-red-500 text-white text-xs font-bold px-2 py-1 rounded-full">
|
| 125 |
+
RAZPRODAJA!
|
| 126 |
+
</div>
|
| 127 |
+
</div>
|
| 128 |
+
|
| 129 |
+
<!-- Product 2 -->
|
| 130 |
+
<div class="product-card bg-white rounded-lg overflow-hidden shadow-md relative transition duration-300" data-aos="fade-up" data-aos-delay="200">
|
| 131 |
+
<img src="https://static.photos/nature/320x240/102" alt="Balzam za ustnice" class="w-full h-48 object-cover">
|
| 132 |
+
<div class="p-4">
|
| 133 |
+
<h3 class="font-bold text-lg mb-2 text-amber-900">Balzam za ustnice iz čebeljega voska</h3>
|
| 134 |
+
<p class="text-amber-700 font-bold text-xl mb-3">2,50€</p>
|
| 135 |
+
<div class="flex justify-between">
|
| 136 |
+
<button class="bg-amber-100 hover:bg-amber-200 text-amber-800 px-3 py-1 rounded text-sm flex items-center transition">
|
| 137 |
+
<i data-feather="eye" class="mr-1 w-4 h-4"></i> Hitri ogled
|
| 138 |
+
</button>
|
| 139 |
+
<button class="bg-amber-600 hover:bg-amber-700 text-white px-3 py-1 rounded text-sm flex items-center transition">
|
| 140 |
+
<i data-feather="shopping-cart" class="mr-1 w-4 h-4"></i> V košarico
|
| 141 |
+
</button>
|
| 142 |
+
</div>
|
| 143 |
+
</div>
|
| 144 |
+
</div>
|
| 145 |
+
</div>
|
| 146 |
+
|
| 147 |
+
<!-- Product 3 -->
|
| 148 |
+
<div class="product-card bg-white rounded-lg overflow-hidden shadow-md relative transition duration-300" data-aos="fade-up" data-aos-delay="300">
|
| 149 |
+
<div class="sale-badge bg-red-500 text-white text-xs font-bold px-2 py-1 rounded-full">
|
| 150 |
+
RAZPRODAJA!
|
| 151 |
+
</div>
|
| 152 |
+
<img src="https://static.photos/nature/320x240/103" alt="Cvetni prah 100g" class="w-full h-48 object-cover">
|
| 153 |
+
<div class="p-4">
|
| 154 |
+
<h3 class="font-bold text-lg mb-2 text-amber-900">Cvetni prah 100g</h3>
|
| 155 |
+
<p class="text-amber-700 font-bold text-xl mb-3">7,00€</p>
|
| 156 |
+
<div class="flex justify-between">
|
| 157 |
+
<button class="bg-amber-100 hover:bg-amber-200 text-amber-800 px-3 py-1 rounded text-sm flex items-center transition">
|
| 158 |
+
<i data-feather="eye" class="mr-1 w-4 h-4"></i> Hitri ogled
|
| 159 |
+
</button>
|
| 160 |
+
<button class="bg-amber-600 hover:bg-amber-700 text-white px-3 py-1 rounded text-sm flex items-center transition">
|
| 161 |
+
<i data-feather="shopping-cart" class="mr-1 w-4 h-4"></i> V košarico
|
| 162 |
+
</button>
|
| 163 |
+
</div>
|
| 164 |
+
</div>
|
| 165 |
+
</div>
|
| 166 |
+
</div>
|
| 167 |
+
|
| 168 |
+
<!-- Product 4 -->
|
| 169 |
+
<div class="product-card bg-white rounded-lg overflow-hidden shadow-md relative transition duration-300" data-aos="fade-up" data-aos-delay="400">
|
| 170 |
+
<div class="sale-badge bg-red-500 text-white text-xs font-bold px-2 py-1 rounded-full">
|
| 171 |
+
RAZPRODAJA!
|
| 172 |
+
</div>
|
| 173 |
+
<img src="https://static.photos/nature/320x240/104" alt="Balzam za ustnice" class="w-full h-48 object-cover">
|
| 174 |
+
<div class="p-4">
|
| 175 |
+
<h3 class="font-bold text-lg mb-2 text-amber-900">Balzam za ustnice iz čebeljega voska</h3>
|
| 176 |
+
<p class="text-amber-700 font-bold text-xl mb-3">2,50€</p>
|
| 177 |
+
<div class="flex justify-between">
|
| 178 |
+
<button class="bg-amber-100 hover:bg-amber-200 text-amber-800 px-3 py-1 rounded text-sm flex items-center transition">
|
| 179 |
+
<i data-feather="eye" class="mr-1 w-4 h-4"></i> Hitri ogled
|
| 180 |
+
</button>
|
| 181 |
+
<button class="bg-amber-600 hover:bg-amber-700 text-white px-3 py-1 rounded text-sm flex items-center transition">
|
| 182 |
+
<i data-feather="shopping-cart" class="mr-1 w-4 h-4"></i> V košarico
|
| 183 |
+
</button>
|
| 184 |
+
</div>
|
| 185 |
+
</div>
|
| 186 |
+
</div>
|
| 187 |
+
</div>
|
| 188 |
+
</div>
|
| 189 |
+
|
| 190 |
+
<div class="text-center mt-12">
|
| 191 |
+
<a href="#" class="inline-flex items-center text-amber-700 hover:text-amber-900 font-medium transition">
|
| 192 |
+
Vsi izdelki <i data-feather="chevron-right" class="ml-1"></i>
|
| 193 |
+
</a>
|
| 194 |
+
</div>
|
| 195 |
+
</div>
|
| 196 |
+
</section>
|
| 197 |
+
|
| 198 |
+
<!-- News Section -->
|
| 199 |
+
<section class="py-16 bg-amber-50">
|
| 200 |
+
<div class="container mx-auto px-4 flex flex-col md:flex-row items-center">
|
| 201 |
+
<div class="md:w-1/2 mb-8 md:mb-0" data-aos="fade-right">
|
| 202 |
+
<img src="https://static.photos/nature/640x360/105" alt="Nov čebelnjak" class="rounded-lg shadow-lg w-full">
|
| 203 |
+
</div>
|
| 204 |
+
<div class="md:w-1/2 md:pl-12" data-aos="fade-left">
|
| 205 |
+
<h2 class="text-3xl font-bold text-amber-900 mb-4">Čebelarstvo Cigoj gradi nov čebelnjak!</h2>
|
| 206 |
+
<p class="text-lg text-amber-800 mb-6">
|
| 207 |
+
Z veseljem sporočamo, da gradimo nov čebelnjak! Korak naprej za boljše pogoje za naše čebele in kakovostnejše pridelke iz domače narave.
|
| 208 |
+
</p>
|
| 209 |
+
<p class="text-amber-800 mb-6">
|
| 210 |
+
Hvala vsem, ki nas spremljate in podpirate.
|
| 211 |
+
</p>
|
| 212 |
+
<a href="#" class="text-amber-700 hover:text-amber-900 font-medium inline-flex items-center transition">
|
| 213 |
+
Preberite več <i data-feather="arrow-right" class="ml-2"></i>
|
| 214 |
+
</a>
|
| 215 |
+
</div>
|
| 216 |
+
</div>
|
| 217 |
+
</section>
|
| 218 |
+
|
| 219 |
+
<!-- Best Sellers -->
|
| 220 |
+
<section class="py-16 bg-white">
|
| 221 |
+
<div class="container mx-auto px-4">
|
| 222 |
+
<h2 class="text-3xl font-bold text-center text-amber-900 mb-12" data-aos="fade-up">Prodajne uspešnice</h2>
|
| 223 |
+
|
| 224 |
+
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8">
|
| 225 |
+
<!-- Product 1 -->
|
| 226 |
+
<div class="product-card bg-white rounded-lg overflow-hidden shadow-md relative transition duration-300" data-aos="fade-up" data-aos-delay="100">
|
| 227 |
+
<img src="https://static.photos/nature/320x240/106" alt="Balzam za ustnice" class="w-full h-48 object-cover">
|
| 228 |
+
<div class="p-4">
|
| 229 |
+
<h3 class="font-bold text-lg mb-2 text-amber-900">Balzam za ustnice iz čebeljega voska</h3>
|
| 230 |
+
<p class="text-amber-700 font-bold text-xl mb-3">2,50€</p>
|
| 231 |
+
<div class="flex justify-between">
|
| 232 |
+
<button class="bg-amber-100 hover:bg-amber-200 text-amber-800 px-3 py-1 rounded text-sm flex items-center transition">
|
| 233 |
+
<i data-feather="eye" class="mr-1 w-4 h-4"></i> Hitri ogled
|
| 234 |
+
</button>
|
| 235 |
+
<button class="bg-amber-600 hover:bg-amber-700 text-white px-3 py-1 rounded text-sm flex items-center transition">
|
| 236 |
+
<i data-feather="shopping-cart" class="mr-1 w-4 h-4"></i> V košarico
|
| 237 |
+
</button>
|
| 238 |
+
</div>
|
| 239 |
+
</div>
|
| 240 |
+
</div>
|
| 241 |
+
|
| 242 |
+
<!-- Product 2 -->
|
| 243 |
+
<div class="product-card bg-white rounded-lg overflow-hidden shadow-md relative transition duration-300" data-aos="fade-up" data-aos-delay="200">
|
| 244 |
+
<div class="sale-badge bg-red-500 text-white text-xs font-bold px-2 py-1 rounded-full">
|
| 245 |
+
RAZPRODAJA!
|
| 246 |
+
</div>
|
| 247 |
+
<img src="https://static.photos/nature/320x240/107" alt="Balzam za ustnice" class="w-full h-48 object-cover">
|
| 248 |
+
<div class="p-4">
|
| 249 |
+
<h3 class="font-bold text-lg mb-2 text-amber-900">Balzam za ustnice iz čebeljega voska</h3>
|
| 250 |
+
<p class="text-amber-700 font-bold text-xl mb-3">2,50€</p>
|
| 251 |
+
<div class="flex justify-between">
|
| 252 |
+
<button class="bg-amber-100 hover:bg-amber-200 text-amber-800 px-3 py-1 rounded text-sm flex items-center transition">
|
| 253 |
+
<i data-feather="eye" class="mr-1 w-4 h-4"></i> Hitri ogled
|
| 254 |
+
</button>
|
| 255 |
+
<button class="bg-amber-600 hover:bg-amber-700 text-white px-3 py-1 rounded text-sm flex items-center transition">
|
| 256 |
+
<i data-feather="shopping-cart" class="mr-1 w-4 h-4"></i> V košarico
|
| 257 |
+
</button>
|
| 258 |
+
</div>
|
| 259 |
+
</div>
|
| 260 |
+
</div>
|
| 261 |
+
|
| 262 |
+
<!-- Product 3 -->
|
| 263 |
+
<div class="product-card bg-white rounded-lg overflow-hidden shadow-md relative transition duration-300" data-aos="fade-up" data-aos-delay="300">
|
| 264 |
+
<div class="sale-badge bg-red-500 text-white text-xs font-bold px-2 py-1 rounded-full">
|
| 265 |
+
RAZPRODAJA!
|
| 266 |
+
</div>
|
| 267 |
+
<img src="https://static.photos/nature/320x240/108" alt="Cvetni prah 100g" class="w-full h-48 object-cover">
|
| 268 |
+
<div class="p-4">
|
| 269 |
+
<h3 class="font-bold text-lg mb-2 text-amber-900">Cvetni prah 100g</h3>
|
| 270 |
+
<p class="text-amber-700 font-bold text-xl mb-3">7,00€</p>
|
| 271 |
+
<div class="flex justify-between">
|
| 272 |
+
<button class="bg-amber-100 hover:bg-amber-200 text-amber-800 px-3 py-1 rounded text-sm flex items-center transition">
|
| 273 |
+
<i data-feather="eye" class="mr-1 w-4 h-4"></i> Hitri ogled
|
| 274 |
+
</button>
|
| 275 |
+
<button class="bg-amber-600 hover:bg-amber-700 text-white px-3 py-1 rounded text-sm flex items-center transition">
|
| 276 |
+
<i data-feather="shopping-cart" class="mr-1 w-4 h-4"></i> V košarico
|
| 277 |
+
</button>
|
| 278 |
+
</div>
|
| 279 |
+
</div>
|
| 280 |
+
</div>
|
| 281 |
+
</div>
|
| 282 |
+
|
| 283 |
+
<div class="text-center mt-12">
|
| 284 |
+
<a href="#" class="inline-flex items-center text-amber-700 hover:text-amber-900 font-medium transition">
|
| 285 |
+
Vse prodajne uspešnice <i data-feather="chevron-right" class="ml-1"></i>
|
| 286 |
+
</a>
|
| 287 |
+
</div>
|
| 288 |
+
</div>
|
| 289 |
+
</section>
|
| 290 |
+
|
| 291 |
+
<!-- Footer -->
|
| 292 |
+
<footer class="bg-amber-900 text-white py-12">
|
| 293 |
+
<div class="container mx-auto px-4">
|
| 294 |
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-8">
|
| 295 |
+
<div>
|
| 296 |
+
<h3 class="text-xl font-bold mb-4">Čebelarstvo Cigoj</h3>
|
| 297 |
+
<p class="text-amber-200">Kakovostni naravni izdelki iz čebeljega panja.</p>
|
| 298 |
+
</div>
|
| 299 |
+
<div>
|
| 300 |
+
<h4 class="font-bold mb-4">Izdelki</h4>
|
| 301 |
+
<ul class="space-y-2">
|
| 302 |
+
<li><a href="#" class="text-amber-200 hover:text-white transition">Med</a></li>
|
| 303 |
+
<li><a href="#" class="text-amber-200 hover:text-white transition">Cvetni prah</a></li>
|
| 304 |
+
<li><a href="#" class="text-amber-200 hover:text-white transition">Čebelji vosek</a></li>
|
| 305 |
+
<li><a href="#" class="text-amber-200 hover:text-white transition">Balzami</a></li>
|
| 306 |
+
</ul>
|
| 307 |
+
</div>
|
| 308 |
+
<div>
|
| 309 |
+
<h4 class="font-bold mb-4">Povezave</h4>
|
| 310 |
+
<ul class="space-y-2">
|
| 311 |
+
<li><a href="#" class="text-amber-200 hover:text-white transition">O nas</a></li>
|
| 312 |
+
<li><a href="#" class="text-amber-200 hover:text-white transition">Blog</a></li>
|
| 313 |
+
<li><a href="#" class="text-amber-200 hover:text-white transition">Pogoji poslovanja</a></li>
|
| 314 |
+
<li><a href="#" class="text-amber-200 hover:text-white transition">Varstvo zasebnosti</a></li>
|
| 315 |
+
<li><a href="backend.php" class="text-amber-200 hover:text-white transition">Administracija</a></li>
|
| 316 |
+
</ul>
|
| 317 |
+
</div>
|
| 318 |
+
<div>
|
| 319 |
+
<h4 class="font-bold mb-4">Kontakt</h4>
|
| 320 |
+
<ul class="space-y-2">
|
| 321 |
+
<li class="flex items-center">
|
| 322 |
+
<i data-feather="mail" class="mr-2"></i> info@cebelarstvo-cigoj.si
|
| 323 |
+
</li>
|
| 324 |
+
<li class="flex items-center">
|
| 325 |
+
<i data-feather="phone" class="mr-2"></i> +386 40 123 456
|
| 326 |
+
</li>
|
| 327 |
+
<li class="flex items-center">
|
| 328 |
+
<i data-feather="map-pin" class="mr-2"></i> Čebelarska ulica 1, 1000 Ljubljana
|
| 329 |
+
</li>
|
| 330 |
+
</ul>
|
| 331 |
+
</div>
|
| 332 |
+
</div>
|
| 333 |
+
<div class="border-t border-amber-800 mt-8 pt-8 text-center text-amber-200">
|
| 334 |
+
<p>© 2023 Čebelarstvo Cigoj. Vse pravice pridržane.</p>
|
| 335 |
+
</div>
|
| 336 |
+
</div>
|
| 337 |
+
</footer>
|
| 338 |
+
|
| 339 |
+
<script>
|
| 340 |
+
// Cart functions
|
| 341 |
+
function addToCart(productId, productName, productPrice) {
|
| 342 |
+
let cart = JSON.parse(localStorage.getItem('cart')) || [];
|
| 343 |
+
let found = false;
|
| 344 |
+
|
| 345 |
+
cart.forEach(item => {
|
| 346 |
+
if(item.id === productId) {
|
| 347 |
+
item.quantity++;
|
| 348 |
+
found = true;
|
| 349 |
+
}
|
| 350 |
+
});
|
| 351 |
+
|
| 352 |
+
if(!found) {
|
| 353 |
+
cart.push({
|
| 354 |
+
id: productId,
|
| 355 |
+
name: productName,
|
| 356 |
+
price: productPrice,
|
| 357 |
+
quantity: 1
|
| 358 |
+
});
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
localStorage.setItem('cart', JSON.stringify(cart));
|
| 362 |
+
updateCartCount();
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
function updateCartCount() {
|
| 366 |
+
const cart = JSON.parse(localStorage.getItem('cart')) || [];
|
| 367 |
+
const count = cart.reduce((total, item) => total + item.quantity, 0);
|
| 368 |
+
const cartCount = document.getElementById('cart-count');
|
| 369 |
+
|
| 370 |
+
cartCount.textContent = count;
|
| 371 |
+
if(count > 0) {
|
| 372 |
+
cartCount.classList.remove('hidden');
|
| 373 |
+
} else {
|
| 374 |
+
cartCount.classList.add('hidden');
|
| 375 |
+
}
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
// Initialize cart count on page load
|
| 379 |
+
updateCartCount();
|
| 380 |
+
|
| 381 |
+
AOS.init({
|
| 382 |
+
duration: 800,
|
| 383 |
+
easing: 'ease-in-out',
|
| 384 |
+
once: true
|
| 385 |
+
});
|
| 386 |
+
feather.replace();
|
| 387 |
+
</script>
|
| 388 |
+
<script src="cart-fix.js"></script>
|
| 389 |
+
</body>
|
| 390 |
+
</html>
|
login.php
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
require_once 'config.php';
|
| 3 |
+
|
| 4 |
+
// Only handle POST requests
|
| 5 |
+
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
| 6 |
+
http_response_code(405);
|
| 7 |
+
echo json_encode(['message' => 'Method not allowed']);
|
| 8 |
+
exit;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
// Support both JSON (AJAX) and form-encoded POSTs. Prefer JSON when Content-Type is application/json.
|
| 12 |
+
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
|
| 13 |
+
if (stripos($contentType, 'application/json') !== false) {
|
| 14 |
+
$input = json_decode(file_get_contents('php://input'), true);
|
| 15 |
+
$username = htmlspecialchars(trim($input['username'] ?? ''), ENT_QUOTES, 'UTF-8');
|
| 16 |
+
$password = $input['password'] ?? '';
|
| 17 |
+
$is_form = false;
|
| 18 |
+
} else {
|
| 19 |
+
// form submission (application/x-www-form-urlencoded or multipart)
|
| 20 |
+
$username = htmlspecialchars(trim($_POST['username'] ?? ''), ENT_QUOTES, 'UTF-8');
|
| 21 |
+
$password = $_POST['password'] ?? '';
|
| 22 |
+
$is_form = true;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
// Basic validation
|
| 26 |
+
if (empty($username) || empty($password)) {
|
| 27 |
+
if ($is_form) {
|
| 28 |
+
// Redirect back with error flag for simple form UX
|
| 29 |
+
header('Location: admin_login.php?error=1');
|
| 30 |
+
exit;
|
| 31 |
+
}
|
| 32 |
+
http_response_code(400);
|
| 33 |
+
echo json_encode(['message' => 'Username and password required']);
|
| 34 |
+
exit;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
// Get client IP
|
| 38 |
+
$ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
| 39 |
+
|
| 40 |
+
// Ensure login_attempts table exists (idempotent)
|
| 41 |
+
$pdo->exec(
|
| 42 |
+
"CREATE TABLE IF NOT EXISTS login_attempts (
|
| 43 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 44 |
+
email TEXT,
|
| 45 |
+
ip_address TEXT,
|
| 46 |
+
attempted_at DATETIME DEFAULT (datetime('now')),
|
| 47 |
+
success INTEGER DEFAULT 0
|
| 48 |
+
);"
|
| 49 |
+
);
|
| 50 |
+
|
| 51 |
+
// DEVELOPMENT: allow temporary bypass of rate-limiting by creating a file named
|
| 52 |
+
// DISABLE_RATE_LIMIT in the project root. This is handy when resetting or bootstrapping
|
| 53 |
+
// the admin account. Remove the file to re-enable rate limiting.
|
| 54 |
+
$bypass_rate_limit = false;
|
| 55 |
+
if (file_exists(__DIR__ . '/DISABLE_RATE_LIMIT')) {
|
| 56 |
+
$bypass_rate_limit = true;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
// Check rate limits (5 attempts per email in 15 minutes, 10 attempts per IP in 15 minutes)
|
| 60 |
+
$now = date('Y-m-d H:i:s');
|
| 61 |
+
$fifteen_minutes_ago = date('Y-m-d H:i:s', strtotime('-15 minutes'));
|
| 62 |
+
|
| 63 |
+
// Check username rate limit (skip if bypass enabled)
|
| 64 |
+
if (!$bypass_rate_limit) {
|
| 65 |
+
$stmt = $pdo->prepare("SELECT COUNT(*) as attempts FROM login_attempts WHERE email = ? AND attempted_at > ? AND success = 0");
|
| 66 |
+
$stmt->execute([$username, $fifteen_minutes_ago]);
|
| 67 |
+
$username_attempts = $stmt->fetch()['attempts'];
|
| 68 |
+
|
| 69 |
+
if ($username_attempts >= 5) {
|
| 70 |
+
// Record failed attempt
|
| 71 |
+
$stmt = $pdo->prepare("INSERT INTO login_attempts (email, ip_address, success) VALUES (?, ?, 0)");
|
| 72 |
+
$stmt->execute([$username, $ip_address]);
|
| 73 |
+
|
| 74 |
+
http_response_code(429);
|
| 75 |
+
echo json_encode(['message' => 'Too many failed attempts for this username. Try again in 15 minutes.', 'retry_after' => 900]);
|
| 76 |
+
exit;
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
// Check IP rate limit (skip if bypass enabled)
|
| 81 |
+
if (!$bypass_rate_limit) {
|
| 82 |
+
$stmt = $pdo->prepare("SELECT COUNT(*) as attempts FROM login_attempts WHERE ip_address = ? AND attempted_at > ? AND success = 0");
|
| 83 |
+
$stmt->execute([$ip_address, $fifteen_minutes_ago]);
|
| 84 |
+
$ip_attempts = $stmt->fetch()['attempts'];
|
| 85 |
+
|
| 86 |
+
if ($ip_attempts >= 10) {
|
| 87 |
+
// Record failed attempt
|
| 88 |
+
$stmt = $pdo->prepare("INSERT INTO login_attempts (email, ip_address, success) VALUES (?, ?, 0)");
|
| 89 |
+
$stmt->execute([$username, $ip_address]);
|
| 90 |
+
|
| 91 |
+
http_response_code(429);
|
| 92 |
+
echo json_encode(['message' => 'Too many failed attempts from this IP. Try again in 15 minutes.', 'retry_after' => 900]);
|
| 93 |
+
exit;
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
// Find user (do not assume `is_active` column exists in older DB schema)
|
| 98 |
+
$stmt = $pdo->prepare("SELECT * FROM admin_users WHERE username = ?");
|
| 99 |
+
$stmt->execute([$username]);
|
| 100 |
+
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
| 101 |
+
|
| 102 |
+
// If the `is_active` column exists, enforce it; otherwise assume active by default
|
| 103 |
+
if ($user && isset($user['is_active']) && (int)$user['is_active'] !== 1) {
|
| 104 |
+
// Record failed attempt
|
| 105 |
+
$stmt = $pdo->prepare("INSERT INTO login_attempts (email, ip_address, success) VALUES (?, ?, 0)");
|
| 106 |
+
$stmt->execute([$username, $ip_address]);
|
| 107 |
+
|
| 108 |
+
http_response_code(401);
|
| 109 |
+
echo json_encode(['message' => 'Invalid credentials']);
|
| 110 |
+
exit;
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
if (!$user || !password_verify($password, $user['password_hash'])) {
|
| 114 |
+
// Record failed attempt
|
| 115 |
+
$stmt = $pdo->prepare("INSERT INTO login_attempts (email, ip_address, success) VALUES (?, ?, 0)");
|
| 116 |
+
$stmt->execute([$username, $ip_address]);
|
| 117 |
+
|
| 118 |
+
http_response_code(401);
|
| 119 |
+
echo json_encode(['message' => 'Invalid credentials']);
|
| 120 |
+
exit;
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
// Record successful attempt
|
| 124 |
+
$stmt = $pdo->prepare("INSERT INTO login_attempts (email, ip_address, success) VALUES (?, ?, 1)");
|
| 125 |
+
$stmt->execute([$username, $ip_address]);
|
| 126 |
+
|
| 127 |
+
// Generate JWT token (15 minutes expiry)
|
| 128 |
+
$payload = [
|
| 129 |
+
'user_id' => $user['id'],
|
| 130 |
+
'username' => $user['username'],
|
| 131 |
+
'email' => $user['email'] ?? '',
|
| 132 |
+
'role' => $user['role'] ?? 'admin',
|
| 133 |
+
'iat' => time(),
|
| 134 |
+
'exp' => time() + (15 * 60) // 15 minutes
|
| 135 |
+
];
|
| 136 |
+
|
| 137 |
+
// Use fully-qualified class name to avoid file-local "use" dependency issues
|
| 138 |
+
$jwt = \Firebase\JWT\JWT::encode($payload, JWT_SECRET, 'HS256');
|
| 139 |
+
|
| 140 |
+
// Set secure cookie (for refresh token - simplified for demo)
|
| 141 |
+
// Choose cookie options depending on whether the request appears to be HTTPS.
|
| 142 |
+
// When served over HTTPS (e.g., GitHub.dev preview), use Secure + SameSite=None so the
|
| 143 |
+
// cookie can be accepted in some embedded contexts. For local HTTP development we keep
|
| 144 |
+
// Secure=false and SameSite=Strict.
|
| 145 |
+
$is_https = false;
|
| 146 |
+
if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
|
| 147 |
+
$is_https = true;
|
| 148 |
+
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
|
| 149 |
+
$is_https = true;
|
| 150 |
+
} elseif (!empty($_SERVER['HTTP_CF_VISITOR']) && strpos($_SERVER['HTTP_CF_VISITOR'], 'https') !== false) {
|
| 151 |
+
$is_https = true;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
$cookie_options = [
|
| 155 |
+
'expires' => time() + (7 * 24 * 60 * 60), // 7 days
|
| 156 |
+
'path' => '/',
|
| 157 |
+
'domain' => '',
|
| 158 |
+
'secure' => $is_https, // require secure when over HTTPS
|
| 159 |
+
'httponly' => true,
|
| 160 |
+
'samesite' => $is_https ? 'None' : 'Strict'
|
| 161 |
+
];
|
| 162 |
+
|
| 163 |
+
// PHP's setcookie accepted an array of options since 7.3; this usage is compatible.
|
| 164 |
+
setcookie('refresh_token', $jwt, $cookie_options);
|
| 165 |
+
|
| 166 |
+
if ($is_form) {
|
| 167 |
+
// For browser form submissions, instead of a bare 302 (which may be followed inside an iframe
|
| 168 |
+
// or blocked by preview environments), return a small HTML page that attempts a top-level
|
| 169 |
+
// navigation. The Set-Cookie header has already been emitted above so the cookie will apply.
|
| 170 |
+
header('Content-Type: text/html; charset=utf-8');
|
| 171 |
+
$dashboard = 'backend.php';
|
| 172 |
+
echo "<!doctype html>\n";
|
| 173 |
+
echo "<html><head><meta charset=\"utf-8\"><title>Prijava uspešna</title>\n";
|
| 174 |
+
// Meta refresh as an extra fallback
|
| 175 |
+
echo "<meta http-equiv=\"refresh\" content=\"0;url={$dashboard}\">\n";
|
| 176 |
+
echo "</head><body>\n";
|
| 177 |
+
echo "<script>\n";
|
| 178 |
+
echo "try {\n";
|
| 179 |
+
echo " if (window.top && window.top !== window) {\n";
|
| 180 |
+
echo " window.top.location.replace('{$dashboard}');\n";
|
| 181 |
+
echo " } else {\n";
|
| 182 |
+
echo " window.location.replace('{$dashboard}');\n";
|
| 183 |
+
echo " }\n";
|
| 184 |
+
echo "} catch (e) {\n";
|
| 185 |
+
echo " try { window.location.replace('{$dashboard}'); } catch (e) { /* ignore */ }\n";
|
| 186 |
+
echo "}\n";
|
| 187 |
+
echo "</script>\n";
|
| 188 |
+
echo "<p>Prijava uspešna. Če se brskalnik ne preusmeri samodejno, <a href=\"{$dashboard}\">kliknite tukaj</a>.</p>\n";
|
| 189 |
+
// Large fallback button to open dashboard in a new tab or top window
|
| 190 |
+
echo "<div style=\"margin-top:16px;\">\n";
|
| 191 |
+
echo " <a href=\"{$dashboard}\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"display:inline-block;background:#f59e0b;color:#fff;padding:8px 12px;border-radius:6px;text-decoration:none\">Open dashboard in new tab</a>\n";
|
| 192 |
+
echo " <button id=\"openTopBtn\" style=\"margin-left:8px;padding:8px 12px;border-radius:6px;background:#ef4444;color:#fff;border:none;cursor:pointer\">Open dashboard (top)</button>\n";
|
| 193 |
+
echo "</div>\n";
|
| 194 |
+
echo "<script>document.getElementById('openTopBtn').addEventListener('click', function(){ try{ if(window.top && window.top !== window){ window.top.location.href='${dashboard}'; } else { window.location.href='${dashboard}'; } }catch(e){ window.open('${dashboard}','_blank'); } });</script>\n";
|
| 195 |
+
echo "</body></html>\n";
|
| 196 |
+
exit;
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
// Default: JSON response for API/AJAX clients
|
| 200 |
+
echo json_encode([
|
| 201 |
+
'message' => 'Login successful',
|
| 202 |
+
'token' => $jwt,
|
| 203 |
+
'expires_in' => 900, // 15 minutes
|
| 204 |
+
'user' => [
|
| 205 |
+
'id' => $user['id'],
|
| 206 |
+
'username' => $user['username'],
|
| 207 |
+
'email' => $user['email'] ?? '',
|
| 208 |
+
'role' => $user['role'] ?? 'admin'
|
| 209 |
+
]
|
| 210 |
+
]);
|
| 211 |
+
?>
|
logout.php
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
// Securely log out admin by clearing the refresh_token cookie and redirecting to login
|
| 3 |
+
setcookie('refresh_token', '', [
|
| 4 |
+
'expires' => time() - 3600,
|
| 5 |
+
'path' => '/',
|
| 6 |
+
'domain' => '',
|
| 7 |
+
'secure' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'),
|
| 8 |
+
'httponly' => true,
|
| 9 |
+
'samesite' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'None' : 'Strict'
|
| 10 |
+
]);
|
| 11 |
+
header('Location: admin_login.php');
|
| 12 |
+
exit;
|
orders.php
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
require_once 'config.php';
|
| 3 |
+
|
| 4 |
+
// GET all orders (Admin only)
|
| 5 |
+
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
| 6 |
+
require_once 'require_auth.php'; // This now includes admin role checking
|
| 7 |
+
$accept = $_SERVER['HTTP_ACCEPT'] ?? '';
|
| 8 |
+
$is_html = strpos($accept, 'text/html') !== false || isset($_GET['id']);
|
| 9 |
+
$order_id = $_GET['id'] ?? null;
|
| 10 |
+
if ($is_html && $order_id) {
|
| 11 |
+
// Render HTML order details page
|
| 12 |
+
$stmt = $pdo->prepare("SELECT * FROM orders WHERE id = ?");
|
| 13 |
+
$stmt->execute([$order_id]);
|
| 14 |
+
$order = $stmt->fetch(PDO::FETCH_ASSOC);
|
| 15 |
+
if (!$order) {
|
| 16 |
+
echo "<h2 style='color:red'>Order not found.</h2>";
|
| 17 |
+
exit;
|
| 18 |
+
}
|
| 19 |
+
$stmt = $pdo->prepare("SELECT * FROM order_items WHERE order_id = ?");
|
| 20 |
+
$stmt->execute([$order_id]);
|
| 21 |
+
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 22 |
+
echo "<!DOCTYPE html><html lang='sl'><head><meta charset='utf-8'><title>Podrobnosti naročila #{$order_id}</title><script src='https://cdn.tailwindcss.com'></script></head><body class='bg-amber-50'>";
|
| 23 |
+
echo "<div class='max-w-2xl mx-auto my-8 bg-white rounded-lg shadow p-6'>";
|
| 24 |
+
echo "<h1 class='text-2xl font-bold mb-4 text-amber-900'>Naročilo #{$order_id}</h1>";
|
| 25 |
+
echo "<div class='mb-4'><strong>Stranka:</strong> {$order['customer_name']}<br><strong>Email:</strong> {$order['customer_email']}<br><strong>Naslov:</strong> {$order['customer_address']}<br><strong>Telefon:</strong> {$order['customer_phone']}</div>";
|
| 26 |
+
echo "<div class='mb-4'><strong>Status:</strong> {$order['status']}<br><strong>Skupaj:</strong> {$order['total_amount']} €<br><strong>Datum:</strong> {$order['created_at']}</div>";
|
| 27 |
+
echo "<h2 class='text-xl font-semibold mb-2 text-amber-800'>Izdelki</h2>";
|
| 28 |
+
echo "<table class='w-full border'><thead><tr class='bg-amber-100'><th class='p-2 text-left'>Izdelek</th><th class='p-2 text-right'>Količina</th><th class='p-2 text-right'>Cena</th></tr></thead><tbody>";
|
| 29 |
+
foreach ($items as $item) {
|
| 30 |
+
echo "<tr><td class='p-2'>{$item['product_name']}</td><td class='p-2 text-right'>{$item['quantity']}</td><td class='p-2 text-right'>{$item['total_price']} €</td></tr>";
|
| 31 |
+
}
|
| 32 |
+
echo "</tbody></table>";
|
| 33 |
+
echo "<div class='mt-6'><a href='backend.php' class='bg-amber-700 text-white px-4 py-2 rounded'>Nazaj na nadzorno ploščo</a></div>";
|
| 34 |
+
echo "</div></body></html>";
|
| 35 |
+
exit;
|
| 36 |
+
}
|
| 37 |
+
// Default: return all orders as JSON (API)
|
| 38 |
+
$stmt = $pdo->query("SELECT * FROM orders");
|
| 39 |
+
$orders = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 40 |
+
foreach($orders as &$order) {
|
| 41 |
+
$stmt = $pdo->prepare("SELECT * FROM order_items WHERE order_id = ?");
|
| 42 |
+
$stmt->execute([$order['id']]);
|
| 43 |
+
$order['items'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 44 |
+
}
|
| 45 |
+
header('Content-Type: application/json; charset=utf-8');
|
| 46 |
+
echo json_encode($orders);
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
// POST create new order
|
| 50 |
+
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
| 51 |
+
$data = json_decode(file_get_contents("php://input"));
|
| 52 |
+
|
| 53 |
+
// Start transaction
|
| 54 |
+
$pdo->beginTransaction();
|
| 55 |
+
|
| 56 |
+
try {
|
| 57 |
+
// Create order
|
| 58 |
+
$stmt = $pdo->prepare("INSERT INTO orders (customer_name, customer_email, customer_address, total_amount, status) VALUES (?, ?, ?, ?, ?)");
|
| 59 |
+
$stmt->execute([
|
| 60 |
+
$data->customer->name,
|
| 61 |
+
$data->customer->email,
|
| 62 |
+
$data->customer->address,
|
| 63 |
+
$data->total,
|
| 64 |
+
'pending'
|
| 65 |
+
]);
|
| 66 |
+
$order_id = $pdo->lastInsertId();
|
| 67 |
+
|
| 68 |
+
// Add order items
|
| 69 |
+
foreach($data->items as $item) {
|
| 70 |
+
$stmt = $pdo->prepare("INSERT INTO order_items (order_id, product_id, product_name, quantity, unit_price) VALUES (?, ?, ?, ?, ?)");
|
| 71 |
+
$stmt->execute([
|
| 72 |
+
$order_id,
|
| 73 |
+
$item->product_id,
|
| 74 |
+
$item->product_name,
|
| 75 |
+
$item->quantity,
|
| 76 |
+
$item->unit_price
|
| 77 |
+
]);
|
| 78 |
+
|
| 79 |
+
// Update product stock
|
| 80 |
+
$stmt = $pdo->prepare("UPDATE products SET stock = stock - ? WHERE id = ?");
|
| 81 |
+
$stmt->execute([$item->quantity, $item->product_id]);
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
// Commit transaction
|
| 85 |
+
$pdo->commit();
|
| 86 |
+
|
| 87 |
+
http_response_code(201);
|
| 88 |
+
echo json_encode(array("success" => true, "order_id" => $order_id));
|
| 89 |
+
} catch(Exception $e) {
|
| 90 |
+
// Rollback on error
|
| 91 |
+
$pdo->rollBack();
|
| 92 |
+
http_response_code(500);
|
| 93 |
+
echo json_encode(array("success" => false, "message" => $e->getMessage()));
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
// UPDATE order status (Admin only)
|
| 98 |
+
if ($_SERVER['REQUEST_METHOD'] === 'PUT') {
|
| 99 |
+
require_once 'require_auth.php'; // This now includes admin role checking
|
| 100 |
+
|
| 101 |
+
$data = json_decode(file_get_contents("php://input"));
|
| 102 |
+
$id = $_GET['id'] ?? null;
|
| 103 |
+
|
| 104 |
+
if($id) {
|
| 105 |
+
$stmt = $pdo->prepare("UPDATE orders SET status = ? WHERE id = ?");
|
| 106 |
+
$stmt->execute([$data->status, $id]);
|
| 107 |
+
|
| 108 |
+
echo json_encode(array("success" => true, "message" => "Order updated"));
|
| 109 |
+
} else {
|
| 110 |
+
http_response_code(400);
|
| 111 |
+
echo json_encode(array("success" => false, "message" => "Order ID required"));
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
?>
|
process_order.php
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
```php
|
| 2 |
+
<?php
|
| 3 |
+
require_once 'config.php';
|
| 4 |
+
|
| 5 |
+
// Get cart from session or cookie
|
| 6 |
+
$cart = isset($_SESSION['cart']) ? $_SESSION['cart'] : (isset($_COOKIE['cart']) ? json_decode($_COOKIE['cart'], true) : []);
|
| 7 |
+
|
| 8 |
+
// Calculate total
|
| 9 |
+
$total = 0;
|
| 10 |
+
foreach($cart as $item) {
|
| 11 |
+
$total += $item['price'] * $item['quantity'];
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
// Handle form submission
|
| 15 |
+
if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
| 16 |
+
$customer = [
|
| 17 |
+
'name' => $_POST['name'],
|
| 18 |
+
'email' => $_POST['email'],
|
| 19 |
+
'address' => $_POST['address'],
|
| 20 |
+
'phone' => $_POST['phone']
|
| 21 |
+
];
|
| 22 |
+
|
| 23 |
+
// DEBUG: Output cart contents for troubleshooting
|
| 24 |
+
file_put_contents('cart_debug.log', print_r($cart, true));
|
| 25 |
+
|
| 26 |
+
// Prepare order data
|
| 27 |
+
$order_data = [
|
| 28 |
+
'customer' => $customer,
|
| 29 |
+
'items' => $cart,
|
| 30 |
+
'total' => $total + 5 // Including shipping
|
| 31 |
+
];
|
| 32 |
+
|
| 33 |
+
// Create order (this would be a call to your orders.php API in a real app)
|
| 34 |
+
$order_id = createOrder($order_data['customer'], $order_data['total'], $cart);
|
| 35 |
+
|
| 36 |
+
// Clear cart
|
| 37 |
+
unset($_SESSION['cart']);
|
| 38 |
+
setcookie('cart', '', time() - 3600, '/');
|
| 39 |
+
|
| 40 |
+
// Redirect to thank you page
|
| 41 |
+
header('Location: thank_you.php?order_id=' . $order_id);
|
| 42 |
+
exit;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
// Function to create order (simplified for example)
|
| 46 |
+
function createOrder($customer, $total, $items) {
|
| 47 |
+
global $pdo;
|
| 48 |
+
// Insert order with customer_phone
|
| 49 |
+
$stmt = $pdo->prepare("INSERT INTO orders (customer_name, customer_email, customer_address, customer_phone, total_amount) VALUES (?, ?, ?, ?, ?)");
|
| 50 |
+
$stmt->execute([$customer['name'], $customer['email'], $customer['address'], $customer['phone'], $total]);
|
| 51 |
+
$order_id = $pdo->lastInsertId();
|
| 52 |
+
|
| 53 |
+
// Insert order items
|
| 54 |
+
foreach($items as $item) {
|
| 55 |
+
if (!isset($item['id']) || empty($item['id'])) {
|
| 56 |
+
// Skip items without a valid product_id
|
| 57 |
+
continue;
|
| 58 |
+
}
|
| 59 |
+
$stmt = $pdo->prepare("INSERT INTO order_items (order_id, product_id, product_name, quantity, unit_price) VALUES (?, ?, ?, ?, ?)");
|
| 60 |
+
$stmt->execute([$order_id, $item['id'], $item['name'], $item['quantity'], $item['price']]);
|
| 61 |
+
|
| 62 |
+
// Update product stock
|
| 63 |
+
$stmt = $pdo->prepare("UPDATE products SET stock = stock - ? WHERE id = ?");
|
| 64 |
+
$stmt->execute([$item['quantity'], $item['id']]);
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
return $order_id;
|
| 68 |
+
}
|
| 69 |
+
?>
|
| 70 |
+
```
|
products.php
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
require_once 'config.php';
|
| 3 |
+
|
| 4 |
+
// GET all products
|
| 5 |
+
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
| 6 |
+
$stmt = $pdo->query("SELECT * FROM products");
|
| 7 |
+
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 8 |
+
echo json_encode($products);
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
// POST new product (Admin only)
|
| 12 |
+
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
| 13 |
+
require_once 'require_auth.php'; // This now includes admin role checking
|
| 14 |
+
|
| 15 |
+
$data = json_decode(file_get_contents("php://input"));
|
| 16 |
+
|
| 17 |
+
$stmt = $pdo->prepare("INSERT INTO products (name, description, price, stock, image_url) VALUES (?, ?, ?, ?, ?)");
|
| 18 |
+
$stmt->execute([$data->name, $data->description, $data->price, $data->stock, $data->image_url]);
|
| 19 |
+
|
| 20 |
+
$product_id = $pdo->lastInsertId();
|
| 21 |
+
$stmt = $pdo->query("SELECT * FROM products WHERE id = $product_id");
|
| 22 |
+
$product = $stmt->fetch(PDO::FETCH_ASSOC);
|
| 23 |
+
|
| 24 |
+
echo json_encode($product);
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
// UPDATE product (Admin only)
|
| 28 |
+
if ($_SERVER['REQUEST_METHOD'] === 'PUT') {
|
| 29 |
+
require_once 'require_auth.php'; // This now includes admin role checking
|
| 30 |
+
|
| 31 |
+
$data = json_decode(file_get_contents("php://input"));
|
| 32 |
+
$id = $_GET['id'] ?? null;
|
| 33 |
+
|
| 34 |
+
if($id) {
|
| 35 |
+
$stmt = $pdo->prepare("UPDATE products SET name = ?, description = ?, price = ?, stock = ?, image_url = ? WHERE id = ?");
|
| 36 |
+
$stmt->execute([$data->name, $data->description, $data->price, $data->stock, $data->image_url, $id]);
|
| 37 |
+
|
| 38 |
+
echo json_encode(array("success" => true, "message" => "Product updated"));
|
| 39 |
+
} else {
|
| 40 |
+
http_response_code(400);
|
| 41 |
+
echo json_encode(array("success" => false, "message" => "Product ID required"));
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
// DELETE product (Admin only)
|
| 46 |
+
if ($_SERVER['REQUEST_METHOD'] === 'DELETE') {
|
| 47 |
+
require_once 'require_auth.php'; // This now includes admin role checking
|
| 48 |
+
|
| 49 |
+
$id = $_GET['id'] ?? null;
|
| 50 |
+
|
| 51 |
+
if($id) {
|
| 52 |
+
$stmt = $pdo->prepare("DELETE FROM products WHERE id = ?");
|
| 53 |
+
$stmt->execute([$id]);
|
| 54 |
+
|
| 55 |
+
echo json_encode(array("success" => true, "message" => "Product deleted"));
|
| 56 |
+
} else {
|
| 57 |
+
http_response_code(400);
|
| 58 |
+
echo json_encode(array("success" => false, "message" => "Product ID required"));
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
?>
|
prompts.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
now make backend
|
| 3 |
+
now make the php fort it to work safely
|
| 4 |
+
now make the php fort it to work safely
|
| 5 |
+
now make the php fort it to work safely
|
| 6 |
+
make the backend so the user can anctualy cehck pout
|
| 7 |
+
make all the backend phps and
|
| 8 |
+
make the backend php
|
| 9 |
+
link it so when the user checkouts it actulay gets redirected to checkout
|
| 10 |
+
fix the main page its not shjowing coreclty
|
replit.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Overview
|
| 2 |
+
|
| 3 |
+
This is a Slovenian beekeeping e-commerce website called "Čebelarstvo Cigoj" (Cigoj Beekeeping). The project is a honey and beekeeping products online store with both customer-facing pages and administrative functionality. It features a modern, responsive design using Tailwind CSS with an amber/brown color scheme that reflects the beekeeping theme.
|
| 4 |
+
|
| 5 |
+
The application includes a product catalog, shopping cart functionality, and an administrative backend for managing products and orders. The site is designed to sell premium natural honey and beekeeping products to customers while providing business owners with tools to manage their inventory and sales.
|
| 6 |
+
|
| 7 |
+
# User Preferences
|
| 8 |
+
|
| 9 |
+
Preferred communication style: Simple, everyday language.
|
| 10 |
+
|
| 11 |
+
# System Architecture
|
| 12 |
+
|
| 13 |
+
## Frontend Architecture
|
| 14 |
+
The frontend is built as a multi-page static HTML application using:
|
| 15 |
+
- **Tailwind CSS** for responsive styling and component design
|
| 16 |
+
- **Feather Icons** for consistent iconography throughout the interface
|
| 17 |
+
- **AOS (Animate On Scroll)** library for smooth scroll animations
|
| 18 |
+
- Vanilla JavaScript for client-side interactivity and cart management
|
| 19 |
+
|
| 20 |
+
The design follows a consistent amber/brown color palette that reflects the beekeeping theme, with the primary brand color being `amber-800`. The layout is fully responsive and includes:
|
| 21 |
+
- Main storefront page (`index.html`)
|
| 22 |
+
- Shopping cart page (`cart.html`)
|
| 23 |
+
- Administrative dashboard (`backend.html`)
|
| 24 |
+
|
| 25 |
+
## Backend Architecture
|
| 26 |
+
The backend is designed to be implemented in PHP with:
|
| 27 |
+
- **Composer** dependency management
|
| 28 |
+
- **Firebase JWT** library for secure authentication and session management
|
| 29 |
+
- Server-side cart processing and checkout functionality
|
| 30 |
+
- Administrative controls for product and order management
|
| 31 |
+
|
| 32 |
+
The architecture separates customer-facing functionality from administrative features, with the admin panel requiring authentication.
|
| 33 |
+
|
| 34 |
+
## Authentication System
|
| 35 |
+
Authentication is handled using JWT tokens via the Firebase PHP-JWT library, providing:
|
| 36 |
+
- Secure login sessions for administrative access
|
| 37 |
+
- Token-based authentication for API endpoints
|
| 38 |
+
- Session management for cart persistence
|
| 39 |
+
|
| 40 |
+
## Cart and Checkout System
|
| 41 |
+
The shopping cart system uses:
|
| 42 |
+
- Client-side JavaScript for immediate cart updates
|
| 43 |
+
- Server-side PHP processing for secure checkout operations
|
| 44 |
+
- Session storage for cart persistence across page loads
|
| 45 |
+
- Integration with payment processing (to be implemented)
|
| 46 |
+
|
| 47 |
+
## Administrative Features
|
| 48 |
+
The admin backend provides:
|
| 49 |
+
- Product inventory management
|
| 50 |
+
- Order processing and tracking
|
| 51 |
+
- Dashboard with business analytics
|
| 52 |
+
- Secure authentication-protected access
|
| 53 |
+
|
| 54 |
+
The admin interface uses the same design system as the customer-facing pages but with additional functionality for business management.
|
| 55 |
+
|
| 56 |
+
# External Dependencies
|
| 57 |
+
|
| 58 |
+
## Frontend Libraries
|
| 59 |
+
- **Tailwind CSS CDN** - Utility-first CSS framework for responsive design
|
| 60 |
+
- **Feather Icons CDN** - Lightweight icon library for consistent UI elements
|
| 61 |
+
- **AOS (Animate On Scroll) CDN** - Animation library for scroll-triggered effects
|
| 62 |
+
|
| 63 |
+
## Backend Dependencies
|
| 64 |
+
- **Firebase PHP-JWT** (v6.11.1) - JWT token creation and validation for secure authentication
|
| 65 |
+
- **PHP 8.0+** - Server-side language requirement for JWT library compatibility
|
| 66 |
+
|
| 67 |
+
## Development Tools
|
| 68 |
+
- **Composer** - PHP dependency management for backend packages
|
| 69 |
+
|
| 70 |
+
The application is designed to be lightweight and fast-loading by leveraging CDN resources for frontend libraries while maintaining secure server-side processing for sensitive operations like authentication and payment processing.
|
reports.php
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
```php
|
| 2 |
+
<?php
|
| 3 |
+
require_once 'config.php';
|
| 4 |
+
|
| 5 |
+
// JWT verification middleware
|
| 6 |
+
verifyToken();
|
| 7 |
+
|
| 8 |
+
// GET sales report
|
| 9 |
+
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
| 10 |
+
$start_date = $_GET['start_date'] ?? date('Y-m-01');
|
| 11 |
+
$end_date = $_GET['end_date'] ?? date('Y-m-t');
|
| 12 |
+
|
| 13 |
+
// Total sales
|
| 14 |
+
$stmt = $pdo->prepare("SELECT SUM(total_amount) as total_sales FROM orders WHERE created_at BETWEEN ? AND ?");
|
| 15 |
+
$stmt->execute([$start_date, $end_date]);
|
| 16 |
+
$total_sales = $stmt->fetch(PDO::FETCH_ASSOC);
|
| 17 |
+
|
| 18 |
+
// Top products
|
| 19 |
+
$stmt = $pdo->prepare("
|
| 20 |
+
SELECT p.name, SUM(oi.quantity) as total_sold, SUM(oi.quantity * oi.unit_price) as revenue
|
| 21 |
+
FROM order_items oi
|
| 22 |
+
JOIN products p ON oi.product_id = p.id
|
| 23 |
+
JOIN orders o ON oi.order_id = o.id
|
| 24 |
+
WHERE o.created_at BETWEEN ? AND ?
|
| 25 |
+
GROUP BY p.id
|
| 26 |
+
ORDER BY total_sold DESC
|
| 27 |
+
LIMIT 5
|
| 28 |
+
");
|
| 29 |
+
$stmt->execute([$start_date, $end_date]);
|
| 30 |
+
$top_products = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 31 |
+
|
| 32 |
+
// Report data
|
| 33 |
+
$report = array(
|
| 34 |
+
'total_sales' => $total_sales['total_sales'] ?? 0,
|
| 35 |
+
'start_date' => $start_date,
|
| 36 |
+
'end_date' => $end_date,
|
| 37 |
+
'top_products' => $top_products
|
| 38 |
+
);
|
| 39 |
+
|
| 40 |
+
echo json_encode($report);
|
| 41 |
+
}
|
| 42 |
+
?>
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
You'll also need to create the database tables. Here's the SQL schema:
|
require_auth.php
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
// Authentication middleware - include at the top of protected pages
|
| 3 |
+
require_once 'config.php';
|
| 4 |
+
|
| 5 |
+
function requireAuth($required_role = null) {
|
| 6 |
+
// Check for JWT token in Authorization header or cookie
|
| 7 |
+
$token = null;
|
| 8 |
+
|
| 9 |
+
// First try Authorization header
|
| 10 |
+
$headers = function_exists('getallheaders') ? getallheaders() : [];
|
| 11 |
+
if (empty($headers) && isset($_SERVER['HTTP_AUTHORIZATION'])) {
|
| 12 |
+
$headers['Authorization'] = $_SERVER['HTTP_AUTHORIZATION'];
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
if (isset($headers['Authorization'])) {
|
| 16 |
+
$token = str_replace('Bearer ', '', $headers['Authorization']);
|
| 17 |
+
}
|
| 18 |
+
// Fallback to cookie
|
| 19 |
+
elseif (isset($_COOKIE['refresh_token'])) {
|
| 20 |
+
$token = $_COOKIE['refresh_token'];
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
if (!$token) {
|
| 24 |
+
// If the client expects HTML (browser navigation), redirect to login page
|
| 25 |
+
$accept = $_SERVER['HTTP_ACCEPT'] ?? '';
|
| 26 |
+
$isBrowser = strpos($accept, 'text/html') !== false || (isset($_SERVER['REQUEST_URI']) && (strpos($_SERVER['REQUEST_URI'], '.php') !== false));
|
| 27 |
+
if ($isBrowser) {
|
| 28 |
+
header('Location: /admin_login.php');
|
| 29 |
+
exit;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
http_response_code(401);
|
| 33 |
+
echo json_encode(['message' => 'Authentication required']);
|
| 34 |
+
exit;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
try {
|
| 38 |
+
// Use fully-qualified class names to ensure the Firebase JWT classes are found
|
| 39 |
+
$decoded = \Firebase\JWT\JWT::decode($token, new \Firebase\JWT\Key(JWT_SECRET, 'HS256'));
|
| 40 |
+
|
| 41 |
+
// Verify user still exists and is active (tolerant of missing is_active column)
|
| 42 |
+
global $pdo;
|
| 43 |
+
$stmt = $pdo->prepare("SELECT * FROM admin_users WHERE id = ?");
|
| 44 |
+
$stmt->execute([$decoded->user_id]);
|
| 45 |
+
$user = $stmt->fetch();
|
| 46 |
+
// If is_active column exists, enforce it; otherwise assume active
|
| 47 |
+
$inactive = false;
|
| 48 |
+
if ($user && array_key_exists('is_active', $user) && (int)$user['is_active'] !== 1) {
|
| 49 |
+
$inactive = true;
|
| 50 |
+
}
|
| 51 |
+
if (!$user || $inactive) {
|
| 52 |
+
$accept = $_SERVER['HTTP_ACCEPT'] ?? '';
|
| 53 |
+
$isBrowser = strpos($accept, 'text/html') !== false || (isset($_SERVER['REQUEST_URI']) && (strpos($_SERVER['REQUEST_URI'], '.php') !== false));
|
| 54 |
+
if ($isBrowser) {
|
| 55 |
+
header('Content-Type: text/html; charset=utf-8');
|
| 56 |
+
echo "<h2 style='color:red'>Admin user not found or inactive. Please check your database.</h2>";
|
| 57 |
+
exit;
|
| 58 |
+
}
|
| 59 |
+
http_response_code(401);
|
| 60 |
+
echo json_encode(['message' => 'User not found or inactive']);
|
| 61 |
+
exit;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
// Check role if specified
|
| 65 |
+
if ($required_role && (!isset($user['role']) || $user['role'] !== $required_role)) {
|
| 66 |
+
http_response_code(403);
|
| 67 |
+
echo json_encode(['message' => 'Insufficient permissions']);
|
| 68 |
+
exit;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
// Set global user data
|
| 72 |
+
global $current_user;
|
| 73 |
+
$current_user = $user;
|
| 74 |
+
|
| 75 |
+
return $decoded;
|
| 76 |
+
|
| 77 |
+
} catch (Exception $e) {
|
| 78 |
+
$accept = $_SERVER['HTTP_ACCEPT'] ?? '';
|
| 79 |
+
$isBrowser = strpos($accept, 'text/html') !== false || (isset($_SERVER['REQUEST_URI']) && (strpos($_SERVER['REQUEST_URI'], '.php') !== false));
|
| 80 |
+
if ($isBrowser) {
|
| 81 |
+
header('Location: /admin_login.php');
|
| 82 |
+
exit;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
http_response_code(401);
|
| 86 |
+
echo json_encode(['message' => 'Invalid or expired token', 'error' => $e->getMessage()]);
|
| 87 |
+
exit;
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
// Call requireAuth() to protect the current page with admin role
|
| 92 |
+
requireAuth('admin');
|
| 93 |
+
?>
|
schema.sql
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-- SQLite schema
|
| 2 |
+
-- Products table
|
| 3 |
+
CREATE TABLE IF NOT EXISTS products (
|
| 4 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 5 |
+
name TEXT NOT NULL,
|
| 6 |
+
description TEXT,
|
| 7 |
+
price REAL NOT NULL,
|
| 8 |
+
stock INTEGER NOT NULL DEFAULT 0,
|
| 9 |
+
image_url TEXT,
|
| 10 |
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 11 |
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 12 |
+
);
|
| 13 |
+
|
| 14 |
+
-- Customers table
|
| 15 |
+
CREATE TABLE IF NOT EXISTS customers (
|
| 16 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 17 |
+
name TEXT NOT NULL,
|
| 18 |
+
email TEXT NOT NULL UNIQUE,
|
| 19 |
+
address TEXT,
|
| 20 |
+
phone TEXT,
|
| 21 |
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 22 |
+
);
|
| 23 |
+
|
| 24 |
+
-- Orders table
|
| 25 |
+
CREATE TABLE IF NOT EXISTS orders (
|
| 26 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 27 |
+
customer_name TEXT NOT NULL,
|
| 28 |
+
customer_email TEXT NOT NULL,
|
| 29 |
+
customer_address TEXT NOT NULL,
|
| 30 |
+
customer_phone TEXT,
|
| 31 |
+
subtotal REAL,
|
| 32 |
+
shipping_cost REAL,
|
| 33 |
+
shipping_method TEXT,
|
| 34 |
+
total_amount REAL NOT NULL,
|
| 35 |
+
status TEXT DEFAULT 'pending',
|
| 36 |
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 37 |
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 38 |
+
);
|
| 39 |
+
|
| 40 |
+
-- Order items table
|
| 41 |
+
CREATE TABLE IF NOT EXISTS order_items (
|
| 42 |
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
| 43 |
+
order_id INT NOT NULL,
|
| 44 |
+
|
| 45 |
+
product_id INTEGER NOT NULL,
|
| 46 |
+
product_name TEXT NOT NULL,
|
| 47 |
+
quantity INTEGER NOT NULL,
|
| 48 |
+
unit_price REAL NOT NULL,
|
| 49 |
+
total_price REAL,
|
| 50 |
+
FOREIGN KEY (order_id) REFERENCES orders(id),
|
| 51 |
+
FOREIGN KEY (product_id) REFERENCES products(id)
|
| 52 |
+
);
|
| 53 |
+
|
| 54 |
+
-- Admin users table
|
| 55 |
+
CREATE TABLE IF NOT EXISTS admin_users (
|
| 56 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 57 |
+
username TEXT NOT NULL UNIQUE,
|
| 58 |
+
password_hash TEXT NOT NULL,
|
| 59 |
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 60 |
+
);
|
| 61 |
+
|
| 62 |
+
-- Insert test admin
|
| 63 |
+
INSERT OR IGNORE INTO admin_users (username, password_hash)
|
| 64 |
+
VALUES ('admin', '$2y$10$J4o8/.jGJt4vB7R7hNQ0L.Q5Qd8RZ1Z1d5Gz0V1W5gX6t5i2b1V1S2');
|
| 65 |
+
|
| 66 |
+
-- Insert sample products
|
| 67 |
+
INSERT OR IGNORE INTO products (name, description, price, stock, image_url) VALUES
|
| 68 |
+
('Cvetni prah 50g', 'Naravni cvetni prah, bogat s proteini in vitamini', 5.00, 100, 'https://static.photos/nature/320x240/101'),
|
| 69 |
+
('Balzam za ustnice iz čebeljega voska', 'Neguje in ščiti ustnice', 2.50, 50, 'https://static.photos/nature/320x240/102'),
|
| 70 |
+
('Med ajdov', 'Visokokakovosten med iz ajdovega cveta', 7.50, 30, 'https://static.photos/nature/320x240/103');
|
style.css
CHANGED
|
@@ -1,28 +1,28 @@
|
|
| 1 |
-
body {
|
| 2 |
-
padding: 2rem;
|
| 3 |
-
font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
|
| 4 |
-
}
|
| 5 |
-
|
| 6 |
-
h1 {
|
| 7 |
-
font-size: 16px;
|
| 8 |
-
margin-top: 0;
|
| 9 |
-
}
|
| 10 |
-
|
| 11 |
-
p {
|
| 12 |
-
color: rgb(107, 114, 128);
|
| 13 |
-
font-size: 15px;
|
| 14 |
-
margin-bottom: 10px;
|
| 15 |
-
margin-top: 5px;
|
| 16 |
-
}
|
| 17 |
-
|
| 18 |
-
.card {
|
| 19 |
-
max-width: 620px;
|
| 20 |
-
margin: 0 auto;
|
| 21 |
-
padding: 16px;
|
| 22 |
-
border: 1px solid lightgray;
|
| 23 |
-
border-radius: 16px;
|
| 24 |
-
}
|
| 25 |
-
|
| 26 |
-
.card p:last-child {
|
| 27 |
-
margin-bottom: 0;
|
| 28 |
-
}
|
|
|
|
| 1 |
+
body {
|
| 2 |
+
padding: 2rem;
|
| 3 |
+
font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
|
| 4 |
+
}
|
| 5 |
+
|
| 6 |
+
h1 {
|
| 7 |
+
font-size: 16px;
|
| 8 |
+
margin-top: 0;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
p {
|
| 12 |
+
color: rgb(107, 114, 128);
|
| 13 |
+
font-size: 15px;
|
| 14 |
+
margin-bottom: 10px;
|
| 15 |
+
margin-top: 5px;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
.card {
|
| 19 |
+
max-width: 620px;
|
| 20 |
+
margin: 0 auto;
|
| 21 |
+
padding: 16px;
|
| 22 |
+
border: 1px solid lightgray;
|
| 23 |
+
border-radius: 16px;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
.card p:last-child {
|
| 27 |
+
margin-bottom: 0;
|
| 28 |
+
}
|
thank_you.php
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
```php
|
| 2 |
+
<?php
|
| 3 |
+
require_once 'config.php';
|
| 4 |
+
|
| 5 |
+
$order_id = $_GET['order_id'] ?? 0;
|
| 6 |
+
?>
|
| 7 |
+
<!DOCTYPE html>
|
| 8 |
+
<html lang="sl">
|
| 9 |
+
<head>
|
| 10 |
+
<meta charset="UTF-8">
|
| 11 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 12 |
+
<title>Hvala za naročilo | Čebelarstvo Cigoj</title>
|
| 13 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 14 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 15 |
+
</head>
|
| 16 |
+
<body class="font-sans bg-gray-50">
|
| 17 |
+
<header class="bg-amber-800 text-white shadow-md">
|
| 18 |
+
<div class="container mx-auto px-4 py-4">
|
| 19 |
+
<h1 class="text-xl font-bold">Čebelarstvo Cigoj</h1>
|
| 20 |
+
</div>
|
| 21 |
+
</header>
|
| 22 |
+
|
| 23 |
+
<main class="container mx-auto px-4 py-12">
|
| 24 |
+
<div class="max-w-lg mx-auto text-center bg-white rounded-lg shadow p-8">
|
| 25 |
+
<div class="text-green-500 mb-4">
|
| 26 |
+
<i data-feather="check-circle" class="w-16 h-16 mx-auto"></i>
|
| 27 |
+
</div>
|
| 28 |
+
<h2 class="text-2xl font-bold text-amber-900 mb-4">Hvala za vaše naročilo!</h2>
|
| 29 |
+
<p class="text-gray-600 mb-6">Vaše naročilo številka <span class="font-bold">#<?php echo $order_id; ?></span> je bilo uspešno oddano.</p>
|
| 30 |
+
<p class="text-gray-600 mb-6">Potrdilo naročila smo vam poslali na vaš e-mail naslov.</p>
|
| 31 |
+
<a href="/" class="inline-block bg-amber-600 hover:bg-amber-700 text-white px-6 py-3 rounded-lg font-medium transition">
|
| 32 |
+
Nazaj v trgovino
|
| 33 |
+
</a>
|
| 34 |
+
</div>
|
| 35 |
+
</main>
|
| 36 |
+
|
| 37 |
+
<script>
|
| 38 |
+
feather.replace();
|
| 39 |
+
</script>
|
| 40 |
+
</body>
|
| 41 |
+
</html>
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
These changes add:
|
| 45 |
+
1. Cart functionality using localStorage
|
| 46 |
+
2. Checkout page with order summary
|
| 47 |
+
3. Order processing page
|
| 48 |
+
4. Thank you page after successful order
|
| 49 |
+
5. Complete order flow from cart to confirmation
|
| 50 |
+
|
| 51 |
+
To fully implement this, make sure your orders.php API endpoint is properly set up to handle the order creation. You might also want to add:
|
| 52 |
+
- Payment gateway integration (like Stripe or PayPal)
|
| 53 |
+
- Email notifications
|
| 54 |
+
- Order status tracking
|
| 55 |
+
- Better validation in the checkout form
|
tmp_update_admin_password (1).php
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
$db = __DIR__ . '/cebelarstvo_cigoj.db';
|
| 3 |
+
$pdo = new PDO('sqlite:' . $db);
|
| 4 |
+
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
| 5 |
+
$hash = '$2y$10$B8XjImKVuSlqv60dOG7z7eiFXY12v4m0Nf5pfVVcwpFGpeTxB8R8C';
|
| 6 |
+
$stmt = $pdo->prepare('UPDATE admin_users SET password_hash = ? WHERE username = ?');
|
| 7 |
+
$stmt->execute([$hash, 'admin']);
|
| 8 |
+
$row = $pdo->query("SELECT id, username, password_hash FROM admin_users WHERE username='admin'")->fetch(PDO::FETCH_ASSOC);
|
| 9 |
+
print_r($row);
|
| 10 |
+
echo "\n";
|
| 11 |
+
?>
|
tmp_update_admin_password.php
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
// Update admin password hash safely using the correct DB path
|
| 3 |
+
$db = __DIR__ . '/cebelarstvo_cigoj.db';
|
| 4 |
+
$pdo = new PDO('sqlite:' . $db);
|
| 5 |
+
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
| 6 |
+
$hash = '$2y$10$B8XjImKVuSlqv60dOG7z7eiFXY12v4m0Nf5pfVVcwpFGpeTxB8R8C';
|
| 7 |
+
$stmt = $pdo->prepare('UPDATE admin_users SET password_hash = ? WHERE username = ?');
|
| 8 |
+
$stmt->execute([$hash, 'admin']);
|
| 9 |
+
$row = $pdo->query("SELECT id, username, password_hash FROM admin_users WHERE username='admin'")->fetch(PDO::FETCH_ASSOC);
|
| 10 |
+
print_r($row);
|
| 11 |
+
// Verify password locally
|
| 12 |
+
var_export(password_verify('secret123', $row['password_hash']));
|
| 13 |
+
echo "\n";
|
| 14 |
+
?>
|