Merge branch 'develop' of github.com:makeplane/plane into develop

This commit is contained in:
sriram veeraghanta 2024-02-20 13:44:11 +05:30
commit a6a28d46c7
64 changed files with 1704 additions and 1223 deletions

View File

@ -4,3 +4,4 @@ export * from "./sidebar";
export * from "./theme";
export * from "./activity";
export * from "./image-picker-popover";
export * from "./page-title";

View File

@ -0,0 +1,18 @@
import Head from "next/head";
type PageHeadTitleProps = {
title?: string;
description?: string;
};
export const PageHead: React.FC<PageHeadTitleProps> = (props) => {
const { title } = props;
if (!title) return null;
return (
<Head>
<title>{title}</title>
</Head>
);
};

View File

@ -96,7 +96,7 @@ export const RecentProjectsWidget: React.FC<WidgetProps> = observer((props) => {
href={`/${workspaceSlug}/projects`}
className="text-lg font-semibold text-custom-text-300 mx-7 hover:underline"
>
Your projects
Recent projects
</Link>
<div className="space-y-8 mt-4 mx-7">
{canCreateProject && (

View File

@ -288,7 +288,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
handleKanbanFilters={handleKanbanFilters}
kanbanFilters={kanbanFilters}
enableQuickIssueCreate={enableQuickAdd}
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
showEmptyGroup={userDisplayFilters?.show_empty_groups ?? true}
quickAddCallback={issues?.quickAddIssue}
viewId={viewId}
disableIssueCreation={!enableIssueCreation || !isEditingAllowed || isCompletedCycle}

View File

@ -48,6 +48,7 @@ export interface IGroupByKanBan {
canEditProperties: (projectId: string | undefined) => boolean;
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
isDragStarted?: boolean;
showEmptyGroup?: boolean;
}
const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
@ -72,6 +73,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
canEditProperties,
scrollableContainerRef,
isDragStarted,
showEmptyGroup = true,
} = props;
const member = useMember();
@ -84,6 +86,10 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
if (!list) return null;
const groupWithIssues = list.filter((_list) => (issueIds as TGroupedIssues)[_list.id]?.length > 0);
const groupList = showEmptyGroup ? list : groupWithIssues;
const visibilityGroupBy = (_list: IGroupByColumn) =>
sub_group_by ? false : kanbanFilters?.group_by.includes(_list.id) ? true : false;
@ -91,9 +97,9 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
return (
<div className={`relative w-full flex gap-3 ${sub_group_by ? "h-full" : "h-full"}`}>
{list &&
list.length > 0 &&
list.map((_list: IGroupByColumn) => {
{groupList &&
groupList.length > 0 &&
groupList.map((_list: IGroupByColumn) => {
const groupByVisibilityToggle = visibilityGroupBy(_list);
return (
@ -196,6 +202,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
canEditProperties,
scrollableContainerRef,
isDragStarted,
showEmptyGroup,
} = props;
const issueKanBanView = useKanbanView();
@ -222,6 +229,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
canEditProperties={canEditProperties}
scrollableContainerRef={scrollableContainerRef}
isDragStarted={isDragStarted}
showEmptyGroup={showEmptyGroup}
/>
);
});

View File

@ -6,6 +6,7 @@ import { useApplication, useUser } from "hooks/store";
import useSignInRedirection from "hooks/use-sign-in-redirection";
// components
import { SignInRoot } from "components/account";
import { PageHead } from "components/core";
// ui
import { Spinner } from "@plane/ui";
// images
@ -34,6 +35,8 @@ export const SignInView = observer(() => {
);
return (
<>
<PageHead title="Sign In" />
<div className="h-full w-full bg-onboarding-gradient-100">
<div className="flex items-center justify-between px-8 pb-4 sm:px-16 sm:py-5 lg:px-28">
<div className="flex items-center gap-x-2 py-10">
@ -48,5 +51,6 @@ export const SignInView = observer(() => {
</div>
</div>
</div>
</>
);
});

View File

@ -2,7 +2,7 @@ import { useRef, useState } from "react";
import { observer } from "mobx-react-lite";
import { ChevronUp, PenSquare, Search } from "lucide-react";
// hooks
import { useApplication, useEventTracker, useUser } from "hooks/store";
import { useApplication, useEventTracker, useProject, useUser } from "hooks/store";
import useLocalStorage from "hooks/use-local-storage";
// components
import { CreateUpdateDraftIssueModal } from "components/issues";
@ -16,6 +16,7 @@ export const WorkspaceSidebarQuickAction = observer(() => {
const { theme: themeStore, commandPalette: commandPaletteStore } = useApplication();
const { setTrackElement } = useEventTracker();
const { joinedProjectIds } = useProject();
const {
membership: { currentWorkspaceRole },
} = useUser();
@ -31,6 +32,8 @@ export const WorkspaceSidebarQuickAction = observer(() => {
const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
const disabled = joinedProjectIds.length === 0;
const onMouseEnter = () => {
//if renet before timout clear the timeout
timeoutRef?.current && clearTimeout(timeoutRef.current);
@ -73,17 +76,18 @@ export const WorkspaceSidebarQuickAction = observer(() => {
type="button"
className={`relative flex flex-shrink-0 flex-grow items-center gap-2 rounded py-1.5 outline-none ${
isSidebarCollapsed ? "justify-center" : ""
}`}
} ${disabled ? "cursor-not-allowed opacity-50" : ""}`}
onClick={() => {
setTrackElement("APP_SIDEBAR_QUICK_ACTIONS");
commandPaletteStore.toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
}}
disabled={disabled}
>
<PenSquare className="h-4 w-4 text-custom-sidebar-text-300" />
{!isSidebarCollapsed && <span className="text-sm font-medium">New Issue</span>}
</button>
{storedValue && Object.keys(JSON.parse(storedValue)).length > 0 && (
{!disabled && storedValue && Object.keys(JSON.parse(storedValue)).length > 0 && (
<>
<div
className={`h-8 w-0.5 bg-custom-sidebar-background-80 ${isSidebarCollapsed ? "hidden" : "block"}`}

View File

@ -2,7 +2,8 @@ import React from "react";
import Link from "next/link";
import Image from "next/image";
// components
import { PageHead } from "components/core";
// layouts
import DefaultLayout from "layouts/default-layout";
// ui
@ -14,6 +15,7 @@ import type { NextPage } from "next";
const PageNotFound: NextPage = () => (
<DefaultLayout>
<PageHead title="404 - Page Not Found" />
<div className="grid h-full place-items-center p-4">
<div className="space-y-8 text-center">
<div className="relative mx-auto h-60 w-60 lg:h-80 lg:w-80">

View File

@ -1,13 +1,28 @@
import { ReactElement } from "react";
import { observer } from "mobx-react";
// components
import { PageHead } from "components/core";
import { WorkspaceActiveCycleHeader } from "components/headers";
import { WorkspaceActiveCyclesUpgrade } from "components/workspace";
// layouts
import { AppLayout } from "layouts/app-layout";
// types
import { NextPageWithLayout } from "lib/types";
// hooks
import { useWorkspace } from "hooks/store";
const WorkspaceActiveCyclesPage: NextPageWithLayout = () => <WorkspaceActiveCyclesUpgrade />;
const WorkspaceActiveCyclesPage: NextPageWithLayout = observer(() => {
const { currentWorkspace } = useWorkspace();
// derived values
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Active Cycles` : undefined;
return (
<>
<PageHead title={pageTitle} />
<WorkspaceActiveCyclesUpgrade />
</>
);
});
WorkspaceActiveCyclesPage.getLayout = function getLayout(page: ReactElement) {
return <AppLayout header={<WorkspaceActiveCycleHeader />}>{page}</AppLayout>;

View File

@ -1,22 +1,23 @@
import React, { Fragment, ReactElement } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Tab } from "@headlessui/react";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useEventTracker, useProject, useUser } from "hooks/store";
import { useApplication, useEventTracker, useProject, useUser, useWorkspace } from "hooks/store";
// layouts
import { AppLayout } from "layouts/app-layout";
// components
import { PageHead } from "components/core";
import { CustomAnalytics, ScopeAndDemand } from "components/analytics";
import { WorkspaceAnalyticsHeader } from "components/headers";
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
// constants
import { ANALYTICS_TABS } from "constants/analytics";
import { EUserWorkspaceRoles } from "constants/workspace";
import { WORKSPACE_EMPTY_STATE_DETAILS } from "constants/empty-state";
// type
import { NextPageWithLayout } from "lib/types";
import { useRouter } from "next/router";
import { WORKSPACE_EMPTY_STATE_DETAILS } from "constants/empty-state";
const AnalyticsPage: NextPageWithLayout = observer(() => {
const router = useRouter();
@ -33,13 +34,16 @@ const AnalyticsPage: NextPageWithLayout = observer(() => {
currentUser,
} = useUser();
const { workspaceProjectIds } = useProject();
const { currentWorkspace } = useWorkspace();
// derived values
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "analytics", isLightMode);
const isEditingAllowed = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Analytics` : undefined;
return (
<>
<PageHead title={pageTitle} />
{workspaceProjectIds && workspaceProjectIds.length > 0 ? (
<div className="flex h-full flex-col overflow-hidden bg-custom-background-100">
<Tab.Group as={Fragment} defaultIndex={analytics_tab === "custom" ? 1 : 0}>

View File

@ -1,13 +1,28 @@
import { ReactElement } from "react";
import { observer } from "mobx-react";
// layouts
import { AppLayout } from "layouts/app-layout";
// components
import { PageHead } from "components/core";
import { WorkspaceDashboardView } from "components/page-views";
import { WorkspaceDashboardHeader } from "components/headers/workspace-dashboard";
// types
import { NextPageWithLayout } from "lib/types";
// hooks
import { useWorkspace } from "hooks/store";
const WorkspacePage: NextPageWithLayout = () => <WorkspaceDashboardView />;
const WorkspacePage: NextPageWithLayout = observer(() => {
const { currentWorkspace } = useWorkspace();
// derived values
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Dashboard` : undefined;
return (
<>
<PageHead title={pageTitle} />
<WorkspaceDashboardView />
</>
);
});
WorkspacePage.getLayout = function getLayout(page: ReactElement) {
return <AppLayout header={<WorkspaceDashboardHeader />}>{page}</AppLayout>;

View File

@ -4,11 +4,17 @@ import { AppLayout } from "layouts/app-layout";
import { ProfileAuthWrapper } from "layouts/user-profile-layout";
// components
import { UserProfileHeader } from "components/headers";
import { PageHead } from "components/core";
// types
import { NextPageWithLayout } from "lib/types";
import { ProfileIssuesPage } from "components/profile/profile-issues";
const ProfileAssignedIssuesPage: NextPageWithLayout = () => <ProfileIssuesPage type="assigned" />;
const ProfileAssignedIssuesPage: NextPageWithLayout = () => (
<>
<PageHead title="Profile - Assigned" />
<ProfileIssuesPage type="assigned" />
</>
);
ProfileAssignedIssuesPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -6,11 +6,17 @@ import { AppLayout } from "layouts/app-layout";
import { ProfileAuthWrapper } from "layouts/user-profile-layout";
// components
import { UserProfileHeader } from "components/headers";
import { PageHead } from "components/core";
// types
import { NextPageWithLayout } from "lib/types";
import { ProfileIssuesPage } from "components/profile/profile-issues";
const ProfileCreatedIssuesPage: NextPageWithLayout = () => <ProfileIssuesPage type="created" />;
const ProfileCreatedIssuesPage: NextPageWithLayout = () => (
<>
<PageHead title="Profile - Created" />
<ProfileIssuesPage type="created" />
</>
);
ProfileCreatedIssuesPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -8,6 +8,7 @@ import { AppLayout } from "layouts/app-layout";
import { ProfileAuthWrapper } from "layouts/user-profile-layout";
// components
import { UserProfileHeader } from "components/headers";
import { PageHead } from "components/core";
import {
ProfileActivity,
ProfilePriorityDistribution,
@ -42,6 +43,8 @@ const ProfileOverviewPage: NextPageWithLayout = () => {
});
return (
<>
<PageHead title="Profile - Summary" />
<div className="h-full w-full space-y-7 overflow-y-auto px-5 py-5 md:px-9">
<ProfileStats userProfile={userProfile} />
<ProfileWorkload stateDistribution={stateDistribution} />
@ -51,12 +54,13 @@ const ProfileOverviewPage: NextPageWithLayout = () => {
</div>
<ProfileActivity />
</div>
</>
);
};
ProfileOverviewPage.getLayout = function getLayout(page: ReactElement) {
return (
<AppLayout header={<UserProfileHeader type='Summary' />}>
<AppLayout header={<UserProfileHeader type="Summary" />}>
<ProfileAuthWrapper>{page}</ProfileAuthWrapper>
</AppLayout>
);

View File

@ -6,11 +6,17 @@ import { AppLayout } from "layouts/app-layout";
import { ProfileAuthWrapper } from "layouts/user-profile-layout";
// components
import { UserProfileHeader } from "components/headers";
import { PageHead } from "components/core";
// types
import { NextPageWithLayout } from "lib/types";
import { ProfileIssuesPage } from "components/profile/profile-issues";
const ProfileSubscribedIssuesPage: NextPageWithLayout = () => <ProfileIssuesPage type="subscribed" />;
const ProfileSubscribedIssuesPage: NextPageWithLayout = () => (
<>
<PageHead title="Profile - Subscribed" />
<ProfileIssuesPage type="subscribed" />
</>
);
ProfileSubscribedIssuesPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -1,5 +1,6 @@
import { useState, ReactElement } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react";
import useSWR from "swr";
// hooks
import useToast from "hooks/use-toast";
@ -9,6 +10,7 @@ import { AppLayout } from "layouts/app-layout";
// components
import { IssueDetailRoot } from "components/issues";
import { ProjectArchivedIssueDetailsHeader } from "components/headers";
import { PageHead } from "components/core";
// ui
import { ArchiveIcon, Loader } from "@plane/ui";
// icons
@ -18,7 +20,7 @@ import { NextPageWithLayout } from "lib/types";
// constants
import { EIssuesStoreType } from "constants/issue";
const ArchivedIssueDetailsPage: NextPageWithLayout = () => {
const ArchivedIssueDetailsPage: NextPageWithLayout = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId, archivedIssueId } = router.query;
@ -45,6 +47,9 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = () => {
);
const issue = getIssueById(archivedIssueId?.toString() || "") || undefined;
const project = (issue?.project_id && getProjectById(issue?.project_id)) || undefined;
const pageTitle = project && issue ? `${project?.identifier}-${issue?.sequence_id} ${issue?.name}` : undefined;
if (!issue) return <></>;
const handleUnArchive = async () => {
@ -79,6 +84,7 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = () => {
return (
<>
<PageHead title={pageTitle} />
{issueLoader ? (
<Loader className="flex h-full gap-5 p-5">
<div className="basis-2/3 space-y-2">
@ -126,7 +132,7 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = () => {
)}
</>
);
};
});
ArchivedIssueDetailsPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -1,22 +1,34 @@
import { ReactElement } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react";
// layouts
import { AppLayout } from "layouts/app-layout";
// contexts
import { ArchivedIssueLayoutRoot } from "components/issues";
// ui
import { ArchiveIcon } from "@plane/ui";
// components
import { ProjectArchivedIssuesHeader } from "components/headers";
import { PageHead } from "components/core";
// icons
import { X } from "lucide-react";
// types
import { NextPageWithLayout } from "lib/types";
// hooks
import { useProject } from "hooks/store";
const ProjectArchivedIssuesPage: NextPageWithLayout = () => {
const ProjectArchivedIssuesPage: NextPageWithLayout = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store hooks
const { getProjectById } = useProject();
// derived values
const project = projectId ? getProjectById(projectId.toString()) : undefined;
const pageTitle = project?.name && `${project?.name} - Archived Issues`;
return (
<>
<PageHead title={pageTitle} />
<div className="flex h-full w-full flex-col">
<div className="ga-1 flex items-center border-b border-custom-border-200 px-4 py-2.5 shadow-sm">
<button
@ -31,8 +43,9 @@ const ProjectArchivedIssuesPage: NextPageWithLayout = () => {
</div>
<ArchivedIssueLayoutRoot />
</div>
</>
);
};
});
ProjectArchivedIssuesPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -1,12 +1,14 @@
import { ReactElement } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { observer } from "mobx-react";
// hooks
import { useCycle } from "hooks/store";
import { useCycle, useProject } from "hooks/store";
import useLocalStorage from "hooks/use-local-storage";
// layouts
import { AppLayout } from "layouts/app-layout";
// components
import { PageHead } from "components/core";
import { CycleIssuesHeader } from "components/headers";
import { CycleDetailsSidebar } from "components/cycles";
import { CycleLayoutRoot } from "components/issues/issue-layouts";
@ -17,27 +19,36 @@ import emptyCycle from "public/empty-state/cycle.svg";
// types
import { NextPageWithLayout } from "lib/types";
const CycleDetailPage: NextPageWithLayout = () => {
const CycleDetailPage: NextPageWithLayout = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query;
// store hooks
const { fetchCycleDetails } = useCycle();
const { fetchCycleDetails, getCycleById } = useCycle();
const { getProjectById } = useProject();
// hooks
const { setValue, storedValue } = useLocalStorage("cycle_sidebar_collapsed", "false");
const isSidebarCollapsed = storedValue ? (storedValue === "true" ? true : false) : false;
// fetching cycle details
const { error } = useSWR(
workspaceSlug && projectId && cycleId ? `CYCLE_DETAILS_${cycleId.toString()}` : null,
workspaceSlug && projectId && cycleId
? () => fetchCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleId.toString())
: null
);
// derived values
const isSidebarCollapsed = storedValue ? (storedValue === "true" ? true : false) : false;
const cycle = cycleId ? getCycleById(cycleId.toString()) : undefined;
const project = projectId ? getProjectById(projectId.toString()) : undefined;
const pageTitle = project?.name && cycle?.name ? `${project?.name} - ${cycle?.name}` : undefined;
/**
* Toggles the sidebar
*/
const toggleSidebar = () => setValue(`${!isSidebarCollapsed}`);
return (
<>
<PageHead title={pageTitle} />
{error ? (
<EmptyState
image={emptyCycle}
@ -70,7 +81,7 @@ const CycleDetailPage: NextPageWithLayout = () => {
)}
</>
);
};
});
CycleDetailPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -4,11 +4,12 @@ import { observer } from "mobx-react-lite";
import { Tab } from "@headlessui/react";
import { useTheme } from "next-themes";
// hooks
import { useEventTracker, useCycle, useUser } from "hooks/store";
import { useEventTracker, useCycle, useUser, useProject } from "hooks/store";
import useLocalStorage from "hooks/use-local-storage";
// layouts
import { AppLayout } from "layouts/app-layout";
// components
import { PageHead } from "components/core";
import { CyclesHeader } from "components/headers";
import { CyclesView, ActiveCycleDetails, CycleCreateUpdateModal } from "components/cycles";
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
@ -34,12 +35,20 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
currentUser,
} = useUser();
const { currentProjectCycleIds, loader } = useCycle();
const { getProjectById } = useProject();
// router
const router = useRouter();
const { workspaceSlug, projectId, peekCycle } = router.query;
// local storage
const { storedValue: cycleTab, setValue: setCycleTab } = useLocalStorage<TCycleView>("cycle_tab", "active");
const { storedValue: cycleLayout, setValue: setCycleLayout } = useLocalStorage<TCycleLayout>("cycle_layout", "list");
// derived values
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "cycles", isLightMode);
const totalCycles = currentProjectCycleIds?.length ?? 0;
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
const project = projectId ? getProjectById(projectId?.toString()) : undefined;
const pageTitle = project?.name ? `${project?.name} - Cycles` : undefined;
const handleCurrentLayout = useCallback(
(_layout: TCycleLayout) => {
@ -56,13 +65,6 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
[handleCurrentLayout, setCycleTab]
);
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "cycles", isLightMode);
const totalCycles = currentProjectCycleIds?.length ?? 0;
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
if (!workspaceSlug || !projectId) return null;
if (loader)
@ -75,6 +77,8 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
);
return (
<>
<PageHead title={pageTitle} />
<div className="w-full h-full">
<CycleCreateUpdateModal
workspaceSlug={workspaceSlug.toString()}
@ -212,6 +216,7 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
</Tab.Group>
)}
</div>
</>
);
});

View File

@ -5,15 +5,26 @@ import { X, PenSquare } from "lucide-react";
import { AppLayout } from "layouts/app-layout";
// components
import { DraftIssueLayoutRoot } from "components/issues/issue-layouts/roots/draft-issue-layout-root";
import { PageHead } from "components/core";
import { ProjectDraftIssueHeader } from "components/headers";
// types
import { NextPageWithLayout } from "lib/types";
// hooks
import { useProject } from "hooks/store";
import { observer } from "mobx-react";
const ProjectDraftIssuesPage: NextPageWithLayout = () => {
const ProjectDraftIssuesPage: NextPageWithLayout = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store
const { getProjectById } = useProject();
// derived values
const project = projectId ? getProjectById(projectId.toString()) : undefined;
const pageTitle = project?.name ? `${project?.name} - Draft Issues` : undefined;
return (
<>
<PageHead title={pageTitle} />
<div className="flex h-full w-full flex-col">
<div className="ga-1 flex items-center border-b border-custom-border-200 px-4 py-2.5 shadow-sm">
<button
@ -23,13 +34,14 @@ const ProjectDraftIssuesPage: NextPageWithLayout = () => {
>
<PenSquare className="h-4 w-4" />
<span>Draft Issues</span>
<X className="h-3 w-3" />
</button>
<X className="h-3 w-3" />
</div>
<DraftIssueLayoutRoot />
</div>
</>
);
};
});
ProjectDraftIssuesPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -7,9 +7,9 @@ import { useProject, useInboxIssues } from "hooks/store";
// layouts
import { AppLayout } from "layouts/app-layout";
// components
import { PageHead } from "components/core";
import { ProjectInboxHeader } from "components/headers";
import { InboxSidebarRoot, InboxContentRoot } from "components/inbox";
// types
import { NextPageWithLayout } from "lib/types";
@ -22,7 +22,7 @@ const ProjectInboxPage: NextPageWithLayout = observer(() => {
filters: { fetchInboxFilters },
issues: { fetchInboxIssues },
} = useInboxIssues();
// fetching the Inbox filters and issues
useSWR(
workspaceSlug && projectId && currentProjectDetails && currentProjectDetails?.inbox_view
? `INBOX_ISSUES_${workspaceSlug.toString()}_${projectId.toString()}`
@ -34,9 +34,14 @@ const ProjectInboxPage: NextPageWithLayout = observer(() => {
}
}
);
// derived values
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Inbox` : undefined;
if (!workspaceSlug || !projectId || !inboxId || !currentProjectDetails?.inbox_view) return <></>;
return (
<>
<PageHead title={pageTitle} />
<div className="relative flex h-full overflow-hidden">
<div className="flex-shrink-0 w-[340px] h-full border-r border-custom-border-300">
<InboxSidebarRoot
@ -54,6 +59,7 @@ const ProjectInboxPage: NextPageWithLayout = observer(() => {
/>
</div>
</div>
</>
);
});

View File

@ -5,14 +5,15 @@ import { observer } from "mobx-react-lite";
// layouts
import { AppLayout } from "layouts/app-layout";
// components
import { PageHead } from "components/core";
import { ProjectIssueDetailsHeader } from "components/headers";
import { IssueDetailRoot } from "components/issues";
// ui
import { Loader } from "@plane/ui";
// types
import { NextPageWithLayout } from "lib/types";
// fetch-keys
import { useApplication, useIssueDetail } from "hooks/store";
// store hooks
import { useApplication, useIssueDetail, useProject } from "hooks/store";
const IssueDetailsPage: NextPageWithLayout = observer(() => {
// router
@ -23,17 +24,20 @@ const IssueDetailsPage: NextPageWithLayout = observer(() => {
fetchIssue,
issue: { getIssueById },
} = useIssueDetail();
const { getProjectById } = useProject();
const { theme: themeStore } = useApplication();
// fetching issue details
const { isLoading } = useSWR(
workspaceSlug && projectId && issueId ? `ISSUE_DETAIL_${workspaceSlug}_${projectId}_${issueId}` : null,
workspaceSlug && projectId && issueId
? () => fetchIssue(workspaceSlug.toString(), projectId.toString(), issueId.toString())
: null
);
// derived values
const issue = getIssueById(issueId?.toString() || "") || undefined;
const project = (issue?.project_id && getProjectById(issue?.project_id)) || undefined;
const issueLoader = !issue || isLoading ? true : false;
const pageTitle = project && issue ? `${project?.identifier}-${issue?.sequence_id} ${issue?.name}` : undefined;
useEffect(() => {
const handleToggleIssueDetailSidebar = () => {
@ -52,6 +56,7 @@ const IssueDetailsPage: NextPageWithLayout = observer(() => {
return (
<>
<PageHead title={pageTitle} />
{issueLoader ? (
<Loader className="flex h-full gap-5 p-5">
<div className="basis-2/3 space-y-2">

View File

@ -1,4 +1,7 @@
import { ReactElement } from "react";
import Head from "next/head";
import { useRouter } from "next/router";
import { observer } from "mobx-react";
// components
import { ProjectLayoutRoot } from "components/issues";
import { ProjectIssuesHeader } from "components/headers";
@ -6,12 +9,36 @@ import { ProjectIssuesHeader } from "components/headers";
import { NextPageWithLayout } from "lib/types";
// layouts
import { AppLayout } from "layouts/app-layout";
// hooks
import { useProject } from "hooks/store";
import { PageHead } from "components/core";
const ProjectIssuesPage: NextPageWithLayout = () => (
const ProjectIssuesPage: NextPageWithLayout = observer(() => {
const router = useRouter();
const { projectId } = router.query;
// store
const { getProjectById } = useProject();
if (!projectId) {
return <></>;
}
// derived values
const project = getProjectById(projectId.toString());
const pageTitle = project?.name ? `${project?.name} - Issues` : undefined;
return (
<>
<PageHead title={pageTitle} />
<Head>
<title>{project?.name} - Issues</title>
</Head>
<div className="h-full w-full">
<ProjectLayoutRoot />
</div>
);
</>
);
});
ProjectIssuesPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -1,8 +1,9 @@
import { ReactElement } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { observer } from "mobx-react";
// hooks
import { useModule } from "hooks/store";
import { useModule, useProject } from "hooks/store";
import useLocalStorage from "hooks/use-local-storage";
// layouts
import { AppLayout } from "layouts/app-layout";
@ -10,37 +11,44 @@ import { AppLayout } from "layouts/app-layout";
import { ModuleDetailsSidebar } from "components/modules";
import { ModuleLayoutRoot } from "components/issues";
import { ModuleIssuesHeader } from "components/headers";
// ui
import { PageHead } from "components/core";
import { EmptyState } from "components/common";
// assets
import emptyModule from "public/empty-state/module.svg";
// types
import { NextPageWithLayout } from "lib/types";
const ModuleIssuesPage: NextPageWithLayout = () => {
const ModuleIssuesPage: NextPageWithLayout = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query;
// store hooks
const { fetchModuleDetails } = useModule();
const { fetchModuleDetails, getModuleById } = useModule();
const { getProjectById } = useProject();
// local storage
const { setValue, storedValue } = useLocalStorage("module_sidebar_collapsed", "false");
const isSidebarCollapsed = storedValue ? (storedValue === "true" ? true : false) : false;
// fetching module details
const { error } = useSWR(
workspaceSlug && projectId && moduleId ? `CURRENT_MODULE_DETAILS_${moduleId.toString()}` : null,
workspaceSlug && projectId && moduleId
? () => fetchModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString())
: null
);
// derived values
const projectModule = moduleId ? getModuleById(moduleId.toString()) : undefined;
const project = projectId ? getProjectById(projectId.toString()) : undefined;
const pageTitle = project?.name && projectModule?.name ? `${project?.name} - ${projectModule?.name}` : undefined;
const toggleSidebar = () => {
setValue(`${!isSidebarCollapsed}`);
};
if (!workspaceSlug || !projectId || !moduleId) return <></>;
return (
<>
<PageHead title={pageTitle} />
{error ? (
<EmptyState
image={emptyModule}
@ -71,7 +79,7 @@ const ModuleIssuesPage: NextPageWithLayout = () => {
)}
</>
);
};
});
ModuleIssuesPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -1,13 +1,33 @@
import { ReactElement } from "react";
import { useRouter } from "next/router";
// layouts
import { AppLayout } from "layouts/app-layout";
// components
import { PageHead } from "components/core";
import { ModulesListView } from "components/modules";
import { ModulesListHeader } from "components/headers";
// types
import { NextPageWithLayout } from "lib/types";
// hooks
import { useProject } from "hooks/store";
import { observer } from "mobx-react";
const ProjectModulesPage: NextPageWithLayout = () => <ModulesListView />;
const ProjectModulesPage: NextPageWithLayout = observer(() => {
const router = useRouter();
const { projectId } = router.query;
// store
const { getProjectById } = useProject();
// derived values
const project = projectId ? getProjectById(projectId.toString()) : undefined;
const pageTitle = project?.name ? `${project?.name} - Modules` : undefined;
return (
<>
<PageHead title={pageTitle} />
<ModulesListView />
</>
);
});
ProjectModulesPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -14,7 +14,7 @@ import { FileService } from "services/file.service";
// layouts
import { AppLayout } from "layouts/app-layout";
// components
import { GptAssistantPopover } from "components/core";
import { GptAssistantPopover, PageHead } from "components/core";
import { PageDetailsHeader } from "components/headers/page-details";
// ui
import { DocumentEditorWithRef, DocumentReadOnlyEditorWithRef } from "@plane/document-editor";
@ -256,6 +256,8 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
return pageIdMobx ? (
<>
<PageHead title={pageTitle} />
<div className="flex h-full flex-col justify-between">
<div className="h-full w-full overflow-hidden">
{isPageReadOnly ? (
@ -363,6 +365,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
<IssuePeekOverview />
</div>
</div>
</>
) : (
<div className="grid h-full w-full place-items-center">
<Spinner />

View File

@ -6,7 +6,7 @@ import useSWR from "swr";
import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
// hooks
import { useApplication, useEventTracker, useUser } from "hooks/store";
import { useApplication, useEventTracker, useUser, useProject } from "hooks/store";
import useLocalStorage from "hooks/use-local-storage";
import useUserAuth from "hooks/use-user-auth";
import useSize from "hooks/use-window-size";
@ -24,6 +24,7 @@ import { PAGE_TABS_LIST } from "constants/page";
import { useProjectPages } from "hooks/store/use-project-page";
import { EUserWorkspaceRoles } from "constants/workspace";
import { PAGE_EMPTY_STATE_DETAILS } from "constants/empty-state";
import { PageHead } from "components/core";
const AllPagesList = dynamic<any>(() => import("components/pages").then((a) => a.AllPagesList), {
ssr: false,
@ -63,7 +64,7 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => {
commandPalette: { toggleCreatePageModal },
} = useApplication();
const { setTrackElement } = useEventTracker();
const { getProjectById } = useProject();
const { fetchProjectPages, fetchArchivedProjectPages, loader, archivedPageLoader, projectPageIds, archivedPageIds } =
useProjectPages();
// hooks
@ -101,10 +102,12 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => {
}
};
// derived values
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "pages", isLightMode);
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
const project = projectId ? getProjectById(projectId.toString()) : undefined;
const pageTitle = project?.name ? `${project?.name} - Pages` : undefined;
const MobileTabList = () => (
<Tab.List as="div" className="flex items-center justify-between border-b border-custom-border-200 px-3 pt-3 mb-4">
@ -129,6 +132,7 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => {
return (
<>
<PageHead title={pageTitle} />
{projectPageIds && archivedPageIds && projectPageIds.length + archivedPageIds.length > 0 ? (
<>
{workspaceSlug && projectId && (

View File

@ -10,6 +10,7 @@ import { ProjectSettingLayout } from "layouts/settings-layout";
import useToast from "hooks/use-toast";
// components
import { AutoArchiveAutomation, AutoCloseAutomation } from "components/automation";
import { PageHead } from "components/core";
import { ProjectSettingHeader } from "components/headers";
// types
import { NextPageWithLayout } from "lib/types";
@ -41,9 +42,13 @@ const AutomationSettingsPage: NextPageWithLayout = observer(() => {
});
};
// derived values
const isAdmin = currentProjectRole === EUserProjectRoles.ADMIN;
const pageTitle = projectDetails?.name ? `${projectDetails?.name} - Automations` : undefined;
return (
<>
<PageHead title={pageTitle} />
<section className={`w-full overflow-y-auto py-8 pr-9 ${isAdmin ? "" : "opacity-60"}`}>
<div className="flex items-center border-b border-custom-border-100 py-3.5">
<h3 className="text-xl font-medium">Automations</h3>
@ -51,6 +56,7 @@ const AutomationSettingsPage: NextPageWithLayout = observer(() => {
<AutoArchiveAutomation handleChange={handleChange} />
<AutoCloseAutomation handleChange={handleChange} />
</section>
</>
);
});

View File

@ -1,11 +1,12 @@
import { ReactElement } from "react";
import { observer } from "mobx-react-lite";
// hooks
import { useUser } from "hooks/store";
import { useUser, useProject } from "hooks/store";
// layouts
import { AppLayout } from "layouts/app-layout";
import { ProjectSettingLayout } from "layouts/settings-layout";
// components
import { PageHead } from "components/core";
import { ProjectSettingHeader } from "components/headers";
import { EstimatesList } from "components/estimates";
// types
@ -17,13 +18,18 @@ const EstimatesSettingsPage: NextPageWithLayout = observer(() => {
const {
membership: { currentProjectRole },
} = useUser();
const { currentProjectDetails } = useProject();
// derived values
const isAdmin = currentProjectRole === EUserProjectRoles.ADMIN;
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Estimates` : undefined;
return (
<>
<PageHead title={pageTitle} />
<div className={`h-full w-full overflow-y-auto py-8 pr-9 ${isAdmin ? "" : "pointer-events-none opacity-60"}`}>
<EstimatesList />
</div>
</>
);
});

View File

@ -1,41 +1,48 @@
import { ReactElement } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { observer } from "mobx-react";
// hooks
import { useUser } from "hooks/store";
import { useProject, useUser } from "hooks/store";
// layouts
import { AppLayout } from "layouts/app-layout";
import { ProjectSettingLayout } from "layouts/settings-layout";
// components
import { PageHead } from "components/core";
import { ProjectSettingHeader } from "components/headers";
import { ProjectFeaturesList } from "components/project";
// types
import { NextPageWithLayout } from "lib/types";
const FeaturesSettingsPage: NextPageWithLayout = () => {
const FeaturesSettingsPage: NextPageWithLayout = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store
const {
membership: { fetchUserProjectInfo },
} = useUser();
const { currentProjectDetails } = useProject();
// fetch the project details
const { data: memberDetails } = useSWR(
workspaceSlug && projectId ? `PROJECT_MEMBERS_ME_${workspaceSlug}_${projectId}` : null,
workspaceSlug && projectId ? () => fetchUserProjectInfo(workspaceSlug.toString(), projectId.toString()) : null
);
// derived values
const isAdmin = memberDetails?.role === 20;
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Features` : undefined;
return (
<>
<PageHead title={pageTitle} />
<section className={`w-full overflow-y-auto py-8 pr-9 ${isAdmin ? "" : "opacity-60"}`}>
<div className="flex items-center border-b border-custom-border-100 py-3.5">
<h3 className="text-xl font-medium">Features</h3>
</div>
<ProjectFeaturesList />
</section>
</>
);
};
});
FeaturesSettingsPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -8,6 +8,7 @@ import { useProject } from "hooks/store";
import { AppLayout } from "layouts/app-layout";
import { ProjectSettingLayout } from "layouts/settings-layout";
// components
import { PageHead } from "components/core";
import { ProjectSettingHeader } from "components/headers";
import {
DeleteProjectModal,
@ -32,14 +33,15 @@ const GeneralSettingsPage: NextPageWithLayout = observer(() => {
workspaceSlug && projectId ? `PROJECT_DETAILS_${projectId}` : null,
workspaceSlug && projectId ? () => fetchProjectDetails(workspaceSlug.toString(), projectId.toString()) : null
);
// derived values
const isAdmin = currentProjectDetails?.member_role === 20;
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - General Settings` : undefined;
// const currentNetwork = NETWORK_CHOICES.find((n) => n.key === projectDetails?.network);
// const selectedNetwork = NETWORK_CHOICES.find((n) => n.key === watch("network"));
const isAdmin = currentProjectDetails?.member_role === 20;
return (
<>
<PageHead title={pageTitle} />
{currentProjectDetails && (
<DeleteProjectModal
project={currentProjectDetails}

View File

@ -2,6 +2,7 @@ import { ReactElement } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { useTheme } from "next-themes";
import { observer } from "mobx-react";
// hooks
import { useUser } from "hooks/store";
// layouts
@ -11,6 +12,7 @@ import { ProjectSettingLayout } from "layouts/settings-layout";
import { IntegrationService } from "services/integrations";
import { ProjectService } from "services/project";
// components
import { PageHead } from "components/core";
import { IntegrationCard } from "components/project";
import { ProjectSettingHeader } from "components/headers";
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
@ -27,31 +29,33 @@ import { PROJECT_SETTINGS_EMPTY_STATE_DETAILS } from "constants/empty-state";
const integrationService = new IntegrationService();
const projectService = new ProjectService();
const ProjectIntegrationsPage: NextPageWithLayout = () => {
const ProjectIntegrationsPage: NextPageWithLayout = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// theme
const { resolvedTheme } = useTheme();
// store hooks
const { currentUser } = useUser();
// fetch project details
const { data: projectDetails } = useSWR<IProject>(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
workspaceSlug && projectId ? () => projectService.getProject(workspaceSlug as string, projectId as string) : null
);
// fetch Integrations list
const { data: workspaceIntegrations } = useSWR(
workspaceSlug ? WORKSPACE_INTEGRATIONS(workspaceSlug as string) : null,
() => (workspaceSlug ? integrationService.getWorkspaceIntegrationsList(workspaceSlug as string) : null)
);
// derived values
const emptyStateDetail = PROJECT_SETTINGS_EMPTY_STATE_DETAILS["integrations"];
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const emptyStateImage = getEmptyStateImagePath("project-settings", "integrations", isLightMode);
const isAdmin = projectDetails?.member_role === 20;
const pageTitle = projectDetails?.name ? `${projectDetails?.name} - Integrations` : undefined;
return (
<>
<PageHead title={pageTitle} />
<div className={`h-full w-full gap-10 overflow-y-auto py-8 pr-9 ${isAdmin ? "" : "opacity-60"}`}>
<div className="flex items-center border-b border-custom-border-100 py-3.5">
<h3 className="text-xl font-medium">Integrations</h3>
@ -82,8 +86,9 @@ const ProjectIntegrationsPage: NextPageWithLayout = () => {
<IntegrationsSettingsLoader />
)}
</div>
</>
);
};
});
ProjectIntegrationsPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -1,18 +1,30 @@
import { ReactElement } from "react";
import { observer } from "mobx-react";
// layouts
import { AppLayout } from "layouts/app-layout";
import { ProjectSettingLayout } from "layouts/settings-layout";
// components
import { PageHead } from "components/core";
import { ProjectSettingsLabelList } from "components/labels";
import { ProjectSettingHeader } from "components/headers";
// types
import { NextPageWithLayout } from "lib/types";
// hooks
import { useProject } from "hooks/store";
const LabelsSettingsPage: NextPageWithLayout = () => (
const LabelsSettingsPage: NextPageWithLayout = observer(() => {
const { currentProjectDetails } = useProject();
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Labels` : undefined;
return (
<>
<PageHead title={pageTitle} />
<div className="h-full w-full gap-10 overflow-y-auto py-8 pr-9">
<ProjectSettingsLabelList />
</div>
);
</>
);
});
LabelsSettingsPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -1,19 +1,33 @@
import { ReactElement } from "react";
import { observer } from "mobx-react";
// layouts
import { AppLayout } from "layouts/app-layout";
import { ProjectSettingLayout } from "layouts/settings-layout";
// components
import { PageHead } from "components/core";
import { ProjectSettingHeader } from "components/headers";
import { ProjectMemberList, ProjectSettingsMemberDefaults } from "components/project";
// types
import { NextPageWithLayout } from "lib/types";
// hooks
import { useProject } from "hooks/store";
const MembersSettingsPage: NextPageWithLayout = () => (
const MembersSettingsPage: NextPageWithLayout = observer(() => {
// store
const { currentProjectDetails } = useProject();
// derived values
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Members` : undefined;
return (
<>
<PageHead title={pageTitle} />
<section className={`w-full overflow-y-auto py-8 pr-9`}>
<ProjectSettingsMemberDefaults />
<ProjectMemberList />
</section>
);
</>
);
});
MembersSettingsPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -1,13 +1,15 @@
import { ReactElement } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { observer } from "mobx-react";
// hooks
import { useProjectView } from "hooks/store";
import { useProject, useProjectView } from "hooks/store";
// layouts
import { AppLayout } from "layouts/app-layout";
// components
import { ProjectViewLayoutRoot } from "components/issues";
import { ProjectViewIssuesHeader } from "components/headers";
import { PageHead } from "components/core";
// ui
import { EmptyState } from "components/common";
// assets
@ -15,12 +17,17 @@ import emptyView from "public/empty-state/view.svg";
// types
import { NextPageWithLayout } from "lib/types";
const ProjectViewIssuesPage: NextPageWithLayout = () => {
const ProjectViewIssuesPage: NextPageWithLayout = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId, viewId } = router.query;
// store hooks
const { fetchViewDetails } = useProjectView();
const { fetchViewDetails, getViewById } = useProjectView();
const { getProjectById } = useProject();
// derived values
const projectView = viewId ? getViewById(viewId.toString()) : undefined;
const project = projectId ? getProjectById(projectId.toString()) : undefined;
const pageTitle = project?.name && projectView?.name ? `${project?.name} - ${projectView?.name}` : undefined;
const { error } = useSWR(
workspaceSlug && projectId && viewId ? `VIEW_DETAILS_${viewId.toString()}` : null,
@ -42,11 +49,14 @@ const ProjectViewIssuesPage: NextPageWithLayout = () => {
}}
/>
) : (
<>
<PageHead title={pageTitle} />
<ProjectViewLayoutRoot />
</>
)}
</>
);
};
});
ProjectViewIssuesPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -1,13 +1,34 @@
import { ReactElement } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react";
// components
import { ProjectViewsHeader } from "components/headers";
import { ProjectViewsList } from "components/views";
import { PageHead } from "components/core";
// hooks
import { useProject } from "hooks/store";
// layouts
import { AppLayout } from "layouts/app-layout";
// types
import { NextPageWithLayout } from "lib/types";
const ProjectViewsPage: NextPageWithLayout = () => <ProjectViewsList />;
const ProjectViewsPage: NextPageWithLayout = observer(() => {
// router
const router = useRouter();
const { projectId } = router.query;
// store
const { getProjectById } = useProject();
// derived values
const project = projectId ? getProjectById(projectId.toString()) : undefined;
const pageTitle = project?.name ? `${project?.name} - Views` : undefined;
return (
<>
<PageHead title={pageTitle} />
<ProjectViewsList />
</>
);
});
ProjectViewsPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -1,13 +1,28 @@
import { ReactElement } from "react";
import { observer } from "mobx-react";
// components
import { PageHead } from "components/core";
import { ProjectCardList } from "components/project";
import { ProjectsHeader } from "components/headers";
// layouts
import { AppLayout } from "layouts/app-layout";
// type
import { NextPageWithLayout } from "lib/types";
import { useWorkspace } from "hooks/store";
const ProjectsPage: NextPageWithLayout = () => <ProjectCardList />;
const ProjectsPage: NextPageWithLayout = observer(() => {
// store
const { currentWorkspace } = useWorkspace();
// derived values
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Projects` : undefined;
return (
<>
<PageHead title={pageTitle} />
<ProjectCardList />
</>
);
});
ProjectsPage.getLayout = function getLayout(page: ReactElement) {
return <AppLayout header={<ProjectsHeader />}>{page}</AppLayout>;

View File

@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
import useSWR from "swr";
import { useTheme } from "next-themes";
// store hooks
import { useUser } from "hooks/store";
import { useUser, useWorkspace } from "hooks/store";
// layouts
import { AppLayout } from "layouts/app-layout";
import { WorkspaceSettingLayout } from "layouts/settings-layout";
@ -23,6 +23,7 @@ import { NextPageWithLayout } from "lib/types";
import { API_TOKENS_LIST } from "constants/fetch-keys";
import { EUserWorkspaceRoles } from "constants/workspace";
import { WORKSPACE_SETTINGS_EMPTY_STATE_DETAILS } from "constants/empty-state";
import { PageHead } from "components/core";
const apiTokenService = new APITokenService();
@ -39,6 +40,7 @@ const ApiTokensPage: NextPageWithLayout = observer(() => {
membership: { currentWorkspaceRole },
currentUser,
} = useUser();
const { currentWorkspace } = useWorkspace();
const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN;
@ -49,12 +51,16 @@ const ApiTokensPage: NextPageWithLayout = observer(() => {
const emptyStateDetail = WORKSPACE_SETTINGS_EMPTY_STATE_DETAILS["api-tokens"];
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const emptyStateImage = getEmptyStateImagePath("workspace-settings", "api-tokens", isLightMode);
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - API Tokens` : undefined;
if (!isAdmin)
return (
<>
<PageHead title={pageTitle} />
<div className="mt-10 flex h-full w-full justify-center p-4">
<p className="text-sm text-custom-text-300">You are not authorized to access this page.</p>
</div>
</>
);
if (!tokens) {
@ -63,6 +69,7 @@ const ApiTokensPage: NextPageWithLayout = observer(() => {
return (
<>
<PageHead title={pageTitle} />
<CreateApiTokenModal isOpen={isCreateTokenModalOpen} onClose={() => setIsCreateTokenModalOpen(false)} />
<section className="h-full w-full overflow-y-auto py-8 pr-9">
{tokens.length > 0 ? (

View File

@ -1,11 +1,12 @@
import { observer } from "mobx-react-lite";
// hooks
import { useUser } from "hooks/store";
import { useUser, useWorkspace } from "hooks/store";
// layouts
import { AppLayout } from "layouts/app-layout";
import { WorkspaceSettingLayout } from "layouts/settings-layout";
// component
import { WorkspaceSettingHeader } from "components/headers";
import { PageHead } from "components/core";
// ui
import { Button } from "@plane/ui";
// types
@ -18,17 +19,24 @@ const BillingSettingsPage: NextPageWithLayout = observer(() => {
const {
membership: { currentWorkspaceRole },
} = useUser();
const { currentWorkspace } = useWorkspace();
// derived values
const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN;
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Billing & Plans` : undefined;
if (!isAdmin)
return (
<>
<PageHead title={pageTitle} />
<div className="mt-10 flex h-full w-full justify-center p-4">
<p className="text-sm text-custom-text-300">You are not authorized to access this page.</p>
</div>
</>
);
return (
<>
<PageHead title={pageTitle} />
<section className="w-full overflow-y-auto py-8 pr-9">
<div>
<div className="flex items-center border-b border-custom-border-100 py-3.5">
@ -45,6 +53,7 @@ const BillingSettingsPage: NextPageWithLayout = observer(() => {
</div>
</div>
</section>
</>
);
});

View File

@ -1,12 +1,13 @@
import { observer } from "mobx-react-lite";
// hooks
import { useUser } from "hooks/store";
import { useUser, useWorkspace } from "hooks/store";
// layout
import { AppLayout } from "layouts/app-layout";
import { WorkspaceSettingLayout } from "layouts/settings-layout";
// components
import { WorkspaceSettingHeader } from "components/headers";
import ExportGuide from "components/exporter/guide";
import { PageHead } from "components/core";
// types
import { NextPageWithLayout } from "lib/types";
// constants
@ -17,24 +18,33 @@ const ExportsPage: NextPageWithLayout = observer(() => {
const {
membership: { currentWorkspaceRole },
} = useUser();
const { currentWorkspace } = useWorkspace();
// derived values
const hasPageAccess =
currentWorkspaceRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentWorkspaceRole);
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Exports` : undefined;
if (!hasPageAccess)
return (
<>
<PageHead title={pageTitle} />
<div className="mt-10 flex h-full w-full justify-center p-4">
<p className="text-sm text-custom-text-300">You are not authorized to access this page.</p>
</div>
</>
);
return (
<>
<PageHead title={pageTitle} />
<div className="w-full overflow-y-auto py-8 pr-9">
<div className="flex items-center border-b border-custom-border-100 py-3.5">
<h3 className="text-xl font-medium">Exports</h3>
</div>
<ExportGuide />
</div>
</>
);
});

View File

@ -1,12 +1,13 @@
import { observer } from "mobx-react-lite";
// hooks
import { useUser } from "hooks/store";
import { useUser, useWorkspace } from "hooks/store";
// layouts
import { WorkspaceSettingLayout } from "layouts/settings-layout";
import { AppLayout } from "layouts/app-layout";
// components
import IntegrationGuide from "components/integration/guide";
import { WorkspaceSettingHeader } from "components/headers";
import { PageHead } from "components/core";
// types
import { NextPageWithLayout } from "lib/types";
// constants
@ -17,23 +18,32 @@ const ImportsPage: NextPageWithLayout = observer(() => {
const {
membership: { currentWorkspaceRole },
} = useUser();
const { currentWorkspace } = useWorkspace();
// derived values
const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN;
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Imports` : undefined;
if (!isAdmin)
return (
<>
<PageHead title={pageTitle} />
<div className="mt-10 flex h-full w-full justify-center p-4">
<p className="text-sm text-custom-text-300">You are not authorized to access this page.</p>
</div>
</>
);
return (
<>
<PageHead title={pageTitle} />
<section className="w-full overflow-y-auto py-8 pr-9">
<div className="flex items-center border-b border-custom-border-100 py-3.5">
<h3 className="text-xl font-medium">Imports</h3>
</div>
<IntegrationGuide />
</section>
</>
);
});

View File

@ -1,14 +1,30 @@
import { ReactElement } from "react";
import { observer } from "mobx-react";
// layouts
import { AppLayout } from "layouts/app-layout";
import { WorkspaceSettingLayout } from "layouts/settings-layout";
// hooks
import { useWorkspace } from "hooks/store";
// components
import { WorkspaceSettingHeader } from "components/headers";
import { WorkspaceDetails } from "components/workspace";
import { PageHead } from "components/core";
// types
import { NextPageWithLayout } from "lib/types";
const WorkspaceSettingsPage: NextPageWithLayout = () => <WorkspaceDetails />;
const WorkspaceSettingsPage: NextPageWithLayout = observer(() => {
// store hooks
const { currentWorkspace } = useWorkspace();
// derived values
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - General Settings` : undefined;
return (
<>
<PageHead title={pageTitle} />
<WorkspaceDetails />
</>
);
});
WorkspaceSettingsPage.getLayout = function getLayout(page: ReactElement) {
return (

View File

@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
// hooks
import { useUser } from "hooks/store";
import { useUser, useWorkspace } from "hooks/store";
// services
import { IntegrationService } from "services/integrations";
// layouts
@ -12,6 +12,7 @@ import { WorkspaceSettingLayout } from "layouts/settings-layout";
// components
import { SingleIntegrationCard } from "components/integration";
import { WorkspaceSettingHeader } from "components/headers";
import { PageHead } from "components/core";
// ui
import { IntegrationAndImportExportBanner, IntegrationsSettingsLoader } from "components/ui";
// types
@ -31,14 +32,20 @@ const WorkspaceIntegrationsPage: NextPageWithLayout = observer(() => {
const {
membership: { currentWorkspaceRole },
} = useUser();
const { currentWorkspace } = useWorkspace();
// derived values
const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN;
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Integrations` : undefined;
if (!isAdmin)
return (
<>
<PageHead title={pageTitle} />
<div className="mt-10 flex h-full w-full justify-center p-4">
<p className="text-sm text-custom-text-300">You are not authorized to access this page.</p>
</div>
</>
);
const { data: appIntegrations } = useSWR(workspaceSlug && isAdmin ? APP_INTEGRATIONS : null, () =>
@ -46,16 +53,21 @@ const WorkspaceIntegrationsPage: NextPageWithLayout = observer(() => {
);
return (
<>
<PageHead title={pageTitle} />
<section className="w-full overflow-y-auto py-8 pr-9">
<IntegrationAndImportExportBanner bannerName="Integrations" />
<div>
{appIntegrations ? (
appIntegrations.map((integration) => <SingleIntegrationCard key={integration.id} integration={integration} />)
appIntegrations.map((integration) => (
<SingleIntegrationCard key={integration.id} integration={integration} />
))
) : (
<IntegrationsSettingsLoader />
)}
</div>
</section>
</>
);
});

View File

@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Search } from "lucide-react";
// hooks
import { useEventTracker, useMember, useUser } from "hooks/store";
import { useEventTracker, useMember, useUser, useWorkspace } from "hooks/store";
import useToast from "hooks/use-toast";
// layouts
import { AppLayout } from "layouts/app-layout";
@ -11,6 +11,7 @@ import { WorkspaceSettingLayout } from "layouts/settings-layout";
// components
import { WorkspaceSettingHeader } from "components/headers";
import { SendWorkspaceInvitationModal, WorkspaceMembersList } from "components/workspace";
import { PageHead } from "components/core";
// ui
import { Button } from "@plane/ui";
// types
@ -37,6 +38,7 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => {
const {
workspace: { inviteMembersToWorkspace },
} = useMember();
const { currentWorkspace } = useWorkspace();
// toast alert
const { setToastAlert } = useToast();
@ -83,11 +85,14 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => {
});
};
// derived values
const hasAddMemberPermission =
currentWorkspaceRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentWorkspaceRole);
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Members` : undefined;
return (
<>
<PageHead title={pageTitle} />
<SendWorkspaceInvitationModal
isOpen={inviteModal}
onClose={() => setInviteModal(false)}

View File

@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
// hooks
import { useUser, useWebhook } from "hooks/store";
import { useUser, useWebhook, useWorkspace } from "hooks/store";
// layouts
import { AppLayout } from "layouts/app-layout";
import { WorkspaceSettingLayout } from "layouts/settings-layout";
@ -12,6 +12,7 @@ import useToast from "hooks/use-toast";
// components
import { WorkspaceSettingHeader } from "components/headers";
import { DeleteWebhookModal, WebhookDeleteSection, WebhookForm } from "components/web-hooks";
import { PageHead } from "components/core";
// ui
import { Spinner } from "@plane/ui";
// types
@ -29,6 +30,7 @@ const WebhookDetailsPage: NextPageWithLayout = observer(() => {
membership: { currentWorkspaceRole },
} = useUser();
const { currentWebhook, fetchWebhookById, updateWebhook } = useWebhook();
const { currentWorkspace } = useWorkspace();
// toast
const { setToastAlert } = useToast();
@ -38,6 +40,7 @@ const WebhookDetailsPage: NextPageWithLayout = observer(() => {
// }, [clearSecretKey, isCreated]);
const isAdmin = currentWorkspaceRole === 20;
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Webhook` : undefined;
useSWR(
workspaceSlug && webhookId && isAdmin ? `WEBHOOK_DETAILS_${workspaceSlug}_${webhookId}` : null,
@ -76,9 +79,12 @@ const WebhookDetailsPage: NextPageWithLayout = observer(() => {
if (!isAdmin)
return (
<>
<PageHead title={pageTitle} />
<div className="mt-10 flex h-full w-full justify-center p-4">
<p className="text-sm text-custom-text-300">You are not authorized to access this page.</p>
</div>
</>
);
if (!currentWebhook)
@ -90,6 +96,7 @@ const WebhookDetailsPage: NextPageWithLayout = observer(() => {
return (
<>
<PageHead title={pageTitle} />
<DeleteWebhookModal isOpen={deleteWebhookModal} onClose={() => setDeleteWebhookModal(false)} />
<div className="w-full space-y-8 overflow-y-auto py-8 pr-9">
<WebhookForm onSubmit={async (data) => await handleUpdateWebhook(data)} data={currentWebhook} />

View File

@ -19,6 +19,7 @@ import { WebhookSettingsLoader } from "components/ui";
import { NextPageWithLayout } from "lib/types";
// constants
import { WORKSPACE_SETTINGS_EMPTY_STATE_DETAILS } from "constants/empty-state";
import { PageHead } from "components/core";
const WebhooksListPage: NextPageWithLayout = observer(() => {
// states
@ -47,6 +48,7 @@ const WebhooksListPage: NextPageWithLayout = observer(() => {
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const emptyStateImage = getEmptyStateImagePath("workspace-settings", "webhooks", isLightMode);
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Webhooks` : undefined;
// clear secret key when modal is closed.
useEffect(() => {
@ -55,14 +57,19 @@ const WebhooksListPage: NextPageWithLayout = observer(() => {
if (!isAdmin)
return (
<>
<PageHead title={pageTitle} />
<div className="mt-10 flex h-full w-full justify-center p-4">
<p className="text-sm text-custom-text-300">You are not authorized to access this page.</p>
</div>
</>
);
if (!webhooks) return <WebhookSettingsLoader />;
return (
<>
<PageHead title={pageTitle} />
<div className="h-full w-full overflow-hidden py-8 pr-9">
<CreateWebhookModal
createWebhook={createWebhook}
@ -102,6 +109,7 @@ const WebhooksListPage: NextPageWithLayout = observer(() => {
</div>
)}
</div>
</>
);
});

View File

@ -7,15 +7,26 @@ import { AllIssueLayoutRoot } from "components/issues";
import { GlobalIssuesHeader } from "components/headers";
// types
import { NextPageWithLayout } from "lib/types";
import { observer } from "mobx-react";
import { useWorkspace } from "hooks/store";
import { PageHead } from "components/core";
const GlobalViewIssuesPage: NextPageWithLayout = () => (
const GlobalViewIssuesPage: NextPageWithLayout = observer(() => {
const { currentWorkspace } = useWorkspace();
// derived values
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Views` : undefined;
return (
<>
<PageHead title={pageTitle} />
<div className="h-full overflow-hidden bg-custom-background-100">
<div className="flex h-full w-full flex-col border-b border-custom-border-300">
<GlobalViewsHeader />
<AllIssueLayoutRoot />
</div>
</div>
);
</>
);
});
GlobalViewIssuesPage.getLayout = function getLayout(page: ReactElement) {
return <AppLayout header={<GlobalIssuesHeader activeLayout="spreadsheet" />}>{page}</AppLayout>;

View File

@ -1,7 +1,9 @@
import React, { useState, ReactElement } from "react";
import { observer } from "mobx-react";
// layouts
import { AppLayout } from "layouts/app-layout";
// components
import { PageHead } from "components/core";
import { GlobalDefaultViewListItem, GlobalViewsList } from "components/workspace";
import { GlobalIssuesHeader } from "components/headers";
// ui
@ -12,11 +14,19 @@ import { Search } from "lucide-react";
import { NextPageWithLayout } from "lib/types";
// constants
import { DEFAULT_GLOBAL_VIEWS_LIST } from "constants/workspace";
// hooks
import { useWorkspace } from "hooks/store";
const WorkspaceViewsPage: NextPageWithLayout = () => {
const WorkspaceViewsPage: NextPageWithLayout = observer(() => {
const [query, setQuery] = useState("");
// store
const { currentWorkspace } = useWorkspace();
// derived values
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - All Views` : undefined;
return (
<>
<PageHead title={pageTitle} />
<div className="flex flex-col">
<div className="flex h-full w-full flex-col overflow-hidden">
<div className="flex w-full items-center gap-2.5 border-b border-custom-border-200 px-5 py-3">
@ -35,8 +45,9 @@ const WorkspaceViewsPage: NextPageWithLayout = () => {
))}
<GlobalViewsList searchQuery={query} />
</div>
</>
);
};
});
WorkspaceViewsPage.getLayout = function getLayout(page: ReactElement) {
return <AppLayout header={<GlobalIssuesHeader activeLayout="list" />}>{page}</AppLayout>;

View File

@ -12,6 +12,7 @@ import { useEventTracker } from "hooks/store";
import DefaultLayout from "layouts/default-layout";
// components
import { LatestFeatureBlock } from "components/common";
import { PageHead } from "components/core";
// ui
import { Button, Input } from "@plane/ui";
// images
@ -85,6 +86,8 @@ const ForgotPasswordPage: NextPageWithLayout = () => {
};
return (
<>
<PageHead title="Forgot Password" />
<div className="h-full w-full bg-onboarding-gradient-100">
<div className="flex items-center justify-between px-8 pb-4 sm:px-16 sm:py-5 lg:px-28 ">
<div className="flex items-center gap-x-2 py-10">
@ -138,6 +141,7 @@ const ForgotPasswordPage: NextPageWithLayout = () => {
</div>
</div>
</div>
</>
);
};

View File

@ -12,6 +12,7 @@ import { useEventTracker } from "hooks/store";
import DefaultLayout from "layouts/default-layout";
// components
import { LatestFeatureBlock } from "components/common";
import { PageHead } from "components/core";
// ui
import { Button, Input } from "@plane/ui";
// images
@ -90,6 +91,8 @@ const ResetPasswordPage: NextPageWithLayout = () => {
};
return (
<>
<PageHead title="Reset Password" />
<div className="h-full w-full bg-onboarding-gradient-100">
<div className="flex items-center justify-between px-8 pb-4 sm:px-16 sm:py-5 lg:px-28 ">
<div className="flex items-center gap-x-2 py-10">
@ -174,6 +177,7 @@ const ResetPasswordPage: NextPageWithLayout = () => {
</div>
</div>
</div>
</>
);
};

View File

@ -7,6 +7,7 @@ import { useApplication, useUser } from "hooks/store";
import DefaultLayout from "layouts/default-layout";
// components
import { SignUpRoot } from "components/account";
import { PageHead } from "components/core";
// ui
import { Spinner } from "@plane/ui";
// assets
@ -29,6 +30,8 @@ const SignUpPage: NextPageWithLayout = observer(() => {
);
return (
<>
<PageHead title="Sign Up" />
<div className="h-full w-full bg-onboarding-gradient-100">
<div className="flex items-center justify-between px-8 pb-4 sm:px-16 sm:py-5 lg:px-28">
<div className="flex items-center gap-x-2 py-10">
@ -43,6 +46,7 @@ const SignUpPage: NextPageWithLayout = observer(() => {
</div>
</div>
</div>
</>
);
});

View File

@ -11,6 +11,7 @@ import DefaultLayout from "layouts/default-layout";
import { UserAuthWrapper } from "layouts/auth-layout";
// components
import { CreateWorkspaceForm } from "components/workspace";
import { PageHead } from "components/core";
// images
import BlackHorizontalLogo from "public/plane-logos/black-horizontal-with-blue-logo.svg";
import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.svg";
@ -37,6 +38,8 @@ const CreateWorkspacePage: NextPageWithLayout = observer(() => {
};
return (
<>
<PageHead title="Create Workspace" />
<div className="flex h-full flex-col gap-y-2 overflow-hidden sm:flex-row sm:gap-y-0">
<div className="relative h-1/6 flex-shrink-0 sm:w-2/12 md:w-3/12 lg:w-1/5">
<div className="absolute left-0 top-1/2 h-[0.5px] w-full -translate-y-1/2 border-b-[0.5px] border-custom-border-200 sm:left-1/2 sm:top-0 sm:h-screen sm:w-[0.5px] sm:-translate-x-1/2 sm:translate-y-0 sm:border-r-[0.5px] md:left-1/3" />
@ -69,6 +72,7 @@ const CreateWorkspacePage: NextPageWithLayout = observer(() => {
</div>
</div>
</div>
</>
);
});

View File

@ -13,6 +13,7 @@ import { Loader } from "@plane/ui";
import { Lightbulb } from "lucide-react";
// components
import { InstanceAIForm } from "components/instance";
import { PageHead } from "components/core";
const InstanceAdminAIPage: NextPageWithLayout = observer(() => {
// store
@ -23,6 +24,8 @@ const InstanceAdminAIPage: NextPageWithLayout = observer(() => {
useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations());
return (
<>
<PageHead title="God Mode - AI" />
<div className="flex flex-col gap-8">
<div className="mb-2 border-b border-custom-border-100 pb-3">
<div className="pb-1 text-xl font-medium text-custom-text-100">AI features for all your workspaces</div>
@ -54,6 +57,7 @@ const InstanceAdminAIPage: NextPageWithLayout = observer(() => {
</Loader>
)}
</div>
</>
);
});

View File

@ -14,6 +14,7 @@ import useToast from "hooks/use-toast";
import { Loader, ToggleSwitch } from "@plane/ui";
// components
import { InstanceGithubConfigForm, InstanceGoogleConfigForm } from "components/instance";
import { PageHead } from "components/core";
const InstanceAdminAuthorizationPage: NextPageWithLayout = observer(() => {
// store
@ -64,6 +65,8 @@ const InstanceAdminAuthorizationPage: NextPageWithLayout = observer(() => {
};
return (
<>
<PageHead title="God Mode - SSO and OAuth" />
<div className="flex flex-col gap-8">
<div className="mb-2 border-b border-custom-border-100 pb-3">
<div className="pb-1 text-xl font-medium text-custom-text-100">Single sign-on and OAuth</div>
@ -176,6 +179,7 @@ const InstanceAdminAuthorizationPage: NextPageWithLayout = observer(() => {
</Loader>
)}
</div>
</>
);
});

View File

@ -11,6 +11,7 @@ import { useApplication } from "hooks/store";
import { Loader } from "@plane/ui";
// components
import { InstanceEmailForm } from "components/instance";
import { PageHead } from "components/core";
const InstanceAdminEmailPage: NextPageWithLayout = observer(() => {
// store
@ -21,6 +22,8 @@ const InstanceAdminEmailPage: NextPageWithLayout = observer(() => {
useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations());
return (
<>
<PageHead title="God Mode - Email" />
<div className="flex flex-col gap-8">
<div className="mb-2 border-b border-custom-border-100 pb-3">
<div className="pb-1 text-xl font-medium text-custom-text-100">Secure emails from your own instance</div>
@ -44,6 +47,7 @@ const InstanceAdminEmailPage: NextPageWithLayout = observer(() => {
</Loader>
)}
</div>
</>
);
});

View File

@ -11,6 +11,7 @@ import { useApplication } from "hooks/store";
import { Loader } from "@plane/ui";
// components
import { InstanceImageConfigForm } from "components/instance";
import { PageHead } from "components/core";
const InstanceAdminImagePage: NextPageWithLayout = observer(() => {
// store
@ -21,6 +22,8 @@ const InstanceAdminImagePage: NextPageWithLayout = observer(() => {
useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations());
return (
<>
<PageHead title="God Mode - Images" />
<div className="flex flex-col gap-8">
<div className="mb-2 border-b border-custom-border-100 pb-3">
<div className="pb-1 text-xl font-medium text-custom-text-100">Third-party image libraries</div>
@ -40,6 +43,7 @@ const InstanceAdminImagePage: NextPageWithLayout = observer(() => {
</Loader>
)}
</div>
</>
);
});

View File

@ -11,6 +11,7 @@ import { useApplication } from "hooks/store";
import { Loader } from "@plane/ui";
// components
import { InstanceGeneralForm } from "components/instance";
import { PageHead } from "components/core";
const InstanceAdminPage: NextPageWithLayout = observer(() => {
// store hooks
@ -22,6 +23,8 @@ const InstanceAdminPage: NextPageWithLayout = observer(() => {
useSWR("INSTANCE_ADMINS", () => fetchInstanceAdmins());
return (
<>
<PageHead title="God Mode - General Settings" />
<div className="flex h-full w-full flex-col gap-8">
<div className="mb-2 border-b border-custom-border-100 pb-3">
<div className="pb-1 text-xl font-medium text-custom-text-100">ID your instance easily</div>
@ -42,6 +45,7 @@ const InstanceAdminPage: NextPageWithLayout = observer(() => {
</Loader>
)}
</div>
</>
);
});

View File

@ -32,7 +32,7 @@ import { ROLE } from "constants/workspace";
import { MEMBER_ACCEPTED } from "constants/event-tracker";
// components
import { EmptyState } from "components/common";
import { PageHead } from "components/core";
// services
const workspaceService = new WorkspaceService();
const userService = new UserService();
@ -126,6 +126,8 @@ const UserInvitationsPage: NextPageWithLayout = observer(() => {
};
return (
<>
<PageHead title="Invitations" />
<div className="flex h-full flex-col gap-y-2 overflow-hidden sm:flex-row sm:gap-y-0">
<div className="relative h-1/6 flex-shrink-0 sm:w-2/12 md:w-3/12 lg:w-1/5">
<div className="absolute left-0 top-1/2 h-[0.5px] w-full -translate-y-1/2 border-b-[0.5px] border-custom-border-200 sm:left-1/2 sm:top-0 sm:h-screen sm:w-[0.5px] sm:-translate-x-1/2 sm:translate-y-0 sm:border-r-[0.5px] md:left-1/3" />
@ -228,6 +230,7 @@ const UserInvitationsPage: NextPageWithLayout = observer(() => {
)
) : null}
</div>
</>
);
});

View File

@ -17,6 +17,7 @@ import DefaultLayout from "layouts/default-layout";
import { UserAuthWrapper } from "layouts/auth-layout";
// components
import { InviteMembers, JoinWorkspaces, UserDetails, SwitchOrDeleteAccountModal } from "components/onboarding";
import { PageHead } from "components/core";
// ui
import { Avatar, Spinner } from "@plane/ui";
// images
@ -142,6 +143,7 @@ const OnboardingPage: NextPageWithLayout = observer(() => {
return (
<>
<PageHead title="Onboarding" />
<SwitchOrDeleteAccountModal isOpen={showDeleteAccountModal} onClose={() => setShowDeleteAccountModal(false)} />
{user && step !== null ? (
<div className={`fixed flex h-full w-full flex-col bg-onboarding-gradient-100`}>

View File

@ -9,7 +9,7 @@ import { UserService } from "services/user.service";
// layouts
import { ProfileSettingsLayout } from "layouts/settings-layout";
// components
import { ActivityIcon, ActivityMessage, IssueLink } from "components/core";
import { ActivityIcon, ActivityMessage, IssueLink, PageHead } from "components/core";
import { RichReadOnlyEditor } from "@plane/rich-text-editor";
// icons
import { History, MessageSquare } from "lucide-react";
@ -32,6 +32,8 @@ const ProfileActivityPage: NextPageWithLayout = observer(() => {
const { theme: themeStore } = useApplication();
return (
<>
<PageHead title="Profile - Activity" />
<section className="mx-auto mt-5 md:mt-16 flex h-full w-full flex-col overflow-hidden px-8 pb-8 lg:w-3/5">
<div className="flex items-center border-b border-custom-border-100 gap-4 pb-3.5">
<SidebarHamburgerToggle onClick={() => themeStore.toggleSidebar()} />
@ -185,6 +187,7 @@ const ProfileActivityPage: NextPageWithLayout = observer(() => {
<ActivitySettingsLoader />
)}
</section>
</>
);
});

View File

@ -6,6 +6,8 @@ import { Controller, useForm } from "react-hook-form";
import { useApplication, useUser } from "hooks/store";
// services
import { UserService } from "services/user.service";
// components
import { PageHead } from "components/core";
// hooks
import useToast from "hooks/use-toast";
// layout
@ -88,6 +90,8 @@ const ChangePasswordPage: NextPageWithLayout = observer(() => {
);
return (
<>
<PageHead title="Profile - Change Password" />
<div className="flex flex-col h-full">
<div className="block md:hidden flex-shrink-0 border-b border-custom-border-200 p-4">
<SidebarHamburgerToggle onClick={() => themeStore.toggleSidebar()} />
@ -164,7 +168,9 @@ const ChangePasswordPage: NextPageWithLayout = observer(() => {
/>
)}
/>
{errors.confirm_password && <span className="text-xs text-red-500">{errors.confirm_password.message}</span>}
{errors.confirm_password && (
<span className="text-xs text-red-500">{errors.confirm_password.message}</span>
)}
</div>
</div>
@ -175,6 +181,7 @@ const ChangePasswordPage: NextPageWithLayout = observer(() => {
</div>
</form>
</div>
</>
);
});

View File

@ -11,7 +11,7 @@ import useToast from "hooks/use-toast";
// layouts
import { ProfileSettingsLayout } from "layouts/settings-layout";
// components
import { ImagePickerPopover, UserImageUploadModal } from "components/core";
import { ImagePickerPopover, UserImageUploadModal, PageHead } from "components/core";
import { DeactivateAccountModal } from "components/account";
// ui
import { Button, CustomSelect, CustomSearchSelect, Input, Spinner } from "@plane/ui";
@ -57,7 +57,7 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => {
// store hooks
const { currentUser: myProfile, updateCurrentUser, currentUserLoader } = useUser();
// custom hooks
const { } = useUserAuth({ user: myProfile, isLoading: currentUserLoader });
const {} = useUserAuth({ user: myProfile, isLoading: currentUserLoader });
const { theme: themeStore } = useApplication();
useEffect(() => {
@ -138,6 +138,7 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => {
return (
<>
<PageHead title="Profile - General Settings" />
<div className="flex flex-col h-full">
<div className="block md:hidden flex-shrink-0 border-b border-custom-border-200 p-4">
<SidebarHamburgerToggle onClick={() => themeStore.toggleSidebar()} />
@ -296,7 +297,8 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => {
ref={ref}
hasError={Boolean(errors.email)}
placeholder="Enter your email"
className={`w-full rounded-md cursor-not-allowed !bg-custom-background-80 ${errors.email ? "border-red-500" : ""
className={`w-full rounded-md cursor-not-allowed !bg-custom-background-80 ${
errors.email ? "border-red-500" : ""
}`}
disabled
/>
@ -385,7 +387,9 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => {
render={({ field: { value, onChange } }) => (
<CustomSearchSelect
value={value}
label={value ? TIME_ZONES.find((t) => t.value === value)?.label ?? value : "Select a timezone"}
label={
value ? TIME_ZONES.find((t) => t.value === value)?.label ?? value : "Select a timezone"
}
options={timeZoneOptions}
onChange={onChange}
optionsClassName="w-full"
@ -409,7 +413,11 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => {
<Disclosure as="div" className="border-t border-custom-border-100 px-8">
{({ open }) => (
<>
<Disclosure.Button as="button" type="button" className="flex w-full items-center justify-between py-4">
<Disclosure.Button
as="button"
type="button"
className="flex w-full items-center justify-between py-4"
>
<span className="text-lg tracking-tight">Deactivate account</span>
<ChevronDown className={`h-5 w-5 transition-all ${open ? "rotate-180" : ""}`} />
</Disclosure.Button>
@ -426,8 +434,8 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => {
<div className="flex flex-col gap-8">
<span className="text-sm tracking-tight">
The danger zone of the profile page is a critical area that requires careful consideration and
attention. When deactivating an account, all of the data and resources within that account will be
permanently removed and cannot be recovered.
attention. When deactivating an account, all of the data and resources within that account
will be permanently removed and cannot be recovered.
</span>
<div>
<Button variant="danger" onClick={() => setDeactivateAccountModal(true)}>

View File

@ -6,6 +6,7 @@ import { ProfilePreferenceSettingsLayout } from "layouts/settings-layout/profile
import { EmailSettingsLoader } from "components/ui";
// components
import { EmailNotificationForm } from "components/profile/preferences";
import { PageHead } from "components/core";
// services
import { UserService } from "services/user.service";
// type
@ -25,9 +26,12 @@ const ProfilePreferencesThemePage: NextPageWithLayout = () => {
}
return (
<>
<PageHead title="Profile - Email Preference" />
<div className="mx-auto mt-8 h-full w-full overflow-y-auto px-6 lg:px-20 pb-8">
<EmailNotificationForm data={data} />
</div>
</>
);
};

View File

@ -7,7 +7,7 @@ import useToast from "hooks/use-toast";
// layouts
import { ProfilePreferenceSettingsLayout } from "layouts/settings-layout/profile/preferences";
// components
import { CustomThemeSelector, ThemeSwitch } from "components/core";
import { CustomThemeSelector, ThemeSwitch, PageHead } from "components/core";
// ui
import { Spinner } from "@plane/ui";
// constants
@ -47,6 +47,7 @@ const ProfilePreferencesThemePage: NextPageWithLayout = observer(() => {
return (
<>
<PageHead title="Profile - Theme Prefrence" />
{currentUser ? (
<div className="mx-auto mt-10 md:mt-14 h-full w-full overflow-y-auto px-6 lg:px-20 pb-8">
<div className="flex items-center border-b border-custom-border-100 pb-3.5">