diff --git a/web/components/command-palette/command-modal.tsx b/web/components/command-palette/command-modal.tsx index cffd3ff11..60c4fcc04 100644 --- a/web/components/command-palette/command-modal.tsx +++ b/web/components/command-palette/command-modal.tsx @@ -4,9 +4,19 @@ import { observer } from "mobx-react-lite"; import { useRouter } from "next/router"; import useSWR from "swr"; import { Dialog, Transition } from "@headlessui/react"; +// icons import { FolderPlus, Search, Settings } from "lucide-react"; // hooks +import { useApplication, useEventTracker, useProject } from "hooks/store"; +import { usePlatformOS } from "hooks/use-platform-os"; +import useDebounce from "hooks/use-debounce"; +// services +import { IssueService } from "services/issue"; +import { WorkspaceService } from "services/workspace.service"; +// ui import { LayersIcon, Loader, ToggleSwitch, Tooltip } from "@plane/ui"; +// components +import { EmptyState } from "components/empty-state"; import { CommandPaletteThemeActions, ChangeIssueAssignee, @@ -18,18 +28,13 @@ import { CommandPaletteWorkspaceSettingsActions, CommandPaletteSearchResults, } from "components/command-palette"; -import { ISSUE_DETAILS } from "constants/fetch-keys"; -import { useApplication, useEventTracker, useProject } from "hooks/store"; -import { usePlatformOS } from "hooks/use-platform-os"; -// services -import useDebounce from "hooks/use-debounce"; -import { IssueService } from "services/issue"; -import { WorkspaceService } from "services/workspace.service"; // types import { IWorkspaceSearchResults } from "@plane/types"; // fetch-keys +// constants +import { EmptyStateType } from "constants/empty-state"; +import { ISSUE_DETAILS } from "constants/fetch-keys"; -// services const workspaceService = new WorkspaceService(); const issueService = new IssueService(); @@ -244,7 +249,9 @@ export const CommandModal: React.FC = observer(() => { )} {!isLoading && resultsCount === 0 && searchTerm !== "" && debouncedSearchTerm !== "" && ( -
No results found.
+
+ +
)} {(isLoading || isSearching) && ( diff --git a/web/components/core/modals/bulk-delete-issues-modal.tsx b/web/components/core/modals/bulk-delete-issues-modal.tsx index 94d665fa7..05b98176c 100644 --- a/web/components/core/modals/bulk-delete-issues-modal.tsx +++ b/web/components/core/modals/bulk-delete-issues-modal.tsx @@ -5,22 +5,22 @@ import { SubmitHandler, useForm } from "react-hook-form"; import useSWR from "swr"; import { Combobox, Dialog, Transition } from "@headlessui/react"; // services -import { Search } from "lucide-react"; -import { Button, LayersIcon, TOAST_TYPE, setToast } from "@plane/ui"; - -import { PROJECT_ISSUES_LIST } from "constants/fetch-keys"; -import { EIssuesStoreType } from "constants/issue"; -import { useIssues, useProject } from "hooks/store"; import { IssueService } from "services/issue"; // ui +import { Button, TOAST_TYPE, setToast } from "@plane/ui"; // icons +import { Search } from "lucide-react"; // types import { IUser, TIssue } from "@plane/types"; -// fetch keys // store hooks +import { useIssues, useProject } from "hooks/store"; // components import { BulkDeleteIssuesModalItem } from "./bulk-delete-issues-modal-item"; +import { EmptyState } from "components/empty-state"; // constants +import { PROJECT_ISSUES_LIST } from "constants/fetch-keys"; +import { EIssuesStoreType } from "constants/issue"; +import { EmptyStateType } from "constants/empty-state"; type FormInput = { delete_issue_ids: string[]; @@ -178,12 +178,15 @@ export const BulkDeleteIssuesModal: React.FC = observer((props) => { ) : ( -
- -

- No issues found. Create a new issue with{" "} -
C
. -

+
+
)} diff --git a/web/components/core/modals/existing-issues-list-modal.tsx b/web/components/core/modals/existing-issues-list-modal.tsx index 79f134b31..b3f81b6ee 100644 --- a/web/components/core/modals/existing-issues-list-modal.tsx +++ b/web/components/core/modals/existing-issues-list-modal.tsx @@ -2,12 +2,14 @@ import React, { useEffect, useState } from "react"; import { Combobox, Dialog, Transition } from "@headlessui/react"; import { Rocket, Search, X } from "lucide-react"; // services -import { Button, LayersIcon, Loader, ToggleSwitch, Tooltip, TOAST_TYPE, setToast } from "@plane/ui"; - +import { ProjectService } from "services/project"; +// hooks import useDebounce from "hooks/use-debounce"; import { usePlatformOS } from "hooks/use-platform-os"; -import { ProjectService } from "services/project"; +// components +import { IssueSearchModalEmptyState } from "./issue-search-modal-empty-state"; // ui +import { Button, Loader, ToggleSwitch, Tooltip, TOAST_TYPE, setToast } from "@plane/ui"; // types import { ISearchIssueResponse, TProjectIssuesSearchParams } from "@plane/types"; @@ -40,7 +42,7 @@ export const ExistingIssuesListModal: React.FC = (props) => { const [isSearching, setIsSearching] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const [isWorkspaceLevel, setIsWorkspaceLevel] = useState(false); - const { isMobile } = usePlatformOS(); + const { isMobile } = usePlatformOS(); const debouncedSearchTerm: string = useDebounce(searchTerm, 500); const handleClose = () => { @@ -192,15 +194,12 @@ export const ExistingIssuesListModal: React.FC = (props) => { )} - {!isSearching && issues.length === 0 && searchTerm !== "" && debouncedSearchTerm !== "" && ( -
- -

- No issues found. Create a new issue with{" "} -
C
. -

-
- )} + {isSearching ? ( diff --git a/web/components/core/modals/index.ts b/web/components/core/modals/index.ts index cf72365f5..a95c22114 100644 --- a/web/components/core/modals/index.ts +++ b/web/components/core/modals/index.ts @@ -4,3 +4,4 @@ export * from "./gpt-assistant-popover"; export * from "./link-modal"; export * from "./user-image-upload-modal"; export * from "./workspace-image-upload-modal"; +export * from "./issue-search-modal-empty-state"; diff --git a/web/components/core/modals/issue-search-modal-empty-state.tsx b/web/components/core/modals/issue-search-modal-empty-state.tsx new file mode 100644 index 000000000..00dcc03bb --- /dev/null +++ b/web/components/core/modals/issue-search-modal-empty-state.tsx @@ -0,0 +1,36 @@ +import React from "react"; +// components +import { EmptyState } from "components/empty-state"; +// types +import { ISearchIssueResponse } from "@plane/types"; +// constants +import { EmptyStateType } from "constants/empty-state"; + +interface EmptyStateProps { + issues: ISearchIssueResponse[]; + searchTerm: string; + debouncedSearchTerm: string; + isSearching: boolean; +} + +export const IssueSearchModalEmptyState: React.FC = ({ + issues, + searchTerm, + debouncedSearchTerm, + isSearching, +}) => { + const renderEmptyState = (type: EmptyStateType) => ( +
+ +
+ ); + + const emptyState = + issues.length === 0 && searchTerm !== "" && debouncedSearchTerm !== "" && !isSearching + ? renderEmptyState(EmptyStateType.ISSUE_RELATION_SEARCH_EMPTY_STATE) + : issues.length === 0 + ? renderEmptyState(EmptyStateType.ISSUE_RELATION_EMPTY_STATE) + : null; + + return emptyState; +}; diff --git a/web/components/empty-state/empty-state.tsx b/web/components/empty-state/empty-state.tsx index e718c065a..783025679 100644 --- a/web/components/empty-state/empty-state.tsx +++ b/web/components/empty-state/empty-state.tsx @@ -16,7 +16,7 @@ import { cn } from "helpers/common.helper"; export type EmptyStateProps = { type: EmptyStateType; size?: "sm" | "md" | "lg"; - layout?: "widget-simple" | "screen-detailed" | "screen-simple"; + layout?: "screen-detailed" | "screen-simple"; additionalPath?: string; primaryButtonOnClick?: () => void; primaryButtonLink?: string; @@ -149,6 +149,28 @@ export const EmptyState: React.FC = (props) => {
)} + {layout === "screen-simple" && ( +
+
+ {key +
+ {description ? ( + <> +

{title}

+

{description}

+ + ) : ( +

{title}

+ )} +
+ )} ); }; diff --git a/web/components/inbox/modals/select-duplicate.tsx b/web/components/inbox/modals/select-duplicate.tsx index 321628f53..b34dd4ee7 100644 --- a/web/components/inbox/modals/select-duplicate.tsx +++ b/web/components/inbox/modals/select-duplicate.tsx @@ -2,15 +2,19 @@ import React, { useEffect, useState } from "react"; import { useRouter } from "next/router"; import useSWR from "swr"; import { Combobox, Dialog, Transition } from "@headlessui/react"; +// hooks +import { useProject, useProjectState } from "hooks/store"; // icons import { Search } from "lucide-react"; +// components +import { EmptyState } from "components/empty-state"; // ui -import { Button, LayersIcon, TOAST_TYPE, setToast } from "@plane/ui"; -// fetch-keys -import { PROJECT_ISSUES_LIST } from "constants/fetch-keys"; -import { useProject, useProjectState } from "hooks/store"; +import { Button, TOAST_TYPE, setToast } from "@plane/ui"; // services import { IssueService } from "services/issue"; +// constants +import { PROJECT_ISSUES_LIST } from "constants/fetch-keys"; +import { EmptyStateType } from "constants/empty-state"; type Props = { isOpen: boolean; @@ -158,12 +162,15 @@ export const SelectDuplicateInboxIssueModal: React.FC = (props) => { ) : ( -
- -

- No issues found. Create a new issue with{" "} -
C
. -

+
+
)} diff --git a/web/components/issues/parent-issues-list-modal.tsx b/web/components/issues/parent-issues-list-modal.tsx index 7f5f4984b..cdf642711 100644 --- a/web/components/issues/parent-issues-list-modal.tsx +++ b/web/components/issues/parent-issues-list-modal.tsx @@ -3,14 +3,16 @@ import { useRouter } from "next/router"; // headless ui import { Combobox, Dialog, Transition } from "@headlessui/react"; // services -import { Rocket, Search } from "lucide-react"; -import { LayersIcon, Loader, ToggleSwitch, Tooltip } from "@plane/ui"; -import useDebounce from "hooks/use-debounce"; import { ProjectService } from "services/project"; // hooks +import useDebounce from "hooks/use-debounce"; import { usePlatformOS } from "hooks/use-platform-os"; +// components +import { IssueSearchModalEmptyState } from "components/core"; // ui +import { Loader, ToggleSwitch, Tooltip } from "@plane/ui"; // icons +import { Rocket, Search } from "lucide-react"; // types import { ISearchIssueResponse } from "@plane/types"; @@ -151,15 +153,12 @@ export const ParentIssuesListModal: React.FC = ({ )} - {!isSearching && issues.length === 0 && searchTerm !== "" && debouncedSearchTerm !== "" && ( -
- -

- No issues found. Create a new issue with{" "} -
C
. -

-
- )} + {isSearching ? ( diff --git a/web/components/notifications/notification-popover.tsx b/web/components/notifications/notification-popover.tsx index c3e508688..4dc595c0b 100644 --- a/web/components/notifications/notification-popover.tsx +++ b/web/components/notifications/notification-popover.tsx @@ -1,21 +1,22 @@ import React, { Fragment } from "react"; import { observer } from "mobx-react-lite"; import { Popover, Transition } from "@headlessui/react"; -import { Bell } from "lucide-react"; // hooks -import { Tooltip } from "@plane/ui"; -import { EmptyState } from "components/common"; -import { SnoozeNotificationModal, NotificationCard, NotificationHeader } from "components/notifications"; -import { NotificationsLoader } from "components/ui"; -import { getNumberCount } from "helpers/string.helper"; import { useApplication } from "hooks/store"; import useOutsideClickDetector from "hooks/use-outside-click-detector"; import useUserNotification from "hooks/use-user-notifications"; import { usePlatformOS } from "hooks/use-platform-os"; +// icons +import { Bell } from "lucide-react"; // components -// images -import emptyNotification from "public/empty-state/notification.svg"; +import { Tooltip } from "@plane/ui"; +import { EmptyState } from "components/empty-state"; +import { NotificationsLoader } from "components/ui"; +import { SnoozeNotificationModal, NotificationCard, NotificationHeader } from "components/notifications"; +// constants +import { EmptyStateType } from "constants/empty-state"; // helpers +import { getNumberCount } from "helpers/string.helper"; export const NotificationPopover = observer(() => { // states @@ -59,6 +60,16 @@ export const NotificationPopover = observer(() => { if (selectedNotificationForSnooze === null) setIsActive(false); }); + const currentTabEmptyState = snoozed + ? EmptyStateType.NOTIFICATION_SNOOZED_EMPTY_STATE + : archived + ? EmptyStateType.NOTIFICATION_ARCHIVED_EMPTY_STATE + : selectedTab === "created" + ? EmptyStateType.NOTIFICATION_CREATED_EMPTY_STATE + : selectedTab === "watching" + ? EmptyStateType.NOTIFICATION_SUBSCRIBED_EMPTY_STATE + : EmptyStateType.NOTIFICATION_MY_ISSUE_EMPTY_STATE; + return ( <> { /> <> - +
) : (
- +
) ) : ( diff --git a/web/constants/empty-state.ts b/web/constants/empty-state.ts index dd6d76ef3..587f58cee 100644 --- a/web/constants/empty-state.ts +++ b/web/constants/empty-state.ts @@ -59,7 +59,6 @@ export enum EmptyStateType { PROJECT_DRAFT_NO_ISSUES = "project-draft-no-issues", VIEWS_EMPTY_SEARCH = "views-empty-search", PROJECTS_EMPTY_SEARCH = "projects-empty-search", - COMMANDK_EMPTY_SEARCH = "commandK-empty-search", MEMBERS_EMPTY_SEARCH = "members-empty-search", PROJECT_MODULE_ISSUES = "project-module-issues", PROJECT_MODULE = "project-module", @@ -71,6 +70,18 @@ export enum EmptyStateType { PROJECT_PAGE_SHARED = "project-page-shared", PROJECT_PAGE_ARCHIVED = "project-page-archived", PROJECT_PAGE_RECENT = "project-page-recent", + + COMMAND_K_SEARCH_EMPTY_STATE = "command-k-search-empty-state", + ISSUE_RELATION_SEARCH_EMPTY_STATE = "issue-relation-search-empty-state", + ISSUE_RELATION_EMPTY_STATE = "issue-relation-empty-state", + ISSUE_COMMENT_EMPTY_STATE = "issue-comment-empty-state", + + NOTIFICATION_MY_ISSUE_EMPTY_STATE = "notification-my-issues-empty-state", + NOTIFICATION_CREATED_EMPTY_STATE = "notification-created-empty-state", + NOTIFICATION_SUBSCRIBED_EMPTY_STATE = "notification-subscribed-empty-state", + NOTIFICATION_ARCHIVED_EMPTY_STATE = "notification-archived-empty-state", + NOTIFICATION_SNOOZED_EMPTY_STATE = "notification-snoozed-empty-state", + NOTIFICATION_UNREAD_EMPTY_STATE = "notification-unread-empty-state", } const emptyStateDetails = { @@ -384,11 +395,6 @@ const emptyStateDetails = { description: "No projects detected with the matching criteria. Create a new project instead.", path: "/empty-state/search/project", }, - [EmptyStateType.COMMANDK_EMPTY_SEARCH]: { - key: EmptyStateType.COMMANDK_EMPTY_SEARCH, - title: "No results found. ", - path: "/empty-state/search/search", - }, [EmptyStateType.MEMBERS_EMPTY_SEARCH]: { key: EmptyStateType.MEMBERS_EMPTY_SEARCH, title: "No matching members", @@ -504,6 +510,66 @@ const emptyStateDetails = { accessType: "project", access: EUserProjectRoles.MEMBER, }, + + [EmptyStateType.COMMAND_K_SEARCH_EMPTY_STATE]: { + key: EmptyStateType.COMMAND_K_SEARCH_EMPTY_STATE, + title: "No results found", + path: "/empty-state/search/search", + }, + [EmptyStateType.ISSUE_RELATION_SEARCH_EMPTY_STATE]: { + key: EmptyStateType.ISSUE_RELATION_SEARCH_EMPTY_STATE, + title: "No maching issues found", + path: "/empty-state/search/search", + }, + [EmptyStateType.ISSUE_RELATION_EMPTY_STATE]: { + key: EmptyStateType.ISSUE_RELATION_EMPTY_STATE, + title: "No issues found", + path: "/empty-state/search/issues", + }, + [EmptyStateType.ISSUE_COMMENT_EMPTY_STATE]: { + key: EmptyStateType.ISSUE_COMMENT_EMPTY_STATE, + title: "No comments yet", + description: "Comments can be used as a discussion and follow-up space for the issues", + path: "/empty-state/search/comments", + }, + + [EmptyStateType.NOTIFICATION_MY_ISSUE_EMPTY_STATE]: { + key: EmptyStateType.NOTIFICATION_MY_ISSUE_EMPTY_STATE, + title: "No issues assigned", + description: "Updates for issues assigned to you can be \n seen here", + path: "/empty-state/search/notification", + }, + + [EmptyStateType.NOTIFICATION_CREATED_EMPTY_STATE]: { + key: EmptyStateType.NOTIFICATION_CREATED_EMPTY_STATE, + title: "No updates to issues", + description: "Updates to issues created by you can be \n seen here", + path: "/empty-state/search/notification", + }, + [EmptyStateType.NOTIFICATION_SUBSCRIBED_EMPTY_STATE]: { + key: EmptyStateType.NOTIFICATION_SUBSCRIBED_EMPTY_STATE, + title: "No updates to issues", + description: "Updates to any issue you are \n subscribed to can be seen here", + path: "/empty-state/search/notification", + }, + [EmptyStateType.NOTIFICATION_UNREAD_EMPTY_STATE]: { + key: EmptyStateType.NOTIFICATION_UNREAD_EMPTY_STATE, + title: "No unread notifications", + description: "Congratulations, you are up-to-date \n with everything happening in the issues \n you care about", + path: "/empty-state/search/notification", + }, + [EmptyStateType.NOTIFICATION_SNOOZED_EMPTY_STATE]: { + key: EmptyStateType.NOTIFICATION_SNOOZED_EMPTY_STATE, + title: "No snoozed notifications yet", + description: "Any notification you snooze for later will \n be available here to act upon", + path: "/empty-state/search/snooze", + }, + [EmptyStateType.NOTIFICATION_ARCHIVED_EMPTY_STATE]: { + key: EmptyStateType.NOTIFICATION_ARCHIVED_EMPTY_STATE, + title: "No archived notifications yet", + description: "Any notification you archive will be \n available here to help you focus", + path: "/empty-state/search/archive", + }, } as const; export const EMPTY_STATE_DETAILS: Record = emptyStateDetails; diff --git a/web/public/empty-state/search/archive-dark.webp b/web/public/empty-state/search/archive-dark.webp new file mode 100644 index 000000000..d586be888 Binary files /dev/null and b/web/public/empty-state/search/archive-dark.webp differ diff --git a/web/public/empty-state/search/archive-light.webp b/web/public/empty-state/search/archive-light.webp new file mode 100644 index 000000000..5a79179e1 Binary files /dev/null and b/web/public/empty-state/search/archive-light.webp differ diff --git a/web/public/empty-state/search/comments-dark.webp b/web/public/empty-state/search/comments-dark.webp new file mode 100644 index 000000000..d06ba7e42 Binary files /dev/null and b/web/public/empty-state/search/comments-dark.webp differ diff --git a/web/public/empty-state/search/comments-light.webp b/web/public/empty-state/search/comments-light.webp new file mode 100644 index 000000000..5a66067d6 Binary files /dev/null and b/web/public/empty-state/search/comments-light.webp differ diff --git a/web/public/empty-state/search/issue-dark.webp b/web/public/empty-state/search/issue-dark.webp new file mode 100644 index 000000000..dcd00aa94 Binary files /dev/null and b/web/public/empty-state/search/issue-dark.webp differ diff --git a/web/public/empty-state/search/issues-light.webp b/web/public/empty-state/search/issues-light.webp new file mode 100644 index 000000000..7703fdede Binary files /dev/null and b/web/public/empty-state/search/issues-light.webp differ diff --git a/web/public/empty-state/search/notification-dark.webp b/web/public/empty-state/search/notification-dark.webp new file mode 100644 index 000000000..cb299d112 Binary files /dev/null and b/web/public/empty-state/search/notification-dark.webp differ diff --git a/web/public/empty-state/search/notification-light.webp b/web/public/empty-state/search/notification-light.webp new file mode 100644 index 000000000..55c4ffac7 Binary files /dev/null and b/web/public/empty-state/search/notification-light.webp differ diff --git a/web/public/empty-state/search/search-dark.webp b/web/public/empty-state/search/search-dark.webp new file mode 100644 index 000000000..92abc616b Binary files /dev/null and b/web/public/empty-state/search/search-dark.webp differ diff --git a/web/public/empty-state/search/search-light.webp b/web/public/empty-state/search/search-light.webp new file mode 100644 index 000000000..807ed2615 Binary files /dev/null and b/web/public/empty-state/search/search-light.webp differ diff --git a/web/public/empty-state/search/snooze-dark.webp b/web/public/empty-state/search/snooze-dark.webp new file mode 100644 index 000000000..296e797e8 Binary files /dev/null and b/web/public/empty-state/search/snooze-dark.webp differ diff --git a/web/public/empty-state/search/snooze-light.webp b/web/public/empty-state/search/snooze-light.webp new file mode 100644 index 000000000..897ff3598 Binary files /dev/null and b/web/public/empty-state/search/snooze-light.webp differ