feat: enhance ReviewsSection with rating functionality and update reviews API
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { fetchReviews, addReview } from "./reviewsApi";
|
import { fetchReviews, addReview } from "./reviewsApi";
|
||||||
import type { Review } from "./reviewsApi";
|
import type { Review } from "./reviewsApi";
|
||||||
|
import { Star } from "lucide-react";
|
||||||
|
|
||||||
const ReviewsSection = ({
|
const ReviewsSection = ({
|
||||||
cardClasses,
|
cardClasses,
|
||||||
@@ -14,6 +15,8 @@ const ReviewsSection = ({
|
|||||||
const [reviews, setReviews] = useState<Review[]>([]);
|
const [reviews, setReviews] = useState<Review[]>([]);
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [message, setMessage] = useState("");
|
const [message, setMessage] = useState("");
|
||||||
|
const [rating, setRating] = useState(5);
|
||||||
|
const [hoverRating, setHoverRating] = useState(0);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [success, setSuccess] = useState("");
|
const [success, setSuccess] = useState("");
|
||||||
@@ -28,10 +31,11 @@ const ReviewsSection = ({
|
|||||||
setError("");
|
setError("");
|
||||||
setSuccess("");
|
setSuccess("");
|
||||||
try {
|
try {
|
||||||
await addReview({ name, message });
|
await addReview({ name, message, rating });
|
||||||
setSuccess("Review submitted!");
|
setSuccess("Review submitted!");
|
||||||
setName("");
|
setName("");
|
||||||
setMessage("");
|
setMessage("");
|
||||||
|
setRating(5);
|
||||||
setReviews(await fetchReviews());
|
setReviews(await fetchReviews());
|
||||||
// Close modal immediately after successful submission
|
// Close modal immediately after successful submission
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
@@ -89,11 +93,9 @@ const ReviewsSection = ({
|
|||||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<h3 className="text-xl font-bold mb-4 text-transparent bg-gradient-to-r from-cyan-400 to-purple-400 bg-clip-text">
|
<h3 className="text-xl font-bold mb-4 text-transparent bg-gradient-to-r from-cyan-400 to-purple-400 bg-clip-text">
|
||||||
Share Your Review
|
Share Your Review
|
||||||
</h3>
|
</h3>{" "}
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
||||||
<input
|
<input
|
||||||
className={`p-3 rounded border focus:outline-none ${
|
className={`p-3 rounded border focus:outline-none ${
|
||||||
@@ -106,6 +108,34 @@ const ReviewsSection = ({
|
|||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
<div className="mb-2">
|
||||||
|
<label className="block text-sm mb-1">Your Rating</label>
|
||||||
|
<div className="flex gap-1">
|
||||||
|
{[1, 2, 3, 4, 5].map((star) => (
|
||||||
|
<button
|
||||||
|
key={star}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setRating(star)}
|
||||||
|
onMouseEnter={() => setHoverRating(star)}
|
||||||
|
onMouseLeave={() => setHoverRating(0)}
|
||||||
|
className="transition-transform hover:scale-110"
|
||||||
|
>
|
||||||
|
<Star
|
||||||
|
size={24}
|
||||||
|
fill={
|
||||||
|
(hoverRating || rating) >= star ? "#38BDF8" : "none"
|
||||||
|
}
|
||||||
|
color={
|
||||||
|
(hoverRating || rating) >= star
|
||||||
|
? "#38BDF8"
|
||||||
|
: "#9CA3AF"
|
||||||
|
}
|
||||||
|
className="transition-colors duration-200"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
className={`p-3 rounded border focus:outline-none ${
|
className={`p-3 rounded border focus:outline-none ${
|
||||||
isDarkMode
|
isDarkMode
|
||||||
@@ -145,7 +175,9 @@ const ReviewsSection = ({
|
|||||||
key={review.id}
|
key={review.id}
|
||||||
className={`p-5 rounded-xl border ${cardClasses}`}
|
className={`p-5 rounded-xl border ${cardClasses}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center mb-2">
|
{" "}
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<div className="flex items-center">
|
||||||
<span className="font-semibold mr-2 text-cyan-400">
|
<span className="font-semibold mr-2 text-cyan-400">
|
||||||
{review.name}
|
{review.name}
|
||||||
</span>
|
</span>
|
||||||
@@ -153,6 +185,17 @@ const ReviewsSection = ({
|
|||||||
{review.createdAt?.toDate?.().toLocaleString?.() || ""}
|
{review.createdAt?.toDate?.().toLocaleString?.() || ""}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
{Array.from({ length: 5 }, (_, i) => (
|
||||||
|
<Star
|
||||||
|
key={i}
|
||||||
|
size={16}
|
||||||
|
fill={i < (review.rating || 5) ? "#38BDF8" : "none"}
|
||||||
|
color={i < (review.rating || 5) ? "#38BDF8" : "#9CA3AF"}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<p className={textClasses}>{review.message}</p>
|
<p className={textClasses}>{review.message}</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export type Review = {
|
|||||||
id?: string;
|
id?: string;
|
||||||
name: string;
|
name: string;
|
||||||
message: string;
|
message: string;
|
||||||
|
rating: number;
|
||||||
createdAt?: Timestamp;
|
createdAt?: Timestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user