225 lines
7.1 KiB
TypeScript
225 lines
7.1 KiB
TypeScript
import React, { useState } from "react";
|
|
import { rentalAPI } from "../services/api";
|
|
import { Rental } from "../types";
|
|
import SuccessModal from "./SuccessModal";
|
|
|
|
interface ReviewRenterModalProps {
|
|
show: boolean;
|
|
onClose: () => void;
|
|
rental: Rental;
|
|
onSuccess: () => void;
|
|
}
|
|
|
|
const ReviewRenterModal: React.FC<ReviewRenterModalProps> = ({
|
|
show,
|
|
onClose,
|
|
rental,
|
|
onSuccess,
|
|
}) => {
|
|
const [rating, setRating] = useState(5);
|
|
const [review, setReview] = useState("");
|
|
const [privateMessage, setPrivateMessage] = useState("");
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [showSuccessModal, setShowSuccessModal] = useState(false);
|
|
const [successMessage, setSuccessMessage] = useState("");
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setError(null);
|
|
setSubmitting(true);
|
|
|
|
try {
|
|
const response = await rentalAPI.reviewRenter(rental.id, {
|
|
rating,
|
|
review,
|
|
privateMessage,
|
|
});
|
|
|
|
// Reset form
|
|
setRating(5);
|
|
setReview("");
|
|
setPrivateMessage("");
|
|
|
|
// Show success modal with appropriate message
|
|
if (response.data.reviewVisible) {
|
|
setSuccessMessage("Review published successfully!");
|
|
} else {
|
|
setSuccessMessage("Review submitted! It will be published when both parties have reviewed or after 10 minutes.");
|
|
}
|
|
setShowSuccessModal(true);
|
|
} catch (err: any) {
|
|
setError(err.response?.data?.error || "Failed to submit review");
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
const handleStarClick = (value: number) => {
|
|
setRating(value);
|
|
};
|
|
|
|
const handleSuccessModalClose = () => {
|
|
setShowSuccessModal(false);
|
|
onSuccess();
|
|
onClose();
|
|
};
|
|
|
|
if (!show) return null;
|
|
|
|
return (
|
|
<div
|
|
className="modal d-block"
|
|
tabIndex={-1}
|
|
style={{ backgroundColor: "rgba(0,0,0,0.5)" }}
|
|
>
|
|
<div className="modal-dialog modal-dialog-centered">
|
|
<div className="modal-content">
|
|
<div className="modal-header">
|
|
<h5 className="modal-title">Review Renter</h5>
|
|
<button
|
|
type="button"
|
|
className="btn-close"
|
|
onClick={onClose}
|
|
></button>
|
|
</div>
|
|
<form onSubmit={handleSubmit}>
|
|
<div className="modal-body">
|
|
{rental.renter && rental.item && (
|
|
<div className="mb-4 text-center">
|
|
<div className="d-flex justify-content-center mb-3">
|
|
{rental.renter.profileImage ? (
|
|
<img
|
|
src={rental.renter.profileImage}
|
|
alt={`${rental.renter.firstName} ${rental.renter.lastName}`}
|
|
className="rounded-circle"
|
|
style={{
|
|
width: "60px",
|
|
height: "60px",
|
|
objectFit: "cover",
|
|
}}
|
|
/>
|
|
) : (
|
|
<div
|
|
className="rounded-circle bg-primary d-flex align-items-center justify-content-center text-white fw-bold"
|
|
style={{ width: "60px", height: "60px" }}
|
|
>
|
|
{rental.renter.firstName[0]}
|
|
{rental.renter.lastName[0]}
|
|
</div>
|
|
)}
|
|
</div>
|
|
<h6 className="mb-1">
|
|
{rental.renter.firstName} {rental.renter.lastName}
|
|
</h6>
|
|
<p className="mb-1 text-muted small">{rental.item.name}</p>
|
|
<small className="text-muted">
|
|
{new Date(rental.startDate).toLocaleDateString()} to{" "}
|
|
{new Date(rental.endDate).toLocaleDateString()}
|
|
</small>
|
|
</div>
|
|
)}
|
|
|
|
{error && (
|
|
<div className="alert alert-danger" role="alert">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
<div className="mb-3">
|
|
<div
|
|
className="d-flex justify-content-center gap-1"
|
|
style={{ fontSize: "2rem" }}
|
|
>
|
|
{[1, 2, 3, 4, 5].map((star) => (
|
|
<button
|
|
key={star}
|
|
type="button"
|
|
className="btn btn-link p-0 text-decoration-none"
|
|
onClick={() => handleStarClick(star)}
|
|
style={{ color: star <= rating ? "#ffc107" : "#dee2e6" }}
|
|
>
|
|
<i
|
|
className={`bi ${
|
|
star <= rating ? "bi-star-fill" : "bi-star"
|
|
}`}
|
|
></i>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mb-3">
|
|
<label htmlFor="privateMessage" className="form-label">
|
|
Private Message to Renter{" "}
|
|
</label>
|
|
<textarea
|
|
className="form-control"
|
|
id="privateMessage"
|
|
rows={3}
|
|
value={privateMessage}
|
|
onChange={(e) => setPrivateMessage(e.target.value)}
|
|
placeholder=""
|
|
disabled={submitting}
|
|
></textarea>
|
|
</div>
|
|
|
|
<div className="mb-3">
|
|
<label htmlFor="review" className="form-label">
|
|
Public Review
|
|
</label>
|
|
<textarea
|
|
className="form-control"
|
|
id="review"
|
|
rows={4}
|
|
value={review}
|
|
onChange={(e) => setReview(e.target.value)}
|
|
placeholder=""
|
|
disabled={submitting}
|
|
></textarea>
|
|
</div>
|
|
</div>
|
|
<div className="modal-footer">
|
|
<button
|
|
type="button"
|
|
className="btn btn-secondary"
|
|
onClick={onClose}
|
|
disabled={submitting}
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
className="btn btn-primary"
|
|
disabled={submitting || !review.trim()}
|
|
>
|
|
{submitting ? (
|
|
<>
|
|
<span
|
|
className="spinner-border spinner-border-sm me-2"
|
|
role="status"
|
|
aria-hidden="true"
|
|
></span>
|
|
Submitting...
|
|
</>
|
|
) : (
|
|
"Submit Review"
|
|
)}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<SuccessModal
|
|
show={showSuccessModal}
|
|
onClose={handleSuccessModalClose}
|
|
title="Thank you for your review!"
|
|
message={successMessage}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ReviewRenterModal;
|