diff --git a/packages/i18n/src/locales/en/translations.json b/packages/i18n/src/locales/en/translations.json
index a1705decebf..32eb6b64f7a 100644
--- a/packages/i18n/src/locales/en/translations.json
+++ b/packages/i18n/src/locales/en/translations.json
@@ -376,6 +376,8 @@
"home": {
"empty": {
+ "quickstart_guide": "Your quickstart guide",
+ "not_right_now": "Not right now",
"create_project": {
"title": "Create a project",
"description": "Most things start with a project in Plane.",
diff --git a/packages/i18n/src/locales/es/translations.json b/packages/i18n/src/locales/es/translations.json
index 1b38b1cbe0a..c7a3da312dc 100644
--- a/packages/i18n/src/locales/es/translations.json
+++ b/packages/i18n/src/locales/es/translations.json
@@ -546,6 +546,8 @@
"home": {
"empty": {
+ "quickstart_guide": "Guía de inicio rápido",
+ "not_right_now": "Ahora no",
"create_project": {
"title": "Crear un proyecto",
"description": "La mayoría de las cosas comienzan con un proyecto en Plane.",
diff --git a/packages/i18n/src/locales/fr/translations.json b/packages/i18n/src/locales/fr/translations.json
index c67ceceaf2e..83b1b31db10 100644
--- a/packages/i18n/src/locales/fr/translations.json
+++ b/packages/i18n/src/locales/fr/translations.json
@@ -546,6 +546,8 @@
"home": {
"empty": {
+ "quickstart_guide": "Guide de démarrage rapide",
+ "not_right_now": "Pas maintenant",
"create_project": {
"title": "Créer un projet",
"description": "La plupart des choses commencent par un projet dans Plane.",
diff --git a/packages/i18n/src/locales/ja/translations.json b/packages/i18n/src/locales/ja/translations.json
index 3b71443fa08..d34a26aaaed 100644
--- a/packages/i18n/src/locales/ja/translations.json
+++ b/packages/i18n/src/locales/ja/translations.json
@@ -546,6 +546,8 @@
"home": {
"empty": {
+ "quickstart_guide": "クイックスタートガイド",
+ "not_right_now": "今はしない",
"create_project": {
"title": "プロジェクトを作成",
"description": "Planeのほとんどはプロジェクトから始まります。",
diff --git a/packages/i18n/src/locales/zh-CN/translations.json b/packages/i18n/src/locales/zh-CN/translations.json
index 3bfe142ea88..f715a21ae51 100644
--- a/packages/i18n/src/locales/zh-CN/translations.json
+++ b/packages/i18n/src/locales/zh-CN/translations.json
@@ -546,6 +546,8 @@
"home": {
"empty": {
+ "quickstart_guide": "快速入门指南",
+ "not_right_now": "暂时不要",
"create_project": {
"title": "创建项目",
"description": "在Plane中,大多数事情都从项目开始。",
diff --git a/web/core/components/home/home-dashboard-widgets.tsx b/web/core/components/home/home-dashboard-widgets.tsx
index e391567c4db..75daa836464 100644
--- a/web/core/components/home/home-dashboard-widgets.tsx
+++ b/web/core/components/home/home-dashboard-widgets.tsx
@@ -1,17 +1,18 @@
import { observer } from "mobx-react";
-import { useParams } from "next/navigation";
+import { useParams, usePathname } from "next/navigation";
// plane imports
import { useTranslation } from "@plane/i18n";
import { THomeWidgetKeys, THomeWidgetProps } from "@plane/types";
// components
import { SimpleEmptyState } from "@/components/empty-state";
// hooks
+import { useProject } from "@/hooks/store";
import { useHome } from "@/hooks/store/use-home";
// plane web components
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
import { HomePageHeader } from "@/plane-web/components/home/header";
import { StickiesWidget } from "../stickies";
-import { RecentActivityWidget } from "./widgets";
+import { HomeLoader, NoProjectsEmptyState, RecentActivityWidget } from "./widgets";
import { DashboardQuickLinks } from "./widgets/links";
import { ManageWidgetsModal } from "./widgets/manage";
@@ -52,14 +53,21 @@ export const HOME_WIDGETS_LIST: {
export const DashboardWidgets = observer(() => {
// router
const { workspaceSlug } = useParams();
+ // navigation
+ const pathname = usePathname();
+ // store hooks
+ const { toggleWidgetSettings, widgetsMap, showWidgetSettings, orderedWidgets, isAnyWidgetEnabled, loading } =
+ useHome();
+ const { loader } = useProject();
// plane hooks
const { t } = useTranslation();
- // store hooks
- const { toggleWidgetSettings, widgetsMap, showWidgetSettings, orderedWidgets, isAnyWidgetEnabled } = useHome();
// derived values
const noWidgetsResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/dashboard/widgets" });
+ // derived values
+ const isWikiApp = pathname.includes(`/${workspaceSlug.toString()}/pages`);
if (!workspaceSlug) return null;
+ if (loading || loader !== "loaded") return ;
return (
@@ -69,6 +77,8 @@ export const DashboardWidgets = observer(() => {
isModalOpen={showWidgetSettings}
handleOnClose={() => toggleWidgetSettings(false)}
/>
+ {!isWikiApp &&
}
+
{isAnyWidgetEnabled ? (
{orderedWidgets.map((key) => {
diff --git a/web/core/components/home/widgets/empty-states/no-projects.tsx b/web/core/components/home/widgets/empty-states/no-projects.tsx
index 08a8bdaf4ee..84d7b638bce 100644
--- a/web/core/components/home/widgets/empty-states/no-projects.tsx
+++ b/web/core/components/home/widgets/empty-states/no-projects.tsx
@@ -1,17 +1,21 @@
import React from "react";
+// mobx
+import { observer } from "mobx-react";
import Link from "next/link";
import { useParams } from "next/navigation";
-import { Briefcase, Hotel, Users } from "lucide-react";
+import { Briefcase, Check, Hotel, Users, X } from "lucide-react";
// plane ui
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
+import { useLocalStorage } from "@plane/hooks";
import { useTranslation } from "@plane/i18n";
// helpers
+import { cn } from "@plane/utils";
import { getFileURL } from "@/helpers/file.helper";
// hooks
-import { useCommandPalette, useEventTracker, useUser, useUserPermissions } from "@/hooks/store";
+import { useCommandPalette, useEventTracker, useProject, useUser, useUserPermissions } from "@/hooks/store";
// plane web constants
-export const NoProjectsEmptyState = () => {
+export const NoProjectsEmptyState = observer(() => {
// navigation
const { workspaceSlug } = useParams();
// store hooks
@@ -19,6 +23,14 @@ export const NoProjectsEmptyState = () => {
const { toggleCreateProjectModal } = useCommandPalette();
const { setTrackElement } = useEventTracker();
const { data: currentUser } = useUser();
+ const { joinedProjectIds } = useProject();
+ // local storage
+ const { storedValue, setValue } = useLocalStorage(`quickstart-guide-${workspaceSlug}`, {
+ hide: false,
+ visited_members: false,
+ visited_workspace: false,
+ visited_profile: false,
+ });
const { t } = useTranslation();
// derived values
const canCreateProject = allowPermissions(
@@ -31,7 +43,8 @@ export const NoProjectsEmptyState = () => {
id: "create-project",
title: "home.empty.create_project.title",
description: "home.empty.create_project.description",
- icon:
,
+ icon:
,
+ flag: "projects",
cta: {
text: "home.empty.create_project.cta",
onClick: (e: React.MouseEvent
) => {
@@ -47,7 +60,8 @@ export const NoProjectsEmptyState = () => {
id: "invite-team",
title: "home.empty.invite_team.title",
description: "home.empty.invite_team.description",
- icon: ,
+ icon: ,
+ flag: "visited_members",
cta: {
text: "home.empty.invite_team.cta",
link: `/${workspaceSlug}/settings/members`,
@@ -57,7 +71,8 @@ export const NoProjectsEmptyState = () => {
id: "configure-workspace",
title: "home.empty.configure_workspace.title",
description: "home.empty.configure_workspace.description",
- icon: ,
+ icon: ,
+ flag: "visited_workspace",
cta: {
text: "home.empty.configure_workspace.cta",
link: "settings",
@@ -85,44 +100,94 @@ export const NoProjectsEmptyState = () => {
),
+ flag: "visited_profile",
cta: {
text: "home.empty.personalize_account.cta",
link: "/profile",
},
},
];
+ const isComplete = (type: string) => {
+ switch (type) {
+ case "projects":
+ return joinedProjectIds?.length > 0;
+ case "visited_members":
+ return storedValue?.visited_members;
+ case "visited_workspace":
+ return storedValue?.visited_workspace;
+ case "visited_profile":
+ return storedValue?.visited_profile;
+ }
+ };
+
+ if (storedValue?.hide) return null;
return (
-
- {EMPTY_STATE_DATA.map((item) => (
-
+
+
{t("home.empty.quickstart_guide")}
+
+
+
+ {EMPTY_STATE_DATA.map((item) => {
+ const isStateComplete = isComplete(item.flag);
+ return (
+
- {t(item.cta.text)}
-
- )}
-
- ))}
+
+ {item.icon}
+
+
{t(item.title)}
+
{t(item.description)}
+ {isStateComplete ? (
+
+
+
+ ) : item.cta.link ? (
+
{
+ if (!storedValue) return;
+ setValue({
+ ...storedValue,
+ [item.flag]: true,
+ });
+ }}
+ className="text-custom-primary-100 hover:text-custom-primary-200 text-sm font-medium"
+ >
+ {t(item.cta.text)}
+
+ ) : (
+
+ {t(item.cta.text)}
+
+ )}
+
+ );
+ })}
+
);
-};
+});
diff --git a/web/core/components/home/widgets/links/use-links.tsx b/web/core/components/home/widgets/links/use-links.tsx
index 0cb1f6ac52a..a2ad8a0cc84 100644
--- a/web/core/components/home/widgets/links/use-links.tsx
+++ b/web/core/components/home/widgets/links/use-links.tsx
@@ -42,9 +42,9 @@ export const useLinks = (workspaceSlug: string) => {
});
toggleLinkModal(false);
} catch (error: any) {
- console.error("error", error);
+ console.error("error", error?.data?.url?.error);
setToast({
- message: error?.data?.error ?? t("links.toasts.not_created.message"),
+ message: error?.data?.url?.error ?? t("links.toasts.not_created.message"),
type: TOAST_TYPE.ERROR,
title: t("links.toasts.not_created.title"),
});
diff --git a/web/core/components/home/widgets/loaders/home-loader.tsx b/web/core/components/home/widgets/loaders/home-loader.tsx
new file mode 100644
index 00000000000..56d32725bf2
--- /dev/null
+++ b/web/core/components/home/widgets/loaders/home-loader.tsx
@@ -0,0 +1,22 @@
+"use client";
+
+import range from "lodash/range";
+// ui
+import { Loader } from "@plane/ui";
+
+export const HomeLoader = () => (
+ <>
+ {range(3).map((index) => (
+
+ ))}
+ >
+);
diff --git a/web/core/components/home/widgets/loaders/index.ts b/web/core/components/home/widgets/loaders/index.ts
index ee5286f0fbf..a0925eccdf2 100644
--- a/web/core/components/home/widgets/loaders/index.ts
+++ b/web/core/components/home/widgets/loaders/index.ts
@@ -1 +1,2 @@
export * from "./loader";
+export * from "./home-loader";
diff --git a/web/core/components/home/widgets/recents/index.tsx b/web/core/components/home/widgets/recents/index.tsx
index 6f06432fdfc..5622a2541de 100644
--- a/web/core/components/home/widgets/recents/index.tsx
+++ b/web/core/components/home/widgets/recents/index.tsx
@@ -2,7 +2,6 @@
import { useRef, useState } from "react";
import { observer } from "mobx-react";
-import { usePathname } from "next/navigation";
import useSWR from "swr";
import { Briefcase, FileText } from "lucide-react";
import { useTranslation } from "@plane/i18n";
@@ -12,11 +11,9 @@ import { TActivityEntityData, THomeWidgetProps, TRecentActivityFilterKeys } from
import { LayersIcon } from "@plane/ui";
// components
import { ContentOverflowWrapper } from "@/components/core/content-overflow-HOC";
-// hooks
-import { useProject } from "@/hooks/store";
// plane web services
import { WorkspaceService } from "@/plane-web/services";
-import { NoProjectsEmptyState, RecentsEmptyState } from "../empty-states";
+import { RecentsEmptyState } from "../empty-states";
import { EWidgetKeys, WidgetLoader } from "../loaders";
import { FiltersDropdown } from "./filters";
import { RecentIssue } from "./issue";
@@ -41,15 +38,9 @@ export const RecentActivityWidget: React.FC = observer((prop
const { presetFilter, showFilterSelect = true, workspaceSlug } = props;
// states
const [filter, setFilter] = useState(presetFilter ?? filters[0].name);
- // navigation
- const pathname = usePathname();
+ const { t } = useTranslation();
// ref
const ref = useRef(null);
- // store hooks
- const { joinedProjectIds, loader } = useProject();
- const { t } = useTranslation();
- // derived values
- const isWikiApp = pathname.includes(`/${workspaceSlug.toString()}/pages`);
const { data: recents, isLoading } = useSWR(
workspaceSlug ? `WORKSPACE_RECENT_ACTIVITY_${workspaceSlug}_${filter}` : null,
@@ -81,8 +72,6 @@ export const RecentActivityWidget: React.FC = observer((prop
}
};
- if (loader === "loaded" && !isWikiApp && joinedProjectIds?.length === 0) return ;
-
if (!isLoading && recents?.length === 0)
return (
diff --git a/web/core/components/stickies/layout/stickies-list.tsx b/web/core/components/stickies/layout/stickies-list.tsx
index 3ee1f568ba5..47e6e669805 100644
--- a/web/core/components/stickies/layout/stickies-list.tsx
+++ b/web/core/components/stickies/layout/stickies-list.tsx
@@ -192,7 +192,7 @@ export const StickiesLayout = (props: TStickiesLayout) => {
const columnCount = getColumnCount(containerWidth);
return (
-
+
);
diff --git a/web/core/store/workspace/home.ts b/web/core/store/workspace/home.ts
index dc53b707b30..023767ef3d6 100644
--- a/web/core/store/workspace/home.ts
+++ b/web/core/store/workspace/home.ts
@@ -7,6 +7,7 @@ import { IWorkspaceLinkStore, WorkspaceLinkStore } from "./link.store";
export interface IHomeStore {
// observables
+ loading: boolean;
showWidgetSettings: boolean;
widgetsMap: Record
;
widgets: THomeWidgetKeys[];
@@ -25,6 +26,7 @@ export interface IHomeStore {
export class HomeStore implements IHomeStore {
// observables
showWidgetSettings = false;
+ loading = false;
widgetsMap: Record = {};
widgets: THomeWidgetKeys[] = [];
// stores
@@ -35,6 +37,7 @@ export class HomeStore implements IHomeStore {
constructor() {
makeObservable(this, {
// observables
+ loading: observable,
showWidgetSettings: observable,
widgetsMap: observable,
widgets: observable,
@@ -68,15 +71,18 @@ export class HomeStore implements IHomeStore {
fetchWidgets = async (workspaceSlug: string) => {
try {
+ this.loading = true;
const widgets = await this.workspaceService.fetchWorkspaceWidgets(workspaceSlug);
runInAction(() => {
this.widgets = orderBy(Object.values(widgets), "sort_order", "desc").map((widget) => widget.key);
widgets.forEach((widget) => {
this.widgetsMap[widget.key] = widget;
});
+ this.loading = false;
});
} catch (error) {
console.error("Failed to fetch widgets");
+ this.loading = false;
throw error;
}
};
diff --git a/web/core/store/workspace/link.store.ts b/web/core/store/workspace/link.store.ts
index a6b2cd40ecd..4a19f92c0f7 100644
--- a/web/core/store/workspace/link.store.ts
+++ b/web/core/store/workspace/link.store.ts
@@ -93,7 +93,6 @@ export class WorkspaceLinkStore implements IWorkspaceLinkStore {
};
createLink = async (workspaceSlug: string, data: Partial) => {
- console.log("hereee");
const response = await this.workspaceService.createWorkspaceLink(workspaceSlug, data);
runInAction(() => {