import React, { useState } from 'react'; import { useSpaceStore } from '../../../stores/spaceStore'; import { api } from '../../../api/client'; import { PermissionBits, stringToPermissions, permissionsToString } from '../../../utils/permissions'; import type { Role } from '@backspace/shared'; // ─── Permission display groups ───────────────────────────────────────────── interface PermDef { bit: bigint; label: string; } const PERMISSION_GROUPS: { name: string; perms: PermDef[] }[] = [ { name: 'General', perms: [ { bit: PermissionBits.ADMINISTRATOR, label: 'Administrator' }, { bit: PermissionBits.VIEW_CHANNEL, label: 'View Channels' }, { bit: PermissionBits.MANAGE_CHANNELS, label: 'Manage Channels' }, { bit: PermissionBits.MANAGE_ROLES, label: 'Manage Roles' }, { bit: PermissionBits.MANAGE_SPACE, label: 'Manage Space' }, { bit: PermissionBits.CREATE_INVITE, label: 'Create Invite' }, { bit: PermissionBits.KICK_MEMBERS, label: 'Kick Members' }, { bit: PermissionBits.BAN_MEMBERS, label: 'Ban Members' }, ], }, { name: 'Text', perms: [ { bit: PermissionBits.SEND_MESSAGES, label: 'Send Messages' }, { bit: PermissionBits.MANAGE_MESSAGES, label: 'Manage Messages' }, { bit: PermissionBits.ATTACH_FILES, label: 'Attach Files' }, { bit: PermissionBits.READ_MESSAGE_HISTORY, label: 'Read Message History' }, { bit: PermissionBits.ADD_REACTIONS, label: 'Add Reactions' }, ], }, { name: 'Voice', perms: [ { bit: PermissionBits.CONNECT, label: 'Connect' }, { bit: PermissionBits.SPEAK, label: 'Speak' }, { bit: PermissionBits.MUTE_MEMBERS, label: 'Mute Members' }, { bit: PermissionBits.DEAFEN_MEMBERS, label: 'Deafen Members' }, { bit: PermissionBits.MOVE_MEMBERS, label: 'Move Members' }, { bit: PermissionBits.USE_VOICE_ACTIVITY, label: 'Voice Activity' }, { bit: PermissionBits.STREAM, label: 'Stream' }, ], }, ]; const PRESET_COLORS = [ '#b9bbbe', '#a5f3c4', '#ffc9a9', '#c4b5fd', '#93c5fd', '#fbbf24', '#fda4af', '#f87171', '#60a5fa', '#34d399', ]; // ─── Component ────────────────────────────────────────────────────────────── interface RolesPanelProps { spaceId: string; } export function RolesPanel({ spaceId }: RolesPanelProps) { const roles = useSpaceStore((s) => s.roles); const loadSpaceDetail = useSpaceStore((s) => s.loadSpaceDetail); const [editingRoleId, setEditingRoleId] = useState(null); const [creating, setCreating] = useState(false); const [error, setError] = useState(''); // Sort: non-everyone roles by position desc, @everyone always last const sortedRoles = [...roles].sort((a, b) => { const aIsEveryone = a.id === spaceId; const bIsEveryone = b.id === spaceId; if (aIsEveryone) return 1; if (bIsEveryone) return -1; return b.position - a.position; }); const handleCreateRole = async () => { setCreating(true); setError(''); try { const newRole = await api.roles.create(spaceId, { name: 'new role' }); await loadSpaceDetail(spaceId); setEditingRoleId(newRole.id); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to create role'); } finally { setCreating(false); } }; if (editingRoleId) { const role = roles.find((r) => r.id === editingRoleId); if (!role) { setEditingRoleId(null); return null; } return ( setEditingRoleId(null)} onDeleted={() => setEditingRoleId(null)} /> ); } return (
{error && (
{error}
)}
{sortedRoles.map((role) => { const isEveryone = role.id === spaceId; return ( ); })}
); } // ─── Role Edit View ───────────────────────────────────────────────────────── interface RoleEditViewProps { role: Role; spaceId: string; onBack: () => void; onDeleted: () => void; } function RoleEditView({ role, spaceId, onBack, onDeleted }: RoleEditViewProps) { const loadSpaceDetail = useSpaceStore((s) => s.loadSpaceDetail); const isEveryone = role.id === spaceId; const [draftName, setDraftName] = useState(role.name); const [draftColor, setDraftColor] = useState(role.color); const [draftPermissions, setDraftPermissions] = useState( stringToPermissions(role.permissions) ); const [saving, setSaving] = useState(false); const [deleting, setDeleting] = useState(false); const [saveError, setSaveError] = useState(''); const [saveSuccess, setSaveSuccess] = useState(false); const [confirmDelete, setConfirmDelete] = useState(false); const hasNameChange = !isEveryone && draftName.trim() !== role.name; const hasColorChange = !isEveryone && draftColor !== role.color; const hasPermChange = permissionsToString(draftPermissions) !== (role.permissions ?? '0'); const hasChanges = hasNameChange || hasColorChange || hasPermChange; const togglePermission = (bit: bigint) => { setDraftPermissions((prev) => (prev & bit) !== 0n ? prev & ~bit : prev | bit); }; const handleSave = async () => { setSaving(true); setSaveError(''); setSaveSuccess(false); try { const data: { name?: string; color?: string; permissions?: string } = {}; if (hasNameChange) data.name = draftName.trim(); if (hasColorChange) data.color = draftColor; if (hasPermChange) data.permissions = permissionsToString(draftPermissions); await api.roles.update(spaceId, role.id, data); await loadSpaceDetail(spaceId); setSaveSuccess(true); setTimeout(() => setSaveSuccess(false), 2000); } catch (err) { setSaveError(err instanceof Error ? err.message : 'Failed to save role'); } finally { setSaving(false); } }; const handleDiscard = () => { setDraftName(role.name); setDraftColor(role.color); setDraftPermissions(stringToPermissions(role.permissions)); setSaveError(''); }; const handleDelete = async () => { if (!confirmDelete) { setConfirmDelete(true); return; } setDeleting(true); setSaveError(''); try { await api.roles.delete(spaceId, role.id); await loadSpaceDetail(spaceId); onDeleted(); } catch (err) { setSaveError(err instanceof Error ? err.message : 'Failed to delete role'); setConfirmDelete(false); } finally { setDeleting(false); } }; return (
{/* Back button */}
{/* Role Name (not editable for @everyone) */} {!isEveryone && (
setDraftName(e.target.value)} className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary text-sm" />
)} {/* Role Color (not editable for @everyone) */} {!isEveryone && (
{PRESET_COLORS.map((c) => (
)} {/* Permissions */}
{PERMISSION_GROUPS.map((group) => (
{group.name}
{group.perms.map((perm) => { const isOn = (draftPermissions & perm.bit) !== 0n; const isAdmin = perm.bit === PermissionBits.ADMINISTRATOR; return (
))}
{/* Save / Discard */} {saveError && (
{saveError}
)} {saveSuccess && (
Role saved
)} {hasChanges && (
)} {/* Delete Role (not for @everyone) */} {!isEveryone && (
)}
); }