Fix product images, add-to-cart, and add Sub Categories filter

- Add display:block to .product-card-image so padding-top aspect ratio works on anchor tags
- Add Cache-Control: no-transform header to disable Cloudflare Rocket Loader (was deferring main.js and breaking add-to-cart click handlers)
- Add Sub Categories filter row on shop page using product_types table
- Show category · sub-category on product cards
- Add Sub Categories section to footer
- Preserve subcat param across category/sort filter links

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-14 19:47:08 +00:00
parent 548713971d
commit 890c98d4cd
4 changed files with 71 additions and 18 deletions
+1
View File
@@ -338,6 +338,7 @@ img {
}
.product-card-image {
display: block;
position: relative;
padding-top: 100%;
overflow: hidden;
+11
View File
@@ -23,6 +23,17 @@
<li><a href="/shop.php?category=blends">Blends</a></li>
</ul>
</div>
<div>
<h4>Sub Categories</h4>
<ul class="footer-links">
<?php
$footerTypes = db()->fetchAll("SELECT type_id, name FROM product_types WHERE is_active=1 ORDER BY sort_order ASC");
foreach ($footerTypes as $ft): ?>
<li><a href="/shop.php?subcat=<?= urlencode($ft['type_id']) ?>"><?= htmlspecialchars($ft['name']) ?></a></li>
<?php endforeach; ?>
</ul>
</div>
<div>
<h4>Company</h4>
+1
View File
@@ -6,6 +6,7 @@
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
header('Cache-Control: no-store, no-cache, must-revalidate, no-transform');
require_once __DIR__ . '/db.php';
require_once __DIR__ . '/../config/config.php';
+58 -18
View File
@@ -8,6 +8,7 @@ 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));
@@ -21,6 +22,11 @@ if ($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 . '%';
@@ -42,7 +48,7 @@ $pagination = paginate($totalProducts, $page, 12);
// Get products
$products = db()->fetchAll(
"SELECT * FROM products WHERE {$whereClause} ORDER BY {$orderBy} LIMIT :limit OFFSET :offset",
"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 {$whereClause} ORDER BY {$orderBy} LIMIT :limit OFFSET :offset",
array_merge($params, ['limit' => $pagination['per_page'], 'offset' => $pagination['offset']])
);
@@ -71,15 +77,29 @@ $productTypesList = db()->fetchAll("SELECT type_id, name, slug FROM product_type
<section class="section">
<div class="container">
<!-- Filters Bar -->
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem; margin-bottom: 2rem;">
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<a href="/shop.php" class="btn <?= !$category ? 'btn-primary' : 'btn-secondary' ?>">All</a>
<?php foreach ($categories as $cat): ?>
<a href="/shop.php?category=<?= urlencode($cat['category']) ?>"
class="btn <?= $category === $cat['category'] ? 'btn-primary' : 'btn-secondary' ?>">
<?= htmlspecialchars(ucfirst($cat['category'])) ?>
</a>
<?php endforeach; ?>
<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;">
@@ -87,24 +107,39 @@ $productTypesList = db()->fetchAll("SELECT type_id, name, slug FROM product_type
<?php if ($category): ?>
<input type="hidden" name="category" value="<?= htmlspecialchars($category) ?>">
<?php endif; ?>
<input type="text" name="search" class="form-input" placeholder="Search..."
<?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<?= $category ? '&category=' . urlencode($category) : '' ?>" <?= $sort === 'newest' ? 'selected' : '' ?>>Newest</option>
<option value="/shop.php?sort=price_low<?= $category ? '&category=' . urlencode($category) : '' ?>" <?= $sort === 'price_low' ? 'selected' : '' ?>>Price: Low to High</option>
<option value="/shop.php?sort=price_high<?= $category ? '&category=' . urlencode($category) : '' ?>" <?= $sort === 'price_high' ? 'selected' : '' ?>>Price: High to Low</option>
<option value="/shop.php?sort=name<?= $category ? '&category=' . urlencode($category) : '' ?>" <?= $sort === 'name' ? 'selected' : '' ?>>Name</option>
<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 ? ' &middot; ' . htmlspecialchars($subcatName) : '' ?>
</p>
<!-- Product Grid -->
@@ -132,8 +167,13 @@ $productTypesList = db()->fetchAll("SELECT type_id, name, slug FROM product_type
<?php endif; ?>
</a>
<div class="product-card-body">
<?php if ($product['category']): ?>
<div class="product-card-category"><?= htmlspecialchars($product['category']) ?></div>
<?php if ($product['category'] || !empty($product['type_name'])): ?>
<div class="product-card-category">
<?= htmlspecialchars($product['category'] ?? '') ?>
<?php if (!empty($product['type_name'])): ?>
<?= $product['category'] ? ' &middot; ' : '' ?><?= 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>