Spaces:
Running
Running
File size: 6,632 Bytes
67264dd 1116aba 67264dd 1116aba 67264dd 1116aba 67264dd 1116aba 67264dd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | "use client";
import React, { useState } from "react";
import { X, Send, AlertCircle } from "lucide-react";
import axios from "axios";
import { motion, AnimatePresence } from "framer-motion";
const getToken = () => typeof window !== 'undefined' ? localStorage.getItem('access_token') : null;
export default function FeedbackModal({ isOpen, onClose }: { isOpen: boolean, onClose: () => void }) {
const [issueType, setIssueType] = useState("Bug");
const [message, setMessage] = useState("");
const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle");
const [errorMsg, setErrorMsg] = useState("");
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (message.trim().length < 5) {
setErrorMsg("Message must be at least 5 characters.");
return;
}
setStatus("loading");
setErrorMsg("");
try {
const baseUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000';
await axios.post(`${baseUrl}/issues/`, { issue_type: issueType, message }, {
headers: { Authorization: `Bearer ${getToken()}` }
});
setStatus("success");
setTimeout(() => {
setStatus("idle");
setMessage("");
onClose();
}, 2000);
} catch (err: any) {
setStatus("error");
setErrorMsg(err.response?.data?.detail || "Failed to submit feedback.");
}
};
if (!isOpen) return null;
return (
<AnimatePresence>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4"
onClick={onClose}
>
<motion.div
initial={{ scale: 0.95, opacity: 0, y: 10 }}
animate={{ scale: 1, opacity: 1, y: 0 }}
exit={{ scale: 0.95, opacity: 0, y: 10 }}
onClick={e => e.stopPropagation()}
className="bg-[var(--theme-bg)] border border-theme-border shadow-2xl rounded-2xl w-full max-w-lg overflow-hidden flex flex-col"
>
<div className="p-6 border-b border-theme-border flex justify-between items-center">
<h2 className="text-xl font-semibold tracking-wide text-[var(--theme-text)]">Report an Issue</h2>
<button onClick={onClose} className="text-theme-text/50 hover:text-[var(--theme-text)] transition-colors !cursor-none">
<X className="w-5 h-5 !cursor-none" />
</button>
</div>
<div className="p-6">
{status === "success" ? (
<div className="flex flex-col items-center justify-center py-8 text-green-400">
<AlertCircle className="w-12 h-12 mb-4" />
<p className="text-lg">Thanks for your feedback!</p>
</div>
) : (
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
{errorMsg && (
<div className="bg-red-500/10 border border-red-500/20 text-red-400 p-3 rounded-lg text-sm">
{errorMsg}
</div>
)}
<div className="flex flex-col gap-2">
<label className="text-sm text-[var(--theme-text)]/70 uppercase tracking-widest font-mono">Issue Type</label>
<select
value={issueType}
onChange={e => setIssueType(e.target.value)}
className="bg-[var(--theme-bg)] border border-theme-border text-[var(--theme-text)] rounded-lg p-3 outline-none focus:border-[#d0c4bb]/50 transition-colors !cursor-none"
>
<option value="Bug">Bug</option>
<option value="UI Issue">UI Issue</option>
<option value="Suggestion">Suggestion</option>
<option value="Other">Other</option>
</select>
</div>
<div className="flex flex-col gap-2">
<label className="text-sm text-[var(--theme-text)]/70 uppercase tracking-widest font-mono">Message</label>
<textarea
value={message}
onChange={e => setMessage(e.target.value)}
placeholder="Describe the issue or suggestion..."
rows={4}
className="bg-[var(--theme-bg)] border border-theme-border text-[var(--theme-text)] rounded-lg p-3 outline-none focus:border-[#d0c4bb]/50 transition-colors resize-none !cursor-none"
/>
</div>
<button
disabled={status === "loading"}
type="submit"
className="mt-2 bg-[#d0c4bb] text-[var(--theme-bg)] hover:bg-[var(--theme-text)] font-semibold py-3 rounded-lg flex items-center justify-center gap-2 transition-all disabled:opacity-50 !cursor-none"
>
{status === "loading" ? "Submitting..." : (
<>
<Send className="w-4 h-4 !cursor-none" />
Submit Feedback
</>
)}
</button>
</form>
)}
</div>
</motion.div>
</motion.div>
</AnimatePresence>
);
}
|