diff --git a/src/components/ReactIcons/MailIcon.tsx b/src/components/ReactIcons/MailIcon.tsx
new file mode 100644
index 000000000..ef1c82f56
--- /dev/null
+++ b/src/components/ReactIcons/MailIcon.tsx
@@ -0,0 +1,23 @@
+interface MailIconProps {
+ className?: string;
+}
+export function MailIcon(props: MailIconProps) {
+ const { className } = props;
+ return (
+
+ );
+}
diff --git a/src/components/TeamMembers/MemberActionDropdown.tsx b/src/components/TeamMembers/MemberActionDropdown.tsx
index 403e4cf4f..029972ea7 100644
--- a/src/components/TeamMembers/MemberActionDropdown.tsx
+++ b/src/components/TeamMembers/MemberActionDropdown.tsx
@@ -9,10 +9,12 @@ export function MemberActionDropdown({
member,
onUpdateMember,
onDeleteMember,
+ onResendInvite,
isDisabled = false,
}: {
onDeleteMember: () => void;
onUpdateMember: () => void;
+ onResendInvite: () => void;
isDisabled: boolean;
member: TeamMemberDocument;
}) {
@@ -25,23 +27,6 @@ export function MemberActionDropdown({
setIsOpen(false);
});
- async function resendInvite() {
- const { response, error } = await httpPatch(
- `${import.meta.env.PUBLIC_API_URL}/v1-resend-invite/${member.teamId}/${
- member._id
- }`,
- {}
- );
-
- if (error || !response) {
- setIsLoading(false);
- toast.error(error?.message || 'Something went wrong');
- return;
- }
-
- window.location.reload();
- }
-
const actions = [
{
name: 'Delete',
@@ -61,7 +46,10 @@ export function MemberActionDropdown({
? [
{
name: 'Resend Invite',
- handleClick: resendInvite,
+ handleClick: () => {
+ onResendInvite();
+ setIsOpen(false);
+ },
},
]
: []),
diff --git a/src/components/TeamMembers/RoleBadge.tsx b/src/components/TeamMembers/RoleBadge.tsx
index f65e8e9f1..18612b5cc 100644
--- a/src/components/TeamMembers/RoleBadge.tsx
+++ b/src/components/TeamMembers/RoleBadge.tsx
@@ -3,11 +3,10 @@ import type { AllowedRoles } from '../CreateTeam/RoleDropdown';
export function MemberRoleBadge({ role }: { role: AllowedRoles }) {
return (
{role}
diff --git a/src/components/TeamMembers/TeamMemberItem.tsx b/src/components/TeamMembers/TeamMemberItem.tsx
new file mode 100644
index 000000000..93e41a568
--- /dev/null
+++ b/src/components/TeamMembers/TeamMemberItem.tsx
@@ -0,0 +1,132 @@
+import { MailIcon } from '../ReactIcons/MailIcon';
+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;
+ handleSendReminder: () => void;
+ onResendInvite: () => void;
+};
+
+export function TeamMemberItem(props: TeamMemberProps) {
+ const {
+ member,
+ index,
+ onResendInvite,
+ onUpdateMember,
+ canManageCurrentTeam,
+ userId,
+ handleDeleteMember,
+ handleSendReminder,
+ } = props;
+
+ const showNoProgress =
+ member.progress.length === 0 && member.status === 'joined';
+ const showReminder =
+ member.progress.length === 0 &&
+ member.status === 'joined' &&
+ !(member.userId === userId);
+
+ return (
+
+
+
+
+
+
+ {showReminder && (
+
+ )}
+
+
+
+ {member.name}
+ {showNoProgress && (
+
+ No Progress
+
+ )}
+ {member.userId === userId && (
+
+ You
+
+ )}
+
+
+ {member.status === 'invited' && (
+
+ Invited
+
+ )}
+ {member.status === 'rejected' && (
+
+ Rejected
+
+ )}
+
+
+
+ {member.invitedEmail}
+
+
+
+
+
+ {showReminder && (
+
+
+
+ )}
+
+
+
+ {canManageCurrentTeam && (
+
+ )}
+
+
+ );
+}
+
+type SendProgressReminderProps = {
+ handleSendReminder: () => void;
+};
+
+function SendProgressReminder(props: SendProgressReminderProps) {
+ const { handleSendReminder } = props;
+
+ return (
+
+ );
+}
diff --git a/src/components/TeamMembers/TeamMembersPage.tsx b/src/components/TeamMembers/TeamMembersPage.tsx
index 9133de018..ea1153c15 100644
--- a/src/components/TeamMembers/TeamMembersPage.tsx
+++ b/src/components/TeamMembers/TeamMembersPage.tsx
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'preact/hooks';
-import { httpDelete, httpGet } from '../../lib/http';
+import { httpDelete, httpGet, httpPatch } from '../../lib/http';
import { MemberActionDropdown } from './MemberActionDropdown';
import { useAuth } from '../../hooks/use-auth';
import { pageProgressMessage } from '../../stores/page';
@@ -14,6 +14,7 @@ import { useStore } from '@nanostores/preact';
import { $canManageCurrentTeam } from '../../stores/team';
import { useToast } from '../../hooks/use-toast';
import { MemberRoleBadge } from './RoleBadge';
+import { TeamMemberItem } from './TeamMemberItem';
export interface TeamMemberDocument {
_id?: string;
@@ -26,9 +27,23 @@ export interface TeamMemberDocument {
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;
avatar: string;
+ progress: UserResourceProgressDocument[];
}
export function TeamMembersPage() {
@@ -79,7 +94,6 @@ export function TeamMembersPage() {
pageProgressMessage.set('');
});
}, [teamId]);
-
async function deleteMember(teamId: string, memberId: string) {
pageProgressMessage.set('Deleting member');
const { response, error } = await httpDelete(
@@ -98,6 +112,50 @@ export function TeamMembersPage() {
await getTeamMemberList();
}
+ async function resendInvite(teamId: string, memberId: string) {
+ pageProgressMessage.set('Resending Invite');
+ const { response, error } = await httpPatch(
+ `${
+ import.meta.env.PUBLIC_API_URL
+ }/v1-resend-invite/${teamId}/${memberId}`,
+ {}
+ );
+
+ if (error || !response) {
+ toast.error(error?.message || 'Something went wrong');
+ return;
+ }
+
+ toast.success('Invite has been sent');
+ }
+
+ async function handleSendReminder(teamId: string, memberId: string) {
+ pageProgressMessage.set('Sending Reminder');
+ const { response, error } = await httpPatch(
+ `${
+ import.meta.env.PUBLIC_API_URL
+ }/v1-send-progress-reminder/${teamId}/${memberId}`,
+ {}
+ );
+
+ if (error || !response) {
+ toast.error(error?.message || 'Something went wrong');
+ return;
+ }
+
+ toast.success('Reminder has been sent');
+ }
+
+ 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 (
{memberToUpdate && (
@@ -139,80 +197,113 @@ export function TeamMembersPage() {
- {teamMembers.map((member, index) => {
+ {joinedMembers.map((member, index) => {
return (
-
-
-
-
-
-
-
-
-
- {member.name}
- {member.userId === user?.id && (
-
- You
-
- )}
-
-
- {member.status === 'invited' && (
-
- Invited
-
- )}
- {member.status === 'rejected' && (
-
- Rejected
-
- )}
-
-
-
- {member.invitedEmail}
-
-
-
-
-
-
-
-
- {canManageCurrentTeam && (
- {
- deleteMember(teamId, member._id!).finally(() => {
- pageProgressMessage.set('');
- });
- }}
- isDisabled={member.userId === user?.id}
- onUpdateMember={() => {
- setMemberToUpdate(member);
- }}
- member={member}
- />
- )}
-
-
+ {
+ resendInvite(teamId, member._id!).finally(() => {
+ pageProgressMessage.set('');
+ });
+ }}
+ canManageCurrentTeam={canManageCurrentTeam}
+ handleDeleteMember={() => {
+ deleteMember(teamId, member._id!).finally(() => {
+ pageProgressMessage.set('');
+ });
+ }}
+ onUpdateMember={() => {
+ setMemberToUpdate(member);
+ }}
+ handleSendReminder={() => {
+ handleSendReminder(teamId, member._id!).finally(() => {
+ pageProgressMessage.set('');
+ });
+ }}
+ />
);
})}
+
+ {invitedMembers.length > 0 && (
+
+
Invited Members
+
+ {invitedMembers.map((member, index) => {
+ return (
+ {
+ resendInvite(teamId, member._id!).finally(() => {
+ pageProgressMessage.set('');
+ });
+ }}
+ canManageCurrentTeam={canManageCurrentTeam}
+ handleDeleteMember={() => {
+ deleteMember(teamId, member._id!).finally(() => {
+ pageProgressMessage.set('');
+ });
+ }}
+ onUpdateMember={() => {
+ setMemberToUpdate(member);
+ }}
+ handleSendReminder={() => {
+ handleSendReminder(teamId, member._id!).finally(() => {
+ pageProgressMessage.set('');
+ });
+ }}
+ />
+ );
+ })}
+
+
+ )}
+
+ {rejectedMembers.length > 0 && (
+
+
Rejected Members
+
+ {rejectedMembers.map((member, index) => {
+ return (
+ {
+ resendInvite(teamId, member._id!).finally(() => {
+ pageProgressMessage.set('');
+ });
+ }}
+ canManageCurrentTeam={canManageCurrentTeam}
+ handleDeleteMember={() => {
+ deleteMember(teamId, member._id!).finally(() => {
+ pageProgressMessage.set('');
+ });
+ }}
+ onUpdateMember={() => {
+ setMemberToUpdate(member);
+ }}
+ handleSendReminder={() => {
+ handleSendReminder(teamId, member._id!).finally(() => {
+ pageProgressMessage.set('');
+ });
+ }}
+ />
+ );
+ })}
+
+
+ )}
{canManageCurrentTeam && (