Files
tomsjavajive/shop.php
T
myron 771e1a15b1 Modularize CSS into page-specific files
Split style.css into home.css (hero, features, newsletter, splash) and
products.css (product grid/cards). Each page loads only what it needs
via $extraHead. style.css now contains only truly shared styles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 17:32:57 +00:00

211 lines
12 KiB
PHP

<?php
/**
* Tom's Java Jive - Shop Page
*/
$pageTitle = "Shop - Tom's Java Jive";
$extraHead = '<link rel="stylesheet" href="/assets/css/products.css?v=' . filemtime(__DIR__ . '/assets/css/products.css') . '">';
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");
?>
<?php
$filterQs = http_build_query(array_filter(['category' => $category, 'subcat' => $subcat]));
$filterQs = $filterQs ? '&' . $filterQs : '';
?>
<!-- Shop Header + Filters combined -->
<section style="background: linear-gradient(135deg, var(--color-secondary) 0%, #5D2E0A 100%); color: white; padding: 1.25rem 0;">
<div class="container">
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 0.75rem;">
<h1 style="font-family: var(--font-display); font-size: 1.4rem; margin: 0; white-space: nowrap;">Our Coffee Collection</h1>
<div style="display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;">
<form method="GET" style="display: flex; gap: 0.4rem;">
<?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: 160px; padding: 0.35rem 0.75rem; font-size: 0.85rem; background: rgba(255,255,255,0.15); border-color: rgba(255,255,255,0.3); color: white;">
<button type="submit" style="background: rgba(255,255,255,0.2); border: 1px solid rgba(255,255,255,0.3); color: white; padding: 0.35rem 0.65rem; border-radius: var(--radius-md); cursor: pointer;">
<i class="fas fa-search" style="font-size: 0.8rem;"></i>
</button>
</form>
<select onchange="window.location.href=this.value"
style="background: rgba(255,255,255,0.15); border: 1px solid rgba(255,255,255,0.3); color: white; padding: 0.35rem 0.6rem; border-radius: var(--radius-md); font-size: 0.85rem; cursor: pointer;">
<option value="/shop.php?sort=newest<?= $filterQs ?>" <?= $sort==='newest' ? 'selected' : '' ?> style="color:#333">Newest</option>
<option value="/shop.php?sort=price_low<?= $filterQs ?>" <?= $sort==='price_low' ? 'selected' : '' ?> style="color:#333">Price ↑</option>
<option value="/shop.php?sort=price_high<?= $filterQs ?>" <?= $sort==='price_high' ? 'selected' : '' ?> style="color:#333">Price ↓</option>
<option value="/shop.php?sort=name<?= $filterQs ?>" <?= $sort==='name' ? 'selected' : '' ?> style="color:#333">Name</option>
</select>
</div>
</div>
<!-- Filter pills -->
<div style="display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; margin-top: 0.75rem;">
<span style="font-size: 0.75rem; opacity: 0.75; text-transform: uppercase; letter-spacing: 0.05em; margin-right: 0.25rem;">Category:</span>
<a href="/shop.php<?= $subcat ? '?subcat='.urlencode($subcat) : '' ?>"
style="<?= !$category ? 'background:white;color:var(--color-secondary);font-weight:700;' : 'background:rgba(255,255,255,0.15);color:white;' ?> padding:0.2rem 0.65rem; border-radius:999px; font-size:0.8rem; text-decoration:none; border:1px solid rgba(255,255,255,0.4);">All</a>
<?php foreach ($categories as $cat): ?>
<a href="/shop.php?category=<?= urlencode($cat['category']) ?><?= $subcat ? '&subcat='.urlencode($subcat) : '' ?>"
style="<?= $category===$cat['category'] ? 'background:white;color:var(--color-secondary);font-weight:700;' : 'background:rgba(255,255,255,0.15);color:white;' ?> padding:0.2rem 0.65rem; border-radius:999px; font-size:0.8rem; text-decoration:none; border:1px solid rgba(255,255,255,0.4);">
<?= htmlspecialchars(ucfirst($cat['category'])) ?>
</a>
<?php endforeach; ?>
<?php if (!empty($productTypesList)): ?>
<span style="width:1px; height:16px; background:rgba(255,255,255,0.3); margin:0 0.25rem;"></span>
<span style="font-size:0.75rem; opacity:0.75; text-transform:uppercase; letter-spacing:0.05em; margin-right:0.25rem;">Type:</span>
<a href="/shop.php<?= $category ? '?category='.urlencode($category) : '' ?>"
style="<?= !$subcat ? 'background:white;color:var(--color-secondary);font-weight:700;' : 'background:rgba(255,255,255,0.15);color:white;' ?> padding:0.2rem 0.65rem; border-radius:999px; font-size:0.8rem; text-decoration:none; border:1px solid rgba(255,255,255,0.4);">All</a>
<?php foreach ($productTypesList as $pt): ?>
<a href="/shop.php?subcat=<?= urlencode($pt['type_id']) ?><?= $category ? '&category='.urlencode($category) : '' ?>"
style="<?= $subcat===$pt['type_id'] ? 'background:white;color:var(--color-secondary);font-weight:700;' : 'background:rgba(255,255,255,0.15);color:white;' ?> padding:0.2rem 0.65rem; border-radius:999px; font-size:0.8rem; text-decoration:none; border:1px solid rgba(255,255,255,0.4);">
<?= htmlspecialchars($pt['name']) ?>
</a>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</section>
<section class="section" style="padding-top: 1.5rem;">
<div class="container">
<!-- 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 -->
<?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'] ? ' &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>
</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'] ?>" onclick="addToCart('<?= $product['product_id'] ?>', 1)">
<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'; ?>