fix: workspace members store added and implemented across the app (#2732)

* fix: minor changes

* fix: workspace members store added and implemnted across the app
This commit is contained in:
sriram veeraghanta 2023-11-09 00:35:12 +05:30 committed by GitHub
parent 556b2d2617
commit a6567bbce4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 529 additions and 352 deletions

View File

@ -41,6 +41,7 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
globalViewFilters: globalViewFiltersStore,
workspaceFilter: workspaceFilterStore,
workspace: workspaceStore,
workspaceMember: { workspaceMembers },
project: projectStore,
} = useMobxStore();
@ -145,7 +146,7 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
handleFiltersUpdate={handleFiltersUpdate}
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet}
labels={workspaceStore.workspaceLabels ?? undefined}
members={workspaceStore.workspaceMembers?.map((m) => m.member) ?? undefined}
members={workspaceMembers?.map((m) => m.member) ?? undefined}
projects={workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined}
/>
</FiltersDropdown>

View File

@ -22,6 +22,7 @@ export const GlobalViewsAppliedFiltersRoot = observer(() => {
globalViewFilters: globalViewFiltersStore,
project: projectStore,
workspace: workspaceStore,
workspaceMember: { workspaceMembers },
} = useMobxStore();
const viewDetails = globalViewId ? globalViewsStore.globalViewDetails[globalViewId.toString()] : undefined;
@ -101,7 +102,7 @@ export const GlobalViewsAppliedFiltersRoot = observer(() => {
handleClearAllFilters={handleClearAllFilters}
handleRemoveFilter={handleRemoveFilter}
labels={workspaceStore.workspaceLabels ?? undefined}
members={workspaceStore.workspaceMembers?.map((m) => m.member)}
members={workspaceMembers?.map((m) => m.member)}
projects={workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined}
/>
{storedFilters && viewDetails && areFiltersDifferent(storedFilters, viewDetails.query_data.filters ?? {}) && (

View File

@ -39,18 +39,19 @@ export const IssuePropertyAssignee: React.FC<IIssuePropertyAssignee> = observer(
multiple = false,
noLabelBorder = false,
} = props;
const { workspace: workspaceStore, project: projectStore } = useMobxStore();
// store
const {
workspace: workspaceStore,
project: projectStore,
workspaceMember: { workspaceMembers, fetchWorkspaceMembers },
} = useMobxStore();
const workspaceSlug = workspaceStore?.workspaceSlug;
// states
const [query, setQuery] = useState("");
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
const [isLoading, setIsLoading] = useState<Boolean>(false);
const workspaceMembers = workspaceSlug ? workspaceStore?.workspaceMembers : undefined;
const fetchProjectMembers = () => {
setIsLoading(true);
if (workspaceSlug && projectId)
@ -59,10 +60,9 @@ export const IssuePropertyAssignee: React.FC<IIssuePropertyAssignee> = observer(
projectStore.fetchProjectMembers(workspaceSlug, projectId).then(() => setIsLoading(false));
};
const fetchWorkspaceMembers = () => {
const getWorkspaceMembers = () => {
setIsLoading(true);
if (workspaceSlug)
workspaceSlug && workspaceStore.fetchWorkspaceMembers(workspaceSlug).then(() => setIsLoading(false));
if (workspaceSlug) workspaceSlug && fetchWorkspaceMembers(workspaceSlug).then(() => setIsLoading(false));
};
const options = (workspaceMembers ?? [])?.map((member) => ({
@ -151,7 +151,7 @@ export const IssuePropertyAssignee: React.FC<IIssuePropertyAssignee> = observer(
className={`flex items-center justify-between gap-1 w-full text-xs ${
disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
onClick={() => !workspaceMembers && fetchWorkspaceMembers()}
onClick={() => !workspaceMembers && getWorkspaceMembers()}
>
{label}
{!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />}

View File

@ -27,6 +27,7 @@ export const GlobalViewLayoutRoot: React.FC<Props> = observer((props) => {
globalViewFilters: globalViewFiltersStore,
workspaceFilter: workspaceFilterStore,
workspace: workspaceStore,
workspaceMember: { workspaceMembers },
issueDetail: issueDetailStore,
project: projectStore,
} = useMobxStore();
@ -106,7 +107,7 @@ export const GlobalViewLayoutRoot: React.FC<Props> = observer((props) => {
displayFilters={workspaceFilterStore.workspaceDisplayFilters}
handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
issues={issues}
members={workspaceStore.workspaceMembers ? workspaceStore.workspaceMembers.map((m) => m.member) : undefined}
members={workspaceMembers?.map((m) => m.member)}
labels={workspaceStore.workspaceLabels ? workspaceStore.workspaceLabels : undefined}
handleIssueAction={() => {}}
handleUpdateIssue={handleUpdateIssue}

View File

@ -22,15 +22,12 @@ export const ProjectLayoutRoot: React.FC = observer(() => {
const { issue: issueStore, issueFilter: issueFilterStore } = useMobxStore();
const { isLoading } = useSWR(
workspaceSlug && projectId ? `PROJECT_FILTERS_AND_ISSUES_${projectId.toString()}` : null,
async () => {
useSWR(workspaceSlug && projectId ? `PROJECT_FILTERS_AND_ISSUES_${projectId.toString()}` : null, async () => {
if (workspaceSlug && projectId) {
await issueFilterStore.fetchUserProjectFilters(workspaceSlug.toString(), projectId.toString());
await issueStore.fetchIssues(workspaceSlug.toString(), projectId.toString());
}
}
);
});
const activeLayout = issueFilterStore.userDisplayFilters.layout;

View File

@ -2,7 +2,7 @@ import { FC } from "react";
import Link from "next/link";
import { History } from "lucide-react";
// packages
import { Loader, Tooltip } from "@plane/ui";
import { Tooltip } from "@plane/ui";
// components
import { ActivityIcon, ActivityMessage } from "components/core";
import { IssueCommentCard } from "./comment-card";

View File

@ -63,8 +63,10 @@ export interface ICreateProjectForm {
export const CreateProjectModal: FC<Props> = observer((props) => {
const { isOpen, onClose, setToFavorite = false, workspaceSlug } = props;
// store
const { project: projectStore, workspace: workspaceStore } = useMobxStore();
const workspaceMembers = workspaceStore.members[workspaceSlug] || [];
const {
project: projectStore,
workspaceMember: { workspaceMembers },
} = useMobxStore();
// states
const [isChangeInIdentifierRequired, setIsChangeInIdentifierRequired] = useState(true);
// toast
@ -370,7 +372,7 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
<WorkspaceMemberSelect
value={value}
onChange={onChange}
options={workspaceMembers}
options={workspaceMembers || []}
placeholder="Select Lead"
/>
)}

View File

@ -72,9 +72,10 @@ export const ProjectFeaturesList: FC<Props> = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store
const { project: projectStore, user: userStore } = useMobxStore();
const { currentUser, currentProjectRole } = userStore;
const { currentProjectDetails } = projectStore;
const {
project: { currentProjectDetails, updateProject },
user: { currentUser, currentProjectRole },
} = useMobxStore();
const isAdmin = currentProjectRole === 20;
// hooks
const { setToastAlert } = useToast();
@ -86,7 +87,7 @@ export const ProjectFeaturesList: FC<Props> = observer(() => {
title: "Success!",
message: "Project feature updated successfully.",
});
projectStore.updateProject(workspaceSlug.toString(), projectId.toString(), formData);
updateProject(workspaceSlug.toString(), projectId.toString(), formData);
};
if (!currentUser) return <></>;

View File

@ -19,8 +19,9 @@ export const ConfirmWorkspaceMemberRemove: React.FC<Props> = observer((props) =>
const [isRemoving, setIsRemoving] = useState(false);
const { user: userStore } = useMobxStore();
const user = userStore.currentUser;
const {
user: { currentUser },
} = useMobxStore();
const handleClose = () => {
onClose();
@ -69,10 +70,10 @@ export const ConfirmWorkspaceMemberRemove: React.FC<Props> = observer((props) =>
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
{user?.id === data?.memberId ? "Leave workspace?" : `Remove ${data?.display_name}?`}
{currentUser?.id === data?.memberId ? "Leave workspace?" : `Remove ${data?.display_name}?`}
</Dialog.Title>
<div className="mt-2">
{user?.id === data?.memberId ? (
{currentUser?.id === data?.memberId ? (
<p className="text-sm text-custom-text-200">
Are you sure you want to leave the workspace? You will no longer have access to this
workspace. This action cannot be undone.

View File

@ -1,28 +1,19 @@
import React, { useEffect } from "react";
import { mutate } from "swr";
import { Controller, useFieldArray, useForm } from "react-hook-form";
import { Dialog, Transition } from "@headlessui/react";
// services
import { WorkspaceService } from "services/workspace.service";
// hooks
import useToast from "hooks/use-toast";
// ui
import { Button, CustomSelect, Input } from "@plane/ui";
// icons
import { Plus, X } from "lucide-react";
// types
import { IUser, TUserWorkspaceRole } from "types";
import { IWorkspaceBulkInviteFormData, TUserWorkspaceRole } from "types";
// constants
import { ROLE } from "constants/workspace";
// fetch-keys
import { WORKSPACE_INVITATIONS } from "constants/fetch-keys";
type Props = {
isOpen: boolean;
onClose: () => void;
workspaceSlug: string;
user: IUser | undefined;
onSuccess?: () => Promise<void>;
onSubmit: (data: IWorkspaceBulkInviteFormData) => Promise<void> | undefined;
};
type EmailRole = {
@ -43,11 +34,10 @@ const defaultValues: FormValues = {
],
};
const workspaceService = new WorkspaceService();
export const SendWorkspaceInvitationModal: React.FC<Props> = (props) => {
const { isOpen, onClose, workspaceSlug, user, onSuccess } = props;
const { isOpen, onClose, onSubmit } = props;
// form info
const {
control,
reset,
@ -60,8 +50,6 @@ export const SendWorkspaceInvitationModal: React.FC<Props> = (props) => {
name: "emails",
});
const { setToastAlert } = useToast();
const handleClose = () => {
onClose();
@ -71,31 +59,29 @@ export const SendWorkspaceInvitationModal: React.FC<Props> = (props) => {
}, 350);
};
const onSubmit = async (formData: FormValues) => {
if (!workspaceSlug) return;
// const onSubmit = async (formData: FormValues) => {
// if (!workspaceSlug) return;
await workspaceService
.inviteWorkspace(workspaceSlug, formData, user)
.then(async () => {
if (onSuccess) await onSuccess();
// return workspaceService
// .inviteWorkspace(workspaceSlug, formData, user)
handleClose();
setToastAlert({
type: "success",
title: "Success!",
message: "Invitations sent successfully.",
});
})
.catch((err) =>
setToastAlert({
type: "error",
title: "Error!",
message: `${err.error ?? "Something went wrong. Please try again."}`,
})
)
.finally(() => mutate(WORKSPACE_INVITATIONS));
};
// .then(async () => {
// if (onSuccess) await onSuccess();
// handleClose();
// setToastAlert({
// type: "success",
// title: "Success!",
// message: "Invitations sent successfully.",
// });
// })
// .catch((err) =>
// setToastAlert({
// type: "error",
// title: "Error!",
// message: `${err.error ?? "Something went wrong. Please try again."}`,
// })
// );
// };
const appendField = () => {
append({ email: "", role: 15 });

View File

@ -3,8 +3,6 @@ import Link from "next/link";
import { useRouter } from "next/router";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// services
import { WorkspaceService } from "services/workspace.service";
// hooks
import useToast from "hooks/use-toast";
// components
@ -33,17 +31,16 @@ type Props = {
};
};
// services
const workspaceService = new WorkspaceService();
export const WorkspaceMembersListItem: FC<Props> = (props) => {
const { member } = props;
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// store
const { workspace: workspaceStore, user: userStore } = useMobxStore();
const { currentWorkspaceMemberInfo, currentWorkspaceRole } = userStore;
const {
workspaceMember: { removeMember, updateMember, deleteWorkspaceInvitation },
user: { currentWorkspaceMemberInfo, currentWorkspaceRole },
} = useMobxStore();
const isAdmin = currentWorkspaceRole === 20;
// states
const [removeMemberModal, setRemoveMemberModal] = useState(false);
@ -54,7 +51,7 @@ export const WorkspaceMembersListItem: FC<Props> = (props) => {
if (!workspaceSlug) return;
if (member.member)
await workspaceStore.removeMember(workspaceSlug.toString(), member.id).catch((err) => {
await removeMember(workspaceSlug.toString(), member.id).catch((err) => {
const error = err?.error;
setToastAlert({
type: "error",
@ -63,8 +60,7 @@ export const WorkspaceMembersListItem: FC<Props> = (props) => {
});
});
else
await workspaceService
.deleteWorkspaceInvitations(workspaceSlug.toString(), member.id)
await deleteWorkspaceInvitation(workspaceSlug.toString(), member.id)
.then(() => {
setToastAlert({
type: "success",
@ -157,11 +153,9 @@ export const WorkspaceMembersListItem: FC<Props> = (props) => {
onChange={(value: TUserWorkspaceRole | undefined) => {
if (!workspaceSlug || !value) return;
workspaceStore
.updateMember(workspaceSlug.toString(), member.id, {
updateMember(workspaceSlug.toString(), member.id, {
role: value,
})
.catch(() => {
}).catch(() => {
setToastAlert({
type: "error",
title: "Error!",

View File

@ -1,64 +1,45 @@
import { FC } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// services
import { WorkspaceService } from "services/workspace.service";
// components
import { WorkspaceMembersListItem } from "components/workspace";
// ui
import { Loader } from "@plane/ui";
const workspaceService = new WorkspaceService();
export const WorkspaceMembersList: React.FC<{ searchQuery: string }> = observer(({ searchQuery }) => {
export const WorkspaceMembersList: FC<{ searchQuery: string }> = observer(({ searchQuery }) => {
const router = useRouter();
const { workspaceSlug } = router.query;
// store
const { workspace: workspaceStore, user: userStore } = useMobxStore();
const workspaceMembers = workspaceStore.workspaceMembers;
const user = userStore.currentWorkspaceMemberInfo;
const {
workspaceMember: {
workspaceMembers,
workspaceMembersWithInvitations,
workspaceMemberInvitations,
fetchWorkspaceMemberInvitations,
},
user: { currentWorkspaceMemberInfo },
} = useMobxStore();
// fetching workspace invitations
const { data: workspaceInvitations } = useSWR(
useSWR(
workspaceSlug ? `WORKSPACE_INVITATIONS_${workspaceSlug.toString()}` : null,
workspaceSlug ? () => workspaceService.workspaceInvitations(workspaceSlug.toString()) : null
workspaceSlug ? () => fetchWorkspaceMemberInvitations(workspaceSlug.toString()) : null
);
const members = [
...(workspaceInvitations?.map((item) => ({
id: item.id,
memberId: item.id,
avatar: "",
first_name: item.email,
last_name: "",
email: item.email,
display_name: item.email,
role: item.role,
status: item.accepted,
member: false,
accountCreated: item.accepted,
})) || []),
...(workspaceMembers?.map((item) => ({
id: item.id,
memberId: item.member?.id,
avatar: item.member?.avatar,
first_name: item.member?.first_name,
last_name: item.member?.last_name,
email: item.member?.email,
display_name: item.member?.display_name,
role: item.role,
status: true,
member: true,
accountCreated: true,
})) || []),
];
const searchedMembers = members?.filter((member) => {
const searchedMembers = workspaceMembersWithInvitations?.filter((member: any) => {
const fullName = `${member.first_name} ${member.last_name}`.toLowerCase();
const displayName = member.display_name.toLowerCase();
return displayName.includes(searchQuery.toLowerCase()) || fullName.includes(searchQuery.toLowerCase());
});
if (!workspaceMembers || !workspaceInvitations || !user)
if (
!workspaceMembers ||
!workspaceMemberInvitations ||
!workspaceMembersWithInvitations ||
!currentWorkspaceMemberInfo
)
return (
<Loader className="space-y-5">
<Loader.Item height="40px" />
@ -70,10 +51,10 @@ export const WorkspaceMembersList: React.FC<{ searchQuery: string }> = observer(
return (
<div className="divide-y-[0.5px] divide-custom-border-200">
{members.length > 0
? searchedMembers.map((member) => <WorkspaceMembersListItem key={member.id} member={member} />)
{workspaceMembersWithInvitations.length > 0
? searchedMembers?.map((member) => <WorkspaceMembersListItem key={member.id} member={member} />)
: null}
{searchedMembers.length === 0 && (
{searchedMembers?.length === 0 && (
<h4 className="text-md text-custom-text-400 text-center mt-20">No matching member</h4>
)}
</div>

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useEffect, useState, FC } from "react";
import { observer } from "mobx-react-lite";
import { Controller, useForm } from "react-hook-form";
import { Disclosure, Transition } from "@headlessui/react";
@ -29,7 +29,7 @@ const defaultValues: Partial<IWorkspace> = {
// services
const fileService = new FileService();
export const WorkspaceDetails: React.FC = observer(() => {
export const WorkspaceDetails: FC = observer(() => {
// states
const [deleteWorkspaceModal, setDeleteWorkspaceModal] = useState(false);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@ -37,9 +37,10 @@ export const WorkspaceDetails: React.FC = observer(() => {
const [isImageRemoving, setIsImageRemoving] = useState(false);
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
// store
const { workspace: workspaceStore, user: userStore } = useMobxStore();
const activeWorkspace = workspaceStore.currentWorkspace;
const { currentWorkspaceRole } = userStore;
const {
workspace: { currentWorkspace, updateWorkspace },
user: { currentWorkspaceRole },
} = useMobxStore();
const isAdmin = currentWorkspaceRole === 20;
// hooks
const { setToastAlert } = useToast();
@ -52,11 +53,11 @@ export const WorkspaceDetails: React.FC = observer(() => {
setValue,
formState: { errors, isSubmitting },
} = useForm<IWorkspace>({
defaultValues: { ...defaultValues, ...activeWorkspace },
defaultValues: { ...defaultValues, ...currentWorkspace },
});
const onSubmit = async (formData: IWorkspace) => {
if (!activeWorkspace) return;
if (!currentWorkspace) return;
const payload: Partial<IWorkspace> = {
logo: formData.logo,
@ -64,8 +65,7 @@ export const WorkspaceDetails: React.FC = observer(() => {
organization_size: formData.organization_size,
};
await workspaceStore
.updateWorkspace(activeWorkspace.slug, payload)
await updateWorkspace(currentWorkspace.slug, payload)
.then(() =>
setToastAlert({
title: "Success",
@ -77,13 +77,12 @@ export const WorkspaceDetails: React.FC = observer(() => {
};
const handleDelete = (url: string | null | undefined) => {
if (!activeWorkspace || !url) return;
if (!currentWorkspace || !url) return;
setIsImageRemoving(true);
fileService.deleteFile(activeWorkspace.id, url).then(() => {
workspaceStore
.updateWorkspace(activeWorkspace.slug, { logo: "" })
fileService.deleteFile(currentWorkspace.id, url).then(() => {
updateWorkspace(currentWorkspace.slug, { logo: "" })
.then(() => {
setToastAlert({
type: "success",
@ -104,10 +103,10 @@ export const WorkspaceDetails: React.FC = observer(() => {
};
useEffect(() => {
if (activeWorkspace) reset({ ...activeWorkspace });
}, [activeWorkspace, reset]);
if (currentWorkspace) reset({ ...currentWorkspace });
}, [currentWorkspace, reset]);
if (!activeWorkspace)
if (!currentWorkspace)
return (
<div className="grid place-items-center h-full w-full px-4 sm:px-0">
<Spinner />
@ -119,13 +118,13 @@ export const WorkspaceDetails: React.FC = observer(() => {
<DeleteWorkspaceModal
isOpen={deleteWorkspaceModal}
onClose={() => setDeleteWorkspaceModal(false)}
data={activeWorkspace}
data={currentWorkspace}
/>
<ImageUploadModal
isOpen={isImageUploadModalOpen}
onClose={() => setIsImageUploadModalOpen(false)}
isRemoving={isImageRemoving}
handleDelete={() => handleDelete(activeWorkspace?.logo)}
handleDelete={() => handleDelete(currentWorkspace?.logo)}
onSuccess={(imageUrl) => {
setIsImageUploading(true);
setValue("logo", imageUrl);
@ -148,7 +147,7 @@ export const WorkspaceDetails: React.FC = observer(() => {
</div>
) : (
<div className="relative flex h-14 w-14 items-center justify-center rounded bg-gray-700 p-4 uppercase text-white">
{activeWorkspace?.name?.charAt(0) ?? "N"}
{currentWorkspace?.name?.charAt(0) ?? "N"}
</div>
)}
</button>
@ -157,7 +156,7 @@ export const WorkspaceDetails: React.FC = observer(() => {
<h3 className="text-lg font-semibold leading-6">{watch("name")}</h3>
<span className="text-sm tracking-tight">{`${
typeof window !== "undefined" && window.location.origin.replace("http://", "").replace("https://", "")
}/${activeWorkspace.slug}`}</span>
}/${currentWorkspace.slug}`}</span>
<div className="flex item-center gap-2.5">
<button
className="flex items-center gap-1.5 text-xs text-left text-custom-primary-100 font-medium"
@ -246,7 +245,7 @@ export const WorkspaceDetails: React.FC = observer(() => {
value={`${
typeof window !== "undefined" &&
window.location.origin.replace("http://", "").replace("https://", "")
}/${activeWorkspace.slug}`}
}/${currentWorkspace.slug}`}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.url)}

View File

@ -49,16 +49,17 @@ const authService = new AuthService();
export const WorkspaceSidebarDropdown = observer(() => {
const router = useRouter();
const { workspaceSlug } = router.query;
const { theme: themeStore, workspace: workspaceStore, user: userStore } = useMobxStore();
const { workspaces, currentWorkspace: activeWorkspace } = workspaceStore;
const user = userStore.currentUser;
// store
const {
theme: { sidebarCollapsed },
workspace: { workspaces, currentWorkspace: activeWorkspace },
user: { currentUser, updateCurrentUser },
} = useMobxStore();
// hooks
const { setToastAlert } = useToast();
const handleWorkspaceNavigation = (workspace: IWorkspace) => {
userStore
.updateCurrentUser({
updateCurrentUser({
last_workspace_id: workspace?.id,
})
.then(() => {
@ -94,7 +95,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
<Menu.Button className="text-custom-sidebar-text-200 rounded-sm text-sm font-medium focus:outline-none w-full h-full truncate">
<div
className={`flex items-center gap-x-2 rounded-sm bg-custom-sidebar-background-80 p-1 truncate ${
themeStore.sidebarCollapsed ? "justify-center" : ""
sidebarCollapsed ? "justify-center" : ""
}`}
>
<div className="relative grid h-6 w-6 place-items-center rounded bg-gray-700 uppercase text-white flex-shrink-0">
@ -109,7 +110,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
)}
</div>
{!themeStore.sidebarCollapsed && (
{!sidebarCollapsed && (
<h4 className="text-custom-text-100 truncate">
{activeWorkspace?.name ? activeWorkspace.name : "Loading..."}
</h4>
@ -198,7 +199,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
)}
</div>
<div className="flex w-full flex-col items-start justify-start gap-2 border-t border-custom-sidebar-border-200 px-3 py-2 text-sm">
{userLinks(workspaceSlug?.toString() ?? "", user?.id ?? "").map((link, index) => (
{userLinks(workspaceSlug?.toString() ?? "", currentUser?.id ?? "").map((link, index) => (
<Menu.Item
key={index}
as="div"
@ -224,10 +225,16 @@ export const WorkspaceSidebarDropdown = observer(() => {
</Transition>
</Menu>
{!themeStore.sidebarCollapsed && (
{!sidebarCollapsed && (
<Menu as="div" className="relative flex-shrink-0">
<Menu.Button className="grid place-items-center outline-none">
<Avatar name={user?.display_name} src={user?.avatar} size={30} shape="square" className="!text-base" />
<Avatar
name={currentUser?.display_name}
src={currentUser?.avatar}
size={30}
shape="square"
className="!text-base"
/>
</Menu.Button>
<Transition
@ -244,8 +251,8 @@ export const WorkspaceSidebarDropdown = observer(() => {
border border-custom-sidebar-border-200 bg-custom-sidebar-background-100 px-1 py-2 divide-y divide-custom-sidebar-border-200 shadow-lg text-xs outline-none"
>
<div className="flex flex-col gap-2.5 pb-2">
<span className="px-2 text-custom-sidebar-text-200">{user?.email}</span>
{profileLinks(workspaceSlug?.toString() ?? "", user?.id ?? "").map((link, index) => (
<span className="px-2 text-custom-sidebar-text-200">{currentUser?.email}</span>
{profileLinks(workspaceSlug?.toString() ?? "", currentUser?.id ?? "").map((link, index) => (
<Menu.Item key={index} as="button" type="button">
<Link href={link.link}>
<a className="flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-custom-sidebar-background-80">

View File

@ -31,7 +31,11 @@ export const WorkspaceViewForm: React.FC<Props> = observer((props) => {
const router = useRouter();
const { workspaceSlug } = router.query;
const { workspace: workspaceStore, project: projectStore } = useMobxStore();
const {
workspace: workspaceStore,
project: projectStore,
workspaceMember: { workspaceMembers },
} = useMobxStore();
const {
formState: { errors, isSubmitting },
@ -143,7 +147,7 @@ export const WorkspaceViewForm: React.FC<Props> = observer((props) => {
}}
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet}
labels={workspaceStore.workspaceLabels ?? undefined}
members={workspaceStore.workspaceMembers?.map((m) => m.member) ?? undefined}
members={workspaceMembers?.map((m) => m.member) ?? undefined}
projects={workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined}
/>
</FiltersDropdown>
@ -157,7 +161,7 @@ export const WorkspaceViewForm: React.FC<Props> = observer((props) => {
handleClearAllFilters={clearAllFilters}
handleRemoveFilter={() => {}}
labels={workspaceStore.workspaceLabels ?? undefined}
members={workspaceStore.workspaceMembers?.map((m) => m.member) ?? undefined}
members={workspaceMembers?.map((m) => m.member) ?? undefined}
states={undefined}
/>
</div>

View File

@ -11,8 +11,9 @@ declare global {
}
const Crisp = observer(() => {
const { user: userStore } = useMobxStore();
const { currentUser } = userStore;
const {
user: { currentUser },
} = useMobxStore();
const validateCurrentUser = useCallback(() => {
if (currentUser) return currentUser.email;

View File

@ -13,19 +13,22 @@ export interface IUserAuthWrapper {
export const UserAuthWrapper: FC<IUserAuthWrapper> = (props) => {
const { children } = props;
// store
const { user: userStore, workspace: workspaceStore } = useMobxStore();
const {
user: { fetchCurrentUser, fetchCurrentUserSettings },
workspace: { fetchWorkspaces },
} = useMobxStore();
// router
const router = useRouter();
// fetching user information
const { data: currentUser, error } = useSWR("CURRENT_USER_DETAILS", () => userStore.fetchCurrentUser(), {
const { data: currentUser, error } = useSWR("CURRENT_USER_DETAILS", () => fetchCurrentUser(), {
shouldRetryOnError: false,
});
// fetching user settings
useSWR("CURRENT_USER_SETTINGS", () => userStore.fetchCurrentUserSettings(), {
useSWR("CURRENT_USER_SETTINGS", () => fetchCurrentUserSettings(), {
shouldRetryOnError: false,
});
// fetching all workspaces
useSWR(`USER_WORKSPACES_LIST`, () => workspaceStore.fetchWorkspaces(), {
useSWR(`USER_WORKSPACES_LIST`, () => fetchWorkspaces(), {
shouldRetryOnError: false,
});

View File

@ -15,30 +15,35 @@ export interface IWorkspaceAuthWrapper {
export const WorkspaceAuthWrapper: FC<IWorkspaceAuthWrapper> = observer((props) => {
const { children } = props;
// store
const { user: userStore, project: projectStore, workspace: workspaceStore } = useMobxStore();
const { currentWorkspaceMemberInfo, hasPermissionToCurrentWorkspace } = userStore;
const {
user: { currentWorkspaceMemberInfo, hasPermissionToCurrentWorkspace, fetchUserWorkspaceInfo },
project: { fetchProjects },
workspace: { fetchWorkspaceLabels },
workspaceMember: { fetchWorkspaceMembers },
} = useMobxStore();
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// fetching user workspace information
useSWR(
workspaceSlug ? `WORKSPACE_MEMBERS_ME_${workspaceSlug}` : null,
workspaceSlug ? () => userStore.fetchUserWorkspaceInfo(workspaceSlug.toString()) : null
workspaceSlug ? () => fetchUserWorkspaceInfo(workspaceSlug.toString()) : null
);
// fetching workspace projects
useSWR(
workspaceSlug ? `WORKSPACE_PROJECTS_${workspaceSlug}` : null,
workspaceSlug ? () => projectStore.fetchProjects(workspaceSlug.toString()) : null
workspaceSlug ? () => fetchProjects(workspaceSlug.toString()) : null
);
// fetch workspace members
useSWR(
workspaceSlug ? `WORKSPACE_MEMBERS_${workspaceSlug}` : null,
workspaceSlug ? () => workspaceStore.fetchWorkspaceMembers(workspaceSlug.toString()) : null
workspaceSlug ? () => fetchWorkspaceMembers(workspaceSlug.toString()) : null
);
// fetch workspace labels
useSWR(
workspaceSlug ? `WORKSPACE_LABELS_${workspaceSlug}` : null,
workspaceSlug ? () => workspaceStore.fetchWorkspaceLabels(workspaceSlug.toString()) : null
workspaceSlug ? () => fetchWorkspaceLabels(workspaceSlug.toString()) : null
);
// while data is being loaded

View File

@ -28,38 +28,36 @@ const AnalyticsPage: NextPageWithLayout = observer(() => {
const router = useRouter();
const { workspaceSlug } = router.query;
// store
const { project: projectStore, user: userStore, commandPalette: commandPaletteStore } = useMobxStore();
const user = userStore.currentUser;
const projects = workspaceSlug ? projectStore.projects[workspaceSlug?.toString()] : null;
const {
project: { workspaceProjects },
user: { currentUser },
commandPalette: { toggleCreateProjectModal },
} = useMobxStore();
const trackAnalyticsEvent = (tab: string) => {
if (!user) return;
if (!currentUser) return;
const eventPayload = {
workspaceSlug: workspaceSlug?.toString(),
};
const eventType =
tab === "scope_and_demand" ? "WORKSPACE_SCOPE_AND_DEMAND_ANALYTICS" : "WORKSPACE_CUSTOM_ANALYTICS";
trackEventService.trackAnalyticsEvent(eventPayload, eventType, user);
trackEventService.trackAnalyticsEvent(eventPayload, eventType, currentUser);
};
useEffect(() => {
if (!workspaceSlug) return;
if (user && workspaceSlug)
if (currentUser && workspaceSlug)
trackEventService.trackAnalyticsEvent(
{ workspaceSlug: workspaceSlug?.toString() },
"WORKSPACE_SCOPE_AND_DEMAND_ANALYTICS",
user
currentUser
);
}, [user, workspaceSlug]);
}, [currentUser, workspaceSlug]);
return (
<>
{projects && projects.length > 0 ? (
{workspaceProjects && workspaceProjects.length > 0 ? (
<div className="h-full flex flex-col overflow-hidden bg-custom-background-100">
<Tab.Group as={Fragment}>
<Tab.List as="div" className="space-x-2 border-b border-custom-border-200 px-5 py-3">
@ -96,7 +94,7 @@ const AnalyticsPage: NextPageWithLayout = observer(() => {
primaryButton={{
icon: <Plus className="h-4 w-4" />,
text: "New Project",
onClick: () => commandPaletteStore.toggleCreateProjectModal(true),
onClick: () => toggleCreateProjectModal(true),
}}
/>
</>

View File

@ -18,11 +18,13 @@ import { I_THEME_OPTION, THEME_OPTIONS } from "constants/themes";
import { NextPageWithLayout } from "types/app";
const ProfilePreferencesPage: NextPageWithLayout = observer(() => {
const { user: userStore } = useMobxStore();
const {
user: { currentUser, updateCurrentUserTheme },
} = useMobxStore();
// states
const [currentTheme, setCurrentTheme] = useState<I_THEME_OPTION | null>(null);
// computed
const userTheme = userStore.currentUser?.theme;
const userTheme = currentUser?.theme;
// hooks
const { setTheme } = useTheme();
const { setToastAlert } = useToast();
@ -38,7 +40,7 @@ const ProfilePreferencesPage: NextPageWithLayout = observer(() => {
const handleThemeChange = (themeOption: I_THEME_OPTION) => {
setTheme(themeOption.value);
userStore.updateCurrentUserTheme(themeOption.value).catch(() => {
updateCurrentUserTheme(themeOption.value).catch(() => {
setToastAlert({
title: "Failed to Update the theme",
type: "error",
@ -48,7 +50,7 @@ const ProfilePreferencesPage: NextPageWithLayout = observer(() => {
return (
<>
{userStore.currentUser ? (
{currentUser ? (
<div className="pr-9 py-8 w-full overflow-y-auto">
<div className="flex items-center py-3.5 border-b border-custom-border-100">
<h3 className="text-xl font-medium">Preferences</h3>

View File

@ -1,7 +1,9 @@
import { useState, ReactElement } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// hooks
import useUser from "hooks/use-user";
import useToast from "hooks/use-toast";
import { useMobxStore } from "lib/mobx/store-provider";
// layouts
import { AppLayout } from "layouts/app-layout";
import { WorkspaceSettingLayout } from "layouts/settings-layout";
@ -14,15 +16,41 @@ import { Button } from "@plane/ui";
import { Search } from "lucide-react";
// types
import { NextPageWithLayout } from "types/app";
import { IWorkspaceBulkInviteFormData } from "types";
const WorkspaceMembersSettingsPage: NextPageWithLayout = () => {
const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => {
const router = useRouter();
const { workspaceSlug } = router.query;
// store
const {
workspaceMember: { inviteMembersToWorkspace },
} = useMobxStore();
// states
const [inviteModal, setInviteModal] = useState(false);
const [searchQuery, setSearchQuery] = useState<string>("");
// hooks
const { user } = useUser();
const { setToastAlert } = useToast();
const handleWorkspaceInvite = (data: IWorkspaceBulkInviteFormData) => {
if (!workspaceSlug) return;
return inviteMembersToWorkspace(workspaceSlug.toString(), data)
.then(async () => {
setInviteModal(false);
setToastAlert({
type: "success",
title: "Success!",
message: "Invitations sent successfully.",
});
})
.catch((err) =>
setToastAlert({
type: "error",
title: "Error!",
message: `${err.error ?? "Something went wrong. Please try again."}`,
})
);
};
return (
<>
@ -30,8 +58,7 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = () => {
<SendWorkspaceInvitationModal
isOpen={inviteModal}
onClose={() => setInviteModal(false)}
workspaceSlug={workspaceSlug.toString()}
user={user}
onSubmit={handleWorkspaceInvite}
/>
)}
<section className="pr-9 py-8 w-full overflow-y-auto">
@ -55,7 +82,7 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = () => {
</section>
</>
);
};
});
WorkspaceMembersSettingsPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -18,23 +18,23 @@ import { IWorkspace } from "types";
import { NextPageWithLayout } from "types/app";
const CreateWorkspacePage: NextPageWithLayout = observer(() => {
// router
const router = useRouter();
// store
const {
user: { currentUser, updateCurrentUser },
} = useMobxStore();
// states
const [defaultValues, setDefaultValues] = useState({
name: "",
slug: "",
organization_size: "",
});
const router = useRouter();
const { user: userStore } = useMobxStore();
const user = userStore.currentUser;
// hooks
const { theme } = useTheme();
const onSubmit = async (workspace: IWorkspace) => {
await userStore
.updateCurrentUser({ last_workspace_id: workspace.id })
.then(() => router.push(`/${workspace.slug}`));
await updateCurrentUser({ last_workspace_id: workspace.id }).then(() => router.push(`/${workspace.slug}`));
};
return (
@ -54,7 +54,7 @@ const CreateWorkspacePage: NextPageWithLayout = observer(() => {
</div>
</button>
<div className="absolute sm:fixed text-custom-text-100 text-sm right-4 top-1/4 sm:top-12 -translate-y-1/2 sm:translate-y-0 sm:right-16 sm:py-5">
{user?.email}
{currentUser?.email}
</div>
</div>
<div className="relative flex justify-center sm:justify-start sm:items-center h-full px-8 pb-8 sm:p-0 sm:pr-[8.33%] sm:w-10/12 md:w-9/12 lg:w-4/5">

View File

@ -30,9 +30,12 @@ const workspaceService = new WorkspaceService();
const OnboardingPage: NextPageWithLayout = observer(() => {
const [step, setStep] = useState<number | null>(null);
const { user: userStore, workspace: workspaceStore } = useMobxStore();
const {
user: { currentUser, updateCurrentUser, updateUserOnBoard },
workspace: workspaceStore,
} = useMobxStore();
const user = userStore.currentUser ?? undefined;
const user = currentUser ?? undefined;
const workspaces = workspaceStore.workspaces;
const userWorkspaces = workspaceStore.workspacesCreateByCurrentUser;
@ -48,7 +51,7 @@ const OnboardingPage: NextPageWithLayout = observer(() => {
const updateLastWorkspace = async () => {
if (!workspaces) return;
await userStore.updateCurrentUser({
await updateCurrentUser({
last_workspace_id: workspaces[0]?.id,
});
};
@ -64,14 +67,14 @@ const OnboardingPage: NextPageWithLayout = observer(() => {
},
};
await userStore.updateCurrentUser(payload);
await updateCurrentUser(payload);
};
// complete onboarding
const finishOnboarding = async () => {
if (!user) return;
await userStore.updateUserOnBoard();
await updateUserOnBoard();
};
useEffect(() => {

View File

@ -28,7 +28,7 @@ export interface IProjectStore {
// computed
searchedProjects: IProject[];
workspaceProjects: IProject[];
workspaceProjects: IProject[] | null;
projectLabels: IIssueLabels[] | null;
projectMembers: IProjectMember[] | null;
projectEstimates: IEstimate[] | null;
@ -183,8 +183,10 @@ export class ProjectStore implements IProjectStore {
}
get workspaceProjects() {
if (!this.rootStore.workspace.workspaceSlug) return [];
return this.projects?.[this.rootStore.workspace.workspaceSlug];
if (!this.rootStore.workspace.workspaceSlug) return null;
const projects = this.projects[this.rootStore.workspace.workspaceSlug];
if (!projects) return null;
return projects;
}
get currentProjectDetails() {

View File

@ -19,7 +19,14 @@ import {
IIssueQuickAddStore,
IssueQuickAddStore,
} from "store/issue";
import { IWorkspaceFilterStore, IWorkspaceStore, WorkspaceFilterStore, WorkspaceStore } from "store/workspace";
import {
IWorkspaceFilterStore,
IWorkspaceStore,
WorkspaceFilterStore,
WorkspaceStore,
WorkspaceMemberStore,
IWorkspaceMemberStore,
} from "store/workspace";
import {
IProjectPublishStore,
IProjectStore,
@ -113,6 +120,7 @@ export class RootStore {
commandPalette: ICommandPaletteStore;
workspace: IWorkspaceStore;
workspaceFilter: IWorkspaceFilterStore;
workspaceMember: IWorkspaceMemberStore;
projectPublish: IProjectPublishStore;
project: IProjectStore;
@ -176,6 +184,7 @@ export class RootStore {
this.workspace = new WorkspaceStore(this);
this.workspaceFilter = new WorkspaceFilterStore(this);
this.workspaceMember = new WorkspaceMemberStore(this);
this.project = new ProjectStore(this);
this.projectState = new ProjectStateStore(this);

View File

@ -1,2 +1,3 @@
export * from "./workspace_filters.store";
export * from "./workspace.store";
export * from "./workspace-member.store";

View File

@ -0,0 +1,274 @@
import { action, computed, observable, makeObservable, runInAction } from "mobx";
import { RootStore } from "../root";
// types
import { IUser, IWorkspaceMember, IWorkspaceMemberInvitation, IWorkspaceBulkInviteFormData } from "types";
// services
import { WorkspaceService } from "services/workspace.service";
export interface IWorkspaceMemberStore {
// states
loader: boolean;
error: any | null;
// observables
members: { [workspaceSlug: string]: IWorkspaceMember[] }; // workspaceSlug: members[]
memberInvitations: { [workspaceSlug: string]: IWorkspaceMemberInvitation[] };
// actions
fetchWorkspaceMembers: (workspaceSlug: string) => Promise<void>;
fetchWorkspaceMemberInvitations: (workspaceSlug: string) => Promise<IWorkspaceMemberInvitation[]>;
updateMember: (workspaceSlug: string, memberId: string, data: Partial<IWorkspaceMember>) => Promise<void>;
removeMember: (workspaceSlug: string, memberId: string) => Promise<void>;
inviteMembersToWorkspace: (workspaceSlug: string, data: IWorkspaceBulkInviteFormData) => Promise<any>;
deleteWorkspaceInvitation: (workspaceSlug: string, memberId: string) => Promise<void>;
// computed
workspaceMembers: IWorkspaceMember[] | null;
workspaceMemberInvitations: IWorkspaceMemberInvitation[] | null;
workspaceMembersWithInvitations: any[] | null;
}
export class WorkspaceMemberStore implements IWorkspaceMemberStore {
// states
loader: boolean = false;
error: any | null = null;
// observables
members: { [workspaceSlug: string]: IWorkspaceMember[] } = {};
memberInvitations: { [workspaceSlug: string]: IWorkspaceMemberInvitation[] } = {};
// services
workspaceService;
// root store
rootStore;
constructor(_rootStore: RootStore) {
makeObservable(this, {
// states
loader: observable.ref,
error: observable.ref,
// observables
members: observable.ref,
memberInvitations: observable.ref,
// actions
fetchWorkspaceMembers: action,
fetchWorkspaceMemberInvitations: action,
updateMember: action,
removeMember: action,
inviteMembersToWorkspace: action,
deleteWorkspaceInvitation: action,
// computed
workspaceMembers: computed,
workspaceMemberInvitations: computed,
workspaceMembersWithInvitations: computed,
});
this.rootStore = _rootStore;
this.workspaceService = new WorkspaceService();
}
/**
* computed value of workspace members using the workspace slug from the store
*/
get workspaceMembers() {
if (!this.rootStore.workspace.workspaceSlug) return null;
const members = this.members?.[this.rootStore.workspace.workspaceSlug];
if (!members) return null;
return members;
}
/**
* Computed value of workspace member invitations using workspace slug from store
*/
get workspaceMemberInvitations() {
if (!this.rootStore.workspace.workspaceSlug) return null;
const invitations = this.memberInvitations?.[this.rootStore.workspace.workspaceSlug];
if (!invitations) return null;
return invitations;
}
/**
* computed value provides the members information including the invitations.
*/
get workspaceMembersWithInvitations() {
if (!this.workspaceMembers || !this.workspaceMemberInvitations) return null;
return [
...(this.workspaceMemberInvitations?.map((item) => ({
id: item.id,
memberId: item.id,
avatar: "",
first_name: item.email,
last_name: "",
email: item.email,
display_name: item.email,
role: item.role,
status: item.accepted,
member: false,
accountCreated: item.accepted,
})) || []),
...(this.workspaceMembers?.map((item) => ({
id: item.id,
memberId: item.member?.id,
avatar: item.member?.avatar,
first_name: item.member?.first_name,
last_name: item.member?.last_name,
email: item.member?.email,
display_name: item.member?.display_name,
role: item.role,
status: true,
member: true,
accountCreated: true,
})) || []),
];
}
/**
* fetch workspace members using workspace slug
* @param workspaceSlug
*/
fetchWorkspaceMembers = async (workspaceSlug: string) => {
try {
runInAction(() => {
this.loader = true;
this.error = null;
});
const membersResponse = await this.workspaceService.fetchWorkspaceMembers(workspaceSlug);
runInAction(() => {
this.members = {
...this.members,
[workspaceSlug]: membersResponse,
};
this.loader = false;
this.error = null;
});
} catch (error) {
runInAction(() => {
this.loader = false;
this.error = error;
});
}
};
/**
* fetching workspace member invitations
* @param workspaceSlug
* @returns
*/
fetchWorkspaceMemberInvitations = async (workspaceSlug: string) => {
try {
const membersInvitations = await this.workspaceService.workspaceInvitations(workspaceSlug);
runInAction(() => {
this.memberInvitations = {
...this.memberInvitations,
[workspaceSlug]: membersInvitations,
};
});
return membersInvitations;
} catch (error) {
throw error;
}
};
/**
* invite members to the workspace using emails
* @param workspaceSlug
* @param data
*/
inviteMembersToWorkspace = async (workspaceSlug: string, data: IWorkspaceBulkInviteFormData) => {
try {
await this.workspaceService.inviteWorkspace(workspaceSlug, data, this.rootStore.user.currentUser as IUser);
await this.fetchWorkspaceMemberInvitations(workspaceSlug);
} catch (error) {
throw error;
}
};
/**
* delete the workspace invitation
* @param workspaceSlug
* @param memberId
*/
deleteWorkspaceInvitation = async (workspaceSlug: string, memberId: string) => {
try {
runInAction(() => {
this.memberInvitations = {
...this.memberInvitations,
[workspaceSlug]: [...this.memberInvitations[workspaceSlug].filter((inv) => inv.id !== memberId)],
};
});
await this.workspaceService.deleteWorkspaceInvitations(workspaceSlug.toString(), memberId);
} catch (error) {
throw error;
}
};
/**
* update workspace member using workspace slug and member id and data
* @param workspaceSlug
* @param memberId
* @param data
*/
updateMember = async (workspaceSlug: string, memberId: string, data: Partial<IWorkspaceMember>) => {
const members = this.members?.[workspaceSlug];
members?.map((m) => (m.id === memberId ? { ...m, ...data } : m));
try {
runInAction(() => {
this.loader = true;
this.error = null;
});
await this.workspaceService.updateWorkspaceMember(workspaceSlug, memberId, data);
runInAction(() => {
this.loader = false;
this.error = null;
this.members = {
...this.members,
[workspaceSlug]: members,
};
});
} catch (error) {
runInAction(() => {
this.loader = false;
this.error = error;
});
throw error;
}
};
/**
* remove workspace member using workspace slug and member id
* @param workspaceSlug
* @param memberId
*/
removeMember = async (workspaceSlug: string, memberId: string) => {
const members = this.members?.[workspaceSlug];
members?.filter((m) => m.id !== memberId);
try {
runInAction(() => {
this.loader = true;
this.error = null;
});
await this.workspaceService.deleteWorkspaceMember(workspaceSlug, memberId);
runInAction(() => {
this.loader = false;
this.error = null;
this.members = {
...this.members,
[workspaceSlug]: members,
};
});
} catch (error) {
runInAction(() => {
this.loader = false;
this.error = error;
});
throw error;
}
};
}

View File

@ -16,7 +16,6 @@ export interface IWorkspaceStore {
workspaceSlug: string | null;
workspaces: IWorkspace[] | undefined;
labels: { [workspaceSlug: string]: IIssueLabels[] }; // workspaceSlug: labels[]
members: { [workspaceSlug: string]: IWorkspaceMember[] }; // workspaceSlug: members[]
// actions
setWorkspaceSlug: (workspaceSlug: string) => void;
@ -24,22 +23,16 @@ export interface IWorkspaceStore {
getWorkspaceLabelById: (workspaceSlug: string, labelId: string) => IIssueLabels | null;
fetchWorkspaces: () => Promise<IWorkspace[]>;
fetchWorkspaceLabels: (workspaceSlug: string) => Promise<void>;
fetchWorkspaceMembers: (workspaceSlug: string) => Promise<void>;
// workspace write operations
createWorkspace: (data: Partial<IWorkspace>) => Promise<IWorkspace>;
updateWorkspace: (workspaceSlug: string, data: Partial<IWorkspace>) => Promise<IWorkspace>;
deleteWorkspace: (workspaceSlug: string) => Promise<void>;
// members write operations
updateMember: (workspaceSlug: string, memberId: string, data: Partial<IWorkspaceMember>) => Promise<void>;
removeMember: (workspaceSlug: string, memberId: string) => Promise<void>;
// computed
currentWorkspace: IWorkspace | null;
workspacesCreateByCurrentUser: IWorkspace[] | null;
workspaceLabels: IIssueLabels[] | null;
workspaceMembers: IWorkspaceMember[] | null;
}
export class WorkspaceStore implements IWorkspaceStore {
@ -72,7 +65,6 @@ export class WorkspaceStore implements IWorkspaceStore {
workspaceSlug: observable.ref,
workspaces: observable.ref,
labels: observable.ref,
members: observable.ref,
// actions
setWorkspaceSlug: action,
@ -80,21 +72,15 @@ export class WorkspaceStore implements IWorkspaceStore {
getWorkspaceLabelById: action,
fetchWorkspaces: action,
fetchWorkspaceLabels: action,
fetchWorkspaceMembers: action,
// workspace write operations
createWorkspace: action,
updateWorkspace: action,
deleteWorkspace: action,
// members write operations
updateMember: action,
removeMember: action,
// computed
currentWorkspace: computed,
workspaceLabels: computed,
workspaceMembers: computed,
});
this.rootStore = _rootStore;
@ -135,15 +121,6 @@ export class WorkspaceStore implements IWorkspaceStore {
return _labels && Object.keys(_labels).length > 0 ? _labels : [];
}
/**
* computed value of workspace members using the workspace slug from the store
*/
get workspaceMembers() {
if (!this.workspaceSlug) return [];
const _members = this.members?.[this.workspaceSlug];
return _members && Object.keys(_members).length > 0 ? _members : [];
}
/**
* set workspace slug in the store
* @param workspaceSlug
@ -224,35 +201,6 @@ export class WorkspaceStore implements IWorkspaceStore {
}
};
/**
* fetch workspace members using workspace slug
* @param workspaceSlug
*/
fetchWorkspaceMembers = async (workspaceSlug: string) => {
try {
runInAction(() => {
this.loader = true;
this.error = null;
});
const membersResponse = await this.workspaceService.fetchWorkspaceMembers(workspaceSlug);
runInAction(() => {
this.members = {
...this.members,
[workspaceSlug]: membersResponse,
};
this.loader = false;
this.error = null;
});
} catch (error) {
runInAction(() => {
this.loader = false;
this.error = error;
});
}
};
/**
* create workspace using the workspace data
* @param data
@ -351,75 +299,4 @@ export class WorkspaceStore implements IWorkspaceStore {
throw error;
}
};
/**
* update workspace member using workspace slug and member id and data
* @param workspaceSlug
* @param memberId
* @param data
*/
updateMember = async (workspaceSlug: string, memberId: string, data: Partial<IWorkspaceMember>) => {
const members = this.members?.[workspaceSlug];
members?.map((m) => (m.id === memberId ? { ...m, ...data } : m));
try {
runInAction(() => {
this.loader = true;
this.error = null;
});
await this.workspaceService.updateWorkspaceMember(workspaceSlug, memberId, data);
runInAction(() => {
this.loader = false;
this.error = null;
this.members = {
...this.members,
[workspaceSlug]: members,
};
});
} catch (error) {
runInAction(() => {
this.loader = false;
this.error = error;
});
throw error;
}
};
/**
* remove workspace member using workspace slug and member id
* @param workspaceSlug
* @param memberId
*/
removeMember = async (workspaceSlug: string, memberId: string) => {
const members = this.members?.[workspaceSlug];
members?.filter((m) => m.id !== memberId);
try {
runInAction(() => {
this.loader = true;
this.error = null;
});
await this.workspaceService.deleteWorkspaceMember(workspaceSlug, memberId);
runInAction(() => {
this.loader = false;
this.error = null;
this.members = {
...this.members,
[workspaceSlug]: members,
};
});
} catch (error) {
runInAction(() => {
this.loader = false;
this.error = error;
});
throw error;
}
};
}