[WEB-2917] Fix home widget (#6560)
* fix: home loading state * fix: quickstart guide * fix: link handling * fix: home completed state * fix: translations
This commit is contained in:
parent
c2da3ea4c8
commit
456c7f55a9
14 changed files with 158 additions and 56 deletions
|
|
@ -376,6 +376,8 @@
|
||||||
|
|
||||||
"home": {
|
"home": {
|
||||||
"empty": {
|
"empty": {
|
||||||
|
"quickstart_guide": "Your quickstart guide",
|
||||||
|
"not_right_now": "Not right now",
|
||||||
"create_project": {
|
"create_project": {
|
||||||
"title": "Create a project",
|
"title": "Create a project",
|
||||||
"description": "Most things start with a project in Plane.",
|
"description": "Most things start with a project in Plane.",
|
||||||
|
|
|
||||||
|
|
@ -546,6 +546,8 @@
|
||||||
|
|
||||||
"home": {
|
"home": {
|
||||||
"empty": {
|
"empty": {
|
||||||
|
"quickstart_guide": "Guía de inicio rápido",
|
||||||
|
"not_right_now": "Ahora no",
|
||||||
"create_project": {
|
"create_project": {
|
||||||
"title": "Crear un proyecto",
|
"title": "Crear un proyecto",
|
||||||
"description": "La mayoría de las cosas comienzan con un proyecto en Plane.",
|
"description": "La mayoría de las cosas comienzan con un proyecto en Plane.",
|
||||||
|
|
|
||||||
|
|
@ -546,6 +546,8 @@
|
||||||
|
|
||||||
"home": {
|
"home": {
|
||||||
"empty": {
|
"empty": {
|
||||||
|
"quickstart_guide": "Guide de démarrage rapide",
|
||||||
|
"not_right_now": "Pas maintenant",
|
||||||
"create_project": {
|
"create_project": {
|
||||||
"title": "Créer un projet",
|
"title": "Créer un projet",
|
||||||
"description": "La plupart des choses commencent par un projet dans Plane.",
|
"description": "La plupart des choses commencent par un projet dans Plane.",
|
||||||
|
|
|
||||||
|
|
@ -546,6 +546,8 @@
|
||||||
|
|
||||||
"home": {
|
"home": {
|
||||||
"empty": {
|
"empty": {
|
||||||
|
"quickstart_guide": "クイックスタートガイド",
|
||||||
|
"not_right_now": "今はしない",
|
||||||
"create_project": {
|
"create_project": {
|
||||||
"title": "プロジェクトを作成",
|
"title": "プロジェクトを作成",
|
||||||
"description": "Planeのほとんどはプロジェクトから始まります。",
|
"description": "Planeのほとんどはプロジェクトから始まります。",
|
||||||
|
|
|
||||||
|
|
@ -546,6 +546,8 @@
|
||||||
|
|
||||||
"home": {
|
"home": {
|
||||||
"empty": {
|
"empty": {
|
||||||
|
"quickstart_guide": "快速入门指南",
|
||||||
|
"not_right_now": "暂时不要",
|
||||||
"create_project": {
|
"create_project": {
|
||||||
"title": "创建项目",
|
"title": "创建项目",
|
||||||
"description": "在Plane中,大多数事情都从项目开始。",
|
"description": "在Plane中,大多数事情都从项目开始。",
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams, usePathname } from "next/navigation";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { THomeWidgetKeys, THomeWidgetProps } from "@plane/types";
|
import { THomeWidgetKeys, THomeWidgetProps } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { SimpleEmptyState } from "@/components/empty-state";
|
import { SimpleEmptyState } from "@/components/empty-state";
|
||||||
// hooks
|
// hooks
|
||||||
|
import { useProject } from "@/hooks/store";
|
||||||
import { useHome } from "@/hooks/store/use-home";
|
import { useHome } from "@/hooks/store/use-home";
|
||||||
// plane web components
|
// plane web components
|
||||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||||
import { HomePageHeader } from "@/plane-web/components/home/header";
|
import { HomePageHeader } from "@/plane-web/components/home/header";
|
||||||
import { StickiesWidget } from "../stickies";
|
import { StickiesWidget } from "../stickies";
|
||||||
import { RecentActivityWidget } from "./widgets";
|
import { HomeLoader, NoProjectsEmptyState, RecentActivityWidget } from "./widgets";
|
||||||
import { DashboardQuickLinks } from "./widgets/links";
|
import { DashboardQuickLinks } from "./widgets/links";
|
||||||
import { ManageWidgetsModal } from "./widgets/manage";
|
import { ManageWidgetsModal } from "./widgets/manage";
|
||||||
|
|
||||||
|
|
@ -52,14 +53,21 @@ export const HOME_WIDGETS_LIST: {
|
||||||
export const DashboardWidgets = observer(() => {
|
export const DashboardWidgets = observer(() => {
|
||||||
// router
|
// router
|
||||||
const { workspaceSlug } = useParams();
|
const { workspaceSlug } = useParams();
|
||||||
|
// navigation
|
||||||
|
const pathname = usePathname();
|
||||||
|
// store hooks
|
||||||
|
const { toggleWidgetSettings, widgetsMap, showWidgetSettings, orderedWidgets, isAnyWidgetEnabled, loading } =
|
||||||
|
useHome();
|
||||||
|
const { loader } = useProject();
|
||||||
// plane hooks
|
// plane hooks
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// store hooks
|
|
||||||
const { toggleWidgetSettings, widgetsMap, showWidgetSettings, orderedWidgets, isAnyWidgetEnabled } = useHome();
|
|
||||||
// derived values
|
// derived values
|
||||||
const noWidgetsResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/dashboard/widgets" });
|
const noWidgetsResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/dashboard/widgets" });
|
||||||
|
|
||||||
|
// derived values
|
||||||
|
const isWikiApp = pathname.includes(`/${workspaceSlug.toString()}/pages`);
|
||||||
if (!workspaceSlug) return null;
|
if (!workspaceSlug) return null;
|
||||||
|
if (loading || loader !== "loaded") return <HomeLoader />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full relative flex flex-col gap-7">
|
<div className="h-full w-full relative flex flex-col gap-7">
|
||||||
|
|
@ -69,6 +77,8 @@ export const DashboardWidgets = observer(() => {
|
||||||
isModalOpen={showWidgetSettings}
|
isModalOpen={showWidgetSettings}
|
||||||
handleOnClose={() => toggleWidgetSettings(false)}
|
handleOnClose={() => toggleWidgetSettings(false)}
|
||||||
/>
|
/>
|
||||||
|
{!isWikiApp && <NoProjectsEmptyState />}
|
||||||
|
|
||||||
{isAnyWidgetEnabled ? (
|
{isAnyWidgetEnabled ? (
|
||||||
<div className="flex flex-col divide-y-[1px] divide-custom-border-100">
|
<div className="flex flex-col divide-y-[1px] divide-custom-border-100">
|
||||||
{orderedWidgets.map((key) => {
|
{orderedWidgets.map((key) => {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,21 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
// mobx
|
||||||
|
import { observer } from "mobx-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { Briefcase, Hotel, Users } from "lucide-react";
|
import { Briefcase, Check, Hotel, Users, X } from "lucide-react";
|
||||||
// plane ui
|
// plane ui
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||||
|
import { useLocalStorage } from "@plane/hooks";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
// helpers
|
// helpers
|
||||||
|
import { cn } from "@plane/utils";
|
||||||
import { getFileURL } from "@/helpers/file.helper";
|
import { getFileURL } from "@/helpers/file.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useCommandPalette, useEventTracker, useUser, useUserPermissions } from "@/hooks/store";
|
import { useCommandPalette, useEventTracker, useProject, useUser, useUserPermissions } from "@/hooks/store";
|
||||||
// plane web constants
|
// plane web constants
|
||||||
|
|
||||||
export const NoProjectsEmptyState = () => {
|
export const NoProjectsEmptyState = observer(() => {
|
||||||
// navigation
|
// navigation
|
||||||
const { workspaceSlug } = useParams();
|
const { workspaceSlug } = useParams();
|
||||||
// store hooks
|
// store hooks
|
||||||
|
|
@ -19,6 +23,14 @@ export const NoProjectsEmptyState = () => {
|
||||||
const { toggleCreateProjectModal } = useCommandPalette();
|
const { toggleCreateProjectModal } = useCommandPalette();
|
||||||
const { setTrackElement } = useEventTracker();
|
const { setTrackElement } = useEventTracker();
|
||||||
const { data: currentUser } = useUser();
|
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();
|
const { t } = useTranslation();
|
||||||
// derived values
|
// derived values
|
||||||
const canCreateProject = allowPermissions(
|
const canCreateProject = allowPermissions(
|
||||||
|
|
@ -31,7 +43,8 @@ export const NoProjectsEmptyState = () => {
|
||||||
id: "create-project",
|
id: "create-project",
|
||||||
title: "home.empty.create_project.title",
|
title: "home.empty.create_project.title",
|
||||||
description: "home.empty.create_project.description",
|
description: "home.empty.create_project.description",
|
||||||
icon: <Briefcase className="w-[40px] h-[40px] text-custom-primary-100" />,
|
icon: <Briefcase className="size-10" />,
|
||||||
|
flag: "projects",
|
||||||
cta: {
|
cta: {
|
||||||
text: "home.empty.create_project.cta",
|
text: "home.empty.create_project.cta",
|
||||||
onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
|
|
@ -47,7 +60,8 @@ export const NoProjectsEmptyState = () => {
|
||||||
id: "invite-team",
|
id: "invite-team",
|
||||||
title: "home.empty.invite_team.title",
|
title: "home.empty.invite_team.title",
|
||||||
description: "home.empty.invite_team.description",
|
description: "home.empty.invite_team.description",
|
||||||
icon: <Users className="w-[40px] h-[40px] text-custom-primary-100" />,
|
icon: <Users className="size-10" />,
|
||||||
|
flag: "visited_members",
|
||||||
cta: {
|
cta: {
|
||||||
text: "home.empty.invite_team.cta",
|
text: "home.empty.invite_team.cta",
|
||||||
link: `/${workspaceSlug}/settings/members`,
|
link: `/${workspaceSlug}/settings/members`,
|
||||||
|
|
@ -57,7 +71,8 @@ export const NoProjectsEmptyState = () => {
|
||||||
id: "configure-workspace",
|
id: "configure-workspace",
|
||||||
title: "home.empty.configure_workspace.title",
|
title: "home.empty.configure_workspace.title",
|
||||||
description: "home.empty.configure_workspace.description",
|
description: "home.empty.configure_workspace.description",
|
||||||
icon: <Hotel className="w-[40px] h-[40px] text-custom-primary-100" />,
|
icon: <Hotel className="size-10" />,
|
||||||
|
flag: "visited_workspace",
|
||||||
cta: {
|
cta: {
|
||||||
text: "home.empty.configure_workspace.cta",
|
text: "home.empty.configure_workspace.cta",
|
||||||
link: "settings",
|
link: "settings",
|
||||||
|
|
@ -85,29 +100,77 @@ export const NoProjectsEmptyState = () => {
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
),
|
),
|
||||||
|
flag: "visited_profile",
|
||||||
cta: {
|
cta: {
|
||||||
text: "home.empty.personalize_account.cta",
|
text: "home.empty.personalize_account.cta",
|
||||||
link: "/profile",
|
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 (
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div className="text-base font-semibold text-custom-text-350">{t("home.empty.quickstart_guide")}</div>
|
||||||
|
<button
|
||||||
|
className="text-custom-text-300 font-medium text-sm flex items-center gap-1"
|
||||||
|
onClick={() => {
|
||||||
|
if (!storedValue) return;
|
||||||
|
setValue({ ...storedValue, hide: true });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<X className="size-4" />
|
||||||
|
{t("home.empty.not_right_now")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4">
|
||||||
{EMPTY_STATE_DATA.map((item) => (
|
{EMPTY_STATE_DATA.map((item) => {
|
||||||
|
const isStateComplete = isComplete(item.flag);
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className="flex flex-col items-center justify-center p-6 bg-custom-background-100 rounded-lg text-center border border-custom-border-200/40"
|
className="flex flex-col items-center justify-center p-6 bg-custom-background-100 rounded-lg text-center border border-custom-border-200/40"
|
||||||
>
|
>
|
||||||
<div className="grid place-items-center bg-custom-primary-100/10 rounded-full size-24 mb-3">
|
<div
|
||||||
|
className={cn(
|
||||||
|
"grid place-items-center bg-custom-background-90 rounded-full size-20 mb-3 text-custom-text-400",
|
||||||
|
{
|
||||||
|
"text-custom-primary-100 bg-custom-primary-100/10": !isStateComplete,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
<span className="text-3xl my-auto">{item.icon}</span>
|
<span className="text-3xl my-auto">{item.icon}</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-medium text-custom-text-100 mb-2">{t(item.title)}</h3>
|
<h3 className="text-base font-medium text-custom-text-100 mb-2">{t(item.title)}</h3>
|
||||||
<p className="text-sm text-custom-text-200 mb-4 w-[80%] flex-1">{t(item.description)}</p>
|
<p className="text-sm text-custom-text-300 mb-2">{t(item.description)}</p>
|
||||||
|
{isStateComplete ? (
|
||||||
{item.cta.link ? (
|
<div className="flex items-center gap-2 bg-[#17a34a] rounded-full p-1">
|
||||||
|
<Check className="size-3 text-custom-primary-100 text-white" />
|
||||||
|
</div>
|
||||||
|
) : item.cta.link ? (
|
||||||
<Link
|
<Link
|
||||||
href={item.cta.link}
|
href={item.cta.link}
|
||||||
|
onClick={() => {
|
||||||
|
if (!storedValue) return;
|
||||||
|
setValue({
|
||||||
|
...storedValue,
|
||||||
|
[item.flag]: true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
className="text-custom-primary-100 hover:text-custom-primary-200 text-sm font-medium"
|
className="text-custom-primary-100 hover:text-custom-primary-200 text-sm font-medium"
|
||||||
>
|
>
|
||||||
{t(item.cta.text)}
|
{t(item.cta.text)}
|
||||||
|
|
@ -122,7 +185,9 @@ export const NoProjectsEmptyState = () => {
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,9 @@ export const useLinks = (workspaceSlug: string) => {
|
||||||
});
|
});
|
||||||
toggleLinkModal(false);
|
toggleLinkModal(false);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("error", error);
|
console.error("error", error?.data?.url?.error);
|
||||||
setToast({
|
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,
|
type: TOAST_TYPE.ERROR,
|
||||||
title: t("links.toasts.not_created.title"),
|
title: t("links.toasts.not_created.title"),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
22
web/core/components/home/widgets/loaders/home-loader.tsx
Normal file
22
web/core/components/home/widgets/loaders/home-loader.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import range from "lodash/range";
|
||||||
|
// ui
|
||||||
|
import { Loader } from "@plane/ui";
|
||||||
|
|
||||||
|
export const HomeLoader = () => (
|
||||||
|
<>
|
||||||
|
{range(3).map((index) => (
|
||||||
|
<div key={index}>
|
||||||
|
<div className="mb-2">
|
||||||
|
<div className="text-base font-semibold text-custom-text-350 mb-4">
|
||||||
|
<Loader.Item height="20px" width="100px" />
|
||||||
|
</div>
|
||||||
|
<Loader className="h-[110px] w-full flex items-center justify-center gap-2 text-custom-text-400 rounded">
|
||||||
|
<Loader.Item height="100%" width="100%" />
|
||||||
|
</Loader>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
export * from "./loader";
|
export * from "./loader";
|
||||||
|
export * from "./home-loader";
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { usePathname } from "next/navigation";
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { Briefcase, FileText } from "lucide-react";
|
import { Briefcase, FileText } from "lucide-react";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
|
|
@ -12,11 +11,9 @@ import { TActivityEntityData, THomeWidgetProps, TRecentActivityFilterKeys } from
|
||||||
import { LayersIcon } from "@plane/ui";
|
import { LayersIcon } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ContentOverflowWrapper } from "@/components/core/content-overflow-HOC";
|
import { ContentOverflowWrapper } from "@/components/core/content-overflow-HOC";
|
||||||
// hooks
|
|
||||||
import { useProject } from "@/hooks/store";
|
|
||||||
// plane web services
|
// plane web services
|
||||||
import { WorkspaceService } from "@/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 { EWidgetKeys, WidgetLoader } from "../loaders";
|
||||||
import { FiltersDropdown } from "./filters";
|
import { FiltersDropdown } from "./filters";
|
||||||
import { RecentIssue } from "./issue";
|
import { RecentIssue } from "./issue";
|
||||||
|
|
@ -41,15 +38,9 @@ export const RecentActivityWidget: React.FC<TRecentWidgetProps> = observer((prop
|
||||||
const { presetFilter, showFilterSelect = true, workspaceSlug } = props;
|
const { presetFilter, showFilterSelect = true, workspaceSlug } = props;
|
||||||
// states
|
// states
|
||||||
const [filter, setFilter] = useState<TRecentActivityFilterKeys>(presetFilter ?? filters[0].name);
|
const [filter, setFilter] = useState<TRecentActivityFilterKeys>(presetFilter ?? filters[0].name);
|
||||||
// navigation
|
const { t } = useTranslation();
|
||||||
const pathname = usePathname();
|
|
||||||
// ref
|
// ref
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
// store hooks
|
|
||||||
const { joinedProjectIds, loader } = useProject();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
// derived values
|
|
||||||
const isWikiApp = pathname.includes(`/${workspaceSlug.toString()}/pages`);
|
|
||||||
|
|
||||||
const { data: recents, isLoading } = useSWR(
|
const { data: recents, isLoading } = useSWR(
|
||||||
workspaceSlug ? `WORKSPACE_RECENT_ACTIVITY_${workspaceSlug}_${filter}` : null,
|
workspaceSlug ? `WORKSPACE_RECENT_ACTIVITY_${workspaceSlug}_${filter}` : null,
|
||||||
|
|
@ -81,8 +72,6 @@ export const RecentActivityWidget: React.FC<TRecentWidgetProps> = observer((prop
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loader === "loaded" && !isWikiApp && joinedProjectIds?.length === 0) return <NoProjectsEmptyState />;
|
|
||||||
|
|
||||||
if (!isLoading && recents?.length === 0)
|
if (!isLoading && recents?.length === 0)
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className="max-h-[500px] overflow-y-scroll">
|
<div ref={ref} className="max-h-[500px] overflow-y-scroll">
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ export const StickiesLayout = (props: TStickiesLayout) => {
|
||||||
const columnCount = getColumnCount(containerWidth);
|
const columnCount = getColumnCount(containerWidth);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className="size-full min-h-[500px]">
|
<div ref={ref} className="size-full">
|
||||||
<StickiesList {...props} columnCount={columnCount} />
|
<StickiesList {...props} columnCount={columnCount} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { IWorkspaceLinkStore, WorkspaceLinkStore } from "./link.store";
|
||||||
|
|
||||||
export interface IHomeStore {
|
export interface IHomeStore {
|
||||||
// observables
|
// observables
|
||||||
|
loading: boolean;
|
||||||
showWidgetSettings: boolean;
|
showWidgetSettings: boolean;
|
||||||
widgetsMap: Record<string, TWidgetEntityData>;
|
widgetsMap: Record<string, TWidgetEntityData>;
|
||||||
widgets: THomeWidgetKeys[];
|
widgets: THomeWidgetKeys[];
|
||||||
|
|
@ -25,6 +26,7 @@ export interface IHomeStore {
|
||||||
export class HomeStore implements IHomeStore {
|
export class HomeStore implements IHomeStore {
|
||||||
// observables
|
// observables
|
||||||
showWidgetSettings = false;
|
showWidgetSettings = false;
|
||||||
|
loading = false;
|
||||||
widgetsMap: Record<string, TWidgetEntityData> = {};
|
widgetsMap: Record<string, TWidgetEntityData> = {};
|
||||||
widgets: THomeWidgetKeys[] = [];
|
widgets: THomeWidgetKeys[] = [];
|
||||||
// stores
|
// stores
|
||||||
|
|
@ -35,6 +37,7 @@ export class HomeStore implements IHomeStore {
|
||||||
constructor() {
|
constructor() {
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
// observables
|
// observables
|
||||||
|
loading: observable,
|
||||||
showWidgetSettings: observable,
|
showWidgetSettings: observable,
|
||||||
widgetsMap: observable,
|
widgetsMap: observable,
|
||||||
widgets: observable,
|
widgets: observable,
|
||||||
|
|
@ -68,15 +71,18 @@ export class HomeStore implements IHomeStore {
|
||||||
|
|
||||||
fetchWidgets = async (workspaceSlug: string) => {
|
fetchWidgets = async (workspaceSlug: string) => {
|
||||||
try {
|
try {
|
||||||
|
this.loading = true;
|
||||||
const widgets = await this.workspaceService.fetchWorkspaceWidgets(workspaceSlug);
|
const widgets = await this.workspaceService.fetchWorkspaceWidgets(workspaceSlug);
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.widgets = orderBy(Object.values(widgets), "sort_order", "desc").map((widget) => widget.key);
|
this.widgets = orderBy(Object.values(widgets), "sort_order", "desc").map((widget) => widget.key);
|
||||||
widgets.forEach((widget) => {
|
widgets.forEach((widget) => {
|
||||||
this.widgetsMap[widget.key] = widget;
|
this.widgetsMap[widget.key] = widget;
|
||||||
});
|
});
|
||||||
|
this.loading = false;
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch widgets");
|
console.error("Failed to fetch widgets");
|
||||||
|
this.loading = false;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,6 @@ export class WorkspaceLinkStore implements IWorkspaceLinkStore {
|
||||||
};
|
};
|
||||||
|
|
||||||
createLink = async (workspaceSlug: string, data: Partial<TLink>) => {
|
createLink = async (workspaceSlug: string, data: Partial<TLink>) => {
|
||||||
console.log("hereee");
|
|
||||||
const response = await this.workspaceService.createWorkspaceLink(workspaceSlug, data);
|
const response = await this.workspaceService.createWorkspaceLink(workspaceSlug, data);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue