wip: team member listing

chore/team-member
Arik Chakma 1 year ago
parent 21008de3d1
commit dac0e9f28f
  1. 14
      src/components/TeamDropdown/TeamDropdown.tsx
  2. 82
      src/components/TeamMembers/TeamMemberItem.tsx
  3. 149
      src/components/TeamMembers/TeamMembersPage.tsx

@ -76,13 +76,13 @@ export function TeamDropdown() {
.filter((team) => team.status === 'invited') .filter((team) => team.status === 'invited')
.map((team) => team._id); .map((team) => team._id);
if ( // if (
!user?.email.endsWith('@insightpartners.com') && // !user?.email.endsWith('@insightpartners.com') &&
!user?.email.endsWith('@roadmap.sh') && // !user?.email.endsWith('@roadmap.sh') &&
!['arikchangma@gmail.com', 'kamranahmed.se@gmail.com', 'stephen.chetcuti@gmail.com'].includes(user?.email!) // !['arikchangma@gmail.com', 'kamranahmed.se@gmail.com', 'stephen.chetcuti@gmail.com'].includes(user?.email!)
) { // ) {
return null; // return null;
} // }
return ( return (
<div className="relative mr-2"> <div className="relative mr-2">

@ -0,0 +1,82 @@
import { MemberActionDropdown } from "./MemberActionDropdown";
import { MemberRoleBadge } from "./RoleBadge";
import type { TeamMemberItem } from "./TeamMembersPage";
type TeamMemberProps = {
member: TeamMemberItem;
userId: string;
index: number;
teamId: string;
canManageCurrentTeam: boolean;
handleDeleteMember: () => void;
onUpdateMember: () => void;
};
export function TeamMemberItem(props: TeamMemberProps) {
const { member, index, teamId, onUpdateMember, canManageCurrentTeam, userId, handleDeleteMember } = props;
const hasProgress = member.progress.length > 0;
return (
<div
className={`flex items-center justify-between gap-2 p-3 ${index === 0 ? '' : 'border-t'
}`}
>
<div className="flex items-center gap-3">
<img
src={
member.avatar
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${member.avatar
}`
: '/images/default-avatar.png'
}
alt={member.name || ''}
className="hidden h-10 w-10 rounded-full sm:block"
/>
<div>
<span class={'mb-1 block sm:hidden'}>
<MemberRoleBadge role={member.role} />
</span>
<div className="flex items-center">
<h3 className="inline-grid grid-cols-[auto_auto] items-center font-medium">
<span className="truncate">{member.name}</span>
{member.userId === userId && (
<span className="ml-2 hidden text-xs font-normal text-blue-500 sm:inline">
You
</span>
)}
</h3>
<div className="ml-2 flex items-center gap-0.5">
{member.status === 'invited' && (
<span className="rounded-full bg-yellow-100 px-2 py-0.5 text-xs text-yellow-700">
Invited
</span>
)}
{member.status === 'rejected' && (
<span className="rounded-full bg-red-100 px-2 py-0.5 text-xs text-red-700">
Rejected
</span>
)}
</div>
</div>
<p className="text-sm text-gray-500">
{member.invitedEmail}
</p>
</div>
</div>
<div className="flex items-center text-sm">
<span class={'hidden sm:block'}>
<MemberRoleBadge role={member.role} />
</span>
{canManageCurrentTeam && (
<MemberActionDropdown
onDeleteMember={handleDeleteMember}
isDisabled={member.userId === userId}
onUpdateMember={onUpdateMember}
member={member}
/>
)}
</div>
</div>
)
}

@ -14,6 +14,7 @@ import { useStore } from '@nanostores/preact';
import { $canManageCurrentTeam } from '../../stores/team'; import { $canManageCurrentTeam } from '../../stores/team';
import { useToast } from '../../hooks/use-toast'; import { useToast } from '../../hooks/use-toast';
import { MemberRoleBadge } from './RoleBadge'; import { MemberRoleBadge } from './RoleBadge';
import { TeamMemberItem } from './TeamMemberItem';
export interface TeamMemberDocument { export interface TeamMemberDocument {
_id?: string; _id?: string;
@ -26,9 +27,23 @@ export interface TeamMemberDocument {
updatedAt: Date; updatedAt: Date;
} }
interface TeamMemberItem extends TeamMemberDocument { export interface UserResourceProgressDocument {
_id?: string;
userId: string;
resourceId: string;
resourceType: 'roadmap' | 'best-practice';
isFavorite?: boolean;
done: string[];
learning: string[];
skipped: string[];
createdAt: Date;
updatedAt: Date;
}
export interface TeamMemberItem extends TeamMemberDocument {
name: string; name: string;
avatar: string; avatar: string;
progress: UserResourceProgressDocument[];
} }
export function TeamMembersPage() { export function TeamMembersPage() {
@ -83,8 +98,7 @@ export function TeamMembersPage() {
async function deleteMember(teamId: string, memberId: string) { async function deleteMember(teamId: string, memberId: string) {
pageProgressMessage.set('Deleting member'); pageProgressMessage.set('Deleting member');
const { response, error } = await httpDelete( const { response, error } = await httpDelete(
`${ `${import.meta.env.PUBLIC_API_URL
import.meta.env.PUBLIC_API_URL
}/v1-delete-member/${teamId}/${memberId}`, }/v1-delete-member/${teamId}/${memberId}`,
{} {}
); );
@ -98,6 +112,16 @@ export function TeamMembersPage() {
await getTeamMemberList(); await getTeamMemberList();
} }
const joinedMembers = teamMembers.filter(
(member) => member.status === 'joined'
);
const invitedMembers = teamMembers.filter(
(member) => member.status === 'invited'
);
const rejectedMembers = teamMembers.filter(
(member) => member.status === 'rejected'
);
return ( return (
<div> <div>
{memberToUpdate && ( {memberToUpdate && (
@ -139,81 +163,84 @@ export function TeamMembersPage() {
</p> </p>
<LeaveTeamButton teamId={team?._id!} /> <LeaveTeamButton teamId={team?._id!} />
</div> </div>
{teamMembers.map((member, index) => { {joinedMembers.map((member, index) => {
return ( return (
<div <TeamMemberItem
className={`flex items-center justify-between gap-2 p-3 ${ key={index}
index === 0 ? '' : 'border-t' member={member}
} ${member.status === 'invited' ? 'bg-gray-50' : ''}`} index={index}
> teamId={teamId}
<div className="flex items-center gap-3"> userId={user?.id!}
<img canManageCurrentTeam={canManageCurrentTeam}
src={ handleDeleteMember={() => {
member.avatar deleteMember(teamId, member._id!).finally(() => {
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${ pageProgressMessage.set('');
member.avatar });
}` }}
: '/images/default-avatar.png' onUpdateMember={() => {
} setMemberToUpdate(member);
alt={member.name || ''} }}
className="hidden h-10 w-10 rounded-full sm:block"
/> />
<div> );
<span class={'mb-1 block sm:hidden'}> })}
<MemberRoleBadge role={member.role} />
</span>
<div className="flex items-center">
<h3 className="inline-grid grid-cols-[auto_auto] items-center font-medium">
<span className="truncate">{member.name}</span>
{member.userId === user?.id && (
<span className="ml-2 hidden text-xs font-normal text-blue-500 sm:inline">
You
</span>
)}
</h3>
<div className="ml-2 flex items-center gap-0.5">
{member.status === 'invited' && (
<span className="rounded-full bg-yellow-100 px-2 py-0.5 text-xs text-yellow-700">
Invited
</span>
)}
{member.status === 'rejected' && (
<span className="rounded-full bg-red-100 px-2 py-0.5 text-xs text-red-700">
Rejected
</span>
)}
</div>
</div>
<p className="text-sm text-gray-500">
{member.invitedEmail}
</p>
</div>
</div> </div>
<div className="flex items-center text-sm"> {invitedMembers.length > 0 && (<div className="mt-6">
<span class={'hidden sm:block'}> <h3 className="font-medium text-xl">Invited Members</h3>
<MemberRoleBadge role={member.role} /> <div className="rounded-b-sm rounded-t-md border mt-2">
</span> {invitedMembers.map((member, index) => {
{canManageCurrentTeam && ( return (
<MemberActionDropdown <TeamMemberItem
onDeleteMember={() => { key={index}
member={member}
index={index}
teamId={teamId}
userId={user?.id!}
canManageCurrentTeam={canManageCurrentTeam}
handleDeleteMember={() => {
deleteMember(teamId, member._id!).finally(() => { deleteMember(teamId, member._id!).finally(() => {
pageProgressMessage.set(''); pageProgressMessage.set('');
}); });
}} }}
isDisabled={member.userId === user?.id}
onUpdateMember={() => { onUpdateMember={() => {
setMemberToUpdate(member); setMemberToUpdate(member);
}} }}
member={member}
/> />
)} );
</div> })}
</div> </div>
</div>)}
{
rejectedMembers.length > 0 && (
<div className="mt-6">
<h3 className="font-medium text-xl">Rejected Members</h3>
<div className="rounded-b-sm rounded-t-md border mt-2">
{rejectedMembers.map((member, index) => {
return (
<TeamMemberItem
key={index}
member={member}
index={index}
teamId={teamId}
userId={user?.id!}
canManageCurrentTeam={canManageCurrentTeam}
handleDeleteMember={() => {
deleteMember(teamId, member._id!).finally(() => {
pageProgressMessage.set('');
});
}}
onUpdateMember={() => {
setMemberToUpdate(member);
}}
/>
); );
})} })}
</div> </div>
</div> </div>
)
}
</div>
{canManageCurrentTeam && ( {canManageCurrentTeam && (
<div className="mt-4"> <div className="mt-4">

Loading…
Cancel
Save