const { Button, Card, PillTag, TextLink } = window.WaraqEnabDesignSystem_f0e2a0;
const { useState, useEffect } = React;
const REVIEW_CFG = window.WE_CONFIG || {};
const REVIEW_LOCAL = "we-reviews-v1";
/* Reviews live on the server. Until the api folder is uploaded they are kept in
this browser so the flow can still be used and demonstrated. */
const reviews = {
readLocal() { try { return JSON.parse(localStorage.getItem(REVIEW_LOCAL)) || []; } catch (e) { return []; } },
writeLocal(list) { try { localStorage.setItem(REVIEW_LOCAL, JSON.stringify(list)); } catch (e) {} },
async list() {
if (REVIEW_CFG.reviewsEndpoint) {
try {
/* the server may not be uploaded yet — never let the page wait on it */
const ctrl = new AbortController();
const bail = setTimeout(() => ctrl.abort(), 4000);
const res = await fetch(REVIEW_CFG.reviewsEndpoint, { signal: ctrl.signal });
clearTimeout(bail);
const d = await res.json();
if (Array.isArray(d.reviews)) return d.reviews;
} catch (e) { /* fall through to the local copy */ }
}
return reviews.readLocal();
},
async add(review) {
const record = Object.assign({ id: String(Date.now()), at: new Date().toISOString() }, review);
if (REVIEW_CFG.reviewsEndpoint) {
try {
const ctrl = new AbortController();
const bail = setTimeout(() => ctrl.abort(), 8000);
const res = await fetch(REVIEW_CFG.reviewsEndpoint, {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify(Object.assign({ company: "" }, review)), signal: ctrl.signal
});
clearTimeout(bail);
const d = await res.json().catch(() => ({}));
if (res.ok && d.ok) return { ok: true };
if (d.error) return { error: d.error };
} catch (e) { /* fall through to the local copy */ }
}
const list = reviews.readLocal();
if (review.ref && list.some(r => r.ref === review.ref)) return { error: t("This order has already been reviewed.") };
reviews.writeLocal([record].concat(list));
return { ok: true };
},
/* has this device already reviewed this order? */
reviewed(ref) { return reviews.readLocal().some(r => r.ref === ref); }
};
/* Five stars. Read-only unless onChange is given. */
function Stars({ value, onChange, size = 22, label }) {
const [hover, setHover] = useState(0);
const shown = hover || value || 0;
if (!onChange) {
return (
{[1, 2, 3, 4, 5].map(n => ★)}
);
}
return (
{[1, 2, 3, 4, 5].map(n => (
))}
);
}
function ratingSummary(list) {
if (!list.length) return { avg: 0, count: 0 };
const sum = list.reduce((n, r) => n + (Number(r.rating) || 0), 0);
return { avg: Math.round((sum / list.length) * 10) / 10, count: list.length };
}
function useReviews(dishId) {
const pick = (all) => dishId ? all.filter(r => (r.dishes || []).includes(dishId)) : all;
const [list, setList] = useState(() => pick(reviews.readLocal()));
useEffect(() => {
let stop = false;
reviews.list().then(all => { if (!stop) setList(pick(all)); });
return () => { stop = true; };
}, [dishId]);
return list;
}
function ReviewCard({ review }) {
const when = new Date(review.at);
const named = (review.dishes || []).map(id => {
let hit = null;
MENU.forEach(s => s.items.forEach(i => { if (i.id === id) hit = i; }));
return hit;
}).filter(Boolean);
return (
{review.comment} {t("It helps the kitchen and it helps the next customer choose.")} {t("Your review appears on the dish pages for other customers to read.")} {err}{t("Thank you for the review.")}
{t("How was the food?")}