chore: implement the new label root store

This commit is contained in:
Aaryan Khandelwal 2023-12-15 14:58:40 +05:30
parent 2e74dfa12c
commit 960f170fd4
42 changed files with 768 additions and 392 deletions

View File

@ -1,9 +1,9 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// cmdk
import { Command } from "cmdk"; import { Command } from "cmdk";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
import { useProjectState } from "hooks/store";
// ui // ui
import { Spinner, StateGroupIcon } from "@plane/ui"; import { Spinner, StateGroupIcon } from "@plane/ui";
// icons // icons
@ -18,14 +18,14 @@ type Props = {
export const ChangeIssueState: React.FC<Props> = observer((props) => { export const ChangeIssueState: React.FC<Props> = observer((props) => {
const { closePalette, issue } = props; const { closePalette, issue } = props;
// router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
// store hooks
const { const {
projectState: { projectStates },
projectIssues: { updateIssue }, projectIssues: { updateIssue },
} = useMobxStore(); } = useMobxStore();
const { projectStates } = useProjectState();
const submitChanges = async (formData: Partial<IIssue>) => { const submitChanges = async (formData: Partial<IIssue>) => {
if (!workspaceSlug || !projectId || !issue) return; if (!workspaceSlug || !projectId || !issue) return;

View File

@ -1,7 +1,8 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useEffect } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// mobx store // hooks
import { useMobxStore } from "lib/mobx/store-provider"; import { useLabel } from "hooks/store";
// hook // hook
import useEstimateOption from "hooks/use-estimate-option"; import useEstimateOption from "hooks/use-estimate-option";
// icons // icons
@ -27,7 +28,6 @@ import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { capitalizeFirstLetter } from "helpers/string.helper"; import { capitalizeFirstLetter } from "helpers/string.helper";
// types // types
import { IIssueActivity } from "types"; import { IIssueActivity } from "types";
import { useEffect } from "react";
const IssueLink = ({ activity }: { activity: IIssueActivity }) => { const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
const router = useRouter(); const router = useRouter();
@ -74,11 +74,10 @@ const UserLink = ({ activity }: { activity: IIssueActivity }) => {
}; };
const LabelPill = observer(({ labelId, workspaceSlug }: { labelId: string; workspaceSlug: string }) => { const LabelPill = observer(({ labelId, workspaceSlug }: { labelId: string; workspaceSlug: string }) => {
// store hooks
const { const {
workspace: { labels, fetchWorkspaceLabels }, workspaceLabel: { workspaceLabels, fetchWorkspaceLabels },
} = useMobxStore(); } = useLabel();
const workspaceLabels = labels[workspaceSlug];
useEffect(() => { useEffect(() => {
if (!workspaceLabels) fetchWorkspaceLabels(workspaceSlug); if (!workspaceLabels) fetchWorkspaceLabels(workspaceSlug);

View File

@ -1,9 +1,9 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks // hooks
import { useMobxStore } from "lib/mobx/store-provider";
import { useApplication, useLabel, useProject, useProjectState, useUser } from "hooks/store";
import useLocalStorage from "hooks/use-local-storage"; import useLocalStorage from "hooks/use-local-storage";
// components // components
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues";
@ -25,27 +25,34 @@ import { EFilterType } from "store_legacy/issues/types";
import { EProjectStore } from "store_legacy/command-palette.store"; import { EProjectStore } from "store_legacy/command-palette.store";
export const CycleIssuesHeader: React.FC = observer(() => { export const CycleIssuesHeader: React.FC = observer(() => {
// states
const [analyticsModal, setAnalyticsModal] = useState(false); const [analyticsModal, setAnalyticsModal] = useState(false);
// router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query as { const { workspaceSlug, projectId, cycleId } = router.query as {
workspaceSlug: string; workspaceSlug: string;
projectId: string; projectId: string;
cycleId: string; cycleId: string;
}; };
// store hooks
const { const {
cycle: cycleStore, cycle: cycleStore,
projectIssuesFilter: projectIssueFiltersStore, projectIssuesFilter: projectIssueFiltersStore,
project: { currentProjectDetails },
projectMember: { projectMembers }, projectMember: { projectMembers },
projectLabel: { projectLabels },
projectState: projectStateStore,
commandPalette: commandPaletteStore,
trackEvent: { setTrackElement },
cycleIssuesFilter: { issueFilters, updateFilters }, cycleIssuesFilter: { issueFilters, updateFilters },
user: { currentProjectRole },
} = useMobxStore(); } = useMobxStore();
const {
commandPalette: { toggleCreateIssueModal },
eventTracker: { setTrackElement },
} = useApplication();
const {
membership: { currentProjectRole },
} = useUser();
const { currentProjectDetails } = useProject();
const { projectStates } = useProjectState();
const {
project: { projectLabels },
} = useLabel();
const activeLayout = projectIssueFiltersStore.issueFilters?.displayFilters?.layout; const activeLayout = projectIssueFiltersStore.issueFilters?.displayFilters?.layout;
@ -180,9 +187,9 @@ export const CycleIssuesHeader: React.FC = observer(() => {
layoutDisplayFiltersOptions={ layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
} }
labels={projectLabels ?? undefined} labels={projectLabels}
members={projectMembers?.map((m) => m.member)} members={projectMembers?.map((m) => m.member)}
states={projectStateStore.states?.[projectId ?? ""] ?? undefined} states={projectStates}
/> />
</FiltersDropdown> </FiltersDropdown>
<FiltersDropdown title="Display" placement="bottom-end"> <FiltersDropdown title="Display" placement="bottom-end">
@ -205,7 +212,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
<Button <Button
onClick={() => { onClick={() => {
setTrackElement("CYCLE_PAGE_HEADER"); setTrackElement("CYCLE_PAGE_HEADER");
commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.CYCLE); toggleCreateIssueModal(true, EProjectStore.CYCLE);
}} }}
size="sm" size="sm"
prependIcon={<Plus />} prependIcon={<Plus />}

View File

@ -2,7 +2,8 @@ import { useCallback, useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// mobx store // hooks
import { useLabel, useUser } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection } from "components/issues"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection } from "components/issues";
@ -29,19 +30,23 @@ type Props = {
export const GlobalIssuesHeader: React.FC<Props> = observer((props) => { export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
const { activeLayout } = props; const { activeLayout } = props;
// states
const [createViewModal, setCreateViewModal] = useState(false); const [createViewModal, setCreateViewModal] = useState(false);
// router
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query as { workspaceSlug: string }; const { workspaceSlug } = router.query;
// store hooks
const { const {
workspace: { workspaceLabels },
workspaceMember: { workspaceMembers }, workspaceMember: { workspaceMembers },
project: { workspaceProjects }, project: { workspaceProjects },
user: { currentWorkspaceRole },
workspaceGlobalIssuesFilter: { issueFilters, updateFilters }, workspaceGlobalIssuesFilter: { issueFilters, updateFilters },
} = useMobxStore(); } = useMobxStore();
const {
membership: { currentWorkspaceRole },
} = useUser();
const {
workspace: { workspaceLabels },
} = useLabel();
const handleFiltersUpdate = useCallback( const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => { (key: keyof IIssueFilterOptions, value: string | string[]) => {
@ -57,7 +62,7 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
else newValues.push(value); else newValues.push(value);
} }
updateFilters(workspaceSlug, EFilterType.FILTERS, { [key]: newValues }); updateFilters(workspaceSlug.toString(), EFilterType.FILTERS, { [key]: newValues });
}, },
[workspaceSlug, issueFilters, updateFilters] [workspaceSlug, issueFilters, updateFilters]
); );
@ -65,7 +70,7 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
const handleDisplayFilters = useCallback( const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => { (updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
updateFilters(workspaceSlug, EFilterType.DISPLAY_FILTERS, updatedDisplayFilter); updateFilters(workspaceSlug.toString(), EFilterType.DISPLAY_FILTERS, updatedDisplayFilter);
}, },
[workspaceSlug, updateFilters] [workspaceSlug, updateFilters]
); );
@ -73,7 +78,7 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
const handleDisplayProperties = useCallback( const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => { (property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
updateFilters(workspaceSlug, EFilterType.DISPLAY_PROPERTIES, property); updateFilters(workspaceSlug.toString(), EFilterType.DISPLAY_PROPERTIES, property);
}, },
[workspaceSlug, updateFilters] [workspaceSlug, updateFilters]
); );

View File

@ -1,9 +1,9 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks // hooks
import { useMobxStore } from "lib/mobx/store-provider";
import { useApplication, useLabel, useProject, useProjectState, useUser } from "hooks/store";
import useLocalStorage from "hooks/use-local-storage"; import useLocalStorage from "hooks/use-local-storage";
// components // components
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues";
@ -25,28 +25,33 @@ import { EFilterType } from "store_legacy/issues/types";
import { EProjectStore } from "store_legacy/command-palette.store"; import { EProjectStore } from "store_legacy/command-palette.store";
export const ModuleIssuesHeader: React.FC = observer(() => { export const ModuleIssuesHeader: React.FC = observer(() => {
// states
const [analyticsModal, setAnalyticsModal] = useState(false); const [analyticsModal, setAnalyticsModal] = useState(false);
// router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query as { const { workspaceSlug, projectId, moduleId } = router.query as {
workspaceSlug: string; workspaceSlug: string;
projectId: string; projectId: string;
moduleId: string; moduleId: string;
}; };
// store hooks
const { const {
module: moduleStore, module: moduleStore,
project: projectStore,
projectMember: { projectMembers }, projectMember: { projectMembers },
projectState: projectStateStore,
commandPalette: commandPaletteStore,
trackEvent: { setTrackElement },
projectLabel: { projectLabels },
moduleIssuesFilter: { issueFilters, updateFilters }, moduleIssuesFilter: { issueFilters, updateFilters },
user: { currentProjectRole },
} = useMobxStore(); } = useMobxStore();
const {
const { currentProjectDetails } = projectStore; commandPalette: { toggleCreateIssueModal },
eventTracker: { setTrackElement },
} = useApplication();
const {
membership: { currentProjectRole },
} = useUser();
const { currentProjectDetails } = useProject();
const {
project: { projectLabels },
} = useLabel();
const { projectStates } = useProjectState();
const { setValue, storedValue } = useLocalStorage("module_sidebar_collapsed", "false"); const { setValue, storedValue } = useLocalStorage("module_sidebar_collapsed", "false");
@ -181,9 +186,9 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
layoutDisplayFiltersOptions={ layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
} }
labels={projectLabels ?? undefined} labels={projectLabels}
members={projectMembers?.map((m) => m.member)} members={projectMembers?.map((m) => m.member)}
states={projectStateStore.states?.[projectId ?? ""] ?? undefined} states={projectStates}
/> />
</FiltersDropdown> </FiltersDropdown>
<FiltersDropdown title="Display" placement="bottom-end"> <FiltersDropdown title="Display" placement="bottom-end">
@ -206,7 +211,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
<Button <Button
onClick={() => { onClick={() => {
setTrackElement("MODULE_PAGE_HEADER"); setTrackElement("MODULE_PAGE_HEADER");
commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.MODULE); toggleCreateIssueModal(true, EProjectStore.MODULE);
}} }}
size="sm" size="sm"
prependIcon={<Plus />} prependIcon={<Plus />}

View File

@ -1,32 +1,35 @@
import { FC } from "react"; import { FC } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { ArrowLeft } from "lucide-react";
// hooks // hooks
import { useLabel, useProject, useProjectState } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// constants // constants
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
// ui // ui
import { Breadcrumbs, LayersIcon } from "@plane/ui"; import { Breadcrumbs, LayersIcon } from "@plane/ui";
// icons
import { ArrowLeft } from "lucide-react";
// components // components
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "components/issues"; import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "components/issues";
// helpers
import { renderEmoji } from "helpers/emoji.helper";
// types // types
import type { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "types"; import type { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "types";
// helper
import { renderEmoji } from "helpers/emoji.helper";
export const ProjectArchivedIssuesHeader: FC = observer(() => { export const ProjectArchivedIssuesHeader: FC = observer(() => {
// router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
// store hooks
const { const {
project: { currentProjectDetails },
projectLabel: { projectLabels },
projectMember: { projectMembers }, projectMember: { projectMembers },
archivedIssueFilters: archivedIssueFiltersStore, archivedIssueFilters: archivedIssueFiltersStore,
projectState: projectStateStore,
} = useMobxStore(); } = useMobxStore();
const { currentProjectDetails } = useProject();
const { projectStates } = useProjectState();
const {
project: { projectLabels },
} = useLabel();
// for archived issues list layout is the only option // for archived issues list layout is the only option
const activeLayout = "list"; const activeLayout = "list";
@ -118,9 +121,9 @@ export const ProjectArchivedIssuesHeader: FC = observer(() => {
layoutDisplayFiltersOptions={ layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.archived_issues[activeLayout] : undefined activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.archived_issues[activeLayout] : undefined
} }
labels={projectLabels ?? undefined} labels={projectLabels}
members={projectMembers?.map((m) => m.member)} members={projectMembers?.map((m) => m.member)}
states={projectStateStore.states?.[projectId?.toString() ?? ""] ?? undefined} states={projectStates}
/> />
</FiltersDropdown> </FiltersDropdown>
<FiltersDropdown title="Display" placement="bottom-end"> <FiltersDropdown title="Display" placement="bottom-end">

View File

@ -2,6 +2,7 @@ import { FC, useCallback } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// hooks // hooks
import { useLabel, useProject, useProjectState } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues";
@ -14,16 +15,19 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
export const ProjectDraftIssueHeader: FC = observer(() => { export const ProjectDraftIssueHeader: FC = observer(() => {
// router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string }; const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
// store hooks
const { const {
project: { currentProjectDetails },
projectLabel: { projectLabels },
projectMember: { projectMembers }, projectMember: { projectMembers },
projectState: projectStateStore,
projectDraftIssuesFilter: { issueFilters, updateFilters }, projectDraftIssuesFilter: { issueFilters, updateFilters },
} = useMobxStore(); } = useMobxStore();
const { currentProjectDetails } = useProject();
const { projectStates } = useProjectState();
const {
project: { projectLabels },
} = useLabel();
const activeLayout = issueFilters?.displayFilters?.layout; const activeLayout = issueFilters?.displayFilters?.layout;
@ -112,9 +116,9 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
layoutDisplayFiltersOptions={ layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
} }
labels={projectLabels ?? undefined} labels={projectLabels}
members={projectMembers?.map((m) => m.member)} members={projectMembers?.map((m) => m.member)}
states={projectStateStore.states?.[projectId ?? ""] ?? undefined} states={projectStates}
/> />
</FiltersDropdown> </FiltersDropdown>
<FiltersDropdown title="Display" placement="bottom-end"> <FiltersDropdown title="Display" placement="bottom-end">

View File

@ -2,7 +2,8 @@ import { useCallback } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
// mobx store // hooks
import { useApplication, useLabel, useProject, useProjectState, useUser } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues";
@ -21,24 +22,31 @@ import { EFilterType } from "store_legacy/issues/types";
import { EProjectStore } from "store_legacy/command-palette.store"; import { EProjectStore } from "store_legacy/command-palette.store";
export const ProjectViewIssuesHeader: React.FC = observer(() => { export const ProjectViewIssuesHeader: React.FC = observer(() => {
// router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, viewId } = router.query as { const { workspaceSlug, projectId, viewId } = router.query as {
workspaceSlug: string; workspaceSlug: string;
projectId: string; projectId: string;
viewId: string; viewId: string;
}; };
// store hooks
const { const {
project: { currentProjectDetails },
projectLabel: { projectLabels },
projectMember: { projectMembers }, projectMember: { projectMembers },
projectState: projectStateStore,
projectViews: projectViewsStore, projectViews: projectViewsStore,
viewIssuesFilter: { issueFilters, updateFilters }, viewIssuesFilter: { issueFilters, updateFilters },
commandPalette: commandPaletteStore,
trackEvent: { setTrackElement },
user: { currentProjectRole },
} = useMobxStore(); } = useMobxStore();
const {
commandPalette: { toggleCreateIssueModal },
eventTracker: { setTrackElement },
} = useApplication();
const {
membership: { currentProjectRole },
} = useUser();
const { currentProjectDetails } = useProject();
const { projectStates } = useProjectState();
const {
project: { projectLabels },
} = useLabel();
const activeLayout = issueFilters?.displayFilters?.layout; const activeLayout = issueFilters?.displayFilters?.layout;
@ -164,9 +172,9 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
layoutDisplayFiltersOptions={ layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
} }
labels={projectLabels ?? undefined} labels={projectLabels}
members={projectMembers?.map((m) => m.member)} members={projectMembers?.map((m) => m.member)}
states={projectStateStore.states?.[projectId ?? ""] ?? undefined} states={projectStates}
/> />
</FiltersDropdown> </FiltersDropdown>
<FiltersDropdown title="Display" placement="bottom-end"> <FiltersDropdown title="Display" placement="bottom-end">
@ -184,7 +192,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
<Button <Button
onClick={() => { onClick={() => {
setTrackElement("PROJECT_VIEW_PAGE_HEADER"); setTrackElement("PROJECT_VIEW_PAGE_HEADER");
commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.PROJECT_VIEW); toggleCreateIssueModal(true, EProjectStore.PROJECT_VIEW);
}} }}
size="sm" size="sm"
prependIcon={<Plus />} prependIcon={<Plus />}

View File

@ -4,9 +4,9 @@ import { observer } from "mobx-react-lite";
import useSWR from "swr"; import useSWR from "swr";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { AlertTriangle, CheckCircle2, Clock, Copy, ExternalLink, Inbox, XCircle } from "lucide-react"; import { AlertTriangle, CheckCircle2, Clock, Copy, ExternalLink, Inbox, XCircle } from "lucide-react";
// hooks
// mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
import { useProjectState, useUser } from "hooks/store";
// components // components
import { IssueDescriptionForm, IssueDetailsSidebar, IssueReaction, IssueUpdateStatus } from "components/issues"; import { IssueDescriptionForm, IssueDetailsSidebar, IssueReaction, IssueUpdateStatus } from "components/issues";
import { InboxIssueActivity } from "components/inbox"; import { InboxIssueActivity } from "components/inbox";
@ -28,19 +28,19 @@ const defaultValues: Partial<IInboxIssue> = {
}; };
export const InboxMainContent: React.FC = observer(() => { export const InboxMainContent: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId, inboxId, inboxIssueId } = router.query;
// states // states
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved"); const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
// router
const router = useRouter();
const { workspaceSlug, projectId, inboxId, inboxIssueId } = router.query;
// store hooks
const { inboxIssues: inboxIssuesStore, inboxIssueDetails: inboxIssueDetailsStore } = useMobxStore();
const { const {
inboxIssues: inboxIssuesStore, currentUser,
inboxIssueDetails: inboxIssueDetailsStore, membership: { currentProjectRole },
user: { currentUser, currentProjectRole }, } = useUser();
projectState: { states }, const { projectStates } = useProjectState();
} = useMobxStore(); // form info
const { reset, control, watch } = useForm<IIssue>({ const { reset, control, watch } = useForm<IIssue>({
defaultValues, defaultValues,
}); });
@ -60,9 +60,7 @@ export const InboxMainContent: React.FC = observer(() => {
const issuesList = inboxId ? inboxIssuesStore.inboxIssues[inboxId.toString()] : undefined; const issuesList = inboxId ? inboxIssuesStore.inboxIssues[inboxId.toString()] : undefined;
const issueDetails = inboxIssueId ? inboxIssueDetailsStore.issueDetails[inboxIssueId.toString()] : undefined; const issueDetails = inboxIssueId ? inboxIssueDetailsStore.issueDetails[inboxIssueId.toString()] : undefined;
const currentIssueState = projectId const currentIssueState = projectStates?.find((s) => s.id === issueDetails?.state);
? states[projectId.toString()]?.find((s) => s.id === issueDetails?.state)
: undefined;
const submitChanges = useCallback( const submitChanges = useCallback(
async (formData: Partial<IInboxIssue>) => { async (formData: Partial<IInboxIssue>) => {

View File

@ -4,22 +4,22 @@ import { observer } from "mobx-react-lite";
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { RichTextEditorWithRef } from "@plane/rich-text-editor"; import { RichTextEditorWithRef } from "@plane/rich-text-editor";
import { Sparkle } from "lucide-react";
// mobx store // hooks
import { useApplication, useWorkspace } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast";
import useEditorSuggestions from "hooks/use-editor-suggestions";
// services // services
import { FileService } from "services/file.service"; import { FileService } from "services/file.service";
import { AIService } from "services/ai.service";
// components // components
import { IssuePrioritySelect } from "components/issues/select"; import { IssuePrioritySelect } from "components/issues/select";
import { GptAssistantModal } from "components/core";
// ui // ui
import { Button, Input, ToggleSwitch } from "@plane/ui"; import { Button, Input, ToggleSwitch } from "@plane/ui";
// types // types
import { IIssue } from "types"; import { IIssue } from "types";
import useEditorSuggestions from "hooks/use-editor-suggestions";
import { GptAssistantModal } from "components/core";
import { Sparkle } from "lucide-react";
import useToast from "hooks/use-toast";
import { AIService } from "services/ai.service";
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
@ -40,30 +40,29 @@ const fileService = new FileService();
export const CreateInboxIssueModal: React.FC<Props> = observer((props) => { export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
const { isOpen, onClose } = props; const { isOpen, onClose } = props;
// states // states
const [createMore, setCreateMore] = useState(false); const [createMore, setCreateMore] = useState(false);
const [gptAssistantModal, setGptAssistantModal] = useState(false); const [gptAssistantModal, setGptAssistantModal] = useState(false);
const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false); const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false);
// refs
const editorRef = useRef<any>(null); const editorRef = useRef<any>(null);
// toast alert
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const editorSuggestion = useEditorSuggestions(); const editorSuggestion = useEditorSuggestions();
// router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, inboxId } = router.query as { const { workspaceSlug, projectId, inboxId } = router.query as {
workspaceSlug: string; workspaceSlug: string;
projectId: string; projectId: string;
inboxId: string; inboxId: string;
}; };
// store hooks
const { inboxIssueDetails: inboxIssueDetailsStore } = useMobxStore();
const { const {
inboxIssueDetails: inboxIssueDetailsStore, config: { envConfig },
trackEvent: { postHogEventTracker }, eventTracker: { postHogEventTracker },
appConfig: { envConfig }, } = useApplication();
workspace: { currentWorkspace }, const { currentWorkspace } = useWorkspace();
} = useMobxStore();
const { const {
control, control,

View File

@ -2,10 +2,9 @@ import React, { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks // hooks
import { useApplication, useWorkspace } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// icons // icons
import { AlertTriangle } from "lucide-react"; import { AlertTriangle } from "lucide-react";
@ -21,16 +20,17 @@ type Props = {
}; };
export const DeleteInboxIssueModal: React.FC<Props> = observer(({ isOpen, onClose, data }) => { export const DeleteInboxIssueModal: React.FC<Props> = observer(({ isOpen, onClose, data }) => {
// states
const [isDeleting, setIsDeleting] = useState(false); const [isDeleting, setIsDeleting] = useState(false);
// router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, inboxId } = router.query; const { workspaceSlug, projectId, inboxId } = router.query;
// store hooks
const { inboxIssueDetails: inboxIssueDetailsStore } = useMobxStore();
const { const {
inboxIssueDetails: inboxIssueDetailsStore, eventTracker: { postHogEventTracker },
trackEvent: { postHogEventTracker }, } = useApplication();
workspace: { currentWorkspace }, const { currentWorkspace } = useWorkspace();
} = useMobxStore();
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();

View File

@ -1,9 +1,9 @@
import { useState } from "react"; import { useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { PlusIcon } from "lucide-react"; import { PlusIcon } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks // hooks
import { useApplication, useUser } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// components // components
import { EmptyState } from "components/common"; import { EmptyState } from "components/common";
@ -28,13 +28,15 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId, cycleId } = props; const { workspaceSlug, projectId, cycleId } = props;
// states // states
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false); const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
// store hooks
const { cycleIssues: cycleIssueStore } = useMobxStore();
const { const {
cycleIssues: cycleIssueStore, commandPalette: { toggleCreateIssueModal },
commandPalette: commandPaletteStore, eventTracker: { setTrackElement },
trackEvent: { setTrackElement }, } = useApplication();
user: { currentProjectRole: userRole }, const {
} = useMobxStore(); membership: { currentProjectRole: userRole },
} = useUser();
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
@ -72,7 +74,7 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />, icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
onClick: () => { onClick: () => {
setTrackElement("CYCLE_EMPTY_STATE"); setTrackElement("CYCLE_EMPTY_STATE");
commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.CYCLE); toggleCreateIssueModal(true, EProjectStore.CYCLE);
}, },
}} }}
secondaryButton={ secondaryButton={

View File

@ -1,15 +1,19 @@
import { useState } from "react";
import { observer } from "mobx-react-lite";
import { PlusIcon } from "lucide-react"; import { PlusIcon } from "lucide-react";
// hooks
import { useApplication, useUser } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast";
// components // components
import { EmptyState } from "components/common"; import { EmptyState } from "components/common";
import { ExistingIssuesListModal } from "components/core";
// ui
import { Button } from "@plane/ui"; import { Button } from "@plane/ui";
// assets // assets
import emptyIssue from "public/empty-state/issue.svg"; import emptyIssue from "public/empty-state/issue.svg";
import { ExistingIssuesListModal } from "components/core"; // types
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
import { ISearchIssueResponse } from "types"; import { ISearchIssueResponse } from "types";
import useToast from "hooks/use-toast";
import { useState } from "react";
// constants // constants
import { EUserWorkspaceRoles } from "constants/workspace"; import { EUserWorkspaceRoles } from "constants/workspace";
@ -23,14 +27,16 @@ export const ModuleEmptyState: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId, moduleId } = props; const { workspaceSlug, projectId, moduleId } = props;
// states // states
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false); const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
// store hooks
const { moduleIssues: moduleIssueStore } = useMobxStore();
const { const {
moduleIssues: moduleIssueStore, commandPalette: { toggleCreateIssueModal },
commandPalette: commandPaletteStore, eventTracker: { setTrackElement },
trackEvent: { setTrackElement }, } = useApplication();
user: { currentProjectRole: userRole }, const {
} = useMobxStore(); membership: { currentProjectRole: userRole },
} = useUser();
// toast alert
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const handleAddIssuesToModule = async (data: ISearchIssueResponse[]) => { const handleAddIssuesToModule = async (data: ISearchIssueResponse[]) => {
@ -67,7 +73,7 @@ export const ModuleEmptyState: React.FC<Props> = observer((props) => {
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />, icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
onClick: () => { onClick: () => {
setTrackElement("MODULE_EMPTY_STATE"); setTrackElement("MODULE_EMPTY_STATE");
commandPaletteStore.toggleCreateIssueModal(true); toggleCreateIssueModal(true);
}, },
}} }}
secondaryButton={ secondaryButton={

View File

@ -1,5 +1,7 @@
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider"; import { X } from "lucide-react";
// hooks
import { useUser } from "hooks/store";
// components // components
import { import {
AppliedDateFilters, AppliedDateFilters,
@ -10,8 +12,6 @@ import {
AppliedStateFilters, AppliedStateFilters,
AppliedStateGroupFilters, AppliedStateGroupFilters,
} from "components/issues"; } from "components/issues";
// icons
import { X } from "lucide-react";
// helpers // helpers
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
// types // types
@ -34,10 +34,10 @@ const dateFilters = ["start_date", "target_date"];
export const AppliedFiltersList: React.FC<Props> = observer((props) => { export const AppliedFiltersList: React.FC<Props> = observer((props) => {
const { appliedFilters, handleClearAllFilters, handleRemoveFilter, labels, members, projects, states } = props; const { appliedFilters, handleClearAllFilters, handleRemoveFilter, labels, members, projects, states } = props;
// store hooks
const { const {
user: { currentProjectRole }, membership: { currentProjectRole },
} = useMobxStore(); } = useUser();
if (!appliedFilters) return null; if (!appliedFilters) return null;

View File

@ -1,15 +1,14 @@
import { Fragment, useState } from "react"; import { Fragment, useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { usePopper } from "react-popper"; import { usePopper } from "react-popper";
import { Check, ChevronDown, Search, Tags } from "lucide-react";
// hooks
import { useApplication, useLabel } from "hooks/store";
// components // components
import { Combobox } from "@headlessui/react"; import { Combobox } from "@headlessui/react";
import { Tooltip } from "@plane/ui"; import { Tooltip } from "@plane/ui";
import { Check, ChevronDown, Search, Tags } from "lucide-react";
// types // types
import { Placement } from "@popperjs/core"; import { Placement } from "@popperjs/core";
import { RootStore } from "store_legacy/root";
import { IIssueLabel } from "types"; import { IIssueLabel } from "types";
export interface IIssuePropertyLabels { export interface IIssuePropertyLabels {
@ -44,18 +43,19 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
noLabelBorder = false, noLabelBorder = false,
placeholderText, placeholderText,
} = props; } = props;
// states
const {
workspace: workspaceStore,
projectLabel: { fetchProjectLabels, labels },
}: RootStore = useMobxStore();
const workspaceSlug = workspaceStore?.workspaceSlug;
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
// popper-js refs
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null); const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null); const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
const [isLoading, setIsLoading] = useState<Boolean>(false); const [isLoading, setIsLoading] = useState<Boolean>(false);
// store hooks
const {
router: { workspaceSlug },
} = useApplication();
const {
project: { fetchProjectLabels, projectLabels: storeLabels },
} = useLabel();
const fetchLabels = () => { const fetchLabels = () => {
setIsLoading(true); setIsLoading(true);
@ -65,7 +65,6 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
if (!value) return null; if (!value) return null;
let projectLabels: IIssueLabel[] = defaultOptions; let projectLabels: IIssueLabel[] = defaultOptions;
const storeLabels = projectId && labels ? labels[projectId] : [];
if (storeLabels && storeLabels.length > 0) projectLabels = storeLabels; if (storeLabels && storeLabels.length > 0) projectLabels = storeLabels;
const options = projectLabels.map((label) => ({ const options = projectLabels.map((label) => ({

View File

@ -3,12 +3,11 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
import { MinusCircle } from "lucide-react"; import { MinusCircle } from "lucide-react";
// mobx store // hooks
import { useMobxStore } from "lib/mobx/store-provider"; import { useApplication, useProject, useProjectState, useUser, useWorkspace } from "hooks/store";
import useToast from "hooks/use-toast";
// services // services
import { IssueService, IssueCommentService } from "services/issue"; import { IssueService, IssueCommentService } from "services/issue";
// hooks
import useToast from "hooks/use-toast";
// components // components
import { import {
AddComment, AddComment,
@ -49,19 +48,19 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId, issueId } = router.query; const { workspaceSlug, projectId, issueId } = router.query;
// toast alert // toast alert
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
// mobx store
const { const {
user: { currentUser, currentProjectRole }, eventTracker: { postHogEventTracker },
project: projectStore, } = useApplication();
projectState: { states }, const {
trackEvent: { postHogEventTracker }, currentUser,
workspace: { currentWorkspace }, membership: { currentProjectRole },
} = useMobxStore(); } = useUser();
const { currentWorkspace } = useWorkspace();
const { getProjectById } = useProject();
const { projectStates } = useProjectState();
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : undefined; const projectDetails = projectId ? getProjectById(projectId.toString()) : null;
const currentIssueState = projectId const currentIssueState = projectStates?.find((s) => s.id === issueDetails.state);
? states[projectId.toString()]?.find((s) => s.id === issueDetails.state)
: undefined;
const { data: siblingIssues } = useSWR( const { data: siblingIssues } = useSWR(
workspaceSlug && projectId && issueDetails?.parent ? SUB_ISSUES(issueDetails.parent) : null, workspaceSlug && projectId && issueDetails?.parent ? SUB_ISSUES(issueDetails.parent) : null,
@ -94,7 +93,7 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
{ {
isGrouping: true, isGrouping: true,
groupType: "Workspace_metrics", groupType: "Workspace_metrics",
gorupId: currentWorkspace?.id!, groupId: currentWorkspace?.id!,
} }
); );
}); });
@ -117,7 +116,7 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
{ {
isGrouping: true, isGrouping: true,
groupType: "Workspace_metrics", groupType: "Workspace_metrics",
gorupId: currentWorkspace?.id!, groupId: currentWorkspace?.id!,
} }
); );
}); });
@ -139,7 +138,7 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
{ {
isGrouping: true, isGrouping: true,
groupType: "Workspace_metrics", groupType: "Workspace_metrics",
gorupId: currentWorkspace?.id!, groupId: currentWorkspace?.id!,
} }
); );
}) })
@ -260,12 +259,12 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
activity={issueActivity} activity={issueActivity}
handleCommentUpdate={handleCommentUpdate} handleCommentUpdate={handleCommentUpdate}
handleCommentDelete={handleCommentDelete} handleCommentDelete={handleCommentDelete}
showAccessSpecifier={projectDetails && projectDetails.is_deployed} showAccessSpecifier={Boolean(projectDetails && projectDetails.is_deployed)}
/> />
<AddComment <AddComment
onSubmit={handleAddComment} onSubmit={handleAddComment}
disabled={uneditable} disabled={uneditable}
showAccessSpecifier={projectDetails && projectDetails.is_deployed} showAccessSpecifier={Boolean(projectDetails && projectDetails.is_deployed)}
/> />
</div> </div>
</> </>

View File

@ -3,13 +3,13 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { mutate } from "swr"; import { mutate } from "swr";
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// services
import { IssueDraftService } from "services/issue";
// hooks // hooks
import { useApplication, useUser, useWorkspace } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
import useLocalStorage from "hooks/use-local-storage"; import useLocalStorage from "hooks/use-local-storage";
// services
import { IssueDraftService } from "services/issue";
// components // components
import { IssueForm, ConfirmIssueDiscard } from "components/issues"; import { IssueForm, ConfirmIssueDiscard } from "components/issues";
// types // types
@ -57,14 +57,13 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
handleSubmit, handleSubmit,
currentStore = EProjectStore.PROJECT, currentStore = EProjectStore.PROJECT,
} = props; } = props;
// states // states
const [createMore, setCreateMore] = useState(false); const [createMore, setCreateMore] = useState(false);
const [formDirtyState, setFormDirtyState] = useState<any>(null); const [formDirtyState, setFormDirtyState] = useState<any>(null);
const [showConfirmDiscard, setShowConfirmDiscard] = useState(false); const [showConfirmDiscard, setShowConfirmDiscard] = useState(false);
const [activeProject, setActiveProject] = useState<string | null>(null); const [activeProject, setActiveProject] = useState<string | null>(null);
const [prePopulateData, setPreloadedData] = useState<Partial<IIssue>>({}); const [prePopulateData, setPreloadedData] = useState<Partial<IIssue>>({});
// router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId } = router.query as { const { workspaceSlug, projectId, cycleId, moduleId } = router.query as {
workspaceSlug: string; workspaceSlug: string;
@ -72,7 +71,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
cycleId: string | undefined; cycleId: string | undefined;
moduleId: string | undefined; moduleId: string | undefined;
}; };
// store hooks
const { const {
project: projectStore, project: projectStore,
projectIssues: projectIssueStore, projectIssues: projectIssueStore,
@ -80,12 +79,12 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
workspaceProfileIssues: profileIssueStore, workspaceProfileIssues: profileIssueStore,
cycleIssues: cycleIssueStore, cycleIssues: cycleIssueStore,
moduleIssues: moduleIssueStore, moduleIssues: moduleIssueStore,
user: userStore,
trackEvent: { postHogEventTracker },
workspace: { currentWorkspace },
} = useMobxStore(); } = useMobxStore();
const {
const user = userStore.currentUser; eventTracker: { postHogEventTracker },
} = useApplication();
const { currentUser } = useUser();
const { currentWorkspace } = useWorkspace();
const issueStores = { const issueStores = {
[EProjectStore.PROJECT]: { [EProjectStore.PROJECT]: {
@ -100,7 +99,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
}, },
[EProjectStore.PROFILE]: { [EProjectStore.PROFILE]: {
store: profileIssueStore, store: profileIssueStore,
dataIdToUpdate: user?.id || undefined, dataIdToUpdate: currentUser?.id || undefined,
viewId: undefined, viewId: undefined,
}, },
[EProjectStore.CYCLE]: { [EProjectStore.CYCLE]: {
@ -150,10 +149,10 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
setPreloadedData((prevData) => ({ setPreloadedData((prevData) => ({
...(prevData ?? {}), ...(prevData ?? {}),
...prePopulateDataProps, ...prePopulateDataProps,
assignees: prePopulateDataProps?.assignees ?? [user?.id ?? ""], assignees: prePopulateDataProps?.assignees ?? [currentUser?.id ?? ""],
})); }));
} }
}, [prePopulateDataProps, cycleId, moduleId, router.asPath, user?.id]); }, [prePopulateDataProps, cycleId, moduleId, router.asPath, currentUser?.id]);
/** /**
* *
@ -260,7 +259,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
{ {
isGrouping: true, isGrouping: true,
groupType: "Workspace_metrics", groupType: "Workspace_metrics",
gorupId: currentWorkspace?.id!, groupId: currentWorkspace?.id!,
} }
); );
if (payload.parent && payload.parent !== "") mutate(SUB_ISSUES(payload.parent)); if (payload.parent && payload.parent !== "") mutate(SUB_ISSUES(payload.parent));
@ -280,7 +279,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
{ {
isGrouping: true, isGrouping: true,
groupType: "Workspace_metrics", groupType: "Workspace_metrics",
gorupId: currentWorkspace?.id!, groupId: currentWorkspace?.id!,
} }
); );
}); });
@ -289,7 +288,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
}; };
const createDraftIssue = async () => { const createDraftIssue = async () => {
if (!workspaceSlug || !activeProject || !user) return; if (!workspaceSlug || !activeProject || !currentUser) return;
const payload: Partial<IIssue> = { const payload: Partial<IIssue> = {
...formDirtyState, ...formDirtyState,
@ -308,7 +307,8 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
setFormDirtyState(null); setFormDirtyState(null);
setShowConfirmDiscard(false); setShowConfirmDiscard(false);
if (payload.assignees?.some((assignee) => assignee === user?.id)) mutate(USER_ISSUE(workspaceSlug as string)); if (payload.assignees?.some((assignee) => assignee === currentUser?.id))
mutate(USER_ISSUE(workspaceSlug as string));
if (payload.parent && payload.parent !== "") mutate(SUB_ISSUES(payload.parent)); if (payload.parent && payload.parent !== "") mutate(SUB_ISSUES(payload.parent));
}) })
@ -343,7 +343,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
{ {
isGrouping: true, isGrouping: true,
groupType: "Workspace_metrics", groupType: "Workspace_metrics",
gorupId: currentWorkspace?.id!, groupId: currentWorkspace?.id!,
} }
); );
}) })
@ -361,7 +361,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
{ {
isGrouping: true, isGrouping: true,
groupType: "Workspace_metrics", groupType: "Workspace_metrics",
gorupId: currentWorkspace?.id!, groupId: currentWorkspace?.id!,
} }
); );
}); });

View File

@ -1,12 +1,12 @@
import { FC, useState } from "react"; import { FC, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// mobx store import { CalendarDays, Link2, Plus, Signal, Tag, Triangle, LayoutPanelTop } from "lucide-react";
// hooks
import { useProject, useUser } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// ui icons // ui icons
import { DiceIcon, DoubleCircleIcon, UserGroupIcon, ContrastIcon } from "@plane/ui"; import { DiceIcon, DoubleCircleIcon, UserGroupIcon, ContrastIcon } from "@plane/ui";
import { CalendarDays, Link2, Plus, Signal, Tag, Triangle, LayoutPanelTop } from "lucide-react";
import { import {
SidebarAssigneeSelect, SidebarAssigneeSelect,
SidebarCycleSelect, SidebarCycleSelect,
@ -39,13 +39,15 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
// states // states
const [linkModal, setLinkModal] = useState(false); const [linkModal, setLinkModal] = useState(false);
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<ILinkDetails | null>(null); const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<ILinkDetails | null>(null);
// store hooks
const { const {
user: { currentProjectRole },
issueDetail: { fetchPeekIssueDetails }, issueDetail: { fetchPeekIssueDetails },
project: { getProjectById },
} = useMobxStore(); } = useMobxStore();
const {
membership: { currentProjectRole },
} = useUser();
const { getProjectById } = useProject();
// router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;

View File

@ -4,15 +4,13 @@ import { observer } from "mobx-react-lite";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { TwitterPicker } from "react-color"; import { TwitterPicker } from "react-color";
import { Popover, Transition } from "@headlessui/react"; import { Popover, Transition } from "@headlessui/react";
// mobx store import { Plus, X } from "lucide-react";
import { useMobxStore } from "lib/mobx/store-provider";
// hooks // hooks
import { useLabel } from "hooks/store";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// ui // ui
import { Input } from "@plane/ui"; import { Input } from "@plane/ui";
import { IssueLabelSelect } from "../select"; import { IssueLabelSelect } from "../select";
// icons
import { Plus, X } from "lucide-react";
// types // types
import { IIssue, IIssueLabel } from "types"; import { IIssue, IIssueLabel } from "types";
@ -40,8 +38,8 @@ export const SidebarLabelSelect: React.FC<Props> = observer((props) => {
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
// mobx store // mobx store
const { const {
projectLabel: { projectLabels, createLabel }, project: { projectLabels, createLabel },
} = useMobxStore(); } = useLabel();
// form info // form info
const { const {
handleSubmit, handleSubmit,

View File

@ -3,9 +3,10 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { mutate } from "swr"; import { mutate } from "swr";
import { Controller, UseFormWatch } from "react-hook-form"; import { Controller, UseFormWatch } from "react-hook-form";
// mobx store import { Bell, CalendarDays, LinkIcon, Plus, Signal, Tag, Trash2, Triangle, LayoutPanelTop } from "lucide-react";
import { useMobxStore } from "lib/mobx/store-provider";
// hooks // hooks
import { useProjectState, useUser } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
import useUserIssueNotificationSubscription from "hooks/use-issue-notification-subscription"; import useUserIssueNotificationSubscription from "hooks/use-issue-notification-subscription";
import useEstimateOption from "hooks/use-estimate-option"; import useEstimateOption from "hooks/use-estimate-option";
@ -32,7 +33,6 @@ import {
// ui // ui
import { CustomDatePicker } from "components/ui"; import { CustomDatePicker } from "components/ui";
// icons // icons
import { Bell, CalendarDays, LinkIcon, Plus, Signal, Tag, Trash2, Triangle, LayoutPanelTop } from "lucide-react";
import { Button, ContrastIcon, DiceIcon, DoubleCircleIcon, StateGroupIcon, UserGroupIcon } from "@plane/ui"; import { Button, ContrastIcon, DiceIcon, DoubleCircleIcon, StateGroupIcon, UserGroupIcon } from "@plane/ui";
// helpers // helpers
import { copyTextToClipboard } from "helpers/string.helper"; import { copyTextToClipboard } from "helpers/string.helper";
@ -75,18 +75,21 @@ const moduleService = new ModuleService();
export const IssueDetailsSidebar: React.FC<Props> = observer((props) => { export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
const { control, submitChanges, issueDetail, watch: watchIssue, fieldsToShow = ["all"], uneditable = false } = props; const { control, submitChanges, issueDetail, watch: watchIssue, fieldsToShow = ["all"], uneditable = false } = props;
// states
const [deleteIssueModal, setDeleteIssueModal] = useState(false); const [deleteIssueModal, setDeleteIssueModal] = useState(false);
const [linkModal, setLinkModal] = useState(false); const [linkModal, setLinkModal] = useState(false);
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<ILinkDetails | null>(null); const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<ILinkDetails | null>(null);
// store hooks
const { const {
user: { currentUser, currentProjectRole },
projectState: { states },
projectIssues: { removeIssue }, projectIssues: { removeIssue },
issueDetail: { createIssueLink, updateIssueLink, deleteIssueLink }, issueDetail: { createIssueLink, updateIssueLink, deleteIssueLink },
} = useMobxStore(); } = useMobxStore();
const {
currentUser,
membership: { currentProjectRole },
} = useUser();
const { projectStates } = useProjectState();
// router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, issueId, inboxIssueId } = router.query; const { workspaceSlug, projectId, issueId, inboxIssueId } = router.query;
@ -190,9 +193,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
const isAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; const isAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
const currentIssueState = projectId const currentIssueState = projectStates?.find((s) => s.id === issueDetail?.state);
? states[projectId.toString()]?.find((s) => s.id === issueDetail?.state)
: undefined;
return ( return (
<> <>

View File

@ -3,8 +3,10 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
import { Plus, ChevronRight, ChevronDown } from "lucide-react"; import { Plus, ChevronRight, ChevronDown } from "lucide-react";
// mobx store // hooks
import { useUser } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast";
// components // components
import { ExistingIssuesListModal } from "components/core"; import { ExistingIssuesListModal } from "components/core";
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues"; import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
@ -12,8 +14,6 @@ import { SubIssuesRootList } from "./issues-list";
import { ProgressBar } from "./progressbar"; import { ProgressBar } from "./progressbar";
// ui // ui
import { CustomMenu } from "@plane/ui"; import { CustomMenu } from "@plane/ui";
// hooks
import useToast from "hooks/use-toast";
// helpers // helpers
import { copyTextToClipboard } from "helpers/string.helper"; import { copyTextToClipboard } from "helpers/string.helper";
// types // types
@ -43,13 +43,15 @@ const issueService = new IssueService();
export const SubIssuesRoot: React.FC<ISubIssuesRoot> = observer((props) => { export const SubIssuesRoot: React.FC<ISubIssuesRoot> = observer((props) => {
const { parentIssue, user } = props; const { parentIssue, user } = props;
// store hooks
const { const {
user: { currentProjectRole },
issue: { updateIssueStructure }, issue: { updateIssueStructure },
projectIssues: { updateIssue, removeIssue }, projectIssues: { updateIssue, removeIssue },
} = useMobxStore(); } = useMobxStore();
const {
membership: { currentProjectRole },
} = useUser();
// router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;

View File

@ -1,21 +1,19 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { TwitterPicker } from "react-color"; import { TwitterPicker } from "react-color";
import { Dialog, Popover, Transition } from "@headlessui/react"; import { Dialog, Popover, Transition } from "@headlessui/react";
import { ChevronDown } from "lucide-react";
// store // hooks
import { observer } from "mobx-react-lite"; import { useLabel } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider"; import useToast from "hooks/use-toast";
// ui // ui
import { Button, Input } from "@plane/ui"; import { Button, Input } from "@plane/ui";
// icons
import { ChevronDown } from "lucide-react";
// types // types
import type { IIssueLabel, IState } from "types"; import type { IIssueLabel, IState } from "types";
// constants // constants
import { LABEL_COLOR_OPTIONS, getRandomLabelColor } from "constants/label"; import { LABEL_COLOR_OPTIONS, getRandomLabelColor } from "constants/label";
import useToast from "hooks/use-toast";
// types // types
type Props = { type Props = {
@ -32,13 +30,14 @@ const defaultValues: Partial<IState> = {
export const CreateLabelModal: React.FC<Props> = observer((props) => { export const CreateLabelModal: React.FC<Props> = observer((props) => {
const { isOpen, projectId, handleClose, onSuccess } = props; const { isOpen, projectId, handleClose, onSuccess } = props;
// router
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
// store hooks
// store const {
const { projectLabel: projectLabelStore } = useMobxStore(); project: { createLabel },
} = useLabel();
// form info
const { const {
formState: { errors, isSubmitting }, formState: { errors, isSubmitting },
handleSubmit, handleSubmit,
@ -72,8 +71,7 @@ export const CreateLabelModal: React.FC<Props> = observer((props) => {
const onSubmit = async (formData: IIssueLabel) => { const onSubmit = async (formData: IIssueLabel) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
await projectLabelStore await createLabel(workspaceSlug.toString(), projectId.toString(), formData)
.createLabel(workspaceSlug.toString(), projectId.toString(), formData)
.then((res) => { .then((res) => {
onClose(); onClose();
if (onSuccess) onSuccess(res); if (onSuccess) onSuccess(res);

View File

@ -1,20 +1,18 @@
import React, { forwardRef, useEffect } from "react"; import React, { forwardRef, useEffect } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { TwitterPicker } from "react-color"; import { TwitterPicker } from "react-color";
import { Controller, SubmitHandler, useForm } from "react-hook-form"; import { Controller, SubmitHandler, useForm } from "react-hook-form";
// stores
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// headless ui
import { Popover, Transition } from "@headlessui/react"; import { Popover, Transition } from "@headlessui/react";
// hooks
import { useLabel } from "hooks/store";
import useToast from "hooks/use-toast";
// ui // ui
import { Button, Input } from "@plane/ui"; import { Button, Input } from "@plane/ui";
// types // types
import { IIssueLabel } from "types"; import { IIssueLabel } from "types";
// fetch-keys // fetch-keys
import { getRandomLabelColor, LABEL_COLOR_OPTIONS } from "constants/label"; import { getRandomLabelColor, LABEL_COLOR_OPTIONS } from "constants/label";
import useToast from "hooks/use-toast";
type Props = { type Props = {
labelForm: boolean; labelForm: boolean;
@ -32,16 +30,16 @@ const defaultValues: Partial<IIssueLabel> = {
export const CreateUpdateLabelInline = observer( export const CreateUpdateLabelInline = observer(
forwardRef<HTMLFormElement, Props>(function CreateUpdateLabelInline(props, ref) { forwardRef<HTMLFormElement, Props>(function CreateUpdateLabelInline(props, ref) {
const { labelForm, setLabelForm, isUpdating, labelToUpdate, onClose } = props; const { labelForm, setLabelForm, isUpdating, labelToUpdate, onClose } = props;
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
// store hooks
// store const {
const { projectLabel: projectLabelStore } = useMobxStore(); project: { createLabel, updateLabel },
} = useLabel();
// toast alert
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
// form info
const { const {
handleSubmit, handleSubmit,
control, control,
@ -63,8 +61,7 @@ export const CreateUpdateLabelInline = observer(
const handleLabelCreate: SubmitHandler<IIssueLabel> = async (formData) => { const handleLabelCreate: SubmitHandler<IIssueLabel> = async (formData) => {
if (!workspaceSlug || !projectId || isSubmitting) return; if (!workspaceSlug || !projectId || isSubmitting) return;
await projectLabelStore await createLabel(workspaceSlug.toString(), projectId.toString(), formData)
.createLabel(workspaceSlug.toString(), projectId.toString(), formData)
.then(() => { .then(() => {
handleClose(); handleClose();
reset(defaultValues); reset(defaultValues);
@ -82,8 +79,7 @@ export const CreateUpdateLabelInline = observer(
const handleLabelUpdate: SubmitHandler<IIssueLabel> = async (formData) => { const handleLabelUpdate: SubmitHandler<IIssueLabel> = async (formData) => {
if (!workspaceSlug || !projectId || isSubmitting) return; if (!workspaceSlug || !projectId || isSubmitting) return;
await projectLabelStore await updateLabel(workspaceSlug.toString(), projectId.toString(), labelToUpdate?.id!, formData)
.updateLabel(workspaceSlug.toString(), projectId.toString(), labelToUpdate?.id!, formData)
.then(() => { .then(() => {
reset(defaultValues); reset(defaultValues);
handleClose(); handleClose();

View File

@ -1,15 +1,13 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR from "swr"; import useSWR from "swr";
import { Combobox, Dialog, Transition } from "@headlessui/react"; import { Combobox, Dialog, Transition } from "@headlessui/react";
import { Search } from "lucide-react";
// store // hooks
import { observer } from "mobx-react-lite"; import { useLabel } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider";
// icons // icons
import { LayerStackIcon } from "@plane/ui"; import { LayerStackIcon } from "@plane/ui";
import { Search } from "lucide-react";
// types // types
import { IIssueLabel } from "types"; import { IIssueLabel } from "types";
@ -21,18 +19,15 @@ type Props = {
export const LabelsListModal: React.FC<Props> = observer((props) => { export const LabelsListModal: React.FC<Props> = observer((props) => {
const { isOpen, handleClose, parent } = props; const { isOpen, handleClose, parent } = props;
// states
const [query, setQuery] = useState("");
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
// store hooks
// store
const { const {
projectLabel: { projectLabels, fetchProjectLabels, updateLabel }, project: { projectLabels, fetchProjectLabels, updateLabel },
} = useMobxStore(); } = useLabel();
// states
const [query, setQuery] = useState("");
// api call to fetch project details // api call to fetch project details
useSWR( useSWR(

View File

@ -1,12 +1,12 @@
import React, { Dispatch, SetStateAction, useState } from "react"; import React, { Dispatch, SetStateAction, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useMobxStore } from "lib/mobx/store-provider";
import { DraggableProvidedDragHandleProps, DraggableStateSnapshot } from "@hello-pangea/dnd"; import { DraggableProvidedDragHandleProps, DraggableStateSnapshot } from "@hello-pangea/dnd";
import { X, Pencil } from "lucide-react";
// hooks
import { useLabel } from "hooks/store";
// types // types
import { IIssueLabel } from "types"; import { IIssueLabel } from "types";
//icons // components
import { X, Pencil } from "lucide-react";
//components
import { ICustomMenuItem, LabelItemBlock } from "./label-block/label-item-block"; import { ICustomMenuItem, LabelItemBlock } from "./label-block/label-item-block";
import { CreateUpdateLabelInline } from "./create-update-label-inline"; import { CreateUpdateLabelInline } from "./create-update-label-inline";
@ -21,23 +21,21 @@ type Props = {
export const ProjectSettingLabelItem: React.FC<Props> = (props) => { export const ProjectSettingLabelItem: React.FC<Props> = (props) => {
const { label, setIsUpdating, handleLabelDelete, draggableSnapshot, dragHandleProps, isChild } = props; const { label, setIsUpdating, handleLabelDelete, draggableSnapshot, dragHandleProps, isChild } = props;
const { combineTargetFor, isDragging } = draggableSnapshot; const { combineTargetFor, isDragging } = draggableSnapshot;
// states
const [isEditLabelForm, setEditLabelForm] = useState(false);
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
// store hooks
// store const {
const { projectLabel: projectLabelStore } = useMobxStore(); project: { updateLabel },
} = useLabel();
//state
const [isEditLabelForm, setEditLabelForm] = useState(false);
const removeFromGroup = (label: IIssueLabel) => { const removeFromGroup = (label: IIssueLabel) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
projectLabelStore.updateLabel(workspaceSlug.toString(), projectId.toString(), label.id, { updateLabel(workspaceSlug.toString(), projectId.toString(), label.id, {
parent: null, parent: null,
}); });
}; };

View File

@ -10,9 +10,8 @@ import {
DropResult, DropResult,
Droppable, Droppable,
} from "@hello-pangea/dnd"; } from "@hello-pangea/dnd";
// store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks // hooks
import { useLabel } from "hooks/store";
import useDraggableInPortal from "hooks/use-draggable-portal"; import useDraggableInPortal from "hooks/use-draggable-portal";
// components // components
import { import {
@ -32,23 +31,22 @@ import { IIssueLabel } from "types";
const LABELS_ROOT = "labels.root"; const LABELS_ROOT = "labels.root";
export const ProjectSettingsLabelList: React.FC = observer(() => { export const ProjectSettingsLabelList: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const renderDraggable = useDraggableInPortal();
// store
const {
projectLabel: { fetchProjectLabels, projectLabels, updateLabelPosition, projectLabelsTree },
} = useMobxStore();
// states // states
const [showLabelForm, setLabelForm] = useState(false); const [showLabelForm, setLabelForm] = useState(false);
const [isUpdating, setIsUpdating] = useState(false); const [isUpdating, setIsUpdating] = useState(false);
const [selectDeleteLabel, setSelectDeleteLabel] = useState<IIssueLabel | null>(null); const [selectDeleteLabel, setSelectDeleteLabel] = useState<IIssueLabel | null>(null);
const [isDraggingGroup, setIsDraggingGroup] = useState(false); const [isDraggingGroup, setIsDraggingGroup] = useState(false);
// ref // refs
const scrollToRef = useRef<HTMLFormElement>(null); const scrollToRef = useRef<HTMLFormElement>(null);
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store hooks
const {
project: { fetchProjectLabels, projectLabels, updateLabelPosition, projectLabelsTree },
} = useLabel();
// portal
const renderDraggable = useDraggableInPortal();
// api call to fetch project details // api call to fetch project details
useSWR( useSWR(

View File

@ -2,9 +2,9 @@ import { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Link from "next/link"; import Link from "next/link";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks // hooks
import { useProject, useUser } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// components // components
import { ConfirmProjectMemberRemove } from "components/project"; import { ConfirmProjectMemberRemove } from "components/project";
@ -28,14 +28,16 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
// store hooks
// store
const { const {
user: { currentUser, currentProjectMemberInfo, currentProjectRole, leaveProject },
projectMember: { removeMemberFromProject, updateMember }, projectMember: { removeMemberFromProject, updateMember },
project: { fetchProjects },
} = useMobxStore(); } = useMobxStore();
// hooks const {
currentUser,
membership: { currentProjectMemberInfo, currentProjectRole, leaveProject },
} = useUser();
const { fetchProjects } = useProject();
// toast alert
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
// derived values // derived values

View File

@ -2,7 +2,8 @@ import { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { mutate } from "swr"; import { mutate } from "swr";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// mobx store // hooks
import { useApplication } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { ProjectMemberListItem, SendProjectInvitationModal } from "components/project"; import { ProjectMemberListItem, SendProjectInvitationModal } from "components/project";
@ -12,19 +13,19 @@ import { Button, Loader } from "@plane/ui";
import { Search } from "lucide-react"; import { Search } from "lucide-react";
export const ProjectMemberList: React.FC = observer(() => { export const ProjectMemberList: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store
const {
projectMember: { projectMembers, fetchProjectMembers },
trackEvent: { setTrackElement },
} = useMobxStore();
// states // states
const [inviteModal, setInviteModal] = useState(false); const [inviteModal, setInviteModal] = useState(false);
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store hooks
const {
projectMember: { projectMembers, fetchProjectMembers },
} = useMobxStore();
const {
eventTracker: { setTrackElement },
} = useApplication();
const searchedMembers = (projectMembers ?? []).filter((member) => { const searchedMembers = (projectMembers ?? []).filter((member) => {
const fullName = `${member.member.first_name} ${member.member.last_name}`.toLowerCase(); const fullName = `${member.member.first_name} ${member.member.last_name}`.toLowerCase();

View File

@ -4,14 +4,14 @@ import { observer } from "mobx-react-lite";
import { useForm, Controller, useFieldArray } from "react-hook-form"; import { useForm, Controller, useFieldArray } from "react-hook-form";
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { ChevronDown, Plus, X } from "lucide-react"; import { ChevronDown, Plus, X } from "lucide-react";
// mobx store // hooks
import { useApplication, useUser, useWorkspace } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast";
// ui // ui
import { Avatar, Button, CustomSelect, CustomSearchSelect } from "@plane/ui"; import { Avatar, Button, CustomSelect, CustomSearchSelect } from "@plane/ui";
// services // services
import { ProjectMemberService } from "services/project"; import { ProjectMemberService } from "services/project";
// hooks
import useToast from "hooks/use-toast";
// types // types
import { IProjectMember, TUserProjectRole } from "types"; import { IProjectMember, TUserProjectRole } from "types";
// constants // constants
@ -47,19 +47,23 @@ const projectMemberService = new ProjectMemberService();
export const SendProjectInvitationModal: React.FC<Props> = observer((props) => { export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
const { isOpen, members, onClose, onSuccess } = props; const { isOpen, members, onClose, onSuccess } = props;
// router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
// toast alert
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
// store hooks
const { const {
user: { currentProjectRole },
workspaceMember: { workspaceMembers }, workspaceMember: { workspaceMembers },
trackEvent: { postHogEventTracker },
workspace: { currentWorkspace },
} = useMobxStore(); } = useMobxStore();
const {
eventTracker: { postHogEventTracker },
} = useApplication();
const {
membership: { currentProjectRole },
} = useUser();
const { currentWorkspace } = useWorkspace();
// form info
const { const {
formState: { errors, isSubmitting }, formState: { errors, isSubmitting },
reset, reset,

View File

@ -1,8 +1,8 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
// hooks
// mobx store import { useLabel, useProjectState } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { AppliedFiltersList, FilterSelection, FiltersDropdown } from "components/issues"; import { AppliedFiltersList, FilterSelection, FiltersDropdown } from "components/issues";
@ -25,12 +25,16 @@ const defaultValues: Partial<IProjectView> = {
description: "", description: "",
}; };
export const ProjectViewForm: React.FC<Props> = observer(({ handleFormSubmit, handleClose, data, preLoadedData }) => { export const ProjectViewForm: React.FC<Props> = observer((props) => {
const { handleFormSubmit, handleClose, data, preLoadedData } = props;
// store hooks
const { const {
projectLabel: { projectLabels },
projectState: projectStateStore,
projectMember: { projectMembers }, projectMember: { projectMembers },
} = useMobxStore(); } = useMobxStore();
const { projectStates } = useProjectState();
const {
project: { projectLabels },
} = useLabel();
const { const {
control, control,
@ -176,7 +180,7 @@ export const ProjectViewForm: React.FC<Props> = observer(({ handleFormSubmit, ha
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues.list} layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues.list}
labels={projectLabels ?? undefined} labels={projectLabels ?? undefined}
members={projectMembers?.map((m) => m.member) ?? undefined} members={projectMembers?.map((m) => m.member) ?? undefined}
states={projectStateStore.projectStates ?? undefined} states={projectStates}
/> />
</FiltersDropdown> </FiltersDropdown>
)} )}
@ -190,7 +194,7 @@ export const ProjectViewForm: React.FC<Props> = observer(({ handleFormSubmit, ha
handleRemoveFilter={handleRemoveFilter} handleRemoveFilter={handleRemoveFilter}
labels={projectLabels ?? []} labels={projectLabels ?? []}
members={projectMembers?.map((m) => m.member) ?? []} members={projectMembers?.map((m) => m.member) ?? []}
states={projectStateStore.projectStates ?? []} states={projectStates}
/> />
</div> </div>
)} )}
@ -206,8 +210,8 @@ export const ProjectViewForm: React.FC<Props> = observer(({ handleFormSubmit, ha
? "Updating View..." ? "Updating View..."
: "Update View" : "Update View"
: isSubmitting : isSubmitting
? "Creating View..." ? "Creating View..."
: "Create View"} : "Create View"}
</Button> </Button>
</div> </div>
</form> </form>

View File

@ -3,9 +3,9 @@ import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { LinkIcon, PencilIcon, StarIcon, TrashIcon } from "lucide-react"; import { LinkIcon, PencilIcon, StarIcon, TrashIcon } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks // hooks
import { useUser } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// components // components
import { CreateUpdateProjectViewModal, DeleteProjectViewModal } from "components/views"; import { CreateUpdateProjectViewModal, DeleteProjectViewModal } from "components/views";
@ -25,19 +25,19 @@ type Props = {
export const ProjectViewListItem: React.FC<Props> = observer((props) => { export const ProjectViewListItem: React.FC<Props> = observer((props) => {
const { view } = props; const { view } = props;
// states
const [createUpdateViewModal, setCreateUpdateViewModal] = useState(false); const [createUpdateViewModal, setCreateUpdateViewModal] = useState(false);
const [deleteViewModal, setDeleteViewModal] = useState(false); const [deleteViewModal, setDeleteViewModal] = useState(false);
// router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
// toast alert
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
// store hooks
const { projectViews: projectViewsStore } = useMobxStore();
const { const {
projectViews: projectViewsStore, membership: { currentProjectRole },
user: { currentProjectRole }, } = useUser();
} = useMobxStore();
const handleAddToFavorites = () => { const handleAddToFavorites = () => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;

View File

@ -1,8 +1,9 @@
import { useState } from "react"; import { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Plus, Search } from "lucide-react";
// mobx store // hooks
import { useApplication, useUser } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { ProjectViewListItem } from "components/views"; import { ProjectViewListItem } from "components/views";
@ -11,22 +12,23 @@ import { NewEmptyState } from "components/common/new-empty-state";
import { Input, Loader } from "@plane/ui"; import { Input, Loader } from "@plane/ui";
// assets // assets
import emptyView from "public/empty-state/empty_view.webp"; import emptyView from "public/empty-state/empty_view.webp";
// icons
import { Plus, Search } from "lucide-react";
// constants // constants
import { EUserWorkspaceRoles } from "constants/workspace"; import { EUserWorkspaceRoles } from "constants/workspace";
export const ProjectViewsList = observer(() => { export const ProjectViewsList = observer(() => {
// states
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
// router
const router = useRouter(); const router = useRouter();
const { projectId } = router.query; const { projectId } = router.query;
// store hooks
const { projectViews: projectViewsStore } = useMobxStore();
const { const {
projectViews: projectViewsStore, commandPalette: { toggleCreateViewModal },
commandPalette: commandPaletteStore, } = useApplication();
user: { currentProjectRole }, const {
} = useMobxStore(); membership: { currentProjectRole },
} = useUser();
const viewsList = projectId ? projectViewsStore.viewsList[projectId.toString()] : undefined; const viewsList = projectId ? projectViewsStore.viewsList[projectId.toString()] : undefined;
@ -79,7 +81,7 @@ export const ProjectViewsList = observer(() => {
primaryButton={{ primaryButton={{
icon: <Plus size={14} strokeWidth={2} />, icon: <Plus size={14} strokeWidth={2} />,
text: "Build your first view", text: "Build your first view",
onClick: () => commandPaletteStore.toggleCreateViewModal(true), onClick: () => toggleCreateViewModal(true),
}} }}
disabled={!isEditingAllowed} disabled={!isEditingAllowed}
/> />

View File

@ -4,9 +4,9 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { mutate } from "swr"; import { mutate } from "swr";
import { ChevronDown, Dot, XCircle } from "lucide-react"; import { ChevronDown, Dot, XCircle } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks // hooks
import { useUser } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// components // components
import { ConfirmWorkspaceMemberRemove } from "components/workspace"; import { ConfirmWorkspaceMemberRemove } from "components/workspace";
@ -35,17 +35,21 @@ type Props = {
export const WorkspaceMembersListItem: FC<Props> = observer((props) => { export const WorkspaceMembersListItem: FC<Props> = observer((props) => {
const { member } = props; const { member } = props;
// states
const [removeMemberModal, setRemoveMemberModal] = useState(false);
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
// store // store hooks
const { const {
workspaceMember: { removeMember, updateMember, updateMemberInvitation, deleteWorkspaceInvitation }, workspaceMember: { removeMember, updateMember, updateMemberInvitation, deleteWorkspaceInvitation },
user: { currentWorkspaceMemberInfo, currentWorkspaceRole, currentUser, currentUserSettings, leaveWorkspace },
} = useMobxStore(); } = useMobxStore();
// states const {
const [removeMemberModal, setRemoveMemberModal] = useState(false); currentUser,
// hooks currentUserSettings,
membership: { currentWorkspaceMemberInfo, currentWorkspaceRole, leaveWorkspace },
} = useUser();
// toast alert
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const handleLeaveWorkspace = async () => { const handleLeaveWorkspace = async () => {

View File

@ -2,10 +2,10 @@ import { useContext } from "react";
// mobx store // mobx store
import { MobxStoreContext } from "lib/mobx/store-provider"; import { MobxStoreContext } from "lib/mobx/store-provider";
// types // types
import { ILabelStore } from "store/label.store"; import { ILabelRootStore } from "store/label";
export const useLabel = (): ILabelStore => { export const useLabel = (): ILabelRootStore => {
const context = useContext(MobxStoreContext); const context = useContext(MobxStoreContext);
if (context === undefined) throw new Error("useMobxStore must be used within MobxStoreProvider"); if (context === undefined) throw new Error("useMobxStore must be used within MobxStoreProvider");
return context.label; return context.labelRoot;
}; };

View File

@ -3,6 +3,7 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import useSWR from "swr"; import useSWR from "swr";
// hooks // hooks
import { useApplication, useCycle, useLabel, useModule, useProjectState, useUser } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { Spinner } from "@plane/ui"; import { Spinner } from "@plane/ui";
@ -10,7 +11,6 @@ import { JoinProject } from "components/auth-screens";
import { EmptyState } from "components/common"; import { EmptyState } from "components/common";
// images // images
import emptyProject from "public/empty-state/project.svg"; import emptyProject from "public/empty-state/project.svg";
import { useApplication, useCycle, useModule, useProjectState, useUser } from "hooks/store";
interface IProjectAuthWrapper { interface IProjectAuthWrapper {
children: ReactNode; children: ReactNode;
@ -21,7 +21,6 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
// store // store
const { const {
project: { fetchProjectDetails, workspaceProjects }, project: { fetchProjectDetails, workspaceProjects },
projectLabel: { fetchProjectLabels },
projectMember: { fetchProjectMembers }, projectMember: { fetchProjectMembers },
projectEstimates: { fetchProjectEstimates }, projectEstimates: { fetchProjectEstimates },
projectViews: { fetchAllViews }, projectViews: { fetchAllViews },
@ -36,6 +35,9 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
const { fetchAllCycles } = useCycle(); const { fetchAllCycles } = useCycle();
const { fetchModules } = useModule(); const { fetchModules } = useModule();
const { fetchProjectStates } = useProjectState(); const { fetchProjectStates } = useProjectState();
const {
project: { fetchProjectLabels },
} = useLabel();
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;

View File

@ -4,7 +4,7 @@ import Link from "next/link";
import useSWR from "swr"; import useSWR from "swr";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// hooks // hooks
import { useProject, useUser } from "hooks/store"; import { useLabel, useProject, useUser } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// icons // icons
import { Button, Spinner } from "@plane/ui"; import { Button, Spinner } from "@plane/ui";
@ -17,13 +17,15 @@ export const WorkspaceAuthWrapper: FC<IWorkspaceAuthWrapper> = observer((props)
const { children } = props; const { children } = props;
// store hooks // store hooks
const { const {
workspace: { fetchWorkspaceLabels },
workspaceMember: { fetchWorkspaceMembers, fetchWorkspaceUserProjectsRole }, workspaceMember: { fetchWorkspaceMembers, fetchWorkspaceUserProjectsRole },
} = useMobxStore(); } = useMobxStore();
const { const {
membership: { currentWorkspaceMemberInfo, hasPermissionToCurrentWorkspace, fetchUserWorkspaceInfo }, membership: { currentWorkspaceMemberInfo, hasPermissionToCurrentWorkspace, fetchUserWorkspaceInfo },
} = useUser(); } = useUser();
const { fetchProjects } = useProject(); const { fetchProjects } = useProject();
const {
workspace: { fetchWorkspaceLabels },
} = useLabel();
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;

47
web/store/label/index.ts Normal file
View File

@ -0,0 +1,47 @@
import { computed, observable, makeObservable } from "mobx";
import { RootStore } from "../root.store";
// types
import { IIssueLabel } from "types";
import { IProjectLabelStore, ProjectLabelStore } from "./project-label.store";
import { IWorkspaceLabelStore, WorkspaceLabelStore } from "./workspace-label.store";
export interface ILabelRootStore {
// observables
labelMap: Record<string, IIssueLabel>;
// computed actions
getLabelById: (labelId: string) => IIssueLabel | null;
// sub-stores
project: IProjectLabelStore;
workspace: IWorkspaceLabelStore;
}
export class LabelRootStore implements ILabelRootStore {
// observables
labelMap: Record<string, IIssueLabel> = {};
// root store
rootStore;
// sub-stores
project: IProjectLabelStore;
workspace: IWorkspaceLabelStore;
constructor(_rootStore: RootStore) {
makeObservable(this, {
// observables
labelMap: observable,
// computed actions
getLabelById: computed,
});
// root store
this.rootStore = _rootStore;
// sub-stores
this.project = new ProjectLabelStore(_rootStore);
this.workspace = new WorkspaceLabelStore(_rootStore);
}
/**
* get label info from the map of labels in the store using label id
* @param labelId
*/
getLabelById = (labelId: string): IIssueLabel | null => this.labelMap?.[labelId] || null;
}

View File

@ -0,0 +1,231 @@
import { action, computed, makeObservable, runInAction } from "mobx";
import { set } from "lodash";
// services
import { IssueLabelService } from "services/issue";
// helpers
import { buildTree } from "helpers/array.helper";
// types
import { RootStore } from "store/root.store";
import { IIssueLabel } from "types";
export interface IProjectLabelStore {
// computed
projectLabels: IIssueLabel[] | undefined;
projectLabelsTree: IIssueLabel[] | undefined;
// actions
fetchProjectLabels: (workspaceSlug: string, projectId: string) => Promise<IIssueLabel[]>;
createLabel: (workspaceSlug: string, projectId: string, data: Partial<IIssueLabel>) => Promise<IIssueLabel>;
updateLabel: (
workspaceSlug: string,
projectId: string,
labelId: string,
data: Partial<IIssueLabel>
) => Promise<IIssueLabel>;
updateLabelPosition: (
workspaceSlug: string,
projectId: string,
labelId: string,
parentId: string | null | undefined,
index: number,
isSameParent: boolean,
prevIndex: number | undefined
) => Promise<IIssueLabel | undefined>;
deleteLabel: (workspaceSlug: string, projectId: string, labelId: string) => Promise<void>;
}
export class ProjectLabelStore implements IProjectLabelStore {
// root store
rootStore;
// root store labelMap
labelMap: Record<string, IIssueLabel> = {};
// services
issueLabelService;
constructor(_rootStore: RootStore) {
makeObservable(this, {
// computed
projectLabels: computed,
projectLabelsTree: computed,
// actions
fetchProjectLabels: action,
createLabel: action,
updateLabel: action,
updateLabelPosition: action,
deleteLabel: action,
});
// root store
this.rootStore = _rootStore;
this.labelMap = this.rootStore.labelRoot.labelMap;
// services
this.issueLabelService = new IssueLabelService();
}
/**
* Returns the labelMap belongs to a specific project
*/
get projectLabels() {
const projectId = this.rootStore.app.router.query?.projectId;
if (!projectId) return;
return Object.values(this.labelMap).filter((label) => label.project === projectId);
}
/**
* Returns the labelMap in a tree format
*/
get projectLabelsTree() {
if (!this.projectLabels) return;
return buildTree(this.projectLabels);
}
/**
* Fetches all the labelMap belongs to a specific project
* @param workspaceSlug
* @param projectId
* @returns Promise<IIssueLabel[]>
*/
fetchProjectLabels = async (workspaceSlug: string, projectId: string) => {
const response = await this.issueLabelService.getProjectIssueLabels(workspaceSlug, projectId);
runInAction(() => {
response.forEach((label) => {
set(this.labelMap, [label.id], label);
});
});
return response;
};
/**
* Creates a new label for a specific project and add it to the store
* @param workspaceSlug
* @param projectId
* @param data
* @returns Promise<IIssueLabel>
*/
createLabel = async (workspaceSlug: string, projectId: string, data: Partial<IIssueLabel>) => {
const response = await this.issueLabelService.createIssueLabel(workspaceSlug, projectId, data);
runInAction(() => {
set(this.labelMap, [response.id], response);
});
return response;
};
/**
* Updates a label for a specific project and update it in the store
* @param workspaceSlug
* @param projectId
* @param labelId
* @param data
* @returns Promise<IIssueLabel>
*/
updateLabel = async (workspaceSlug: string, projectId: string, labelId: string, data: Partial<IIssueLabel>) => {
const originalLabel = this.labelMap[labelId];
try {
runInAction(() => {
set(this.labelMap, [labelId], { ...this.labelMap[labelId], ...data });
});
const response = await this.issueLabelService.patchIssueLabel(workspaceSlug, projectId, labelId, data);
return response;
} catch (error) {
console.log("Failed to update label from project store");
runInAction(() => {
set(this.labelMap, [labelId], originalLabel);
});
throw error;
}
};
/**
* updates the sort order of a label and updates the label information using API.
* @param workspaceSlug
* @param projectId
* @param labelId
* @param parentId
* @param index
* @param isSameParent
* @param prevIndex
* @returns
*/
updateLabelPosition = async (
workspaceSlug: string,
projectId: string,
labelId: string,
parentId: string | null | undefined,
index: number,
isSameParent: boolean,
prevIndex: number | undefined
) => {
const currLabel = this.labelMap?.[labelId];
const labelTree = this.projectLabelsTree;
let currentArray: IIssueLabel[];
if (!currLabel || !labelTree) return;
const data: Partial<IIssueLabel> = { parent: parentId };
//find array in which the label is to be added
if (!parentId) currentArray = labelTree;
else currentArray = labelTree?.find((label) => label.id === parentId)?.children || [];
//Add the array at the destination
if (isSameParent && prevIndex !== undefined) currentArray.splice(prevIndex, 1);
currentArray.splice(index, 0, currLabel);
//if currently adding to a new array, then let backend assign a sort order
if (currentArray.length > 1) {
let prevSortOrder: number | undefined, nextSortOrder: number | undefined;
if (typeof currentArray[index - 1] !== "undefined") {
prevSortOrder = currentArray[index - 1].sort_order;
}
if (typeof currentArray[index + 1] !== "undefined") {
nextSortOrder = currentArray[index + 1].sort_order;
}
let sortOrder: number;
//based on the next and previous labelMap calculate current sort order
if (prevSortOrder && nextSortOrder) {
sortOrder = (prevSortOrder + nextSortOrder) / 2;
} else if (nextSortOrder) {
sortOrder = nextSortOrder + 10000;
} else {
sortOrder = prevSortOrder! / 2;
}
data.sort_order = sortOrder;
}
return this.updateLabel(workspaceSlug, projectId, labelId, data);
};
/**
* Delete the label from the project and remove it from the labelMap object
* @param workspaceSlug
* @param projectId
* @param labelId
*/
deleteLabel = async (workspaceSlug: string, projectId: string, labelId: string) => {
const originalLabel = this.labelMap[labelId];
try {
if (!this.labelMap[labelId]) return;
runInAction(() => {
delete this.labelMap[labelId];
});
// deleting using api
await this.issueLabelService.deleteIssueLabel(workspaceSlug, projectId, labelId);
} catch (error) {
console.log("Failed to delete label from project store");
// reverting back to original label list
runInAction(() => {
set(this.labelMap, [labelId], originalLabel);
});
}
};
}

View File

@ -0,0 +1,63 @@
import { action, computed, makeObservable, runInAction } from "mobx";
import { set } from "lodash";
// services
import { IssueLabelService } from "services/issue";
// types
import { RootStore } from "store/root.store";
import { IIssueLabel } from "types";
export interface IWorkspaceLabelStore {
// computed
workspaceLabels: IIssueLabel[] | undefined;
// actions
fetchWorkspaceLabels: (workspaceSlug: string) => Promise<IIssueLabel[]>;
}
export class WorkspaceLabelStore implements IWorkspaceLabelStore {
// root store
rootStore;
// root store labelMap
labelMap: Record<string, IIssueLabel> = {};
// services
issueLabelService;
constructor(_rootStore: RootStore) {
makeObservable(this, {
// computed
workspaceLabels: computed,
// actions
fetchWorkspaceLabels: action,
});
// root store
this.rootStore = _rootStore;
this.labelMap = this.rootStore.labelRoot.labelMap;
// services
this.issueLabelService = new IssueLabelService();
}
/**
* Returns the labelMap belongs to a specific workspace
*/
get workspaceLabels() {
const currentWorkspaceDetails = this.rootStore.workspaceRoot.currentWorkspace;
if (!currentWorkspaceDetails) return;
return Object.values(this.labelMap).filter((label) => label.workspace === currentWorkspaceDetails.id);
}
/**
* Fetches all the labelMap belongs to a specific project
* @param workspaceSlug
* @param projectId
* @returns Promise<IIssueLabel[]>
*/
fetchWorkspaceLabels = async (workspaceSlug: string) => {
const response = await this.issueLabelService.getWorkspaceIssueLabels(workspaceSlug);
runInAction(() => {
response.forEach((label) => {
set(this.labelMap, [label.id], label);
});
});
return response;
};
}

View File

@ -8,22 +8,20 @@ import { IssueLabelService, IssueService } from "services/issue";
import { ProjectService, ProjectStateService } from "services/project"; import { ProjectService, ProjectStateService } from "services/project";
export interface IProjectStore { export interface IProjectStore {
// states
loader: boolean; loader: boolean;
error: any | null; error: any | null;
// observables
searchQuery: string; searchQuery: string;
projectId: string | null;
projectMap: { projectMap: {
[projectId: string]: IProject; // projectId: project Info [projectId: string]: IProject; // projectId: project Info
}; };
// computed // computed
searchedProjects: string[]; searchedProjects: string[];
workspaceProjects: string[] | null; workspaceProjects: string[] | null;
joinedProjects: string[]; joinedProjects: string[];
favoriteProjects: string[]; favoriteProjects: string[];
currentProjectDetails: IProject | undefined; currentProjectDetails: IProject | undefined;
// actions // actions
setSearchQuery: (query: string) => void; setSearchQuery: (query: string) => void;
getProjectById: (projectId: string) => IProject | null; getProjectById: (projectId: string) => IProject | null;
@ -43,15 +41,14 @@ export interface IProjectStore {
} }
export class ProjectStore implements IProjectStore { export class ProjectStore implements IProjectStore {
// states
loader: boolean = false; loader: boolean = false;
error: any | null = null; error: any | null = null;
// observables
projectId: string | null = null;
searchQuery: string = ""; searchQuery: string = "";
projectMap: { projectMap: {
[projectId: string]: IProject; // projectId: project Info [projectId: string]: IProject; // projectId: project Info
} = {}; } = {};
// root store // root store
rootStore: RootStore; rootStore: RootStore;
// service // service
@ -62,24 +59,19 @@ export class ProjectStore implements IProjectStore {
constructor(_rootStore: RootStore) { constructor(_rootStore: RootStore) {
makeObservable(this, { makeObservable(this, {
// observable // states
loader: observable.ref, loader: observable.ref,
error: observable.ref, error: observable.ref,
// observables
searchQuery: observable.ref, searchQuery: observable.ref,
projectId: observable.ref,
projectMap: observable, projectMap: observable,
// computed // computed
searchedProjects: computed, searchedProjects: computed,
workspaceProjects: computed, workspaceProjects: computed,
currentProjectDetails: computed, currentProjectDetails: computed,
joinedProjects: computed, joinedProjects: computed,
favoriteProjects: computed, favoriteProjects: computed,
// actions
// action
setSearchQuery: action, setSearchQuery: action,
fetchProjects: action, fetchProjects: action,
fetchProjectDetails: action, fetchProjectDetails: action,

View File

@ -6,11 +6,11 @@ import { CycleStore, ICycleStore } from "./cycle.store";
import { IProjectViewsStore, ProjectViewsStore } from "./project-view.store"; import { IProjectViewsStore, ProjectViewsStore } from "./project-view.store";
import { IModuleStore, ModulesStore } from "./module.store"; import { IModuleStore, ModulesStore } from "./module.store";
import { IUserStore, UserStore } from "./user"; import { IUserStore, UserStore } from "./user";
import { ILabelStore, LabelStore } from "./label.store";
import { IWorkspaceRootStore, WorkspaceRootStore } from "./workspace"; import { IWorkspaceRootStore, WorkspaceRootStore } from "./workspace";
import { IssueRootStore, IIssueRootStore } from "./issue/root.store"; import { IssueRootStore, IIssueRootStore } from "./issue/root.store";
import { IStateStore, StateStore } from "./state.store"; import { IStateStore, StateStore } from "./state.store";
import { IPageStore, PageStore } from "./page.store"; import { IPageStore, PageStore } from "./page.store";
import { ILabelRootStore, LabelRootStore } from "./label";
enableStaticRendering(typeof window === "undefined"); enableStaticRendering(typeof window === "undefined");
@ -19,11 +19,11 @@ export class RootStore {
user: IUserStore; user: IUserStore;
workspaceRoot: IWorkspaceRootStore; workspaceRoot: IWorkspaceRootStore;
projectRoot: IProjectRootStore; projectRoot: IProjectRootStore;
labelRoot: ILabelRootStore;
cycle: ICycleStore; cycle: ICycleStore;
module: IModuleStore; module: IModuleStore;
projectView: IProjectViewsStore; projectView: IProjectViewsStore;
page: IPageStore; page: IPageStore;
label: ILabelStore;
issue: IIssueRootStore; issue: IIssueRootStore;
state: IStateStore; state: IStateStore;
@ -32,8 +32,8 @@ export class RootStore {
this.user = new UserStore(this); this.user = new UserStore(this);
this.workspaceRoot = new WorkspaceRootStore(this); this.workspaceRoot = new WorkspaceRootStore(this);
this.projectRoot = new ProjectRootStore(this); this.projectRoot = new ProjectRootStore(this);
this.labelRoot = new LabelRootStore(this);
// independent stores // independent stores
this.label = new LabelStore(this);
this.state = new StateStore(this); this.state = new StateStore(this);
this.issue = new IssueRootStore(this); this.issue = new IssueRootStore(this);
this.cycle = new CycleStore(this); this.cycle = new CycleStore(this);

View File

@ -42,8 +42,8 @@ export class WorkspaceRootStore implements IWorkspaceRootStore {
// root store // root store
rootStore; rootStore;
// sub-stores // sub-stores
webhook: WebhookStore; webhook: IWebhookStore;
apiToken: ApiTokenStore; apiToken: IApiTokenStore;
constructor(_rootStore: RootStore) { constructor(_rootStore: RootStore) {
makeObservable(this, { makeObservable(this, {