chore: implemented hooks for all the created stores

This commit is contained in:
Aaryan Khandelwal 2023-12-14 17:26:16 +05:30
parent 39ac5a87ca
commit bdacdc32a9
219 changed files with 2573 additions and 2675 deletions

View File

@ -4,8 +4,8 @@ import { useTheme } from "next-themes";
import { Dialog, Transition } from "@headlessui/react";
import { Trash2 } from "lucide-react";
import { mutate } from "swr";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useUser } from "hooks/store";
// ui
import { Button } from "@plane/ui";
// hooks
@ -22,9 +22,7 @@ export const DeactivateAccountModal: React.FC<Props> = (props) => {
// states
const [isDeactivating, setIsDeactivating] = useState(false);
const {
user: { deactivateAccount },
} = useMobxStore();
const { deactivateAccount } = useUser();
const router = useRouter();

View File

@ -1,9 +1,8 @@
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// services
import { AuthService } from "services/auth.service";
// hooks
import { useApplication } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { GitHubSignInButton, GoogleSignInButton } from "components/account";
@ -21,8 +20,8 @@ export const OAuthOptions: React.FC<Props> = observer((props) => {
const { setToastAlert } = useToast();
// mobx store
const {
appConfig: { envConfig },
} = useMobxStore();
config: { envConfig },
} = useApplication();
const handleGoogleSignIn = async ({ clientId, credential }: any) => {
try {

View File

@ -1,8 +1,7 @@
import React, { useState } from "react";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication } from "hooks/store";
import useSignInRedirection from "hooks/use-sign-in-redirection";
// components
import { LatestFeatureBlock } from "components/common";
@ -38,8 +37,8 @@ export const SignInRoot = observer(() => {
const { handleRedirection } = useSignInRedirection();
// mobx store
const {
appConfig: { envConfig },
} = useMobxStore();
config: { envConfig },
} = useApplication();
const isOAuthEnabled = envConfig && (envConfig.google_client_id || envConfig.github_client_id);

View File

@ -1,9 +1,7 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Control, Controller, UseFormSetValue } from "react-hook-form";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useProject } from "hooks/store";
// components
import { SelectProject, SelectSegment, SelectXAxis, SelectYAxis } from "components/analytics";
// types
@ -20,12 +18,7 @@ type Props = {
export const CustomAnalyticsSelectBar: React.FC<Props> = observer((props) => {
const { control, setValue, params, fullScreen, isProjectLevel } = props;
const router = useRouter();
const { workspaceSlug } = router.query;
const { project: projectStore } = useMobxStore();
const projectsList = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
const { workspaceProjects } = useProject();
return (
<div
@ -40,7 +33,11 @@ export const CustomAnalyticsSelectBar: React.FC<Props> = observer((props) => {
name="project"
control={control}
render={({ field: { value, onChange } }) => (
<SelectProject value={value ?? undefined} onChange={onChange} projects={projectsList ?? undefined} />
<SelectProject
value={value ?? undefined}
onChange={onChange}
projectIds={workspaceProjects ?? undefined}
/>
)}
/>
</div>

View File

@ -1,25 +1,33 @@
import { observer } from "mobx-react-lite";
// hooks
import { useProject } from "hooks/store";
// ui
import { CustomSearchSelect } from "@plane/ui";
// types
import { IProject } from "types";
type Props = {
value: string[] | undefined;
onChange: (val: string[] | null) => void;
projects: IProject[] | undefined;
projectIds: string[] | undefined;
};
export const SelectProject: React.FC<Props> = ({ value, onChange, projects }) => {
const options = projects?.map((project) => ({
value: project.id,
query: project.name + project.identifier,
content: (
<div className="flex items-center gap-2">
<span className="text-[0.65rem] text-custom-text-200">{project.identifier}</span>
{project.name}
</div>
),
}));
export const SelectProject: React.FC<Props> = observer((props) => {
const { value, onChange, projectIds } = props;
const { getProjectById } = useProject();
const options = projectIds?.map((projectId) => {
const projectDetails = getProjectById(projectId);
return {
value: projectDetails?.id,
query: `${projectDetails?.name} ${projectDetails?.identifier}`,
content: (
<div className="flex items-center gap-2">
<span className="text-[0.65rem] text-custom-text-200">{projectDetails?.identifier}</span>
{projectDetails?.name}
</div>
),
};
});
return (
<CustomSearchSelect
@ -28,9 +36,9 @@ export const SelectProject: React.FC<Props> = ({ value, onChange, projects }) =>
options={options}
label={
value && value.length > 0
? projects
?.filter((p) => value.includes(p.id))
.map((p) => p.identifier)
? projectIds
?.filter((p) => value.includes(p))
.map((p) => getProjectById(p)?.name)
.join(", ")
: "All projects"
}
@ -38,4 +46,4 @@ export const SelectProject: React.FC<Props> = ({ value, onChange, projects }) =>
multiple
/>
);
};
});

View File

@ -1,65 +1,74 @@
import { observer } from "mobx-react-lite";
// hooks
import { useProject } from "hooks/store";
// icons
import { Contrast, LayoutGrid, Users } from "lucide-react";
// helpers
import { renderEmoji } from "helpers/emoji.helper";
import { truncateText } from "helpers/string.helper";
// types
import { IProject } from "types";
type Props = {
projects: IProject[];
projectIds: string[];
};
export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = (props) => {
const { projects } = props;
export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = observer((props) => {
const { projectIds } = props;
const { getProjectById } = useProject();
return (
<div className="hidden h-full overflow-hidden md:flex md:flex-col">
<h4 className="font-medium">Selected Projects</h4>
<div className="mt-4 h-full space-y-6 overflow-y-auto">
{projects.map((project) => (
<div key={project.id} className="w-full">
<div className="flex items-center gap-1 text-sm">
{project.emoji ? (
<span className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.emoji)}</span>
) : project.icon_prop ? (
<div className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.icon_prop)}</div>
) : (
<span className="mr-1 grid h-6 w-6 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
{project?.name.charAt(0)}
</span>
)}
<h5 className="flex items-center gap-1">
<p className="break-words">{truncateText(project.name, 20)}</p>
<span className="ml-1 text-xs text-custom-text-200">({project.identifier})</span>
</h5>
</div>
<div className="mt-4 w-full space-y-3 pl-2">
<div className="flex items-center justify-between gap-2 text-xs">
<div className="flex items-center gap-2">
<Users className="text-custom-text-200" size={14} strokeWidth={2} />
<h6>Total members</h6>
</div>
<span className="text-custom-text-200">{project.total_members}</span>
{projectIds.map((projectId) => {
const project = getProjectById(projectId);
if (!project) return;
return (
<div key={projectId} className="w-full">
<div className="flex items-center gap-1 text-sm">
{project.emoji ? (
<span className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.emoji)}</span>
) : project.icon_prop ? (
<div className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.icon_prop)}</div>
) : (
<span className="mr-1 grid h-6 w-6 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
{project?.name.charAt(0)}
</span>
)}
<h5 className="flex items-center gap-1">
<p className="break-words">{truncateText(project.name, 20)}</p>
<span className="ml-1 text-xs text-custom-text-200">({project.identifier})</span>
</h5>
</div>
<div className="flex items-center justify-between gap-2 text-xs">
<div className="flex items-center gap-2">
<Contrast className="text-custom-text-200" size={14} strokeWidth={2} />
<h6>Total cycles</h6>
<div className="mt-4 w-full space-y-3 pl-2">
<div className="flex items-center justify-between gap-2 text-xs">
<div className="flex items-center gap-2">
<Users className="text-custom-text-200" size={14} strokeWidth={2} />
<h6>Total members</h6>
</div>
<span className="text-custom-text-200">{project.total_members}</span>
</div>
<span className="text-custom-text-200">{project.total_cycles}</span>
</div>
<div className="flex items-center justify-between gap-2 text-xs">
<div className="flex items-center gap-2">
<LayoutGrid className="text-custom-text-200" size={14} strokeWidth={2} />
<h6>Total modules</h6>
<div className="flex items-center justify-between gap-2 text-xs">
<div className="flex items-center gap-2">
<Contrast className="text-custom-text-200" size={14} strokeWidth={2} />
<h6>Total cycles</h6>
</div>
<span className="text-custom-text-200">{project.total_cycles}</span>
</div>
<div className="flex items-center justify-between gap-2 text-xs">
<div className="flex items-center gap-2">
<LayoutGrid className="text-custom-text-200" size={14} strokeWidth={2} />
<h6>Total modules</h6>
</div>
<span className="text-custom-text-200">{project.total_modules}</span>
</div>
<span className="text-custom-text-200">{project.total_modules}</span>
</div>
</div>
</div>
))}
);
})}
</div>
</div>
);
};
});

View File

@ -1,8 +1,7 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useCycle, useModule, useProject } from "hooks/store";
// helpers
import { renderEmoji } from "helpers/emoji.helper";
import { renderShortDate } from "helpers/date-time.helper";
@ -11,16 +10,15 @@ import { NETWORK_CHOICES } from "constants/project";
export const CustomAnalyticsSidebarHeader = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
const { projectId, cycleId, moduleId } = router.query;
const { cycle: cycleStore, module: moduleStore, project: projectStore } = useMobxStore();
const { getProjectById } = useProject();
const { getCycleById } = useCycle();
const { getModuleById } = useModule();
const cycleDetails = cycleId ? cycleStore.getCycleById(cycleId.toString()) : undefined;
const moduleDetails = moduleId ? moduleStore.getModuleById(moduleId.toString()) : undefined;
const projectDetails =
workspaceSlug && projectId
? projectStore.getProjectById(workspaceSlug.toString(), projectId.toString())
: undefined;
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined;
const projectDetails = projectId ? getProjectById(projectId.toString()) : undefined;
return (
<>

View File

@ -5,8 +5,8 @@ import { mutate } from "swr";
// services
import { AnalyticsService } from "services/analytics.service";
// hooks
import { useCycle, useModule, useProject, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { CustomAnalyticsSidebarHeader, CustomAnalyticsSidebarProjectsList } from "components/analytics";
// ui
@ -29,172 +29,167 @@ type Props = {
const analyticsService = new AnalyticsService();
export const CustomAnalyticsSidebar: React.FC<Props> = observer(
({ analytics, params, fullScreen, isProjectLevel = false }) => {
const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
export const CustomAnalyticsSidebar: React.FC<Props> = observer((props) => {
const { analytics, params, fullScreen, isProjectLevel = false } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
// toast alert
const { setToastAlert } = useToast();
// store hooks
const { currentUser } = useUser();
const { workspaceProjects, getProjectById } = useProject();
const { fetchCycleDetails, getCycleById } = useCycle();
const { fetchModuleDetails, getModuleById } = useModule();
const { setToastAlert } = useToast();
const projectDetails = projectId ? getProjectById(projectId.toString()) ?? undefined : undefined;
const { user: userStore, project: projectStore, cycle: cycleStore, module: moduleStore } = useMobxStore();
const trackExportAnalytics = () => {
if (!currentUser) return;
const user = userStore.currentUser;
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
const projectDetails =
workspaceSlug && projectId
? projectStore.getProjectById(workspaceSlug.toString(), projectId.toString()) ?? undefined
: undefined;
const trackExportAnalytics = () => {
if (!user) return;
const eventPayload: any = {
workspaceSlug: workspaceSlug?.toString(),
params: {
x_axis: params.x_axis,
y_axis: params.y_axis,
group: params.segment,
project: params.project,
},
};
if (projectDetails) {
const workspaceDetails = projectDetails.workspace as IWorkspace;
eventPayload.workspaceId = workspaceDetails.id;
eventPayload.workspaceName = workspaceDetails.name;
eventPayload.projectId = projectDetails.id;
eventPayload.projectIdentifier = projectDetails.identifier;
eventPayload.projectName = projectDetails.name;
}
if (cycleDetails || moduleDetails) {
const details = cycleDetails || moduleDetails;
eventPayload.workspaceId = details?.workspace_detail?.id;
eventPayload.workspaceName = details?.workspace_detail?.name;
eventPayload.projectId = details?.project_detail.id;
eventPayload.projectIdentifier = details?.project_detail.identifier;
eventPayload.projectName = details?.project_detail.name;
}
if (cycleDetails) {
eventPayload.cycleId = cycleDetails.id;
eventPayload.cycleName = cycleDetails.name;
}
if (moduleDetails) {
eventPayload.moduleId = moduleDetails.id;
eventPayload.moduleName = moduleDetails.name;
}
};
const exportAnalytics = () => {
if (!workspaceSlug) return;
const data: IExportAnalyticsFormData = {
const eventPayload: any = {
workspaceSlug: workspaceSlug?.toString(),
params: {
x_axis: params.x_axis,
y_axis: params.y_axis,
};
if (params.segment) data.segment = params.segment;
if (params.project) data.project = params.project;
analyticsService
.exportAnalytics(workspaceSlug.toString(), data)
.then((res) => {
setToastAlert({
type: "success",
title: "Success!",
message: res.message,
});
trackExportAnalytics();
})
.catch(() =>
setToastAlert({
type: "error",
title: "Error!",
message: "There was some error in exporting the analytics. Please try again.",
})
);
group: params.segment,
project: params.project,
},
};
const cycleDetails = cycleId ? cycleStore.getCycleById(cycleId.toString()) : undefined;
const moduleDetails = moduleId ? moduleStore.getModuleById(moduleId.toString()) : undefined;
if (projectDetails) {
const workspaceDetails = projectDetails.workspace as IWorkspace;
// fetch cycle details
useEffect(() => {
if (!workspaceSlug || !projectId || !cycleId || cycleDetails) return;
eventPayload.workspaceId = workspaceDetails.id;
eventPayload.workspaceName = workspaceDetails.name;
eventPayload.projectId = projectDetails.id;
eventPayload.projectIdentifier = projectDetails.identifier;
eventPayload.projectName = projectDetails.name;
}
cycleStore.fetchCycleWithId(workspaceSlug.toString(), projectId.toString(), cycleId.toString());
}, [cycleId, cycleDetails, cycleStore, projectId, workspaceSlug]);
if (cycleDetails || moduleDetails) {
const details = cycleDetails || moduleDetails;
// fetch module details
useEffect(() => {
if (!workspaceSlug || !projectId || !moduleId || moduleDetails) return;
eventPayload.workspaceId = details?.workspace_detail?.id;
eventPayload.workspaceName = details?.workspace_detail?.name;
eventPayload.projectId = details?.project_detail.id;
eventPayload.projectIdentifier = details?.project_detail.identifier;
eventPayload.projectName = details?.project_detail.name;
}
moduleStore.fetchModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString());
}, [moduleId, moduleDetails, moduleStore, projectId, workspaceSlug]);
if (cycleDetails) {
eventPayload.cycleId = cycleDetails.id;
eventPayload.cycleName = cycleDetails.name;
}
const selectedProjects = params.project && params.project.length > 0 ? params.project : projects?.map((p) => p.id);
if (moduleDetails) {
eventPayload.moduleId = moduleDetails.id;
eventPayload.moduleName = moduleDetails.name;
}
};
return (
<div
className={`flex items-center justify-between space-y-2 px-5 py-2.5 ${
fullScreen
? "overflow-hidden border-l border-custom-border-200 md:h-full md:flex-col md:items-start md:space-y-4 md:border-l md:border-custom-border-200 md:py-5"
: ""
}`}
>
<div className="flex flex-wrap items-center gap-2">
const exportAnalytics = () => {
if (!workspaceSlug) return;
const data: IExportAnalyticsFormData = {
x_axis: params.x_axis,
y_axis: params.y_axis,
};
if (params.segment) data.segment = params.segment;
if (params.project) data.project = params.project;
analyticsService
.exportAnalytics(workspaceSlug.toString(), data)
.then((res) => {
setToastAlert({
type: "success",
title: "Success!",
message: res.message,
});
trackExportAnalytics();
})
.catch(() =>
setToastAlert({
type: "error",
title: "Error!",
message: "There was some error in exporting the analytics. Please try again.",
})
);
};
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined;
// fetch cycle details
useEffect(() => {
if (!workspaceSlug || !projectId || !cycleId || cycleDetails) return;
fetchCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleId.toString());
}, [cycleId, cycleDetails, fetchCycleDetails, projectId, workspaceSlug]);
// fetch module details
useEffect(() => {
if (!workspaceSlug || !projectId || !moduleId || moduleDetails) return;
fetchModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString());
}, [moduleId, moduleDetails, fetchModuleDetails, projectId, workspaceSlug]);
const selectedProjects = params.project && params.project.length > 0 ? params.project : workspaceProjects;
return (
<div
className={`flex items-center justify-between space-y-2 px-5 py-2.5 ${
fullScreen
? "overflow-hidden border-l border-custom-border-200 md:h-full md:flex-col md:items-start md:space-y-4 md:border-l md:border-custom-border-200 md:py-5"
: ""
}`}
>
<div className="flex flex-wrap items-center gap-2">
<div className="flex items-center gap-1 rounded-md bg-custom-background-80 px-3 py-1 text-xs text-custom-text-200">
<LayersIcon height={14} width={14} />
{analytics ? analytics.total : "..."} Issues
</div>
{isProjectLevel && (
<div className="flex items-center gap-1 rounded-md bg-custom-background-80 px-3 py-1 text-xs text-custom-text-200">
<LayersIcon height={14} width={14} />
{analytics ? analytics.total : "..."} Issues
<CalendarDays className="h-3.5 w-3.5" />
{renderShortDate(
(cycleId
? cycleDetails?.created_at
: moduleId
? moduleDetails?.created_at
: projectDetails?.created_at) ?? ""
)}
</div>
{isProjectLevel && (
<div className="flex items-center gap-1 rounded-md bg-custom-background-80 px-3 py-1 text-xs text-custom-text-200">
<CalendarDays className="h-3.5 w-3.5" />
{renderShortDate(
(cycleId
? cycleDetails?.created_at
: moduleId
? moduleDetails?.created_at
: projectDetails?.created_at) ?? ""
)}
</div>
)}
</div>
<div className="h-full w-full overflow-hidden">
{fullScreen ? (
<>
{!isProjectLevel && selectedProjects && selectedProjects.length > 0 && (
<CustomAnalyticsSidebarProjectsList
projects={projects?.filter((p) => selectedProjects.includes(p.id)) ?? []}
/>
)}
<CustomAnalyticsSidebarHeader />
</>
) : null}
</div>
<div className="flex flex-wrap items-center gap-2 justify-self-end">
<Button
variant="neutral-primary"
prependIcon={<RefreshCw className="h-3.5 w-3.5" />}
onClick={() => {
if (!workspaceSlug) return;
mutate(ANALYTICS(workspaceSlug.toString(), params));
}}
>
Refresh
</Button>
<Button variant="primary" prependIcon={<Download className="h-3.5 w-3.5" />} onClick={exportAnalytics}>
Export as CSV
</Button>
</div>
)}
</div>
);
}
);
<div className="h-full w-full overflow-hidden">
{fullScreen ? (
<>
{!isProjectLevel && selectedProjects && selectedProjects.length > 0 && (
<CustomAnalyticsSidebarProjectsList projectIds={selectedProjects} />
)}
<CustomAnalyticsSidebarHeader />
</>
) : null}
</div>
<div className="flex flex-wrap items-center gap-2 justify-self-end">
<Button
variant="neutral-primary"
prependIcon={<RefreshCw className="h-3.5 w-3.5" />}
onClick={() => {
if (!workspaceSlug) return;
mutate(ANALYTICS(workspaceSlug.toString(), params));
}}
>
Refresh
</Button>
<Button variant="primary" prependIcon={<Download className="h-3.5 w-3.5" />} onClick={exportAnalytics}>
Export as CSV
</Button>
</div>
</div>
);
});

View File

@ -1,12 +1,12 @@
import React from "react";
// next
import Link from "next/link";
import Image from "next/image";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// hooks
import { useUser } from "hooks/store";
// layouts
import DefaultLayout from "layouts/default-layout";
// hooks
import useUser from "hooks/use-user";
// images
import ProjectNotAuthorizedImg from "public/auth/project-not-authorized.svg";
import WorkspaceNotAuthorizedImg from "public/auth/workspace-not-authorized.svg";
@ -16,9 +16,12 @@ type Props = {
type: "project" | "workspace";
};
export const NotAuthorizedView: React.FC<Props> = ({ actionButton, type }) => {
const { user } = useUser();
export const NotAuthorizedView: React.FC<Props> = observer((props) => {
const { actionButton, type } = props;
// router
const { asPath: currentPath } = useRouter();
// store hooks
const { currentUser } = useUser();
return (
<DefaultLayout>
@ -34,9 +37,9 @@ export const NotAuthorizedView: React.FC<Props> = ({ actionButton, type }) => {
<h1 className="text-xl font-medium text-custom-text-100">Oops! You are not authorized to view this page</h1>
<div className="w-full max-w-md text-base text-custom-text-200">
{user ? (
{currentUser ? (
<p>
You have signed in as {user.email}. <br />
You have signed in as {currentUser.email}. <br />
<Link href={`/?next=${currentPath}`}>
<span className="font-medium text-custom-text-100">Sign in</span>
</Link>{" "}
@ -57,4 +60,4 @@ export const NotAuthorizedView: React.FC<Props> = ({ actionButton, type }) => {
</div>
</DefaultLayout>
);
};
});

View File

@ -1,9 +1,8 @@
import { useState } from "react";
import Image from "next/image";
import { useRouter } from "next/router";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store_legacy/root";
// hooks
import { useProject, useUser } from "hooks/store";
// ui
import { Button } from "@plane/ui";
// icons
@ -12,12 +11,13 @@ import { ClipboardList } from "lucide-react";
import JoinProjectImg from "public/auth/project-not-authorized.svg";
export const JoinProject: React.FC = () => {
// states
const [isJoiningProject, setIsJoiningProject] = useState(false);
// store hooks
const {
project: projectStore,
user: { joinProject },
}: RootStore = useMobxStore();
membership: { joinProject },
} = useUser();
const { fetchProjects } = useProject();
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
@ -28,12 +28,8 @@ export const JoinProject: React.FC = () => {
setIsJoiningProject(true);
joinProject(workspaceSlug.toString(), [projectId.toString()])
.then(() => {
projectStore.fetchProjects(workspaceSlug.toString());
})
.finally(() => {
setIsJoiningProject(false);
});
.then(() => fetchProjects(workspaceSlug.toString()))
.finally(() => setIsJoiningProject(false));
};
return (

View File

@ -1,7 +1,7 @@
import React, { useState } from "react";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useProject, useUser } from "hooks/store";
// component
import { CustomSelect, Loader, ToggleSwitch } from "@plane/ui";
import { SelectMonthModal } from "components/automation";
@ -23,13 +23,13 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
const { handleChange } = props;
// states
const [monthModal, setmonthModal] = useState(false);
// store hooks
const {
membership: { currentProjectRole },
} = useUser();
const { currentProjectDetails } = useProject();
const { user: userStore, project: projectStore } = useMobxStore();
const projectDetails = projectStore.currentProjectDetails;
const userRole = userStore.currentProjectRole;
const isAdmin = userRole === EUserWorkspaceRoles.ADMIN;
const isAdmin = currentProjectRole === EUserWorkspaceRoles.ADMIN;
return (
<>
@ -54,24 +54,28 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
</div>
</div>
<ToggleSwitch
value={projectDetails?.archive_in !== 0}
value={currentProjectDetails?.archive_in !== 0}
onChange={() =>
projectDetails?.archive_in === 0 ? handleChange({ archive_in: 1 }) : handleChange({ archive_in: 0 })
currentProjectDetails?.archive_in === 0
? handleChange({ archive_in: 1 })
: handleChange({ archive_in: 0 })
}
size="sm"
disabled={!isAdmin}
/>
</div>
{projectDetails ? (
projectDetails.archive_in !== 0 && (
{currentProjectDetails ? (
currentProjectDetails.archive_in !== 0 && (
<div className="ml-12">
<div className="flex w-full items-center justify-between gap-2 rounded border border-custom-border-200 bg-custom-background-90 px-5 py-4">
<div className="w-1/2 text-sm font-medium">Auto-archive issues that are closed for</div>
<div className="w-1/2">
<CustomSelect
value={projectDetails?.archive_in}
label={`${projectDetails?.archive_in} ${projectDetails?.archive_in === 1 ? "Month" : "Months"}`}
value={currentProjectDetails?.archive_in}
label={`${currentProjectDetails?.archive_in} ${
currentProjectDetails?.archive_in === 1 ? "Month" : "Months"
}`}
onChange={(val: number) => {
handleChange({ archive_in: val });
}}

View File

@ -1,7 +1,7 @@
import React, { useState } from "react";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useProject, useProjectState, useUser } from "hooks/store";
// component
import { SelectMonthModal } from "components/automation";
import { CustomSelect, CustomSearchSelect, ToggleSwitch, StateGroupIcon, DoubleCircleIcon, Loader } from "@plane/ui";
@ -21,15 +21,16 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
const { handleChange } = props;
// states
const [monthModal, setmonthModal] = useState(false);
// store hooks
const {
membership: { currentProjectRole },
} = useUser();
const { currentProjectDetails } = useProject();
const { projectStates } = useProjectState();
const { user: userStore, project: projectStore, projectState: projectStateStore } = useMobxStore();
const userRole = userStore.currentProjectRole;
const projectDetails = projectStore.currentProjectDetails;
// const stateGroups = projectStateStore.groupedProjectStates ?? undefined;
const states = projectStateStore.projectStates;
const options = states
const options = projectStates
?.filter((state) => state.group === "cancelled")
.map((state) => ({
value: state.id,
@ -44,17 +45,17 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
const multipleOptions = (options ?? []).length > 1;
const defaultState = states?.find((s) => s.group === "cancelled")?.id || null;
const defaultState = projectStates?.find((s) => s.group === "cancelled")?.id || null;
const selectedOption = states?.find((s) => s.id === projectDetails?.default_state ?? defaultState);
const currentDefaultState = states?.find((s) => s.id === defaultState);
const selectedOption = projectStates?.find((s) => s.id === currentProjectDetails?.default_state ?? defaultState);
const currentDefaultState = projectStates?.find((s) => s.id === defaultState);
const initialValues: Partial<IProject> = {
close_in: 1,
default_state: defaultState,
};
const isAdmin = userRole === EUserWorkspaceRoles.ADMIN;
const isAdmin = currentProjectRole === EUserWorkspaceRoles.ADMIN;
return (
<>
@ -79,9 +80,9 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
</div>
</div>
<ToggleSwitch
value={projectDetails?.close_in !== 0}
value={currentProjectDetails?.close_in !== 0}
onChange={() =>
projectDetails?.close_in === 0
currentProjectDetails?.close_in === 0
? handleChange({ close_in: 1, default_state: defaultState })
: handleChange({ close_in: 0, default_state: null })
}
@ -90,16 +91,18 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
/>
</div>
{projectDetails ? (
projectDetails.close_in !== 0 && (
{currentProjectDetails ? (
currentProjectDetails.close_in !== 0 && (
<div className="ml-12">
<div className="flex flex-col rounded border border-custom-border-200 bg-custom-background-90">
<div className="flex w-full items-center justify-between gap-2 px-5 py-4">
<div className="w-1/2 text-sm font-medium">Auto-close issues that are inactive for</div>
<div className="w-1/2">
<CustomSelect
value={projectDetails?.close_in}
label={`${projectDetails?.close_in} ${projectDetails?.close_in === 1 ? "Month" : "Months"}`}
value={currentProjectDetails?.close_in}
label={`${currentProjectDetails?.close_in} ${
currentProjectDetails?.close_in === 1 ? "Month" : "Months"
}`}
onChange={(val: number) => {
handleChange({ close_in: val });
}}
@ -118,7 +121,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
className="flex w-full select-none items-center rounded px-1 py-1.5 text-custom-text-200 hover:bg-custom-background-80"
onClick={() => setmonthModal(true)}
>
Customise Time Range
Customize Time Range
</button>
</>
</CustomSelect>
@ -129,7 +132,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
<div className="w-1/2 text-sm font-medium">Auto-close Status</div>
<div className="w-1/2 ">
<CustomSearchSelect
value={projectDetails?.default_state ?? defaultState}
value={currentProjectDetails?.default_state ?? defaultState}
label={
<div className="flex items-center gap-2">
{selectedOption ? (

View File

@ -1,7 +1,7 @@
import { Command } from "cmdk";
import { FileText, GithubIcon, MessageSquare, Rocket } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication } from "hooks/store";
// ui
import { DiscordIcon } from "@plane/ui";
@ -14,7 +14,7 @@ export const CommandPaletteHelpActions: React.FC<Props> = (props) => {
const {
commandPalette: { toggleShortcutModal },
} = useMobxStore();
} = useApplication();
return (
<Command.Group heading="Help">

View File

@ -5,6 +5,8 @@ import { LinkIcon, Signal, Trash2, UserMinus2, UserPlus2 } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication, useUser } from "hooks/store";
// hooks
import useToast from "hooks/use-toast";
// ui
import { DoubleCircleIcon, UserGroupIcon } from "@plane/ui";
@ -29,10 +31,12 @@ export const CommandPaletteIssueActions: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId } = router.query;
const {
commandPalette: { toggleCommandPaletteModal, toggleDeleteIssueModal },
projectIssues: { updateIssue },
user: { currentUser },
} = useMobxStore();
const {
commandPalette: { toggleCommandPaletteModal, toggleDeleteIssueModal },
} = useApplication();
const { currentUser } = useUser();
const { setToastAlert } = useToast();

View File

@ -1,7 +1,7 @@
import { Command } from "cmdk";
import { ContrastIcon, FileText } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication } from "hooks/store";
// ui
import { DiceIcon, PhotoFilterIcon } from "@plane/ui";
@ -14,8 +14,8 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
const {
commandPalette: { toggleCreateCycleModal, toggleCreateModuleModal, toggleCreatePageModal, toggleCreateViewModal },
trackEvent: { setTrackElement },
} = useMobxStore();
eventTracker: { setTrackElement },
} = useApplication();
return (
<>

View File

@ -4,8 +4,8 @@ import { useTheme } from "next-themes";
import { Settings } from "lucide-react";
import { observer } from "mobx-react-lite";
// hooks
import { useUser } from "hooks/store";
import useToast from "hooks/use-toast";
import { useMobxStore } from "lib/mobx/store-provider";
// constants
import { THEME_OPTIONS } from "constants/themes";
@ -18,9 +18,7 @@ export const CommandPaletteThemeActions: FC<Props> = observer((props) => {
// states
const [mounted, setMounted] = useState(false);
// store
const {
user: { updateCurrentUserTheme },
} = useMobxStore();
const { updateCurrentUserTheme } = useUser();
// hooks
const { setTheme } = useTheme();
const { setToastAlert } = useToast();

View File

@ -5,8 +5,8 @@ import { Command } from "cmdk";
import { Dialog, Transition } from "@headlessui/react";
import { observer } from "mobx-react-lite";
import { FolderPlus, Search, Settings } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication } from "hooks/store";
// services
import { WorkspaceService } from "services/workspace.service";
import { IssueService } from "services/issue";
@ -62,8 +62,8 @@ export const CommandModal: React.FC = observer(() => {
toggleCreateIssueModal,
toggleCreateProjectModal,
},
trackEvent: { setTrackElement },
} = useMobxStore();
eventTracker: { setTrackElement },
} = useApplication();
// router
const router = useRouter();

View File

@ -3,6 +3,7 @@ import { useRouter } from "next/router";
import useSWR from "swr";
import { observer } from "mobx-react-lite";
// hooks
import { useApplication, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { CommandModal, ShortcutsModal } from "components/command-palette";
@ -19,8 +20,6 @@ import { copyTextToClipboard } from "helpers/string.helper";
import { IssueService } from "services/issue";
// fetch keys
import { ISSUE_DETAILS } from "constants/fetch-keys";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// services
const issueService = new IssueService();
@ -28,14 +27,14 @@ const issueService = new IssueService();
export const CommandPalette: FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId, issueId, cycleId, moduleId } = router.query;
// store
const {
commandPalette,
theme: { toggleSidebar },
user: { currentUser },
trackEvent: { setTrackElement },
projectIssues: { removeIssue },
} = useMobxStore();
eventTracker: { setTrackElement },
} = useApplication();
const { currentUser } = useUser();
const {
toggleCommandPaletteModal,
isCreateIssueModalOpen,

View File

@ -1,5 +1,5 @@
export * from "./actions";
export * from "./shortcuts-modal";
export * from "./command-modal";
export * from "./command-pallette";
export * from "./command-palette";
export * from "./helpers";

View File

@ -6,8 +6,8 @@ import useSWR from "swr";
import { useDropzone } from "react-dropzone";
import { Tab, Transition, Popover } from "@headlessui/react";
import { Control, Controller } from "react-hook-form";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication, useWorkspace } from "hooks/store";
// services
import { FileService } from "services/file.service";
// hooks
@ -45,25 +45,24 @@ const fileService = new FileService();
export const ImagePickerPopover: React.FC<Props> = observer((props) => {
const { label, value, control, onChange, disabled = false } = props;
// states
const [image, setImage] = useState<File | null>(null);
const [isImageUploading, setIsImageUploading] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [searchParams, setSearchParams] = useState("");
const [formData, setFormData] = useState({
search: "",
});
// refs
const ref = useRef<HTMLDivElement>(null);
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// store hooks
const {
workspace: { currentWorkspace },
appConfig: { envConfig },
} = useMobxStore();
config: { envConfig },
} = useApplication();
const { currentWorkspace } = useWorkspace();
const { data: unsplashImages, error: unsplashError } = useSWR(
`UNSPLASH_IMAGES_${searchParams}`,

View File

@ -2,8 +2,8 @@ import React, { useState } from "react";
import { observer } from "mobx-react-lite";
import { useDropzone } from "react-dropzone";
import { Transition, Dialog } from "@headlessui/react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication } from "hooks/store";
// services
import { FileService } from "services/file.service";
// hooks
@ -32,12 +32,12 @@ export const UserImageUploadModal: React.FC<Props> = observer((props) => {
// states
const [image, setImage] = useState<File | null>(null);
const [isImageUploading, setIsImageUploading] = useState(false);
// toast alert
const { setToastAlert } = useToast();
// store hooks
const {
appConfig: { envConfig },
} = useMobxStore();
config: { envConfig },
} = useApplication();
const onDrop = (acceptedFiles: File[]) => setImage(acceptedFiles[0]);

View File

@ -3,8 +3,8 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { useDropzone } from "react-dropzone";
import { Transition, Dialog } from "@headlessui/react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication, useWorkspace } from "hooks/store";
// services
import { FileService } from "services/file.service";
// hooks
@ -40,9 +40,9 @@ export const WorkspaceImageUploadModal: React.FC<Props> = observer((props) => {
const { setToastAlert } = useToast();
const {
workspace: { currentWorkspace },
appConfig: { envConfig },
} = useMobxStore();
config: { envConfig },
} = useApplication();
const { currentWorkspace } = useWorkspace();
const onDrop = (acceptedFiles: File[]) => setImage(acceptedFiles[0]);

View File

@ -1,8 +1,8 @@
import { observer } from "mobx-react-lite";
import { Controller, useForm } from "react-hook-form";
import { useTheme } from "next-themes";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useUser } from "hooks/store";
// ui
import { Button, InputColorPicker } from "@plane/ui";
// types
@ -25,8 +25,8 @@ const inputRules = {
};
export const CustomThemeSelector: React.FC = observer(() => {
const { user: userStore } = useMobxStore();
const userTheme = userStore?.currentUser?.theme;
const { currentUser, updateCurrentUser } = useUser();
const userTheme = currentUser?.theme;
// hooks
const { setTheme } = useTheme();
@ -61,7 +61,7 @@ export const CustomThemeSelector: React.FC = observer(() => {
setTheme("custom");
return userStore.updateCurrentUser({ theme: payload });
return updateCurrentUser({ theme: payload });
};
const handleValueChange = (val: string | undefined, onChange: any) => {

View File

@ -3,9 +3,9 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
import { useApplication } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { SingleProgressStats } from "components/core";
@ -67,12 +67,15 @@ interface IActiveCycleDetails {
}
export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props) => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = props;
const { cycle: cycleStore, commandPalette: commandPaletteStore } = useMobxStore();
// store hooks
const { cycle: cycleStore } = useMobxStore();
const {
commandPalette: { toggleCreateCycleModal },
} = useApplication();
// toast alert
const { setToastAlert } = useToast();
useSWR(
@ -118,7 +121,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
<button
type="button"
className="text-sm text-custom-primary-100 outline-none"
onClick={() => commandPaletteStore.toggleCreateCycleModal(true)}
onClick={() => toggleCreateCycleModal(true)}
>
Create a new cycle
</button>
@ -187,12 +190,12 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
cycleStatus === "current"
? "#09A953"
: cycleStatus === "upcoming"
? "#F7AE59"
: cycleStatus === "completed"
? "#3F76FF"
: cycleStatus === "draft"
? "rgb(var(--color-text-200))"
: ""
? "#F7AE59"
: cycleStatus === "completed"
? "#3F76FF"
: cycleStatus === "draft"
? "rgb(var(--color-text-200))"
: ""
}`}
/>
</span>
@ -207,12 +210,12 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
cycleStatus === "current"
? "bg-green-600/5 text-green-600"
: cycleStatus === "upcoming"
? "bg-orange-300/5 text-orange-300"
: cycleStatus === "completed"
? "bg-blue-500/5 text-blue-500"
: cycleStatus === "draft"
? "bg-neutral-400/5 text-neutral-400"
: ""
? "bg-orange-300/5 text-orange-300"
: cycleStatus === "completed"
? "bg-blue-500/5 text-blue-500"
: cycleStatus === "draft"
? "bg-neutral-400/5 text-neutral-400"
: ""
}`}
>
{cycleStatus === "current" ? (

View File

@ -1,7 +1,7 @@
import { FC } from "react";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication } from "hooks/store";
// components
import { CyclePeekOverview, CyclesBoardCard } from "components/cycles";
// types
@ -17,8 +17,8 @@ export interface ICyclesBoard {
export const CyclesBoard: FC<ICyclesBoard> = observer((props) => {
const { cycles, filter, workspaceSlug, projectId, peekCycle } = props;
const { commandPalette: commandPaletteStore } = useMobxStore();
// store hooks
const { commandPalette: commandPaletteStore } = useApplication();
return (
<>

View File

@ -1,7 +1,7 @@
import { FC } from "react";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication } from "hooks/store";
// components
import { CyclePeekOverview, CyclesListItem } from "components/cycles";
// ui
@ -18,11 +18,11 @@ export interface ICyclesList {
export const CyclesList: FC<ICyclesList> = observer((props) => {
const { cycles, filter, workspaceSlug, projectId } = props;
// store hooks
const {
commandPalette: commandPaletteStore,
trackEvent: { setTrackElement },
} = useMobxStore();
eventTracker: { setTrackElement },
} = useApplication();
return (
<>

View File

@ -1,19 +1,18 @@
import { FC } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { KeyedMutator } from "swr";
// hooks
import { useUser } from "hooks/store";
// services
import { CycleService } from "services/cycle.service";
// hooks
import useUser from "hooks/use-user";
import useProjectDetails from "hooks/use-project-details";
// components
import { GanttChartRoot, IBlockUpdateData, CycleGanttSidebar } from "components/gantt-chart";
import { CycleGanttBlock } from "components/cycles";
// types
import { ICycle } from "types";
// constants
import { EUserWorkspaceRoles } from "constants/workspace";
type Props = {
workspaceSlug: string;
@ -24,15 +23,18 @@ type Props = {
// services
const cycleService = new CycleService();
export const CyclesListGanttChartView: FC<Props> = ({ cycles, mutateCycles }) => {
export const CyclesListGanttChartView: FC<Props> = observer((props) => {
const { cycles, mutateCycles } = props;
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const { user } = useUser();
const { projectDetails } = useProjectDetails();
// store hooks
const {
membership: { currentProjectRole },
} = useUser();
const handleCycleUpdate = (cycle: ICycle, payload: IBlockUpdateData) => {
if (!workspaceSlug || !user) return;
if (!workspaceSlug) return;
mutateCycles &&
mutateCycles((prevData: any) => {
if (!prevData) return prevData;
@ -76,7 +78,8 @@ export const CyclesListGanttChartView: FC<Props> = ({ cycles, mutateCycles }) =>
}))
: [];
const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
const isAllowed =
currentProjectRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentProjectRole);
return (
<div className="h-full w-full overflow-y-auto">
@ -94,4 +97,4 @@ export const CyclesListGanttChartView: FC<Props> = ({ cycles, mutateCycles }) =>
/>
</div>
);
};
});

View File

@ -1,11 +1,8 @@
import React from "react";
import { useRouter } from "next/router";
// store
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useProject } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { Button, CustomMenu } from "@plane/ui";
@ -27,10 +24,8 @@ export const EstimateListItem: React.FC<Props> = observer((props) => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store
const {
project: { currentProjectDetails, updateProject },
} = useMobxStore();
// store hooks
const { currentProjectDetails, updateProject } = useProject();
// hooks
const { setToastAlert } = useToast();

View File

@ -2,8 +2,8 @@ import React, { useState } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Dialog, Transition } from "@headlessui/react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useProject } from "hooks/store";
// services
import { ProjectExportService } from "services/project";
// hooks
@ -26,28 +26,30 @@ const projectExportService = new ProjectExportService();
export const Exporter: React.FC<Props> = observer((props) => {
const { isOpen, handleClose, user, provider, mutateServices } = props;
// states
const [exportLoading, setExportLoading] = useState(false);
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const { project: projectStore } = useMobxStore();
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
// store hooks
const { workspaceProjects, getProjectById } = useProject();
// toast alert
const { setToastAlert } = useToast();
const options = projects?.map((project) => ({
value: project.id,
query: project.name + project.identifier,
content: (
<div className="flex items-center gap-2">
<span className="text-[0.65rem] text-custom-text-200">{project.identifier}</span>
{project.name}
</div>
),
}));
const options = workspaceProjects?.map((projectId) => {
const projectDetails = getProjectById(projectId);
return {
value: projectDetails?.id,
query: `${projectDetails?.name} ${projectDetails?.identifier}`,
content: (
<div className="flex items-center gap-2">
<span className="text-[0.65rem] text-custom-text-200">{projectDetails?.identifier}</span>
{projectDetails?.name}
</div>
),
};
});
const [value, setValue] = React.useState<string[]>([]);
const [multiple, setMultiple] = React.useState<boolean>(false);
@ -131,10 +133,12 @@ export const Exporter: React.FC<Props> = observer((props) => {
input
label={
value && value.length > 0
? projects &&
projects
.filter((p) => value.includes(p.id))
.map((p) => p.identifier)
? value
.map((projectId) => {
const projectDetails = getProjectById(projectId);
return projectDetails?.identifier;
})
.join(", ")
: "All projects"
}

View File

@ -2,8 +2,8 @@ import { FC } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Plus } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication, useProject, useUser } from "hooks/store";
// ui
import { Breadcrumbs, Button, ContrastIcon } from "@plane/ui";
// helpers
@ -14,14 +14,15 @@ export const CyclesHeader: FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// store
// store hooks
const {
project: projectStore,
user: { currentProjectRole },
commandPalette: commandPaletteStore,
trackEvent: { setTrackElement },
} = useMobxStore();
const { currentProjectDetails } = projectStore;
commandPalette: { toggleCreateCycleModal },
eventTracker: { setTrackElement },
} = useApplication();
const {
membership: { currentProjectRole },
} = useUser();
const { currentProjectDetails } = useProject();
const canUserCreateCycle =
currentProjectRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentProjectRole);
@ -63,7 +64,7 @@ export const CyclesHeader: FC = observer(() => {
prependIcon={<Plus />}
onClick={() => {
setTrackElement("CYCLES_PAGE_HEADER");
commandPaletteStore.toggleCreateCycleModal(true);
toggleCreateCycleModal(true);
}}
>
Add Cycle

View File

@ -1,9 +1,8 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Plus } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication, useProject, useUser } from "hooks/store";
import useLocalStorage from "hooks/use-local-storage";
// ui
import { Breadcrumbs, Button, Tooltip, DiceIcon } from "@plane/ui";
@ -17,13 +16,12 @@ export const ModulesListHeader: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// store
// store hooks
const { commandPalette: commandPaletteStore } = useApplication();
const {
project: projectStore,
commandPalette: commandPaletteStore,
user: { currentProjectRole },
} = useMobxStore();
const { currentProjectDetails } = projectStore;
membership: { currentProjectRole },
} = useUser();
const { currentProjectDetails } = useProject();
const { storedValue: modulesView, setValue: setModulesView } = useLocalStorage("modules_view", "grid");

View File

@ -1,21 +1,18 @@
import { FC } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
import { FileText, Plus } from "lucide-react";
// hooks
import { useApplication, useProject } from "hooks/store";
// services
import { PageService } from "services/page.service";
// constants
import { PAGE_DETAILS } from "constants/fetch-keys";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
// ui
import { Breadcrumbs, Button } from "@plane/ui";
// helper
// helpers
import { renderEmoji } from "helpers/emoji.helper";
import useSWR from "swr";
// fetch-keys
import { PAGE_DETAILS } from "constants/fetch-keys";
export interface IPagesHeaderProps {
showButton?: boolean;
@ -28,8 +25,8 @@ export const PageDetailsHeader: FC<IPagesHeaderProps> = observer((props) => {
const router = useRouter();
const { workspaceSlug, pageId } = router.query;
const { project: projectStore, commandPalette: commandPaletteStore } = useMobxStore();
const { currentProjectDetails } = projectStore;
const { commandPalette: commandPaletteStore } = useApplication();
const { currentProjectDetails } = useProject();
const { data: pageDetails } = useSWR(
workspaceSlug && currentProjectDetails?.id && pageId ? PAGE_DETAILS(pageId as string) : null,

View File

@ -2,10 +2,10 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { FileText, Plus } from "lucide-react";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
import { useApplication, useProject, useUser } from "hooks/store";
// ui
import { Breadcrumbs, Button } from "@plane/ui";
// helper
// helpers
import { renderEmoji } from "helpers/emoji.helper";
// constants
import { EUserWorkspaceRoles } from "constants/workspace";
@ -14,12 +14,14 @@ export const PagesHeader = observer(() => {
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// mobx store
// store hooks
const {
user: { currentProjectRole },
project: { currentProjectDetails },
commandPalette: { toggleCreatePageModal },
} = useMobxStore();
} = useApplication();
const {
membership: { currentProjectRole },
} = useUser();
const { currentProjectDetails } = useProject();
const canUserCreatePage =
currentProjectRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentProjectRole);

View File

@ -3,7 +3,7 @@ import useSWR from "swr";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
import { useProject } from "hooks/store";
// ui
import { Breadcrumbs, LayersIcon } from "@plane/ui";
// types
@ -18,12 +18,11 @@ import { renderEmoji } from "helpers/emoji.helper";
const issueArchiveService = new IssueArchiveService();
export const ProjectArchivedIssueDetailsHeader: FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId, archivedIssueId } = router.query;
const { project: projectStore } = useMobxStore();
const { currentProjectDetails } = projectStore;
// store hooks
const { currentProjectDetails } = useProject();
const { data: issueDetails } = useSWR<IIssue | undefined>(
workspaceSlug && projectId && archivedIssueId ? ISSUE_DETAILS(archivedIssueId as string) : null,

View File

@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Plus } from "lucide-react";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
import { useProject } from "hooks/store";
// ui
import { Breadcrumbs, Button, LayersIcon } from "@plane/ui";
// components
@ -12,13 +12,13 @@ import { CreateInboxIssueModal } from "components/inbox";
import { renderEmoji } from "helpers/emoji.helper";
export const ProjectInboxHeader: FC = observer(() => {
// states
const [createIssueModal, setCreateIssueModal] = useState(false);
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const [createIssueModal, setCreateIssueModal] = useState(false);
const { project: projectStore } = useMobxStore();
const { currentProjectDetails } = projectStore;
// store hooks
const { currentProjectDetails } = useProject();
return (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">

View File

@ -2,27 +2,26 @@ import { FC } from "react";
import useSWR from "swr";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// hooks
import { useProject } from "hooks/store";
// ui
import { Breadcrumbs, LayersIcon } from "@plane/ui";
// helper
// helpers
import { renderEmoji } from "helpers/emoji.helper";
// services
import { IssueService } from "services/issue";
// constants
import { ISSUE_DETAILS } from "constants/fetch-keys";
import { useMobxStore } from "lib/mobx/store-provider";
// services
const issueService = new IssueService();
export const ProjectIssueDetailsHeader: FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
const { project: projectStore } = useMobxStore();
const { currentProjectDetails } = projectStore;
// store hooks
const { currentProjectDetails } = useProject();
const { data: issueDetails } = useSWR(
workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId as string) : null,

View File

@ -1,13 +1,12 @@
import { FC } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// ui
import { Breadcrumbs } from "@plane/ui";
// helper
import { renderEmoji } from "helpers/emoji.helper";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
import { observer } from "mobx-react-lite";
import { useProject, useUser } from "hooks/store";
// constants
import { EUserWorkspaceRoles } from "constants/workspace";
@ -17,14 +16,14 @@ export interface IProjectSettingHeader {
export const ProjectSettingHeader: FC<IProjectSettingHeader> = observer((props) => {
const { title } = props;
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// store
// store hooks
const {
project: projectStore,
user: { currentProjectRole },
} = useMobxStore();
const { currentProjectDetails } = projectStore;
membership: { currentProjectRole },
} = useUser();
const { currentProjectDetails } = useProject();
if (currentProjectRole && currentProjectRole <= EUserWorkspaceRoles.VIEWER) return null;

View File

@ -1,8 +1,8 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Plus } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication, useProject } from "hooks/store";
// components
import { Breadcrumbs, PhotoFilterIcon, Button } from "@plane/ui";
// helpers
@ -12,9 +12,11 @@ export const ProjectViewsHeader: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const { project: projectStore, commandPalette } = useMobxStore();
const { currentProjectDetails } = projectStore;
// store hooks
const {
commandPalette: { toggleCreateViewModal },
} = useApplication();
const { currentProjectDetails } = useProject();
return (
<>
@ -56,7 +58,7 @@ export const ProjectViewsHeader: React.FC = observer(() => {
variant="primary"
size="sm"
prependIcon={<Plus className="h-3.5 w-3.5 stroke-2" />}
onClick={() => commandPalette.toggleCreateViewModal(true)}
onClick={() => toggleCreateViewModal(true)}
>
Create View
</Button>

View File

@ -1,23 +1,17 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Search, Plus, Briefcase } from "lucide-react";
// hooks
import { useApplication, useProject } from "hooks/store";
// ui
import { Breadcrumbs, Button } from "@plane/ui";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
import { observer } from "mobx-react-lite";
export const ProjectsHeader = observer(() => {
const router = useRouter();
const { workspaceSlug } = router.query;
// store
// store hooks
const {
project: projectStore,
commandPalette: commandPaletteStore,
trackEvent: { setTrackElement },
} = useMobxStore();
const projectsList = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : [];
eventTracker: { setTrackElement },
} = useApplication();
const { workspaceProjects, searchQuery, setSearchQuery } = useProject();
return (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
@ -33,13 +27,13 @@ export const ProjectsHeader = observer(() => {
</div>
</div>
<div className="flex items-center gap-3">
{projectsList?.length > 0 && (
{workspaceProjects && workspaceProjects?.length > 0 && (
<div className="flex w-full items-center justify-start gap-1 rounded-md border border-custom-border-200 bg-custom-background-100 px-2.5 py-1.5 text-custom-text-400">
<Search className="h-3.5 w-3.5" />
<input
className="w-full min-w-[234px] border-none bg-transparent text-sm focus:outline-none"
value={projectStore.searchQuery}
onChange={(e) => projectStore.setSearchQuery(e.target.value)}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search"
/>
</div>

View File

@ -1,8 +1,8 @@
import { useRouter } from "next/router";
import useSWR, { mutate } from "swr";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication, useUser, useWorkspace } from "hooks/store";
// components
import { AddComment, IssueActivitySection } from "components/issues";
// services
@ -21,14 +21,15 @@ const issueService = new IssueService();
const issueCommentService = new IssueCommentService();
export const InboxIssueActivity: React.FC<Props> = observer(({ issueDetails }) => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store hooks
const {
user: userStore,
trackEvent: { postHogEventTracker },
workspace: { currentWorkspace },
} = useMobxStore();
eventTracker: { postHogEventTracker },
} = useApplication();
const { currentUser } = useUser();
const { currentWorkspace } = useWorkspace();
const { setToastAlert } = useToast();
@ -39,13 +40,11 @@ export const InboxIssueActivity: React.FC<Props> = observer(({ issueDetails }) =
: null
);
const user = userStore.currentUser;
const handleCommentUpdate = async (commentId: string, data: Partial<any>) => {
if (!workspaceSlug || !projectId || !issueDetails.id || !user) return;
if (!workspaceSlug || !projectId || !issueDetails.id || !currentUser) return;
await issueCommentService
.patchIssueComment(workspaceSlug as string, projectId as string, issueDetails.id as string, commentId, data)
.patchIssueComment(workspaceSlug.toString(), projectId.toString(), issueDetails.id, commentId, data)
.then((res) => {
mutateIssueActivity();
postHogEventTracker(
@ -57,19 +56,19 @@ export const InboxIssueActivity: React.FC<Props> = observer(({ issueDetails }) =
{
isGrouping: true,
groupType: "Workspace_metrics",
gorupId: currentWorkspace?.id!,
groupId: currentWorkspace?.id!,
}
);
});
};
const handleCommentDelete = async (commentId: string) => {
if (!workspaceSlug || !projectId || !issueDetails.id || !user) return;
if (!workspaceSlug || !projectId || !issueDetails.id || !currentUser) return;
mutateIssueActivity((prevData: any) => prevData?.filter((p: any) => p.id !== commentId), false);
await issueCommentService
.deleteIssueComment(workspaceSlug as string, projectId as string, issueDetails.id as string, commentId)
.deleteIssueComment(workspaceSlug.toString(), projectId.toString(), issueDetails.id, commentId)
.then(() => {
mutateIssueActivity();
postHogEventTracker(
@ -80,14 +79,14 @@ export const InboxIssueActivity: React.FC<Props> = observer(({ issueDetails }) =
{
isGrouping: true,
groupType: "Workspace_metrics",
gorupId: currentWorkspace?.id!,
groupId: currentWorkspace?.id!,
}
);
});
};
const handleAddComment = async (formData: IIssueActivity) => {
if (!workspaceSlug || !issueDetails || !user) return;
if (!workspaceSlug || !issueDetails || !currentUser) return;
await issueCommentService
.createIssueComment(workspaceSlug.toString(), issueDetails.project, issueDetails.id, formData)
@ -102,7 +101,7 @@ export const InboxIssueActivity: React.FC<Props> = observer(({ issueDetails }) =
{
isGrouping: true,
groupType: "Workspace_metrics",
gorupId: currentWorkspace?.id!,
groupId: currentWorkspace?.id!,
}
);
})

View File

@ -1,15 +1,13 @@
import { FC, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { Eye, EyeOff } from "lucide-react";
// ui
import { Button, Input } from "@plane/ui";
// types
import { IFormattedInstanceConfiguration } from "types/instance";
// hooks
import { useApplication } from "hooks/store";
import useToast from "hooks/use-toast";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// icons
import { Eye, EyeOff } from "lucide-react";
export interface IInstanceAIForm {
config: IFormattedInstanceConfiguration;
@ -25,7 +23,7 @@ export const InstanceAIForm: FC<IInstanceAIForm> = (props) => {
// states
const [showPassword, setShowPassword] = useState(false);
// store
const { instance: instanceStore } = useMobxStore();
const { instance: instanceStore } = useApplication();
// toast
const { setToastAlert } = useToast();
// form data

View File

@ -6,9 +6,8 @@ import { Eye, EyeOff } from "lucide-react";
// types
import { IFormattedInstanceConfiguration } from "types/instance";
// hooks
import { useApplication } from "hooks/store";
import useToast from "hooks/use-toast";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
export interface IInstanceEmailForm {
config: IFormattedInstanceConfiguration;
@ -27,8 +26,8 @@ export const InstanceEmailForm: FC<IInstanceEmailForm> = (props) => {
const { config } = props;
// states
const [showPassword, setShowPassword] = useState(false);
// store
const { instance: instanceStore } = useMobxStore();
// store hooks
const { instance: instanceStore } = useApplication();
// toast
const { setToastAlert } = useToast();
// form data

View File

@ -5,8 +5,8 @@ import { Button, Input } from "@plane/ui";
// types
import { IInstance, IInstanceAdmin } from "types/instance";
// hooks
import { useApplication } from "hooks/store";
import useToast from "hooks/use-toast";
import { useMobxStore } from "lib/mobx/store-provider";
export interface IInstanceGeneralForm {
instance: IInstance;
@ -20,8 +20,8 @@ export interface GeneralFormValues {
export const InstanceGeneralForm: FC<IInstanceGeneralForm> = (props) => {
const { instance, instanceAdmins } = props;
// store
const { instance: instanceStore } = useMobxStore();
// store hooks
const { instance: instanceStore } = useApplication();
// toast
const { setToastAlert } = useToast();
// form data

View File

@ -1,15 +1,13 @@
import { FC, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { Copy, Eye, EyeOff } from "lucide-react";
// ui
import { Button, Input } from "@plane/ui";
// types
import { IFormattedInstanceConfiguration } from "types/instance";
// hooks
import { useApplication } from "hooks/store";
import useToast from "hooks/use-toast";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// icons
import { Copy, Eye, EyeOff } from "lucide-react";
export interface IInstanceGithubConfigForm {
config: IFormattedInstanceConfiguration;
@ -24,8 +22,8 @@ export const InstanceGithubConfigForm: FC<IInstanceGithubConfigForm> = (props) =
const { config } = props;
// states
const [showPassword, setShowPassword] = useState(false);
// store
const { instance: instanceStore } = useMobxStore();
// store hooks
const { instance: instanceStore } = useApplication();
// toast
const { setToastAlert } = useToast();
// form data

View File

@ -1,15 +1,13 @@
import { FC } from "react";
import { Controller, useForm } from "react-hook-form";
import { Copy } from "lucide-react";
// ui
import { Button, Input } from "@plane/ui";
// types
import { IFormattedInstanceConfiguration } from "types/instance";
// hooks
import { useApplication } from "hooks/store";
import useToast from "hooks/use-toast";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// icons
import { Copy } from "lucide-react";
export interface IInstanceGoogleConfigForm {
config: IFormattedInstanceConfiguration;
@ -22,8 +20,8 @@ export interface GoogleConfigFormValues {
export const InstanceGoogleConfigForm: FC<IInstanceGoogleConfigForm> = (props) => {
const { config } = props;
// store
const { instance: instanceStore } = useMobxStore();
// store hooks
const { instance: instanceStore } = useApplication();
// toast
const { setToastAlert } = useToast();
// form data

View File

@ -1,10 +1,10 @@
import { FC, useState, useRef } from "react";
import { Transition } from "@headlessui/react";
import Link from "next/link";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// icons
import { FileText, HelpCircle, MessagesSquare, MoveLeft } from "lucide-react";
// hooks
import { useApplication } from "hooks/store";
// icons
import { DiscordIcon, GithubIcon } from "@plane/ui";
// assets
import packageJson from "package.json";
@ -39,7 +39,7 @@ export const InstanceHelpSection: FC = () => {
// store
const {
theme: { sidebarCollapsed, toggleSidebar },
} = useMobxStore();
} = useApplication();
// refs
const helpOptionsRef = useRef<HTMLDivElement | null>(null);

View File

@ -1,15 +1,13 @@
import { FC, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { Eye, EyeOff } from "lucide-react";
// ui
import { Button, Input } from "@plane/ui";
// types
import { IFormattedInstanceConfiguration } from "types/instance";
// hooks
import { useApplication } from "hooks/store";
import useToast from "hooks/use-toast";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// icons
import { Eye, EyeOff } from "lucide-react";
export interface IInstanceImageConfigForm {
config: IFormattedInstanceConfiguration;
@ -23,8 +21,8 @@ export const InstanceImageConfigForm: FC<IInstanceImageConfigForm> = (props) =>
const { config } = props;
// states
const [showPassword, setShowPassword] = useState(false);
// store
const { instance: instanceStore } = useMobxStore();
// store hooks
const { instance: instanceStore } = useApplication();
// toast
const { setToastAlert } = useToast();
// form data

View File

@ -1,6 +1,8 @@
import React, { useState } from "react";
import Image from "next/image";
import { useTheme } from "next-themes";
// hooks
import { useApplication } from "hooks/store";
// ui
import { Button } from "@plane/ui";
import { UserCog2 } from "lucide-react";
@ -8,17 +10,16 @@ import { UserCog2 } from "lucide-react";
import instanceSetupDone from "public/instance-setup-done.webp";
import PlaneBlackLogo from "public/plane-logos/black-horizontal-with-blue-logo.svg";
import PlaneWhiteLogo from "public/plane-logos/white-horizontal-with-blue-logo.svg";
import { useMobxStore } from "lib/mobx/store-provider";
export const InstanceSetupDone = () => {
// states
const [isRedirecting, setIsRedirecting] = useState(false);
// next-themes
const { resolvedTheme } = useTheme();
// mobx store
// store hooks
const {
instance: { fetchInstanceInfo },
} = useMobxStore();
} = useApplication();
const planeLogo = resolvedTheme === "dark" ? PlaneWhiteLogo : PlaneBlackLogo;

View File

@ -1,11 +1,10 @@
import { FC } from "react";
import { useForm, Controller } from "react-hook-form";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import { XCircle } from "lucide-react";
// hooks
import { useUser } from "hooks/store";
// ui
import { Input, Button } from "@plane/ui";
// icons
import { XCircle } from "lucide-react";
// services
import { AuthService } from "services/auth.service";
const authService = new AuthService();
@ -25,9 +24,8 @@ export interface IInstanceSetupEmailForm {
export const InstanceSetupSignInForm: FC<IInstanceSetupEmailForm> = (props) => {
const { handleNextStep } = props;
const {
user: { fetchCurrentUser },
} = useMobxStore();
// store hooks
const { fetchCurrentUser } = useUser();
// form info
const {
control,

View File

@ -4,15 +4,13 @@ import Image from "next/image";
// components
import { InstanceSetupFormRoot } from "components/instance";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
import { useUser } from "hooks/store";
// images
import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
export const InstanceSetupView = observer(() => {
// store
const {
user: { fetchCurrentUser },
} = useMobxStore();
// store hooks
const { fetchCurrentUser } = useUser();
const mutateUserInfo = useCallback(() => {
fetchCurrentUser();

View File

@ -8,8 +8,8 @@ import { mutate } from "swr";
import { Menu, Transition } from "@headlessui/react";
// icons
import { LogIn, LogOut, Settings, UserCog2 } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication, useUser } from "hooks/store";
// hooks
import useToast from "hooks/use-toast";
// ui
@ -26,13 +26,14 @@ const PROFILE_LINKS = [
];
export const InstanceSidebarDropdown = observer(() => {
// router
const router = useRouter();
// store
// store hooks
const {
theme: { sidebarCollapsed },
workspace: { workspaceSlug },
user: { signOut, currentUser, currentUserSettings },
} = useMobxStore();
router: { workspaceSlug },
} = useApplication();
const { signOut, currentUser, currentUserSettings } = useUser();
// hooks
const { setToastAlert } = useToast();
const { setTheme } = useTheme();

View File

@ -1,9 +1,8 @@
import Link from "next/link";
import { useRouter } from "next/router";
// icons
import { Image, BrainCog, Cog, Lock, Mail } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication } from "hooks/store";
// ui
import { Tooltip } from "@plane/ui";
@ -41,9 +40,10 @@ const INSTANCE_ADMIN_LINKS = [
];
export const InstanceAdminSidebarMenu = () => {
// store hooks
const {
theme: { sidebarCollapsed },
} = useMobxStore();
} = useApplication();
// router
const router = useRouter();

View File

@ -1,11 +1,11 @@
import { observer } from "mobx-react-lite";
// hooks
import { useApplication } from "hooks/store";
import useIntegrationPopup from "hooks/use-integration-popup";
// ui
import { Button } from "@plane/ui";
// types
import { IWorkspaceIntegration } from "types";
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
type Props = {
workspaceIntegration: false | IWorkspaceIntegration | undefined;
@ -13,9 +13,10 @@ type Props = {
};
export const GithubAuth: React.FC<Props> = observer(({ workspaceIntegration, provider }) => {
// store hooks
const {
appConfig: { envConfig },
} = useMobxStore();
config: { envConfig },
} = useApplication();
// hooks
const { startAuth, isConnecting } = useIntegrationPopup({
provider,

View File

@ -1,9 +1,8 @@
import { FC } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Control, Controller, UseFormWatch } from "react-hook-form";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useProject } from "hooks/store";
// components
import { SelectRepository, TFormValues, TIntegrationSteps } from "components/integration";
// ui
@ -22,21 +21,18 @@ type Props = {
export const GithubImportData: FC<Props> = observer((props) => {
const { handleStepChange, integration, control, watch } = props;
// store hooks
const { workspaceProjects, getProjectById } = useProject();
const router = useRouter();
const { workspaceSlug } = router.query;
const options = workspaceProjects?.map((projectId) => {
const projectDetails = getProjectById(projectId);
const { project: projectStore } = useMobxStore();
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
const options = projects
? projects.map((project) => ({
value: project.id,
query: project.name,
content: <p>{truncateText(project.name, 25)}</p>,
}))
: undefined;
return {
value: `${projectDetails?.id}`,
query: `${projectDetails?.name}`,
content: <p>{truncateText(projectDetails?.name ?? "", 25)}</p>,
};
});
return (
<div className="mt-6">
@ -74,7 +70,7 @@ export const GithubImportData: FC<Props> = observer((props) => {
<p className="text-xs text-custom-text-200">Select the project to import the issues to.</p>
</div>
<div className="col-span-12 sm:col-span-4">
{projects && (
{workspaceProjects && (
<Controller
control={control}
name="project"
@ -82,11 +78,7 @@ export const GithubImportData: FC<Props> = observer((props) => {
<CustomSearchSelect
value={value}
label={
value ? (
projects.find((p) => p.id === value)?.name
) : (
<span className="text-custom-text-200">Select Project</span>
)
value ? getProjectById(value)?.name : <span className="text-custom-text-200">Select Project</span>
}
onChange={onChange}
options={options}

View File

@ -1,28 +1,23 @@
import React from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import { observer } from "mobx-react-lite";
import { useFormContext, Controller } from "react-hook-form";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// icons
import { Plus } from "lucide-react";
// hooks
import { useApplication, useProject } from "hooks/store";
// components
import { CustomSelect, Input } from "@plane/ui";
// types
import { IJiraImporterForm } from "types";
export const JiraGetImportDetail: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug } = router.query;
// store hooks
const {
project: projectStore,
commandPalette: commandPaletteStore,
trackEvent: { setTrackElement },
} = useMobxStore();
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
eventTracker: { setTrackElement },
} = useApplication();
const { workspaceProjects, getProjectById } = useProject();
// form info
const {
control,
formState: { errors },
@ -170,20 +165,26 @@ export const JiraGetImportDetail: React.FC = observer(() => {
onChange={onChange}
label={
<span>
{value && value !== "" ? (
projects?.find((p) => p.id === value)?.name
{value && value.trim() !== "" ? (
getProjectById(value)?.name
) : (
<span className="text-custom-text-200">Select a project</span>
)}
</span>
}
>
{projects && projects.length > 0 ? (
projects.map((project) => (
<CustomSelect.Option key={project.id} value={project.id}>
{project.name}
</CustomSelect.Option>
))
{workspaceProjects && workspaceProjects.length > 0 ? (
workspaceProjects.map((projectId) => {
const projectDetails = getProjectById(projectId);
if (!projectDetails) return;
return (
<CustomSelect.Option key={projectId} value={projectId}>
{projectDetails.name}
</CustomSelect.Option>
);
})
) : (
<div className="flex cursor-pointer select-none items-center space-x-2 truncate rounded px-1 py-1.5 text-custom-text-200">
<p>You don{"'"}t have any project. Please create a project first.</p>

View File

@ -2,12 +2,12 @@ import { useState } from "react";
import Image from "next/image";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR, { mutate } from "swr";
// services
import { IntegrationService } from "services/integrations";
// hooks
import { useApplication, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
import useIntegrationPopup from "hooks/use-integration-popup";
// ui
@ -20,8 +20,6 @@ import { CheckCircle } from "lucide-react";
import { IAppIntegration, IWorkspaceIntegration } from "types";
// fetch-keys
import { WORKSPACE_INTEGRATIONS } from "constants/fetch-keys";
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
type Props = {
integration: IAppIntegration;
@ -44,20 +42,23 @@ const integrationDetails: { [key: string]: any } = {
const integrationService = new IntegrationService();
export const SingleIntegrationCard: React.FC<Props> = observer(({ integration }) => {
const {
appConfig: { envConfig },
user: { currentWorkspaceRole },
} = useMobxStore();
const isUserAdmin = currentWorkspaceRole === 20;
// states
const [deletingIntegration, setDeletingIntegration] = useState(false);
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// store hooks
const {
config: { envConfig },
} = useApplication();
const {
membership: { currentWorkspaceRole },
} = useUser();
// toast alert
const { setToastAlert } = useToast();
const isUserAdmin = currentWorkspaceRole === 20;
const { startAuth, isConnecting: isInstalling } = useIntegrationPopup({
provider: integration.provider,
github_app_name: envConfig?.github_app_name || "",

View File

@ -2,18 +2,17 @@ import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import useSWR, { mutate } from "swr";
import { observer } from "mobx-react-lite";
// hooks
import { useApplication } from "hooks/store";
import useIntegrationPopup from "hooks/use-integration-popup";
// services
import { AppInstallationService } from "services/app_installation.service";
// ui
import { Loader } from "@plane/ui";
// hooks
import useIntegrationPopup from "hooks/use-integration-popup";
// types
import { IWorkspaceIntegration, ISlackIntegration } from "types";
// fetch-keys
import { SLACK_CHANNEL_INFO } from "constants/fetch-keys";
// lib
import { useMobxStore } from "lib/mobx/store-provider";
type Props = {
integration: IWorkspaceIntegration;
@ -22,10 +21,10 @@ type Props = {
const appInstallationService = new AppInstallationService();
export const SelectChannel: React.FC<Props> = observer(({ integration }) => {
// store
// store hooks
const {
appConfig: { envConfig },
} = useMobxStore();
config: { envConfig },
} = useApplication();
// states
const [slackChannelAvailabilityToggle, setSlackChannelAvailabilityToggle] = useState<boolean>(false);
const [slackChannel, setSlackChannel] = useState<ISlackIntegration | null>(null);

View File

@ -3,12 +3,11 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { mutate } from "swr";
import { useDropzone } from "react-dropzone";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication } from "hooks/store";
import useToast from "hooks/use-toast";
// services
import { IssueAttachmentService } from "services/issue";
// hooks
import useToast from "hooks/use-toast";
// types
import { IIssueAttachment } from "types";
// fetch-keys
@ -29,12 +28,12 @@ export const IssueAttachmentUpload: React.FC<Props> = observer((props) => {
// router
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
// toast alert
const { setToastAlert } = useToast();
// store hooks
const {
appConfig: { envConfig },
} = useMobxStore();
config: { envConfig },
} = useApplication();
const onDrop = useCallback((acceptedFiles: File[]) => {
if (!acceptedFiles[0] || !workspaceSlug) return;

View File

@ -1,12 +1,13 @@
import React, { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { observer } from "mobx-react-lite";
// hooks
import { useUser } from "hooks/store";
import useEditorSuggestions from "hooks/use-editor-suggestions";
// services
import { FileService } from "services/file.service";
// icons
import { Check, Globe2, Lock, MessageSquare, Pencil, Trash2, X } from "lucide-react";
// hooks
import useUser from "hooks/use-user";
// ui
import { CustomMenu } from "@plane/ui";
import { CommentReaction } from "components/issues";
@ -15,7 +16,6 @@ import { LiteTextEditorWithRef, LiteReadOnlyEditorWithRef } from "@plane/lite-te
import { timeAgo } from "helpers/date-time.helper";
// types
import type { IIssueActivity } from "types";
import useEditorSuggestions from "hooks/use-editor-suggestions";
// services
const fileService = new FileService();
@ -28,22 +28,18 @@ type Props = {
workspaceSlug: string;
};
export const CommentCard: React.FC<Props> = ({
comment,
handleCommentDeletion,
onSubmit,
showAccessSpecifier = false,
workspaceSlug,
}) => {
const { user } = useUser();
export const CommentCard: React.FC<Props> = observer((props) => {
const { comment, handleCommentDeletion, onSubmit, showAccessSpecifier = false, workspaceSlug } = props;
// states
const [isEditing, setIsEditing] = useState(false);
// refs
const editorRef = React.useRef<any>(null);
const showEditorRef = React.useRef<any>(null);
const editorSuggestions = useEditorSuggestions();
const [isEditing, setIsEditing] = useState(false);
// store hooks
const { currentUser } = useUser();
// form info
const {
formState: { isSubmitting },
handleSubmit,
@ -152,7 +148,7 @@ export const CommentCard: React.FC<Props> = ({
</div>
</div>
</div>
{user?.id === comment.actor && (
{currentUser?.id === comment.actor && (
<CustomMenu ellipsis>
<CustomMenu.MenuItem onClick={() => setIsEditing(true)} className="flex items-center gap-1">
<Pencil className="h-3 w-3" />
@ -192,4 +188,4 @@ export const CommentCard: React.FC<Props> = ({
)}
</div>
);
};
});

View File

@ -1,12 +1,14 @@
import { FC } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// hooks
import useUser from "hooks/use-user";
import { useUser } from "hooks/store";
import useCommentReaction from "hooks/use-comment-reaction";
// ui
import { ReactionSelector } from "components/core";
// helper
import { renderEmoji } from "helpers/emoji.helper";
// types
import { IssueCommentReaction } from "types";
type Props = {
@ -15,13 +17,13 @@ type Props = {
readonly?: boolean;
};
export const CommentReaction: FC<Props> = (props) => {
export const CommentReaction: FC<Props> = observer((props) => {
const { projectId, commentId, readonly = false } = props;
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const { user } = useUser();
// store hooks
const { currentUser } = useUser();
const { commentReactions, groupedReactions, handleReactionCreate, handleReactionDelete } = useCommentReaction(
workspaceSlug,
@ -33,7 +35,7 @@ export const CommentReaction: FC<Props> = (props) => {
if (!workspaceSlug || !projectId || !commentId) return;
const isSelected = commentReactions?.some(
(r: IssueCommentReaction) => r.actor === user?.id && r.reaction === reaction
(r: IssueCommentReaction) => r.actor === currentUser?.id && r.reaction === reaction
);
if (isSelected) {
@ -51,7 +53,7 @@ export const CommentReaction: FC<Props> = (props) => {
position="top"
value={
commentReactions
?.filter((reaction: IssueCommentReaction) => reaction.actor === user?.id)
?.filter((reaction: IssueCommentReaction) => reaction.actor === currentUser?.id)
.map((r: IssueCommentReaction) => r.reaction) || []
}
onSelect={handleReactionClick}
@ -70,7 +72,9 @@ export const CommentReaction: FC<Props> = (props) => {
}}
key={reaction}
className={`flex h-full items-center gap-1 rounded-md px-2 py-1 text-sm text-custom-text-100 ${
commentReactions?.some((r: IssueCommentReaction) => r.actor === user?.id && r.reaction === reaction)
commentReactions?.some(
(r: IssueCommentReaction) => r.actor === currentUser?.id && r.reaction === reaction
)
? "bg-custom-primary-100/10"
: "bg-custom-background-80"
}`}
@ -78,7 +82,9 @@ export const CommentReaction: FC<Props> = (props) => {
<span>{renderEmoji(reaction)}</span>
<span
className={
commentReactions?.some((r: IssueCommentReaction) => r.actor === user?.id && r.reaction === reaction)
commentReactions?.some(
(r: IssueCommentReaction) => r.actor === currentUser?.id && r.reaction === reaction
)
? "text-custom-primary-100"
: ""
}
@ -90,4 +96,4 @@ export const CommentReaction: FC<Props> = (props) => {
)}
</div>
);
};
});

View File

@ -1,9 +1,6 @@
import React, { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Dialog, Transition } from "@headlessui/react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// services
import { IssueDraftService } from "services/issue";
// hooks
@ -24,17 +21,14 @@ type Props = {
const issueDraftService = new IssueDraftService();
export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
export const DeleteDraftIssueModal: React.FC<Props> = (props) => {
const { isOpen, handleClose, data, onSubmit } = props;
// states
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const { user: userStore } = useMobxStore();
const user = userStore.currentUser;
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// toast alert
const { setToastAlert } = useToast();
useEffect(() => {
@ -47,12 +41,12 @@ export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
};
const handleDeletion = async () => {
if (!workspaceSlug || !data || !user) return;
if (!workspaceSlug || !data) return;
setIsDeleteLoading(true);
await issueDraftService
.deleteDraftIssue(workspaceSlug as string, data.project, data.id)
.deleteDraftIssue(workspaceSlug.toString(), data.project, data.id)
.then(() => {
setIsDeleteLoading(false);
handleClose();
@ -138,4 +132,4 @@ export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
</Dialog>
</Transition.Root>
);
});
};

View File

@ -1,12 +1,16 @@
import React, { FC, useState, useEffect, useRef } from "react";
import { useRouter } from "next/router";
import { Controller, useForm } from "react-hook-form";
import { observer } from "mobx-react-lite";
import { Sparkle, X } from "lucide-react";
// hooks
import { useApplication } from "hooks/store";
import useToast from "hooks/use-toast";
import useLocalStorage from "hooks/use-local-storage";
import useEditorSuggestions from "hooks/use-editor-suggestions";
// services
import { AIService } from "services/ai.service";
import { FileService } from "services/file.service";
// hooks
import useToast from "hooks/use-toast";
import useLocalStorage from "hooks/use-local-storage";
// components
import { GptAssistantModal } from "components/core";
import { ParentIssuesListModal } from "components/issues";
@ -21,18 +25,11 @@ import {
} from "components/issues/select";
import { CreateStateModal } from "components/states";
import { CreateLabelModal } from "components/labels";
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
// ui
import {} from "components/ui";
import { Button, CustomMenu, Input, ToggleSwitch } from "@plane/ui";
// icons
import { Sparkle, X } from "lucide-react";
// types
import type { IUser, IIssue, ISearchIssueResponse } from "types";
// components
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
import useEditorSuggestions from "hooks/use-editor-suggestions";
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
const aiService = new AIService();
const fileService = new FileService();
@ -123,8 +120,8 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
const { workspaceSlug } = router.query;
// store
const {
appConfig: { envConfig },
} = useMobxStore();
config: { envConfig },
} = useApplication();
// form info
const {
formState: { errors, isSubmitting },

View File

@ -2,8 +2,10 @@ import React, { FC, useState, useEffect, useRef } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Controller, useForm } from "react-hook-form";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import { LayoutPanelTop, Sparkle, X } from "lucide-react";
// hooks
import { useApplication, useUser } from "hooks/store";
import useEditorSuggestions from "hooks/use-editor-suggestions";
// services
import { AIService } from "services/ai.service";
import { FileService } from "services/file.service";
@ -25,15 +27,11 @@ import {
} from "components/issues/select";
import { CreateStateModal } from "components/states";
import { CreateLabelModal } from "components/labels";
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
// ui
import { Button, CustomMenu, Input, ToggleSwitch } from "@plane/ui";
// icons
import { LayoutPanelTop, Sparkle, X } from "lucide-react";
// types
import type { IIssue, ISearchIssueResponse } from "types";
// components
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
import useEditorSuggestions from "hooks/use-editor-suggestions";
const defaultValues: Partial<IIssue> = {
project: "",
@ -106,12 +104,11 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// store
// store hooks
const {
user: userStore,
appConfig: { envConfig },
} = useMobxStore();
const user = userStore.currentUser;
config: { envConfig },
} = useApplication();
const {} = useUser();
// hooks
const editorSuggestion = useEditorSuggestions();
const { setToastAlert } = useToast();
@ -183,12 +180,12 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
};
const handleAutoGenerateDescription = async () => {
if (!workspaceSlug || !projectId || !user) return;
if (!workspaceSlug || !projectId) return;
setIAmFeelingLucky(true);
aiService
.createGptTask(workspaceSlug as string, projectId as string, {
.createGptTask(workspaceSlug.toString(), projectId.toString(), {
prompt: issueName,
task: "Generate a proper description for this issue.",
})

View File

@ -2,9 +2,8 @@ import { useEffect, useRef, useState } from "react";
import { useRouter } from "next/router";
import { useForm } from "react-hook-form";
import { observer } from "mobx-react-lite";
// store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useProject, useWorkspace } from "hooks/store";
import useToast from "hooks/use-toast";
import useKeypress from "hooks/use-keypress";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
@ -13,7 +12,7 @@ import { createIssuePayload } from "helpers/issue.helper";
// icons
import { PlusIcon } from "lucide-react";
// types
import { IIssue, IProject } from "types";
import { IIssue } from "types";
type Props = {
formKey: keyof IIssue;
@ -58,25 +57,22 @@ const Inputs = (props: any) => {
export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
const { formKey, groupId, prePopulatedData, quickAddCallback, viewId } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const { workspace: workspaceStore, project: projectStore } = useMobxStore();
// ref
const { workspaceSlug, projectId } = router.query;
// store hooks
const { getProjectById } = useProject();
const { getWorkspaceBySlug } = useWorkspace();
// refs
const ref = useRef<HTMLDivElement>(null);
// states
const [isOpen, setIsOpen] = useState(false);
// toast alert
const { setToastAlert } = useToast();
// derived values
const workspaceDetail = (workspaceSlug && workspaceStore.getWorkspaceBySlug(workspaceSlug)) || null;
const projectDetail: IProject | null =
(workspaceSlug && projectId && projectStore.getProjectById(workspaceSlug, projectId)) || null;
const workspaceDetail = (workspaceSlug && getWorkspaceBySlug(workspaceSlug.toString())) || null;
const projectDetail = projectId ? getProjectById(projectId.toString()) : null;
const {
reset,
@ -112,7 +108,7 @@ export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
}, [errors, setToastAlert]);
const onSubmitHandler = async (formData: IIssue) => {
if (isSubmitting || !groupId || !workspaceDetail || !projectDetail) return;
if (isSubmitting || !groupId || !workspaceDetail || !projectDetail || !workspaceSlug || !projectId) return;
reset({ ...defaultValues });
@ -124,8 +120,8 @@ export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
try {
quickAddCallback &&
(await quickAddCallback(
workspaceSlug,
projectId,
workspaceSlug.toString(),
projectId.toString(),
{
...payload,
},

View File

@ -1,31 +1,24 @@
// next
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import { Plus, PlusIcon } from "lucide-react";
// hooks
import { useApplication, useProject } from "hooks/store";
// components
import { EmptyState } from "components/common";
// assets
import emptyIssue from "public/empty-state/issue.svg";
import emptyProject from "public/empty-state/project.svg";
// icons
import { Plus, PlusIcon } from "lucide-react";
export const GlobalViewEmptyState: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug } = router.query;
// store hooks
const {
commandPalette: commandPaletteStore,
project: projectStore,
trackEvent: { setTrackElement },
} = useMobxStore();
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
commandPalette: { toggleCreateIssueModal, toggleCreateProjectModal },
eventTracker: { setTrackElement },
} = useApplication();
const { workspaceProjects } = useProject();
return (
<div className="grid h-full w-full place-items-center">
{!projects || projects?.length === 0 ? (
{!workspaceProjects || workspaceProjects?.length === 0 ? (
<EmptyState
image={emptyProject}
title="No projects yet"
@ -35,7 +28,7 @@ export const GlobalViewEmptyState: React.FC = observer(() => {
text: "New Project",
onClick: () => {
setTrackElement("ALL_ISSUES_EMPTY_STATE");
commandPaletteStore.toggleCreateProjectModal(true);
toggleCreateProjectModal(true);
},
}}
/>
@ -49,7 +42,7 @@ export const GlobalViewEmptyState: React.FC = observer(() => {
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
onClick: () => {
setTrackElement("ALL_ISSUES_EMPTY_STATE");
commandPaletteStore.toggleCreateIssueModal(true);
toggleCreateIssueModal(true);
},
}}
/>

View File

@ -1,7 +1,7 @@
import { observer } from "mobx-react-lite";
import { PlusIcon } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication } from "hooks/store";
// components
import { EmptyState } from "components/common";
// assets
@ -9,10 +9,11 @@ import emptyIssue from "public/empty-state/issue.svg";
import { EProjectStore } from "store_legacy/command-palette.store";
export const ProjectViewEmptyState: React.FC = observer(() => {
// store hooks
const {
commandPalette: commandPaletteStore,
trackEvent: { setTrackElement },
} = useMobxStore();
eventTracker: { setTrackElement },
} = useApplication();
return (
<div className="grid h-full w-full place-items-center">

View File

@ -1,7 +1,7 @@
import { observer } from "mobx-react-lite";
import { PlusIcon } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication } from "hooks/store";
// components
import { NewEmptyState } from "components/common/new-empty-state";
// assets
@ -9,10 +9,11 @@ import emptyIssue from "public/empty-state/empty_issues.webp";
import { EProjectStore } from "store_legacy/command-palette.store";
export const ProjectEmptyState: React.FC = observer(() => {
// store hooks
const {
commandPalette: commandPaletteStore,
trackEvent: { setTrackElement },
} = useMobxStore();
eventTracker: { setTrackElement },
} = useApplication();
return (
<div className="grid h-full w-full place-items-center">

View File

@ -1,8 +1,8 @@
import React from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useUser } from "hooks/store";
// components
import { IssueGanttBlock, IssuePeekOverview } from "components/issues";
import {
@ -39,13 +39,13 @@ interface IBaseGanttRoot {
export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGanttRoot) => {
const { issueFiltersStore, issueStore, viewId } = props;
// router
const router = useRouter();
const { workspaceSlug, peekIssueId, peekProjectId } = router.query;
// store hooks
const {
user: { currentProjectRole },
} = useMobxStore();
membership: { currentProjectRole },
} = useUser();
const appliedDisplayFilters = issueFiltersStore.issueFilters?.displayFilters;

View File

@ -3,12 +3,10 @@ import { useRouter } from "next/router";
import { useForm } from "react-hook-form";
import { observer } from "mobx-react-lite";
import { PlusIcon } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useProject, useWorkspace } from "hooks/store";
import useToast from "hooks/use-toast";
import useKeypress from "hooks/use-keypress";
import useProjectDetails from "hooks/use-project-details";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
// helpers
import { renderDateFormat } from "helpers/date-time.helper";
@ -55,16 +53,13 @@ const Inputs = (props: any) => {
export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
const { prePopulatedData, quickAddCallback, viewId } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
// store
const { workspace: workspaceStore } = useMobxStore();
const { projectDetails } = useProjectDetails();
const { workspaceSlug, projectId } = router.query;
// store hooks
const { getWorkspaceBySlug } = useWorkspace();
const { currentProjectDetails } = useProject();
// form info
const {
reset,
handleSubmit,
@ -87,7 +82,7 @@ export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
const { setToastAlert } = useToast();
// derived values
const workspaceDetail = workspaceStore.getWorkspaceBySlug(workspaceSlug?.toString()!);
const workspaceDetail = getWorkspaceBySlug(workspaceSlug?.toString()!);
useEffect(() => {
if (!isOpen) reset({ ...defaultValues });
@ -113,7 +108,7 @@ export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
// resetting the form so that user can add another issue quickly
reset({ ...defaultValues, ...(prePopulatedData ?? {}) });
const payload = createIssuePayload(workspaceDetail!, projectDetails!, {
const payload = createIssuePayload(workspaceDetail!, currentProjectDetails!, {
...(prePopulatedData ?? {}),
...formData,
start_date: renderDateFormat(new Date()),
@ -122,7 +117,7 @@ export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
try {
if (quickAddCallback) {
await quickAddCallback(workspaceSlug, projectId, payload, viewId);
await quickAddCallback(workspaceSlug.toString(), projectId.toString(), payload, viewId);
}
setToastAlert({
type: "success",
@ -152,7 +147,7 @@ export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
onSubmit={handleSubmit(onSubmitHandler)}
>
<div className="h-3 w-3 flex-shrink-0 rounded-full border border-custom-border-1000" />
<h4 className="text-xs text-custom-text-400">{projectDetails?.identifier ?? "..."}</h4>
<h4 className="text-xs text-custom-text-400">{currentProjectDetails?.identifier ?? "..."}</h4>
<Inputs register={register} setFocus={setFocus} />
</form>
)}

View File

@ -2,8 +2,9 @@ import { FC, useCallback, useState } from "react";
import { DragDropContext, DragStart, DraggableLocation, DropResult, Droppable } from "@hello-pangea/dnd";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { Spinner } from "@plane/ui";
// types
@ -24,8 +25,6 @@ import {
} from "store_legacy/issues";
import { IQuickActionProps } from "../list/list-view-types";
import { IIssueKanBanViewStore } from "store_legacy/issue";
// hooks
import useToast from "hooks/use-toast";
//components
import { KanBan } from "./default";
import { KanBanSwimLanes } from "./swimlanes";
@ -92,14 +91,13 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
// router
const router = useRouter();
const { workspaceSlug, peekIssueId, peekProjectId } = router.query;
// mobx store
const { user: userStore } = useMobxStore();
// hooks
// store hooks
const {
membership: { currentProjectRole },
} = useUser();
// toast alert
const { setToastAlert } = useToast();
const { currentProjectRole } = userStore;
const issues = issueStore?.getIssues || {};
const issueIds = issueStore?.getIssuesIds || [];

View File

@ -1,18 +1,17 @@
import { useEffect, useState, useRef } from "react";
import { useRouter } from "next/router";
import { useForm } from "react-hook-form";
import { PlusIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
// store
import { useMobxStore } from "lib/mobx/store-provider";
import { PlusIcon } from "lucide-react";
// hooks
import { useProject, useWorkspace } from "hooks/store";
import useToast from "hooks/use-toast";
import useKeypress from "hooks/use-keypress";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
// helpers
import { createIssuePayload } from "helpers/issue.helper";
// types
import { IIssue, IProject } from "types";
import { IIssue } from "types";
const Inputs = (props: any) => {
const { register, setFocus, projectDetail } = props;
@ -56,16 +55,15 @@ const defaultValues: Partial<IIssue> = {
export const KanBanQuickAddIssueForm: React.FC<IKanBanQuickAddIssueForm> = observer((props) => {
const { formKey, groupId, prePopulatedData, quickAddCallback, viewId } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const { workspaceSlug, projectId } = router.query;
// store hooks
const { getWorkspaceBySlug } = useWorkspace();
const { getProjectById } = useProject();
const { workspace: workspaceStore, project: projectStore } = useMobxStore();
const workspaceDetail = (workspaceSlug && workspaceStore.getWorkspaceBySlug(workspaceSlug)) || null;
const projectDetail: IProject | null =
(workspaceSlug && projectId && projectStore.getProjectById(workspaceSlug, projectId)) || null;
const workspaceDetail = workspaceSlug ? getWorkspaceBySlug(workspaceSlug.toString()) : null;
const projectDetail = projectId ? getProjectById(projectId.toString()) : null;
const ref = useRef<HTMLFormElement>(null);
@ -89,7 +87,7 @@ export const KanBanQuickAddIssueForm: React.FC<IKanBanQuickAddIssueForm> = obser
}, [isOpen, reset]);
const onSubmitHandler = async (formData: IIssue) => {
if (isSubmitting || !groupId || !workspaceDetail || !projectDetail) return;
if (isSubmitting || !groupId || !workspaceDetail || !projectDetail || !workspaceSlug || !projectId) return;
reset({ ...defaultValues });
@ -101,8 +99,8 @@ export const KanBanQuickAddIssueForm: React.FC<IKanBanQuickAddIssueForm> = obser
try {
quickAddCallback &&
(await quickAddCallback(
workspaceSlug,
projectId,
workspaceSlug.toString(),
projectId.toString(),
{
...payload,
},

View File

@ -4,11 +4,10 @@ import { useForm } from "react-hook-form";
import { PlusIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
// hooks
import { useProject, useWorkspace } from "hooks/store";
import useToast from "hooks/use-toast";
import useKeypress from "hooks/use-keypress";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
// store
import { useMobxStore } from "lib/mobx/store-provider";
// constants
import { IIssue, IProject } from "types";
// types
@ -60,15 +59,12 @@ const defaultValues: Partial<IIssue> = {
export const ListQuickAddIssueForm: FC<IListQuickAddIssueForm> = observer((props) => {
const { prePopulatedData, quickAddCallback, viewId } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const { workspace: workspaceStore, project: projectStore } = useMobxStore();
const workspaceDetail = (workspaceSlug && workspaceStore.getWorkspaceBySlug(workspaceSlug)) || null;
const projectDetail: IProject | null =
(workspaceSlug && projectId && projectStore.getProjectById(workspaceSlug, projectId)) || null;
const { workspaceSlug, projectId } = router.query;
// store hooks
const { currentWorkspace } = useWorkspace();
const { currentProjectDetails } = useProject();
const ref = useRef<HTMLFormElement>(null);
@ -92,17 +88,18 @@ export const ListQuickAddIssueForm: FC<IListQuickAddIssueForm> = observer((props
}, [isOpen, reset]);
const onSubmitHandler = async (formData: IIssue) => {
if (isSubmitting || !workspaceDetail || !projectDetail) return;
if (isSubmitting || !currentWorkspace || !currentProjectDetails || !workspaceSlug || !projectId) return;
reset({ ...defaultValues });
const payload = createIssuePayload(workspaceDetail, projectDetail, {
const payload = createIssuePayload(currentWorkspace, currentProjectDetails, {
...(prePopulatedData ?? {}),
...formData,
});
try {
quickAddCallback && (await quickAddCallback(workspaceSlug, projectId, { ...payload }, viewId));
quickAddCallback &&
(await quickAddCallback(workspaceSlug.toString(), projectId.toString(), { ...payload }, viewId));
setToastAlert({
type: "success",
title: "Success!",
@ -130,7 +127,12 @@ export const ListQuickAddIssueForm: FC<IListQuickAddIssueForm> = observer((props
onSubmit={handleSubmit(onSubmitHandler)}
className="flex w-full items-center gap-x-3 border-[0.5px] border-t-0 border-custom-border-100 bg-custom-background-100 px-3"
>
<Inputs formKey={"name"} register={register} setFocus={setFocus} projectDetail={projectDetail} />
<Inputs
formKey={"name"}
register={register}
setFocus={setFocus}
projectDetail={currentProjectDetails ?? null}
/>
</form>
<div className="px-3 py-2 text-xs italic text-custom-text-200">{`Press 'Enter' to add another issue`}</div>
</div>

View File

@ -1,18 +1,15 @@
import { Fragment, useState } from "react";
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { usePopper } from "react-popper";
// ui
import { Placement } from "@popperjs/core";
import { Combobox } from "@headlessui/react";
import { StateGroupIcon, Tooltip } from "@plane/ui";
import { Check, ChevronDown, Search } from "lucide-react";
// hooks
import { useApplication, useProjectState } from "hooks/store";
// ui
import { StateGroupIcon, Tooltip } from "@plane/ui";
// types
import { IState } from "types";
import { Placement } from "@popperjs/core";
import { RootStore } from "store_legacy/root";
export interface IIssuePropertyState {
projectId: string | null;
@ -40,25 +37,24 @@ export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props
optionsClassName = "",
placement,
} = props;
const { workspace: workspaceStore, projectState: projectStateStore }: RootStore = 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);
// store hooks
const {
router: { workspaceSlug },
} = useApplication();
const { projectStates: storeStates, fetchProjectStates } = useProjectState();
let projectStates: IState[] = defaultOptions;
const storeStates = projectId ? projectStateStore.states[projectId] : [];
if (storeStates && storeStates.length > 0) projectStates = storeStates;
const fetchProjectStates = () => {
const handleFetchProjectStates = () => {
setIsLoading(true);
if (workspaceSlug && projectId)
workspaceSlug &&
projectId &&
projectStateStore.fetchProjectStates(workspaceSlug, projectId).then(() => setIsLoading(false));
workspaceSlug && projectId && fetchProjectStates(workspaceSlug, projectId).then(() => setIsLoading(false));
};
const selectedOption: IState | undefined =
@ -121,7 +117,7 @@ export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props
className={`flex h-5 w-full items-center justify-between gap-1 rounded border-[0.5px] border-custom-border-300 px-2.5 py-1 text-xs ${
disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
onClick={() => !storeStates && fetchProjectStates()}
onClick={() => !storeStates && handleFetchProjectStates()}
>
{label}
{!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />}

View File

@ -2,9 +2,8 @@ import { useState } from "react";
import { useRouter } from "next/router";
import { CustomMenu } from "@plane/ui";
import { Copy, Link, Pencil, Trash2 } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
@ -19,18 +18,17 @@ import { EUserWorkspaceRoles } from "constants/workspace";
export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = (props) => {
const { issue, handleDelete, handleUpdate, customActionButton } = props;
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// states
const [createUpdateIssueModal, setCreateUpdateIssueModal] = useState(false);
const [issueToEdit, setIssueToEdit] = useState<IIssue | null>(null);
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
const { user: userStore } = useMobxStore();
const { currentProjectRole } = userStore;
// store hooks
const {
membership: { currentProjectRole },
} = useUser();
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;

View File

@ -1,5 +1,6 @@
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useProject } from "hooks/store";
// components
import { SpreadsheetColumn } from "components/issues";
// types
@ -31,10 +32,8 @@ export const SpreadsheetColumnsList: React.FC<Props> = observer((props) => {
labels,
states,
} = props;
const {
project: { currentProjectDetails },
} = useMobxStore();
// store hooks
const { currentProjectDetails } = useProject();
const isEstimateEnabled: boolean = currentProjectDetails?.estimate !== null;

View File

@ -1,18 +1,16 @@
import { useEffect, useState, useRef } from "react";
import { useRouter } from "next/router";
import { useForm } from "react-hook-form";
import { observer } from "mobx-react-lite";
import { PlusIcon } from "lucide-react";
// hooks
import { useProject, useWorkspace } from "hooks/store";
import useToast from "hooks/use-toast";
import useKeypress from "hooks/use-keypress";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
// store
import { useMobxStore } from "lib/mobx/store-provider";
// helpers
import { createIssuePayload } from "helpers/issue.helper";
// types
import { IIssue, IProject } from "types";
import { IIssue } from "types";
type Props = {
formKey: keyof IIssue;
@ -57,14 +55,10 @@ const Inputs = (props: any) => {
export const SpreadsheetQuickAddIssueForm: React.FC<Props> = observer((props) => {
const { formKey, prePopulatedData, quickAddCallback, viewId } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
// store
const { workspace: workspaceStore, project: projectStore } = useMobxStore();
// store hooks
const { currentWorkspace } = useWorkspace();
const { currentProjectDetails } = useProject();
// form info
const {
reset,
handleSubmit,
@ -86,11 +80,6 @@ export const SpreadsheetQuickAddIssueForm: React.FC<Props> = observer((props) =>
useOutsideClickDetector(ref, handleClose);
const { setToastAlert } = useToast();
// derived values
const workspaceDetail = (workspaceSlug && workspaceStore.getWorkspaceBySlug(workspaceSlug)) || null;
const projectDetail: IProject | null =
(workspaceSlug && projectId && projectStore.getProjectById(workspaceSlug, projectId)) || null;
useEffect(() => {
setFocus("name");
}, [setFocus, isOpen]);
@ -155,17 +144,18 @@ export const SpreadsheetQuickAddIssueForm: React.FC<Props> = observer((props) =>
// };
const onSubmitHandler = async (formData: IIssue) => {
if (isSubmitting || !workspaceDetail || !projectDetail) return;
if (isSubmitting || !currentWorkspace || !currentProjectDetails) return;
reset({ ...defaultValues });
const payload = createIssuePayload(workspaceDetail, projectDetail, {
const payload = createIssuePayload(currentWorkspace, currentProjectDetails, {
...(prePopulatedData ?? {}),
...formData,
});
try {
quickAddCallback && (await quickAddCallback(workspaceSlug, projectId, { ...payload } as IIssue, viewId));
quickAddCallback &&
(await quickAddCallback(currentWorkspace.slug, currentProjectDetails.id, { ...payload } as IIssue, viewId));
setToastAlert({
type: "success",
title: "Success!",
@ -190,7 +180,12 @@ export const SpreadsheetQuickAddIssueForm: React.FC<Props> = observer((props) =>
onSubmit={handleSubmit(onSubmitHandler)}
className="z-10 flex items-center gap-x-5 border-[0.5px] border-t-0 border-custom-border-100 bg-custom-background-100 px-4 shadow-custom-shadow-sm"
>
<Inputs formKey={formKey} register={register} setFocus={setFocus} projectDetails={projectDetail} />
<Inputs
formKey={formKey}
register={register}
setFocus={setFocus}
projectDetails={currentProjectDetails ?? null}
/>
</form>
</div>
)}

View File

@ -1,11 +1,11 @@
import { observer } from "mobx-react-lite";
// hooks
import { useUser } from "hooks/store";
import useIssueReaction from "hooks/use-issue-reaction";
// components
import { ReactionSelector } from "components/core";
// string helpers
import { renderEmoji } from "helpers/emoji.helper";
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// types
type Props = {
@ -17,9 +17,7 @@ type Props = {
export const IssueReaction: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId, issueId } = props;
const {
user: { currentUser },
} = useMobxStore();
const { currentUser } = useUser();
const { reactions, groupedReactions, handleReactionCreate, handleReactionDelete } = useIssueReaction(
workspaceSlug,

View File

@ -3,9 +3,8 @@ import { Controller, useForm } from "react-hook-form";
import debounce from "lodash/debounce";
// packages
import { RichTextEditor } from "@plane/rich-text-editor";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useUser } from "hooks/store";
import useReloadConfirmations from "hooks/use-reload-confirmation";
import useEditorSuggestions from "hooks/use-editor-suggestions";
// components
@ -46,8 +45,9 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
setIsSubmitting,
} = props;
// store
const { user: userStore } = useMobxStore();
const { currentProjectRole } = userStore;
const {
membership: { currentProjectRole },
} = useUser();
const isAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
// states
const [characterLimit, setCharacterLimit] = useState(false);

View File

@ -1,14 +1,12 @@
import React, { useState } from "react";
import { observer } from "mobx-react-lite";
import { usePopper } from "react-popper";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// ui
import { Combobox } from "@headlessui/react";
import { Check, Search } from "lucide-react";
// hooks
import { useModule } from "hooks/store";
// icons
import { DiceIcon } from "@plane/ui";
// icons
import { Check, Search } from "lucide-react";
export interface IssueModuleSelectProps {
workspaceSlug: string;
@ -19,37 +17,39 @@ export interface IssueModuleSelectProps {
export const IssueModuleSelect: React.FC<IssueModuleSelectProps> = observer((props) => {
const { workspaceSlug, projectId, value, onChange } = props;
// states
const [query, setQuery] = useState("");
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
// store hooks
const { projectModules, getModuleById, fetchModules } = useModule();
// popper-js
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: "bottom-start",
});
const { module: moduleStore } = useMobxStore();
const fetchModules = () => {
if (workspaceSlug && projectId) moduleStore.fetchModules(workspaceSlug, projectId);
const handleFetchModules = () => {
if (workspaceSlug && projectId) fetchModules(workspaceSlug, projectId);
};
const modules = projectId ? moduleStore.modules[projectId] : undefined;
const selectedModule = value ? getModuleById(value) : null;
const selectedModule = modules ? modules?.find((i) => i.id === value) : undefined;
const options = projectModules?.map((moduleId) => {
const moduleDetails = getModuleById(moduleId);
const options = modules?.map((module) => ({
value: module.id,
query: module.name,
content: (
<div className="flex items-center gap-1.5 truncate">
<span className="flex h-3.5 w-3.5 flex-shrink-0 items-center justify-center">
<DiceIcon />
</span>
<span className="flex-grow truncate">{module.name}</span>
</div>
),
}));
return {
value: `${moduleDetails?.id}`,
query: `${moduleDetails?.name}`,
content: (
<div className="flex items-center gap-1.5 truncate">
<span className="flex h-3.5 w-3.5 flex-shrink-0 items-center justify-center">
<DiceIcon />
</span>
<span className="flex-grow truncate">{moduleDetails?.name}</span>
</div>
),
};
});
const filteredOptions =
query === "" ? options : options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase()));
@ -73,7 +73,7 @@ export const IssueModuleSelect: React.FC<IssueModuleSelectProps> = observer((pro
ref={setReferenceElement}
type="button"
className="flex w-full cursor-pointer items-center justify-between gap-1 rounded border-[0.5px] border-custom-border-300 px-2 py-1 text-xs text-custom-text-200 hover:bg-custom-background-80"
onClick={fetchModules}
onClick={handleFetchModules}
>
{label}
</button>

View File

@ -1,16 +1,13 @@
import React, { useState } from "react";
import { observer } from "mobx-react-lite";
import type { FieldError } from "react-hook-form";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// popper js
import { usePopper } from "react-popper";
// ui
import { Combobox } from "@headlessui/react";
import { Check, Clipboard, Search } from "lucide-react";
// hooks
import { useProject } from "hooks/store";
// helpers
import { renderEmoji } from "helpers/emoji.helper";
// icons
import { Check, Clipboard, Search } from "lucide-react";
export interface IssueProjectSelectProps {
value: string;
@ -28,25 +25,31 @@ export const IssueProjectSelect: React.FC<IssueProjectSelectProps> = observer((p
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: "bottom-start",
});
// store hooks
const { joinedProjects, getProjectById } = useProject();
const {
project: { joinedProjects },
} = useMobxStore();
const selectedProject = getProjectById(value);
const selectedProject = joinedProjects?.find((i) => i.id === value);
const options = joinedProjects?.map((projectId) => {
const projectDetails = getProjectById(projectId);
const options = joinedProjects?.map((project) => ({
value: project.id,
query: project.name,
content: (
<div className="flex items-center gap-1.5 truncate">
<span className="grid flex-shrink-0 place-items-center">
{project.emoji ? renderEmoji(project.emoji) : project.icon_prop ? renderEmoji(project.icon_prop) : null}
</span>
<span className="flex-grow truncate">{project.name}</span>
</div>
),
}));
return {
value: `${projectDetails?.id}`,
query: `${projectDetails?.name}`,
content: (
<div className="flex items-center gap-1.5 truncate">
<span className="grid flex-shrink-0 place-items-center">
{projectDetails?.emoji
? renderEmoji(projectDetails?.emoji)
: projectDetails?.icon_prop
? renderEmoji(projectDetails?.icon_prop)
: null}
</span>
<span className="flex-grow truncate">{projectDetails?.name}</span>
</div>
),
};
});
const filteredOptions =
query === "" ? options : options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase()));

View File

@ -2,12 +2,11 @@ import React from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { observer } from "mobx-react-lite";
// store
import { useMobxStore } from "lib/mobx/store-provider";
import { Plus } from "lucide-react";
// hooks
import { useProjectState } from "hooks/store";
// ui
import { CustomSearchSelect, DoubleCircleIcon, StateGroupIcon } from "@plane/ui";
// icons
import { Plus } from "lucide-react";
type Props = {
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
@ -18,23 +17,18 @@ type Props = {
export const IssueStateSelect: React.FC<Props> = observer((props) => {
const { setIsOpen, value, onChange, projectId } = props;
// states
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const {
projectState: { states: projectStates, fetchProjectStates },
} = useMobxStore();
// store hooks
const { projectStates, fetchProjectStates } = useProjectState();
useSWR(
workspaceSlug && projectId ? `STATES_LIST_${projectId.toUpperCase()}` : null,
workspaceSlug && projectId ? () => fetchProjectStates(workspaceSlug.toString(), projectId) : null
);
const states = projectStates?.[projectId] || [];
const options = states?.map((state) => ({
const options = projectStates?.map((state) => ({
value: state.id,
query: state.name,
content: (
@ -45,8 +39,8 @@ export const IssueStateSelect: React.FC<Props> = observer((props) => {
),
}));
const selectedOption = states?.find((s) => s.id === value);
const currentDefaultState = states?.find((s) => s.default);
const selectedOption = projectStates?.find((s) => s.id === value);
const currentDefaultState = projectStates?.find((s) => s.default);
return (
<CustomSearchSelect

View File

@ -1,17 +1,16 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
// react-hook-form
import { UseFormWatch } from "react-hook-form";
import { observer } from "mobx-react-lite";
import { X } from "lucide-react";
// hooks
import { useUser } from "hooks/store";
import useToast from "hooks/use-toast";
import useUser from "hooks/use-user";
// services
import { IssueService } from "services/issue";
// components
import { ExistingIssuesListModal } from "components/core";
// icons
import { X } from "lucide-react";
import { BlockedIcon } from "@plane/ui";
// types
import { BlockeIssueDetail, IIssue, ISearchIssueResponse } from "types";
@ -26,14 +25,17 @@ type Props = {
// services
const issueService = new IssueService();
export const SidebarBlockedSelect: React.FC<Props> = ({ issueId, submitChanges, watch, disabled = false }) => {
export const SidebarBlockedSelect: React.FC<Props> = observer((props) => {
const { issueId, submitChanges, watch, disabled = false } = props;
// states
const [isBlockedModalOpen, setIsBlockedModalOpen] = useState(false);
const { user } = useUser();
const { setToastAlert } = useToast();
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// toast alert
const { setToastAlert } = useToast();
// store hooks
const { currentUser } = useUser();
const handleClose = () => {
setIsBlockedModalOpen(false);
@ -65,7 +67,7 @@ export const SidebarBlockedSelect: React.FC<Props> = ({ issueId, submitChanges,
},
}));
if (!user) return;
if (!currentUser) return;
issueService
.createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, {
@ -128,7 +130,7 @@ export const SidebarBlockedSelect: React.FC<Props> = ({ issueId, submitChanges,
related_issues: updatedRelations,
});
if (!user) return;
if (!currentUser) return;
issueService.deleteIssueRelation(
workspaceSlug as string,
@ -158,4 +160,4 @@ export const SidebarBlockedSelect: React.FC<Props> = ({ issueId, submitChanges,
</div>
</>
);
};
});

View File

@ -1,18 +1,16 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
// react-hook-form
import { UseFormWatch } from "react-hook-form";
import { observer } from "mobx-react-lite";
import { X } from "lucide-react";
// hooks
import { useUser } from "hooks/store";
import useToast from "hooks/use-toast";
import useUser from "hooks/use-user";
// components
import { ExistingIssuesListModal } from "components/core";
// services
import { IssueService } from "services/issue";
// icons
import { X } from "lucide-react";
import { BlockerIcon } from "@plane/ui";
// types
import { BlockeIssueDetail, IIssue, ISearchIssueResponse } from "types";
@ -27,14 +25,17 @@ type Props = {
// services
const issueService = new IssueService();
export const SidebarBlockerSelect: React.FC<Props> = ({ issueId, submitChanges, watch, disabled = false }) => {
export const SidebarBlockerSelect: React.FC<Props> = observer((props) => {
const { issueId, submitChanges, watch, disabled = false } = props;
// states
const [isBlockerModalOpen, setIsBlockerModalOpen] = useState(false);
const { user } = useUser();
const { setToastAlert } = useToast();
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// toast alert
const { setToastAlert } = useToast();
// store hooks
const { currentUser } = useUser();
const handleClose = () => {
setIsBlockerModalOpen(false);
@ -66,7 +67,7 @@ export const SidebarBlockerSelect: React.FC<Props> = ({ issueId, submitChanges,
},
}));
if (!user) return;
if (!currentUser) return;
issueService
.createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, {
@ -138,7 +139,7 @@ export const SidebarBlockerSelect: React.FC<Props> = ({ issueId, submitChanges,
issue_relations: updatedBlockers,
});
if (!user) return;
if (!currentUser) return;
issueService.deleteIssueRelation(
workspaceSlug as string,
@ -168,4 +169,4 @@ export const SidebarBlockerSelect: React.FC<Props> = ({ issueId, submitChanges,
</div>
</>
);
};
});

View File

@ -1,13 +1,11 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
// react-hook-form
import { UseFormWatch } from "react-hook-form";
// hooks
import useToast from "hooks/use-toast";
import useUser from "hooks/use-user";
// icons
import { observer } from "mobx-react-lite";
import { X, CopyPlus } from "lucide-react";
// hooks
import { useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { ExistingIssuesListModal } from "components/core";
// services
@ -25,16 +23,17 @@ type Props = {
// services
const issueService = new IssueService();
export const SidebarDuplicateSelect: React.FC<Props> = (props) => {
export const SidebarDuplicateSelect: React.FC<Props> = observer((props) => {
const { issueId, submitChanges, watch, disabled = false } = props;
// states
const [isDuplicateModalOpen, setIsDuplicateModalOpen] = useState(false);
const { user } = useUser();
const { setToastAlert } = useToast();
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// toast alert
const { setToastAlert } = useToast();
// store hooks
const { currentUser } = useUser();
const handleClose = () => {
setIsDuplicateModalOpen(false);
@ -64,7 +63,7 @@ export const SidebarDuplicateSelect: React.FC<Props> = (props) => {
},
}));
if (!user) return;
if (!currentUser) return;
issueService
.createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, {
@ -130,7 +129,7 @@ export const SidebarDuplicateSelect: React.FC<Props> = (props) => {
type="button"
className="opacity-0 duration-300 group-hover:opacity-100"
onClick={() => {
if (!user) return;
if (!currentUser) return;
issueService
.deleteIssueRelation(
@ -164,4 +163,4 @@ export const SidebarDuplicateSelect: React.FC<Props> = (props) => {
</div>
</>
);
};
});

View File

@ -1,13 +1,12 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
// react-hook-form
import { UseFormWatch } from "react-hook-form";
// hooks
import useToast from "hooks/use-toast";
import useUser from "hooks/use-user";
// icons
import { observer } from "mobx-react-lite";
import { X } from "lucide-react";
// hooks
import { useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// icons
import { RelatedIcon } from "@plane/ui";
// components
import { ExistingIssuesListModal } from "components/core";
@ -26,16 +25,17 @@ type Props = {
// services
const issueService = new IssueService();
export const SidebarRelatesSelect: React.FC<Props> = (props) => {
export const SidebarRelatesSelect: React.FC<Props> = observer((props) => {
const { issueId, submitChanges, watch, disabled = false } = props;
// states
const [isRelatesToModalOpen, setIsRelatesToModalOpen] = useState(false);
const { user } = useUser();
const { setToastAlert } = useToast();
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// toast alert
const { setToastAlert } = useToast();
// store hooks
const { currentUser } = useUser();
const handleClose = () => {
setIsRelatesToModalOpen(false);
@ -65,7 +65,7 @@ export const SidebarRelatesSelect: React.FC<Props> = (props) => {
},
}));
if (!user) return;
if (!currentUser) return;
issueService
.createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, {
@ -131,7 +131,7 @@ export const SidebarRelatesSelect: React.FC<Props> = (props) => {
type="button"
className="opacity-0 duration-300 group-hover:opacity-100"
onClick={() => {
if (!user) return;
if (!currentUser) return;
issueService
.deleteIssueRelation(
@ -165,4 +165,4 @@ export const SidebarRelatesSelect: React.FC<Props> = (props) => {
</div>
</>
);
};
});

View File

@ -1,7 +1,9 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import { Dialog, Transition } from "@headlessui/react";
import { observer } from "mobx-react-lite";
// hooks
import { useApplication, useModule } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { Button } from "@plane/ui";
@ -9,8 +11,6 @@ import { Button } from "@plane/ui";
import { AlertTriangle } from "lucide-react";
// types
import type { IModule } from "types";
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
type Props = {
data: IModule;
@ -20,17 +20,17 @@ type Props = {
export const DeleteModuleModal: React.FC<Props> = observer((props) => {
const { data, isOpen, onClose } = props;
// states
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
// router
const router = useRouter();
const { workspaceSlug, projectId, moduleId, peekModule } = router.query;
// store hooks
const {
module: moduleStore,
trackEvent: { postHogEventTracker },
} = useMobxStore();
eventTracker: { postHogEventTracker },
} = useApplication();
const { deleteModule } = useModule();
// toast alert
const { setToastAlert } = useToast();
const handleClose = () => {
@ -43,8 +43,7 @@ export const DeleteModuleModal: React.FC<Props> = observer((props) => {
setIsDeleteLoading(true);
await moduleStore
.deleteModule(workspaceSlug.toString(), projectId.toString(), data.id)
await deleteModule(workspaceSlug.toString(), projectId.toString(), data.id)
.then(() => {
if (moduleId || peekModule) router.push(`/${workspaceSlug}/projects/${data.project}/modules`);
handleClose();

View File

@ -2,12 +2,11 @@ import React, { useEffect, useState } from "react";
import { observer } from "mobx-react-lite";
import { useForm } from "react-hook-form";
import { Dialog, Transition } from "@headlessui/react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication, useModule, useProject } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { ModuleForm } from "components/modules";
// hooks
import useToast from "hooks/use-toast";
// types
import type { IModule } from "types";
@ -29,17 +28,15 @@ const defaultValues: Partial<IModule> = {
export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
const { isOpen, onClose, data, workspaceSlug, projectId } = props;
// states
const [activeProject, setActiveProject] = useState<string | null>(null);
// store hooks
const {
project: projectStore,
module: moduleStore,
trackEvent: { postHogEventTracker },
} = useMobxStore();
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
eventTracker: { postHogEventTracker },
} = useApplication();
const { workspaceProjects } = useProject();
const { createModule, updateModuleDetails } = useModule();
// toast alert
const { setToastAlert } = useToast();
const handleClose = () => {
@ -51,11 +48,11 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
defaultValues,
});
const createModule = async (payload: Partial<IModule>) => {
const handleCreateModule = async (payload: Partial<IModule>) => {
if (!workspaceSlug || !projectId) return;
const selectedProjectId = payload.project ?? projectId.toString();
await moduleStore
.createModule(workspaceSlug.toString(), selectedProjectId, payload)
await createModule(workspaceSlug.toString(), selectedProjectId, payload)
.then((res) => {
handleClose();
@ -81,11 +78,11 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
});
};
const updateModule = async (payload: Partial<IModule>) => {
const handleUpdateModule = async (payload: Partial<IModule>) => {
if (!workspaceSlug || !projectId || !data) return;
const selectedProjectId = payload.project ?? projectId.toString();
await moduleStore
.updateModuleDetails(workspaceSlug.toString(), selectedProjectId, data.id, payload)
await updateModuleDetails(workspaceSlug.toString(), selectedProjectId, data.id, payload)
.then((res) => {
handleClose();
@ -117,8 +114,8 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
const payload: Partial<IModule> = {
...formData,
};
if (!data) await createModule(payload);
else await updateModule(payload);
if (!data) await handleCreateModule(payload);
else await handleUpdateModule(payload);
};
useEffect(() => {
@ -138,9 +135,9 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
// if data is not present, set active project to the project
// in the url. This has the least priority.
if (projects && projects.length > 0 && !activeProject)
setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null);
}, [activeProject, data, projectId, projects, isOpen]);
if (workspaceProjects && workspaceProjects.length > 0 && !activeProject)
setActiveProject(projectId ?? workspaceProjects?.[0] ?? null);
}, [activeProject, data, projectId, workspaceProjects, isOpen]);
return (
<Transition.Root show={isOpen} as={React.Fragment}>

View File

@ -2,78 +2,50 @@ import React, { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import { Info, LinkIcon, Pencil, Star, Trash2 } from "lucide-react";
// hooks
import { useModule, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { CreateUpdateModuleModal, DeleteModuleModal } from "components/modules";
// ui
import { Avatar, AvatarGroup, CustomMenu, LayersIcon, Tooltip } from "@plane/ui";
// icons
import { Info, LinkIcon, Pencil, Star, Trash2 } from "lucide-react";
// helpers
import { copyUrlToClipboard } from "helpers/string.helper";
import { renderShortDate, renderShortMonthDate } from "helpers/date-time.helper";
// types
import { IModule } from "types";
// constants
import { MODULE_STATUS } from "constants/module";
import { EUserWorkspaceRoles } from "constants/workspace";
type Props = {
module: IModule;
moduleId: string;
};
export const ModuleCardItem: React.FC<Props> = observer((props) => {
const { module } = props;
const { moduleId } = props;
// states
const [editModal, setEditModal] = useState(false);
const [deleteModal, setDeleteModal] = useState(false);
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// toast alert
const { setToastAlert } = useToast();
const { module: moduleStore, user: userStore } = useMobxStore();
const { currentProjectRole } = userStore;
// store hooks
const {
membership: { currentProjectRole },
} = useUser();
const { getModuleById, addModuleToFavorites, removeModuleFromFavorites } = useModule();
// derived values
const moduleDetails = getModuleById(moduleId);
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
const moduleTotalIssues =
module.backlog_issues +
module.unstarted_issues +
module.started_issues +
module.completed_issues +
module.cancelled_issues;
const completionPercentage = (module.completed_issues / moduleTotalIssues) * 100;
const endDate = new Date(module.target_date ?? "");
const startDate = new Date(module.start_date ?? "");
const isDateValid = module.target_date || module.start_date;
const areYearsEqual = startDate.getFullYear() === endDate.getFullYear();
const moduleStatus = MODULE_STATUS.find((status) => status.value === module.status);
const issueCount = module
? !moduleTotalIssues || moduleTotalIssues === 0
? "0 Issue"
: moduleTotalIssues === module.completed_issues
? `${moduleTotalIssues} Issue${moduleTotalIssues > 1 ? "s" : ""}`
: `${module.completed_issues}/${moduleTotalIssues} Issues`
: "0 Issue";
const handleAddToFavorites = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
e.preventDefault();
if (!workspaceSlug || !projectId) return;
moduleStore.addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), module.id).catch(() => {
addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), moduleId).catch(() => {
setToastAlert({
type: "error",
title: "Error!",
@ -87,7 +59,7 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
e.preventDefault();
if (!workspaceSlug || !projectId) return;
moduleStore.removeModuleFromFavorites(workspaceSlug.toString(), projectId.toString(), module.id).catch(() => {
removeModuleFromFavorites(workspaceSlug.toString(), projectId.toString(), moduleId).catch(() => {
setToastAlert({
type: "error",
title: "Error!",
@ -99,7 +71,7 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
const handleCopyText = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
e.preventDefault();
copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/modules/${module.id}`).then(() => {
copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/modules/${moduleId}`).then(() => {
setToastAlert({
type: "success",
title: "Link Copied!",
@ -127,28 +99,56 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
router.push({
pathname: router.pathname,
query: { ...query, peekModule: module.id },
query: { ...query, peekModule: moduleId },
});
};
if (!moduleDetails) return null;
const moduleTotalIssues =
moduleDetails.backlog_issues +
moduleDetails.unstarted_issues +
moduleDetails.started_issues +
moduleDetails.completed_issues +
moduleDetails.cancelled_issues;
const completionPercentage = (moduleDetails.completed_issues / moduleTotalIssues) * 100;
const endDate = new Date(moduleDetails.target_date ?? "");
const startDate = new Date(moduleDetails.start_date ?? "");
const isDateValid = moduleDetails.target_date || moduleDetails.start_date;
const areYearsEqual = startDate.getFullYear() === endDate.getFullYear();
const moduleStatus = MODULE_STATUS.find((status) => status.value === moduleDetails.status);
const issueCount = module
? !moduleTotalIssues || moduleTotalIssues === 0
? "0 Issue"
: moduleTotalIssues === moduleDetails.completed_issues
? `${moduleTotalIssues} Issue${moduleTotalIssues > 1 ? "s" : ""}`
: `${moduleDetails.completed_issues}/${moduleTotalIssues} Issues`
: "0 Issue";
return (
<>
{workspaceSlug && projectId && (
<CreateUpdateModuleModal
isOpen={editModal}
onClose={() => setEditModal(false)}
data={module}
data={moduleDetails}
projectId={projectId.toString()}
workspaceSlug={workspaceSlug.toString()}
/>
)}
<DeleteModuleModal data={module} isOpen={deleteModal} onClose={() => setDeleteModal(false)} />
<Link href={`/${workspaceSlug}/projects/${module.project}/modules/${module.id}`}>
<DeleteModuleModal data={moduleDetails} isOpen={deleteModal} onClose={() => setDeleteModal(false)} />
<Link href={`/${workspaceSlug}/projects/${moduleDetails.project}/modules/${moduleDetails.id}`}>
<div className="flex h-44 w-full min-w-[250px] flex-col justify-between rounded border border-custom-border-100 bg-custom-background-100 p-4 text-sm hover:shadow-md">
<div>
<div className="flex items-center justify-between gap-2">
<Tooltip tooltipContent={module.name} position="top">
<span className="truncate text-base font-medium">{module.name}</span>
<Tooltip tooltipContent={moduleDetails.name} position="top">
<span className="truncate text-base font-medium">{moduleDetails.name}</span>
</Tooltip>
<div className="flex items-center gap-2">
{moduleStatus && (
@ -175,11 +175,11 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
<LayersIcon className="h-4 w-4 text-custom-text-300" />
<span className="text-xs text-custom-text-300">{issueCount ?? "0 Issue"}</span>
</div>
{module.members_detail.length > 0 && (
<Tooltip tooltipContent={`${module.members_detail.length} Members`}>
{moduleDetails.members_detail.length > 0 && (
<Tooltip tooltipContent={`${moduleDetails.members_detail.length} Members`}>
<div className="flex cursor-default items-center gap-1">
<AvatarGroup showTooltip={false}>
{module.members_detail.map((member) => (
{moduleDetails.members_detail.map((member) => (
<Avatar key={member.id} name={member.display_name} src={member.avatar} />
))}
</AvatarGroup>
@ -223,7 +223,7 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
<div className="z-10 flex items-center gap-1.5">
{isEditingAllowed &&
(module.is_favorite ? (
(moduleDetails.is_favorite ? (
<button type="button" onClick={handleRemoveFromFavorites}>
<Star className="h-3.5 w-3.5 fill-current text-amber-500" />
</button>

View File

@ -2,67 +2,50 @@ import React, { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import { Check, Info, LinkIcon, Pencil, Star, Trash2, User2 } from "lucide-react";
// hooks
import { useModule, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { CreateUpdateModuleModal, DeleteModuleModal } from "components/modules";
// ui
import { Avatar, AvatarGroup, CircularProgressIndicator, CustomMenu, Tooltip } from "@plane/ui";
// icons
import { Check, Info, LinkIcon, Pencil, Star, Trash2, User2 } from "lucide-react";
// helpers
import { copyUrlToClipboard } from "helpers/string.helper";
import { renderShortDate, renderShortMonthDate } from "helpers/date-time.helper";
// types
import { IModule } from "types";
// constants
import { MODULE_STATUS } from "constants/module";
import { EUserWorkspaceRoles } from "constants/workspace";
type Props = {
module: IModule;
moduleId: string;
};
export const ModuleListItem: React.FC<Props> = observer((props) => {
const { module } = props;
const { moduleId } = props;
// states
const [editModal, setEditModal] = useState(false);
const [deleteModal, setDeleteModal] = useState(false);
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// toast alert
const { setToastAlert } = useToast();
const { module: moduleStore, user: userStore } = useMobxStore();
const { currentProjectRole } = userStore;
// store hooks
const {
membership: { currentProjectRole },
} = useUser();
const { getModuleById, addModuleToFavorites, removeModuleFromFavorites } = useModule();
// derived values
const moduleDetails = getModuleById(moduleId);
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
const completionPercentage = ((module.completed_issues + module.cancelled_issues) / module.total_issues) * 100;
const endDate = new Date(module.target_date ?? "");
const startDate = new Date(module.start_date ?? "");
const renderDate = module.start_date || module.target_date;
const areYearsEqual = startDate.getFullYear() === endDate.getFullYear();
const moduleStatus = MODULE_STATUS.find((status) => status.value === module.status);
const progress = isNaN(completionPercentage) ? 0 : Math.floor(completionPercentage);
const completedModuleCheck = module.status === "completed";
const handleAddToFavorites = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
e.preventDefault();
if (!workspaceSlug || !projectId) return;
moduleStore.addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), module.id).catch(() => {
addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), moduleId).catch(() => {
setToastAlert({
type: "error",
title: "Error!",
@ -76,7 +59,7 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
e.preventDefault();
if (!workspaceSlug || !projectId) return;
moduleStore.removeModuleFromFavorites(workspaceSlug.toString(), projectId.toString(), module.id).catch(() => {
removeModuleFromFavorites(workspaceSlug.toString(), projectId.toString(), moduleId).catch(() => {
setToastAlert({
type: "error",
title: "Error!",
@ -88,7 +71,7 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
const handleCopyText = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
e.preventDefault();
copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/modules/${module.id}`).then(() => {
copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/modules/${moduleId}`).then(() => {
setToastAlert({
type: "success",
title: "Link Copied!",
@ -116,23 +99,41 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
router.push({
pathname: router.pathname,
query: { ...query, peekModule: module.id },
query: { ...query, peekModule: moduleId },
});
};
if (!moduleDetails) return null;
const completionPercentage =
((moduleDetails.completed_issues + moduleDetails.cancelled_issues) / moduleDetails.total_issues) * 100;
const endDate = new Date(moduleDetails.target_date ?? "");
const startDate = new Date(moduleDetails.start_date ?? "");
const renderDate = moduleDetails.start_date || moduleDetails.target_date;
const areYearsEqual = startDate.getFullYear() === endDate.getFullYear();
const moduleStatus = MODULE_STATUS.find((status) => status.value === moduleDetails.status);
const progress = isNaN(completionPercentage) ? 0 : Math.floor(completionPercentage);
const completedModuleCheck = moduleDetails.status === "completed";
return (
<>
{workspaceSlug && projectId && (
<CreateUpdateModuleModal
isOpen={editModal}
onClose={() => setEditModal(false)}
data={module}
data={moduleDetails}
projectId={projectId.toString()}
workspaceSlug={workspaceSlug.toString()}
/>
)}
<DeleteModuleModal data={module} isOpen={deleteModal} onClose={() => setDeleteModal(false)} />
<Link href={`/${workspaceSlug}/projects/${module.project}/modules/${module.id}`}>
<DeleteModuleModal data={moduleDetails} isOpen={deleteModal} onClose={() => setDeleteModal(false)} />
<Link href={`/${workspaceSlug}/projects/${moduleDetails.project}/modules/${moduleDetails.id}`}>
<div className="group flex h-16 w-full items-center justify-between gap-5 border-b border-custom-border-100 bg-custom-background-100 px-5 py-6 text-sm hover:bg-custom-background-90">
<div className="flex w-full items-center gap-3 truncate">
<div className="flex items-center gap-4 truncate">
@ -151,8 +152,8 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
)}
</CircularProgressIndicator>
</span>
<Tooltip tooltipContent={module.name} position="top">
<span className="truncate text-base font-medium">{module.name}</span>
<Tooltip tooltipContent={moduleDetails.name} position="top">
<span className="truncate text-base font-medium">{moduleDetails.name}</span>
</Tooltip>
</div>
<button onClick={openModuleOverview} className="z-10 hidden flex-shrink-0 group-hover:flex">
@ -183,11 +184,11 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
</span>
)}
<Tooltip tooltipContent={`${module.members_detail.length} Members`}>
<Tooltip tooltipContent={`${moduleDetails.members_detail.length} Members`}>
<div className="flex w-16 cursor-default items-center justify-center gap-1">
{module.members_detail.length > 0 ? (
{moduleDetails.members_detail.length > 0 ? (
<AvatarGroup showTooltip={false}>
{module.members_detail.map((member) => (
{moduleDetails.members_detail.map((member) => (
<Avatar key={member.id} name={member.display_name} src={member.avatar} />
))}
</AvatarGroup>
@ -200,7 +201,7 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
</Tooltip>
{isEditingAllowed &&
(module.is_favorite ? (
(moduleDetails.is_favorite ? (
<button type="button" onClick={handleRemoveFromFavorites} className="z-[1]">
<Star className="h-3.5 w-3.5 fill-current text-amber-500" />
</button>

View File

@ -1,10 +1,8 @@
import React, { useEffect } from "react";
import { useRouter } from "next/router";
// mobx
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useModule } from "hooks/store";
// components
import { ModuleDetailsSidebar } from "./sidebar";
@ -14,13 +12,13 @@ type Props = {
};
export const ModulePeekOverview: React.FC<Props> = observer(({ projectId, workspaceSlug }) => {
// router
const router = useRouter();
const { peekModule } = router.query;
// refs
const ref = React.useRef(null);
const { module: moduleStore } = useMobxStore();
const { fetchModuleDetails } = moduleStore;
// store hooks
const { fetchModuleDetails } = useModule();
const handleClose = () => {
delete router.query.peekModule;

View File

@ -1,9 +1,8 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Plus } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useApplication, useModule } from "hooks/store";
import useLocalStorage from "hooks/use-local-storage";
// components
import { ModuleCardItem, ModuleListItem, ModulePeekOverview, ModulesListGanttChartView } from "components/modules";
@ -14,16 +13,16 @@ import emptyModule from "public/empty-state/empty_modules.webp";
import { NewEmptyState } from "components/common/new-empty-state";
export const ModulesListView: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId, peekModule } = router.query;
const { module: moduleStore, commandPalette: commandPaletteStore } = useMobxStore();
// store hooks
const { commandPalette: commandPaletteStore } = useApplication();
const { projectModules } = useModule();
const { storedValue: modulesView } = useLocalStorage("modules_view", "grid");
const modulesList = moduleStore.projectModules;
if (!modulesList)
if (!projectModules)
return (
<Loader className="grid grid-cols-3 gap-4 p-8">
<Loader.Item height="176px" />
@ -37,14 +36,14 @@ export const ModulesListView: React.FC = observer(() => {
return (
<>
{modulesList.length > 0 ? (
{projectModules.length > 0 ? (
<>
{modulesView === "list" && (
<div className="h-full overflow-y-auto">
<div className="flex h-full w-full justify-between">
<div className="flex h-full w-full flex-col overflow-y-auto">
{modulesList.map((module) => (
<ModuleListItem key={module.id} module={module} />
{projectModules.map((moduleId) => (
<ModuleListItem key={moduleId} moduleId={moduleId} />
))}
</div>
<ModulePeekOverview
@ -64,8 +63,8 @@ export const ModulesListView: React.FC = observer(() => {
: "lg:grid-cols-2 xl:grid-cols-3 3xl:grid-cols-4"
} auto-rows-max transition-all `}
>
{modulesList.map((module) => (
<ModuleCardItem key={module.id} module={module} />
{projectModules.map((moduleId) => (
<ModuleCardItem key={moduleId} moduleId={moduleId} />
))}
</div>
<ModulePeekOverview

View File

@ -3,8 +3,8 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Controller, useForm } from "react-hook-form";
import { Disclosure, Popover, Transition } from "@headlessui/react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useModule, useUser } from "hooks/store";
// hooks
import useToast from "hooks/use-toast";
// components
@ -46,27 +46,20 @@ type Props = {
// TODO: refactor this component
export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
const { moduleId, handleClose } = props;
// states
const [moduleDeleteModal, setModuleDeleteModal] = useState(false);
const [moduleLinkModal, setModuleLinkModal] = useState(false);
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<ILinkDetails | null>(null);
// router
const router = useRouter();
const { workspaceSlug, projectId, peekModule } = router.query;
// store hooks
const {
module: {
moduleDetails: _moduleDetails,
updateModuleDetails,
createModuleLink,
updateModuleLink,
deleteModuleLink,
},
user: userStore,
} = useMobxStore();
membership: { currentProjectRole },
} = useUser();
const { getModuleById, updateModuleDetails, createModuleLink, updateModuleLink, deleteModuleLink } = useModule();
const userRole = userStore.currentProjectRole;
const moduleDetails = _moduleDetails[moduleId] ?? undefined;
const moduleDetails = getModuleById(moduleId);
const { setToastAlert } = useToast();
@ -551,7 +544,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
<Transition show={open}>
<Disclosure.Panel>
<div className="mt-2 flex h-72 w-full flex-col space-y-3 overflow-y-auto">
{userRole && moduleDetails.link_module && moduleDetails.link_module.length > 0 ? (
{currentProjectRole && moduleDetails.link_module && moduleDetails.link_module.length > 0 ? (
<>
<div className="flex w-full items-center justify-end">
<button
@ -568,10 +561,10 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
handleEditLink={handleEditLink}
handleDeleteLink={handleDeleteLink}
userAuth={{
isGuest: userRole === EUserWorkspaceRoles.GUEST,
isViewer: userRole === EUserWorkspaceRoles.VIEWER,
isMember: userRole === EUserWorkspaceRoles.MEMBER,
isOwner: userRole === EUserWorkspaceRoles.ADMIN,
isGuest: currentProjectRole === EUserWorkspaceRoles.GUEST,
isViewer: currentProjectRole === EUserWorkspaceRoles.VIEWER,
isMember: currentProjectRole === EUserWorkspaceRoles.MEMBER,
isOwner: currentProjectRole === EUserWorkspaceRoles.ADMIN,
}}
/>
</>

View File

@ -3,6 +3,7 @@ import { Popover, Transition } from "@headlessui/react";
import { Bell } from "lucide-react";
import { observer } from "mobx-react-lite";
// hooks
import { useApplication } from "hooks/store";
import useUserNotification from "hooks/use-user-notifications";
// components
import { EmptyState } from "components/common";
@ -12,11 +13,10 @@ import { Loader, Tooltip } from "@plane/ui";
import emptyNotification from "public/empty-state/notification.svg";
// helpers
import { getNumberCount } from "helpers/string.helper";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
export const NotificationPopover = observer(() => {
const { theme: themeStore } = useMobxStore();
// store hooks
const { theme: themeStore } = useApplication();
const {
notifications,

View File

@ -1,16 +1,14 @@
import React, { useState } from "react";
import useSWR, { mutate } from "swr";
// hooks
import { useApplication, useUser, useWorkspace } from "hooks/store";
// components
import { Button } from "@plane/ui";
// helpers
import { truncateText } from "helpers/string.helper";
// mobx
import { useMobxStore } from "lib/mobx/store-provider";
// services
import { WorkspaceService } from "services/workspace.service";
// contants
// constants
import { USER_WORKSPACES, USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys";
import { ROLE } from "constants/workspace";
// types
@ -26,14 +24,17 @@ const workspaceService = new WorkspaceService();
export const Invitations: React.FC<Props> = (props) => {
const { handleNextStep, setTryDiffAccount } = props;
// states
const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false);
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
// store hooks
const {
workspace: workspaceStore,
user: { currentUser, updateCurrentUser },
trackEvent: { postHogEventTracker },
} = useMobxStore();
eventTracker: { postHogEventTracker },
} = useApplication();
const { currentUser, updateCurrentUser } = useUser();
const { workspaces, fetchWorkspaces } = useWorkspace();
const workspacesList = Object.values(workspaces);
const { data: invitations, mutate: mutateInvitations } = useSWR(USER_WORKSPACE_INVITATIONS, () =>
workspaceService.userWorkspaceInvitations()
@ -48,9 +49,9 @@ export const Invitations: React.FC<Props> = (props) => {
};
const updateLastWorkspace = async () => {
if (!workspaceStore.workspaces) return;
if (!workspacesList) return;
await updateCurrentUser({
last_workspace_id: workspaceStore.workspaces[0].id,
last_workspace_id: workspacesList[0]?.id,
});
};
@ -63,7 +64,7 @@ export const Invitations: React.FC<Props> = (props) => {
.joinWorkspaces({ invitations: invitationsRespond })
.then(async (res) => {
postHogEventTracker("MEMBER_ACCEPTED", { ...res, state: "SUCCESS", accepted_from: "App" });
await workspaceStore.fetchWorkspaces();
await fetchWorkspaces();
await mutate(USER_WORKSPACES);
await updateLastWorkspace();
await handleNextStep();

View File

@ -1,7 +1,8 @@
import React from "react";
import { Controller, useForm } from "react-hook-form";
import { observer } from "mobx-react-lite";
// hooks
import useUser from "hooks/use-user";
import { useUser } from "hooks/store";
// components
import { Invitations, OnboardingSidebar, OnboardingStepIndicator, Workspace } from "components/onboarding";
// types
@ -13,8 +14,11 @@ type Props = {
setTryDiffAccount: () => void;
};
export const JoinWorkspaces: React.FC<Props> = ({ stepChange, setTryDiffAccount }) => {
const { user } = useUser();
export const JoinWorkspaces: React.FC<Props> = observer((props) => {
const { stepChange, setTryDiffAccount } = props;
// store hooks
const { currentUser } = useUser();
// form info
const {
handleSubmit,
control,
@ -30,7 +34,7 @@ export const JoinWorkspaces: React.FC<Props> = ({ stepChange, setTryDiffAccount
});
const handleNextStep = async () => {
if (!user) return;
if (!currentUser) return;
await stepChange({ workspace_join: true, workspace_create: true });
};
@ -59,7 +63,7 @@ export const JoinWorkspaces: React.FC<Props> = ({ stepChange, setTryDiffAccount
</div>
<Workspace
stepChange={stepChange}
user={user}
user={currentUser ?? undefined}
control={control}
handleSubmit={handleSubmit}
setValue={setValue}
@ -78,4 +82,4 @@ export const JoinWorkspaces: React.FC<Props> = ({ stepChange, setTryDiffAccount
</div>
</div>
);
};
});

View File

@ -17,9 +17,8 @@ import {
Bell,
} from "lucide-react";
import { Avatar, DiceIcon, PhotoFilterIcon } from "@plane/ui";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useUser, useWorkspace } from "hooks/store";
// types
import { IWorkspace } from "types";
// assets
@ -92,11 +91,10 @@ var lastWorkspaceName: string = "";
export const OnboardingSidebar: React.FC<Props> = (props) => {
const { workspaceName, showProject, control, setValue, watch, userFullName } = props;
const {
workspace: workspaceStore,
user: { currentUser },
} = useMobxStore();
const workspace = workspaceStore.workspaces ? workspaceStore.workspaces[0] : null;
// store hooks
const { currentUser } = useUser();
const { workspaces } = useWorkspace();
const workspaceDetails = Object.values(workspaces ?? {})?.[0];
const { resolvedTheme } = useTheme();
@ -170,7 +168,7 @@ export const OnboardingSidebar: React.FC<Props> = (props) => {
<div className="flex w-full items-center gap-y-2 truncate border border-transparent px-4 pt-6 transition-all">
<div className="flex flex-shrink-0">
<Avatar
name={value.length > 0 ? value : workspace ? workspace.name : "New Workspace"}
name={value.length > 0 ? value : workspaceDetails ? workspaceDetails.name : "New Workspace"}
src={""}
size={24}
shape="square"
@ -200,7 +198,7 @@ export const OnboardingSidebar: React.FC<Props> = (props) => {
<div className="flex w-full items-center gap-y-2 truncate px-4 pt-6 transition-all">
<div className="flex flex-shrink-0">
<Avatar
name={workspace ? workspace.name : "New Workspace"}
name={workspaceDetails ? workspaceDetails.name : "New Workspace"}
src={""}
size={24}
shape="square"

View File

@ -4,9 +4,8 @@ import { mutate } from "swr";
import { useTheme } from "next-themes";
import { Dialog, Transition } from "@headlessui/react";
import { Trash2 } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useUser } from "hooks/store";
import useToast from "hooks/use-toast";
type Props = {
@ -16,16 +15,13 @@ type Props = {
export const SwitchOrDeleteAccountModal: React.FC<Props> = (props) => {
const { isOpen, onClose } = props;
// states
const [switchingAccount, setSwitchingAccount] = useState(false);
const [isDeactivating, setIsDeactivating] = useState(false);
const {
user: { deactivateAccount, signOut },
} = useMobxStore();
// router
const router = useRouter();
// store hooks
const { deactivateAccount, signOut } = useUser();
const { resolvedTheme, setTheme } = useTheme();

View File

@ -1,14 +1,13 @@
import { useState } from "react";
import Image from "next/image";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import { X } from "lucide-react";
// hooks
import { useApplication, useUser } from "hooks/store";
// components
import { TourSidebar } from "components/onboarding";
// ui
import { Button } from "@plane/ui";
// icons
import { X } from "lucide-react";
// assets
import PlaneWhiteLogo from "public/plane-logos/white-horizontal.svg";
import IssuesTour from "public/onboarding/issues.webp";
@ -78,13 +77,12 @@ export const TourRoot: React.FC<Props> = observer((props) => {
const { onComplete } = props;
// states
const [step, setStep] = useState<TTourSteps>("welcome");
// store hooks
const {
user: userStore,
commandPalette: commandPaletteStore,
trackEvent: { setTrackElement },
} = useMobxStore();
const user = userStore.currentUser;
eventTracker: { setTrackElement },
} = useApplication();
const { currentUser } = useUser();
const currentStepIndex = TOUR_STEPS.findIndex((tourStep) => tourStep.key === step);
const currentStep = TOUR_STEPS[currentStepIndex];
@ -99,7 +97,7 @@ export const TourRoot: React.FC<Props> = observer((props) => {
</div>
<div className="flex h-2/5 flex-col overflow-y-auto p-6">
<h3 className="font-semibold sm:text-xl">
Welcome to Plane, {user?.first_name} {user?.last_name}
Welcome to Plane, {currentUser?.first_name} {currentUser?.last_name}
</h3>
<p className="mt-3 text-sm text-custom-text-200">
We{"'"}re glad that you decided to try out Plane. You can now manage your projects with ease. Get

View File

@ -3,8 +3,8 @@ import Image from "next/image";
import { Controller, useForm } from "react-hook-form";
import { observer } from "mobx-react-lite";
import { Camera, User2 } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { useUser, useWorkspace } from "hooks/store";
// components
import { Button, Input } from "@plane/ui";
import { OnboardingSidebar, OnboardingStepIndicator } from "components/onboarding";
@ -42,13 +42,15 @@ const fileService = new FileService();
export const UserDetails: React.FC<Props> = observer((props) => {
const { user, setUserName } = props;
// states
const [isRemoving, setIsRemoving] = useState(false);
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
const {
user: userStore,
workspace: { workspaces },
} = useMobxStore();
const workspaceName = workspaces ? workspaces[0]?.name : "New Workspace";
// store hooks
const { updateCurrentUser } = useUser();
const { workspaces } = useWorkspace();
// derived values
const workspaceName = workspaces ? Object.values(workspaces)?.[0]?.name : "New Workspace";
// form info
const {
getValues,
handleSubmit,
@ -74,7 +76,7 @@ export const UserDetails: React.FC<Props> = observer((props) => {
},
};
await userStore.updateCurrentUser(payload);
await updateCurrentUser(payload);
};
const handleDelete = (url: string | null | undefined) => {
if (!url) return;

View File

@ -5,11 +5,10 @@ import { Button, Input } from "@plane/ui";
// types
import { IUser, IWorkspace, TOnboardingSteps } from "types";
// hooks
import { useUser, useWorkspace } from "hooks/store";
import useToast from "hooks/use-toast";
// services
import { WorkspaceService } from "services/workspace.service";
// mobx
import { useMobxStore } from "lib/mobx/store-provider";
// constants
import { RESTRICTED_URLS } from "constants/workspace";
@ -28,14 +27,13 @@ const workspaceService = new WorkspaceService();
export const Workspace: React.FC<Props> = (props) => {
const { stepChange, user, control, handleSubmit, setValue, errors, isSubmitting } = props;
// states
const [slugError, setSlugError] = useState(false);
const [invalidSlug, setInvalidSlug] = useState(false);
const {
workspace: workspaceStore,
user: { updateCurrentUser },
} = useMobxStore();
// store hooks
const { updateCurrentUser } = useUser();
const { createWorkspace, fetchWorkspaces, workspaces } = useWorkspace();
// toast alert
const { setToastAlert } = useToast();
const handleCreateWorkspace = async (formData: IWorkspace) => {
@ -47,15 +45,14 @@ export const Workspace: React.FC<Props> = (props) => {
if (res.status === true && !RESTRICTED_URLS.includes(formData.slug)) {
setSlugError(false);
await workspaceStore
.createWorkspace(formData)
await createWorkspace(formData)
.then(async () => {
setToastAlert({
type: "success",
title: "Success!",
message: "Workspace created successfully.",
});
await workspaceStore.fetchWorkspaces();
await fetchWorkspaces();
await completeStep();
})
.catch(() =>
@ -77,7 +74,9 @@ export const Workspace: React.FC<Props> = (props) => {
};
const completeStep = async () => {
if (!user || !workspaceStore.workspaces) return;
if (!user || !workspaces) return;
const firstWorkspace = Object.values(workspaces ?? {})?.[0];
const payload: Partial<TOnboardingSteps> = {
workspace_create: true,
@ -86,7 +85,7 @@ export const Workspace: React.FC<Props> = (props) => {
await stepChange(payload);
await updateCurrentUser({
last_workspace_id: workspaceStore.workspaces[0]?.id,
last_workspace_id: firstWorkspace?.id,
});
};

Some files were not shown because too many files have changed in this diff Show More