import { useCallback, useEffect, useMemo, useState } from 'react'; import { Copy, ExternalLink, Loader2, Trash2, UserPlus, Users, } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { toast } from 'sonner'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Item, ItemActions, ItemContent, ItemDescription, ItemMedia, ItemTitle, } from '@/components/ui/item'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import type { CurrentWorkspace, WorkspaceInvitation, WorkspaceMembership, WorkspaceRole, } from '@/app/infra/entities/workspace'; import { backendClient, systemInfo } from '@/app/infra/http'; import { PanelBody, PanelToolbar, } from '@/app/home/components/settings-dialog/panel-layout'; interface WorkspaceSettingsPanelProps { active: boolean; } const ASSIGNABLE_ROLES: Exclude[] = [ 'admin', 'developer', 'operator', 'viewer', ]; export default function WorkspaceSettingsPanel({ active, }: WorkspaceSettingsPanelProps) { const { t } = useTranslation(); const [workspaceInfo, setWorkspaceInfo] = useState( null, ); const [members, setMembers] = useState([]); const [invitations, setInvitations] = useState([]); const [loading, setLoading] = useState(false); const [inviteEmail, setInviteEmail] = useState(''); const [inviteRole, setInviteRole] = useState>('viewer'); const [inviteLoading, setInviteLoading] = useState(false); const [oneTimeInviteLink, setOneTimeInviteLink] = useState( null, ); const permissions = useMemo( () => new Set(workspaceInfo?.permissions ?? []), [workspaceInfo], ); const isCloudProjection = workspaceInfo?.workspace.source === 'cloud_projection'; const canViewMembers = permissions.has('member.view'); const canInvite = permissions.has('member.invite'); const canUpdateMembers = permissions.has('member.update_role'); const canRemoveMembers = permissions.has('member.remove'); const cloudPortalURL = workspaceInfo ? `${systemInfo.cloud_service_url.replace(/\/$/, '')}/cloud?workspace=${encodeURIComponent(workspaceInfo.workspace.uuid)}&step=plan` : ''; const loadWorkspace = useCallback(async () => { setLoading(true); try { const current = await backendClient.getCurrentWorkspace(); setWorkspaceInfo(current); const [memberResponse, invitationResponse] = await Promise.all([ current.permissions.includes('member.view') ? backendClient.getWorkspaceMembers(current.workspace.uuid) : Promise.resolve({ members: [] }), current.permissions.includes('member.invite') ? backendClient.getWorkspaceInvitations(current.workspace.uuid) : Promise.resolve({ invitations: [] }), ]); setMembers(memberResponse.members); setInvitations(invitationResponse.invitations); } catch { toast.error(t('workspace.loadFailed')); } finally { setLoading(false); } }, [t]); useEffect(() => { if (active) void loadWorkspace(); }, [active, loadWorkspace]); async function createInvitation() { if (!workspaceInfo || !inviteEmail.trim()) return; setInviteLoading(true); try { const response = await backendClient.createWorkspaceInvitation( workspaceInfo.workspace.uuid, inviteEmail.trim(), inviteRole, ); setOneTimeInviteLink(response.link); setInviteEmail(''); await loadWorkspace(); toast.success(t(`workspace.delivery.${response.delivery.status}`)); } catch { toast.error(t('workspace.invitationCreateFailed')); } finally { setInviteLoading(false); } } async function copyInvitationLink() { if (!oneTimeInviteLink) return; await navigator.clipboard.writeText(oneTimeInviteLink); toast.success(t('workspace.invitationCopied')); } async function updateMemberRole( member: WorkspaceMembership, role: WorkspaceRole, ) { if (!workspaceInfo || member.role === role) return; try { await backendClient.updateWorkspaceMemberRole( workspaceInfo.workspace.uuid, member.account_uuid, role, ); await loadWorkspace(); toast.success(t('workspace.memberUpdated')); } catch { toast.error(t('workspace.memberUpdateFailed')); } } async function removeMember(member: WorkspaceMembership) { if (!workspaceInfo) return; if (!window.confirm(t('workspace.removeMemberConfirm'))) return; try { await backendClient.removeWorkspaceMember( workspaceInfo.workspace.uuid, member.account_uuid, ); await loadWorkspace(); toast.success(t('workspace.memberRemoved')); } catch { toast.error(t('workspace.memberRemoveFailed')); } } async function revokeInvitation(invitation: WorkspaceInvitation) { if (!workspaceInfo) return; try { await backendClient.revokeWorkspaceInvitation( workspaceInfo.workspace.uuid, invitation.uuid, ); await loadWorkspace(); toast.success(t('workspace.invitationRevoked')); } catch { toast.error(t('workspace.invitationRevokeFailed')); } } if (loading && !workspaceInfo) { return (
); } return ( <>

{workspaceInfo?.workspace.name ?? t('workspace.title')}

{t( isCloudProjection ? 'workspace.cloudManagedDescription' : 'workspace.ossSingletonDescription', )}

{workspaceInfo && ( {t(`workspace.roles.${workspaceInfo.membership.role}`)} )} {isCloudProjection && workspaceInfo && ( )}
{canInvite && (

{t('workspace.inviteMember')}

{t('workspace.inviteDescription')}

setInviteEmail(event.target.value)} placeholder={t('workspace.emailPlaceholder')} className="flex-1" />
{oneTimeInviteLink && (

{t('workspace.oneTimeLinkWarning')}

)}
)} {canViewMembers && (

{t('workspace.members')}

{members.map((member) => { const isSelf = member.account_uuid === workspaceInfo?.membership.account_uuid; return ( {member.email} {isSelf && ( {t('workspace.you')} )} {t(`workspace.roles.${member.role}`)} {canUpdateMembers && member.role !== 'owner' && ( )} {canRemoveMembers && !isSelf && member.role !== 'owner' && ( )} ); })}
)} {canInvite && invitations.length > 0 && (

{t('workspace.pendingInvitations')}

{invitations.map((invitation) => ( {invitation.normalized_email} {t(`workspace.roles.${invitation.role}`)} ยท{' '} {t('workspace.expiresAt', { date: new Date(invitation.expires_at).toLocaleString(), })} ))}
)}
); }