Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions packages/types/src/home.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { TLogoProps } from "./common";
import { TIssuePriorities } from "./issues";

export type TRecentActivityFilterKeys = "all item" | "issue" | "page" | "project";
export type THomeWidgetKeys = "quick_links" | "recents" | "my_stickies" | "quick_tutorial" | "new_at_plane";

export type THomeWidgetProps = {
workspaceSlug: string;
};

export type TPageEntityData = {
id: string;
name: string;
logo_props: TLogoProps;
project_id: string;
owned_by: string;
project_identifier: string;
};

export type TProjectEntityData = {
id: string;
name: string;
logo_props: TLogoProps;
project_members: string[];
identifier: string;
};

export type TIssueEntityData = {
id: string;
name: string;
state: string;
priority: TIssuePriorities;
assignees: string[];
type: string | null;
sequence_id: number;
project_id: string;
project_identifier: string;
};

export type TActivityEntityData = {
id: string;
entity_name: "page" | "project" | "issue";
entity_identifier: string;
visited_at: string;
entity_data: TPageEntityData | TProjectEntityData | TIssueEntityData;
};

export type TLinkEditableFields = {
title: string;
url: string;
};

export type TLink = TLinkEditableFields & {
created_by_id: string;
id: string;
metadata: any;
workspace_slug: string;

//need
created_at: Date;
};

export type TLinkMap = {
[workspace_slug: string]: TLink;
};

export type TLinkIdMap = {
[workspace_slug: string]: string[];
};

export type TWidgetEntityData = {
key: THomeWidgetKeys;
name: string;
is_enabled: boolean;
sort_order: number;
};
1 change: 1 addition & 0 deletions packages/types/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ export * from "./timezone";
export * from "./activity";
export * from "./epics";
export * from "./charts";
export * from "./home";
60 changes: 60 additions & 0 deletions web/app/[workspaceSlug]/(projects)/home/header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"use client";

import Image from "next/image";
import { useTheme } from "next-themes";
import { Home } from "lucide-react";
// images
import githubBlackImage from "/public/logos/github-black.png";
import githubWhiteImage from "/public/logos/github-white.png";
// ui
import { Breadcrumbs, Header } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common";
// constants
import { GITHUB_REDIRECTED } from "@/constants/event-tracker";
// hooks
import { useEventTracker } from "@/hooks/store";

export const WorkspaceDashboardHeader = () => {
// hooks
const { captureEvent } = useEventTracker();
const { resolvedTheme } = useTheme();

return (
<>
<Header>
<Header.LeftItem>
<div>
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
link={<BreadcrumbLink label="Home" icon={<Home className="h-4 w-4 text-custom-text-300" />} />}
/>
</Breadcrumbs>
</div>
</Header.LeftItem>
<Header.RightItem>
<a
onClick={() =>
captureEvent(GITHUB_REDIRECTED, {
element: "navbar",
})
}
className="flex flex-shrink-0 items-center gap-1.5 rounded bg-custom-background-80 px-3 py-1.5"
href="https://github.com/makeplane/plane"
target="_blank"
rel="noopener noreferrer"
>
<Image
src={resolvedTheme === "dark" ? githubWhiteImage : githubBlackImage}
height={16}
width={16}
alt="GitHub Logo"
/>
<span className="hidden text-xs font-medium sm:hidden md:block">Star us on GitHub</span>
</a>
</Header.RightItem>
</Header>
</>
);
};
28 changes: 28 additions & 0 deletions web/app/[workspaceSlug]/(projects)/home/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use client";

import { observer } from "mobx-react";
// components
import { PageHead, AppHeader, ContentWrapper } from "@/components/core";
// hooks
import { WorkspaceHomeView } from "@/components/home";
import { useWorkspace } from "@/hooks/store";
// local components
import { WorkspaceDashboardHeader } from "../header";

const WorkspaceDashboardPage = observer(() => {
const { currentWorkspace } = useWorkspace();
// derived values
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Home` : undefined;

return (
<>
<AppHeader header={<WorkspaceDashboardHeader />} />
<ContentWrapper>
<PageHead title={pageTitle} />
<WorkspaceHomeView />
</ContentWrapper>
</>
);
});

export default WorkspaceDashboardPage;
1 change: 1 addition & 0 deletions web/ce/components/home/header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const HomePageHeader = () => <></>;
1 change: 1 addition & 0 deletions web/ce/components/stickies/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./widget";
1 change: 1 addition & 0 deletions web/ce/components/stickies/widget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const StickiesWidget = () => <></>;
4 changes: 3 additions & 1 deletion web/core/components/core/list/list-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface IListItemProps {
parentRef: React.RefObject<HTMLDivElement>;
disableLink?: boolean;
className?: string;
itemClassName?: string;
actionItemContainerClassName?: string;
isSidebarOpen?: boolean;
quickActionElement?: JSX.Element;
Expand All @@ -38,6 +39,7 @@ export const ListItem: FC<IListItemProps> = (props) => {
actionItemContainerClassName = "",
isSidebarOpen = false,
quickActionElement,
itemClassName = "",
} = props;

// router
Expand All @@ -61,7 +63,7 @@ export const ListItem: FC<IListItemProps> = (props) => {
className
)}
>
<div className="relative flex w-full items-center justify-between gap-3 overflow-hidden">
<div className={cn("relative flex w-full items-center justify-between gap-3 overflow-hidden", itemClassName)}>
<ControlLink
className="relative flex w-full items-center gap-3 overflow-hidden"
href={itemLink}
Expand Down
55 changes: 55 additions & 0 deletions web/core/components/home/home-dashboard-widgets.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// types
import { THomeWidgetKeys, THomeWidgetProps } from "@plane/types";
// hooks
import { useHome } from "@/hooks/store/use-home";
// components
import { HomePageHeader } from "@/plane-web/components/home/header";
import { StickiesWidget } from "@/plane-web/components/stickies";
import { RecentActivityWidget } from "./widgets";
import { DashboardQuickLinks } from "./widgets/links";
import { ManageWidgetsModal } from "./widgets/manage";

const WIDGETS_LIST: {
[key in THomeWidgetKeys]: { component: React.FC<THomeWidgetProps> | null; fullWidth: boolean };
} = {
quick_links: { component: DashboardQuickLinks, fullWidth: false },
recents: { component: RecentActivityWidget, fullWidth: false },
my_stickies: { component: StickiesWidget, fullWidth: false },
new_at_plane: { component: null, fullWidth: false },
quick_tutorial: { component: null, fullWidth: false },
};

export const DashboardWidgets = observer(() => {
// router
const { workspaceSlug } = useParams();
// store hooks
const { toggleWidgetSettings, widgetsMap, showWidgetSettings, orderedWidgets } = useHome();

if (!workspaceSlug) return null;

return (
<div className="relative flex flex-col gap-7">
<HomePageHeader />
<ManageWidgetsModal
workspaceSlug={workspaceSlug.toString()}
isModalOpen={showWidgetSettings}
handleOnClose={() => toggleWidgetSettings(false)}
/>

{orderedWidgets.map((key) => {
const WidgetComponent = WIDGETS_LIST[key]?.component;
const isEnabled = widgetsMap[key]?.is_enabled;
if (!WidgetComponent || !isEnabled) return null;
if (WIDGETS_LIST[key]?.fullWidth)
return (
<div key={key} className="lg:col-span-2">
<WidgetComponent workspaceSlug={workspaceSlug.toString()} />
</div>
);
else return <WidgetComponent key={key} workspaceSlug={workspaceSlug.toString()} />;
})}
</div>
);
});
4 changes: 4 additions & 0 deletions web/core/components/home/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./widgets";
export * from "./home-dashboard-widgets";
export * from "./project-empty-state";
export * from "./root";
46 changes: 46 additions & 0 deletions web/core/components/home/project-empty-state.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"use client";

import { observer } from "mobx-react";
import Image from "next/image";
// ui
import { Button } from "@plane/ui";
// hooks
import { useCommandPalette, useEventTracker, useUserPermissions } from "@/hooks/store";
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
// assets
import ProjectEmptyStateImage from "@/public/empty-state/onboarding/dashboard-light.webp";

export const DashboardProjectEmptyState = observer(() => {
// store hooks
const { toggleCreateProjectModal } = useCommandPalette();
const { setTrackElement } = useEventTracker();
const { allowPermissions } = useUserPermissions();

// derived values
const canCreateProject = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);

return (
<div className="mx-auto flex h-full flex-col justify-center space-y-4 lg:w-3/5">
<h4 className="text-xl font-semibold">Overview of your projects, activity, and metrics</h4>
<p className="text-custom-text-300">
Welcome to Plane, we are excited to have you here. Create your first project and track your issues, and this
page will transform into a space that helps you progress. Admins will also see items which help their team
progress.
</p>
<Image src={ProjectEmptyStateImage} className="w-full" alt="Project empty state" />
{canCreateProject && (
<div className="flex justify-center">
<Button
variant="primary"
onClick={() => {
setTrackElement("Project empty state");
toggleCreateProjectModal(true);
}}
>
Build your first project
</Button>
</div>
)}
</div>
);
});
Loading