From cf3b88846583cf7718deadff26b8703c687505ec Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Tue, 20 Feb 2024 13:36:38 +0530 Subject: [PATCH] chore: adding page titles using project title. (#3692) * chore: adding page titles * chore: added title to remaining pages * fix: added observer at required places --------- Co-authored-by: LAKHAN BAHETI --- web/components/core/index.ts | 1 + web/components/core/page-title.tsx | 18 ++ web/components/page-views/signin.tsx | 24 +- web/pages/404.tsx | 4 +- web/pages/[workspaceSlug]/active-cycles.tsx | 17 +- web/pages/[workspaceSlug]/analytics.tsx | 12 +- web/pages/[workspaceSlug]/index.tsx | 17 +- .../profile/[userId]/assigned.tsx | 8 +- .../profile/[userId]/created.tsx | 8 +- .../profile/[userId]/index.tsx | 22 +- .../profile/[userId]/subscribed.tsx | 8 +- .../archived-issues/[archivedIssueId].tsx | 10 +- .../[projectId]/archived-issues/index.tsx | 43 ++- .../projects/[projectId]/cycles/[cycleId].tsx | 25 +- .../projects/[projectId]/cycles/index.tsx | 283 +++++++++--------- .../[projectId]/draft-issues/index.tsx | 40 ++- .../projects/[projectId]/inbox/[inboxId].tsx | 42 +-- .../projects/[projectId]/issues/[issueId].tsx | 13 +- .../projects/[projectId]/issues/index.tsx | 37 ++- .../[projectId]/modules/[moduleId].tsx | 20 +- .../projects/[projectId]/modules/index.tsx | 22 +- .../projects/[projectId]/pages/[pageId].tsx | 215 ++++++------- .../projects/[projectId]/pages/index.tsx | 10 +- .../[projectId]/settings/automations.tsx | 20 +- .../[projectId]/settings/estimates.tsx | 16 +- .../[projectId]/settings/features.tsx | 29 +- .../projects/[projectId]/settings/index.tsx | 8 +- .../[projectId]/settings/integrations.tsx | 75 ++--- .../projects/[projectId]/settings/labels.tsx | 22 +- .../projects/[projectId]/settings/members.tsx | 26 +- .../projects/[projectId]/views/[viewId].tsx | 20 +- .../projects/[projectId]/views/index.tsx | 23 +- web/pages/[workspaceSlug]/projects/index.tsx | 17 +- .../[workspaceSlug]/settings/api-tokens.tsx | 15 +- .../[workspaceSlug]/settings/billing.tsx | 47 +-- .../[workspaceSlug]/settings/exports.tsx | 28 +- .../[workspaceSlug]/settings/imports.tsx | 30 +- web/pages/[workspaceSlug]/settings/index.tsx | 18 +- .../[workspaceSlug]/settings/integrations.tsx | 40 ++- .../[workspaceSlug]/settings/members.tsx | 7 +- .../settings/webhooks/[webhookId].tsx | 15 +- .../settings/webhooks/index.tsx | 88 +++--- .../workspace-views/[globalViewId].tsx | 27 +- .../[workspaceSlug]/workspace-views/index.tsx | 47 +-- web/pages/accounts/forgot-password.tsx | 104 +++---- web/pages/accounts/reset-password.tsx | 152 +++++----- web/pages/accounts/sign-up.tsx | 24 +- web/pages/create-workspace.tsx | 58 ++-- web/pages/god-mode/ai.tsx | 60 ++-- web/pages/god-mode/authorization.tsx | 166 +++++----- web/pages/god-mode/email.tsx | 48 +-- web/pages/god-mode/image.tsx | 40 +-- web/pages/god-mode/index.tsx | 42 +-- web/pages/invitations/index.tsx | 203 ++++++------- web/pages/onboarding/index.tsx | 2 + web/pages/profile/activity.tsx | 267 +++++++++-------- web/pages/profile/change-password.tsx | 179 +++++------ web/pages/profile/index.tsx | 24 +- web/pages/profile/preferences/email.tsx | 10 +- web/pages/profile/preferences/theme.tsx | 3 +- 60 files changed, 1684 insertions(+), 1215 deletions(-) create mode 100644 web/components/core/page-title.tsx diff --git a/web/components/core/index.ts b/web/components/core/index.ts index 4f99f3606..f68ff5f3c 100644 --- a/web/components/core/index.ts +++ b/web/components/core/index.ts @@ -4,3 +4,4 @@ export * from "./sidebar"; export * from "./theme"; export * from "./activity"; export * from "./image-picker-popover"; +export * from "./page-title"; diff --git a/web/components/core/page-title.tsx b/web/components/core/page-title.tsx new file mode 100644 index 000000000..f9f4e94b2 --- /dev/null +++ b/web/components/core/page-title.tsx @@ -0,0 +1,18 @@ +import Head from "next/head"; + +type PageHeadTitleProps = { + title?: string; + description?: string; +}; + +export const PageHead: React.FC = (props) => { + const { title } = props; + + if (!title) return null; + + return ( + + {title} + + ); +}; diff --git a/web/components/page-views/signin.tsx b/web/components/page-views/signin.tsx index 89bca5d62..2f1d62f84 100644 --- a/web/components/page-views/signin.tsx +++ b/web/components/page-views/signin.tsx @@ -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,19 +35,22 @@ export const SignInView = observer(() => { ); return ( -
-
-
- Plane Logo - Plane + <> + +
+
+
+ Plane Logo + Plane +
-
-
-
- +
+
+ +
-
+ ); }); diff --git a/web/pages/404.tsx b/web/pages/404.tsx index 0e8c8a038..a73cd2074 100644 --- a/web/pages/404.tsx +++ b/web/pages/404.tsx @@ -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 = () => ( +
diff --git a/web/pages/[workspaceSlug]/active-cycles.tsx b/web/pages/[workspaceSlug]/active-cycles.tsx index c04789815..f366ddbd6 100644 --- a/web/pages/[workspaceSlug]/active-cycles.tsx +++ b/web/pages/[workspaceSlug]/active-cycles.tsx @@ -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 = () => ; +const WorkspaceActiveCyclesPage: NextPageWithLayout = observer(() => { + const { currentWorkspace } = useWorkspace(); + // derived values + const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Active Cycles` : undefined; + + return ( + <> + + + + ); +}); WorkspaceActiveCyclesPage.getLayout = function getLayout(page: ReactElement) { return }>{page}; diff --git a/web/pages/[workspaceSlug]/analytics.tsx b/web/pages/[workspaceSlug]/analytics.tsx index d9b973e62..31c396b54 100644 --- a/web/pages/[workspaceSlug]/analytics.tsx +++ b/web/pages/[workspaceSlug]/analytics.tsx @@ -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 ( <> + {workspaceProjectIds && workspaceProjectIds.length > 0 ? (
diff --git a/web/pages/[workspaceSlug]/index.tsx b/web/pages/[workspaceSlug]/index.tsx index 219631b46..8a6782de8 100644 --- a/web/pages/[workspaceSlug]/index.tsx +++ b/web/pages/[workspaceSlug]/index.tsx @@ -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 = () => ; +const WorkspacePage: NextPageWithLayout = observer(() => { + const { currentWorkspace } = useWorkspace(); + // derived values + const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Dashboard` : undefined; + + return ( + <> + + + + ); +}); WorkspacePage.getLayout = function getLayout(page: ReactElement) { return }>{page}; diff --git a/web/pages/[workspaceSlug]/profile/[userId]/assigned.tsx b/web/pages/[workspaceSlug]/profile/[userId]/assigned.tsx index 0808b9503..1cef81e78 100644 --- a/web/pages/[workspaceSlug]/profile/[userId]/assigned.tsx +++ b/web/pages/[workspaceSlug]/profile/[userId]/assigned.tsx @@ -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 = () => ; +const ProfileAssignedIssuesPage: NextPageWithLayout = () => ( + <> + + + +); ProfileAssignedIssuesPage.getLayout = function getLayout(page: ReactElement) { return ( diff --git a/web/pages/[workspaceSlug]/profile/[userId]/created.tsx b/web/pages/[workspaceSlug]/profile/[userId]/created.tsx index 76f4c1fba..47a8445d7 100644 --- a/web/pages/[workspaceSlug]/profile/[userId]/created.tsx +++ b/web/pages/[workspaceSlug]/profile/[userId]/created.tsx @@ -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 = () => ; +const ProfileCreatedIssuesPage: NextPageWithLayout = () => ( + <> + + + +); ProfileCreatedIssuesPage.getLayout = function getLayout(page: ReactElement) { return ( diff --git a/web/pages/[workspaceSlug]/profile/[userId]/index.tsx b/web/pages/[workspaceSlug]/profile/[userId]/index.tsx index 486d8d7e3..a4d1debe1 100644 --- a/web/pages/[workspaceSlug]/profile/[userId]/index.tsx +++ b/web/pages/[workspaceSlug]/profile/[userId]/index.tsx @@ -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,21 +43,24 @@ const ProfileOverviewPage: NextPageWithLayout = () => { }); return ( -
- - -
- - + <> + +
+ + +
+ + +
+
- -
+ ); }; ProfileOverviewPage.getLayout = function getLayout(page: ReactElement) { return ( - }> + }> {page} ); diff --git a/web/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx b/web/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx index 257c23655..c05c39302 100644 --- a/web/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx +++ b/web/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx @@ -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 = () => ; +const ProfileSubscribedIssuesPage: NextPageWithLayout = () => ( + <> + + + +); ProfileSubscribedIssuesPage.getLayout = function getLayout(page: ReactElement) { return ( diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx index 046fdc0c4..1538d240f 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx @@ -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 ( <> + {issueLoader ? (
@@ -126,7 +132,7 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = () => { )} ); -}; +}); ArchivedIssueDetailsPage.getLayout = function getLayout(page: ReactElement) { return ( diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/index.tsx index 503110abe..c24c80a92 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/index.tsx @@ -1,38 +1,51 @@ 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 ( -
-
- + <> + +
+
+ +
+
- -
+ ); -}; +}); ProjectArchivedIssuesPage.getLayout = function getLayout(page: ReactElement) { return ( diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx index f24fc5597..58431bd38 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx @@ -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 ( <> + {error ? ( { )} ); -}; +}); CycleDetailPage.getLayout = function getLayout(page: ReactElement) { return ( diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx index 0b9af62fd..0f86089aa 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx @@ -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("cycle_tab", "active"); const { storedValue: cycleLayout, setValue: setCycleLayout } = useLocalStorage("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,143 +77,146 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => { ); return ( -
- setCreateModal(false)} - /> - {totalCycles === 0 ? ( -
- { - setTrackElement("Cycle empty state"); - setCreateModal(true); - }, - }} - size="lg" - disabled={!isEditingAllowed} - /> -
- ) : ( - i.key == cycleTab)} - selectedIndex={CYCLE_TAB_LIST.findIndex((i) => i.key == cycleTab)} - onChange={(i) => handleCurrentView(CYCLE_TAB_LIST[i]?.key ?? "active")} - > -
- - {CYCLE_TAB_LIST.map((tab) => ( - - `border-b-2 p-4 text-sm font-medium outline-none ${ - selected ? "border-custom-primary-100 text-custom-primary-100" : "border-transparent" - }` - } - > - {tab.name} - - ))} - -
- {cycleTab !== "active" && ( -
- {CYCLE_VIEW_LAYOUTS.map((layout) => { - if (layout.key === "gantt" && cycleTab === "draft") return null; - - return ( - - - - ); - })} -
- )} -
+ <> + +
+ setCreateModal(false)} + /> + {totalCycles === 0 ? ( +
+ { + setTrackElement("Cycle empty state"); + setCreateModal(true); + }, + }} + size="lg" + disabled={!isEditingAllowed} + />
+ ) : ( + i.key == cycleTab)} + selectedIndex={CYCLE_TAB_LIST.findIndex((i) => i.key == cycleTab)} + onChange={(i) => handleCurrentView(CYCLE_TAB_LIST[i]?.key ?? "active")} + > +
+ + {CYCLE_TAB_LIST.map((tab) => ( + + `border-b-2 p-4 text-sm font-medium outline-none ${ + selected ? "border-custom-primary-100 text-custom-primary-100" : "border-transparent" + }` + } + > + {tab.name} + + ))} + +
+ {cycleTab !== "active" && ( +
+ {CYCLE_VIEW_LAYOUTS.map((layout) => { + if (layout.key === "gantt" && cycleTab === "draft") return null; - - - {cycleTab && cycleLayout && ( - - )} - + return ( + + + + ); + })} +
+ )} +
+
- - - + + + {cycleTab && cycleLayout && ( + + )} + - - {cycleTab && cycleLayout && ( - - )} - + + + - - {cycleTab && cycleLayout && workspaceSlug && projectId && ( - - )} - + + {cycleTab && cycleLayout && ( + + )} + - - {cycleTab && cycleLayout && workspaceSlug && projectId && ( - - )} - - -
- )} -
+ + {cycleTab && cycleLayout && workspaceSlug && projectId && ( + + )} + + + + {cycleTab && cycleLayout && workspaceSlug && projectId && ( + + )} + + + + )} +
+ ); }); diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/draft-issues/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/draft-issues/index.tsx index eaf7ce3d3..e2ad25214 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/draft-issues/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/draft-issues/index.tsx @@ -5,31 +5,43 @@ 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 ( -
-
- - +
+
- -
+ ); -}; +}); ProjectDraftIssuesPage.getLayout = function getLayout(page: ReactElement) { return ( diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx index 5cd1e6c2c..125ee4245 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx @@ -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,26 +34,32 @@ const ProjectInboxPage: NextPageWithLayout = observer(() => { } } ); + // derived values + const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Inbox` : undefined; if (!workspaceSlug || !projectId || !inboxId || !currentProjectDetails?.inbox_view) return <>; + return ( -
-
- + <> + +
+
+ +
+
+ +
-
- -
-
+ ); }); diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index 64f43939e..6ff7d5aa5 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -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 ( <> + {issueLoader ? (
diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx index 3193fe64e..2aa9ab2e6 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx @@ -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 ( + <> + + + {project?.name} - Issues + +
+ +
+ + ); +}); ProjectIssuesPage.getLayout = function getLayout(page: ReactElement) { return ( diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx index 946041176..37f7e6b0e 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx @@ -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 ( <> + {error ? ( { )} ); -}; +}); ModuleIssuesPage.getLayout = function getLayout(page: ReactElement) { return ( diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx index 8e63c2899..085f1e3c3 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx @@ -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 = () => ; +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 ( + <> + + + + ); +}); ProjectModulesPage.getLayout = function getLayout(page: ReactElement) { return ( diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index 93a814d57..bee4fc9c7 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -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,113 +256,116 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole); return pageIdMobx ? ( -
-
- {isPageReadOnly ? ( - - ) : ( -
- ( - { - setShowAlert(true); - onChange(description_html); - handleSubmit(updatePage)(); - }} - duplicationConfig={userCanDuplicate ? { action: duplicate_page } : undefined} - pageArchiveConfig={ - userCanArchive - ? { - is_archived: archived_at ? true : false, - action: archived_at ? unArchivePage : archivePage, - } - : undefined - } - pageLockConfig={userCanLock ? { is_locked: false, action: lockPage } : undefined} - /> - )} + <> + +
+
+ {isPageReadOnly ? ( + - {projectId && envConfig?.has_openai_configured && ( -
- { - setGptModal((prevData) => !prevData); - // this is done so that the title do not reset after gpt popover closed - reset(getValues()); - }} - onResponse={(response) => { - handleAiAssistance(response); - }} - placement="top-end" - button={ - - } - className="!min-w-[38rem]" - /> -
- )} -
- )} - + ) : ( +
+ ( + { + setShowAlert(true); + onChange(description_html); + handleSubmit(updatePage)(); + }} + duplicationConfig={userCanDuplicate ? { action: duplicate_page } : undefined} + pageArchiveConfig={ + userCanArchive + ? { + is_archived: archived_at ? true : false, + action: archived_at ? unArchivePage : archivePage, + } + : undefined + } + pageLockConfig={userCanLock ? { is_locked: false, action: lockPage } : undefined} + /> + )} + /> + {projectId && envConfig?.has_openai_configured && ( +
+ { + setGptModal((prevData) => !prevData); + // this is done so that the title do not reset after gpt popover closed + reset(getValues()); + }} + onResponse={(response) => { + handleAiAssistance(response); + }} + placement="top-end" + button={ + + } + className="!min-w-[38rem]" + /> +
+ )} +
+ )} + +
-
+ ) : (
diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx index 5dad4ede0..fd2da0258 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx @@ -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(() => 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 = () => ( @@ -129,6 +132,7 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => { return ( <> + {projectPageIds && archivedPageIds && projectPageIds.length + archivedPageIds.length > 0 ? ( <> {workspaceSlug && projectId && ( diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx index f17b7cc8a..8c4780cba 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx @@ -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,16 +42,21 @@ const AutomationSettingsPage: NextPageWithLayout = observer(() => { }); }; + // derived values const isAdmin = currentProjectRole === EUserProjectRoles.ADMIN; + const pageTitle = projectDetails?.name ? `${projectDetails?.name} - Automations` : undefined; return ( -
-
-

Automations

-
- - -
+ <> + +
+
+

Automations

+
+ + +
+ ); }); diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx index bc90c110f..3aea45adb 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx @@ -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 ( -
- -
+ <> + +
+ +
+ ); }); diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx index a6f66d963..b618437ab 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx @@ -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 ( -
-
-

Features

-
- -
+ <> + +
+
+

Features

+
+ +
+ ); -}; +}); FeaturesSettingsPage.getLayout = function getLayout(page: ReactElement) { return ( diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx index 72b8ce6f5..347d64f84 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx @@ -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 ( <> + {currentProjectDetails && ( { +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( 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 ( -
-
-

Integrations

-
- {workspaceIntegrations ? ( - workspaceIntegrations.length > 0 ? ( -
- {workspaceIntegrations.map((integration) => ( - - ))} -
+ <> + +
+
+

Integrations

+
+ {workspaceIntegrations ? ( + workspaceIntegrations.length > 0 ? ( +
+ {workspaceIntegrations.map((integration) => ( + + ))} +
+ ) : ( +
+ router.push(`/${workspaceSlug}/settings/integrations`), + }} + size="lg" + disabled={!isAdmin} + /> +
+ ) ) : ( -
- router.push(`/${workspaceSlug}/settings/integrations`), - }} - size="lg" - disabled={!isAdmin} - /> -
- ) - ) : ( - - )} -
+ + )} +
+ ); -}; +}); ProjectIntegrationsPage.getLayout = function getLayout(page: ReactElement) { return ( diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx index 02a700bbe..3bb1c8c04 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx @@ -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 ( + <> + +
+ +
+ + ); +}); LabelsSettingsPage.getLayout = function getLayout(page: ReactElement) { return ( diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx index 3b66f40bb..f74d464d5 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx @@ -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 ( + <> + +
+ + +
+ + ); +}); MembersSettingsPage.getLayout = function getLayout(page: ReactElement) { return ( diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/views/[viewId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/views/[viewId].tsx index 51c811195..2ac6b2e00 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/views/[viewId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/views/[viewId].tsx @@ -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 = () => { }} /> ) : ( - + <> + + + )} ); -}; +}); ProjectViewIssuesPage.getLayout = function getLayout(page: ReactElement) { return ( diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/views/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/views/index.tsx index 239318763..33be5d102 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/views/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/views/index.tsx @@ -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 = () => ; +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 ( + <> + + + + ); +}); ProjectViewsPage.getLayout = function getLayout(page: ReactElement) { return ( diff --git a/web/pages/[workspaceSlug]/projects/index.tsx b/web/pages/[workspaceSlug]/projects/index.tsx index 734098280..1a145a2d1 100644 --- a/web/pages/[workspaceSlug]/projects/index.tsx +++ b/web/pages/[workspaceSlug]/projects/index.tsx @@ -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 = () => ; +const ProjectsPage: NextPageWithLayout = observer(() => { + // store + const { currentWorkspace } = useWorkspace(); + // derived values + const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Projects` : undefined; + + return ( + <> + + + + ); +}); ProjectsPage.getLayout = function getLayout(page: ReactElement) { return }>{page}; diff --git a/web/pages/[workspaceSlug]/settings/api-tokens.tsx b/web/pages/[workspaceSlug]/settings/api-tokens.tsx index 3d65c2d7b..1f203ff04 100644 --- a/web/pages/[workspaceSlug]/settings/api-tokens.tsx +++ b/web/pages/[workspaceSlug]/settings/api-tokens.tsx @@ -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 ( -
-

You are not authorized to access this page.

-
+ <> + +
+

You are not authorized to access this page.

+
+ ); if (!tokens) { @@ -63,6 +69,7 @@ const ApiTokensPage: NextPageWithLayout = observer(() => { return ( <> + setIsCreateTokenModalOpen(false)} />
{tokens.length > 0 ? ( diff --git a/web/pages/[workspaceSlug]/settings/billing.tsx b/web/pages/[workspaceSlug]/settings/billing.tsx index 7cc9c7a28..f4f5d5397 100644 --- a/web/pages/[workspaceSlug]/settings/billing.tsx +++ b/web/pages/[workspaceSlug]/settings/billing.tsx @@ -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,33 +19,41 @@ 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 ( -
-

You are not authorized to access this page.

-
+ <> + +
+

You are not authorized to access this page.

+
+ ); return ( -
-
-
-

Billing & Plans

-
-
-
+ <> + +
-

Current plan

-

You are currently using the free plan

- - - +
+

Billing & Plans

+
-
-
+
+
+

Current plan

+

You are currently using the free plan

+ + + +
+
+
+ ); }); diff --git a/web/pages/[workspaceSlug]/settings/exports.tsx b/web/pages/[workspaceSlug]/settings/exports.tsx index f491581fd..c124a6423 100644 --- a/web/pages/[workspaceSlug]/settings/exports.tsx +++ b/web/pages/[workspaceSlug]/settings/exports.tsx @@ -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 ( -
-

You are not authorized to access this page.

-
+ <> + +
+

You are not authorized to access this page.

+
+ ); return ( -
-
-

Exports

+ <> + +
+
+

Exports

+
+
- -
+ ); }); diff --git a/web/pages/[workspaceSlug]/settings/imports.tsx b/web/pages/[workspaceSlug]/settings/imports.tsx index 2e4be4cc1..5178209d2 100644 --- a/web/pages/[workspaceSlug]/settings/imports.tsx +++ b/web/pages/[workspaceSlug]/settings/imports.tsx @@ -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 ( -
-

You are not authorized to access this page.

-
+ <> + +
+

You are not authorized to access this page.

+
+ ); return ( -
-
-

Imports

-
- -
+ <> + +
+
+

Imports

+
+ +
+ ); }); diff --git a/web/pages/[workspaceSlug]/settings/index.tsx b/web/pages/[workspaceSlug]/settings/index.tsx index b2920aade..2924b13c4 100644 --- a/web/pages/[workspaceSlug]/settings/index.tsx +++ b/web/pages/[workspaceSlug]/settings/index.tsx @@ -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 = () => ; +const WorkspaceSettingsPage: NextPageWithLayout = observer(() => { + // store hooks + const { currentWorkspace } = useWorkspace(); + // derived values + const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - General Settings` : undefined; + + return ( + <> + + + + ); +}); WorkspaceSettingsPage.getLayout = function getLayout(page: ReactElement) { return ( diff --git a/web/pages/[workspaceSlug]/settings/integrations.tsx b/web/pages/[workspaceSlug]/settings/integrations.tsx index 940c90f3a..500533877 100644 --- a/web/pages/[workspaceSlug]/settings/integrations.tsx +++ b/web/pages/[workspaceSlug]/settings/integrations.tsx @@ -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 ( -
-

You are not authorized to access this page.

-
+ <> + +
+

You are not authorized to access this page.

+
+ ); const { data: appIntegrations } = useSWR(workspaceSlug && isAdmin ? APP_INTEGRATIONS : null, () => @@ -46,16 +53,21 @@ const WorkspaceIntegrationsPage: NextPageWithLayout = observer(() => { ); return ( -
- -
- {appIntegrations ? ( - appIntegrations.map((integration) => ) - ) : ( - - )} -
-
+ <> + +
+ +
+ {appIntegrations ? ( + appIntegrations.map((integration) => ( + + )) + ) : ( + + )} +
+
+ ); }); diff --git a/web/pages/[workspaceSlug]/settings/members.tsx b/web/pages/[workspaceSlug]/settings/members.tsx index 6e9d8d924..b8739ae77 100644 --- a/web/pages/[workspaceSlug]/settings/members.tsx +++ b/web/pages/[workspaceSlug]/settings/members.tsx @@ -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 ( <> + setInviteModal(false)} diff --git a/web/pages/[workspaceSlug]/settings/webhooks/[webhookId].tsx b/web/pages/[workspaceSlug]/settings/webhooks/[webhookId].tsx index 562578b66..60e65e905 100644 --- a/web/pages/[workspaceSlug]/settings/webhooks/[webhookId].tsx +++ b/web/pages/[workspaceSlug]/settings/webhooks/[webhookId].tsx @@ -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 ( -
-

You are not authorized to access this page.

-
+ <> + +
+

You are not authorized to access this page.

+
+ ); if (!currentWebhook) @@ -90,6 +96,7 @@ const WebhookDetailsPage: NextPageWithLayout = observer(() => { return ( <> + setDeleteWebhookModal(false)} />
await handleUpdateWebhook(data)} data={currentWebhook} /> diff --git a/web/pages/[workspaceSlug]/settings/webhooks/index.tsx b/web/pages/[workspaceSlug]/settings/webhooks/index.tsx index cafac485e..46c7e99cb 100644 --- a/web/pages/[workspaceSlug]/settings/webhooks/index.tsx +++ b/web/pages/[workspaceSlug]/settings/webhooks/index.tsx @@ -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,53 +57,59 @@ const WebhooksListPage: NextPageWithLayout = observer(() => { if (!isAdmin) return ( -
-

You are not authorized to access this page.

-
+ <> + +
+

You are not authorized to access this page.

+
+ ); if (!webhooks) return ; return ( -
- { - setShowCreateWebhookModal(false); - }} - /> - {Object.keys(webhooks).length > 0 ? ( -
-
-
Webhooks
- + <> + +
+ { + setShowCreateWebhookModal(false); + }} + /> + {Object.keys(webhooks).length > 0 ? ( +
+
+
Webhooks
+ +
+
- -
- ) : ( -
-
-
Webhooks
- + ) : ( +
+
+
Webhooks
+ +
+
+ +
-
- -
-
- )} -
+ )} +
+ ); }); diff --git a/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx b/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx index e89e2c70f..43a0ad494 100644 --- a/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx +++ b/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx @@ -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 ( + <> + +
+
+ + +
+
+ + ); +}); GlobalViewIssuesPage.getLayout = function getLayout(page: ReactElement) { return }>{page}; diff --git a/web/pages/[workspaceSlug]/workspace-views/index.tsx b/web/pages/[workspaceSlug]/workspace-views/index.tsx index db1a6fca7..656120a3e 100644 --- a/web/pages/[workspaceSlug]/workspace-views/index.tsx +++ b/web/pages/[workspaceSlug]/workspace-views/index.tsx @@ -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,31 +14,40 @@ 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 ( -
-
-
- - setQuery(e.target.value)} - placeholder="Search" - mode="true-transparent" - /> + <> + +
+
+
+ + setQuery(e.target.value)} + placeholder="Search" + mode="true-transparent" + /> +
+ {DEFAULT_GLOBAL_VIEWS_LIST.filter((v) => v.label.toLowerCase().includes(query.toLowerCase())).map((option) => ( + + ))} +
- {DEFAULT_GLOBAL_VIEWS_LIST.filter((v) => v.label.toLowerCase().includes(query.toLowerCase())).map((option) => ( - - ))} - -
+ ); -}; +}); WorkspaceViewsPage.getLayout = function getLayout(page: ReactElement) { return }>{page}; diff --git a/web/pages/accounts/forgot-password.tsx b/web/pages/accounts/forgot-password.tsx index 07fa86045..0eef16009 100644 --- a/web/pages/accounts/forgot-password.tsx +++ b/web/pages/accounts/forgot-password.tsx @@ -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,59 +86,62 @@ const ForgotPasswordPage: NextPageWithLayout = () => { }; return ( -
-
-
- Plane Logo - Plane -
-
- -
-
-
-

- Get on your flight deck -

-

Get a link to reset your password

-
- checkEmailValidity(value) || "Email is invalid", - }} - render={({ field: { value, onChange, ref } }) => ( - - )} - /> - - + <> + +
+
+
+ Plane Logo + Plane +
+
+ +
+
+
+

+ Get on your flight deck +

+

Get a link to reset your password

+
+ checkEmailValidity(value) || "Email is invalid", + }} + render={({ field: { value, onChange, ref } }) => ( + + )} + /> + + +
+
-
-
+ ); }; diff --git a/web/pages/accounts/reset-password.tsx b/web/pages/accounts/reset-password.tsx index c4258f39e..c848245ac 100644 --- a/web/pages/accounts/reset-password.tsx +++ b/web/pages/accounts/reset-password.tsx @@ -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,90 +91,93 @@ const ResetPasswordPage: NextPageWithLayout = () => { }; return ( -
-
-
- Plane Logo - Plane + <> + +
+
+
+ Plane Logo + Plane +
-
-
-
-
-

- Let{"'"}s get a new password -

-
- checkEmailValidity(value) || "Email is invalid", - }} - render={({ field: { value, onChange, ref } }) => ( - - )} - /> - ( -
+
+
+
+

+ Let{"'"}s get a new password +

+ + checkEmailValidity(value) || "Email is invalid", + }} + render={({ field: { value, onChange, ref } }) => ( - {showPassword ? ( - setShowPassword(false)} + )} + /> + ( +
+ - ) : ( - setShowPassword(true)} - /> - )} -
- )} - /> - - + {showPassword ? ( + setShowPassword(false)} + /> + ) : ( + setShowPassword(true)} + /> + )} +
+ )} + /> + + +
+
-
-
+ ); }; diff --git a/web/pages/accounts/sign-up.tsx b/web/pages/accounts/sign-up.tsx index 5b5648439..cba9c0166 100644 --- a/web/pages/accounts/sign-up.tsx +++ b/web/pages/accounts/sign-up.tsx @@ -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,20 +30,23 @@ const SignUpPage: NextPageWithLayout = observer(() => { ); return ( -
-
-
- Plane Logo - Plane + <> + +
+
+
+ Plane Logo + Plane +
-
-
-
- +
+
+ +
-
+ ); }); diff --git a/web/pages/create-workspace.tsx b/web/pages/create-workspace.tsx index 10eb11f55..952ed0b68 100644 --- a/web/pages/create-workspace.tsx +++ b/web/pages/create-workspace.tsx @@ -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,38 +38,41 @@ const CreateWorkspacePage: NextPageWithLayout = observer(() => { }; return ( -
-
-
- -
- {theme === "light" ? ( - Plane black logo - ) : ( - Plane white logo - )} + <> + +
+
+
+ +
+ {theme === "light" ? ( + Plane black logo + ) : ( + Plane white logo + )} +
+ +
+ {currentUser?.email}
- -
- {currentUser?.email}
-
-
-
-

Create your workspace

-
- +
+
+

Create your workspace

+
+ +
-
+ ); }); diff --git a/web/pages/god-mode/ai.tsx b/web/pages/god-mode/ai.tsx index 3ceb902c9..b84e98098 100644 --- a/web/pages/god-mode/ai.tsx +++ b/web/pages/god-mode/ai.tsx @@ -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,37 +24,40 @@ const InstanceAdminAIPage: NextPageWithLayout = observer(() => { useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations()); return ( -
-
-
AI features for all your workspaces
-
- Configure your AI API credentials so Plane AI features are turned on for all your workspaces. + <> + +
+
+
AI features for all your workspaces
+
+ Configure your AI API credentials so Plane AI features are turned on for all your workspaces. +
-
- {formattedConfig ? ( - <> -
-
OpenAI
-
If you use ChatGPT, this is for you.
-
- -
-
- -
If you have a preferred AI models vendor, please get in touch with us.
+ {formattedConfig ? ( + <> +
+
OpenAI
+
If you use ChatGPT, this is for you.
+
+ +
+
+ +
If you have a preferred AI models vendor, please get in touch with us.
+
+
+ + ) : ( + +
+ +
-
- - ) : ( - -
- -
- -
- )} -
+ + )} +
+ ); }); diff --git a/web/pages/god-mode/authorization.tsx b/web/pages/god-mode/authorization.tsx index 5085ff61a..e36a1a455 100644 --- a/web/pages/god-mode/authorization.tsx +++ b/web/pages/god-mode/authorization.tsx @@ -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,69 +65,71 @@ const InstanceAdminAuthorizationPage: NextPageWithLayout = observer(() => { }; return ( -
-
-
Single sign-on and OAuth
-
- Make your teams life easy by letting them sign-up with their Google and GitHub accounts, and below are the - settings. + <> + +
+
+
Single sign-on and OAuth
+
+ Make your teams life easy by letting them sign-up with their Google and GitHub accounts, and below are the + settings. +
-
- {formattedConfig ? ( - <> -
-
-
-
- Turn Magic Links {Boolean(parseInt(enableMagicLogin)) ? "off" : "on"} + {formattedConfig ? ( + <> +
+
+
+
+ Turn Magic Links {Boolean(parseInt(enableMagicLogin)) ? "off" : "on"} +
+
+

Slack-like emails for authentication.

+ You need to have set up email{" "} + + here + {" "} + to enable this. +
-
-

Slack-like emails for authentication.

- You need to have set up email{" "} - - here - {" "} - to enable this. +
+ { + // Boolean(parseInt(enableMagicLogin)) === true + // ? updateConfig("ENABLE_MAGIC_LINK_LOGIN", "0") + // : updateConfig("ENABLE_MAGIC_LINK_LOGIN", "1"); + // }} + onChange={() => {}} + size="sm" + disabled={isSubmitting} + />
-
- { - // Boolean(parseInt(enableMagicLogin)) === true - // ? updateConfig("ENABLE_MAGIC_LINK_LOGIN", "0") - // : updateConfig("ENABLE_MAGIC_LINK_LOGIN", "1"); - // }} - onChange={() => {}} - size="sm" - disabled={isSubmitting} - /> -
-
-
-
-
- Let your users log in via the methods below +
+
+
+ Let your users log in via the methods below +
+
+ Toggling this off will disable all previous configs. Users will only be able to login with an e-mail + and password combo. +
-
- Toggling this off will disable all previous configs. Users will only be able to login with an e-mail - and password combo. +
+ { + Boolean(parseInt(enableSignup)) === true + ? updateConfig("ENABLE_SIGNUP", "0") + : updateConfig("ENABLE_SIGNUP", "1"); + }} + size="sm" + disabled={isSubmitting} + />
-
- { - Boolean(parseInt(enableSignup)) === true - ? updateConfig("ENABLE_SIGNUP", "0") - : updateConfig("ENABLE_SIGNUP", "1"); - }} - size="sm" - disabled={isSubmitting} - /> -
-
- {/*
+ {/*
Turn Email Password {Boolean(parseInt(enableEmailPassword)) ? "off" : "on"} @@ -146,36 +149,37 @@ const InstanceAdminAuthorizationPage: NextPageWithLayout = observer(() => { />
*/} -
-
-
-
- Google +
+
+
+
+ Google +
+
+ +
-
- +
+
+ Github +
+
+ +
-
-
- Github -
-
- -
+ + ) : ( + +
+ +
-
- - ) : ( - -
- -
- -
- )} -
+ + )} +
+ ); }); diff --git a/web/pages/god-mode/email.tsx b/web/pages/god-mode/email.tsx index bea14a357..65889607f 100644 --- a/web/pages/god-mode/email.tsx +++ b/web/pages/god-mode/email.tsx @@ -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,29 +22,32 @@ const InstanceAdminEmailPage: NextPageWithLayout = observer(() => { useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations()); return ( -
-
-
Secure emails from your own instance
-
- Plane can send useful emails to you and your users from your own instance without talking to the Internet. -
-
- Set it up below and please test your settings before you save them.{" "} - Misconfigs can lead to email bounces and errors. -
-
- {formattedConfig ? ( - - ) : ( - -
- - + <> + +
+
+
Secure emails from your own instance
+
+ Plane can send useful emails to you and your users from your own instance without talking to the Internet.
- - - )} -
+
+ Set it up below and please test your settings before you save them.{" "} + Misconfigs can lead to email bounces and errors. +
+
+ {formattedConfig ? ( + + ) : ( + +
+ + +
+ +
+ )} +
+ ); }); diff --git a/web/pages/god-mode/image.tsx b/web/pages/god-mode/image.tsx index f7cf21c2f..349dccf4b 100644 --- a/web/pages/god-mode/image.tsx +++ b/web/pages/god-mode/image.tsx @@ -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,25 +22,28 @@ const InstanceAdminImagePage: NextPageWithLayout = observer(() => { useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations()); return ( -
-
-
Third-party image libraries
-
- Let your users search and choose images from third-party libraries -
-
- {formattedConfig ? ( - - ) : ( - -
- - + <> + +
+
+
Third-party image libraries
+
+ Let your users search and choose images from third-party libraries
- - - )} -
+
+ {formattedConfig ? ( + + ) : ( + +
+ + +
+ +
+ )} +
+ ); }); diff --git a/web/pages/god-mode/index.tsx b/web/pages/god-mode/index.tsx index 35a8ed0c4..a93abad31 100644 --- a/web/pages/god-mode/index.tsx +++ b/web/pages/god-mode/index.tsx @@ -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,26 +23,29 @@ const InstanceAdminPage: NextPageWithLayout = observer(() => { useSWR("INSTANCE_ADMINS", () => fetchInstanceAdmins()); return ( -
-
-
ID your instance easily
-
- Change the name of your instance and instance admin e-mail addresses. If you have a paid subscription, you - will find your license key here. -
-
- {instance && instanceAdmins ? ( - - ) : ( - -
- - + <> + +
+
+
ID your instance easily
+
+ Change the name of your instance and instance admin e-mail addresses. If you have a paid subscription, you + will find your license key here.
- - - )} -
+
+ {instance && instanceAdmins ? ( + + ) : ( + +
+ + +
+ +
+ )} +
+ ); }); diff --git a/web/pages/invitations/index.tsx b/web/pages/invitations/index.tsx index 26ced2010..b5acec196 100644 --- a/web/pages/invitations/index.tsx +++ b/web/pages/invitations/index.tsx @@ -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,108 +126,111 @@ const UserInvitationsPage: NextPageWithLayout = observer(() => { }; return ( -
-
-
-
-
- {theme === "light" ? ( - Plane black logo - ) : ( - Plane white logo - )} -
-
-
- {currentUser?.email} -
-
- {invitations ? ( - invitations.length > 0 ? ( -
-
-
We see that someone has invited you to
-

Join a workspace

-
- {invitations.map((invitation) => { - const isSelected = invitationsRespond.includes(invitation.id); - - return ( -
handleInvitation(invitation, isSelected ? "withdraw" : "accepted")} - > -
-
- {invitation.workspace.logo && invitation.workspace.logo.trim() !== "" ? ( - {invitation.workspace.name} - ) : ( - - {invitation.workspace.name[0]} - - )} -
-
-
-
{truncateText(invitation.workspace.name, 30)}
-

{ROLE[invitation.role]}

-
- - - -
- ); - })} -
-
- - - - - - -
+ <> + +
+
+
+
+
+ {theme === "light" ? ( + Plane black logo + ) : ( + Plane white logo + )}
- ) : ( -
- router.push("/"), - }} - /> +
+ {currentUser?.email}
- ) - ) : null} -
+
+ {invitations ? ( + invitations.length > 0 ? ( +
+
+
We see that someone has invited you to
+

Join a workspace

+
+ {invitations.map((invitation) => { + const isSelected = invitationsRespond.includes(invitation.id); + + return ( +
handleInvitation(invitation, isSelected ? "withdraw" : "accepted")} + > +
+
+ {invitation.workspace.logo && invitation.workspace.logo.trim() !== "" ? ( + {invitation.workspace.name} + ) : ( + + {invitation.workspace.name[0]} + + )} +
+
+
+
{truncateText(invitation.workspace.name, 30)}
+

{ROLE[invitation.role]}

+
+ + + +
+ ); + })} +
+
+ + + + + + +
+
+
+ ) : ( +
+ router.push("/"), + }} + /> +
+ ) + ) : null} +
+ ); }); diff --git a/web/pages/onboarding/index.tsx b/web/pages/onboarding/index.tsx index 99886156d..5b5b91280 100644 --- a/web/pages/onboarding/index.tsx +++ b/web/pages/onboarding/index.tsx @@ -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 ( <> + setShowDeleteAccountModal(false)} /> {user && step !== null ? (
diff --git a/web/pages/profile/activity.tsx b/web/pages/profile/activity.tsx index 051127dd3..4460f2ec5 100644 --- a/web/pages/profile/activity.tsx +++ b/web/pages/profile/activity.tsx @@ -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,159 +32,162 @@ const ProfileActivityPage: NextPageWithLayout = observer(() => { const { theme: themeStore } = useApplication(); return ( -
-
- themeStore.toggleSidebar()} /> -

Activity

-
- {userActivity ? ( -
-
    - {userActivity.results.map((activityItem: any) => { - if (activityItem.field === "comment") { - return ( -
    -
    -
    - {activityItem.field ? ( - activityItem.new_value === "restore" && ( - - ) - ) : activityItem.actor_detail.avatar && activityItem.actor_detail.avatar !== "" ? ( - {activityItem.actor_detail.display_name} - ) : ( -
    - {activityItem.actor_detail.display_name?.charAt(0)} -
    - )} + <> + +
    +
    + themeStore.toggleSidebar()} /> +

    Activity

    +
    + {userActivity ? ( +
    +
      + {userActivity.results.map((activityItem: any) => { + if (activityItem.field === "comment") { + return ( +
      +
      +
      + {activityItem.field ? ( + activityItem.new_value === "restore" && ( + + ) + ) : activityItem.actor_detail.avatar && activityItem.actor_detail.avatar !== "" ? ( + {activityItem.actor_detail.display_name} + ) : ( +
      + {activityItem.actor_detail.display_name?.charAt(0)} +
      + )} - - -
      -
      -
      -
      - {activityItem.actor_detail.is_bot - ? activityItem.actor_detail.first_name + " Bot" - : activityItem.actor_detail.display_name} -
      -

      - Commented {calculateTimeAgo(activityItem.created_at)} -

      + +
      -
      - +
      +
      +
      + {activityItem.actor_detail.is_bot + ? activityItem.actor_detail.first_name + " Bot" + : activityItem.actor_detail.display_name} +
      +

      + Commented {calculateTimeAgo(activityItem.created_at)} +

      +
      +
      + +
      -
      - ); - } + ); + } - const message = - activityItem.verb === "created" && + const message = + activityItem.verb === "created" && activityItem.field !== "cycles" && activityItem.field !== "modules" && activityItem.field !== "attachment" && activityItem.field !== "link" && activityItem.field !== "estimate" && !activityItem.field ? ( - - created - - ) : ( - - ); + + created + + ) : ( + + ); - if ("field" in activityItem && activityItem.field !== "updated_by") { - return ( -
    • -
      -
      - <> -
      -
      -
      -
      - {activityItem.field ? ( - activityItem.new_value === "restore" ? ( - + if ("field" in activityItem && activityItem.field !== "updated_by") { + return ( +
    • +
      +
      + <> +
      +
      +
      +
      + {activityItem.field ? ( + activityItem.new_value === "restore" ? ( + + ) : ( + + ) + ) : activityItem.actor_detail.avatar && activityItem.actor_detail.avatar !== "" ? ( + {activityItem.actor_detail.display_name} ) : ( - - ) - ) : activityItem.actor_detail.avatar && activityItem.actor_detail.avatar !== "" ? ( - {activityItem.actor_detail.display_name} - ) : ( -
      - {activityItem.actor_detail.display_name?.charAt(0)} -
      - )} +
      + {activityItem.actor_detail.display_name?.charAt(0)} +
      + )} +
      -
      -
      -
      - {activityItem.field === "archived_at" && activityItem.new_value !== "restore" ? ( - Plane - ) : activityItem.actor_detail.is_bot ? ( - - {activityItem.actor_detail.first_name} Bot - - ) : ( - +
      +
      + {activityItem.field === "archived_at" && activityItem.new_value !== "restore" ? ( + Plane + ) : activityItem.actor_detail.is_bot ? ( - {currentUser?.id === activityItem.actor_detail.id - ? "You" - : activityItem.actor_detail.display_name} + {activityItem.actor_detail.first_name} Bot - - )}{" "} -
      - {message}{" "} - - {calculateTimeAgo(activityItem.created_at)} - + ) : ( + + + {currentUser?.id === activityItem.actor_detail.id + ? "You" + : activityItem.actor_detail.display_name} + + + )}{" "} +
      + {message}{" "} + + {calculateTimeAgo(activityItem.created_at)} + +
      -
      - + +
      -
      -
    • - ); - } - })} -
    -
    - ) : ( - - )} -
    + + ); + } + })} +
+
+ ) : ( + + )} +
+ ); }); diff --git a/web/pages/profile/change-password.tsx b/web/pages/profile/change-password.tsx index 15cb946ed..80e2965d6 100644 --- a/web/pages/profile/change-password.tsx +++ b/web/pages/profile/change-password.tsx @@ -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,93 +90,98 @@ const ChangePasswordPage: NextPageWithLayout = observer(() => { ); return ( -
-
- themeStore.toggleSidebar()} /> + <> + +
+
+ themeStore.toggleSidebar()} /> +
+
+

Change password

+
+
+

Current password

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

New password

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

Confirm password

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

Change password

-
-
-

Current password

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

New password

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

Confirm password

- ( - - )} - /> - {errors.confirm_password && {errors.confirm_password.message}} -
-
- -
- -
-
-
+ ); }); diff --git a/web/pages/profile/index.tsx b/web/pages/profile/index.tsx index da8626dd1..e967df828 100644 --- a/web/pages/profile/index.tsx +++ b/web/pages/profile/index.tsx @@ -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 ( <> +
themeStore.toggleSidebar()} /> @@ -296,8 +297,9 @@ 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 } }) => ( 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(() => { {({ open }) => ( <> - + Deactivate account @@ -426,8 +434,8 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => {
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.