Spaces:
Sleeping
Sleeping
| import React, { useState, useRef } from 'react'; | |
| export default function CreateServerModal({ onClose, onCreated, token }) { | |
| const [name, setName] = useState(''); | |
| const [icon, setIcon] = useState(null); | |
| const [iconPreview, setIconPreview] = useState(null); | |
| const [loading, setLoading] = useState(false); | |
| const [error, setError] = useState(''); | |
| const fileRef = useRef(null); | |
| const handleIconChange = (e) => { | |
| const file = e.target.files?.[0]; | |
| if (file) { | |
| setIcon(file); | |
| const reader = new FileReader(); | |
| reader.onloadend = () => setIconPreview(reader.result); | |
| reader.readAsDataURL(file); | |
| } | |
| }; | |
| const handleCreate = async (e) => { | |
| e.preventDefault(); | |
| if (!name.trim()) { setError('Server name is required'); return; } | |
| setLoading(true); | |
| setError(''); | |
| try { | |
| // Upload icon first if provided | |
| let iconUrl = null; | |
| if (icon) { | |
| const form = new FormData(); | |
| form.append('file', icon); | |
| const uploadRes = await fetch('/api/upload', { | |
| method: 'POST', | |
| headers: { 'Authorization': `Bearer ${token}` }, | |
| body: form, | |
| }); | |
| if (uploadRes.ok) { | |
| const uploadData = await uploadRes.json(); | |
| iconUrl = uploadData.url; | |
| } | |
| } | |
| const body = { name: name.trim() }; | |
| if (iconUrl) body.icon = iconUrl; | |
| const res = await fetch('/api/servers', { | |
| method: 'POST', | |
| headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(body), | |
| }); | |
| if (!res.ok) { | |
| const data = await res.json().catch(() => ({})); | |
| throw new Error(data.error || 'Failed to create server'); | |
| } | |
| const data = await res.json(); | |
| onCreated(data.server || data); | |
| onClose(); | |
| } catch (err) { | |
| setError(err.message); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| return ( | |
| <div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50" onClick={onClose}> | |
| <div className="bg-[#1e1e22] rounded-xl p-6 max-w-md w-full mx-4 shadow-2xl" onClick={e => e.stopPropagation()}> | |
| <h2 className="text-xl font-bold text-[#FFD700] mb-6 text-center">Create Your Server</h2> | |
| <form onSubmit={handleCreate}> | |
| {/* Icon upload */} | |
| <div className="flex justify-center mb-6"> | |
| <button | |
| type="button" | |
| onClick={() => fileRef.current?.click()} | |
| className="w-20 h-20 rounded-full bg-[#25252a] border-2 border-dashed border-[#3a3a42] hover:border-[#FFD700]/50 flex items-center justify-center transition-colors duration-200 overflow-hidden" | |
| > | |
| {iconPreview ? ( | |
| <img src={iconPreview} alt="" className="w-full h-full object-cover" /> | |
| ) : ( | |
| <div className="text-center"> | |
| <svg className="w-6 h-6 text-gray-500 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" /> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" /> | |
| </svg> | |
| <span className="text-[10px] text-gray-500">Icon</span> | |
| </div> | |
| )} | |
| </button> | |
| <input ref={fileRef} type="file" accept="image/*" className="hidden" onChange={handleIconChange} /> | |
| </div> | |
| {/* Server name */} | |
| <div className="mb-4"> | |
| <label className="block text-sm font-medium text-gray-300 mb-1.5">Server Name</label> | |
| <input | |
| type="text" | |
| value={name} | |
| onChange={e => setName(e.target.value)} | |
| placeholder="My Awesome Server" | |
| className="w-full bg-[#111114] border border-[#3a3a42] rounded-lg px-4 py-2.5 text-gray-100 placeholder-gray-500 outline-none focus:border-[#FFD700]/50 transition-colors duration-200" | |
| autoFocus | |
| maxLength={100} | |
| /> | |
| </div> | |
| {error && <p className="text-red-400 text-sm mb-4">{error}</p>} | |
| {/* Buttons */} | |
| <div className="flex gap-3 justify-end"> | |
| <button | |
| type="button" | |
| onClick={onClose} | |
| className="px-4 py-2 text-gray-400 hover:text-gray-200 transition-colors duration-200 rounded-lg" | |
| > | |
| Cancel | |
| </button> | |
| <button | |
| type="submit" | |
| disabled={loading || !name.trim()} | |
| className="px-6 py-2 bg-[#FFD700] text-black font-semibold rounded-lg hover:bg-yellow-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200" | |
| > | |
| {loading ? 'Creating…' : 'Create'} | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| ); | |
| } | |