mirror of
https://github.com/myronblair/tomsjavajive-app
synced 2026-06-30 17:50:56 -05:00
v1.0.0 - Initial backup
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
<?php
|
||||
/**
|
||||
* Tom's Java Jive - Customer Addresses
|
||||
*/
|
||||
|
||||
$pageTitle = "My Addresses - Tom's Java Jive";
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
|
||||
CustomerAuth::require();
|
||||
$customer = CustomerAuth::getFullUser();
|
||||
$currentPage = 'addresses';
|
||||
|
||||
// Get addresses from customer record
|
||||
$addresses = json_decode($customer['addresses'] ?? '[]', true);
|
||||
if (!is_array($addresses)) $addresses = [];
|
||||
|
||||
// Handle form submissions
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = $_POST['action'] ?? '';
|
||||
|
||||
if ($action === 'add' || $action === 'edit') {
|
||||
$index = isset($_POST['index']) ? intval($_POST['index']) : -1;
|
||||
|
||||
$address = [
|
||||
'id' => $index >= 0 && isset($addresses[$index]['id']) ? $addresses[$index]['id'] : uniqid('addr_'),
|
||||
'name' => trim($_POST['name'] ?? ''),
|
||||
'phone' => trim($_POST['phone'] ?? ''),
|
||||
'address' => trim($_POST['address'] ?? ''),
|
||||
'address2' => trim($_POST['address2'] ?? ''),
|
||||
'city' => trim($_POST['city'] ?? ''),
|
||||
'state' => trim($_POST['state'] ?? ''),
|
||||
'zip' => trim($_POST['zip'] ?? ''),
|
||||
'country' => trim($_POST['country'] ?? 'USA'),
|
||||
'is_default' => isset($_POST['is_default']),
|
||||
];
|
||||
|
||||
// Validate
|
||||
if (empty($address['name']) || empty($address['address']) || empty($address['city']) || empty($address['zip'])) {
|
||||
setFlash('error', 'Please fill in all required fields');
|
||||
} else {
|
||||
// Handle default
|
||||
if ($address['is_default']) {
|
||||
foreach ($addresses as &$a) {
|
||||
$a['is_default'] = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($index >= 0 && isset($addresses[$index])) {
|
||||
$addresses[$index] = $address;
|
||||
} else {
|
||||
$addresses[] = $address;
|
||||
}
|
||||
|
||||
// Save
|
||||
db()->query(
|
||||
"UPDATE customers SET addresses = :addresses WHERE customer_id = :id",
|
||||
['addresses' => json_encode($addresses), 'id' => $customer['customer_id']]
|
||||
);
|
||||
|
||||
setFlash('success', $action === 'add' ? 'Address added' : 'Address updated');
|
||||
redirect('/account/addresses.php');
|
||||
}
|
||||
}
|
||||
|
||||
if ($action === 'delete') {
|
||||
$index = intval($_POST['index'] ?? -1);
|
||||
if ($index >= 0 && isset($addresses[$index])) {
|
||||
array_splice($addresses, $index, 1);
|
||||
db()->query(
|
||||
"UPDATE customers SET addresses = :addresses WHERE customer_id = :id",
|
||||
['addresses' => json_encode($addresses), 'id' => $customer['customer_id']]
|
||||
);
|
||||
setFlash('success', 'Address deleted');
|
||||
}
|
||||
redirect('/account/addresses.php');
|
||||
}
|
||||
|
||||
if ($action === 'set_default') {
|
||||
$index = intval($_POST['index'] ?? -1);
|
||||
if ($index >= 0 && isset($addresses[$index])) {
|
||||
foreach ($addresses as $i => &$a) {
|
||||
$a['is_default'] = ($i === $index);
|
||||
}
|
||||
db()->query(
|
||||
"UPDATE customers SET addresses = :addresses WHERE customer_id = :id",
|
||||
['addresses' => json_encode($addresses), 'id' => $customer['customer_id']]
|
||||
);
|
||||
setFlash('success', 'Default address updated');
|
||||
}
|
||||
redirect('/account/addresses.php');
|
||||
}
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../includes/header.php';
|
||||
require_once __DIR__ . '/includes/sidebar.php';
|
||||
?>
|
||||
|
||||
<div class="account-header" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<h1>My Addresses</h1>
|
||||
<p class="text-muted">Manage your shipping and billing addresses</p>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="openAddressModal()">
|
||||
<i class="fas fa-plus"></i> Add Address
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<?php if (hasFlash('success')): ?>
|
||||
<div class="alert alert-success mb-2">
|
||||
<i class="fas fa-check-circle"></i> <?= getFlash('success') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (hasFlash('error')): ?>
|
||||
<div class="alert alert-error mb-2">
|
||||
<i class="fas fa-exclamation-circle"></i> <?= getFlash('error') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (empty($addresses)): ?>
|
||||
<div class="section-card">
|
||||
<div class="section-card-body text-center" style="padding: 3rem;">
|
||||
<i class="fas fa-map-marker-alt" style="font-size: 3rem; color: var(--color-text-muted); margin-bottom: 1rem;"></i>
|
||||
<h3>No addresses saved</h3>
|
||||
<p class="text-muted">Add an address for faster checkout.</p>
|
||||
<button class="btn btn-primary mt-1" onclick="openAddressModal()">
|
||||
<i class="fas fa-plus"></i> Add Address
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1.5rem;">
|
||||
<?php foreach ($addresses as $index => $addr): ?>
|
||||
<div class="section-card">
|
||||
<div class="section-card-body">
|
||||
<?php if (!empty($addr['is_default'])): ?>
|
||||
<span class="badge badge-primary" style="margin-bottom: 0.75rem;">Default</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<h4 style="margin: 0 0 0.5rem;"><?= htmlspecialchars($addr['name']) ?></h4>
|
||||
|
||||
<p style="margin: 0 0 0.25rem;"><?= htmlspecialchars($addr['address']) ?></p>
|
||||
<?php if (!empty($addr['address2'])): ?>
|
||||
<p style="margin: 0 0 0.25rem;"><?= htmlspecialchars($addr['address2']) ?></p>
|
||||
<?php endif; ?>
|
||||
<p style="margin: 0 0 0.25rem;">
|
||||
<?= htmlspecialchars($addr['city']) ?>, <?= htmlspecialchars($addr['state']) ?> <?= htmlspecialchars($addr['zip']) ?>
|
||||
</p>
|
||||
<p style="margin: 0 0 0.75rem;"><?= htmlspecialchars($addr['country'] ?? 'USA') ?></p>
|
||||
|
||||
<?php if (!empty($addr['phone'])): ?>
|
||||
<p class="text-muted" style="margin: 0 0 1rem;">
|
||||
<i class="fas fa-phone"></i> <?= htmlspecialchars($addr['phone']) ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
||||
<button class="btn btn-sm btn-secondary" onclick="editAddress(<?= $index ?>)">
|
||||
<i class="fas fa-edit"></i> Edit
|
||||
</button>
|
||||
|
||||
<?php if (empty($addr['is_default'])): ?>
|
||||
<form method="POST" style="display: inline;">
|
||||
<input type="hidden" name="action" value="set_default">
|
||||
<input type="hidden" name="index" value="<?= $index ?>">
|
||||
<button type="submit" class="btn btn-sm btn-secondary">
|
||||
<i class="fas fa-check"></i> Make Default
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST" style="display: inline;" onsubmit="return confirm('Delete this address?')">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
<input type="hidden" name="index" value="<?= $index ?>">
|
||||
<button type="submit" class="btn btn-sm btn-danger">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Address Modal -->
|
||||
<div class="modal-overlay" id="addressModal">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" id="addressModalTitle">Add Address</h3>
|
||||
<button type="button" class="modal-close" onclick="Modal.close('addressModal')">×</button>
|
||||
</div>
|
||||
<form method="POST" id="addressForm">
|
||||
<input type="hidden" name="action" id="addressAction" value="add">
|
||||
<input type="hidden" name="index" id="addressIndex" value="-1">
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Full Name *</label>
|
||||
<input type="text" name="name" id="addr_name" class="form-input" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Phone Number</label>
|
||||
<input type="tel" name="phone" id="addr_phone" class="form-input">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Street Address *</label>
|
||||
<input type="text" name="address" id="addr_address" class="form-input" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Apartment, suite, etc.</label>
|
||||
<input type="text" name="address2" id="addr_address2" class="form-input">
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 2fr 1fr 1fr; gap: 1rem;">
|
||||
<div class="form-group">
|
||||
<label class="form-label">City *</label>
|
||||
<input type="text" name="city" id="addr_city" class="form-input" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">State *</label>
|
||||
<input type="text" name="state" id="addr_state" class="form-input" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">ZIP *</label>
|
||||
<input type="text" name="zip" id="addr_zip" class="form-input" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Country</label>
|
||||
<select name="country" id="addr_country" class="form-select">
|
||||
<option value="USA">United States</option>
|
||||
<option value="Canada">Canada</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-checkbox">
|
||||
<input type="checkbox" name="is_default" id="addr_default">
|
||||
Set as default address
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="Modal.close('addressModal')">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Save Address</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const addresses = <?= json_encode($addresses) ?>;
|
||||
|
||||
function openAddressModal() {
|
||||
document.getElementById('addressModalTitle').textContent = 'Add Address';
|
||||
document.getElementById('addressAction').value = 'add';
|
||||
document.getElementById('addressIndex').value = '-1';
|
||||
document.getElementById('addressForm').reset();
|
||||
Modal.open('addressModal');
|
||||
}
|
||||
|
||||
function editAddress(index) {
|
||||
const addr = addresses[index];
|
||||
if (!addr) return;
|
||||
|
||||
document.getElementById('addressModalTitle').textContent = 'Edit Address';
|
||||
document.getElementById('addressAction').value = 'edit';
|
||||
document.getElementById('addressIndex').value = index;
|
||||
|
||||
document.getElementById('addr_name').value = addr.name || '';
|
||||
document.getElementById('addr_phone').value = addr.phone || '';
|
||||
document.getElementById('addr_address').value = addr.address || '';
|
||||
document.getElementById('addr_address2').value = addr.address2 || '';
|
||||
document.getElementById('addr_city').value = addr.city || '';
|
||||
document.getElementById('addr_state').value = addr.state || '';
|
||||
document.getElementById('addr_zip').value = addr.zip || '';
|
||||
document.getElementById('addr_country').value = addr.country || 'USA';
|
||||
document.getElementById('addr_default').checked = !!addr.is_default;
|
||||
|
||||
Modal.open('addressModal');
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
@@ -0,0 +1,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php require_once __DIR__ . '/../../includes/footer.php'; ?>
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* Account Page Sidebar Include
|
||||
*/
|
||||
|
||||
$customer = CustomerAuth::getFullUser();
|
||||
?>
|
||||
|
||||
<style>
|
||||
.account-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 250px 1fr;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.account-sidebar {
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1.5rem;
|
||||
height: fit-content;
|
||||
position: sticky;
|
||||
top: 90px;
|
||||
}
|
||||
|
||||
.account-nav {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.account-nav li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.account-nav a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-text);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.account-nav a:hover {
|
||||
background: var(--color-background);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.account-nav a.active {
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.account-nav a i {
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.account-content {
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.account-header {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.account-header h1 {
|
||||
font-size: 1.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.section-card {
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius-lg);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.section-card-header {
|
||||
padding: 1rem 1.5rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-card-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.section-card-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.account-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.account-sidebar {
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<section class="section" style="padding-top: 2rem;">
|
||||
<div class="container">
|
||||
<div class="account-layout">
|
||||
<!-- Sidebar -->
|
||||
<aside class="account-sidebar">
|
||||
<div style="text-align: center; margin-bottom: 1.5rem;">
|
||||
<div style="width: 80px; height: 80px; background: var(--color-primary); color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 2rem; margin: 0 auto 1rem;">
|
||||
<?= strtoupper(substr($customer['name'] ?? $customer['email'], 0, 1)) ?>
|
||||
</div>
|
||||
<h3 style="margin: 0 0 0.25rem; font-size: 1rem;"><?= htmlspecialchars($customer['name'] ?? 'Customer') ?></h3>
|
||||
<p class="text-muted" style="font-size: 0.875rem; margin: 0;"><?= htmlspecialchars($customer['email']) ?></p>
|
||||
</div>
|
||||
|
||||
<ul class="account-nav">
|
||||
<li><a href="/account/" class="<?= ($currentPage ?? '') === 'dashboard' ? 'active' : '' ?>"><i class="fas fa-tachometer-alt"></i> Dashboard</a></li>
|
||||
<li><a href="/account/orders.php" class="<?= ($currentPage ?? '') === 'orders' ? 'active' : '' ?>"><i class="fas fa-shopping-bag"></i> My Orders</a></li>
|
||||
<li><a href="/account/wishlist.php" class="<?= ($currentPage ?? '') === 'wishlist' ? 'active' : '' ?>"><i class="fas fa-heart"></i> Wishlist</a></li>
|
||||
<li><a href="/account/wallet.php" class="<?= ($currentPage ?? '') === 'wallet' ? 'active' : '' ?>"><i class="fas fa-wallet"></i> Wallet</a></li>
|
||||
<li><a href="/account/rewards.php" class="<?= ($currentPage ?? '') === 'rewards' ? 'active' : '' ?>"><i class="fas fa-crown"></i> Rewards</a></li>
|
||||
<li><a href="/account/addresses.php" class="<?= ($currentPage ?? '') === 'addresses' ? 'active' : '' ?>"><i class="fas fa-map-marker-alt"></i> Addresses</a></li>
|
||||
<li><a href="/account/profile.php" class="<?= ($currentPage ?? '') === 'profile' ? 'active' : '' ?>"><i class="fas fa-user"></i> Profile</a></li>
|
||||
<li><a href="/account/reviews.php" class="<?= ($currentPage ?? '') === 'reviews' ? 'active' : '' ?>"><i class="fas fa-star"></i> My Reviews</a></li>
|
||||
<li><a href="/logout.php" style="color: var(--color-error);"><i class="fas fa-sign-out-alt"></i> Logout</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="account-content">
|
||||
@@ -0,0 +1,357 @@
|
||||
<?php
|
||||
/**
|
||||
* Tom's Java Jive - Customer Account Dashboard
|
||||
*/
|
||||
|
||||
$pageTitle = "My Account - Tom's Java Jive";
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
|
||||
CustomerAuth::require();
|
||||
|
||||
$customer = CustomerAuth::getFullUser();
|
||||
|
||||
// Get recent orders
|
||||
$recentOrders = db()->fetchAll(
|
||||
"SELECT * FROM orders WHERE customer_id = :id ORDER BY created_at DESC LIMIT 5",
|
||||
['id' => $customer['customer_id']]
|
||||
);
|
||||
|
||||
// Get order stats
|
||||
$orderStats = db()->fetch(
|
||||
"SELECT COUNT(*) as total_orders, COALESCE(SUM(total), 0) as total_spent
|
||||
FROM orders WHERE customer_id = :id AND payment_status = 'paid'",
|
||||
['id' => $customer['customer_id']]
|
||||
);
|
||||
|
||||
// Get wishlist count
|
||||
$wishlistCount = db()->count('wishlist', 'customer_id = :id', ['id' => $customer['customer_id']]);
|
||||
|
||||
// Get recent wallet transactions
|
||||
$walletTransactions = db()->fetchAll(
|
||||
"SELECT * FROM wallet_transactions WHERE customer_id = :id ORDER BY created_at DESC LIMIT 5",
|
||||
['id' => $customer['customer_id']]
|
||||
);
|
||||
|
||||
require_once __DIR__ . '/../includes/header.php';
|
||||
?>
|
||||
|
||||
<style>
|
||||
.account-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 250px 1fr;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.account-sidebar {
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1.5rem;
|
||||
height: fit-content;
|
||||
position: sticky;
|
||||
top: 90px;
|
||||
}
|
||||
|
||||
.account-nav {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.account-nav li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.account-nav a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-text);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.account-nav a:hover {
|
||||
background: var(--color-background);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.account-nav a.active {
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.account-nav a i {
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.account-content {
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.account-header {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.account-header h1 {
|
||||
font-size: 1.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.dashboard-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.dashboard-stat {
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dashboard-stat-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 1rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.dashboard-stat-icon.primary {
|
||||
background: rgba(255, 94, 26, 0.1);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.dashboard-stat-icon.success {
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.dashboard-stat-icon.warning {
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
.dashboard-stat-value {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.dashboard-stat-label {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.section-card {
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius-lg);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.section-card-header {
|
||||
padding: 1rem 1.5rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-card-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.section-card-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.order-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem 0;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.order-item:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.order-item:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.order-info h4 {
|
||||
margin: 0 0 0.25rem;
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
.order-info p {
|
||||
margin: 0;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.account-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.account-sidebar {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.dashboard-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<section class="section" style="padding-top: 2rem;">
|
||||
<div class="container">
|
||||
<?php if (hasFlash('success')): ?>
|
||||
<div class="alert alert-success mb-2">
|
||||
<i class="fas fa-check-circle"></i> <?= getFlash('success') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="account-layout">
|
||||
<!-- Sidebar -->
|
||||
<aside class="account-sidebar">
|
||||
<div style="text-align: center; margin-bottom: 1.5rem;">
|
||||
<div style="width: 80px; height: 80px; background: var(--color-primary); color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 2rem; margin: 0 auto 1rem;">
|
||||
<?= strtoupper(substr($customer['name'] ?? $customer['email'], 0, 1)) ?>
|
||||
</div>
|
||||
<h3 style="margin: 0 0 0.25rem; font-size: 1rem;"><?= htmlspecialchars($customer['name'] ?? 'Customer') ?></h3>
|
||||
<p class="text-muted" style="font-size: 0.875rem; margin: 0;"><?= htmlspecialchars($customer['email']) ?></p>
|
||||
</div>
|
||||
|
||||
<ul class="account-nav">
|
||||
<li><a href="/account/" class="active"><i class="fas fa-tachometer-alt"></i> Dashboard</a></li>
|
||||
<li><a href="/account/orders.php"><i class="fas fa-shopping-bag"></i> My Orders</a></li>
|
||||
<li><a href="/account/wishlist.php"><i class="fas fa-heart"></i> Wishlist</a></li>
|
||||
<li><a href="/account/wallet.php"><i class="fas fa-wallet"></i> Wallet</a></li>
|
||||
<li><a href="/account/addresses.php"><i class="fas fa-map-marker-alt"></i> Addresses</a></li>
|
||||
<li><a href="/account/profile.php"><i class="fas fa-user"></i> Profile</a></li>
|
||||
<li><a href="/account/reviews.php"><i class="fas fa-star"></i> My Reviews</a></li>
|
||||
<li><a href="/logout.php" style="color: var(--color-error);"><i class="fas fa-sign-out-alt"></i> Logout</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="account-content">
|
||||
<div class="account-header">
|
||||
<h1>Welcome back, <?= htmlspecialchars($customer['name'] ?? 'Coffee Lover') ?>!</h1>
|
||||
<p class="text-muted">Here's an overview of your account activity.</p>
|
||||
</div>
|
||||
|
||||
<!-- Stats Grid -->
|
||||
<div class="dashboard-grid">
|
||||
<div class="dashboard-stat">
|
||||
<div class="dashboard-stat-icon primary">
|
||||
<i class="fas fa-shopping-bag"></i>
|
||||
</div>
|
||||
<div class="dashboard-stat-value"><?= $orderStats['total_orders'] ?? 0 ?></div>
|
||||
<div class="dashboard-stat-label">Total Orders</div>
|
||||
</div>
|
||||
<div class="dashboard-stat">
|
||||
<div class="dashboard-stat-icon success">
|
||||
<i class="fas fa-dollar-sign"></i>
|
||||
</div>
|
||||
<div class="dashboard-stat-value"><?= formatCurrency($orderStats['total_spent'] ?? 0) ?></div>
|
||||
<div class="dashboard-stat-label">Total Spent</div>
|
||||
</div>
|
||||
<div class="dashboard-stat">
|
||||
<div class="dashboard-stat-icon warning">
|
||||
<i class="fas fa-wallet"></i>
|
||||
</div>
|
||||
<div class="dashboard-stat-value"><?= formatCurrency($customer['wallet_balance'] ?? 0) ?></div>
|
||||
<div class="dashboard-stat-label">Wallet Balance</div>
|
||||
</div>
|
||||
<div class="dashboard-stat">
|
||||
<div class="dashboard-stat-icon primary">
|
||||
<i class="fas fa-star"></i>
|
||||
</div>
|
||||
<div class="dashboard-stat-value"><?= number_format($customer['reward_points'] ?? 0) ?></div>
|
||||
<div class="dashboard-stat-label">Reward Points</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Orders -->
|
||||
<div class="section-card">
|
||||
<div class="section-card-header">
|
||||
<h3><i class="fas fa-clock"></i> Recent Orders</h3>
|
||||
<a href="/account/orders.php" class="btn btn-sm btn-secondary">View All</a>
|
||||
</div>
|
||||
<div class="section-card-body">
|
||||
<?php if (empty($recentOrders)): ?>
|
||||
<p class="text-muted text-center" style="padding: 2rem 0;">
|
||||
<i class="fas fa-shopping-bag" style="font-size: 2rem; opacity: 0.5; display: block; margin-bottom: 1rem;"></i>
|
||||
No orders yet. <a href="/shop.php">Start shopping!</a>
|
||||
</p>
|
||||
<?php else: ?>
|
||||
<?php foreach ($recentOrders as $order): ?>
|
||||
<div class="order-item">
|
||||
<div class="order-info">
|
||||
<h4><a href="/account/order.php?id=<?= $order['order_id'] ?>">Order #<?= htmlspecialchars($order['order_number']) ?></a></h4>
|
||||
<p><?= formatDate($order['created_at']) ?> · <?= count(json_decode($order['items'], true)) ?> items</p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<div style="font-weight: 600; margin-bottom: 0.25rem;"><?= formatCurrency($order['total']) ?></div>
|
||||
<?php
|
||||
$statusClass = match($order['order_status']) {
|
||||
'pending' => 'warning',
|
||||
'confirmed', 'processing' => 'primary',
|
||||
'shipped' => 'primary',
|
||||
'delivered' => 'success',
|
||||
'cancelled', 'refunded' => 'error',
|
||||
default => 'primary'
|
||||
};
|
||||
?>
|
||||
<span class="badge badge-<?= $statusClass ?>"><?= ucfirst($order['order_status']) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Wallet Activity -->
|
||||
<?php if (!empty($walletTransactions)): ?>
|
||||
<div class="section-card">
|
||||
<div class="section-card-header">
|
||||
<h3><i class="fas fa-wallet"></i> Recent Wallet Activity</h3>
|
||||
<a href="/account/wallet.php" class="btn btn-sm btn-secondary">View All</a>
|
||||
</div>
|
||||
<div class="section-card-body">
|
||||
<?php foreach ($walletTransactions as $tx): ?>
|
||||
<div class="order-item">
|
||||
<div class="order-info">
|
||||
<h4><?= htmlspecialchars($tx['description'] ?? ucfirst($tx['type'])) ?></h4>
|
||||
<p><?= formatDateTime($tx['created_at']) ?></p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<div style="font-weight: 600; color: <?= $tx['amount'] > 0 ? 'var(--color-success)' : 'var(--color-error)' ?>;">
|
||||
<?= $tx['amount'] > 0 ? '+' : '' ?><?= formatCurrency($tx['amount']) ?>
|
||||
</div>
|
||||
<small class="text-muted">Balance: <?= formatCurrency($tx['balance_after']) ?></small>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
|
||||
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
/**
|
||||
* Tom's Java Jive - Order Detail Page
|
||||
*/
|
||||
|
||||
$pageTitle = "Order Details - Tom's Java Jive";
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
|
||||
CustomerAuth::require();
|
||||
$customer = CustomerAuth::getFullUser();
|
||||
$currentPage = 'orders';
|
||||
|
||||
$orderId = $_GET['id'] ?? '';
|
||||
|
||||
$order = db()->fetch(
|
||||
"SELECT * FROM orders WHERE order_id = :id AND customer_id = :cid",
|
||||
['id' => $orderId, 'cid' => $customer['customer_id']]
|
||||
);
|
||||
|
||||
if (!$order) {
|
||||
redirect('/account/orders.php');
|
||||
}
|
||||
|
||||
$items = json_decode($order['items'], true);
|
||||
$shippingAddress = json_decode($order['shipping_address'] ?? '{}', true);
|
||||
|
||||
require_once __DIR__ . '/../includes/header.php';
|
||||
require_once __DIR__ . '/includes/sidebar.php';
|
||||
|
||||
$statusClass = match($order['order_status']) {
|
||||
'pending' => 'warning',
|
||||
'confirmed', 'processing' => 'primary',
|
||||
'shipped' => 'primary',
|
||||
'delivered' => 'success',
|
||||
'cancelled', 'refunded' => 'error',
|
||||
default => 'primary'
|
||||
};
|
||||
?>
|
||||
|
||||
<div class="account-header">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<a href="/account/orders.php" class="text-muted" style="font-size: 0.875rem;">
|
||||
<i class="fas fa-arrow-left"></i> Back to Orders
|
||||
</a>
|
||||
<h1 style="margin-top: 0.5rem;">Order #<?= htmlspecialchars($order['order_number']) ?></h1>
|
||||
</div>
|
||||
<span class="badge badge-<?= $statusClass ?>" style="font-size: 1rem; padding: 0.5rem 1rem;">
|
||||
<?= ucfirst($order['order_status']) ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Order Progress -->
|
||||
<?php if (!in_array($order['order_status'], ['cancelled', 'refunded'])): ?>
|
||||
<div class="section-card">
|
||||
<div class="section-card-body">
|
||||
<?php
|
||||
$steps = ['pending', 'confirmed', 'processing', 'shipped', 'delivered'];
|
||||
$currentStep = array_search($order['order_status'], $steps);
|
||||
if ($currentStep === false) $currentStep = 0;
|
||||
?>
|
||||
<div style="display: flex; justify-content: space-between; position: relative;">
|
||||
<div style="position: absolute; top: 15px; left: 0; right: 0; height: 2px; background: var(--color-border);"></div>
|
||||
<div style="position: absolute; top: 15px; left: 0; width: <?= ($currentStep / 4) * 100 ?>%; height: 2px; background: var(--color-primary); transition: width 0.3s;"></div>
|
||||
|
||||
<?php foreach ($steps as $i => $step): ?>
|
||||
<div style="text-align: center; position: relative; z-index: 1;">
|
||||
<div style="width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 0.5rem; <?= $i <= $currentStep ? 'background: var(--color-primary); color: white;' : 'background: var(--color-surface); border: 2px solid var(--color-border);' ?>">
|
||||
<?php if ($i < $currentStep): ?>
|
||||
<i class="fas fa-check"></i>
|
||||
<?php else: ?>
|
||||
<?= $i + 1 ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div style="font-size: 0.75rem; <?= $i <= $currentStep ? 'color: var(--color-text);' : 'color: var(--color-text-muted);' ?>">
|
||||
<?= ucfirst($step) ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 2fr 1fr; gap: 1.5rem;">
|
||||
<div>
|
||||
<!-- Order Items -->
|
||||
<div class="section-card">
|
||||
<div class="section-card-header">
|
||||
<h3>Order Items</h3>
|
||||
</div>
|
||||
<div class="section-card-body" style="padding: 0;">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<th style="text-align: center;">Qty</th>
|
||||
<th style="text-align: right;">Price</th>
|
||||
<th style="text-align: right;">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($items as $item): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<strong><?= htmlspecialchars($item['name']) ?></strong>
|
||||
<?php if (!empty($item['options'])): ?>
|
||||
<br><small class="text-muted"><?= htmlspecialchars($item['options']) ?></small>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td style="text-align: center;"><?= $item['quantity'] ?></td>
|
||||
<td style="text-align: right;"><?= formatCurrency($item['price']) ?></td>
|
||||
<td style="text-align: right;"><?= formatCurrency($item['total']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Shipping Info -->
|
||||
<?php if (!empty($shippingAddress)): ?>
|
||||
<div class="section-card">
|
||||
<div class="section-card-header">
|
||||
<h3>Shipping Information</h3>
|
||||
</div>
|
||||
<div class="section-card-body">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
|
||||
<div>
|
||||
<h4 style="margin: 0 0 0.5rem; font-size: 0.875rem; color: var(--color-text-muted);">Shipping Address</h4>
|
||||
<p style="margin: 0;">
|
||||
<?= htmlspecialchars($shippingAddress['name'] ?? $order['customer_name']) ?><br>
|
||||
<?= htmlspecialchars($shippingAddress['address'] ?? '') ?><br>
|
||||
<?= htmlspecialchars($shippingAddress['city'] ?? '') ?>, <?= htmlspecialchars($shippingAddress['state'] ?? '') ?> <?= htmlspecialchars($shippingAddress['zip'] ?? '') ?><br>
|
||||
<?= htmlspecialchars($shippingAddress['country'] ?? '') ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php if ($order['tracking_number']): ?>
|
||||
<div>
|
||||
<h4 style="margin: 0 0 0.5rem; font-size: 0.875rem; color: var(--color-text-muted);">Tracking</h4>
|
||||
<p style="margin: 0 0 1rem;">
|
||||
<strong><?= htmlspecialchars($order['tracking_number']) ?></strong>
|
||||
</p>
|
||||
<?php if ($order['tracking_url']): ?>
|
||||
<a href="<?= htmlspecialchars($order['tracking_url']) ?>" target="_blank" class="btn btn-sm btn-primary">
|
||||
<i class="fas fa-truck"></i> Track Package
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<!-- Order Summary -->
|
||||
<div class="section-card">
|
||||
<div class="section-card-header">
|
||||
<h3>Order Summary</h3>
|
||||
</div>
|
||||
<div class="section-card-body">
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 0.5rem;">
|
||||
<span class="text-muted">Subtotal</span>
|
||||
<span><?= formatCurrency($order['subtotal']) ?></span>
|
||||
</div>
|
||||
<?php if ($order['discount'] > 0): ?>
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 0.5rem; color: var(--color-success);">
|
||||
<span>Discount</span>
|
||||
<span>-<?= formatCurrency($order['discount']) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 0.5rem;">
|
||||
<span class="text-muted">Shipping</span>
|
||||
<span><?= $order['shipping'] > 0 ? formatCurrency($order['shipping']) : 'Free' ?></span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 0.5rem;">
|
||||
<span class="text-muted">Tax</span>
|
||||
<span><?= formatCurrency($order['tax']) ?></span>
|
||||
</div>
|
||||
<?php if ($order['wallet_amount_used'] > 0): ?>
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 0.5rem; color: var(--color-success);">
|
||||
<span>Wallet</span>
|
||||
<span>-<?= formatCurrency($order['wallet_amount_used']) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div style="border-top: 2px solid var(--color-border); padding-top: 1rem; display: flex; justify-content: space-between;">
|
||||
<strong>Total</strong>
|
||||
<strong style="font-size: 1.25rem; color: var(--color-primary);"><?= formatCurrency($order['total']) ?></strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Payment Info -->
|
||||
<div class="section-card">
|
||||
<div class="section-card-header">
|
||||
<h3>Payment</h3>
|
||||
</div>
|
||||
<div class="section-card-body">
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 0.5rem;">
|
||||
<span class="text-muted">Method</span>
|
||||
<span><?= ucfirst($order['payment_method'] ?? 'N/A') ?></span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span class="text-muted">Status</span>
|
||||
<span class="badge badge-<?= $order['payment_status'] === 'paid' ? 'success' : 'warning' ?>">
|
||||
<?= ucfirst($order['payment_status'] ?? 'Pending') ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Order Date -->
|
||||
<div class="section-card">
|
||||
<div class="section-card-body">
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 0.5rem;">
|
||||
<span class="text-muted">Order Date</span>
|
||||
<span><?= formatDateTime($order['created_at']) ?></span>
|
||||
</div>
|
||||
<?php if ($order['updated_at']): ?>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span class="text-muted">Last Updated</span>
|
||||
<span><?= formatDateTime($order['updated_at']) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Need Help -->
|
||||
<div class="section-card">
|
||||
<div class="section-card-body text-center">
|
||||
<p class="text-muted" style="margin: 0 0 1rem;">Need help with this order?</p>
|
||||
<a href="/contact.php?order=<?= $order['order_number'] ?>" class="btn btn-secondary btn-block">
|
||||
<i class="fas fa-headset"></i> Contact Support
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
/**
|
||||
* Tom's Java Jive - Customer Orders
|
||||
*/
|
||||
|
||||
$pageTitle = "My Orders - Tom's Java Jive";
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
|
||||
CustomerAuth::require();
|
||||
$customer = CustomerAuth::getFullUser();
|
||||
$currentPage = 'orders';
|
||||
|
||||
// Pagination
|
||||
$page = max(1, intval($_GET['page'] ?? 1));
|
||||
$status = $_GET['status'] ?? '';
|
||||
|
||||
$where = 'customer_id = :id';
|
||||
$params = ['id' => $customer['customer_id']];
|
||||
|
||||
if ($status) {
|
||||
$where .= ' AND order_status = :status';
|
||||
$params['status'] = $status;
|
||||
}
|
||||
|
||||
$total = db()->count('orders', $where, $params);
|
||||
$pagination = paginate($total, $page, 10);
|
||||
|
||||
$orders = db()->fetchAll(
|
||||
"SELECT * FROM orders WHERE {$where} ORDER BY created_at DESC LIMIT :limit OFFSET :offset",
|
||||
array_merge($params, ['limit' => $pagination['per_page'], 'offset' => $pagination['offset']])
|
||||
);
|
||||
|
||||
require_once __DIR__ . '/../includes/header.php';
|
||||
require_once __DIR__ . '/includes/sidebar.php';
|
||||
?>
|
||||
|
||||
<div class="account-header">
|
||||
<h1>My Orders</h1>
|
||||
<p class="text-muted">View and track your order history</p>
|
||||
</div>
|
||||
|
||||
<!-- Filter -->
|
||||
<div class="section-card">
|
||||
<div class="section-card-body" style="padding: 1rem 1.5rem;">
|
||||
<form method="GET" style="display: flex; gap: 1rem; flex-wrap: wrap; align-items: center;">
|
||||
<select name="status" class="form-select" style="width: auto;" onchange="this.form.submit()">
|
||||
<option value="">All Orders</option>
|
||||
<option value="pending" <?= $status === 'pending' ? 'selected' : '' ?>>Pending</option>
|
||||
<option value="confirmed" <?= $status === 'confirmed' ? 'selected' : '' ?>>Confirmed</option>
|
||||
<option value="processing" <?= $status === 'processing' ? 'selected' : '' ?>>Processing</option>
|
||||
<option value="shipped" <?= $status === 'shipped' ? 'selected' : '' ?>>Shipped</option>
|
||||
<option value="delivered" <?= $status === 'delivered' ? 'selected' : '' ?>>Delivered</option>
|
||||
<option value="cancelled" <?= $status === 'cancelled' ? 'selected' : '' ?>>Cancelled</option>
|
||||
</select>
|
||||
<span class="text-muted"><?= $total ?> orders found</span>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (empty($orders)): ?>
|
||||
<div class="section-card">
|
||||
<div class="section-card-body text-center" style="padding: 3rem;">
|
||||
<i class="fas fa-shopping-bag" style="font-size: 3rem; color: var(--color-text-muted); margin-bottom: 1rem;"></i>
|
||||
<h3>No orders yet</h3>
|
||||
<p class="text-muted">Start shopping to see your orders here!</p>
|
||||
<a href="/shop.php" class="btn btn-primary mt-1">Browse Products</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($orders as $order):
|
||||
$items = json_decode($order['items'], true);
|
||||
$statusClass = match($order['order_status']) {
|
||||
'pending' => 'warning',
|
||||
'confirmed', 'processing' => 'primary',
|
||||
'shipped' => 'primary',
|
||||
'delivered' => 'success',
|
||||
'cancelled', 'refunded' => 'error',
|
||||
default => 'primary'
|
||||
};
|
||||
?>
|
||||
<div class="section-card">
|
||||
<div class="section-card-header">
|
||||
<div>
|
||||
<strong>Order #<?= htmlspecialchars($order['order_number']) ?></strong>
|
||||
<span class="text-muted" style="margin-left: 1rem;"><?= formatDate($order['created_at']) ?></span>
|
||||
</div>
|
||||
<span class="badge badge-<?= $statusClass ?>"><?= ucfirst($order['order_status']) ?></span>
|
||||
</div>
|
||||
<div class="section-card-body">
|
||||
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
|
||||
<?php foreach (array_slice($items, 0, 4) as $item): ?>
|
||||
<div style="display: flex; align-items: center; gap: 0.75rem; background: var(--color-background); padding: 0.5rem 1rem; border-radius: var(--radius-md);">
|
||||
<span><?= $item['quantity'] ?>x</span>
|
||||
<span><?= htmlspecialchars(truncate($item['name'], 30)) ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php if (count($items) > 4): ?>
|
||||
<div style="padding: 0.5rem 1rem; color: var(--color-text-muted);">
|
||||
+<?= count($items) - 4 ?> more items
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--color-border);">
|
||||
<div>
|
||||
<span class="text-muted">Total:</span>
|
||||
<strong style="font-size: 1.125rem; margin-left: 0.5rem;"><?= formatCurrency($order['total']) ?></strong>
|
||||
</div>
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<?php if ($order['tracking_number']): ?>
|
||||
<a href="<?= htmlspecialchars($order['tracking_url'] ?? '#') ?>" target="_blank" class="btn btn-sm btn-secondary">
|
||||
<i class="fas fa-truck"></i> Track
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<a href="/account/order.php?id=<?= $order['order_id'] ?>" class="btn btn-sm btn-primary">
|
||||
View Details
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if ($pagination['total_pages'] > 1): ?>
|
||||
<div style="display: flex; justify-content: center; gap: 0.5rem; margin-top: 2rem;">
|
||||
<?php for ($i = 1; $i <= $pagination['total_pages']; $i++): ?>
|
||||
<a href="/account/orders.php?page=<?= $i ?><?= $status ? '&status=' . $status : '' ?>"
|
||||
class="btn <?= $i === $page ? 'btn-primary' : 'btn-secondary' ?>">
|
||||
<?= $i ?>
|
||||
</a>
|
||||
<?php endfor; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
/**
|
||||
* Tom's Java Jive - Customer Profile
|
||||
*/
|
||||
|
||||
$pageTitle = "My Profile - Tom's Java Jive";
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
|
||||
CustomerAuth::require();
|
||||
$customer = CustomerAuth::getFullUser();
|
||||
$currentPage = 'profile';
|
||||
|
||||
$error = '';
|
||||
$success = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = $_POST['action'] ?? '';
|
||||
|
||||
if ($action === 'update_profile') {
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
$phone = trim($_POST['phone'] ?? '');
|
||||
|
||||
db()->query(
|
||||
"UPDATE customers SET name = :name, phone = :phone, updated_at = NOW() WHERE customer_id = :id",
|
||||
['name' => $name, 'phone' => $phone, 'id' => $customer['customer_id']]
|
||||
);
|
||||
|
||||
$success = 'Profile updated successfully';
|
||||
$customer['name'] = $name;
|
||||
$customer['phone'] = $phone;
|
||||
}
|
||||
|
||||
if ($action === 'change_password') {
|
||||
$currentPassword = $_POST['current_password'] ?? '';
|
||||
$newPassword = $_POST['new_password'] ?? '';
|
||||
$confirmPassword = $_POST['confirm_password'] ?? '';
|
||||
|
||||
if (!password_verify($currentPassword, $customer['password_hash'])) {
|
||||
$error = 'Current password is incorrect';
|
||||
} elseif (strlen($newPassword) < 8) {
|
||||
$error = 'New password must be at least 8 characters';
|
||||
} elseif ($newPassword !== $confirmPassword) {
|
||||
$error = 'New passwords do not match';
|
||||
} else {
|
||||
$newHash = password_hash($newPassword, PASSWORD_DEFAULT);
|
||||
db()->query(
|
||||
"UPDATE customers SET password_hash = :hash, updated_at = NOW() WHERE customer_id = :id",
|
||||
['hash' => $newHash, 'id' => $customer['customer_id']]
|
||||
);
|
||||
$success = 'Password changed successfully';
|
||||
}
|
||||
}
|
||||
|
||||
if ($action === 'update_preferences') {
|
||||
$newsletter = isset($_POST['newsletter']) ? 1 : 0;
|
||||
$smsNotifications = isset($_POST['sms_notifications']) ? 1 : 0;
|
||||
|
||||
$preferences = [
|
||||
'newsletter' => $newsletter,
|
||||
'sms_notifications' => $smsNotifications
|
||||
];
|
||||
|
||||
db()->query(
|
||||
"UPDATE customers SET preferences = :prefs, updated_at = NOW() WHERE customer_id = :id",
|
||||
['prefs' => json_encode($preferences), 'id' => $customer['customer_id']]
|
||||
);
|
||||
|
||||
// Update newsletter subscription
|
||||
if ($newsletter) {
|
||||
$existing = db()->fetch("SELECT id FROM email_subscribers WHERE email = :email", ['email' => $customer['email']]);
|
||||
if (!$existing) {
|
||||
db()->insert('email_subscribers', [
|
||||
'email' => strtolower($customer['email']),
|
||||
'name' => $customer['name'],
|
||||
'source' => 'account'
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
db()->query("DELETE FROM email_subscribers WHERE email = :email", ['email' => $customer['email']]);
|
||||
}
|
||||
|
||||
$success = 'Preferences updated';
|
||||
}
|
||||
}
|
||||
|
||||
$preferences = json_decode($customer['preferences'] ?? '{}', true);
|
||||
|
||||
require_once __DIR__ . '/../includes/header.php';
|
||||
require_once __DIR__ . '/includes/sidebar.php';
|
||||
?>
|
||||
|
||||
<div class="account-header">
|
||||
<h1>My Profile</h1>
|
||||
<p class="text-muted">Manage your account settings</p>
|
||||
</div>
|
||||
|
||||
<?php if ($success): ?>
|
||||
<div class="alert alert-success mb-2">
|
||||
<i class="fas fa-check-circle"></i> <?= htmlspecialchars($success) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-error mb-2">
|
||||
<i class="fas fa-exclamation-circle"></i> <?= htmlspecialchars($error) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Profile Information -->
|
||||
<div class="section-card">
|
||||
<div class="section-card-header">
|
||||
<h3><i class="fas fa-user"></i> Personal Information</h3>
|
||||
</div>
|
||||
<div class="section-card-body">
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="update_profile">
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem;">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Full Name</label>
|
||||
<input type="text" name="name" class="form-input" value="<?= htmlspecialchars($customer['name'] ?? '') ?>">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Email Address</label>
|
||||
<input type="email" class="form-input" value="<?= htmlspecialchars($customer['email']) ?>" disabled>
|
||||
<small class="text-muted">Contact support to change your email</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Phone Number</label>
|
||||
<input type="tel" name="phone" class="form-input" value="<?= htmlspecialchars($customer['phone'] ?? '') ?>">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Member Since</label>
|
||||
<input type="text" class="form-input" value="<?= formatDate($customer['created_at']) ?>" disabled>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary mt-1">
|
||||
<i class="fas fa-save"></i> Save Changes
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Change Password -->
|
||||
<div class="section-card">
|
||||
<div class="section-card-header">
|
||||
<h3><i class="fas fa-lock"></i> Change Password</h3>
|
||||
</div>
|
||||
<div class="section-card-body">
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="change_password">
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1.5rem;">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Current Password</label>
|
||||
<input type="password" name="current_password" class="form-input" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">New Password</label>
|
||||
<input type="password" name="new_password" class="form-input" required minlength="8">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Confirm New Password</label>
|
||||
<input type="password" name="confirm_password" class="form-input" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-key"></i> Change Password
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Communication Preferences -->
|
||||
<div class="section-card">
|
||||
<div class="section-card-header">
|
||||
<h3><i class="fas fa-bell"></i> Communication Preferences</h3>
|
||||
</div>
|
||||
<div class="section-card-body">
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="update_preferences">
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-checkbox">
|
||||
<input type="checkbox" name="newsletter" <?= !empty($preferences['newsletter']) ? 'checked' : '' ?>>
|
||||
<strong>Email Newsletter</strong>
|
||||
<br><span class="text-muted" style="font-size: 0.875rem; margin-left: 1.5rem;">
|
||||
Receive updates about new products, promotions, and news
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-checkbox">
|
||||
<input type="checkbox" name="sms_notifications" <?= !empty($preferences['sms_notifications']) ? 'checked' : '' ?>>
|
||||
<strong>SMS Notifications</strong>
|
||||
<br><span class="text-muted" style="font-size: 0.875rem; margin-left: 1.5rem;">
|
||||
Receive order updates and alerts via text message
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save"></i> Save Preferences
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Account -->
|
||||
<div class="section-card" style="border: 1px solid var(--color-error);">
|
||||
<div class="section-card-header" style="background: rgba(239, 68, 68, 0.1);">
|
||||
<h3 style="color: var(--color-error);"><i class="fas fa-exclamation-triangle"></i> Danger Zone</h3>
|
||||
</div>
|
||||
<div class="section-card-body">
|
||||
<p class="text-muted" style="margin-bottom: 1rem;">
|
||||
Once you delete your account, there is no going back. Please be certain.
|
||||
</p>
|
||||
<button class="btn btn-danger" onclick="confirmDeleteAccount()">
|
||||
<i class="fas fa-trash"></i> Delete My Account
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function confirmDeleteAccount() {
|
||||
if (confirm('Are you sure you want to delete your account? This action cannot be undone.')) {
|
||||
if (confirm('This will permanently delete all your data including orders, wishlist, and wallet balance. Type your email to confirm.')) {
|
||||
const email = prompt('Type your email to confirm deletion:');
|
||||
if (email === '<?= addslashes($customer['email']) ?>') {
|
||||
window.location.href = '/api/delete-account.php';
|
||||
} else {
|
||||
alert('Email does not match. Account not deleted.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
@@ -0,0 +1,294 @@
|
||||
<?php
|
||||
/**
|
||||
* Tom's Java Jive - Customer Reviews
|
||||
*/
|
||||
|
||||
$pageTitle = "My Reviews - Tom's Java Jive";
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
|
||||
CustomerAuth::require();
|
||||
$customer = CustomerAuth::getFullUser();
|
||||
$currentPage = 'reviews';
|
||||
|
||||
// Handle delete action
|
||||
if (isset($_POST['delete_review'])) {
|
||||
$reviewId = $_POST['review_id'] ?? '';
|
||||
db()->query(
|
||||
"DELETE FROM reviews WHERE review_id = :rid AND customer_id = :cid",
|
||||
['rid' => $reviewId, 'cid' => $customer['customer_id']]
|
||||
);
|
||||
setFlash('success', 'Review deleted');
|
||||
redirect('/account/reviews.php');
|
||||
}
|
||||
|
||||
// Get customer's reviews with product info
|
||||
$reviews = db()->fetchAll(
|
||||
"SELECT r.*, p.name as product_name, p.slug as product_slug, p.images as product_images
|
||||
FROM reviews r
|
||||
LEFT JOIN products p ON r.product_id = p.product_id
|
||||
WHERE r.customer_id = :id
|
||||
ORDER BY r.created_at DESC",
|
||||
['id' => $customer['customer_id']]
|
||||
);
|
||||
|
||||
// Get products eligible for review (purchased but not reviewed)
|
||||
$eligibleProducts = db()->fetchAll(
|
||||
"SELECT DISTINCT p.product_id, p.name, p.slug, p.images
|
||||
FROM orders o
|
||||
JOIN order_items oi ON o.order_id = oi.order_id
|
||||
JOIN products p ON oi.product_id = p.product_id
|
||||
WHERE o.customer_id = :cid
|
||||
AND o.order_status = 'delivered'
|
||||
AND p.product_id NOT IN (SELECT product_id FROM reviews WHERE customer_id = :cid2)
|
||||
LIMIT 10",
|
||||
['cid' => $customer['customer_id'], 'cid2' => $customer['customer_id']]
|
||||
);
|
||||
|
||||
require_once __DIR__ . '/../includes/header.php';
|
||||
require_once __DIR__ . '/includes/sidebar.php';
|
||||
?>
|
||||
|
||||
<div class="account-header">
|
||||
<h1>My Reviews</h1>
|
||||
<p class="text-muted">Manage your product reviews</p>
|
||||
</div>
|
||||
|
||||
<?php if (hasFlash('success')): ?>
|
||||
<div class="alert alert-success mb-2">
|
||||
<i class="fas fa-check-circle"></i> <?= getFlash('success') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Products to Review -->
|
||||
<?php if (!empty($eligibleProducts)): ?>
|
||||
<div class="section-card">
|
||||
<div class="section-card-header">
|
||||
<h3><i class="fas fa-edit"></i> Products to Review</h3>
|
||||
</div>
|
||||
<div class="section-card-body">
|
||||
<p class="text-muted" style="margin-bottom: 1rem;">You've purchased these products. Share your experience!</p>
|
||||
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
|
||||
<?php foreach ($eligibleProducts as $product):
|
||||
$images = json_decode($product['images'] ?? '[]', true);
|
||||
$imageUrl = !empty($images) ? $images[0] : '/assets/images/placeholder-product.svg';
|
||||
?>
|
||||
<div style="display: flex; align-items: center; gap: 0.75rem; background: var(--color-background); padding: 0.75rem 1rem; border-radius: var(--radius-md);">
|
||||
<img src="<?= htmlspecialchars($imageUrl) ?>" alt="" style="width: 40px; height: 40px; border-radius: 4px; object-fit: cover;" onerror="this.src='/assets/images/placeholder-product.svg'">
|
||||
<span style="font-size: 0.875rem;"><?= htmlspecialchars(truncate($product['name'], 25)) ?></span>
|
||||
<button class="btn btn-sm btn-primary" onclick="openReviewModal('<?= $product['product_id'] ?>', '<?= htmlspecialchars(addslashes($product['name'])) ?>')">
|
||||
Review
|
||||
</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- My Reviews -->
|
||||
<div class="section-card">
|
||||
<div class="section-card-header">
|
||||
<h3><i class="fas fa-star"></i> My Reviews (<?= count($reviews) ?>)</h3>
|
||||
</div>
|
||||
<div class="section-card-body" style="padding: 0;">
|
||||
<?php if (empty($reviews)): ?>
|
||||
<div class="text-center" style="padding: 3rem;">
|
||||
<i class="fas fa-star" style="font-size: 3rem; color: var(--color-text-muted); margin-bottom: 1rem;"></i>
|
||||
<h3>No reviews yet</h3>
|
||||
<p class="text-muted">Purchase products and share your thoughts!</p>
|
||||
<a href="/shop.php" class="btn btn-primary mt-1">Browse Products</a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($reviews as $review):
|
||||
$images = json_decode($review['product_images'] ?? '[]', true);
|
||||
$imageUrl = !empty($images) ? $images[0] : '/assets/images/placeholder-product.svg';
|
||||
?>
|
||||
<div style="padding: 1.5rem; border-bottom: 1px solid var(--color-border);">
|
||||
<div style="display: flex; gap: 1rem;">
|
||||
<img src="<?= htmlspecialchars($imageUrl) ?>" alt="" style="width: 80px; height: 80px; border-radius: var(--radius-md); object-fit: cover;" onerror="this.src='/assets/images/placeholder-product.svg'">
|
||||
|
||||
<div style="flex: 1;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 0.5rem;">
|
||||
<div>
|
||||
<a href="/product.php?slug=<?= htmlspecialchars($review['product_slug']) ?>" style="font-weight: 600; color: inherit;">
|
||||
<?= htmlspecialchars($review['product_name'] ?? 'Product') ?>
|
||||
</a>
|
||||
<div style="margin-top: 0.25rem;">
|
||||
<?php for ($i = 1; $i <= 5; $i++): ?>
|
||||
<i class="fas fa-star" style="color: <?= $i <= $review['rating'] ? 'var(--color-warning)' : 'var(--color-border)' ?>; font-size: 0.875rem;"></i>
|
||||
<?php endfor; ?>
|
||||
<span class="text-muted" style="margin-left: 0.5rem; font-size: 0.875rem;">
|
||||
<?= formatDate($review['created_at']) ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<?php
|
||||
$statusBadge = match($review['status']) {
|
||||
'approved' => ['success', 'Published'],
|
||||
'pending' => ['warning', 'Pending'],
|
||||
'rejected' => ['error', 'Rejected'],
|
||||
default => ['secondary', $review['status']]
|
||||
};
|
||||
?>
|
||||
<span class="badge badge-<?= $statusBadge[0] ?>"><?= $statusBadge[1] ?></span>
|
||||
|
||||
<form method="POST" style="display: inline;" onsubmit="return confirm('Delete this review?')">
|
||||
<input type="hidden" name="review_id" value="<?= $review['review_id'] ?>">
|
||||
<button type="submit" name="delete_review" class="btn btn-sm btn-danger">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($review['title']): ?>
|
||||
<h4 style="margin: 0 0 0.5rem; font-size: 1rem;"><?= htmlspecialchars($review['title']) ?></h4>
|
||||
<?php endif; ?>
|
||||
|
||||
<p style="margin: 0; color: var(--color-text-muted); font-size: 0.9375rem;">
|
||||
<?= nl2br(htmlspecialchars($review['content'])) ?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Review Modal -->
|
||||
<div class="modal-overlay" id="reviewModal">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">Write a Review</h3>
|
||||
<button type="button" class="modal-close" onclick="Modal.close('reviewModal')">×</button>
|
||||
</div>
|
||||
<form action="/api/submit-review.php" method="POST" id="reviewForm">
|
||||
<input type="hidden" name="product_id" id="review_product_id">
|
||||
<div class="modal-body">
|
||||
<p style="margin-bottom: 1rem;"><strong id="review_product_name"></strong></p>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Rating *</label>
|
||||
<div id="rating-stars" style="font-size: 2rem;">
|
||||
<?php for ($i = 1; $i <= 5; $i++): ?>
|
||||
<i class="far fa-star rating-star" data-rating="<?= $i ?>" style="cursor: pointer; color: var(--color-warning);"></i>
|
||||
<?php endfor; ?>
|
||||
</div>
|
||||
<input type="hidden" name="rating" id="rating_value" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Review Title</label>
|
||||
<input type="text" name="title" class="form-input" placeholder="Summarize your review">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Your Review *</label>
|
||||
<textarea name="content" class="form-input" rows="4" required placeholder="Share your experience with this product"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="Modal.close('reviewModal')">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-paper-plane"></i> Submit Review
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openReviewModal(productId, productName) {
|
||||
document.getElementById('review_product_id').value = productId;
|
||||
document.getElementById('review_product_name').textContent = productName;
|
||||
document.getElementById('rating_value').value = '';
|
||||
document.getElementById('reviewForm').reset();
|
||||
|
||||
// Reset stars
|
||||
document.querySelectorAll('.rating-star').forEach(star => {
|
||||
star.classList.remove('fas');
|
||||
star.classList.add('far');
|
||||
});
|
||||
|
||||
Modal.open('reviewModal');
|
||||
}
|
||||
|
||||
// Star rating interaction
|
||||
document.querySelectorAll('.rating-star').forEach(star => {
|
||||
star.addEventListener('click', function() {
|
||||
const rating = parseInt(this.dataset.rating);
|
||||
document.getElementById('rating_value').value = rating;
|
||||
|
||||
document.querySelectorAll('.rating-star').forEach((s, index) => {
|
||||
if (index < rating) {
|
||||
s.classList.remove('far');
|
||||
s.classList.add('fas');
|
||||
} else {
|
||||
s.classList.remove('fas');
|
||||
s.classList.add('far');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
star.addEventListener('mouseenter', function() {
|
||||
const rating = parseInt(this.dataset.rating);
|
||||
document.querySelectorAll('.rating-star').forEach((s, index) => {
|
||||
if (index < rating) {
|
||||
s.classList.remove('far');
|
||||
s.classList.add('fas');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
star.addEventListener('mouseleave', function() {
|
||||
const currentRating = parseInt(document.getElementById('rating_value').value) || 0;
|
||||
document.querySelectorAll('.rating-star').forEach((s, index) => {
|
||||
if (index < currentRating) {
|
||||
s.classList.remove('far');
|
||||
s.classList.add('fas');
|
||||
} else {
|
||||
s.classList.remove('fas');
|
||||
s.classList.add('far');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Form submission
|
||||
document.getElementById('reviewForm').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const rating = document.getElementById('rating_value').value;
|
||||
if (!rating) {
|
||||
alert('Please select a rating');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData(this);
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/submit-review.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.error) {
|
||||
alert(result.error);
|
||||
} else {
|
||||
alert('Thank you! Your review has been submitted and is pending approval.');
|
||||
window.location.reload();
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Failed to submit review. Please try again.');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
@@ -0,0 +1,452 @@
|
||||
<?php
|
||||
/**
|
||||
* Tom's Java Jive - Customer Loyalty Rewards Page
|
||||
*/
|
||||
|
||||
$pageTitle = "My Rewards - Tom's Java Jive";
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
require_once __DIR__ . '/../includes/loyalty.php';
|
||||
|
||||
CustomerAuth::require();
|
||||
$customer = CustomerAuth::getFullUser();
|
||||
$currentPage = 'rewards';
|
||||
|
||||
// Get loyalty status
|
||||
$loyaltyStatus = loyalty()->getCustomerTier($customer['customer_id']);
|
||||
$tiers = loyalty()->getTiers();
|
||||
$conversion = loyalty()->getConversionInfo();
|
||||
$history = loyalty()->getHistory($customer['customer_id'], 20);
|
||||
|
||||
// Handle redemption
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['redeem_points'])) {
|
||||
$points = intval($_POST['points'] ?? 0);
|
||||
|
||||
if ($points >= 100) {
|
||||
$result = loyalty()->redeemPoints($customer['customer_id'], $points);
|
||||
|
||||
if ($result['success']) {
|
||||
setFlash('success', "Successfully redeemed {$points} points for " . formatCurrency($result['credit_value']) . " wallet credit!");
|
||||
} else {
|
||||
setFlash('error', $result['error']);
|
||||
}
|
||||
} else {
|
||||
setFlash('error', 'Minimum 100 points required for redemption');
|
||||
}
|
||||
|
||||
redirect('/account/rewards.php');
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../includes/header.php';
|
||||
require_once __DIR__ . '/includes/sidebar.php';
|
||||
?>
|
||||
|
||||
<style>
|
||||
.tier-card {
|
||||
background: linear-gradient(135deg, <?= $loyaltyStatus['info']['color'] ?>22, <?= $loyaltyStatus['info']['color'] ?>44);
|
||||
border: 2px solid <?= $loyaltyStatus['info']['color'] ?>;
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tier-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
right: -20%;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
background: <?= $loyaltyStatus['info']['color'] ?>;
|
||||
opacity: 0.1;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.tier-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
background: <?= $loyaltyStatus['info']['color'] ?>;
|
||||
color: <?= in_array($loyaltyStatus['tier'], ['silver', 'platinum']) ? '#333' : 'white' ?>;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.points-display {
|
||||
display: flex;
|
||||
gap: 3rem;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.points-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.points-value {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.points-label {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.progress-to-next {
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.progress-bar-wrapper {
|
||||
background: var(--color-border);
|
||||
border-radius: 10px;
|
||||
height: 12px;
|
||||
overflow: hidden;
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
.progress-bar-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, <?= $loyaltyStatus['info']['color'] ?>, <?= $loyaltyStatus['next_tier_info']['color'] ?? $loyaltyStatus['info']['color'] ?>);
|
||||
border-radius: 10px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.tier-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 1rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.tier-item {
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1.25rem;
|
||||
text-align: center;
|
||||
border: 2px solid transparent;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tier-item.current {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 4px 12px rgba(255, 94, 26, 0.2);
|
||||
}
|
||||
|
||||
.tier-item-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 0.75rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.tier-item h4 {
|
||||
margin: 0 0 0.25rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.tier-item .points {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.redeem-card {
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.redeem-options {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.redeem-option {
|
||||
background: var(--color-background);
|
||||
border: 2px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.redeem-option:hover {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.redeem-option.selected {
|
||||
border-color: var(--color-primary);
|
||||
background: rgba(255, 94, 26, 0.05);
|
||||
}
|
||||
|
||||
.redeem-option .points {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.redeem-option .value {
|
||||
color: var(--color-success);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.points-display {
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.tier-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.redeem-options {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="account-header">
|
||||
<h1>My Rewards</h1>
|
||||
<p class="text-muted">Earn points and unlock exclusive benefits</p>
|
||||
</div>
|
||||
|
||||
<?php if (hasFlash('success')): ?>
|
||||
<div class="alert alert-success mb-2">
|
||||
<i class="fas fa-check-circle"></i> <?= getFlash('success') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (hasFlash('error')): ?>
|
||||
<div class="alert alert-error mb-2">
|
||||
<i class="fas fa-exclamation-circle"></i> <?= getFlash('error') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Current Tier Card -->
|
||||
<div class="tier-card">
|
||||
<div class="tier-badge">
|
||||
<i class="fas <?= $loyaltyStatus['info']['icon'] ?>"></i>
|
||||
<?= $loyaltyStatus['info']['name'] ?>
|
||||
</div>
|
||||
|
||||
<div class="points-display">
|
||||
<div class="points-item">
|
||||
<div class="points-value"><?= number_format($loyaltyStatus['points']) ?></div>
|
||||
<div class="points-label">Available Points</div>
|
||||
</div>
|
||||
<div class="points-item">
|
||||
<div class="points-value"><?= formatCurrency($loyaltyStatus['points'] * $conversion['points_value']) ?></div>
|
||||
<div class="points-label">Points Value</div>
|
||||
</div>
|
||||
<div class="points-item">
|
||||
<div class="points-value"><?= $loyaltyStatus['info']['multiplier'] ?>x</div>
|
||||
<div class="points-label">Earning Rate</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($loyaltyStatus['next_tier']): ?>
|
||||
<div class="progress-to-next">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>Progress to <?= $loyaltyStatus['next_tier_info']['name'] ?></span>
|
||||
<span style="font-weight: 600;"><?= number_format($loyaltyStatus['points_to_next']) ?> points to go</span>
|
||||
</div>
|
||||
<div class="progress-bar-wrapper">
|
||||
<div class="progress-bar-fill" style="width: <?= $loyaltyStatus['progress_percent'] ?>%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="progress-to-next" style="text-align: center;">
|
||||
<i class="fas fa-crown" style="font-size: 2rem; color: <?= $loyaltyStatus['info']['color'] ?>; margin-bottom: 0.5rem;"></i>
|
||||
<p style="margin: 0; font-weight: 600;">You've reached the highest tier!</p>
|
||||
<p class="text-muted" style="margin: 0.25rem 0 0;">Enjoy all the exclusive benefits of <?= $loyaltyStatus['info']['name'] ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- All Tiers -->
|
||||
<div class="section-card">
|
||||
<div class="section-card-header">
|
||||
<h3><i class="fas fa-layer-group"></i> Loyalty Tiers</h3>
|
||||
</div>
|
||||
<div class="section-card-body">
|
||||
<div class="tier-grid">
|
||||
<?php foreach ($tiers as $key => $tier): ?>
|
||||
<div class="tier-item <?= $key === $loyaltyStatus['tier'] ? 'current' : '' ?>">
|
||||
<div class="tier-item-icon" style="background: <?= $tier['color'] ?>22; color: <?= $tier['color'] ?>;">
|
||||
<i class="fas <?= $tier['icon'] ?>"></i>
|
||||
</div>
|
||||
<h4><?= $tier['name'] ?></h4>
|
||||
<div class="points"><?= number_format($tier['min_points']) ?>+ pts</div>
|
||||
<div style="margin-top: 0.5rem; font-weight: 600;"><?= $tier['multiplier'] ?>x points</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<details style="margin-top: 1rem;">
|
||||
<summary style="cursor: pointer; font-weight: 600; color: var(--color-primary);">View All Benefits</summary>
|
||||
<div style="margin-top: 1rem;">
|
||||
<?php foreach ($tiers as $key => $tier): ?>
|
||||
<div style="margin-bottom: 1rem; padding: 1rem; background: var(--color-background); border-radius: var(--radius-md);">
|
||||
<h4 style="margin: 0 0 0.5rem; display: flex; align-items: center; gap: 0.5rem;">
|
||||
<i class="fas <?= $tier['icon'] ?>" style="color: <?= $tier['color'] ?>;"></i>
|
||||
<?= $tier['name'] ?>
|
||||
</h4>
|
||||
<ul style="margin: 0; padding-left: 1.5rem; color: var(--color-text-muted);">
|
||||
<?php foreach ($tier['benefits'] as $benefit): ?>
|
||||
<li><?= htmlspecialchars($benefit) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Redeem Points -->
|
||||
<div class="redeem-card">
|
||||
<h3 style="margin: 0 0 0.5rem;"><i class="fas fa-gift"></i> Redeem Points</h3>
|
||||
<p class="text-muted" style="margin: 0 0 1rem;">Convert your points to wallet credit. <?= $conversion['points_for_one_dollar'] ?> points = $1</p>
|
||||
|
||||
<?php if ($loyaltyStatus['points'] >= 100): ?>
|
||||
<form method="POST">
|
||||
<div class="redeem-options">
|
||||
<?php
|
||||
$redeemOptions = [100, 500, 1000];
|
||||
foreach ($redeemOptions as $pts):
|
||||
if ($pts <= $loyaltyStatus['points']):
|
||||
?>
|
||||
<label class="redeem-option">
|
||||
<input type="radio" name="points" value="<?= $pts ?>" style="display: none;">
|
||||
<div class="points"><?= number_format($pts) ?></div>
|
||||
<div>points</div>
|
||||
<div class="value" style="margin-top: 0.5rem;"><?= formatCurrency($pts * $conversion['points_value']) ?> credit</div>
|
||||
</label>
|
||||
<?php
|
||||
endif;
|
||||
endforeach;
|
||||
?>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 1rem; display: flex; gap: 1rem; align-items: center;">
|
||||
<div class="form-group" style="margin: 0; flex: 1;">
|
||||
<input type="number" name="custom_points" class="form-input" placeholder="Or enter custom amount" min="100" max="<?= $loyaltyStatus['points'] ?>" step="50">
|
||||
</div>
|
||||
<button type="submit" name="redeem_points" class="btn btn-primary">
|
||||
<i class="fas fa-exchange-alt"></i> Redeem
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<p class="text-muted" style="text-align: center; padding: 2rem;">
|
||||
You need at least 100 points to redeem. Keep shopping to earn more!
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Points History -->
|
||||
<div class="section-card">
|
||||
<div class="section-card-header">
|
||||
<h3><i class="fas fa-history"></i> Points History</h3>
|
||||
</div>
|
||||
<div class="section-card-body" style="padding: 0;">
|
||||
<?php if (empty($history)): ?>
|
||||
<p class="text-muted text-center" style="padding: 2rem;">No points activity yet</p>
|
||||
<?php else: ?>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Activity</th>
|
||||
<th style="text-align: right;">Points</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($history as $tx): ?>
|
||||
<tr>
|
||||
<td><?= formatDate($tx['created_at']) ?></td>
|
||||
<td>
|
||||
<?php
|
||||
$icon = match($tx['type']) {
|
||||
'earn' => 'fa-plus-circle',
|
||||
'redeem' => 'fa-gift',
|
||||
'tier_upgrade' => 'fa-arrow-up',
|
||||
'birthday_bonus' => 'fa-birthday-cake',
|
||||
'referral_bonus', 'referral_welcome' => 'fa-user-plus',
|
||||
default => 'fa-circle'
|
||||
};
|
||||
?>
|
||||
<i class="fas <?= $icon ?>" style="color: <?= $tx['points'] > 0 ? 'var(--color-success)' : 'var(--color-primary)' ?>;"></i>
|
||||
<?= htmlspecialchars($tx['description'] ?? ucfirst(str_replace('_', ' ', $tx['type']))) ?>
|
||||
</td>
|
||||
<td style="text-align: right; font-weight: 600; color: <?= $tx['points'] > 0 ? 'var(--color-success)' : 'var(--color-text)' ?>;">
|
||||
<?= $tx['points'] > 0 ? '+' : '' ?><?= number_format($tx['points']) ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Handle redeem option selection
|
||||
document.querySelectorAll('.redeem-option').forEach(opt => {
|
||||
opt.addEventListener('click', function() {
|
||||
document.querySelectorAll('.redeem-option').forEach(o => o.classList.remove('selected'));
|
||||
this.classList.add('selected');
|
||||
this.querySelector('input[type="radio"]').checked = true;
|
||||
document.querySelector('input[name="custom_points"]').value = '';
|
||||
});
|
||||
});
|
||||
|
||||
// Custom points input clears radio selection
|
||||
document.querySelector('input[name="custom_points"]')?.addEventListener('input', function() {
|
||||
if (this.value) {
|
||||
document.querySelectorAll('.redeem-option').forEach(o => {
|
||||
o.classList.remove('selected');
|
||||
o.querySelector('input[type="radio"]').checked = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Form submission - use custom or selected
|
||||
document.querySelector('form')?.addEventListener('submit', function(e) {
|
||||
const customPoints = document.querySelector('input[name="custom_points"]').value;
|
||||
const selectedRadio = document.querySelector('input[name="points"]:checked');
|
||||
|
||||
if (!customPoints && !selectedRadio) {
|
||||
e.preventDefault();
|
||||
alert('Please select or enter the number of points to redeem');
|
||||
return;
|
||||
}
|
||||
|
||||
if (customPoints) {
|
||||
// Create hidden input with custom points as 'points'
|
||||
const hidden = document.createElement('input');
|
||||
hidden.type = 'hidden';
|
||||
hidden.name = 'points';
|
||||
hidden.value = customPoints;
|
||||
this.appendChild(hidden);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
/**
|
||||
* Tom's Java Jive - Customer Wallet
|
||||
*/
|
||||
|
||||
$pageTitle = "My Wallet - Tom's Java Jive";
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
|
||||
CustomerAuth::require();
|
||||
$customer = CustomerAuth::getFullUser();
|
||||
$currentPage = 'wallet';
|
||||
|
||||
// Pagination
|
||||
$page = max(1, intval($_GET['page'] ?? 1));
|
||||
|
||||
$total = db()->count('wallet_transactions', 'customer_id = :id', ['id' => $customer['customer_id']]);
|
||||
$pagination = paginate($total, $page, 15);
|
||||
|
||||
$transactions = db()->fetchAll(
|
||||
"SELECT * FROM wallet_transactions WHERE customer_id = :id ORDER BY created_at DESC LIMIT :limit OFFSET :offset",
|
||||
['id' => $customer['customer_id'], 'limit' => $pagination['per_page'], 'offset' => $pagination['offset']]
|
||||
);
|
||||
|
||||
require_once __DIR__ . '/../includes/header.php';
|
||||
require_once __DIR__ . '/includes/sidebar.php';
|
||||
?>
|
||||
|
||||
<div class="account-header">
|
||||
<h1>My Wallet</h1>
|
||||
<p class="text-muted">View your wallet balance and transaction history</p>
|
||||
</div>
|
||||
|
||||
<!-- Wallet Balance Card -->
|
||||
<div class="section-card" style="background: linear-gradient(135deg, var(--color-primary), #c4420f); color: white;">
|
||||
<div class="section-card-body" style="padding: 2rem;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<div style="font-size: 0.875rem; opacity: 0.9; margin-bottom: 0.5rem;">Available Balance</div>
|
||||
<div style="font-size: 2.5rem; font-weight: 700;"><?= formatCurrency($customer['wallet_balance'] ?? 0) ?></div>
|
||||
</div>
|
||||
<div style="background: rgba(255,255,255,0.2); padding: 1.5rem; border-radius: var(--radius-lg);">
|
||||
<i class="fas fa-wallet" style="font-size: 2.5rem;"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 1rem; margin-top: 1.5rem;">
|
||||
<div style="flex: 1; background: rgba(255,255,255,0.15); padding: 1rem; border-radius: var(--radius-md); text-align: center;">
|
||||
<div style="font-size: 1.25rem; font-weight: 600;"><?= number_format($customer['reward_points'] ?? 0) ?></div>
|
||||
<div style="font-size: 0.75rem; opacity: 0.9;">Reward Points</div>
|
||||
</div>
|
||||
<div style="flex: 1; background: rgba(255,255,255,0.15); padding: 1rem; border-radius: var(--radius-md); text-align: center;">
|
||||
<div style="font-size: 1.25rem; font-weight: 600;"><?= formatCurrency(($customer['reward_points'] ?? 0) * 0.01) ?></div>
|
||||
<div style="font-size: 0.75rem; opacity: 0.9;">Points Value</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info Cards -->
|
||||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; margin-bottom: 1.5rem;">
|
||||
<div class="section-card">
|
||||
<div class="section-card-body text-center">
|
||||
<i class="fas fa-gift" style="font-size: 2rem; color: var(--color-primary); margin-bottom: 0.75rem;"></i>
|
||||
<h4 style="margin: 0 0 0.5rem;">Have a Gift Card?</h4>
|
||||
<p class="text-muted" style="font-size: 0.875rem; margin-bottom: 1rem;">Redeem your gift card to add funds to your wallet</p>
|
||||
<button class="btn btn-secondary" onclick="Modal.open('redeemModal')">
|
||||
<i class="fas fa-plus"></i> Redeem Gift Card
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section-card">
|
||||
<div class="section-card-body text-center">
|
||||
<i class="fas fa-star" style="font-size: 2rem; color: var(--color-warning); margin-bottom: 0.75rem;"></i>
|
||||
<h4 style="margin: 0 0 0.5rem;">Earn More Points</h4>
|
||||
<p class="text-muted" style="font-size: 0.875rem; margin-bottom: 1rem;">Earn 1 point for every $1 spent. 100 points = $1</p>
|
||||
<a href="/shop.php" class="btn btn-primary">
|
||||
<i class="fas fa-shopping-cart"></i> Shop Now
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Transaction History -->
|
||||
<div class="section-card">
|
||||
<div class="section-card-header">
|
||||
<h3><i class="fas fa-history"></i> Transaction History</h3>
|
||||
</div>
|
||||
<div class="section-card-body" style="padding: 0;">
|
||||
<?php if (empty($transactions)): ?>
|
||||
<div class="text-center" style="padding: 3rem;">
|
||||
<i class="fas fa-receipt" style="font-size: 3rem; color: var(--color-text-muted); margin-bottom: 1rem;"></i>
|
||||
<p class="text-muted">No transactions yet</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Description</th>
|
||||
<th>Type</th>
|
||||
<th style="text-align: right;">Amount</th>
|
||||
<th style="text-align: right;">Balance</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($transactions as $tx): ?>
|
||||
<tr>
|
||||
<td><?= formatDate($tx['created_at']) ?></td>
|
||||
<td><?= htmlspecialchars($tx['description'] ?? ucfirst($tx['type'])) ?></td>
|
||||
<td>
|
||||
<?php
|
||||
$typeIcon = match($tx['type']) {
|
||||
'deposit', 'gift_card', 'refund' => 'fa-arrow-down',
|
||||
'purchase', 'withdrawal' => 'fa-arrow-up',
|
||||
default => 'fa-exchange-alt'
|
||||
};
|
||||
$typeColor = $tx['amount'] > 0 ? 'success' : 'error';
|
||||
?>
|
||||
<span class="badge badge-<?= $typeColor ?>">
|
||||
<i class="fas <?= $typeIcon ?>"></i> <?= ucfirst(str_replace('_', ' ', $tx['type'])) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td style="text-align: right; font-weight: 600; color: <?= $tx['amount'] > 0 ? 'var(--color-success)' : 'var(--color-error)' ?>;">
|
||||
<?= $tx['amount'] > 0 ? '+' : '' ?><?= formatCurrency($tx['amount']) ?>
|
||||
</td>
|
||||
<td style="text-align: right;"><?= formatCurrency($tx['balance_after']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($pagination['total_pages'] > 1): ?>
|
||||
<div style="display: flex; justify-content: center; gap: 0.5rem; margin-top: 2rem;">
|
||||
<?php for ($i = 1; $i <= $pagination['total_pages']; $i++): ?>
|
||||
<a href="/account/wallet.php?page=<?= $i ?>"
|
||||
class="btn <?= $i === $page ? 'btn-primary' : 'btn-secondary' ?>">
|
||||
<?= $i ?>
|
||||
</a>
|
||||
<?php endfor; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Redeem Gift Card Modal -->
|
||||
<div class="modal-overlay" id="redeemModal">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">Redeem Gift Card</h3>
|
||||
<button type="button" class="modal-close" onclick="Modal.close('redeemModal')">×</button>
|
||||
</div>
|
||||
<form action="/api/redeem-gift-card.php" method="POST" id="redeemForm">
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Gift Card Code</label>
|
||||
<input type="text" name="code" class="form-input" required placeholder="XXXX-XXXX-XXXX" style="text-transform: uppercase; text-align: center; font-size: 1.25rem; letter-spacing: 2px;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="Modal.close('redeemModal')">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-gift"></i> Redeem
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('redeemForm').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
const code = this.querySelector('[name="code"]').value.trim();
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/redeem-gift-card.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ code })
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
alert(data.error);
|
||||
} else {
|
||||
alert('Success! ' + data.message);
|
||||
window.location.reload();
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Failed to redeem gift card. Please try again.');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
/**
|
||||
* Tom's Java Jive - Customer Wishlist
|
||||
*/
|
||||
|
||||
$pageTitle = "My Wishlist - Tom's Java Jive";
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
|
||||
CustomerAuth::require();
|
||||
$customer = CustomerAuth::getFullUser();
|
||||
$currentPage = 'wishlist';
|
||||
|
||||
// Handle remove action
|
||||
if (isset($_POST['remove_wishlist'])) {
|
||||
$productId = $_POST['product_id'] ?? '';
|
||||
db()->query(
|
||||
"DELETE FROM wishlist WHERE customer_id = :cid AND product_id = :pid",
|
||||
['cid' => $customer['customer_id'], 'pid' => $productId]
|
||||
);
|
||||
setFlash('success', 'Item removed from wishlist');
|
||||
redirect('/account/wishlist.php');
|
||||
}
|
||||
|
||||
// Get wishlist items with product details
|
||||
$wishlistItems = db()->fetchAll(
|
||||
"SELECT w.*, p.product_id, p.name, p.slug, p.price, p.sale_price, p.images, p.stock, p.is_active
|
||||
FROM wishlist w
|
||||
JOIN products p ON w.product_id = p.product_id
|
||||
WHERE w.customer_id = :id
|
||||
ORDER BY w.created_at DESC",
|
||||
['id' => $customer['customer_id']]
|
||||
);
|
||||
|
||||
require_once __DIR__ . '/../includes/header.php';
|
||||
require_once __DIR__ . '/includes/sidebar.php';
|
||||
?>
|
||||
|
||||
<div class="account-header">
|
||||
<h1>My Wishlist</h1>
|
||||
<p class="text-muted"><?= count($wishlistItems) ?> items saved</p>
|
||||
</div>
|
||||
|
||||
<?php if (hasFlash('success')): ?>
|
||||
<div class="alert alert-success mb-2">
|
||||
<i class="fas fa-check-circle"></i> <?= getFlash('success') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (empty($wishlistItems)): ?>
|
||||
<div class="section-card">
|
||||
<div class="section-card-body text-center" style="padding: 3rem;">
|
||||
<i class="fas fa-heart" style="font-size: 3rem; color: var(--color-text-muted); margin-bottom: 1rem;"></i>
|
||||
<h3>Your wishlist is empty</h3>
|
||||
<p class="text-muted">Save items you love by clicking the heart icon on products.</p>
|
||||
<a href="/shop.php" class="btn btn-primary mt-1">Browse Products</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 1.5rem;">
|
||||
<?php foreach ($wishlistItems as $item):
|
||||
$images = json_decode($item['images'] ?? '[]', true);
|
||||
$imageUrl = !empty($images) ? $images[0] : '/assets/images/placeholder-product.svg';
|
||||
$isAvailable = $item['is_active'] && $item['stock'] > 0;
|
||||
?>
|
||||
<div class="section-card" style="overflow: hidden;">
|
||||
<a href="/product.php?slug=<?= htmlspecialchars($item['slug']) ?>">
|
||||
<img src="<?= htmlspecialchars($imageUrl) ?>"
|
||||
alt="<?= htmlspecialchars($item['name']) ?>"
|
||||
style="width: 100%; height: 200px; object-fit: cover;"
|
||||
onerror="this.src='/assets/images/placeholder-product.svg'">
|
||||
</a>
|
||||
<div class="section-card-body">
|
||||
<h4 style="margin: 0 0 0.5rem;">
|
||||
<a href="/product.php?slug=<?= htmlspecialchars($item['slug']) ?>" style="color: inherit;">
|
||||
<?= htmlspecialchars($item['name']) ?>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<?php if ($item['sale_price']): ?>
|
||||
<span style="font-size: 1.25rem; font-weight: 600; color: var(--color-primary);">
|
||||
<?= formatCurrency($item['sale_price']) ?>
|
||||
</span>
|
||||
<span style="text-decoration: line-through; color: var(--color-text-muted); margin-left: 0.5rem;">
|
||||
<?= formatCurrency($item['price']) ?>
|
||||
</span>
|
||||
<?php else: ?>
|
||||
<span style="font-size: 1.25rem; font-weight: 600;">
|
||||
<?= formatCurrency($item['price']) ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!$isAvailable): ?>
|
||||
<p style="color: var(--color-error); font-size: 0.875rem; margin-bottom: 1rem;">
|
||||
<i class="fas fa-times-circle"></i> Out of stock
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<?php if ($isAvailable): ?>
|
||||
<button class="btn btn-primary" style="flex: 1;" onclick="addToCart('<?= $item['product_id'] ?>')">
|
||||
<i class="fas fa-shopping-cart"></i> Add to Cart
|
||||
</button>
|
||||
<?php else: ?>
|
||||
<button class="btn btn-secondary" style="flex: 1;" disabled>
|
||||
<i class="fas fa-shopping-cart"></i> Unavailable
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST" style="display: inline;">
|
||||
<input type="hidden" name="product_id" value="<?= $item['product_id'] ?>">
|
||||
<button type="submit" name="remove_wishlist" class="btn btn-danger" title="Remove">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<script>
|
||||
function addToCart(productId) {
|
||||
fetch('/api/cart.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'add', product_id: productId, quantity: 1 })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('Added to cart!');
|
||||
// Update cart count if exists
|
||||
const cartCount = document.querySelector('.cart-count');
|
||||
if (cartCount) {
|
||||
cartCount.textContent = data.cart_count || '';
|
||||
}
|
||||
} else {
|
||||
alert(data.error || 'Failed to add to cart');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
Reference in New Issue
Block a user