Spaces:
Running
Running
| "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> | |
| ); | |
| } | |