chore: removed unused hooks and components (#2611)

* chore: remove unused hooks

* chore: removed useProjectMembers hook

* chore: removed issue hooks

* fix: build errors
This commit is contained in:
Aaryan Khandelwal 2023-11-02 17:11:33 +05:30 committed by GitHub
parent c987c6f308
commit d5fd69354e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 229 additions and 2267 deletions

View File

@ -1,12 +1,13 @@
import React, { FC, Dispatch, SetStateAction, useEffect, useState } from "react";
import { Command } from "cmdk";
import { THEME_OPTIONS } from "constants/themes";
import { useTheme } from "next-themes";
import { Settings } from "lucide-react";
import { observer } from "mobx-react-lite";
// hooks
import useToast from "hooks/use-toast";
import { useMobxStore } from "lib/mobx/store-provider";
// constants
import { THEME_OPTIONS } from "constants/themes";
type Props = {
setIsPaletteOpen: Dispatch<SetStateAction<boolean>>;

View File

@ -1,19 +1,19 @@
import { Dispatch, SetStateAction, useCallback, FC } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { mutate } from "swr";
import { Command } from "cmdk";
import { Check } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// services
import { IssueService } from "services/issue";
// hooks
import useProjectMembers from "hooks/use-project-members";
// constants
import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
// ui
import { Avatar } from "@plane/ui";
// icons
import { Check } from "lucide-react";
// types
import { IUser, IIssue } from "types";
// constants
import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
type Props = {
setIsPaletteOpen: Dispatch<SetStateAction<boolean>>;
@ -24,11 +24,14 @@ type Props = {
// services
const issueService = new IssueService();
export const ChangeIssueAssignee: FC<Props> = ({ setIsPaletteOpen, issue, user }) => {
export const ChangeIssueAssignee: FC<Props> = observer((props) => {
const { setIsPaletteOpen, issue, user } = props;
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
const { members } = useProjectMembers(workspaceSlug as string, projectId as string);
const { project: projectStore } = useMobxStore();
const members = projectId ? projectStore.members?.[projectId.toString()] : undefined;
const options =
members?.map(({ member }) => ({
@ -104,4 +107,4 @@ export const ChangeIssueAssignee: FC<Props> = ({ setIsPaletteOpen, issue, user }
))}
</>
);
};
});

View File

@ -89,6 +89,7 @@ const LabelPill = ({ labelId }: { labelId: string }) => {
/>
);
};
const EstimatePoint = ({ point }: { point: string }) => {
const { estimateValue, isEstimateActive } = useEstimateOption(Number(point));
const currentPoint = Number(point) + 1;

View File

@ -1,349 +0,0 @@
import React from "react";
import { X } from "lucide-react";
// ui
import { Avatar, PriorityIcon, StateGroupIcon } from "@plane/ui";
// helpers
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
// types
import { IIssueFilterOptions, IIssueLabels, IState, IUserLite, TStateGroups } from "types";
// constants
import { STATE_GROUP_COLORS } from "constants/state";
type Props = {
filters: Partial<IIssueFilterOptions>;
setFilters: (updatedFilter: Partial<IIssueFilterOptions>) => void;
clearAllFilters: (...args: any) => void;
labels: IIssueLabels[] | undefined;
members: IUserLite[] | undefined;
states: IState[] | undefined;
};
export const FiltersList: React.FC<Props> = ({ filters, setFilters, clearAllFilters, labels, members, states }) => {
if (!filters) return <></>;
const nullFilters = Object.keys(filters).filter((key) => filters[key as keyof IIssueFilterOptions] === null);
return (
<div className="flex flex-1 flex-wrap items-center gap-2 text-xs">
{Object.keys(filters).map((filterKey) => {
const key = filterKey as keyof typeof filters;
if (filters[key] === null) return null;
return (
<div
key={key}
className="flex items-center gap-x-2 rounded-full border border-custom-border-200 bg-custom-background-80 px-2 py-1"
>
<span className="capitalize text-custom-text-200">
{key === "target_date" ? "Due Date" : replaceUnderscoreIfSnakeCase(key)}:
</span>
{filters[key] === null || (filters[key]?.length ?? 0) <= 0 ? (
<span className="inline-flex items-center px-2 py-0.5 font-medium">None</span>
) : Array.isArray(filters[key]) ? (
<div className="space-x-2">
<div className="flex flex-wrap items-center gap-1">
{key === "state"
? filters.state?.map((stateId: string) => {
const state = states?.find((s) => s.id === stateId);
return (
<p
key={state?.id}
className="inline-flex items-center gap-x-1 rounded-full px-2 py-0.5 font-medium"
style={{
color: state?.color,
backgroundColor: `${state?.color}20`,
}}
>
<span>
<StateGroupIcon stateGroup={state?.group ?? "backlog"} color={state?.color} />
</span>
<span>{state?.name ?? ""}</span>
<span
className="cursor-pointer"
onClick={() =>
setFilters({
state: filters.state?.filter((s: any) => s !== stateId),
})
}
>
<X className="h-3 w-3" />
</span>
</p>
);
})
: key === "state_group"
? filters.state_group?.map((stateGroup) => {
const group = stateGroup as TStateGroups;
return (
<p
key={group}
className="inline-flex items-center gap-x-1 rounded-full px-2 py-0.5 capitalize"
style={{
color: STATE_GROUP_COLORS[group],
backgroundColor: `${STATE_GROUP_COLORS[group]}20`,
}}
>
<span>
<StateGroupIcon stateGroup={group} color={undefined} />
</span>
<span>{group}</span>
<span
className="cursor-pointer"
onClick={() =>
setFilters({
state_group: filters.state_group?.filter((g) => g !== group),
})
}
>
<X className="h-3 w-3" />
</span>
</p>
);
})
: key === "priority"
? filters.priority?.map((priority: any) => (
<p
key={priority}
className={`inline-flex items-center gap-x-1 rounded-full px-2 py-0.5 capitalize ${
priority === "urgent"
? "bg-red-500/20 text-red-500"
: priority === "high"
? "bg-orange-500/20 text-orange-500"
: priority === "medium"
? "bg-yellow-500/20 text-yellow-500"
: priority === "low"
? "bg-green-500/20 text-green-500"
: "bg-custom-background-90 text-custom-text-200"
}`}
>
<span>
<PriorityIcon priority={priority} className="h-3 w-3" />
</span>
<span>{priority === "null" ? "None" : priority}</span>
<span
className="cursor-pointer"
onClick={() =>
setFilters({
priority: filters.priority?.filter((p: any) => p !== priority),
})
}
>
<X className="h-3 w-3" />
</span>
</p>
))
: key === "mentions"
? filters.mentions?.map((mentionId: string) => {
const member = members?.find((m) => m.id === mentionId);
return (
<div
key={mentionId}
className="inline-flex items-center gap-x-1 rounded-full bg-custom-background-90 px-1"
>
<Avatar name={member?.display_name} src={member?.avatar} showTooltip={false} />
<span>{member?.display_name}</span>
<span
className="cursor-pointer"
onClick={() =>
setFilters({
mentions: filters.mentions?.filter((p: any) => p !== mentionId),
})
}
>
<X className="h-3 w-3" />
</span>
</div>
);
})
: key === "assignees"
? filters.assignees?.map((memberId: string) => {
const member = members?.find((m) => m.id === memberId);
return (
<div
key={memberId}
className="inline-flex items-center gap-x-1 rounded-full bg-custom-background-90 px-1"
>
<Avatar name={member?.display_name} src={member?.avatar} showTooltip={false} />
<span>{member?.display_name}</span>
<span
className="cursor-pointer"
onClick={() =>
setFilters({
subscriber: filters.subscriber?.filter((p: any) => p !== memberId),
})
}
>
<X className="h-3 w-3" />
</span>
</div>
);
})
: key === "created_by"
? filters.created_by?.map((memberId: string) => {
const member = members?.find((m) => m.id === memberId);
return (
<div
key={`${memberId}-${key}`}
className="inline-flex items-center gap-x-1 rounded-full bg-custom-background-90 px-1 capitalize"
>
<Avatar name={member?.display_name} src={member?.avatar} showTooltip={false} />
<span>{member?.display_name}</span>
<span
className="cursor-pointer"
onClick={() =>
setFilters({
created_by: filters.created_by?.filter((p: any) => p !== memberId),
})
}
>
<X className="h-3 w-3" />
</span>
</div>
);
})
: key === "labels"
? filters.labels?.map((labelId: string) => {
const label = labels?.find((l) => l.id === labelId);
if (!label) return null;
const color = label.color !== "" ? label.color : "#0f172a";
return (
<div
className="inline-flex items-center gap-x-1 rounded-full px-2 py-0.5"
style={{
color: color,
backgroundColor: `${color}20`, // add 20% opacity
}}
key={labelId}
>
<div
className="h-1.5 w-1.5 rounded-full"
style={{
backgroundColor: color,
}}
/>
<span>{label.name}</span>
<span
className="cursor-pointer"
onClick={() =>
setFilters({
labels: filters.labels?.filter((l: any) => l !== labelId),
})
}
>
<X
className="h-3 w-3"
style={{
color: color,
}}
/>
</span>
</div>
);
})
: key === "start_date"
? filters.start_date?.map((date: string) => {
if (filters.start_date && filters.start_date.length <= 0) return null;
const splitDate = date.split(";");
return (
<div
key={date}
className="inline-flex items-center gap-x-1 rounded-full border border-custom-border-200 bg-custom-background-100 px-1 py-0.5"
>
<div className="h-1.5 w-1.5 rounded-full" />
<span className="capitalize">
{splitDate[1]} {renderShortDateWithYearFormat(splitDate[0])}
</span>
<span
className="cursor-pointer"
onClick={() =>
setFilters({
start_date: filters.start_date?.filter((d: any) => d !== date),
})
}
>
<X className="h-3 w-3" />
</span>
</div>
);
})
: key === "target_date"
? filters.target_date?.map((date: string) => {
if (filters.target_date && filters.target_date.length <= 0) return null;
const splitDate = date.split(";");
return (
<div
key={date}
className="inline-flex items-center gap-x-1 rounded-full border border-custom-border-200 bg-custom-background-100 px-1 py-0.5"
>
<div className="h-1.5 w-1.5 rounded-full" />
<span className="capitalize">
{splitDate[1]} {renderShortDateWithYearFormat(splitDate[0])}
</span>
<span
className="cursor-pointer"
onClick={() =>
setFilters({
target_date: filters.target_date?.filter((d: any) => d !== date),
})
}
>
<X className="h-3 w-3" />
</span>
</div>
);
})
: (filters[key] as any)?.join(", ")}
<button
type="button"
onClick={() =>
setFilters({
[key]: null,
})
}
>
<X className="h-3 w-3" />
</button>
</div>
</div>
) : (
<div className="flex items-center gap-x-1 capitalize">
{filters[key as keyof typeof filters]}
<button
type="button"
onClick={() =>
setFilters({
[key]: null,
})
}
>
<X className="h-3 w-3" />
</button>
</div>
)}
</div>
);
})}
{Object.keys(filters).length > 0 && nullFilters.length !== Object.keys(filters).length && (
<button
type="button"
onClick={clearAllFilters}
className="flex items-center gap-x-1 rounded-full border border-custom-border-200 bg-custom-background-80 px-3 py-1.5 text-xs"
>
<span>Clear all filters</span>
<X className="h-3 w-3" />
</button>
)}
</div>
);
};

View File

@ -1,3 +1,2 @@
export * from "./date-filter-modal";
export * from "./date-filter-select";
export * from "./filters-list";

View File

@ -2,7 +2,6 @@ export * from "./filters";
export * from "./modals";
export * from "./sidebar";
export * from "./theme";
export * from "./views";
export * from "./activity";
export * from "./reaction-selector";
export * from "./image-picker-popover";

View File

@ -1,6 +1,6 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import useSWR, { mutate } from "swr";
import useSWR from "swr";
// react hook form
import { SubmitHandler, useForm } from "react-hook-form";
// headless ui
@ -9,7 +9,6 @@ import { Combobox, Dialog, Transition } from "@headlessui/react";
import { IssueService } from "services/issue";
// hooks
import useToast from "hooks/use-toast";
import useIssuesView from "hooks/use-issues-view";
// ui
import { Button, LayersIcon } from "@plane/ui";
// icons
@ -17,15 +16,7 @@ import { Search } from "lucide-react";
// types
import { IUser, IIssue } from "types";
// fetch keys
import {
CYCLE_DETAILS,
CYCLE_ISSUES_WITH_PARAMS,
MODULE_DETAILS,
MODULE_ISSUES_WITH_PARAMS,
PROJECT_ISSUES_LIST,
PROJECT_ISSUES_LIST_WITH_PARAMS,
VIEW_ISSUES,
} from "constants/fetch-keys";
import { PROJECT_ISSUES_LIST } from "constants/fetch-keys";
type FormInput = {
delete_issue_ids: string[];
@ -43,7 +34,7 @@ export const BulkDeleteIssuesModal: React.FC<Props> = (props) => {
const { isOpen, onClose, user } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
const { workspaceSlug, projectId } = router.query;
// states
const [query, setQuery] = useState("");
// fetching project issues.
@ -53,9 +44,6 @@ export const BulkDeleteIssuesModal: React.FC<Props> = (props) => {
);
const { setToastAlert } = useToast();
const { displayFilters, params } = useIssuesView();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { order_by, group_by, ...viewGanttParams } = params;
const {
handleSubmit,
@ -89,14 +77,6 @@ export const BulkDeleteIssuesModal: React.FC<Props> = (props) => {
if (!Array.isArray(data.delete_issue_ids)) data.delete_issue_ids = [data.delete_issue_ids];
const ganttFetchKey = cycleId
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString())
: moduleId
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString())
: viewId
? VIEW_ISSUES(viewId.toString(), viewGanttParams)
: PROJECT_ISSUES_LIST_WITH_PARAMS(projectId?.toString() ?? "");
await issueService
.bulkDeleteIssues(
workspaceSlug as string,
@ -113,17 +93,6 @@ export const BulkDeleteIssuesModal: React.FC<Props> = (props) => {
message: "Issues deleted successfully!",
});
if (displayFilters.layout === "gantt_chart") mutate(ganttFetchKey);
else {
if (cycleId) {
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params));
mutate(CYCLE_DETAILS(cycleId.toString()));
} else if (moduleId) {
mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
mutate(MODULE_DETAILS(moduleId as string));
} else mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params));
}
handleClose();
})
.catch(() =>

View File

@ -1,30 +1,16 @@
import React, { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { mutate } from "swr";
// headless ui
import { Combobox, Dialog, Transition } from "@headlessui/react";
import { Rocket, Search, X } from "lucide-react";
// services
import { ProjectService } from "services/project";
// hooks
import useToast from "hooks/use-toast";
import useIssuesView from "hooks/use-issues-view";
import useDebounce from "hooks/use-debounce";
// ui
import { Button, LayersIcon, Loader, ToggleSwitch, Tooltip } from "@plane/ui";
// icons
import { Rocket, Search, X } from "lucide-react";
// types
import { ISearchIssueResponse, TProjectIssuesSearchParams } from "types";
// fetch-keys
import {
CYCLE_DETAILS,
CYCLE_ISSUES_WITH_PARAMS,
MODULE_DETAILS,
MODULE_ISSUES_WITH_PARAMS,
} from "constants/fetch-keys";
type Props = {
isOpen: boolean;
@ -53,12 +39,10 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
const debouncedSearchTerm: string = useDebounce(searchTerm, 500);
const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
const { workspaceSlug, projectId } = router.query;
const { setToastAlert } = useToast();
const { params } = useIssuesView();
const handleClose = () => {
onClose();
setSearchTerm("");
@ -81,16 +65,6 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
await handleOnSubmit(selectedIssues).finally(() => setIsSubmitting(false));
if (cycleId) {
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params));
mutate(CYCLE_DETAILS(cycleId as string));
}
if (moduleId) {
mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
mutate(MODULE_DETAILS(moduleId as string));
}
handleClose();
setToastAlert({

View File

@ -15,6 +15,7 @@ type Props = {
export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, handleEditLink, userAuth }) => {
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
return (
<>
{links.map((link) => (

View File

@ -5,7 +5,6 @@ import Image from "next/image";
import { Tab } from "@headlessui/react";
// hooks
import useLocalStorage from "hooks/use-local-storage";
import useIssuesView from "hooks/use-issues-view";
// images
import emptyLabel from "public/empty-state/empty_label.svg";
import emptyMembers from "public/empty-state/empty_members.svg";
@ -47,8 +46,6 @@ export const SidebarProgressStats: React.FC<Props> = ({
noBackground,
isPeekView = false,
}) => {
const { filters, setFilters } = useIssuesView();
const { storedValue: tab, setValue: setTab } = useLocalStorage("tab", "Assignees");
const currentValue = (tab: string | null) => {
@ -145,16 +142,17 @@ export const SidebarProgressStats: React.FC<Props> = ({
total={assignee.total_issues}
{...(!isPeekView && {
onClick: () => {
if (filters?.assignees?.includes(assignee.assignee_id ?? ""))
setFilters({
assignees: filters?.assignees?.filter((a) => a !== assignee.assignee_id),
});
else
setFilters({
assignees: [...(filters?.assignees ?? []), assignee.assignee_id ?? ""],
});
// TODO: set filters here
// if (filters?.assignees?.includes(assignee.assignee_id ?? ""))
// setFilters({
// assignees: filters?.assignees?.filter((a) => a !== assignee.assignee_id),
// });
// else
// setFilters({
// assignees: [...(filters?.assignees ?? []), assignee.assignee_id ?? ""],
// });
},
selected: filters?.assignees?.includes(assignee.assignee_id ?? ""),
// selected: filters?.assignees?.includes(assignee.assignee_id ?? ""),
})}
/>
);
@ -203,14 +201,15 @@ export const SidebarProgressStats: React.FC<Props> = ({
completed={label.completed_issues}
total={label.total_issues}
{...(!isPeekView && {
// TODO: set filters here
onClick: () => {
if (filters.labels?.includes(label.label_id ?? ""))
setFilters({
labels: filters?.labels?.filter((l) => l !== label.label_id),
});
else setFilters({ labels: [...(filters?.labels ?? []), label.label_id ?? ""] });
// if (filters.labels?.includes(label.label_id ?? ""))
// setFilters({
// labels: filters?.labels?.filter((l) => l !== label.label_id),
// });
// else setFilters({ labels: [...(filters?.labels ?? []), label.label_id ?? ""] });
},
selected: filters?.labels?.includes(label.label_id ?? ""),
// selected: filters?.labels?.includes(label.label_id ?? ""),
})}
/>
))

View File

@ -1,62 +0,0 @@
import { useEffect } from "react";
// react hook form
import { useFormContext } from "react-hook-form";
// hooks
import useProjectDetails from "hooks/use-project-details";
// components
import { InlineCreateIssueFormWrapper } from "components/core";
// types
import { IIssue } from "types";
type Props = {
isOpen: boolean;
handleClose: () => void;
onSuccess?: (data: IIssue) => Promise<void> | void;
prePopulatedData?: Partial<IIssue>;
};
const InlineInput = () => {
const { projectDetails } = useProjectDetails();
const { register, setFocus } = useFormContext();
useEffect(() => {
setFocus("name");
}, [setFocus]);
return (
<>
<div className="w-[14px] h-[14px] rounded-full border border-custom-border-1000 flex-shrink-0" />
<h4 className="text-sm text-custom-text-400">{projectDetails?.identifier ?? "..."}</h4>
<input
type="text"
autoComplete="off"
placeholder="Issue Title"
{...register("name", {
required: "Issue title is required.",
})}
className="w-full px-2 rounded-md bg-transparent text-sm font-medium leading-5 text-custom-text-200 outline-none"
/>
</>
);
};
export const GanttInlineCreateIssueForm: React.FC<Props> = (props) => (
<>
<InlineCreateIssueFormWrapper
className="flex py-3 px-4 border-[0.5px] border-custom-border-100 mr-2.5 items-center rounded gap-x-2 bg-custom-background-100 shadow-custom-shadow-sm"
{...props}
>
<InlineInput />
</InlineCreateIssueFormWrapper>
{props.isOpen && (
<p className="text-xs ml-3 mt-3 italic text-custom-text-200">
Press {"'"}Enter{"'"} to add another issue
</p>
)}
</>
);

View File

@ -1 +0,0 @@
export * from "./inline-issue-create-wrapper";

View File

@ -1,238 +0,0 @@
import { useEffect, useRef } from "react";
import { useRouter } from "next/router";
import { mutate } from "swr";
import { useForm, FormProvider } from "react-hook-form";
import { Transition } from "@headlessui/react";
// services
import { ModuleService } from "services/module.service";
import { IssueService, IssueDraftService } from "services/issue";
// hooks
import useToast from "hooks/use-toast";
import useUser from "hooks/use-user";
import useKeypress from "hooks/use-keypress";
import useIssuesView from "hooks/use-issues-view";
import useMyIssues from "hooks/my-issues/use-my-issues";
import useGanttChartIssues from "hooks/gantt-chart/issue-view";
// import useCalendarIssuesView from "hooks/use-calendar-issues-view";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
// import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view";
// helpers
import { getFetchKeysForIssueMutation } from "helpers/string.helper";
// fetch-keys
import {
USER_ISSUE,
SUB_ISSUES,
CYCLE_ISSUES_WITH_PARAMS,
MODULE_ISSUES_WITH_PARAMS,
CYCLE_DETAILS,
MODULE_DETAILS,
PROJECT_ISSUES_LIST_WITH_PARAMS,
PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS,
} from "constants/fetch-keys";
// types
import { IIssue } from "types";
const defaultValues: Partial<IIssue> = {
name: "",
};
type Props = {
isOpen: boolean;
handleClose: () => void;
onSuccess?: (data: IIssue) => Promise<void> | void;
prePopulatedData?: Partial<IIssue>;
className?: string;
children?: React.ReactNode;
};
const issueService = new IssueService();
const issueDraftService = new IssueDraftService();
const moduleService = new ModuleService();
export const addIssueToCycle = async (
workspaceSlug: string,
projectId: string,
issueId: string,
cycleId: string,
user: any,
params: any
) => {
if (!workspaceSlug || !projectId) return;
await issueService
.addIssueToCycle(
workspaceSlug as string,
projectId.toString(),
cycleId,
{
issues: [issueId],
},
user
)
.then(() => {
if (cycleId) {
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId, params));
mutate(CYCLE_DETAILS(cycleId as string));
}
});
};
export const addIssueToModule = async (
workspaceSlug: string,
projectId: string,
issueId: string,
moduleId: string,
user: any,
params: any
) => {
await moduleService
.addIssuesToModule(
workspaceSlug as string,
projectId.toString(),
moduleId as string,
{
issues: [issueId],
},
user
)
.then(() => {
if (moduleId) {
mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
mutate(MODULE_DETAILS(moduleId as string));
}
});
};
export const InlineCreateIssueFormWrapper: React.FC<Props> = (props) => {
const { isOpen, handleClose, onSuccess, prePopulatedData, children, className } = props;
const ref = useRef<HTMLFormElement>(null);
const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
const isDraftIssues = router.pathname?.split("/")?.[4] === "draft-issues";
const { user } = useUser();
const { setToastAlert } = useToast();
const { displayFilters, params } = useIssuesView();
// const { params: calendarParams } = useCalendarIssuesView();
const { ...viewGanttParams } = params;
// const { params: spreadsheetParams } = useSpreadsheetIssuesView();
const { groupedIssues, mutateMyIssues } = useMyIssues(workspaceSlug?.toString());
const { params: ganttParams } = useGanttChartIssues(workspaceSlug?.toString(), projectId?.toString());
const method = useForm<IIssue>({ defaultValues });
const {
reset,
handleSubmit,
getValues,
formState: { errors, isSubmitting },
} = method;
useOutsideClickDetector(ref, handleClose);
useKeypress("Escape", handleClose);
useEffect(() => {
const values = getValues();
if (prePopulatedData) reset({ ...defaultValues, ...values, ...prePopulatedData });
}, [reset, prePopulatedData, getValues]);
useEffect(() => {
if (!isOpen) reset({ ...defaultValues });
}, [isOpen, reset]);
useEffect(() => {
if (!errors) return;
Object.keys(errors).forEach((key) => {
const error = errors[key as keyof IIssue];
setToastAlert({
type: "error",
title: "Error!",
message: error?.message?.toString() || "Some error occurred. Please try again.",
});
});
}, [errors, setToastAlert]);
const { ganttFetchKey } = getFetchKeysForIssueMutation({
cycleId: cycleId,
moduleId: moduleId,
viewId: viewId,
projectId: projectId?.toString() ?? "",
viewGanttParams,
ganttParams,
});
const onSubmitHandler = async (formData: IIssue) => {
if (!workspaceSlug || !projectId || !user || isSubmitting) return;
reset({ ...defaultValues });
await (!isDraftIssues
? issueService.createIssue(workspaceSlug.toString(), projectId.toString(), formData, user)
: issueDraftService.createDraftIssue(workspaceSlug.toString(), projectId.toString(), formData)
)
.then(async (res) => {
await mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params));
if (formData.cycle && formData.cycle !== "")
await addIssueToCycle(workspaceSlug.toString(), projectId.toString(), res.id, formData.cycle, user, params);
if (formData.module && formData.module !== "")
await addIssueToModule(workspaceSlug.toString(), projectId.toString(), res.id, formData.module, user, params);
if (isDraftIssues) await mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(projectId.toString() ?? "", params));
if (displayFilters.layout === "gantt_chart") await mutate(ganttFetchKey);
if (groupedIssues) await mutateMyIssues();
setToastAlert({
type: "success",
title: "Success!",
message: "Issue created successfully.",
});
if (onSuccess) await onSuccess(res);
if (formData.assignees?.some((assignee) => assignee === user?.id)) mutate(USER_ISSUE(workspaceSlug as string));
if (formData.parent && formData.parent !== "") mutate(SUB_ISSUES(formData.parent));
})
.catch((err) => {
Object.keys(err || {}).forEach((key) => {
const error = err?.[key];
const errorTitle = error ? (Array.isArray(error) ? error.join(", ") : error) : null;
setToastAlert({
type: "error",
title: "Error!",
message: errorTitle || "Some error occurred. Please try again.",
});
});
});
};
return (
<>
<Transition
show={isOpen}
enter="transition ease-in-out duration-200 transform"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="transition ease-in-out duration-200 transform"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<FormProvider {...method}>
<form ref={ref} className={className} onSubmit={handleSubmit(onSubmitHandler)}>
{children}
</form>
</FormProvider>
</Transition>
</>
);
};

View File

@ -1,17 +1,16 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import useSWR, { mutate } from "swr";
import useSWR from "swr";
import { Dialog, Transition } from "@headlessui/react";
// services
import { CycleService } from "services/cycle.service";
// hooks
import useToast from "hooks/use-toast";
import useIssuesView from "hooks/use-issues-view";
//icons
import { ContrastIcon, TransferIcon } from "@plane/ui";
import { AlertCircle, Search, X } from "lucide-react";
// fetch-key
import { CYCLE_ISSUES_WITH_PARAMS, INCOMPLETE_CYCLES_LIST } from "constants/fetch-keys";
import { INCOMPLETE_CYCLES_LIST } from "constants/fetch-keys";
// types
import { ICycle } from "types";
//helper
@ -30,15 +29,12 @@ export const TransferIssuesModal: React.FC<Props> = ({ isOpen, handleClose }) =>
const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query;
const { params } = useIssuesView();
const { setToastAlert } = useToast();
const transferIssue = async (payload: any) => {
await cycleService
.transferIssues(workspaceSlug as string, projectId as string, cycleId as string, payload)
.then(() => {
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params));
setToastAlert({
type: "success",
title: "Issues transfered successfully",

View File

@ -6,8 +6,6 @@ import { Popover } from "@headlessui/react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// contexts
import { useProjectMyMembership } from "contexts/project-member.context";
// hooks
import useToast from "hooks/use-toast";
// components
@ -38,9 +36,9 @@ export const InboxActionsHeader = observer(() => {
const { inboxIssues: inboxIssuesStore, inboxIssueDetails: inboxIssueDetailsStore, user: userStore } = useMobxStore();
const user = userStore?.currentUser;
const userRole = userStore.currentProjectRole;
const issuesList = inboxId ? inboxIssuesStore.inboxIssues[inboxId.toString()] : null;
const { memberRole } = useProjectMyMembership();
const { setToastAlert } = useToast();
const markInboxStatus = async (data: TInboxStatus) => {
@ -73,7 +71,7 @@ export const InboxActionsHeader = observer(() => {
}, [issue]);
const issueStatus = issue?.issue_inbox[0].status;
const isAllowed = memberRole.isMember || memberRole.isOwner;
const isAllowed = userRole === 15 || userRole === 20;
const today = new Date();
const tomorrow = new Date(today);

View File

@ -7,8 +7,6 @@ import { AlertTriangle, CheckCircle2, Clock, Copy, ExternalLink, Inbox, XCircle
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// contexts
import { useProjectMyMembership } from "contexts/project-member.context";
// components
import { IssueDescriptionForm, IssueDetailsSidebar, IssueReaction } from "components/issues";
import { InboxIssueActivity } from "components/inbox";
@ -35,8 +33,7 @@ export const InboxMainContent: React.FC = observer(() => {
const { inboxIssues: inboxIssuesStore, inboxIssueDetails: inboxIssueDetailsStore, user: userStore } = useMobxStore();
const user = userStore.currentUser;
const { memberRole } = useProjectMyMembership();
const userRole = userStore.currentProjectRole;
const { reset, control, watch } = useForm<IIssue>({
defaultValues,
@ -225,7 +222,7 @@ export const InboxMainContent: React.FC = observer(() => {
description_html: issueDetails.description_html,
}}
handleFormSubmit={submitChanges}
isAllowed={memberRole.isMember || memberRole.isOwner || user?.id === issueDetails.created_by}
isAllowed={userRole === 15 || userRole === 20 || user?.id === issueDetails.created_by}
/>
</div>

View File

@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Dialog, Transition } from "@headlessui/react";
import { Controller, useForm } from "react-hook-form";
import { IMentionHighlight, IMentionSuggestion, RichTextEditorWithRef } from "@plane/rich-text-editor";
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
@ -15,8 +15,6 @@ import { IssuePrioritySelect } from "components/issues/select";
import { Button, Input, ToggleSwitch } from "@plane/ui";
// types
import { IIssue } from "types";
import useProjectMembers from "hooks/use-project-members";
import useUser from "hooks/use-user";
type Props = {
isOpen: boolean;

View File

@ -1,17 +1,12 @@
import React, { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { mutate } from "swr";
import useUser from "hooks/use-user";
// headless ui
import { observer } from "mobx-react-lite";
import { Dialog, Transition } from "@headlessui/react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// services
import { IssueDraftService } from "services/issue";
// hooks
import useIssuesView from "hooks/use-issues-view";
import useToast from "hooks/use-toast";
// icons
import { AlertTriangle } from "lucide-react";
@ -19,8 +14,6 @@ import { AlertTriangle } from "lucide-react";
import { Button } from "@plane/ui";
// types
import type { IIssue } from "types";
// fetch-keys
import { PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS } from "constants/fetch-keys";
type Props = {
isOpen: boolean;
@ -31,20 +24,19 @@ type Props = {
const issueDraftService = new IssueDraftService();
export const DeleteDraftIssueModal: React.FC<Props> = (props) => {
export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
const { isOpen, handleClose, data, onSubmit } = props;
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { user: userStore } = useMobxStore();
const user = userStore.currentUser;
const { params } = useIssuesView();
const router = useRouter();
const { workspaceSlug } = router.query;
const { setToastAlert } = useToast();
const { user } = useUser();
useEffect(() => {
setIsDeleteLoading(false);
}, [isOpen]);
@ -64,7 +56,7 @@ export const DeleteDraftIssueModal: React.FC<Props> = (props) => {
.then(() => {
setIsDeleteLoading(false);
handleClose();
mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(projectId as string, params));
setToastAlert({
title: "Success",
message: "Draft Issue deleted successfully",
@ -146,4 +138,4 @@ export const DeleteDraftIssueModal: React.FC<Props> = (props) => {
</Dialog>
</Transition.Root>
);
};
});

View File

@ -9,28 +9,14 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { IssueService, IssueDraftService } from "services/issue";
import { ModuleService } from "services/module.service";
// hooks
import useUser from "hooks/use-user";
import useIssuesView from "hooks/use-issues-view";
import useToast from "hooks/use-toast";
import useLocalStorage from "hooks/use-local-storage";
import useMyIssues from "hooks/my-issues/use-my-issues";
// components
import { DraftIssueForm } from "components/issues";
// types
import type { IIssue } from "types";
// fetch-keys
import {
PROJECT_ISSUES_DETAILS,
USER_ISSUE,
SUB_ISSUES,
PROJECT_ISSUES_LIST_WITH_PARAMS,
CYCLE_ISSUES_WITH_PARAMS,
MODULE_ISSUES_WITH_PARAMS,
VIEW_ISSUES,
PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS,
CYCLE_DETAILS,
MODULE_DETAILS,
} from "constants/fetch-keys";
import { PROJECT_ISSUES_DETAILS, USER_ISSUE, SUB_ISSUES } from "constants/fetch-keys";
interface IssuesModalProps {
data?: IIssue | null;
@ -77,21 +63,15 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
const [prePopulateData, setPreloadedData] = useState<Partial<IIssue> | undefined>(undefined);
const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
const { project: projectStore } = useMobxStore();
const { project: projectStore, user: userStore } = useMobxStore();
const user = userStore.currentUser;
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
const { displayFilters, params } = useIssuesView();
const { ...viewGanttParams } = params;
const { user } = useUser();
const { clearValue: clearDraftIssueLocalStorage } = useLocalStorage("draftedIssue", {});
const { groupedIssues, mutateMyIssues } = useMyIssues(workspaceSlug?.toString());
const { setToastAlert } = useToast();
const onClose = () => {
@ -184,31 +164,19 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null);
}, [activeProject, data, projectId, projects, isOpen, prePopulateData]);
const ganttFetchKey = cycleId
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString())
: moduleId
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString())
: viewId
? VIEW_ISSUES(viewId.toString(), viewGanttParams)
: PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject?.toString() ?? "");
const createDraftIssue = async (payload: Partial<IIssue>) => {
if (!workspaceSlug || !activeProject || !user) return;
await issueDraftService
.createDraftIssue(workspaceSlug as string, activeProject ?? "", payload)
.then(async () => {
mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
if (groupedIssues) mutateMyIssues();
setToastAlert({
type: "success",
title: "Success!",
message: "Issue created successfully.",
});
if (payload.assignees?.some((assignee) => assignee === user?.id)) mutate(USER_ISSUE(workspaceSlug as string));
if (payload.assignees?.some((assignee) => assignee === user?.id)) mutate(USER_ISSUE(workspaceSlug.toString()));
})
.catch(() => {
setToastAlert({
@ -231,8 +199,6 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
mutate<IIssue>(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false);
} else {
if (payload.parent) mutate(SUB_ISSUES(payload.parent.toString()));
mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
}
if (!payload.is_draft) {
@ -258,64 +224,42 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
};
const addIssueToCycle = async (issueId: string, cycleId: string) => {
if (!workspaceSlug || !activeProject) return;
if (!workspaceSlug || !activeProject || !user) return;
await issueService
.addIssueToCycle(
workspaceSlug as string,
activeProject ?? "",
cycleId,
{
issues: [issueId],
},
user
)
.then(() => {
if (cycleId) {
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId, params));
mutate(CYCLE_DETAILS(cycleId as string));
}
});
await issueService.addIssueToCycle(
workspaceSlug as string,
activeProject ?? "",
cycleId,
{
issues: [issueId],
},
user
);
};
const addIssueToModule = async (issueId: string, moduleId: string) => {
if (!workspaceSlug || !activeProject) return;
if (!workspaceSlug || !activeProject || !user) return;
await moduleService
.addIssuesToModule(
workspaceSlug as string,
activeProject ?? "",
moduleId as string,
{
issues: [issueId],
},
user
)
.then(() => {
if (moduleId) {
mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
mutate(MODULE_DETAILS(moduleId as string));
}
});
await moduleService.addIssuesToModule(
workspaceSlug as string,
activeProject ?? "",
moduleId as string,
{
issues: [issueId],
},
user
);
};
const createIssue = async (payload: Partial<IIssue>) => {
if (!workspaceSlug || !activeProject) return;
if (!workspaceSlug || !activeProject || !user) return;
await issueService
.createIssue(workspaceSlug as string, activeProject ?? "", payload, user)
.createIssue(workspaceSlug.toString(), activeProject, payload, user)
.then(async (res) => {
mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res.id, payload.cycle);
if (payload.module && payload.module !== "") await addIssueToModule(res.id, payload.module);
if (displayFilters.layout === "gantt_chart")
mutate(ganttFetchKey, {
start_target_date: true,
order_by: "sort_order",
});
if (groupedIssues) mutateMyIssues();
setToastAlert({
type: "success",
title: "Success!",
@ -400,7 +344,7 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
projectId={activeProject ?? ""}
setActiveProject={setActiveProject}
status={data ? true : false}
user={user}
user={user ?? undefined}
fieldsToShow={fieldsToShow}
/>
</Dialog.Panel>

View File

@ -1,7 +1,9 @@
import { FC, useState } from "react";
import { mutate } from "swr";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// ui icons
import { DiceIcon, DoubleCircleIcon, UserGroupIcon } from "@plane/ui";
import { CalendarDays, ContrastIcon, Link2, Plus, Signal, Tag, Triangle, User2 } from "lucide-react";
@ -23,10 +25,7 @@ import { CustomDatePicker } from "components/ui";
import { LinkModal, LinksList } from "components/core";
// types
import { ICycle, IIssue, IIssueLink, IModule, TIssuePriorities, linkDetails } from "types";
// contexts
import { useProjectMyMembership } from "contexts/project-member.context";
import { ISSUE_DETAILS } from "constants/fetch-keys";
// services
import { IssueService } from "services/issue";
@ -38,18 +37,19 @@ interface IPeekOverviewProperties {
const issueService = new IssueService();
export const PeekOverviewProperties: FC<IPeekOverviewProperties> = (props) => {
export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((props) => {
const { issue, issueUpdate, user } = props;
const [linkModal, setLinkModal] = useState(false);
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<linkDetails | null>(null);
const { user: userStore } = useMobxStore();
const userRole = userStore.currentProjectRole;
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { setToastAlert } = useToast();
const { memberRole } = useProjectMyMembership();
const handleState = (_state: string) => {
issueUpdate({ ...issue, state: _state });
};
@ -346,7 +346,12 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = (props) => {
links={issue.issue_link}
handleDeleteLink={handleDeleteLink}
handleEditLink={handleEditLink}
userAuth={memberRole}
userAuth={{
isGuest: userRole === 5,
isViewer: userRole === 10,
isMember: userRole === 15,
isOwner: userRole === 20,
}}
/>
) : null}
</div>
@ -355,4 +360,4 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = (props) => {
</div>
</>
);
};
});

View File

@ -1,16 +1,13 @@
import Link from "next/link";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR, { mutate } from "swr";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// services
import { IssueService, IssueCommentService } from "services/issue";
// hooks
import useUserAuth from "hooks/use-user-auth";
import useToast from "hooks/use-toast";
import useProjectDetails from "hooks/use-project-details";
// contexts
import { useProjectMyMembership } from "contexts/project-member.context";
// components
import {
AddComment,
@ -40,16 +37,18 @@ type Props = {
const issueService = new IssueService();
const issueCommentService = new IssueCommentService();
export const IssueMainContent: React.FC<Props> = ({ issueDetails, submitChanges, uneditable = false }) => {
export const IssueMainContent: React.FC<Props> = observer((props) => {
const { issueDetails, submitChanges, uneditable = false } = props;
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
const { setToastAlert } = useToast();
const { user } = useUserAuth();
const { memberRole } = useProjectMyMembership();
const { projectDetails } = useProjectDetails();
const { user: userStore, project: projectStore } = useMobxStore();
const user = userStore.currentUser ?? undefined;
const userRole = userStore.currentProjectRole;
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : undefined;
const { data: siblingIssues } = useSWR(
workspaceSlug && projectId && issueDetails?.parent ? SUB_ISSUES(issueDetails.parent) : null,
@ -75,7 +74,7 @@ export const IssueMainContent: React.FC<Props> = ({ issueDetails, submitChanges,
};
const handleCommentDelete = async (commentId: string) => {
if (!workspaceSlug || !projectId || !issueId) return;
if (!workspaceSlug || !projectId || !issueId || !user) return;
mutateIssueActivity((prevData: any) => prevData?.filter((p: any) => p.id !== commentId), false);
@ -85,7 +84,7 @@ export const IssueMainContent: React.FC<Props> = ({ issueDetails, submitChanges,
};
const handleAddComment = async (formData: IIssueComment) => {
if (!workspaceSlug || !issueDetails) return;
if (!workspaceSlug || !issueDetails || !user) return;
await issueCommentService
.createIssueComment(workspaceSlug.toString(), issueDetails.project, issueDetails.id, formData, user)
@ -167,7 +166,7 @@ export const IssueMainContent: React.FC<Props> = ({ issueDetails, submitChanges,
workspaceSlug={workspaceSlug as string}
issue={issueDetails}
handleFormSubmit={submitChanges}
isAllowed={memberRole.isMember || memberRole.isOwner || !uneditable}
isAllowed={userRole === 20 || userRole === 15 || !uneditable}
/>
<IssueReaction workspaceSlug={workspaceSlug} issueId={issueId} projectId={projectId} />
@ -199,4 +198,4 @@ export const IssueMainContent: React.FC<Props> = ({ issueDetails, submitChanges,
</div>
</>
);
};
});

View File

@ -1,17 +1,17 @@
import React, { useCallback, useState } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { mutate } from "swr";
import { Controller, UseFormWatch } from "react-hook-form";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import useToast from "hooks/use-toast";
import useUserAuth from "hooks/use-user-auth";
import useUserIssueNotificationSubscription from "hooks/use-issue-notification-subscription";
import useEstimateOption from "hooks/use-estimate-option";
// services
import { IssueService } from "services/issue";
import { ModuleService } from "services/module.service";
// contexts
import { useProjectMyMembership } from "contexts/project-member.context";
// components
import { LinkModal, LinksList } from "components/core";
import {
@ -72,23 +72,20 @@ type Props = {
const issueService = new IssueService();
const moduleService = new ModuleService();
export const IssueDetailsSidebar: React.FC<Props> = ({
control,
submitChanges,
issueDetail,
watch: watchIssue,
fieldsToShow = ["all"],
uneditable = false,
}) => {
export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
const { control, submitChanges, issueDetail, watch: watchIssue, fieldsToShow = ["all"], uneditable = false } = props;
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
const [linkModal, setLinkModal] = useState(false);
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<linkDetails | null>(null);
const { user: userStore } = useMobxStore();
const user = userStore.currentUser;
const userRole = userStore.currentProjectRole;
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
const { user } = useUserAuth();
const { isEstimateActive } = useEstimateOption();
const { loading, handleSubscribe, handleUnsubscribe, subscribed } = useUserIssueNotificationSubscription(
@ -97,13 +94,11 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
issueId
);
const { memberRole } = useProjectMyMembership();
const { setToastAlert } = useToast();
const handleCycleChange = useCallback(
(cycleDetails: ICycle) => {
if (!workspaceSlug || !projectId || !issueDetail) return;
if (!workspaceSlug || !projectId || !issueDetail || !user) return;
issueService
.addIssueToCycle(
@ -124,7 +119,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
const handleModuleChange = useCallback(
(moduleDetail: IModule) => {
if (!workspaceSlug || !projectId || !issueDetail) return;
if (!workspaceSlug || !projectId || !issueDetail || !user) return;
moduleService
.addIssuesToModule(
@ -262,7 +257,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
setLinkModal(true);
};
const isNotAllowed = memberRole.isGuest || memberRole.isViewer;
const isNotAllowed = userRole === 5 || userRole === 10;
return (
<>
@ -341,7 +336,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
<SidebarStateSelect
value={value}
onChange={(val: string) => submitChanges({ state: val })}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
disabled={isNotAllowed || uneditable}
/>
)}
/>
@ -362,7 +357,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
<SidebarAssigneeSelect
value={value}
onChange={(val: string[]) => submitChanges({ assignees: val })}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
disabled={isNotAllowed || uneditable}
/>
)}
/>
@ -383,7 +378,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
<SidebarPrioritySelect
value={value}
onChange={(val) => submitChanges({ priority: val })}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
disabled={isNotAllowed || uneditable}
/>
)}
/>
@ -404,7 +399,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
<SidebarEstimateSelect
value={value}
onChange={(val: number | null) => submitChanges({ estimate_point: val })}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
disabled={isNotAllowed || uneditable}
/>
)}
/>
@ -432,7 +427,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
onChange(val);
}}
issueDetails={issueDetail}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
disabled={isNotAllowed || uneditable}
/>
)}
/>
@ -457,7 +452,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
}}
watch={watchIssue}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
disabled={isNotAllowed || uneditable}
/>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("blocked")) && (
@ -478,7 +473,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
}}
watch={watchIssue}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
disabled={isNotAllowed || uneditable}
/>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("duplicate")) && (
@ -496,7 +491,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
}}
watch={watchIssue}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
disabled={isNotAllowed || uneditable}
/>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("relates_to")) && (
@ -514,7 +509,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
}}
watch={watchIssue}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
disabled={isNotAllowed || uneditable}
/>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("startDate")) && (
@ -587,7 +582,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
<SidebarCycleSelect
issueDetail={issueDetail}
handleCycleChange={handleCycleChange}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
disabled={isNotAllowed || uneditable}
/>
</div>
</div>
@ -602,7 +597,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
<SidebarModuleSelect
issueDetail={issueDetail}
handleModuleChange={handleModuleChange}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
disabled={isNotAllowed || uneditable}
/>
</div>
</div>
@ -650,7 +645,12 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
links={issueDetail.issue_link}
handleDeleteLink={handleDeleteLink}
handleEditLink={handleEditLink}
userAuth={memberRole}
userAuth={{
isGuest: userRole === 5,
isViewer: userRole === 10,
isMember: userRole === 15,
isOwner: userRole === 20,
}}
/>
) : null}
</div>
@ -660,4 +660,4 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
</div>
</>
);
};
});

View File

@ -1,10 +1,10 @@
import React, { useCallback } from "react";
// next imports
import { useRouter } from "next/router";
// swr
import { observer } from "mobx-react-lite";
import useSWR, { mutate } from "swr";
// lucide icons
import { Plus, ChevronRight, ChevronDown } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { ExistingIssuesListModal } from "components/core";
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
@ -13,8 +13,6 @@ import { ProgressBar } from "./progressbar";
// ui
import { CustomMenu } from "@plane/ui";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
import { useProjectMyMembership } from "contexts/project-member.context";
import useToast from "hooks/use-toast";
// helpers
import { copyTextToClipboard } from "helpers/string.helper";
@ -42,23 +40,21 @@ export interface ISubIssuesRootLoadersHandler {
const issueService = new IssueService();
export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user }) => {
export const SubIssuesRoot: React.FC<ISubIssuesRoot> = observer((props) => {
const { parentIssue, user } = props;
const { user: userStore, issue: issueStore, issueDetail: issueDetailStore } = useMobxStore();
const userRole = userStore.currentProjectRole;
const router = useRouter();
const { workspaceSlug, projectId } = router.query as {
workspaceSlug: string;
projectId: string;
peekIssue: string;
};
const { workspaceSlug, projectId } = router.query;
const { issue: issueStore, issueDetail: issueDetailStore } = useMobxStore();
const { memberRole } = useProjectMyMembership();
const { setToastAlert } = useToast();
const { data: issues, isLoading } = useSWR(
workspaceSlug && projectId && parentIssue && parentIssue?.id ? SUB_ISSUES(parentIssue?.id) : null,
workspaceSlug && projectId && parentIssue && parentIssue?.id
? () => issueService.subIssues(workspaceSlug, projectId, parentIssue.id)
? () => issueService.subIssues(workspaceSlug.toString(), projectId.toString(), parentIssue.id)
: null
);
@ -117,20 +113,20 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user }) =
};
const addAsSubIssueFromExistingIssues = async (data: ISearchIssueResponse[]) => {
if (!workspaceSlug || !parentIssue || issueCrudOperation?.existing?.issueId === null) return;
if (!workspaceSlug || !projectId || !parentIssue || issueCrudOperation?.existing?.issueId === null) return;
const issueId = issueCrudOperation?.existing?.issueId;
const payload = {
sub_issue_ids: data.map((i) => i.id),
};
await issueService.addSubIssues(workspaceSlug, projectId, issueId, payload).finally(() => {
await issueService.addSubIssues(workspaceSlug.toString(), projectId.toString(), issueId, payload).finally(() => {
if (issueId) mutate(SUB_ISSUES(issueId));
});
};
const removeIssueFromSubIssues = async (parentIssueId: string, issue: IIssue) => {
if (!workspaceSlug || !parentIssue || !issue?.id) return;
if (!workspaceSlug || !projectId || !parentIssue || !issue?.id) return;
issueService
.patchIssue(workspaceSlug, projectId, issue.id, { parent: null }, user)
.patchIssue(workspaceSlug.toString(), projectId.toString(), issue.id, { parent: null }, user)
.then(async () => {
if (parentIssueId) await mutate(SUB_ISSUES(parentIssueId));
handleIssuesLoader({ key: "delete", issueId: issue?.id });
@ -176,7 +172,7 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user }) =
[issueStore, issueDetailStore, projectId, user, workspaceSlug]
);
const isEditable = memberRole?.isGuest || memberRole?.isViewer ? false : true;
const isEditable = userRole === 5 || userRole === 10 ? false : true;
const mutateSubIssues = (parentIssueId: string | null) => {
if (parentIssueId) mutate(SUB_ISSUES(parentIssueId));
@ -233,11 +229,11 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user }) =
</div>
{/* issues */}
{issuesLoader.visibility.includes(parentIssue?.id) && (
{issuesLoader.visibility.includes(parentIssue?.id) && workspaceSlug && projectId && (
<div className="border border-b-0 border-custom-border-100">
<SubIssuesRootList
workspaceSlug={workspaceSlug}
projectId={projectId}
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
parentIssue={parentIssue}
user={undefined}
editable={isEditable}
@ -371,4 +367,4 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user }) =
)}
</div>
);
};
});

View File

@ -8,8 +8,6 @@ import { Disclosure, Transition } from "@headlessui/react";
import { useMobxStore } from "lib/mobx/store-provider";
// services
import { ModuleService } from "services/module.service";
// contexts
import { useProjectMyMembership } from "contexts/project-member.context";
// hooks
import useToast from "hooks/use-toast";
// components
@ -60,10 +58,9 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
const { module: moduleStore, user: userStore } = useMobxStore();
const user = userStore.currentUser ?? undefined;
const userRole = userStore.currentProjectRole;
const moduleDetails = moduleStore.moduleDetails[moduleId] ?? undefined;
const { memberRole } = useProjectMyMembership();
const { setToastAlert } = useToast();
const { reset, control } = useForm({
@ -429,7 +426,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
<Transition show={open}>
<Disclosure.Panel>
<div className="flex flex-col w-full mt-2 space-y-3 h-72 overflow-y-auto">
{memberRole && moduleDetails.link_module && moduleDetails.link_module.length > 0 ? (
{userRole && moduleDetails.link_module && moduleDetails.link_module.length > 0 ? (
<>
<div className="flex items-center justify-end w-full">
<button
@ -445,7 +442,12 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
links={moduleDetails.link_module}
handleEditLink={handleEditLink}
handleDeleteLink={handleDeleteLink}
userAuth={memberRole}
userAuth={{
isGuest: userRole === 5,
isViewer: userRole === 10,
isMember: userRole === 15,
isOwner: userRole === 20,
}}
/>
</>
) : (

View File

@ -15,7 +15,7 @@ import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown";
// icons
import { Check, ChevronDown, Plus, X } from "lucide-react";
// types
import { IUser, IWorkspace, TOnboardingSteps } from "types";
import { IUser, IWorkspace, TOnboardingSteps, TUserWorkspaceRole } from "types";
// constants
import { ROLE } from "constants/workspace";
@ -28,7 +28,7 @@ type Props = {
type EmailRole = {
email: string;
role: 5 | 10 | 15 | 20;
role: TUserWorkspaceRole;
};
type FormValues = {

View File

@ -1,6 +1,5 @@
export * from "./overview";
export * from "./navbar";
export * from "./profile-issues-view";
export * from "./sidebar";
export * from "./profile-issues-filter";

View File

@ -1,297 +0,0 @@
import { useCallback, useState } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { DropResult } from "react-beautiful-dnd";
// services
import { IssueService, IssueLabelService } from "services/issue";
import { UserService } from "services/user.service";
// hooks
import useProfileIssues from "hooks/use-profile-issues";
import useUser from "hooks/use-user";
// components
import { FiltersList } from "components/core";
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
// helpers
import { orderArrayBy } from "helpers/array.helper";
// types
import { IIssue, IIssueFilterOptions, TIssuePriorities } from "types";
// fetch-keys
import { USER_PROFILE_PROJECT_SEGREGATION, WORKSPACE_LABELS } from "constants/fetch-keys";
// services
const issueService = new IssueService();
const issueLabelService = new IssueLabelService();
const userService = new UserService();
export const ProfileIssuesView = () => {
// create issue modal
const [createIssueModal, setCreateIssueModal] = useState(false);
const [preloadedData, setPreloadedData] = useState<
(Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | undefined
>(undefined);
// update issue modal
const [editIssueModal, setEditIssueModal] = useState(false);
const [issueToEdit, setIssueToEdit] = useState<(IIssue & { actionType: "edit" | "delete" }) | undefined>(undefined);
// delete issue modal
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
const [issueToDelete, setIssueToDelete] = useState<IIssue | null>(null);
// trash box
const [trashBox, setTrashBox] = useState(false);
const router = useRouter();
const { workspaceSlug, userId } = router.query;
const { user } = useUser();
const {
groupedIssues,
mutateProfileIssues,
displayFilters,
isEmpty,
filters,
setFilters,
displayProperties,
params,
} = useProfileIssues(workspaceSlug?.toString(), userId?.toString());
const { data: profileData } = useSWR(
workspaceSlug && userId ? USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString()) : null,
workspaceSlug && userId
? () => userService.getUserProfileProjectsSegregation(workspaceSlug.toString(), userId.toString())
: null
);
const { data: labels } = useSWR(
workspaceSlug && (filters?.labels ?? []).length > 0 ? WORKSPACE_LABELS(workspaceSlug.toString()) : null,
workspaceSlug && (filters?.labels ?? []).length > 0
? () => issueLabelService.getWorkspaceIssueLabels(workspaceSlug.toString())
: null
);
const handleDeleteIssue = useCallback(
(issue: IIssue) => {
setDeleteIssueModal(true);
setIssueToDelete(issue);
},
[setDeleteIssueModal, setIssueToDelete]
);
const handleOnDragEnd = useCallback(
async (result: DropResult) => {
setTrashBox(false);
if (!result.destination || !workspaceSlug || !groupedIssues || displayFilters?.group_by !== "priority") return;
const { source, destination } = result;
if (source.droppableId === destination.droppableId) return;
const draggedItem = groupedIssues[source.droppableId][source.index];
if (!draggedItem) return;
if (destination.droppableId === "trashBox") handleDeleteIssue(draggedItem);
else {
const sourceGroup = source.droppableId;
const destinationGroup = destination.droppableId;
draggedItem[displayFilters.group_by] = destinationGroup as TIssuePriorities;
mutateProfileIssues((prevData: any) => {
if (!prevData) return prevData;
const sourceGroupArray = [...groupedIssues[sourceGroup]];
const destinationGroupArray = [...groupedIssues[destinationGroup]];
sourceGroupArray.splice(source.index, 1);
destinationGroupArray.splice(destination.index, 0, draggedItem);
return {
...prevData,
[sourceGroup]: orderArrayBy(sourceGroupArray, displayFilters.order_by ?? "-created_at"),
[destinationGroup]: orderArrayBy(destinationGroupArray, displayFilters.order_by ?? "-created_at"),
};
}, false);
// patch request
issueService
.patchIssue(
workspaceSlug as string,
draggedItem.project,
draggedItem.id,
{
priority: draggedItem.priority,
},
user
)
.catch(() => mutateProfileIssues());
}
},
[displayFilters, groupedIssues, handleDeleteIssue, mutateProfileIssues, user, workspaceSlug]
);
const addIssueToGroup = useCallback(
(groupTitle: string) => {
setCreateIssueModal(true);
let preloadedValue: string | string[] = groupTitle;
if (displayFilters?.group_by === "labels") {
if (groupTitle === "None") preloadedValue = [];
else preloadedValue = [groupTitle];
}
if (displayFilters?.group_by)
setPreloadedData({
[displayFilters?.group_by]: preloadedValue,
actionType: "createIssue",
});
else setPreloadedData({ actionType: "createIssue" });
},
[setCreateIssueModal, setPreloadedData, displayFilters?.group_by]
);
const addIssueToDate = useCallback(
(date: string) => {
setCreateIssueModal(true);
setPreloadedData({
target_date: date,
actionType: "createIssue",
});
},
[setCreateIssueModal, setPreloadedData]
);
const makeIssueCopy = useCallback(
(issue: IIssue) => {
setCreateIssueModal(true);
setPreloadedData({ ...issue, name: `${issue.name} (Copy)`, actionType: "createIssue" });
},
[setCreateIssueModal, setPreloadedData]
);
const handleEditIssue = useCallback(
(issue: IIssue) => {
setEditIssueModal(true);
setIssueToEdit({
...issue,
actionType: "edit",
cycle: issue.issue_cycle ? issue.issue_cycle.cycle : null,
module: issue.issue_module ? issue.issue_module.module : null,
});
},
[setEditIssueModal, setIssueToEdit]
);
const handleIssueAction = useCallback(
(issue: IIssue, action: "copy" | "edit" | "delete" | "updateDraft") => {
if (action === "copy") makeIssueCopy(issue);
else if (action === "edit") handleEditIssue(issue);
else if (action === "delete") handleDeleteIssue(issue);
},
[makeIssueCopy, handleEditIssue, handleDeleteIssue]
);
const filtersToDisplay = { ...filters, assignees: null, created_by: null, subscriber: null };
const nullFilters = Object.keys(filtersToDisplay).filter(
(key) => filtersToDisplay[key as keyof IIssueFilterOptions] === null
);
const areFiltersApplied =
Object.keys(filtersToDisplay).length > 0 && nullFilters.length !== Object.keys(filtersToDisplay).length;
const isSubscribedIssuesRoute = router.pathname.includes("subscribed");
const isMySubscribedIssues =
(filters.subscriber && filters.subscriber.length > 0 && router.pathname.includes("my-issues")) ?? false;
const disableAddIssueOption = isSubscribedIssuesRoute || isMySubscribedIssues || user?.id !== userId;
return (
<>
<CreateUpdateIssueModal
isOpen={createIssueModal && preloadedData?.actionType === "createIssue"}
handleClose={() => setCreateIssueModal(false)}
prePopulateData={{
...preloadedData,
}}
onSubmit={async () => {
mutateProfileIssues();
}}
/>
<CreateUpdateIssueModal
isOpen={editIssueModal && issueToEdit?.actionType !== "delete"}
handleClose={() => setEditIssueModal(false)}
data={issueToEdit}
onSubmit={async () => {
mutateProfileIssues();
}}
/>
{issueToDelete && (
<DeleteIssueModal
handleClose={() => setDeleteIssueModal(false)}
isOpen={deleteIssueModal}
data={issueToDelete}
onSubmit={async () => {
mutateProfileIssues();
}}
/>
)}
{areFiltersApplied && (
<>
<div className="flex items-center justify-between gap-2 px-5 pt-3 pb-0">
<FiltersList
filters={filtersToDisplay}
setFilters={setFilters}
labels={labels}
members={undefined}
states={undefined}
clearAllFilters={() =>
setFilters({
mentions: null,
labels: null,
priority: null,
state_group: null,
start_date: null,
target_date: null,
})
}
/>
</div>
{<div className="mt-3 border-t border-custom-border-200" />}
</>
)}
{/* <AllViews
addIssueToDate={addIssueToDate}
addIssueToGroup={addIssueToGroup}
disableUserActions={false}
dragDisabled={displayFilters?.group_by !== "priority"}
emptyState={{
title: router.pathname.includes("assigned")
? `Issues assigned to ${profileData?.user_data.display_name} will appear here`
: router.pathname.includes("created")
? `Issues created by ${profileData?.user_data.display_name} will appear here`
: `Issues subscribed by ${profileData?.user_data.display_name} will appear here`,
}}
handleOnDragEnd={handleOnDragEnd}
handleIssueAction={handleIssueAction}
openIssuesListModal={null}
removeIssue={null}
disableAddIssueOption={disableAddIssueOption}
trashBox={trashBox}
setTrashBox={setTrashBox}
viewProps={{
groupedIssues,
displayFilters,
isEmpty,
mutateIssues: mutateProfileIssues,
params,
properties: displayProperties,
}}
/> */}
</>
);
};

View File

@ -16,6 +16,7 @@ import { CustomMenu, CustomSelect } from "@plane/ui";
import { ChevronDown, X } from "lucide-react";
// constants
import { ROLE } from "constants/workspace";
import { TUserProjectRole } from "types";
// services
const projectInvitationService = new ProjectInvitationService();
@ -144,7 +145,7 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
</div>
}
value={member.role}
onChange={(value: 5 | 10 | 15 | 20 | undefined) => {
onChange={(value: TUserProjectRole | undefined) => {
if (!workspaceSlug || !projectId) return;
projectStore

View File

@ -1,20 +1,21 @@
import React, { useEffect } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
import { useForm, Controller, useFieldArray } from "react-hook-form";
import { Dialog, Transition } from "@headlessui/react";
import { ChevronDown, Plus, X } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// ui
import { Avatar, Button, CustomSelect, CustomSearchSelect } from "@plane/ui";
// services
import { ProjectService } from "services/project";
import { WorkspaceService } from "services/workspace.service";
// contexts
import { useProjectMyMembership } from "contexts/project-member.context";
// hooks
import useToast from "hooks/use-toast";
// types
import { IUser } from "types";
import { IUser, TUserProjectRole } from "types";
// fetch-keys
import { WORKSPACE_MEMBERS } from "constants/fetch-keys";
// constants
@ -29,7 +30,7 @@ type Props = {
};
type member = {
role: 5 | 10 | 15 | 20;
role: TUserProjectRole;
member_id: string;
};
@ -50,14 +51,16 @@ const defaultValues: FormValues = {
const projectService = new ProjectService();
const workspaceService = new WorkspaceService();
export const SendProjectInvitationModal: React.FC<Props> = (props) => {
export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
const { isOpen, setIsOpen, members, user, onSuccess } = props;
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { setToastAlert } = useToast();
const { memberDetails } = useProjectMyMembership();
const { user: userStore } = useMobxStore();
const userRole = userStore.currentProjectRole;
const { data: people } = useSWR(
workspaceSlug ? WORKSPACE_MEMBERS(workspaceSlug as string) : null,
@ -243,7 +246,7 @@ export const SendProjectInvitationModal: React.FC<Props> = (props) => {
width="w-full"
>
{Object.entries(ROLE).map(([key, label]) => {
if (parseInt(key) > (memberDetails?.role ?? 5)) return null;
if (parseInt(key) > (userRole ?? 5)) return null;
return (
<CustomSelect.Option key={key} value={key}>
@ -305,4 +308,4 @@ export const SendProjectInvitationModal: React.FC<Props> = (props) => {
</Dialog>
</Transition.Root>
);
};
});

View File

@ -1,182 +0,0 @@
import useSWR from "swr";
import { observer } from "mobx-react-lite";
import Image from "next/image";
import { useRouter } from "next/router";
// hooks
import useToast from "hooks/use-toast";
import { useMobxStore } from "lib/mobx/store-provider";
// services
import { AuthService } from "services/auth.service";
import { AppConfigService } from "services/app_config.service";
// components
import {
GoogleLoginButton,
GithubLoginButton,
EmailCodeForm,
EmailPasswordForm,
EmailPasswordFormValues,
} from "components/account";
// ui
import { Spinner } from "@plane/ui";
// images
import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
import { IUserSettings } from "types";
const appConfigService = new AppConfigService();
const authService = new AuthService();
export const SignInView = observer(() => {
const { user: userStore } = useMobxStore();
// router
const router = useRouter();
// toast
const { setToastAlert } = useToast();
// fetch app config
const { data } = useSWR("APP_CONFIG", () => appConfigService.envConfig());
// fetch user info
const { data: user, error } = useSWR("USER_INFO", () => userStore.fetchCurrentUser());
// computed
const enableEmailPassword =
data?.email_password_login || !(data?.email_password_login || data?.magic_login || data?.google || data?.github);
const handleLoginRedirection = () =>
userStore.fetchCurrentUserSettings().then((userSettings: IUserSettings) => {
const workspaceSlug =
userSettings?.workspace?.last_workspace_slug || userSettings?.workspace?.fallback_workspace_slug;
router.push(`/${workspaceSlug}`);
});
const handleGoogleSignIn = async ({ clientId, credential }: any) => {
try {
if (clientId && credential) {
const socialAuthPayload = {
medium: "google",
credential,
clientId,
};
const response = await authService.socialAuth(socialAuthPayload);
if (response) {
handleLoginRedirection();
}
} else {
throw Error("Cant find credentials");
}
} catch (err: any) {
setToastAlert({
title: "Error signing in!",
type: "error",
message: err?.error || "Something went wrong. Please try again later or contact the support team.",
});
}
};
const handleGitHubSignIn = async (credential: string) => {
try {
if (data && data.github && credential) {
const socialAuthPayload = {
medium: "github",
credential,
clientId: data.github,
};
const response = await authService.socialAuth(socialAuthPayload);
if (response) {
handleLoginRedirection();
}
} else {
throw Error("Cant find credentials");
}
} catch (err: any) {
setToastAlert({
title: "Error signing in!",
type: "error",
message: err?.error || "Something went wrong. Please try again later or contact the support team.",
});
}
};
const handlePasswordSignIn = async (formData: EmailPasswordFormValues) => {
await authService
.emailLogin(formData)
.then((response) => {
if (response) {
handleLoginRedirection();
}
})
.catch((err) =>
setToastAlert({
type: "error",
title: "Error!",
message: err?.error || "Something went wrong. Please try again later or contact the support team.",
})
);
};
const handleEmailCodeSignIn = async (response: any) => {
try {
if (response) {
handleLoginRedirection();
}
} catch (err: any) {
setToastAlert({
type: "error",
title: "Error!",
message: err?.error || "Something went wrong. Please try again later or contact the support team.",
});
}
};
return (
<>
{!user && !error ? (
<div className="grid place-items-center h-screen">
<Spinner />
</div>
) : (
<>
<>
<div className="hidden sm:block sm:fixed border-r-[0.5px] border-custom-border-200 h-screen w-[0.5px] top-0 left-20 lg:left-32" />
<div className="fixed grid place-items-center bg-custom-background-100 sm:py-5 top-11 sm:top-12 left-7 sm:left-16 lg:left-28">
<div className="grid place-items-center bg-custom-background-100">
<div className="h-[30px] w-[30px]">
<Image src={BluePlaneLogoWithoutText} alt="Plane Logo" />
</div>
</div>
</div>
</>
<div className="grid place-items-center h-full overflow-y-auto py-5 px-7">
<div>
<h1 className="text-center text-2xl sm:text-2.5xl font-semibold text-custom-text-100">
Sign in to Plane
</h1>
<>
{enableEmailPassword && <EmailPasswordForm onSubmit={handlePasswordSignIn} />}
{data?.magic_login && (
<div className="flex flex-col divide-y divide-custom-border-200">
<div className="pb-7">
<EmailCodeForm handleSignIn={handleEmailCodeSignIn} />
</div>
</div>
)}
<div className="flex flex-col items-center justify-center gap-4 pt-7 sm:w-[360px] mx-auto overflow-hidden">
{data?.google && <GoogleLoginButton clientId={data?.google} handleSignIn={handleGoogleSignIn} />}
{data?.github && <GithubLoginButton clientId={data?.github} handleSignIn={handleGitHubSignIn} />}
</div>
</>
<p className="pt-16 text-custom-text-200 text-sm text-center">
By signing up, you agree to the{" "}
<a
href="https://plane.so/terms-and-conditions"
target="_blank"
rel="noopener noreferrer"
className="font-medium underline"
>
Terms & Conditions
</a>
</p>
</div>
</div>
</>
)}
</>
);
});

View File

@ -11,7 +11,7 @@ import { Button, CustomSelect, Input } from "@plane/ui";
// icons
import { Plus, X } from "lucide-react";
// types
import { IUser } from "types";
import { IUser, TUserWorkspaceRole } from "types";
// constants
import { ROLE } from "constants/workspace";
// fetch-keys
@ -27,7 +27,7 @@ type Props = {
type EmailRole = {
email: string;
role: 5 | 10 | 15 | 20;
role: TUserWorkspaceRole;
};
type FormValues = {

View File

@ -15,6 +15,7 @@ import { CustomSelect, Tooltip } from "@plane/ui";
import { ChevronDown, XCircle } from "lucide-react";
// constants
import { ROLE } from "constants/workspace";
import { TUserWorkspaceRole } from "types";
type Props = {
member: {
@ -25,7 +26,7 @@ type Props = {
last_name: string;
email: string | undefined;
display_name: string;
role: 5 | 10 | 15 | 20;
role: TUserWorkspaceRole;
status: boolean;
member: boolean;
accountCreated: boolean;
@ -153,7 +154,7 @@ export const WorkspaceMembersListItem: FC<Props> = (props) => {
</div>
}
value={member.role}
onChange={(value: 5 | 10 | 15 | 20 | undefined) => {
onChange={(value: TUserWorkspaceRole | undefined) => {
if (!workspaceSlug || !value) return;
workspaceStore

View File

@ -8,10 +8,8 @@ import { Check, LogOut, Plus, Settings, UserCircle2 } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import useUser from "hooks/use-user";
import useToast from "hooks/use-toast";
// services
import { UserService } from "services/user.service";
import { AuthService } from "services/auth.service";
// ui
import { Avatar, Loader } from "@plane/ui";
@ -47,30 +45,26 @@ const profileLinks = (workspaceSlug: string, userId: string) => [
},
];
const userService = new UserService();
const authService = new AuthService();
export const WorkspaceSidebarDropdown = observer(() => {
const router = useRouter();
const { workspaceSlug } = router.query;
const { theme: themeStore, workspace: workspaceStore } = useMobxStore();
const { theme: themeStore, workspace: workspaceStore, user: userStore } = useMobxStore();
const { workspaces, currentWorkspace: activeWorkspace } = workspaceStore;
const { user, mutateUser } = useUser();
const user = userStore.currentUser;
const { setTheme } = useTheme();
const { setToastAlert } = useToast();
const handleWorkspaceNavigation = (workspace: IWorkspace) => {
userService
.updateUser({
userStore
.updateCurrentUser({
last_workspace_id: workspace?.id,
})
.then(() => {
mutateUser();
router.push(`/${workspace.slug}/`);
})
.catch(() =>
@ -86,7 +80,6 @@ export const WorkspaceSidebarDropdown = observer(() => {
await authService
.signOut()
.then(() => {
mutateUser(undefined);
router.push("/");
setTheme("system");
})

View File

@ -1,61 +0,0 @@
import { createContext, useContext } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
// services
import { ProjectService } from "services/project";
// keys
import { USER_PROJECT_VIEW } from "constants/fetch-keys";
// types
import { IProjectMember } from "types";
type ContextType = {
loading: boolean;
memberDetails?: IProjectMember;
error: any;
};
// services
const projectService = new ProjectService();
export const ProjectMemberContext = createContext<ContextType>({} as ContextType);
type Props = {
children: React.ReactNode;
};
export const ProjectMemberProvider: React.FC<Props> = (props) => {
const { children } = props;
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { data: memberDetails, error } = useSWR(
workspaceSlug && projectId ? USER_PROJECT_VIEW(projectId.toString()) : null,
workspaceSlug && projectId
? () => projectService.projectMemberMe(workspaceSlug.toString(), projectId.toString())
: null
);
const loading = !memberDetails && !error;
return (
<ProjectMemberContext.Provider value={{ loading, memberDetails, error }}>{children}</ProjectMemberContext.Provider>
);
};
export const useProjectMyMembership = () => {
const context = useContext(ProjectMemberContext);
if (context === undefined) throw new Error(`useProjectMember must be used within a ProjectMemberProvider.`);
return {
...context,
memberRole: {
isOwner: context.memberDetails?.role === 20,
isMember: context.memberDetails?.role === 15,
isViewer: context.memberDetails?.role === 10,
isGuest: context.memberDetails?.role === 5,
},
};
};

View File

@ -1,43 +0,0 @@
import useSWR from "swr";
// services
import { IssueService } from "services/issue";
// hooks
import useIssuesView from "hooks/use-issues-view";
// fetch-keys
import { PROJECT_ISSUES_LIST_WITH_PARAMS } from "constants/fetch-keys";
const issueService = new IssueService();
const useGanttChartIssues = (workspaceSlug: string | undefined, projectId: string | undefined) => {
const { displayFilters, filters } = useIssuesView();
const params: any = {
order_by: displayFilters.order_by,
type: displayFilters?.type ? displayFilters?.type : undefined,
sub_issue: displayFilters.sub_issue,
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
state: filters?.state ? filters?.state.join(",") : undefined,
priority: filters?.priority ? filters?.priority.join(",") : undefined,
labels: filters?.labels ? filters?.labels.join(",") : undefined,
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
start_date: filters?.start_date ? filters?.start_date.join(",") : undefined,
target_date: filters?.target_date ? filters?.target_date.join(",") : undefined,
start_target_date: true, // to fetch only issues with a start and target date
};
// all issues under the workspace and project
const { data: ganttIssues, mutate: mutateGanttIssues } = useSWR(
workspaceSlug && projectId ? PROJECT_ISSUES_LIST_WITH_PARAMS(projectId, params) : null,
workspaceSlug && projectId
? () => issueService.getIssuesWithParams(workspaceSlug.toString(), projectId.toString(), params)
: null
);
return {
ganttIssues,
mutateGanttIssues,
params,
};
};
export default useGanttChartIssues;

View File

@ -1,178 +0,0 @@
import { useEffect, useCallback } from "react";
import useSWR, { mutate } from "swr";
// services
import { WorkspaceService } from "services/workspace.service";
// types
import {
IIssueDisplayFilterOptions,
IIssueFilterOptions,
IWorkspaceMemberMe,
IWorkspaceViewProps,
Properties,
} from "types";
// fetch-keys
import { WORKSPACE_MEMBERS_ME } from "constants/fetch-keys";
const workspaceService = new WorkspaceService();
const initialValues: IWorkspaceViewProps = {
display_filters: {
group_by: null,
layout: "list",
order_by: "-created_at",
show_empty_groups: true,
sub_issue: true,
type: null,
},
display_properties: {
assignee: true,
start_date: true,
due_date: true,
key: true,
labels: true,
priority: true,
state: true,
sub_issue_count: true,
attachment_count: true,
link: true,
estimate: true,
created_on: true,
updated_on: true,
},
filters: {
assignees: null,
created_by: null,
labels: null,
priority: null,
state_group: null,
subscriber: null,
start_date: null,
target_date: null,
},
};
const useMyIssuesFilters = (workspaceSlug: string | undefined) => {
const { data: myWorkspace } = useSWR(
workspaceSlug ? WORKSPACE_MEMBERS_ME(workspaceSlug as string) : null,
workspaceSlug ? () => workspaceService.workspaceMemberMe(workspaceSlug as string) : null,
{
shouldRetryOnError: false,
}
);
const saveData = useCallback(
(data: Partial<IWorkspaceViewProps>) => {
if (!workspaceSlug || !myWorkspace) return;
const oldData = { ...myWorkspace };
mutate<IWorkspaceMemberMe>(
WORKSPACE_MEMBERS_ME(workspaceSlug.toString()),
(prevData) => {
if (!prevData) return;
return {
...prevData,
view_props: {
...prevData?.view_props,
...data,
},
};
},
false
);
workspaceService.updateWorkspaceView(workspaceSlug, {
view_props: {
...oldData.view_props,
...data,
},
});
},
[myWorkspace, workspaceSlug]
);
const groupBy = (myWorkspace?.view_props ?? initialValues).display_filters?.group_by;
const filters = (myWorkspace?.view_props ?? initialValues).filters;
const setDisplayFilters = useCallback(
(displayFilter: Partial<IIssueDisplayFilterOptions>) => {
const payload: Partial<IWorkspaceViewProps> = {
display_filters: {
...myWorkspace?.view_props?.display_filters,
...displayFilter,
},
};
if (displayFilter.layout && displayFilter.layout === "kanban" && groupBy === null && payload.display_filters)
payload.display_filters.group_by = "state_detail.group";
saveData(payload);
},
[groupBy, myWorkspace?.view_props.display_filters, saveData]
);
const setProperty = useCallback(
(key: keyof Properties) => {
if (!myWorkspace?.view_props.display_properties) return;
saveData({
display_properties: {
...myWorkspace.view_props?.display_properties,
[key]: !myWorkspace.view_props?.display_properties[key],
},
});
},
[myWorkspace, saveData]
);
const setFilters = useCallback(
(updatedFilter: Partial<IIssueFilterOptions>) => {
if (!myWorkspace) return;
saveData({
filters: {
...myWorkspace.view_props?.filters,
...updatedFilter,
},
});
},
[myWorkspace, saveData]
);
useEffect(() => {
if (!myWorkspace || !workspaceSlug) return;
if (!myWorkspace.view_props) {
workspaceService.updateWorkspaceView(workspaceSlug, {
view_props: { ...initialValues },
});
}
}, [myWorkspace, workspaceSlug]);
const newProperties: Properties = {
assignee: myWorkspace?.view_props.display_properties?.assignee ?? true,
start_date: myWorkspace?.view_props.display_properties?.start_date ?? true,
due_date: myWorkspace?.view_props.display_properties?.due_date ?? true,
key: myWorkspace?.view_props.display_properties?.key ?? true,
labels: myWorkspace?.view_props.display_properties?.labels ?? true,
priority: myWorkspace?.view_props.display_properties?.priority ?? true,
state: myWorkspace?.view_props.display_properties?.state ?? true,
sub_issue_count: myWorkspace?.view_props.display_properties?.sub_issue_count ?? true,
attachment_count: myWorkspace?.view_props.display_properties?.attachment_count ?? true,
link: myWorkspace?.view_props.display_properties?.link ?? true,
estimate: myWorkspace?.view_props.display_properties?.estimate ?? true,
created_on: myWorkspace?.view_props.display_properties?.created_on ?? true,
updated_on: myWorkspace?.view_props.display_properties?.updated_on ?? true,
};
return {
displayFilters: myWorkspace?.view_props?.display_filters,
setDisplayFilters,
properties: newProperties,
setProperty,
filters,
setFilters,
};
};
export default useMyIssuesFilters;

View File

@ -1,87 +0,0 @@
import { useMemo } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
// services
import { UserService } from "services/user.service";
// hooks
import useMyIssuesFilters from "hooks/my-issues/use-my-issues-filter";
// types
import { IIssue } from "types";
// fetch-keys
import { USER_ISSUES } from "constants/fetch-keys";
// services
const userService = new UserService();
const useMyIssues = (workspaceSlug: string | undefined) => {
const router = useRouter();
const { filters, displayFilters } = useMyIssuesFilters(workspaceSlug);
const params: any = {
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
group_by: displayFilters?.group_by,
labels: filters?.labels ? filters?.labels.join(",") : undefined,
order_by: displayFilters?.order_by,
priority: filters?.priority ? filters?.priority.join(",") : undefined,
state_group: filters?.state_group ? filters?.state_group.join(",") : undefined,
subscriber: filters?.subscriber ? filters?.subscriber.join(",") : undefined,
start_date: filters?.start_date ? filters?.start_date.join(",") : undefined,
target_date: filters?.target_date ? filters?.target_date.join(",") : undefined,
type: displayFilters?.type,
};
const { data: myIssues, mutate: mutateMyIssues } = useSWR(
workspaceSlug && router.pathname.includes("my-issues") ? USER_ISSUES(workspaceSlug.toString(), params) : null,
workspaceSlug && router.pathname.includes("my-issues")
? () => userService.userIssues(workspaceSlug.toString(), params)
: null
);
const groupedIssues:
| {
[key: string]: IIssue[];
}
| undefined = useMemo(() => {
if (!myIssues) return undefined;
if (Array.isArray(myIssues))
return {
allIssues: myIssues,
};
if (displayFilters?.group_by === "state_detail.group") {
return myIssues
? Object.assign(
{
backlog: [],
unstarted: [],
started: [],
completed: [],
cancelled: [],
},
myIssues
)
: undefined;
}
return myIssues;
}, [displayFilters, myIssues]);
const isEmpty =
Object.values(groupedIssues ?? {}).every((group) => group.length === 0) ||
Object.keys(groupedIssues ?? {}).length === 0;
return {
groupedIssues,
isEmpty,
mutateMyIssues,
params,
};
};
export default useMyIssues;

View File

@ -1,19 +1,15 @@
import { IMentionHighlight, IMentionSuggestion } from "@plane/rich-text-editor";
import useProjectMembers from "./use-project-members";
import useUser from "./use-user";
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
const useEditorSuggestions = (
_workspaceSlug: string | undefined,
_projectId: string | undefined,
) => {
const { mentionsStore }: RootStore = useMobxStore()
const useEditorSuggestions = (_workspaceSlug: string | undefined, _projectId: string | undefined) => {
const { mentionsStore }: RootStore = useMobxStore();
return {
mentionSuggestions: mentionsStore.mentionSuggestions,
mentionHighlights: mentionsStore.mentionHighlights
}
return {
mentionSuggestions: mentionsStore.mentionSuggestions,
mentionHighlights: mentionsStore.mentionHighlights,
};
};
export default useEditorSuggestions;
export default useEditorSuggestions;

View File

@ -1,211 +0,0 @@
import { useContext, useMemo } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
// contexts
import { issueViewContext } from "contexts/issue-view.context";
// services
import { IssueService, IssueArchiveService, IssueDraftService } from "services/issue";
import { CycleService } from "services/cycle.service";
import { ModuleService } from "services/module.service";
import { ProjectStateService } from "services/project";
// helpers
import { getStatesList } from "helpers/state.helper";
// types
import type { IIssue } from "types";
// fetch-keys
import {
CYCLE_ISSUES_WITH_PARAMS,
MODULE_ISSUES_WITH_PARAMS,
PROJECT_ARCHIVED_ISSUES_LIST_WITH_PARAMS,
PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS,
PROJECT_ISSUES_LIST_WITH_PARAMS,
STATES_LIST,
VIEW_ISSUES,
} from "constants/fetch-keys";
// services
const issueService = new IssueService();
const issueArchiveService = new IssueArchiveService();
const issueDraftService = new IssueDraftService();
const cycleService = new CycleService();
const moduleService = new ModuleService();
const projectStateService = new ProjectStateService();
const useIssuesView = () => {
const {
display_filters: displayFilters,
setDisplayFilters,
filters,
setFilters,
resetFilterToDefault,
setNewFilterDefaultView,
} = useContext(issueViewContext);
const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId, viewId, archivedIssueId } = router.query;
const isArchivedIssues = router.pathname.includes("archived-issues");
const isDraftIssues = router.pathname.includes("draft-issues");
const params: any = {
order_by: displayFilters?.order_by,
group_by: displayFilters?.group_by,
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
mentions: filters?.mentions ? filters?.mentions.join(",") : undefined,
state: filters?.state ? filters?.state.join(",") : undefined,
priority: filters?.priority ? filters?.priority.join(",") : undefined,
type: !isArchivedIssues ? (displayFilters?.type ? displayFilters?.type : undefined) : undefined,
labels: filters?.labels ? filters?.labels.join(",") : undefined,
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
start_date: filters?.start_date ? filters?.start_date.join(",") : undefined,
target_date: filters?.target_date ? filters?.target_date.join(",") : undefined,
sub_issue: displayFilters?.sub_issue,
};
const { data: projectIssues, mutate: mutateProjectIssues } = useSWR(
workspaceSlug && projectId && params ? PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params) : null,
workspaceSlug && projectId && params
? () => issueService.getIssuesWithParams(workspaceSlug as string, projectId as string, params)
: null
);
const { data: projectArchivedIssues, mutate: mutateProjectArchivedIssues } = useSWR(
workspaceSlug && projectId && params && isArchivedIssues && !archivedIssueId
? PROJECT_ARCHIVED_ISSUES_LIST_WITH_PARAMS(projectId as string, params)
: null,
workspaceSlug && projectId && params && isArchivedIssues && !archivedIssueId
? () => issueArchiveService.getArchivedIssues(workspaceSlug as string, projectId as string, params)
: null
);
const { data: draftIssues, mutate: mutateDraftIssues } = useSWR(
workspaceSlug && projectId && params && isDraftIssues && !archivedIssueId
? PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(projectId as string, params)
: null,
workspaceSlug && projectId && params && isDraftIssues && !archivedIssueId
? () => issueDraftService.getDraftIssues(workspaceSlug as string, projectId as string, params)
: null
);
const { data: cycleIssues, mutate: mutateCycleIssues } = useSWR(
workspaceSlug && projectId && cycleId && params ? CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params) : null,
workspaceSlug && projectId && cycleId && params
? () =>
cycleService.getCycleIssuesWithParams(workspaceSlug as string, projectId as string, cycleId as string, params)
: null
);
const { data: moduleIssues, mutate: mutateModuleIssues } = useSWR(
workspaceSlug && projectId && moduleId && params ? MODULE_ISSUES_WITH_PARAMS(moduleId as string, params) : null,
workspaceSlug && projectId && moduleId && params
? () =>
moduleService.getModuleIssuesWithParams(
workspaceSlug as string,
projectId as string,
moduleId as string,
params
)
: null
);
const { data: viewIssues, mutate: mutateViewIssues } = useSWR(
workspaceSlug && projectId && viewId && params ? VIEW_ISSUES(viewId.toString(), params) : null,
workspaceSlug && projectId && viewId && params
? () => issueService.getIssuesWithParams(workspaceSlug.toString(), projectId.toString(), params)
: null
);
const { data: states } = useSWR(
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
workspaceSlug && projectId
? () => projectStateService.getStates(workspaceSlug as string, projectId as string)
: null
);
const statesList = getStatesList(states);
const activeStatesList = statesList?.filter((state) => state.group === "started" || state.group === "unstarted");
const backlogStatesList = statesList?.filter((state) => state.group === "backlog");
const stateIds =
displayFilters && displayFilters?.type === "active"
? activeStatesList?.map((state) => state.id)
: displayFilters?.type === "backlog"
? backlogStatesList?.map((state) => state.id)
: statesList?.map((state) => state.id);
const filteredStateIds =
(filters && filters?.state ? stateIds?.filter((s) => filters.state?.includes(s)) : stateIds) ?? [];
const emptyStatesObject: { [key: string]: [] } = {};
for (let i = 0; i < filteredStateIds.length; i++) {
emptyStatesObject[filteredStateIds[i]] = [];
}
const groupedByIssues:
| {
[key: string]: IIssue[];
}
| undefined = useMemo(() => {
const issuesToGroup = cycleId
? cycleIssues
: moduleId
? moduleIssues
: viewId
? viewIssues
: isArchivedIssues
? projectArchivedIssues
: isDraftIssues
? draftIssues
: projectIssues;
if (Array.isArray(issuesToGroup)) return { allIssues: issuesToGroup };
if (displayFilters?.group_by === "state")
return issuesToGroup ? Object.assign(emptyStatesObject, issuesToGroup) : undefined;
return issuesToGroup;
}, [
displayFilters?.group_by,
projectIssues,
cycleIssues,
moduleIssues,
viewIssues,
projectArchivedIssues,
cycleId,
moduleId,
viewId,
isArchivedIssues,
isDraftIssues,
draftIssues,
emptyStatesObject,
]);
const isEmpty =
Object.values(groupedByIssues ?? {}).every((group) => group.length === 0) ||
Object.keys(groupedByIssues ?? {}).length === 0;
return {
displayFilters: {
...displayFilters,
layout: isArchivedIssues ? "list" : displayFilters?.layout,
},
setDisplayFilters,
groupedByIssues,
mutateIssues: cycleId
? mutateCycleIssues
: moduleId
? mutateModuleIssues
: viewId
? mutateViewIssues
: isArchivedIssues
? mutateProjectArchivedIssues
: isDraftIssues
? mutateDraftIssues
: mutateProjectIssues,
filters,
setFilters,
params,
isEmpty,
resetFilterToDefault,
setNewFilterDefaultView,
} as const;
};
export default useIssuesView;

View File

@ -1,123 +0,0 @@
import { useContext, useEffect, useMemo } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
// services
import { UserService } from "services/user.service";
// contexts
import { profileIssuesContext } from "contexts/profile-issues-context";
import { useWorkspaceMyMembership } from "contexts/workspace-member.context";
// types
import { IIssue } from "types";
// fetch-keys
import { USER_PROFILE_ISSUES } from "constants/fetch-keys";
const userService = new UserService();
const useProfileIssues = (workspaceSlug: string | undefined, userId: string | undefined) => {
const {
display_filters: displayFilters,
setDisplayFilters,
filters,
setFilters,
display_properties: displayProperties,
setProperties,
} = useContext(profileIssuesContext);
const router = useRouter();
const { memberRole } = useWorkspaceMyMembership();
const params: any = {
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
group_by: displayFilters?.group_by,
labels: filters?.labels ? filters?.labels.join(",") : undefined,
order_by: displayFilters?.order_by,
priority: filters?.priority ? filters?.priority.join(",") : undefined,
state_group: filters?.state_group ? filters?.state_group.join(",") : undefined,
start_date: filters?.start_date ? filters?.start_date.join(",") : undefined,
target_date: filters?.target_date ? filters?.target_date.join(",") : undefined,
type: displayFilters?.type ? displayFilters?.type : undefined,
subscriber: filters?.subscriber ? filters?.subscriber.join(",") : undefined,
};
const { data: userProfileIssues, mutate: mutateProfileIssues } = useSWR(
workspaceSlug && userId && (memberRole.isOwner || memberRole.isMember || memberRole.isViewer)
? USER_PROFILE_ISSUES(workspaceSlug.toString(), userId.toString(), params)
: null,
workspaceSlug && userId && (memberRole.isOwner || memberRole.isMember || memberRole.isViewer)
? () => userService.getUserProfileIssues(workspaceSlug.toString(), userId.toString(), params)
: null
);
const groupedIssues:
| {
[key: string]: IIssue[];
}
| undefined = useMemo(() => {
if (!userProfileIssues) return undefined;
if (Array.isArray(userProfileIssues))
return {
allIssues: userProfileIssues,
};
if (displayFilters?.group_by === "state_detail.group") {
return userProfileIssues
? Object.assign(
{
backlog: [],
unstarted: [],
started: [],
completed: [],
cancelled: [],
},
userProfileIssues
)
: undefined;
}
return userProfileIssues;
}, [displayFilters?.group_by, userProfileIssues]);
useEffect(() => {
if (!userId || !filters) return;
if (router.pathname.includes("assigned") && (!filters.assignees || !filters.assignees.includes(userId))) {
setFilters({ assignees: [...(filters.assignees ?? []), userId] });
return;
}
if (router.pathname.includes("created") && (!filters.created_by || !filters.created_by.includes(userId))) {
setFilters({ created_by: [...(filters.created_by ?? []), userId] });
return;
}
if (router.pathname.includes("subscribed") && filters.subscriber === null) {
setFilters({ subscriber: [userId] });
return;
}
}, [filters, router, setFilters, userId]);
const isEmpty =
Object.values(groupedIssues ?? {}).every((group) => group.length === 0) ||
Object.keys(groupedIssues ?? {}).length === 0;
return {
groupedIssues,
displayFilters,
setDisplayFilters,
filters,
setFilters,
displayProperties,
setProperties,
isEmpty,
mutateProfileIssues,
params,
};
};
export default useProfileIssues;

View File

@ -1,44 +0,0 @@
import useSWR from "swr";
// services
import { ProjectService } from "services/project";
// fetch-keys
import { PROJECT_MEMBERS } from "constants/fetch-keys";
// hooks
import useUser from "./use-user";
const projectService = new ProjectService();
const useProjectMembers = (
workspaceSlug: string | undefined,
projectId: string | undefined,
fetchCondition?: boolean
) => {
fetchCondition = fetchCondition ?? true;
const { user } = useUser();
// fetching project members
const { data: members } = useSWR(
workspaceSlug && projectId && fetchCondition ? PROJECT_MEMBERS(projectId) : null,
workspaceSlug && projectId && fetchCondition
? () => projectService.fetchProjectMembers(workspaceSlug, projectId)
: null
);
const hasJoined = members?.some((item: any) => item.member.id === (user as any)?.id);
const isOwner = members?.some((item) => item.member.id === (user as any)?.id && item.role === 20);
const isMember = members?.some((item) => item.member.id === (user as any)?.id && item.role === 15);
const isViewer = members?.some((item) => item.member.id === (user as any)?.id && item.role === 10);
const isGuest = members?.some((item) => item.member.id === (user as any)?.id && item.role === 5);
return {
members,
hasJoined,
isOwner,
isMember,
isViewer,
isGuest,
};
};
export default useProjectMembers;

View File

@ -1,39 +0,0 @@
import useSWR from "swr";
// services
import { WorkspaceService } from "services/workspace.service";
// fetch-keys
import { WORKSPACE_MEMBERS } from "constants/fetch-keys";
// hooks
import useUser from "./use-user";
const workspaceService = new WorkspaceService();
const useWorkspaceMembers = (workspaceSlug: string | undefined, fetchCondition?: boolean) => {
fetchCondition = fetchCondition ?? true;
const { user } = useUser();
const { data: workspaceMembers, error: workspaceMemberErrors } = useSWR(
workspaceSlug && fetchCondition ? WORKSPACE_MEMBERS(workspaceSlug) : null,
workspaceSlug && fetchCondition ? () => workspaceService.fetchWorkspaceMembers(workspaceSlug) : null
);
const hasJoined = workspaceMembers?.some((item: any) => item.member.id === (user as any)?.id);
const isOwner = workspaceMembers?.some((item) => item.member.id === (user as any)?.id && item.role === 20);
const isMember = workspaceMembers?.some((item) => item.member.id === (user as any)?.id && item.role === 15);
const isViewer = workspaceMembers?.some((item) => item.member.id === (user as any)?.id && item.role === 10);
const isGuest = workspaceMembers?.some((item) => item.member.id === (user as any)?.id && item.role === 5);
return {
workspaceMembers,
workspaceMemberErrors,
hasJoined,
isOwner,
isMember,
isViewer,
isGuest,
};
};
export default useWorkspaceMembers;

View File

@ -2,9 +2,9 @@ import React, { useState } from "react";
import { useRouter } from "next/router";
import Image from "next/image";
import { useTheme } from "next-themes";
// hooks
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import useUser from "hooks/use-user";
// layouts
import DefaultLayout from "layouts/default-layout";
import { UserAuthWrapper } from "layouts/auth-layout";
@ -17,7 +17,7 @@ import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-l
import { IWorkspace } from "types";
import type { NextPage } from "next";
const CreateWorkspace: NextPage = () => {
const CreateWorkspace: NextPage = observer(() => {
const [defaultValues, setDefaultValues] = useState({
name: "",
slug: "",
@ -27,11 +27,10 @@ const CreateWorkspace: NextPage = () => {
const router = useRouter();
const { user: userStore } = useMobxStore();
const user = userStore.currentUser;
const { theme } = useTheme();
const { user } = useUser();
const onSubmit = async (workspace: IWorkspace) => {
await userStore
.updateCurrentUser({ last_workspace_id: workspace.id })
@ -76,6 +75,6 @@ const CreateWorkspace: NextPage = () => {
</DefaultLayout>
</UserAuthWrapper>
);
};
});
export default CreateWorkspace;

View File

@ -6,7 +6,7 @@ import { UserService } from "services/user.service";
import { WorkspaceService } from "services/workspace.service";
// interfaces
import { IUser, IUserSettings } from "types/users";
import { IWorkspaceMemberMe, IProjectMember } from "types";
import { IWorkspaceMemberMe, IProjectMember, TUserProjectRole, TUserWorkspaceRole } from "types";
import { RootStore } from "./root";
export interface IUserStore {
@ -34,8 +34,8 @@ export interface IUserStore {
currentProjectMemberInfo: IProjectMember | undefined;
currentWorkspaceMemberInfo: IWorkspaceMemberMe | undefined;
currentProjectRole: number | undefined;
currentWorkspaceRole: number | undefined;
currentProjectRole: TUserProjectRole | undefined;
currentWorkspaceRole: TUserWorkspaceRole | undefined;
hasPermissionToCurrentWorkspace: boolean | undefined;
hasPermissionToCurrentProject: boolean | undefined;
@ -261,7 +261,15 @@ class UserStore implements IUserStore {
updateCurrentUser = async (data: Partial<IUser>) => {
try {
runInAction(() => {
this.currentUser = {
...this.currentUser,
...data,
} as IUser;
});
const response = await this.userService.updateUser(data);
runInAction(() => {
this.currentUser = response;
});

View File

@ -1,5 +1,7 @@
import type { IUserLite, IWorkspace, IWorkspaceLite, IUserMemberLite, TStateGroups, IProjectViewProps } from ".";
export type TUserProjectRole = 5 | 10 | 15 | 20;
export interface IProject {
archive_in: number;
close_in: number;
@ -32,7 +34,7 @@ export interface IProject {
is_deployed: boolean;
is_favorite: boolean;
is_member: boolean;
member_role: 5 | 10 | 15 | 20 | null;
member_role: TUserProjectRole | null;
members: IProjectMemberLite[];
issue_views_view: boolean;
module_view: boolean;
@ -76,7 +78,7 @@ export interface IProjectMember {
project: IProjectLite;
workspace: IWorkspaceLite;
comment: string;
role: 5 | 10 | 15 | 20;
role: TUserProjectRole;
preferences: ProjectPreferences;
@ -100,7 +102,7 @@ export interface IProjectMemberInvitation {
token: string;
message: string;
responded_at: Date;
role: 5 | 10 | 15 | 20;
role: TUserProjectRole;
created_at: Date;
updated_at: Date;
@ -109,7 +111,7 @@ export interface IProjectMemberInvitation {
}
export interface IProjectBulkAddFormData {
members: { role: 5 | 10 | 15 | 20; member_id: string }[];
members: { role: TUserProjectRole; member_id: string }[];
}
export interface IGithubRepository {

View File

@ -1,5 +1,7 @@
import type { IProjectMember, IUser, IUserLite, IUserMemberLite, IWorkspaceViewProps } from "types";
export type TUserWorkspaceRole = 5 | 10 | 15 | 20;
export interface IWorkspace {
readonly id: string;
readonly owner: IUser;
@ -30,13 +32,13 @@ export interface IWorkspaceMemberInvitation {
token: string;
message: string;
responded_at: Date;
role: 5 | 10 | 15 | 20;
role: TUserWorkspaceRole;
created_by_detail: IUser;
workspace: IWorkspace;
}
export interface IWorkspaceBulkInviteFormData {
emails: { email: string; role: 5 | 10 | 15 | 20 }[];
emails: { email: string; role: TUserWorkspaceRole }[];
}
export type Properties = {
@ -61,7 +63,7 @@ export interface IWorkspaceMember {
created_by: string;
id: string;
member: IUserLite;
role: 5 | 10 | 15 | 20;
role: TUserWorkspaceRole;
updated_at: Date;
updated_by: string;
workspace: IWorkspaceLite;
@ -74,7 +76,7 @@ export interface IWorkspaceMemberMe {
default_props: IWorkspaceViewProps;
id: string;
member: string;
role: 5 | 10 | 15 | 20;
role: TUserWorkspaceRole;
updated_at: Date;
updated_by: string;
view_props: IWorkspaceViewProps;