WhitelistModal.tsx
5.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
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;