create review and profile
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { upsertTripReviewAction } from "@/features/review/actions";
|
||||
|
||||
type ReviewUser = { id: string; name: string; image: string | null };
|
||||
|
||||
export type TripReviewItem = {
|
||||
id: string;
|
||||
rating: number;
|
||||
comment: string | null;
|
||||
createdAt: Date;
|
||||
user: ReviewUser;
|
||||
};
|
||||
|
||||
interface TripReviewSectionProps {
|
||||
tripId: string;
|
||||
reviews: TripReviewItem[];
|
||||
averageRating: number | null;
|
||||
canReview: boolean;
|
||||
myReview: { rating: number; comment: string | null } | null;
|
||||
}
|
||||
|
||||
export function TripReviewSection({
|
||||
tripId,
|
||||
reviews,
|
||||
averageRating,
|
||||
canReview,
|
||||
myReview,
|
||||
}: TripReviewSectionProps) {
|
||||
const router = useRouter();
|
||||
const [rating, setRating] = useState(myReview?.rating ?? 5);
|
||||
const [comment, setComment] = useState(myReview?.comment ?? "");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
setLoading(true);
|
||||
const fd = new FormData();
|
||||
fd.set("tripId", tripId);
|
||||
fd.set("rating", String(rating));
|
||||
fd.set("comment", comment);
|
||||
const result = await upsertTripReviewAction(fd);
|
||||
setLoading(false);
|
||||
if (result.error) {
|
||||
setError(result.error);
|
||||
} else {
|
||||
router.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-neutral-200 bg-white p-4 sm:p-5">
|
||||
<div className="mb-4 flex flex-wrap items-baseline justify-between gap-2">
|
||||
<h2 className="text-sm font-bold text-neutral-800 sm:text-base">
|
||||
Ulasan peserta
|
||||
</h2>
|
||||
{averageRating != null && reviews.length > 0 && (
|
||||
<p className="text-sm text-neutral-600">
|
||||
<span className="font-bold text-amber-600">{averageRating}</span>
|
||||
<span className="text-neutral-400"> /5</span>
|
||||
<span className="text-neutral-400"> · {reviews.length} ulasan</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{reviews.length === 0 && !canReview && (
|
||||
<p className="text-xs text-neutral-500 sm:text-sm">
|
||||
Belum ada ulasan. Peserta bisa menulis ulasan setelah trip selesai.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{reviews.length > 0 && (
|
||||
<ul className="mb-5 space-y-3 border-b border-neutral-100 pb-5">
|
||||
{reviews.map((r) => (
|
||||
<li
|
||||
key={r.id}
|
||||
className="rounded-lg bg-neutral-50 px-3 py-2.5 sm:px-4 sm:py-3"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-xs font-semibold text-neutral-800 sm:text-sm">
|
||||
{r.user.name}
|
||||
</span>
|
||||
<span className="text-xs font-bold text-amber-600">
|
||||
{r.rating}/5
|
||||
</span>
|
||||
</div>
|
||||
{r.comment && (
|
||||
<p className="mt-1.5 text-xs leading-relaxed text-neutral-600 sm:text-sm">
|
||||
{r.comment}
|
||||
</p>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{canReview && (
|
||||
<div>
|
||||
<p className="mb-3 text-xs text-neutral-500 sm:text-sm">
|
||||
{myReview
|
||||
? "Ubah ulasan kamu untuk trip ini."
|
||||
: "Bagikan pengalamanmu setelah trip selesai."}
|
||||
</p>
|
||||
{error && (
|
||||
<div className="mb-3 rounded-lg bg-red-50 px-3 py-2 text-xs font-medium text-red-600">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
<form onSubmit={handleSubmit} className="space-y-3">
|
||||
<div>
|
||||
<label className="mb-1.5 block text-xs font-semibold text-neutral-700 sm:text-sm">
|
||||
Rating
|
||||
</label>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{[1, 2, 3, 4, 5].map((n) => (
|
||||
<button
|
||||
key={n}
|
||||
type="button"
|
||||
onClick={() => setRating(n)}
|
||||
className={`rounded-lg px-2.5 py-1.5 text-xs font-bold sm:px-3 sm:text-sm ${
|
||||
rating === n
|
||||
? "bg-primary-600 text-white"
|
||||
: "bg-neutral-100 text-neutral-600 hover:bg-neutral-200"
|
||||
}`}
|
||||
>
|
||||
{n}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="review-comment"
|
||||
className="mb-1.5 block text-xs font-semibold text-neutral-700 sm:text-sm"
|
||||
>
|
||||
Komentar (opsional)
|
||||
</label>
|
||||
<textarea
|
||||
id="review-comment"
|
||||
value={comment}
|
||||
onChange={(e) => setComment(e.target.value)}
|
||||
rows={3}
|
||||
maxLength={500}
|
||||
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-3 py-2 text-xs text-neutral-800 placeholder:text-neutral-400 focus:bg-white sm:text-sm"
|
||||
placeholder="Ceritakan pengalaman trip..."
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full rounded-xl bg-secondary-600 py-2.5 text-xs font-bold text-white shadow-md hover:bg-secondary-700 disabled:opacity-50 sm:text-sm"
|
||||
>
|
||||
{loading ? "Menyimpan..." : myReview ? "Perbarui ulasan" : "Kirim ulasan"}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user