fix(knowledge): resolve TypeScript errors in tags service
- Fix updateData typing for partial updates - Add slug field to CreateTagDto - Build now passes Note: tasks.controller.spec.ts needs test config update for WorkspaceGuard
This commit is contained in:
165
apps/web/src/components/team/TeamMemberList.tsx
Normal file
165
apps/web/src/components/team/TeamMemberList.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import type { TeamMember, User } from "@mosaic/shared";
|
||||
import { TeamMemberRole } from "@mosaic/shared";
|
||||
import { Card, CardHeader, CardContent, Button, Select, Avatar } from "@mosaic/ui";
|
||||
|
||||
interface TeamMemberWithUser extends TeamMember {
|
||||
user: User;
|
||||
}
|
||||
|
||||
interface TeamMemberListProps {
|
||||
members: TeamMemberWithUser[];
|
||||
onAddMember: (userId: string, role?: TeamMemberRole) => Promise<void>;
|
||||
onRemoveMember: (userId: string) => Promise<void>;
|
||||
availableUsers?: User[];
|
||||
}
|
||||
|
||||
const roleOptions = [
|
||||
{ value: TeamMemberRole.MEMBER, label: "Member" },
|
||||
{ value: TeamMemberRole.ADMIN, label: "Admin" },
|
||||
{ value: TeamMemberRole.OWNER, label: "Owner" },
|
||||
];
|
||||
|
||||
export function TeamMemberList({
|
||||
members,
|
||||
onAddMember,
|
||||
onRemoveMember,
|
||||
availableUsers = [],
|
||||
}: TeamMemberListProps) {
|
||||
const [isAdding, setIsAdding] = useState(false);
|
||||
const [selectedUserId, setSelectedUserId] = useState("");
|
||||
const [selectedRole, setSelectedRole] = useState(TeamMemberRole.MEMBER);
|
||||
const [removingUserId, setRemovingUserId] = useState<string | null>(null);
|
||||
|
||||
const handleAddMember = async () => {
|
||||
if (!selectedUserId) return;
|
||||
|
||||
setIsAdding(true);
|
||||
try {
|
||||
await onAddMember(selectedUserId, selectedRole);
|
||||
setSelectedUserId("");
|
||||
setSelectedRole(TeamMemberRole.MEMBER);
|
||||
} catch (error) {
|
||||
console.error("Failed to add member:", error);
|
||||
alert("Failed to add member. Please try again.");
|
||||
} finally {
|
||||
setIsAdding(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveMember = async (userId: string) => {
|
||||
setRemovingUserId(userId);
|
||||
try {
|
||||
await onRemoveMember(userId);
|
||||
} catch (error) {
|
||||
console.error("Failed to remove member:", error);
|
||||
alert("Failed to remove member. Please try again.");
|
||||
} finally {
|
||||
setRemovingUserId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const memberUserIds = new Set(members.map((m) => m.userId));
|
||||
const usersToAdd = availableUsers.filter((user) => !memberUserIds.has(user.id));
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold text-gray-900">Team Members</h2>
|
||||
<span className="text-sm text-gray-500">{members.length} member{members.length !== 1 ? "s" : ""}</span>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{/* Member list */}
|
||||
<div className="space-y-3 mb-6">
|
||||
{members.length === 0 ? (
|
||||
<p className="text-center text-gray-500 py-4">No members yet</p>
|
||||
) : (
|
||||
members.map((member) => (
|
||||
<div
|
||||
key={member.userId}
|
||||
className="flex items-center justify-between p-3 bg-gray-50 rounded-lg"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar
|
||||
src={member.user.image ?? ""}
|
||||
alt={member.user.name}
|
||||
fallback={member.user.name.charAt(0).toUpperCase()}
|
||||
/>
|
||||
<div>
|
||||
<p className="font-medium text-gray-900">{member.user.name}</p>
|
||||
<p className="text-sm text-gray-500">{member.user.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={`px-3 py-1 text-xs font-medium rounded-full ${
|
||||
member.role === TeamMemberRole.OWNER
|
||||
? "bg-purple-100 text-purple-700"
|
||||
: member.role === TeamMemberRole.ADMIN
|
||||
? "bg-blue-100 text-blue-700"
|
||||
: "bg-gray-100 text-gray-700"
|
||||
}`}
|
||||
>
|
||||
{member.role}
|
||||
</span>
|
||||
{member.role !== TeamMemberRole.OWNER && (
|
||||
<Button
|
||||
variant="danger"
|
||||
size="sm"
|
||||
onClick={() => handleRemoveMember(member.userId)}
|
||||
disabled={removingUserId === member.userId}
|
||||
>
|
||||
{removingUserId === member.userId ? "Removing..." : "Remove"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Add member form */}
|
||||
{usersToAdd.length > 0 && (
|
||||
<div className="pt-4 border-t border-gray-200">
|
||||
<h3 className="text-sm font-medium text-gray-700 mb-3">Add Member</h3>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex-1">
|
||||
<Select
|
||||
options={usersToAdd.map((user) => ({
|
||||
value: user.id,
|
||||
label: `${user.name} (${user.email})`,
|
||||
}))}
|
||||
value={selectedUserId}
|
||||
onChange={(e) => setSelectedUserId(e.target.value)}
|
||||
placeholder="Select a user..."
|
||||
fullWidth
|
||||
disabled={isAdding}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-32">
|
||||
<Select
|
||||
options={roleOptions}
|
||||
value={selectedRole}
|
||||
onChange={(e) => setSelectedRole(e.target.value as TeamMemberRole)}
|
||||
fullWidth
|
||||
disabled={isAdding}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleAddMember}
|
||||
disabled={!selectedUserId || isAdding}
|
||||
>
|
||||
{isAdding ? "Adding..." : "Add"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -6,12 +6,11 @@ import { Card, CardHeader, CardContent, CardFooter, Button, Input, Textarea } fr
|
||||
|
||||
interface TeamSettingsProps {
|
||||
team: Team;
|
||||
workspaceId: string;
|
||||
onUpdate: (data: { name?: string; description?: string }) => Promise<void>;
|
||||
onDelete: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function TeamSettings({ team, workspaceId, onUpdate, onDelete }: TeamSettingsProps) {
|
||||
export function TeamSettings({ team, onUpdate, onDelete }: TeamSettingsProps) {
|
||||
const [name, setName] = useState(team.name);
|
||||
const [description, setDescription] = useState(team.description || "");
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
@@ -26,10 +25,14 @@ export function TeamSettings({ team, workspaceId, onUpdate, onDelete }: TeamSett
|
||||
|
||||
setIsSaving(true);
|
||||
try {
|
||||
await onUpdate({
|
||||
name: name !== team.name ? name : undefined,
|
||||
description: description !== (team.description || "") ? description : undefined,
|
||||
});
|
||||
const updates: { name?: string; description?: string } = {};
|
||||
if (name !== team.name) {
|
||||
updates.name = name;
|
||||
}
|
||||
if (description !== (team.description || "")) {
|
||||
updates.description = description;
|
||||
}
|
||||
await onUpdate(updates);
|
||||
setIsEditing(false);
|
||||
} catch (error) {
|
||||
console.error("Failed to update team:", error);
|
||||
|
||||
Reference in New Issue
Block a user