Product Page Reviews Overview (JudgeMe)
<div class="review-section">
<t4 class="review-heading">REAL TALK, REAL CUSTOMERS</t4>
<div class="rating-summary">
<t1 class="avg-rating"></t1> <!-- Injected dynamically -->
<div class="star-avg-rating">
<div class="star-group"></div> <!-- Injected dynamically -->
<t6 class="review-count"></t6>
</div>
</div>
<outlined-button class="write-review" onclick="writeAReview()">Write a review</outlined-button>
<div class="review-scroll-wrapper">
<div class="review-scroll" id="judgeReviewContainer"></div>
</div>
<filled-button class="view-all" onclick="viewAllReviews()">View all reviews</filled-button>
</div>.review-section {
padding: 16px 16px;
}
.review-heading {
font-weight: 700;
margin-bottom: 8px;
}
.review-scroll {
display: flex;
gap: 16px;
align-items: flex-start;
}
.review-product-name {
font-size: 16px;
line-height: 24px;
}
.product-title {
font-weight: 600;
}
.product-content {
margin-top: 10px;
}
.rating-summary {
display: flex;
gap: 15px;
flex-direction: row;
justify-content: start;
align-items: end;
}
.star-avg-rating {
display: flex;
flex-direction: column;
}
.avg-rating {
font-size: 40px;
font-weight: 400;
}
.star-group {
display: flex;
gap: 2px;
margin: 6px 0;
}
.star-group img {
width: 20px;
height: 20px;
}
.review-count {
font-size: 12px;
color: #555;
}
.write-review {
width: 100%;
margin-top: 12px;
text-align: center;
font-weight: 500;
padding: 10px;
margin-bottom: 24px;
}
.review-scroll-wrapper {
overflow-x: auto;
padding-bottom: 8px;
scrollbar-width: none;
scroll-behavior: smooth;
scroll-snap-type: x mandatory;
touch-action: pan-x;
-ms-touch-action: pan-x;
overscroll-behavior-x: contain;
height: auto;
}
.review-scroll-wrapper::-webkit-scrollbar {
display: none;
}
.review-scroll {
display: flex;
gap: 16px;
}
.review-card {
display: flex;
flex-direction: column;
flex: 0 0 80%;
border-radius: 14px;
padding: 16px;
border: 1px solid #EEEEEE;
height: auto;
background: #f3f3f3;
}
.review-card h4 {
margin: 0%;
font-size: 16px;
font-weight: 700;
margin-bottom: 6px;
}
.review-card p {
font-size: 14px;
color: #333;
margin: 0 0 12px;
}
.review-meta {
border-top: 1px solid #E3E3E3;
display: flex;
flex-direction: column;
justify-content: start;
align-items: start;
gap: 8px;
padding-top: 12px;
}
.reviewer-info {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
}
.initials {
width: 56px;
height: 56px;
border-radius: 50%;
background: #E3E3E3;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
color: #111;
font-size: 14px;
flex-shrink: 0;
/* ✅ Prevent shrinking inside flex container */
}
.meta-name-time {
display: flex;
flex-direction: column;
gap: 8px;
}
.meta-name {
font-weight: 600;
font-size: 14px;
white-space: nowrap;
}
.meta-time {
font-weight: 400;
font-size: 14px;
white-space: nowrap;
}
.meta-stars {
display: flex;
gap: 2px;
}
.meta-stars img {
width: 20px;
height: 20px;
}
.view-all {
display: none;
text-align: center;
font-size: 14px;
margin-top: 16px;
cursor: pointer;
}// 📦 Judge.me widget API configuration
// 🛠️ Configuration
const apiToken = {API_TOKEN};
const shopDomain = {SHOPIFY_DOMAIN} ;
const productId = VajroSDK.variables.product.id;
console.log(productId)
const reviewsApiUrl = `https://judge.me/api/v1/widgets/product_review?shop_domain=${shopDomain}&api_token=${apiToken}&external_id=${productId}&per_page=6`;
// 🌟 Star rendering helper
function generateStars(rating) {
console.log("rating");
const filledStar = "https://res.cloudinary.com/dixyq8hvr/image/upload/v1747398424/golden-star_jwk9ox.png";
const blankStar = "https://res.cloudinary.com/dixyq8hvr/image/upload/v1747398424/blank-star_mhyhcu.png";
let stars = "";
for (let i = 1; i <= 5; i++) {
const src = i <= rating ? filledStar : blankStar;
stars += `<img src="${src}" class="star-icon" alt="${i <= rating ? 'Filled' : 'Empty'} star"/>`;
}
return stars;
}
function formatDate(dateString) {
if (!dateString) return "";
// Convert "2025-06-09 08:20:17 UTC" → "2025-06-09T08:20:17Z"
const isoString = dateString.replace(" ", "T").replace(" UTC", "Z");
const date = new Date(isoString);
return isNaN(date) ? "" : date.toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
});
}
// 🔢 Format large review counts (e.g. 1.5k)
function formatCount(num) {
return num >= 1000 ? `${(num / 1000).toFixed(1)}k` : num.toString();
}
// 📊 Render summary: avg rating + stars + total count
function renderSummary(avgRating, totalReviews) {
document.querySelector('.avg-rating').textContent = avgRating.toFixed(1);
document.querySelector('.review-count').textContent = `${formatCount(totalReviews)} ratings`;
const starGroup = document.querySelector('.star-group');
if (starGroup) {
starGroup.innerHTML = "";
const roundedRating = avgRating % 1 >= 0.5 ? Math.ceil(avgRating) : Math.floor(avgRating);
for (let i = 1; i <= 5; i++) {
const img = document.createElement('img');
img.src = i <= roundedRating
? "https://res.cloudinary.com/dixyq8hvr/image/upload/v1747398424/golden-star_jwk9ox.png"
: "https://res.cloudinary.com/dixyq8hvr/image/upload/v1747398424/blank-star_mhyhcu.png";
img.className = 'star-icon';
img.alt = i <= roundedRating ? 'Filled star' : 'Empty star';
starGroup.appendChild(img);
}
}
}
// 💬 Render up to 5 reviews; show "View All" button if more
function renderReviews() {
fetch(reviewsApiUrl)
.then(res => res.json())
.then(data => {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.widget;
const summary = tempDiv.querySelector('.jdgm-rev-widg');
const avgRating = parseFloat(summary?.getAttribute('data-average-rating') || "0");
const totalReviews = parseInt(summary?.getAttribute('data-number-of-reviews') || "0");
renderSummary(avgRating, totalReviews);
const rawReviews = tempDiv.querySelectorAll('.jdgm-rev');
const reviews = Array.from(rawReviews).map(el => {
const name = el.querySelector('.jdgm-rev__author')?.textContent.trim() || "Anonymous";
return {
name,
initials: name.split(" ").map(n => n[0]).join("").slice(0, 2).toUpperCase(),
rating: parseInt(el.querySelector('.jdgm-rev__rating')?.getAttribute('data-score') || "0"),
content: el.querySelector('.jdgm-rev__body')?.textContent.trim() || "",
timestamp: formatDate(el.querySelector('.jdgm-rev__timestamp')?.getAttribute('data-content')),
productTitle: el.getAttribute('data-product-title') || ""
};
});
const container = document.getElementById('judgeReviewContainer');
const reviewScrollWrapper = document.querySelector('.review-scroll-wrapper');
const viewAllButton = document.querySelector('.view-all');
if (reviews.length > 0 && container && reviewScrollWrapper) {
reviewScrollWrapper.style.display = "block";
const visibleReviews = reviews.slice(0, 5);
container.innerHTML = visibleReviews.map(r => `
<div class="review-card">
<t4 class="product-title">${r.productTitle}</t4>
<t4 class="product-content">${r.content}</t4>
<div class="review-meta">
<div class="reviewer-info">
<div class="initials">${r.initials}</div>
<div class="meta-name-time">
<t4 class="meta-name">${r.name}</t4>
<t6 class="meta-time">${r.timestamp}</t6>
</div>
</div>
<div class="meta-stars">${generateStars(r.rating)}</div>
</div>
</div>
`).join('');
// Show "View All" button if more than 5 reviews
viewAllButton.style.display = reviews.length > 5 ? "block" : "none";
}
else {
document.querySelector(".review-count").style.display = "none"
document.querySelector(".avg-rating").style.display = "none"
document.querySelector(".write-review").style.marginBottom = "8px"
}
})
.catch(err => {
console.error("❌ Failed to load Judge.me reviews:", err);
});
}
// 🔗 Open full reviews screen via SDK
function viewAllReviews() {
VajroSDK.actions.openCustomBlock({
id: "cbk_0m0ymn6rsja38", // 👈 update with actual block ID
type: "full_screen",
params: { productId }
}).catch(err => {
VajroSDK.actions.showAlert({ title: "Error", message: "Could not open reviews." });
});
}
// ✍️ Trigger review form via SDK
function writeAReview() {
VajroSDK.actions.openCustomBlock({
id: "cbk_0m0zavn3sjj56", // 👈 update with actual review form block ID
type: "popup_bottom",
params: { productId }
}).catch(err => {
VajroSDK.actions.showAlert({ title: "Error", message: JSON.stringify(err) });
});
}
// 🚀 Init reviews on load
renderReviews();
window.writeAReview = writeAReview;
window.viewAllReviews = viewAllReviews;Updated 3 months ago