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/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/exporter/guide.tsx b/web/components/exporter/guide.tsx index a98381ebd..ed6a39220 100644 --- a/web/components/exporter/guide.tsx +++ b/web/components/exporter/guide.tsx @@ -15,7 +15,8 @@ import { IntegrationService } from "services/integrations"; 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 @@ -158,12 +159,7 @@ const IntegrationGuide = observer(() => { ) ) : ( - - - - - - + )} diff --git a/web/components/integration/guide.tsx b/web/components/integration/guide.tsx index ba9e61607..29dfa5b30 100644 --- a/web/components/integration/guide.tsx +++ b/web/components/integration/guide.tsx @@ -14,7 +14,8 @@ import { IntegrationService } from "services/integrations"; 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 @@ -153,12 +154,7 @@ const IntegrationGuide = observer(() => { ) ) : ( - - - - - - + )} 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/issue-layouts/empty-states/module.tsx b/web/components/issues/issue-layouts/empty-states/module.tsx index 7508ad1f5..4285368e9 100644 --- a/web/components/issues/issue-layouts/empty-states/module.tsx +++ b/web/components/issues/issue-layouts/empty-states/module.tsx @@ -70,7 +70,7 @@ export const ModuleEmptyState: React.FC = observer((props) => { projectId={projectId} isOpen={moduleIssuesListModal} handleClose={() => setModuleIssuesListModal(false)} - searchParams={{ module: moduleId != undefined ? [moduleId.toString()] : [] }} + searchParams={{ module: moduleId != undefined ? moduleId.toString() : "" }} handleOnSubmit={handleAddIssuesToModule} />
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/kanban/block.tsx b/web/components/issues/issue-layouts/kanban/block.tsx index 24cbe9908..6102ce0dd 100644 --- a/web/components/issues/issue-layouts/kanban/block.tsx +++ b/web/components/issues/issue-layouts/kanban/block.tsx @@ -138,14 +138,14 @@ export const KanbanIssueBlock: React.FC = memo((props) => { >
= observer((props) => { const { setToastAlert } = useToast(); const renderExistingIssueModal = moduleId || cycleId; - const ExistingIssuesListModalPayload = moduleId ? { module: [moduleId.toString()] } : { cycle: true }; + const ExistingIssuesListModalPayload = moduleId ? { module: moduleId.toString() } : { cycle: true }; const handleAddIssuesToView = async (data: ISearchIssueResponse[]) => { if (!workspaceSlug || !projectId) return; diff --git a/web/components/issues/issue-layouts/kanban/utils.ts b/web/components/issues/issue-layouts/kanban/utils.ts index 064b4c23f..617598524 100644 --- a/web/components/issues/issue-layouts/kanban/utils.ts +++ b/web/components/issues/issue-layouts/kanban/utils.ts @@ -67,10 +67,13 @@ export const handleDragDrop = async ( let updateIssue: any = {}; - const sourceColumnId = (source?.droppableId && source?.droppableId.split("__")) || null; - const destinationColumnId = (destination?.droppableId && destination?.droppableId.split("__")) || null; + const sourceDroppableId = source?.droppableId; + const destinationDroppableId = destination?.droppableId; - if (!sourceColumnId || !destinationColumnId) return; + const sourceColumnId = (sourceDroppableId && sourceDroppableId.split("__")) || null; + const destinationColumnId = (destinationDroppableId && destinationDroppableId.split("__")) || null; + + if (!sourceColumnId || !destinationColumnId || !sourceDroppableId || !destinationDroppableId) return; const sourceGroupByColumnId = sourceColumnId[0] || null; const destinationGroupByColumnId = destinationColumnId[0] || null; @@ -123,7 +126,11 @@ export const handleDragDrop = async ( // for both horizontal and vertical dnd updateIssue = { ...updateIssue, - ...handleSortOrder(destinationIssues, destination.index, issueMap), + ...handleSortOrder( + sourceDroppableId === destinationDroppableId ? sourceIssues : destinationIssues, + destination.index, + issueMap + ), }; if (subGroupBy && sourceSubGroupByColumnId && destinationSubGroupByColumnId) { diff --git a/web/components/issues/issue-layouts/list/headers/group-by-card.tsx b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx index 90270e1a1..5a6b3c462 100644 --- a/web/components/issues/issue-layouts/list/headers/group-by-card.tsx +++ b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx @@ -41,7 +41,7 @@ export const HeaderGroupByCard = observer( const { setToastAlert } = useToast(); const renderExistingIssueModal = moduleId || cycleId; - const ExistingIssuesListModalPayload = moduleId ? { module: [moduleId.toString()] } : { cycle: true }; + const ExistingIssuesListModalPayload = moduleId ? { module: moduleId.toString() } : { cycle: true }; const handleAddIssuesToView = async (data: ISearchIssueResponse[]) => { if (!workspaceSlug || !projectId) return; diff --git a/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx index d1e9e1172..59cf5b9af 100644 --- a/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo } from "react"; +import React, { Fragment, useCallback, useMemo } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import useSWR from "swr"; @@ -12,8 +12,7 @@ import { GlobalViewsAppliedFiltersRoot, IssuePeekOverview } from "components/iss import { SpreadsheetView } from "components/issues/issue-layouts"; import { AllIssueQuickActions } from "components/issues/issue-layouts/quick-action-dropdowns"; import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; -// ui -import { Spinner } from "@plane/ui"; +import { SpreadsheetLayoutLoader } from "components/ui"; // types import { TIssue, IIssueDisplayFilterOptions } from "@plane/types"; import { EIssueActions } from "../types"; @@ -178,66 +177,62 @@ export const AllIssueLayoutRoot: React.FC = observer(() => { const isEditingAllowed = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; + if (loader === "init-loader" || !globalViewId || globalViewId !== dataViewId || !issueIds) { + return ; + } + return (
- {!globalViewId || globalViewId !== dataViewId || loader === "init-loader" || !issueIds ? ( -
- -
- ) : ( - <> - - - {(issueIds ?? {}).length == 0 ? ( - 0 ? currentViewDetails.title : "No project"} - description={ - (workspaceProjectIds ?? []).length > 0 - ? currentViewDetails.description - : "To create issues or manage your work, you need to create a project or be a part of one." - } - size="sm" - primaryButton={ - (workspaceProjectIds ?? []).length > 0 - ? currentView !== "custom-view" && currentView !== "subscribed" - ? { - text: "Create new issue", - onClick: () => { - setTrackElement("All issues empty state"); - commandPaletteStore.toggleCreateIssueModal(true, EIssuesStoreType.PROJECT); - }, - } - : undefined - : { - text: "Start your first project", +
+ + {issueIds.length === 0 ? ( + 0 ? currentViewDetails.title : "No project"} + description={ + (workspaceProjectIds ?? []).length > 0 + ? currentViewDetails.description + : "To create issues or manage your work, you need to create a project or be a part of one." + } + size="sm" + primaryButton={ + (workspaceProjectIds ?? []).length > 0 + ? currentView !== "custom-view" && currentView !== "subscribed" + ? { + text: "Create new issue", onClick: () => { setTrackElement("All issues empty state"); - commandPaletteStore.toggleCreateProjectModal(true); + commandPaletteStore.toggleCreateIssueModal(true, EIssuesStoreType.PROJECT); }, } - } - disabled={!isEditingAllowed} + : undefined + : { + text: "Start your first project", + onClick: () => { + setTrackElement("All issues empty state"); + commandPaletteStore.toggleCreateProjectModal(true); + }, + } + } + disabled={!isEditingAllowed} + /> + ) : ( + + - ) : ( -
- -
- )} - - )} - - {/* peek overview */} - + {/* peek overview */} + +
+ )} +
); }); diff --git a/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx b/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx index 430383a9f..5f049d4c3 100644 --- a/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { Fragment } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import useSWR from "swr"; @@ -13,7 +13,7 @@ import { } from "components/issues"; import { EIssuesStoreType } from "constants/issue"; // ui -import { Spinner } from "@plane/ui"; +import { ListLayoutLoader } from "components/ui"; export const ArchivedIssueLayoutRoot: React.FC = observer(() => { // router @@ -36,30 +36,26 @@ export const ArchivedIssueLayoutRoot: React.FC = observer(() => { } ); + if (issues?.loader === "init-loader" || !issues?.groupedIssueIds) { + return ; + } + if (!workspaceSlug || !projectId) return <>; return (
- {issues?.loader === "init-loader" ? ( -
- + {issues?.groupedIssueIds?.length === 0 ? ( +
+
) : ( - <> - {!issues?.groupedIssueIds ? ( - - ) : ( - <> -
- -
- - {/* peek overview */} - - - )} - + +
+ +
+ +
)}
); diff --git a/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx b/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx index 763215d64..95204b5e5 100644 --- a/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { Fragment, useState } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import useSWR from "swr"; @@ -16,6 +16,7 @@ import { IssuePeekOverview, } from "components/issues"; import { TransferIssues, TransferIssuesModal } from "components/cycles"; +import { ActiveLoader } from "components/ui"; // ui import { Spinner } from "@plane/ui"; // constants @@ -53,47 +54,55 @@ export const CycleLayoutRoot: React.FC = observer(() => { const cycleStatus = cycleDetails?.status?.toLocaleLowerCase() ?? "draft"; if (!workspaceSlug || !projectId || !cycleId) return <>; + + if (issues?.loader === "init-loader" || !issues?.groupedIssueIds) { + return ( + <> + {activeLayout ? ( + + ) : ( +
+ +
+ )} + + ); + } + return ( <> setTransferIssuesModal(false)} isOpen={transferIssuesModal} /> -
{cycleStatus === "completed" && setTransferIssuesModal(true)} />} - {issues?.loader === "init-loader" || !issues?.groupedIssueIds ? ( -
- + {issues?.groupedIssueIds?.length === 0 ? ( +
+
) : ( - <> - {issues?.groupedIssueIds?.length === 0 ? ( - - ) : ( - <> -
- {activeLayout === "list" ? ( - - ) : activeLayout === "kanban" ? ( - - ) : activeLayout === "calendar" ? ( - - ) : activeLayout === "gantt_chart" ? ( - - ) : activeLayout === "spreadsheet" ? ( - - ) : null} -
- {/* peek overview */} - - - )} - + +
+ {activeLayout === "list" ? ( + + ) : activeLayout === "kanban" ? ( + + ) : activeLayout === "calendar" ? ( + + ) : activeLayout === "gantt_chart" ? ( + + ) : activeLayout === "spreadsheet" ? ( + + ) : null} +
+ {/* peek overview */} + +
)}
diff --git a/web/components/issues/issue-layouts/roots/draft-issue-layout-root.tsx b/web/components/issues/issue-layouts/roots/draft-issue-layout-root.tsx index 8071b40e5..2528baf69 100644 --- a/web/components/issues/issue-layouts/roots/draft-issue-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/draft-issue-layout-root.tsx @@ -9,6 +9,7 @@ import { DraftIssueAppliedFiltersRoot } from "../filters/applied-filters/roots/d import { DraftIssueListLayout } from "../list/roots/draft-issue-root"; import { ProjectDraftEmptyState } from "../empty-states"; import { IssuePeekOverview } from "components/issues/peek-overview"; +import { ActiveLoader } from "components/ui"; // ui import { Spinner } from "@plane/ui"; import { DraftKanBanLayout } from "../kanban/roots/draft-issue-root"; @@ -39,30 +40,39 @@ export const DraftIssueLayoutRoot: React.FC = observer(() => { const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout || undefined; if (!workspaceSlug || !projectId) return <>; + + if (issues?.loader === "init-loader" || !issues?.groupedIssueIds) { + return ( + <> + {activeLayout ? ( + + ) : ( +
+ +
+ )} + + ); + } + return (
- {issues?.loader === "init-loader" ? ( -
- + {issues?.groupedIssueIds?.length === 0 ? ( +
+
) : ( - <> - {!issues?.groupedIssueIds ? ( - - ) : ( -
- {activeLayout === "list" ? ( - - ) : activeLayout === "kanban" ? ( - - ) : null} - {/* issue peek overview */} - -
- )} - +
+ {activeLayout === "list" ? ( + + ) : activeLayout === "kanban" ? ( + + ) : null} + {/* issue peek overview */} + +
)}
); diff --git a/web/components/issues/issue-layouts/roots/module-layout-root.tsx b/web/components/issues/issue-layouts/roots/module-layout-root.tsx index af0ed5db5..5adc33d78 100644 --- a/web/components/issues/issue-layouts/roots/module-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/module-layout-root.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { Fragment } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import useSWR from "swr"; @@ -15,6 +15,7 @@ import { ModuleListLayout, ModuleSpreadsheetLayout, } from "components/issues"; +import { ActiveLoader } from "components/ui"; // ui import { Spinner } from "@plane/ui"; // constants @@ -44,46 +45,55 @@ export const ModuleLayoutRoot: React.FC = observer(() => { } ); + if (!workspaceSlug || !projectId || !moduleId) return <>; + const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout || undefined; - if (!workspaceSlug || !projectId || !moduleId) return <>; + if (issues?.loader === "init-loader" || !issues?.groupedIssueIds) { + return ( + <> + {activeLayout ? ( + + ) : ( +
+ +
+ )} + + ); + } + return (
- {issues?.loader === "init-loader" || !issues?.groupedIssueIds ? ( -
- + {issues?.groupedIssueIds?.length === 0 ? ( +
+
) : ( - <> - {issues?.groupedIssueIds?.length === 0 ? ( - - ) : ( - <> -
- {activeLayout === "list" ? ( - - ) : activeLayout === "kanban" ? ( - - ) : activeLayout === "calendar" ? ( - - ) : activeLayout === "gantt_chart" ? ( - - ) : activeLayout === "spreadsheet" ? ( - - ) : null} -
- {/* peek overview */} - - - )} - + +
+ {activeLayout === "list" ? ( + + ) : activeLayout === "kanban" ? ( + + ) : activeLayout === "calendar" ? ( + + ) : activeLayout === "gantt_chart" ? ( + + ) : activeLayout === "spreadsheet" ? ( + + ) : null} +
+ {/* peek overview */} + +
)}
); diff --git a/web/components/issues/issue-layouts/roots/project-layout-root.tsx b/web/components/issues/issue-layouts/roots/project-layout-root.tsx index ddc1e9917..75bb4bfad 100644 --- a/web/components/issues/issue-layouts/roots/project-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/project-layout-root.tsx @@ -1,4 +1,4 @@ -import { FC } from "react"; +import { FC, Fragment } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import useSWR from "swr"; @@ -17,6 +17,8 @@ import { import { Spinner } from "@plane/ui"; // hooks import { useIssues } from "hooks/store"; +// helpers +import { ActiveLoader } from "components/ui"; // constants import { EIssuesStoreType } from "constants/issue"; @@ -41,48 +43,42 @@ export const ProjectLayoutRoot: FC = observer(() => { const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout; if (!workspaceSlug || !projectId) return <>; + + if (issues?.loader === "init-loader" || !issues?.groupedIssueIds) { + return <>{activeLayout && }; + } + return (
- {issues?.loader === "init-loader" || !issues?.groupedIssueIds ? ( -
- -
+ {issues?.groupedIssueIds?.length === 0 ? ( + ) : ( - <> - {issues?.groupedIssueIds?.length === 0 ? ( -
- -
- ) : ( - <> -
- {/* mutation loader */} - {issues?.loader === "mutation" && ( -
- -
- )} - - {activeLayout === "list" ? ( - - ) : activeLayout === "kanban" ? ( - - ) : activeLayout === "calendar" ? ( - - ) : activeLayout === "gantt_chart" ? ( - - ) : activeLayout === "spreadsheet" ? ( - - ) : null} + +
+ {/* mutation loader */} + {issues?.loader === "mutation" && ( +
+
+ )} + {activeLayout === "list" ? ( + + ) : activeLayout === "kanban" ? ( + + ) : activeLayout === "calendar" ? ( + + ) : activeLayout === "gantt_chart" ? ( + + ) : activeLayout === "spreadsheet" ? ( + + ) : null} +
- {/* peek overview */} - - - )} - + {/* peek overview */} + +
)}
); diff --git a/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx b/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx index 75ac2bd9e..2e55a499d 100644 --- a/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from "react"; +import React, { Fragment, useMemo } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import useSWR from "swr"; @@ -16,6 +16,7 @@ import { ProjectViewSpreadsheetLayout, } from "components/issues"; import { Spinner } from "@plane/ui"; +import { ActiveLoader } from "components/ui"; // constants import { EIssuesStoreType } from "constants/issue"; // types @@ -63,39 +64,48 @@ export const ProjectViewLayoutRoot: React.FC = observer(() => { const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout; if (!workspaceSlug || !projectId || !viewId) return <>; + + if (issues?.loader === "init-loader" || !issues?.groupedIssueIds) { + return ( + <> + {activeLayout ? ( + + ) : ( +
+ +
+ )} + + ); + } + return (
- {issues?.loader === "init-loader" || !issues?.groupedIssueIds ? ( -
- + {issues?.groupedIssueIds?.length === 0 ? ( +
+
) : ( - <> - {issues?.groupedIssueIds?.length === 0 ? ( - - ) : ( - <> -
- {activeLayout === "list" ? ( - - ) : activeLayout === "kanban" ? ( - - ) : activeLayout === "calendar" ? ( - - ) : activeLayout === "gantt_chart" ? ( - - ) : activeLayout === "spreadsheet" ? ( - - ) : null} -
+ +
+ {activeLayout === "list" ? ( + + ) : activeLayout === "kanban" ? ( + + ) : activeLayout === "calendar" ? ( + + ) : activeLayout === "gantt_chart" ? ( + + ) : activeLayout === "spreadsheet" ? ( + + ) : null} +
- {/* peek overview */} - - - )} - + {/* peek overview */} + +
)}
); diff --git a/web/components/modules/modules-list-view.tsx b/web/components/modules/modules-list-view.tsx index 5694c7932..d86373c4f 100644 --- a/web/components/modules/modules-list-view.tsx +++ b/web/components/modules/modules-list-view.tsx @@ -8,7 +8,7 @@ import useLocalStorage from "hooks/use-local-storage"; import { ModuleCardItem, ModuleListItem, ModulePeekOverview, ModulesListGanttChartView } from "components/modules"; import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // ui -import { Loader, Spinner } from "@plane/ui"; +import { CycleModuleBoardLayout, CycleModuleListLayout, GanttLayoutLoader } from "components/ui"; // constants import { EUserProjectRoles } from "constants/project"; import { MODULE_EMPTY_STATE_DETAILS } from "constants/empty-state"; @@ -35,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 ( 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/profile/profile-issues.tsx b/web/components/profile/profile-issues.tsx index a41a853a3..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,7 +7,7 @@ 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"; @@ -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 98ef14234..7a5430554 100644 --- a/web/components/project/card-list.tsx +++ b/web/components/project/card-list.tsx @@ -4,8 +4,8 @@ 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"; @@ -27,17 +27,7 @@ export const ProjectCardList = observer(() => { const isEditingAllowed = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; - if (!workspaceProjectIds) - return ( - - - - - - - - - ); + if (!workspaceProjectIds) return ; return ( <> 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/ui/index.ts b/web/components/ui/index.ts index 6f1bba3ed..3a867b18c 100644 --- a/web/components/ui/index.ts +++ b/web/components/ui/index.ts @@ -7,3 +7,4 @@ export * from "./markdown-to-component"; export * from "./integration-and-import-export-banner"; export * from "./range-datepicker"; export * from "./profile-empty-state"; +export * from "./loader"; diff --git a/web/components/ui/loader/cycle-module-board-loader.tsx b/web/components/ui/loader/cycle-module-board-loader.tsx new file mode 100644 index 000000000..09c885fb9 --- /dev/null +++ b/web/components/ui/loader/cycle-module-board-loader.tsx @@ -0,0 +1,38 @@ +export const CycleModuleBoardLayout = () => ( +
+
+
+ {[...Array(5)].map(() => ( +
+
+ +
+ + +
+
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + +
+
+
+
+ ))} +
+
+
+); diff --git a/web/components/ui/loader/cycle-module-list-loader.tsx b/web/components/ui/loader/cycle-module-list-loader.tsx new file mode 100644 index 000000000..8787a1425 --- /dev/null +++ b/web/components/ui/loader/cycle-module-list-loader.tsx @@ -0,0 +1,28 @@ +export const CycleModuleListLayout = () => ( +
+
+
+ {[...Array(5)].map(() => ( +
+
+
+
+ + +
+
+ +
+
+
+ + + +
+
+
+ ))} +
+
+
+); diff --git a/web/components/ui/loader/index.ts b/web/components/ui/loader/index.ts new file mode 100644 index 000000000..c36666583 --- /dev/null +++ b/web/components/ui/loader/index.ts @@ -0,0 +1,9 @@ +export * from "./layouts"; +export * from "./settings"; +export * from "./pages-loader"; +export * from "./notification-loader"; +export * from "./cycle-module-board-loader"; +export * from "./cycle-module-list-loader"; +export * from "./view-list-loader"; +export * from "./projects-loader"; +export * from "./utils"; diff --git a/web/components/ui/loader/layouts/calendar-layout-loader.tsx b/web/components/ui/loader/layouts/calendar-layout-loader.tsx new file mode 100644 index 000000000..db7bc4066 --- /dev/null +++ b/web/components/ui/loader/layouts/calendar-layout-loader.tsx @@ -0,0 +1,48 @@ +import { getRandomInt } from "../utils"; + +const CalendarDay = () => { + const dataCount = getRandomInt(0, 1); + const dataBlocks = Array.from({ length: dataCount }, (_, index) => ( + + )); + + return ( +
+
+ +
+
{dataBlocks}
+
+ ); +}; + +export const CalendarLayoutLoader = () => ( +
+
+
+ + +
+
+ + +
+
+ + {[...Array(5)].map((_, index) => ( + + ))} + +
+
+ {[...Array(6)].map((_, index) => ( +
+ {[...Array(5)].map((_, index) => ( + + ))} +
+ ))} +
+
+
+); diff --git a/web/components/ui/loader/layouts/gantt-layout-loader.tsx b/web/components/ui/loader/layouts/gantt-layout-loader.tsx new file mode 100644 index 000000000..fd50638f6 --- /dev/null +++ b/web/components/ui/loader/layouts/gantt-layout-loader.tsx @@ -0,0 +1,50 @@ +import { getRandomLength } from "../utils"; + +export const GanttLayoutLoader = () => ( +
+
+ +
+
+
+
+
+ + +
+
+
+ {[...Array(6)].map((_, index) => ( +
+ + +
+ ))} +
+
+
+
+
+ +
+
+ {[...Array(15)].map((_, index) => ( + + ))} +
+
+
+ {[...Array(6)].map((_, index) => ( +
+ +
+ ))} +
+
+
+
+); diff --git a/web/components/ui/loader/layouts/index.ts b/web/components/ui/loader/layouts/index.ts new file mode 100644 index 000000000..72d2f9e44 --- /dev/null +++ b/web/components/ui/loader/layouts/index.ts @@ -0,0 +1,5 @@ +export * from "./list-layout-loader"; +export * from "./kanban-layout-loader"; +export * from "./calendar-layout-loader"; +export * from "./spreadsheet-layout-loader"; +export * from "./gantt-layout-loader"; diff --git a/web/components/ui/loader/layouts/kanban-layout-loader.tsx b/web/components/ui/loader/layouts/kanban-layout-loader.tsx new file mode 100644 index 000000000..b949c1b73 --- /dev/null +++ b/web/components/ui/loader/layouts/kanban-layout-loader.tsx @@ -0,0 +1,18 @@ +export const KanbanLayoutLoader = ({ cardsInEachColumn = [2, 3, 2, 4, 3] }: { cardsInEachColumn?: number[] }) => ( +
+ {cardsInEachColumn.map((cardsInColumn, columnIndex) => ( +
+
+
+ + +
+ +
+ {Array.from({ length: cardsInColumn }, (_, cardIndex) => ( + + ))} +
+ ))} +
+); diff --git a/web/components/ui/loader/layouts/list-layout-loader.tsx b/web/components/ui/loader/layouts/list-layout-loader.tsx new file mode 100644 index 000000000..9b861cc97 --- /dev/null +++ b/web/components/ui/loader/layouts/list-layout-loader.tsx @@ -0,0 +1,45 @@ +import { getRandomInt, getRandomLength } from "../utils"; + +const ListItemRow = () => ( +
+
+ + +
+
+ {[...Array(6)].map((_, index) => ( + <> + {getRandomInt(1, 2) % 2 === 0 ? ( + + ) : ( + + )} + + ))} +
+
+); + +const ListSection = ({ itemCount }: { itemCount: number }) => ( +
+
+
+ + +
+
+
+ {[...Array(itemCount)].map((_, index) => ( + + ))} +
+
+); + +export const ListLayoutLoader = () => ( +
+ {[6, 5, 2].map((itemCount, index) => ( + + ))} +
+); diff --git a/web/components/ui/loader/layouts/spreadsheet-layout-loader.tsx b/web/components/ui/loader/layouts/spreadsheet-layout-loader.tsx new file mode 100644 index 000000000..3b9e9bc65 --- /dev/null +++ b/web/components/ui/loader/layouts/spreadsheet-layout-loader.tsx @@ -0,0 +1,38 @@ +import { getRandomLength } from "../utils"; + +export const SpreadsheetLayoutLoader = () => ( +
+ + + + + + + {[...Array(16)].map((_, rowIndex) => ( + + + {[...Array(10)].map((_, colIndex) => ( + + ))} + + ))} + +
+ {[...Array(10)].map((_, index) => ( + + ))} +
+
+ + +
+
+
+ +
+
+
+); diff --git a/web/components/ui/loader/notification-loader.tsx b/web/components/ui/loader/notification-loader.tsx new file mode 100644 index 000000000..143f1a9b6 --- /dev/null +++ b/web/components/ui/loader/notification-loader.tsx @@ -0,0 +1,16 @@ +export const NotificationsLoader = () => ( +
+ {[...Array(3)].map(() => ( +
+ +
+ +
+ + +
+
+
+ ))} +
+); diff --git a/web/components/ui/loader/pages-loader.tsx b/web/components/ui/loader/pages-loader.tsx new file mode 100644 index 000000000..e31e82942 --- /dev/null +++ b/web/components/ui/loader/pages-loader.tsx @@ -0,0 +1,28 @@ +export const PagesLoader = () => ( +
+
+

Pages

+
+
+ {[...Array(5)].map(() => ( + + ))} +
+
+ {[...Array(5)].map(() => ( +
+
+ + +
+
+ + + + +
+
+ ))} +
+
+); diff --git a/web/components/ui/loader/projects-loader.tsx b/web/components/ui/loader/projects-loader.tsx new file mode 100644 index 000000000..9548a1f48 --- /dev/null +++ b/web/components/ui/loader/projects-loader.tsx @@ -0,0 +1,34 @@ +export const ProjectsLoader = () => ( +
+
+ {[...Array(3)].map(() => ( +
+
+
+
+
+ +
+ + +
+
+
+ + +
+
+
+
+
+ +
+ + +
+
+
+ ))} +
+
+); diff --git a/web/components/ui/loader/settings/activity.tsx b/web/components/ui/loader/settings/activity.tsx new file mode 100644 index 000000000..7bc5c392f --- /dev/null +++ b/web/components/ui/loader/settings/activity.tsx @@ -0,0 +1,12 @@ +import { getRandomLength } from "../utils"; + +export const ActivitySettingsLoader = () => ( +
+ {[...Array(10)].map(() => ( +
+ + +
+ ))} +
+); diff --git a/web/components/ui/loader/settings/api-token.tsx b/web/components/ui/loader/settings/api-token.tsx new file mode 100644 index 000000000..fc5b4c41d --- /dev/null +++ b/web/components/ui/loader/settings/api-token.tsx @@ -0,0 +1,19 @@ +export const APITokenSettingsLoader = () => ( +
+
+

API tokens

+ +
+
+ {[...Array(2)].map(() => ( +
+
+ + +
+ +
+ ))} +
+
+); diff --git a/web/components/ui/loader/settings/email.tsx b/web/components/ui/loader/settings/email.tsx new file mode 100644 index 000000000..fa68b972f --- /dev/null +++ b/web/components/ui/loader/settings/email.tsx @@ -0,0 +1,27 @@ +export const EmailSettingsLoader = () => ( +
+
+ + +
+
+
+ +
+ {[...Array(4)].map(() => ( +
+
+ + +
+
+ +
+
+ ))} +
+ +
+
+
+); diff --git a/web/components/ui/loader/settings/import-and-export.tsx b/web/components/ui/loader/settings/import-and-export.tsx new file mode 100644 index 000000000..70496d1c1 --- /dev/null +++ b/web/components/ui/loader/settings/import-and-export.tsx @@ -0,0 +1,18 @@ +export const ImportExportSettingsLoader = () => ( +
+ {[...Array(2)].map(() => ( +
+
+
+ + +
+
+ + +
+
+
+ ))} +
+); diff --git a/web/components/ui/loader/settings/index.ts b/web/components/ui/loader/settings/index.ts new file mode 100644 index 000000000..8b73cd98d --- /dev/null +++ b/web/components/ui/loader/settings/index.ts @@ -0,0 +1,7 @@ +export * from "./activity"; +export * from "./api-token"; +export * from "./email"; +export * from "./integration"; +export * from "./members"; +export * from "./web-hook"; +export * from "./import-and-export"; diff --git a/web/components/ui/loader/settings/integration.tsx b/web/components/ui/loader/settings/integration.tsx new file mode 100644 index 000000000..871b570b1 --- /dev/null +++ b/web/components/ui/loader/settings/integration.tsx @@ -0,0 +1,16 @@ +export const IntegrationsSettingsLoader = () => ( +
+ {[...Array(2)].map(() => ( +
+
+ +
+ + +
+
+ +
+ ))} +
+); diff --git a/web/components/ui/loader/settings/members.tsx b/web/components/ui/loader/settings/members.tsx new file mode 100644 index 000000000..3ed2c41ef --- /dev/null +++ b/web/components/ui/loader/settings/members.tsx @@ -0,0 +1,16 @@ +export const MembersSettingsLoader = () => ( +
+ {[...Array(4)].map(() => ( +
+
+ +
+ + +
+
+ +
+ ))} +
+); diff --git a/web/components/ui/loader/settings/web-hook.tsx b/web/components/ui/loader/settings/web-hook.tsx new file mode 100644 index 000000000..87ddbd19a --- /dev/null +++ b/web/components/ui/loader/settings/web-hook.tsx @@ -0,0 +1,20 @@ +export const WebhookSettingsLoader = () => ( +
+
+
+
Webhooks
+ +
+
+
+
+ + + + +
+
+
+
+
+); diff --git a/web/components/ui/loader/utils.tsx b/web/components/ui/loader/utils.tsx new file mode 100644 index 000000000..312df038e --- /dev/null +++ b/web/components/ui/loader/utils.tsx @@ -0,0 +1,35 @@ +import { + CalendarLayoutLoader, + GanttLayoutLoader, + KanbanLayoutLoader, + ListLayoutLoader, + SpreadsheetLayoutLoader, +} from "./layouts"; + +export const getRandomInt = (min: number, max: number) => Math.floor(Math.random() * (max - min + 1)) + min; + +export const getRandomLength = (lengthArray: string[]) => { + const randomIndex = Math.floor(Math.random() * lengthArray.length); + return `${lengthArray[randomIndex]}`; +}; + +interface Props { + layout: string; +} +export const ActiveLoader: React.FC = (props) => { + const { layout } = props; + switch (layout) { + case "list": + return ; + case "kanban": + return ; + case "spreadsheet": + return ; + case "calendar": + return ; + case "gantt_chart": + return ; + default: + return ; + } +}; diff --git a/web/components/ui/loader/view-list-loader.tsx b/web/components/ui/loader/view-list-loader.tsx new file mode 100644 index 000000000..97899a657 --- /dev/null +++ b/web/components/ui/loader/view-list-loader.tsx @@ -0,0 +1,18 @@ +export const ViewListLoader = () => ( +
+ {[...Array(8)].map(() => ( +
+
+
+ + +
+
+ + +
+
+
+ ))} +
+); diff --git a/web/components/views/views-list.tsx b/web/components/views/views-list.tsx index ea5215fa7..902193dba 100644 --- a/web/components/views/views-list.tsx +++ b/web/components/views/views-list.tsx @@ -8,7 +8,8 @@ import { useApplication, useProjectView, useUser } from "hooks/store"; import { ProjectViewListItem } from "components/views"; import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // ui -import { Input, Loader, Spinner } from "@plane/ui"; +import { Input } from "@plane/ui"; +import { ViewListLoader } from "components/ui"; // constants import { EUserProjectRoles } from "constants/project"; import { VIEW_EMPTY_STATE_DETAILS } from "constants/empty-state"; @@ -28,22 +29,7 @@ export const ProjectViewsList = observer(() => { } = useUser(); const { projectViewIds, getViewById, loader } = useProjectView(); - if (loader) - return ( -
- -
- ); - - if (!projectViewIds) - return ( - - - - - - - ); + if (loader || !projectViewIds) return ; const viewsList = projectViewIds.map((viewId) => getViewById(viewId)); 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/views/views-list.tsx b/web/components/workspace/views/views-list.tsx index 6a04374b2..9a8758d2d 100644 --- a/web/components/workspace/views/views-list.tsx +++ b/web/components/workspace/views/views-list.tsx @@ -6,7 +6,7 @@ import { useGlobalView } from "hooks/store"; // components import { GlobalViewListItem } from "components/workspace"; // ui -import { Loader } from "@plane/ui"; +import { ViewListLoader } from "components/ui"; type Props = { searchQuery: string; @@ -25,15 +25,7 @@ export const GlobalViewsList: React.FC = observer((props) => { workspaceSlug ? () => fetchAllGlobalViews(workspaceSlug.toString()) : null ); - if (!currentWorkspaceViews) - return ( - - - - - - - ); + if (!currentWorkspaceViews) return ; const filteredViewsList = getSearchedViews(searchQuery); diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx index b1a7acfbd..0b9af62fd 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx @@ -13,7 +13,8 @@ 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"; @@ -66,9 +67,11 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => { if (loader) return ( -
- -
+ <> + {cycleLayout === "list" && } + {cycleLayout === "board" && } + {cycleLayout === "gantt" && } + ); return ( diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx index 008720c4d..5dad4ede0 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx @@ -16,7 +16,7 @@ import { AppLayout } from "layouts/app-layout"; 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 @@ -125,12 +125,7 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => { ); - if (loader || archivedPageLoader) - return ( -
- -
- ); + if (loader || archivedPageLoader) return ; return ( <> diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx index 76bb000f0..a44fbeaa4 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx @@ -15,7 +15,7 @@ import { IntegrationCard } from "components/project"; import { ProjectSettingHeader } from "components/headers"; import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // ui -import { Loader } from "@plane/ui"; +import { IntegrationsSettingsLoader } from "components/ui"; // types import { IProject } from "@plane/types"; import { NextPageWithLayout } from "lib/types"; @@ -79,12 +79,7 @@ const ProjectIntegrationsPage: NextPageWithLayout = () => {
) ) : ( - - - - - - + )}
); diff --git a/web/pages/[workspaceSlug]/settings/api-tokens.tsx b/web/pages/[workspaceSlug]/settings/api-tokens.tsx index a90a0dcec..3d65c2d7b 100644 --- a/web/pages/[workspaceSlug]/settings/api-tokens.tsx +++ b/web/pages/[workspaceSlug]/settings/api-tokens.tsx @@ -13,7 +13,8 @@ import { WorkspaceSettingHeader } from "components/headers"; 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 @@ -56,49 +57,47 @@ const ApiTokensPage: NextPageWithLayout = observer(() => {
); + if (!tokens) { + return ; + } + return ( <> setIsCreateTokenModalOpen(false)} /> - {tokens ? ( -
- {tokens.length > 0 ? ( - <> -
-

API tokens

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

API tokens

- -
-
- -
+
+ {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 281c2916a..cafac485e 100644 --- a/web/pages/[workspaceSlug]/settings/webhooks/index.tsx +++ b/web/pages/[workspaceSlug]/settings/webhooks/index.tsx @@ -13,7 +13,8 @@ import { WorkspaceSettingHeader } from "components/headers"; 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 @@ -59,12 +60,7 @@ const WebhooksListPage: NextPageWithLayout = observer(() => {
); - if (!webhooks) - return ( -
- -
- ); + if (!webhooks) return ; return (
diff --git a/web/pages/profile/activity.tsx b/web/pages/profile/activity.tsx index d0c83ffdb..031e68f35 100644 --- a/web/pages/profile/activity.tsx +++ b/web/pages/profile/activity.tsx @@ -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 @@ -31,7 +31,6 @@ const ProfileActivityPage: NextPageWithLayout = observer(() => { const { currentUser } = useUser(); return ( -
@@ -97,12 +96,12 @@ const ProfileActivityPage: NextPageWithLayout = observer(() => { const message = activityItem.verb === "created" && - activityItem.field !== "cycles" && - activityItem.field !== "modules" && - activityItem.field !== "attachment" && - activityItem.field !== "link" && - activityItem.field !== "estimate" && - !activityItem.field ? ( + activityItem.field !== "cycles" && + activityItem.field !== "modules" && + activityItem.field !== "attachment" && + activityItem.field !== "link" && + activityItem.field !== "estimate" && + !activityItem.field ? ( created @@ -182,15 +181,9 @@ const ProfileActivityPage: NextPageWithLayout = observer(() => {
) : ( - - - - - - + )}
- ); }); diff --git a/web/pages/profile/preferences/email.tsx b/web/pages/profile/preferences/email.tsx index 7db6df113..8b896bbcb 100644 --- a/web/pages/profile/preferences/email.tsx +++ b/web/pages/profile/preferences/email.tsx @@ -3,7 +3,7 @@ import useSWR from "swr"; // layouts import { ProfilePreferenceSettingsLayout } from "layouts/settings-layout/profile/preferences"; // ui -import { Loader } from "@plane/ui"; +import { EmailSettingsLoader } from "components/ui"; // components import { EmailNotificationForm } from "components/profile/preferences"; // services @@ -20,18 +20,8 @@ const ProfilePreferencesThemePage: NextPageWithLayout = () => { userService.currentUserEmailNotificationSettings() ); - if (isLoading) { - return ( - - - - - - ); - } - - if (!data) { - return null; + if (!data || isLoading) { + return ; } return ( 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 4e6a36ba6..1f9b26965 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7172,9 +7172,9 @@ postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.29: source-map-js "^1.0.2" posthog-js@^1.105.0: - version "1.105.6" - resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.105.6.tgz#3544de4389d5c7743fa420178bd127e49c4dc825" - integrity sha512-5ITXsh29XIuNohHLy21nawGnfFZDpyt+yfnWge9sJl5yv0nNuoUmLiDgw1tJafoqGrfd5CUasKyzSI21HxsSeQ== + 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.8" preact "^10.19.3"