Files
myron 5637b6d7f5 CSS modularization Phase 2: account, cart, checkout
Extract account/cart/checkout styles into dedicated CSS files; remove inline styles and orphaned style blocks from HTML. Wire $extraHead on all account pages, cart.php, and checkout.php.

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

291 lines
13 KiB
PHP

<?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.product_id as product_pid, 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.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']]
);
$extraHead = '<link rel="stylesheet" href="/assets/css/account.css?v='. filemtime(__DIR__ . '/../assets/css/account.css') .'">';
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?id=<?= htmlspecialchars($review['product_pid']) ?>" 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 = $review['is_approved'] ? ['success', 'Published'] : ['warning', 'Pending'];
?>
<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['comment'] ?? '')) ?>
</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')">&times;</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'; ?>