forked from github/plane
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:
parent
556b2d2617
commit
a6567bbce4
@ -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>
|
||||
|
@ -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 ?? {}) && (
|
||||
|
@ -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" />}
|
||||
|
@ -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}
|
||||
|
@ -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 () => {
|
||||
if (workspaceSlug && projectId) {
|
||||
await issueFilterStore.fetchUserProjectFilters(workspaceSlug.toString(), projectId.toString());
|
||||
await issueStore.fetchIssues(workspaceSlug.toString(), projectId.toString());
|
||||
}
|
||||
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;
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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"
|
||||
/>
|
||||
)}
|
||||
|
@ -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 <></>;
|
||||
|
@ -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.
|
||||
|
@ -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 });
|
||||
|
@ -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,17 +153,15 @@ export const WorkspaceMembersListItem: FC<Props> = (props) => {
|
||||
onChange={(value: TUserWorkspaceRole | undefined) => {
|
||||
if (!workspaceSlug || !value) return;
|
||||
|
||||
workspaceStore
|
||||
.updateMember(workspaceSlug.toString(), member.id, {
|
||||
role: value,
|
||||
})
|
||||
.catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "An error occurred while updating member role. Please try again.",
|
||||
});
|
||||
updateMember(workspaceSlug.toString(), member.id, {
|
||||
role: value,
|
||||
}).catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "An error occurred while updating member role. Please try again.",
|
||||
});
|
||||
});
|
||||
}}
|
||||
disabled={
|
||||
member.memberId === currentWorkspaceMemberInfo.member ||
|
||||
|
@ -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>
|
||||
|
@ -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)}
|
||||
|
@ -49,18 +49,19 @@ 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({
|
||||
last_workspace_id: workspace?.id,
|
||||
})
|
||||
updateCurrentUser({
|
||||
last_workspace_id: workspace?.id,
|
||||
})
|
||||
.then(() => {
|
||||
router.push(`/${workspace.slug}/`);
|
||||
})
|
||||
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
});
|
||||
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
@ -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>
|
||||
|
@ -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 (
|
||||
|
@ -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">
|
||||
|
@ -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(() => {
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
|
@ -1,2 +1,3 @@
|
||||
export * from "./workspace_filters.store";
|
||||
export * from "./workspace.store";
|
||||
export * from "./workspace-member.store";
|
||||
|
274
web/store/workspace/workspace-member.store.ts
Normal file
274
web/store/workspace/workspace-member.store.ts
Normal 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;
|
||||
}
|
||||
};
|
||||
}
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user