forked from github/plane
style: new empty state ui (#2923)
This commit is contained in:
parent
d2a3d00e82
commit
e16e468b8f
113
web/components/common/new-empty-state.tsx
Normal file
113
web/components/common/new-empty-state.tsx
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
// ui
|
||||||
|
import { Button } from "@plane/ui";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: string;
|
||||||
|
description?: React.ReactNode;
|
||||||
|
image: any;
|
||||||
|
comicBox?: {
|
||||||
|
direction: "left" | "right";
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
extraPadding?: boolean;
|
||||||
|
};
|
||||||
|
primaryButton?: {
|
||||||
|
icon?: any;
|
||||||
|
text: string;
|
||||||
|
onClick: () => void;
|
||||||
|
};
|
||||||
|
secondaryButton?: React.ReactNode;
|
||||||
|
disabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NewEmptyState: React.FC<Props> = ({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
image,
|
||||||
|
primaryButton,
|
||||||
|
secondaryButton,
|
||||||
|
disabled = false,
|
||||||
|
comicBox,
|
||||||
|
}) => {
|
||||||
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
setIsHovered(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
setIsHovered(false);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className=" flex flex-col justify-center items-center h-full w-full ">
|
||||||
|
<div className="border border-custom-border-200 rounded-xl px-10 py-7 flex flex-col gap-5 max-w-6xl m-5 md:m-16 shadow-sm">
|
||||||
|
<h3 className="font-semibold text-2xl">{title}</h3>
|
||||||
|
{description && <p className=" text-lg">{description}</p>}
|
||||||
|
<div className="relative w-full max-w-6xl">
|
||||||
|
<Image src={image} className="w-52 sm:w-60" alt={primaryButton?.text} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-center items-start relative">
|
||||||
|
{primaryButton && (
|
||||||
|
<Button
|
||||||
|
className={`max-w-min m-3 relative !px-6 ${comicBox?.direction === "left" ? "flex-row-reverse" : ""}`}
|
||||||
|
size="lg"
|
||||||
|
variant="primary"
|
||||||
|
onClick={primaryButton.onClick}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{primaryButton.text}
|
||||||
|
<div
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
className={`bg-blue-300 absolute ${
|
||||||
|
comicBox?.direction === "left" ? "left-0 ml-2" : "right-0 mr-2"
|
||||||
|
} h-2.5 w-2.5 z-10 rounded-full animate-ping`}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={`bg-blue-400/40 absolute ${
|
||||||
|
comicBox?.direction === "left" ? "left-0 ml-2.5" : "right-0 mr-2.5"
|
||||||
|
} h-1.5 w-1.5 rounded-full`}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{comicBox &&
|
||||||
|
isHovered &&
|
||||||
|
(comicBox.direction === "right" ? (
|
||||||
|
<div
|
||||||
|
className={`flex max-w-sm absolute top-0 left-1/2 ${
|
||||||
|
comicBox?.extraPadding ? "ml-[125px]" : "ml-[90px]"
|
||||||
|
} pb-5`}
|
||||||
|
>
|
||||||
|
<div className="relative w-0 h-0 border-t-[11px] mt-5 border-custom-border-200 border-b-[11px] border-r-[11px] border-y-transparent">
|
||||||
|
<div className="absolute top-[-10px] right-[-12px] w-0 h-0 border-t-[10px] border-custom-background-100 border-b-[10px] border-r-[10px] border-y-transparent" />
|
||||||
|
</div>
|
||||||
|
<div className="border border-custom-border-200 rounded-md bg-custom-background-100">
|
||||||
|
<h1 className="p-5">
|
||||||
|
<h3 className="font-semibold text-lg">{comicBox?.title}</h3>
|
||||||
|
<h4 className="text-sm mt-1">{comicBox?.description}</h4>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-row-reverse max-w-sm absolute top-0 right-1/2 mr-[90px] pb-5">
|
||||||
|
<div className="relative w-0 h-0 border-t-[11px] mt-5 border-custom-border-200 border-b-[11px] border-l-[11px] border-y-transparent">
|
||||||
|
<div className="absolute top-[-10px] left-[-12px] w-0 h-0 border-t-[10px] border-custom-background-100 border-b-[10px] border-l-[10px] border-y-transparent" />
|
||||||
|
</div>
|
||||||
|
<div className="border border-custom-border-200 rounded-md bg-custom-background-100">
|
||||||
|
<h1 className="p-5">
|
||||||
|
<h3 className="font-semibold text-lg">{comicBox?.title}</h3>
|
||||||
|
<h4 className="text-sm mt-1">{comicBox?.description}</h4>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -5,8 +5,9 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
|||||||
// components
|
// components
|
||||||
import { EmptyState } from "components/common";
|
import { EmptyState } from "components/common";
|
||||||
// assets
|
// assets
|
||||||
import emptyIssue from "public/empty-state/issue.svg";
|
import emptyIssue from "public/empty-state/empty_issues.webp";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import { NewEmptyState } from "components/common/new-empty-state";
|
||||||
|
|
||||||
export const ProjectEmptyState: React.FC = observer(() => {
|
export const ProjectEmptyState: React.FC = observer(() => {
|
||||||
const {
|
const {
|
||||||
@ -16,12 +17,18 @@ export const ProjectEmptyState: React.FC = observer(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full grid place-items-center">
|
<div className="h-full w-full grid place-items-center">
|
||||||
<EmptyState
|
<NewEmptyState
|
||||||
title="Project issues will appear here"
|
title="Create an issue and assign it to someone, even yourself"
|
||||||
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
|
description="Think of issues as jobs, tasks, work, or JTBD. Which we like.An issue and its sub-issues are usually time-based actionables assigned to members of your team. Your team creates, assigns, and completes issues to move your project towards its goal."
|
||||||
image={emptyIssue}
|
image={emptyIssue}
|
||||||
|
comicBox={{
|
||||||
|
title: "Issues are building blocks in Plane.",
|
||||||
|
direction: "left",
|
||||||
|
description:
|
||||||
|
"Redesign the Plane UI, Rebrand the company, or Launch the new fuel injection system are examples of issues that likely have sub-issues.",
|
||||||
|
}}
|
||||||
primaryButton={{
|
primaryButton={{
|
||||||
text: "New issue",
|
text: "Create your first issue",
|
||||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setTrackElement("PROJECT_EMPTY_STATE");
|
setTrackElement("PROJECT_EMPTY_STATE");
|
||||||
|
@ -68,7 +68,9 @@ export const CycleLayoutRoot: React.FC = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* <CycleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} cycleId={cycleId} /> */}
|
{Object.keys(getIssues ?? {}).length == 0 ? (
|
||||||
|
<CycleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} cycleId={cycleId} />
|
||||||
|
) : (
|
||||||
<div className="h-full w-full overflow-auto">
|
<div className="h-full w-full overflow-auto">
|
||||||
{activeLayout === "list" ? (
|
{activeLayout === "list" ? (
|
||||||
<CycleListLayout />
|
<CycleListLayout />
|
||||||
@ -82,6 +84,7 @@ export const CycleLayoutRoot: React.FC = observer(() => {
|
|||||||
<CycleSpreadsheetLayout />
|
<CycleSpreadsheetLayout />
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,7 +53,9 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* <ModuleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} moduleId={moduleId} /> */}
|
{Object.keys(getIssues ?? {}).length == 0 ? (
|
||||||
|
<ModuleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} moduleId={moduleId} />
|
||||||
|
) : (
|
||||||
<div className="h-full w-full overflow-auto">
|
<div className="h-full w-full overflow-auto">
|
||||||
{activeLayout === "list" ? (
|
{activeLayout === "list" ? (
|
||||||
<ModuleListLayout />
|
<ModuleListLayout />
|
||||||
@ -67,6 +69,8 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
|
|||||||
<ModuleSpreadsheetLayout />
|
<ModuleSpreadsheetLayout />
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
{/* <ModuleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} moduleId={moduleId} /> */}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,7 +53,9 @@ export const ProjectLayoutRoot: React.FC = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* {(activeLayout === "list" || activeLayout === "spreadsheet") && issueCount === 0 && <ProjectEmptyState />} */}
|
{Object.keys(getIssues ?? {}).length == 0 ? (
|
||||||
|
<ProjectEmptyState />
|
||||||
|
) : (
|
||||||
<div className="w-full h-full relative overflow-auto">
|
<div className="w-full h-full relative overflow-auto">
|
||||||
{activeLayout === "list" ? (
|
{activeLayout === "list" ? (
|
||||||
<ListLayout />
|
<ListLayout />
|
||||||
@ -67,6 +69,7 @@ export const ProjectLayoutRoot: React.FC = observer(() => {
|
|||||||
<ProjectSpreadsheetLayout />
|
<ProjectSpreadsheetLayout />
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,7 +11,8 @@ import { EmptyState } from "components/common";
|
|||||||
// ui
|
// ui
|
||||||
import { Loader } from "@plane/ui";
|
import { Loader } from "@plane/ui";
|
||||||
// assets
|
// assets
|
||||||
import emptyModule from "public/empty-state/module.svg";
|
import emptyModule from "public/empty-state/empty_modules.webp";
|
||||||
|
import { NewEmptyState } from "components/common/new-empty-state";
|
||||||
|
|
||||||
export const ModulesListView: React.FC = observer(() => {
|
export const ModulesListView: React.FC = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -78,13 +79,19 @@ export const ModulesListView: React.FC = observer(() => {
|
|||||||
{modulesView === "gantt_chart" && <ModulesListGanttChartView />}
|
{modulesView === "gantt_chart" && <ModulesListGanttChartView />}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<EmptyState
|
<NewEmptyState
|
||||||
title="Manage your project with modules"
|
title="Map your project milestones to Modules and track aggregated work easily."
|
||||||
description="Modules are smaller, focused projects that help you group and organize issues."
|
description="A group of issues that belong to a logical, hierarchical parent form a module. Think of them as a way to track work by project milestones. They have their own periods and deadlines as well as analytics to help you see how close or far you are from a milestone."
|
||||||
image={emptyModule}
|
image={emptyModule}
|
||||||
|
comicBox={{
|
||||||
|
title: "Modules help group work by hierarchy.",
|
||||||
|
direction: "right",
|
||||||
|
description:
|
||||||
|
"A cart module, a chassis module, and a warehouse module are all good example of this grouping.",
|
||||||
|
}}
|
||||||
primaryButton={{
|
primaryButton={{
|
||||||
icon: <Plus className="h-4 w-4" />,
|
icon: <Plus className="h-4 w-4" />,
|
||||||
text: "New Module",
|
text: "Build your first module",
|
||||||
onClick: () => commandPaletteStore.toggleCreateModuleModal(true),
|
onClick: () => commandPaletteStore.toggleCreateModuleModal(true),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -12,6 +12,8 @@ import { CompletedIssuesGraph, IssuesList, IssuesPieChart, IssuesStats } from "c
|
|||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// images
|
// images
|
||||||
import emptyDashboard from "public/empty-state/dashboard.svg";
|
import emptyDashboard from "public/empty-state/dashboard.svg";
|
||||||
|
import { NewEmptyState } from "components/common/new-empty-state";
|
||||||
|
import emptyProject from "public/empty-state/dashboard_empty_project.webp";
|
||||||
|
|
||||||
export const WorkspaceDashboardView = observer(() => {
|
export const WorkspaceDashboardView = observer(() => {
|
||||||
// router
|
// router
|
||||||
@ -19,7 +21,12 @@ export const WorkspaceDashboardView = observer(() => {
|
|||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
// store
|
// store
|
||||||
|
|
||||||
const { user: userStore, project: projectStore, commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore();
|
const {
|
||||||
|
user: userStore,
|
||||||
|
project: projectStore,
|
||||||
|
commandPalette: commandPaletteStore,
|
||||||
|
trackEvent: { setTrackElement },
|
||||||
|
} = useMobxStore();
|
||||||
|
|
||||||
const user = userStore.currentUser;
|
const user = userStore.currentUser;
|
||||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
|
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
|
||||||
@ -65,22 +72,23 @@ export const WorkspaceDashboardView = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="bg-custom-primary-100/5 flex justify-between gap-5 md:gap-8">
|
<NewEmptyState
|
||||||
<div className="p-5 md:p-8 pr-0">
|
image={emptyProject}
|
||||||
<h5 className="text-xl font-semibold">Create a project</h5>
|
title="Overview of your projects, activity, and metrics"
|
||||||
<p className="mt-2 mb-5">Manage your projects by creating issues, cycles, modules, views and pages.</p>
|
description="When you have created a project and have issues assigned, you will see metrics, activity, and things you care about here. This is personalized to your role in projects, so project admins will see more than members."
|
||||||
<Button variant="primary" size="sm" onClick={() => {
|
comicBox={{
|
||||||
|
title: "Everything starts with a project in Plane",
|
||||||
|
direction: "right",
|
||||||
|
description: "A project could be a product’s roadmap, a marketing campaign, or launching a new car.",
|
||||||
|
}}
|
||||||
|
primaryButton={{
|
||||||
|
text: "Build your first project",
|
||||||
|
onClick: () => {
|
||||||
setTrackElement("DASHBOARD_PAGE");
|
setTrackElement("DASHBOARD_PAGE");
|
||||||
commandPaletteStore.toggleCreateProjectModal(true)
|
commandPaletteStore.toggleCreateProjectModal(true);
|
||||||
}
|
},
|
||||||
}>
|
}}
|
||||||
Create Project
|
/>
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="hidden md:block self-end overflow-hidden pt-8">
|
|
||||||
<Image src={emptyDashboard} alt="Empty Dashboard" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,9 +7,10 @@ import { ProjectCard } from "components/project";
|
|||||||
import { EmptyState } from "components/project/empty-state";
|
import { EmptyState } from "components/project/empty-state";
|
||||||
import { Loader } from "@plane/ui";
|
import { Loader } from "@plane/ui";
|
||||||
// images
|
// images
|
||||||
import emptyProject from "public/empty-state/Project_full_screen.svg";
|
import emptyProject from "public/empty-state/empty_project.webp";
|
||||||
// icons
|
// icons
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
|
import { NewEmptyState } from "components/common/new-empty-state";
|
||||||
|
|
||||||
export interface IProjectCardList {
|
export interface IProjectCardList {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
@ -18,7 +19,11 @@ export interface IProjectCardList {
|
|||||||
export const ProjectCardList: FC<IProjectCardList> = observer((props) => {
|
export const ProjectCardList: FC<IProjectCardList> = observer((props) => {
|
||||||
const { workspaceSlug } = props;
|
const { workspaceSlug } = props;
|
||||||
// store
|
// store
|
||||||
const { project: projectStore, commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore();
|
const {
|
||||||
|
project: projectStore,
|
||||||
|
commandPalette: commandPaletteStore,
|
||||||
|
trackEvent: { setTrackElement },
|
||||||
|
} = useMobxStore();
|
||||||
|
|
||||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
|
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
|
||||||
|
|
||||||
@ -50,17 +55,21 @@ export const ProjectCardList: FC<IProjectCardList> = observer((props) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<EmptyState
|
<NewEmptyState
|
||||||
image={emptyProject}
|
image={emptyProject}
|
||||||
title="Why no fly 😔"
|
title="Start a Project"
|
||||||
description="Let’s take off, cap’n!"
|
description="Think of each project as the parent for goal-oriented work. Projects are where Jobs, Cycles, and Modules live and, along with your colleagues, help you achieve that goal."
|
||||||
|
comicBox={{
|
||||||
|
title: "Everything starts with a project in Plane",
|
||||||
|
direction: "right",
|
||||||
|
description: "A project could be a product’s roadmap, a marketing campaign, or launching a new car.",
|
||||||
|
}}
|
||||||
primaryButton={{
|
primaryButton={{
|
||||||
icon: <Plus className="h-4 w-4" />,
|
text: "Start your first project",
|
||||||
text: "Start something new",
|
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setTrackElement("PROJECTS_EMPTY_STATE");
|
setTrackElement("PROJECTS_EMPTY_STATE");
|
||||||
commandPaletteStore.toggleCreateProjectModal(true)
|
commandPaletteStore.toggleCreateProjectModal(true);
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -17,13 +17,14 @@ import emptyAnalytics from "public/empty-state/analytics.svg";
|
|||||||
import { ANALYTICS_TABS } from "constants/analytics";
|
import { ANALYTICS_TABS } from "constants/analytics";
|
||||||
// type
|
// type
|
||||||
import { NextPageWithLayout } from "types/app";
|
import { NextPageWithLayout } from "types/app";
|
||||||
|
import { NewEmptyState } from "components/common/new-empty-state";
|
||||||
|
|
||||||
const AnalyticsPage: NextPageWithLayout = observer(() => {
|
const AnalyticsPage: NextPageWithLayout = observer(() => {
|
||||||
// store
|
// store
|
||||||
const {
|
const {
|
||||||
project: { workspaceProjects },
|
project: { workspaceProjects },
|
||||||
commandPalette: { toggleCreateProjectModal },
|
commandPalette: { toggleCreateProjectModal },
|
||||||
trackEvent: { setTrackElement }
|
trackEvent: { setTrackElement },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -36,10 +37,11 @@ const AnalyticsPage: NextPageWithLayout = observer(() => {
|
|||||||
<Tab
|
<Tab
|
||||||
key={tab.key}
|
key={tab.key}
|
||||||
className={({ selected }) =>
|
className={({ selected }) =>
|
||||||
`rounded-3xl border border-custom-border-200 px-4 py-2 text-xs hover:bg-custom-background-80 ${selected ? "bg-custom-background-80" : ""
|
`rounded-3xl border border-custom-border-200 px-4 py-2 text-xs hover:bg-custom-background-80 ${
|
||||||
|
selected ? "bg-custom-background-80" : ""
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
onClick={() => { }}
|
onClick={() => {}}
|
||||||
>
|
>
|
||||||
{tab.title}
|
{tab.title}
|
||||||
</Tab>
|
</Tab>
|
||||||
@ -66,8 +68,8 @@ const AnalyticsPage: NextPageWithLayout = observer(() => {
|
|||||||
text: "New Project",
|
text: "New Project",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setTrackElement("ANALYTICS_EMPTY_STATE");
|
setTrackElement("ANALYTICS_EMPTY_STATE");
|
||||||
toggleCreateProjectModal(true)
|
toggleCreateProjectModal(true);
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@ -14,7 +14,7 @@ import { CyclesView, ActiveCycleDetails, CycleCreateUpdateModal } from "componen
|
|||||||
import { EmptyState } from "components/common";
|
import { EmptyState } from "components/common";
|
||||||
import { Tooltip } from "@plane/ui";
|
import { Tooltip } from "@plane/ui";
|
||||||
// images
|
// images
|
||||||
import emptyCycle from "public/empty-state/cycle.svg";
|
import emptyCycle from "public/empty-state/empty_cycles.webp";
|
||||||
// types
|
// types
|
||||||
import { TCycleView, TCycleLayout } from "types";
|
import { TCycleView, TCycleLayout } from "types";
|
||||||
import { NextPageWithLayout } from "types/app";
|
import { NextPageWithLayout } from "types/app";
|
||||||
@ -22,13 +22,14 @@ import { NextPageWithLayout } from "types/app";
|
|||||||
import { CYCLE_TAB_LIST, CYCLE_VIEW_LAYOUTS } from "constants/cycle";
|
import { CYCLE_TAB_LIST, CYCLE_VIEW_LAYOUTS } from "constants/cycle";
|
||||||
// lib cookie
|
// lib cookie
|
||||||
import { setLocalStorage, getLocalStorage } from "lib/local-storage";
|
import { setLocalStorage, getLocalStorage } from "lib/local-storage";
|
||||||
|
import { NewEmptyState } from "components/common/new-empty-state";
|
||||||
// TODO: use-local-storage hook instead of lib file.
|
// TODO: use-local-storage hook instead of lib file.
|
||||||
|
|
||||||
const ProjectCyclesPage: NextPageWithLayout = observer(() => {
|
const ProjectCyclesPage: NextPageWithLayout = observer(() => {
|
||||||
const [createModal, setCreateModal] = useState(false);
|
const [createModal, setCreateModal] = useState(false);
|
||||||
// store
|
// store
|
||||||
const { project: projectStore, cycle: cycleStore } = useMobxStore();
|
const { project: projectStore, cycle: cycleStore } = useMobxStore();
|
||||||
const { projectCycles } = cycleStore
|
const { projectCycles } = cycleStore;
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, peekCycle } = router.query;
|
const { workspaceSlug, projectId, peekCycle } = router.query;
|
||||||
@ -73,7 +74,7 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
|
|||||||
|
|
||||||
const cycleView = cycleStore?.cycleView;
|
const cycleView = cycleStore?.cycleView;
|
||||||
const cycleLayout = cycleStore?.cycleLayout;
|
const cycleLayout = cycleStore?.cycleLayout;
|
||||||
const totalCycles = projectCycles?.length ?? 0
|
const totalCycles = projectCycles?.length ?? 0;
|
||||||
|
|
||||||
if (!workspaceSlug || !projectId) return null;
|
if (!workspaceSlug || !projectId) return null;
|
||||||
|
|
||||||
@ -87,13 +88,19 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
|
|||||||
/>
|
/>
|
||||||
{totalCycles === 0 ? (
|
{totalCycles === 0 ? (
|
||||||
<div className="h-full grid place-items-center">
|
<div className="h-full grid place-items-center">
|
||||||
<EmptyState
|
<NewEmptyState
|
||||||
title="Plan your project with cycles"
|
title="Group and timebox your work in Cycles."
|
||||||
description="Cycle is a custom time period in which a team works to complete items on their backlog."
|
description="Break work down by timeboxed chunks, work backwards from your project deadline to set dates, and make tangible progress as a team."
|
||||||
image={emptyCycle}
|
image={emptyCycle}
|
||||||
|
comicBox={{
|
||||||
|
title: "Cycles are repetitive time-boxes.",
|
||||||
|
direction: "right",
|
||||||
|
description:
|
||||||
|
"A sprint, an iteration, and or any other term you use for weekly or fortnightly tracking of work is a cycle.",
|
||||||
|
}}
|
||||||
primaryButton={{
|
primaryButton={{
|
||||||
icon: <Plus className="h-4 w-4" />,
|
icon: <Plus className="h-4 w-4" />,
|
||||||
text: "New Cycle",
|
text: "Set your first cycle",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setCreateModal(true);
|
setCreateModal(true);
|
||||||
},
|
},
|
||||||
|
BIN
web/public/empty-state/dashboard_empty_project.webp
Normal file
BIN
web/public/empty-state/dashboard_empty_project.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
BIN
web/public/empty-state/empty_cycles.webp
Normal file
BIN
web/public/empty-state/empty_cycles.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
BIN
web/public/empty-state/empty_issues.webp
Normal file
BIN
web/public/empty-state/empty_issues.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 59 KiB |
BIN
web/public/empty-state/empty_modules.webp
Normal file
BIN
web/public/empty-state/empty_modules.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
web/public/empty-state/empty_project.webp
Normal file
BIN
web/public/empty-state/empty_project.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
Loading…
Reference in New Issue
Block a user