feat: language support (#6472)
* chore: ln support modules constants * fix: translation key * chore: empty state refactor (#6404) * chore: asset path helper hook added * chore: detailed and simple empty state component added * chore: section empty state component added * chore: language translation for all empty states * chore: new empty state implementation * improvement: add more translations * improvement: user permissions and workspace draft empty state * chore: update translation structure * chore: inbox empty states * chore: disabled project features empty state * chore: active cycle progress empty state * chore: notification empty state * chore: connections translation * chore: issue comment, relation, bulk delete, and command k empty state translation * chore: project pages empty state and translations * chore: project module and view related empty state * chore: remove project draft related empty state * chore: project cycle, views and archived issues empty state * chore: project cycles related empty state * chore: project settings empty state * chore: profile issue and acitivity empty state * chore: workspace settings realted constants * chore: stickies and home widgets empty state * chore: remove all reference to deprecated empty state component and constnats * chore: add support to ignore theme in resolved asset path hook * chore: minor updates * fix: build errors --------- Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com> * fix: language support fo profile (#6461) * fix: ln support fo profile * fix: merge changes * fix: merge changes * [WEB-3165]feat: language support for issues (#6452) * * chore: moved issue constants to packages * chore: restructured issue constants * improvement: added translations to issue constants * chore: updated translation structure * * chore: updated chinese, spanish and french translation * chore: updated translation for issues mobile header * chore: updated spanish translation * chore: removed translation for issue priorities * fix: build errors * chore: minor updates --------- Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> * chore: migrated filters.ts to packages (#6459) Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> * chore: workspace drafts constant moved to plane constant package * feat: home language support without stickies (#6443) * feat: home language support without stickies * fix: home sidebar * fix: added missing keys * fix: show all btn * fix: recents empty state * chore: translation update * feat: workspace constant language support and refactor (#6462) * chore: workspace constant language support and refactor * chore: workspace constant language support and refactor * chore: code refactor * chore: code refactor * merge conflict * chore: code refactor --------- Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> * chore: tab indices constant moved to plane package (#6464) * chore: notification language support and refactor * chore: ln support for inbox constants (#6432) * chore: ln support for inbox constants * fix: snooze duration * fix: enum * fix: translation keys * fix: inbox status icon * fix: status icon * fix: naming --------- Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> * fix: ln support for views constants (#6431) * fix: ln support for views constants * fix: added translation * fix: translation keys * fix: access * chore: code refactor * chore: ln support workspace projects constants (#6429) * chore: ln support workspace projects constants * fix: translation key * fix: removed state translation * fix: removed state translation * fi: added translations * Chore: theme language support and refactor (#6465) * chore: themes language support and refactor * chore: theme language support and refactor * fix * [WEB-3173] chore: language support for cycles constant file (#6415) * chore: ln support for cycles constant file * fix: added chinese * fix: lint * fix: translation key * fix: build errors * minor updates * chore: minor translation update * chore: minor translation update * refactor: move labels contants to packages * refactor: move swr, file and error related constants to packages * chore: timezones constant moved to plane package * chore: metadata constant code refactor * chore: code refactor * fix: dashboard constants moved * chore: code refactor (#6478) * refactor: spreadsheet constants * chore: drafts language support (#6485) * chore: workspace drafts language support * chore: code refactor * feat: ln support for notifications (#6486) * feat: ln support for notifications * fix: translations * * refactor: moved page constants to packages (#6480) * fix: removed use-client * chore: removed unnecessary commnets * chore: workspace draft language support (#6490) * chore: workspace drafts language support * chore: code refactor * chore: draft language support * Feat constant event tracker (#6479) * fix: event tracjer constants * fix: constants event tracker * feat: language translation - projects list (#6493) * feat: added translation to projects list page * chore: restructured translation file * chore: module language support (#6499) * chore: module language support added * chore: code refactor * chore: workspace views language support (#6492) * chore: workspace views language support * chore: code refactor * feat: custom analytics language support (#6494) * feat: custom analytics language support * fix: key * fix: refactoring --------- Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> * chore: minor improvements * feat: language support for intake (#6498) * feat: language support for intake * fix: key name * refactor: authentications related translations * feat: language support issues (#6501) * enhancement: added translations for issue list view * chore: added translations for issue detail widgets * chore: added missing translations * chore: modified issue to work items * chore: updated translations * Feat: workspace settings language support (#6508) * feat: language support for workspace settings * fix: lint * fix: export title * chore project settings language support (#6502) * chore: project settings language support * chore: code refactor * refactor: workspace creation related translations * chore: renamed issues to work items * fix: build errors * fix: lint * chore: modified translations * chore: remove duplicate * improvement: french translation * chore: chinese translation improvement * fix: japanese translations * chore: added spanish translation * minor improvements * fix: miscelleous language translations * fix: clear_all key * fix: moved user permission constants (#6516) * feat: language support for issues (#6513) * chore: added language support to issue detail widgets * improvement: added translation for issue detail * enhancement: added language trasnlation to issue layouts * chore: translation improvement (#6518) * feat: language support description (#6519) * enhancement: added language support for description * fix: updated keys * chore: renamed issue to work item (#6522) * chore: replace missing issue occurances to work items * fix: build errors * minor improvements * fix: profile links * Feat ln cycles (#6528) * feat: added language support for cycles * feat: added language support for cycles * chore: added core.json * fix: translation keys * fix: translation keys (#6530) * fix: changed sidebar keys * fix: removed extras * fix: updated keys * chore: optimize translation imports * fix: updated keys (#6534) * fix: updated keys * fix-sub work items toasts * chore: add missing translation and minor fixes * chore: code refactor * fix: language support keys (#6553) * minor improvements * minor fixes * fix: remove lucide import from constants package * chore: regenerate all translations * chore: addded chinese and japanese translation files * chore: remove all from translations * fix: added member * fix: language support keys (#6558) * fix: renamed keys * fix: space app * chore: renamed issues to work items * chore: update site manifest * chore: updated translations * fix: lang keys * chore: update translations --------- Co-authored-by: gakshita <akshitagoyal1516@gmail.com> Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com> Co-authored-by: Akshita Goyal <36129505+gakshita@users.noreply.github.com> Co-authored-by: Vamsi Krishna <46787868+mathalav55@users.noreply.github.com> Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so> Co-authored-by: Vamsi krishna <matalav55@gmail.com> Co-authored-by: Vamsi Krishna <46787868+vamsikrishnamathala@users.noreply.github.com>
This commit is contained in:
parent
e244f48776
commit
d36c3acbf7
693 changed files with 18182 additions and 10485 deletions
|
|
@ -41,7 +41,12 @@ export const WorkspaceAnalyticsHeader = observer(() => {
|
|||
<Breadcrumbs>
|
||||
<Breadcrumbs.BreadcrumbItem
|
||||
type="text"
|
||||
link={<BreadcrumbLink label={t("analytics")} icon={<BarChart2 className="h-4 w-4 text-custom-text-300" />} />}
|
||||
link={
|
||||
<BreadcrumbLink
|
||||
label={t("workspace_analytics.label")}
|
||||
icon={<BarChart2 className="h-4 w-4 text-custom-text-300" />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Breadcrumbs>
|
||||
{analytics_tab === "custom" ? (
|
||||
|
|
|
|||
|
|
@ -5,27 +5,40 @@ import { observer } from "mobx-react";
|
|||
import { useSearchParams } from "next/navigation";
|
||||
import { Tab } from "@headlessui/react";
|
||||
// plane package imports
|
||||
import { ANALYTICS_TABS } from "@plane/constants";
|
||||
import { ANALYTICS_TABS, EUserPermissionsLevel, EUserPermissions } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Header, EHeaderVariant } from "@plane/ui";
|
||||
// components
|
||||
import { CustomAnalytics, ScopeAndDemand } from "@/components/analytics";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useProject, useWorkspace } from "@/hooks/store";
|
||||
import { useCommandPalette, useEventTracker, useProject, useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
const AnalyticsPage = observer(() => {
|
||||
const searchParams = useSearchParams();
|
||||
const analytics_tab = searchParams.get("analytics_tab");
|
||||
// plane imports
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { workspaceProjectIds, loader } = useProject();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// helper hooks
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/analytics" });
|
||||
// derived values
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Analytics` : undefined;
|
||||
const pageTitle = currentWorkspace?.name
|
||||
? t(`workspace_analytics.page_label`, { workspace: currentWorkspace?.name })
|
||||
: undefined;
|
||||
|
||||
// permissions
|
||||
const canPerformEmptyStateActions = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
|
||||
// TODO: refactor loader implementation
|
||||
return (
|
||||
|
|
@ -46,7 +59,7 @@ const AnalyticsPage = observer(() => {
|
|||
selected ? "text-custom-primary-100 " : "hover:text-custom-text-200"
|
||||
}`}
|
||||
>
|
||||
{tab.title}
|
||||
{t(tab.i18n_title)}
|
||||
<div
|
||||
className={`border absolute bottom-0 right-0 left-0 rounded-t-md ${selected ? "border-custom-primary-100" : "border-transparent group-hover:border-custom-border-200"}`}
|
||||
/>
|
||||
|
|
@ -67,12 +80,22 @@ const AnalyticsPage = observer(() => {
|
|||
</Tab.Group>
|
||||
</div>
|
||||
) : (
|
||||
<EmptyState
|
||||
type={EmptyStateType.WORKSPACE_ANALYTICS}
|
||||
primaryButtonOnClick={() => {
|
||||
setTrackElement("Analytics empty state");
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
<DetailedEmptyState
|
||||
title={t("workspace_analytics.empty_state.general.title")}
|
||||
description={t("workspace_analytics.empty_state.general.description")}
|
||||
assetPath={resolvedPath}
|
||||
customPrimaryButton={
|
||||
<ComicBoxButton
|
||||
label={t("workspace_analytics.empty_state.general.primary_button.text")}
|
||||
title={t("workspace_analytics.empty_state.general.primary_button.comic.title")}
|
||||
description={t("workspace_analytics.empty_state.general.primary_button.comic.description")}
|
||||
onClick={() => {
|
||||
setTrackElement("Analytics empty state");
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
disabled={!canPerformEmptyStateActions}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { PenSquare } from "lucide-react";
|
||||
import { EIssuesStoreType } from "@plane/constants";
|
||||
import { EIssuesStoreType, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { Breadcrumbs, Button, Header } from "@plane/ui";
|
||||
// components
|
||||
|
|
@ -12,8 +13,6 @@ import { CreateUpdateIssueModal } from "@/components/issues";
|
|||
|
||||
// hooks
|
||||
import { useProject, useUserPermissions, useWorkspaceDraftIssues } from "@/hooks/store";
|
||||
// plane-web
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
export const WorkspaceDraftHeader = observer(() => {
|
||||
// state
|
||||
|
|
@ -22,7 +21,9 @@ export const WorkspaceDraftHeader = observer(() => {
|
|||
const { allowPermissions } = useUserPermissions();
|
||||
const { paginationInfo } = useWorkspaceDraftIssues();
|
||||
const { joinedProjectIds } = useProject();
|
||||
// check if user is authorized to create draft issue
|
||||
|
||||
const { t } = useTranslation();
|
||||
// check if user is authorized to create draft work item
|
||||
const isAuthorizedUser = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
|
|
@ -42,7 +43,9 @@ export const WorkspaceDraftHeader = observer(() => {
|
|||
<Breadcrumbs>
|
||||
<Breadcrumbs.BreadcrumbItem
|
||||
type="text"
|
||||
link={<BreadcrumbLink label={`Drafts`} icon={<PenSquare className="h-4 w-4 text-custom-text-300" />} />}
|
||||
link={
|
||||
<BreadcrumbLink label={t("drafts")} icon={<PenSquare className="h-4 w-4 text-custom-text-300" />} />
|
||||
}
|
||||
/>
|
||||
</Breadcrumbs>
|
||||
{paginationInfo?.total_count && paginationInfo?.total_count > 0 ? (
|
||||
|
|
@ -62,7 +65,7 @@ export const WorkspaceDraftHeader = observer(() => {
|
|||
onClick={() => setIsDraftIssueModalOpen(true)}
|
||||
disabled={!isAuthorizedUser}
|
||||
>
|
||||
Draft<span className="hidden sm:inline-block"> an issue</span>
|
||||
{t("workspace_draft_issues.draft_an_issue")}
|
||||
</Button>
|
||||
)}
|
||||
</Header.RightItem>
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@ import { Home } from "lucide-react";
|
|||
import githubBlackImage from "/public/logos/github-black.png";
|
||||
import githubWhiteImage from "/public/logos/github-white.png";
|
||||
// ui
|
||||
import { GITHUB_REDIRECTED } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Breadcrumbs, Header } from "@plane/ui";
|
||||
// components
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
// constants
|
||||
import { GITHUB_REDIRECTED } from "@/constants/event-tracker";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
|
||||
|
|
@ -19,6 +20,7 @@ export const WorkspaceDashboardHeader = () => {
|
|||
// hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { resolvedTheme } = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -28,7 +30,9 @@ export const WorkspaceDashboardHeader = () => {
|
|||
<Breadcrumbs>
|
||||
<Breadcrumbs.BreadcrumbItem
|
||||
type="text"
|
||||
link={<BreadcrumbLink label="Home" icon={<Home className="h-4 w-4 text-custom-text-300" />} />}
|
||||
link={
|
||||
<BreadcrumbLink label={t("home.title")} icon={<Home className="h-4 w-4 text-custom-text-300" />} />
|
||||
}
|
||||
/>
|
||||
</Breadcrumbs>
|
||||
</div>
|
||||
|
|
@ -51,7 +55,7 @@ export const WorkspaceDashboardHeader = () => {
|
|||
width={16}
|
||||
alt="GitHub Logo"
|
||||
/>
|
||||
<span className="hidden text-xs font-medium sm:hidden md:block">Star us on GitHub</span>
|
||||
<span className="hidden text-xs font-medium sm:hidden md:block">{t("home.star_us_on_github")}</span>
|
||||
</a>
|
||||
</Header.RightItem>
|
||||
</Header>
|
||||
|
|
|
|||
|
|
@ -4,21 +4,24 @@ import { useCallback, useEffect } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// plane imports
|
||||
import { ENotificationLoader, ENotificationQueryParamType } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// components
|
||||
import { LogoSpinner } from "@/components/common";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { SimpleEmptyState } from "@/components/empty-state";
|
||||
import { InboxContentRoot } from "@/components/inbox";
|
||||
import { IssuePeekOverview } from "@/components/issues";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
import { ENotificationLoader, ENotificationQueryParamType } from "@/constants/notification";
|
||||
// hooks
|
||||
import { useIssueDetail, useUserPermissions, useWorkspace, useWorkspaceNotifications } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import { useWorkspaceIssueProperties } from "@/hooks/use-workspace-issue-properties";
|
||||
|
||||
const WorkspaceDashboardPage = observer(() => {
|
||||
const { workspaceSlug } = useParams();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// hooks
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const {
|
||||
|
|
@ -31,11 +34,14 @@ const WorkspaceDashboardPage = observer(() => {
|
|||
const { fetchUserProjectInfo } = useUserPermissions();
|
||||
const { setPeekIssue } = useIssueDetail();
|
||||
// derived values
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Inbox` : undefined;
|
||||
const pageTitle = currentWorkspace?.name
|
||||
? t("notification.page_label", { workspace: currentWorkspace?.name })
|
||||
: undefined;
|
||||
const { workspace_slug, project_id, issue_id, is_inbox_issue } =
|
||||
notificationLiteByNotificationId(currentSelectedNotificationId);
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/intake/issue-detail" });
|
||||
|
||||
// fetching workspace issue properties
|
||||
// fetching workspace work item properties
|
||||
useWorkspaceIssueProperties(workspaceSlug);
|
||||
|
||||
// fetch workspace notifications
|
||||
|
|
@ -82,7 +88,7 @@ const WorkspaceDashboardPage = observer(() => {
|
|||
<div className="w-full h-full overflow-hidden overflow-y-auto">
|
||||
{!currentSelectedNotificationId ? (
|
||||
<div className="w-full h-screen flex justify-center items-center">
|
||||
<EmptyState type={EmptyStateType.NOTIFICATION_DETAIL_EMPTY_STATE} layout="screen-simple" />
|
||||
<SimpleEmptyState title={t("notification.empty_state.detail.title")} assetPath={resolvedPath} />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { PageHead, AppHeader, ContentWrapper } from "@/components/core";
|
||||
import { WorkspaceHomeView } from "@/components/home";
|
||||
// hooks
|
||||
|
|
@ -11,8 +12,9 @@ import { WorkspaceDashboardHeader } from "./header";
|
|||
|
||||
const WorkspaceDashboardPage = observer(() => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Home` : undefined;
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - ${t("home.title")}` : undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// ui
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/ui";
|
||||
// components
|
||||
import { PageHead } from "@/components/core";
|
||||
|
|
@ -10,7 +12,6 @@ import { DownloadActivityButton, WorkspaceActivityListPage } from "@/components/
|
|||
// hooks
|
||||
import { useUserPermissions } from "@/hooks/store";
|
||||
// plane-web constants
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
const PER_PAGE = 100;
|
||||
|
||||
|
|
@ -21,6 +22,8 @@ const ProfileActivityPage = observer(() => {
|
|||
const [resultsCount, setResultsCount] = useState(0);
|
||||
// router
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
//hooks
|
||||
const { t } = useTranslation();
|
||||
|
||||
const updateTotalPages = (count: number) => setTotalPages(count);
|
||||
|
||||
|
|
@ -50,7 +53,7 @@ const ProfileActivityPage = observer(() => {
|
|||
<PageHead title="Profile - Activity" />
|
||||
<div className="flex h-full w-full flex-col overflow-hidden py-5">
|
||||
<div className="flex items-center justify-between gap-2 px-5 md:px-9">
|
||||
<h3 className="text-lg font-medium">Recent activity</h3>
|
||||
<h3 className="text-lg font-medium">{t("profile.stats.recent_activity.title")}</h3>
|
||||
{canDownloadActivity && <DownloadActivityButton />}
|
||||
</div>
|
||||
<div className="vertical-scrollbar scrollbar-md flex h-full flex-col overflow-y-auto px-5 md:px-9">
|
||||
|
|
@ -58,7 +61,7 @@ const ProfileActivityPage = observer(() => {
|
|||
{pageCount < totalPages && resultsCount !== 0 && (
|
||||
<div className="flex w-full items-center justify-center text-xs">
|
||||
<Button variant="accent-primary" size="sm" onClick={handleLoadMore}>
|
||||
Load more
|
||||
{t("common.load_more")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -6,15 +6,15 @@ import { observer } from "mobx-react";
|
|||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
import { ChevronDown, PanelRight } from "lucide-react";
|
||||
import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IUserProfileProjectSegregation } from "@plane/types";
|
||||
import { Breadcrumbs, Header, CustomMenu, UserActivityIcon } from "@plane/ui";
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
// components
|
||||
import { ProfileIssuesFilter } from "@/components/profile";
|
||||
import { PROFILE_ADMINS_TAB, PROFILE_VIEWER_TAB } from "@/constants/profile";
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { useAppTheme, useUser, useUserPermissions } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
type TUserProfileHeader = {
|
||||
userProjectsData: IUserProfileProjectSegregation | undefined;
|
||||
|
|
@ -30,6 +30,7 @@ export const UserProfileHeader: FC<TUserProfileHeader> = observer((props) => {
|
|||
const { toggleProfileSidebar, profileSidebarCollapsed } = useAppTheme();
|
||||
const { data: currentUser } = useUser();
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const isAuthorized = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
|
|
@ -44,7 +45,7 @@ export const UserProfileHeader: FC<TUserProfileHeader> = observer((props) => {
|
|||
|
||||
const isCurrentUser = currentUser?.id === userId;
|
||||
|
||||
const breadcrumbLabel = `${isCurrentUser ? "Your" : userName} Work`;
|
||||
const breadcrumbLabel = isCurrentUser ? t("profile.page_label") : `${userName} ${t("profile.work")}`;
|
||||
|
||||
return (
|
||||
<Header>
|
||||
|
|
@ -86,7 +87,7 @@ export const UserProfileHeader: FC<TUserProfileHeader> = observer((props) => {
|
|||
href={`/${workspaceSlug}/profile/${userId}/${tab.route}`}
|
||||
className="w-full text-custom-text-300"
|
||||
>
|
||||
{tab.label}
|
||||
{t(tab.i18n_label)}
|
||||
</Link>
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -4,16 +4,15 @@ import { observer } from "mobx-react";
|
|||
import { useParams, usePathname } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// components
|
||||
import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { AppHeader, ContentWrapper } from "@/components/core";
|
||||
import { ProfileSidebar } from "@/components/profile";
|
||||
// constants
|
||||
import { USER_PROFILE_PROJECT_SEGREGATION } from "@/constants/fetch-keys";
|
||||
import { PROFILE_ADMINS_TAB, PROFILE_VIEWER_TAB } from "@/constants/profile";
|
||||
// hooks
|
||||
import { useUserPermissions } from "@/hooks/store";
|
||||
import useSize from "@/hooks/use-window-size";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
// local components
|
||||
import { UserService } from "@/services/user.service";
|
||||
import { UserProfileHeader } from "./header";
|
||||
|
|
@ -66,7 +65,7 @@ const UseProfileLayout: React.FC<Props> = observer((props) => {
|
|||
<AppHeader
|
||||
header={
|
||||
<UserProfileHeader
|
||||
type={currentTab?.label}
|
||||
type={currentTab?.i18n_label}
|
||||
userProjectsData={userProjectsData}
|
||||
showProfileIssuesFilter={isIssuesTab}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -6,21 +6,30 @@ import { useParams } from "next/navigation";
|
|||
// icons
|
||||
import { ChevronDown } from "lucide-react";
|
||||
// plane constants
|
||||
import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants";
|
||||
import {
|
||||
EIssueLayoutTypes,
|
||||
EIssueFilterType,
|
||||
EIssuesStoreType,
|
||||
ISSUE_LAYOUTS,
|
||||
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||
} from "@plane/constants";
|
||||
// plane i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
||||
// ui
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// components
|
||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue";
|
||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown, IssueLayoutIcon } from "@/components/issues";
|
||||
|
||||
// helpers
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
import { useIssues, useLabel } from "@/hooks/store";
|
||||
|
||||
export const ProfileIssuesMobileHeader = observer(() => {
|
||||
// plane i18n
|
||||
const { t } = useTranslation();
|
||||
// router
|
||||
const { workspaceSlug, userId } = useParams();
|
||||
// store hook
|
||||
|
|
@ -112,7 +121,7 @@ export const ProfileIssuesMobileHeader = observer(() => {
|
|||
placement="bottom-start"
|
||||
customButton={
|
||||
<div className="flex flex-center text-sm text-custom-text-200">
|
||||
Layout
|
||||
{t("common.layout")}
|
||||
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200 my-auto" strokeWidth={2} />
|
||||
</div>
|
||||
}
|
||||
|
|
@ -129,19 +138,19 @@ export const ProfileIssuesMobileHeader = observer(() => {
|
|||
}}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<layout.icon className="h-3 w-3" />
|
||||
<div className="text-custom-text-300">{layout.title}</div>
|
||||
<IssueLayoutIcon layout={ISSUE_LAYOUTS[index].key} className="h-3 w-3" />
|
||||
<div className="text-custom-text-300">{t(layout.i18n_title)}</div>
|
||||
</CustomMenu.MenuItem>
|
||||
);
|
||||
})}
|
||||
</CustomMenu>
|
||||
<div className="flex flex-grow items-center justify-center border-l border-custom-border-200 text-sm text-custom-text-200">
|
||||
<FiltersDropdown
|
||||
title="Filters"
|
||||
title={t("common.filters")}
|
||||
placement="bottom-end"
|
||||
menuButton={
|
||||
<div className="flex flex-center text-sm text-custom-text-200">
|
||||
Filters
|
||||
{t("common.filters")}
|
||||
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" strokeWidth={2} />
|
||||
</div>
|
||||
}
|
||||
|
|
@ -149,7 +158,7 @@ export const ProfileIssuesMobileHeader = observer(() => {
|
|||
>
|
||||
<FilterSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.profile_issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.profile_issues[activeLayout] : undefined
|
||||
}
|
||||
filters={issueFilters?.filters ?? {}}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
|
|
@ -163,18 +172,18 @@ export const ProfileIssuesMobileHeader = observer(() => {
|
|||
</div>
|
||||
<div className="flex flex-grow items-center justify-center border-l border-custom-border-200 text-sm text-custom-text-200">
|
||||
<FiltersDropdown
|
||||
title="Display"
|
||||
title={t("common.display")}
|
||||
placement="bottom-end"
|
||||
menuButton={
|
||||
<div className="flex flex-center text-sm text-custom-text-200">
|
||||
Display
|
||||
{t("common.display")}
|
||||
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" strokeWidth={2} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.profile_issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.profile_issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ import React from "react";
|
|||
|
||||
import Link from "next/link";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
|
||||
// components
|
||||
// constants
|
||||
import { Header, EHeaderVariant } from "@plane/ui";
|
||||
import { PROFILE_ADMINS_TAB, PROFILE_VIEWER_TAB } from "@/constants/profile";
|
||||
|
||||
type Props = {
|
||||
isAuthorized: boolean;
|
||||
|
|
@ -33,7 +33,7 @@ export const ProfileNavbar: React.FC<Props> = (props) => {
|
|||
: "border-transparent"
|
||||
}`}
|
||||
>
|
||||
{t(tab.label)}
|
||||
{t(tab.i18n_label)}
|
||||
</span>
|
||||
</Link>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// types
|
||||
import { GROUP_CHOICES } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IUserStateDistribution, TStateGroups } from "@plane/types";
|
||||
// components
|
||||
import { ContentWrapper } from "@plane/ui";
|
||||
|
|
@ -16,7 +18,6 @@ import {
|
|||
} from "@/components/profile";
|
||||
// constants
|
||||
import { USER_PROFILE_DATA } from "@/constants/fetch-keys";
|
||||
import { GROUP_CHOICES } from "@/constants/project";
|
||||
// services
|
||||
import { UserService } from "@/services/user.service";
|
||||
|
||||
|
|
@ -26,6 +27,7 @@ const userService = new UserService();
|
|||
export default function ProfileOverviewPage() {
|
||||
const { workspaceSlug, userId } = useParams();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { data: userProfile } = useSWR(
|
||||
workspaceSlug && userId ? USER_PROFILE_DATA(workspaceSlug.toString(), userId.toString()) : null,
|
||||
workspaceSlug && userId ? () => userService.getUserProfileData(workspaceSlug.toString(), userId.toString()) : null
|
||||
|
|
@ -40,7 +42,7 @@ export default function ProfileOverviewPage() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<PageHead title="Your work" />
|
||||
<PageHead title={t("profile.page_label")} />
|
||||
<ContentWrapper className="space-y-7">
|
||||
<ProfileStats userProfile={userProfile} />
|
||||
<ProfileWorkload stateDistribution={stateDistribution} />
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const PROJECT_ARCHIVES_BREADCRUMB_LIST: {
|
|||
};
|
||||
} = {
|
||||
issues: {
|
||||
label: "Issues",
|
||||
label: "Work items",
|
||||
href: "/issues",
|
||||
icon: LayersIcon,
|
||||
},
|
||||
|
|
@ -92,7 +92,7 @@ export const ProjectArchivesHeader: FC<TProps> = observer((props: TProps) => {
|
|||
{activeTab === "issues" && issueCount && issueCount > 0 ? (
|
||||
<Tooltip
|
||||
isMobile={isMobile}
|
||||
tooltipContent={`There are ${issueCount} ${issueCount > 1 ? "issues" : "issue"} in project's archived`}
|
||||
tooltipContent={`There are ${issueCount} ${issueCount > 1 ? "work items" : "work item"} in project's archived`}
|
||||
position="bottom"
|
||||
>
|
||||
<span className="cursor-default flex items-center text-center justify-center px-2.5 py-0.5 flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-semibold rounded-xl">
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export const ProjectArchivedIssueDetailsHeader = observer(() => {
|
|||
link={
|
||||
<BreadcrumbLink
|
||||
href={`/${workspaceSlug}/projects/${projectId}/archives/issues`}
|
||||
label="Issues"
|
||||
label="Work items"
|
||||
icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />}
|
||||
/>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ const ProjectArchivedIssuesPage = observer(() => {
|
|||
const { getProjectById } = useProject();
|
||||
// derived values
|
||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
const pageTitle = project?.name && `${project?.name} - Archived issues`;
|
||||
const pageTitle = project?.name && `${project?.name} - Archived work items`;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,16 @@ import { useParams } from "next/navigation";
|
|||
// icons
|
||||
import { ArrowRight, PanelRight } from "lucide-react";
|
||||
// plane constants
|
||||
import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants";
|
||||
import {
|
||||
EIssueLayoutTypes,
|
||||
EIssueFilterType,
|
||||
EIssuesStoreType,
|
||||
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
} from "@plane/constants";
|
||||
// i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
// ui
|
||||
|
|
@ -16,8 +25,6 @@ import { Breadcrumbs, Button, ContrastIcon, CustomMenu, Tooltip, Header } from "
|
|||
import { ProjectAnalyticsModal } from "@/components/analytics";
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
|
|
@ -39,7 +46,6 @@ import useLocalStorage from "@/hooks/use-local-storage";
|
|||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web
|
||||
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
const CycleDropdownOption: React.FC<{ cycleId: string }> = ({ cycleId }) => {
|
||||
// router
|
||||
|
|
@ -72,6 +78,8 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||
projectId: string;
|
||||
cycleId: string;
|
||||
};
|
||||
// i18n
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const {
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
|
|
@ -184,7 +192,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||
type="text"
|
||||
link={
|
||||
<BreadcrumbLink
|
||||
label="Cycles"
|
||||
label={t("common.cycles")}
|
||||
href={`/${workspaceSlug}/projects/${projectId}/cycles`}
|
||||
icon={<ContrastIcon className="h-4 w-4 text-custom-text-300" />}
|
||||
/>
|
||||
|
|
@ -203,7 +211,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||
<Tooltip
|
||||
isMobile={isMobile}
|
||||
tooltipContent={`There are ${issuesCount} ${
|
||||
issuesCount > 1 ? "issues" : "issue"
|
||||
issuesCount > 1 ? "work items" : "work item"
|
||||
} in this cycle`}
|
||||
position="bottom"
|
||||
>
|
||||
|
|
@ -239,7 +247,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||
selectedLayout={activeLayout}
|
||||
/>
|
||||
<FiltersDropdown
|
||||
title="Filters"
|
||||
title={t("common.filters")}
|
||||
placement="bottom-end"
|
||||
isFiltersApplied={isIssueFilterActive(issueFilters)}
|
||||
>
|
||||
|
|
@ -247,7 +255,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||
filters={issueFilters?.filters ?? {}}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
|
|
@ -258,10 +266,10 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||
moduleViewDisabled={!currentProjectDetails?.module_view}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
<FiltersDropdown title="Display" placement="bottom-end">
|
||||
<FiltersDropdown title={t("common.display")} placement="bottom-end">
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
|
|
@ -276,18 +284,18 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||
{canUserCreateIssue && (
|
||||
<>
|
||||
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
||||
Analytics
|
||||
{t("common.analytics")}
|
||||
</Button>
|
||||
{!isCompletedCycle && (
|
||||
<Button
|
||||
className="h-full self-start"
|
||||
onClick={() => {
|
||||
setTrackElement("Cycle issues page");
|
||||
setTrackElement("Cycle work items page");
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.CYCLE);
|
||||
}}
|
||||
size="sm"
|
||||
>
|
||||
Add issue
|
||||
{t("issue.add.label")}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -5,32 +5,42 @@ import { useParams } from "next/navigation";
|
|||
// icons
|
||||
import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
|
||||
// plane constants
|
||||
import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants";
|
||||
import {
|
||||
EIssueLayoutTypes,
|
||||
EIssueFilterType,
|
||||
EIssuesStoreType,
|
||||
ISSUE_LAYOUTS,
|
||||
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||
} from "@plane/constants";
|
||||
// i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
// ui
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// components
|
||||
import { ProjectAnalyticsModal } from "@/components/analytics";
|
||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue";
|
||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown, IssueLayoutIcon } from "@/components/issues";
|
||||
// helpers
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
import { useIssues, useCycle, useProjectState, useLabel, useMember, useProject } from "@/hooks/store";
|
||||
|
||||
export const CycleIssuesMobileHeader = () => {
|
||||
// i18n
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [analyticsModal, setAnalyticsModal] = useState(false);
|
||||
const { getCycleById } = useCycle();
|
||||
const layouts = [
|
||||
{ key: "list", title: "List", icon: List },
|
||||
{ key: "kanban", title: "Board", icon: Kanban },
|
||||
{ key: "calendar", title: "Calendar", icon: Calendar },
|
||||
{ key: "list", titleTranslationKey: "issue.layouts.list", icon: List },
|
||||
{ key: "kanban", titleTranslationKey: "issue.layouts.kanban", icon: Kanban },
|
||||
{ key: "calendar", titleTranslationKey: "issue.layouts.calendar", icon: Calendar },
|
||||
];
|
||||
|
||||
const { workspaceSlug, projectId, cycleId } = useParams();
|
||||
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
|
||||
|
||||
// store hooks
|
||||
const { currentProjectDetails } = useProject();
|
||||
const {
|
||||
|
|
@ -123,7 +133,9 @@ export const CycleIssuesMobileHeader = () => {
|
|||
maxHeight={"md"}
|
||||
className="flex flex-grow justify-center text-custom-text-200 text-sm"
|
||||
placement="bottom-start"
|
||||
customButton={<span className="flex flex-grow justify-center text-custom-text-200 text-sm">Layout</span>}
|
||||
customButton={
|
||||
<span className="flex flex-grow justify-center text-custom-text-200 text-sm">{t("common.layout")}</span>
|
||||
}
|
||||
customButtonClassName="flex flex-grow justify-center text-custom-text-200 text-sm"
|
||||
closeOnSelect
|
||||
>
|
||||
|
|
@ -135,18 +147,18 @@ export const CycleIssuesMobileHeader = () => {
|
|||
}}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<layout.icon className="w-3 h-3" />
|
||||
<div className="text-custom-text-300">{layout.title}</div>
|
||||
<IssueLayoutIcon layout={ISSUE_LAYOUTS[index].key} className="w-3 h-3" />
|
||||
<div className="text-custom-text-300">{t(layout.titleTranslationKey)}</div>
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
</CustomMenu>
|
||||
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
|
||||
<FiltersDropdown
|
||||
title="Filters"
|
||||
title={t("common.filters")}
|
||||
placement="bottom-end"
|
||||
menuButton={
|
||||
<span className="flex items-center text-custom-text-200 text-sm">
|
||||
Filters
|
||||
{t("common.filters")}
|
||||
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
|
||||
</span>
|
||||
}
|
||||
|
|
@ -156,7 +168,7 @@ export const CycleIssuesMobileHeader = () => {
|
|||
filters={issueFilters?.filters ?? {}}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
|
|
@ -170,18 +182,18 @@ export const CycleIssuesMobileHeader = () => {
|
|||
</div>
|
||||
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
|
||||
<FiltersDropdown
|
||||
title="Display"
|
||||
title={t("common.display")}
|
||||
placement="bottom-end"
|
||||
menuButton={
|
||||
<span className="flex items-center text-custom-text-200 text-sm">
|
||||
Display
|
||||
{t("common.display")}
|
||||
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
|
|
@ -198,7 +210,7 @@ export const CycleIssuesMobileHeader = () => {
|
|||
onClick={() => setAnalyticsModal(true)}
|
||||
className="flex flex-grow justify-center text-custom-text-200 text-sm border-l border-custom-border-200"
|
||||
>
|
||||
Analytics
|
||||
{t("common.analytics")}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// ui
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Breadcrumbs, Button, ContrastIcon, Header } from "@plane/ui";
|
||||
// components
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
|
|
@ -13,7 +15,6 @@ import { useAppRouter } from "@/hooks/use-app-router";
|
|||
// plane web
|
||||
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
||||
// constants
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
export const CyclesListHeader: FC = observer(() => {
|
||||
// router
|
||||
|
|
@ -23,6 +24,7 @@ export const CyclesListHeader: FC = observer(() => {
|
|||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { currentProjectDetails, loader } = useProject();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const canUserCreateCycle = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
|
|
@ -36,7 +38,12 @@ export const CyclesListHeader: FC = observer(() => {
|
|||
<ProjectBreadcrumb />
|
||||
<Breadcrumbs.BreadcrumbItem
|
||||
type="text"
|
||||
link={<BreadcrumbLink label="Cycles" icon={<ContrastIcon className="h-4 w-4 text-custom-text-300" />} />}
|
||||
link={
|
||||
<BreadcrumbLink
|
||||
label={t("cycle.label", { count: 2 })}
|
||||
icon={<ContrastIcon className="h-4 w-4 text-custom-text-300" />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Breadcrumbs>
|
||||
</Header.LeftItem>
|
||||
|
|
@ -51,7 +58,8 @@ export const CyclesListHeader: FC = observer(() => {
|
|||
toggleCreateCycleModal(true);
|
||||
}}
|
||||
>
|
||||
<div className="hidden sm:block">Add</div> Cycle
|
||||
<div className="sm:hidden block">{t("add")}</div>
|
||||
<div className="hidden sm:block">{t("project_cycles.add_cycle")}</div>
|
||||
</Button>
|
||||
</Header.RightItem>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -3,20 +3,22 @@
|
|||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// types
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel, EUserProjectRoles } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TCycleFilters } from "@plane/types";
|
||||
// components
|
||||
import { Header, EHeaderVariant } from "@plane/ui";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { CyclesView, CycleCreateUpdateModal, CycleAppliedFiltersList } from "@/components/cycles";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||
import { CycleModuleListLayout } from "@/components/ui";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// helpers
|
||||
import { calculateTotalFilters } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useCycle, useProject, useCycleFilter } from "@/hooks/store";
|
||||
import { useEventTracker, useCycle, useProject, useCycleFilter, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
const ProjectCyclesPage = observer(() => {
|
||||
// states
|
||||
|
|
@ -26,13 +28,23 @@ const ProjectCyclesPage = observer(() => {
|
|||
const { currentProjectCycleIds, loader } = useCycle();
|
||||
const { getProjectById, currentProjectDetails } = useProject();
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// cycle filters hook
|
||||
const { clearAllFilters, currentProjectFilters, updateFilters } = useCycleFilter();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const totalCycles = currentProjectCycleIds?.length ?? 0;
|
||||
const project = projectId ? getProjectById(projectId?.toString()) : undefined;
|
||||
const pageTitle = project?.name ? `${project?.name} - Cycles` : undefined;
|
||||
const pageTitle = project?.name ? `${project?.name} - ${t("cycles.label", { count: 2 })}` : undefined;
|
||||
const hasAdminLevelPermission = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||
const hasMemberLevelPermission = allowPermissions(
|
||||
[EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER],
|
||||
EUserPermissionsLevel.PROJECT
|
||||
);
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/cycles" });
|
||||
|
||||
const handleRemoveFilter = (key: keyof TCycleFilters, value: string | null) => {
|
||||
if (!projectId) return;
|
||||
|
|
@ -50,9 +62,17 @@ const ProjectCyclesPage = observer(() => {
|
|||
if (currentProjectDetails?.cycle_view === false)
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<EmptyState
|
||||
type={EmptyStateType.DISABLED_PROJECT_CYCLE}
|
||||
primaryButtonLink={`/${workspaceSlug}/projects/${projectId}/settings/features`}
|
||||
<DetailedEmptyState
|
||||
title={t("disabled_project.empty_state.cycle.title")}
|
||||
description={t("disabled_project.empty_state.cycle.description")}
|
||||
assetPath={resolvedPath}
|
||||
primaryButton={{
|
||||
text: t("disabled_project.empty_state.cycle.primary_button.text"),
|
||||
onClick: () => {
|
||||
router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`);
|
||||
},
|
||||
disabled: !hasAdminLevelPermission,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -71,12 +91,22 @@ const ProjectCyclesPage = observer(() => {
|
|||
/>
|
||||
{totalCycles === 0 ? (
|
||||
<div className="h-full place-items-center">
|
||||
<EmptyState
|
||||
type={EmptyStateType.PROJECT_CYCLES}
|
||||
primaryButtonOnClick={() => {
|
||||
setTrackElement("Cycle empty state");
|
||||
setCreateModal(true);
|
||||
}}
|
||||
<DetailedEmptyState
|
||||
title={t("project_cycles.empty_state.general.title")}
|
||||
description={t("project_cycles.empty_state.general.description")}
|
||||
assetPath={resolvedPath}
|
||||
customPrimaryButton={
|
||||
<ComicBoxButton
|
||||
label={t("project_cycles.empty_state.general.primary_button.text")}
|
||||
title={t("project_cycles.empty_state.general.primary_button.comic.title")}
|
||||
description={t("project_cycles.empty_state.general.primary_button.comic.description")}
|
||||
onClick={() => {
|
||||
setTrackElement("Cycle empty state");
|
||||
setCreateModal(true);
|
||||
}}
|
||||
disabled={!hasMemberLevelPermission}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import { FC, useCallback } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane constants
|
||||
import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants";
|
||||
import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
|
||||
// i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
// ui
|
||||
|
|
@ -12,8 +14,6 @@ import { Breadcrumbs, LayersIcon, Tooltip } from "@plane/ui";
|
|||
// components
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||
// helpers
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
|
|
@ -24,6 +24,8 @@ import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
|||
|
||||
// FIXME: Deprecated. Remove it
|
||||
export const ProjectDraftIssueHeader: FC = observer(() => {
|
||||
// i18n
|
||||
const { t } = useTranslation();
|
||||
// router
|
||||
const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string };
|
||||
// store hooks
|
||||
|
|
@ -96,14 +98,17 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
|
|||
<Breadcrumbs.BreadcrumbItem
|
||||
type="text"
|
||||
link={
|
||||
<BreadcrumbLink label="Draft Issues" icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />} />
|
||||
<BreadcrumbLink
|
||||
label="Draft work items"
|
||||
icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Breadcrumbs>
|
||||
{issueCount && issueCount > 0 ? (
|
||||
<Tooltip
|
||||
isMobile={isMobile}
|
||||
tooltipContent={`There are ${issueCount} ${issueCount > 1 ? "issues" : "issue"} in project's draft`}
|
||||
tooltipContent={`There are ${issueCount} ${issueCount > 1 ? "work items" : "work item"} in project's draft`}
|
||||
position="bottom"
|
||||
>
|
||||
<span className="cursor-default flex items-center text-center justify-center px-2.5 py-0.5 flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-semibold rounded-xl">
|
||||
|
|
@ -119,14 +124,18 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
|
|||
onChange={(layout) => handleLayoutChange(layout)}
|
||||
selectedLayout={activeLayout}
|
||||
/>
|
||||
<FiltersDropdown title="Filters" placement="bottom-end" isFiltersApplied={isIssueFilterActive(issueFilters)}>
|
||||
<FiltersDropdown
|
||||
title={t("common.filters")}
|
||||
placement="bottom-end"
|
||||
isFiltersApplied={isIssueFilterActive(issueFilters)}
|
||||
>
|
||||
<FilterSelection
|
||||
filters={issueFilters?.filters ?? {}}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
labels={projectLabels}
|
||||
memberIds={projectMemberIds ?? undefined}
|
||||
|
|
@ -135,10 +144,10 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
|
|||
moduleViewDisabled={!currentProjectDetails?.module_view}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
<FiltersDropdown title="Display" placement="bottom-end">
|
||||
<FiltersDropdown title={t("common.display")} placement="bottom-end">
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const ProjectDraftIssuesPage = observer(() => {
|
|||
const { getProjectById } = useProject();
|
||||
// derived values
|
||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
const pageTitle = project?.name ? `${project?.name} - Draft Issues` : undefined;
|
||||
const pageTitle = project?.name ? `${project?.name} - Draft work items` : undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -30,7 +30,7 @@ const ProjectDraftIssuesPage = observer(() => {
|
|||
className="flex items-center gap-1.5 rounded-full border border-custom-border-200 px-3 py-1.5 text-xs"
|
||||
>
|
||||
<PenSquare className="h-4 w-4" />
|
||||
<span>Draft Issues</span>
|
||||
<span>Draft work items</span>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -40,4 +40,4 @@ const ProjectDraftIssuesPage = observer(() => {
|
|||
);
|
||||
});
|
||||
|
||||
export default ProjectDraftIssuesPage;
|
||||
export default ProjectDraftIssuesPage;
|
||||
|
|
|
|||
|
|
@ -2,41 +2,62 @@
|
|||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import { useParams, useSearchParams } from "next/navigation";
|
||||
import { EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserProjectRoles } from "@plane/constants/src/user";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
import { InboxIssueRoot } from "@/components/inbox";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// helpers
|
||||
import { EInboxIssueCurrentTab } from "@/helpers/inbox.helper";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store";
|
||||
import { useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
const ProjectInboxPage = observer(() => {
|
||||
/// router
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const navigationTab = searchParams.get("currentTab");
|
||||
const inboxIssueId = searchParams.get("inboxIssueId");
|
||||
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// hooks
|
||||
const { currentProjectDetails } = useProject();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/intake" });
|
||||
|
||||
// No access to inbox
|
||||
if (currentProjectDetails?.inbox_view === false)
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<EmptyState
|
||||
type={EmptyStateType.DISABLED_PROJECT_INBOX}
|
||||
primaryButtonLink={`/${workspaceSlug}/projects/${projectId}/settings/features`}
|
||||
<DetailedEmptyState
|
||||
title={t("disabled_project.empty_state.inbox.title")}
|
||||
description={t("disabled_project.empty_state.inbox.description")}
|
||||
assetPath={resolvedPath}
|
||||
primaryButton={{
|
||||
text: t("disabled_project.empty_state.inbox.primary_button.text"),
|
||||
onClick: () => {
|
||||
router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`);
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
// derived values
|
||||
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Intake` : "Plane - Intake";
|
||||
const pageTitle = currentProjectDetails?.name
|
||||
? t("inbox_issue.page_label", {
|
||||
workspace: currentProjectDetails?.name,
|
||||
})
|
||||
: t("inbox_issue.page_label", {
|
||||
workspace: "Plane",
|
||||
});
|
||||
|
||||
const currentNavigationTab = navigationTab
|
||||
? navigationTab === "open"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import { observer } from "mobx-react";
|
|||
import { useParams } from "next/navigation";
|
||||
import { useTheme } from "next-themes";
|
||||
import useSWR from "swr";
|
||||
// i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
|
|
@ -19,6 +21,8 @@ import emptyIssueDark from "@/public/empty-state/search/issues-dark.webp";
|
|||
import emptyIssueLight from "@/public/empty-state/search/issues-light.webp";
|
||||
|
||||
const IssueDetailsPage = observer(() => {
|
||||
// i18n
|
||||
const { t } = useTranslation();
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId, issueId } = useParams();
|
||||
|
|
@ -31,7 +35,7 @@ const IssueDetailsPage = observer(() => {
|
|||
} = useIssueDetail();
|
||||
const { getProjectById } = useProject();
|
||||
const { toggleIssueDetailSidebar, issueDetailSidebarCollapsed } = useAppTheme();
|
||||
// fetching issue details
|
||||
// fetching work item details
|
||||
const { isLoading, error } = useSWR(
|
||||
workspaceSlug && projectId && issueId ? `ISSUE_DETAIL_${workspaceSlug}_${projectId}_${issueId}` : null,
|
||||
workspaceSlug && projectId && issueId
|
||||
|
|
@ -64,10 +68,10 @@ const IssueDetailsPage = observer(() => {
|
|||
{error ? (
|
||||
<EmptyState
|
||||
image={resolvedTheme === "dark" ? emptyIssueDark : emptyIssueLight}
|
||||
title="Issue does not exist"
|
||||
description="The issue you are looking for does not exist, has been archived, or has been deleted."
|
||||
title={t("issue.empty_state.issue_detail.title")}
|
||||
description={t("issue.empty_state.issue_detail.description")}
|
||||
primaryButton={{
|
||||
text: "View other issues",
|
||||
text: t("issue.empty_state.issue_detail.primary_button.text"),
|
||||
onClick: () => router.push(`/${workspaceSlug}/projects/${projectId}/issues`),
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { Breadcrumbs, LayersIcon, Header } from "@plane/ui";
|
||||
// components
|
||||
|
|
@ -14,6 +16,7 @@ import { useAppRouter } from "@/hooks/use-app-router";
|
|||
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
||||
|
||||
export const ProjectIssueDetailsHeader = observer(() => {
|
||||
const { t } = useTranslation();
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId, issueId } = useParams();
|
||||
|
|
@ -37,7 +40,7 @@ export const ProjectIssueDetailsHeader = observer(() => {
|
|||
link={
|
||||
<BreadcrumbLink
|
||||
href={`/${workspaceSlug}/projects/${projectId}/issues`}
|
||||
label="Issues"
|
||||
label={t("issue.label", { count: 2 })} // count is for pluralization
|
||||
icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />}
|
||||
/>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,26 +6,39 @@ import { useParams } from "next/navigation";
|
|||
// icons
|
||||
import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
|
||||
// plane constants
|
||||
import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants";
|
||||
import {
|
||||
EIssueLayoutTypes,
|
||||
EIssueFilterType,
|
||||
EIssuesStoreType,
|
||||
ISSUE_LAYOUTS,
|
||||
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||
} from "@plane/constants";
|
||||
// i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
// ui
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// components
|
||||
import { ProjectAnalyticsModal } from "@/components/analytics";
|
||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues/issue-layouts";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue";
|
||||
import {
|
||||
DisplayFiltersSelection,
|
||||
FilterSelection,
|
||||
FiltersDropdown,
|
||||
IssueLayoutIcon,
|
||||
} from "@/components/issues/issue-layouts";
|
||||
// helpers
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
import { useIssues, useLabel, useMember, useProject, useProjectState } from "@/hooks/store";
|
||||
|
||||
export const ProjectIssuesMobileHeader = observer(() => {
|
||||
// i18n
|
||||
const { t } = useTranslation();
|
||||
const layouts = [
|
||||
{ key: "list", title: "List", icon: List },
|
||||
{ key: "kanban", title: "Board", icon: Kanban },
|
||||
{ key: "calendar", title: "Calendar", icon: Calendar },
|
||||
{ key: "list", titleTranslationKey: "issue.layouts.list", icon: List },
|
||||
{ key: "kanban", titleTranslationKey: "issue.layouts.kanban", icon: Kanban },
|
||||
{ key: "calendar", titleTranslationKey: "issue.layouts.calendar", icon: Calendar },
|
||||
];
|
||||
const [analyticsModal, setAnalyticsModal] = useState(false);
|
||||
const { workspaceSlug, projectId } = useParams() as {
|
||||
|
|
@ -104,7 +117,7 @@ export const ProjectIssuesMobileHeader = observer(() => {
|
|||
placement="bottom-start"
|
||||
customButton={
|
||||
<div className="flex flex-start text-sm text-custom-text-200">
|
||||
Layout
|
||||
{t("common.layout")}
|
||||
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200 my-auto" strokeWidth={2} />
|
||||
</div>
|
||||
}
|
||||
|
|
@ -119,18 +132,18 @@ export const ProjectIssuesMobileHeader = observer(() => {
|
|||
}}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<layout.icon className="h-3 w-3" />
|
||||
<div className="text-custom-text-300">{layout.title}</div>
|
||||
<IssueLayoutIcon layout={ISSUE_LAYOUTS[index].key} className="h-3 w-3" />
|
||||
<div className="text-custom-text-300">{t(layout.titleTranslationKey)}</div>
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
</CustomMenu>
|
||||
<div className="flex flex-grow items-center justify-center border-l border-custom-border-200 text-sm text-custom-text-200">
|
||||
<FiltersDropdown
|
||||
title="Filters"
|
||||
title={t("common.filters")}
|
||||
placement="bottom-end"
|
||||
menuButton={
|
||||
<span className="flex items-center text-sm text-custom-text-200">
|
||||
Filters
|
||||
{t("common.filters")}
|
||||
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" />
|
||||
</span>
|
||||
}
|
||||
|
|
@ -142,7 +155,7 @@ export const ProjectIssuesMobileHeader = observer(() => {
|
|||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
labels={projectLabels}
|
||||
memberIds={projectMemberIds ?? undefined}
|
||||
|
|
@ -154,18 +167,18 @@ export const ProjectIssuesMobileHeader = observer(() => {
|
|||
</div>
|
||||
<div className="flex flex-grow items-center justify-center border-l border-custom-border-200 text-sm text-custom-text-200">
|
||||
<FiltersDropdown
|
||||
title="Display"
|
||||
title={t("common.display")}
|
||||
placement="bottom-end"
|
||||
menuButton={
|
||||
<span className="flex items-center text-sm text-custom-text-200">
|
||||
Display
|
||||
{t("common.display")}
|
||||
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" />
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
|
|
@ -181,7 +194,7 @@ export const ProjectIssuesMobileHeader = observer(() => {
|
|||
onClick={() => setAnalyticsModal(true)}
|
||||
className="flex flex-grow justify-center border-l border-custom-border-200 text-sm text-custom-text-200"
|
||||
>
|
||||
Analytics
|
||||
{t("common.analytics")}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
import { observer } from "mobx-react";
|
||||
import Head from "next/head";
|
||||
import { useParams } from "next/navigation";
|
||||
// i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// components
|
||||
import { PageHead } from "@/components/core";
|
||||
import { ProjectLayoutRoot } from "@/components/issues";
|
||||
|
|
@ -11,6 +13,8 @@ import { useProject } from "@/hooks/store";
|
|||
|
||||
const ProjectIssuesPage = observer(() => {
|
||||
const { projectId } = useParams();
|
||||
// i18n
|
||||
const { t } = useTranslation();
|
||||
// store
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
|
|
@ -20,13 +24,15 @@ const ProjectIssuesPage = observer(() => {
|
|||
|
||||
// derived values
|
||||
const project = getProjectById(projectId.toString());
|
||||
const pageTitle = project?.name ? `${project?.name} - Issues` : undefined;
|
||||
const pageTitle = project?.name ? `${project?.name} - ${t("issue.label", { count: 2 })}` : undefined; // Count is for pluralization
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
<Head>
|
||||
<title>{project?.name} - Issues</title>
|
||||
<title>
|
||||
{project?.name} - {t("issue.label", { count: 2 })}
|
||||
</title>
|
||||
</Head>
|
||||
<div className="h-full w-full">
|
||||
<ProjectLayoutRoot />
|
||||
|
|
|
|||
|
|
@ -7,7 +7,14 @@ import { useParams } from "next/navigation";
|
|||
// icons
|
||||
import { ArrowRight, PanelRight } from "lucide-react";
|
||||
// plane constants
|
||||
import { EIssueLayoutTypes, EIssuesStoreType, EIssueFilterType } from "@plane/constants";
|
||||
import {
|
||||
EIssueLayoutTypes,
|
||||
EIssuesStoreType,
|
||||
EIssueFilterType,
|
||||
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
} from "@plane/constants";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
// ui
|
||||
|
|
@ -16,8 +23,6 @@ import { Breadcrumbs, Button, CustomMenu, DiceIcon, Tooltip, Header } from "@pla
|
|||
import { ProjectAnalyticsModal } from "@/components/analytics";
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
|
|
@ -40,7 +45,6 @@ import useLocalStorage from "@/hooks/use-local-storage";
|
|||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web
|
||||
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
const ModuleDropdownOption: React.FC<{ moduleId: string }> = ({ moduleId }) => {
|
||||
// router
|
||||
|
|
@ -202,7 +206,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||
<Tooltip
|
||||
isMobile={isMobile}
|
||||
tooltipContent={`There are ${issuesCount} ${
|
||||
issuesCount > 1 ? "issues" : "issue"
|
||||
issuesCount > 1 ? "work items" : "work item"
|
||||
} in this module`}
|
||||
position="bottom"
|
||||
>
|
||||
|
|
@ -247,7 +251,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
labels={projectLabels}
|
||||
memberIds={projectMemberIds ?? undefined}
|
||||
|
|
@ -259,7 +263,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||
<FiltersDropdown title="Display" placement="bottom-end">
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
|
|
@ -285,12 +289,12 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||
<Button
|
||||
className="hidden sm:flex"
|
||||
onClick={() => {
|
||||
setTrackElement("Module issues page");
|
||||
setTrackElement("Module work items page");
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.MODULE);
|
||||
}}
|
||||
size="sm"
|
||||
>
|
||||
Add issue
|
||||
Add work item
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -6,16 +6,27 @@ import { useParams } from "next/navigation";
|
|||
// icons
|
||||
import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
|
||||
// plane constants
|
||||
import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants";
|
||||
import {
|
||||
EIssueLayoutTypes,
|
||||
EIssueFilterType,
|
||||
EIssuesStoreType,
|
||||
ISSUE_LAYOUTS,
|
||||
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||
} from "@plane/constants";
|
||||
// plane i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
// ui
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// components
|
||||
import { ProjectAnalyticsModal } from "@/components/analytics";
|
||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues/issue-layouts";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue";
|
||||
import {
|
||||
DisplayFiltersSelection,
|
||||
FilterSelection,
|
||||
FiltersDropdown,
|
||||
IssueLayoutIcon,
|
||||
} from "@/components/issues/issue-layouts";
|
||||
// helpers
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
|
|
@ -25,10 +36,11 @@ export const ModuleIssuesMobileHeader = observer(() => {
|
|||
const [analyticsModal, setAnalyticsModal] = useState(false);
|
||||
const { currentProjectDetails } = useProject();
|
||||
const { getModuleById } = useModule();
|
||||
const { t } = useTranslation();
|
||||
const layouts = [
|
||||
{ key: "list", title: "List", icon: List },
|
||||
{ key: "kanban", title: "Board", icon: Kanban },
|
||||
{ key: "calendar", title: "Calendar", icon: Calendar },
|
||||
{ key: "list", i18n_title: "issue.layouts.list", icon: List },
|
||||
{ key: "kanban", i18n_title: "issue.layouts.kanban", icon: Kanban },
|
||||
{ key: "calendar", i18n_title: "issue.layouts.calendar", icon: Calendar },
|
||||
];
|
||||
const { workspaceSlug, projectId, moduleId } = useParams() as {
|
||||
workspaceSlug: string;
|
||||
|
|
@ -116,8 +128,8 @@ export const ModuleIssuesMobileHeader = observer(() => {
|
|||
}}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<layout.icon className="h-3 w-3" />
|
||||
<div className="text-custom-text-300">{layout.title}</div>
|
||||
<IssueLayoutIcon layout={ISSUE_LAYOUTS[index].key} className="h-3 w-3" />
|
||||
<div className="text-custom-text-300">{t(layout.i18n_title)}</div>
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
</CustomMenu>
|
||||
|
|
@ -139,7 +151,7 @@ export const ModuleIssuesMobileHeader = observer(() => {
|
|||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
labels={projectLabels}
|
||||
memberIds={projectMemberIds ?? undefined}
|
||||
|
|
@ -162,7 +174,7 @@ export const ModuleIssuesMobileHeader = observer(() => {
|
|||
>
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { Breadcrumbs, Button, DiceIcon, Header } from "@plane/ui";
|
||||
// components
|
||||
|
|
@ -12,7 +15,6 @@ import { useAppRouter } from "@/hooks/use-app-router";
|
|||
// plane web
|
||||
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
||||
// constants
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
export const ModulesListHeader: React.FC = observer(() => {
|
||||
// router
|
||||
|
|
@ -24,6 +26,8 @@ export const ModulesListHeader: React.FC = observer(() => {
|
|||
|
||||
const { loader } = useProject();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
// auth
|
||||
const canUserCreateModule = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
|
|
@ -38,7 +42,9 @@ export const ModulesListHeader: React.FC = observer(() => {
|
|||
<ProjectBreadcrumb />
|
||||
<Breadcrumbs.BreadcrumbItem
|
||||
type="text"
|
||||
link={<BreadcrumbLink label="Modules" icon={<DiceIcon className="h-4 w-4 text-custom-text-300" />} />}
|
||||
link={
|
||||
<BreadcrumbLink label={t("modules")} icon={<DiceIcon className="h-4 w-4 text-custom-text-300" />} />
|
||||
}
|
||||
/>
|
||||
</Breadcrumbs>
|
||||
</div>
|
||||
|
|
@ -54,7 +60,8 @@ export const ModulesListHeader: React.FC = observer(() => {
|
|||
toggleCreateModuleModal(true);
|
||||
}}
|
||||
>
|
||||
<div className="hidden sm:block">Add</div> Module
|
||||
<div className="sm:hidden block">{t("add")}</div>
|
||||
<div className="hidden sm:block">{t("project_module.add_module")}</div>
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
|
|
|
|||
|
|
@ -2,13 +2,16 @@
|
|||
|
||||
import { observer } from "mobx-react";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { MODULE_VIEW_LAYOUTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { CustomMenu, Row } from "@plane/ui";
|
||||
import { MODULE_VIEW_LAYOUTS } from "@/constants/module";
|
||||
import { ModuleLayoutIcon } from "@/components/modules";
|
||||
import { useModuleFilter, useProject } from "@/hooks/store";
|
||||
|
||||
export const ModulesListMobileHeader = observer(() => {
|
||||
const { currentProjectDetails } = useProject();
|
||||
const { updateDisplayFilters } = useModuleFilter();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="flex justify-start md:hidden">
|
||||
|
|
@ -34,8 +37,8 @@ export const ModulesListMobileHeader = observer(() => {
|
|||
}}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<layout.icon className="w-3 h-3" />
|
||||
<div className="text-custom-text-300">{layout.title}</div>
|
||||
<ModuleLayoutIcon layoutType={layout.key} />
|
||||
<div className="text-custom-text-300">{t(layout.i18n_title)}</div>
|
||||
</CustomMenu.MenuItem>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -4,27 +4,36 @@ import { useCallback } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// types
|
||||
import { EUserPermissionsLevel, EUserProjectRoles } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TModuleFilters } from "@plane/types";
|
||||
// components
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
import { ModuleAppliedFiltersList, ModulesListView } from "@/components/modules";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// helpers
|
||||
import { calculateTotalFilters } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
import { useModuleFilter, useProject } from "@/hooks/store";
|
||||
import { useModuleFilter, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
const ProjectModulesPage = observer(() => {
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store
|
||||
const { getProjectById, currentProjectDetails } = useProject();
|
||||
const { currentProjectFilters, currentProjectDisplayFilters, clearAllFilters, updateFilters, updateDisplayFilters } =
|
||||
useModuleFilter();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
const pageTitle = project?.name ? `${project?.name} - Modules` : undefined;
|
||||
const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/modules" });
|
||||
|
||||
const handleRemoveFilter = useCallback(
|
||||
(key: keyof TModuleFilters, value: string | null) => {
|
||||
|
|
@ -45,9 +54,17 @@ const ProjectModulesPage = observer(() => {
|
|||
if (currentProjectDetails?.module_view === false)
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<EmptyState
|
||||
type={EmptyStateType.DISABLED_PROJECT_MODULE}
|
||||
primaryButtonLink={`/${workspaceSlug}/projects/${projectId}/settings/features`}
|
||||
<DetailedEmptyState
|
||||
title={t("disabled_project.empty_state.module.title")}
|
||||
description={t("disabled_project.empty_state.module.description")}
|
||||
assetPath={resolvedPath}
|
||||
primaryButton={{
|
||||
text: t("disabled_project.empty_state.module.primary_button.text"),
|
||||
onClick: () => {
|
||||
router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`);
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ import { useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams, useRouter, useSearchParams } from "next/navigation";
|
||||
import { FileText } from "lucide-react";
|
||||
// constants
|
||||
import { EPageAccess } from "@plane/constants";
|
||||
// plane types
|
||||
import { TPage } from "@plane/types";
|
||||
// plane ui
|
||||
import { Breadcrumbs, Button, Header, setToast, TOAST_TYPE } from "@plane/ui";
|
||||
// helpers
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
// constants
|
||||
import { EPageAccess } from "@/constants/page";
|
||||
// hooks
|
||||
import { useEventTracker, useProject, useProjectPages } from "@/hooks/store";
|
||||
// plane web
|
||||
|
|
|
|||
|
|
@ -2,27 +2,35 @@
|
|||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, useSearchParams } from "next/navigation";
|
||||
// types
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel, EUserProjectRoles } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TPageNavigationTabs } from "@plane/types";
|
||||
// components
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
import { PagesListRoot, PagesListView } from "@/components/pages";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store";
|
||||
import { useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
const ProjectPagesPage = observer(() => {
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const type = searchParams.get("type");
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { getProjectById, currentProjectDetails } = useProject();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
const pageTitle = project?.name ? `${project?.name} - Pages` : undefined;
|
||||
const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/pages" });
|
||||
|
||||
const currentPageType = (): TPageNavigationTabs => {
|
||||
const pageType = type?.toString();
|
||||
|
|
@ -37,9 +45,17 @@ const ProjectPagesPage = observer(() => {
|
|||
if (currentProjectDetails?.page_view === false)
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<EmptyState
|
||||
type={EmptyStateType.DISABLED_PROJECT_PAGE}
|
||||
primaryButtonLink={`/${workspaceSlug}/projects/${projectId}/settings/features`}
|
||||
<DetailedEmptyState
|
||||
title={t("disabled_project.empty_state.page.title")}
|
||||
description={t("disabled_project.empty_state.page.description")}
|
||||
assetPath={resolvedPath}
|
||||
primaryButton={{
|
||||
text: t("disabled_project.empty_state.page.primary_button.text"),
|
||||
onClick: () => {
|
||||
router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`);
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IProject } from "@plane/types";
|
||||
// ui
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
|
|
@ -12,7 +14,6 @@ import { AutoArchiveAutomation, AutoCloseAutomation } from "@/components/automat
|
|||
import { PageHead } from "@/components/core";
|
||||
// hooks
|
||||
import { useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
const AutomationSettingsPage = observer(() => {
|
||||
// router
|
||||
|
|
@ -21,6 +22,8 @@ const AutomationSettingsPage = observer(() => {
|
|||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
const { currentProjectDetails: projectDetails, updateProject } = useProject();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
// derived values
|
||||
const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||
|
||||
|
|
@ -48,7 +51,7 @@ const AutomationSettingsPage = observer(() => {
|
|||
<PageHead title={pageTitle} />
|
||||
<section className={`w-full overflow-y-auto ${canPerformProjectAdminActions ? "" : "opacity-60"}`}>
|
||||
<div className="flex flex-col items-start border-b border-custom-border-100 pb-3.5">
|
||||
<h3 className="text-xl font-medium leading-normal">Automations</h3>
|
||||
<h3 className="text-xl font-medium leading-normal">{t("project_settings.automations.label")}</h3>
|
||||
</div>
|
||||
<AutoArchiveAutomation handleChange={handleChange} />
|
||||
<AutoCloseAutomation handleChange={handleChange} />
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EstimateRoot } from "@/components/estimates";
|
||||
// hooks
|
||||
import { useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
const EstimatesSettingsPage = observer(() => {
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { ProjectFeaturesList } from "@/components/project";
|
||||
// hooks
|
||||
import { useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
const FeaturesSettingsPage = observer(() => {
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import { observer } from "mobx-react";
|
|||
import { useParams } from "next/navigation";
|
||||
// ui
|
||||
import { Settings } from "lucide-react";
|
||||
import { EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Breadcrumbs, CustomMenu, Header } from "@plane/ui";
|
||||
// components
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
|
|
@ -14,7 +16,6 @@ import { useAppRouter } from "@/hooks/use-app-router";
|
|||
// plane web
|
||||
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
||||
import { PROJECT_SETTINGS_LINKS } from "@/plane-web/constants/project";
|
||||
import { EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
export const ProjectSettingHeader: FC = observer(() => {
|
||||
// router
|
||||
|
|
@ -24,6 +25,8 @@ export const ProjectSettingHeader: FC = observer(() => {
|
|||
const { allowPermissions } = useUserPermissions();
|
||||
const { loader } = useProject();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Header>
|
||||
<Header.LeftItem>
|
||||
|
|
@ -65,7 +68,7 @@ export const ProjectSettingHeader: FC = observer(() => {
|
|||
key={item.key}
|
||||
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}${item.href}`)}
|
||||
>
|
||||
{item.label}
|
||||
{t(item.i18n_label)}
|
||||
</CustomMenu.MenuItem>
|
||||
)
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
|||
import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element";
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { ProjectSettingsLabelList } from "@/components/labels";
|
||||
// hooks
|
||||
import { useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
const LabelsSettingsPage = observer(() => {
|
||||
// store hooks
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { ProjectMemberList, ProjectSettingsMemberDefaults } from "@/components/project";
|
||||
// hooks
|
||||
import { useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
const MembersSettingsPage = observer(() => {
|
||||
// store
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { observer } from "mobx-react";
|
|||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// components
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { PageHead } from "@/components/core";
|
||||
import {
|
||||
ArchiveRestoreProjectModal,
|
||||
|
|
@ -16,7 +17,6 @@ import {
|
|||
} from "@/components/project";
|
||||
// hooks
|
||||
import { useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
const GeneralSettingsPage = observer(() => {
|
||||
// states
|
||||
|
|
@ -43,8 +43,6 @@ const GeneralSettingsPage = observer(() => {
|
|||
);
|
||||
|
||||
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"));
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import range from "lodash/range";
|
|||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
|
|
@ -13,7 +15,6 @@ import { SidebarNavItem } from "@/components/sidebar";
|
|||
import { useUserPermissions } from "@/hooks/store";
|
||||
// plane web constants
|
||||
import { PROJECT_SETTINGS_LINKS } from "@/plane-web/constants/project";
|
||||
import { EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
export const ProjectSettingsSidebar = observer(() => {
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
|
|
@ -21,6 +22,8 @@ export const ProjectSettingsSidebar = observer(() => {
|
|||
// mobx store
|
||||
const { allowPermissions, projectUserInfo } = useUserPermissions();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
// derived values
|
||||
const currentProjectRole = projectUserInfo?.[workspaceSlug?.toString()]?.[projectId?.toString()]?.role;
|
||||
|
||||
|
|
@ -58,7 +61,7 @@ export const ProjectSettingsSidebar = observer(() => {
|
|||
isActive={link.highlight(pathname, `/${workspaceSlug}/projects/${projectId}`)}
|
||||
className="text-sm font-medium px-4 py-2"
|
||||
>
|
||||
{link.label}
|
||||
{t(link.i18n_label)}
|
||||
</SidebarNavItem>
|
||||
</Link>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@
|
|||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { ProjectStateRoot } from "@/components/project-states";
|
||||
// hook
|
||||
import { useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
const StatesSettingsPage = observer(() => {
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
|
|
@ -16,6 +17,8 @@ const StatesSettingsPage = observer(() => {
|
|||
const { currentProjectDetails } = useProject();
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
// derived values
|
||||
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - States` : undefined;
|
||||
// derived values
|
||||
|
|
@ -32,7 +35,7 @@ const StatesSettingsPage = observer(() => {
|
|||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
<div className="flex items-center border-b border-custom-border-100">
|
||||
<h3 className="text-xl font-medium">States</h3>
|
||||
<h3 className="text-xl font-medium">{t("common.states")}</h3>
|
||||
</div>
|
||||
{workspaceSlug && projectId && (
|
||||
<ProjectStateRoot workspaceSlug={workspaceSlug.toString()} projectId={projectId.toString()} />
|
||||
|
|
|
|||
|
|
@ -6,7 +6,15 @@ import Link from "next/link";
|
|||
import { useParams } from "next/navigation";
|
||||
import { Layers, Lock } from "lucide-react";
|
||||
// plane constants
|
||||
import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants";
|
||||
import {
|
||||
EIssueLayoutTypes,
|
||||
EIssueFilterType,
|
||||
EIssuesStoreType,
|
||||
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||
EViewAccess,
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
} from "@plane/constants";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
// ui
|
||||
|
|
@ -16,8 +24,6 @@ import { BreadcrumbLink, Logo } from "@/components/common";
|
|||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||
// constants
|
||||
import { ViewQuickActions } from "@/components/views";
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||
import { EViewAccess } from "@/constants/views";
|
||||
// helpers
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
import { truncateText } from "@/helpers/string.helper";
|
||||
|
|
@ -35,7 +41,6 @@ import {
|
|||
} from "@/hooks/store";
|
||||
// plane web
|
||||
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||
// refs
|
||||
|
|
@ -242,7 +247,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
|||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
projectId={projectId.toString()}
|
||||
labels={projectLabels}
|
||||
|
|
@ -255,7 +260,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
|||
<FiltersDropdown title="Display" placement="bottom-end">
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
|
|
@ -277,7 +282,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
|||
}}
|
||||
size="sm"
|
||||
>
|
||||
Add issue
|
||||
Add work item
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
|
|
|
|||
|
|
@ -4,29 +4,37 @@ import { useCallback } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
import { EUserPermissionsLevel, EUserProjectRoles, EViewAccess } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TViewFilterProps } from "@plane/types";
|
||||
import { Header, EHeaderVariant } from "@plane/ui";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
import { ProjectViewsList } from "@/components/views";
|
||||
import { ViewAppliedFiltersList } from "@/components/views/applied-filters";
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// constants
|
||||
import { EViewAccess } from "@/constants/views";
|
||||
// helpers
|
||||
import { calculateTotalFilters } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
import { useProject, useProjectView } from "@/hooks/store";
|
||||
import { useProject, useProjectView, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
const ProjectViewsPage = observer(() => {
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store
|
||||
const { getProjectById, currentProjectDetails } = useProject();
|
||||
const { filters, updateFilters, clearAllFilters } = useProjectView();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
const pageTitle = project?.name ? `${project?.name} - Views` : undefined;
|
||||
const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/views" });
|
||||
|
||||
const handleRemoveFilter = useCallback(
|
||||
(key: keyof TViewFilterProps, value: string | EViewAccess | null) => {
|
||||
|
|
@ -53,9 +61,17 @@ const ProjectViewsPage = observer(() => {
|
|||
if (currentProjectDetails?.issue_views_view === false)
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<EmptyState
|
||||
type={EmptyStateType.DISABLED_PROJECT_VIEW}
|
||||
primaryButtonLink={`/${workspaceSlug}/projects/${projectId}/settings/features`}
|
||||
<DetailedEmptyState
|
||||
title={t("disabled_project.empty_state.view.title")}
|
||||
description={t("disabled_project.empty_state.view.description")}
|
||||
assetPath={resolvedPath}
|
||||
primaryButton={{
|
||||
text: t("disabled_project.empty_state.view.primary_button.text"),
|
||||
onClick: () => {
|
||||
router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`);
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,20 +4,20 @@ import React, { useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// ui
|
||||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/ui";
|
||||
// component
|
||||
import { ApiTokenListItem, CreateApiTokenModal } from "@/components/api-token";
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
import { APITokenSettingsLoader } from "@/components/ui";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
import { API_TOKENS_LIST } from "@/constants/fetch-keys";
|
||||
// store hooks
|
||||
import { useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
// services
|
||||
import { APITokenService } from "@/services/api_token.service";
|
||||
|
||||
|
|
@ -28,11 +28,14 @@ const ApiTokensPage = observer(() => {
|
|||
const [isCreateTokenModalOpen, setIsCreateTokenModalOpen] = useState(false);
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const canPerformWorkspaceAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/workspace-settings/api-tokens" });
|
||||
|
||||
const { data: tokens } = useSWR(
|
||||
workspaceSlug && canPerformWorkspaceAdminActions ? API_TOKENS_LIST(workspaceSlug.toString()) : null,
|
||||
|
|
@ -40,7 +43,9 @@ const ApiTokensPage = observer(() => {
|
|||
workspaceSlug && canPerformWorkspaceAdminActions ? apiTokenService.getApiTokens(workspaceSlug.toString()) : null
|
||||
);
|
||||
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - API Tokens` : undefined;
|
||||
const pageTitle = currentWorkspace?.name
|
||||
? `${currentWorkspace.name} - ${t("workspace_settings.settings.api_tokens.title")}`
|
||||
: undefined;
|
||||
|
||||
if (workspaceUserInfo && !canPerformWorkspaceAdminActions) {
|
||||
return <NotAuthorizedView section="settings" />;
|
||||
|
|
@ -58,9 +63,9 @@ const ApiTokensPage = observer(() => {
|
|||
{tokens.length > 0 ? (
|
||||
<>
|
||||
<div className="flex items-center justify-between border-b border-custom-border-200 pb-3.5">
|
||||
<h3 className="text-xl font-medium">API tokens</h3>
|
||||
<h3 className="text-xl font-medium">{t("workspace_settings.settings.api_tokens.title")}</h3>
|
||||
<Button variant="primary" onClick={() => setIsCreateTokenModalOpen(true)}>
|
||||
Add API token
|
||||
{t("workspace_settings.settings.api_tokens.add_token")}
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -72,13 +77,17 @@ const ApiTokensPage = observer(() => {
|
|||
) : (
|
||||
<div className="flex h-full w-full flex-col">
|
||||
<div className="flex items-center justify-between gap-4 border-b border-custom-border-200 pb-3.5">
|
||||
<h3 className="text-xl font-medium">API tokens</h3>
|
||||
<h3 className="text-xl font-medium">{t("workspace_settings.settings.api_tokens.title")}</h3>
|
||||
<Button variant="primary" onClick={() => setIsCreateTokenModalOpen(true)}>
|
||||
Add API token
|
||||
{t("workspace_settings.settings.api_tokens.add_token")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="h-full w-full flex items-center justify-center">
|
||||
<EmptyState type={EmptyStateType.WORKSPACE_SETTINGS_API_TOKENS} />
|
||||
<DetailedEmptyState
|
||||
title={t("workspace_settings.empty_state.api_tokens.title")}
|
||||
description={t("workspace_settings.empty_state.api_tokens.description")}
|
||||
assetPath={resolvedPath}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
import { observer } from "mobx-react";
|
||||
// component
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
// hooks
|
||||
import { useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
// plane web components
|
||||
import { BillingRoot } from "@/plane-web/components/workspace";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
const BillingSettingsPage = observer(() => {
|
||||
// store hooks
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
import ExportGuide from "@/components/exporter/guide";
|
||||
|
|
@ -9,19 +11,21 @@ import ExportGuide from "@/components/exporter/guide";
|
|||
import { cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import { useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
const ExportsPage = observer(() => {
|
||||
// store hooks
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// derived values
|
||||
const canPerformWorkspaceMemberActions = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Exports` : undefined;
|
||||
const pageTitle = currentWorkspace?.name
|
||||
? `${currentWorkspace.name} - ${t("workspace_settings.settings.exports.title")}`
|
||||
: undefined;
|
||||
|
||||
// if user is not authorized to view this page
|
||||
if (workspaceUserInfo && !canPerformWorkspaceMemberActions) {
|
||||
|
|
@ -37,7 +41,7 @@ const ExportsPage = observer(() => {
|
|||
})}
|
||||
>
|
||||
<div className="flex items-center border-b border-custom-border-100 pb-3.5">
|
||||
<h3 className="text-xl font-medium">Exports</h3>
|
||||
<h3 className="text-xl font-medium">{t("workspace_settings.settings.exports.title")}</h3>
|
||||
</div>
|
||||
<ExportGuide />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { FC } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { Settings } from "lucide-react";
|
||||
// ui
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Breadcrumbs, Header } from "@plane/ui";
|
||||
// components
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
|
|
@ -12,6 +13,7 @@ import { useWorkspace } from "@/hooks/store";
|
|||
|
||||
export const WorkspaceSettingHeader: FC = observer(() => {
|
||||
const { currentWorkspace, loader } = useWorkspace();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Header>
|
||||
|
|
@ -27,7 +29,7 @@ export const WorkspaceSettingHeader: FC = observer(() => {
|
|||
/>
|
||||
}
|
||||
/>
|
||||
<Breadcrumbs.BreadcrumbItem type="text" link={<BreadcrumbLink label="Settings" />} />
|
||||
<Breadcrumbs.BreadcrumbItem type="text" link={<BreadcrumbLink label={t("settings")} />} />
|
||||
</Breadcrumbs>
|
||||
</Header.LeftItem>
|
||||
</Header>
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { PageHead } from "@/components/core";
|
||||
import IntegrationGuide from "@/components/integration/guide";
|
||||
// hooks
|
||||
import { useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
const ImportsPage = observer(() => {
|
||||
// store hooks
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { observer } from "mobx-react";
|
|||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// components
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { SingleIntegrationCard } from "@/components/integration";
|
||||
import { IntegrationAndImportExportBanner, IntegrationsSettingsLoader } from "@/components/ui";
|
||||
|
|
@ -10,7 +11,6 @@ import { IntegrationAndImportExportBanner, IntegrationsSettingsLoader } from "@/
|
|||
import { APP_INTEGRATIONS } from "@/constants/fetch-keys";
|
||||
// hooks
|
||||
import { useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
// services
|
||||
import { IntegrationService } from "@/services/integrations";
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
import { FC, ReactNode } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { AppHeader } from "@/components/core";
|
||||
// hooks
|
||||
import { useUserPermissions } from "@/hooks/store";
|
||||
// plane web constants
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
// local components
|
||||
import { WorkspaceSettingHeader } from "./header";
|
||||
import { MobileWorkspaceSettingsTabs } from "./mobile-header-tabs";
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import { observer } from "mobx-react";
|
|||
import { useParams } from "next/navigation";
|
||||
import { Search } from "lucide-react";
|
||||
// types
|
||||
import { MEMBER_INVITED, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IWorkspaceBulkInviteFormData } from "@plane/types";
|
||||
// ui
|
||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
|
|
@ -13,13 +15,11 @@ import { NotAuthorizedView } from "@/components/auth-screens";
|
|||
import { PageHead } from "@/components/core";
|
||||
import { SendWorkspaceInvitationModal, WorkspaceMembersList } from "@/components/workspace";
|
||||
// constants
|
||||
import { MEMBER_INVITED } from "@/constants/event-tracker";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { getUserRole } from "@/helpers/user.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
const WorkspaceMembersSettingsPage = observer(() => {
|
||||
// states
|
||||
|
|
@ -34,6 +34,7 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
|||
workspace: { inviteMembersToWorkspace },
|
||||
} = useMember();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// derived values
|
||||
const canPerformWorkspaceAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
|
||||
|
|
@ -62,7 +63,7 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
|||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: "Invitations sent successfully.",
|
||||
message: t("workspace_settings.settings.members.invitations_sent_successfully"),
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
|
|
@ -80,7 +81,7 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
|||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: `${err.error ?? "Something went wrong. Please try again."}`,
|
||||
message: `${err.error ?? t("something_went_wrong_please_try_again")}`,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -107,12 +108,12 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
|||
})}
|
||||
>
|
||||
<div className="flex justify-between gap-4 pb-3.5 items-start ">
|
||||
<h4 className="text-xl font-medium">Members</h4>
|
||||
<h4 className="text-xl font-medium">{t("workspace_settings.settings.members.title")}</h4>
|
||||
<div className="ml-auto flex items-center gap-1.5 rounded-md border border-custom-border-200 bg-custom-background-100 px-2.5 py-1.5">
|
||||
<Search className="h-3.5 w-3.5 text-custom-text-400" />
|
||||
<input
|
||||
className="w-full max-w-[234px] border-none bg-transparent text-sm outline-none placeholder:text-custom-text-400"
|
||||
placeholder="Search..."
|
||||
placeholder={`${t("search")}...`}
|
||||
value={searchQuery}
|
||||
autoFocus
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
|
|
@ -120,7 +121,7 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
|||
</div>
|
||||
{canPerformWorkspaceAdminActions && (
|
||||
<Button variant="primary" size="sm" onClick={() => setInviteModal(true)}>
|
||||
Add member
|
||||
{t("workspace_settings.settings.members.add_member")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { WORKSPACE_SETTINGS_LINKS, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// hooks
|
||||
import { useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// plane web constants
|
||||
import { EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
import { WORKSPACE_SETTINGS_LINKS } from "@/plane-web/constants/workspace";
|
||||
// plane web helpers
|
||||
import { shouldRenderSettingLink } from "@/plane-web/helpers/workspace.helper";
|
||||
|
||||
|
|
@ -13,6 +12,7 @@ export const MobileWorkspaceSettingsTabs = observer(() => {
|
|||
const router = useAppRouter();
|
||||
const { workspaceSlug } = useParams();
|
||||
const pathname = usePathname();
|
||||
const { t } = useTranslation();
|
||||
// mobx store
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ export const MobileWorkspaceSettingsTabs = observer(() => {
|
|||
key={index}
|
||||
onClick={() => router.push(`/${workspaceSlug}${item.href}`)}
|
||||
>
|
||||
{item.label}
|
||||
{t(item.i18n_label)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { WorkspaceDetails } from "@/components/workspace";
|
||||
// hooks
|
||||
|
|
@ -10,8 +11,11 @@ import { useWorkspace } from "@/hooks/store";
|
|||
const WorkspaceSettingsPage = observer(() => {
|
||||
// store hooks
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - General Settings` : undefined;
|
||||
const pageTitle = currentWorkspace?.name
|
||||
? t("workspace_settings.page_label", { workspace: currentWorkspace.name })
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -4,13 +4,12 @@ import React from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { WORKSPACE_SETTINGS_LINKS, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// components
|
||||
import { SidebarNavItem } from "@/components/sidebar";
|
||||
// hooks
|
||||
import { useUserPermissions } from "@/hooks/store";
|
||||
// plane web constants
|
||||
import { EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
import { WORKSPACE_SETTINGS_LINKS } from "@/plane-web/constants/workspace";
|
||||
// plane web helpers
|
||||
import { shouldRenderSettingLink } from "@/plane-web/helpers/workspace.helper";
|
||||
|
||||
|
|
@ -19,12 +18,13 @@ export const WorkspaceSettingsSidebar = observer(() => {
|
|||
const { workspaceSlug } = useParams();
|
||||
const pathname = usePathname();
|
||||
// mobx store
|
||||
const { t } = useTranslation();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
return (
|
||||
<div className="flex w-[280px] flex-col gap-6">
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-xs font-semibold text-custom-sidebar-text-400">SETTINGS</span>
|
||||
<span className="text-xs font-semibold text-custom-sidebar-text-400 uppercase">{t("settings")}</span>
|
||||
<div className="flex w-full flex-col gap-1">
|
||||
{WORKSPACE_SETTINGS_LINKS.map(
|
||||
(link) =>
|
||||
|
|
@ -36,7 +36,7 @@ export const WorkspaceSettingsSidebar = observer(() => {
|
|||
isActive={link.highlight(pathname, `/${workspaceSlug}`)}
|
||||
className="text-sm font-medium px-4 py-2"
|
||||
>
|
||||
{link.label}
|
||||
{t(link.i18n_label)}
|
||||
</SidebarNavItem>
|
||||
</Link>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { IWebhook } from "@plane/types";
|
||||
// ui
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
|
|
@ -13,7 +14,6 @@ import { PageHead } from "@/components/core";
|
|||
import { DeleteWebhookModal, WebhookDeleteSection, WebhookForm } from "@/components/web-hooks";
|
||||
// hooks
|
||||
import { useUserPermissions, useWebhook, useWorkspace } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
const WebhookDetailsPage = observer(() => {
|
||||
// states
|
||||
|
|
|
|||
|
|
@ -4,39 +4,43 @@ import React, { useEffect, useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// ui
|
||||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/ui";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
import { WebhookSettingsLoader } from "@/components/ui";
|
||||
import { WebhooksList, CreateWebhookModal } from "@/components/web-hooks";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// hooks
|
||||
import { useUserPermissions, useWebhook, useWorkspace } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
const WebhooksListPage = observer(() => {
|
||||
// states
|
||||
const [showCreateWebhookModal, setShowCreateWebhookModal] = useState(false);
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// mobx store
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
|
||||
const { fetchWebhooks, webhooks, clearSecretKey, webhookSecretKey, createWebhook } = useWebhook();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
// derived values
|
||||
const canPerformWorkspaceAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/workspace-settings/webhooks" });
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && canPerformWorkspaceAdminActions ? `WEBHOOKS_LIST_${workspaceSlug}` : null,
|
||||
workspaceSlug && canPerformWorkspaceAdminActions ? () => fetchWebhooks(workspaceSlug.toString()) : null
|
||||
);
|
||||
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Webhooks` : undefined;
|
||||
const pageTitle = currentWorkspace?.name
|
||||
? `${currentWorkspace.name} - ${t("workspace_settings.settings.webhooks.title")}`
|
||||
: undefined;
|
||||
|
||||
// clear secret key when modal is closed.
|
||||
useEffect(() => {
|
||||
|
|
@ -65,9 +69,9 @@ const WebhooksListPage = observer(() => {
|
|||
{Object.keys(webhooks).length > 0 ? (
|
||||
<div className="flex h-full w-full flex-col">
|
||||
<div className="flex items-center justify-between gap-4 border-b border-custom-border-200 pb-3.5">
|
||||
<div className="text-xl font-medium">Webhooks</div>
|
||||
<div className="text-xl font-medium">{t("workspace_settings.settings.webhooks.title")}</div>
|
||||
<Button variant="primary" size="sm" onClick={() => setShowCreateWebhookModal(true)}>
|
||||
Add webhook
|
||||
{t("workspace_settings.settings.webhooks.add_webhook")}
|
||||
</Button>
|
||||
</div>
|
||||
<WebhooksList />
|
||||
|
|
@ -75,13 +79,17 @@ const WebhooksListPage = observer(() => {
|
|||
) : (
|
||||
<div className="flex h-full w-full flex-col">
|
||||
<div className="flex items-center justify-between gap-4 border-b border-custom-border-200 pb-3.5">
|
||||
<div className="text-xl font-medium">Webhooks</div>
|
||||
<div className="text-xl font-medium">{t("workspace_settings.settings.webhooks.title")}</div>
|
||||
<Button variant="primary" size="sm" onClick={() => setShowCreateWebhookModal(true)}>
|
||||
Add webhook
|
||||
{t("workspace_settings.settings.webhooks.add_webhook")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="h-full w-full flex items-center justify-center">
|
||||
<EmptyState type={EmptyStateType.WORKSPACE_SETTINGS_WEBHOOKS} />
|
||||
<DetailedEmptyState
|
||||
title={t("workspace_settings.empty_state.webhooks.title")}
|
||||
description={t("workspace_settings.empty_state.webhooks.description")}
|
||||
assetPath={resolvedPath}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { FC, useEffect, useRef } from "react";
|
|||
import isEmpty from "lodash/isEmpty";
|
||||
import { observer } from "mobx-react";
|
||||
// plane helpers
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useOutsideClickDetector } from "@plane/hooks";
|
||||
// components
|
||||
import {
|
||||
|
|
@ -22,7 +23,6 @@ import useSize from "@/hooks/use-window-size";
|
|||
// plane web components
|
||||
import { SidebarAppSwitcher } from "@/plane-web/components/sidebar";
|
||||
import { SidebarTeamsList } from "@/plane-web/components/workspace/sidebar/teams-sidebar-list";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
export const AppSidebar: FC = observer(() => {
|
||||
// store hooks
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@
|
|||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { DEFAULT_GLOBAL_VIEWS_LIST } from "@plane/constants";
|
||||
// components
|
||||
import { PageHead } from "@/components/core";
|
||||
import { AllIssueLayoutRoot, GlobalViewsAppliedFiltersRoot } from "@/components/issues";
|
||||
import { GlobalViewsHeader } from "@/components/workspace";
|
||||
// constants
|
||||
import { DEFAULT_GLOBAL_VIEWS_LIST } from "@/constants/workspace";
|
||||
// hooks
|
||||
import { useWorkspace } from "@/hooks/store";
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import { observer } from "mobx-react";
|
|||
import { useParams } from "next/navigation";
|
||||
import { Layers } from "lucide-react";
|
||||
// plane constants
|
||||
import { EIssueFilterType, EIssuesStoreType } from "@plane/constants";
|
||||
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
// ui
|
||||
|
|
@ -14,8 +15,6 @@ import { Breadcrumbs, Button, Header } from "@plane/ui";
|
|||
import { BreadcrumbLink } from "@/components/common";
|
||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection } from "@/components/issues";
|
||||
import { CreateUpdateWorkspaceViewModal } from "@/components/workspace";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||
// helpers
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
|
|
@ -35,6 +34,7 @@ export const GlobalIssuesHeader = observer(() => {
|
|||
const {
|
||||
workspace: { workspaceMemberIds },
|
||||
} = useMember();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const issueFilters = globalViewId ? filters[globalViewId.toString()] : undefined;
|
||||
|
||||
|
|
@ -105,7 +105,7 @@ export const GlobalIssuesHeader = observer(() => {
|
|||
<Breadcrumbs>
|
||||
<Breadcrumbs.BreadcrumbItem
|
||||
type="text"
|
||||
link={<BreadcrumbLink label={`Views`} icon={<Layers className="h-4 w-4 text-custom-text-300" />} />}
|
||||
link={<BreadcrumbLink label={t("views")} icon={<Layers className="h-4 w-4 text-custom-text-300" />} />}
|
||||
/>
|
||||
</Breadcrumbs>
|
||||
</Header.LeftItem>
|
||||
|
|
@ -114,12 +114,12 @@ export const GlobalIssuesHeader = observer(() => {
|
|||
{!isLocked ? (
|
||||
<>
|
||||
<FiltersDropdown
|
||||
title="Filters"
|
||||
title={t("common.filters")}
|
||||
placement="bottom-end"
|
||||
isFiltersApplied={isIssueFilterActive(issueFilters)}
|
||||
>
|
||||
<FilterSelection
|
||||
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet}
|
||||
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_PAGE.my_issues.spreadsheet}
|
||||
filters={issueFilters?.filters ?? {}}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
|
|
@ -128,9 +128,9 @@ export const GlobalIssuesHeader = observer(() => {
|
|||
memberIds={workspaceMemberIds ?? undefined}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
<FiltersDropdown title="Display" placement="bottom-end">
|
||||
<FiltersDropdown title={t("common.display")} placement="bottom-end">
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet}
|
||||
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_PAGE.my_issues.spreadsheet}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||
|
|
@ -143,7 +143,7 @@ export const GlobalIssuesHeader = observer(() => {
|
|||
)}
|
||||
|
||||
<Button variant="primary" size="sm" onClick={() => setCreateViewModal(true)}>
|
||||
Add view
|
||||
{t("workspace_views.add_view")}
|
||||
</Button>
|
||||
</Header.RightItem>
|
||||
</Header>
|
||||
|
|
|
|||
|
|
@ -4,13 +4,15 @@ import React, { useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
// icons
|
||||
import { Search } from "lucide-react";
|
||||
// plane imports
|
||||
import { DEFAULT_GLOBAL_VIEWS_LIST } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { Input } from "@plane/ui";
|
||||
// components
|
||||
import { PageHead } from "@/components/core";
|
||||
import { GlobalDefaultViewListItem, GlobalViewsList } from "@/components/workspace";
|
||||
// constants
|
||||
import { DEFAULT_GLOBAL_VIEWS_LIST } from "@/constants/workspace";
|
||||
// hooks
|
||||
import { useWorkspace } from "@/hooks/store";
|
||||
|
||||
|
|
@ -18,6 +20,7 @@ const WorkspaceViewsPage = observer(() => {
|
|||
const [query, setQuery] = useState("");
|
||||
// store
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - All Views` : undefined;
|
||||
|
||||
|
|
@ -36,7 +39,7 @@ const WorkspaceViewsPage = observer(() => {
|
|||
/>
|
||||
</div>
|
||||
<div className="flex flex-col h-full w-full vertical-scrollbar scrollbar-lg">
|
||||
{DEFAULT_GLOBAL_VIEWS_LIST.filter((v) => v.label.toLowerCase().includes(query.toLowerCase())).map(
|
||||
{DEFAULT_GLOBAL_VIEWS_LIST.filter((v) => t(v.i18n_label).toLowerCase().includes(query.toLowerCase())).map(
|
||||
(option) => (
|
||||
<GlobalDefaultViewListItem key={option.key} view={option} />
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
|
|
@ -7,10 +8,10 @@ import { useTheme } from "next-themes";
|
|||
import { Controller, useForm } from "react-hook-form";
|
||||
// icons
|
||||
import { CircleCheck } from "lucide-react";
|
||||
// ui
|
||||
// plane imports
|
||||
import { FORGOT_PASS_LINK, NAVIGATE_TO_SIGNUP } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button, Input, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
||||
// constants
|
||||
import { FORGOT_PASS_LINK, NAVIGATE_TO_SIGNUP } from "@/constants/event-tracker";
|
||||
// helpers
|
||||
import { EPageTypes } from "@/helpers/authentication.helper";
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
|
|
@ -20,12 +21,12 @@ import { useEventTracker } from "@/hooks/store";
|
|||
import useTimer from "@/hooks/use-timer";
|
||||
// wrappers
|
||||
import { AuthenticationWrapper } from "@/lib/wrappers";
|
||||
// services
|
||||
// images
|
||||
import PlaneBackgroundPatternDark from "@/public/auth/background-pattern-dark.svg";
|
||||
import PlaneBackgroundPattern from "@/public/auth/background-pattern.svg";
|
||||
import BlackHorizontalLogo from "@/public/plane-logos/black-horizontal-with-blue-logo.png";
|
||||
import WhiteHorizontalLogo from "@/public/plane-logos/white-horizontal-with-blue-logo.png";
|
||||
// services
|
||||
import { AuthService } from "@/services/auth.service";
|
||||
|
||||
type TForgotPasswordFormValues = {
|
||||
|
|
@ -39,10 +40,12 @@ const defaultValues: TForgotPasswordFormValues = {
|
|||
// services
|
||||
const authService = new AuthService();
|
||||
|
||||
export default function ForgotPasswordPage() {
|
||||
const ForgotPasswordPage = observer(() => {
|
||||
// search params
|
||||
const searchParams = useSearchParams();
|
||||
const email = searchParams.get("email");
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
// hooks
|
||||
|
|
@ -73,9 +76,8 @@ export default function ForgotPasswordPage() {
|
|||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Email sent",
|
||||
message:
|
||||
"Check your inbox for a link to reset your password. If it doesn't appear within a few minutes, check your spam folder.",
|
||||
title: t("auth.forgot_password.toast.success.title"),
|
||||
message: t("auth.forgot_password.toast.success.message"),
|
||||
});
|
||||
setResendCodeTimer(30);
|
||||
})
|
||||
|
|
@ -85,8 +87,8 @@ export default function ForgotPasswordPage() {
|
|||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: err?.error ?? "Something went wrong. Please try again.",
|
||||
title: t("auth.forgot_password.toast.error.title"),
|
||||
message: err?.error ?? t("auth.forgot_password.toast.error.message"),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -111,13 +113,13 @@ export default function ForgotPasswordPage() {
|
|||
</Link>
|
||||
</div>
|
||||
<div className="flex flex-col items-end sm:items-center sm:gap-2 sm:flex-row text-center text-sm font-medium text-onboarding-text-300">
|
||||
New to Plane?{" "}
|
||||
{t("auth.common.new_to_plane")}
|
||||
<Link
|
||||
href="/"
|
||||
onClick={() => captureEvent(NAVIGATE_TO_SIGNUP, {})}
|
||||
className="font-semibold text-custom-primary-100 hover:underline"
|
||||
>
|
||||
Create an account
|
||||
{t("auth.common.create_account")}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -125,23 +127,21 @@ export default function ForgotPasswordPage() {
|
|||
<div className="relative flex flex-col space-y-6">
|
||||
<div className="text-center space-y-1 py-4">
|
||||
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
||||
Reset your password
|
||||
{t("auth.forgot_password.title")}
|
||||
</h3>
|
||||
<p className="font-medium text-onboarding-text-400">
|
||||
Enter your user account{"'"}s verified email address and we will send you a password reset link.
|
||||
</p>
|
||||
<p className="font-medium text-onboarding-text-400">{t("auth.forgot_password.description")}</p>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit(handleForgotPassword)} className="mt-5 space-y-4">
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
|
||||
Email
|
||||
{t("auth.common.email.label")}
|
||||
</label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="email"
|
||||
rules={{
|
||||
required: "Email is required",
|
||||
validate: (value) => checkEmailValidity(value) || "Email is invalid",
|
||||
required: t("auth.common.email.errors.required"),
|
||||
validate: (value) => checkEmailValidity(value) || t("auth.common.email.errors.invalid"),
|
||||
}}
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<Input
|
||||
|
|
@ -152,7 +152,7 @@ export default function ForgotPasswordPage() {
|
|||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.email)}
|
||||
placeholder="name@company.com"
|
||||
placeholder={t("auth.common.email.placeholder")}
|
||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||
autoComplete="on"
|
||||
disabled={resendTimerCode > 0}
|
||||
|
|
@ -162,7 +162,7 @@ export default function ForgotPasswordPage() {
|
|||
{resendTimerCode > 0 && (
|
||||
<p className="flex w-full items-start px-1 gap-1 text-xs font-medium text-green-700">
|
||||
<CircleCheck height={12} width={12} className="mt-0.5" />
|
||||
We sent the reset link to your email address
|
||||
{t("auth.forgot_password.email_sent")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -174,10 +174,12 @@ export default function ForgotPasswordPage() {
|
|||
disabled={!isValid}
|
||||
loading={isSubmitting || resendTimerCode > 0}
|
||||
>
|
||||
{resendTimerCode > 0 ? `Resend in ${resendTimerCode} seconds` : "Send reset link"}
|
||||
{resendTimerCode > 0
|
||||
? t("auth.common.resend_in", { seconds: resendTimerCode })
|
||||
: t("auth.forgot_password.send_reset_link")}
|
||||
</Button>
|
||||
<Link href="/" className={cn("w-full", getButtonStyling("link-neutral", "lg"))}>
|
||||
Back to sign in
|
||||
{t("auth.common.back_to_sign_in")}
|
||||
</Link>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -186,4 +188,6 @@ export default function ForgotPasswordPage() {
|
|||
</div>
|
||||
</AuthenticationWrapper>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default ForgotPasswordPage;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
|
|
@ -8,6 +9,7 @@ import { useSearchParams } from "next/navigation";
|
|||
import { useTheme } from "next-themes";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
// ui
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button, Input } from "@plane/ui";
|
||||
// components
|
||||
import { AuthBanner, PasswordStrengthMeter } from "@/components/account";
|
||||
|
|
@ -45,7 +47,7 @@ const defaultValues: TResetPasswordFormValues = {
|
|||
// services
|
||||
const authService = new AuthService();
|
||||
|
||||
export default function ResetPasswordPage() {
|
||||
const ResetPasswordPage = observer(() => {
|
||||
// search params
|
||||
const searchParams = useSearchParams();
|
||||
const uidb64 = searchParams.get("uidb64");
|
||||
|
|
@ -65,7 +67,8 @@ export default function ResetPasswordPage() {
|
|||
const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false);
|
||||
const [isRetryPasswordInputFocused, setIsRetryPasswordInputFocused] = useState(false);
|
||||
const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(undefined);
|
||||
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// hooks
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
|
|
@ -127,9 +130,9 @@ export default function ResetPasswordPage() {
|
|||
<div className="relative flex flex-col space-y-6">
|
||||
<div className="text-center space-y-1 py-4">
|
||||
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
||||
Set new password
|
||||
{t("auth.reset_password.title")}
|
||||
</h3>
|
||||
<p className="font-medium text-onboarding-text-400">Secure your account with a strong password</p>
|
||||
<p className="font-medium text-onboarding-text-400">{t("auth.reset_password.description")}</p>
|
||||
</div>
|
||||
{errorInfo && errorInfo?.type === EErrorAlertType.BANNER_ALERT && (
|
||||
<AuthBanner bannerData={errorInfo} handleBannerData={(value) => setErrorInfo(value)} />
|
||||
|
|
@ -142,7 +145,7 @@ export default function ResetPasswordPage() {
|
|||
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
|
||||
Email
|
||||
{t("auth.common.email.label")}
|
||||
</label>
|
||||
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
||||
<Input
|
||||
|
|
@ -151,7 +154,7 @@ export default function ResetPasswordPage() {
|
|||
type="email"
|
||||
value={resetFormData.email}
|
||||
//hasError={Boolean(errors.email)}
|
||||
placeholder="name@company.com"
|
||||
placeholder={t("auth.common.email.placeholder")}
|
||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 text-onboarding-text-400 cursor-not-allowed"
|
||||
autoComplete="on"
|
||||
disabled
|
||||
|
|
@ -160,7 +163,7 @@ export default function ResetPasswordPage() {
|
|||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="password">
|
||||
Password
|
||||
{t("auth.common.password.label")}
|
||||
</label>
|
||||
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
||||
<Input
|
||||
|
|
@ -169,7 +172,7 @@ export default function ResetPasswordPage() {
|
|||
value={resetFormData.password}
|
||||
onChange={(e) => handleFormChange("password", e.target.value)}
|
||||
//hasError={Boolean(errors.password)}
|
||||
placeholder="Enter password"
|
||||
placeholder={t("auth.common.password.placeholder")}
|
||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||
minLength={8}
|
||||
onFocus={() => setIsPasswordInputFocused(true)}
|
||||
|
|
@ -193,7 +196,7 @@ export default function ResetPasswordPage() {
|
|||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
|
||||
Confirm password
|
||||
{t("auth.common.password.confirm_password.label")}
|
||||
</label>
|
||||
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
||||
<Input
|
||||
|
|
@ -201,7 +204,7 @@ export default function ResetPasswordPage() {
|
|||
name="confirm_password"
|
||||
value={resetFormData.confirm_password}
|
||||
onChange={(e) => handleFormChange("confirm_password", e.target.value)}
|
||||
placeholder="Confirm password"
|
||||
placeholder={t("auth.common.password.confirm_password.placeholder")}
|
||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||
onFocus={() => setIsRetryPasswordInputFocused(true)}
|
||||
onBlur={() => setIsRetryPasswordInputFocused(false)}
|
||||
|
|
@ -220,10 +223,12 @@ export default function ResetPasswordPage() {
|
|||
</div>
|
||||
{!!resetFormData.confirm_password &&
|
||||
resetFormData.password !== resetFormData.confirm_password &&
|
||||
renderPasswordMatchError && <span className="text-sm text-red-500">Passwords don{"'"}t match</span>}
|
||||
renderPasswordMatchError && (
|
||||
<span className="text-sm text-red-500">{t("auth.common.password.errors.match")}</span>
|
||||
)}
|
||||
</div>
|
||||
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
|
||||
Set password
|
||||
{t("auth.common.password.submit")}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -232,4 +237,6 @@ export default function ResetPasswordPage() {
|
|||
</div>
|
||||
</AuthenticationWrapper>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default ResetPasswordPage;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ import { useSearchParams } from "next/navigation";
|
|||
// icons
|
||||
import { useTheme } from "next-themes";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
// ui
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { PasswordStrengthMeter } from "@/components/account";
|
||||
|
|
@ -60,9 +61,10 @@ const SetPasswordPage = observer(() => {
|
|||
const [csrfToken, setCsrfToken] = useState<string | undefined>(undefined);
|
||||
const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false);
|
||||
const [isRetryPasswordInputFocused, setIsRetryPasswordInputFocused] = useState(false);
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// hooks
|
||||
const { resolvedTheme } = useTheme();
|
||||
// hooks
|
||||
const { data: user, handleSetPassword } = useUser();
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -95,8 +97,8 @@ const SetPasswordPage = observer(() => {
|
|||
} catch (err: any) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: err?.error ?? "Something went wrong. Please try again.",
|
||||
title: t("common.errors.default.title"),
|
||||
message: err?.error ?? t("common.errors.default.message"),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -108,7 +110,8 @@ const SetPasswordPage = observer(() => {
|
|||
const logo = resolvedTheme === "light" ? BlackHorizontalLogo : WhiteHorizontalLogo;
|
||||
|
||||
return (
|
||||
<AuthenticationWrapper pageType={EPageTypes.SET_PASSWORD}>
|
||||
// TODO: change to EPageTypes.SET_PASSWORD
|
||||
<AuthenticationWrapper pageType={EPageTypes.NON_AUTHENTICATED}>
|
||||
<div className="relative w-screen h-screen overflow-hidden">
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image
|
||||
|
|
@ -129,14 +132,14 @@ const SetPasswordPage = observer(() => {
|
|||
<div className="relative flex flex-col space-y-6">
|
||||
<div className="text-center space-y-1 py-4">
|
||||
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
||||
Secure your account
|
||||
{t("auth.set_password.title")}
|
||||
</h3>
|
||||
<p className="font-medium text-onboarding-text-400">Setting password helps you login securely</p>
|
||||
<p className="font-medium text-onboarding-text-400">{t("auth.set_password.description")}</p>
|
||||
</div>
|
||||
<form className="mt-5 space-y-4" onSubmit={(e) => handleSubmit(e)}>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
|
||||
Email
|
||||
{t("auth.common.email.label")}
|
||||
</label>
|
||||
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
||||
<Input
|
||||
|
|
@ -145,7 +148,7 @@ const SetPasswordPage = observer(() => {
|
|||
type="email"
|
||||
value={user?.email}
|
||||
//hasError={Boolean(errors.email)}
|
||||
placeholder="name@company.com"
|
||||
placeholder={t("auth.common.email.placeholder")}
|
||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 text-onboarding-text-400 cursor-not-allowed"
|
||||
autoComplete="on"
|
||||
disabled
|
||||
|
|
@ -154,7 +157,7 @@ const SetPasswordPage = observer(() => {
|
|||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="password">
|
||||
Set a password
|
||||
{t("auth.common.password.label")}
|
||||
</label>
|
||||
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
||||
<Input
|
||||
|
|
@ -163,7 +166,7 @@ const SetPasswordPage = observer(() => {
|
|||
value={passwordFormData.password}
|
||||
onChange={(e) => handleFormChange("password", e.target.value)}
|
||||
//hasError={Boolean(errors.password)}
|
||||
placeholder="Enter password"
|
||||
placeholder={t("auth.common.password.placeholder")}
|
||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||
minLength={8}
|
||||
onFocus={() => setIsPasswordInputFocused(true)}
|
||||
|
|
@ -187,7 +190,7 @@ const SetPasswordPage = observer(() => {
|
|||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
|
||||
Confirm password
|
||||
{t("auth.common.password.confirm_password.label")}
|
||||
</label>
|
||||
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
||||
<Input
|
||||
|
|
@ -195,7 +198,7 @@ const SetPasswordPage = observer(() => {
|
|||
name="confirm_password"
|
||||
value={passwordFormData.confirm_password}
|
||||
onChange={(e) => handleFormChange("confirm_password", e.target.value)}
|
||||
placeholder="Confirm password"
|
||||
placeholder={t("auth.common.password.confirm_password.placeholder")}
|
||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||
onFocus={() => setIsRetryPasswordInputFocused(true)}
|
||||
onBlur={() => setIsRetryPasswordInputFocused(false)}
|
||||
|
|
@ -214,10 +217,12 @@ const SetPasswordPage = observer(() => {
|
|||
</div>
|
||||
{!!passwordFormData.confirm_password &&
|
||||
passwordFormData.password !== passwordFormData.confirm_password &&
|
||||
renderPasswordMatchError && <span className="text-sm text-red-500">Passwords don{"'"}t match</span>}
|
||||
renderPasswordMatchError && (
|
||||
<span className="text-sm text-red-500">{t("auth.common.password.errors.match")}</span>
|
||||
)}
|
||||
</div>
|
||||
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
|
||||
Continue
|
||||
{t("common.continue")}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -42,11 +42,12 @@ const CreateWorkspacePage = observer(() => {
|
|||
|
||||
// methods
|
||||
const getMailtoHref = () => {
|
||||
const subject = t("workspace_request_subject");
|
||||
const body = t("workspace_request_body")
|
||||
.replace("{{firstName}}", currentUser?.first_name || "")
|
||||
.replace("{{lastName}}", currentUser?.last_name || "")
|
||||
.replace("{{email}}", currentUser?.email || "");
|
||||
const subject = t("workspace_creation.request_email.subject");
|
||||
const body = t("workspace_creation.request_email.body", {
|
||||
firstName: currentUser?.first_name || "",
|
||||
lastName: currentUser?.last_name || "",
|
||||
email: currentUser?.email || "",
|
||||
});
|
||||
|
||||
return `mailto:?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
|
||||
};
|
||||
|
|
@ -67,7 +68,7 @@ const CreateWorkspacePage = observer(() => {
|
|||
href="/"
|
||||
>
|
||||
<div className="h-[30px] w-[133px]">
|
||||
<Image src={logo} alt={t("plane_logo")} />
|
||||
<Image src={logo} alt="Plane logo" />
|
||||
</div>
|
||||
</Link>
|
||||
<div className="absolute right-4 top-1/4 -translate-y-1/2 text-sm text-custom-text-100 sm:fixed sm:right-16 sm:top-12 sm:translate-y-0 sm:py-5">
|
||||
|
|
@ -77,30 +78,25 @@ const CreateWorkspacePage = observer(() => {
|
|||
<div className="relative flex h-full justify-center px-8 pb-8 sm:w-10/12 sm:items-center sm:justify-start sm:p-0 sm:pr-[8.33%] md:w-9/12 lg:w-4/5">
|
||||
{isWorkspaceCreationDisabled ? (
|
||||
<div className="w-4/5 h-full flex flex-col items-center justify-center text-lg font-medium gap-1">
|
||||
<Image
|
||||
src={WorkspaceCreationDisabled}
|
||||
width={200}
|
||||
alt={t("workspace_creation_disabled")}
|
||||
className="mb-4"
|
||||
/>
|
||||
<Image src={WorkspaceCreationDisabled} width={200} alt="Workspace creation disabled" className="mb-4" />
|
||||
<div className="text-lg font-medium text-center">
|
||||
{t("only_your_instance_admin_can_create_workspaces")}
|
||||
{t("workspace_creation.errors.creation_disabled.title")}
|
||||
</div>
|
||||
<p className="text-sm text-custom-text-300 break-words text-center">
|
||||
{t("only_your_instance_admin_can_create_workspaces_description")}
|
||||
{t("workspace_creation.errors.creation_disabled.description")}
|
||||
</p>
|
||||
<div className="flex gap-4 mt-6">
|
||||
<Button variant="primary" onClick={() => router.back()}>
|
||||
{t("go_back")}
|
||||
{t("common.go_back")}
|
||||
</Button>
|
||||
<a href={getMailtoHref()} className={getButtonStyling("outline-primary", "md")}>
|
||||
{t("request_instance_admin")}
|
||||
{t("workspace_creation.errors.creation_disabled.request_button")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full space-y-7 sm:space-y-10">
|
||||
<h4 className="text-2xl font-semibold">{t("create_your_workspace")}</h4>
|
||||
<h4 className="text-2xl font-semibold">{t("workspace_creation.heading")}</h4>
|
||||
<div className="sm:w-3/4 md:w-2/5">
|
||||
<CreateWorkspaceForm
|
||||
onSubmit={onSubmit}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import Link from "next/link";
|
|||
import { useTheme } from "next-themes";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { CheckCircle2 } from "lucide-react";
|
||||
// plane imports
|
||||
import { ROLE, MEMBER_ACCEPTED, EUserPermissions } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import type { IWorkspaceMemberInvitation } from "@plane/types";
|
||||
|
|
@ -15,10 +17,7 @@ import type { IWorkspaceMemberInvitation } from "@plane/types";
|
|||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { EmptyState } from "@/components/common";
|
||||
// constants
|
||||
import { MEMBER_ACCEPTED } from "@/constants/event-tracker";
|
||||
import { USER_WORKSPACES_LIST } from "@/constants/fetch-keys";
|
||||
import { ROLE } from "@/constants/workspace";
|
||||
// helpers
|
||||
import { truncateText } from "@/helpers/string.helper";
|
||||
import { getUserRole } from "@/helpers/user.helper";
|
||||
|
|
@ -27,8 +26,6 @@ import { useEventTracker, useUser, useUserProfile, useWorkspace } from "@/hooks/
|
|||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// services
|
||||
import { AuthenticationWrapper } from "@/lib/wrappers";
|
||||
// plane web constants
|
||||
import { EUserPermissions } from "@/plane-web/constants/user-permissions";
|
||||
// plane web services
|
||||
import { WorkspaceService } from "@/plane-web/services";
|
||||
// images
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import "@/styles/command-pallette.css";
|
|||
import "@/styles/emoji.css";
|
||||
import "@/styles/react-day-picker.css";
|
||||
// meta data info
|
||||
import { SITE_NAME, SITE_DESCRIPTION } from "@/constants/meta";
|
||||
|
||||
import { SITE_DESCRIPTION, SITE_NAME } from "@plane/constants";
|
||||
// helpers
|
||||
import { API_BASE_URL, cn } from "@/helpers/common.helper";
|
||||
// local
|
||||
|
|
@ -17,12 +18,11 @@ export const metadata: Metadata = {
|
|||
description: SITE_DESCRIPTION,
|
||||
openGraph: {
|
||||
title: "Plane | Simple, extensible, open-source project management tool.",
|
||||
description:
|
||||
"Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind.",
|
||||
description: "Open-source project management tool to manage work items, cycles, and product roadmaps easily",
|
||||
url: "https://app.plane.so/",
|
||||
},
|
||||
keywords:
|
||||
"software development, plan, ship, software, accelerate, code management, release management, project management, issue tracking, agile, scrum, kanban, collaboration",
|
||||
"software development, plan, ship, software, accelerate, code management, release management, project management, work item tracking, agile, scrum, kanban, collaboration",
|
||||
twitter: {
|
||||
site: "@planepowers",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useEffect, useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
// types
|
||||
import { USER_ONBOARDING_COMPLETED } from "@plane/constants";
|
||||
import { TOnboardingSteps, TUserProfile } from "@plane/types";
|
||||
// ui
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
|
|
@ -11,7 +12,6 @@ import { TOAST_TYPE, setToast } from "@plane/ui";
|
|||
import { LogoSpinner } from "@/components/common";
|
||||
import { InviteMembers, CreateOrJoinWorkspaces, ProfileSetup } from "@/components/onboarding";
|
||||
// constants
|
||||
import { USER_ONBOARDING_COMPLETED } from "@/constants/event-tracker";
|
||||
import { USER_WORKSPACES_LIST } from "@/constants/fetch-keys";
|
||||
// helpers
|
||||
import { EPageTypes } from "@/helpers/authentication.helper";
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@ import Link from "next/link";
|
|||
// ui
|
||||
import { useTheme } from "next-themes";
|
||||
// components
|
||||
import { NAVIGATE_TO_SIGNUP } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { AuthRoot } from "@/components/account";
|
||||
import { PageHead } from "@/components/core";
|
||||
// constants
|
||||
import { NAVIGATE_TO_SIGNUP } from "@/constants/event-tracker";
|
||||
// helpers
|
||||
import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper";
|
||||
// hooks
|
||||
|
|
@ -27,6 +28,8 @@ import WhiteHorizontalLogo from "@/public/plane-logos/white-horizontal-with-blue
|
|||
|
||||
const HomePage = observer(() => {
|
||||
const { resolvedTheme } = useTheme();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
|
||||
|
|
@ -37,7 +40,7 @@ const HomePage = observer(() => {
|
|||
<AuthenticationWrapper pageType={EPageTypes.NON_AUTHENTICATED}>
|
||||
<>
|
||||
<div className="relative w-screen h-screen overflow-hidden">
|
||||
<PageHead title="Log in - Plane" />
|
||||
<PageHead title={t("auth.common.login") + " - Plane"} />
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image
|
||||
src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern}
|
||||
|
|
@ -53,13 +56,13 @@ const HomePage = observer(() => {
|
|||
</Link>
|
||||
</div>
|
||||
<div className="flex flex-col items-end sm:items-center sm:gap-2 sm:flex-row text-center text-sm font-medium text-onboarding-text-300">
|
||||
New to Plane?{" "}
|
||||
{t("auth.common.new_to_plane")}
|
||||
<Link
|
||||
href="/sign-up"
|
||||
onClick={() => captureEvent(NAVIGATE_TO_SIGNUP, {})}
|
||||
className="font-semibold text-custom-primary-100 hover:underline"
|
||||
>
|
||||
Create an account
|
||||
{t("auth.common.create_account")}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,24 +7,27 @@ import { useTranslation } from "@plane/i18n";
|
|||
import { Button } from "@plane/ui";
|
||||
// components
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
import {
|
||||
ProfileActivityListPage,
|
||||
ProfileSettingContentHeader,
|
||||
ProfileSettingContentWrapper,
|
||||
} from "@/components/profile";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// hooks
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
const PER_PAGE = 100;
|
||||
|
||||
const ProfileActivityPage = observer(() => {
|
||||
const { t } = useTranslation();
|
||||
// states
|
||||
const [pageCount, setPageCount] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(0);
|
||||
const [resultsCount, setResultsCount] = useState(0);
|
||||
const [isEmpty, setIsEmpty] = useState(false);
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/profile/activity" });
|
||||
|
||||
const updateTotalPages = (count: number) => setTotalPages(count);
|
||||
|
||||
|
|
@ -50,7 +53,13 @@ const ProfileActivityPage = observer(() => {
|
|||
const isLoadMoreVisible = pageCount < totalPages && resultsCount !== 0;
|
||||
|
||||
if (isEmpty) {
|
||||
return <EmptyState type={EmptyStateType.PROFILE_ACTIVITY} layout="screen-detailed" />;
|
||||
return (
|
||||
<DetailedEmptyState
|
||||
title={t("profile.empty_state.activity.title")}
|
||||
description={t("profile.empty_state.activity.description")}
|
||||
assetPath={resolvedPath}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useTheme } from "next-themes";
|
||||
// plane imports
|
||||
import { I_THEME_OPTION, THEME_OPTIONS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IUserTheme } from "@plane/types";
|
||||
import { setPromiseToast } from "@plane/ui";
|
||||
|
|
@ -11,7 +13,6 @@ import { LogoSpinner } from "@/components/common";
|
|||
import { CustomThemeSelector, ThemeSwitch, PageHead } from "@/components/core";
|
||||
import { ProfileSettingContentHeader, ProfileSettingContentWrapper } from "@/components/profile";
|
||||
// constants
|
||||
import { I_THEME_OPTION, THEME_OPTIONS } from "@/constants/themes";
|
||||
// helpers
|
||||
import { applyTheme, unsetCustomCssVariables } from "@/helpers/theme.helper";
|
||||
// hooks
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export default function ProfileNotificationPage() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={`${t("profile")} - ${t("notifications")}`} />
|
||||
<PageHead title={`${t("profile.label")} - ${t("notifications")}`} />
|
||||
<ProfileSettingContentWrapper>
|
||||
<ProfileSettingContentHeader
|
||||
title={t("email_notifications")}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ const ProfileSettingsPage = observer(() => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={`${t("profile")} - ${t("general_settings")}`} />
|
||||
<PageHead title={`${t("profile.label")} - ${t("general_settings")}`} />
|
||||
<ProfileSettingContentWrapper>
|
||||
<ProfileForm user={currentUser} profile={userProfile.data} />
|
||||
</ProfileSettingContentWrapper>
|
||||
|
|
|
|||
|
|
@ -79,16 +79,16 @@ const SecurityPage = observer(() => {
|
|||
setShowPassword(defaultShowPassword);
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: t("success"),
|
||||
message: t("password_changed_successfully"),
|
||||
title: t("auth.common.password.toast.change_password.success.title"),
|
||||
message: t("auth.common.password.toast.change_password.success.message"),
|
||||
});
|
||||
} catch (err: any) {
|
||||
const errorInfo = authErrorHandler(err.error_code?.toString());
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: errorInfo?.title ?? "Error!",
|
||||
title: errorInfo?.title ?? t("auth.common.password.toast.error.title"),
|
||||
message:
|
||||
typeof errorInfo?.message === "string" ? errorInfo.message : t("something_went_wrong_please_try_again"),
|
||||
typeof errorInfo?.message === "string" ? errorInfo.message : t("auth.common.password.toast.error.message"),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -112,17 +112,17 @@ const SecurityPage = observer(() => {
|
|||
<>
|
||||
<PageHead title="Profile - Security" />
|
||||
<ProfileSettingContentWrapper>
|
||||
<ProfileSettingContentHeader title={t("change_password")} />
|
||||
<ProfileSettingContentHeader title={t("auth.common.password.change_password.label.default")} />
|
||||
<form onSubmit={handleSubmit(handleChangePassword)} className="flex flex-col gap-8 py-6">
|
||||
<div className="flex flex-col gap-10 w-full max-w-96">
|
||||
<div className="space-y-1">
|
||||
<h4 className="text-sm">{t("current_password")}</h4>
|
||||
<h4 className="text-sm">{t("auth.common.password.current_password.label")}</h4>
|
||||
<div className="relative flex items-center rounded-md">
|
||||
<Controller
|
||||
control={control}
|
||||
name="old_password"
|
||||
rules={{
|
||||
required: t("this_field_is_required"),
|
||||
required: t("common.errors.required"),
|
||||
}}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<Input
|
||||
|
|
@ -151,20 +151,20 @@ const SecurityPage = observer(() => {
|
|||
{errors.old_password && <span className="text-xs text-red-500">{errors.old_password.message}</span>}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<h4 className="text-sm">{t("new_password")}</h4>
|
||||
<h4 className="text-sm">{t("auth.common.password.new_password.label")}</h4>
|
||||
<div className="relative flex items-center rounded-md">
|
||||
<Controller
|
||||
control={control}
|
||||
name="new_password"
|
||||
rules={{
|
||||
required: t("this_field_is_required"),
|
||||
required: t("common.errors.required"),
|
||||
}}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<Input
|
||||
id="new_password"
|
||||
type={showPassword?.password ? "text" : "password"}
|
||||
value={value}
|
||||
placeholder={t("new_password")}
|
||||
placeholder={t("auth.common.password.new_password.placeholder")}
|
||||
onChange={onChange}
|
||||
className="w-full"
|
||||
hasError={Boolean(errors.new_password)}
|
||||
|
|
@ -191,19 +191,19 @@ const SecurityPage = observer(() => {
|
|||
)}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<h4 className="text-sm">{t("confirm_password")}</h4>
|
||||
<h4 className="text-sm">{t("auth.common.password.confirm_password.label")}</h4>
|
||||
<div className="relative flex items-center rounded-md">
|
||||
<Controller
|
||||
control={control}
|
||||
name="confirm_password"
|
||||
rules={{
|
||||
required: t("this_field_is_required"),
|
||||
required: t("common.errors.required"),
|
||||
}}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<Input
|
||||
id="confirm_password"
|
||||
type={showPassword?.confirmPassword ? "text" : "password"}
|
||||
placeholder={t("confirm_password")}
|
||||
placeholder={t("auth.common.password.confirm_password.placeholder")}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className="w-full"
|
||||
|
|
@ -226,14 +226,16 @@ const SecurityPage = observer(() => {
|
|||
)}
|
||||
</div>
|
||||
{!!confirmPassword && password !== confirmPassword && renderPasswordMatchError && (
|
||||
<span className="text-sm text-red-500">{t("passwords_dont_match")}</span>
|
||||
<span className="text-sm text-red-500">{t("auth.common.password.errors.match")}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between py-2">
|
||||
<Button variant="primary" type="submit" loading={isSubmitting} disabled={isButtonDisabled}>
|
||||
{isSubmitting ? `${t("changing_password")}...` : t("change_password")}
|
||||
{isSubmitting
|
||||
? `${t("auth.common.password.change_password.label.submitting")}`
|
||||
: t("auth.common.password.change_password.label.default")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -5,15 +5,26 @@ import { observer } from "mobx-react";
|
|||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
// icons
|
||||
import { ChevronLeft, LogOut, MoveLeft, Plus, UserPlus } from "lucide-react";
|
||||
import {
|
||||
ChevronLeft,
|
||||
LogOut,
|
||||
MoveLeft,
|
||||
Plus,
|
||||
UserPlus,
|
||||
Activity,
|
||||
Bell,
|
||||
CircleUser,
|
||||
KeyRound,
|
||||
Settings2,
|
||||
} from "lucide-react";
|
||||
// plane imports
|
||||
import { PROFILE_ACTION_LINKS } from "@plane/constants";
|
||||
import { useOutsideClickDetector } from "@plane/hooks";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { SidebarNavItem } from "@/components/sidebar";
|
||||
// constants
|
||||
import { PROFILE_ACTION_LINKS } from "@/constants/profile";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { getFileURL } from "@/helpers/file.helper";
|
||||
|
|
@ -36,6 +47,19 @@ const WORKSPACE_ACTION_LINKS = [
|
|||
},
|
||||
];
|
||||
|
||||
export const ProjectActionIcons = ({ type, size, className }: { type: string; size?: number; className?: string }) => {
|
||||
const icons = {
|
||||
profile: CircleUser,
|
||||
security: KeyRound,
|
||||
activity: Activity,
|
||||
appearance: Settings2,
|
||||
notifications: Bell,
|
||||
};
|
||||
|
||||
if (type === undefined) return null;
|
||||
const Icon = icons[type as keyof typeof icons];
|
||||
return <Icon size={size} className={className} />;
|
||||
};
|
||||
export const ProfileLayoutSidebar = observer(() => {
|
||||
// states
|
||||
const [isSigningOut, setIsSigningOut] = useState(false);
|
||||
|
|
@ -92,8 +116,8 @@ export const ProfileLayoutSidebar = observer(() => {
|
|||
.catch(() =>
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("error"),
|
||||
message: t("failed_to_sign_out_please_try_again"),
|
||||
title: t("sign_out.toast.error.title"),
|
||||
message: t("sign_out.toast.error.message"),
|
||||
})
|
||||
)
|
||||
.finally(() => setIsSigningOut(false));
|
||||
|
|
@ -145,8 +169,9 @@ export const ProfileLayoutSidebar = observer(() => {
|
|||
isActive={link.highlight(pathname)}
|
||||
>
|
||||
<div className="flex items-center gap-1.5 py-[1px]">
|
||||
<link.Icon className="size-4" />
|
||||
{!sidebarCollapsed && <p className="text-sm leading-5 font-medium">{t(link.key)}</p>}
|
||||
<ProjectActionIcons type={link.key} size={16} />
|
||||
|
||||
{!sidebarCollapsed && <p className="text-sm leading-5 font-medium">{t(link.i18n_label)}</p>}
|
||||
</div>
|
||||
</SidebarNavItem>
|
||||
</Tooltip>
|
||||
|
|
|
|||
|
|
@ -5,10 +5,9 @@ import dynamic from "next/dynamic";
|
|||
import { useTheme, ThemeProvider } from "next-themes";
|
||||
import { SWRConfig } from "swr";
|
||||
// Plane Imports
|
||||
import { WEB_SWR_CONFIG } from "@plane/constants";
|
||||
import { TranslationProvider } from "@plane/i18n";
|
||||
import { Toast } from "@plane/ui";
|
||||
// constants
|
||||
import { SWR_CONFIG } from "@/constants/swr-config";
|
||||
//helpers
|
||||
import { resolveGeneralTheme } from "@/helpers/theme.helper";
|
||||
// nprogress
|
||||
|
|
@ -47,7 +46,7 @@ export const AppProvider: FC<IAppProvider> = (props) => {
|
|||
<InstanceWrapper>
|
||||
<IntercomProvider>
|
||||
<PostHogProvider>
|
||||
<SWRConfig value={SWR_CONFIG}>{children}</SWRConfig>
|
||||
<SWRConfig value={WEB_SWR_CONFIG}>{children}</SWRConfig>
|
||||
</PostHogProvider>
|
||||
</IntercomProvider>
|
||||
</InstanceWrapper>
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ import Link from "next/link";
|
|||
// ui
|
||||
import { useTheme } from "next-themes";
|
||||
// components
|
||||
import { NAVIGATE_TO_SIGNIN } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { AuthRoot } from "@/components/account";
|
||||
// constants
|
||||
import { NAVIGATE_TO_SIGNIN } from "@/constants/event-tracker";
|
||||
// helpers
|
||||
import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper";
|
||||
// hooks
|
||||
|
|
@ -23,6 +24,8 @@ import WhiteHorizontalLogo from "@/public/plane-logos/white-horizontal-with-blue
|
|||
export type AuthType = "sign-in" | "sign-up";
|
||||
|
||||
const SignInPage = observer(() => {
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
// hooks
|
||||
|
|
@ -48,13 +51,13 @@ const SignInPage = observer(() => {
|
|||
</Link>
|
||||
</div>
|
||||
<div className="flex flex-col items-end sm:items-center sm:gap-2 sm:flex-row text-center text-sm font-medium text-onboarding-text-300">
|
||||
Already have an account?{" "}
|
||||
{t("auth.common.already_have_an_account")}
|
||||
<Link
|
||||
href="/"
|
||||
onClick={() => captureEvent(NAVIGATE_TO_SIGNIN, {})}
|
||||
className="font-semibold text-custom-primary-100 hover:underline"
|
||||
>
|
||||
Log in
|
||||
{t("auth.common.login")}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ const WorkspaceInvitationPage = observer(() => {
|
|||
) : (
|
||||
<EmptySpace
|
||||
title={`You have been invited to ${invitationDetail.workspace.name}`}
|
||||
description="Your workspace is where you'll create projects, collaborate on your issues, and organize different streams of work in your Plane account."
|
||||
description="Your workspace is where you'll create projects, collaborate on your work items, and organize different streams of work in your Plane account."
|
||||
>
|
||||
<EmptySpaceItem Icon={Check} title="Accept" action={handleAccept} />
|
||||
<EmptySpaceItem Icon={X} title="Ignore" action={handleReject} />
|
||||
|
|
@ -92,14 +92,14 @@ const WorkspaceInvitationPage = observer(() => {
|
|||
invitationDetail?.accepted ? (
|
||||
<EmptySpace
|
||||
title={`You are already a member of ${invitationDetail.workspace.name}`}
|
||||
description="Your workspace is where you'll create projects, collaborate on your issues, and organize different streams of work in your Plane account."
|
||||
description="Your workspace is where you'll create projects, collaborate on your work items, and organize different streams of work in your Plane account."
|
||||
>
|
||||
<EmptySpaceItem Icon={Boxes} title="Continue to home" href="/" />
|
||||
</EmptySpace>
|
||||
) : (
|
||||
<EmptySpace
|
||||
title="This invitation link is not active anymore."
|
||||
description="Your workspace is where you'll create projects, collaborate on your issues, and organize different streams of work in your Plane account."
|
||||
description="Your workspace is where you'll create projects, collaborate on your work items, and organize different streams of work in your Plane account."
|
||||
link={{ text: "Or start from an empty project", href: "/" }}
|
||||
>
|
||||
{!currentUser ? (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue