Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: empty state refactor #6404

Open
wants to merge 29 commits into
base: preview
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
eb91533
chore: asset path helper hook added
anmolsinghbhatia Jan 15, 2025
05ab094
chore: detailed and simple empty state component added
anmolsinghbhatia Jan 15, 2025
3307594
chore: section empty state component added
anmolsinghbhatia Jan 15, 2025
be40d60
chore: language translation for all empty states
prateekshourya29 Jan 15, 2025
55e28c6
Merge branch 'preview' of github.com:makeplane/plane into chore-empty…
anmolsinghbhatia Jan 16, 2025
ce42651
chore: new empty state implementation
anmolsinghbhatia Jan 16, 2025
6d1ebca
Merge branch 'chore-empty-state-refactor' of github.com:makeplane/pla…
anmolsinghbhatia Jan 16, 2025
86f5398
improvement: add more translations
prateekshourya29 Jan 16, 2025
239f79b
improvement: user permissions and workspace draft empty state
prateekshourya29 Jan 16, 2025
3adaccf
Merge branch 'preview' of github.com:makeplane/plane into chore-empty…
prateekshourya29 Jan 16, 2025
bb2542b
chore: update translation structure
prateekshourya29 Jan 16, 2025
7f6ad5f
chore: inbox empty states
prateekshourya29 Jan 16, 2025
c3752e4
Merge branch 'preview' of github.com:makeplane/plane into chore-empty…
prateekshourya29 Jan 16, 2025
a092719
chore: disabled project features empty state
prateekshourya29 Jan 16, 2025
f7274e4
chore: active cycle progress empty state
prateekshourya29 Jan 16, 2025
bafa6ac
chore: notification empty state
prateekshourya29 Jan 16, 2025
38927c1
chore: connections translation
prateekshourya29 Jan 17, 2025
3fe9077
chore: issue comment, relation, bulk delete, and command k empty stat…
prateekshourya29 Jan 17, 2025
2611105
chore: project pages empty state and translations
prateekshourya29 Jan 17, 2025
7e413e1
Merge branch 'preview' of github.com:makeplane/plane into chore-empty…
prateekshourya29 Jan 17, 2025
77a4833
chore: project module and view related empty state
prateekshourya29 Jan 17, 2025
90a8834
chore: remove project draft related empty state
prateekshourya29 Jan 17, 2025
007c5cf
chore: project cycle, views and archived issues empty state
prateekshourya29 Jan 17, 2025
07ab153
chore: project cycles related empty state
prateekshourya29 Jan 17, 2025
1314b90
chore: project settings empty state
prateekshourya29 Jan 17, 2025
8497947
chore: profile issue and acitivity empty state
prateekshourya29 Jan 17, 2025
214c820
chore: workspace settings realted constants
prateekshourya29 Jan 17, 2025
0e3cdee
chore: stickies and home widgets empty state
prateekshourya29 Jan 17, 2025
e95a40c
chore: remove all reference to deprecated empty state component and c…
prateekshourya29 Jan 17, 2025
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
469 changes: 465 additions & 4 deletions packages/i18n/src/locales/en/translations.json

Large diffs are not rendered by default.

468 changes: 464 additions & 4 deletions packages/i18n/src/locales/es/translations.json

Large diffs are not rendered by default.

467 changes: 464 additions & 3 deletions packages/i18n/src/locales/fr/translations.json

Large diffs are not rendered by default.

467 changes: 464 additions & 3 deletions packages/i18n/src/locales/ja/translations.json

Large diffs are not rendered by default.

45 changes: 34 additions & 11 deletions web/app/[workspaceSlug]/(projects)/analytics/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,41 @@ import { observer } from "mobx-react";
import { useSearchParams } from "next/navigation";
import { Tab } from "@headlessui/react";
// plane package imports
import { ANALYTICS_TABS } from "@plane/constants";
import { ANALYTICS_TABS, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { Header, EHeaderVariant } from "@plane/ui";
// components
import { CustomAnalytics, ScopeAndDemand } from "@/components/analytics";
import { PageHead } from "@/components/core";
import { EmptyState } from "@/components/empty-state";
// constants
import { EmptyStateType } from "@/constants/empty-state";
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
// hooks
import { useCommandPalette, useEventTracker, useProject, useWorkspace } from "@/hooks/store";
import { useCommandPalette, useEventTracker, useProject, useUserPermissions, useWorkspace } from "@/hooks/store";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
// plane web
import { EUserPermissions } from "@/plane-web/constants";

const AnalyticsPage = observer(() => {
const searchParams = useSearchParams();
const analytics_tab = searchParams.get("analytics_tab");
// plane imports
const { t } = useTranslation();
// store hooks
const { toggleCreateProjectModal } = useCommandPalette();
const { setTrackElement } = useEventTracker();
const { workspaceProjectIds, loader } = useProject();
const { currentWorkspace } = useWorkspace();
const { allowPermissions } = useUserPermissions();
// helper hooks
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/analytics" });
// derived values
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Analytics` : undefined;

// permissions
const canPerformEmptyStateActions = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
EUserPermissionsLevel.WORKSPACE
);

// TODO: refactor loader implementation
return (
<>
Expand Down Expand Up @@ -67,12 +80,22 @@ const AnalyticsPage = observer(() => {
</Tab.Group>
</div>
) : (
<EmptyState
type={EmptyStateType.WORKSPACE_ANALYTICS}
primaryButtonOnClick={() => {
setTrackElement("Analytics empty state");
toggleCreateProjectModal(true);
}}
<DetailedEmptyState
title={t("workspace_analytics.empty_state.general.title")}
description={t("workspace_analytics.empty_state.general.description")}
assetPath={resolvedPath}
customPrimaryButton={
<ComicBoxButton
label={t("workspace_analytics.empty_state.general.primary_button.text")}
title={t("workspace_analytics.empty_state.general.primary_button.comic.title")}
description={t("workspace_analytics.empty_state.general.primary_button.comic.description")}
onClick={() => {
setTrackElement("Analytics empty state");
toggleCreateProjectModal(true);
}}
disabled={!canPerformEmptyStateActions}
/>
}
/>
)}
</>
Expand Down
12 changes: 8 additions & 4 deletions web/app/[workspaceSlug]/(projects)/notifications/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,24 @@ import { useCallback, useEffect } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import useSWR from "swr";
// plane imports
import { useTranslation } from "@plane/i18n";
// components
import { LogoSpinner } from "@/components/common";
import { PageHead } from "@/components/core";
import { EmptyState } from "@/components/empty-state";
import { SimpleEmptyState } from "@/components/empty-state";
import { InboxContentRoot } from "@/components/inbox";
import { IssuePeekOverview } from "@/components/issues";
// constants
import { EmptyStateType } from "@/constants/empty-state";
import { ENotificationLoader, ENotificationQueryParamType } from "@/constants/notification";
// hooks
import { useIssueDetail, useUserPermissions, useWorkspace, useWorkspaceNotifications } from "@/hooks/store";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
import { useWorkspaceIssueProperties } from "@/hooks/use-workspace-issue-properties";

const WorkspaceDashboardPage = observer(() => {
const { workspaceSlug } = useParams();
// plane hooks
const { t } = useTranslation();
// hooks
const { currentWorkspace } = useWorkspace();
const {
Expand All @@ -34,6 +37,7 @@ const WorkspaceDashboardPage = observer(() => {
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Inbox` : undefined;
const { workspace_slug, project_id, issue_id, is_inbox_issue } =
notificationLiteByNotificationId(currentSelectedNotificationId);
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/intake/issue-detail" });

// fetching workspace issue properties
useWorkspaceIssueProperties(workspaceSlug);
Expand Down Expand Up @@ -82,7 +86,7 @@ const WorkspaceDashboardPage = observer(() => {
<div className="w-full h-full overflow-hidden overflow-y-auto">
{!currentSelectedNotificationId ? (
<div className="w-full h-screen flex justify-center items-center">
<EmptyState type={EmptyStateType.NOTIFICATION_DETAIL_EMPTY_STATE} layout="screen-simple" />
<SimpleEmptyState title={t("notification.empty_state.detail.title")} assetPath={resolvedPath} />
</div>
) : (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@
import { useState } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// types
// plane imports
import { EUserPermissionsLevel, EUserProjectRoles } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { TCycleFilters } from "@plane/types";
// components
import { Header, EHeaderVariant } from "@plane/ui";
import { PageHead } from "@/components/core";
import { CyclesView, CycleCreateUpdateModal, CycleAppliedFiltersList } from "@/components/cycles";
import { EmptyState } from "@/components/empty-state";
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
import { CycleModuleListLayout } from "@/components/ui";
// constants
import { EmptyStateType } from "@/constants/empty-state";
// helpers
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks
import { useEventTracker, useCycle, useProject, useCycleFilter } from "@/hooks/store";
import { useEventTracker, useCycle, useProject, useCycleFilter, useUserPermissions } from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";

const ProjectCyclesPage = observer(() => {
// states
Expand All @@ -26,13 +28,23 @@ const ProjectCyclesPage = observer(() => {
const { currentProjectCycleIds, loader } = useCycle();
const { getProjectById, currentProjectDetails } = useProject();
// router
const router = useAppRouter();
const { workspaceSlug, projectId } = useParams();
// plane hooks
const { t } = useTranslation();
// cycle filters hook
const { clearAllFilters, currentProjectFilters, updateFilters } = useCycleFilter();
const { allowPermissions } = useUserPermissions();
// derived values
const totalCycles = currentProjectCycleIds?.length ?? 0;
const project = projectId ? getProjectById(projectId?.toString()) : undefined;
const pageTitle = project?.name ? `${project?.name} - Cycles` : undefined;
const hasAdminLevelPermission = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
const hasMemberLevelPermission = allowPermissions(
[EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER],
EUserPermissionsLevel.PROJECT
);
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/cycles" });

const handleRemoveFilter = (key: keyof TCycleFilters, value: string | null) => {
if (!projectId) return;
Expand All @@ -50,9 +62,17 @@ const ProjectCyclesPage = observer(() => {
if (currentProjectDetails?.cycle_view === false)
return (
<div className="flex items-center justify-center h-full w-full">
<EmptyState
type={EmptyStateType.DISABLED_PROJECT_CYCLE}
primaryButtonLink={`/${workspaceSlug}/projects/${projectId}/settings/features`}
<DetailedEmptyState
title={t("disabled_project.empty_state.cycle.title")}
description={t("disabled_project.empty_state.cycle.description")}
assetPath={resolvedPath}
primaryButton={{
text: t("disabled_project.empty_state.cycle.primary_button.text"),
onClick: () => {
router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`);
},
disabled: !hasAdminLevelPermission,
}}
/>
</div>
);
Expand All @@ -71,12 +91,22 @@ const ProjectCyclesPage = observer(() => {
/>
{totalCycles === 0 ? (
<div className="h-full place-items-center">
<EmptyState
type={EmptyStateType.PROJECT_CYCLES}
primaryButtonOnClick={() => {
setTrackElement("Cycle empty state");
setCreateModal(true);
}}
<DetailedEmptyState
title={t("project_cycles.empty_state.general.title")}
description={t("project_cycles.empty_state.general.description")}
assetPath={resolvedPath}
customPrimaryButton={
<ComicBoxButton
label={t("project_cycles.empty_state.general.primary_button.text")}
title={t("project_cycles.empty_state.general.primary_button.comic.title")}
description={t("project_cycles.empty_state.general.primary_button.comic.description")}
onClick={() => {
setTrackElement("Cycle empty state");
setCreateModal(true);
}}
disabled={!hasMemberLevelPermission}
/>
}
/>
</div>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,50 @@
import { observer } from "mobx-react";
// components
import { useParams, useSearchParams } from "next/navigation";
import { EUserProjectRoles } from "@plane/constants/src/user";
import { useTranslation } from "@plane/i18n";
import { PageHead } from "@/components/core";
import { EmptyState } from "@/components/empty-state";
import { DetailedEmptyState } from "@/components/empty-state";
import { InboxIssueRoot } from "@/components/inbox";
// constants
import { EmptyStateType } from "@/constants/empty-state";
// helpers
import { EInboxIssueCurrentTab } from "@/helpers/inbox.helper";
// hooks
import { useProject } from "@/hooks/store";
import { useProject, useUserPermissions } from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
import { EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";

const ProjectInboxPage = observer(() => {
/// router
const router = useAppRouter();
const { workspaceSlug, projectId } = useParams();

const searchParams = useSearchParams();

const navigationTab = searchParams.get("currentTab");
const inboxIssueId = searchParams.get("inboxIssueId");

// plane hooks
const { t } = useTranslation();
// hooks
const { currentProjectDetails } = useProject();
const { allowPermissions } = useUserPermissions();
// derived values
const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/intake" });

// No access to inbox
if (currentProjectDetails?.inbox_view === false)
return (
<div className="flex items-center justify-center h-full w-full">
<EmptyState
type={EmptyStateType.DISABLED_PROJECT_INBOX}
primaryButtonLink={`/${workspaceSlug}/projects/${projectId}/settings/features`}
<DetailedEmptyState
title={t("disabled_project.empty_state.inbox.title")}
description={t("disabled_project.empty_state.inbox.description")}
assetPath={resolvedPath}
primaryButton={{
text: t("disabled_project.empty_state.inbox.primary_button.text"),
onClick: () => {
router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`);
},
disabled: !canPerformEmptyStateActions,
}}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,36 @@ import { useCallback } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// types
import { EUserPermissionsLevel, EUserProjectRoles } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { TModuleFilters } from "@plane/types";
// components
import { PageHead } from "@/components/core";
import { EmptyState } from "@/components/empty-state";
import { DetailedEmptyState } from "@/components/empty-state";
import { ModuleAppliedFiltersList, ModulesListView } from "@/components/modules";
// constants
import { EmptyStateType } from "@/constants/empty-state";
// helpers
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks
import { useModuleFilter, useProject } from "@/hooks/store";
import { useModuleFilter, useProject, useUserPermissions } from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";

const ProjectModulesPage = observer(() => {
// router
const router = useAppRouter();
const { workspaceSlug, projectId } = useParams();
// plane hooks
const { t } = useTranslation();
// store
const { getProjectById, currentProjectDetails } = useProject();
const { currentProjectFilters, currentProjectDisplayFilters, clearAllFilters, updateFilters, updateDisplayFilters } =
useModuleFilter();
const { allowPermissions } = useUserPermissions();
// derived values
const project = projectId ? getProjectById(projectId.toString()) : undefined;
const pageTitle = project?.name ? `${project?.name} - Modules` : undefined;
const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/modules" });

const handleRemoveFilter = useCallback(
(key: keyof TModuleFilters, value: string | null) => {
Expand All @@ -45,9 +54,17 @@ const ProjectModulesPage = observer(() => {
if (currentProjectDetails?.module_view === false)
return (
<div className="flex items-center justify-center h-full w-full">
<EmptyState
type={EmptyStateType.DISABLED_PROJECT_MODULE}
primaryButtonLink={`/${workspaceSlug}/projects/${projectId}/settings/features`}
<DetailedEmptyState
title={t("disabled_project.empty_state.module.title")}
description={t("disabled_project.empty_state.module.description")}
assetPath={resolvedPath}
primaryButton={{
text: t("disabled_project.empty_state.module.primary_button.text"),
onClick: () => {
router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`);
},
disabled: !canPerformEmptyStateActions,
}}
/>
</div>
);
Expand Down
Loading
Loading