| // src/components/messages/PollCreateModal.tsx | |
| import { useState } from "react"; | |
| import { X, Plus, Trash2, HelpCircle, BarChart3, Clock, GripVertical } from "lucide-react"; | |
| import { toast } from "react-toastify"; | |
| interface PollCreateModalProps { | |
| isOpen: boolean; | |
| onClose: () => void; | |
| onCreatePoll: (pollData: { | |
| question: string; | |
| options: string[]; | |
| multipleChoice: boolean; | |
| isQuiz: boolean; | |
| expiresIn?: number; // minutes | |
| explanation?: string; | |
| }) => void; | |
| } | |
| export function PollCreateModal({ isOpen, onClose, onCreatePoll }: PollCreateModalProps) { | |
| const [question, setQuestion] = useState(""); | |
| const [options, setOptions] = useState<string[]>(["", ""]); | |
| const [multipleChoice, setMultipleChoice] = useState(false); | |
| const [isQuiz, setIsQuiz] = useState(false); | |
| const [correctOptionIdx, setCorrectOptionIdx] = useState<number>(-1); | |
| const [explanation, setExplanation] = useState(""); | |
| const [expiresIn, setExpiresIn] = useState<number>(0); // 0 = no expiry | |
| if (!isOpen) return null; | |
| const addOption = () => setOptions([...options, ""]); | |
| const removeOption = (idx: number) => { | |
| if (options.length <= 2) return; | |
| setOptions(options.filter((_, i) => i !== idx)); | |
| if (correctOptionIdx === idx) setCorrectOptionIdx(-1); | |
| }; | |
| const updateOption = (idx: number, value: string) => { | |
| setOptions(options.map((o, i) => (i === idx ? value : o))); | |
| }; | |
| const handleCreate = () => { | |
| const trimmedQuestion = question.trim(); | |
| const trimmedOptions = options.map((o) => o.trim()).filter(Boolean); | |
| if (!trimmedQuestion) return toast.warning("Please enter a question"); | |
| if (trimmedOptions.length < 2) return toast.warning("Add at least 2 options"); | |
| if (isQuiz && correctOptionIdx < 0) return toast.warning("Select a correct answer for the quiz"); | |
| onCreatePoll({ | |
| question: trimmedQuestion, | |
| options: trimmedOptions, | |
| multipleChoice, | |
| isQuiz, | |
| expiresIn: expiresIn || undefined, | |
| explanation: isQuiz ? explanation.trim() : undefined, | |
| }); | |
| // Reset form | |
| setQuestion(""); | |
| setOptions(["", ""]); | |
| setMultipleChoice(false); | |
| setIsQuiz(false); | |
| setCorrectOptionIdx(-1); | |
| setExplanation(""); | |
| setExpiresIn(0); | |
| onClose(); | |
| }; | |
| return ( | |
| <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4" onClick={onClose}> | |
| <div | |
| className="bg-white dark:bg-gray-800 rounded-2xl max-w-md w-full p-6 shadow-2xl overflow-y-auto max-h-[90vh]" | |
| onClick={(e) => e.stopPropagation()} | |
| > | |
| <div className="flex items-center justify-between mb-4"> | |
| <h2 className="text-xl font-bold flex items-center gap-2"> | |
| {isQuiz ? ( | |
| <HelpCircle className="w-5 h-5 text-purple-500" /> | |
| ) : ( | |
| <BarChart3 className="w-5 h-5 text-primary" /> | |
| )} | |
| Create {isQuiz ? "Quiz" : "Poll"} | |
| </h2> | |
| <button onClick={onClose} className="text-gray-500 hover:text-gray-700"> | |
| <X className="w-5 h-5" /> | |
| </button> | |
| </div> | |
| {/* Type toggle */} | |
| <div className="flex bg-gray-100 dark:bg-gray-700 rounded-xl p-1 mb-4"> | |
| <button | |
| onClick={() => { setIsQuiz(false); setCorrectOptionIdx(-1); }} | |
| className={`flex-1 py-2 text-sm font-medium rounded-lg transition ${ | |
| !isQuiz ? "bg-white dark:bg-gray-600 shadow text-primary" : "text-gray-500" | |
| }`} | |
| > | |
| 📊 Poll | |
| </button> | |
| <button | |
| onClick={() => setIsQuiz(true)} | |
| className={`flex-1 py-2 text-sm font-medium rounded-lg transition ${ | |
| isQuiz ? "bg-white dark:bg-gray-600 shadow text-purple-500" : "text-gray-500" | |
| }`} | |
| > | |
| 🧠 Quiz | |
| </button> | |
| </div> | |
| {/* Question */} | |
| <input | |
| type="text" | |
| placeholder="Ask a question..." | |
| value={question} | |
| onChange={(e) => setQuestion(e.target.value)} | |
| className="w-full p-3 border border-gray-200 dark:border-gray-700 rounded-xl mb-4 bg-transparent focus:ring-2 focus:ring-primary focus:outline-none" | |
| autoFocus | |
| /> | |
| {/* Options */} | |
| <div className="space-y-2 mb-4"> | |
| {options.map((opt, idx) => ( | |
| <div key={idx} className="flex items-center gap-2"> | |
| <GripVertical className="w-4 h-4 text-gray-400 shrink-0" /> | |
| <input | |
| type="text" | |
| placeholder={`Option ${idx + 1}`} | |
| value={opt} | |
| onChange={(e) => updateOption(idx, e.target.value)} | |
| className="flex-1 p-2.5 border border-gray-200 dark:border-gray-700 rounded-xl bg-transparent focus:ring-2 focus:ring-primary focus:outline-none text-sm" | |
| /> | |
| {isQuiz && ( | |
| <button | |
| onClick={() => setCorrectOptionIdx(idx)} | |
| className={`p-1.5 rounded-lg text-xs shrink-0 transition ${ | |
| correctOptionIdx === idx | |
| ? "bg-green-100 dark:bg-green-900/30 text-green-600" | |
| : "bg-gray-100 dark:bg-gray-700 text-gray-400" | |
| }`} | |
| title="Mark as correct answer" | |
| > | |
| ✓ | |
| </button> | |
| )} | |
| <button | |
| onClick={() => removeOption(idx)} | |
| className="p-1.5 text-gray-400 hover:text-red-500 shrink-0" | |
| disabled={options.length <= 2} | |
| > | |
| <Trash2 className="w-4 h-4" /> | |
| </button> | |
| </div> | |
| ))} | |
| <button | |
| onClick={addOption} | |
| className="flex items-center gap-2 text-sm text-primary hover:underline p-2" | |
| > | |
| <Plus className="w-4 h-4" /> Add option | |
| </button> | |
| </div> | |
| {/* Settings */} | |
| <div className="space-y-3 mb-4 border-t border-gray-100 dark:border-gray-700 pt-3"> | |
| {/* Multiple choice */} | |
| <label className="flex items-center justify-between cursor-pointer"> | |
| <span className="text-sm text-gray-600 dark:text-gray-400">Allow multiple answers</span> | |
| <input | |
| type="checkbox" | |
| checked={multipleChoice} | |
| onChange={(e) => setMultipleChoice(e.target.checked)} | |
| className="rounded border-gray-300 dark:border-gray-600 text-primary focus:ring-primary" | |
| /> | |
| </label> | |
| {/* Expiry */} | |
| <div className="flex items-center gap-3"> | |
| <Clock className="w-4 h-4 text-gray-400" /> | |
| <select | |
| value={expiresIn} | |
| onChange={(e) => setExpiresIn(Number(e.target.value))} | |
| className="text-sm border border-gray-200 dark:border-gray-700 rounded-lg p-2 bg-transparent" | |
| > | |
| <option value={0}>No expiry</option> | |
| <option value={5}>5 minutes</option> | |
| <option value={30}>30 minutes</option> | |
| <option value={60}>1 hour</option> | |
| <option value={1440}>24 hours</option> | |
| <option value={10080}>7 days</option> | |
| </select> | |
| </div> | |
| {/* Quiz explanation */} | |
| {isQuiz && ( | |
| <textarea | |
| placeholder="Explanation after answer (optional)" | |
| value={explanation} | |
| onChange={(e) => setExplanation(e.target.value)} | |
| className="w-full p-2.5 border border-gray-200 dark:border-gray-700 rounded-xl bg-transparent text-sm focus:ring-2 focus:ring-primary focus:outline-none" | |
| rows={2} | |
| /> | |
| )} | |
| </div> | |
| {/* Create button */} | |
| <button onClick={handleCreate} className="btn btn-primary w-full"> | |
| Create {isQuiz ? "Quiz" : "Poll"} | |
| </button> | |
| </div> | |
| </div> | |
| ); | |
| } |
Xet Storage Details
- Size:
- 7.98 kB
- Xet hash:
- 6374dbe89e2f99461f0704b97451b845ad58a349481c4211a9de7b5a1b4a82f3
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.