"use client"; // ============================================================================ // Workspace Settings Page // ============================================================================ import { useState, useEffect, useRef } from "react"; import { useParams, useRouter } from "next/navigation"; import { Loader2, Settings, Pencil, Trash2, Users, Shield, ChevronDown, UserPlus, Mail, Clock, Upload, ImageIcon } from "lucide-react"; import { DashboardHeader } from "@/components/layout/dashboard-header"; import { useWorkspace } from "@/components/providers/workspace-provider"; import { workspacesApi, membersApi, invitesApi } from "@/lib/api"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { PERMISSION_LABELS } from "@/lib/types/api"; import { PermissionModal } from "@/components/ui/permission-modal"; // ============================================================================ // Component // ============================================================================ export default function SettingsPage() { const params = useParams(); const router = useRouter(); const workspaceId = params?.workspaceId as string; const { currentWorkspace, refreshWorkspaces } = useWorkspace(); const [name, setName] = useState(currentWorkspace?.name || ""); const [isUpdating, setIsUpdating] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [deleteConfirm, setDeleteConfirm] = useState(""); const [showDeleteDialog, setShowDeleteDialog] = useState(false); // Avatar upload const [avatarFile, setAvatarFile] = useState(null); const [isUploadingAvatar, setIsUploadingAvatar] = useState(false); const [isDeletingAvatar, setIsDeletingAvatar] = useState(false); const fileInputRef = useRef(null); // Members and invites const [members, setMembers] = useState([]); const [invites, setInvites] = useState([]); const [loadingMembers, setLoadingMembers] = useState(true); const [updating, setUpdating] = useState(null); const [showInviteDialog, setShowInviteDialog] = useState(false); const [username, setUsername] = useState(""); const [isCreating, setIsCreating] = useState(false); const [error, setError] = useState(null); // Permission modal const [showPermissionModal, setShowPermissionModal] = useState(false); const [selectedMember, setSelectedMember] = useState(null); const [projects, setProjects] = useState([]); const [loadingProjects, setLoadingProjects] = useState(false); useEffect(() => { if (currentWorkspace) { setName(currentWorkspace.name); } }, [currentWorkspace]); useEffect(() => { loadMembers(); }, [workspaceId]); const loadMembers = async () => { try { setLoadingMembers(true); const [membersResponse, invitesResponse] = await Promise.all([ membersApi.list(workspaceId, { size: 100 }), invitesApi.list(workspaceId, { size: 100 }), ]); setMembers(membersResponse.items); setInvites(invitesResponse.items); } catch (err) { console.error("Failed to load data:", err); } finally { setLoadingMembers(false); } }; const loadProjects = async () => { try { setLoadingProjects(true); const response = await workspacesApi.getProjects(workspaceId, { size: 100 }); setProjects(response.items); } catch (err) { console.error("Failed to load projects:", err); } finally { setLoadingProjects(false); } }; const openPermissionModal = async (member: WorkspaceMember) => { setSelectedMember(member); await loadProjects(); setShowPermissionModal(true); }; const handlePermissionSave = async ( wsId: string, memberId: string, userId: string, permissions: { key: PermissionKey; scopes?: { type: string; id: string }[] }[] ) => { await membersApi.updatePermissions(wsId, userId, { permissions }); await loadMembers(); }; const handleUpdate = async () => { if (!name.trim() || name === currentWorkspace?.name) return; try { setIsUpdating(true); await workspacesApi.update(workspaceId, { name: name.trim() }); await refreshWorkspaces(); } catch (err) { console.error("Failed to update workspace:", err); } finally { setIsUpdating(false); } }; const handleAvatarUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; try { setIsUploadingAvatar(true); const updatedWorkspace = await workspacesApi.uploadAvatar(workspaceId, file); await refreshWorkspaces(); if (fileInputRef.current) { fileInputRef.current.value = ""; } setAvatarFile(null); } catch (err) { console.error("Failed to upload avatar:", err); } finally { setIsUploadingAvatar(false); } }; const handleDelete = async () => { if (deleteConfirm !== `Удалить ${currentWorkspace?.name}`) return; try { setIsDeleting(true); await workspacesApi.delete(workspaceId); router.replace("/"); } catch (err) { console.error("Failed to delete workspace:", err); } finally { setIsDeleting(false); setShowDeleteDialog(false); setDeleteConfirm(""); } }; const hasPermission = (member: WorkspaceMember, key: PermissionKey) => { return member.permissions.some((p) => p.key === key); }; const renderPermissionBadges = (member: WorkspaceMember) => { if (member.permissions.length === 0) { return Нет прав; } const adminPerm = member.permissions.find((p) => p.key === "admin_full"); if (adminPerm) { const isGlobal = !adminPerm.scopes || adminPerm.scopes.length === 0; return (
Админ {isGlobal ? "" : "(ограничен)"}
); } return (
{member.permissions.map((p) => { const isGlobal = !p.scopes || p.scopes.length === 0; const projectCount = p.scopes ? p.scopes.filter((s) => s.type === "project").length : 0; return ( {PERMISSION_LABELS[p.key]} {!isGlobal && projectCount > 0 && ( ({projectCount}) )} ); })}
); }; // Combine members and pending invites const pendingInvites = invites.filter((inv) => inv.status === "pending"); const allPeople = [ ...members.map((m) => ({ type: "member" as const, data: m })), ...pendingInvites.map((inv) => ({ type: "invite" as const, data: inv })), ]; if (!currentWorkspace) { return ( <>
); } return ( <>

Настройки воркспейса

Управление настройками воркспейса

{/* General Settings */} Основные настройки Название и аватар воркспейса {/* Avatar */}
{currentWorkspace.name.charAt(0).toUpperCase()}
{currentWorkspace.avatar_url && ( Удалить аватар Вы уверены, что хотите удалить аватар воркспейса? Отмена Удалить )}

PNG, JPG или GIF. Максимум 2 МБ.

{/* Name */}
setName(e.target.value)} placeholder="Название воркспейса" />
{/* Team Management */}
Участники Управление участниками воркспейса
Пригласить участника Введите username Telegram пользователя для отправки приглашения через бота
setUsername(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { handleCreateInvite(); } }} />
{error && (
{error}
)}
{loadingMembers ? (
) : allPeople.length === 0 ? (

Нет участников

Пригласите участников в воркспейс

) : ( Пользователь Статус Права доступа {allPeople.map((item) => { if (item.type === "invite") { const invite = item.data; return (
@{invite.username}
Ожидает принятия
); } else { const member = item.data; return (
{[member.user.first_name, member.user.last_name] .filter(Boolean) .join(" ") || member.user.username || `User ${member.user.telegram_id}`}
{member.user.username && (
@{member.user.username}
)}
{member.status === "active" ? "Активен" : "Приглашён"} {renderPermissionBadges(member)}
); } })}
)}
{/* Danger Zone */} Опасная зона Необратимые действия с воркспейсом Удалить воркспейс? Это действие необратимо. Все данные воркспейса будут удалены, включая проекты, креативы, размещения и аналитику.
setDeleteConfirm(e.target.value)} placeholder={`Удалить ${currentWorkspace.name}`} className="font-mono" />
{ setShowDeleteDialog(false); setDeleteConfirm(""); }}> Отмена {isDeleting ? ( <> Удаление... ) : ( "Удалить" )}
{selectedMember && ( )}
); }