guides
Feedback Mobile Integration
How to integrate the Feedback Service into React Native (Expo) mobile apps. Reviews, ratings, favorites, and feature requests.
feedbackmobilereact-nativeexpointegration
Feedback Mobile Integration
This guide shows how to integrate the Feedback Service into React Native (Expo) mobile apps like Forsa.
Prerequisites
- Access token from zv-flex-auth-service
- Project ID (default:
11) - Feedback API base URL:
https://test.zonevast.com/feedback/api/v1
API Client Setup
// src/services/feedback/api.ts
const FEEDBACK_BASE = 'https://test.zonevast.com/feedback/api/v1';
async function feedbackFetch(path: string, options: RequestInit = {}, token?: string) {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'X-Project-ID': '11',
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const res = await fetch(`${FEEDBACK_BASE}${path}`, { ...options, headers });
return res.json();
}
Star Rating Component
// src/components/StarRating.tsx
import React from 'react';
import { View, TouchableOpacity } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
interface StarRatingProps {
rating: number;
size?: number;
interactive?: boolean;
onRate?: (rating: number) => void;
}
export function StarRating({ rating, size = 24, interactive = false, onRate }: StarRatingProps) {
return (
<View style={{ flexDirection: 'row', gap: 2 }}>
{[1, 2, 3, 4, 5].map(star => (
<TouchableOpacity
key={star}
disabled={!interactive}
onPress={() => onRate?.(star)}
>
<Ionicons
name={star <= rating ? 'star' : 'star-outline'}
size={size}
color={star <= rating ? '#FFC000' : '#666'}
/>
</TouchableOpacity>
))}
</View>
);
}
Review Bottom Sheet
// src/components/ReviewBottomSheet.tsx
import React, { useState } from 'react';
import { View, TextInput, TouchableOpacity, Text } from 'react-native';
interface ReviewBottomSheetProps {
itemId: number;
itemType: string;
token: string;
onSubmit: () => void;
}
export function ReviewBottomSheet({ itemId, itemType, token, onSubmit }: ReviewBottomSheetProps) {
const [rating, setRating] = useState(0);
const [comment, setComment] = useState('');
const submit = async () => {
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, comment }),
});
onSubmit();
};
return (
<View style={{ padding: 16 }}>
<Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 12 }}>Write a Review</Text>
<StarRating rating={rating} size={36} interactive onRate={setRating} />
<TextInput
multiline
placeholder="Share your experience..."
value={comment}
onChangeText={setComment}
style={{ borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 12, marginTop: 12, minHeight: 100 }}
/>
<TouchableOpacity
onPress={submit}
disabled={rating === 0}
style={{ backgroundColor: rating > 0 ? '#FFC000' : '#ccc', padding: 14, borderRadius: 8, marginTop: 12, alignItems: 'center' }}
>
<Text style={{ fontWeight: '600' }}>Submit Review</Text>
</TouchableOpacity>
</View>
);
}
Favorites Toggle (using existing pattern)
The Forsa app already has a favorites system. To integrate with the new feedback service:
// The existing toggle pattern works the same way
async function toggleFavorite(itemId: number, itemType: string, token: string) {
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();
return data.isFavorite;
}
Fetch Reviews for Product Detail
async function getItemReviews(itemType: string, itemId: number, page = 1) {
const res = await fetch(
`https://test.zonevast.com/feedback/api/v1/reviews?itemType=${itemType}&itemId=${itemId}&page=${page}&limit=10`
);
return res.json(); // { success, data: Review[], pagination }
}
async function getItemRatingStats(itemType: string, itemId: number) {
const res = await fetch(
`https://test.zonevast.com/feedback/api/v1/reviews/stats/${itemType}/${itemId}`
);
return res.json(); // { data: { averageRating, totalReviews, distribution } }
}
Feature Requests List
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();
}
async function voteOnPost(postId: number, token: string) {
const res = await fetch(`https://test.zonevast.com/feedback/api/v1/posts/${postId}/vote`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'X-Project-ID': '11',
},
});
return res.json();
}
TypeScript Types
interface Review {
id: number;
itemId: number;
itemType: string;
userId: string;
rating: number;
title?: string;
comment?: string;
status: 'pending' | 'approved' | 'rejected';
createdAt: string;
}
interface ReviewStats {
averageRating: number;
totalReviews: number;
distribution: { 1: number; 2: number; 3: number; 4: number; 5: 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;
}
Guest Review Bottom Sheet (Email-only)
Allow unauthenticated users to submit reviews from the mobile app using only their email.
// src/components/GuestReviewBottomSheet.tsx
import React, { useState } from 'react';
import { View, TextInput, TouchableOpacity, Text } from 'react-native';
interface GuestReviewBottomSheetProps {
itemId: number;
itemType: string;
onSubmit: () => void;
}
export function GuestReviewBottomSheet({ itemId, itemType, onSubmit }: GuestReviewBottomSheetProps) {
const [guestEmail, setGuestEmail] = useState('');
const [guestName, setGuestName] = useState('');
const [rating, setRating] = useState(0);
const [comment, setComment] = useState('');
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 }),
});
onSubmit();
};
return (
<View style={{ padding: 16 }}>
<Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 12 }}>Write a Review</Text>
<TextInput
keyboardType="email-address"
autoCapitalize="none"
placeholder="Email address *"
value={guestEmail}
onChangeText={setGuestEmail}
style={{ borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 12, marginBottom: 8 }}
/>
<TextInput
placeholder="Name (optional)"
value={guestName}
onChangeText={setGuestName}
style={{ borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 12, marginBottom: 8 }}
/>
<View style={{ flexDirection: 'row', gap: 4, marginBottom: 8 }}>
{[1, 2, 3, 4, 5].map((star) => (
<TouchableOpacity key={star} onPress={() => setRating(star)}>
<Ionicons
name={star <= rating ? 'star' : 'star-outline'}
size={32}
color={star <= rating ? '#FFC000' : '#666'}
/>
</TouchableOpacity>
))}
</View>
<TextInput
multiline
placeholder="Share your experience..."
value={comment}
onChangeText={setComment}
style={{ borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 12, minHeight: 80, marginBottom: 12 }}
/>
<TouchableOpacity
onPress={submit}
disabled={rating === 0 || !guestEmail}
style={{ backgroundColor: rating > 0 && guestEmail ? '#FFC000' : '#ccc', padding: 14, borderRadius: 8, alignItems: 'center' }}
>
<Text style={{ fontWeight: '600' }}>Submit Review</Text>
</TouchableOpacity>
<Text style={{ fontSize: 11, color: '#888', marginTop: 8, textAlign: 'center' }}>
Guest reviews are pending admin approval.
</Text>
</View>
);
}
Key points:
- No auth token required
guestEmailis required- Reviews will have
status: 'pending'until an admin approves them
What's Next
- Feedback Service Overview - Full API reference
- Web Integration Guide - Next.js integration
Last validated: 2026-05-09