diff --git a/web/components/cycles/cycles-view.tsx b/web/components/cycles/cycles-view.tsx index 7b58bde4533..a321be0b592 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 a98381ebdc9..ed6a392207d 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 ba9e61607a8..29dfa5b30f3 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 70bbb5fa452..3026d698114 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/kanban/block.tsx b/web/components/issues/issue-layouts/kanban/block.tsx index 24cbe990843..6102ce0dd5d 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) => { >
{ const isEditingAllowed = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; - 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", - onClick: () => { - setTrackElement("All issues empty state"); - commandPaletteStore.toggleCreateProjectModal(true); - }, - } + if (loader === "init-loader" || !globalViewId || globalViewId !== dataViewId || !issueIds) { + return ; + } + + if (issueIds.length === 0) { + return ( + 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", + onClick: () => { + setTrackElement("All issues empty state"); + commandPaletteStore.toggleCreateProjectModal(true); + }, } - disabled={!isEditingAllowed} - /> - ) : ( -
- -
- )} - - )} + } + disabled={!isEditingAllowed} + /> + ); + } + return ( +
+ +
+ +
{/* 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 430383a9f24..1a78a872951 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 @@ -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,31 +36,26 @@ export const ArchivedIssueLayoutRoot: React.FC = observer(() => { } ); + if (issues?.loader === "init-loader" || !issues?.groupedIssueIds) { + return ; + } + + if (issues?.groupedIssueIds?.length === 0) { + return ( +
+ +
+ ); + } + if (!workspaceSlug || !projectId) return <>; return (
- - {issues?.loader === "init-loader" ? ( -
- -
- ) : ( - <> - {!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 763215d64ee..72f496aaa5e 100644 --- a/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx @@ -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,6 +54,34 @@ 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 ? ( + + ) : ( +
+ +
+ )} + + ); + } + + if (issues?.groupedIssueIds?.length === 0) { + return ( +
+ +
+ ); + } + return ( <> setTransferIssuesModal(false)} isOpen={transferIssuesModal} /> @@ -61,40 +90,21 @@ export const CycleLayoutRoot: React.FC = observer(() => { {cycleStatus === "completed" && setTransferIssuesModal(true)} />} - {issues?.loader === "init-loader" || !issues?.groupedIssueIds ? ( -
- -
- ) : ( - <> - {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 8071b40e59a..6a22b884fd7 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,31 +40,37 @@ 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 ? ( + + ) : ( +
+ +
+ )} + + ); + } + + if (issues?.groupedIssueIds?.length === 0) { + return ( +
+ +
+ ); + } return (
- {issues?.loader === "init-loader" ? ( -
- -
- ) : ( - <> - {!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 af0ed5db50e..12b91a3757a 100644 --- a/web/components/issues/issue-layouts/roots/module-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/module-layout-root.tsx @@ -15,6 +15,7 @@ import { ModuleListLayout, ModuleSpreadsheetLayout, } from "components/issues"; +import { ActiveLoader } from "components/ui"; // ui import { Spinner } from "@plane/ui"; // constants @@ -44,47 +45,56 @@ 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 ? ( + + ) : ( +
+ +
+ )} + + ); + } + + if (issues?.groupedIssueIds?.length === 0) { + return ( +
+ +
+ ); + } + return (
- {issues?.loader === "init-loader" || !issues?.groupedIssueIds ? ( -
- -
- ) : ( - <> - {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 ddc1e991795..5f644ce453b 100644 --- a/web/components/issues/issue-layouts/roots/project-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/project-layout-root.tsx @@ -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,49 +43,45 @@ export const ProjectLayoutRoot: FC = observer(() => { const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout; if (!workspaceSlug || !projectId) return <>; + + if (issues?.loader === "init-loader" || !issues?.groupedIssueIds) { + return <>{activeLayout && }; + } + + if (issues?.groupedIssueIds?.length === 0) { + return ( +
+ +
+ ); + } + return (
- {issues?.loader === "init-loader" || !issues?.groupedIssueIds ? ( -
- -
- ) : ( - <> - {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 75ac2bd9ec1..b6e2e36a7cd 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 @@ -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,40 +64,49 @@ 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 ? ( + + ) : ( +
+ +
+ )} + + ); + } + + if (issues?.groupedIssueIds?.length === 0) { + return ( +
+ +
+ ); + } + return (
- {issues?.loader === "init-loader" || !issues?.groupedIssueIds ? ( -
- -
- ) : ( - <> - {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 5694c793262..d86373c4fab 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 a8cdd4264ab..8496d1a35b4 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 a41a853a35e..8b11ca51847 100644 --- a/web/components/profile/profile-issues.tsx +++ b/web/components/profile/profile-issues.tsx @@ -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"; @@ -57,38 +57,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 98ef14234a4..7a5430554a1 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 fa8008c8f2e..d7b43244530 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 6f1bba3ed7d..3a867b18c32 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 00000000000..09c885fb928 --- /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 00000000000..8787a142541 --- /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 00000000000..c36666583fd --- /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 00000000000..db7bc406659 --- /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 00000000000..fd50638f67a --- /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 00000000000..72d2f9e44a0 --- /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 00000000000..b949c1b73c2 --- /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 00000000000..9b861cc9713 --- /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 00000000000..3b9e9bc6538 --- /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 00000000000..143f1a9b64a --- /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 00000000000..e31e8294216 --- /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 00000000000..9548a1f483b --- /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 00000000000..7bc5c392f4d --- /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 00000000000..fc5b4c41d60 --- /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 00000000000..fa68b972f5a --- /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 00000000000..70496d1c19a --- /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 00000000000..8b73cd98dd1 --- /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 00000000000..871b570b19b --- /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 00000000000..3ed2c41efc3 --- /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 00000000000..87ddbd19a0e --- /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 00000000000..312df038e41 --- /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 00000000000..97899a65764 --- /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 ea5215fa716..902193dba64 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 97f253afc6b..1dc02d508e3 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 6a04374b227..9a8758d2dc9 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 b1a7acfbd56..0b9af62fd24 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 008720c4ded..5dad4ede01c 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 76bb000f061..a44fbeaa49f 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 a90a0dcec59..3d65c2d7b53 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 70147c312c5..940c90f3a67 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 281c2916a1a..cafac485e62 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 d0c83ffdbdd..031e68f3527 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 7db6df11359..8b896bbcb09 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 (