Spaces:
Sleeping
Sleeping
| "use client"; | |
| import { useState, useEffect } from "react"; | |
| import { motion, AnimatePresence } from "framer-motion"; | |
| import { X, Save, RotateCcw, Check } from "lucide-react"; | |
| import { Button } from "@/components/ui/Button"; | |
| import { CHATBOT_CONFIG, type ChatbotConfig, getChatbotConfig, saveChatbotConfig, resetChatbotConfig } from "@/lib/chatbotConfig"; | |
| import { cn } from "@/lib/utils"; | |
| interface SettingsModalProps { | |
| isOpen: boolean; | |
| onClose: () => void; | |
| } | |
| export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { | |
| const [config, setConfig] = useState<ChatbotConfig>(CHATBOT_CONFIG); | |
| const [saved, setSaved] = useState(false); | |
| // Load current config when modal opens | |
| useEffect(() => { | |
| if (isOpen) { | |
| setConfig(getChatbotConfig()); | |
| setSaved(false); | |
| } | |
| }, [isOpen]); | |
| const handleChange = (field: keyof ChatbotConfig, value: string) => { | |
| setConfig((prev) => ({ ...prev, [field]: value })); | |
| }; | |
| const handleSave = () => { | |
| saveChatbotConfig(config); | |
| setSaved(true); | |
| // Close after showing success | |
| setTimeout(() => { | |
| onClose(); | |
| }, 800); | |
| }; | |
| const handleReset = () => { | |
| setConfig(CHATBOT_CONFIG); | |
| resetChatbotConfig(); | |
| }; | |
| return ( | |
| <AnimatePresence> | |
| {isOpen && ( | |
| <> | |
| {/* Backdrop */} | |
| <motion.div | |
| initial={{ opacity: 0 }} | |
| animate={{ opacity: 1 }} | |
| exit={{ opacity: 0 }} | |
| transition={{ duration: 0.2 }} | |
| className="fixed inset-0 bg-black/40 backdrop-blur-sm z-50" | |
| onClick={onClose} | |
| /> | |
| {/* Modal */} | |
| <div className="fixed inset-0 z-50 flex items-center justify-center p-4"> | |
| <motion.div | |
| initial={{ opacity: 0, scale: 0.95, y: 8 }} | |
| animate={{ opacity: 1, scale: 1, y: 0 }} | |
| exit={{ opacity: 0, scale: 0.95, y: 8 }} | |
| transition={{ duration: 0.2, ease: "easeOut" }} | |
| className="w-full max-w-md bg-white rounded-2xl shadow-2xl overflow-hidden" | |
| onClick={(e) => e.stopPropagation()} | |
| > | |
| {/* Header */} | |
| <div className="flex items-center justify-between px-6 py-4 border-b border-neutral-100"> | |
| <h2 className="text-lg font-semibold text-neutral-900"> | |
| Pengaturan Chatbot | |
| </h2> | |
| <button | |
| onClick={onClose} | |
| className="p-1.5 rounded-lg text-neutral-400 hover:bg-neutral-100 hover:text-neutral-700 transition-colors" | |
| > | |
| <X className="h-4 w-4" /> | |
| </button> | |
| </div> | |
| {/* Body */} | |
| <div className="px-6 py-5 space-y-5"> | |
| {/* Name */} | |
| <div className="flex flex-col gap-2"> | |
| <label className="text-sm font-medium text-neutral-700"> | |
| Nama Chatbot | |
| </label> | |
| <input | |
| type="text" | |
| value={config.name} | |
| onChange={(e) => handleChange("name", e.target.value)} | |
| maxLength={30} | |
| className="w-full rounded-xl border border-neutral-200 bg-white px-4 py-2.5 text-sm text-neutral-900 placeholder:text-neutral-400 transition-all duration-200 outline-none focus:border-brand-green focus:ring-3 focus:ring-brand-green/10" | |
| placeholder="Contoh: EMA" | |
| /> | |
| <p className="text-xs text-neutral-400">{config.name.length}/30 karakter</p> | |
| </div> | |
| {/* Caption */} | |
| <div className="flex flex-col gap-2"> | |
| <label className="text-sm font-medium text-neutral-700"> | |
| Caption / Tagline | |
| </label> | |
| <input | |
| type="text" | |
| value={config.caption} | |
| onChange={(e) => handleChange("caption", e.target.value)} | |
| maxLength={50} | |
| className="w-full rounded-xl border border-neutral-200 bg-white px-4 py-2.5 text-sm text-neutral-900 placeholder:text-neutral-400 transition-all duration-200 outline-none focus:border-brand-green focus:ring-3 focus:ring-brand-green/10" | |
| placeholder="Contoh: Expert Mining Assistant" | |
| /> | |
| <p className="text-xs text-neutral-400">{config.caption.length}/50 karakter</p> | |
| </div> | |
| {/* Description */} | |
| <div className="flex flex-col gap-2"> | |
| <label className="text-sm font-medium text-neutral-700"> | |
| Deskripsi | |
| </label> | |
| <textarea | |
| value={config.description} | |
| onChange={(e) => handleChange("description", e.target.value)} | |
| maxLength={200} | |
| rows={3} | |
| className="w-full rounded-xl border border-neutral-200 bg-white px-4 py-2.5 text-sm text-neutral-900 placeholder:text-neutral-400 transition-all duration-200 outline-none focus:border-brand-green focus:ring-3 focus:ring-brand-green/10 resize-none" | |
| placeholder="Deskripsi singkat tentang chatbot" | |
| /> | |
| <p className="text-xs text-neutral-400">{config.description.length}/200 karakter</p> | |
| </div> | |
| {/* Preview */} | |
| <div className="p-3 rounded-xl bg-neutral-50 border border-neutral-100"> | |
| <p className="text-xs font-medium text-neutral-400 mb-2">Preview</p> | |
| <p className="text-sm font-semibold text-neutral-900">{config.name}</p> | |
| <p className="text-xs text-neutral-500">{config.caption}</p> | |
| <p className="text-xs text-neutral-400 mt-1 leading-relaxed line-clamp-2">{config.description}</p> | |
| </div> | |
| </div> | |
| {/* Footer */} | |
| <div className="flex items-center justify-between px-6 py-4 border-t border-neutral-100 bg-neutral-50/50"> | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| onClick={handleReset} | |
| className="text-neutral-500" | |
| > | |
| <RotateCcw className="h-3.5 w-3.5" /> | |
| Reset | |
| </Button> | |
| <div className="flex items-center gap-2"> | |
| <Button | |
| variant="secondary" | |
| size="sm" | |
| onClick={onClose} | |
| > | |
| Batal | |
| </Button> | |
| <AnimatePresence mode="wait"> | |
| {saved ? ( | |
| <motion.div | |
| key="saved" | |
| initial={{ opacity: 0, scale: 0.9 }} | |
| animate={{ opacity: 1, scale: 1 }} | |
| exit={{ opacity: 0, scale: 0.9 }} | |
| className="inline-flex items-center gap-1.5 px-4 py-2 rounded-xl bg-green-500 text-white text-sm font-semibold" | |
| > | |
| <Check className="h-3.5 w-3.5" /> | |
| Tersimpan | |
| </motion.div> | |
| ) : ( | |
| <motion.div | |
| key="save" | |
| initial={{ opacity: 0, scale: 0.9 }} | |
| animate={{ opacity: 1, scale: 1 }} | |
| exit={{ opacity: 0, scale: 0.9 }} | |
| > | |
| <Button | |
| variant="primary" | |
| size="sm" | |
| onClick={handleSave} | |
| > | |
| <Save className="h-3.5 w-3.5" /> | |
| Simpan | |
| </Button> | |
| </motion.div> | |
| )} | |
| </AnimatePresence> | |
| </div> | |
| </div> | |
| </motion.div> | |
| </div> | |
| </> | |
| )} | |
| </AnimatePresence> | |
| ); | |
| } | |