mirror of
https://github.com/myronblair/tomsjavajive
synced 2026-06-30 17:50:32 -05:00
5c0927af39
Prefixed is_active, category, product_type_id, name, description, and ORDER BY columns with table alias p to resolve ambiguity with the product_types JOIN. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
210 lines
11 KiB
PHP
210 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* Tom's Java Jive - Shop Page
|
|
*/
|
|
|
|
$pageTitle = "Shop - Tom's Java Jive";
|
|
require_once __DIR__ . '/includes/functions.php';
|
|
|
|
// Get filters
|
|
$category = $_GET['category'] ?? '';
|
|
$subcat = $_GET['subcat'] ?? '';
|
|
$search = $_GET['search'] ?? '';
|
|
$sort = $_GET['sort'] ?? 'newest';
|
|
$page = max(1, intval($_GET['page'] ?? 1));
|
|
|
|
// Build query
|
|
$where = ['is_active = 1'];
|
|
$params = [];
|
|
|
|
if ($category) {
|
|
$where[] = 'category = :category';
|
|
$params['category'] = $category;
|
|
}
|
|
|
|
if ($subcat) {
|
|
$where[] = 'product_type_id = :subcat';
|
|
$params['subcat'] = $subcat;
|
|
}
|
|
|
|
if ($search) {
|
|
$where[] = '(name LIKE :search OR description LIKE :search)';
|
|
$params['search'] = '%' . $search . '%';
|
|
}
|
|
|
|
$whereClause = implode(' AND ', $where);
|
|
// Prefix columns that are ambiguous in the JOIN query
|
|
$joinWhereClause = str_replace(
|
|
['is_active = 1', 'category =', 'product_type_id =', '(name LIKE', 'description LIKE'],
|
|
['p.is_active = 1', 'p.category =', 'p.product_type_id =', '(p.name LIKE', 'p.description LIKE'],
|
|
$whereClause
|
|
);
|
|
|
|
// Sort
|
|
$orderBy = match($sort) {
|
|
'price_low' => 'COALESCE(p.sale_price, p.price) ASC',
|
|
'price_high' => 'COALESCE(p.sale_price, p.price) DESC',
|
|
'name' => 'p.name ASC',
|
|
default => 'p.created_at DESC'
|
|
};
|
|
|
|
// Get total count
|
|
$totalProducts = db()->count('products', $whereClause, $params);
|
|
$pagination = paginate($totalProducts, $page, 12);
|
|
|
|
// Get products
|
|
$products = db()->fetchAll(
|
|
"SELECT p.*, pt.name AS type_name, pt.type_id AS type_slug FROM products p LEFT JOIN product_types pt ON p.product_type_id = pt.type_id WHERE {$joinWhereClause} ORDER BY {$orderBy} LIMIT :limit OFFSET :offset",
|
|
array_merge($params, ['limit' => $pagination['per_page'], 'offset' => $pagination['offset']])
|
|
);
|
|
|
|
// Get categories for filter
|
|
$categories = db()->fetchAll(
|
|
"SELECT DISTINCT category FROM products WHERE category IS NOT NULL AND category != '' AND is_active = 1 ORDER BY category"
|
|
);
|
|
|
|
$metaTitle = "Shop Premium Coffee Beans | Tom's Java Jive";
|
|
$metaDescription = 'Browse our selection of premium artisan coffee beans. Single origin, blends, light, medium and dark roasts. Free shipping over $50.';
|
|
$metaKeywords = 'buy coffee beans online, artisan coffee, single origin, blends, light roast, dark roast';
|
|
$canonicalUrl = 'https://tomsjavajive.com/shop.php';
|
|
require_once __DIR__ . '/includes/header.php';
|
|
$productTypesList = db()->fetchAll("SELECT type_id, name, slug FROM product_types WHERE is_active=1 ORDER BY sort_order ASC");
|
|
|
|
?>
|
|
|
|
<!-- Page Header -->
|
|
<section style="background: linear-gradient(135deg, var(--color-secondary) 0%, #5D2E0A 100%); color: white; padding: 3rem 0; text-align: center;">
|
|
<div class="container">
|
|
<h1 style="font-family: var(--font-display); font-size: 2.5rem; margin-bottom: 0.5rem;">Our Coffee Collection</h1>
|
|
<p style="opacity: 0.9;">Discover your perfect brew from our selection of premium coffees</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section">
|
|
<div class="container">
|
|
<!-- Filters Bar -->
|
|
<div style="display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: 1rem; margin-bottom: 2rem;">
|
|
<div style="display: flex; flex-direction: column; gap: 0.75rem;">
|
|
<div style="display: flex; gap: 0.75rem; flex-wrap: wrap; align-items: center;">
|
|
<a href="/shop.php<?= $subcat ? '?subcat=' . urlencode($subcat) : '' ?>" class="btn <?= !$category ? 'btn-primary' : 'btn-secondary' ?>">All</a>
|
|
<?php foreach ($categories as $cat): ?>
|
|
<a href="/shop.php?category=<?= urlencode($cat['category']) ?><?= $subcat ? '&subcat=' . urlencode($subcat) : '' ?>"
|
|
class="btn <?= $category === $cat['category'] ? 'btn-primary' : 'btn-secondary' ?>">
|
|
<?= htmlspecialchars(ucfirst($cat['category'])) ?>
|
|
</a>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php if (!empty($productTypesList)): ?>
|
|
<div style="display: flex; gap: 0.75rem; flex-wrap: wrap; align-items: center;">
|
|
<span class="text-muted" style="font-size: 0.85rem; font-weight: 600; white-space: nowrap;">Sub Categories:</span>
|
|
<a href="/shop.php<?= $category ? '?category=' . urlencode($category) : '' ?>" class="btn btn-sm <?= !$subcat ? 'btn-primary' : 'btn-secondary' ?>">All</a>
|
|
<?php foreach ($productTypesList as $pt): ?>
|
|
<a href="/shop.php?subcat=<?= urlencode($pt['type_id']) ?><?= $category ? '&category=' . urlencode($category) : '' ?>"
|
|
class="btn btn-sm <?= $subcat === $pt['type_id'] ? 'btn-primary' : 'btn-secondary' ?>">
|
|
<?= htmlspecialchars($pt['name']) ?>
|
|
</a>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<div style="display: flex; gap: 1rem; align-items: center;">
|
|
<form method="GET" style="display: flex; gap: 0.5rem;">
|
|
<?php if ($category): ?>
|
|
<input type="hidden" name="category" value="<?= htmlspecialchars($category) ?>">
|
|
<?php endif; ?>
|
|
<?php if ($subcat): ?>
|
|
<input type="hidden" name="subcat" value="<?= htmlspecialchars($subcat) ?>">
|
|
<?php endif; ?>
|
|
<input type="text" name="search" class="form-input" placeholder="Search..."
|
|
value="<?= htmlspecialchars($search) ?>" style="width: 200px;">
|
|
<button type="submit" class="btn btn-secondary"><i class="fas fa-search"></i></button>
|
|
</form>
|
|
<?php
|
|
$filterQs = http_build_query(array_filter(['category' => $category, 'subcat' => $subcat]));
|
|
$filterQs = $filterQs ? '&' . $filterQs : '';
|
|
?>
|
|
<select onchange="window.location.href=this.value" class="form-select" style="width: auto;">
|
|
<option value="/shop.php?sort=newest<?= $filterQs ?>" <?= $sort === 'newest' ? 'selected' : '' ?>>Newest</option>
|
|
<option value="/shop.php?sort=price_low<?= $filterQs ?>" <?= $sort === 'price_low' ? 'selected' : '' ?>>Price: Low to High</option>
|
|
<option value="/shop.php?sort=price_high<?= $filterQs ?>" <?= $sort === 'price_high' ? 'selected' : '' ?>>Price: High to Low</option>
|
|
<option value="/shop.php?sort=name<?= $filterQs ?>" <?= $sort === 'name' ? 'selected' : '' ?>>Name</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Results count -->
|
|
<?php
|
|
$subcatName = '';
|
|
if ($subcat) {
|
|
foreach ($productTypesList as $pt) {
|
|
if ($pt['type_id'] === $subcat) { $subcatName = $pt['name']; break; }
|
|
}
|
|
}
|
|
?>
|
|
<p class="text-muted" style="margin-bottom: 1.5rem;">
|
|
Showing <?= count($products) ?> of <?= $totalProducts ?> products
|
|
<?= $category ? ' in ' . htmlspecialchars(ucfirst($category)) : '' ?>
|
|
<?= $subcatName ? ' · ' . htmlspecialchars($subcatName) : '' ?>
|
|
</p>
|
|
|
|
<!-- Product Grid -->
|
|
<?php if (empty($products)): ?>
|
|
<div style="text-align: center; padding: 4rem 0;">
|
|
<i class="fas fa-coffee" style="font-size: 4rem; color: var(--color-text-muted); margin-bottom: 1rem;"></i>
|
|
<h3>No products found</h3>
|
|
<p class="text-muted">Try adjusting your search or filters</p>
|
|
<a href="/shop.php" class="btn btn-primary mt-1">View All Products</a>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="product-grid">
|
|
<?php foreach ($products as $product):
|
|
$images = json_decode($product['images'] ?? '[]', true);
|
|
$imageUrl = !empty($images) ? $images[0] : '/assets/images/placeholder-product.svg';
|
|
$price = $product['sale_price'] ?? $product['price'];
|
|
?>
|
|
<div class="product-card">
|
|
<a href="/product.php?id=<?= $product['product_id'] ?>" class="product-card-image">
|
|
<img src="<?= htmlspecialchars($imageUrl) ?>" alt="<?= htmlspecialchars($product['name']) ?>">
|
|
<?php if ($product['sale_price']): ?>
|
|
<span class="product-card-badge">Sale</span>
|
|
<?php elseif ($product['is_featured']): ?>
|
|
<span class="product-card-badge">Featured</span>
|
|
<?php endif; ?>
|
|
</a>
|
|
<div class="product-card-body">
|
|
<?php if ($product['category'] || !empty($product['type_name'])): ?>
|
|
<div class="product-card-category">
|
|
<?= htmlspecialchars($product['category'] ?? '') ?>
|
|
<?php if (!empty($product['type_name'])): ?>
|
|
<?= $product['category'] ? ' · ' : '' ?><?= htmlspecialchars($product['type_name']) ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
<h3 class="product-card-title">
|
|
<a href="/product.php?id=<?= $product['product_id'] ?>"><?= htmlspecialchars($product['name']) ?></a>
|
|
</h3>
|
|
<div class="product-card-price">
|
|
<span class="current"><?= formatCurrency($price) ?></span>
|
|
<?php if ($product['sale_price']): ?>
|
|
<span class="original"><?= formatCurrency($product['price']) ?></span>
|
|
<?php endif; ?>
|
|
</div>
|
|
<button class="btn btn-primary btn-block add-to-cart-btn" data-product-id="<?= $product['product_id'] ?>">
|
|
<i class="fas fa-shopping-bag"></i> Add to Cart
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<?php if ($pagination['total_pages'] > 1): ?>
|
|
<?= renderPagination($pagination, '/shop.php?' . http_build_query(array_filter(['category' => $category, 'sort' => $sort]))) ?>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
</section>
|
|
|
|
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|