WhitelistModal.tsx 5.6 KB
import React, { useState, useEffect } from 'react';
import { X, Plus, RefreshCw, Trash2 } from 'lucide-react';
import { getWhitelist, addWhitelist, removeWhitelist } from '../services/authService';

interface WhitelistModalProps {
  isOpen: boolean;
  onClose: () => void;
}

const WhitelistModal: React.FC<WhitelistModalProps> = ({ isOpen, onClose }) => {
  const [whitelist, setWhitelist] = useState<string[]>([]);
  const [newIds, setNewIds] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    if (isOpen) {
      loadWhitelist();
    }
  }, [isOpen]);

  const loadWhitelist = async () => {
    setIsLoading(true);
    try {
      const data = await getWhitelist();
      setWhitelist(data);
      setError(null);
    } catch (e) {
      setError('无法获取白名单');
    } finally {
      setIsLoading(false);
    }
  };

  const handleAdd = async () => {
    if (!newIds.trim()) return;
    
    // Split by newlines, commas, or spaces
    const idsToAdd = newIds
      .split(/[\n, ]+/)
      .map(s => s.trim())
      .filter(s => s.length > 0);

    if (idsToAdd.length === 0) return;

    setIsLoading(true);
    try {
      const updated = await addWhitelist(idsToAdd);
      setWhitelist(updated);
      setNewIds('');
      setError(null);
    } catch (e) {
      setError('添加失败');
    } finally {
      setIsLoading(false);
    }
  };

  const handleDelete = async (id: string) => {
    if (!confirm(`确定要移除用户 ${id} 吗?`)) return;

    setIsLoading(true);
    try {
      const updated = await removeWhitelist(id);
      setWhitelist(updated);
      setError(null);
    } catch (e) {
      setError('移除失败');
    } finally {
      setIsLoading(false);
    }
  };

  if (!isOpen) return null;

  return (
    <div className="fixed inset-0 z-[110] flex items-center justify-center p-4">
      <div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={onClose} />
      
      <div className="relative bg-white dark:bg-gray-900 w-full max-w-2xl rounded-2xl shadow-2xl overflow-hidden flex flex-col max-h-[80vh]">
        <div className="flex justify-between items-center p-6 border-b border-gray-100 dark:border-gray-800">
          <h2 className="text-xl font-bold text-gray-800 dark:text-white flex items-center gap-2">
            用户白名单管理
            <span className="text-sm font-normal text-gray-500 bg-gray-100 dark:bg-gray-800 px-2 py-0.5 rounded-full">
              {whitelist.length} 人
            </span>
          </h2>
          <button onClick={onClose} className="p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-full">
            <X size={20} />
          </button>
        </div>

        <div className="flex-1 overflow-hidden flex flex-col p-6 gap-6">
           {/* Add Section */}
           <div className="space-y-3">
              <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
                批量添加 (每行一个,或用逗号/空格分隔)
              </label>
              <div className="flex gap-3">
                <textarea 
                  value={newIds}
                  onChange={(e) => setNewIds(e.target.value)}
                  placeholder="例如:\n1001\n1002\n1003"
                  className="flex-1 p-3 bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl focus:ring-2 focus:ring-black dark:focus:ring-white outline-none min-h-[100px] font-mono text-sm resize-none"
                />
              </div>
              <button 
                  onClick={handleAdd}
                  disabled={isLoading || !newIds.trim()}
                  className="w-full py-2 bg-black dark:bg-white text-white dark:text-black rounded-lg hover:opacity-90 transition-opacity font-medium disabled:opacity-50 flex items-center justify-center gap-2"
                >
                  <Plus size={18} />
                  添加至白名单
              </button>
           </div>

           {/* List Section */}
           <div className="flex-1 overflow-y-auto border border-gray-100 dark:border-gray-800 rounded-xl bg-gray-50 dark:bg-gray-800/50 p-4">
              <div className="flex justify-between items-center mb-3">
                 <h3 className="font-medium text-gray-700 dark:text-gray-300 text-sm">已授权用户</h3>
                 <button onClick={loadWhitelist} className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 p-1">
                   <RefreshCw size={14} className={isLoading ? "animate-spin" : ""} />
                 </button>
              </div>
              
              {error ? (
                <div className="text-red-500 text-center py-4 text-sm">{error}</div>
              ) : (
                <div className="grid grid-cols-2 md:grid-cols-3 gap-2">
                  {whitelist.map(id => (
                    <div key={id} className="bg-white dark:bg-gray-800 px-3 py-2 rounded border border-gray-100 dark:border-gray-700 flex justify-between items-center group">
                      <span className="font-mono text-sm">{id}</span>
                      <button 
                        onClick={() => handleDelete(id)}
                        className="text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity p-1"
                        title="移除用户"
                      >
                        <Trash2 size={14} />
                      </button>
                    </div>
                  ))}
                </div>
              )}
           </div>
        </div>
      </div>
    </div>
  );
};

export default WhitelistModal;