tahamajs/IE / Projects /front /src /components /project /ProjectMembersPanel.tsx
tahamajs's picture
download
raw
3.28 kB
// src/components/project/ProjectMembersPanel.tsx
import { useState } from "react";
import { useProjectSpace } from "@/hooks/useProjectSpace";
import type { Project } from "@/types/project";
import { Plus, Trash2, ChevronDown, User, Shield } from "lucide-react";
interface ProjectMembersPanelProps {
projectId: string;
project: Project;
}
const ROLE_OPTIONS = [
{ value: "editor", label: "Editor" },
{ value: "commenter", label: "Commenter" },
{ value: "viewer", label: "Viewer" },
];
export function ProjectMembersPanel({ projectId, project }: ProjectMembersPanelProps) {
const { addMember, updateMemberRole, removeMember } = useProjectSpace(projectId);
const [newUsername, setNewUsername] = useState("");
const [newRole, setNewRole] = useState<string>("editor");
const handleAdd = () => {
if (!newUsername.trim()) return;
addMember(newUsername.trim(), newRole as any);
setNewUsername("");
};
return (
<div className="space-y-4">
<h3 className="font-bold text-lg">Team & Permissions</h3>
<div className="flex gap-2 mb-4">
<input
type="text"
value={newUsername}
onChange={e => setNewUsername(e.target.value)}
placeholder="Username"
className="input flex-1 text-sm"
/>
<select aria-label="Select option" value={newRole} onChange={e => setNewRole(e.target.value)} className="input text-sm w-28">
{ROLE_OPTIONS.map(r => <option key={r.value} value={r.value}>{r.label}</option>)}
</select>
<button onClick={handleAdd} className="btn btn-primary btn-sm gap-1"><Plus className="w-4 h-4"/> Add</button>
</div>
<div className="space-y-2">
{project.members.map(member => (
<div key={member.userId} className="flex items-center justify-between p-3 rounded-xl bg-white/5">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-linear-to-br from-primary to-secondary text-white flex items-center justify-center font-bold text-xs">
{member.fullName?.[0]?.toUpperCase()}
</div>
<div>
<p className="font-semibold text-sm">{member.fullName || member.username}</p>
<p className="text-xs text-gray-500">@{member.username}</p>
</div>
{member.role === "owner" && (
<span className="text-[10px] bg-amber-100 dark:bg-amber-900/30 text-amber-700 px-2 py-0.5 rounded-full">Owner</span>
)}
</div>
{member.role !== "owner" && (
<div className="flex items-center gap-2">
<select
value={member.role}
onChange={e => updateMemberRole(member.userId, e.target.value as any)}
className="text-xs bg-white/10 rounded-lg px-2 py-1 border-none"
>
{ROLE_OPTIONS.map(r => <option key={r.value} value={r.value}>{r.label}</option>)}
</select>
<button onClick={() => removeMember(member.userId)} className="text-gray-400 hover:text-red-500"><Trash2 className="w-4 h-4"/></button>
</div>
)}
</div>
))}
</div>
</div>
);
}

Xet Storage Details

Size:
3.28 kB
·
Xet hash:
e26a055b3f24d43c461fcbe5b5107317df7dca2cfc782960734494c99c2a033e

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.