/** * Copyright (c) 2023-present Plane Software, Inc. and contributors * SPDX-License-Identifier: AGPL-3.0-only * See the LICENSE file for details. */ import { Fragment, useState } from "react"; import type { ReactNode } from "react"; import { Dialog, Transition } from "@headlessui/react"; import useSWR from "swr"; import { Loader, ShieldCheck, Trash2, UsersRound, X } from "lucide-react"; // plane imports import { Button } from "@plane/propel/button"; import { setToast, TOAST_TYPE } from "@plane/propel/toast"; import { InstanceWorkspaceService } from "@plane/services"; import type { TInstanceWorkspaceFeature, TInstanceWorkspaceMember } from "@plane/types"; import { ToggleSwitch } from "@plane/ui"; import { cn } from "@plane/utils"; // hooks import { useWorkspace } from "@/hooks/store"; type TWorkspaceAdminModalProps = { isOpen: boolean; onClose: () => void; workspaceId: string | null; }; const instanceWorkspaceService = new InstanceWorkspaceService(); const ROLE_OPTIONS = [ { label: "Гость", value: 5 }, { label: "Участник", value: 15 }, { label: "Администратор", value: 20 }, ]; const ROLE_LABELS: Record = { 5: "Гость", 15: "Участник", 20: "Администратор", }; const ACCESS_MODE_LABELS: Record = { all_workspace_members: "Весь воркспейс", admins_only: "Только админы", selected_projects: "По контурам", selected_members: "По людям", }; function getMemberName(member: TInstanceWorkspaceMember) { return member.member.display_name || member.member.email || "Пользователь"; } function getErrorMessage(error: unknown, fallback: string) { if (error && typeof error === "object" && "error" in error && typeof error.error === "string") return error.error; return fallback; } function WorkspaceModalShell(props: TWorkspaceAdminModalProps & { children: ReactNode; title: string; icon: ReactNode }) { const { children, icon, isOpen, onClose, title, workspaceId } = props; const { getWorkspaceById } = useWorkspace(); const workspace = workspaceId ? getWorkspaceById(workspaceId) : undefined; return (
{icon}
{title}
{workspace ? `${workspace.name} / [${workspace.slug}]` : "Воркспейс"}
{children}
); } export function WorkspaceMembersModal(props: TWorkspaceAdminModalProps) { const { isOpen, onClose, workspaceId } = props; const [mutatingMemberId, setMutatingMemberId] = useState(null); const { data, isLoading, mutate } = useSWR( isOpen && workspaceId ? ["INSTANCE_WORKSPACE_MEMBERS", workspaceId] : null, () => instanceWorkspaceService.listMembers(workspaceId as string) ); const handleRoleChange = async (workspaceMemberId: string, role: number) => { if (!workspaceId) return; setMutatingMemberId(workspaceMemberId); try { await instanceWorkspaceService.updateMemberRole(workspaceId, workspaceMemberId, role); await mutate(); setToast({ type: TOAST_TYPE.SUCCESS, title: "Роль участника обновлена" }); } catch (error) { setToast({ type: TOAST_TYPE.ERROR, title: "Не удалось обновить роль", message: getErrorMessage(error, "Проверьте ограничения по администраторам воркспейса и проектов."), }); } finally { setMutatingMemberId(null); } }; const handleRemove = async (workspaceMember: TInstanceWorkspaceMember) => { if (!workspaceId) return; const confirmed = window.confirm(`Отключить ${getMemberName(workspaceMember)} от этого воркспейса и его проектов?`); if (!confirmed) return; setMutatingMemberId(workspaceMember.id); try { await instanceWorkspaceService.removeMember(workspaceId, workspaceMember.id); await mutate(); setToast({ type: TOAST_TYPE.SUCCESS, title: "Доступ к воркспейсу отключен" }); } catch (error) { setToast({ type: TOAST_TYPE.ERROR, title: "Не удалось отключить участника", message: getErrorMessage(error, "Пользователь может быть единственным администратором."), }); } finally { setMutatingMemberId(null); } }; return ( }> {isLoading ? (
) : (
Пользователь
Роль
Проекты
Админ проектов
Доступ
{(data ?? []).map((workspaceMember) => (
{getMemberName(workspaceMember)}
{workspaceMember.member.email}
{workspaceMember.active_project_count}
{workspaceMember.admin_project_count}
))}
{(data ?? []).length === 0 &&
Активных участников нет
}
)}
); } export function WorkspaceFeaturesModal(props: TWorkspaceAdminModalProps) { const { isOpen, workspaceId } = props; const [mutatingFeatureKey, setMutatingFeatureKey] = useState(null); const { data, isLoading, mutate } = useSWR( isOpen && workspaceId ? ["INSTANCE_WORKSPACE_FEATURES", workspaceId] : null, () => instanceWorkspaceService.retrieveFeatures(workspaceId as string) ); const handleToggle = async (feature: TInstanceWorkspaceFeature) => { if (!workspaceId) return; setMutatingFeatureKey(feature.key); try { await instanceWorkspaceService.updateFeature(workspaceId, feature.key, !feature.is_enabled); await mutate(); setToast({ type: TOAST_TYPE.SUCCESS, title: feature.is_enabled ? "Фича отключена для воркспейса" : "Фича выдана воркспейсу", }); } catch (error) { setToast({ type: TOAST_TYPE.ERROR, title: "Не удалось обновить доступ", message: getErrorMessage(error, "Попробуйте еще раз."), }); } finally { setMutatingFeatureKey(null); } }; return ( }> {isLoading ? (
) : (
{(data?.features ?? []).map((feature) => (
{feature.title}
{feature.is_enabled ? "Доступ выдан" : "Не выдано"}
{feature.description}
Workspace: {feature.workspace_setting_enabled ? "включено" : "выключено"} Доступ: {ACCESS_MODE_LABELS[feature.access_mode]} OpenAI key: {feature.has_workspace_key ? "есть" : "нет"}
handleToggle(feature)} size="sm" disabled={mutatingFeatureKey === feature.key} />
))} {(data?.features ?? []).length === 0 &&
Функции не найдены
}
)}
); }