[WEB-5230 | WEB-5231] chore: new empty state implementation (#7972)
This commit is contained in:
parent
a60d74a3c0
commit
68fd2463f4
72 changed files with 5260 additions and 746 deletions
|
|
@ -6,6 +6,7 @@ import { useParams } from "next/navigation";
|
|||
// plane imports
|
||||
import { EUserPermissionsLevel, CYCLE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateDetailed } from "@plane/propel/empty-state";
|
||||
import type { TCycleFilters } from "@plane/types";
|
||||
import { EUserProjectRoles } from "@plane/types";
|
||||
// components
|
||||
|
|
@ -15,7 +16,6 @@ import { PageHead } from "@/components/core/page-title";
|
|||
import { CycleAppliedFiltersList } from "@/components/cycles/applied-filters";
|
||||
import { CyclesView } from "@/components/cycles/cycles-view";
|
||||
import { CycleCreateUpdateModal } from "@/components/cycles/modal";
|
||||
import { ComicBoxButton } from "@/components/empty-state/comic-box-button";
|
||||
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
import { CycleModuleListLayoutLoader } from "@/components/ui/loader/cycle-module-list-loader";
|
||||
// hooks
|
||||
|
|
@ -96,22 +96,19 @@ const ProjectCyclesPage = observer(() => {
|
|||
/>
|
||||
{totalCycles === 0 ? (
|
||||
<div className="h-full place-items-center">
|
||||
<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")}
|
||||
data-ph-element={CYCLE_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON}
|
||||
onClick={() => {
|
||||
setCreateModal(true);
|
||||
}}
|
||||
disabled={!hasMemberLevelPermission}
|
||||
/>
|
||||
}
|
||||
<EmptyStateDetailed
|
||||
assetKey="cycle"
|
||||
title={t("project.cycles.title")}
|
||||
description={t("project.cycles.description")}
|
||||
actions={[
|
||||
{
|
||||
label: t("project.cycles.cta_primary"),
|
||||
onClick: () => setCreateModal(true),
|
||||
variant: "primary",
|
||||
disabled: !hasMemberLevelPermission,
|
||||
"data-ph-element": CYCLE_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -7,18 +7,17 @@ import useSWR from "swr";
|
|||
import { PROFILE_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// component
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
import { APITokenService } from "@plane/services";
|
||||
import { CreateApiTokenModal } from "@/components/api-token/modal/create-token-modal";
|
||||
import { ApiTokenListItem } from "@/components/api-token/token-list-item";
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
import { SettingsHeading } from "@/components/settings/heading";
|
||||
import { APITokenSettingsLoader } from "@/components/ui/loader/settings/api-token";
|
||||
import { API_TOKENS_LIST } from "@/constants/fetch-keys";
|
||||
// store hooks
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
const apiTokenService = new APITokenService();
|
||||
|
||||
|
|
@ -30,8 +29,6 @@ const ApiTokensPage = observer(() => {
|
|||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
// derived values
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/workspace-settings/api-tokens" });
|
||||
|
||||
const { data: tokens } = useSWR(API_TOKENS_LIST, () => apiTokenService.list());
|
||||
|
||||
|
|
@ -70,7 +67,7 @@ const ApiTokensPage = observer(() => {
|
|||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex h-full w-full flex-col">
|
||||
<div className="flex h-full w-full flex-col py-">
|
||||
<SettingsHeading
|
||||
title={t("account_settings.api_tokens.heading")}
|
||||
description={t("account_settings.api_tokens.description")}
|
||||
|
|
@ -84,24 +81,26 @@ const ApiTokensPage = observer(() => {
|
|||
},
|
||||
}}
|
||||
/>
|
||||
<div className="h-full w-full flex items-center justify-center">
|
||||
<DetailedEmptyState
|
||||
title=""
|
||||
description=""
|
||||
assetPath={resolvedPath}
|
||||
className="w-full !p-0 justify-center mx-auto"
|
||||
size="md"
|
||||
primaryButton={{
|
||||
text: t("workspace_settings.settings.api_tokens.add_token"),
|
||||
|
||||
<EmptyStateCompact
|
||||
assetKey="token"
|
||||
assetClassName="size-20"
|
||||
title={t("settings.tokens.title")}
|
||||
description={t("settings.tokens.description")}
|
||||
actions={[
|
||||
{
|
||||
label: t("settings.tokens.cta_primary"),
|
||||
onClick: () => {
|
||||
captureClick({
|
||||
elementName: PROFILE_SETTINGS_TRACKER_ELEMENTS.EMPTY_STATE_ADD_PAT_BUTTON,
|
||||
});
|
||||
setIsCreateTokenModalOpen(true);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
},
|
||||
]}
|
||||
align="start"
|
||||
rootClassName="py-20"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -14,18 +14,16 @@ import {
|
|||
getFacetedRowModel,
|
||||
getFacetedUniqueValues,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { Search, X } from "lucide-react";
|
||||
// plane package imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@plane/propel/table";
|
||||
import { cn } from "@plane/utils";
|
||||
// plane web components
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import AnalyticsEmptyState from "../empty-state";
|
||||
|
||||
interface DataTableProps<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
|
|
@ -42,7 +40,6 @@ export function DataTable<TData, TValue>({ columns, data, searchPlaceholder, act
|
|||
const { t } = useTranslation();
|
||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||
const [isSearchOpen, setIsSearchOpen] = React.useState(false);
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics/empty-table" });
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
|
|
@ -156,14 +153,12 @@ export function DataTable<TData, TValue>({ columns, data, searchPlaceholder, act
|
|||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="p-0">
|
||||
<div className="flex h-[350px] w-full items-center justify-center border border-custom-border-100 ">
|
||||
<AnalyticsEmptyState
|
||||
title={t("workspace_analytics.empty_state.customized_insights.title")}
|
||||
description={t("workspace_analytics.empty_state.customized_insights.description")}
|
||||
className="border-0"
|
||||
assetPath={resolvedPath}
|
||||
/>
|
||||
</div>
|
||||
<EmptyStateCompact
|
||||
assetKey="unknown"
|
||||
assetClassName="size-20"
|
||||
rootClassName="border border-custom-border-100 px-5 py-10 md:py-20 md:px-20"
|
||||
title={t("workspace.analytics_work_items.title")}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,14 @@ import { useParams } from "next/navigation";
|
|||
import useSWR from "swr";
|
||||
// plane package imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
import type { TChartData } from "@plane/types";
|
||||
// hooks
|
||||
import { useAnalytics } from "@/hooks/store/use-analytics";
|
||||
// services
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import { AnalyticsService } from "@/services/analytics.service";
|
||||
// plane web components
|
||||
import AnalyticsSectionWrapper from "../analytics-section-wrapper";
|
||||
import AnalyticsEmptyState from "../empty-state";
|
||||
import { ProjectInsightsLoader } from "../loaders";
|
||||
|
||||
const RadarChart = dynamic(() =>
|
||||
|
|
@ -29,7 +28,6 @@ const ProjectInsights = observer(() => {
|
|||
const workspaceSlug = params.workspaceSlug.toString();
|
||||
const { selectedDuration, selectedDurationLabel, selectedProjects, selectedCycle, selectedModule, isPeekView } =
|
||||
useAnalytics();
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics/empty-chart-radar" });
|
||||
|
||||
const { data: projectInsightsData, isLoading: isLoadingProjectInsight } = useSWR(
|
||||
`radar-chart-project-insights-${workspaceSlug}-${selectedDuration}-${selectedProjects}-${selectedCycle}-${selectedModule}-${isPeekView}`,
|
||||
|
|
@ -56,11 +54,11 @@ const ProjectInsights = observer(() => {
|
|||
{isLoadingProjectInsight ? (
|
||||
<ProjectInsightsLoader />
|
||||
) : projectInsightsData && projectInsightsData?.length == 0 ? (
|
||||
<AnalyticsEmptyState
|
||||
title={t("workspace_analytics.empty_state.project_insights.title")}
|
||||
description={t("workspace_analytics.empty_state.project_insights.description")}
|
||||
className="h-[300px]"
|
||||
assetPath={resolvedPath}
|
||||
<EmptyStateCompact
|
||||
assetKey="unknown"
|
||||
assetClassName="size-20"
|
||||
rootClassName="border border-custom-border-100 px-5 py-10 md:py-20 md:px-20"
|
||||
title={t("workspace.analytics_work_items.title")}
|
||||
/>
|
||||
) : (
|
||||
<div className="gap-8 lg:flex">
|
||||
|
|
|
|||
|
|
@ -5,16 +5,15 @@ import useSWR from "swr";
|
|||
// plane package imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { AreaChart } from "@plane/propel/charts/area-chart";
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
import type { IChartResponse, TChartData } from "@plane/types";
|
||||
import { renderFormattedDate } from "@plane/utils";
|
||||
// hooks
|
||||
import { useAnalytics } from "@/hooks/store/use-analytics";
|
||||
// services
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import { AnalyticsService } from "@/services/analytics.service";
|
||||
// plane web components
|
||||
import AnalyticsSectionWrapper from "../analytics-section-wrapper";
|
||||
import AnalyticsEmptyState from "../empty-state";
|
||||
import { ChartLoader } from "../loaders";
|
||||
|
||||
const analyticsService = new AnalyticsService();
|
||||
|
|
@ -31,7 +30,6 @@ const CreatedVsResolved = observer(() => {
|
|||
const params = useParams();
|
||||
const { t } = useTranslation();
|
||||
const workspaceSlug = params.workspaceSlug.toString();
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics/empty-chart-area" });
|
||||
const { data: createdVsResolvedData, isLoading: isCreatedVsResolvedLoading } = useSWR(
|
||||
`created-vs-resolved-${workspaceSlug}-${selectedDuration}-${selectedProjects}-${selectedCycle}-${selectedModule}-${isPeekView}-${isEpic}`,
|
||||
() =>
|
||||
|
|
@ -121,11 +119,11 @@ const CreatedVsResolved = observer(() => {
|
|||
}}
|
||||
/>
|
||||
) : (
|
||||
<AnalyticsEmptyState
|
||||
title={t("workspace_analytics.empty_state.created_vs_resolved.title")}
|
||||
description={t("workspace_analytics.empty_state.created_vs_resolved.description")}
|
||||
className="h-[350px]"
|
||||
assetPath={resolvedPath}
|
||||
<EmptyStateCompact
|
||||
assetKey="unknown"
|
||||
assetClassName="size-20"
|
||||
rootClassName="border border-custom-border-100 px-5 py-10 md:py-20 md:px-20"
|
||||
title={t("workspace.analytics_work_items.title")}
|
||||
/>
|
||||
)}
|
||||
</AnalyticsSectionWrapper>
|
||||
|
|
|
|||
|
|
@ -11,15 +11,14 @@ import { ANALYTICS_X_AXIS_VALUES, ANALYTICS_Y_AXIS_VALUES, CHART_COLOR_PALETTES,
|
|||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { BarChart } from "@plane/propel/charts/bar-chart";
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
import type { TBarItem, TChart, TChartDatum, ChartXAxisProperty, ChartYAxisMetric } from "@plane/types";
|
||||
// plane web components
|
||||
import { generateExtendedColors, parseChartData } from "@/components/chart/utils";
|
||||
// hooks
|
||||
import { useAnalytics } from "@/hooks/store/use-analytics";
|
||||
import { useProjectState } from "@/hooks/store/use-project-state";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import { AnalyticsService } from "@/services/analytics.service";
|
||||
import AnalyticsEmptyState from "../empty-state";
|
||||
import { exportCSV } from "../export";
|
||||
import { DataTable } from "../insight-table/data-table";
|
||||
import { ChartLoader } from "../loaders";
|
||||
|
|
@ -46,7 +45,6 @@ const analyticsService = new AnalyticsService();
|
|||
const PriorityChart = observer((props: Props) => {
|
||||
const { x_axis, y_axis, group_by } = props;
|
||||
const { t } = useTranslation();
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics/empty-chart-bar" });
|
||||
// store hooks
|
||||
const { selectedDuration, selectedProjects, selectedCycle, selectedModule, isPeekView, isEpic } = useAnalytics();
|
||||
const { workspaceStates } = useProjectState();
|
||||
|
|
@ -232,11 +230,11 @@ const PriorityChart = observer((props: Props) => {
|
|||
/>
|
||||
</>
|
||||
) : (
|
||||
<AnalyticsEmptyState
|
||||
title={t("workspace_analytics.empty_state.customized_insights.title")}
|
||||
description={t("workspace_analytics.empty_state.customized_insights.description")}
|
||||
className="h-[350px]"
|
||||
assetPath={resolvedPath}
|
||||
<EmptyStateCompact
|
||||
assetKey="unknown"
|
||||
assetClassName="size-20"
|
||||
rootClassName="border border-custom-border-100 px-5 py-10 md:py-20 md:px-20"
|
||||
title={t("workspace.analytics_work_items.title")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import useSWR from "swr";
|
|||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// hooks
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
import { useProjectEstimates } from "@/hooks/store/estimates";
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
// plane web components
|
||||
|
|
@ -13,7 +14,6 @@ import { UpdateEstimateModal } from "@/plane-web/components/estimates";
|
|||
import { SettingsHeading } from "../settings/heading";
|
||||
import { CreateEstimateModal } from "./create/modal";
|
||||
import { DeleteEstimateModal } from "./delete/modal";
|
||||
import { EstimateEmptyScreen } from "./empty-screen";
|
||||
import { EstimateDisableSwitch } from "./estimate-disable-switch";
|
||||
import { EstimateList } from "./estimate-list";
|
||||
import { EstimateLoaderScreen } from "./loader-screen";
|
||||
|
|
@ -76,7 +76,20 @@ export const EstimateRoot: FC<TEstimateRoot> = observer((props) => {
|
|||
/>
|
||||
</div>
|
||||
) : (
|
||||
<EstimateEmptyScreen onButtonClick={() => setIsEstimateCreateModalOpen(true)} />
|
||||
<EmptyStateCompact
|
||||
assetKey="estimate"
|
||||
assetClassName="size-20"
|
||||
title={t("settings.estimates.title")}
|
||||
description={t("settings.estimates.description")}
|
||||
actions={[
|
||||
{
|
||||
label: t("settings.estimates.cta_primary"),
|
||||
onClick: () => setIsEstimateCreateModalOpen(true),
|
||||
},
|
||||
]}
|
||||
align="start"
|
||||
rootClassName="py-20"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* archived estimates section */}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import { Link2 } from "lucide-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
|
||||
export const LinksEmptyState = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="min-h-[110px] flex w-full justify-center py-6 bg-custom-border-100 rounded">
|
||||
<div className="m-auto flex gap-2">
|
||||
<Link2 size={30} className="text-custom-text-400/40 -rotate-45" />
|
||||
<div className="text-custom-text-400 text-sm text-center my-auto">{t("home.quick_links.empty")}</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-center py-10 bg-custom-background-90 w-full">
|
||||
<EmptyStateCompact
|
||||
assetKey="link"
|
||||
assetClassName="w-20 h-20"
|
||||
title={t("workspace.home_widget_quick_links.title")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,41 +1,40 @@
|
|||
import { History } from "lucide-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { PageIcon, ProjectIcon, WorkItemsIcon } from "@plane/propel/icons";
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
import type { CompactAssetType } from "@plane/propel/empty-state";
|
||||
|
||||
const getDisplayContent = (type: string) => {
|
||||
const getDisplayContent = (type: string): { assetKey: CompactAssetType; text: string } => {
|
||||
switch (type) {
|
||||
case "project":
|
||||
return {
|
||||
icon: <ProjectIcon height={30} width={30} className="text-custom-text-400/40" />,
|
||||
assetKey: "project",
|
||||
text: "home.recents.empty.project",
|
||||
};
|
||||
case "page":
|
||||
return {
|
||||
icon: <PageIcon height={30} width={30} className="text-custom-text-400/40" />,
|
||||
assetKey: "note",
|
||||
text: "home.recents.empty.page",
|
||||
};
|
||||
case "issue":
|
||||
return {
|
||||
icon: <WorkItemsIcon className="text-custom-text-400/40 w-[30px] h-[30px]" />,
|
||||
assetKey: "work-item",
|
||||
text: "home.recents.empty.issue",
|
||||
};
|
||||
default:
|
||||
return {
|
||||
icon: <History height={30} width={30} className="text-custom-text-400/40" />,
|
||||
assetKey: "work-item",
|
||||
text: "home.recents.empty.default",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const RecentsEmptyState = ({ type }: { type: string }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { icon, text } = getDisplayContent(type);
|
||||
const { assetKey, text } = getDisplayContent(type);
|
||||
|
||||
return (
|
||||
<div className="min-h-[120px] flex w-full justify-center py-6 bg-custom-border-100 rounded">
|
||||
<div className="m-auto flex gap-2">
|
||||
{icon} <div className="text-custom-text-400 text-sm text-center my-auto">{t(text)}</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-center py-10 bg-custom-background-90 w-full">
|
||||
<EmptyStateCompact assetKey={assetKey} assetClassName="size-20" title={t(text)} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
// plane ui
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { RecentStickyIcon } from "@plane/propel/icons";
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
|
||||
export const StickiesEmptyState = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="min-h-[110px] flex w-full justify-center py-6 bg-custom-border-100 rounded">
|
||||
<div className="m-auto flex gap-2">
|
||||
<RecentStickyIcon className="h-[30px] w-[30px] text-custom-text-400/40" />
|
||||
<div className="text-custom-text-400 text-sm text-center my-auto">{t("stickies.empty_state.simple")}</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-center py-10 bg-custom-background-90 w-full">
|
||||
<EmptyStateCompact assetKey="note" assetClassName="size-20" title={t("stickies.empty_state.simple")} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ import { observer } from "mobx-react";
|
|||
import { PanelLeft } from "lucide-react";
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
import { IntakeIcon } from "@plane/propel/icons";
|
||||
import { EInboxIssueCurrentTab } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { SimpleEmptyState } from "@/components/empty-state/simple-empty-state-root";
|
||||
import { InboxContentRoot } from "@/components/inbox/content";
|
||||
import { InboxSidebar } from "@/components/inbox/sidebar";
|
||||
import { InboxLayoutLoader } from "@/components/ui/loader/layouts/project-inbox/inbox-layout-loader";
|
||||
|
|
@ -101,9 +101,7 @@ export const InboxIssueRoot: FC<TInboxIssueRoot> = observer((props) => {
|
|||
inboxIssueId={inboxIssueId.toString()}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full relative flex justify-center items-center">
|
||||
<SimpleEmptyState title={t("inbox_issue.empty_state.detail.title")} assetPath={resolvedPath} />
|
||||
</div>
|
||||
<EmptyStateCompact assetKey="intake" title={t("project.intake_main.title")} assetClassName="size-20" />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -4,20 +4,19 @@ import type { FC } from "react";
|
|||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateDetailed } from "@plane/propel/empty-state";
|
||||
import type { TInboxIssueCurrentTab } from "@plane/types";
|
||||
import { EInboxIssueCurrentTab } from "@plane/types";
|
||||
// plane imports
|
||||
import { Header, Loader, EHeaderVariant } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { SimpleEmptyState } from "@/components/empty-state/simple-empty-state-root";
|
||||
import { InboxSidebarLoader } from "@/components/ui/loader/layouts/project-inbox/inbox-sidebar-loader";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import { useProjectInbox } from "@/hooks/store/use-project-inbox";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { useIntersectionObserver } from "@/hooks/use-intersection-observer";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
// local imports
|
||||
import { FiltersRoot } from "../inbox-filter";
|
||||
import { InboxIssueAppliedFilters } from "../inbox-filter/applied-filters/root";
|
||||
|
|
@ -62,11 +61,6 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
|
|||
getAppliedFiltersCount,
|
||||
} = useProjectInbox();
|
||||
// derived values
|
||||
const sidebarAssetPath = useResolvedAssetPath({ basePath: "/empty-state/intake/intake-issue" });
|
||||
const sidebarFilterAssetPath = useResolvedAssetPath({
|
||||
basePath: "/empty-state/intake/filter-issue",
|
||||
});
|
||||
|
||||
const fetchNextPages = useCallback(() => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
fetchInboxPaginationIssues(workspaceSlug.toString(), projectId.toString());
|
||||
|
|
@ -141,22 +135,33 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
|
|||
) : (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
{getAppliedFiltersCount > 0 ? (
|
||||
<SimpleEmptyState
|
||||
title={t("inbox_issue.empty_state.sidebar_filter.title")}
|
||||
description={t("inbox_issue.empty_state.sidebar_filter.description")}
|
||||
assetPath={sidebarFilterAssetPath}
|
||||
<EmptyStateDetailed
|
||||
assetKey="search"
|
||||
title={t("common.search.title")}
|
||||
description={t("common.search.description")}
|
||||
assetClassName="size-20"
|
||||
/>
|
||||
) : currentTab === EInboxIssueCurrentTab.OPEN ? (
|
||||
<SimpleEmptyState
|
||||
title={t("inbox_issue.empty_state.sidebar_open_tab.title")}
|
||||
description={t("inbox_issue.empty_state.sidebar_open_tab.description")}
|
||||
assetPath={sidebarAssetPath}
|
||||
<EmptyStateDetailed
|
||||
assetKey="inbox"
|
||||
title={t("project.intake_sidebar.title")}
|
||||
description={t("project.intake_sidebar.description")}
|
||||
assetClassName="size-20"
|
||||
actions={[
|
||||
{
|
||||
label: t("project.intake_sidebar.cta_primary"),
|
||||
onClick: () => router.push(`/${workspaceSlug}/projects/${projectId}/intake`),
|
||||
variant: "primary",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<SimpleEmptyState
|
||||
title={t("inbox_issue.empty_state.sidebar_closed_tab.title")}
|
||||
description={t("inbox_issue.empty_state.sidebar_closed_tab.description")}
|
||||
assetPath={sidebarAssetPath}
|
||||
// TODO: Add translation
|
||||
<EmptyStateDetailed
|
||||
assetKey="inbox"
|
||||
title="No request closed yet"
|
||||
description="All the work items whether accepted or declined can be found here."
|
||||
assetClassName="size-20"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,19 +7,18 @@ import { useParams } from "next/navigation";
|
|||
// plane imports
|
||||
import { EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateDetailed } from "@plane/propel/empty-state";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import type { ISearchIssueResponse } from "@plane/types";
|
||||
import { EIssuesStoreType, EUserProjectRoles } from "@plane/types";
|
||||
// components
|
||||
import { ExistingIssuesListModal } from "@/components/core/modals/existing-issues-list-modal";
|
||||
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useCommandPalette } from "@/hooks/store/use-command-palette";
|
||||
import { useCycle } from "@/hooks/store/use-cycle";
|
||||
import { useIssues } from "@/hooks/store/use-issues";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import { useWorkItemFilterInstance } from "@/hooks/store/work-item-filters/use-work-item-filter-instance";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
export const CycleEmptyState: React.FC = observer(() => {
|
||||
// router
|
||||
|
|
@ -33,31 +32,18 @@ export const CycleEmptyState: React.FC = observer(() => {
|
|||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { getCycleById } = useCycle();
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
|
||||
const { issues } = useIssues(EIssuesStoreType.CYCLE);
|
||||
const { toggleCreateIssueModal } = useCommandPalette();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const cycleWorkItemFilter = cycleId ? useWorkItemFilterInstance(EIssuesStoreType.CYCLE, cycleId) : undefined;
|
||||
const cycleDetails = cycleId ? getCycleById(cycleId) : undefined;
|
||||
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
|
||||
const isCompletedCycleSnapshotAvailable = !isEmpty(cycleDetails?.progress_snapshot ?? {});
|
||||
const isCompletedAndEmpty = isCompletedCycleSnapshotAvailable || cycleDetails?.status?.toLowerCase() === "completed";
|
||||
const additionalPath = activeLayout ?? "list";
|
||||
const canPerformEmptyStateActions = allowPermissions(
|
||||
[EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER],
|
||||
EUserPermissionsLevel.PROJECT
|
||||
);
|
||||
const emptyFilterResolvedPath = useResolvedAssetPath({
|
||||
basePath: "/empty-state/empty-filters/",
|
||||
additionalPath: additionalPath,
|
||||
});
|
||||
const noIssueResolvedPath = useResolvedAssetPath({
|
||||
basePath: "/empty-state/cycle-issues/",
|
||||
additionalPath: additionalPath,
|
||||
});
|
||||
const completedNoIssuesResolvedPath = useResolvedAssetPath({
|
||||
basePath: "/empty-state/cycle/completed-no-issues",
|
||||
});
|
||||
|
||||
const handleAddIssuesToCycle = async (data: ISearchIssueResponse[]) => {
|
||||
if (!workspaceSlug || !projectId || !cycleId) return;
|
||||
|
|
@ -94,39 +80,50 @@ export const CycleEmptyState: React.FC = observer(() => {
|
|||
/>
|
||||
<div className="grid h-full w-full place-items-center">
|
||||
{isCompletedAndEmpty ? (
|
||||
<DetailedEmptyState
|
||||
// TODO: Empty state ux copy needs to be updated
|
||||
<EmptyStateDetailed
|
||||
assetKey="work-item"
|
||||
title={t("project_cycles.empty_state.completed_no_issues.title")}
|
||||
description={t("project_cycles.empty_state.completed_no_issues.description")}
|
||||
assetPath={completedNoIssuesResolvedPath}
|
||||
/>
|
||||
) : cycleWorkItemFilter?.hasActiveFilters ? (
|
||||
<DetailedEmptyState
|
||||
title={t("project_issues.empty_state.issues_empty_filter.title")}
|
||||
assetPath={emptyFilterResolvedPath}
|
||||
secondaryButton={{
|
||||
text: t("project_issues.empty_state.issues_empty_filter.secondary_button.text"),
|
||||
onClick: cycleWorkItemFilter?.clearFilters,
|
||||
disabled: !canPerformEmptyStateActions || !cycleWorkItemFilter,
|
||||
}}
|
||||
<EmptyStateDetailed
|
||||
assetKey="search"
|
||||
title={t("common.search.title")}
|
||||
description={t("common.search.description")}
|
||||
actions={[
|
||||
{
|
||||
label: t("common.search.cta_secondary"),
|
||||
onClick: cycleWorkItemFilter?.clearFilters,
|
||||
disabled: !canPerformEmptyStateActions || !cycleWorkItemFilter,
|
||||
variant: "outline-primary",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<DetailedEmptyState
|
||||
title={t("project_cycles.empty_state.no_issues.title")}
|
||||
description={t("project_cycles.empty_state.no_issues.description")}
|
||||
assetPath={noIssueResolvedPath}
|
||||
primaryButton={{
|
||||
text: t("project_cycles.empty_state.no_issues.primary_button.text"),
|
||||
onClick: () => {
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.CYCLE });
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.CYCLE);
|
||||
<EmptyStateDetailed
|
||||
assetKey="work-item"
|
||||
title={t("project.cycle_work_items.title")}
|
||||
description={t("project.cycle_work_items.description")}
|
||||
actions={[
|
||||
{
|
||||
label: t("project.cycle_work_items.cta_primary"),
|
||||
onClick: () => {
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.CYCLE });
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.CYCLE);
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
variant: "primary",
|
||||
"data-ph-element": WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.CYCLE,
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
}}
|
||||
secondaryButton={{
|
||||
text: t("project_cycles.empty_state.no_issues.secondary_button.text"),
|
||||
onClick: () => setCycleIssuesListModal(true),
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
}}
|
||||
{
|
||||
label: t("project.cycle_work_items.cta_secondary"),
|
||||
onClick: () => setCycleIssuesListModal(true),
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
variant: "outline-primary",
|
||||
"data-ph-element": WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.CYCLE,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,16 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateDetailed } from "@plane/propel/empty-state";
|
||||
import { EIssuesStoreType, EUserWorkspaceRoles } from "@plane/types";
|
||||
// components
|
||||
import { ComicBoxButton } from "@/components/empty-state/comic-box-button";
|
||||
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
// hooks
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useCommandPalette } from "@/hooks/store/use-command-palette";
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
export const GlobalViewEmptyState: React.FC = observer(() => {
|
||||
const { globalViewId } = useParams();
|
||||
// plane imports
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
|
|
@ -27,56 +22,46 @@ export const GlobalViewEmptyState: React.FC = observer(() => {
|
|||
[EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
const isDefaultView = ["all-issues", "assigned", "created", "subscribed"].includes(globalViewId?.toString() ?? "");
|
||||
const currentView = isDefaultView && globalViewId ? globalViewId : "custom-view";
|
||||
const resolvedCurrentView = currentView?.toString();
|
||||
const noProjectResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/projects" });
|
||||
const globalViewsResolvedPath = useResolvedAssetPath({
|
||||
basePath: "/empty-state/all-issues/",
|
||||
additionalPath: resolvedCurrentView,
|
||||
});
|
||||
|
||||
if (workspaceProjectIds?.length === 0) {
|
||||
return (
|
||||
<DetailedEmptyState
|
||||
size="sm"
|
||||
<EmptyStateDetailed
|
||||
title={t("workspace_projects.empty_state.no_projects.title")}
|
||||
description={t("workspace_projects.empty_state.no_projects.description")}
|
||||
assetPath={noProjectResolvedPath}
|
||||
customPrimaryButton={
|
||||
<ComicBoxButton
|
||||
label={t("workspace_projects.empty_state.no_projects.primary_button.text")}
|
||||
title={t("workspace_projects.empty_state.no_projects.primary_button.comic.title")}
|
||||
description={t("workspace_projects.empty_state.no_projects.primary_button.comic.description")}
|
||||
onClick={() => {
|
||||
assetKey="project"
|
||||
assetClassName="size-40"
|
||||
actions={[
|
||||
{
|
||||
label: t("workspace_projects.empty_state.no_projects.primary_button.text"),
|
||||
onClick: () => {
|
||||
toggleCreateProjectModal(true);
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.GLOBAL_VIEW });
|
||||
}}
|
||||
disabled={!hasMemberLevelPermission}
|
||||
/>
|
||||
}
|
||||
},
|
||||
disabled: !hasMemberLevelPermission,
|
||||
variant: "primary",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DetailedEmptyState
|
||||
size="sm"
|
||||
title={t(`workspace_views.empty_state.${resolvedCurrentView}.title`)}
|
||||
description={t(`workspace_views.empty_state.${resolvedCurrentView}.description`)}
|
||||
assetPath={globalViewsResolvedPath}
|
||||
primaryButton={
|
||||
["subscribed", "custom-view"].includes(resolvedCurrentView) === false
|
||||
? {
|
||||
text: t(`workspace_views.empty_state.${resolvedCurrentView}.primary_button.text`),
|
||||
onClick: () => {
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.GLOBAL_VIEW });
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
|
||||
},
|
||||
disabled: !hasMemberLevelPermission,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
<EmptyStateDetailed
|
||||
title={t(`workspace.views.title`)}
|
||||
description={t(`workspace.views.description`)}
|
||||
assetKey="project"
|
||||
assetClassName="size-40"
|
||||
actions={[
|
||||
{
|
||||
label: t(`workspace.views.cta_primary`),
|
||||
onClick: () => {
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.GLOBAL_VIEW });
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
|
||||
},
|
||||
disabled: !hasMemberLevelPermission,
|
||||
variant: "primary",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,19 +6,18 @@ import { useParams } from "next/navigation";
|
|||
// plane imports
|
||||
import { EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateDetailed } from "@plane/propel/empty-state";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import type { ISearchIssueResponse } from "@plane/types";
|
||||
import { EIssuesStoreType, EUserProjectRoles } from "@plane/types";
|
||||
// components
|
||||
import { ExistingIssuesListModal } from "@/components/core/modals/existing-issues-list-modal";
|
||||
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useCommandPalette } from "@/hooks/store/use-command-palette";
|
||||
import { useIssues } from "@/hooks/store/use-issues";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import { useWorkItemFilterInstance } from "@/hooks/store/work-item-filters/use-work-item-filter-instance";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
export const ModuleEmptyState: React.FC = observer(() => {
|
||||
// router
|
||||
|
|
@ -31,25 +30,15 @@ export const ModuleEmptyState: React.FC = observer(() => {
|
|||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE);
|
||||
const { issues } = useIssues(EIssuesStoreType.MODULE);
|
||||
const { toggleCreateIssueModal } = useCommandPalette();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const moduleWorkItemFilter = moduleId ? useWorkItemFilterInstance(EIssuesStoreType.MODULE, moduleId) : undefined;
|
||||
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
|
||||
const additionalPath = activeLayout ?? "list";
|
||||
const canPerformEmptyStateActions = allowPermissions(
|
||||
[EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER],
|
||||
EUserPermissionsLevel.PROJECT
|
||||
);
|
||||
const emptyFilterResolvedPath = useResolvedAssetPath({
|
||||
basePath: "/empty-state/empty-filters/",
|
||||
additionalPath: additionalPath,
|
||||
});
|
||||
const moduleIssuesResolvedPath = useResolvedAssetPath({
|
||||
basePath: "/empty-state/module-issues/",
|
||||
additionalPath: additionalPath,
|
||||
});
|
||||
|
||||
const handleAddIssuesToModule = async (data: ISearchIssueResponse[]) => {
|
||||
if (!workspaceSlug || !projectId || !moduleId) return;
|
||||
|
|
@ -85,33 +74,41 @@ export const ModuleEmptyState: React.FC = observer(() => {
|
|||
/>
|
||||
<div className="grid h-full w-full place-items-center">
|
||||
{moduleWorkItemFilter?.hasActiveFilters ? (
|
||||
<DetailedEmptyState
|
||||
title={t("project_issues.empty_state.issues_empty_filter.title")}
|
||||
assetPath={emptyFilterResolvedPath}
|
||||
secondaryButton={{
|
||||
text: t("project_issues.empty_state.issues_empty_filter.secondary_button.text"),
|
||||
onClick: moduleWorkItemFilter?.clearFilters,
|
||||
disabled: !canPerformEmptyStateActions || !moduleWorkItemFilter,
|
||||
}}
|
||||
<EmptyStateDetailed
|
||||
assetKey="search"
|
||||
title={t("common.search.title")}
|
||||
description={t("common.search.description")}
|
||||
actions={[
|
||||
{
|
||||
label: t("common.search.cta_secondary"),
|
||||
onClick: moduleWorkItemFilter?.clearFilters,
|
||||
disabled: !canPerformEmptyStateActions || !moduleWorkItemFilter,
|
||||
variant: "outline-primary",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<DetailedEmptyState
|
||||
title={t("project_module.empty_state.no_issues.title")}
|
||||
description={t("project_module.empty_state.no_issues.description")}
|
||||
assetPath={moduleIssuesResolvedPath}
|
||||
primaryButton={{
|
||||
text: t("project_module.empty_state.no_issues.primary_button.text"),
|
||||
onClick: () => {
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.MODULE });
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.MODULE);
|
||||
<EmptyStateDetailed
|
||||
assetKey="work-item"
|
||||
title={t("project.module_work_items.title")}
|
||||
description={t("project.module_work_items.description")}
|
||||
actions={[
|
||||
{
|
||||
label: t("project.module_work_items.cta_primary"),
|
||||
onClick: () => {
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.MODULE });
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.MODULE);
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
variant: "primary",
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
}}
|
||||
secondaryButton={{
|
||||
text: t("project_module.empty_state.no_issues.secondary_button.text"),
|
||||
onClick: () => setModuleIssuesListModal(true),
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
}}
|
||||
{
|
||||
label: t("project.module_work_items.cta_secondary"),
|
||||
onClick: () => setModuleIssuesListModal(true),
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
variant: "outline-primary",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,17 +3,14 @@ import { useParams } from "next/navigation";
|
|||
// plane imports
|
||||
import { EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateDetailed } from "@plane/propel/empty-state";
|
||||
import { EIssuesStoreType, EUserProjectRoles } from "@plane/types";
|
||||
// components
|
||||
import { ComicBoxButton } from "@/components/empty-state/comic-box-button";
|
||||
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useCommandPalette } from "@/hooks/store/use-command-palette";
|
||||
import { useIssues } from "@/hooks/store/use-issues";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import { useWorkItemFilterInstance } from "@/hooks/store/work-item-filters/use-work-item-filter-instance";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
export const ProjectEmptyState: React.FC = observer(() => {
|
||||
// router
|
||||
|
|
@ -23,53 +20,47 @@ export const ProjectEmptyState: React.FC = observer(() => {
|
|||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { toggleCreateIssueModal } = useCommandPalette();
|
||||
const { issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const projectWorkItemFilter = projectId ? useWorkItemFilterInstance(EIssuesStoreType.PROJECT, projectId) : undefined;
|
||||
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
|
||||
const additionalPath = projectWorkItemFilter?.hasActiveFilters ? (activeLayout ?? "list") : undefined;
|
||||
|
||||
const canPerformEmptyStateActions = allowPermissions(
|
||||
[EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER],
|
||||
EUserPermissionsLevel.PROJECT
|
||||
);
|
||||
const emptyFilterResolvedPath = useResolvedAssetPath({
|
||||
basePath: "/empty-state/empty-filters/",
|
||||
additionalPath: additionalPath,
|
||||
});
|
||||
const projectIssuesResolvedPath = useResolvedAssetPath({
|
||||
basePath: "/empty-state/onboarding/issues",
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative h-full w-full overflow-y-auto">
|
||||
{projectWorkItemFilter?.hasActiveFilters ? (
|
||||
<DetailedEmptyState
|
||||
title={t("project_issues.empty_state.issues_empty_filter.title")}
|
||||
assetPath={emptyFilterResolvedPath}
|
||||
secondaryButton={{
|
||||
text: t("project_issues.empty_state.issues_empty_filter.secondary_button.text"),
|
||||
onClick: projectWorkItemFilter?.clearFilters,
|
||||
disabled: !canPerformEmptyStateActions || !projectWorkItemFilter,
|
||||
}}
|
||||
<EmptyStateDetailed
|
||||
assetKey="search"
|
||||
title={t("common.search.title")}
|
||||
description={t("common.search.description")}
|
||||
actions={[
|
||||
{
|
||||
label: t("project_issues.empty_state.issues_empty_filter.secondary_button.text"),
|
||||
onClick: projectWorkItemFilter?.clearFilters,
|
||||
disabled: !canPerformEmptyStateActions || !projectWorkItemFilter,
|
||||
variant: "outline-primary",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<DetailedEmptyState
|
||||
title={t("project_issues.empty_state.no_issues.title")}
|
||||
description={t("project_issues.empty_state.no_issues.description")}
|
||||
assetPath={projectIssuesResolvedPath}
|
||||
customPrimaryButton={
|
||||
<ComicBoxButton
|
||||
label={t("project_issues.empty_state.no_issues.primary_button.text")}
|
||||
title={t("project_issues.empty_state.no_issues.primary_button.comic.title")}
|
||||
description={t("project_issues.empty_state.no_issues.primary_button.comic.description")}
|
||||
onClick={() => {
|
||||
<EmptyStateDetailed
|
||||
assetKey="work-item"
|
||||
title={t("project.work_items.title")}
|
||||
description={t("project.work_items.description")}
|
||||
actions={[
|
||||
{
|
||||
label: t("project.work_items.cta_primary"),
|
||||
onClick: () => {
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.WORK_ITEMS });
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
|
||||
}}
|
||||
disabled={!canPerformEmptyStateActions}
|
||||
/>
|
||||
}
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
variant: "primary",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// components
|
||||
import { EUserPermissions, EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { EmptyStateDetailed } from "@plane/propel/empty-state";
|
||||
import { EIssuesStoreType } from "@plane/types";
|
||||
import { EmptyState } from "@/components/common/empty-state";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useCommandPalette } from "@/hooks/store/use-command-palette";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
// assets
|
||||
import emptyIssue from "@/public/empty-state/issue.svg";
|
||||
|
||||
export const ProjectViewEmptyState: React.FC = observer(() => {
|
||||
// store hooks
|
||||
|
|
@ -23,24 +20,22 @@ export const ProjectViewEmptyState: React.FC = observer(() => {
|
|||
);
|
||||
|
||||
return (
|
||||
<div className="grid h-full w-full place-items-center">
|
||||
<EmptyState
|
||||
title="View work items will appear here"
|
||||
description="Work items help you track individual pieces of work. With work items, keep track of what's going on, who is working on it, and what's done."
|
||||
image={emptyIssue}
|
||||
primaryButton={
|
||||
isCreatingIssueAllowed
|
||||
? {
|
||||
text: "New work item",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => {
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.PROJECT_VIEW });
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT_VIEW);
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
// TODO: Add translation
|
||||
<EmptyStateDetailed
|
||||
assetKey="work-item"
|
||||
title="View work items will appear here"
|
||||
description="Work items help you track individual pieces of work. With work items, keep track of what's going on, who is working on it, and what's done."
|
||||
actions={[
|
||||
{
|
||||
label: "New work item",
|
||||
onClick: () => {
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON.PROJECT_VIEW });
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT_VIEW);
|
||||
},
|
||||
disabled: !isCreatingIssueAllowed,
|
||||
variant: "primary",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,12 +6,11 @@ import { Fragment, useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateDetailed } from "@plane/propel/empty-state";
|
||||
import { EIssuesStoreType, EUserWorkspaceRoles } from "@plane/types";
|
||||
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
import { CreateUpdateIssueModal } from "@/components/issues/issue-modal/modal";
|
||||
// constants
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
export const WorkspaceDraftEmptyState: FC = observer(() => {
|
||||
// state
|
||||
|
|
@ -24,7 +23,6 @@ export const WorkspaceDraftEmptyState: FC = observer(() => {
|
|||
[EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/cycles" });
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
|
|
@ -35,17 +33,21 @@ export const WorkspaceDraftEmptyState: FC = observer(() => {
|
|||
isDraft
|
||||
/>
|
||||
<div className="relative h-full w-full overflow-y-auto">
|
||||
<DetailedEmptyState
|
||||
title={t("workspace_draft_issues.empty_state.title")}
|
||||
description={t("workspace_draft_issues.empty_state.description")}
|
||||
assetPath={resolvedPath}
|
||||
primaryButton={{
|
||||
text: t("workspace_draft_issues.empty_state.primary_button.text"),
|
||||
onClick: () => {
|
||||
setIsDraftIssueModalOpen(true);
|
||||
<EmptyStateDetailed
|
||||
title={t("workspace.drafts.title")}
|
||||
description={t("workspace.drafts.description")}
|
||||
assetKey="draft"
|
||||
assetClassName="size-20"
|
||||
actions={[
|
||||
{
|
||||
label: t("workspace.drafts.cta_primary"),
|
||||
onClick: () => {
|
||||
setIsDraftIssueModalOpen(true);
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
variant: "primary",
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
}}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
|
|
|
|||
|
|
@ -7,11 +7,10 @@ import useSWR from "swr";
|
|||
// plane imports
|
||||
import { EUserPermissionsLevel, EDraftIssuePaginationType, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateDetailed } from "@plane/propel/empty-state";
|
||||
import { EUserWorkspaceRoles } from "@plane/types";
|
||||
// components
|
||||
import { cn } from "@plane/utils";
|
||||
import { ComicBoxButton } from "@/components/empty-state/comic-box-button";
|
||||
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// constants
|
||||
|
||||
|
|
@ -70,23 +69,22 @@ export const WorkspaceDraftIssuesRoot: FC<TWorkspaceDraftIssuesRoot> = observer(
|
|||
|
||||
if (workspaceProjectIds?.length === 0)
|
||||
return (
|
||||
<DetailedEmptyState
|
||||
size="sm"
|
||||
<EmptyStateDetailed
|
||||
title={t("workspace_projects.empty_state.no_projects.title")}
|
||||
description={t("workspace_projects.empty_state.no_projects.description")}
|
||||
assetPath={noProjectResolvedPath}
|
||||
customPrimaryButton={
|
||||
<ComicBoxButton
|
||||
label={t("workspace_projects.empty_state.no_projects.primary_button.text")}
|
||||
title={t("workspace_projects.empty_state.no_projects.primary_button.comic.title")}
|
||||
description={t("workspace_projects.empty_state.no_projects.primary_button.comic.description")}
|
||||
onClick={() => {
|
||||
assetKey="project"
|
||||
assetClassName="size-40"
|
||||
actions={[
|
||||
{
|
||||
label: t("workspace_projects.empty_state.no_projects.primary_button.text"),
|
||||
onClick: () => {
|
||||
toggleCreateProjectModal(true);
|
||||
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON });
|
||||
}}
|
||||
disabled={!hasMemberLevelPermission}
|
||||
/>
|
||||
}
|
||||
},
|
||||
disabled: !hasMemberLevelPermission,
|
||||
variant: "primary",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import { useParams } from "next/navigation";
|
|||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel, PROJECT_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
import type { IIssueLabel } from "@plane/types";
|
||||
import { Loader } from "@plane/ui";
|
||||
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
import type { TLabelOperationsCallbacks } from "@/components/labels";
|
||||
import {
|
||||
CreateUpdateLabelInline,
|
||||
|
|
@ -20,7 +20,6 @@ import {
|
|||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useLabel } from "@/hooks/store/use-label";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
// local imports
|
||||
import { SettingsHeading } from "../settings/heading";
|
||||
|
||||
|
|
@ -40,7 +39,6 @@ export const ProjectSettingsLabelList: React.FC = observer(() => {
|
|||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const isEditable = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/project-settings/labels" });
|
||||
const labelOperationsCallbacks: TLabelOperationsCallbacks = {
|
||||
createLabel: (data: Partial<IIssueLabel>) => createLabel(workspaceSlug?.toString(), projectId?.toString(), data),
|
||||
updateLabel: (labelId: string, data: Partial<IIssueLabel>) =>
|
||||
|
|
@ -111,24 +109,25 @@ export const ProjectSettingsLabelList: React.FC = observer(() => {
|
|||
)}
|
||||
{projectLabels ? (
|
||||
projectLabels.length === 0 && !showLabelForm ? (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<DetailedEmptyState
|
||||
title={""}
|
||||
description={""}
|
||||
primaryButton={{
|
||||
text: "Create your first label",
|
||||
<EmptyStateCompact
|
||||
assetKey="label"
|
||||
assetClassName="size-20"
|
||||
title={t("settings.labels.title")}
|
||||
description={t("settings.labels.description")}
|
||||
actions={[
|
||||
{
|
||||
label: t("settings.labels.cta_primary"),
|
||||
onClick: () => {
|
||||
newLabel();
|
||||
captureClick({
|
||||
elementName: PROJECT_SETTINGS_TRACKER_ELEMENTS.LABELS_EMPTY_STATE_CREATE_BUTTON,
|
||||
});
|
||||
},
|
||||
}}
|
||||
assetPath={resolvedPath}
|
||||
className="w-full !px-0 !py-0"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
},
|
||||
]}
|
||||
align="start"
|
||||
rootClassName="py-20"
|
||||
/>
|
||||
) : (
|
||||
projectLabelsTree && (
|
||||
<div className="mt-3">
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
import { useParams, useSearchParams } from "next/navigation";
|
||||
// components
|
||||
import { EUserPermissionsLevel, MODULE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateDetailed } from "@plane/propel/empty-state";
|
||||
import { EUserProjectRoles } from "@plane/types";
|
||||
import { ContentWrapper, Row, ERowVariant } from "@plane/ui";
|
||||
import { ListLayout } from "@/components/core/list";
|
||||
import { ComicBoxButton } from "@/components/empty-state/comic-box-button";
|
||||
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
import { ModuleCardItem, ModuleListItem, ModulePeekOverview, ModulesListGanttChartView } from "@/components/modules";
|
||||
import { CycleModuleBoardLayoutLoader } from "@/components/ui/loader/cycle-module-board-loader";
|
||||
import { CycleModuleListLayoutLoader } from "@/components/ui/loader/cycle-module-list-loader";
|
||||
|
|
@ -18,9 +16,6 @@ import { useCommandPalette } from "@/hooks/store/use-command-palette";
|
|||
import { useModule } from "@/hooks/store/use-module";
|
||||
import { useModuleFilter } from "@/hooks/store/use-module-filter";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import AllFiltersImage from "@/public/empty-state/module/all-filters.svg";
|
||||
import NameFilterImage from "@/public/empty-state/module/name-filter.svg";
|
||||
|
||||
export const ModulesListView: React.FC = observer(() => {
|
||||
// router
|
||||
|
|
@ -32,7 +27,7 @@ export const ModulesListView: React.FC = observer(() => {
|
|||
// store hooks
|
||||
const { toggleCreateModuleModal } = useCommandPalette();
|
||||
const { getProjectModuleIds, getFilteredModuleIds, loader } = useModule();
|
||||
const { currentProjectDisplayFilters: displayFilters, searchQuery } = useModuleFilter();
|
||||
const { currentProjectDisplayFilters: displayFilters } = useModuleFilter();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const projectModuleIds = projectId ? getProjectModuleIds(projectId.toString()) : undefined;
|
||||
|
|
@ -41,9 +36,6 @@ export const ModulesListView: React.FC = observer(() => {
|
|||
[EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER],
|
||||
EUserPermissionsLevel.PROJECT
|
||||
);
|
||||
const generalViewResolvedPath = useResolvedAssetPath({
|
||||
basePath: "/empty-state/onboarding/modules",
|
||||
});
|
||||
|
||||
if (loader || !projectModuleIds || !filteredModuleIds)
|
||||
return (
|
||||
|
|
@ -56,42 +48,29 @@ export const ModulesListView: React.FC = observer(() => {
|
|||
|
||||
if (projectModuleIds.length === 0)
|
||||
return (
|
||||
<DetailedEmptyState
|
||||
title={t("project_module.empty_state.general.title")}
|
||||
description={t("project_module.empty_state.general.description")}
|
||||
assetPath={generalViewResolvedPath}
|
||||
customPrimaryButton={
|
||||
<ComicBoxButton
|
||||
label={t("project_module.empty_state.general.primary_button.text")}
|
||||
title={t("project_module.empty_state.general.primary_button.comic.title")}
|
||||
description={t("project_module.empty_state.general.primary_button.comic.description")}
|
||||
data-ph-element={MODULE_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON}
|
||||
onClick={() => {
|
||||
toggleCreateModuleModal(true);
|
||||
}}
|
||||
disabled={!canPerformEmptyStateActions}
|
||||
/>
|
||||
}
|
||||
<EmptyStateDetailed
|
||||
assetKey="module"
|
||||
title={t("project.modules.title")}
|
||||
description={t("project.modules.description")}
|
||||
actions={[
|
||||
{
|
||||
label: t("project.modules.cta_primary"),
|
||||
onClick: () => toggleCreateModuleModal(true),
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
variant: "primary",
|
||||
"data-ph-element": MODULE_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
if (filteredModuleIds.length === 0)
|
||||
return (
|
||||
<div className="grid h-full w-full place-items-center">
|
||||
<div className="text-center">
|
||||
<Image
|
||||
src={searchQuery.trim() === "" ? AllFiltersImage : NameFilterImage}
|
||||
className="mx-auto h-36 w-36 sm:h-48 sm:w-48"
|
||||
alt="No matching modules"
|
||||
/>
|
||||
<h5 className="mb-1 mt-7 text-xl font-medium">No matching modules</h5>
|
||||
<p className="text-base text-custom-text-400">
|
||||
{searchQuery.trim() === ""
|
||||
? "Remove the filters to see all modules"
|
||||
: "Remove the search criteria to see all modules"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<EmptyStateDetailed
|
||||
assetKey="search"
|
||||
title={t("common.search.title")}
|
||||
description={t("common.search.description")}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
"use client";
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
// plane imports
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import {
|
||||
|
|
@ -11,16 +10,15 @@ import {
|
|||
PROJECT_PAGE_TRACKER_EVENTS,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateDetailed } from "@plane/propel/empty-state";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import type { TPage, TPageNavigationTabs } from "@plane/types";
|
||||
import { EUserProjectRoles } from "@plane/types";
|
||||
// components
|
||||
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
import { PageLoader } from "@/components/pages/loaders/page-loader";
|
||||
import { captureClick, captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
// plane web hooks
|
||||
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
|
||||
|
||||
|
|
@ -52,23 +50,6 @@ export const PagesListMainContent: React.FC<Props> = observer((props) => {
|
|||
[EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER],
|
||||
EUserPermissionsLevel.PROJECT
|
||||
);
|
||||
const generalPageResolvedPath = useResolvedAssetPath({
|
||||
basePath: "/empty-state/onboarding/pages",
|
||||
});
|
||||
const publicPageResolvedPath = useResolvedAssetPath({
|
||||
basePath: "/empty-state/wiki/public",
|
||||
});
|
||||
const privatePageResolvedPath = useResolvedAssetPath({
|
||||
basePath: "/empty-state/wiki/private",
|
||||
});
|
||||
const archivedPageResolvedPath = useResolvedAssetPath({
|
||||
basePath: "/empty-state/wiki/archived",
|
||||
});
|
||||
const resolvedFiltersImage = useResolvedAssetPath({ basePath: "/empty-state/wiki/all-filters", extension: "svg" });
|
||||
const resolvedNameFilterImage = useResolvedAssetPath({
|
||||
basePath: "/empty-state/wiki/name-filter",
|
||||
extension: "svg",
|
||||
});
|
||||
|
||||
// handle page create
|
||||
const handleCreatePage = async () => {
|
||||
|
|
@ -111,80 +92,79 @@ export const PagesListMainContent: React.FC<Props> = observer((props) => {
|
|||
if (!isAnyPageAvailable || pageIds?.length === 0) {
|
||||
if (!isAnyPageAvailable) {
|
||||
return (
|
||||
<DetailedEmptyState
|
||||
title={t("project_page.empty_state.general.title")}
|
||||
description={t("project_page.empty_state.general.description")}
|
||||
assetPath={generalPageResolvedPath}
|
||||
primaryButton={{
|
||||
text: isCreatingPage ? t("creating") : t("project_page.empty_state.general.primary_button.text"),
|
||||
onClick: () => {
|
||||
handleCreatePage();
|
||||
captureClick({ elementName: PROJECT_PAGE_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_BUTTON });
|
||||
<EmptyStateDetailed
|
||||
assetKey="page"
|
||||
title={t("project.pages.title")}
|
||||
description={t("project.pages.description")}
|
||||
actions={[
|
||||
{
|
||||
label: t("project.pages.cta_primary"),
|
||||
onClick: () => {
|
||||
handleCreatePage();
|
||||
captureClick({ elementName: PROJECT_PAGE_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_BUTTON });
|
||||
},
|
||||
variant: "primary",
|
||||
disabled: !canPerformEmptyStateActions || isCreatingPage,
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions || isCreatingPage,
|
||||
}}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (pageType === "public")
|
||||
return (
|
||||
<DetailedEmptyState
|
||||
title={t("project_page.empty_state.public.title")}
|
||||
description={t("project_page.empty_state.public.description")}
|
||||
assetPath={publicPageResolvedPath}
|
||||
primaryButton={{
|
||||
text: isCreatingPage ? t("creating") : t("project_page.empty_state.public.primary_button.text"),
|
||||
onClick: () => {
|
||||
handleCreatePage();
|
||||
captureClick({ elementName: PROJECT_PAGE_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_BUTTON });
|
||||
<EmptyStateDetailed
|
||||
assetKey="page"
|
||||
title={t("project.pages.title")}
|
||||
description={t("project.pages.description")}
|
||||
actions={[
|
||||
{
|
||||
label: t("project.pages.cta_primary"),
|
||||
onClick: () => {
|
||||
handleCreatePage();
|
||||
captureClick({ elementName: PROJECT_PAGE_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_BUTTON });
|
||||
},
|
||||
variant: "primary",
|
||||
disabled: !canPerformEmptyStateActions || isCreatingPage,
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions || isCreatingPage,
|
||||
}}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
if (pageType === "private")
|
||||
return (
|
||||
<DetailedEmptyState
|
||||
title={t("project_page.empty_state.private.title")}
|
||||
description={t("project_page.empty_state.private.description")}
|
||||
assetPath={privatePageResolvedPath}
|
||||
primaryButton={{
|
||||
text: isCreatingPage ? t("creating") : t("project_page.empty_state.private.primary_button.text"),
|
||||
onClick: () => {
|
||||
handleCreatePage();
|
||||
captureClick({ elementName: PROJECT_PAGE_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_BUTTON });
|
||||
<EmptyStateDetailed
|
||||
assetKey="page"
|
||||
title={t("project.pages.title")}
|
||||
description={t("project.pages.description")}
|
||||
actions={[
|
||||
{
|
||||
label: t("project.pages.cta_primary"),
|
||||
onClick: () => {
|
||||
handleCreatePage();
|
||||
captureClick({ elementName: PROJECT_PAGE_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_BUTTON });
|
||||
},
|
||||
variant: "primary",
|
||||
disabled: !canPerformEmptyStateActions || isCreatingPage,
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions || isCreatingPage,
|
||||
}}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
if (pageType === "archived")
|
||||
return (
|
||||
<DetailedEmptyState
|
||||
title={t("project_page.empty_state.archived.title")}
|
||||
description={t("project_page.empty_state.archived.description")}
|
||||
assetPath={archivedPageResolvedPath}
|
||||
<EmptyStateDetailed
|
||||
assetKey="page"
|
||||
title={t("project.archive_pages.title")}
|
||||
description={t("project.archive_pages.description")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
// if no pages match the filter criteria
|
||||
if (filteredPageIds?.length === 0)
|
||||
return (
|
||||
<div className="h-full w-full grid place-items-center">
|
||||
<div className="text-center">
|
||||
<Image
|
||||
src={filters.searchQuery.length > 0 ? resolvedNameFilterImage : resolvedFiltersImage}
|
||||
className="h-36 sm:h-48 w-36 sm:w-48 mx-auto"
|
||||
alt="No matching modules"
|
||||
/>
|
||||
<h5 className="text-xl font-medium mt-7 mb-1">No matching pages</h5>
|
||||
<p className="text-custom-text-400 text-base">
|
||||
{filters.searchQuery.length > 0
|
||||
? "Remove the search criteria to see all pages"
|
||||
: "Remove the filters to see all pages"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<EmptyStateDetailed
|
||||
assetKey="search"
|
||||
title={t("common.search.title")}
|
||||
description={t("common.search.description")}
|
||||
/>
|
||||
);
|
||||
|
||||
return <div className="h-full w-full overflow-hidden">{children}</div>;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { useParams } from "next/navigation";
|
|||
import useSWR from "swr";
|
||||
// ui
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
import { Loader, Card } from "@plane/ui";
|
||||
import { calculateTimeAgo, getFileURL } from "@plane/utils";
|
||||
// components
|
||||
|
|
@ -83,11 +84,7 @@ export const ProfileActivity = observer(() => {
|
|||
))}
|
||||
</div>
|
||||
) : (
|
||||
<ProfileEmptyState
|
||||
title={t("no_data_yet")}
|
||||
description={t("profile.stats.recent_activity.empty")}
|
||||
image={recentActivityEmptyState}
|
||||
/>
|
||||
<EmptyStateCompact title={t("no_data_yet")} assetKey="unknown" assetClassName="size-20" />
|
||||
)
|
||||
) : (
|
||||
<Loader className="space-y-5">
|
||||
|
|
|
|||
|
|
@ -3,13 +3,10 @@
|
|||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { BarChart } from "@plane/propel/charts/bar-chart";
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
import type { IUserProfileData } from "@plane/types";
|
||||
import { Loader, Card } from "@plane/ui";
|
||||
import { capitalizeFirstLetter } from "@plane/utils";
|
||||
// components
|
||||
import { ProfileEmptyState } from "@/components/ui/profile-empty-state";
|
||||
// assets
|
||||
import emptyBarGraph from "@/public/empty-state/empty_bar_graph.svg";
|
||||
|
||||
type Props = {
|
||||
userProfile: IUserProfileData | undefined;
|
||||
|
|
@ -62,13 +59,11 @@ export const ProfilePriorityDistribution: React.FC<Props> = ({ userProfile }) =>
|
|||
barSize={20}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex-grow p-7">
|
||||
<ProfileEmptyState
|
||||
title={t("no_data_yet")}
|
||||
description={t("profile.stats.priority_distribution.empty")}
|
||||
image={emptyBarGraph}
|
||||
/>
|
||||
</div>
|
||||
<EmptyStateCompact
|
||||
assetKey="priority"
|
||||
assetClassName="size-20"
|
||||
title={t("workspace.your_work_by_priority.title")}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -2,13 +2,10 @@
|
|||
import { STATE_GROUPS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { PieChart } from "@plane/propel/charts/pie-chart";
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
import type { IUserProfileData, IUserStateDistribution } from "@plane/types";
|
||||
import { Card } from "@plane/ui";
|
||||
import { capitalizeFirstLetter } from "@plane/utils";
|
||||
// components
|
||||
import { ProfileEmptyState } from "@/components/ui/profile-empty-state";
|
||||
// assets
|
||||
import stateGraph from "@/public/empty-state/state_graph.svg";
|
||||
|
||||
type Props = {
|
||||
stateDistribution: IUserStateDistribution[];
|
||||
|
|
@ -74,10 +71,10 @@ export const ProfileStateDistribution: React.FC<Props> = ({ stateDistribution, u
|
|||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ProfileEmptyState
|
||||
title={t("no_data_yet")}
|
||||
description={t("profile.stats.state_distribution.empty")}
|
||||
image={stateGraph}
|
||||
<EmptyStateCompact
|
||||
assetKey="priority"
|
||||
assetClassName="size-20"
|
||||
title={t("workspace.your_work_by_priority.title")}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel, EUserPermissions, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateDetailed } from "@plane/propel/empty-state";
|
||||
import { ContentWrapper } from "@plane/ui";
|
||||
// components
|
||||
import { ComicBoxButton } from "@/components/empty-state/comic-box-button";
|
||||
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
import { calculateTotalFilters } from "@plane/utils";
|
||||
import { ProjectsLoader } from "@/components/ui/loader/projects-loader";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
|
|
@ -14,7 +13,6 @@ import { useCommandPalette } from "@/hooks/store/use-command-palette";
|
|||
import { useProject } from "@/hooks/store/use-project";
|
||||
import { useProjectFilter } from "@/hooks/store/use-project-filter";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
// local imports
|
||||
import { ProjectCard } from "./card";
|
||||
|
||||
|
|
@ -36,20 +34,9 @@ export const ProjectCardList = observer((props: TProjectCardListProps) => {
|
|||
filteredProjectIds: storeFilteredProjectIds,
|
||||
getProjectById,
|
||||
} = useProject();
|
||||
const { searchQuery, currentWorkspaceDisplayFilters } = useProjectFilter();
|
||||
const { currentWorkspaceDisplayFilters, currentWorkspaceFilters } = useProjectFilter();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
// helper hooks
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/projects" });
|
||||
const resolvedFiltersImage = useResolvedAssetPath({
|
||||
basePath: "/empty-state/project/all-filters",
|
||||
extension: "svg",
|
||||
});
|
||||
const resolvedNameFilterImage = useResolvedAssetPath({
|
||||
basePath: "/empty-state/project/name-filter",
|
||||
extension: "svg",
|
||||
});
|
||||
|
||||
// derived values
|
||||
const workspaceProjectIds = totalProjectIdsProps ?? storeWorkspaceProjectIds;
|
||||
const filteredProjectIds = filteredProjectIdsProps ?? storeFilteredProjectIds;
|
||||
|
|
@ -65,42 +52,48 @@ export const ProjectCardList = observer((props: TProjectCardListProps) => {
|
|||
|
||||
if (workspaceProjectIds?.length === 0 && !currentWorkspaceDisplayFilters?.archived_projects)
|
||||
return (
|
||||
<DetailedEmptyState
|
||||
<EmptyStateDetailed
|
||||
title={t("workspace_projects.empty_state.general.title")}
|
||||
description={t("workspace_projects.empty_state.general.description")}
|
||||
assetPath={resolvedPath}
|
||||
customPrimaryButton={
|
||||
<ComicBoxButton
|
||||
label={t("workspace_projects.empty_state.general.primary_button.text")}
|
||||
title={t("workspace_projects.empty_state.general.primary_button.comic.title")}
|
||||
description={t("workspace_projects.empty_state.general.primary_button.comic.description")}
|
||||
onClick={() => {
|
||||
assetKey="project"
|
||||
assetClassName="size-40"
|
||||
actions={[
|
||||
{
|
||||
label: t("workspace_projects.empty_state.general.primary_button.text"),
|
||||
onClick: () => {
|
||||
toggleCreateProjectModal(true);
|
||||
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON });
|
||||
}}
|
||||
disabled={!canPerformEmptyStateActions}
|
||||
/>
|
||||
}
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
variant: "primary",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
if (filteredProjectIds.length === 0)
|
||||
return (
|
||||
<div className="grid h-full w-full place-items-center">
|
||||
<div className="text-center">
|
||||
<Image
|
||||
src={searchQuery.trim() === "" ? resolvedFiltersImage : resolvedNameFilterImage}
|
||||
className="mx-auto h-36 w-36 sm:h-48 sm:w-48"
|
||||
alt="No matching projects"
|
||||
/>
|
||||
<h5 className="mb-1 mt-7 text-xl font-medium">{t("workspace_projects.empty_state.filter.title")}</h5>
|
||||
<p className="whitespace-pre-line text-base text-custom-text-400">
|
||||
{searchQuery.trim() === ""
|
||||
? t("workspace_projects.empty_state.filter.description")
|
||||
: t("workspace_projects.empty_state.search.description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<EmptyStateDetailed
|
||||
title={
|
||||
currentWorkspaceDisplayFilters?.archived_projects &&
|
||||
calculateTotalFilters(currentWorkspaceFilters ?? {}) === 0
|
||||
? t("workspace.projects_archived.title")
|
||||
: t("common.search.title")
|
||||
}
|
||||
description={
|
||||
currentWorkspaceDisplayFilters?.archived_projects &&
|
||||
calculateTotalFilters(currentWorkspaceFilters ?? {}) === 0
|
||||
? t("workspace.projects_archived.description")
|
||||
: t("common.search.description")
|
||||
}
|
||||
assetKey={
|
||||
currentWorkspaceDisplayFilters?.archived_projects &&
|
||||
calculateTotalFilters(currentWorkspaceFilters ?? {}) === 0
|
||||
? "archived-work-item"
|
||||
: "search"
|
||||
}
|
||||
assetClassName="size-40"
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,21 +1,17 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel, PROJECT_VIEW_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateDetailed } from "@plane/propel/empty-state";
|
||||
import { EUserProjectRoles } from "@plane/types";
|
||||
// components
|
||||
import { ListLayout } from "@/components/core/list";
|
||||
import { ComicBoxButton } from "@/components/empty-state/comic-box-button";
|
||||
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
import { SimpleEmptyState } from "@/components/empty-state/simple-empty-state-root";
|
||||
import { ViewListLoader } from "@/components/ui/loader/view-list-loader";
|
||||
// hooks
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useCommandPalette } from "@/hooks/store/use-command-palette";
|
||||
import { useProjectView } from "@/hooks/store/use-project-view";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
// local imports
|
||||
import { ProjectViewListItem } from "./view-list-item";
|
||||
|
||||
|
|
@ -34,24 +30,16 @@ export const ProjectViewsList = observer(() => {
|
|||
[EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER, EUserProjectRoles.GUEST],
|
||||
EUserPermissionsLevel.PROJECT
|
||||
);
|
||||
const generalViewResolvedPath = useResolvedAssetPath({
|
||||
basePath: "/empty-state/onboarding/views",
|
||||
});
|
||||
const filteredViewResolvedPath = useResolvedAssetPath({
|
||||
basePath: "/empty-state/search/views",
|
||||
});
|
||||
|
||||
if (loader || !projectViews || !filteredProjectViews) return <ViewListLoader />;
|
||||
|
||||
if (filteredProjectViews.length === 0 && projectViews.length > 0) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<SimpleEmptyState
|
||||
title={t("project_views.empty_state.filter.title")}
|
||||
description={t("project_views.empty_state.filter.description")}
|
||||
assetPath={filteredViewResolvedPath}
|
||||
/>
|
||||
</div>
|
||||
<EmptyStateDetailed
|
||||
assetKey="search"
|
||||
title={t("common.search.title")}
|
||||
description={t("common.search.description")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -68,22 +56,18 @@ export const ProjectViewsList = observer(() => {
|
|||
</ListLayout>
|
||||
</div>
|
||||
) : (
|
||||
<DetailedEmptyState
|
||||
title={t("project_views.empty_state.general.title")}
|
||||
description={t("project_views.empty_state.general.description")}
|
||||
assetPath={generalViewResolvedPath}
|
||||
customPrimaryButton={
|
||||
<ComicBoxButton
|
||||
label={t("project_views.empty_state.general.primary_button.text")}
|
||||
title={t("project_views.empty_state.general.primary_button.comic.title")}
|
||||
description={t("project_views.empty_state.general.primary_button.comic.description")}
|
||||
onClick={() => {
|
||||
toggleCreateViewModal(true);
|
||||
captureClick({ elementName: PROJECT_VIEW_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_BUTTON });
|
||||
}}
|
||||
disabled={!canPerformEmptyStateActions}
|
||||
/>
|
||||
}
|
||||
<EmptyStateDetailed
|
||||
assetKey="view"
|
||||
title={t("project.views.title")}
|
||||
description={t("project.views.description")}
|
||||
actions={[
|
||||
{
|
||||
label: t("project.views.cta_primary"),
|
||||
onClick: () => toggleCreateViewModal(true),
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
variant: "primary",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import useSWR from "swr";
|
|||
// plane imports
|
||||
import { ENotificationLoader, ENotificationQueryParamType } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||
import { SimpleEmptyState } from "@/components/empty-state/simple-empty-state-root";
|
||||
// hooks
|
||||
import { useWorkspaceNotifications } from "@/hooks/store/notifications";
|
||||
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
|
|
@ -87,8 +87,8 @@ export const NotificationsRoot = observer(({ workspaceSlug }: NotificationsRootP
|
|||
return (
|
||||
<div className={cn("w-full h-full overflow-hidden ", isWorkItem && "overflow-y-auto")}>
|
||||
{!currentSelectedNotificationId ? (
|
||||
<div className="w-full h-screen flex justify-center items-center">
|
||||
<SimpleEmptyState title={t("notification.empty_state.detail.title")} assetPath={resolvedPath} />
|
||||
<div className="flex justify-center items-center size-full">
|
||||
<EmptyStateCompact assetKey="unknown" assetClassName="size-20" />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -4,33 +4,29 @@ import type { FC } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { ENotificationTab } from "@plane/constants";
|
||||
// components
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { SimpleEmptyState } from "@/components/empty-state/simple-empty-state-root";
|
||||
// constants
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
|
||||
export const NotificationEmptyState: FC = observer(() => {
|
||||
type TNotificationEmptyStateProps = {
|
||||
currentNotificationTab: ENotificationTab;
|
||||
};
|
||||
|
||||
export const NotificationEmptyState: FC<TNotificationEmptyStateProps> = observer(({ currentNotificationTab }) => {
|
||||
// plane imports
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/notification" });
|
||||
|
||||
return (
|
||||
<>
|
||||
{ENotificationTab.ALL ? (
|
||||
<SimpleEmptyState
|
||||
title={t("notification.empty_state.all.title")}
|
||||
description={t("notification.empty_state.all.description")}
|
||||
assetPath={resolvedPath}
|
||||
/>
|
||||
) : (
|
||||
<SimpleEmptyState
|
||||
title={t("notification.empty_state.mentions.title")}
|
||||
description={t("notification.empty_state.mentions.description")}
|
||||
assetPath={resolvedPath}
|
||||
/>
|
||||
)}
|
||||
<EmptyStateCompact
|
||||
assetKey="inbox"
|
||||
assetClassName="size-24"
|
||||
title={
|
||||
currentNotificationTab === ENotificationTab.ALL
|
||||
? t("workspace.inbox_sidebar_all.title")
|
||||
: t("workspace.inbox_sidebar_mentions.title")
|
||||
}
|
||||
className="max-w-56"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ export const NotificationsSidebarRoot: FC = observer(() => {
|
|||
</ContentWrapper>
|
||||
) : (
|
||||
<div className="relative w-full h-full flex justify-center items-center">
|
||||
<NotificationEmptyState />
|
||||
<NotificationEmptyState currentNotificationTab={currentNotificationTab} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -7,12 +7,11 @@ import useSWR from "swr";
|
|||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateDetailed } from "@plane/propel/empty-state";
|
||||
import { EProjectNetwork } from "@plane/types";
|
||||
// components
|
||||
import { JoinProject } from "@/components/auth-screens/project/join-project";
|
||||
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||
import { ComicBoxButton } from "@/components/empty-state/comic-box-button";
|
||||
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
import { ETimeLineTypeType } from "@/components/gantt-chart/contexts";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
|
|
@ -26,7 +25,6 @@ import { useProject } from "@/hooks/store/use-project";
|
|||
import { useProjectState } from "@/hooks/store/use-project-state";
|
||||
import { useProjectView } from "@/hooks/store/use-project-view";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import { useTimeLineChart } from "@/hooks/use-timeline-chart";
|
||||
// local
|
||||
import { persistence } from "@/local-db/storage.sqlite";
|
||||
|
|
@ -58,9 +56,6 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
|
|||
const { fetchProjectLabels } = useLabel();
|
||||
const { getProjectEstimates } = useProjectEstimates();
|
||||
|
||||
// helper hooks
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/projects" });
|
||||
|
||||
// derived values
|
||||
const projectExists = projectId ? getProjectById(projectId.toString()) : null;
|
||||
const projectMemberInfo = getProjectRoleByWorkspaceSlugAndProjectId(workspaceSlug, projectId);
|
||||
|
|
@ -184,22 +179,22 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
|
|||
if (loader === "loaded" && projectId && !!hasPermissionToCurrentProject === false)
|
||||
return (
|
||||
<div className="grid h-full place-items-center bg-custom-background-100">
|
||||
<DetailedEmptyState
|
||||
<EmptyStateDetailed
|
||||
title={t("workspace_projects.empty_state.general.title")}
|
||||
description={t("workspace_projects.empty_state.general.description")}
|
||||
assetPath={resolvedPath}
|
||||
customPrimaryButton={
|
||||
<ComicBoxButton
|
||||
label={t("workspace_projects.empty_state.general.primary_button.text")}
|
||||
title={t("workspace_projects.empty_state.general.primary_button.comic.title")}
|
||||
description={t("workspace_projects.empty_state.general.primary_button.comic.description")}
|
||||
onClick={() => {
|
||||
assetKey="project"
|
||||
assetClassName="size-40"
|
||||
actions={[
|
||||
{
|
||||
label: t("workspace_projects.empty_state.general.primary_button.text"),
|
||||
onClick: () => {
|
||||
toggleCreateProjectModal(true);
|
||||
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON });
|
||||
}}
|
||||
disabled={!canPerformEmptyStateActions}
|
||||
/>
|
||||
}
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
variant: "primary",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue