import { action, computed, makeObservable, observable, runInAction } from "mobx"; import { computedFn } from "mobx-utils"; import set from "lodash/set"; import sortBy from "lodash/sortBy"; // services import { ProjectMemberService } from "services/project"; // types import { RootStore } from "store/root.store"; import { IProjectBulkAddFormData, IProjectMember, IProjectMembership, IUserLite } from "@plane/types"; // constants import { EUserProjectRoles } from "constants/project"; import { IMemberRootStore } from "."; import { IRouterStore } from "store/application/router.store"; import { IUserRootStore } from "store/user"; interface IProjectMemberDetails { id: string; member: IUserLite; role: EUserProjectRoles; } export interface IProjectMemberStore { // observables projectMemberMap: { [projectId: string]: Record; }; // computed projectMemberIds: string[] | null; // computed actions getProjectMemberDetails: (userId: string) => IProjectMemberDetails | null; getProjectMemberIds: (projectId: string) => string[] | null; // fetch actions fetchProjectMembers: (workspaceSlug: string, projectId: string) => Promise; // bulk operation actions bulkAddMembersToProject: ( workspaceSlug: string, projectId: string, data: IProjectBulkAddFormData ) => Promise; // crud actions updateMember: ( workspaceSlug: string, projectId: string, userId: string, data: { role: EUserProjectRoles } ) => Promise; removeMemberFromProject: (workspaceSlug: string, projectId: string, userId: string) => Promise; } export class ProjectMemberStore implements IProjectMemberStore { // observables projectMemberMap: { [projectId: string]: Record; } = {}; // stores routerStore: IRouterStore; userStore: IUserRootStore; memberRoot: IMemberRootStore; // services projectMemberService; constructor(_memberRoot: IMemberRootStore, _rootStore: RootStore) { makeObservable(this, { // observables projectMemberMap: observable, // computed projectMemberIds: computed, // actions fetchProjectMembers: action, bulkAddMembersToProject: action, updateMember: action, removeMemberFromProject: action, }); // root store this.routerStore = _rootStore.app.router; this.userStore = _rootStore.user; this.memberRoot = _memberRoot; // services this.projectMemberService = new ProjectMemberService(); } /** * @description get the list of all the user ids of all the members of the current project */ get projectMemberIds() { const projectId = this.routerStore.projectId; if (!projectId) return null; let members = Object.values(this.projectMemberMap?.[projectId] ?? {}); members = sortBy(members, [ (m) => m.member !== this.userStore.currentUser?.id, (m) => this.memberRoot.memberMap?.[m.member]?.display_name.toLowerCase(), ]); const memberIds = members.map((m) => m.member); return memberIds; } /** * @description get the details of a project member * @param userId */ getProjectMemberDetails = computedFn((userId: string) => { const projectId = this.routerStore.projectId; if (!projectId) return null; const projectMember = this.projectMemberMap?.[projectId]?.[userId]; if (!projectMember) return null; const memberDetails: IProjectMemberDetails = { id: projectMember.id, role: projectMember.role, member: this.memberRoot?.memberMap?.[projectMember.member], }; return memberDetails; }); /** * @description get the list of all the user ids of all the members of a project using projectId * @param projectId */ getProjectMemberIds = computedFn((projectId: string): string[] | null => { if (!this.projectMemberMap?.[projectId]) return null; let members = Object.values(this.projectMemberMap?.[projectId]); members = sortBy(members, [ (m) => m.member !== this.userStore.currentUser?.id, (m) => this.memberRoot?.memberMap?.[m.member]?.display_name?.toLowerCase(), ]); const memberIds = members.map((m) => m.member); return memberIds; }); /** * @description fetch the list of all the members of a project * @param workspaceSlug * @param projectId */ fetchProjectMembers = async (workspaceSlug: string, projectId: string) => await this.projectMemberService.fetchProjectMembers(workspaceSlug, projectId).then((response) => { runInAction(() => { response.forEach((member) => { set(this.projectMemberMap, [projectId, member.member], member); }); }); return response; }); /** * @description bulk add members to a project * @param workspaceSlug * @param projectId * @param data * @returns Promise */ bulkAddMembersToProject = async (workspaceSlug: string, projectId: string, data: IProjectBulkAddFormData) => await this.projectMemberService.bulkAddMembersToProject(workspaceSlug, projectId, data).then((response) => { runInAction(() => { response.forEach((member) => { set(this.projectMemberMap, [projectId, member.member], member); }); }); return response; }); /** * @description update the role of a member in a project * @param workspaceSlug * @param projectId * @param userId * @param data */ updateMember = async ( workspaceSlug: string, projectId: string, userId: string, data: { role: EUserProjectRoles } ) => { const memberDetails = this.getProjectMemberDetails(userId); if (!memberDetails) throw new Error("Member not found"); // original data to revert back in case of error const originalProjectMemberData = this.projectMemberMap?.[projectId]?.[userId]; try { runInAction(() => { set(this.projectMemberMap, [projectId, userId, "role"], data.role); }); const response = await this.projectMemberService.updateProjectMember( workspaceSlug, projectId, memberDetails?.id, data ); return response; } catch (error) { // revert back to original members in case of error runInAction(() => { set(this.projectMemberMap, [projectId, userId], originalProjectMemberData); }); throw error; } }; /** * @description remove a member from a project * @param workspaceSlug * @param projectId * @param userId */ removeMemberFromProject = async (workspaceSlug: string, projectId: string, userId: string) => { const memberDetails = this.getProjectMemberDetails(userId); if (!memberDetails) throw new Error("Member not found"); await this.projectMemberService.deleteProjectMember(workspaceSlug, projectId, memberDetails?.id).then(() => { runInAction(() => { delete this.projectMemberMap?.[projectId]?.[userId]; }); }); }; }