Headless kullanım
MarkdownYorum özetinin ve listesinin kendi tasarımınızla gösterilmesi; yorum yazma akışının Sayproof'a bırakılması.
Yorum bölümünün görünümünü tamamen kendiniz belirlemek istiyorsanız veriyi Sayproof’tan alır; özeti, listeyi, sıralamayı ve filtreleri kendi bileşenlerinizle gösterirsiniz. Bu kullanım biçimine headless denir. Yöntem framework’ten bağımsızdır: düz JavaScript, React, Vue, Svelte ya da sunucu tarafında çalışan herhangi bir template engine ile uygulanabilir.
Görev dağılımı:
| Marka tarafında | Sayproof tarafında |
|---|---|
| Özet, liste, yorum kartı, sıralama, filtreler, sayfalama, oy ve bildirim düğmeleri | Sipariş doğrulaması, yorum formu, fotoğraf ve video yükleme, davet ve düzeltme bağlantıları |
Yorum yazma akışı script’te kalır. Böylece doğrulama kuralları, onay metinleri ve kötüye kullanım korumaları her markada aynı biçimde çalışır ve formun sipariş doğrulanmadan açılmaması güvence altına alınır.
Container ve script
Bölüm başlığı “Container ve script”Container’a data-review-mode="headless" attribute’unu ekleyin. Script bu container’da özet, liste ve sıralama oluşturmaz; yalnızca yorum yazma akışını yürütür ve davet bağlantılarını işler.
<div id="reviews" data-review-mount data-review-mode="headless" data-review-product="URUN-KODU" data-review-scope="variant" data-review-locale="tr"></div>
<script type="module" src="https://cdn.sayproof.example/sayproof.js" data-review-site-key="SITE-ANAHTARINIZ" data-review-base="https://api.sayproof.example" data-review-locale="tr"></script>Script bu container’ın içine herhangi bir içerik eklemez; container’ın sayfada bulunması yeterlidir. Yorum formu ve sipariş doğrulaması sayfanın üzerinde açılan bir dialog içinde gösterilir.
Verinin alınması
Bölüm başlığı “Verinin alınması”Veri iki kaynaktan alınabilir; iki kaynağın alanları aynıdır:
- Sunucuda: SSR yanıtındaki
statealanı. Liste sunucuda oluşturulur ve arama motorları metni ilk yanıtta görür. Aynı yanıttakistate_scriptetiketini container’ın doğrudan alt öğesi olarak ekleyin; script arayüz metinlerini ve zorunlu öğeleri ağ isteği yapmadan buradan alır. - Tarayıcıda:
GET /v1/reviews/listendpoint’i. Site anahtarı URL’de gönderilir ve özel bir header kullanılmaz; bu nedenle tarayıcı preflight isteği yapmaz.
const base = "https://api.sayproof.example";const siteKey = "SITE-ANAHTARINIZ";
export async function listReviews({ product, scope = "variant", lang = "tr", sort, page = 1, filters = {} }) { const query = new URLSearchParams({ key: siteKey, product, scope, lang, page: String(page) }); if (sort) query.set("sort", sort); if (filters.rating) query.set("rating", String(filters.rating)); if (filters.verified) query.set("verified", "1"); if (filters.media) query.set("media", "1"); if (filters.q) query.set("q", filters.q); if (filters.topic) query.set("topic", filters.topic); const answer = await fetch(`${base}/v1/reviews/list?${query}`, { credentials: "omit" }); if (!answer.ok) throw new Error(`sayproof_list_${answer.status}`); return answer.json();}Yanıtta kullanacağınız başlıca alanlar:
| Alan | İçerik |
|---|---|
aggregate |
count, average, stars_filled (dolu yıldız sayısı; yarım değerler yukarı yuvarlanır), distribution, verified_count, imported_count, imported_label, structured_count, structured_average (son ikisi aktarılmış yorumlar hariç sayı ve ortalamadır; yapılandırılmış veri bunlardan hesaplanır) |
reviews[] |
id, rating, title, body, author, published_at, verified, helpful_count, unhelpful_count, aspects, media[] (url, srcset, width, height, alt_text, kind, poster_url), seller_response, resolution, origin, experience (process ise yorumu words.experience_process etiketiyle gösterin) |
sorts |
İzin verilen sıralama seçenekleri ve etiketleri; sıralama menünüz yalnızca bu seçenekleri sunmalıdır |
aspects, topics |
Değerlendirme başlıklarının ortalamaları ve yorumlarda en sık geçen kelimeler |
gallery, media_reviews |
Ürünün yorumlarındaki en yeni fotoğraf ve videolar (her biri yorumun puanı ve yazarıyla) ve fotoğraflı ya da videolu yorum sayısı |
core |
Zorunlu öğeler: method_statement.url ve label, sort.allowed ve sort.default |
words |
İstenen dildeki arayüz metinleri: verified, seller_response, resolution, count, statement, search, filter_verified, filter_media, gallery, helpful_question, helpful_yes, helpful_no… |
report_reasons |
Bildirim gerekçeleri ve etiketleri |
page, pages, page_size, total |
Sayfalama bilgileri |
Sonraki sayfaları isterken yanıttaki page_size değerini geri gönderin. Tüm alanlar ve filtre kuralları için: Storefront API.
Oy ve bildirim
Bölüm başlığı “Oy ve bildirim”“Faydalı” oyu:
export async function vote(reviewId, direction, visitorKey) { const answer = await fetch(`${base}/v1/reviews/${encodeURIComponent(reviewId)}/helpful`, { method: "POST", credentials: "omit", headers: { "Content-Type": "application/json", "X-Sayproof-Site-Key": siteKey }, body: JSON.stringify({ vote: direction, visitor_key: visitorKey }), }); if (!answer.ok) throw new Error(`sayproof_vote_${answer.status}`); return answer.json();}votedeğeriup,downya danoneolabilir. Seçili yöne yeniden basıldığındanonegönderin; oy geri alınır.- Ekranda ve kendi kayıtlarınızda yanıttaki
vote,helpful_countveunhelpful_countdeğerlerini kullanın; sonucu tarayıcıda tahmin etmeyin. visitor_key, aynı ziyaretçinin oylarını tekilleştirmek için kullanılır; tarayıcıda saklanan rastgele bir değer yeterlidir.- Varsayılan bileşen ziyaretçinin oylarını
localStorageiçindesayproof-votesanahtarında tutar (yorum kimliği →up/down). Aynı anahtarı kullanırsanız iki arayüz aynı oy kaydını paylaşır.
Bildirim: POST /v1/reviews/<id>/report, body {"reason": "…", "note": "…"}. Gerekçe listesini report_reasons alanından alın; client tarafında sabit olarak tanımlamayın.
“Yorum yaz” düğmesi
Bölüm başlığı ““Yorum yaz” düğmesi”Düğmeniz window.Sayproof.write(container) metodunu çağırır. Sonuç:
| Müşteri durumu | Açılan içerik |
|---|---|
Oturum açmış müşteri yorum yazabilir (can_write) |
Yorum formu, sipariş numarası sorulmadan |
Bu ürün için zaten yorum yazmış (written) |
Hiçbir şey; metot false döndürür |
| Bilinmiyor ya da oturum yok | Sipariş numarası ve e-posta adresi istenir; koşullar sağlanırsa form aynı dialog içinde açılır |
Container hazır değilse (data-review-ready attribute’u yoksa) ya da başka bir dialog açıksa metot false döndürür. Oturum açmış müşteri için: Oturum açmış müşteri.
Gönderim başarılı olduğunda container, bubbling özellikli sayproof:submitted event’ini yayar. Yorum onaylandıktan sonra yayınlandığı için listeyi hemen yenilemeniz gerekmez.
const container = document.querySelector("#reviews");document.querySelector("#write-review").addEventListener("click", () => window.Sayproof?.write(container));container.addEventListener("sayproof:submitted", (event) => showThanks(event.detail.product));Örnek: düz JavaScript
Bölüm başlığı “Örnek: düz JavaScript”Özet, sıralama, liste ve zorunlu bağlantı. Metinler yanıttaki words alanından alınır.
function node(tag, text, attributes = {}) { const element = document.createElement(tag); if (text) element.textContent = text; for (const [name, value] of Object.entries(attributes)) element.setAttribute(name, value); return element;}
function fill(template, values) { return template.replace(/\{(\w+)\}/g, (_, name) => String(values[name] ?? ""));}
function renderReviews(root, answer, onSort) { const { aggregate, core, words, sorts } = answer;
const summary = node("p", `${aggregate.average} · ${fill(words.count, { count: aggregate.count })}`); const statement = node("a", core.method_statement.label, { href: core.method_statement.url, target: "_blank", rel: "noopener" });
const select = node("select", "", { "aria-label": words.sort }); for (const sort of sorts) { const option = node("option", sort.label, { value: sort.code }); if (sort.code === answer.sort) option.selected = true; select.append(option); } select.addEventListener("change", () => onSort(select.value));
const list = node("ol"); for (const review of answer.reviews) { const item = node("li", "", { "data-review-id": review.id }); item.append(node("strong", `${review.rating}/5`)); if (review.verified) item.append(node("span", words.verified)); if (review.title) item.append(node("h3", review.title)); if (review.body) item.append(node("p", review.body)); if (review.seller_response) item.append(node("blockquote", `${words.seller_response}: ${review.seller_response.body}`)); if (review.resolution) item.append(node("aside", `${words.resolution}: ${review.resolution.body}`)); list.append(item); }
root.replaceChildren(summary, select, list, statement);}Örnek: React
Bölüm başlığı “Örnek: React”import { useEffect, useState } from "react";import { listReviews } from "./sayproof";
export function Reviews({ product, locale }: { product: string; locale: string }) { const [answer, setAnswer] = useState(null); const [sort, setSort] = useState<string | undefined>();
useEffect(() => { let alive = true; listReviews({ product, lang: locale, sort }).then((next) => alive && setAnswer(next), () => alive && setAnswer(null)); return () => { alive = false; }; }, [product, locale, sort]);
if (!answer) return null; const { aggregate, core, words, sorts, reviews } = answer;
return ( <section> <p>{aggregate.average} · {words.count.replace("{count}", String(aggregate.count))}</p> <select aria-label={words.sort} value={sort ?? answer.sort} onChange={(event) => setSort(event.target.value)}> {sorts.map((one) => ( <option key={one.code} value={one.code}>{one.label}</option> ))} </select> <ol> {reviews.map((review) => ( <li key={review.id}> <strong>{review.rating}/5</strong> {review.verified ? <span>{words.verified}</span> : null} {review.title ? <h3>{review.title}</h3> : null} {review.body ? <p>{review.body}</p> : null} {review.seller_response ? <blockquote>{words.seller_response}: {review.seller_response.body}</blockquote> : null} {review.resolution ? <aside>{words.resolution}: {review.resolution.body}</aside> : null} </li> ))} </ol> <a href={core.method_statement.url} target="_blank" rel="noopener">{core.method_statement.label}</a> </section> );}İlk görüntüleme için SSR kullanıyorsanız answer başlangıç değerini sunucudan gelen state ile verin; sayfa ilk yanıtta dolu olarak gelir.
Görüntülenme sayımı
Bölüm başlığı “Görüntülenme sayımı”Script, headless kullanılan container’lar için de ürün görüntülenmelerini sayar; ek bir işlem gerekmez.