import { useState, useEffect, useCallback } from 'react'; import { AccessPermission, AccessLevel } from '../types'; import { listProjectPermissions, grantProjectAccess, updateProjectAccess, revokeProjectAccess, } from '../api'; import './AccessManagement.css'; interface AccessManagementProps { projectName: string; } export function AccessManagement({ projectName }: AccessManagementProps) { const [permissions, setPermissions] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); // Form state const [showAddForm, setShowAddForm] = useState(false); const [newUsername, setNewUsername] = useState(''); const [newLevel, setNewLevel] = useState('read'); const [newExpiresAt, setNewExpiresAt] = useState(''); const [submitting, setSubmitting] = useState(false); // Edit state const [editingUser, setEditingUser] = useState(null); const [editLevel, setEditLevel] = useState('read'); const [editExpiresAt, setEditExpiresAt] = useState(''); const loadPermissions = useCallback(async () => { try { setLoading(true); const data = await listProjectPermissions(projectName); setPermissions(data); setError(null); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load permissions'); } finally { setLoading(false); } }, [projectName]); useEffect(() => { loadPermissions(); }, [loadPermissions]); const handleGrant = async (e: React.FormEvent) => { e.preventDefault(); if (!newUsername.trim()) return; try { setSubmitting(true); setError(null); await grantProjectAccess(projectName, { username: newUsername.trim(), level: newLevel, expires_at: newExpiresAt || undefined, }); setSuccess(`Access granted to ${newUsername}`); setNewUsername(''); setNewLevel('read'); setNewExpiresAt(''); setShowAddForm(false); await loadPermissions(); setTimeout(() => setSuccess(null), 3000); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to grant access'); } finally { setSubmitting(false); } }; const handleUpdate = async (username: string) => { try { setSubmitting(true); setError(null); await updateProjectAccess(projectName, username, { level: editLevel, expires_at: editExpiresAt || null, }); setSuccess(`Updated access for ${username}`); setEditingUser(null); await loadPermissions(); setTimeout(() => setSuccess(null), 3000); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to update access'); } finally { setSubmitting(false); } }; const handleRevoke = async (username: string) => { if (!confirm(`Revoke access for ${username}?`)) return; try { setSubmitting(true); setError(null); await revokeProjectAccess(projectName, username); setSuccess(`Access revoked for ${username}`); await loadPermissions(); setTimeout(() => setSuccess(null), 3000); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to revoke access'); } finally { setSubmitting(false); } }; const startEdit = (permission: AccessPermission) => { setEditingUser(permission.user_id); setEditLevel(permission.level as AccessLevel); // Convert ISO date to local date format for date input setEditExpiresAt(permission.expires_at ? permission.expires_at.split('T')[0] : ''); }; const cancelEdit = () => { setEditingUser(null); setEditExpiresAt(''); }; const formatExpiration = (expiresAt: string | null) => { if (!expiresAt) return 'Never'; const date = new Date(expiresAt); const now = new Date(); const isExpired = date < now; return ( {date.toLocaleDateString()} {isExpired && ' (Expired)'} ); }; if (loading) { return
Loading permissions...
; } return (

Access Management

{error &&
{error}
} {success &&
{success}
} {showAddForm && (
setNewUsername(e.target.value)} placeholder="Enter username" required disabled={submitting} />
setNewExpiresAt(e.target.value)} disabled={submitting} min={new Date().toISOString().split('T')[0]} />
)}
{permissions.length === 0 ? (

No explicit permissions set. Only the project owner has access.

) : ( {permissions.map((p) => { const isTeamBased = p.source === 'team'; return ( ); })}
User Access Level Source Granted Expires Actions
{p.user_id} {editingUser === p.user_id && !isTeamBased ? ( ) : ( {p.level} )} {isTeamBased ? ( Team: {p.team_slug} ) : ( Explicit )} {new Date(p.created_at).toLocaleDateString()} {editingUser === p.user_id && !isTeamBased ? ( setEditExpiresAt(e.target.value)} disabled={submitting} min={new Date().toISOString().split('T')[0]} /> ) : ( formatExpiration(p.expires_at) )} {isTeamBased ? ( Via team ) : editingUser === p.user_id ? ( <> ) : ( <> )}
)}
); }