Skip to content

Commit 456c7f5

Browse files
authored
[WEB-2917] Fix home widget (#6560)
* fix: home loading state * fix: quickstart guide * fix: link handling * fix: home completed state * fix: translations
1 parent c2da3ea commit 456c7f5

File tree

14 files changed

+158
-56
lines changed

14 files changed

+158
-56
lines changed

packages/i18n/src/locales/en/translations.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,8 @@
376376

377377
"home": {
378378
"empty": {
379+
"quickstart_guide": "Your quickstart guide",
380+
"not_right_now": "Not right now",
379381
"create_project": {
380382
"title": "Create a project",
381383
"description": "Most things start with a project in Plane.",

packages/i18n/src/locales/es/translations.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,8 @@
546546

547547
"home": {
548548
"empty": {
549+
"quickstart_guide": "Guía de inicio rápido",
550+
"not_right_now": "Ahora no",
549551
"create_project": {
550552
"title": "Crear un proyecto",
551553
"description": "La mayoría de las cosas comienzan con un proyecto en Plane.",

packages/i18n/src/locales/fr/translations.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,8 @@
546546

547547
"home": {
548548
"empty": {
549+
"quickstart_guide": "Guide de démarrage rapide",
550+
"not_right_now": "Pas maintenant",
549551
"create_project": {
550552
"title": "Créer un projet",
551553
"description": "La plupart des choses commencent par un projet dans Plane.",

packages/i18n/src/locales/ja/translations.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,8 @@
546546

547547
"home": {
548548
"empty": {
549+
"quickstart_guide": "クイックスタートガイド",
550+
"not_right_now": "今はしない",
549551
"create_project": {
550552
"title": "プロジェクトを作成",
551553
"description": "Planeのほとんどはプロジェクトから始まります。",

packages/i18n/src/locales/zh-CN/translations.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,8 @@
546546

547547
"home": {
548548
"empty": {
549+
"quickstart_guide": "快速入门指南",
550+
"not_right_now": "暂时不要",
549551
"create_project": {
550552
"title": "创建项目",
551553
"description": "在Plane中,大多数事情都从项目开始。",

web/core/components/home/home-dashboard-widgets.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import { observer } from "mobx-react";
2-
import { useParams } from "next/navigation";
2+
import { useParams, usePathname } from "next/navigation";
33
// plane imports
44
import { useTranslation } from "@plane/i18n";
55
import { THomeWidgetKeys, THomeWidgetProps } from "@plane/types";
66
// components
77
import { SimpleEmptyState } from "@/components/empty-state";
88
// hooks
9+
import { useProject } from "@/hooks/store";
910
import { useHome } from "@/hooks/store/use-home";
1011
// plane web components
1112
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
1213
import { HomePageHeader } from "@/plane-web/components/home/header";
1314
import { StickiesWidget } from "../stickies";
14-
import { RecentActivityWidget } from "./widgets";
15+
import { HomeLoader, NoProjectsEmptyState, RecentActivityWidget } from "./widgets";
1516
import { DashboardQuickLinks } from "./widgets/links";
1617
import { ManageWidgetsModal } from "./widgets/manage";
1718

@@ -52,14 +53,21 @@ export const HOME_WIDGETS_LIST: {
5253
export const DashboardWidgets = observer(() => {
5354
// router
5455
const { workspaceSlug } = useParams();
56+
// navigation
57+
const pathname = usePathname();
58+
// store hooks
59+
const { toggleWidgetSettings, widgetsMap, showWidgetSettings, orderedWidgets, isAnyWidgetEnabled, loading } =
60+
useHome();
61+
const { loader } = useProject();
5562
// plane hooks
5663
const { t } = useTranslation();
57-
// store hooks
58-
const { toggleWidgetSettings, widgetsMap, showWidgetSettings, orderedWidgets, isAnyWidgetEnabled } = useHome();
5964
// derived values
6065
const noWidgetsResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/dashboard/widgets" });
6166

67+
// derived values
68+
const isWikiApp = pathname.includes(`/${workspaceSlug.toString()}/pages`);
6269
if (!workspaceSlug) return null;
70+
if (loading || loader !== "loaded") return <HomeLoader />;
6371

6472
return (
6573
<div className="h-full w-full relative flex flex-col gap-7">
@@ -69,6 +77,8 @@ export const DashboardWidgets = observer(() => {
6977
isModalOpen={showWidgetSettings}
7078
handleOnClose={() => toggleWidgetSettings(false)}
7179
/>
80+
{!isWikiApp && <NoProjectsEmptyState />}
81+
7282
{isAnyWidgetEnabled ? (
7383
<div className="flex flex-col divide-y-[1px] divide-custom-border-100">
7484
{orderedWidgets.map((key) => {
Lines changed: 100 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,36 @@
11
import React from "react";
2+
// mobx
3+
import { observer } from "mobx-react";
24
import Link from "next/link";
35
import { useParams } from "next/navigation";
4-
import { Briefcase, Hotel, Users } from "lucide-react";
6+
import { Briefcase, Check, Hotel, Users, X } from "lucide-react";
57
// plane ui
68
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
9+
import { useLocalStorage } from "@plane/hooks";
710
import { useTranslation } from "@plane/i18n";
811
// helpers
12+
import { cn } from "@plane/utils";
913
import { getFileURL } from "@/helpers/file.helper";
1014
// hooks
11-
import { useCommandPalette, useEventTracker, useUser, useUserPermissions } from "@/hooks/store";
15+
import { useCommandPalette, useEventTracker, useProject, useUser, useUserPermissions } from "@/hooks/store";
1216
// plane web constants
1317

14-
export const NoProjectsEmptyState = () => {
18+
export const NoProjectsEmptyState = observer(() => {
1519
// navigation
1620
const { workspaceSlug } = useParams();
1721
// store hooks
1822
const { allowPermissions } = useUserPermissions();
1923
const { toggleCreateProjectModal } = useCommandPalette();
2024
const { setTrackElement } = useEventTracker();
2125
const { data: currentUser } = useUser();
26+
const { joinedProjectIds } = useProject();
27+
// local storage
28+
const { storedValue, setValue } = useLocalStorage(`quickstart-guide-${workspaceSlug}`, {
29+
hide: false,
30+
visited_members: false,
31+
visited_workspace: false,
32+
visited_profile: false,
33+
});
2234
const { t } = useTranslation();
2335
// derived values
2436
const canCreateProject = allowPermissions(
@@ -31,7 +43,8 @@ export const NoProjectsEmptyState = () => {
3143
id: "create-project",
3244
title: "home.empty.create_project.title",
3345
description: "home.empty.create_project.description",
34-
icon: <Briefcase className="w-[40px] h-[40px] text-custom-primary-100" />,
46+
icon: <Briefcase className="size-10" />,
47+
flag: "projects",
3548
cta: {
3649
text: "home.empty.create_project.cta",
3750
onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
@@ -47,7 +60,8 @@ export const NoProjectsEmptyState = () => {
4760
id: "invite-team",
4861
title: "home.empty.invite_team.title",
4962
description: "home.empty.invite_team.description",
50-
icon: <Users className="w-[40px] h-[40px] text-custom-primary-100" />,
63+
icon: <Users className="size-10" />,
64+
flag: "visited_members",
5165
cta: {
5266
text: "home.empty.invite_team.cta",
5367
link: `/${workspaceSlug}/settings/members`,
@@ -57,7 +71,8 @@ export const NoProjectsEmptyState = () => {
5771
id: "configure-workspace",
5872
title: "home.empty.configure_workspace.title",
5973
description: "home.empty.configure_workspace.description",
60-
icon: <Hotel className="w-[40px] h-[40px] text-custom-primary-100" />,
74+
icon: <Hotel className="size-10" />,
75+
flag: "visited_workspace",
6176
cta: {
6277
text: "home.empty.configure_workspace.cta",
6378
link: "settings",
@@ -85,44 +100,94 @@ export const NoProjectsEmptyState = () => {
85100
</span>
86101
</Link>
87102
),
103+
flag: "visited_profile",
88104
cta: {
89105
text: "home.empty.personalize_account.cta",
90106
link: "/profile",
91107
},
92108
},
93109
];
110+
const isComplete = (type: string) => {
111+
switch (type) {
112+
case "projects":
113+
return joinedProjectIds?.length > 0;
114+
case "visited_members":
115+
return storedValue?.visited_members;
116+
case "visited_workspace":
117+
return storedValue?.visited_workspace;
118+
case "visited_profile":
119+
return storedValue?.visited_profile;
120+
}
121+
};
122+
123+
if (storedValue?.hide) return null;
94124

95125
return (
96-
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4">
97-
{EMPTY_STATE_DATA.map((item) => (
98-
<div
99-
key={item.id}
100-
className="flex flex-col items-center justify-center p-6 bg-custom-background-100 rounded-lg text-center border border-custom-border-200/40"
126+
<div>
127+
<div className="flex items-center justify-between mb-4">
128+
<div className="text-base font-semibold text-custom-text-350">{t("home.empty.quickstart_guide")}</div>
129+
<button
130+
className="text-custom-text-300 font-medium text-sm flex items-center gap-1"
131+
onClick={() => {
132+
if (!storedValue) return;
133+
setValue({ ...storedValue, hide: true });
134+
}}
101135
>
102-
<div className="grid place-items-center bg-custom-primary-100/10 rounded-full size-24 mb-3">
103-
<span className="text-3xl my-auto">{item.icon}</span>
104-
</div>
105-
<h3 className="text-lg font-medium text-custom-text-100 mb-2">{t(item.title)}</h3>
106-
<p className="text-sm text-custom-text-200 mb-4 w-[80%] flex-1">{t(item.description)}</p>
107-
108-
{item.cta.link ? (
109-
<Link
110-
href={item.cta.link}
111-
className="text-custom-primary-100 hover:text-custom-primary-200 text-sm font-medium"
112-
>
113-
{t(item.cta.text)}
114-
</Link>
115-
) : (
116-
<button
117-
type="button"
118-
className="text-custom-primary-100 hover:text-custom-primary-200 text-sm font-medium"
119-
onClick={item.cta.onClick}
136+
<X className="size-4" />
137+
{t("home.empty.not_right_now")}
138+
</button>
139+
</div>
140+
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4">
141+
{EMPTY_STATE_DATA.map((item) => {
142+
const isStateComplete = isComplete(item.flag);
143+
return (
144+
<div
145+
key={item.id}
146+
className="flex flex-col items-center justify-center p-6 bg-custom-background-100 rounded-lg text-center border border-custom-border-200/40"
120147
>
121-
{t(item.cta.text)}
122-
</button>
123-
)}
124-
</div>
125-
))}
148+
<div
149+
className={cn(
150+
"grid place-items-center bg-custom-background-90 rounded-full size-20 mb-3 text-custom-text-400",
151+
{
152+
"text-custom-primary-100 bg-custom-primary-100/10": !isStateComplete,
153+
}
154+
)}
155+
>
156+
<span className="text-3xl my-auto">{item.icon}</span>
157+
</div>
158+
<h3 className="text-base font-medium text-custom-text-100 mb-2">{t(item.title)}</h3>
159+
<p className="text-sm text-custom-text-300 mb-2">{t(item.description)}</p>
160+
{isStateComplete ? (
161+
<div className="flex items-center gap-2 bg-[#17a34a] rounded-full p-1">
162+
<Check className="size-3 text-custom-primary-100 text-white" />
163+
</div>
164+
) : item.cta.link ? (
165+
<Link
166+
href={item.cta.link}
167+
onClick={() => {
168+
if (!storedValue) return;
169+
setValue({
170+
...storedValue,
171+
[item.flag]: true,
172+
});
173+
}}
174+
className="text-custom-primary-100 hover:text-custom-primary-200 text-sm font-medium"
175+
>
176+
{t(item.cta.text)}
177+
</Link>
178+
) : (
179+
<button
180+
type="button"
181+
className="text-custom-primary-100 hover:text-custom-primary-200 text-sm font-medium"
182+
onClick={item.cta.onClick}
183+
>
184+
{t(item.cta.text)}
185+
</button>
186+
)}
187+
</div>
188+
);
189+
})}
190+
</div>
126191
</div>
127192
);
128-
};
193+
});

web/core/components/home/widgets/links/use-links.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ export const useLinks = (workspaceSlug: string) => {
4242
});
4343
toggleLinkModal(false);
4444
} catch (error: any) {
45-
console.error("error", error);
45+
console.error("error", error?.data?.url?.error);
4646
setToast({
47-
message: error?.data?.error ?? t("links.toasts.not_created.message"),
47+
message: error?.data?.url?.error ?? t("links.toasts.not_created.message"),
4848
type: TOAST_TYPE.ERROR,
4949
title: t("links.toasts.not_created.title"),
5050
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"use client";
2+
3+
import range from "lodash/range";
4+
// ui
5+
import { Loader } from "@plane/ui";
6+
7+
export const HomeLoader = () => (
8+
<>
9+
{range(3).map((index) => (
10+
<div key={index}>
11+
<div className="mb-2">
12+
<div className="text-base font-semibold text-custom-text-350 mb-4">
13+
<Loader.Item height="20px" width="100px" />
14+
</div>
15+
<Loader className="h-[110px] w-full flex items-center justify-center gap-2 text-custom-text-400 rounded">
16+
<Loader.Item height="100%" width="100%" />
17+
</Loader>
18+
</div>
19+
</div>
20+
))}
21+
</>
22+
);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from "./loader";
2+
export * from "./home-loader";

0 commit comments

Comments
 (0)