diff --git a/apiserver/plane/app/views/search.py b/apiserver/plane/app/views/search.py index 13acabfe8..ccef3d18f 100644 --- a/apiserver/plane/app/views/search.py +++ b/apiserver/plane/app/views/search.py @@ -247,12 +247,7 @@ class IssueSearchEndpoint(BaseAPIView): if parent == "true" and issue_id: issue = Issue.issue_objects.get(pk=issue_id) issues = issues.filter( - ~Q(pk=issue_id), ~Q(pk=issue.parent_id), parent__isnull=True - ).exclude( - pk__in=Issue.issue_objects.filter( - parent__isnull=False - ).values_list("parent_id", flat=True) - ) + ~Q(pk=issue_id), ~Q(pk=issue.parent_id), ~Q(parent_id=issue_id)) if issue_relation == "true" and issue_id: issue = Issue.issue_objects.get(pk=issue_id) issues = issues.filter( diff --git a/docker-compose-local.yml b/docker-compose-local.yml index a2e518708..5f49e4897 100644 --- a/docker-compose-local.yml +++ b/docker-compose-local.yml @@ -137,7 +137,7 @@ services: dockerfile: Dockerfile.dev args: DOCKER_BUILDKIT: 1 - restart: no + restart: "no" networks: - dev_env volumes: diff --git a/packages/types/src/projects.d.ts b/packages/types/src/projects.d.ts index b54e3f0f9..86b352482 100644 --- a/packages/types/src/projects.d.ts +++ b/packages/types/src/projects.d.ts @@ -1,5 +1,11 @@ import { EUserProjectRoles } from "constants/project"; -import type { IUser, IUserLite, IWorkspace, IWorkspaceLite, TStateGroups } from "."; +import type { + IUser, + IUserLite, + IWorkspace, + IWorkspaceLite, + TStateGroups, +} from "."; export interface IProject { archive_in: number; @@ -117,7 +123,7 @@ export type TProjectIssuesSearchParams = { parent?: boolean; issue_relation?: boolean; cycle?: boolean; - module?: string[]; + module?: string; sub_issue?: boolean; issue_id?: string; workspace_search: boolean; diff --git a/web/components/core/modals/existing-issues-list-modal.tsx b/web/components/core/modals/existing-issues-list-modal.tsx index f136b099f..c4fa25c6d 100644 --- a/web/components/core/modals/existing-issues-list-modal.tsx +++ b/web/components/core/modals/existing-issues-list-modal.tsx @@ -78,7 +78,6 @@ export const ExistingIssuesListModal: React.FC = (props) => { useEffect(() => { if (!isOpen || !workspaceSlug || !projectId) return; - if (issues.length <= 0) setIsSearching(true); projectService .projectIssuesSearch(workspaceSlug as string, projectId as string, { @@ -88,16 +87,7 @@ export const ExistingIssuesListModal: React.FC = (props) => { }) .then((res) => setIssues(res)) .finally(() => setIsSearching(false)); - }, [issues, debouncedSearchTerm, isOpen, isWorkspaceLevel, projectId, searchParams, workspaceSlug]); - - useEffect(() => { - setSearchTerm(""); - setIssues([]); - setSelectedIssues([]); - setIsSearching(false); - setIsSubmitting(false); - setIsWorkspaceLevel(false); - }, [isOpen]); + }, [debouncedSearchTerm, isOpen, isWorkspaceLevel, projectId, searchParams, workspaceSlug]); return ( <> diff --git a/web/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx b/web/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx index 30d764ab1..cb433de05 100644 --- a/web/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx +++ b/web/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx @@ -3,12 +3,20 @@ import { Menu } from "lucide-react"; import { useApplication } from "hooks/store"; import { observer } from "mobx-react"; -export const SidebarHamburgerToggle: FC = observer(() => { - const { theme: themStore } = useApplication(); +type Props = { + onClick?: () => void; +} + +export const SidebarHamburgerToggle: FC = observer((props) => { + const { onClick } = props + const { theme: themeStore } = useApplication(); return (
themStore.toggleSidebar()} + onClick={() => { + if (onClick) onClick() + else themeStore.toggleMobileSidebar() + }} >
diff --git a/web/components/cycles/active-cycle-details.tsx b/web/components/cycles/active-cycle-details.tsx index 2fa79ec3a..5d4bcd768 100644 --- a/web/components/cycles/active-cycle-details.tsx +++ b/web/components/cycles/active-cycle-details.tsx @@ -34,7 +34,8 @@ import { ICycle, TCycleGroups } from "@plane/types"; // constants import { EIssuesStoreType } from "constants/issue"; import { CYCLE_ISSUES_WITH_PARAMS } from "constants/fetch-keys"; -import { CYCLE_EMPTY_STATE_DETAILS, CYCLE_STATE_GROUPS_DETAILS } from "constants/cycle"; +import { CYCLE_STATE_GROUPS_DETAILS } from "constants/cycle"; +import { CYCLE_EMPTY_STATE_DETAILS } from "constants/empty-state"; interface IActiveCycleDetails { workspaceSlug: string; diff --git a/web/components/cycles/cycles-board-card.tsx b/web/components/cycles/cycles-board-card.tsx index 07e946c80..e96b01858 100644 --- a/web/components/cycles/cycles-board-card.tsx +++ b/web/components/cycles/cycles-board-card.tsx @@ -1,6 +1,7 @@ import { FC, MouseEvent, useState } from "react"; import { useRouter } from "next/router"; import Link from "next/link"; +import { observer } from "mobx-react"; // hooks import { useEventTracker, useCycle, useUser } from "hooks/store"; import useToast from "hooks/use-toast"; @@ -26,7 +27,7 @@ export interface ICyclesBoardCard { cycleId: string; } -export const CyclesBoardCard: FC = (props) => { +export const CyclesBoardCard: FC = observer((props) => { const { cycleId, workspaceSlug, projectId } = props; // states const [updateModal, setUpdateModal] = useState(false); @@ -69,8 +70,8 @@ export const CyclesBoardCard: FC = (props) => { ? cycleTotalIssues === 0 ? "0 Issue" : cycleTotalIssues === cycleDetails.completed_issues - ? `${cycleTotalIssues} Issue${cycleTotalIssues > 1 ? "s" : ""}` - : `${cycleDetails.completed_issues}/${cycleTotalIssues} Issues` + ? `${cycleTotalIssues} Issue${cycleTotalIssues > 1 ? "s" : ""}` + : `${cycleDetails.completed_issues}/${cycleTotalIssues} Issues` : "0 Issue"; const handleCopyText = (e: MouseEvent) => { @@ -295,4 +296,4 @@ export const CyclesBoardCard: FC = (props) => { ); -}; +}); diff --git a/web/components/cycles/cycles-board.tsx b/web/components/cycles/cycles-board.tsx index 19e7f2225..34e973614 100644 --- a/web/components/cycles/cycles-board.tsx +++ b/web/components/cycles/cycles-board.tsx @@ -7,7 +7,7 @@ import { useUser } from "hooks/store"; import { CyclePeekOverview, CyclesBoardCard } from "components/cycles"; import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // constants -import { CYCLE_EMPTY_STATE_DETAILS } from "constants/cycle"; +import { CYCLE_EMPTY_STATE_DETAILS } from "constants/empty-state"; export interface ICyclesBoard { cycleIds: string[]; diff --git a/web/components/cycles/cycles-list-item.tsx b/web/components/cycles/cycles-list-item.tsx index da2654aaf..ed2b26c53 100644 --- a/web/components/cycles/cycles-list-item.tsx +++ b/web/components/cycles/cycles-list-item.tsx @@ -1,6 +1,7 @@ import { FC, MouseEvent, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; +import { observer } from "mobx-react"; // hooks import { useEventTracker, useCycle, useUser } from "hooks/store"; import useToast from "hooks/use-toast"; @@ -30,7 +31,7 @@ type TCyclesListItem = { projectId: string; }; -export const CyclesListItem: FC = (props) => { +export const CyclesListItem: FC = observer((props) => { const { cycleId, workspaceSlug, projectId } = props; // states const [updateModal, setUpdateModal] = useState(false); @@ -289,4 +290,4 @@ export const CyclesListItem: FC = (props) => { ); -}; +}); diff --git a/web/components/cycles/cycles-list.tsx b/web/components/cycles/cycles-list.tsx index 90fcdd8f9..838d88a30 100644 --- a/web/components/cycles/cycles-list.tsx +++ b/web/components/cycles/cycles-list.tsx @@ -9,7 +9,7 @@ import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // ui import { Loader } from "@plane/ui"; // constants -import { CYCLE_EMPTY_STATE_DETAILS } from "constants/cycle"; +import { CYCLE_EMPTY_STATE_DETAILS } from "constants/empty-state"; export interface ICyclesList { cycleIds: string[]; diff --git a/web/components/cycles/cycles-view.tsx b/web/components/cycles/cycles-view.tsx index 7b58bde45..a321be0b5 100644 --- a/web/components/cycles/cycles-view.tsx +++ b/web/components/cycles/cycles-view.tsx @@ -5,7 +5,7 @@ import { useCycle } from "hooks/store"; // components import { CyclesBoard, CyclesList, CyclesListGanttChartView } from "components/cycles"; // ui components -import { Loader } from "@plane/ui"; +import { CycleModuleBoardLayout, CycleModuleListLayout, GanttLayoutLoader } from "components/ui"; // types import { TCycleLayout, TCycleView } from "@plane/types"; @@ -25,6 +25,7 @@ export const CyclesView: FC = observer((props) => { currentProjectDraftCycleIds, currentProjectUpcomingCycleIds, currentProjectCycleIds, + loader, } = useCycle(); const cyclesList = @@ -36,55 +37,32 @@ export const CyclesView: FC = observer((props) => { ? currentProjectUpcomingCycleIds : currentProjectCycleIds; + if (loader || !cyclesList) + return ( + <> + {layout === "list" && } + {layout === "board" && } + {layout === "gantt" && } + + ); + return ( <> {layout === "list" && ( - <> - {cyclesList ? ( - - ) : ( - - - - - - )} - + )} {layout === "board" && ( - <> - {cyclesList ? ( - - ) : ( - - - - - - )} - + )} - {layout === "gantt" && ( - <> - {cyclesList ? ( - - ) : ( - - - - - - )} - - )} + {layout === "gantt" && } ); }); diff --git a/web/components/estimates/estimates-list.tsx b/web/components/estimates/estimates-list.tsx index e226e17e0..1dabc6181 100644 --- a/web/components/estimates/estimates-list.tsx +++ b/web/components/estimates/estimates-list.tsx @@ -1,21 +1,21 @@ import React, { useState } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; -import { Plus } from "lucide-react"; +import { useTheme } from "next-themes"; // store hooks -import { useEstimate, useProject } from "hooks/store"; +import { useEstimate, useProject, useUser } from "hooks/store"; import useToast from "hooks/use-toast"; // components import { CreateUpdateEstimateModal, DeleteEstimateModal, EstimateListItem } from "components/estimates"; +import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // ui import { Button, Loader } from "@plane/ui"; -import { EmptyState } from "components/common"; -// images -import emptyEstimate from "public/empty-state/estimate.svg"; // types import { IEstimate } from "@plane/types"; // helpers import { orderArrayBy } from "helpers/array.helper"; +// constants +import { PROJECT_SETTINGS_EMPTY_STATE_DETAILS } from "constants/empty-state"; export const EstimatesList: React.FC = observer(() => { // states @@ -25,9 +25,12 @@ export const EstimatesList: React.FC = observer(() => { // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; + // theme + const { resolvedTheme } = useTheme(); // store hooks const { updateProject, currentProjectDetails } = useProject(); const { projectEstimates, getProjectEstimateById } = useEstimate(); + const { currentUser } = useUser(); // toast alert const { setToastAlert } = useToast(); @@ -55,6 +58,10 @@ export const EstimatesList: React.FC = observer(() => { }); }; + const emptyStateDetail = PROJECT_SETTINGS_EMPTY_STATE_DETAILS["estimate"]; + const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light"; + const emptyStateImage = getEmptyStateImagePath("project-settings", "estimates", isLightMode); + return ( <> { ))} ) : ( -
+
, - text: "Add Estimate", - onClick: () => { - setEstimateFormOpen(true); - setEstimateToUpdate(undefined); - }, - }} + title={emptyStateDetail.title} + description={emptyStateDetail.description} + image={emptyStateImage} + size="lg" />
) diff --git a/web/components/exporter/guide.tsx b/web/components/exporter/guide.tsx index 87bf0604a..ed6a39220 100644 --- a/web/components/exporter/guide.tsx +++ b/web/components/exporter/guide.tsx @@ -3,25 +3,28 @@ import { useState } from "react"; import Link from "next/link"; import Image from "next/image"; import { useRouter } from "next/router"; - +import { useTheme } from "next-themes"; import useSWR, { mutate } from "swr"; - +import { observer } from "mobx-react-lite"; // hooks +import { useUser } from "hooks/store"; import useUserAuth from "hooks/use-user-auth"; // services import { IntegrationService } from "services/integrations"; // components import { Exporter, SingleExport } from "components/exporter"; +import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // ui -import { Button, Loader } from "@plane/ui"; +import { Button } from "@plane/ui"; +import { ImportExportSettingsLoader } from "components/ui"; // icons import { MoveLeft, MoveRight, RefreshCw } from "lucide-react"; // fetch-keys import { EXPORT_SERVICES_LIST } from "constants/fetch-keys"; // constants import { EXPORTERS_LIST } from "constants/workspace"; -import { observer } from "mobx-react-lite"; -import { useUser } from "hooks/store"; + +import { WORKSPACE_SETTINGS_EMPTY_STATE_DETAILS } from "constants/empty-state"; // services const integrationService = new IntegrationService(); @@ -34,6 +37,8 @@ const IntegrationGuide = observer(() => { // router const router = useRouter(); const { workspaceSlug, provider } = router.query; + // theme + const { resolvedTheme } = useTheme(); // store hooks const { currentUser, currentUserLoader } = useUser(); // custom hooks @@ -46,6 +51,10 @@ const IntegrationGuide = observer(() => { : null ); + const emptyStateDetail = WORKSPACE_SETTINGS_EMPTY_STATE_DETAILS["export"]; + const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light"; + const emptyStateImage = getEmptyStateImagePath("workspace-settings", "exports", isLightMode); + const handleCsvClose = () => { router.replace(`/${workspaceSlug?.toString()}/settings/exports`); }; @@ -140,15 +149,17 @@ const IntegrationGuide = observer(() => {
) : ( -

No previous export available.

+
+ +
) ) : ( - - - - - - + )} diff --git a/web/components/gantt-chart/blocks/blocks-list.tsx b/web/components/gantt-chart/blocks/blocks-list.tsx index 1bc75ea8f..15a3e5295 100644 --- a/web/components/gantt-chart/blocks/blocks-list.tsx +++ b/web/components/gantt-chart/blocks/blocks-list.tsx @@ -86,7 +86,7 @@ export const GanttChartBlocksList: FC = observer((props) className="h-full" style={{ width: `${itemsContainerWidth}px`, - marginTop: `${HEADER_HEIGHT}px`, + transform: `translateY(${HEADER_HEIGHT}px)`, }} > {blocks?.map((block) => { diff --git a/web/components/gantt-chart/chart/main-content.tsx b/web/components/gantt-chart/chart/main-content.tsx index 35bd6bc07..7a35adbb6 100644 --- a/web/components/gantt-chart/chart/main-content.tsx +++ b/web/components/gantt-chart/chart/main-content.tsx @@ -33,6 +33,7 @@ type Props = { sidebarToRender: (props: any) => React.ReactNode; title: string; updateCurrentViewRenderPayload: (direction: "left" | "right", currentView: TGanttViews) => void; + quickAdd?: React.JSX.Element | undefined; }; export const GanttChartMainContent: React.FC = (props) => { @@ -52,6 +53,7 @@ export const GanttChartMainContent: React.FC = (props) => { sidebarToRender, title, updateCurrentViewRenderPayload, + quickAdd, } = props; // chart hook const { currentView, currentViewData, updateScrollLeft } = useChart(); @@ -101,6 +103,7 @@ export const GanttChartMainContent: React.FC = (props) => { enableReorder={enableReorder} sidebarToRender={sidebarToRender} title={title} + quickAdd={quickAdd} />
diff --git a/web/components/gantt-chart/chart/root.tsx b/web/components/gantt-chart/chart/root.tsx index 4cb6bc10e..877c15901 100644 --- a/web/components/gantt-chart/chart/root.tsx +++ b/web/components/gantt-chart/chart/root.tsx @@ -31,6 +31,7 @@ type ChartViewRootProps = { enableAddBlock: boolean; bottomSpacing: boolean; showAllBlocks: boolean; + quickAdd?: React.JSX.Element | undefined; }; export const ChartViewRoot: FC = (props) => { @@ -49,6 +50,7 @@ export const ChartViewRoot: FC = (props) => { enableAddBlock, bottomSpacing, showAllBlocks, + quickAdd, } = props; // states const [itemsContainerWidth, setItemsContainerWidth] = useState(0); @@ -200,6 +202,7 @@ export const ChartViewRoot: FC = (props) => { sidebarToRender={sidebarToRender} title={title} updateCurrentViewRenderPayload={updateCurrentViewRenderPayload} + quickAdd={quickAdd} />
); diff --git a/web/components/gantt-chart/root.tsx b/web/components/gantt-chart/root.tsx index 2e9a8aca1..ac132500b 100644 --- a/web/components/gantt-chart/root.tsx +++ b/web/components/gantt-chart/root.tsx @@ -12,6 +12,7 @@ type GanttChartRootProps = { blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void; blockToRender: (data: any) => React.ReactNode; sidebarToRender: (props: any) => React.ReactNode; + quickAdd?: React.JSX.Element | undefined; enableBlockLeftResize?: boolean; enableBlockRightResize?: boolean; enableBlockMove?: boolean; @@ -37,6 +38,7 @@ export const GanttChartRoot: FC = (props) => { enableAddBlock = false, bottomSpacing = false, showAllBlocks = false, + quickAdd, } = props; return ( @@ -56,6 +58,7 @@ export const GanttChartRoot: FC = (props) => { enableAddBlock={enableAddBlock} bottomSpacing={bottomSpacing} showAllBlocks={showAllBlocks} + quickAdd={quickAdd} /> ); diff --git a/web/components/gantt-chart/sidebar/issues.tsx b/web/components/gantt-chart/sidebar/issues.tsx index 77b373ac8..52e30ded5 100644 --- a/web/components/gantt-chart/sidebar/issues.tsx +++ b/web/components/gantt-chart/sidebar/issues.tsx @@ -7,42 +7,23 @@ import { useIssueDetail } from "hooks/store"; // ui import { Loader } from "@plane/ui"; // components -import { GanttQuickAddIssueForm, IssueGanttSidebarBlock } from "components/issues"; +import { IssueGanttSidebarBlock } from "components/issues"; // helpers import { findTotalDaysInRange } from "helpers/date-time.helper"; import { cn } from "helpers/common.helper"; // types import { IGanttBlock, IBlockUpdateData } from "components/gantt-chart/types"; -import { TIssue } from "@plane/types"; import { BLOCK_HEIGHT } from "../constants"; type Props = { blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void; blocks: IGanttBlock[] | null; enableReorder: boolean; - enableQuickIssueCreate?: boolean; - quickAddCallback?: ( - workspaceSlug: string, - projectId: string, - data: TIssue, - viewId?: string - ) => Promise; - viewId?: string; - disableIssueCreation?: boolean; showAllBlocks?: boolean; }; -export const IssueGanttSidebar: React.FC = observer((props) => { - const { - blockUpdateHandler, - blocks, - enableReorder, - enableQuickIssueCreate, - quickAddCallback, - viewId, - disableIssueCreation, - showAllBlocks = false, - } = props; +export const IssueGanttSidebar: React.FC = observer((props: Props) => { + const { blockUpdateHandler, blocks, enableReorder, showAllBlocks = false } = props; const { activeBlock, dispatch } = useChart(); const { peekIssue } = useIssueDetail(); @@ -187,9 +168,6 @@ export const IssueGanttSidebar: React.FC = observer((props) => { )} - {enableQuickIssueCreate && !disableIssueCreation && ( - - )} ); }); diff --git a/web/components/gantt-chart/sidebar/root.tsx b/web/components/gantt-chart/sidebar/root.tsx index b76112059..0b877ba33 100644 --- a/web/components/gantt-chart/sidebar/root.tsx +++ b/web/components/gantt-chart/sidebar/root.tsx @@ -9,16 +9,17 @@ type Props = { enableReorder: boolean; sidebarToRender: (props: any) => React.ReactNode; title: string; + quickAdd?: React.JSX.Element | undefined; }; export const GanttChartSidebar: React.FC = (props) => { - const { blocks, blockUpdateHandler, enableReorder, sidebarToRender, title } = props; + const { blocks, blockUpdateHandler, enableReorder, sidebarToRender, title, quickAdd } = props; return (
= (props) => {
Duration
-
+
{sidebarToRender && sidebarToRender({ title, blockUpdateHandler, blocks, enableReorder })}
+ {quickAdd ? quickAdd : null}
); }; diff --git a/web/components/integration/guide.tsx b/web/components/integration/guide.tsx index 9499988fa..29dfa5b30 100644 --- a/web/components/integration/guide.tsx +++ b/web/components/integration/guide.tsx @@ -4,6 +4,7 @@ import Image from "next/image"; import { useRouter } from "next/router"; import useSWR, { mutate } from "swr"; import { observer } from "mobx-react-lite"; +import { useTheme } from "next-themes"; // hooks import { useUser } from "hooks/store"; import useUserAuth from "hooks/use-user-auth"; @@ -11,8 +12,10 @@ import useUserAuth from "hooks/use-user-auth"; import { IntegrationService } from "services/integrations"; // components import { DeleteImportModal, GithubImporterRoot, JiraImporterRoot, SingleImport } from "components/integration"; +import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // ui -import { Button, Loader } from "@plane/ui"; +import { Button } from "@plane/ui"; +import { ImportExportSettingsLoader } from "components/ui"; // icons import { RefreshCw } from "lucide-react"; // types @@ -21,6 +24,7 @@ import { IImporterService } from "@plane/types"; import { IMPORTER_SERVICES_LIST } from "constants/fetch-keys"; // constants import { IMPORTERS_LIST } from "constants/workspace"; +import { WORKSPACE_SETTINGS_EMPTY_STATE_DETAILS } from "constants/empty-state"; // services const integrationService = new IntegrationService(); @@ -33,6 +37,8 @@ const IntegrationGuide = observer(() => { // router const router = useRouter(); const { workspaceSlug, provider } = router.query; + // theme + const { resolvedTheme } = useTheme(); // store hooks const { currentUser, currentUserLoader } = useUser(); // custom hooks @@ -43,6 +49,10 @@ const IntegrationGuide = observer(() => { workspaceSlug ? () => integrationService.getImporterServicesList(workspaceSlug as string) : null ); + const emptyStateDetail = WORKSPACE_SETTINGS_EMPTY_STATE_DETAILS["import"]; + const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light"; + const emptyStateImage = getEmptyStateImagePath("workspace-settings", "imports", isLightMode); + const handleDeleteImport = (importService: IImporterService) => { setImportToDelete(importService); setDeleteImportModal(true); @@ -134,15 +144,17 @@ const IntegrationGuide = observer(() => { ) : ( -

No previous imports available.

+
+ +
) ) : ( - - - - - - + )} diff --git a/web/components/integration/single-integration-card.tsx b/web/components/integration/single-integration-card.tsx index 70bbb5fa4..3026d6981 100644 --- a/web/components/integration/single-integration-card.tsx +++ b/web/components/integration/single-integration-card.tsx @@ -168,7 +168,7 @@ export const SingleIntegrationCard: React.FC = observer(({ integration }) ) ) : ( - + )} diff --git a/web/components/issues/description-form.tsx b/web/components/issues/description-form.tsx index b7601ef52..452e37e75 100644 --- a/web/components/issues/description-form.tsx +++ b/web/components/issues/description-form.tsx @@ -13,7 +13,6 @@ import { TIssueOperations } from "./issue-detail"; import { FileService } from "services/file.service"; import { useMention, useWorkspace } from "hooks/store"; import { observer } from "mobx-react"; -import { isNil } from "lodash"; export interface IssueDescriptionFormValues { name: string; @@ -79,13 +78,13 @@ export const IssueDescriptionForm: FC = observer((props) => { }, [issue.id]); // TODO: verify the exhaustive-deps warning useEffect(() => { - if (issue.description_html) { + if (["", undefined, null].includes(localIssueDescription.description_html)) { setLocalIssueDescription((state) => { - if (!isNil(state.description_html)) return state; - return { id: issue.id, description_html: issue.description_html }; + if (!["", undefined, null].includes(state.description_html)) return state; + return { id: issue.id, description_html: issue.description_html || "

" }; }); } - }, [issue.description_html]); + }, [issue.description_html, localIssueDescription.description_html, issue.id]); const handleDescriptionFormSubmit = useCallback( async (formData: Partial) => { @@ -177,7 +176,7 @@ export const IssueDescriptionForm: FC = observer((props) => { {errors.name ? errors.name.message : null}
- {issue.description_html ? ( + {localIssueDescription.description_html ? ( = (props) => { const { workspaceSlug, projectId, issueId, issue, issueOperations } = props; // hooks const { issueMap } = useIssues(); - const { peekIssue } = useIssueDetail(); const { getProjectById } = useProject(); const { getProjectStates } = useProjectState(); @@ -39,7 +38,7 @@ export const IssueParentDetail: FC = (props) => { return ( <>
- +
diff --git a/web/components/issues/issue-detail/sidebar.tsx b/web/components/issues/issue-detail/sidebar.tsx index c78bbe942..1c93385c0 100644 --- a/web/components/issues/issue-detail/sidebar.tsx +++ b/web/components/issues/issue-detail/sidebar.tsx @@ -15,7 +15,7 @@ import { CalendarCheck2, } from "lucide-react"; // hooks -import { useEstimate, useIssueDetail, useProject, useProjectState, useUser } from "hooks/store"; +import { useEstimate, useIssueDetail, useProject, useUser } from "hooks/store"; import useToast from "hooks/use-toast"; // components import { @@ -56,11 +56,9 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { const { workspaceSlug, projectId, issueId, issueOperations, is_archived, is_editable } = props; // router const router = useRouter(); - const { inboxIssueId } = router.query; // store hooks const { getProjectById } = useProject(); const { currentUser } = useUser(); - const { projectStates } = useProjectState(); const { areEstimatesEnabledForCurrentProject } = useEstimate(); const { setToastAlert } = useToast(); const { @@ -91,8 +89,6 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { const maxDate = issue.target_date ? new Date(issue.target_date) : null; maxDate?.setDate(maxDate.getDate()); - const currentIssueState = projectStates?.find((s) => s.id === issue.state_id); - return ( <> {workspaceSlug && projectId && issue && ( @@ -108,22 +104,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { )}
-
-
- {currentIssueState ? ( - - ) : inboxIssueId ? ( - - ) : null} -

- {projectDetails?.identifier}-{issue?.sequence_id} -

-
- +
{currentUser && !is_archived && ( @@ -187,8 +168,9 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { buttonVariant={issue?.assignee_ids?.length > 1 ? "transparent-without-text" : "transparent-with-text"} className="w-3/5 flex-grow group" buttonContainerClassName="w-full text-left" - buttonClassName={`text-sm justify-between ${issue?.assignee_ids.length > 0 ? "" : "text-custom-text-400" - }`} + buttonClassName={`text-sm justify-between ${ + issue?.assignee_ids.length > 0 ? "" : "text-custom-text-400" + }`} hideIcon={issue.assignee_ids?.length === 0} dropdownArrow dropdownArrowClassName="h-3.5 w-3.5 hidden group-hover:inline" @@ -232,8 +214,8 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { buttonClassName={`text-sm ${issue?.start_date ? "" : "text-custom-text-400"}`} hideIcon clearIconClassName="h-3 w-3 hidden group-hover:inline" - // TODO: add this logic - // showPlaceholderIcon + // TODO: add this logic + // showPlaceholderIcon />
@@ -258,8 +240,8 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { buttonClassName={`text-sm ${issue?.target_date ? "" : "text-custom-text-400"}`} hideIcon clearIconClassName="h-3 w-3 hidden group-hover:inline" - // TODO: add this logic - // showPlaceholderIcon + // TODO: add this logic + // showPlaceholderIcon />
diff --git a/web/components/issues/issue-layouts/empty-states/archived-issues.tsx b/web/components/issues/issue-layouts/empty-states/archived-issues.tsx index 89eb58110..0c8fb377a 100644 --- a/web/components/issues/issue-layouts/empty-states/archived-issues.tsx +++ b/web/components/issues/issue-layouts/empty-states/archived-issues.tsx @@ -9,6 +9,7 @@ import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // constants import { EUserProjectRoles } from "constants/project"; import { EIssueFilterType, EIssuesStoreType } from "constants/issue"; +import { EMPTY_FILTER_STATE_DETAILS, EMPTY_ISSUE_STATE_DETAILS } from "constants/empty-state"; // types import { IIssueFilterOptions } from "@plane/types"; @@ -65,20 +66,19 @@ export const ProjectArchivedEmptyState: React.FC = observer(() => { const emptyStateProps: EmptyStateProps = issueFilterCount > 0 ? { - title: "No issues found matching the filters applied", + title: EMPTY_FILTER_STATE_DETAILS["archived"].title, image: currentLayoutEmptyStateImagePath, secondaryButton: { - text: "Clear all filters", + text: EMPTY_FILTER_STATE_DETAILS["archived"].secondaryButton?.text, onClick: handleClearAllFilters, }, } : { - title: "No archived issues yet", - description: - "Archived issues help you remove issues you completed or cancelled from focus. You can set automation to auto archive issues and find them here.", + title: EMPTY_ISSUE_STATE_DETAILS["archived"].title, + description: EMPTY_ISSUE_STATE_DETAILS["archived"].description, image: EmptyStateImagePath, primaryButton: { - text: "Set Automation", + text: EMPTY_ISSUE_STATE_DETAILS["archived"].primaryButton.text, onClick: () => router.push(`/${workspaceSlug}/projects/${projectId}/settings/automations`), }, size: "sm", diff --git a/web/components/issues/issue-layouts/empty-states/cycle.tsx b/web/components/issues/issue-layouts/empty-states/cycle.tsx index 02bdf6820..69c2279dd 100644 --- a/web/components/issues/issue-layouts/empty-states/cycle.tsx +++ b/web/components/issues/issue-layouts/empty-states/cycle.tsx @@ -1,32 +1,46 @@ import { useState } from "react"; import { observer } from "mobx-react-lite"; import { PlusIcon } from "lucide-react"; +import { useTheme } from "next-themes"; // hooks import { useApplication, useEventTracker, useIssueDetail, useIssues, useUser } from "hooks/store"; import useToast from "hooks/use-toast"; // components -import { EmptyState } from "components/common"; import { ExistingIssuesListModal } from "components/core"; -// ui -import { Button } from "@plane/ui"; -// assets -import emptyIssue from "public/empty-state/issue.svg"; +import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // types -import { ISearchIssueResponse } from "@plane/types"; +import { ISearchIssueResponse, TIssueLayouts } from "@plane/types"; // constants import { EUserProjectRoles } from "constants/project"; import { EIssuesStoreType } from "constants/issue"; +import { CYCLE_EMPTY_STATE_DETAILS, EMPTY_FILTER_STATE_DETAILS } from "constants/empty-state"; type Props = { workspaceSlug: string | undefined; projectId: string | undefined; cycleId: string | undefined; + activeLayout: TIssueLayouts | undefined; + handleClearAllFilters: () => void; + isEmptyFilters?: boolean; }; +interface EmptyStateProps { + title: string; + image: string; + description?: string; + comicBox?: { title: string; description: string }; + primaryButton?: { text: string; icon?: React.ReactNode; onClick: () => void }; + secondaryButton?: { text: string; icon?: React.ReactNode; onClick: () => void }; + size?: "lg" | "sm" | undefined; + disabled?: boolean | undefined; +} + export const CycleEmptyState: React.FC = observer((props) => { - const { workspaceSlug, projectId, cycleId } = props; + const { workspaceSlug, projectId, cycleId, activeLayout, handleClearAllFilters, isEmptyFilters = false } = props; // states const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false); + // theme + const { resolvedTheme } = useTheme(); // store hooks const { issues } = useIssues(EIssuesStoreType.CYCLE); const { updateIssue, fetchIssue } = useIssueDetail(); @@ -36,6 +50,7 @@ export const CycleEmptyState: React.FC = observer((props) => { const { setTrackElement } = useEventTracker(); const { membership: { currentProjectRole: userRole }, + currentUser, } = useUser(); const { setToastAlert } = useToast(); @@ -60,8 +75,44 @@ export const CycleEmptyState: React.FC = observer((props) => { }); }; + const emptyStateDetail = CYCLE_EMPTY_STATE_DETAILS["no-issues"]; + + const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light"; + const currentLayoutEmptyStateImagePath = getEmptyStateImagePath("empty-filters", activeLayout ?? "list", isLightMode); + const emptyStateImage = getEmptyStateImagePath("cycle-issues", activeLayout ?? "list", isLightMode); + const isEditingAllowed = !!userRole && userRole >= EUserProjectRoles.MEMBER; + const emptyStateProps: EmptyStateProps = isEmptyFilters + ? { + title: EMPTY_FILTER_STATE_DETAILS["project"].title, + image: currentLayoutEmptyStateImagePath, + secondaryButton: { + text: EMPTY_FILTER_STATE_DETAILS["project"].secondaryButton.text, + onClick: handleClearAllFilters, + }, + } + : { + title: emptyStateDetail.title, + description: emptyStateDetail.description, + image: emptyStateImage, + primaryButton: { + text: emptyStateDetail.primaryButton.text, + icon: , + onClick: () => { + setTrackElement("Cycle issue empty state"); + toggleCreateIssueModal(true, EIssuesStoreType.CYCLE); + }, + }, + secondaryButton: { + text: emptyStateDetail.secondaryButton.text, + icon: , + onClick: () => setCycleIssuesListModal(true), + }, + size: "sm", + disabled: !isEditingAllowed, + }; + return ( <> = observer((props) => { handleOnSubmit={handleAddIssuesToCycle} />
- , - onClick: () => { - setTrackElement("Cycle issue empty state"); - toggleCreateIssueModal(true, EIssuesStoreType.CYCLE); - }, - }} - secondaryButton={ - - } - disabled={!isEditingAllowed} - /> +
); diff --git a/web/components/issues/issue-layouts/empty-states/draft-issues.tsx b/web/components/issues/issue-layouts/empty-states/draft-issues.tsx index 1d2695ff9..347778d8f 100644 --- a/web/components/issues/issue-layouts/empty-states/draft-issues.tsx +++ b/web/components/issues/issue-layouts/empty-states/draft-issues.tsx @@ -9,6 +9,7 @@ import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // constants import { EUserProjectRoles } from "constants/project"; import { EIssueFilterType, EIssuesStoreType } from "constants/issue"; +import { EMPTY_FILTER_STATE_DETAILS, EMPTY_ISSUE_STATE_DETAILS } from "constants/empty-state"; // types import { IIssueFilterOptions } from "@plane/types"; @@ -65,17 +66,16 @@ export const ProjectDraftEmptyState: React.FC = observer(() => { const emptyStateProps: EmptyStateProps = issueFilterCount > 0 ? { - title: "No issues found matching the filters applied", + title: EMPTY_FILTER_STATE_DETAILS["draft"].title, image: currentLayoutEmptyStateImagePath, secondaryButton: { - text: "Clear all filters", + text: EMPTY_FILTER_STATE_DETAILS["draft"].secondaryButton.text, onClick: handleClearAllFilters, }, } : { - title: "No draft issues yet", - description: - "Quickly stepping away but want to keep your place? No worries – save a draft now. Your issues will be right here waiting for you.", + title: EMPTY_ISSUE_STATE_DETAILS["draft"].title, + description: EMPTY_ISSUE_STATE_DETAILS["draft"].description, image: EmptyStateImagePath, size: "sm", disabled: !isEditingAllowed, diff --git a/web/components/issues/issue-layouts/empty-states/module.tsx b/web/components/issues/issue-layouts/empty-states/module.tsx index c656bd622..ef7ec729c 100644 --- a/web/components/issues/issue-layouts/empty-states/module.tsx +++ b/web/components/issues/issue-layouts/empty-states/module.tsx @@ -1,41 +1,55 @@ import { useState } from "react"; import { observer } from "mobx-react-lite"; import { PlusIcon } from "lucide-react"; +import { useTheme } from "next-themes"; // hooks import { useApplication, useEventTracker, useIssues, useUser } from "hooks/store"; import useToast from "hooks/use-toast"; // components -import { EmptyState } from "components/common"; import { ExistingIssuesListModal } from "components/core"; -// ui -import { Button } from "@plane/ui"; -// assets -import emptyIssue from "public/empty-state/issue.svg"; +import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // types -import { ISearchIssueResponse } from "@plane/types"; +import { ISearchIssueResponse, TIssueLayouts } from "@plane/types"; // constants import { EUserProjectRoles } from "constants/project"; import { EIssuesStoreType } from "constants/issue"; +import { EMPTY_FILTER_STATE_DETAILS, MODULE_EMPTY_STATE_DETAILS } from "constants/empty-state"; type Props = { workspaceSlug: string | undefined; projectId: string | undefined; moduleId: string | undefined; + activeLayout: TIssueLayouts | undefined; + handleClearAllFilters: () => void; + isEmptyFilters?: boolean; }; +interface EmptyStateProps { + title: string; + image: string; + description?: string; + comicBox?: { title: string; description: string }; + primaryButton?: { text: string; icon?: React.ReactNode; onClick: () => void }; + secondaryButton?: { text: string; icon?: React.ReactNode; onClick: () => void }; + size?: "lg" | "sm" | undefined; + disabled?: boolean | undefined; +} + export const ModuleEmptyState: React.FC = observer((props) => { - const { workspaceSlug, projectId, moduleId } = props; + const { workspaceSlug, projectId, moduleId, activeLayout, handleClearAllFilters, isEmptyFilters = false } = props; // states const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false); + // theme + const { resolvedTheme } = useTheme(); // store hooks const { issues } = useIssues(EIssuesStoreType.MODULE); - const { commandPalette: { toggleCreateIssueModal }, } = useApplication(); const { setTrackElement } = useEventTracker(); const { membership: { currentProjectRole: userRole }, + currentUser, } = useUser(); // toast alert const { setToastAlert } = useToast(); @@ -55,8 +69,43 @@ export const ModuleEmptyState: React.FC = observer((props) => { ); }; + const emptyStateDetail = MODULE_EMPTY_STATE_DETAILS["no-issues"]; + + const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light"; + const currentLayoutEmptyStateImagePath = getEmptyStateImagePath("empty-filters", activeLayout ?? "list", isLightMode); + const emptyStateImage = getEmptyStateImagePath("module-issues", activeLayout ?? "list", isLightMode); + const isEditingAllowed = !!userRole && userRole >= EUserProjectRoles.MEMBER; + const emptyStateProps: EmptyStateProps = isEmptyFilters + ? { + title: EMPTY_FILTER_STATE_DETAILS["project"].title, + image: currentLayoutEmptyStateImagePath, + secondaryButton: { + text: EMPTY_FILTER_STATE_DETAILS["project"].secondaryButton.text, + onClick: handleClearAllFilters, + }, + } + : { + title: emptyStateDetail.title, + description: emptyStateDetail.description, + image: emptyStateImage, + primaryButton: { + text: emptyStateDetail.primaryButton.text, + icon: , + onClick: () => { + setTrackElement("Module issue empty state"); + toggleCreateIssueModal(true, EIssuesStoreType.MODULE); + }, + }, + secondaryButton: { + text: emptyStateDetail.secondaryButton.text, + icon: , + onClick: () => setModuleIssuesListModal(true), + }, + disabled: !isEditingAllowed, + }; + return ( <> = observer((props) => { projectId={projectId} isOpen={moduleIssuesListModal} handleClose={() => setModuleIssuesListModal(false)} - searchParams={{ module: moduleId != undefined ? [moduleId.toString()] : [] }} + searchParams={{ module: moduleId != undefined ? moduleId.toString() : "" }} handleOnSubmit={handleAddIssuesToModule} />
- , - onClick: () => { - setTrackElement("Module issue empty state"); - toggleCreateIssueModal(true, EIssuesStoreType.MODULE); - }, - }} - secondaryButton={ - - } - disabled={!isEditingAllowed} - /> +
); diff --git a/web/components/issues/issue-layouts/empty-states/project-issues.tsx b/web/components/issues/issue-layouts/empty-states/project-issues.tsx index 32a60e996..c7185934c 100644 --- a/web/components/issues/issue-layouts/empty-states/project-issues.tsx +++ b/web/components/issues/issue-layouts/empty-states/project-issues.tsx @@ -9,6 +9,7 @@ import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // constants import { EUserProjectRoles } from "constants/project"; import { EIssueFilterType, EIssuesStoreType } from "constants/issue"; +import { EMPTY_FILTER_STATE_DETAILS, EMPTY_ISSUE_STATE_DETAILS } from "constants/empty-state"; // types import { IIssueFilterOptions } from "@plane/types"; @@ -67,26 +68,23 @@ export const ProjectEmptyState: React.FC = observer(() => { const emptyStateProps: EmptyStateProps = issueFilterCount > 0 ? { - title: "No issues found matching the filters applied", + title: EMPTY_FILTER_STATE_DETAILS["project"].title, image: currentLayoutEmptyStateImagePath, secondaryButton: { - text: "Clear all filters", + text: EMPTY_FILTER_STATE_DETAILS["project"].secondaryButton.text, onClick: handleClearAllFilters, }, } : { - title: "Create an issue and assign it to someone, even yourself", - description: - "Think of issues as jobs, tasks, work, or JTBD. Which we like. An issue and its sub-issues are usually time-based actionables assigned to members of your team. Your team creates, assigns, and completes issues to move your project towards its goal.", + title: EMPTY_ISSUE_STATE_DETAILS["project"].title, + description: EMPTY_ISSUE_STATE_DETAILS["project"].description, image: EmptyStateImagePath, comicBox: { - title: "Issues are building blocks in Plane.", - description: - "Redesign the Plane UI, Rebrand the company, or Launch the new fuel injection system are examples of issues that likely have sub-issues.", + title: EMPTY_ISSUE_STATE_DETAILS["project"].comicBox.title, + description: EMPTY_ISSUE_STATE_DETAILS["project"].comicBox.description, }, primaryButton: { - text: "Create your first issue", - + text: EMPTY_ISSUE_STATE_DETAILS["project"].primaryButton.text, onClick: () => { setTrackElement("Project issue empty state"); commandPaletteStore.toggleCreateIssueModal(true, EIssuesStoreType.PROJECT); diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx index 827382da7..daa194c9d 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx @@ -36,22 +36,34 @@ export const CycleAppliedFiltersRoot: React.FC = observer(() => { const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => { if (!workspaceSlug || !projectId || !cycleId) return; if (!value) { - updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { - [key]: null, - }); + updateFilters( + workspaceSlug, + projectId, + EIssueFilterType.FILTERS, + { + [key]: null, + }, + cycleId + ); return; } let newValues = issueFilters?.filters?.[key] ?? []; newValues = newValues.filter((val) => val !== value); - updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { - [key]: newValues, - }); + updateFilters( + workspaceSlug, + projectId, + EIssueFilterType.FILTERS, + { + [key]: newValues, + }, + cycleId + ); }; const handleClearAllFilters = () => { - if (!workspaceSlug || !projectId) return; + if (!workspaceSlug || !projectId || !cycleId) return; const newFilters: IIssueFilterOptions = {}; Object.keys(userFilters ?? {}).forEach((key) => { newFilters[key as keyof IIssueFilterOptions] = null; diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx index b823a4bd1..055c32d20 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx @@ -33,24 +33,36 @@ export const ModuleAppliedFiltersRoot: React.FC = observer(() => { }); const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => { - if (!workspaceSlug || !projectId) return; + if (!workspaceSlug || !projectId || !moduleId) return; if (!value) { - updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { - [key]: null, - }); + updateFilters( + workspaceSlug, + projectId, + EIssueFilterType.FILTERS, + { + [key]: null, + }, + moduleId + ); return; } let newValues = issueFilters?.filters?.[key] ?? []; newValues = newValues.filter((val) => val !== value); - updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { - [key]: newValues, - }); + updateFilters( + workspaceSlug, + projectId, + EIssueFilterType.FILTERS, + { + [key]: newValues, + }, + moduleId + ); }; const handleClearAllFilters = () => { - if (!workspaceSlug || !projectId) return; + if (!workspaceSlug || !projectId || !moduleId) return; const newFilters: IIssueFilterOptions = {}; Object.keys(userFilters ?? {}).forEach((key) => { newFilters[key as keyof IIssueFilterOptions] = null; diff --git a/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx b/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx index 95729a103..b5f092aba 100644 --- a/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx +++ b/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx @@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite"; // hooks import { useIssues, useUser } from "hooks/store"; // components -import { IssueGanttBlock } from "components/issues"; +import { GanttQuickAddIssueForm, IssueGanttBlock } from "components/issues"; import { GanttChartRoot, IBlockUpdateData, @@ -70,21 +70,17 @@ export const BaseGanttRoot: React.FC = observer((props: IBaseGan blocks={issues ? renderIssueBlocksStructure(issues as TIssue[]) : null} blockUpdateHandler={updateIssueBlockStructure} blockToRender={(data: TIssue) => } - sidebarToRender={(props) => ( - - )} + sidebarToRender={(props) => } enableBlockLeftResize={isAllowed} enableBlockRightResize={isAllowed} enableBlockMove={isAllowed} enableReorder={appliedDisplayFilters?.order_by === "sort_order" && isAllowed} enableAddBlock={isAllowed} + quickAdd={ + enableIssueCreation && isAllowed ? ( + + ) : undefined + } showAllBlocks />
diff --git a/web/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx b/web/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx index 1ddd21ce2..7ed6a8730 100644 --- a/web/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx +++ b/web/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx @@ -159,7 +159,7 @@ export const GanttQuickAddIssueForm: React.FC = observe ) : (
-
+
{showLabelForm && (
{ )} {projectLabels ? ( projectLabels.length === 0 && !showLabelForm ? ( - newLabel(), - }} - /> +
+ +
) : ( projectLabelsTree && ( { // router @@ -34,23 +35,13 @@ export const ModulesListView: React.FC = observer(() => { const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; - if (loader) + if (loader || !projectModuleIds) return ( -
- -
- ); - - if (!projectModuleIds) - return ( - - - - - - - - + <> + {modulesView === "list" && } + {modulesView === "grid" && } + {modulesView === "gantt_chart" && } + ); return ( @@ -97,16 +88,15 @@ export const ModulesListView: React.FC = observer(() => { ) : ( { setTrackElement("Module empty state"); commandPaletteStore.toggleCreateModuleModal(true); diff --git a/web/components/notifications/notification-popover.tsx b/web/components/notifications/notification-popover.tsx index a8cdd4264..8496d1a35 100644 --- a/web/components/notifications/notification-popover.tsx +++ b/web/components/notifications/notification-popover.tsx @@ -9,7 +9,8 @@ import useOutsideClickDetector from "hooks/use-outside-click-detector"; // components import { EmptyState } from "components/common"; import { SnoozeNotificationModal, NotificationCard, NotificationHeader } from "components/notifications"; -import { Loader, Tooltip } from "@plane/ui"; +import { Tooltip } from "@plane/ui"; +import { NotificationsLoader } from "components/ui"; // images import emptyNotification from "public/empty-state/notification.svg"; // helpers @@ -188,13 +189,7 @@ export const NotificationPopover = observer(() => {
) ) : ( - - - - - - - + )} diff --git a/web/components/page-views/workspace-dashboard.tsx b/web/components/page-views/workspace-dashboard.tsx index 890cce27f..98e37ceca 100644 --- a/web/components/page-views/workspace-dashboard.tsx +++ b/web/components/page-views/workspace-dashboard.tsx @@ -14,6 +14,7 @@ import { Spinner } from "@plane/ui"; // constants import { EUserWorkspaceRoles } from "constants/workspace"; import { PRODUCT_TOUR_COMPLETED } from "constants/event-tracker"; +import { WORKSPACE_EMPTY_STATE_DETAILS } from "constants/empty-state"; export const WorkspaceDashboardView = observer(() => { // theme @@ -78,20 +79,18 @@ export const WorkspaceDashboardView = observer(() => { ) : ( { setTrackElement("Dashboard empty state"); toggleCreateProjectModal(true); }, }} comicBox={{ - title: "Everything starts with a project in Plane", - description: "A project could be a product’s roadmap, a marketing campaign, or launching a new car.", + title: WORKSPACE_EMPTY_STATE_DETAILS["dashboard"].comicBox.title, + description: WORKSPACE_EMPTY_STATE_DETAILS["dashboard"].comicBox.description, }} size="lg" disabled={!isEditingAllowed} diff --git a/web/components/pages/pages-list/list-view.tsx b/web/components/pages/pages-list/list-view.tsx index 4b5634736..2a5a1c677 100644 --- a/web/components/pages/pages-list/list-view.tsx +++ b/web/components/pages/pages-list/list-view.tsx @@ -11,7 +11,7 @@ import { PagesListItem } from "./list-item"; import { Loader } from "@plane/ui"; // constants import { EUserProjectRoles } from "constants/project"; -import { PAGE_EMPTY_STATE_DETAILS } from "constants/page"; +import { PAGE_EMPTY_STATE_DETAILS } from "constants/empty-state"; type IPagesListView = { pageIds: string[]; diff --git a/web/components/pages/pages-list/recent-pages-list.tsx b/web/components/pages/pages-list/recent-pages-list.tsx index e998629c6..71bbf12ac 100644 --- a/web/components/pages/pages-list/recent-pages-list.tsx +++ b/web/components/pages/pages-list/recent-pages-list.tsx @@ -13,6 +13,7 @@ import { Loader } from "@plane/ui"; import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; // constants import { EUserProjectRoles } from "constants/project"; +import { PAGE_EMPTY_STATE_DETAILS } from "constants/empty-state"; export const RecentPagesList: FC = observer(() => { // theme @@ -63,11 +64,11 @@ export const RecentPagesList: FC = observer(() => { ) : ( <> commandPaletteStore.toggleCreatePageModal(true), }} size="sm" diff --git a/web/components/profile/profile-issues.tsx b/web/components/profile/profile-issues.tsx index 81c9b141c..e7b61b4d2 100644 --- a/web/components/profile/profile-issues.tsx +++ b/web/components/profile/profile-issues.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import { useRouter } from "next/router"; import useSWR from "swr"; import { observer } from "mobx-react-lite"; @@ -7,14 +7,14 @@ import { useTheme } from "next-themes"; import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root"; import { ProfileIssuesKanBanLayout } from "components/issues/issue-layouts/kanban/roots/profile-issues-root"; import { IssuePeekOverview, ProfileIssuesAppliedFiltersRoot } from "components/issues"; -import { Spinner } from "@plane/ui"; +import { KanbanLayoutLoader, ListLayoutLoader } from "components/ui"; import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // hooks import { useIssues, useUser } from "hooks/store"; // constants import { EUserWorkspaceRoles } from "constants/workspace"; -import { PROFILE_EMPTY_STATE_DETAILS } from "constants/profile"; import { EIssuesStoreType } from "constants/issue"; +import { PROFILE_EMPTY_STATE_DETAILS } from "constants/empty-state"; interface IProfileIssuesPage { type: "assigned" | "subscribed" | "created"; @@ -36,10 +36,14 @@ export const ProfileIssuesPage = observer((props: IProfileIssuesPage) => { currentUser, } = useUser(); const { - issues: { loader, groupedIssueIds, fetchIssues }, + issues: { loader, groupedIssueIds, fetchIssues, setViewId }, issuesFilter: { issueFilters, fetchFilters }, } = useIssues(EIssuesStoreType.PROFILE); + useEffect(() => { + setViewId(type); + }, [type]); + useSWR( workspaceSlug && userId ? `CURRENT_WORKSPACE_PROFILE_ISSUES_${workspaceSlug}_${userId}_${type}` : null, async () => { @@ -57,38 +61,33 @@ export const ProfileIssuesPage = observer((props: IProfileIssuesPage) => { const isEditingAllowed = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; + if (!groupedIssueIds || loader === "init-loader") + return <>{activeLayout === "list" ? : }; + + if (groupedIssueIds.length === 0) { + return ( + + ); + } + return ( <> - {loader === "init-loader" ? ( -
- -
- ) : ( - <> - {groupedIssueIds ? ( - <> - -
- {activeLayout === "list" ? ( - - ) : activeLayout === "kanban" ? ( - - ) : null} -
- {/* peek overview */} - - - ) : ( - - )} - - )} + +
+ {activeLayout === "list" ? ( + + ) : activeLayout === "kanban" ? ( + + ) : null} +
+ {/* peek overview */} + ); }); diff --git a/web/components/project/card-list.tsx b/web/components/project/card-list.tsx index e7601ce35..7a5430554 100644 --- a/web/components/project/card-list.tsx +++ b/web/components/project/card-list.tsx @@ -4,10 +4,11 @@ import { useTheme } from "next-themes"; import { useApplication, useEventTracker, useProject, useUser } from "hooks/store"; // components import { ProjectCard } from "components/project"; -import { Loader } from "@plane/ui"; import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; +import { ProjectsLoader } from "components/ui"; // constants import { EUserWorkspaceRoles } from "constants/workspace"; +import { WORKSPACE_EMPTY_STATE_DETAILS } from "constants/empty-state"; export const ProjectCardList = observer(() => { // theme @@ -26,17 +27,7 @@ export const ProjectCardList = observer(() => { const isEditingAllowed = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; - if (!workspaceProjectIds) - return ( - - - - - - - - - ); + if (!workspaceProjectIds) return ; return ( <> @@ -59,18 +50,18 @@ export const ProjectCardList = observer(() => { ) : ( { setTrackElement("Project empty state"); commandPaletteStore.toggleCreateProjectModal(true); }, }} comicBox={{ - title: "Everything starts with a project in Plane", - description: "A project could be a product’s roadmap, a marketing campaign, or launching a new car.", + title: WORKSPACE_EMPTY_STATE_DETAILS["projects"].comicBox.title, + description: WORKSPACE_EMPTY_STATE_DETAILS["projects"].comicBox.description, }} size="lg" disabled={!isEditingAllowed} diff --git a/web/components/project/member-list.tsx b/web/components/project/member-list.tsx index fa8008c8f..d7b432445 100644 --- a/web/components/project/member-list.tsx +++ b/web/components/project/member-list.tsx @@ -6,7 +6,8 @@ import { useEventTracker, useMember } from "hooks/store"; // components import { ProjectMemberListItem, SendProjectInvitationModal } from "components/project"; // ui -import { Button, Loader } from "@plane/ui"; +import { Button } from "@plane/ui"; +import { MembersSettingsLoader } from "components/ui"; export const ProjectMemberList: React.FC = observer(() => { // states @@ -56,12 +57,7 @@ export const ProjectMemberList: React.FC = observer(() => {
{!projectMemberIds ? ( - - - - - - + ) : (
{projectMemberIds.length > 0 diff --git a/web/components/project/sidebar-list-item.tsx b/web/components/project/sidebar-list-item.tsx index d9cd81d5f..64134b242 100644 --- a/web/components/project/sidebar-list-item.tsx +++ b/web/components/project/sidebar-list-item.tsx @@ -18,7 +18,7 @@ import { MoreHorizontal, } from "lucide-react"; // hooks -import { useApplication,useEventTracker, useProject } from "hooks/store"; +import { useApplication, useEventTracker, useProject } from "hooks/store"; import useOutsideClickDetector from "hooks/use-outside-click-detector"; import useToast from "hooks/use-toast"; // helpers @@ -131,7 +131,7 @@ export const ProjectSidebarListItem: React.FC = observer((props) => { const handleProjectClick = () => { if (window.innerWidth < 768) { - themeStore.toggleSidebar(); + themeStore.toggleMobileSidebar(); } }; @@ -147,9 +147,8 @@ export const ProjectSidebarListItem: React.FC = observer((props) => { {({ open }) => ( <>
{provided && ( = observer((props) => { > @@ -101,10 +101,9 @@ export const WorkspaceHelpSection: React.FC = observe @@ -122,9 +121,8 @@ export const WorkspaceHelpSection: React.FC = observe leaveTo="transform opacity-0 scale-95" >
diff --git a/web/components/workspace/settings/members-list.tsx b/web/components/workspace/settings/members-list.tsx index 97f253afc..1dc02d508 100644 --- a/web/components/workspace/settings/members-list.tsx +++ b/web/components/workspace/settings/members-list.tsx @@ -7,7 +7,7 @@ import { useMember } from "hooks/store"; // components import { WorkspaceInvitationsListItem, WorkspaceMembersListItem } from "components/workspace"; // ui -import { Loader } from "@plane/ui"; +import { MembersSettingsLoader } from "components/ui"; export const WorkspaceMembersList: FC<{ searchQuery: string }> = observer((props) => { const { searchQuery } = props; @@ -30,15 +30,7 @@ export const WorkspaceMembersList: FC<{ searchQuery: string }> = observer((props workspaceSlug ? () => fetchWorkspaceMemberInvitations(workspaceSlug.toString()) : null ); - if (!workspaceMemberIds && !workspaceMemberInvitationIds) - return ( - - - - - - - ); + if (!workspaceMemberIds && !workspaceMemberInvitationIds) return ; // derived values const searchedMemberIds = getSearchedWorkspaceMemberIds(searchQuery); diff --git a/web/components/workspace/sidebar-dropdown.tsx b/web/components/workspace/sidebar-dropdown.tsx index 87bb4c868..7d4c6adec 100644 --- a/web/components/workspace/sidebar-dropdown.tsx +++ b/web/components/workspace/sidebar-dropdown.tsx @@ -54,7 +54,7 @@ export const WorkspaceSidebarDropdown = observer(() => { const { workspaceSlug } = router.query; // store hooks const { - theme: { sidebarCollapsed, toggleSidebar }, + theme: { sidebarCollapsed, toggleMobileSidebar }, } = useApplication(); const { setTrackElement } = useEventTracker(); const { currentUser, updateCurrentUser, isUserInstanceAdmin, signOut } = useUser(); @@ -98,7 +98,7 @@ export const WorkspaceSidebarDropdown = observer(() => { }; const handleItemClick = () => { if (window.innerWidth < 768) { - toggleSidebar(); + toggleMobileSidebar(); } }; const workspacesList = Object.values(workspaces ?? {}); @@ -110,15 +110,13 @@ export const WorkspaceSidebarDropdown = observer(() => { <>
{activeWorkspace?.logo && activeWorkspace.logo !== "" ? ( {
{!sidebarCollapsed && (
@@ -179,9 +176,8 @@ export const WorkspaceSidebarDropdown = observer(() => { >
{workspace?.logo && workspace.logo !== "" ? ( { )}
{workspace.name}
diff --git a/web/components/workspace/sidebar-menu.tsx b/web/components/workspace/sidebar-menu.tsx index 774a231db..9f3f5e1d6 100644 --- a/web/components/workspace/sidebar-menu.tsx +++ b/web/components/workspace/sidebar-menu.tsx @@ -31,7 +31,7 @@ export const WorkspaceSidebarMenu = observer(() => { const handleLinkClick = (itemKey: string) => { if (window.innerWidth < 768) { - themeStore.toggleSidebar(); + themeStore.toggleMobileSidebar(); } captureEvent(SIDEBAR_CLICKED, { destination: itemKey, @@ -52,11 +52,10 @@ export const WorkspaceSidebarMenu = observer(() => { disabled={!themeStore?.sidebarCollapsed} >
{ = observer((props) => { workspaceSlug ? () => fetchAllGlobalViews(workspaceSlug.toString()) : null ); - if (!currentWorkspaceViews) - return ( - - - - - - - ); + if (!currentWorkspaceViews) return ; const filteredViewsList = getSearchedViews(searchQuery); diff --git a/web/constants/cycle.ts b/web/constants/cycle.ts index 7a2c48634..63900b6b7 100644 --- a/web/constants/cycle.ts +++ b/web/constants/cycle.ts @@ -163,26 +163,4 @@ export const WORKSPACE_ACTIVE_CYCLES_DETAILS = [ }, ]; -export const CYCLE_EMPTY_STATE_DETAILS = { - active: { - key: "active", - title: "No active cycles", - description: - "An active cycle includes any period that encompasses today's date within its range. Find the progress and details of the active cycle here.", - }, - upcoming: { - key: "upcoming", - title: "No upcoming cycles", - description: "Upcoming cycles on deck! Just add dates to cycles in draft, and they'll show up right here.", - }, - completed: { - key: "completed", - title: "No completed cycles", - description: "Any cycle with a past due date is considered completed. Explore all completed cycles here.", - }, - draft: { - key: "draft", - title: "No draft cycles", - description: "No dates added in cycles? Find them here as drafts.", - }, -}; + diff --git a/web/constants/empty-state.ts b/web/constants/empty-state.ts new file mode 100644 index 000000000..eaf7f4b05 --- /dev/null +++ b/web/constants/empty-state.ts @@ -0,0 +1,366 @@ +// workspace empty state +export const WORKSPACE_EMPTY_STATE_DETAILS = { + dashboard: { + title: "Overview of your projects, activity, and metrics", + description: + " Welcome to Plane, we are excited to have you here. Create your first project and track your issues, and this page will transform into a space that helps you progress. Admins will also see items which help their team progress.", + primaryButton: { + text: "Build your first project", + }, + comicBox: { + title: "Everything starts with a project in Plane", + description: "A project could be a product’s roadmap, a marketing campaign, or launching a new car.", + }, + }, + analytics: { + title: "Track progress, workloads, and allocations. Spot trends, remove blockers, and move work faster", + description: + "See scope versus demand, estimates, and scope creep. Get performance by team members and teams, and make sure your project runs on time.", + primaryButton: { + text: "Create Cycles and Modules first", + }, + comicBox: { + title: "Analytics works best with Cycles + Modules", + description: + "First, timebox your issues into Cycles and, if you can, group issues that span more than a cycle into Modules. Check out both on the left nav.", + }, + }, + projects: { + title: "Start a Project", + description: + "Think of each project as the parent for goal-oriented work. Projects are where Jobs, Cycles, and Modules live and, along with your colleagues, help you achieve that goal.", + primaryButton: { + text: "Start your first project", + }, + comicBox: { + title: "Everything starts with a project in Plane", + description: "A project could be a product’s roadmap, a marketing campaign, or launching a new car.", + }, + }, + "assigned-notification": { + key: "assigned-notification", + title: "No issues assigned", + description: "Updates for issues assigned to you can be seen here", + }, + "created-notification": { + key: "created-notification", + title: "No updates to issues", + description: "Updates to issues created by you can be seen here", + }, + "subscribed-notification": { + key: "subscribed-notification", + title: "No updates to issues", + description: "Updates to any issue you are subscribed to can be seen here", + }, +}; + +export const ALL_ISSUES_EMPTY_STATE_DETAILS = { + "all-issues": { + key: "all-issues", + title: "No issues in the project", + description: "First project done! Now, slice your work into trackable pieces with issues. Let's go!", + }, + assigned: { + key: "assigned", + title: "No issues yet", + description: "Issues assigned to you can be tracked from here.", + }, + created: { + key: "created", + title: "No issues yet", + description: "All issues created by you come here, track them here directly.", + }, + subscribed: { + key: "subscribed", + title: "No issues yet", + description: "Subscribe to issues you are interested in, track all of them here.", + }, + "custom-view": { + key: "custom-view", + title: "No issues yet", + description: "Issues that applies to the filters, track all of them here.", + }, +}; + +export const SEARCH_EMPTY_STATE_DETAILS = { + views: { + key: "views", + title: "No matching views", + description: "No views match the search criteria. Create a new view instead.", + }, + projects: { + key: "projects", + title: "No matching projects", + description: "No projects detected with the matching criteria. Create a new project instead.", + }, + commandK: { + key: "commandK", + title: "No results found. ", + }, + members: { + key: "members", + title: "No matching members", + description: "Add them to the project if they are already a part of the workspace", + }, +}; + +export const WORKSPACE_SETTINGS_EMPTY_STATE_DETAILS = { + "api-tokens": { + key: "api-tokens", + title: "No API tokens created", + description: + "Plane APIs can be used to integrate your data in Plane with any external system. Create a token to get started.", + }, + webhooks: { + key: "webhooks", + title: "No webhooks added", + description: "Create webhooks to receive real-time updates and automate actions.", + }, + export: { + key: "export", + title: "No previous exports yet", + description: "Anytime you export, you will also have a copy here for reference.", + }, + import: { + key: "export", + title: "No previous imports yet", + description: "Find all your previous imports here and download them.", + }, +}; + +// profile empty state +export const PROFILE_EMPTY_STATE_DETAILS = { + assigned: { + key: "assigned", + title: "No issues are assigned to you", + description: "Issues assigned to you can be tracked from here.", + }, + subscribed: { + key: "created", + title: "No issues yet", + description: "All issues created by you come here, track them here directly.", + }, + created: { + key: "subscribed", + title: "No issues yet", + description: "Subscribe to issues you are interested in, track all of them here.", + }, +}; + +// project empty state + +export const PROJECT_SETTINGS_EMPTY_STATE_DETAILS = { + labels: { + key: "labels", + title: "No labels yet", + description: "Create labels to help organize and filter issues in you project.", + }, + integrations: { + key: "integrations", + title: "No integrations configured", + description: "Configure GitHub and other integrations to sync your project issues.", + }, + estimate: { + key: "estimate", + title: "No estimates added", + description: "Create a set of estimates to communicate the amount of work per issue.", + }, +}; + +export const CYCLE_EMPTY_STATE_DETAILS = { + cycles: { + title: "Group and timebox your work in Cycles.", + description: + "Break work down by timeboxed chunks, work backwards from your project deadline to set dates, and make tangible progress as a team.", + comicBox: { + title: "Cycles are repetitive time-boxes.", + description: + "A sprint, an iteration, and or any other term you use for weekly or fortnightly tracking of work is a cycle.", + }, + primaryButton: { + text: "Set your first cycle", + }, + }, + "no-issues": { + key: "no-issues", + title: "No issues added to the cycle", + description: "Add or create issues you wish to timebox and deliver within this cycle", + primaryButton: { + text: "Create new issue ", + }, + secondaryButton: { + text: "Add an existing issue", + }, + }, + active: { + key: "active", + title: "No active cycles", + description: + "An active cycle includes any period that encompasses today's date within its range. Find the progress and details of the active cycle here.", + }, + upcoming: { + key: "upcoming", + title: "No upcoming cycles", + description: "Upcoming cycles on deck! Just add dates to cycles in draft, and they'll show up right here.", + }, + completed: { + key: "completed", + title: "No completed cycles", + description: "Any cycle with a past due date is considered completed. Explore all completed cycles here.", + }, + draft: { + key: "draft", + title: "No draft cycles", + description: "No dates added in cycles? Find them here as drafts.", + }, +}; + +export const EMPTY_FILTER_STATE_DETAILS = { + archived: { + key: "archived", + title: "No issues found matching the filters applied", + secondaryButton: { + text: "Clear all filters", + }, + }, + draft: { + key: "draft", + title: "No issues found matching the filters applied", + secondaryButton: { + text: "Clear all filters", + }, + }, + project: { + key: "project", + title: "No issues found matching the filters applied", + secondaryButton: { + text: "Clear all filters", + }, + }, +}; + +export const EMPTY_ISSUE_STATE_DETAILS = { + archived: { + key: "archived", + title: "No archived issues yet", + description: + "Archived issues help you remove issues you completed or cancelled from focus. You can set automation to auto archive issues and find them here.", + primaryButton: { + text: "Set Automation", + }, + }, + draft: { + key: "draft", + title: "No draft issues yet", + description: + "Quickly stepping away but want to keep your place? No worries – save a draft now. Your issues will be right here waiting for you.", + }, + project: { + key: "project", + title: "Create an issue and assign it to someone, even yourself", + description: + "Think of issues as jobs, tasks, work, or JTBD. Which we like. An issue and its sub-issues are usually time-based actionables assigned to members of your team. Your team creates, assigns, and completes issues to move your project towards its goal.", + comicBox: { + title: "Issues are building blocks in Plane.", + description: + "Redesign the Plane UI, Rebrand the company, or Launch the new fuel injection system are examples of issues that likely have sub-issues.", + }, + primaryButton: { + text: "Create your first issue", + }, + }, +}; + +export const MODULE_EMPTY_STATE_DETAILS = { + "no-issues": { + key: "no-issues", + title: "No issues in the module", + description: "Create or add issues which you want to accomplish as part of this module", + primaryButton: { + text: "Create new issue ", + }, + secondaryButton: { + text: "Add an existing issue", + }, + }, + modules: { + title: "Map your project milestones to Modules and track aggregated work easily.", + description: + "A group of issues that belong to a logical, hierarchical parent form a module. Think of them as a way to track work by project milestones. They have their own periods and deadlines as well as analytics to help you see how close or far you are from a milestone.", + + comicBox: { + title: "Modules help group work by hierarchy.", + description: "A cart module, a chassis module, and a warehouse module are all good example of this grouping.", + }, + primaryButton: { + text: "Build your first module", + }, + }, +}; + +export const VIEW_EMPTY_STATE_DETAILS = { + "project-views": { + title: "Save filtered views for your project. Create as many as you need", + description: + "Views are a set of saved filters that you use frequently or want easy access to. All your colleagues in a project can see everyone’s views and choose whichever suits their needs best.", + comicBox: { + title: "Views work atop Issue properties.", + description: "You can create a view from here with as many properties as filters as you see fit.", + }, + primaryButton: { + text: "Create your first view", + }, + }, +}; + +export const PAGE_EMPTY_STATE_DETAILS = { + pages: { + key: "pages", + title: "Write a note, a doc, or a full knowledge base. Get Galileo, Plane’s AI assistant, to help you get started", + description: + "Pages are thoughts potting space in Plane. Take down meeting notes, format them easily, embed issues, lay them out using a library of components, and keep them all in your project’s context. To make short work of any doc, invoke Galileo, Plane’s AI, with a shortcut or the click of a button.", + primaryButton: { + text: "Create your first page", + }, + comicBox: { + title: "A page can be a doc or a doc of docs.", + description: + "We wrote Nikhil and Meera’s love story. You could write your project’s mission, goals, and eventual vision.", + }, + }, + All: { + key: "all", + title: "Write a note, a doc, or a full knowledge base", + description: + "Pages help you organise your thoughts to create wikis, discussions or even document heated takes for your project. Use it wisely!", + }, + Favorites: { + key: "favorites", + title: "No favorite pages yet", + description: "Favorites for quick access? mark them and find them right here.", + }, + Private: { + key: "private", + title: "No private pages yet", + description: "Keep your private thoughts here. When you're ready to share, the team's just a click away.", + }, + Shared: { + key: "shared", + title: "No shared pages yet", + description: "See pages shared with everyone in your project right here.", + }, + Archived: { + key: "archived", + title: "No archived pages yet", + description: "Archive pages not on your radar. Access them here when needed.", + }, + Recent: { + key: "recent", + title: "Write a note, a doc, or a full knowledge base", + description: + "Pages help you organise your thoughts to create wikis, discussions or even document heated takes for your project. Use it wisely! Pages will be sorted and grouped by last updated", + primaryButton: { + text: "Create new page", + }, + }, +}; diff --git a/web/constants/page.ts b/web/constants/page.ts index 4183d46d1..4b303ae73 100644 --- a/web/constants/page.ts +++ b/web/constants/page.ts @@ -52,38 +52,3 @@ export const PAGE_ACCESS_SPECIFIERS: { key: number; label: string; icon: any }[] icon: Lock, }, ]; - -export const PAGE_EMPTY_STATE_DETAILS = { - All: { - key: "all", - title: "Write a note, a doc, or a full knowledge base", - description: - "Pages help you organise your thoughts to create wikis, discussions or even document heated takes for your project. Use it wisely!", - }, - Favorites: { - key: "favorites", - title: "No favorite pages yet", - description: "Favorites for quick access? mark them and find them right here.", - }, - Private: { - key: "private", - title: "No private pages yet", - description: "Keep your private thoughts here. When you're ready to share, the team's just a click away.", - }, - Shared: { - key: "shared", - title: "No shared pages yet", - description: "See pages shared with everyone in your project right here.", - }, - Archived: { - key: "archived", - title: "No archived pages yet", - description: "Archive pages not on your radar. Access them here when needed.", - }, - Recent: { - key: "recent", - title: "Write a note, a doc, or a full knowledge base", - description: - "Pages help you organise your thoughts to create wikis, discussions or even document heated takes for your project. Use it wisely! Pages will be sorted and grouped by last updated", - }, -}; diff --git a/web/constants/profile.ts b/web/constants/profile.ts index 0fffdbc9b..463fd27ee 100644 --- a/web/constants/profile.ts +++ b/web/constants/profile.ts @@ -64,21 +64,3 @@ export const PROFILE_ADMINS_TAB = [ selected: "/[workspaceSlug]/profile/[userId]/subscribed", }, ]; - -export const PROFILE_EMPTY_STATE_DETAILS = { - assigned: { - key: "assigned", - title: "No issues are assigned to you", - description: "Issues assigned to you can be tracked from here.", - }, - subscribed: { - key: "created", - title: "No issues yet", - description: "All issues created by you come here, track them here directly.", - }, - created: { - key: "subscribed", - title: "No issues yet", - description: "Subscribe to issues you are interested in, track all of them here.", - }, -}; diff --git a/web/constants/workspace.ts b/web/constants/workspace.ts index 4e7a0d6ae..1471de395 100644 --- a/web/constants/workspace.ts +++ b/web/constants/workspace.ts @@ -190,31 +190,3 @@ export const WORKSPACE_SETTINGS_LINKS: { Icon: SettingIcon, }, ]; - -export const ALL_ISSUES_EMPTY_STATE_DETAILS = { - "all-issues": { - key: "all-issues", - title: "No issues in the project", - description: "First project done! Now, slice your work into trackable pieces with issues. Let's go!", - }, - assigned: { - key: "assigned", - title: "No issues yet", - description: "Issues assigned to you can be tracked from here.", - }, - created: { - key: "created", - title: "No issues yet", - description: "All issues created by you come here, track them here directly.", - }, - subscribed: { - key: "subscribed", - title: "No issues yet", - description: "Subscribe to issues you are interested in, track all of them here.", - }, - "custom-view": { - key: "custom-view", - title: "No issues yet", - description: "Issues that applies to the filters, track all of them here.", - }, -}; diff --git a/web/hooks/use-window-size.tsx b/web/hooks/use-window-size.tsx new file mode 100644 index 000000000..dbe44a777 --- /dev/null +++ b/web/hooks/use-window-size.tsx @@ -0,0 +1,19 @@ +import { useEffect, useState } from "react"; + +const useSize = () => { + const [windowSize, setWindowSize] = useState([window.innerWidth, window.innerHeight]); + + useEffect(() => { + const windowSizeHandler = () => { + setWindowSize([window.innerWidth, window.innerHeight]); + }; + window.addEventListener("resize", windowSizeHandler); + return () => { + window.removeEventListener("resize", windowSizeHandler); + }; + }, []); + + return windowSize; +}; + +export default useSize; diff --git a/web/layouts/app-layout/sidebar.tsx b/web/layouts/app-layout/sidebar.tsx index 0ccbf2606..8477525d4 100644 --- a/web/layouts/app-layout/sidebar.tsx +++ b/web/layouts/app-layout/sidebar.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useRef } from "react"; +import { FC, useRef } from "react"; import { observer } from "mobx-react-lite"; // components import { @@ -12,7 +12,7 @@ import { ProjectSidebarList } from "components/project"; import { useApplication } from "hooks/store"; import useOutsideClickDetector from "hooks/use-outside-click-detector"; -export interface IAppSidebar { } +export interface IAppSidebar {} export const AppSidebar: FC = observer(() => { // store hooks @@ -20,35 +20,19 @@ export const AppSidebar: FC = observer(() => { const ref = useRef(null); useOutsideClickDetector(ref, () => { - if (themStore.sidebarCollapsed === false) { + if (themStore.mobileSidebarCollapsed === false) { if (window.innerWidth < 768) { - themStore.toggleSidebar(); + themStore.toggleMobileSidebar(); } } }); - useEffect(() => { - const handleResize = () => { - if (window.innerWidth <= 768) { - themStore.toggleSidebar(true); - } - if (window.innerWidth > 768) { - themStore.toggleSidebar(false); - } - }; - handleResize(); - window.addEventListener("resize", handleResize); - return () => { - window.removeEventListener("resize", handleResize); - }; - }, [themStore]); - return (
= (props) => { const { children, header } = props; const router = useRouter(); + const { theme: themeStore } = useApplication(); const showMenuItem = () => { const item = router.asPath.split('/'); @@ -42,7 +44,7 @@ export const ProfilePreferenceSettingsLayout: FC - + themeStore.toggleSidebar()} /> { const { setToastAlert } = useToast(); // store hooks const { - theme: { sidebarCollapsed, toggleSidebar }, + theme: { sidebarCollapsed, toggleSidebar, toggleMobileSidebar }, } = useApplication(); const { currentUser, currentUserSettings, signOut } = useUser(); const { workspaces } = useWorkspace(); @@ -78,7 +78,7 @@ export const ProfileLayoutSidebar = observer(() => { const handleItemClick = () => { if (window.innerWidth < 768) { - toggleSidebar(); + toggleMobileSidebar(); } }; @@ -111,7 +111,7 @@ export const ProfileLayoutSidebar = observer(() => { `} >
- +
{ - const router = useRouter() - const { analytics_tab } = router.query + const router = useRouter(); + const { analytics_tab } = router.query; // theme const { resolvedTheme } = useTheme(); // store hooks @@ -41,18 +42,21 @@ const AnalyticsPage: NextPageWithLayout = observer(() => { <> {workspaceProjectIds && workspaceProjectIds.length > 0 ? (
- + {ANALYTICS_TABS.map((tab) => ( - `rounded-0 w-full md:w-max md:rounded-3xl border-b md:border border-custom-border-200 focus:outline-none px-0 md:px-4 py-2 text-xs hover:bg-custom-background-80 ${selected ? "border-custom-primary-100 text-custom-primary-100 md:bg-custom-background-80 md:text-custom-text-200 md:border-custom-border-200" : "border-transparent" + `rounded-0 w-full md:w-max md:rounded-3xl border-b md:border border-custom-border-200 focus:outline-none px-0 md:px-4 py-2 text-xs hover:bg-custom-background-80 ${ + selected + ? "border-custom-primary-100 text-custom-primary-100 md:bg-custom-background-80 md:text-custom-text-200 md:border-custom-border-200" + : "border-transparent" }` } onClick={() => { - router.query.analytics_tab = tab.key - router.push(router) + router.query.analytics_tab = tab.key; + router.push(router); }} > {tab.title} @@ -72,19 +76,18 @@ const AnalyticsPage: NextPageWithLayout = observer(() => { ) : ( { setTrackElement("Analytics empty state"); toggleCreateProjectModal(true); }, }} comicBox={{ - title: "Analytics works best with Cycles + Modules", - description: - "First, timebox your issues into Cycles and, if you can, group issues that span more than a cycle into Modules. Check out both on the left nav.", + title: WORKSPACE_EMPTY_STATE_DETAILS["analytics"].comicBox.title, + description: WORKSPACE_EMPTY_STATE_DETAILS["analytics"].comicBox.description, }} size="lg" disabled={!isEditingAllowed} diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx index 0541dfce4..0b9af62fd 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx @@ -13,13 +13,15 @@ import { CyclesHeader } from "components/headers"; import { CyclesView, ActiveCycleDetails, CycleCreateUpdateModal } from "components/cycles"; import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // ui -import { Spinner, Tooltip } from "@plane/ui"; +import { Tooltip } from "@plane/ui"; +import { CycleModuleBoardLayout, CycleModuleListLayout, GanttLayoutLoader } from "components/ui"; // types import { TCycleView, TCycleLayout } from "@plane/types"; import { NextPageWithLayout } from "lib/types"; // constants import { CYCLE_TAB_LIST, CYCLE_VIEW_LAYOUTS } from "constants/cycle"; import { EUserWorkspaceRoles } from "constants/workspace"; +import { CYCLE_EMPTY_STATE_DETAILS } from "constants/empty-state"; const ProjectCyclesPage: NextPageWithLayout = observer(() => { const [createModal, setCreateModal] = useState(false); @@ -65,9 +67,11 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => { if (loader) return ( -
- -
+ <> + {cycleLayout === "list" && } + {cycleLayout === "board" && } + {cycleLayout === "gantt" && } + ); return ( @@ -81,16 +85,15 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => { {totalCycles === 0 ? (
{ setTrackElement("Cycle empty state"); setCreateModal(true); @@ -114,7 +117,8 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => { - `border-b-2 p-4 text-sm font-medium outline-none ${selected ? "border-custom-primary-100 text-custom-primary-100" : "border-transparent" + `border-b-2 p-4 text-sm font-medium outline-none ${ + selected ? "border-custom-primary-100 text-custom-primary-100" : "border-transparent" }` } > @@ -132,14 +136,16 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => { diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx index 1f3204045..5dad4ede0 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx @@ -9,19 +9,21 @@ import { useTheme } from "next-themes"; import { useApplication, useEventTracker, useUser } from "hooks/store"; import useLocalStorage from "hooks/use-local-storage"; import useUserAuth from "hooks/use-user-auth"; +import useSize from "hooks/use-window-size"; // layouts import { AppLayout } from "layouts/app-layout"; // components import { RecentPagesList, CreateUpdatePageModal } from "components/pages"; import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; import { PagesHeader } from "components/headers"; -import { Spinner } from "@plane/ui"; +import { PagesLoader } from "components/ui"; // types import { NextPageWithLayout } from "lib/types"; // constants import { PAGE_TABS_LIST } from "constants/page"; import { useProjectPages } from "hooks/store/use-project-page"; import { EUserWorkspaceRoles } from "constants/workspace"; +import { PAGE_EMPTY_STATE_DETAILS } from "constants/empty-state"; const AllPagesList = dynamic(() => import("components/pages").then((a) => a.AllPagesList), { ssr: false, @@ -66,6 +68,7 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => { useProjectPages(); // hooks const {} = useUserAuth({ user: currentUser, isLoading: currentUserLoader }); + const [windowWidth] = useSize(); // local storage const { storedValue: pageTab, setValue: setPageTab } = useLocalStorage("pageTab", "Recent"); // fetching pages from API @@ -103,7 +106,7 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => { const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; - const mobileTabList = ( + const MobileTabList = () => (
{PAGE_TABS_LIST.map((tab) => ( @@ -122,12 +125,7 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => { ); - if (loader || archivedPageLoader) - return ( -
- -
- ); + if (loader || archivedPageLoader) return ; return ( <> @@ -166,25 +164,29 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => { } }} > -
{mobileTabList}
- -
- {PAGE_TABS_LIST.map((tab) => ( - - `rounded-full border px-5 py-1.5 text-sm outline-none ${ - selected - ? "border-custom-primary bg-custom-primary text-white" - : "border-custom-border-200 bg-custom-background-100 hover:bg-custom-background-90" - }` - } - > - {tab.title} - - ))} -
-
+ {windowWidth < 768 ? ( + + ) : ( + +
+ {PAGE_TABS_LIST.map((tab) => ( + + `rounded-full border px-5 py-1.5 text-sm outline-none ${ + selected + ? "border-custom-primary bg-custom-primary text-white" + : "border-custom-border-200 bg-custom-background-100 hover:bg-custom-background-90" + }` + } + > + {tab.title} + + ))} +
+
+ )} + @@ -211,19 +213,18 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => { ) : ( { setTrackElement("Pages empty state"); toggleCreatePageModal(true); }, }} comicBox={{ - title: "A page can be a doc or a doc of docs.", - description: - "We wrote Nikhil and Meera’s love story. You could write your project’s mission, goals, and eventual vision.", + title: PAGE_EMPTY_STATE_DETAILS["pages"].comicBox.title, + description: PAGE_EMPTY_STATE_DETAILS["pages"].comicBox.description, }} size="lg" disabled={!isEditingAllowed} diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx index 2a2322e31..bc90c110f 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx @@ -21,7 +21,7 @@ const EstimatesSettingsPage: NextPageWithLayout = observer(() => { const isAdmin = currentProjectRole === EUserProjectRoles.ADMIN; return ( -
+
); diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx index bc0163464..a44fbeaa4 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx @@ -1,6 +1,9 @@ import { ReactElement } from "react"; import { useRouter } from "next/router"; import useSWR from "swr"; +import { useTheme } from "next-themes"; +// hooks +import { useUser } from "hooks/store"; // layouts import { AppLayout } from "layouts/app-layout"; import { ProjectSettingLayout } from "layouts/settings-layout"; @@ -10,16 +13,15 @@ import { ProjectService } from "services/project"; // components import { IntegrationCard } from "components/project"; import { ProjectSettingHeader } from "components/headers"; +import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // ui -import { EmptyState } from "components/common"; -import { Loader } from "@plane/ui"; -// images -import emptyIntegration from "public/empty-state/integration.svg"; +import { IntegrationsSettingsLoader } from "components/ui"; // types import { IProject } from "@plane/types"; import { NextPageWithLayout } from "lib/types"; // fetch-keys import { PROJECT_DETAILS, WORKSPACE_INTEGRATIONS } from "constants/fetch-keys"; +import { PROJECT_SETTINGS_EMPTY_STATE_DETAILS } from "constants/empty-state"; // services const integrationService = new IntegrationService(); @@ -28,6 +30,10 @@ const projectService = new ProjectService(); const ProjectIntegrationsPage: NextPageWithLayout = () => { const router = useRouter(); const { workspaceSlug, projectId } = router.query; + // theme + const { resolvedTheme } = useTheme(); + // store hooks + const { currentUser } = useUser(); const { data: projectDetails } = useSWR( workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null, @@ -39,10 +45,14 @@ const ProjectIntegrationsPage: NextPageWithLayout = () => { () => (workspaceSlug ? integrationService.getWorkspaceIntegrationsList(workspaceSlug as string) : null) ); + const emptyStateDetail = PROJECT_SETTINGS_EMPTY_STATE_DETAILS["integrations"]; + const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light"; + const emptyStateImage = getEmptyStateImagePath("project-settings", "integrations", isLightMode); + const isAdmin = projectDetails?.member_role === 20; return ( -
+

Integrations

@@ -54,26 +64,22 @@ const ProjectIntegrationsPage: NextPageWithLayout = () => { ))}
) : ( -
+
router.push(`/${workspaceSlug}/settings/integrations`), }} + size="lg" disabled={!isAdmin} />
) ) : ( - - - - - - + )}
); diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx index edede146f..02a700bbe 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx @@ -9,7 +9,7 @@ import { ProjectSettingHeader } from "components/headers"; import { NextPageWithLayout } from "lib/types"; const LabelsSettingsPage: NextPageWithLayout = () => ( -
+
); diff --git a/web/pages/[workspaceSlug]/settings/api-tokens.tsx b/web/pages/[workspaceSlug]/settings/api-tokens.tsx index e7307c97d..3d65c2d7b 100644 --- a/web/pages/[workspaceSlug]/settings/api-tokens.tsx +++ b/web/pages/[workspaceSlug]/settings/api-tokens.tsx @@ -2,6 +2,7 @@ import React, { useState } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import useSWR from "swr"; +import { useTheme } from "next-themes"; // store hooks import { useUser } from "hooks/store"; // layouts @@ -9,9 +10,11 @@ import { AppLayout } from "layouts/app-layout"; import { WorkspaceSettingLayout } from "layouts/settings-layout"; // component import { WorkspaceSettingHeader } from "components/headers"; -import { ApiTokenEmptyState, ApiTokenListItem, CreateApiTokenModal } from "components/api-token"; +import { ApiTokenListItem, CreateApiTokenModal } from "components/api-token"; +import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // ui -import { Button, Spinner } from "@plane/ui"; +import { Button } from "@plane/ui"; +import { APITokenSettingsLoader } from "components/ui"; // services import { APITokenService } from "services/api_token.service"; // types @@ -19,6 +22,7 @@ import { NextPageWithLayout } from "lib/types"; // constants import { API_TOKENS_LIST } from "constants/fetch-keys"; import { EUserWorkspaceRoles } from "constants/workspace"; +import { WORKSPACE_SETTINGS_EMPTY_STATE_DETAILS } from "constants/empty-state"; const apiTokenService = new APITokenService(); @@ -28,9 +32,12 @@ const ApiTokensPage: NextPageWithLayout = observer(() => { // router const router = useRouter(); const { workspaceSlug } = router.query; + // theme + const { resolvedTheme } = useTheme(); // store hooks const { membership: { currentWorkspaceRole }, + currentUser, } = useUser(); const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN; @@ -39,6 +46,10 @@ const ApiTokensPage: NextPageWithLayout = observer(() => { workspaceSlug && isAdmin ? apiTokenService.getApiTokens(workspaceSlug.toString()) : null ); + const emptyStateDetail = WORKSPACE_SETTINGS_EMPTY_STATE_DETAILS["api-tokens"]; + const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light"; + const emptyStateImage = getEmptyStateImagePath("workspace-settings", "api-tokens", isLightMode); + if (!isAdmin) return (
@@ -46,36 +57,47 @@ const ApiTokensPage: NextPageWithLayout = observer(() => {
); + if (!tokens) { + return ; + } + return ( <> setIsCreateTokenModalOpen(false)} /> - {tokens ? ( -
- {tokens.length > 0 ? ( - <> -
-

API tokens

- -
-
- {tokens.map((token) => ( - - ))} -
- - ) : ( -
- setIsCreateTokenModalOpen(true)} /> +
+ {tokens.length > 0 ? ( + <> +
+

API tokens

+
- )} -
- ) : ( -
- -
- )} +
+ {tokens.map((token) => ( + + ))} +
+ + ) : ( +
+
+

API tokens

+ +
+
+ +
+
+ )} +
); }); diff --git a/web/pages/[workspaceSlug]/settings/integrations.tsx b/web/pages/[workspaceSlug]/settings/integrations.tsx index 70147c312..940c90f3a 100644 --- a/web/pages/[workspaceSlug]/settings/integrations.tsx +++ b/web/pages/[workspaceSlug]/settings/integrations.tsx @@ -13,8 +13,7 @@ import { WorkspaceSettingLayout } from "layouts/settings-layout"; import { SingleIntegrationCard } from "components/integration"; import { WorkspaceSettingHeader } from "components/headers"; // ui -import { IntegrationAndImportExportBanner } from "components/ui"; -import { Loader } from "@plane/ui"; +import { IntegrationAndImportExportBanner, IntegrationsSettingsLoader } from "components/ui"; // types import { NextPageWithLayout } from "lib/types"; // fetch-keys @@ -53,10 +52,7 @@ const WorkspaceIntegrationsPage: NextPageWithLayout = observer(() => { {appIntegrations ? ( appIntegrations.map((integration) => ) ) : ( - - - - + )}
diff --git a/web/pages/[workspaceSlug]/settings/webhooks/index.tsx b/web/pages/[workspaceSlug]/settings/webhooks/index.tsx index d8ba13f93..cafac485e 100644 --- a/web/pages/[workspaceSlug]/settings/webhooks/index.tsx +++ b/web/pages/[workspaceSlug]/settings/webhooks/index.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react"; import { useRouter } from "next/router"; import useSWR from "swr"; import { observer } from "mobx-react-lite"; +import { useTheme } from "next-themes"; // hooks import { useUser, useWebhook, useWorkspace } from "hooks/store"; // layouts @@ -9,11 +10,15 @@ import { AppLayout } from "layouts/app-layout"; import { WorkspaceSettingLayout } from "layouts/settings-layout"; // components import { WorkspaceSettingHeader } from "components/headers"; -import { WebhooksList, WebhooksEmptyState, CreateWebhookModal } from "components/web-hooks"; +import { WebhooksList, CreateWebhookModal } from "components/web-hooks"; +import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // ui -import { Button, Spinner } from "@plane/ui"; +import { Button } from "@plane/ui"; +import { WebhookSettingsLoader } from "components/ui"; // types import { NextPageWithLayout } from "lib/types"; +// constants +import { WORKSPACE_SETTINGS_EMPTY_STATE_DETAILS } from "constants/empty-state"; const WebhooksListPage: NextPageWithLayout = observer(() => { // states @@ -21,9 +26,12 @@ const WebhooksListPage: NextPageWithLayout = observer(() => { // router const router = useRouter(); const { workspaceSlug } = router.query; + // theme + const { resolvedTheme } = useTheme(); // mobx store const { membership: { currentWorkspaceRole }, + currentUser, } = useUser(); const { fetchWebhooks, webhooks, clearSecretKey, webhookSecretKey, createWebhook } = useWebhook(); const { currentWorkspace } = useWorkspace(); @@ -35,6 +43,11 @@ const WebhooksListPage: NextPageWithLayout = observer(() => { workspaceSlug && isAdmin ? () => fetchWebhooks(workspaceSlug.toString()) : null ); + const emptyStateDetail = WORKSPACE_SETTINGS_EMPTY_STATE_DETAILS["webhooks"]; + + const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light"; + const emptyStateImage = getEmptyStateImagePath("workspace-settings", "webhooks", isLightMode); + // clear secret key when modal is closed. useEffect(() => { if (!showCreateWebhookModal && webhookSecretKey) clearSecretKey(); @@ -47,12 +60,7 @@ const WebhooksListPage: NextPageWithLayout = observer(() => {
); - if (!webhooks) - return ( -
- -
- ); + if (!webhooks) return ; return (
@@ -76,8 +84,21 @@ const WebhooksListPage: NextPageWithLayout = observer(() => {
) : ( -
- setShowCreateWebhookModal(true)} /> +
+
+
Webhooks
+ +
+
+ +
)}
diff --git a/web/pages/profile/activity.tsx b/web/pages/profile/activity.tsx index d0c83ffdb..051127dd3 100644 --- a/web/pages/profile/activity.tsx +++ b/web/pages/profile/activity.tsx @@ -3,7 +3,7 @@ import useSWR from "swr"; import Link from "next/link"; import { observer } from "mobx-react"; //hooks -import { useUser } from "hooks/store"; +import { useApplication, useUser } from "hooks/store"; // services import { UserService } from "services/user.service"; // layouts @@ -14,7 +14,7 @@ import { RichReadOnlyEditor } from "@plane/rich-text-editor"; // icons import { History, MessageSquare } from "lucide-react"; // ui -import { Loader } from "@plane/ui"; +import { ActivitySettingsLoader } from "components/ui"; // fetch-keys import { USER_ACTIVITY } from "constants/fetch-keys"; // helper @@ -29,12 +29,12 @@ const ProfileActivityPage: NextPageWithLayout = observer(() => { const { data: userActivity } = useSWR(USER_ACTIVITY, () => userService.getUserActivity()); // store hooks const { currentUser } = useUser(); + const { theme: themeStore } = useApplication(); return ( -
- + themeStore.toggleSidebar()} />

Activity

{userActivity ? ( @@ -182,15 +182,9 @@ const ProfileActivityPage: NextPageWithLayout = observer(() => {
) : ( - - - - - - + )} - ); }); diff --git a/web/pages/profile/change-password.tsx b/web/pages/profile/change-password.tsx index 4641837fd..15cb946ed 100644 --- a/web/pages/profile/change-password.tsx +++ b/web/pages/profile/change-password.tsx @@ -3,7 +3,7 @@ import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { Controller, useForm } from "react-hook-form"; // hooks -import { useUser } from "hooks/store"; +import { useApplication, useUser } from "hooks/store"; // services import { UserService } from "services/user.service"; // hooks @@ -32,7 +32,8 @@ const userService = new UserService(); const ChangePasswordPage: NextPageWithLayout = observer(() => { const [isPageLoading, setIsPageLoading] = useState(true); - + // hooks + const { theme: themeStore } = useApplication(); const { currentUser } = useUser(); const router = useRouter(); @@ -89,90 +90,90 @@ const ChangePasswordPage: NextPageWithLayout = observer(() => { return (
- + themeStore.toggleSidebar()} />
-
-

Change password

-
-
-

Current password

- ( - - )} - /> - {errors.old_password && {errors.old_password.message}} + +

Change password

+
+
+

Current password

+ ( + + )} + /> + {errors.old_password && {errors.old_password.message}} +
+ +
+

New password

+ ( + + )} + /> + {errors.new_password && {errors.new_password.message}} +
+ +
+

Confirm password

+ ( + + )} + /> + {errors.confirm_password && {errors.confirm_password.message}} +
-
-

New password

- ( - - )} - /> - {errors.new_password && {errors.new_password.message}} +
+
- -
-

Confirm password

- ( - - )} - /> - {errors.confirm_password && {errors.confirm_password.message}} -
-
- -
- -
- +
); }); diff --git a/web/pages/profile/index.tsx b/web/pages/profile/index.tsx index 655d6a4bd..da8626dd1 100644 --- a/web/pages/profile/index.tsx +++ b/web/pages/profile/index.tsx @@ -5,7 +5,7 @@ import { observer } from "mobx-react-lite"; // services import { FileService } from "services/file.service"; // hooks -import { useUser } from "hooks/store"; +import { useApplication, useUser } from "hooks/store"; import useUserAuth from "hooks/use-user-auth"; import useToast from "hooks/use-toast"; // layouts @@ -58,6 +58,7 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => { const { currentUser: myProfile, updateCurrentUser, currentUserLoader } = useUser(); // custom hooks const { } = useUserAuth({ user: myProfile, isLoading: currentUserLoader }); + const { theme: themeStore } = useApplication(); useEffect(() => { reset({ ...defaultValues, ...myProfile }); @@ -139,7 +140,7 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => { <>
- + themeStore.toggleSidebar()} />
{ userService.currentUserEmailNotificationSettings() ); - if (isLoading) { - return ( - - - - - - ); - } - - if (!data) { - return null; + if (!data || isLoading) { + return ; } return ( diff --git a/web/public/empty-state/all-issues/all-issues-dark.webp b/web/public/empty-state/all-issues/all-issues-dark.webp index 2e7da76b3..6221d397f 100644 Binary files a/web/public/empty-state/all-issues/all-issues-dark.webp and b/web/public/empty-state/all-issues/all-issues-dark.webp differ diff --git a/web/public/empty-state/all-issues/all-issues-light.webp b/web/public/empty-state/all-issues/all-issues-light.webp index 6b5897bf9..bb7e86294 100644 Binary files a/web/public/empty-state/all-issues/all-issues-light.webp and b/web/public/empty-state/all-issues/all-issues-light.webp differ diff --git a/web/public/empty-state/all-issues/assigned-dark.webp b/web/public/empty-state/all-issues/assigned-dark.webp index 5e8e3916f..21b9162a5 100644 Binary files a/web/public/empty-state/all-issues/assigned-dark.webp and b/web/public/empty-state/all-issues/assigned-dark.webp differ diff --git a/web/public/empty-state/all-issues/assigned-light.webp b/web/public/empty-state/all-issues/assigned-light.webp index 6b04f3b30..8e9247850 100644 Binary files a/web/public/empty-state/all-issues/assigned-light.webp and b/web/public/empty-state/all-issues/assigned-light.webp differ diff --git a/web/public/empty-state/all-issues/created-dark.webp b/web/public/empty-state/all-issues/created-dark.webp index 6394e63f7..1e2b9abaf 100644 Binary files a/web/public/empty-state/all-issues/created-dark.webp and b/web/public/empty-state/all-issues/created-dark.webp differ diff --git a/web/public/empty-state/all-issues/created-light.webp b/web/public/empty-state/all-issues/created-light.webp index cf2b55dbb..17d33cd01 100644 Binary files a/web/public/empty-state/all-issues/created-light.webp and b/web/public/empty-state/all-issues/created-light.webp differ diff --git a/web/public/empty-state/all-issues/custom-view-dark.webp b/web/public/empty-state/all-issues/custom-view-dark.webp index aba847d79..2127ef965 100644 Binary files a/web/public/empty-state/all-issues/custom-view-dark.webp and b/web/public/empty-state/all-issues/custom-view-dark.webp differ diff --git a/web/public/empty-state/all-issues/custom-view-light.webp b/web/public/empty-state/all-issues/custom-view-light.webp index a531babb3..110a08c25 100644 Binary files a/web/public/empty-state/all-issues/custom-view-light.webp and b/web/public/empty-state/all-issues/custom-view-light.webp differ diff --git a/web/public/empty-state/all-issues/no-project-dark.webp b/web/public/empty-state/all-issues/no-project-dark.webp index 50c1ccf21..a6501a1ea 100644 Binary files a/web/public/empty-state/all-issues/no-project-dark.webp and b/web/public/empty-state/all-issues/no-project-dark.webp differ diff --git a/web/public/empty-state/all-issues/no-project-light.webp b/web/public/empty-state/all-issues/no-project-light.webp index 564c74ee5..d1f553248 100644 Binary files a/web/public/empty-state/all-issues/no-project-light.webp and b/web/public/empty-state/all-issues/no-project-light.webp differ diff --git a/web/public/empty-state/all-issues/subscribed-dark.webp b/web/public/empty-state/all-issues/subscribed-dark.webp index 6923b65e1..fbc5270e8 100644 Binary files a/web/public/empty-state/all-issues/subscribed-dark.webp and b/web/public/empty-state/all-issues/subscribed-dark.webp differ diff --git a/web/public/empty-state/all-issues/subscribed-light.webp b/web/public/empty-state/all-issues/subscribed-light.webp index d0411895b..216c42393 100644 Binary files a/web/public/empty-state/all-issues/subscribed-light.webp and b/web/public/empty-state/all-issues/subscribed-light.webp differ diff --git a/web/public/empty-state/archived/empty-issues-dark.webp b/web/public/empty-state/archived/empty-issues-dark.webp index 09d522d28..264488cbf 100644 Binary files a/web/public/empty-state/archived/empty-issues-dark.webp and b/web/public/empty-state/archived/empty-issues-dark.webp differ diff --git a/web/public/empty-state/archived/empty-issues-light.webp b/web/public/empty-state/archived/empty-issues-light.webp index 7aa422a4f..602f0b14f 100644 Binary files a/web/public/empty-state/archived/empty-issues-light.webp and b/web/public/empty-state/archived/empty-issues-light.webp differ diff --git a/web/public/empty-state/cycle-issues/calendar-dark-resp.webp b/web/public/empty-state/cycle-issues/calendar-dark-resp.webp new file mode 100644 index 000000000..8ef665909 Binary files /dev/null and b/web/public/empty-state/cycle-issues/calendar-dark-resp.webp differ diff --git a/web/public/empty-state/cycle-issues/calendar-dark.webp b/web/public/empty-state/cycle-issues/calendar-dark.webp new file mode 100644 index 000000000..1ad89607d Binary files /dev/null and b/web/public/empty-state/cycle-issues/calendar-dark.webp differ diff --git a/web/public/empty-state/cycle-issues/calendar-light-resp.webp b/web/public/empty-state/cycle-issues/calendar-light-resp.webp new file mode 100644 index 000000000..0706693b8 Binary files /dev/null and b/web/public/empty-state/cycle-issues/calendar-light-resp.webp differ diff --git a/web/public/empty-state/cycle-issues/calendar-light.webp b/web/public/empty-state/cycle-issues/calendar-light.webp new file mode 100644 index 000000000..c844daf5a Binary files /dev/null and b/web/public/empty-state/cycle-issues/calendar-light.webp differ diff --git a/web/public/empty-state/cycle-issues/gantt_chart-dark-resp.webp b/web/public/empty-state/cycle-issues/gantt_chart-dark-resp.webp new file mode 100644 index 000000000..d824ba289 Binary files /dev/null and b/web/public/empty-state/cycle-issues/gantt_chart-dark-resp.webp differ diff --git a/web/public/empty-state/cycle-issues/gantt_chart-dark.webp b/web/public/empty-state/cycle-issues/gantt_chart-dark.webp new file mode 100644 index 000000000..d03aac9e0 Binary files /dev/null and b/web/public/empty-state/cycle-issues/gantt_chart-dark.webp differ diff --git a/web/public/empty-state/cycle-issues/gantt_chart-light-resp.webp b/web/public/empty-state/cycle-issues/gantt_chart-light-resp.webp new file mode 100644 index 000000000..6309d9854 Binary files /dev/null and b/web/public/empty-state/cycle-issues/gantt_chart-light-resp.webp differ diff --git a/web/public/empty-state/cycle-issues/gantt_chart-light.webp b/web/public/empty-state/cycle-issues/gantt_chart-light.webp new file mode 100644 index 000000000..b77192863 Binary files /dev/null and b/web/public/empty-state/cycle-issues/gantt_chart-light.webp differ diff --git a/web/public/empty-state/cycle-issues/kanban-dark-resp.webp b/web/public/empty-state/cycle-issues/kanban-dark-resp.webp new file mode 100644 index 000000000..f2ce02bfa Binary files /dev/null and b/web/public/empty-state/cycle-issues/kanban-dark-resp.webp differ diff --git a/web/public/empty-state/cycle-issues/kanban-dark.webp b/web/public/empty-state/cycle-issues/kanban-dark.webp new file mode 100644 index 000000000..19785a8ba Binary files /dev/null and b/web/public/empty-state/cycle-issues/kanban-dark.webp differ diff --git a/web/public/empty-state/cycle-issues/kanban-light-resp.webp b/web/public/empty-state/cycle-issues/kanban-light-resp.webp new file mode 100644 index 000000000..355b007eb Binary files /dev/null and b/web/public/empty-state/cycle-issues/kanban-light-resp.webp differ diff --git a/web/public/empty-state/cycle-issues/kanban-light.webp b/web/public/empty-state/cycle-issues/kanban-light.webp new file mode 100644 index 000000000..c22f8308c Binary files /dev/null and b/web/public/empty-state/cycle-issues/kanban-light.webp differ diff --git a/web/public/empty-state/cycle-issues/list-dark-resp.webp b/web/public/empty-state/cycle-issues/list-dark-resp.webp new file mode 100644 index 000000000..c631e36bf Binary files /dev/null and b/web/public/empty-state/cycle-issues/list-dark-resp.webp differ diff --git a/web/public/empty-state/cycle-issues/list-dark.webp b/web/public/empty-state/cycle-issues/list-dark.webp new file mode 100644 index 000000000..89a83d9f8 Binary files /dev/null and b/web/public/empty-state/cycle-issues/list-dark.webp differ diff --git a/web/public/empty-state/cycle-issues/list-light-resp.webp b/web/public/empty-state/cycle-issues/list-light-resp.webp new file mode 100644 index 000000000..5d54bc6b3 Binary files /dev/null and b/web/public/empty-state/cycle-issues/list-light-resp.webp differ diff --git a/web/public/empty-state/cycle-issues/list-light.webp b/web/public/empty-state/cycle-issues/list-light.webp new file mode 100644 index 000000000..2769501a9 Binary files /dev/null and b/web/public/empty-state/cycle-issues/list-light.webp differ diff --git a/web/public/empty-state/cycle-issues/spreadsheet-dark-resp.webp b/web/public/empty-state/cycle-issues/spreadsheet-dark-resp.webp new file mode 100644 index 000000000..9c4ecbd87 Binary files /dev/null and b/web/public/empty-state/cycle-issues/spreadsheet-dark-resp.webp differ diff --git a/web/public/empty-state/cycle-issues/spreadsheet-dark.webp b/web/public/empty-state/cycle-issues/spreadsheet-dark.webp new file mode 100644 index 000000000..f4015a026 Binary files /dev/null and b/web/public/empty-state/cycle-issues/spreadsheet-dark.webp differ diff --git a/web/public/empty-state/cycle-issues/spreadsheet-light-resp.webp b/web/public/empty-state/cycle-issues/spreadsheet-light-resp.webp new file mode 100644 index 000000000..89c90d4e8 Binary files /dev/null and b/web/public/empty-state/cycle-issues/spreadsheet-light-resp.webp differ diff --git a/web/public/empty-state/cycle-issues/spreadsheet-light.webp b/web/public/empty-state/cycle-issues/spreadsheet-light.webp new file mode 100644 index 000000000..055638b42 Binary files /dev/null and b/web/public/empty-state/cycle-issues/spreadsheet-light.webp differ diff --git a/web/public/empty-state/cycle/active-dark.webp b/web/public/empty-state/cycle/active-dark.webp index 76de47194..336fd8173 100644 Binary files a/web/public/empty-state/cycle/active-dark.webp and b/web/public/empty-state/cycle/active-dark.webp differ diff --git a/web/public/empty-state/cycle/active-light.webp b/web/public/empty-state/cycle/active-light.webp index d5508c069..2ea9a8a88 100644 Binary files a/web/public/empty-state/cycle/active-light.webp and b/web/public/empty-state/cycle/active-light.webp differ diff --git a/web/public/empty-state/cycle/completed-dark.webp b/web/public/empty-state/cycle/completed-dark.webp index 9121d1f4d..eebe656d6 100644 Binary files a/web/public/empty-state/cycle/completed-dark.webp and b/web/public/empty-state/cycle/completed-dark.webp differ diff --git a/web/public/empty-state/cycle/completed-light.webp b/web/public/empty-state/cycle/completed-light.webp index c1799c34a..6c192fd35 100644 Binary files a/web/public/empty-state/cycle/completed-light.webp and b/web/public/empty-state/cycle/completed-light.webp differ diff --git a/web/public/empty-state/cycle/draft-dark.webp b/web/public/empty-state/cycle/draft-dark.webp index 251016532..887e2e916 100644 Binary files a/web/public/empty-state/cycle/draft-dark.webp and b/web/public/empty-state/cycle/draft-dark.webp differ diff --git a/web/public/empty-state/cycle/draft-light.webp b/web/public/empty-state/cycle/draft-light.webp index 7e809f362..d1c88bc37 100644 Binary files a/web/public/empty-state/cycle/draft-light.webp and b/web/public/empty-state/cycle/draft-light.webp differ diff --git a/web/public/empty-state/cycle/upcoming-dark.webp b/web/public/empty-state/cycle/upcoming-dark.webp index d412702c0..205164f48 100644 Binary files a/web/public/empty-state/cycle/upcoming-dark.webp and b/web/public/empty-state/cycle/upcoming-dark.webp differ diff --git a/web/public/empty-state/cycle/upcoming-light.webp b/web/public/empty-state/cycle/upcoming-light.webp index febf6ec89..522122f4a 100644 Binary files a/web/public/empty-state/cycle/upcoming-light.webp and b/web/public/empty-state/cycle/upcoming-light.webp differ diff --git a/web/public/empty-state/draft/draft-issues-empty-dark.webp b/web/public/empty-state/draft/draft-issues-empty-dark.webp new file mode 100644 index 000000000..0a3689022 Binary files /dev/null and b/web/public/empty-state/draft/draft-issues-empty-dark.webp differ diff --git a/web/public/empty-state/draft/draft-issues-empty-light.webp b/web/public/empty-state/draft/draft-issues-empty-light.webp new file mode 100644 index 000000000..c4e84a9d3 Binary files /dev/null and b/web/public/empty-state/draft/draft-issues-empty-light.webp differ diff --git a/web/public/empty-state/draft/empty-issues-dark.webp b/web/public/empty-state/draft/empty-issues-dark.webp deleted file mode 100644 index 0973e5290..000000000 Binary files a/web/public/empty-state/draft/empty-issues-dark.webp and /dev/null differ diff --git a/web/public/empty-state/draft/empty-issues-light.webp b/web/public/empty-state/draft/empty-issues-light.webp deleted file mode 100644 index 0ce0bb9f3..000000000 Binary files a/web/public/empty-state/draft/empty-issues-light.webp and /dev/null differ diff --git a/web/public/empty-state/empty-filters/calendar-dark.webp b/web/public/empty-state/empty-filters/calendar-dark.webp index 160e1d533..18fa5dfdd 100644 Binary files a/web/public/empty-state/empty-filters/calendar-dark.webp and b/web/public/empty-state/empty-filters/calendar-dark.webp differ diff --git a/web/public/empty-state/empty-filters/calendar-light.webp b/web/public/empty-state/empty-filters/calendar-light.webp index 0ab8f52c6..3c443cef8 100644 Binary files a/web/public/empty-state/empty-filters/calendar-light.webp and b/web/public/empty-state/empty-filters/calendar-light.webp differ diff --git a/web/public/empty-state/empty-filters/gantt_chart-dark.webp b/web/public/empty-state/empty-filters/gantt_chart-dark.webp index 861439ba4..0dd590ef7 100644 Binary files a/web/public/empty-state/empty-filters/gantt_chart-dark.webp and b/web/public/empty-state/empty-filters/gantt_chart-dark.webp differ diff --git a/web/public/empty-state/empty-filters/gantt_chart-light.webp b/web/public/empty-state/empty-filters/gantt_chart-light.webp index 22537f8c5..348d8a72c 100644 Binary files a/web/public/empty-state/empty-filters/gantt_chart-light.webp and b/web/public/empty-state/empty-filters/gantt_chart-light.webp differ diff --git a/web/public/empty-state/empty-filters/kanban-dark.webp b/web/public/empty-state/empty-filters/kanban-dark.webp index 7845a126f..8a742ab19 100644 Binary files a/web/public/empty-state/empty-filters/kanban-dark.webp and b/web/public/empty-state/empty-filters/kanban-dark.webp differ diff --git a/web/public/empty-state/empty-filters/kanban-light.webp b/web/public/empty-state/empty-filters/kanban-light.webp index e071a09a4..d0c8acf65 100644 Binary files a/web/public/empty-state/empty-filters/kanban-light.webp and b/web/public/empty-state/empty-filters/kanban-light.webp differ diff --git a/web/public/empty-state/empty-filters/list-dark.webp b/web/public/empty-state/empty-filters/list-dark.webp index 8d9f160b8..2edf6eecc 100644 Binary files a/web/public/empty-state/empty-filters/list-dark.webp and b/web/public/empty-state/empty-filters/list-dark.webp differ diff --git a/web/public/empty-state/empty-filters/list-light.webp b/web/public/empty-state/empty-filters/list-light.webp index 00bacf12e..e034115e7 100644 Binary files a/web/public/empty-state/empty-filters/list-light.webp and b/web/public/empty-state/empty-filters/list-light.webp differ diff --git a/web/public/empty-state/empty-filters/spreadsheet-dark.webp b/web/public/empty-state/empty-filters/spreadsheet-dark.webp index 3e86982e2..1d8a3d6be 100644 Binary files a/web/public/empty-state/empty-filters/spreadsheet-dark.webp and b/web/public/empty-state/empty-filters/spreadsheet-dark.webp differ diff --git a/web/public/empty-state/empty-filters/spreadsheet-light.webp b/web/public/empty-state/empty-filters/spreadsheet-light.webp index e9c6e3a02..b4ee9ee4c 100644 Binary files a/web/public/empty-state/empty-filters/spreadsheet-light.webp and b/web/public/empty-state/empty-filters/spreadsheet-light.webp differ diff --git a/web/public/empty-state/inbox/inbox-dark-resp.webp b/web/public/empty-state/inbox/inbox-dark-resp.webp new file mode 100644 index 000000000..690d3377c Binary files /dev/null and b/web/public/empty-state/inbox/inbox-dark-resp.webp differ diff --git a/web/public/empty-state/inbox/inbox-dark.webp b/web/public/empty-state/inbox/inbox-dark.webp new file mode 100644 index 000000000..fe3d48637 Binary files /dev/null and b/web/public/empty-state/inbox/inbox-dark.webp differ diff --git a/web/public/empty-state/inbox/inbox-light-resp.webp b/web/public/empty-state/inbox/inbox-light-resp.webp new file mode 100644 index 000000000..7bb5f04e6 Binary files /dev/null and b/web/public/empty-state/inbox/inbox-light-resp.webp differ diff --git a/web/public/empty-state/inbox/inbox-light.webp b/web/public/empty-state/inbox/inbox-light.webp new file mode 100644 index 000000000..83be70e0d Binary files /dev/null and b/web/public/empty-state/inbox/inbox-light.webp differ diff --git a/web/public/empty-state/module-issues/calendar-dark-resp.webp b/web/public/empty-state/module-issues/calendar-dark-resp.webp new file mode 100644 index 000000000..80d7637c5 Binary files /dev/null and b/web/public/empty-state/module-issues/calendar-dark-resp.webp differ diff --git a/web/public/empty-state/module-issues/calendar-dark.webp b/web/public/empty-state/module-issues/calendar-dark.webp new file mode 100644 index 000000000..18adf277c Binary files /dev/null and b/web/public/empty-state/module-issues/calendar-dark.webp differ diff --git a/web/public/empty-state/module-issues/calendar-light-resp.webp b/web/public/empty-state/module-issues/calendar-light-resp.webp new file mode 100644 index 000000000..acc31bc19 Binary files /dev/null and b/web/public/empty-state/module-issues/calendar-light-resp.webp differ diff --git a/web/public/empty-state/module-issues/calendar-light.webp b/web/public/empty-state/module-issues/calendar-light.webp new file mode 100644 index 000000000..f542f91e1 Binary files /dev/null and b/web/public/empty-state/module-issues/calendar-light.webp differ diff --git a/web/public/empty-state/module-issues/gantt-dark-resp.webp b/web/public/empty-state/module-issues/gantt-dark-resp.webp new file mode 100644 index 000000000..90bf1dd8f Binary files /dev/null and b/web/public/empty-state/module-issues/gantt-dark-resp.webp differ diff --git a/web/public/empty-state/module-issues/gantt-dark.webp b/web/public/empty-state/module-issues/gantt-dark.webp new file mode 100644 index 000000000..365ab04c7 Binary files /dev/null and b/web/public/empty-state/module-issues/gantt-dark.webp differ diff --git a/web/public/empty-state/module-issues/gantt-light-resp.webp b/web/public/empty-state/module-issues/gantt-light-resp.webp new file mode 100644 index 000000000..c160bf4a3 Binary files /dev/null and b/web/public/empty-state/module-issues/gantt-light-resp.webp differ diff --git a/web/public/empty-state/module-issues/gantt-light.webp b/web/public/empty-state/module-issues/gantt-light.webp new file mode 100644 index 000000000..19531a8eb Binary files /dev/null and b/web/public/empty-state/module-issues/gantt-light.webp differ diff --git a/web/public/empty-state/module-issues/kanban-dark-resp.webp b/web/public/empty-state/module-issues/kanban-dark-resp.webp new file mode 100644 index 000000000..d6fa66d3e Binary files /dev/null and b/web/public/empty-state/module-issues/kanban-dark-resp.webp differ diff --git a/web/public/empty-state/module-issues/kanban-dark.webp b/web/public/empty-state/module-issues/kanban-dark.webp new file mode 100644 index 000000000..ad1b9dcad Binary files /dev/null and b/web/public/empty-state/module-issues/kanban-dark.webp differ diff --git a/web/public/empty-state/module-issues/kanban-light-resp.webp b/web/public/empty-state/module-issues/kanban-light-resp.webp new file mode 100644 index 000000000..97dd429ed Binary files /dev/null and b/web/public/empty-state/module-issues/kanban-light-resp.webp differ diff --git a/web/public/empty-state/module-issues/kanban-light.webp b/web/public/empty-state/module-issues/kanban-light.webp new file mode 100644 index 000000000..cad700e03 Binary files /dev/null and b/web/public/empty-state/module-issues/kanban-light.webp differ diff --git a/web/public/empty-state/module-issues/list-dark-resp.webp b/web/public/empty-state/module-issues/list-dark-resp.webp new file mode 100644 index 000000000..f60248bfc Binary files /dev/null and b/web/public/empty-state/module-issues/list-dark-resp.webp differ diff --git a/web/public/empty-state/module-issues/list-dark.webp b/web/public/empty-state/module-issues/list-dark.webp new file mode 100644 index 000000000..3b00a2c2d Binary files /dev/null and b/web/public/empty-state/module-issues/list-dark.webp differ diff --git a/web/public/empty-state/module-issues/list-light-resp.webp b/web/public/empty-state/module-issues/list-light-resp.webp new file mode 100644 index 000000000..2226988ae Binary files /dev/null and b/web/public/empty-state/module-issues/list-light-resp.webp differ diff --git a/web/public/empty-state/module-issues/list-light.webp b/web/public/empty-state/module-issues/list-light.webp new file mode 100644 index 000000000..5d9735ee1 Binary files /dev/null and b/web/public/empty-state/module-issues/list-light.webp differ diff --git a/web/public/empty-state/module-issues/spreadsheet-dark-resp.webp b/web/public/empty-state/module-issues/spreadsheet-dark-resp.webp new file mode 100644 index 000000000..1beb57b09 Binary files /dev/null and b/web/public/empty-state/module-issues/spreadsheet-dark-resp.webp differ diff --git a/web/public/empty-state/module-issues/spreadsheet-dark.webp b/web/public/empty-state/module-issues/spreadsheet-dark.webp new file mode 100644 index 000000000..58698ed74 Binary files /dev/null and b/web/public/empty-state/module-issues/spreadsheet-dark.webp differ diff --git a/web/public/empty-state/module-issues/spreadsheet-light-resp.webp b/web/public/empty-state/module-issues/spreadsheet-light-resp.webp new file mode 100644 index 000000000..8f3a0679b Binary files /dev/null and b/web/public/empty-state/module-issues/spreadsheet-light-resp.webp differ diff --git a/web/public/empty-state/module-issues/spreadsheet-light.webp b/web/public/empty-state/module-issues/spreadsheet-light.webp new file mode 100644 index 000000000..a1735e1d1 Binary files /dev/null and b/web/public/empty-state/module-issues/spreadsheet-light.webp differ diff --git a/web/public/empty-state/onboarding/analytics-dark.webp b/web/public/empty-state/onboarding/analytics-dark.webp index f9ed54ae6..a8a1d9576 100644 Binary files a/web/public/empty-state/onboarding/analytics-dark.webp and b/web/public/empty-state/onboarding/analytics-dark.webp differ diff --git a/web/public/empty-state/onboarding/analytics-light.webp b/web/public/empty-state/onboarding/analytics-light.webp index ca0f5f551..76d5cfc28 100644 Binary files a/web/public/empty-state/onboarding/analytics-light.webp and b/web/public/empty-state/onboarding/analytics-light.webp differ diff --git a/web/public/empty-state/onboarding/archive-dark.png b/web/public/empty-state/onboarding/archive-dark.png new file mode 100644 index 000000000..54cca36ec Binary files /dev/null and b/web/public/empty-state/onboarding/archive-dark.png differ diff --git a/web/public/empty-state/onboarding/archive-light.png b/web/public/empty-state/onboarding/archive-light.png new file mode 100644 index 000000000..8530bbac1 Binary files /dev/null and b/web/public/empty-state/onboarding/archive-light.png differ diff --git a/web/public/empty-state/onboarding/cycles-dark.webp b/web/public/empty-state/onboarding/cycles-dark.webp index d655b5226..716f121d7 100644 Binary files a/web/public/empty-state/onboarding/cycles-dark.webp and b/web/public/empty-state/onboarding/cycles-dark.webp differ diff --git a/web/public/empty-state/onboarding/cycles-light.webp b/web/public/empty-state/onboarding/cycles-light.webp index ca069f50a..0b2b40088 100644 Binary files a/web/public/empty-state/onboarding/cycles-light.webp and b/web/public/empty-state/onboarding/cycles-light.webp differ diff --git a/web/public/empty-state/onboarding/dashboard-dark.webp b/web/public/empty-state/onboarding/dashboard-dark.webp index 486060c09..afa10bea7 100644 Binary files a/web/public/empty-state/onboarding/dashboard-dark.webp and b/web/public/empty-state/onboarding/dashboard-dark.webp differ diff --git a/web/public/empty-state/onboarding/dashboard-light.webp b/web/public/empty-state/onboarding/dashboard-light.webp index 89d97bf08..d4bce8bee 100644 Binary files a/web/public/empty-state/onboarding/dashboard-light.webp and b/web/public/empty-state/onboarding/dashboard-light.webp differ diff --git a/web/public/empty-state/onboarding/graph-dark.png b/web/public/empty-state/onboarding/graph-dark.png new file mode 100644 index 000000000..e8a938d8f Binary files /dev/null and b/web/public/empty-state/onboarding/graph-dark.png differ diff --git a/web/public/empty-state/onboarding/graph-light.png b/web/public/empty-state/onboarding/graph-light.png new file mode 100644 index 000000000..eb48b45a1 Binary files /dev/null and b/web/public/empty-state/onboarding/graph-light.png differ diff --git a/web/public/empty-state/onboarding/issues-closed-dark.png b/web/public/empty-state/onboarding/issues-closed-dark.png new file mode 100644 index 000000000..d4bafb28e Binary files /dev/null and b/web/public/empty-state/onboarding/issues-closed-dark.png differ diff --git a/web/public/empty-state/onboarding/issues-closed-light.png b/web/public/empty-state/onboarding/issues-closed-light.png new file mode 100644 index 000000000..739acd10d Binary files /dev/null and b/web/public/empty-state/onboarding/issues-closed-light.png differ diff --git a/web/public/empty-state/onboarding/issues-dark.webp b/web/public/empty-state/onboarding/issues-dark.webp index d1b1338a1..bbcf81b19 100644 Binary files a/web/public/empty-state/onboarding/issues-dark.webp and b/web/public/empty-state/onboarding/issues-dark.webp differ diff --git a/web/public/empty-state/onboarding/issues-light.webp b/web/public/empty-state/onboarding/issues-light.webp index b875a5eb0..0e67d81a3 100644 Binary files a/web/public/empty-state/onboarding/issues-light.webp and b/web/public/empty-state/onboarding/issues-light.webp differ diff --git a/web/public/empty-state/onboarding/members-dark.png b/web/public/empty-state/onboarding/members-dark.png new file mode 100644 index 000000000..2f8a1c873 Binary files /dev/null and b/web/public/empty-state/onboarding/members-dark.png differ diff --git a/web/public/empty-state/onboarding/members-light.png b/web/public/empty-state/onboarding/members-light.png new file mode 100644 index 000000000..eaa43ef90 Binary files /dev/null and b/web/public/empty-state/onboarding/members-light.png differ diff --git a/web/public/empty-state/onboarding/modules-dark.webp b/web/public/empty-state/onboarding/modules-dark.webp index ee86e7880..3e16dfc42 100644 Binary files a/web/public/empty-state/onboarding/modules-dark.webp and b/web/public/empty-state/onboarding/modules-dark.webp differ diff --git a/web/public/empty-state/onboarding/modules-light.webp b/web/public/empty-state/onboarding/modules-light.webp index 1eedadb6e..47d319fee 100644 Binary files a/web/public/empty-state/onboarding/modules-light.webp and b/web/public/empty-state/onboarding/modules-light.webp differ diff --git a/web/public/empty-state/onboarding/notification-dark.png b/web/public/empty-state/onboarding/notification-dark.png new file mode 100644 index 000000000..dee284ce6 Binary files /dev/null and b/web/public/empty-state/onboarding/notification-dark.png differ diff --git a/web/public/empty-state/onboarding/notification-light.png b/web/public/empty-state/onboarding/notification-light.png new file mode 100644 index 000000000..3065787e1 Binary files /dev/null and b/web/public/empty-state/onboarding/notification-light.png differ diff --git a/web/public/empty-state/onboarding/pages-dark.webp b/web/public/empty-state/onboarding/pages-dark.webp index 278d228df..def65dd51 100644 Binary files a/web/public/empty-state/onboarding/pages-dark.webp and b/web/public/empty-state/onboarding/pages-dark.webp differ diff --git a/web/public/empty-state/onboarding/projects-dark.webp b/web/public/empty-state/onboarding/projects-dark.webp index 2f34a02de..c5482908a 100644 Binary files a/web/public/empty-state/onboarding/projects-dark.webp and b/web/public/empty-state/onboarding/projects-dark.webp differ diff --git a/web/public/empty-state/onboarding/projects-light.webp b/web/public/empty-state/onboarding/projects-light.webp index 54894cbde..20206adfb 100644 Binary files a/web/public/empty-state/onboarding/projects-light.webp and b/web/public/empty-state/onboarding/projects-light.webp differ diff --git a/web/public/empty-state/onboarding/search-dark.png b/web/public/empty-state/onboarding/search-dark.png new file mode 100644 index 000000000..531081d43 Binary files /dev/null and b/web/public/empty-state/onboarding/search-dark.png differ diff --git a/web/public/empty-state/onboarding/search-light.png b/web/public/empty-state/onboarding/search-light.png new file mode 100644 index 000000000..39e263667 Binary files /dev/null and b/web/public/empty-state/onboarding/search-light.png differ diff --git a/web/public/empty-state/onboarding/snooze-light.png b/web/public/empty-state/onboarding/snooze-light.png new file mode 100644 index 000000000..6ccd7ab91 Binary files /dev/null and b/web/public/empty-state/onboarding/snooze-light.png differ diff --git a/web/public/empty-state/onboarding/snoozed-dark.png b/web/public/empty-state/onboarding/snoozed-dark.png new file mode 100644 index 000000000..c8b88fe56 Binary files /dev/null and b/web/public/empty-state/onboarding/snoozed-dark.png differ diff --git a/web/public/empty-state/onboarding/views-dark.webp b/web/public/empty-state/onboarding/views-dark.webp index bca0f383c..7ad4e6a87 100644 Binary files a/web/public/empty-state/onboarding/views-dark.webp and b/web/public/empty-state/onboarding/views-dark.webp differ diff --git a/web/public/empty-state/onboarding/views-light.webp b/web/public/empty-state/onboarding/views-light.webp index cd6900834..88c5967fb 100644 Binary files a/web/public/empty-state/onboarding/views-light.webp and b/web/public/empty-state/onboarding/views-light.webp differ diff --git a/web/public/empty-state/onboarding/workspace-invites-dark.webp b/web/public/empty-state/onboarding/workspace-invites-dark.webp new file mode 100644 index 000000000..d6e10046a Binary files /dev/null and b/web/public/empty-state/onboarding/workspace-invites-dark.webp differ diff --git a/web/public/empty-state/onboarding/workspace-invites-light.webp b/web/public/empty-state/onboarding/workspace-invites-light.webp new file mode 100644 index 000000000..123a600ef Binary files /dev/null and b/web/public/empty-state/onboarding/workspace-invites-light.webp differ diff --git a/web/public/empty-state/pages/all-dark.webp b/web/public/empty-state/pages/all-dark.webp index 3c9ea167a..9ce16f6d4 100644 Binary files a/web/public/empty-state/pages/all-dark.webp and b/web/public/empty-state/pages/all-dark.webp differ diff --git a/web/public/empty-state/pages/all-light.webp b/web/public/empty-state/pages/all-light.webp index 54b1b47d9..ad70d20d8 100644 Binary files a/web/public/empty-state/pages/all-light.webp and b/web/public/empty-state/pages/all-light.webp differ diff --git a/web/public/empty-state/pages/archived-dark.webp b/web/public/empty-state/pages/archived-dark.webp index 3a9543b54..38a9481cf 100644 Binary files a/web/public/empty-state/pages/archived-dark.webp and b/web/public/empty-state/pages/archived-dark.webp differ diff --git a/web/public/empty-state/pages/archived-light.webp b/web/public/empty-state/pages/archived-light.webp index 54cc928c9..0dbb1e854 100644 Binary files a/web/public/empty-state/pages/archived-light.webp and b/web/public/empty-state/pages/archived-light.webp differ diff --git a/web/public/empty-state/pages/favorites-dark.webp b/web/public/empty-state/pages/favorites-dark.webp index 9ae678870..c58b0aab3 100644 Binary files a/web/public/empty-state/pages/favorites-dark.webp and b/web/public/empty-state/pages/favorites-dark.webp differ diff --git a/web/public/empty-state/pages/favorites-light.webp b/web/public/empty-state/pages/favorites-light.webp index 88b62af4d..bd8d06132 100644 Binary files a/web/public/empty-state/pages/favorites-light.webp and b/web/public/empty-state/pages/favorites-light.webp differ diff --git a/web/public/empty-state/pages/private-dark.webp b/web/public/empty-state/pages/private-dark.webp index ac3e836b4..389849909 100644 Binary files a/web/public/empty-state/pages/private-dark.webp and b/web/public/empty-state/pages/private-dark.webp differ diff --git a/web/public/empty-state/pages/private-light.webp b/web/public/empty-state/pages/private-light.webp index 760e13657..9acf5dbcf 100644 Binary files a/web/public/empty-state/pages/private-light.webp and b/web/public/empty-state/pages/private-light.webp differ diff --git a/web/public/empty-state/pages/recent-dark.webp b/web/public/empty-state/pages/recent-dark.webp index 4a103354e..f96ef1564 100644 Binary files a/web/public/empty-state/pages/recent-dark.webp and b/web/public/empty-state/pages/recent-dark.webp differ diff --git a/web/public/empty-state/pages/recent-light.webp b/web/public/empty-state/pages/recent-light.webp index 4b908d398..9c8d64a4f 100644 Binary files a/web/public/empty-state/pages/recent-light.webp and b/web/public/empty-state/pages/recent-light.webp differ diff --git a/web/public/empty-state/pages/shared-dark.webp b/web/public/empty-state/pages/shared-dark.webp index 941960280..444886758 100644 Binary files a/web/public/empty-state/pages/shared-dark.webp and b/web/public/empty-state/pages/shared-dark.webp differ diff --git a/web/public/empty-state/pages/shared-light.webp b/web/public/empty-state/pages/shared-light.webp index a3ead55f3..03b3051b6 100644 Binary files a/web/public/empty-state/pages/shared-light.webp and b/web/public/empty-state/pages/shared-light.webp differ diff --git a/web/public/empty-state/profile/assigned-dark.webp b/web/public/empty-state/profile/assigned-dark.webp index eaec74dde..39b56fff9 100644 Binary files a/web/public/empty-state/profile/assigned-dark.webp and b/web/public/empty-state/profile/assigned-dark.webp differ diff --git a/web/public/empty-state/profile/assigned-light.webp b/web/public/empty-state/profile/assigned-light.webp index 59a7b06e3..2963fa41b 100644 Binary files a/web/public/empty-state/profile/assigned-light.webp and b/web/public/empty-state/profile/assigned-light.webp differ diff --git a/web/public/empty-state/profile/created-dark.webp b/web/public/empty-state/profile/created-dark.webp index 12f153519..f006ddf74 100644 Binary files a/web/public/empty-state/profile/created-dark.webp and b/web/public/empty-state/profile/created-dark.webp differ diff --git a/web/public/empty-state/profile/created-light.webp b/web/public/empty-state/profile/created-light.webp index f95679f11..446b8f450 100644 Binary files a/web/public/empty-state/profile/created-light.webp and b/web/public/empty-state/profile/created-light.webp differ diff --git a/web/public/empty-state/profile/subscribed-dark.webp b/web/public/empty-state/profile/subscribed-dark.webp index ae30d3d5d..15125f546 100644 Binary files a/web/public/empty-state/profile/subscribed-dark.webp and b/web/public/empty-state/profile/subscribed-dark.webp differ diff --git a/web/public/empty-state/profile/subscribed-light.webp b/web/public/empty-state/profile/subscribed-light.webp index d24f58f24..9204dca81 100644 Binary files a/web/public/empty-state/profile/subscribed-light.webp and b/web/public/empty-state/profile/subscribed-light.webp differ diff --git a/web/public/empty-state/project-settings/estimates-dark-resp.webp b/web/public/empty-state/project-settings/estimates-dark-resp.webp new file mode 100644 index 000000000..ff136236c Binary files /dev/null and b/web/public/empty-state/project-settings/estimates-dark-resp.webp differ diff --git a/web/public/empty-state/project-settings/estimates-dark.webp b/web/public/empty-state/project-settings/estimates-dark.webp new file mode 100644 index 000000000..35d42e52d Binary files /dev/null and b/web/public/empty-state/project-settings/estimates-dark.webp differ diff --git a/web/public/empty-state/project-settings/estimates-light-resp.webp b/web/public/empty-state/project-settings/estimates-light-resp.webp new file mode 100644 index 000000000..31daa2c19 Binary files /dev/null and b/web/public/empty-state/project-settings/estimates-light-resp.webp differ diff --git a/web/public/empty-state/project-settings/estimates-light.webp b/web/public/empty-state/project-settings/estimates-light.webp new file mode 100644 index 000000000..b329ea916 Binary files /dev/null and b/web/public/empty-state/project-settings/estimates-light.webp differ diff --git a/web/public/empty-state/project-settings/integrations-dark-resp.webp b/web/public/empty-state/project-settings/integrations-dark-resp.webp new file mode 100644 index 000000000..3f9e00956 Binary files /dev/null and b/web/public/empty-state/project-settings/integrations-dark-resp.webp differ diff --git a/web/public/empty-state/project-settings/integrations-dark.webp b/web/public/empty-state/project-settings/integrations-dark.webp new file mode 100644 index 000000000..603a658ef Binary files /dev/null and b/web/public/empty-state/project-settings/integrations-dark.webp differ diff --git a/web/public/empty-state/project-settings/integrations-light-resp.webp b/web/public/empty-state/project-settings/integrations-light-resp.webp new file mode 100644 index 000000000..13e08be84 Binary files /dev/null and b/web/public/empty-state/project-settings/integrations-light-resp.webp differ diff --git a/web/public/empty-state/project-settings/integrations-light.webp b/web/public/empty-state/project-settings/integrations-light.webp new file mode 100644 index 000000000..eb62503e0 Binary files /dev/null and b/web/public/empty-state/project-settings/integrations-light.webp differ diff --git a/web/public/empty-state/project-settings/labels-dark-resp.webp b/web/public/empty-state/project-settings/labels-dark-resp.webp new file mode 100644 index 000000000..fe961c061 Binary files /dev/null and b/web/public/empty-state/project-settings/labels-dark-resp.webp differ diff --git a/web/public/empty-state/project-settings/labels-dark.webp b/web/public/empty-state/project-settings/labels-dark.webp new file mode 100644 index 000000000..a5489d3ae Binary files /dev/null and b/web/public/empty-state/project-settings/labels-dark.webp differ diff --git a/web/public/empty-state/project-settings/labels-light-resp.webp b/web/public/empty-state/project-settings/labels-light-resp.webp new file mode 100644 index 000000000..f49009b71 Binary files /dev/null and b/web/public/empty-state/project-settings/labels-light-resp.webp differ diff --git a/web/public/empty-state/project-settings/labels-light.webp b/web/public/empty-state/project-settings/labels-light.webp new file mode 100644 index 000000000..eddb82f5f Binary files /dev/null and b/web/public/empty-state/project-settings/labels-light.webp differ diff --git a/web/public/empty-state/search/all-issue-view-dark.webp b/web/public/empty-state/search/all-issue-view-dark.webp new file mode 100644 index 000000000..5db15e165 Binary files /dev/null and b/web/public/empty-state/search/all-issue-view-dark.webp differ diff --git a/web/public/empty-state/search/all-issues-view-light.webp b/web/public/empty-state/search/all-issues-view-light.webp new file mode 100644 index 000000000..af44f0ed7 Binary files /dev/null and b/web/public/empty-state/search/all-issues-view-light.webp differ diff --git a/web/public/empty-state/search/member-dark.webp b/web/public/empty-state/search/member-dark.webp new file mode 100644 index 000000000..20521a5d5 Binary files /dev/null and b/web/public/empty-state/search/member-dark.webp differ diff --git a/web/public/empty-state/search/member-light.webp b/web/public/empty-state/search/member-light.webp new file mode 100644 index 000000000..dcd38c974 Binary files /dev/null and b/web/public/empty-state/search/member-light.webp differ diff --git a/web/public/empty-state/search/project-dark.webp b/web/public/empty-state/search/project-dark.webp new file mode 100644 index 000000000..c25806b5b Binary files /dev/null and b/web/public/empty-state/search/project-dark.webp differ diff --git a/web/public/empty-state/search/project-light.webp b/web/public/empty-state/search/project-light.webp new file mode 100644 index 000000000..2e4bbad4c Binary files /dev/null and b/web/public/empty-state/search/project-light.webp differ diff --git a/web/public/empty-state/workspace-settings/api-tokens-dark-resp.webp b/web/public/empty-state/workspace-settings/api-tokens-dark-resp.webp new file mode 100644 index 000000000..1ac7f6dd9 Binary files /dev/null and b/web/public/empty-state/workspace-settings/api-tokens-dark-resp.webp differ diff --git a/web/public/empty-state/workspace-settings/api-tokens-dark.webp b/web/public/empty-state/workspace-settings/api-tokens-dark.webp new file mode 100644 index 000000000..768e69233 Binary files /dev/null and b/web/public/empty-state/workspace-settings/api-tokens-dark.webp differ diff --git a/web/public/empty-state/workspace-settings/api-tokens-light-resp.webp b/web/public/empty-state/workspace-settings/api-tokens-light-resp.webp new file mode 100644 index 000000000..1ed0bf367 Binary files /dev/null and b/web/public/empty-state/workspace-settings/api-tokens-light-resp.webp differ diff --git a/web/public/empty-state/workspace-settings/api-tokens-light.webp b/web/public/empty-state/workspace-settings/api-tokens-light.webp new file mode 100644 index 000000000..537532c76 Binary files /dev/null and b/web/public/empty-state/workspace-settings/api-tokens-light.webp differ diff --git a/web/public/empty-state/workspace-settings/exports-dark-resp.webp b/web/public/empty-state/workspace-settings/exports-dark-resp.webp new file mode 100644 index 000000000..b175142b3 Binary files /dev/null and b/web/public/empty-state/workspace-settings/exports-dark-resp.webp differ diff --git a/web/public/empty-state/workspace-settings/exports-dark.webp b/web/public/empty-state/workspace-settings/exports-dark.webp new file mode 100644 index 000000000..beac5a82e Binary files /dev/null and b/web/public/empty-state/workspace-settings/exports-dark.webp differ diff --git a/web/public/empty-state/workspace-settings/exports-light-resp.webp b/web/public/empty-state/workspace-settings/exports-light-resp.webp new file mode 100644 index 000000000..65c4297b9 Binary files /dev/null and b/web/public/empty-state/workspace-settings/exports-light-resp.webp differ diff --git a/web/public/empty-state/workspace-settings/exports-light.webp b/web/public/empty-state/workspace-settings/exports-light.webp new file mode 100644 index 000000000..6d482cddb Binary files /dev/null and b/web/public/empty-state/workspace-settings/exports-light.webp differ diff --git a/web/public/empty-state/workspace-settings/imports-dark-resp.webp b/web/public/empty-state/workspace-settings/imports-dark-resp.webp new file mode 100644 index 000000000..5aefc4110 Binary files /dev/null and b/web/public/empty-state/workspace-settings/imports-dark-resp.webp differ diff --git a/web/public/empty-state/workspace-settings/imports-dark.webp b/web/public/empty-state/workspace-settings/imports-dark.webp new file mode 100644 index 000000000..65642f612 Binary files /dev/null and b/web/public/empty-state/workspace-settings/imports-dark.webp differ diff --git a/web/public/empty-state/workspace-settings/imports-light-resp.webp b/web/public/empty-state/workspace-settings/imports-light-resp.webp new file mode 100644 index 000000000..5a24ad681 Binary files /dev/null and b/web/public/empty-state/workspace-settings/imports-light-resp.webp differ diff --git a/web/public/empty-state/workspace-settings/imports-light.webp b/web/public/empty-state/workspace-settings/imports-light.webp new file mode 100644 index 000000000..fe0307065 Binary files /dev/null and b/web/public/empty-state/workspace-settings/imports-light.webp differ diff --git a/web/public/empty-state/workspace-settings/integrations-dark-resp.webp b/web/public/empty-state/workspace-settings/integrations-dark-resp.webp new file mode 100644 index 000000000..3f9e00956 Binary files /dev/null and b/web/public/empty-state/workspace-settings/integrations-dark-resp.webp differ diff --git a/web/public/empty-state/workspace-settings/integrations-dark.webp b/web/public/empty-state/workspace-settings/integrations-dark.webp new file mode 100644 index 000000000..603a658ef Binary files /dev/null and b/web/public/empty-state/workspace-settings/integrations-dark.webp differ diff --git a/web/public/empty-state/workspace-settings/integrations-light-resp.webp b/web/public/empty-state/workspace-settings/integrations-light-resp.webp new file mode 100644 index 000000000..13e08be84 Binary files /dev/null and b/web/public/empty-state/workspace-settings/integrations-light-resp.webp differ diff --git a/web/public/empty-state/workspace-settings/integrations-light.webp b/web/public/empty-state/workspace-settings/integrations-light.webp new file mode 100644 index 000000000..eb62503e0 Binary files /dev/null and b/web/public/empty-state/workspace-settings/integrations-light.webp differ diff --git a/web/public/empty-state/workspace-settings/webhooks-dark-resp.webp b/web/public/empty-state/workspace-settings/webhooks-dark-resp.webp new file mode 100644 index 000000000..dde3ca718 Binary files /dev/null and b/web/public/empty-state/workspace-settings/webhooks-dark-resp.webp differ diff --git a/web/public/empty-state/workspace-settings/webhooks-dark.webp b/web/public/empty-state/workspace-settings/webhooks-dark.webp new file mode 100644 index 000000000..9196c14af Binary files /dev/null and b/web/public/empty-state/workspace-settings/webhooks-dark.webp differ diff --git a/web/public/empty-state/workspace-settings/webhooks-light-resp.webp b/web/public/empty-state/workspace-settings/webhooks-light-resp.webp new file mode 100644 index 000000000..8ee7a8078 Binary files /dev/null and b/web/public/empty-state/workspace-settings/webhooks-light-resp.webp differ diff --git a/web/public/empty-state/workspace-settings/webhooks-light.webp b/web/public/empty-state/workspace-settings/webhooks-light.webp new file mode 100644 index 000000000..d51d58696 Binary files /dev/null and b/web/public/empty-state/workspace-settings/webhooks-light.webp differ diff --git a/web/store/application/theme.store.ts b/web/store/application/theme.store.ts index 505f3803c..3873e7386 100644 --- a/web/store/application/theme.store.ts +++ b/web/store/application/theme.store.ts @@ -6,12 +6,14 @@ import { applyTheme, unsetCustomCssVariables } from "helpers/theme.helper"; export interface IThemeStore { // observables theme: string | null; + mobileSidebarCollapsed: boolean | undefined; sidebarCollapsed: boolean | undefined; profileSidebarCollapsed: boolean | undefined; workspaceAnalyticsSidebarCollapsed: boolean | undefined; issueDetailSidebarCollapsed: boolean | undefined; // actions toggleSidebar: (collapsed?: boolean) => void; + toggleMobileSidebar: (collapsed?: boolean) => void; setTheme: (theme: any) => void; toggleProfileSidebar: (collapsed?: boolean) => void; toggleWorkspaceAnalyticsSidebar: (collapsed?: boolean) => void; @@ -20,6 +22,7 @@ export interface IThemeStore { export class ThemeStore implements IThemeStore { // observables + mobileSidebarCollapsed: boolean | undefined = true; sidebarCollapsed: boolean | undefined = undefined; theme: string | null = null; profileSidebarCollapsed: boolean | undefined = undefined; @@ -31,6 +34,7 @@ export class ThemeStore implements IThemeStore { constructor(_rootStore: any | null = null) { makeObservable(this, { // observable + mobileSidebarCollapsed: observable.ref, sidebarCollapsed: observable.ref, theme: observable.ref, profileSidebarCollapsed: observable.ref, @@ -38,6 +42,7 @@ export class ThemeStore implements IThemeStore { issueDetailSidebarCollapsed: observable.ref, // action toggleSidebar: action, + toggleMobileSidebar: action, setTheme: action, toggleProfileSidebar: action, toggleWorkspaceAnalyticsSidebar: action, @@ -61,6 +66,19 @@ export class ThemeStore implements IThemeStore { localStorage.setItem("app_sidebar_collapsed", this.sidebarCollapsed.toString()); }; + /** + * Toggle mobile sidebar collapsed state + * @param collapsed + */ + toggleMobileSidebar = (collapsed?: boolean) => { + if (collapsed === undefined) { + this.mobileSidebarCollapsed = !this.mobileSidebarCollapsed; + } else { + this.mobileSidebarCollapsed = collapsed; + } + localStorage.setItem("mobile_sidebar_collapsed", this.mobileSidebarCollapsed.toString()); + }; + /** * Toggle the profile sidebar collapsed state * @param collapsed @@ -88,13 +106,13 @@ export class ThemeStore implements IThemeStore { }; toggleIssueDetailSidebar = (collapsed?: boolean) => { - if(collapsed === undefined) { + if (collapsed === undefined) { this.issueDetailSidebarCollapsed = !this.issueDetailSidebarCollapsed; } else { this.issueDetailSidebarCollapsed = collapsed; } localStorage.setItem("issue_detail_sidebar_collapsed", this.issueDetailSidebarCollapsed.toString()); - } + }; /** * Sets the user theme and applies it to the platform diff --git a/web/store/issue/archived/issue.store.ts b/web/store/issue/archived/issue.store.ts index a31cdfef1..dca00d702 100644 --- a/web/store/issue/archived/issue.store.ts +++ b/web/store/issue/archived/issue.store.ts @@ -67,10 +67,11 @@ export class ArchivedIssues extends IssueHelperStore implements IArchivedIssues const orderBy = displayFilters?.order_by; const layout = displayFilters?.layout; - const archivedIssueIds = this.issues[projectId] ?? []; + const archivedIssueIds = this.issues[projectId]; + if (!archivedIssueIds) return undefined; const _issues = this.rootIssueStore.issues.getIssuesByIds(archivedIssueIds); - if (!_issues) return undefined; + if (!_issues) return []; let issues: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined = undefined; diff --git a/web/store/issue/draft/issue.store.ts b/web/store/issue/draft/issue.store.ts index dc0f601eb..5e42e9bab 100644 --- a/web/store/issue/draft/issue.store.ts +++ b/web/store/issue/draft/issue.store.ts @@ -78,10 +78,11 @@ export class DraftIssues extends IssueHelperStore implements IDraftIssues { const orderBy = displayFilters?.order_by; const layout = displayFilters?.layout; - const draftIssueIds = this.issues[projectId] ?? []; + const draftIssueIds = this.issues[projectId]; + if (!draftIssueIds) return undefined; const _issues = this.rootIssueStore.issues.getIssuesByIds(draftIssueIds); - if (!_issues) return undefined; + if (!_issues) return []; let issues: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined = undefined; diff --git a/web/store/issue/profile/issue.store.ts b/web/store/issue/profile/issue.store.ts index e754758cc..5cde37230 100644 --- a/web/store/issue/profile/issue.store.ts +++ b/web/store/issue/profile/issue.store.ts @@ -9,9 +9,7 @@ import { IIssueRootStore } from "../root.store"; import { TIssue, TLoader, TGroupedIssues, TSubGroupedIssues, TUnGroupedIssues, ViewFlags } from "@plane/types"; interface IProfileIssueTabTypes { - assigned: string[]; - created: string[]; - subscribed: string[]; + [key: string]: string[]; } export interface IProfileIssues { @@ -23,6 +21,7 @@ export interface IProfileIssues { groupedIssueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined; viewFlags: ViewFlags; // actions + setViewId: (viewId: "assigned" | "created" | "subscribed") => void; fetchIssues: ( workspaceSlug: string, projectId: string | undefined, @@ -73,6 +72,7 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues { groupedIssueIds: computed, viewFlags: computed, // action + setViewId: action.bound, fetchIssues: action, createIssue: action, updateIssue: action, @@ -86,8 +86,11 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues { get groupedIssueIds() { const userId = this.rootIssueStore.userId; + const workspaceSlug = this.rootIssueStore.workspaceSlug; const currentView = this.currentView; - if (!userId || !currentView) return undefined; + if (!userId || !currentView || !workspaceSlug) return undefined; + + const uniqueViewId = `${workspaceSlug}_${currentView}`; const displayFilters = this.rootIssueStore?.profileIssuesFilter?.issueFilters?.displayFilters; if (!displayFilters) return undefined; @@ -97,12 +100,12 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues { const orderBy = displayFilters?.order_by; const layout = displayFilters?.layout; - const userIssueIds = this.issues[userId]?.[currentView]; + const userIssueIds = this.issues[userId]?.[uniqueViewId]; if (!userIssueIds) return; const _issues = this.rootStore.issues.getIssuesByIds(userIssueIds); - if (!_issues) return undefined; + if (!_issues) return []; let issues: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined = undefined; @@ -131,6 +134,10 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues { }; } + setViewId(viewId: "assigned" | "created" | "subscribed") { + this.currentView = viewId; + } + fetchIssues = async ( workspaceSlug: string, projectId: string | undefined, @@ -145,6 +152,8 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues { this.loader = loadType; this.currentView = view; + const uniqueViewId = `${workspaceSlug}_${view}`; + let params: any = this.rootIssueStore?.profileIssuesFilter?.appliedFilters; params = { ...params, @@ -161,7 +170,7 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues { runInAction(() => { set( this.issues, - [userId, view], + [userId, uniqueViewId], response.map((issue) => issue.id) ); this.loader = undefined; @@ -187,8 +196,10 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues { const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data); + const uniqueViewId = `${workspaceSlug}_${this.currentView}`; + runInAction(() => { - this.issues[userId][this.currentView].push(response.id); + this.issues[userId][uniqueViewId].push(response.id); }); this.rootStore.issues.addIssue([response]); @@ -234,10 +245,12 @@ export class ProfileIssues extends IssueHelperStore implements IProfileIssues { try { const response = await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); - const issueIndex = this.issues[userId][this.currentView].findIndex((_issueId) => _issueId === issueId); + const uniqueViewId = `${workspaceSlug}_${this.currentView}`; + + const issueIndex = this.issues[userId][uniqueViewId].findIndex((_issueId) => _issueId === issueId); if (issueIndex >= 0) runInAction(() => { - this.issues[userId][this.currentView].splice(issueIndex, 1); + this.issues[userId][uniqueViewId].splice(issueIndex, 1); }); return response; diff --git a/web/store/issue/workspace/issue.store.ts b/web/store/issue/workspace/issue.store.ts index 7e317e5b4..e168f85c2 100644 --- a/web/store/issue/workspace/issue.store.ts +++ b/web/store/issue/workspace/issue.store.ts @@ -77,14 +77,17 @@ export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssue get groupedIssueIds() { const viewId = this.rootIssueStore.globalViewId; - if (!viewId) return { dataViewId: "", issueIds: undefined }; + const workspaceSlug = this.rootIssueStore.workspaceSlug; + if (!workspaceSlug || !viewId) return { dataViewId: "", issueIds: undefined }; + + const uniqueViewId = `${workspaceSlug}_${viewId}`; const displayFilters = this.rootIssueStore?.workspaceIssuesFilter?.filters?.[viewId]?.displayFilters; if (!displayFilters) return { dataViewId: viewId, issueIds: undefined }; const orderBy = displayFilters?.order_by; - const viewIssueIds = this.issues[viewId]; + const viewIssueIds = this.issues[uniqueViewId]; if (!viewIssueIds) return { dataViewId: viewId, issueIds: undefined }; @@ -102,13 +105,15 @@ export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssue try { this.loader = loadType; + const uniqueViewId = `${workspaceSlug}_${viewId}`; + const params = this.rootIssueStore?.workspaceIssuesFilter?.getAppliedFilters(viewId); const response = await this.workspaceService.getViewIssues(workspaceSlug, params); runInAction(() => { set( this.issues, - [viewId], + [uniqueViewId], response.map((issue) => issue.id) ); this.loader = undefined; @@ -133,10 +138,12 @@ export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssue try { if (!viewId) throw new Error("View id is required"); + const uniqueViewId = `${workspaceSlug}_${viewId}`; + const response = await this.issueService.createIssue(workspaceSlug, projectId, data); runInAction(() => { - this.issues[viewId].push(response.id); + this.issues[uniqueViewId].push(response.id); }); this.rootStore.issues.addIssue([response]); @@ -175,12 +182,14 @@ export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssue try { if (!viewId) throw new Error("View id is required"); + const uniqueViewId = `${workspaceSlug}_${viewId}`; + const response = await this.issueService.deleteIssue(workspaceSlug, projectId, issueId); - const issueIndex = this.issues[viewId].findIndex((_issueId) => _issueId === issueId); + const issueIndex = this.issues[uniqueViewId].findIndex((_issueId) => _issueId === issueId); if (issueIndex >= 0) runInAction(() => { - this.issues[viewId].splice(issueIndex, 1); + this.issues[uniqueViewId].splice(issueIndex, 1); }); this.rootStore.issues.removeIssue(issueId); diff --git a/yarn.lock b/yarn.lock index 291c710bd..1f9b26965 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5012,7 +5012,7 @@ fault@^2.0.0: dependencies: format "^0.2.0" -fflate@^0.4.1: +fflate@^0.4.8: version "0.4.8" resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae" integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== @@ -7171,12 +7171,18 @@ postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.29: picocolors "^1.0.0" source-map-js "^1.0.2" -posthog-js@^1.88.4: - version "1.96.1" - resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.96.1.tgz#4f9719a24e4e14037b0e72d430194d7cdb576447" - integrity sha512-kv1vQqYMt2BV3YHS+wxsbGuP+tz+M3y1AzNhz8TfkpY1HT8W/ONT0i0eQpeRr9Y+d4x/fZ6M4cXG5GMvi9lRCA== +posthog-js@^1.105.0: + version "1.105.8" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.105.8.tgz#934602f0c7a5e522a25828062b5841ad8780756f" + integrity sha512-zKZKNVLLQQgkJyY3DnzHHTasu6x4xM4MOvH7UbMz6BmrgUPboS6/3akgz+WKD+JV6qFj68bm80iJw0Jtj+pt8Q== dependencies: - fflate "^0.4.1" + fflate "^0.4.8" + preact "^10.19.3" + +preact@^10.19.3: + version "10.19.4" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.19.4.tgz#735d331d5b1bd2182cc36f2ba481fd6f0da3fe3b" + integrity sha512-dwaX5jAh0Ga8uENBX1hSOujmKWgx9RtL80KaKUFLc6jb4vCEAc3EeZ0rnQO/FO4VgjfPMfoLFWnNG8bHuZ9VLw== prebuild-install@^7.1.1: version "7.1.1"