Review Widget

Customize and embed your reviews widget on your website

Widget Title
Review Filters
Keyword Filter
Only show reviews that mention these keywords (great for topic-specific pages). Leave empty to show all.
Display Options
Live Preview
This is how your widget will look on your website

What Our Customers Say

Y

0 reviews

No reviews match your filters yet.
Embed Code
Copy and paste this snippet into your website's HTML. Make sure your app is published first.

Endpoint: https://app.base44.app/api/apps/6a184a632e40e6e6306fd741/functions/getWidgetData

⚠️ Publish your app first — the widget only works on the published domain.

<!-- Reviews Widget -->
<div id="reviews-widget-undefined"></div>
<script>
(function() {
  fetch('https://app.base44.app/api/apps/6a184a632e40e6e6306fd741/functions/getWidgetData', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ client_id: 'undefined' })
  })
  .then(function(r) { return r.json(); })
  .then(function(data) {
    var cfg = data.config || {};
    var reviews = data.reviews || [];
    var appOrigin = 'https://app.base44.app/api/apps/6a184a632e40e6e6306fd741/functions/getWidgetData'.split('/api/')[0];
    var reviewUrl = data.review_url || (data.funnel_slug ? (appOrigin + '/review/' + data.funnel_slug) : '#');
    var bg = cfg.background_color || '#ffffff';
    var txt = cfg.text_color || '#111827';
    var star = cfg.star_color || '#F59E0B';
    var radius = (cfg.border_radius || 12) + 'px';
    var layout = cfg.layout || 'grid';

    function stars(n) {
      var s = '';
      for (var i = 1; i <= 5; i++) {
        s += '<span style="color:' + (i <= n ? star : '#d1d5db') + ';font-size:16px;">&#9733;</span>';
      }
      return s;
    }

    function altText(r) {
      var nm = r.reviewer_name || 'Anonymous';
      var platMap = { google: 'Google', yelp: 'Yelp', facebook: 'Facebook', internal: 'Direct' };
      var plat = platMap[r.platform] || (r.platform ? r.platform.charAt(0).toUpperCase() + r.platform.slice(1) : '');
      var rt = r.rating ? r.rating + '-star' : '';
      var snip = (r.review_text || '').replace(/s+/g, ' ').trim().slice(0, 120).replace(/"/g, '&quot;');
      var parts = [nm, rt, plat ? plat + ' review' : 'review'];
      if (snip) parts.push('&quot;' + snip + '&quot;');
      return parts.join(' - ');
    }
    function avatar(r) {
      var nm = r.reviewer_name || 'Anonymous';
      var sp = nm.indexOf(' ');
      var ini = (nm[0] + (sp > 0 ? nm[sp + 1] : '')).toUpperCase();
      var palette = ['#4285F4','#34A853','#EA4335','#FBBC05','#8B5CF6','#2563EB'];
      var h = 0; for (var i = 0; i < nm.length; i++) h = (h * 31 + nm.charCodeAt(i)) >>> 0;
      var bgc = palette[h % palette.length];
      return r.reviewer_photo_url
        ? '<img src="' + r.reviewer_photo_url + '" alt="' + altText(r) + '" style="width:40px;height:40px;border-radius:50%;object-fit:cover;flex-shrink:0;">'
        : '<div style="width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:600;font-size:14px;background:' + bgc + ';flex-shrink:0;">' + ini + '</div>';
    }
    function card(r) {
      var date = r.review_date ? new Date(r.review_date).toLocaleDateString('en-US', { month: 'short', year: 'numeric' }) : '';
      var platformColors = { google: '#4285F4', yelp: '#D32323', facebook: '#1877F2', internal: '#8B5CF6' };
      var pColor = platformColors[r.platform] || '#888';
      return '<div style="background:#fff;border:1px solid #e5e7eb;border-radius:' + radius + ';padding:20px;box-sizing:border-box;">' +
        '<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:10px;">' +
          '<div style="display:flex;gap:10px;align-items:center;">' + avatar(r) + '<div>' + stars(r.rating) + (cfg.show_reviewer_name !== false && r.reviewer_name ? '<p style="margin:4px 0 0;font-size:13px;font-weight:600;color:' + txt + ';">' + r.reviewer_name + '</p>' : '') + '</div></div>' +
          (cfg.show_platform_badge !== false && r.platform ? '<span style="font-size:11px;padding:2px 8px;border-radius:20px;background:' + pColor + '20;color:' + pColor + ';">' + r.platform.charAt(0).toUpperCase() + r.platform.slice(1) + '</span>' : '') +
        '</div>' +
        (r.review_text ? '<p style="margin:0 0 8px;font-size:13px;line-height:1.5;color:' + txt + 'cc;">' + r.review_text.substring(0, 200) + (r.review_text.length > 200 ? '...' : '') + '</p>' : '') +
        (cfg.show_review_date !== false && date ? '<p style="margin:0;font-size:11px;color:' + txt + '55;">' + date + '</p>' : '') +
      '</div>';
    }

    var gSvg = '<svg width="18" height="18" viewBox="0 0 48 48"><path fill="#4285F4" d="M45 24c0-1.6-.1-3.1-.4-4.5H24v9h11.8c-.5 2.8-2.1 5.1-4.4 6.6v5.5h7.1c4.2-3.8 6.5-9.5 6.5-16.1z"/><path fill="#34A853" d="M24 46c5.9 0 10.9-2 14.5-5.3l-7.1-5.5c-2 1.3-4.5 2.1-7.4 2.1-5.7 0-10.5-3.9-12.2-9H4.3v5.7C7.9 41 15.4 46 24 46z"/><path fill="#FBBC05" d="M11.8 28.3c-.4-1.3-.7-2.7-.7-4.3s.3-3 .7-4.3v-5.7H4.3C2.8 17.5 2 20.6 2 24s.8 6.5 2.3 9.7l7.5-5.7z"/><path fill="#EA4335" d="M24 10.7c3.2 0 6.1 1.1 8.4 3.3l6.3-6.3C34.9 4.2 29.9 2 24 2 15.4 2 7.9 6.9 4.3 14l7.5 5.7c1.7-5.1 6.5-9 12.2-9z"/></svg>';
    var vCheck = '<svg width="16" height="16" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="#0d6efd"/><path d="M7 12.5l3 3 6-6" stroke="#fff" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>';
    function scCard(r) {
      var date = r.review_date ? new Date(r.review_date) : null;
      var ago = '';
      if (date) {
        var d = Math.floor((Date.now() - date.getTime()) / 86400000);
        if (d < 1) ago = 'recently';
        else if (d < 30) ago = d + ' day' + (d > 1 ? 's' : '') + ' ago';
        else if (d < 365) { var m = Math.floor(d / 30); ago = m + ' month' + (m > 1 ? 's' : '') + ' ago'; }
        else { var y = Math.floor(d / 365); ago = y + ' year' + (y > 1 ? 's' : '') + ' ago'; }
      }
      var nm = r.reviewer_name || 'Anonymous';
      var sp = nm.indexOf(' ');
      var ini = (nm[0] + (sp > 0 ? nm[sp + 1] : '')).toUpperCase();
      var palette = ['#4285F4','#34A853','#EA4335','#FBBC05','#8B5CF6','#2563EB'];
      var h = 0; for (var i = 0; i < nm.length; i++) h = (h * 31 + nm.charCodeAt(i)) >>> 0;
      var bg = palette[h % palette.length];
      var photo = r.reviewer_photo_url
        ? '<img src="' + r.reviewer_photo_url + '" alt="' + altText(r) + '" style="width:44px;height:44px;border-radius:50%;object-fit:cover;">'
        : '<div style="width:44px;height:44px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:600;background:' + bg + ';">' + ini + '</div>';
      var text = r.review_text || '';
      return '<div style="background:#fff;border:1px solid #e5e7eb;border-radius:' + radius + ';padding:20px;box-sizing:border-box;display:flex;flex-direction:column;height:100%;">'
        + '<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:12px;">'
        + '<div style="display:flex;gap:12px;align-items:center;">' + photo + '<div><div style="font-size:14px;font-weight:600;color:#212529;line-height:1.2;">' + nm + '</div><div style="font-size:12px;color:#6c757d;margin-top:2px;">' + ago + '</div></div></div>'
        + (r.platform === 'google' ? gSvg : '')
        + '</div>'
        + '<div style="display:flex;align-items:center;gap:6px;margin-bottom:8px;">' + stars(r.rating) + vCheck + '</div>'
        + '<div class="rw-clamp" style="font-size:14px;line-height:1.5;color:#212529;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;">' + text + '</div>'
        + (text.length > 180 ? '<button type="button" class="rw-more" style="align-self:flex-start;margin-top:4px;background:none;border:none;color:#0d6efd;font-size:14px;cursor:pointer;padding:0;font-family:inherit;">Show more</button>' : '')
        + '</div>';
    }
    var accent = cfg.primary_color || '#2563eb';
    var html = '<div style="font-family:system-ui,sans-serif;background:' + bg + ';padding:24px;box-sizing:border-box;">';
    if (cfg.show_title !== false && cfg.title && layout !== 'carousel_auto') {
      html += '<h2 style="text-align:center;font-size:22px;font-weight:700;margin:0 0 20px;color:' + txt + ';">' + cfg.title + '</h2>';
    }
    if (layout === 'list') {
      html += '<div style="display:flex;flex-direction:column;gap:16px;">' + reviews.map(card).join('') + '</div>';
    } else if (layout === 'badge') {
      html += '<div style="display:inline-flex;align-items:center;gap:12px;border:1px solid #e5e7eb;border-radius:' + radius + ';padding:12px 20px;background:#fff;">' +
        '<span style="font-size:28px;font-weight:700;color:' + txt + ';">' + (data.avg_rating || '—') + '</span>' +
        '<div>' + stars(Math.round(data.avg_rating || 0)) + '<div style="font-size:12px;color:#6b7280;">' + (data.total_reviews || 0) + ' reviews</div></div>' +
        '</div>';
    } else if (layout === 'carousel') {
      html += '<div style="display:flex;gap:16px;overflow-x:auto;padding-bottom:8px;scroll-snap-type:x mandatory;">' +
        reviews.map(function(r){ return '<div style="min-width:280px;scroll-snap-align:start;">' + card(r) + '</div>'; }).join('') +
        '</div>';
    } else if (layout === 'carousel_auto') {
      html += '<div style="position:relative;overflow:hidden;">' +
        '<div id="rw-track-undefined" style="display:flex;transition:transform 0.5s ease;">' +
          reviews.map(function(r){ return '<div style="min-width:100%;box-sizing:border-box;padding:0 4px;">' + card(r) + '</div>'; }).join('') +
        '</div>' +
        '</div>';
    } else if (layout === 'summary_carousel') {
      var rated = reviews.filter(function(r){ return r.rating; });
      var avg = rated.length ? (rated.reduce(function(a,b){return a+b.rating;},0)/rated.length).toFixed(1) : '—';
      var logoHtml = data.logo_url
        ? '<img src="' + data.logo_url + '" style="width:64px;height:64px;border-radius:50%;object-fit:cover;margin-bottom:12px;">'
        : '<div style="width:64px;height:64px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-size:18px;font-weight:700;background:' + (cfg.primary_color||'#4285F4') + ';margin-bottom:12px;">' + (data.business_name||'Y').charAt(0).toUpperCase() + '</div>';
      var summaryCard = '<div style="background:#fff;border:1px solid #e5e7eb;border-radius:' + radius + ';padding:24px;box-sizing:border-box;display:flex;flex-direction:column;align-items:center;text-align:center;min-width:220px;">'
        + logoHtml
        + '<div style="font-size:16px;font-weight:700;color:#212529;margin-bottom:8px;">' + (data.business_name||'') + '</div>'
        + '<div style="display:flex;align-items:center;gap:6px;margin-bottom:4px;"><span style="font-size:24px;font-weight:700;color:' + star + ';">' + avg + '</span>' + stars(Math.round(avg==='—'?0:avg)) + '</div>'
        + '<div style="display:flex;align-items:center;gap:6px;font-size:14px;color:#212529;margin-bottom:16px;"><span>' + (data.total_reviews||rated.length) + ' reviews on</span>' + gSvg + '</div>'
        + '<a href="' + reviewUrl + '" target="_blank" rel="noopener noreferrer" style="display:block;width:100%;padding:10px;text-align:center;font-size:12px;font-weight:600;letter-spacing:0.5px;color:#212529;border:1px solid #dee2e6;border-radius:6px;background:#fff;text-decoration:none;">LEAVE A REVIEW</a>'
        + '</div>';
      var perPage = 3;
      var pages = [];
      for (var pi = 0; pi < reviews.length; pi += perPage) pages.push(reviews.slice(pi, pi + perPage));
      var pagesHtml = pages.map(function(pg, idx) {
        return '<div class="rw-page" data-idx="' + idx + '" style="display:' + (idx === 0 ? 'grid' : 'none') + ';grid-template-columns:repeat(3,1fr);grid-auto-rows:1fr;gap:16px;height:100%;">'
          + pg.map(scCard).join('')
          + '</div>';
      }).join('');
      html += '<div style="display:flex;gap:20px;align-items:stretch;">'
        + '<div style="flex:0 0 auto;">' + summaryCard + '</div>'
        + '<div id="rw-pages-undefined" style="position:relative;flex:1;min-width:0;">'
        + pagesHtml
        + (pages.length > 1
          ? '<button id="rw-prev-undefined" type="button" style="position:absolute;left:0;top:50%;transform:translate(-8px,-50%);width:36px;height:36px;border-radius:50%;background:#fff;border:1px solid #dee2e6;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 1px 3px rgba(0,0,0,0.1);font-size:18px;color:#6c757d;">&#8249;</button>'
            + '<button id="rw-next-undefined" type="button" style="position:absolute;right:0;top:50%;transform:translate(8px,-50%);width:36px;height:36px;border-radius:50%;background:#fff;border:1px solid #dee2e6;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 1px 3px rgba(0,0,0,0.1);font-size:18px;color:#6c757d;">&#8250;</button>'
          : '')
        + '</div>'
        + '</div>';
        } else {
        var locs = data.locations || [];
        var pubOrigin = data.public_app_url || appOrigin;
        function locUrl(id) {
          var slug;
          if (id === 'all') slug = data.funnel_slug;
          else { var l = locs.find(function(x){return x.id===id;}); slug = l ? l.funnel_slug : data.funnel_slug; }
          if (slug) return (pubOrigin || appOrigin) + '/review/' + slug;
          return '#';
        }
        function locStats(id) {
          if (id === 'all') return { avg: data.avg_rating, count: data.total_reviews };
          var l = locs.find(function(x){return x.id===id;});
          return l ? { avg: l.avg_rating, count: l.review_count } : { avg: null, count: 0 };
        }
        var gridSummary = '<div style="background:#fff;border:1px solid #e5e7eb;border-radius:' + radius + ';padding:20px;box-sizing:border-box;display:flex;flex-direction:column;align-items:center;text-align:center;justify-content:center;">'
          + (data.logo_url ? '<img src="' + data.logo_url + '" style="width:48px;height:48px;border-radius:50%;object-fit:cover;margin-bottom:8px;">' : '<div style="width:48px;height:48px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-size:18px;font-weight:700;background:' + (cfg.primary_color||'#4285F4') + ';margin-bottom:8px;">' + (data.business_name||'Y').charAt(0).toUpperCase() + '</div>')
          + '<div style="font-size:14px;font-weight:700;color:#212529;margin-bottom:6px;">' + (data.business_name||'') + '</div>'
          + '<div id="rw-gavg-undefined" style="display:flex;align-items:center;gap:6px;margin-bottom:4px;"><span style="font-size:20px;font-weight:700;color:' + star + ';">' + (data.avg_rating||'—') + '</span>' + stars(Math.round(data.avg_rating||0)) + '</div>'
          + '<div id="rw-gcount-undefined" style="font-size:12px;color:#6c757d;margin-bottom:12px;">' + (data.total_reviews||0) + ' reviews</div>'
          + '<a id="rw-glink-undefined" href="' + locUrl('all') + '" target="_blank" rel="noopener noreferrer" style="display:block;width:100%;padding:8px;text-align:center;font-size:11px;font-weight:600;letter-spacing:0.5px;color:#212529;border:1px solid #dee2e6;border-radius:6px;background:#fff;text-decoration:none;">LEAVE A REVIEW</a>'
          + '</div>';
        var locFilter = locs.length > 1
          ? '<div style="margin-bottom:16px;"><select id="rw-gloc-undefined" style="width:100%;padding:8px 10px;font-size:13px;border:1px solid #dee2e6;border-radius:6px;background:#fff;color:#212529;"><option value="all">All Locations</option>' + locs.map(function(l){ return '<option value="' + l.id + '">' + l.name + '</option>'; }).join('') + '</select></div>'
          : '';
        function buildCards(arr) {
          var g = (arr.length > 0 && arr.length % 2 === 0) ? arr.slice(0, -1) : arr;
          if (!g.length) return '<div style="grid-column:2;display:flex;align-items:center;justify-content:center;color:#6c757d;font-size:13px;padding:20px;">No reviews match your filters yet.</div>';
          return g.map(function(r){ return '<div class="rw-gcard" data-loc="' + (r.location_id||'') + '">' + card(r) + '</div>'; }).join('');
        }
        var cardsHtml = buildCards(reviews);
        html += locFilter + '<div style="display:grid;grid-template-columns:repeat(2,1fr);gap:16px;">' + gridSummary + '<div id="rw-gcards-undefined" style="display:contents;">' + cardsHtml + '</div>' + '</div>';
        }
        html += '<div style="text-align:center;padding:16px 0 4px;"><a href="https://yeloreputation.com" target="_blank" rel="noopener noreferrer" style="font-size:12px;color:' + txt + '44;text-decoration:none;">Powered by Yelo</a></div>';
        html += '</div>';
    document.getElementById('reviews-widget-undefined').innerHTML = html;

    if (layout === 'carousel_auto' && reviews.length > 1) {
      var idx = 0;
      var track = document.getElementById('rw-track-undefined');
      setInterval(function() {
        idx = (idx + 1) % reviews.length;
        track.style.transform = 'translateX(-' + (idx * 100) + '%)';
      }, 4000);
    }

    if (layout === 'summary_carousel') {
      var pagesWrap = document.getElementById('rw-pages-undefined');
      var prevBtn = document.getElementById('rw-prev-undefined');
      var nextBtn = document.getElementById('rw-next-undefined');
      if (pagesWrap) {
        var pageEls = pagesWrap.querySelectorAll('.rw-page');
        var curPage = 0;
        function showPage(n) {
          pageEls.forEach(function(el, i) { el.style.display = (i === n) ? 'grid' : 'none'; });
        }
        if (prevBtn) prevBtn.addEventListener('click', function(){ curPage = (curPage - 1 + pageEls.length) % pageEls.length; showPage(curPage); });
        if (nextBtn) nextBtn.addEventListener('click', function(){ curPage = (curPage + 1) % pageEls.length; showPage(curPage); });
        pagesWrap.querySelectorAll('.rw-more').forEach(function(btn){
          btn.addEventListener('click', function(){
            var body = btn.previousElementSibling;
            var open = body.style.webkitLineClamp === 'none';
            body.style.webkitLineClamp = open ? '3' : 'none';
            btn.textContent = open ? 'Show more' : 'Show less';
          });
        });
      }
    }

    var glocSel = document.getElementById('rw-gloc-undefined');
    if (glocSel) {
      glocSel.addEventListener('change', function() {
        var id = glocSel.value;
        var set = (id === 'all') ? reviews : ((data.reviews_by_location && data.reviews_by_location[id]) || []);
        var gcardsEl = document.getElementById('rw-gcards-undefined');
        if (gcardsEl) gcardsEl.innerHTML = buildCards(set);
        var stats = locStats(id);
        var avgEl = document.getElementById('rw-gavg-undefined');
        var cntEl = document.getElementById('rw-gcount-undefined');
        var lnkEl = document.getElementById('rw-glink-undefined');
        if (avgEl) avgEl.innerHTML = '<span style="font-size:20px;font-weight:700;color:' + star + ';">' + (stats.avg||'—') + '</span>' + stars(Math.round(stats.avg||0));
        if (cntEl) cntEl.textContent = (stats.count||0) + ' reviews';
        if (lnkEl) lnkEl.href = locUrl(id);
      });
    }
  })
  .catch(function(e) { console.error('Widget error:', e); });
})();
</script>