import React, { useState, useEffect } from 'react'; import { Lock, Globe, Users, UserPlus, Shield, Trash2, CheckCircle2, Copy, Key, X, RefreshCw, AlertCircle } from 'lucide-react'; import { api } from '../api/client'; export default function AccessManagementModal({ workspace, isOpen, onClose, onWorkspaceUpdated }) { const [accessData, setAccessData] = useState(null); const [loading, setLoading] = useState(true); const [visibility, setVisibility] = useState(workspace?.visibility || 'private'); const [savingVis, setSavingVis] = useState(false); // Invite state const [inviteEmail, setInviteEmail] = useState(''); const [inviteRole, setInviteRole] = useState('EDITOR'); const [inviting, setInviting] = useState(false); const [errorMsg, setErrorMsg] = useState(''); const [successMsg, setSuccessMsg] = useState(''); // Scoped Token state const [generatedToken, setGeneratedToken] = useState(''); const [copiedToken, setCopiedToken] = useState(false); useEffect(() => { if (isOpen && workspace?.id) { loadAccess(); } }, [isOpen, workspace?.id]); const loadAccess = async () => { setLoading(true); setErrorMsg(''); try { const data = await api.getWorkspaceAccess(workspace.id); setAccessData(data); setVisibility(data.visibility || 'private'); } catch (err) { setErrorMsg(`Could not load access data: ${err.message}`); } finally { setLoading(false); } }; const handleVisibilityChange = async (newVis) => { if (newVis === visibility) return; setSavingVis(true); setErrorMsg(''); setSuccessMsg(''); try { await api.updateWorkspaceVisibility(workspace.id, newVis); setVisibility(newVis); setSuccessMsg(`Workspace is now ${newVis.toUpperCase()}.`); if (onWorkspaceUpdated) { onWorkspaceUpdated({ ...workspace, visibility: newVis }); } } catch (err) { setErrorMsg(`Failed to update visibility: ${err.message}`); } finally { setSavingVis(false); } }; const handleInvite = async (e) => { e.preventDefault(); if (!inviteEmail.trim()) return; setInviting(true); setErrorMsg(''); setSuccessMsg(''); try { await api.addWorkspaceMember(workspace.id, inviteEmail.trim(), inviteRole); setInviteEmail(''); setSuccessMsg(`Collaborator added successfully.`); await loadAccess(); } catch (err) { setErrorMsg(`Could not add collaborator: ${err.message}`); } finally { setInviting(false); } }; const handleRemoveMember = async (memberId) => { try { await api.removeWorkspaceMember(workspace.id, memberId); setSuccessMsg('Collaborator removed.'); await loadAccess(); } catch (err) { setErrorMsg(`Failed to remove: ${err.message}`); } }; const handleUpdateRole = async (memberId, newRole) => { try { await api.updateWorkspaceMemberRole(workspace.id, memberId, newRole); setSuccessMsg(`Role updated to ${newRole}.`); await loadAccess(); } catch (err) { setErrorMsg(`Failed to update role: ${err.message}`); } }; const handleGenerateToken = () => { const token = `dw_ws_${workspace.id.replace(/-/g, '').slice(0, 12)}_${Math.random().toString(36).substring(2, 10)}`; setGeneratedToken(token); setCopiedToken(false); }; const handleCopyToken = () => { if (!generatedToken) return; navigator.clipboard.writeText(generatedToken); setCopiedToken(true); setTimeout(() => setCopiedToken(false), 2500); }; if (!isOpen) return null; return (
{/* Header */}

Manage Workspace Access {workspace?.name}

Control who can query, commit changes, and review Knowledge PRs in this repository.

{/* Alerts */} {errorMsg && (
{errorMsg}
)} {successMsg && (
{successMsg}
)}
{/* Section 1: Visibility Controls */}
{/* Section 2: Invite Collaborators */}
setInviteEmail(e.target.value)} required className="flex-1 bg-[#121726] border border-slate-800 rounded-xl px-3.5 py-2 text-xs text-slate-200 placeholder-slate-500 focus:outline-none focus:ring-1 focus:ring-emerald-500" />
{/* Section 3: Collaborators List */}
{/* Workspace Owner */}
{(accessData?.owner?.username || 'O')[0].toUpperCase()}
{accessData?.owner?.username || 'Workspace Owner'} {accessData?.owner?.email || 'owner@diffweave.io'}
OWNER
{/* Invited Members */} {(accessData?.members || []).map((m) => (
{m.email[0].toUpperCase()}
{m.email} Added {m.created_at ? new Date(m.created_at).toLocaleDateString() : 'recently'}
))} {(accessData?.members || []).length === 0 && (
No additional collaborators added yet. Invite coworkers above to pair on this workspace.
)}
{/* Section 4: Scoped API Access Tokens for AI & CLI */}

Generate a dedicated workspace token for Claude, Cursor, or terminal CI pipelines.

{generatedToken && (
{generatedToken}
)}
{/* Footer */}
); }