Skip to main content
Glama

Convex MCP server

Official
by get-convex
TeamMemberListItem.tsx8.08 kB
import type { MemberResponse, ProjectDetails, ProjectMemberRoleResponse, UpdateProjectRolesArgs, Team, TeamMemberResponse, } from "generatedApi"; import { useRouter } from "next/router"; import { useRef, useState } from "react"; import { Button } from "@ui/Button"; import { Tooltip } from "@ui/Tooltip"; import { Combobox, Option } from "@ui/Combobox"; import { ConfirmationDialog } from "@ui/ConfirmationDialog"; import { CaretSortIcon } from "@radix-ui/react-icons"; import { useMount } from "react-use"; import classNames from "classnames"; import startCase from "lodash/startCase"; import Link from "next/link"; import { Callout } from "@ui/Callout"; import { MemberProjectRolesModal } from "./MemberProjectRolesModal"; export const roleOptions: Option<"admin" | "developer">[] = [ { label: "Admin", value: "admin" }, { label: "Developer", value: "developer" }, ]; type TeamMemberListItemProps = { team: Team; myProfile: MemberResponse; member: TeamMemberResponse; members: TeamMemberResponse[]; canChangeRole: boolean; onChangeRole: (body: { memberId: number; role: "admin" | "developer"; }) => Promise<Response>; onRemoveMember: (body: { memberId: number }) => Promise<Response>; onUpdateProjectRoles: (body: UpdateProjectRolesArgs) => Promise<undefined>; hasAdminPermissions: boolean; projectRoles: ProjectMemberRoleResponse[]; projects: ProjectDetails[]; }; export function TeamMemberListItem({ team, myProfile, member, members, canChangeRole, onChangeRole, onUpdateProjectRoles, onRemoveMember, hasAdminPermissions, projectRoles, projects, }: TeamMemberListItemProps) { const router = useRouter(); const isMemberTheLastAdmin = members.filter((m) => m.role === "admin" && m.id !== member.id).length === 0; const isMemberMe = member.id === myProfile?.id; const canManageMember = (hasAdminPermissions || isMemberMe) && !isMemberTheLastAdmin; const isHighlighted = window.location.hash === `#${member.id}`; const ref = useRef<HTMLDivElement | null>(null); useMount(() => { isHighlighted && ref.current?.scrollIntoView(); }); let removeMemberMessage = ""; if (isMemberTheLastAdmin) { removeMemberMessage = "You cannot remove the last admin from this team. Contact us for help at support@convex.dev"; } else if (!canManageMember) { removeMemberMessage = "You do not have permission to remove members from this team."; } let updateRoleMessage = ""; if (team.managedBy) { updateRoleMessage = `This team is managed by ${startCase(team.managedBy)}. You may manage team roles in ${startCase(team.managedBy)}.`; } else if (isMemberTheLastAdmin) { updateRoleMessage = "You cannot change the role of the last admin."; } else if (!hasAdminPermissions) { updateRoleMessage = "You do not have permission to change member roles."; } const [showRemoveMember, setShowRemoveMember] = useState(false); const [isUpdatingRole, setIsUpdatingRole] = useState(false); const confirmationDisplayName = member.name ? `${member.name} (${member.email})` : member.email; const [showProjecRolesModal, setShowProjectRolesModal] = useState(false); return ( <div ref={ref} className={classNames( "flex flex-wrap justify-between items-center gap-4 py-2", isHighlighted ? "bg-highlight px-2 -mx-2 rounded-sm border" : "border-b last:border-b-0", )} > <div className="flex max-w-[40%] flex-col sm:max-w-[50%] md:max-w-[80%]"> {member.name && ( <div className="text-sm text-content-primary">{member.name}</div> )} <div className={`${ member.name ? "text-xs text-content-secondary" : "text-sm text-content-primary" }`} > {member.email} </div> </div> <div className="flex flex-wrap items-center gap-2"> <div className="flex items-center gap-2"> {!canChangeRole ? ( <div className="text-sm text-content-primary"> {startCase(member.role)} </div> ) : !canManageMember || team.managedBy ? ( // Combobox is difficult to create a disabled state for, so we're using a div here that looks like a disabled input <Tooltip tip={updateRoleMessage}> <div className="flex cursor-not-allowed items-center gap-1 rounded-sm border bg-background-tertiary p-1.5 text-content-secondary"> {startCase(member.role)} <CaretSortIcon className="h-5 w-5" /> </div> </Tooltip> ) : ( <Combobox buttonClasses="w-fit" disableSearch label="Role" options={roleOptions} selectedOption={member.role} buttonProps={{ loading: isUpdatingRole, tip: ( <span> Change this member's{" "} <Link href="https://docs.convex.dev/dashboard/teams#roles-and-permissions" className="underline" > team role </Link> . </span> ), tipSide: "top", }} setSelectedOption={async (role) => { if (!role) { return; } setIsUpdatingRole(true); try { await onChangeRole({ memberId: member.id, role }); } finally { setIsUpdatingRole(false); } }} /> )} </div> <Button variant="neutral" onClick={() => setShowProjectRolesModal(true)} > Project Roles ({projectRoles?.length || 0}) </Button> <Button variant="danger" disabled={!canManageMember} tip={removeMemberMessage} onClick={() => setShowRemoveMember(true)} > {isMemberMe ? "Leave team" : "Remove member"} </Button> {showRemoveMember && ( <ConfirmationDialog onClose={() => setShowRemoveMember(false)} onConfirm={async () => { await onRemoveMember({ memberId: member.id }); if (isMemberMe) { await router.push("/"); } }} dialogTitle={isMemberMe ? "Leave team" : "Remove team member"} dialogBody={ isMemberMe ? ( `You are about to leave ${team.name}, are you sure you want to continue?` ) : ( <div className="flex flex-col gap-1"> <p> You are about to remove {confirmationDisplayName} from{" "} {team.name}, are you sure you want to continue?{" "} </p> <p className="font-semibold"> All development deployments created by this member will be deleted. </p> {team.managedBy && ( <Callout> Note that this member may be able to re-join the team through the {startCase(team.managedBy)} dashboard if they are still a member of your {startCase(team.managedBy)}{" "} team. </Callout> )} </div> ) } confirmText="Confirm" /> )} {showProjecRolesModal && ( <MemberProjectRolesModal member={member} team={team} projects={projects} projectRoles={projectRoles} onClose={() => setShowProjectRolesModal(false)} onUpdateProjectRoles={onUpdateProjectRoles} /> )} </div> </div> ); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/get-convex/convex-backend'

If you have feedback or need assistance with the MCP directory API, please join our Discord server