guides
Feedback Web Integration
How to integrate the Feedback Service into Next.js web applications. Reviews, ratings, favorites, feature requests, and comments.
feedbackwebintegrationnextjsreact
Feedback Web Integration
This guide shows how to integrate the Feedback Service into any Next.js web application in the ZoneVast ecosystem.
Prerequisites
- Access token from zv-flex-auth-service or zv-auth-service
- Project ID (default:
11) - Feedback API base URL:
https://test.zonevast.com/feedback/api/v1
Quick Start: Display Reviews
const FEEDBACK_BASE = 'https://test.zonevast.com/feedback/api/v1';
// Fetch reviews for an auction/product
async function getReviews(itemType: string, itemId: number) {
const res = await fetch(
`${FEEDBACK_BASE}/reviews?itemType=${itemType}&itemId=${itemId}&limit=10`
);
const { data, pagination } = await res.json();
return { reviews: data, pagination };
}
Star Rating Display Component
function StarRating({ rating, size = 20 }: { rating: number; size?: number }) {
return (
<div className="flex items-center gap-0.5">
{[1, 2, 3, 4, 5].map(star => (
<svg
key={star}
width={size}
height={size}
viewBox="0 0 24 24"
fill={star <= rating ? '#FFC000' : '#333'}
stroke={star <= rating ? '#FFC000' : '#555'}
strokeWidth={1}
>
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
</svg>
))}
</div>
);
}
Create Review Form
'use client';
import { useState } from 'react';
export function ReviewForm({ itemId, itemType }: { itemId: number; itemType: string }) {
const [rating, setRating] = useState(0);
const [title, setTitle] = useState('');
const [comment, setComment] = useState('');
const [submitting, setSubmitting] = useState(false);
const submit = async () => {
const token = getToken(); // From your auth system
setSubmitting(true);
await fetch('https://test.zonevast.com/feedback/api/v1/reviews', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
'X-Project-ID': '11',
},
body: JSON.stringify({ itemId, itemType, rating, title, comment }),
});
setSubmitting(false);
// Refresh reviews list
};
return (
<form onSubmit={e => { e.preventDefault(); submit(); }}>
<div className="flex gap-1 mb-3">
{[1,2,3,4,5].map(s => (
<button type="button" key={s} onClick={() => setRating(s)}>
<StarRating rating={s} size={32} />
</button>
))}
</div>
<input value={title} onChange={e => setTitle(e.target.value)} placeholder="Review title" />
<textarea value={comment} onChange={e => setComment(e.target.value)} placeholder="Write your review..." />
<button type="submit" disabled={submitting || rating === 0}>Submit Review</button>
</form>
);
}
Favorite Toggle Button
'use client';
import { useState, useEffect } from 'react';
export function FavoriteButton({ itemId, itemType }: { itemId: number; itemType: string }) {
const [isFavorite, setIsFavorite] = useState(false);
const toggle = async () => {
const token = getToken();
const res = await fetch('https://test.zonevast.com/feedback/api/v1/favorites/toggle', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
'X-Project-ID': '11',
},
body: JSON.stringify({ itemId, itemType }),
});
const { data } = await res.json();
setIsFavorite(data.isFavorite);
};
return (
<button onClick={toggle} className={isFavorite ? 'text-red-500' : 'text-gray-400'}>
{isFavorite ? '♥' : '♡'} Favorite
</button>
);
}
Feature Request Board
async function getFeatureRequests(sort: 'votes' | 'newest' = 'votes') {
const res = await fetch(
`https://test.zonevast.com/feedback/api/v1/posts?sort=${sort}&limit=20`
);
return res.json();
}
TypeScript Types
interface Review {
id: number;
itemId: number;
itemType: string;
userId: string;
rating: number;
title?: string;
comment?: string;
images?: string[];
status: 'pending' | 'approved' | 'rejected';
createdAt: string;
updatedAt: string;
}
interface ReviewStats {
itemType: string;
itemId: number;
averageRating: number;
totalReviews: number;
distribution: Record<number, number>;
}
interface FeedbackPost {
id: number;
title: string;
description: string;
category: 'bug' | 'feature' | 'improvement' | 'other';
status: 'under_review' | 'planned' | 'in_progress' | 'completed' | 'declined';
votesCount: number;
commentsCount: number;
createdAt: string;
}
interface Comment {
id: number;
entityType: string;
entityId: number;
parentId?: number;
content: string;
createdAt: string;
}
Guest Review Form (Email-only)
Allow unauthenticated visitors to submit reviews using only their email address. Guest reviews start as pending and require admin approval.
'use client';
import { useState } from 'react';
interface GuestReviewFormProps {
itemId: number;
itemType: string;
}
export function GuestReviewForm({ itemId, itemType }: GuestReviewFormProps) {
const [guestEmail, setGuestEmail] = useState('');
const [guestName, setGuestName] = useState('');
const [rating, setRating] = useState(0);
const [comment, setComment] = useState('');
const [submitted, setSubmitted] = useState(false);
const submit = async () => {
await fetch('https://test.zonevast.com/feedback/api/v1/reviews', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Project-ID': '11',
},
body: JSON.stringify({ itemId, itemType, rating, comment, guestEmail, guestName }),
});
setSubmitted(true);
};
if (submitted) {
return <p>Thank you! Your review is pending moderation.</p>;
}
return (
<form onSubmit={(e) => { e.preventDefault(); submit(); }}>
<input
type="email"
required
value={guestEmail}
onChange={(e) => setGuestEmail(e.target.value)}
placeholder="Your email *"
/>
<input
value={guestName}
onChange={(e) => setGuestName(e.target.value)}
placeholder="Your name (optional)"
/>
<div className="flex gap-1">
{[1, 2, 3, 4, 5].map((s) => (
<button type="button" key={s} onClick={() => setRating(s)}>
{s <= rating ? '★' : '☆'}
</button>
))}
</div>
<textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
placeholder="Write your review..."
/>
<button type="submit" disabled={rating === 0 || !guestEmail}>Submit Review</button>
</form>
);
}
Key points:
- No
Authorizationheader needed guestEmailis required,guestNameis optional- Response
statuswill bepending(notapproved) - Use
X-Project-IDheader for multi-tenant scoping
What's Next
- Feedback Service Overview - Full API reference
- Mobile Integration Guide - React Native integration
Last validated: 2026-05-09