mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge branch 'develop' of gurusainath:makeplane/plane into feat-global-views
This commit is contained in:
commit
6bde956166
@ -3,12 +3,20 @@ import { Menu } from "lucide-react";
|
|||||||
import { useApplication } from "hooks/store";
|
import { useApplication } from "hooks/store";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
|
||||||
export const SidebarHamburgerToggle: FC = observer(() => {
|
type Props = {
|
||||||
const { theme: themStore } = useApplication();
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SidebarHamburgerToggle: FC<Props> = observer((props) => {
|
||||||
|
const { onClick } = props
|
||||||
|
const { theme: themeStore } = useApplication();
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="w-7 h-7 flex-shrink-0 rounded flex justify-center items-center bg-custom-background-80 transition-all hover:bg-custom-background-90 cursor-pointer group md:hidden"
|
className="w-7 h-7 flex-shrink-0 rounded flex justify-center items-center bg-custom-background-80 transition-all hover:bg-custom-background-90 cursor-pointer group md:hidden"
|
||||||
onClick={() => themStore.toggleMobileSidebar()}
|
onClick={() => {
|
||||||
|
if (onClick) onClick()
|
||||||
|
else themeStore.toggleMobileSidebar()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Menu size={14} className="text-custom-text-200 group-hover:text-custom-text-100 transition-all" />
|
<Menu size={14} className="text-custom-text-200 group-hover:text-custom-text-100 transition-all" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { FC, MouseEvent, useState } from "react";
|
import { FC, MouseEvent, useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useCycle, useUser } from "hooks/store";
|
import { useEventTracker, useCycle, useUser } from "hooks/store";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
@ -26,7 +27,7 @@ export interface ICyclesBoardCard {
|
|||||||
cycleId: string;
|
cycleId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
export const CyclesBoardCard: FC<ICyclesBoardCard> = observer((props) => {
|
||||||
const { cycleId, workspaceSlug, projectId } = props;
|
const { cycleId, workspaceSlug, projectId } = props;
|
||||||
// states
|
// states
|
||||||
const [updateModal, setUpdateModal] = useState(false);
|
const [updateModal, setUpdateModal] = useState(false);
|
||||||
@ -69,8 +70,8 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
|||||||
? cycleTotalIssues === 0
|
? cycleTotalIssues === 0
|
||||||
? "0 Issue"
|
? "0 Issue"
|
||||||
: cycleTotalIssues === cycleDetails.completed_issues
|
: cycleTotalIssues === cycleDetails.completed_issues
|
||||||
? `${cycleTotalIssues} Issue${cycleTotalIssues > 1 ? "s" : ""}`
|
? `${cycleTotalIssues} Issue${cycleTotalIssues > 1 ? "s" : ""}`
|
||||||
: `${cycleDetails.completed_issues}/${cycleTotalIssues} Issues`
|
: `${cycleDetails.completed_issues}/${cycleTotalIssues} Issues`
|
||||||
: "0 Issue";
|
: "0 Issue";
|
||||||
|
|
||||||
const handleCopyText = (e: MouseEvent<HTMLButtonElement>) => {
|
const handleCopyText = (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
@ -295,4 +296,4 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { FC, MouseEvent, useState } from "react";
|
import { FC, MouseEvent, useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useCycle, useUser } from "hooks/store";
|
import { useEventTracker, useCycle, useUser } from "hooks/store";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
@ -30,7 +31,7 @@ type TCyclesListItem = {
|
|||||||
projectId: string;
|
projectId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
export const CyclesListItem: FC<TCyclesListItem> = observer((props) => {
|
||||||
const { cycleId, workspaceSlug, projectId } = props;
|
const { cycleId, workspaceSlug, projectId } = props;
|
||||||
// states
|
// states
|
||||||
const [updateModal, setUpdateModal] = useState(false);
|
const [updateModal, setUpdateModal] = useState(false);
|
||||||
@ -289,4 +290,4 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
|||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
CalendarCheck2,
|
CalendarCheck2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEstimate, useIssueDetail, useProject, useProjectState, useUser } from "hooks/store";
|
import { useEstimate, useIssueDetail, useProject, useUser } from "hooks/store";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// components
|
// components
|
||||||
import {
|
import {
|
||||||
@ -56,11 +56,9 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
const { workspaceSlug, projectId, issueId, issueOperations, is_archived, is_editable } = props;
|
const { workspaceSlug, projectId, issueId, issueOperations, is_archived, is_editable } = props;
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { inboxIssueId } = router.query;
|
|
||||||
// store hooks
|
// store hooks
|
||||||
const { getProjectById } = useProject();
|
const { getProjectById } = useProject();
|
||||||
const { currentUser } = useUser();
|
const { currentUser } = useUser();
|
||||||
const { projectStates } = useProjectState();
|
|
||||||
const { areEstimatesEnabledForCurrentProject } = useEstimate();
|
const { areEstimatesEnabledForCurrentProject } = useEstimate();
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
const {
|
const {
|
||||||
@ -91,8 +89,6 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
const maxDate = issue.target_date ? new Date(issue.target_date) : null;
|
const maxDate = issue.target_date ? new Date(issue.target_date) : null;
|
||||||
maxDate?.setDate(maxDate.getDate());
|
maxDate?.setDate(maxDate.getDate());
|
||||||
|
|
||||||
const currentIssueState = projectStates?.find((s) => s.id === issue.state_id);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{workspaceSlug && projectId && issue && (
|
{workspaceSlug && projectId && issue && (
|
||||||
@ -108,22 +104,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex h-full w-full flex-col divide-y-2 divide-custom-border-200 overflow-hidden">
|
<div className="flex h-full w-full flex-col divide-y-2 divide-custom-border-200 overflow-hidden">
|
||||||
<div className="flex items-center justify-between px-5 pb-3">
|
<div className="flex items-center justify-end px-5 pb-3">
|
||||||
<div className="flex items-center gap-x-2">
|
|
||||||
{currentIssueState ? (
|
|
||||||
<StateGroupIcon
|
|
||||||
className="h-4 w-4"
|
|
||||||
stateGroup={currentIssueState.group}
|
|
||||||
color={currentIssueState.color}
|
|
||||||
/>
|
|
||||||
) : inboxIssueId ? (
|
|
||||||
<StateGroupIcon className="h-4 w-4" stateGroup="backlog" color="#ff7700" />
|
|
||||||
) : null}
|
|
||||||
<h4 className="text-lg font-medium text-custom-text-300">
|
|
||||||
{projectDetails?.identifier}-{issue?.sequence_id}
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{currentUser && !is_archived && (
|
{currentUser && !is_archived && (
|
||||||
<IssueSubscription workspaceSlug={workspaceSlug} projectId={projectId} issueId={issueId} />
|
<IssueSubscription workspaceSlug={workspaceSlug} projectId={projectId} issueId={issueId} />
|
||||||
@ -187,8 +168,9 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
buttonVariant={issue?.assignee_ids?.length > 1 ? "transparent-without-text" : "transparent-with-text"}
|
buttonVariant={issue?.assignee_ids?.length > 1 ? "transparent-without-text" : "transparent-with-text"}
|
||||||
className="w-3/5 flex-grow group"
|
className="w-3/5 flex-grow group"
|
||||||
buttonContainerClassName="w-full text-left"
|
buttonContainerClassName="w-full text-left"
|
||||||
buttonClassName={`text-sm justify-between ${issue?.assignee_ids.length > 0 ? "" : "text-custom-text-400"
|
buttonClassName={`text-sm justify-between ${
|
||||||
}`}
|
issue?.assignee_ids.length > 0 ? "" : "text-custom-text-400"
|
||||||
|
}`}
|
||||||
hideIcon={issue.assignee_ids?.length === 0}
|
hideIcon={issue.assignee_ids?.length === 0}
|
||||||
dropdownArrow
|
dropdownArrow
|
||||||
dropdownArrowClassName="h-3.5 w-3.5 hidden group-hover:inline"
|
dropdownArrowClassName="h-3.5 w-3.5 hidden group-hover:inline"
|
||||||
@ -232,8 +214,8 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
buttonClassName={`text-sm ${issue?.start_date ? "" : "text-custom-text-400"}`}
|
buttonClassName={`text-sm ${issue?.start_date ? "" : "text-custom-text-400"}`}
|
||||||
hideIcon
|
hideIcon
|
||||||
clearIconClassName="h-3 w-3 hidden group-hover:inline"
|
clearIconClassName="h-3 w-3 hidden group-hover:inline"
|
||||||
// TODO: add this logic
|
// TODO: add this logic
|
||||||
// showPlaceholderIcon
|
// showPlaceholderIcon
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -258,8 +240,8 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
buttonClassName={`text-sm ${issue?.target_date ? "" : "text-custom-text-400"}`}
|
buttonClassName={`text-sm ${issue?.target_date ? "" : "text-custom-text-400"}`}
|
||||||
hideIcon
|
hideIcon
|
||||||
clearIconClassName="h-3 w-3 hidden group-hover:inline"
|
clearIconClassName="h-3 w-3 hidden group-hover:inline"
|
||||||
// TODO: add this logic
|
// TODO: add this logic
|
||||||
// showPlaceholderIcon
|
// showPlaceholderIcon
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,29 +1,42 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { PlusIcon } from "lucide-react";
|
import { PlusIcon } from "lucide-react";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
// hooks
|
// hooks
|
||||||
import { useApplication, useEventTracker, useIssueDetail, useIssues, useUser } from "hooks/store";
|
import { useApplication, useEventTracker, useIssueDetail, useIssues, useUser } from "hooks/store";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// components
|
// components
|
||||||
import { ExistingIssuesListModal } from "components/core";
|
import { ExistingIssuesListModal } from "components/core";
|
||||||
|
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
|
||||||
// types
|
// types
|
||||||
import { ISearchIssueResponse, TIssueLayouts } from "@plane/types";
|
import { ISearchIssueResponse, TIssueLayouts } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
import { EIssuesStoreType } from "constants/issue";
|
import { EIssuesStoreType } from "constants/issue";
|
||||||
import { CYCLE_EMPTY_STATE_DETAILS } from "constants/empty-state";
|
import { CYCLE_EMPTY_STATE_DETAILS, EMPTY_FILTER_STATE_DETAILS } from "constants/empty-state";
|
||||||
import { useTheme } from "next-themes";
|
|
||||||
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspaceSlug: string | undefined;
|
workspaceSlug: string | undefined;
|
||||||
projectId: string | undefined;
|
projectId: string | undefined;
|
||||||
cycleId: string | undefined;
|
cycleId: string | undefined;
|
||||||
activeLayout: TIssueLayouts | undefined;
|
activeLayout: TIssueLayouts | undefined;
|
||||||
|
handleClearAllFilters: () => void;
|
||||||
|
isEmptyFilters?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface EmptyStateProps {
|
||||||
|
title: string;
|
||||||
|
image: string;
|
||||||
|
description?: string;
|
||||||
|
comicBox?: { title: string; description: string };
|
||||||
|
primaryButton?: { text: string; icon?: React.ReactNode; onClick: () => void };
|
||||||
|
secondaryButton?: { text: string; icon?: React.ReactNode; onClick: () => void };
|
||||||
|
size?: "lg" | "sm" | undefined;
|
||||||
|
disabled?: boolean | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export const CycleEmptyState: React.FC<Props> = observer((props) => {
|
export const CycleEmptyState: React.FC<Props> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, cycleId, activeLayout } = props;
|
const { workspaceSlug, projectId, cycleId, activeLayout, handleClearAllFilters, isEmptyFilters = false } = props;
|
||||||
// states
|
// states
|
||||||
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
|
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
|
||||||
// theme
|
// theme
|
||||||
@ -65,10 +78,41 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
|
|||||||
const emptyStateDetail = CYCLE_EMPTY_STATE_DETAILS["no-issues"];
|
const emptyStateDetail = CYCLE_EMPTY_STATE_DETAILS["no-issues"];
|
||||||
|
|
||||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||||
|
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath("empty-filters", activeLayout ?? "list", isLightMode);
|
||||||
const emptyStateImage = getEmptyStateImagePath("cycle-issues", activeLayout ?? "list", isLightMode);
|
const emptyStateImage = getEmptyStateImagePath("cycle-issues", activeLayout ?? "list", isLightMode);
|
||||||
|
|
||||||
const isEditingAllowed = !!userRole && userRole >= EUserProjectRoles.MEMBER;
|
const isEditingAllowed = !!userRole && userRole >= EUserProjectRoles.MEMBER;
|
||||||
|
|
||||||
|
const emptyStateProps: EmptyStateProps = isEmptyFilters
|
||||||
|
? {
|
||||||
|
title: EMPTY_FILTER_STATE_DETAILS["project"].title,
|
||||||
|
image: currentLayoutEmptyStateImagePath,
|
||||||
|
secondaryButton: {
|
||||||
|
text: EMPTY_FILTER_STATE_DETAILS["project"].secondaryButton.text,
|
||||||
|
onClick: handleClearAllFilters,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
title: emptyStateDetail.title,
|
||||||
|
description: emptyStateDetail.description,
|
||||||
|
image: emptyStateImage,
|
||||||
|
primaryButton: {
|
||||||
|
text: emptyStateDetail.primaryButton.text,
|
||||||
|
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||||
|
onClick: () => {
|
||||||
|
setTrackElement("Cycle issue empty state");
|
||||||
|
toggleCreateIssueModal(true, EIssuesStoreType.CYCLE);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
secondaryButton: {
|
||||||
|
text: emptyStateDetail.secondaryButton.text,
|
||||||
|
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||||
|
onClick: () => setCycleIssuesListModal(true),
|
||||||
|
},
|
||||||
|
size: "sm",
|
||||||
|
disabled: !isEditingAllowed,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ExistingIssuesListModal
|
<ExistingIssuesListModal
|
||||||
@ -80,26 +124,7 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
|
|||||||
handleOnSubmit={handleAddIssuesToCycle}
|
handleOnSubmit={handleAddIssuesToCycle}
|
||||||
/>
|
/>
|
||||||
<div className="grid h-full w-full place-items-center">
|
<div className="grid h-full w-full place-items-center">
|
||||||
<EmptyState
|
<EmptyState {...emptyStateProps} />
|
||||||
title={emptyStateDetail.title}
|
|
||||||
description={emptyStateDetail.description}
|
|
||||||
image={emptyStateImage}
|
|
||||||
primaryButton={{
|
|
||||||
text: emptyStateDetail.primaryButton.text,
|
|
||||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
|
||||||
onClick: () => {
|
|
||||||
setTrackElement("Cycle issue empty state");
|
|
||||||
toggleCreateIssueModal(true, EIssuesStoreType.CYCLE);
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
secondaryButton={{
|
|
||||||
text: emptyStateDetail.secondaryButton.text,
|
|
||||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
|
||||||
onClick: () => setCycleIssuesListModal(true),
|
|
||||||
}}
|
|
||||||
size="sm"
|
|
||||||
disabled={!isEditingAllowed}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,29 +1,42 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { PlusIcon } from "lucide-react";
|
import { PlusIcon } from "lucide-react";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
// hooks
|
// hooks
|
||||||
import { useApplication, useEventTracker, useIssues, useUser } from "hooks/store";
|
import { useApplication, useEventTracker, useIssues, useUser } from "hooks/store";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// components
|
// components
|
||||||
import { ExistingIssuesListModal } from "components/core";
|
import { ExistingIssuesListModal } from "components/core";
|
||||||
|
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
|
||||||
// types
|
// types
|
||||||
import { ISearchIssueResponse, TIssueLayouts } from "@plane/types";
|
import { ISearchIssueResponse, TIssueLayouts } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
import { EIssuesStoreType } from "constants/issue";
|
import { EIssuesStoreType } from "constants/issue";
|
||||||
import { MODULE_EMPTY_STATE_DETAILS } from "constants/empty-state";
|
import { EMPTY_FILTER_STATE_DETAILS, MODULE_EMPTY_STATE_DETAILS } from "constants/empty-state";
|
||||||
import { useTheme } from "next-themes";
|
|
||||||
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspaceSlug: string | undefined;
|
workspaceSlug: string | undefined;
|
||||||
projectId: string | undefined;
|
projectId: string | undefined;
|
||||||
moduleId: string | undefined;
|
moduleId: string | undefined;
|
||||||
activeLayout: TIssueLayouts | undefined;
|
activeLayout: TIssueLayouts | undefined;
|
||||||
|
handleClearAllFilters: () => void;
|
||||||
|
isEmptyFilters?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface EmptyStateProps {
|
||||||
|
title: string;
|
||||||
|
image: string;
|
||||||
|
description?: string;
|
||||||
|
comicBox?: { title: string; description: string };
|
||||||
|
primaryButton?: { text: string; icon?: React.ReactNode; onClick: () => void };
|
||||||
|
secondaryButton?: { text: string; icon?: React.ReactNode; onClick: () => void };
|
||||||
|
size?: "lg" | "sm" | undefined;
|
||||||
|
disabled?: boolean | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export const ModuleEmptyState: React.FC<Props> = observer((props) => {
|
export const ModuleEmptyState: React.FC<Props> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, moduleId, activeLayout } = props;
|
const { workspaceSlug, projectId, moduleId, activeLayout, handleClearAllFilters, isEmptyFilters = false } = props;
|
||||||
// states
|
// states
|
||||||
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
|
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
|
||||||
// theme
|
// theme
|
||||||
@ -59,10 +72,40 @@ export const ModuleEmptyState: React.FC<Props> = observer((props) => {
|
|||||||
const emptyStateDetail = MODULE_EMPTY_STATE_DETAILS["no-issues"];
|
const emptyStateDetail = MODULE_EMPTY_STATE_DETAILS["no-issues"];
|
||||||
|
|
||||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||||
const emptyStateImage = getEmptyStateImagePath("cycle-issues", activeLayout ?? "list", isLightMode);
|
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath("empty-filters", activeLayout ?? "list", isLightMode);
|
||||||
|
const emptyStateImage = getEmptyStateImagePath("module-issues", activeLayout ?? "list", isLightMode);
|
||||||
|
|
||||||
const isEditingAllowed = !!userRole && userRole >= EUserProjectRoles.MEMBER;
|
const isEditingAllowed = !!userRole && userRole >= EUserProjectRoles.MEMBER;
|
||||||
|
|
||||||
|
const emptyStateProps: EmptyStateProps = isEmptyFilters
|
||||||
|
? {
|
||||||
|
title: EMPTY_FILTER_STATE_DETAILS["project"].title,
|
||||||
|
image: currentLayoutEmptyStateImagePath,
|
||||||
|
secondaryButton: {
|
||||||
|
text: EMPTY_FILTER_STATE_DETAILS["project"].secondaryButton.text,
|
||||||
|
onClick: handleClearAllFilters,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
title: emptyStateDetail.title,
|
||||||
|
description: emptyStateDetail.description,
|
||||||
|
image: emptyStateImage,
|
||||||
|
primaryButton: {
|
||||||
|
text: emptyStateDetail.primaryButton.text,
|
||||||
|
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||||
|
onClick: () => {
|
||||||
|
setTrackElement("Module issue empty state");
|
||||||
|
toggleCreateIssueModal(true, EIssuesStoreType.MODULE);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
secondaryButton: {
|
||||||
|
text: emptyStateDetail.secondaryButton.text,
|
||||||
|
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||||
|
onClick: () => setModuleIssuesListModal(true),
|
||||||
|
},
|
||||||
|
disabled: !isEditingAllowed,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ExistingIssuesListModal
|
<ExistingIssuesListModal
|
||||||
@ -74,25 +117,7 @@ export const ModuleEmptyState: React.FC<Props> = observer((props) => {
|
|||||||
handleOnSubmit={handleAddIssuesToModule}
|
handleOnSubmit={handleAddIssuesToModule}
|
||||||
/>
|
/>
|
||||||
<div className="grid h-full w-full place-items-center">
|
<div className="grid h-full w-full place-items-center">
|
||||||
<EmptyState
|
<EmptyState {...emptyStateProps} />
|
||||||
title={emptyStateDetail.title}
|
|
||||||
description={emptyStateDetail.description}
|
|
||||||
image={emptyStateImage}
|
|
||||||
primaryButton={{
|
|
||||||
text: emptyStateDetail.primaryButton.text,
|
|
||||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
|
||||||
onClick: () => {
|
|
||||||
setTrackElement("Module issue empty state");
|
|
||||||
toggleCreateIssueModal(true, EIssuesStoreType.MODULE);
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
secondaryButton={{
|
|
||||||
text: emptyStateDetail.secondaryButton.text,
|
|
||||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
|
||||||
onClick: () => setModuleIssuesListModal(true),
|
|
||||||
}}
|
|
||||||
disabled={!isEditingAllowed}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -2,6 +2,7 @@ import React, { Fragment, useState } from "react";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
import size from "lodash/size";
|
||||||
// hooks
|
// hooks
|
||||||
import { useCycle, useIssues } from "hooks/store";
|
import { useCycle, useIssues } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
@ -18,7 +19,9 @@ import {
|
|||||||
import { TransferIssues, TransferIssuesModal } from "components/cycles";
|
import { TransferIssues, TransferIssuesModal } from "components/cycles";
|
||||||
import { ActiveLoader } from "components/ui";
|
import { ActiveLoader } from "components/ui";
|
||||||
// constants
|
// constants
|
||||||
import { EIssuesStoreType } from "constants/issue";
|
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
||||||
|
// types
|
||||||
|
import { IIssueFilterOptions } from "@plane/types";
|
||||||
|
|
||||||
export const CycleLayoutRoot: React.FC = observer(() => {
|
export const CycleLayoutRoot: React.FC = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -51,6 +54,31 @@ export const CycleLayoutRoot: React.FC = observer(() => {
|
|||||||
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
|
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
|
||||||
const cycleStatus = cycleDetails?.status?.toLocaleLowerCase() ?? "draft";
|
const cycleStatus = cycleDetails?.status?.toLocaleLowerCase() ?? "draft";
|
||||||
|
|
||||||
|
const userFilters = issuesFilter?.issueFilters?.filters;
|
||||||
|
|
||||||
|
const issueFilterCount = size(
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(userFilters ?? {}).filter(([, value]) => value && Array.isArray(value) && value.length > 0)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClearAllFilters = () => {
|
||||||
|
if (!workspaceSlug || !projectId || !cycleId) return;
|
||||||
|
const newFilters: IIssueFilterOptions = {};
|
||||||
|
Object.keys(userFilters ?? {}).forEach((key) => {
|
||||||
|
newFilters[key as keyof IIssueFilterOptions] = null;
|
||||||
|
});
|
||||||
|
issuesFilter.updateFilters(
|
||||||
|
workspaceSlug.toString(),
|
||||||
|
projectId.toString(),
|
||||||
|
EIssueFilterType.FILTERS,
|
||||||
|
{
|
||||||
|
...newFilters,
|
||||||
|
},
|
||||||
|
cycleId.toString()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
if (!workspaceSlug || !projectId || !cycleId) return <></>;
|
if (!workspaceSlug || !projectId || !cycleId) return <></>;
|
||||||
|
|
||||||
if (issues?.loader === "init-loader" || !issues?.groupedIssueIds) {
|
if (issues?.loader === "init-loader" || !issues?.groupedIssueIds) {
|
||||||
@ -71,6 +99,8 @@ export const CycleLayoutRoot: React.FC = observer(() => {
|
|||||||
projectId={projectId.toString()}
|
projectId={projectId.toString()}
|
||||||
cycleId={cycleId.toString()}
|
cycleId={cycleId.toString()}
|
||||||
activeLayout={activeLayout}
|
activeLayout={activeLayout}
|
||||||
|
handleClearAllFilters={handleClearAllFilters}
|
||||||
|
isEmptyFilters={issueFilterCount > 0}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
@ -2,6 +2,7 @@ import React, { Fragment } from "react";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
import size from "lodash/size";
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useIssues } from "hooks/store";
|
import { useIssues } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
@ -17,7 +18,9 @@ import {
|
|||||||
} from "components/issues";
|
} from "components/issues";
|
||||||
import { ActiveLoader } from "components/ui";
|
import { ActiveLoader } from "components/ui";
|
||||||
// constants
|
// constants
|
||||||
import { EIssuesStoreType } from "constants/issue";
|
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
||||||
|
// types
|
||||||
|
import { IIssueFilterOptions } from "@plane/types";
|
||||||
|
|
||||||
export const ModuleLayoutRoot: React.FC = observer(() => {
|
export const ModuleLayoutRoot: React.FC = observer(() => {
|
||||||
// router
|
// router
|
||||||
@ -43,6 +46,31 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const userFilters = issuesFilter?.issueFilters?.filters;
|
||||||
|
|
||||||
|
const issueFilterCount = size(
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(userFilters ?? {}).filter(([, value]) => value && Array.isArray(value) && value.length > 0)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClearAllFilters = () => {
|
||||||
|
if (!workspaceSlug || !projectId || !moduleId) return;
|
||||||
|
const newFilters: IIssueFilterOptions = {};
|
||||||
|
Object.keys(userFilters ?? {}).forEach((key) => {
|
||||||
|
newFilters[key as keyof IIssueFilterOptions] = null;
|
||||||
|
});
|
||||||
|
issuesFilter.updateFilters(
|
||||||
|
workspaceSlug.toString(),
|
||||||
|
projectId.toString(),
|
||||||
|
EIssueFilterType.FILTERS,
|
||||||
|
{
|
||||||
|
...newFilters,
|
||||||
|
},
|
||||||
|
moduleId.toString()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
if (!workspaceSlug || !projectId || !moduleId) return <></>;
|
if (!workspaceSlug || !projectId || !moduleId) return <></>;
|
||||||
|
|
||||||
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout || undefined;
|
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout || undefined;
|
||||||
@ -62,6 +90,8 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
|
|||||||
projectId={projectId.toString()}
|
projectId={projectId.toString()}
|
||||||
moduleId={moduleId.toString()}
|
moduleId={moduleId.toString()}
|
||||||
activeLayout={activeLayout}
|
activeLayout={activeLayout}
|
||||||
|
handleClearAllFilters={handleClearAllFilters}
|
||||||
|
isEmptyFilters={issueFilterCount > 0}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
import { useApplication,useEventTracker, useProject } from "hooks/store";
|
import { useApplication, useEventTracker, useProject } from "hooks/store";
|
||||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// helpers
|
// helpers
|
||||||
@ -131,7 +131,7 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
const handleProjectClick = () => {
|
const handleProjectClick = () => {
|
||||||
if (window.innerWidth < 768) {
|
if (window.innerWidth < 768) {
|
||||||
themeStore.toggleSidebar();
|
themeStore.toggleMobileSidebar();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -147,9 +147,8 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
|||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={`group relative flex w-full items-center rounded-md px-2 py-1 text-custom-sidebar-text-10 hover:bg-custom-sidebar-background-80 ${
|
className={`group relative flex w-full items-center rounded-md px-2 py-1 text-custom-sidebar-text-10 hover:bg-custom-sidebar-background-80 ${snapshot?.isDragging ? "opacity-60" : ""
|
||||||
snapshot?.isDragging ? "opacity-60" : ""
|
} ${isMenuActive ? "!bg-custom-sidebar-background-80" : ""}`}
|
||||||
} ${isMenuActive ? "!bg-custom-sidebar-background-80" : ""}`}
|
|
||||||
>
|
>
|
||||||
{provided && (
|
{provided && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@ -158,11 +157,9 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`absolute -left-2.5 top-1/2 hidden -translate-y-1/2 rounded p-0.5 text-custom-sidebar-text-400 ${
|
className={`absolute -left-2.5 top-1/2 hidden -translate-y-1/2 rounded p-0.5 text-custom-sidebar-text-400 ${isCollapsed ? "" : "group-hover:!flex"
|
||||||
isCollapsed ? "" : "group-hover:!flex"
|
} ${project.sort_order === null ? "cursor-not-allowed opacity-60" : ""} ${isMenuActive ? "!flex" : ""
|
||||||
} ${project.sort_order === null ? "cursor-not-allowed opacity-60" : ""} ${
|
}`}
|
||||||
isMenuActive ? "!flex" : ""
|
|
||||||
}`}
|
|
||||||
{...provided?.dragHandleProps}
|
{...provided?.dragHandleProps}
|
||||||
>
|
>
|
||||||
<MoreVertical className="h-3.5" />
|
<MoreVertical className="h-3.5" />
|
||||||
@ -173,14 +170,12 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
|||||||
<Tooltip tooltipContent={`${project.name}`} position="right" className="ml-2" disabled={!isCollapsed}>
|
<Tooltip tooltipContent={`${project.name}`} position="right" className="ml-2" disabled={!isCollapsed}>
|
||||||
<Disclosure.Button
|
<Disclosure.Button
|
||||||
as="div"
|
as="div"
|
||||||
className={`flex flex-grow cursor-pointer select-none items-center truncate text-left text-sm font-medium ${
|
className={`flex flex-grow cursor-pointer select-none items-center truncate text-left text-sm font-medium ${isCollapsed ? "justify-center" : `justify-between`
|
||||||
isCollapsed ? "justify-center" : `justify-between`
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex w-full flex-grow items-center gap-x-2 truncate ${
|
className={`flex w-full flex-grow items-center gap-x-2 truncate ${isCollapsed ? "justify-center" : ""
|
||||||
isCollapsed ? "justify-center" : ""
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{project.emoji ? (
|
{project.emoji ? (
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
||||||
@ -200,9 +195,8 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
{!isCollapsed && (
|
{!isCollapsed && (
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
className={`hidden h-4 w-4 flex-shrink-0 ${open ? "rotate-180" : ""} ${
|
className={`hidden h-4 w-4 flex-shrink-0 ${open ? "rotate-180" : ""} ${isMenuActive ? "!block" : ""
|
||||||
isMenuActive ? "!block" : ""
|
} mb-0.5 text-custom-sidebar-text-400 duration-300 group-hover:!block`}
|
||||||
} mb-0.5 text-custom-sidebar-text-400 duration-300 group-hover:!block`}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Disclosure.Button>
|
</Disclosure.Button>
|
||||||
@ -326,11 +320,10 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
|||||||
disabled={!isCollapsed}
|
disabled={!isCollapsed}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`group flex items-center gap-2.5 rounded-md px-2 py-1.5 text-xs font-medium outline-none ${
|
className={`group flex items-center gap-2.5 rounded-md px-2 py-1.5 text-xs font-medium outline-none ${router.asPath.includes(item.href)
|
||||||
router.asPath.includes(item.href)
|
? "bg-custom-primary-100/10 text-custom-primary-100"
|
||||||
? "bg-custom-primary-100/10 text-custom-primary-100"
|
: "text-custom-sidebar-text-300 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
|
||||||
: "text-custom-sidebar-text-300 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
|
} ${isCollapsed ? "justify-center" : ""}`}
|
||||||
} ${isCollapsed ? "justify-center" : ""}`}
|
|
||||||
>
|
>
|
||||||
<item.Icon className="h-4 w-4 stroke-[1.5]" />
|
<item.Icon className="h-4 w-4 stroke-[1.5]" />
|
||||||
{!isCollapsed && item.name}
|
{!isCollapsed && item.name}
|
||||||
|
@ -10,6 +10,7 @@ import { FileText, HelpCircle, MessagesSquare, MoveLeft, Zap } from "lucide-reac
|
|||||||
import { DiscordIcon, GithubIcon, Tooltip } from "@plane/ui";
|
import { DiscordIcon, GithubIcon, Tooltip } from "@plane/ui";
|
||||||
// assets
|
// assets
|
||||||
import packageJson from "package.json";
|
import packageJson from "package.json";
|
||||||
|
import useSize from "hooks/use-window-size";
|
||||||
|
|
||||||
const helpOptions = [
|
const helpOptions = [
|
||||||
{
|
{
|
||||||
@ -42,9 +43,11 @@ export interface WorkspaceHelpSectionProps {
|
|||||||
export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observer(() => {
|
export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observer(() => {
|
||||||
// store hooks
|
// store hooks
|
||||||
const {
|
const {
|
||||||
theme: { sidebarCollapsed, toggleSidebar },
|
theme: { sidebarCollapsed, toggleSidebar, toggleMobileSidebar },
|
||||||
commandPalette: { toggleShortcutModal },
|
commandPalette: { toggleShortcutModal },
|
||||||
} = useApplication();
|
} = useApplication();
|
||||||
|
|
||||||
|
const [windowWidth] = useSize();
|
||||||
// states
|
// states
|
||||||
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
|
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
|
||||||
// refs
|
// refs
|
||||||
@ -57,9 +60,8 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observe
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={`flex w-full items-center justify-between gap-1 self-baseline border-t border-custom-border-200 bg-custom-sidebar-background-100 px-4 py-2 ${
|
className={`flex w-full items-center justify-between gap-1 self-baseline border-t border-custom-border-200 bg-custom-sidebar-background-100 px-4 py-2 ${isCollapsed ? "flex-col" : ""
|
||||||
isCollapsed ? "flex-col" : ""
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{!isCollapsed && (
|
{!isCollapsed && (
|
||||||
<div className="w-1/2 cursor-default rounded-md bg-green-500/10 px-2.5 py-1.5 text-center text-sm font-medium text-green-500 outline-none">
|
<div className="w-1/2 cursor-default rounded-md bg-green-500/10 px-2.5 py-1.5 text-center text-sm font-medium text-green-500 outline-none">
|
||||||
@ -70,9 +72,8 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observe
|
|||||||
<Tooltip tooltipContent="Shortcuts">
|
<Tooltip tooltipContent="Shortcuts">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`grid place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 ${
|
className={`grid place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 ${isCollapsed ? "w-full" : ""
|
||||||
isCollapsed ? "w-full" : ""
|
}`}
|
||||||
}`}
|
|
||||||
onClick={() => toggleShortcutModal(true)}
|
onClick={() => toggleShortcutModal(true)}
|
||||||
>
|
>
|
||||||
<Zap className="h-3.5 w-3.5" />
|
<Zap className="h-3.5 w-3.5" />
|
||||||
@ -81,9 +82,8 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observe
|
|||||||
<Tooltip tooltipContent="Help">
|
<Tooltip tooltipContent="Help">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`grid place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 ${
|
className={`grid place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 ${isCollapsed ? "w-full" : ""
|
||||||
isCollapsed ? "w-full" : ""
|
}`}
|
||||||
}`}
|
|
||||||
onClick={() => setIsNeedHelpOpen((prev) => !prev)}
|
onClick={() => setIsNeedHelpOpen((prev) => !prev)}
|
||||||
>
|
>
|
||||||
<HelpCircle className="h-3.5 w-3.5" />
|
<HelpCircle className="h-3.5 w-3.5" />
|
||||||
@ -93,7 +93,7 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observe
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="grid place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 md:hidden"
|
className="grid place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 md:hidden"
|
||||||
onClick={() => toggleSidebar()}
|
onClick={() => windowWidth <= 768 ? toggleMobileSidebar() : toggleSidebar()}
|
||||||
>
|
>
|
||||||
<MoveLeft className="h-3.5 w-3.5" />
|
<MoveLeft className="h-3.5 w-3.5" />
|
||||||
</button>
|
</button>
|
||||||
@ -101,10 +101,9 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observe
|
|||||||
<Tooltip tooltipContent={`${isCollapsed ? "Expand" : "Hide"}`}>
|
<Tooltip tooltipContent={`${isCollapsed ? "Expand" : "Hide"}`}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`hidden place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 md:grid ${
|
className={`hidden place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 md:grid ${isCollapsed ? "w-full" : ""
|
||||||
isCollapsed ? "w-full" : ""
|
}`}
|
||||||
}`}
|
onClick={() => windowWidth <= 768 ? toggleMobileSidebar() : toggleSidebar()}
|
||||||
onClick={() => toggleSidebar()}
|
|
||||||
>
|
>
|
||||||
<MoveLeft className={`h-3.5 w-3.5 duration-300 ${isCollapsed ? "rotate-180" : ""}`} />
|
<MoveLeft className={`h-3.5 w-3.5 duration-300 ${isCollapsed ? "rotate-180" : ""}`} />
|
||||||
</button>
|
</button>
|
||||||
@ -122,9 +121,8 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observe
|
|||||||
leaveTo="transform opacity-0 scale-95"
|
leaveTo="transform opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`absolute bottom-2 min-w-[10rem] ${
|
className={`absolute bottom-2 min-w-[10rem] ${isCollapsed ? "left-full" : "-left-[75px]"
|
||||||
isCollapsed ? "left-full" : "-left-[75px]"
|
} divide-y divide-custom-border-200 whitespace-nowrap rounded bg-custom-background-100 p-1 shadow-custom-shadow-xs`}
|
||||||
} divide-y divide-custom-border-200 whitespace-nowrap rounded bg-custom-background-100 p-1 shadow-custom-shadow-xs`}
|
|
||||||
ref={helpOptionsRef}
|
ref={helpOptionsRef}
|
||||||
>
|
>
|
||||||
<div className="space-y-1 pb-2">
|
<div className="space-y-1 pb-2">
|
||||||
|
@ -54,7 +54,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
|||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
// store hooks
|
// store hooks
|
||||||
const {
|
const {
|
||||||
theme: { sidebarCollapsed, toggleSidebar },
|
theme: { sidebarCollapsed, toggleMobileSidebar },
|
||||||
} = useApplication();
|
} = useApplication();
|
||||||
const { setTrackElement } = useEventTracker();
|
const { setTrackElement } = useEventTracker();
|
||||||
const { currentUser, updateCurrentUser, isUserInstanceAdmin, signOut } = useUser();
|
const { currentUser, updateCurrentUser, isUserInstanceAdmin, signOut } = useUser();
|
||||||
@ -98,7 +98,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
|||||||
};
|
};
|
||||||
const handleItemClick = () => {
|
const handleItemClick = () => {
|
||||||
if (window.innerWidth < 768) {
|
if (window.innerWidth < 768) {
|
||||||
toggleSidebar();
|
toggleMobileSidebar();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const workspacesList = Object.values(workspaces ?? {});
|
const workspacesList = Object.values(workspaces ?? {});
|
||||||
@ -110,15 +110,13 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
|||||||
<>
|
<>
|
||||||
<Menu.Button className="group/menu-button h-full w-full truncate rounded-md text-sm font-medium text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:outline-none">
|
<Menu.Button className="group/menu-button h-full w-full truncate rounded-md text-sm font-medium text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:outline-none">
|
||||||
<div
|
<div
|
||||||
className={`flex items-center gap-x-2 truncate rounded p-1 ${
|
className={`flex items-center gap-x-2 truncate rounded p-1 ${sidebarCollapsed ? "justify-center" : "justify-between"
|
||||||
sidebarCollapsed ? "justify-center" : "justify-between"
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 truncate">
|
<div className="flex items-center gap-2 truncate">
|
||||||
<div
|
<div
|
||||||
className={`relative grid h-6 w-6 flex-shrink-0 place-items-center uppercase ${
|
className={`relative grid h-6 w-6 flex-shrink-0 place-items-center uppercase ${!activeWorkspace?.logo && "rounded bg-custom-primary-500 text-white"
|
||||||
!activeWorkspace?.logo && "rounded bg-custom-primary-500 text-white"
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{activeWorkspace?.logo && activeWorkspace.logo !== "" ? (
|
{activeWorkspace?.logo && activeWorkspace.logo !== "" ? (
|
||||||
<img
|
<img
|
||||||
@ -138,9 +136,8 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
{!sidebarCollapsed && (
|
{!sidebarCollapsed && (
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
className={`mx-1 hidden h-4 w-4 flex-shrink-0 group-hover/menu-button:block ${
|
className={`mx-1 hidden h-4 w-4 flex-shrink-0 group-hover/menu-button:block ${open ? "rotate-180" : ""
|
||||||
open ? "rotate-180" : ""
|
} text-custom-sidebar-text-400 duration-300`}
|
||||||
} text-custom-sidebar-text-400 duration-300`}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -179,9 +176,8 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center justify-start gap-2.5 truncate">
|
<div className="flex items-center justify-start gap-2.5 truncate">
|
||||||
<span
|
<span
|
||||||
className={`relative flex h-6 w-6 flex-shrink-0 items-center justify-center p-2 text-xs uppercase ${
|
className={`relative flex h-6 w-6 flex-shrink-0 items-center justify-center p-2 text-xs uppercase ${!workspace?.logo && "rounded bg-custom-primary-500 text-white"
|
||||||
!workspace?.logo && "rounded bg-custom-primary-500 text-white"
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{workspace?.logo && workspace.logo !== "" ? (
|
{workspace?.logo && workspace.logo !== "" ? (
|
||||||
<img
|
<img
|
||||||
@ -194,9 +190,8 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<h5
|
<h5
|
||||||
className={`truncate text-sm font-medium ${
|
className={`truncate text-sm font-medium ${workspaceSlug === workspace.slug ? "" : "text-custom-text-200"
|
||||||
workspaceSlug === workspace.slug ? "" : "text-custom-text-200"
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{workspace.name}
|
{workspace.name}
|
||||||
</h5>
|
</h5>
|
||||||
|
@ -31,7 +31,7 @@ export const WorkspaceSidebarMenu = observer(() => {
|
|||||||
|
|
||||||
const handleLinkClick = (itemKey: string) => {
|
const handleLinkClick = (itemKey: string) => {
|
||||||
if (window.innerWidth < 768) {
|
if (window.innerWidth < 768) {
|
||||||
themeStore.toggleSidebar();
|
themeStore.toggleMobileSidebar();
|
||||||
}
|
}
|
||||||
captureEvent(SIDEBAR_CLICKED, {
|
captureEvent(SIDEBAR_CLICKED, {
|
||||||
destination: itemKey,
|
destination: itemKey,
|
||||||
@ -52,11 +52,10 @@ export const WorkspaceSidebarMenu = observer(() => {
|
|||||||
disabled={!themeStore?.sidebarCollapsed}
|
disabled={!themeStore?.sidebarCollapsed}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${
|
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${link.highlight(router.asPath, `/${workspaceSlug}`)
|
||||||
link.highlight(router.asPath, `/${workspaceSlug}`)
|
? "bg-custom-primary-100/10 text-custom-primary-100"
|
||||||
? "bg-custom-primary-100/10 text-custom-primary-100"
|
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
|
||||||
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
|
} ${themeStore?.sidebarCollapsed ? "justify-center" : ""}`}
|
||||||
} ${themeStore?.sidebarCollapsed ? "justify-center" : ""}`}
|
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
<link.Icon
|
<link.Icon
|
||||||
|
@ -7,6 +7,7 @@ import { CustomMenu } from "@plane/ui";
|
|||||||
import { ChevronDown } from "lucide-react";
|
import { ChevronDown } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { useApplication } from "hooks/store";
|
||||||
|
|
||||||
interface IProfilePreferenceSettingsLayout {
|
interface IProfilePreferenceSettingsLayout {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -16,6 +17,7 @@ interface IProfilePreferenceSettingsLayout {
|
|||||||
export const ProfilePreferenceSettingsLayout: FC<IProfilePreferenceSettingsLayout> = (props) => {
|
export const ProfilePreferenceSettingsLayout: FC<IProfilePreferenceSettingsLayout> = (props) => {
|
||||||
const { children, header } = props;
|
const { children, header } = props;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { theme: themeStore } = useApplication();
|
||||||
|
|
||||||
const showMenuItem = () => {
|
const showMenuItem = () => {
|
||||||
const item = router.asPath.split('/');
|
const item = router.asPath.split('/');
|
||||||
@ -42,7 +44,7 @@ export const ProfilePreferenceSettingsLayout: FC<IProfilePreferenceSettingsLayou
|
|||||||
return (
|
return (
|
||||||
<ProfileSettingsLayout header={
|
<ProfileSettingsLayout header={
|
||||||
<div className="md:hidden flex flex-shrink-0 gap-4 items-center justify-start border-b border-custom-border-200 p-4">
|
<div className="md:hidden flex flex-shrink-0 gap-4 items-center justify-start border-b border-custom-border-200 p-4">
|
||||||
<SidebarHamburgerToggle />
|
<SidebarHamburgerToggle onClick={() => themeStore.toggleSidebar()} />
|
||||||
<CustomMenu
|
<CustomMenu
|
||||||
maxHeight={"md"}
|
maxHeight={"md"}
|
||||||
className="flex flex-grow justify-center text-custom-text-200 text-sm"
|
className="flex flex-grow justify-center text-custom-text-200 text-sm"
|
||||||
|
@ -40,7 +40,7 @@ export const ProfileLayoutSidebar = observer(() => {
|
|||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
// store hooks
|
// store hooks
|
||||||
const {
|
const {
|
||||||
theme: { sidebarCollapsed, toggleSidebar },
|
theme: { sidebarCollapsed, toggleSidebar, toggleMobileSidebar },
|
||||||
} = useApplication();
|
} = useApplication();
|
||||||
const { currentUser, currentUserSettings, signOut } = useUser();
|
const { currentUser, currentUserSettings, signOut } = useUser();
|
||||||
const { workspaces } = useWorkspace();
|
const { workspaces } = useWorkspace();
|
||||||
@ -78,7 +78,7 @@ export const ProfileLayoutSidebar = observer(() => {
|
|||||||
|
|
||||||
const handleItemClick = () => {
|
const handleItemClick = () => {
|
||||||
if (window.innerWidth < 768) {
|
if (window.innerWidth < 768) {
|
||||||
toggleSidebar();
|
toggleMobileSidebar();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ export const ProfileLayoutSidebar = observer(() => {
|
|||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<div ref={ref} className="flex h-full w-full flex-col gap-y-4">
|
<div ref={ref} className="flex h-full w-full flex-col gap-y-4">
|
||||||
<Link href={`/${redirectWorkspaceSlug}`}>
|
<Link href={`/${redirectWorkspaceSlug}`} onClick={handleItemClick}>
|
||||||
<div
|
<div
|
||||||
className={`flex flex-shrink-0 items-center gap-2 truncate px-4 pt-4 ${sidebarCollapsed ? "justify-center" : ""
|
className={`flex flex-shrink-0 items-center gap-2 truncate px-4 pt-4 ${sidebarCollapsed ? "justify-center" : ""
|
||||||
}`}
|
}`}
|
||||||
|
@ -3,7 +3,7 @@ import useSWR from "swr";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
//hooks
|
//hooks
|
||||||
import { useUser } from "hooks/store";
|
import { useApplication, useUser } from "hooks/store";
|
||||||
// services
|
// services
|
||||||
import { UserService } from "services/user.service";
|
import { UserService } from "services/user.service";
|
||||||
// layouts
|
// layouts
|
||||||
@ -29,11 +29,12 @@ const ProfileActivityPage: NextPageWithLayout = observer(() => {
|
|||||||
const { data: userActivity } = useSWR(USER_ACTIVITY, () => userService.getUserActivity());
|
const { data: userActivity } = useSWR(USER_ACTIVITY, () => userService.getUserActivity());
|
||||||
// store hooks
|
// store hooks
|
||||||
const { currentUser } = useUser();
|
const { currentUser } = useUser();
|
||||||
|
const { theme: themeStore } = useApplication();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="mx-auto mt-5 md:mt-16 flex h-full w-full flex-col overflow-hidden px-8 pb-8 lg:w-3/5">
|
<section className="mx-auto mt-5 md:mt-16 flex h-full w-full flex-col overflow-hidden px-8 pb-8 lg:w-3/5">
|
||||||
<div className="flex items-center border-b border-custom-border-100 gap-4 pb-3.5">
|
<div className="flex items-center border-b border-custom-border-100 gap-4 pb-3.5">
|
||||||
<SidebarHamburgerToggle />
|
<SidebarHamburgerToggle onClick={() => themeStore.toggleSidebar()} />
|
||||||
<h3 className="text-xl font-medium">Activity</h3>
|
<h3 className="text-xl font-medium">Activity</h3>
|
||||||
</div>
|
</div>
|
||||||
{userActivity ? (
|
{userActivity ? (
|
||||||
@ -96,12 +97,12 @@ const ProfileActivityPage: NextPageWithLayout = observer(() => {
|
|||||||
|
|
||||||
const message =
|
const message =
|
||||||
activityItem.verb === "created" &&
|
activityItem.verb === "created" &&
|
||||||
activityItem.field !== "cycles" &&
|
activityItem.field !== "cycles" &&
|
||||||
activityItem.field !== "modules" &&
|
activityItem.field !== "modules" &&
|
||||||
activityItem.field !== "attachment" &&
|
activityItem.field !== "attachment" &&
|
||||||
activityItem.field !== "link" &&
|
activityItem.field !== "link" &&
|
||||||
activityItem.field !== "estimate" &&
|
activityItem.field !== "estimate" &&
|
||||||
!activityItem.field ? (
|
!activityItem.field ? (
|
||||||
<span>
|
<span>
|
||||||
created <IssueLink activity={activityItem} />
|
created <IssueLink activity={activityItem} />
|
||||||
</span>
|
</span>
|
||||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "hooks/store";
|
import { useApplication, useUser } from "hooks/store";
|
||||||
// services
|
// services
|
||||||
import { UserService } from "services/user.service";
|
import { UserService } from "services/user.service";
|
||||||
// hooks
|
// hooks
|
||||||
@ -32,7 +32,8 @@ const userService = new UserService();
|
|||||||
|
|
||||||
const ChangePasswordPage: NextPageWithLayout = observer(() => {
|
const ChangePasswordPage: NextPageWithLayout = observer(() => {
|
||||||
const [isPageLoading, setIsPageLoading] = useState(true);
|
const [isPageLoading, setIsPageLoading] = useState(true);
|
||||||
|
// hooks
|
||||||
|
const { theme: themeStore } = useApplication();
|
||||||
const { currentUser } = useUser();
|
const { currentUser } = useUser();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -89,90 +90,90 @@ const ChangePasswordPage: NextPageWithLayout = observer(() => {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
<div className="block md:hidden flex-shrink-0 border-b border-custom-border-200 p-4">
|
<div className="block md:hidden flex-shrink-0 border-b border-custom-border-200 p-4">
|
||||||
<SidebarHamburgerToggle />
|
<SidebarHamburgerToggle onClick={() => themeStore.toggleSidebar()} />
|
||||||
</div>
|
</div>
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit(handleChangePassword)}
|
onSubmit={handleSubmit(handleChangePassword)}
|
||||||
className="mx-auto mt-16 flex h-full w-full flex-col gap-8 px-8 pb-8 lg:w-3/5"
|
className="mx-auto mt-16 flex h-full w-full flex-col gap-8 px-8 pb-8 lg:w-3/5"
|
||||||
>
|
>
|
||||||
<h3 className="text-xl font-medium">Change password</h3>
|
<h3 className="text-xl font-medium">Change password</h3>
|
||||||
<div className="grid-col grid w-full grid-cols-1 items-center justify-between gap-10 xl:grid-cols-2 2xl:grid-cols-3">
|
<div className="grid-col grid w-full grid-cols-1 items-center justify-between gap-10 xl:grid-cols-2 2xl:grid-cols-3">
|
||||||
<div className="flex flex-col gap-1 ">
|
<div className="flex flex-col gap-1 ">
|
||||||
<h4 className="text-sm">Current password</h4>
|
<h4 className="text-sm">Current password</h4>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="old_password"
|
name="old_password"
|
||||||
rules={{
|
rules={{
|
||||||
required: "This field is required",
|
required: "This field is required",
|
||||||
}}
|
}}
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<Input
|
<Input
|
||||||
id="old_password"
|
id="old_password"
|
||||||
type="password"
|
type="password"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
placeholder="Old password"
|
placeholder="Old password"
|
||||||
className="w-full rounded-md font-medium"
|
className="w-full rounded-md font-medium"
|
||||||
hasError={Boolean(errors.old_password)}
|
hasError={Boolean(errors.old_password)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{errors.old_password && <span className="text-xs text-red-500">{errors.old_password.message}</span>}
|
{errors.old_password && <span className="text-xs text-red-500">{errors.old_password.message}</span>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-1 ">
|
||||||
|
<h4 className="text-sm">New password</h4>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="new_password"
|
||||||
|
rules={{
|
||||||
|
required: "This field is required",
|
||||||
|
}}
|
||||||
|
render={({ field: { value, onChange } }) => (
|
||||||
|
<Input
|
||||||
|
id="new_password"
|
||||||
|
type="password"
|
||||||
|
value={value}
|
||||||
|
placeholder="New password"
|
||||||
|
onChange={onChange}
|
||||||
|
className="w-full"
|
||||||
|
hasError={Boolean(errors.new_password)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.new_password && <span className="text-xs text-red-500">{errors.new_password.message}</span>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-1 ">
|
||||||
|
<h4 className="text-sm">Confirm password</h4>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="confirm_password"
|
||||||
|
rules={{
|
||||||
|
required: "This field is required",
|
||||||
|
}}
|
||||||
|
render={({ field: { value, onChange } }) => (
|
||||||
|
<Input
|
||||||
|
id="confirm_password"
|
||||||
|
type="password"
|
||||||
|
placeholder="Confirm password"
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
className="w-full"
|
||||||
|
hasError={Boolean(errors.confirm_password)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.confirm_password && <span className="text-xs text-red-500">{errors.confirm_password.message}</span>}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-1 ">
|
<div className="flex items-center justify-between py-2">
|
||||||
<h4 className="text-sm">New password</h4>
|
<Button variant="primary" type="submit" loading={isSubmitting}>
|
||||||
<Controller
|
{isSubmitting ? "Changing password..." : "Change password"}
|
||||||
control={control}
|
</Button>
|
||||||
name="new_password"
|
|
||||||
rules={{
|
|
||||||
required: "This field is required",
|
|
||||||
}}
|
|
||||||
render={({ field: { value, onChange } }) => (
|
|
||||||
<Input
|
|
||||||
id="new_password"
|
|
||||||
type="password"
|
|
||||||
value={value}
|
|
||||||
placeholder="New password"
|
|
||||||
onChange={onChange}
|
|
||||||
className="w-full"
|
|
||||||
hasError={Boolean(errors.new_password)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{errors.new_password && <span className="text-xs text-red-500">{errors.new_password.message}</span>}
|
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
<div className="flex flex-col gap-1 ">
|
|
||||||
<h4 className="text-sm">Confirm password</h4>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="confirm_password"
|
|
||||||
rules={{
|
|
||||||
required: "This field is required",
|
|
||||||
}}
|
|
||||||
render={({ field: { value, onChange } }) => (
|
|
||||||
<Input
|
|
||||||
id="confirm_password"
|
|
||||||
type="password"
|
|
||||||
placeholder="Confirm password"
|
|
||||||
value={value}
|
|
||||||
onChange={onChange}
|
|
||||||
className="w-full"
|
|
||||||
hasError={Boolean(errors.confirm_password)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{errors.confirm_password && <span className="text-xs text-red-500">{errors.confirm_password.message}</span>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between py-2">
|
|
||||||
<Button variant="primary" type="submit" loading={isSubmitting}>
|
|
||||||
{isSubmitting ? "Changing password..." : "Change password"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -5,7 +5,7 @@ import { observer } from "mobx-react-lite";
|
|||||||
// services
|
// services
|
||||||
import { FileService } from "services/file.service";
|
import { FileService } from "services/file.service";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "hooks/store";
|
import { useApplication, useUser } from "hooks/store";
|
||||||
import useUserAuth from "hooks/use-user-auth";
|
import useUserAuth from "hooks/use-user-auth";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// layouts
|
// layouts
|
||||||
@ -58,6 +58,7 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => {
|
|||||||
const { currentUser: myProfile, updateCurrentUser, currentUserLoader } = useUser();
|
const { currentUser: myProfile, updateCurrentUser, currentUserLoader } = useUser();
|
||||||
// custom hooks
|
// custom hooks
|
||||||
const { } = useUserAuth({ user: myProfile, isLoading: currentUserLoader });
|
const { } = useUserAuth({ user: myProfile, isLoading: currentUserLoader });
|
||||||
|
const { theme: themeStore } = useApplication();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reset({ ...defaultValues, ...myProfile });
|
reset({ ...defaultValues, ...myProfile });
|
||||||
@ -139,7 +140,7 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => {
|
|||||||
<>
|
<>
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
<div className="block md:hidden flex-shrink-0 border-b border-custom-border-200 p-4">
|
<div className="block md:hidden flex-shrink-0 border-b border-custom-border-200 p-4">
|
||||||
<SidebarHamburgerToggle />
|
<SidebarHamburgerToggle onClick={() => themeStore.toggleSidebar()} />
|
||||||
</div>
|
</div>
|
||||||
<div className="overflow-hidden">
|
<div className="overflow-hidden">
|
||||||
<Controller
|
<Controller
|
||||||
|
Loading…
Reference in New Issue
Block a user