Files
rentall-app/frontend/src/components/FeedbackModal.tsx
2025-12-22 22:35:57 -05:00

218 lines
5.9 KiB
TypeScript

import React, { useState, useEffect } from "react";
import { feedbackAPI } from "../services/api";
interface FeedbackModalProps {
show: boolean;
onClose: () => void;
}
const FeedbackModal: React.FC<FeedbackModalProps> = ({ show, onClose }) => {
const [feedbackText, setFeedbackText] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [success, setSuccess] = useState(false);
const MIN_LENGTH = 5;
const MAX_LENGTH = 5000;
const charCount = feedbackText.length;
const isValid = charCount >= MIN_LENGTH && charCount <= MAX_LENGTH;
useEffect(() => {
if (!show) {
// Reset form when modal closes
setTimeout(() => {
setFeedbackText("");
setError("");
setSuccess(false);
}, 300);
}
}, [show]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!isValid) {
setError(
`Feedback must be between ${MIN_LENGTH} and ${MAX_LENGTH} characters`
);
return;
}
setLoading(true);
setError("");
try {
// Capture the current URL
const currentUrl = window.location.href;
await feedbackAPI.submitFeedback({
feedbackText: feedbackText.trim(),
url: currentUrl,
});
setSuccess(true);
setLoading(false);
} catch (err: any) {
setError(
err.response?.data?.error ||
"Failed to submit feedback. Please try again."
);
setLoading(false);
}
};
if (!show) return null;
return (
<>
<div className={`feedback-panel ${show ? "show" : ""}`}>
<div className="feedback-panel-content">
<div className="modal-header">
<h5 className="modal-title">Share Feedback</h5>
<button
type="button"
className="btn-close"
onClick={onClose}
disabled={loading}
></button>
</div>
<form onSubmit={handleSubmit}>
<div className="modal-body">
{success ? (
<div className="alert alert-success" role="alert">
<h6 className="alert-heading">Thank you!</h6>
<p className="mb-0">
Your feedback has been submitted successfully! We appreciate
you making Village Share better!
</p>
</div>
) : (
<>
<p className="text-muted mb-3">
Share your thoughts, report bugs, or suggest improvements.
Your feedback helps us make Village Share better for everyone!
</p>
{error && (
<div className="alert alert-danger" role="alert">
{error}
</div>
)}
<div className="mb-3">
<textarea
id="feedbackText"
className="form-control"
rows={6}
value={feedbackText}
onChange={(e) => setFeedbackText(e.target.value)}
disabled={loading}
maxLength={MAX_LENGTH}
required
/>
{charCount > 0 && charCount < MIN_LENGTH && (
<div
className="text-danger mt-2"
style={{ fontSize: "0.875rem" }}
>
Minimum {MIN_LENGTH} characters required
</div>
)}
</div>
</>
)}
</div>
{!success && (
<div className="modal-footer" style={{ gap: "10px" }}>
<button
type="button"
className="btn btn-secondary"
onClick={onClose}
disabled={loading}
>
Cancel
</button>
<button
type="submit"
className="btn btn-primary"
disabled={loading || !isValid}
>
{loading ? (
<>
<span
className="spinner-border spinner-border-sm me-2"
role="status"
aria-hidden="true"
></span>
Sending...
</>
) : (
"Share"
)}
</button>
</div>
)}
</form>
</div>
</div>
<style>{`
.feedback-panel {
position: fixed;
top: 50%;
right: -400px;
transform: translateY(-50%);
height: auto;
max-height: calc(100vh - 40px);
width: 400px;
background-color: white;
box-shadow: -4px 0 12px rgba(0, 0, 0, 0.2);
border-radius: 8px 0 0 8px;
z-index: 1050;
transition: right 0.3s ease;
overflow-y: auto;
}
.feedback-panel.show {
right: 0;
}
.feedback-panel-content {
height: 100%;
display: flex;
flex-direction: column;
}
.feedback-panel .modal-header {
border-bottom: none;
padding: 1rem 1.5rem 0.5rem 1.5rem;
flex-shrink: 0;
}
.feedback-panel .modal-body {
flex: 1;
padding: 0.5rem 1.5rem 1.5rem 1.5rem;
overflow-y: auto;
}
.feedback-panel .modal-footer {
border-top: none;
padding: 0 1.5rem 1rem 1.5rem;
flex-shrink: 0;
}
@media (max-width: 576px) {
.feedback-panel {
width: 100%;
right: -100%;
}
}
`}</style>
</>
);
};
export default FeedbackModal;