import { useEffect, useState, useCallback } from 'react'; import { useParams, Link } from 'react-router-dom'; import { useRoleStore, type Role, type CreateRoleData, type UpdateRoleData } from '../stores/role.ts'; // Permission bitflags const PERMS = { VIEW_CHANNEL: 1, SEND_MESSAGES: 2, MANAGE_MESSAGES: 4, KICK_MEMBERS: 8, BAN_MEMBERS: 16, MANAGE_SERVER: 32, MANAGE_CHANNELS: 64, ADMINISTRATOR: 128, CONNECT_VOICE: 256, SPEAK_VOICE: 512, SHARE_SCREEN: 1024, MUTE_MEMBERS: 2048, CREATE_INSTANT_INVITE: 4096, CHANGE_NICKNAME: 8192, MANAGE_NICKNAMES: 16384, MANAGE_ROLES: 32768, MANAGE_WEBHOOKS: 65536, EMBED_LINKS: 131072, ATTACH_FILES: 262144, ADD_REACTIONS: 524288, USE_EXTERNAL_EMOJIS: 1048576, MENTION_EVERYONE: 2097152, } as const; const PERM_CATEGORIES = [ { name: 'General', perms: [ { key: 'VIEW_CHANNEL', label: 'View Channel', flag: PERMS.VIEW_CHANNEL }, { key: 'SEND_MESSAGES', label: 'Send Messages', flag: PERMS.SEND_MESSAGES }, { key: 'MANAGE_MESSAGES', label: 'Manage Messages', flag: PERMS.MANAGE_MESSAGES }, { key: 'ADD_REACTIONS', label: 'Add Reactions', flag: PERMS.ADD_REACTIONS }, { key: 'EMBED_LINKS', label: 'Embed Links', flag: PERMS.EMBED_LINKS }, { key: 'ATTACH_FILES', label: 'Attach Files', flag: PERMS.ATTACH_FILES }, { key: 'MENTION_EVERYONE', label: 'Mention @everyone', flag: PERMS.MENTION_EVERYONE }, { key: 'USE_EXTERNAL_EMOJIS', label: 'Use External Emojis', flag: PERMS.USE_EXTERNAL_EMOJIS }, ], }, { name: 'Moderation', perms: [ { key: 'KICK_MEMBERS', label: 'Kick Members', flag: PERMS.KICK_MEMBERS }, { key: 'BAN_MEMBERS', label: 'Ban Members', flag: PERMS.BAN_MEMBERS }, { key: 'MUTE_MEMBERS', label: 'Mute Members', flag: PERMS.MUTE_MEMBERS }, { key: 'MANAGE_NICKNAMES', label: 'Manage Nicknames', flag: PERMS.MANAGE_NICKNAMES }, { key: 'CHANGE_NICKNAME', label: 'Change Nickname', flag: PERMS.CHANGE_NICKNAME }, ], }, { name: 'Server', perms: [ { key: 'MANAGE_SERVER', label: 'Manage Server', flag: PERMS.MANAGE_SERVER }, { key: 'MANAGE_CHANNELS', label: 'Manage Channels', flag: PERMS.MANAGE_CHANNELS }, { key: 'MANAGE_ROLES', label: 'Manage Roles', flag: PERMS.MANAGE_ROLES }, { key: 'MANAGE_WEBHOOKS', label: 'Manage Webhooks', flag: PERMS.MANAGE_WEBHOOKS }, { key: 'CREATE_INSTANT_INVITE', label: 'Create Instant Invite', flag: PERMS.CREATE_INSTANT_INVITE }, { key: 'ADMINISTRATOR', label: 'Administrator', flag: PERMS.ADMINISTRATOR }, ], }, { name: 'Voice', perms: [ { key: 'CONNECT_VOICE', label: 'Connect Voice', flag: PERMS.CONNECT_VOICE }, { key: 'SPEAK_VOICE', label: 'Speak Voice', flag: PERMS.SPEAK_VOICE }, { key: 'SHARE_SCREEN', label: 'Share Screen', flag: PERMS.SHARE_SCREEN }, ], }, ]; const GRUVBOX_COLORS = [ '#fb4934', '#b8bb26', '#fabd2f', '#83a598', '#d3869b', '#8ec07c', '#fe8019', '#ebdbb2', '#a89984', '#928374', '#282828', ]; function hasPerm(permissions: number, flag: number): boolean { return (permissions & flag) !== 0; } function permSummary(permissions: number): string { if (hasPerm(permissions, PERMS.ADMINISTRATOR)) return 'ADMINISTRATOR'; const active: string[] = []; if (hasPerm(permissions, PERMS.MANAGE_SERVER)) active.push('MGR_SERVER'); if (hasPerm(permissions, PERMS.MANAGE_CHANNELS)) active.push('MGR_CHAN'); if (hasPerm(permissions, PERMS.MANAGE_ROLES)) active.push('MGR_ROLES'); if (hasPerm(permissions, PERMS.MANAGE_MESSAGES)) active.push('MGR_MSG'); if (hasPerm(permissions, PERMS.KICK_MEMBERS)) active.push('KICK'); if (hasPerm(permissions, PERMS.BAN_MEMBERS)) active.push('BAN'); if (hasPerm(permissions, PERMS.MUTE_MEMBERS)) active.push('MUTE'); return active.length > 0 ? active.join('+') : 'BASIC'; } interface RoleFormProps { serverId: string; role?: Role; onClose: () => void; } function RoleForm({ serverId, role, onClose }: RoleFormProps) { const createRole = useRoleStore((s) => s.createRole); const updateRole = useRoleStore((s) => s.updateRole); const loading = useRoleStore((s) => s.loading); const [name, setName] = useState(role?.name ?? ''); const [color, setColor] = useState(role?.color ?? '#ebdbb2'); const [position, setPosition] = useState(role?.position ?? 0); const [permissions, setPermissions] = useState(role?.permissions ?? 0); const togglePerm = useCallback((flag: number) => { setPermissions((prev) => prev ^ flag); }, []); const handleAdminToggle = useCallback(() => { setPermissions((prev) => { if (hasPerm(prev, PERMS.ADMINISTRATOR)) return 0; return 0xFFFFFFFF; // grant all }); }, []); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!name.trim()) return; try { if (role) { const data: UpdateRoleData = { name: name.trim(), color, permissions, position }; await updateRole(serverId, role.id, data); } else { const data: CreateRoleData = { name: name.trim(), color, permissions, position }; await createRole(serverId, data); } onClose(); } catch { // error in store } }; return (

{'>'} {role ? 'EDIT ROLE' : 'CREATE ROLE'}

setName(e.target.value.slice(0, 64))} maxLength={64} className="terminal-input w-full" placeholder="role-name" autoFocus />
{GRUVBOX_COLORS.map((c) => (
setColor(e.target.value)} className="terminal-input w-full text-xs" placeholder="#ebdbb2" />
setPosition(parseInt(e.target.value) || 0)} className="terminal-input w-24" />
{/* Administrator all-or-nothing */}
{/* Category groups */} {!hasPerm(permissions, PERMS.ADMINISTRATOR) && PERM_CATEGORIES.filter((cat) => cat.name !== 'Server' || true).map((cat) => (

[{cat.name.toUpperCase()}]

{cat.perms .filter((p) => p.key !== 'ADMINISTRATOR') .map((p) => ( ))}
))}
); } export function RoleManager({ serverId: propServerId }: { serverId?: string } = {}) { const { serverId: paramServerId } = useParams<{ serverId: string }>(); const serverId = propServerId || paramServerId; const roles = useRoleStore((s) => s.roles); const loading = useRoleStore((s) => s.loading); const error = useRoleStore((s) => s.error); const fetchRoles = useRoleStore((s) => s.fetchRoles); const deleteRole = useRoleStore((s) => s.deleteRole); const [showForm, setShowForm] = useState(false); const [editingRole, setEditingRole] = useState(null); useEffect(() => { if (serverId) fetchRoles(serverId); }, [serverId, fetchRoles]); const handleDelete = async (role: Role) => { if (!serverId) return; if (!window.confirm(`Delete role "${role.name}"? This cannot be undone.`)) return; try { await deleteRole(serverId, role.id); } catch { // error in store } }; if (!serverId) { return (

ERR: no server selected

); } return (
            {'┌──────────────────────────────────┐\n'}
            {'│      === ROLE MANAGER ===        │\n'}
            {'└──────────────────────────────────┘'}
          
{error && (

ERR: {error}

)}
{loading && roles.length === 0 && (

[loading roles...]

)} {!loading && roles.length === 0 && (

[no roles configured]

)}
{roles.map((role) => (
{role.name} {role.is_default && ( [DEFAULT] )} POS:{role.position}

perms: {permSummary(role.permissions)}

{!role.is_default && ( )}
))}
{'<'} [BACK TO CHAT]
{showForm && ( { setShowForm(false); setEditingRole(null); }} /> )}
); }