Discord ma zaawansowany system roles z permissions - kluczowy dla moderacji i organizacji community.
1// Bitwise permissions like Discord
2enum PermissionBits {
3 CREATE_INSTANT_INVITE = 1 << 0,
4 KICK_MEMBERS = 1 << 1,
5 BAN_MEMBERS = 1 << 2,
6 ADMINISTRATOR = 1 << 3,
7 MANAGE_CHANNELS = 1 << 4,
8 MANAGE_SERVER = 1 << 5,
9 ADD_REACTIONS = 1 << 6,
10 VIEW_AUDIT_LOG = 1 << 7,
11 PRIORITY_SPEAKER = 1 << 8, // voice
12 STREAM = 1 << 9, // voice
13 VIEW_CHANNEL = 1 << 10,
14 SEND_MESSAGES = 1 << 11,
15 SEND_TTS_MESSAGES = 1 << 12,
16 MANAGE_MESSAGES = 1 << 13,
17 EMBED_LINKS = 1 << 14,
18 ATTACH_FILES = 1 << 15,
19 READ_MESSAGE_HISTORY = 1 << 16,
20 MENTION_EVERYONE = 1 << 17,
21 USE_EXTERNAL_EMOJIS = 1 << 18,
22 CONNECT = 1 << 20, // voice
23 SPEAK = 1 << 21, // voice
24 MUTE_MEMBERS = 1 << 22, // voice
25 DEAFEN_MEMBERS = 1 << 23, // voice
26 MOVE_MEMBERS = 1 << 24, // voice
27 USE_VAD = 1 << 25, // voice activity detection
28 CHANGE_NICKNAME = 1 << 26,
29 MANAGE_NICKNAMES = 1 << 27,
30 MANAGE_ROLES = 1 << 28,
31 MANAGE_WEBHOOKS = 1 << 29,
32 MANAGE_EMOJIS = 1 << 30,
33}
34
35// Check if user has permission
36function hasPermission(
37 member: Member,
38 permission: PermissionBits,
39 roles: Role[],
40 channel?: Channel
41): boolean {
42 // Owner has all permissions
43 if (member.isOwner) return true;
44
45 // Get all member roles
46 const memberRoles = roles.filter(r => member.roles.includes(r.id));
47
48 // Check for ADMINISTRATOR
49 if (memberRoles.some(r => r.permissions & PermissionBits.ADMINISTRATOR)) {
50 return true;
51 }
52
53 // Calculate base permissions from roles
54 let permissions = 0;
55 memberRoles.forEach(role => {
56 permissions |= role.permissions;
57 });
58
59 // Apply channel-specific overrides
60 if (channel) {
61 channel.permissions.forEach(override => {
62 if (member.roles.includes(override.roleId)) {
63 // Apply denies first
64 override.deny.forEach(perm => {
65 permissions &= ~perm;
66 });
67 // Then apply allows
68 override.allow.forEach(perm => {
69 permissions |= perm;
70 });
71 }
72 });
73 }
74
75 return (permissions & permission) === permission;
76}1'use client';
2
3import { useState } from 'react';
4
5interface RoleManagerProps {
6 server: Server;
7 onCreateRole: () => void;
8 onEditRole: (roleId: string) => void;
9 onDeleteRole: (roleId: string) => void;
10}
11
12export default function RoleManager({
13 server,
14 onCreateRole,
15 onEditRole,
16 onDeleteRole
17}: RoleManagerProps) {
18 const [selectedRoleId, setSelectedRoleId] = useState<string | null>(null);
19
20 const selectedRole = server.roles.find(r => r.id === selectedRoleId);
21
22 return (
23 <div className="flex h-screen bg-gray-800">
24 {/* Roles List */}
25 <div className="w-64 bg-gray-900 p-4">
26 <div className="flex items-center justify-between mb-4">
27 <h3 className="font-bold text-white">Roles</h3>
28 <button
29 onClick={onCreateRole}
30 className="w-8 h-8 bg-green-600 hover:bg-green-700 rounded-full text-white"
31 >
32 +
33 </button>
34 </div>
35
36 <div className="space-y-1">
37 {server.roles
38 .sort((a, b) => b.position - a.position)
39 .map(role => (
40 <button
41 key={role.id}
42 onClick={() => setSelectedRoleId(role.id)}
43 className={`w-full text-left px-3 py-2 rounded flex items-center justify-between ${
44 selectedRoleId === role.id
45 ? 'bg-gray-700'
46 : 'hover:bg-gray-800'
47 }`}
48 >
49 <div className="flex items-center gap-2 flex-1 min-w-0">
50 <div
51 className="w-3 h-3 rounded-full"
52 style={{ backgroundColor: role.color || '#99AAB5' }}
53 />
54 <span className="text-white text-sm truncate">
55 {role.name}
56 </span>
57 </div>
58 <span className="text-xs text-gray-400">
59 {role.memberCount}
60 </span>
61 </button>
62 ))}
63 </div>
64
65 <div className="mt-4 pt-4 border-t border-gray-800 text-xs text-gray-400">
66 Drag to reorder roles
67 </div>
68 </div>
69
70 {/* Role Editor */}
71 {selectedRole ? (
72 <div className="flex-1 p-6 overflow-y-auto">
73 <div className="max-w-2xl">
74 <h2 className="text-2xl font-bold text-white mb-6">
75 Edit Role: {selectedRole.name}
76 </h2>
77
78 {/* Display */}
79 <div className="bg-gray-700 rounded-lg p-4 mb-6">
80 <h3 className="font-bold text-white mb-3">Display</h3>
81
82 <div className="mb-4">
83 <label className="block text-sm text-gray-300 mb-2">
84 Role Name
85 </label>
86 <input
87 type="text"
88 value={selectedRole.name}
89 className="w-full bg-gray-900 text-white px-3 py-2 rounded"
90 />
91 </div>
92
93 <div className="mb-4">
94 <label className="block text-sm text-gray-300 mb-2">
95 Role Color
96 </label>
97 <div className="flex items-center gap-3">
98 <input
99 type="color"
100 value={selectedRole.color || '#99AAB5'}
101 className="w-16 h-10 rounded cursor-pointer"
102 />
103 <span className="text-gray-400 text-sm">
104 {selectedRole.color || 'Default'}
105 </span>
106 </div>
107 </div>
108
109 <div className="space-y-2">
110 <label className="flex items-center gap-2 text-sm text-gray-300">
111 <input
112 type="checkbox"
113 checked={selectedRole.isHoisted}
114 className="w-4 h-4"
115 />
116 Display role members separately from online members
117 </label>
118
119 <label className="flex items-center gap-2 text-sm text-gray-300">
120 <input
121 type="checkbox"
122 checked={selectedRole.isMentionable}
123 className="w-4 h-4"
124 />
125 Allow anyone to @mention this role
126 </label>
127 </div>
128 </div>
129
130 {/* Permissions */}
131 <div className="bg-gray-700 rounded-lg p-4">
132 <h3 className="font-bold text-white mb-3">Permissions</h3>
133
134 <div className="space-y-3">
135 {Object.entries(PermissionBits).map(([name, bit]) => {
136 const hasPermission = (selectedRole.permissions & bit) === bit;
137
138 return (
139 <label
140 key={name}
141 className="flex items-center gap-3 text-sm text-gray-300 hover:bg-gray-600 p-2 rounded cursor-pointer"
142 >
143 <input
144 type="checkbox"
145 checked={hasPermission}
146 className="w-4 h-4"
147 />
148 <div className="flex-1">
149 <div className="font-semibold">
150 {name.split('_').map(w => w.charAt(0) + w.slice(1).toLowerCase()).join(' ')}
151 </div>
152 <div className="text-xs text-gray-400">
153 {getPermissionDescription(name)}
154 </div>
155 </div>
156 </label>
157 );
158 })}
159 </div>
160 </div>
161
162 {/* Actions */}
163 <div className="mt-6 flex gap-3">
164 <button
165 onClick={() => onEditRole(selectedRole.id)}
166 className="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded font-semibold"
167 >
168 Save Changes
169 </button>
170 <button
171 onClick={() => onDeleteRole(selectedRole.id)}
172 className="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded font-semibold"
173 >
174 Delete Role
175 </button>
176 </div>
177 </div>
178 </div>
179 ) : (
180 <div className="flex-1 flex items-center justify-center text-gray-500">
181 Select a role to edit
182 </div>
183 )}
184 </div>
185 );
186}
187
188function getPermissionDescription(permission: string): string {
189 const descriptions: Record<string, string> = {
190 ADMINISTRATOR: 'Full access to all channels and settings',
191 MANAGE_SERVER: 'Change server name, region, and other settings',
192 MANAGE_ROLES: 'Create, edit, and delete roles lower than this one',
193 MANAGE_CHANNELS: 'Create, edit, and delete channels',
194 KICK_MEMBERS: 'Remove members from the server',
195 BAN_MEMBERS: 'Ban members from the server',
196 SEND_MESSAGES: 'Send messages in text channels',
197 MANAGE_MESSAGES: 'Delete messages sent by other members',
198 MENTION_EVERYONE: 'Use @everyone and @here mentions',
199 CONNECT: 'Connect to voice channels',
200 SPEAK: 'Speak in voice channels',
201 MUTE_MEMBERS: 'Mute members in voice channels',
202 DEAFEN_MEMBERS: 'Deafen members in voice channels',
203 // Add more descriptions...
204 };
205
206 return descriptions[permission] || 'Permission description';
207}1interface ModerationAction {
2 id: string;
3 type: 'kick' | 'ban' | 'timeout' | 'warn' | 'unban';
4 targetUserId: string;
5 moderatorId: string;
6 reason: string;
7 duration?: number; // for timeout/ban (seconds)
8 createdAt: Date;
9}
10
11interface ModeratorToolsProps {
12 server: Server;
13 targetMember: Member;
14 currentUserId: string;
15 onKick: (reason: string) => void;
16 onBan: (reason: string, duration?: number) => void;
17 onTimeout: (duration: number, reason: string) => void;
18}
19
20export default function ModeratorTools({
21 server,
22 targetMember,
23 currentUserId,
24 onKick,
25 onBan,
26 onTimeout
27}: ModeratorToolsProps) {
28 const [action, setAction] = useState<'kick' | 'ban' | 'timeout' | null>(null);
29 const [reason, setReason] = useState('');
30 const [duration, setDuration] = useState(3600); // 1 hour default
31
32 const currentMember = server.members.find(m => m.userId === currentUserId);
33 if (!currentMember) return null;
34
35 const canKick = hasPermission(currentMember, PermissionBits.KICK_MEMBERS, server.roles);
36 const canBan = hasPermission(currentMember, PermissionBits.BAN_MEMBERS, server.roles);
37
38 return (
39 <div className="bg-gray-800 rounded-lg p-4">
40 <h3 className="font-bold text-white mb-4">Moderator Actions</h3>
41
42 <div className="space-y-2 mb-4">
43 {canKick && (
44 <button
45 onClick={() => setAction('kick')}
46 className="w-full py-2 bg-orange-600 hover:bg-orange-700 text-white rounded font-semibold"
47 >
48 Kick from Server
49 </button>
50 )}
51
52 {canBan && (
53 <>
54 <button
55 onClick={() => setAction('ban')}
56 className="w-full py-2 bg-red-600 hover:bg-red-700 text-white rounded font-semibold"
57 >
58 Ban from Server
59 </button>
60
61 <button
62 onClick={() => setAction('timeout')}
63 className="w-full py-2 bg-yellow-600 hover:bg-yellow-700 text-white rounded font-semibold"
64 >
65 Timeout
66 </button>
67 </>
68 )}
69 </div>
70
71 {action && (
72 <div className="border-t border-gray-700 pt-4">
73 <h4 className="text-white font-semibold mb-3">
74 {action === 'kick' && 'Kick Member'}
75 {action === 'ban' && 'Ban Member'}
76 {action === 'timeout' && 'Timeout Member'}
77 </h4>
78
79 {action === 'timeout' && (
80 <div className="mb-3">
81 <label className="block text-sm text-gray-300 mb-2">
82 Duration
83 </label>
84 <select
85 value={duration}
86 onChange={(e) => setDuration(parseInt(e.target.value))}
87 className="w-full bg-gray-900 text-white px-3 py-2 rounded"
88 >
89 <option value={60}>60 seconds</option>
90 <option value={300}>5 minutes</option>
91 <option value={600}>10 minutes</option>
92 <option value={3600}>1 hour</option>
93 <option value={86400}>1 day</option>
94 <option value={604800}>1 week</option>
95 </select>
96 </div>
97 )}
98
99 <div className="mb-3">
100 <label className="block text-sm text-gray-300 mb-2">
101 Reason
102 </label>
103 <textarea
104 value={reason}
105 onChange={(e) => setReason(e.target.value)}
106 className="w-full bg-gray-900 text-white px-3 py-2 rounded resize-none"
107 rows={3}
108 />
109 </div>
110
111 <div className="flex gap-2">
112 <button
113 onClick={() => {
114 if (action === 'kick') onKick(reason);
115 if (action === 'ban') onBan(reason);
116 if (action === 'timeout') onTimeout(duration, reason);
117 setAction(null);
118 setReason('');
119 }}
120 disabled={!reason.trim()}
121 className="flex-1 py-2 bg-red-600 hover:bg-red-700 disabled:bg-gray-600 text-white rounded font-semibold"
122 >
123 Confirm
124 </button>
125 <button
126 onClick={() => setAction(null)}
127 className="px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded"
128 >
129 Cancel
130 </button>
131 </div>
132 </div>
133 )}
134 </div>
135 );
136}✅ Permission system - bitwise permissions jak Discord ✅ Role hierarchy - position determines priority ✅ Role manager - create, edit, delete roles ✅ Role colors - custom colors w member list ✅ Hoisted roles - separate display w member list ✅ Channel overrides - per-channel permission overrides ✅ Moderation tools - kick, ban, timeout ✅ Audit log - track moderator actions ✅ Permission checks - hasPermission helper
Masz teraz kompletny Discord clone z servers, voice, roles i moderation! 🎮
Do zobaczenia! 🚀