fix: store level fixes (#2597)

This commit is contained in:
sriram veeraghanta 2023-11-01 19:22:10 +05:30 committed by GitHub
parent d46eb9c59a
commit 8c620c4f96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 233 additions and 185 deletions

View File

@ -24,19 +24,15 @@ type Props = {
export const EstimateListItem: React.FC<Props> = observer((props) => {
const { estimate, editEstimate, deleteEstimate } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store
const { project: projectStore } = useMobxStore();
const { currentProjectDetails } = projectStore;
// hooks
const { setToastAlert } = useToast();
// derived values
const projectDetails = projectStore.project_details?.[projectId?.toString()!];
const handleUseEstimate = async () => {
if (!workspaceSlug || !projectId) return;
@ -63,7 +59,7 @@ export const EstimateListItem: React.FC<Props> = observer((props) => {
<div>
<h6 className="flex w-[40vw] items-center gap-2 truncate text-sm font-medium">
{estimate.name}
{projectDetails?.estimate && projectDetails?.estimate === estimate.id && (
{currentProjectDetails?.estimate && currentProjectDetails?.estimate === estimate.id && (
<span className="rounded bg-green-500/20 px-2 py-0.5 text-xs text-green-500">In use</span>
)}
</h6>
@ -72,7 +68,7 @@ export const EstimateListItem: React.FC<Props> = observer((props) => {
</p>
</div>
<div className="flex items-center gap-2">
{projectDetails?.estimate !== estimate?.id && estimate?.points?.length > 0 && (
{currentProjectDetails?.estimate !== estimate?.id && estimate?.points?.length > 0 && (
<Button variant="neutral-primary" onClick={handleUseEstimate}>
Use
</Button>
@ -88,7 +84,7 @@ export const EstimateListItem: React.FC<Props> = observer((props) => {
<span>Edit estimate</span>
</div>
</CustomMenu.MenuItem>
{projectDetails?.estimate !== estimate.id && (
{currentProjectDetails?.estimate !== estimate.id && (
<CustomMenu.MenuItem
onClick={() => {
deleteEstimate(estimate.id);

View File

@ -1,6 +1,5 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
// store
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
@ -25,18 +24,15 @@ export const EstimatesList: React.FC = observer(() => {
// store
const { project: projectStore } = useMobxStore();
const { currentProjectDetails } = projectStore;
// states
const [estimateFormOpen, setEstimateFormOpen] = useState(false);
const [estimateToDelete, setEstimateToDelete] = useState<string | null>(null);
const [estimateToUpdate, setEstimateToUpdate] = useState<IEstimate | undefined>();
// hooks
const { setToastAlert } = useToast();
// derived values
const estimatesList = projectStore.projectEstimates;
const projectDetails = projectStore.project_details?.[projectId?.toString()!];
const editEstimate = (estimate: IEstimate) => {
setEstimateFormOpen(true);
@ -88,7 +84,7 @@ export const EstimatesList: React.FC = observer(() => {
>
Add Estimate
</Button>
{projectDetails?.estimate && (
{currentProjectDetails?.estimate && (
<Button variant="neutral-primary" onClick={disableEstimates}>
Disable Estimates
</Button>

View File

@ -29,10 +29,10 @@ const moduleViewOptions: { type: "list" | "grid" | "gantt_chart"; icon: any }[]
export const ModulesListHeader: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { workspaceSlug } = router.query;
// store
const { project: projectStore } = useMobxStore();
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : undefined;
const { currentProjectDetails } = projectStore;
const { storedValue: modulesView, setValue: setModulesView } = useLocalStorage("modules_view", "grid");
@ -52,7 +52,7 @@ export const ModulesListHeader: React.FC = observer(() => {
</Link>
}
/>
<BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Modules`} />
<BreadcrumbItem title={`${truncateText(currentProjectDetails?.name ?? "Project", 32)} Modules`} />
</Breadcrumbs>
</div>
</div>

View File

@ -17,10 +17,10 @@ export interface IProjectSettingHeader {
export const ProjectSettingHeader: FC<IProjectSettingHeader> = observer((props) => {
const { title } = props;
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { workspaceSlug } = router.query;
// store
const { project: projectStore } = useMobxStore();
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null;
const { currentProjectDetails } = projectStore;
return (
<div
@ -31,9 +31,9 @@ export const ProjectSettingHeader: FC<IProjectSettingHeader> = observer((props)
<Breadcrumbs onBack={() => router.back()}>
<BreadcrumbItem
link={
<Link href={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}>
<Link href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}>
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}>
<p className="truncate">{`${truncateText(projectDetails?.name ?? "Project", 32)}`}</p>
<p className="truncate">{`${truncateText(currentProjectDetails?.name ?? "Project", 32)}`}</p>
</a>
</Link>
}

View File

@ -18,6 +18,10 @@ import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
export interface ICycleKanBanLayout {}
export const CycleKanBanLayout: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, cycleId } = router.query;
// store
const {
project: projectStore,
cycleIssue: cycleIssueStore,
@ -25,9 +29,7 @@ export const CycleKanBanLayout: React.FC = observer(() => {
cycleIssueKanBanView: cycleIssueKanBanViewStore,
issueDetail: issueDetailStore,
} = useMobxStore();
const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query;
const { currentProjectDetails } = projectStore;
const issues = cycleIssueStore?.getIssues;
@ -83,8 +85,6 @@ export const CycleKanBanLayout: React.FC = observer(() => {
cycleIssueKanBanViewStore.handleKanBanToggle(toggle, value);
};
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null;
const states = projectStore?.projectStates || null;
const priorities = ISSUE_PRIORITIES || null;
const labels = projectStore?.projectLabels || null;
@ -92,8 +92,8 @@ export const CycleKanBanLayout: React.FC = observer(() => {
const stateGroups = ISSUE_STATE_GROUPS || null;
const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null;
const estimates =
projectDetails?.estimate !== null
? projectStore.projectEstimates?.find((e) => e.id === projectDetails?.estimate) || null
currentProjectDetails?.estimate !== null
? projectStore.projectEstimates?.find((e) => e.id === currentProjectDetails?.estimate) || null
: null;
return (

View File

@ -18,6 +18,9 @@ import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
export interface IModuleKanBanLayout {}
export const ModuleKanBanLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, moduleId } = router.query;
// store
const {
project: projectStore,
moduleIssue: moduleIssueStore,
@ -25,9 +28,7 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
moduleIssueKanBanView: moduleIssueKanBanViewStore,
issueDetail: issueDetailStore,
} = useMobxStore();
const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query;
const { currentProjectDetails } = projectStore;
const issues = moduleIssueStore?.getIssues;
@ -83,8 +84,6 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
moduleIssueKanBanViewStore.handleKanBanToggle(toggle, value);
};
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null;
const states = projectStore?.projectStates || null;
const priorities = ISSUE_PRIORITIES || null;
const labels = projectStore?.projectLabels || null;
@ -92,8 +91,8 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
const stateGroups = ISSUE_STATE_GROUPS || null;
const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null;
const estimates =
projectDetails?.estimate !== null
? projectStore.projectEstimates?.find((e) => e.id === projectDetails?.estimate) || null
currentProjectDetails?.estimate !== null
? projectStore.projectEstimates?.find((e) => e.id === currentProjectDetails?.estimate) || null
: null;
return (

View File

@ -19,7 +19,7 @@ export interface IKanBanLayout {}
export const KanBanLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { workspaceSlug } = router.query;
const {
project: projectStore,
@ -28,6 +28,7 @@ export const KanBanLayout: React.FC = observer(() => {
issueKanBanView: issueKanBanViewStore,
issueDetail: issueDetailStore,
} = useMobxStore();
const { currentProjectDetails } = projectStore;
const issues = issueStore?.getIssues;
@ -74,8 +75,6 @@ export const KanBanLayout: React.FC = observer(() => {
issueKanBanViewStore.handleKanBanToggle(toggle, value);
};
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null;
const states = projectStore?.projectStates || null;
const priorities = ISSUE_PRIORITIES || null;
const labels = projectStore?.projectLabels || null;
@ -83,8 +82,8 @@ export const KanBanLayout: React.FC = observer(() => {
const stateGroups = ISSUE_STATE_GROUPS || null;
const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null;
const estimates =
projectDetails?.estimate !== null
? projectStore.projectEstimates?.find((e) => e.id === projectDetails?.estimate) || null
currentProjectDetails?.estimate !== null
? projectStore.projectEstimates?.find((e) => e.id === currentProjectDetails?.estimate) || null
: null;
return (

View File

@ -17,14 +17,15 @@ export interface ICycleListLayout {}
export const CycleListLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query;
const { workspaceSlug, cycleId } = router.query;
// store
const {
project: projectStore,
issueFilter: issueFilterStore,
cycleIssue: cycleIssueStore,
issueDetail: issueDetailStore,
} = useMobxStore();
const { currentProjectDetails } = projectStore;
const issues = cycleIssueStore?.getIssues;
@ -54,8 +55,6 @@ export const CycleListLayout: React.FC = observer(() => {
[cycleIssueStore, issueDetailStore, cycleId, workspaceSlug]
);
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null;
const states = projectStore?.projectStates || null;
const priorities = ISSUE_PRIORITIES || null;
const labels = projectStore?.projectLabels || null;
@ -63,8 +62,8 @@ export const CycleListLayout: React.FC = observer(() => {
const stateGroups = ISSUE_STATE_GROUPS || null;
const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null;
const estimates =
projectDetails?.estimate !== null
? projectStore.projectEstimates?.find((e) => e.id === projectDetails?.estimate) || null
currentProjectDetails?.estimate !== null
? projectStore.projectEstimates?.find((e) => e.id === currentProjectDetails?.estimate) || null
: null;
return (

View File

@ -17,7 +17,7 @@ export interface IModuleListLayout {}
export const ModuleListLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query;
const { workspaceSlug, moduleId } = router.query;
const {
project: projectStore,
@ -25,6 +25,7 @@ export const ModuleListLayout: React.FC = observer(() => {
moduleIssue: moduleIssueStore,
issueDetail: issueDetailStore,
} = useMobxStore();
const { currentProjectDetails } = projectStore;
const issues = moduleIssueStore?.getIssues;
@ -54,8 +55,6 @@ export const ModuleListLayout: React.FC = observer(() => {
[moduleIssueStore, issueDetailStore, moduleId, workspaceSlug]
);
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null;
const states = projectStore?.projectStates || null;
const priorities = ISSUE_PRIORITIES || null;
const labels = projectStore?.projectLabels || null;
@ -63,8 +62,8 @@ export const ModuleListLayout: React.FC = observer(() => {
const stateGroups = ISSUE_STATE_GROUPS || null;
const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null;
const estimates =
projectDetails?.estimate !== null
? projectStore.projectEstimates?.find((e) => e.id === projectDetails?.estimate) || null
currentProjectDetails?.estimate !== null
? projectStore.projectEstimates?.find((e) => e.id === currentProjectDetails?.estimate) || null
: null;
return (

View File

@ -15,14 +15,15 @@ import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
export const ListLayout: FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { workspaceSlug } = router.query;
// store
const {
project: projectStore,
issue: issueStore,
issueDetail: issueDetailStore,
issueFilter: issueFilterStore,
} = useMobxStore();
const { currentProjectDetails } = projectStore;
const issues = issueStore?.getIssues;
@ -43,8 +44,6 @@ export const ListLayout: FC = observer(() => {
[issueStore, issueDetailStore, workspaceSlug]
);
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null;
const states = projectStore?.projectStates || null;
const priorities = ISSUE_PRIORITIES || null;
const labels = projectStore?.projectLabels || null;
@ -52,8 +51,8 @@ export const ListLayout: FC = observer(() => {
const stateGroups = ISSUE_STATE_GROUPS || null;
const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null;
const estimates =
projectDetails?.estimate !== null
? projectStore.projectEstimates?.find((e) => e.id === projectDetails?.estimate) || null
currentProjectDetails?.estimate !== null
? projectStore.projectEstimates?.find((e) => e.id === currentProjectDetails?.estimate) || null
: null;
return (

View File

@ -31,7 +31,8 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
const { workspaceSlug, issue, issueReactions, user, issueUpdate, issueReactionCreate, issueReactionRemove } = props;
// store
const { user: userStore } = useMobxStore();
const isAllowed = [5, 10].includes(userStore.projectMemberInfo?.role || 0);
const { currentProjectRole } = userStore;
const isAllowed = [5, 10].includes(currentProjectRole || 0);
// states
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
const [characterLimit, setCharacterLimit] = useState(false);

View File

@ -9,12 +9,12 @@ import { ModuleGanttBlock, ModuleGanttSidebarBlock } from "components/modules";
import { IModule } from "types";
export const ModulesListGanttChartView: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { workspaceSlug } = router.query;
// store
const { project: projectStore, module: moduleStore } = useMobxStore();
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : undefined;
const { currentProjectDetails } = projectStore;
const modules = moduleStore.projectModules;
const handleModuleUpdate = (module: IModule, payload: IBlockUpdateData) => {
@ -36,7 +36,7 @@ export const ModulesListGanttChartView: React.FC = observer(() => {
}))
: [];
const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
const isAllowed = currentProjectDetails?.member_role === 20 || currentProjectDetails?.member_role === 15;
return (
<div className="w-full h-full overflow-y-auto">

View File

@ -26,32 +26,28 @@ type Props = {
export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
const { member } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// states
const [selectedRemoveMember, setSelectedRemoveMember] = useState<any | null>(null);
const [selectedInviteRemoveMember, setSelectedInviteRemoveMember] = useState<any | null>(null);
// store
const { user: userStore, project: projectStore } = useMobxStore();
// hooks
const { setToastAlert } = useToast();
// fetching project members
useSWR(
workspaceSlug && projectId ? `PROJECT_MEMBERS_${projectId.toString().toUpperCase()}` : null,
workspaceSlug && projectId
? () => projectStore.fetchProjectMembers(workspaceSlug.toString(), projectId.toString())
: null
);
// derived values
const user = userStore.currentUser;
const memberDetails = userStore.projectMemberInfo;
const isAdmin = memberDetails?.role === 20;
const isOwner = memberDetails?.role === 20;
const { currentProjectRole } = userStore;
const isAdmin = currentProjectRole === 20;
const isOwner = currentProjectRole === 20;
const projectMembers = projectStore.members?.[projectId?.toString()!];
const currentUser = projectMembers?.find((item) => item.member.id === user?.id);

View File

@ -25,20 +25,16 @@ export const ProjectSettingsMemberDefaults: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store
const { user: userStore, project: projectStore } = useMobxStore();
const { currentProjectDetails } = projectStore;
const { currentProjectRole } = userStore;
const isAdmin = currentProjectRole === 20;
// hooks
const { setToastAlert } = useToast();
// derived values
const memberDetails = userStore.projectMemberInfo;
const isAdmin = memberDetails?.role === 20;
const projectDetails = projectStore.project_details[projectId?.toString()!];
// form info
const { reset, control } = useForm<IProject>({ defaultValues });
// fetching user members
useSWR(
workspaceSlug && projectId ? PROJECT_MEMBERS(projectId.toString()) : null,
workspaceSlug && projectId
@ -47,23 +43,23 @@ export const ProjectSettingsMemberDefaults: React.FC = observer(() => {
);
useEffect(() => {
if (!projectDetails) return;
if (!currentProjectDetails) return;
reset({
...projectDetails,
default_assignee: projectDetails.default_assignee?.id ?? projectDetails.default_assignee,
project_lead: (projectDetails.project_lead as IUserLite)?.id ?? projectDetails.project_lead,
workspace: (projectDetails.workspace as IWorkspace).id,
...currentProjectDetails,
default_assignee: currentProjectDetails.default_assignee?.id ?? currentProjectDetails.default_assignee,
project_lead: (currentProjectDetails.project_lead as IUserLite)?.id ?? currentProjectDetails.project_lead,
workspace: (currentProjectDetails.workspace as IWorkspace).id,
});
}, [projectDetails, reset]);
}, [currentProjectDetails, reset]);
const submitChanges = async (formData: Partial<IProject>) => {
if (!workspaceSlug || !projectId) return;
reset({
...projectDetails,
default_assignee: projectDetails.default_assignee?.id ?? projectDetails.default_assignee,
project_lead: (projectDetails.project_lead as IUserLite)?.id ?? projectDetails.project_lead,
...currentProjectDetails,
default_assignee: currentProjectDetails?.default_assignee?.id ?? currentProjectDetails?.default_assignee,
project_lead: (currentProjectDetails?.project_lead as IUserLite)?.id ?? currentProjectDetails?.project_lead,
...formData,
});
@ -96,7 +92,7 @@ export const ProjectSettingsMemberDefaults: React.FC = observer(() => {
<div className="flex flex-col gap-2 w-1/2">
<h4 className="text-sm">Project Lead</h4>
<div className="">
{projectDetails ? (
{currentProjectDetails ? (
<Controller
control={control}
name="project_lead"
@ -121,7 +117,7 @@ export const ProjectSettingsMemberDefaults: React.FC = observer(() => {
<div className="flex flex-col gap-2 w-1/2">
<h4 className="text-sm">Default Assignee</h4>
<div className="">
{projectDetails ? (
{currentProjectDetails ? (
<Controller
control={control}
name="default_assignee"

View File

@ -1,3 +1,4 @@
import { FC } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { ContrastIcon, FileText, Inbox, Layers } from "lucide-react";
@ -18,7 +19,6 @@ const PROJECT_FEATURES_LIST = [
title: "Cycles",
description: "Cycles are enabled for all the projects in this workspace. Access them from the sidebar.",
icon: <ContrastIcon className="h-4 w-4 text-purple-500 flex-shrink-0 rotate-180" />,
property: "cycle_view",
},
{
@ -67,32 +67,30 @@ const getEventType = (feature: string, toggle: boolean): MiscellaneousEventType
// services
const trackEventService = new TrackEventService();
export const ProjectFeaturesList: React.FC<Props> = observer((props) => {
const {} = props;
export const ProjectFeaturesList: FC<Props> = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store
const { project: projectStore, user: userStore } = useMobxStore();
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : undefined;
const user = userStore.currentUser ?? undefined;
const isAdmin = userStore.projectMemberInfo?.role === 20;
const { currentUser, currentProjectRole } = userStore;
const { currentProjectDetails } = projectStore;
const isAdmin = currentProjectRole === 20;
// hooks
const { setToastAlert } = useToast();
const handleSubmit = async (formData: Partial<IProject>) => {
if (!workspaceSlug || !projectId || !projectDetails) return;
if (!workspaceSlug || !projectId || !currentProjectDetails) return;
setToastAlert({
type: "success",
title: "Success!",
message: "Project feature updated successfully.",
});
projectStore.updateProject(workspaceSlug.toString(), projectId.toString(), formData);
};
if (!currentUser) return <></>;
return (
<div>
{PROJECT_FEATURES_LIST.map((feature) => (
@ -108,21 +106,21 @@ export const ProjectFeaturesList: React.FC<Props> = observer((props) => {
</div>
</div>
<ToggleSwitch
value={projectDetails?.[feature.property as keyof IProject]}
value={currentProjectDetails?.[feature.property as keyof IProject]}
onChange={() => {
trackEventService.trackMiscellaneousEvent(
{
workspaceId: (projectDetails?.workspace as any)?.id,
workspaceId: (currentProjectDetails?.workspace as any)?.id,
workspaceSlug,
projectId,
projectIdentifier: projectDetails?.identifier,
projectName: projectDetails?.name,
projectIdentifier: currentProjectDetails?.identifier,
projectName: currentProjectDetails?.name,
},
getEventType(feature.title, !projectDetails?.[feature.property as keyof IProject]),
user
getEventType(feature.title, !currentProjectDetails?.[feature.property as keyof IProject]),
currentUser
);
handleSubmit({
[feature.property]: !projectDetails?.[feature.property as keyof IProject],
[feature.property]: !currentProjectDetails?.[feature.property as keyof IProject],
});
}}
disabled={!isAdmin}

View File

@ -1,9 +1,6 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
// store
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
@ -22,10 +19,9 @@ export const ProjectSettingStateList: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store
const { project: projectStore } = useMobxStore();
const { currentProjectDetails } = projectStore;
// state
const [activeGroup, setActiveGroup] = useState<StateGroup>(null);
const [selectedState, setSelectedState] = useState<string | null>(null);
@ -47,7 +43,6 @@ export const ProjectSettingStateList: React.FC = observer(() => {
// derived values
const states = projectStore.projectStatesByGroups;
const projectDetails = projectStore.project_details[projectId?.toString()!] ?? null;
const orderedStateGroups = orderStateGroups(states!);
const statesList = getStatesList(orderedStateGroups);
@ -60,7 +55,7 @@ export const ProjectSettingStateList: React.FC = observer(() => {
/>
<div className="space-y-8 py-6">
{states && projectDetails && orderedStateGroups ? (
{states && currentProjectDetails && orderedStateGroups ? (
Object.keys(orderedStateGroups || {}).map((key) => {
if (orderedStateGroups[key].length !== 0)
return (

View File

@ -1,4 +1,4 @@
import { useState } from "react";
import { useState, FC } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
// mobx store
@ -35,20 +35,19 @@ type Props = {
// services
const workspaceService = new WorkspaceService();
export const WorkspaceMembersListItem: React.FC<Props> = (props) => {
export const WorkspaceMembersListItem: FC<Props> = (props) => {
const { member } = props;
const [removeMemberModal, setRemoveMemberModal] = useState(false);
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const { setToastAlert } = useToast();
// store
const { workspace: workspaceStore, user: userStore } = useMobxStore();
const user = userStore.workspaceMemberInfo;
const isAdmin = userStore.workspaceMemberInfo?.role === 20;
const { currentWorkspaceMemberInfo, currentWorkspaceRole } = userStore;
const isAdmin = currentWorkspaceRole === 20;
// states
const [removeMemberModal, setRemoveMemberModal] = useState(false);
// hooks
const { setToastAlert } = useToast();
const handleRemoveMember = async () => {
if (!workspaceSlug) return;
@ -83,7 +82,7 @@ export const WorkspaceMembersListItem: React.FC<Props> = (props) => {
});
};
if (!user) return null;
if (!currentWorkspaceMemberInfo) return null;
return (
<>
@ -141,12 +140,12 @@ export const WorkspaceMembersListItem: React.FC<Props> = (props) => {
<div className="flex item-center gap-1 px-2 py-0.5 rounded">
<span
className={`flex items-center text-xs font-medium rounded ${
member.memberId !== user.member ? "" : "text-custom-sidebar-text-400"
member.memberId !== currentWorkspaceMemberInfo.member ? "" : "text-custom-sidebar-text-400"
}`}
>
{ROLE[member.role as keyof typeof ROLE]}
</span>
{member.memberId !== user.member && (
{member.memberId !== currentWorkspaceMemberInfo.member && (
<span className="grid place-items-center">
<ChevronDown className="h-3 w-3" />
</span>
@ -155,7 +154,7 @@ export const WorkspaceMembersListItem: React.FC<Props> = (props) => {
}
value={member.role}
onChange={(value: 5 | 10 | 15 | 20 | undefined) => {
if (!workspaceSlug) return;
if (!workspaceSlug || !value) return;
workspaceStore
.updateMember(workspaceSlug.toString(), member.id, {
@ -170,12 +169,15 @@ export const WorkspaceMembersListItem: React.FC<Props> = (props) => {
});
}}
disabled={
member.memberId === user.member || !member.status || (user.role !== 20 && user.role < member.role)
member.memberId === currentWorkspaceMemberInfo.member ||
!member.status ||
Boolean(currentWorkspaceRole && currentWorkspaceRole !== 20 && currentWorkspaceRole < member.role)
}
placement="bottom-end"
>
{Object.keys(ROLE).map((key) => {
if (user.role !== 20 && user.role < parseInt(key)) return null;
if (currentWorkspaceRole && currentWorkspaceRole !== 20 && currentWorkspaceRole < parseInt(key))
return null;
return (
<CustomSelect.Option key={key} value={parseInt(key, 10)}>
@ -185,7 +187,11 @@ export const WorkspaceMembersListItem: React.FC<Props> = (props) => {
})}
</CustomSelect>
{isAdmin && (
<Tooltip tooltipContent={member.memberId === user.member ? "Leave workspace" : "Remove member"}>
<Tooltip
tooltipContent={
member.memberId === currentWorkspaceMemberInfo.member ? "Leave workspace" : "Remove member"
}
>
<button
type="button"
onClick={() => setRemoveMemberModal(true)}

View File

@ -15,12 +15,11 @@ const workspaceService = new WorkspaceService();
export const WorkspaceMembersList: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug } = router.query;
// store
const { workspace: workspaceStore, user: userStore } = useMobxStore();
const workspaceMembers = workspaceStore.workspaceMembers;
const user = userStore.workspaceMemberInfo;
const user = userStore.currentWorkspaceMemberInfo;
// fetching workspace invitations
const { data: workspaceInvitations } = useSWR(
workspaceSlug ? `WORKSPACE_INVITATIONS_${workspaceSlug.toString()}` : null,
workspaceSlug ? () => workspaceService.workspaceInvitations(workspaceSlug.toString()) : null

View File

@ -30,16 +30,20 @@ const defaultValues: Partial<IWorkspace> = {
const fileService = new FileService();
export const WorkspaceDetails: React.FC = observer(() => {
// states
const [deleteWorkspaceModal, setDeleteWorkspaceModal] = useState(false);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [isImageUploading, setIsImageUploading] = useState(false);
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 isAdmin = currentWorkspaceRole === 20;
// hooks
const { setToastAlert } = useToast();
// form info
const {
handleSubmit,
control,
@ -103,8 +107,6 @@ export const WorkspaceDetails: React.FC = observer(() => {
if (activeWorkspace) reset({ ...activeWorkspace });
}, [activeWorkspace, reset]);
const isAdmin = userStore.workspaceMemberInfo?.role === 20;
if (!activeWorkspace)
return (
<div className="grid place-items-center h-full w-full px-4 sm:px-0">

View File

@ -16,6 +16,7 @@ export const WorkspaceAuthWrapper: FC<IWorkspaceAuthWrapper> = observer((props)
const { children } = props;
// store
const { user: userStore, project: projectStore, workspace: workspaceStore } = useMobxStore();
const { currentWorkspaceMemberInfo, hasPermissionToCurrentWorkspace } = userStore;
// router
const router = useRouter();
const { workspaceSlug } = router.query;
@ -43,11 +44,7 @@ export const WorkspaceAuthWrapper: FC<IWorkspaceAuthWrapper> = observer((props)
);
// while data is being loaded
if (
!userStore.workspaceMemberInfo &&
workspaceSlug &&
userStore.hasPermissionToWorkspace[workspaceSlug.toString()] === null
) {
if (!currentWorkspaceMemberInfo && hasPermissionToCurrentWorkspace === undefined) {
return (
<div className="grid h-screen place-items-center p-4 bg-custom-background-100">
<div className="flex flex-col items-center gap-3 text-center">
@ -57,11 +54,7 @@ export const WorkspaceAuthWrapper: FC<IWorkspaceAuthWrapper> = observer((props)
);
}
// while user does not have access to view that workspace
if (
userStore.hasPermissionToWorkspace !== null &&
workspaceSlug &&
userStore.hasPermissionToWorkspace[workspaceSlug.toString()] === false
) {
if (hasPermissionToCurrentWorkspace !== undefined && hasPermissionToCurrentWorkspace === false) {
return (
<div className={`h-screen w-full overflow-hidden bg-custom-background-100`}>
<div className="grid h-full place-items-center p-4">

View File

@ -27,6 +27,7 @@ const ProjectCyclesPage: NextPage = observer(() => {
const [createModal, setCreateModal] = useState(false);
// store
const { project: projectStore, cycle: cycleStore } = useMobxStore();
const { currentProjectDetails } = projectStore;
// router
const router = useRouter();
const { workspaceSlug, projectId, peekCycle } = router.query as {
@ -78,19 +79,18 @@ const ProjectCyclesPage: NextPage = observer(() => {
}
}, [projectId, cycleStore, handleCurrentView, handleCurrentLayout]);
const projectDetails = projectId ? projectStore.project_details[projectId] : null;
const cycleView = cycleStore?.cycleView;
const cycleLayout = cycleStore?.cycleLayout;
return (
<AppLayout header={<CyclesHeader name={projectDetails?.name} />} withProjectWrapper>
<AppLayout header={<CyclesHeader name={currentProjectDetails?.name} />} withProjectWrapper>
<CycleCreateUpdateModal
workspaceSlug={workspaceSlug}
projectId={projectId}
isOpen={createModal}
handleClose={() => setCreateModal(false)}
/>
{projectDetails?.total_cycles === 0 ? (
{currentProjectDetails?.total_cycles === 0 ? (
<div className="h-full grid place-items-center">
<EmptyState
title="Plan your project with cycles"

View File

@ -20,14 +20,14 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { observer } from "mobx-react-lite";
const GeneralSettings: NextPage = observer(() => {
// store
const { project: projectStore } = useMobxStore();
const { currentProjectDetails } = projectStore;
// states
const [selectProject, setSelectedProject] = useState<string | null>(null);
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// derived values
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null;
// api call to fetch project details
useSWR(
workspaceSlug && projectId ? "PROJECT_DETAILS" : null,
@ -39,30 +39,34 @@ const GeneralSettings: NextPage = observer(() => {
// const currentNetwork = NETWORK_CHOICES.find((n) => n.key === projectDetails?.network);
// const selectedNetwork = NETWORK_CHOICES.find((n) => n.key === watch("network"));
const isAdmin = projectDetails?.member_role === 20;
const isAdmin = currentProjectDetails?.member_role === 20;
return (
<AppLayout header={<ProjectSettingHeader title="General Settings" />} withProjectWrapper>
<ProjectSettingLayout>
{projectDetails && (
{currentProjectDetails && (
<DeleteProjectModal
project={projectDetails}
project={currentProjectDetails}
isOpen={Boolean(selectProject)}
onClose={() => setSelectedProject(null)}
/>
)}
<div className={`pr-9 py-8 w-full overflow-y-auto ${isAdmin ? "" : "opacity-60"}`}>
{projectDetails && workspaceSlug ? (
<ProjectDetailsForm project={projectDetails} workspaceSlug={workspaceSlug.toString()} isAdmin={isAdmin} />
{currentProjectDetails && workspaceSlug ? (
<ProjectDetailsForm
project={currentProjectDetails}
workspaceSlug={workspaceSlug.toString()}
isAdmin={isAdmin}
/>
) : (
<ProjectDetailsFormLoader />
)}
{isAdmin && (
<DeleteProjectSection
projectDetails={projectDetails}
handleDelete={() => setSelectedProject(projectDetails.id ?? null)}
projectDetails={currentProjectDetails}
handleDelete={() => setSelectedProject(currentProjectDetails.id ?? null)}
/>
)}
</div>

View File

@ -41,6 +41,8 @@ export interface IProjectStore {
joinedProjects: IProject[];
favoriteProjects: IProject[];
currentProjectDetails: IProject | undefined;
// actions
setProjectId: (projectId: string) => void;
setSearchQuery: (query: string) => void;
@ -137,6 +139,8 @@ export class ProjectStore implements IProjectStore {
projectMembers: computed,
projectEstimates: computed,
currentProjectDetails: computed,
joinedProjects: computed,
favoriteProjects: computed,
@ -198,6 +202,11 @@ export class ProjectStore implements IProjectStore {
return this.projects?.[this.rootStore.workspace.workspaceSlug];
}
get currentProjectDetails() {
if (!this.projectId) return;
return this.project_details[this.projectId];
}
get joinedProjects() {
if (!this.rootStore.workspace.workspaceSlug) return [];
return this.projects?.[this.rootStore.workspace.workspaceSlug]?.filter((p) => p.is_member);

View File

@ -98,10 +98,7 @@ import {
InboxStore,
} from "store/inbox";
import {
IMentionsStore,
MentionsStore
} from "store/editor"
import { IMentionsStore, MentionsStore } from "store/editor";
enableStaticRendering(typeof window === "undefined");

View File

@ -1,5 +1,5 @@
// mobx
import { action, observable, runInAction, makeObservable } from "mobx";
import { action, observable, runInAction, makeObservable, computed } from "mobx";
// services
import { ProjectService } from "services/project";
import { UserService } from "services/user.service";
@ -7,6 +7,7 @@ import { WorkspaceService } from "services/workspace.service";
// interfaces
import { IUser, IUserSettings } from "types/users";
import { IWorkspaceMemberMe, IProjectMember } from "types";
import { RootStore } from "./root";
export interface IUserStore {
loader: boolean;
@ -17,16 +18,28 @@ export interface IUserStore {
dashboardInfo: any;
workspaceMemberInfo: IWorkspaceMemberMe | null;
workspaceMemberInfo: {
[workspaceSlug: string]: IWorkspaceMemberMe;
};
hasPermissionToWorkspace: {
[workspaceSlug: string]: boolean | null;
};
projectMemberInfo: IProjectMember | null;
projectMemberInfo: {
[projectId: string]: IProjectMember;
};
hasPermissionToProject: {
[projectId: string]: boolean | null;
};
currentProjectMemberInfo: IProjectMember | undefined;
currentWorkspaceMemberInfo: IWorkspaceMemberMe | undefined;
currentProjectRole: number | undefined;
currentWorkspaceRole: number | undefined;
hasPermissionToCurrentWorkspace: boolean | undefined;
hasPermissionToCurrentProject: boolean | undefined;
fetchCurrentUser: () => Promise<IUser>;
fetchCurrentUserSettings: () => Promise<IUserSettings>;
@ -48,14 +61,18 @@ class UserStore implements IUserStore {
dashboardInfo: any = null;
workspaceMemberInfo: IWorkspaceMemberMe | null = null;
workspaceMemberInfo: {
[workspaceSlug: string]: IWorkspaceMemberMe;
} = {};
hasPermissionToWorkspace: {
[workspaceSlug: string]: boolean | null;
[workspaceSlug: string]: boolean;
} = {};
projectMemberInfo: IProjectMember | null = null;
projectMemberInfo: {
[projectId: string]: IProjectMember;
} = {};
hasPermissionToProject: {
[projectId: string]: boolean | null;
[projectId: string]: boolean;
} = {};
// root store
rootStore;
@ -64,7 +81,7 @@ class UserStore implements IUserStore {
workspaceService;
projectService;
constructor(_rootStore: any) {
constructor(_rootStore: RootStore) {
makeObservable(this, {
// observable
loader: observable.ref,
@ -78,7 +95,19 @@ class UserStore implements IUserStore {
// action
fetchCurrentUser: action,
fetchCurrentUserSettings: action,
fetchUserDashboardInfo: action,
fetchUserWorkspaceInfo: action,
fetchUserProjectInfo: action,
updateTourCompleted: action,
updateCurrentUser: action,
updateCurrentUserTheme: action,
// computed
currentProjectMemberInfo: computed,
currentWorkspaceMemberInfo: computed,
currentProjectRole: computed,
currentWorkspaceRole: computed,
hasPermissionToCurrentWorkspace: computed,
hasPermissionToCurrentProject: computed,
});
this.rootStore = _rootStore;
this.userService = new UserService();
@ -86,6 +115,36 @@ class UserStore implements IUserStore {
this.projectService = new ProjectService();
}
get currentWorkspaceMemberInfo() {
if (!this.rootStore.workspace.workspaceSlug) return;
return this.workspaceMemberInfo[this.rootStore.workspace.workspaceSlug];
}
get currentWorkspaceRole() {
if (!this.rootStore.workspace.workspaceSlug) return;
return this.workspaceMemberInfo[this.rootStore.workspace.workspaceSlug].role;
}
get currentProjectMemberInfo() {
if (!this.rootStore.project.projectId) return;
return this.projectMemberInfo[this.rootStore.project.projectId];
}
get currentProjectRole() {
if (!this.rootStore.project.projectId) return;
return this.projectMemberInfo[this.rootStore.project.projectId].role;
}
get hasPermissionToCurrentWorkspace() {
if (!this.rootStore.workspace.workspaceSlug) return;
return this.hasPermissionToWorkspace[this.rootStore.workspace.workspaceSlug];
}
get hasPermissionToCurrentProject() {
if (!this.rootStore.project.projectId) return;
return this.hasPermissionToProject[this.rootStore.project.projectId];
}
fetchCurrentUser = async () => {
try {
const response = await this.userService.currentUser();
@ -132,10 +191,13 @@ class UserStore implements IUserStore {
fetchUserWorkspaceInfo = async (workspaceSlug: string) => {
try {
const response = await this.workspaceService.workspaceMemberMe(workspaceSlug.toString());
const response = await this.workspaceService.workspaceMemberMe(workspaceSlug);
runInAction(() => {
this.workspaceMemberInfo = response;
this.workspaceMemberInfo = {
...this.workspaceMemberInfo,
[workspaceSlug]: response,
};
this.hasPermissionToWorkspace = {
...this.hasPermissionToWorkspace,
[workspaceSlug]: true,
@ -158,7 +220,10 @@ class UserStore implements IUserStore {
const response = await this.projectService.projectMemberMe(workspaceSlug, projectId);
runInAction(() => {
this.projectMemberInfo = response;
this.projectMemberInfo = {
...this.projectMemberInfo,
[projectId]: response,
};
this.hasPermissionToProject = {
...this.hasPermissionToProject,
[projectId]: true,