QR-master/src/app/feedback/[slug]/page.tsx

205 lines
8.8 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import { useParams } from 'next/navigation';
import { Star, Send, Check } from 'lucide-react';
interface FeedbackData {
businessName: string;
googleReviewUrl?: string;
thankYouMessage?: string;
}
export default function FeedbackPage() {
const params = useParams();
const slug = params.slug as string;
const [feedback, setFeedback] = useState<FeedbackData | null>(null);
const [loading, setLoading] = useState(true);
const [rating, setRating] = useState(0);
const [hoverRating, setHoverRating] = useState(0);
const [comment, setComment] = useState('');
const [submitted, setSubmitted] = useState(false);
const [submitting, setSubmitting] = useState(false);
useEffect(() => {
async function fetchFeedback() {
try {
const res = await fetch(`/api/qrs/public/${slug}`);
if (res.ok) {
const data = await res.json();
if (data.contentType === 'FEEDBACK') {
setFeedback(data.content as FeedbackData);
}
}
} catch (error) {
console.error('Error fetching feedback data:', error);
} finally {
setLoading(false);
}
}
fetchFeedback();
}, [slug]);
const handleSubmit = async () => {
if (rating === 0) return;
setSubmitting(true);
try {
await fetch('/api/feedback', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ slug, rating, comment }),
});
setSubmitted(true);
if (rating >= 4 && feedback?.googleReviewUrl) {
setTimeout(() => {
const url = ensureAbsoluteUrl(feedback.googleReviewUrl);
if (url) window.location.href = url;
}, 2000);
}
} catch (error) {
console.error('Error submitting feedback:', error);
} finally {
setSubmitting(false);
}
};
// Loading
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-[#FAF8F5] via-[#C6C0B3] to-[#4C5F4E]">
<div className="w-10 h-10 border-3 border-indigo-200 border-t-indigo-600 rounded-full animate-spin"></div>
</div>
);
}
// Not found
if (!feedback) {
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-[#FAF8F5] via-[#C6C0B3] to-[#4C5F4E] px-6">
<div className="text-center bg-white rounded-2xl p-8 shadow-lg">
<p className="text-gray-500 text-lg">This feedback form is not available.</p>
</div>
</div>
);
}
// Success
if (submitted) {
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-[#FAF8F5] via-[#C6C0B3] to-[#4C5F4E] px-6">
<div className="max-w-sm w-full bg-white rounded-3xl shadow-xl p-10 text-center">
<div className="w-20 h-20 bg-gradient-to-br from-[#4C5F4E] to-[#FAF8F5] rounded-full flex items-center justify-center mx-auto mb-6 shadow-lg">
<Check className="w-10 h-10 text-white" strokeWidth={2.5} />
</div>
<h1 className="text-2xl font-bold text-gray-900 mb-2">
Thank you!
</h1>
<p className="text-gray-500">
{feedback.thankYouMessage || 'Your feedback has been submitted.'}
</p>
{rating >= 4 && feedback.googleReviewUrl && (
<p className="text-sm text-teal-600 mt-6 font-medium">
Redirecting to Google Reviews...
</p>
)}
</div>
</div>
);
}
// Rating Form
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-[#FAF8F5] via-[#C6C0B3] to-[#4C5F4E] px-6 py-12">
<div className="max-w-md w-full">
{/* Card */}
<div className="bg-white rounded-3xl shadow-xl overflow-hidden">
{/* Colored Header */}
<div className="bg-gradient-to-r from-[#FAF8F5] via-[#C6C0B3] to-[#4C5F4E] p-8 text-center">
<div className="w-14 h-14 bg-[#4C5F4E]/10 rounded-2xl flex items-center justify-center mx-auto mb-4">
<Star className="w-7 h-7 text-[#4C5F4E]" />
</div>
<h1 className="text-2xl font-bold mb-1 text-gray-900">How was your experience?</h1>
<p className="text-gray-700">{feedback.businessName}</p>
</div>
{/* Content */}
<div className="p-8">
{/* Stars */}
<div className="flex justify-center gap-2 mb-2">
{[1, 2, 3, 4, 5].map((star) => (
<button
key={star}
onClick={() => setRating(star)}
onMouseEnter={() => setHoverRating(star)}
onMouseLeave={() => setHoverRating(0)}
className="p-1 transition-transform hover:scale-110 focus:outline-none"
aria-label={`Rate ${star} stars`}
>
<Star
className={`w-11 h-11 transition-all ${star <= (hoverRating || rating)
? 'text-amber-400 fill-amber-400 drop-shadow-sm'
: 'text-gray-200'
}`}
/>
</button>
))}
</div>
{/* Rating text */}
<p className="text-center text-sm font-medium h-6 mb-6" style={{ color: rating > 0 ? '#6366f1' : 'transparent' }}>
{rating === 1 && 'Poor'}
{rating === 2 && 'Fair'}
{rating === 3 && 'Good'}
{rating === 4 && 'Great!'}
{rating === 5 && 'Excellent!'}
</p>
{/* Comment */}
<div className="mb-6">
<textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
placeholder="Share your thoughts (optional)"
rows={3}
className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent resize-none"
/>
</div>
{/* Submit */}
<button
onClick={handleSubmit}
disabled={rating === 0 || submitting}
className={`w-full py-4 rounded-xl font-semibold flex items-center justify-center gap-2 transition-all ${rating === 0
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: 'bg-gradient-to-r from-[#4C5F4E] to-[#0C342C] text-white hover:from-[#5a705c] hover:to-[#0E4036] shadow-lg shadow-emerald-200'
}`}
>
<Send className="w-4 h-4" />
{submitting ? 'Sending...' : 'Submit Feedback'}
</button>
</div>
</div>
{/* Footer */}
<p className="text-center text-sm text-white/60 mt-6">
Powered by QR Master
</p>
</div>
</div>
);
}
function ensureAbsoluteUrl(url: string | undefined): string | undefined {
if (!url) return undefined;
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url)) {
return url;
}
return `https://${url}`;
}