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
|
|
@ -1,7 +1,8 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { X } from "lucide-react";
|
||||
// constants
|
||||
import { NETWORK_CHOICES } from "@/constants/project";
|
||||
import { NETWORK_CHOICES } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
|
||||
type Props = {
|
||||
handleRemove: (val: string) => void;
|
||||
|
|
@ -11,6 +12,7 @@ type Props = {
|
|||
|
||||
export const AppliedAccessFilters: React.FC<Props> = observer((props) => {
|
||||
const { handleRemove, values, editable } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -18,7 +20,7 @@ export const AppliedAccessFilters: React.FC<Props> = observer((props) => {
|
|||
const accessDetails = NETWORK_CHOICES.find((s) => `${s.key}` === status);
|
||||
return (
|
||||
<div key={status} className="flex items-center gap-1 rounded px-1.5 py-1 text-xs bg-custom-background-80">
|
||||
{accessDetails?.label}
|
||||
{accessDetails && t(accessDetails?.i18n_label)}
|
||||
{editable && (
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { X } from "lucide-react";
|
||||
// helpers
|
||||
import { PROJECT_CREATED_AT_FILTER_OPTIONS } from "@/constants/filters";
|
||||
import { PROJECT_CREATED_AT_FILTER_OPTIONS } from "@plane/constants";
|
||||
import { renderFormattedDate } from "@/helpers/date-time.helper";
|
||||
import { capitalizeFirstLetter } from "@/helpers/string.helper";
|
||||
// constants
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ import { observer } from "mobx-react";
|
|||
// icons
|
||||
import { X } from "lucide-react";
|
||||
// types
|
||||
import { PROJECT_DISPLAY_FILTER_OPTIONS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TProjectAppliedDisplayFilterKeys } from "@plane/types";
|
||||
// constants
|
||||
import { PROJECT_DISPLAY_FILTER_OPTIONS } from "@/constants/project";
|
||||
|
||||
type Props = {
|
||||
handleRemove: (key: TProjectAppliedDisplayFilterKeys) => void;
|
||||
|
|
@ -14,14 +15,15 @@ type Props = {
|
|||
|
||||
export const AppliedProjectDisplayFilters: React.FC<Props> = observer((props) => {
|
||||
const { handleRemove, values, editable } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
{values.map((key) => {
|
||||
const filterLabel = PROJECT_DISPLAY_FILTER_OPTIONS.find((s) => s.key === key)?.label;
|
||||
const filterLabel = PROJECT_DISPLAY_FILTER_OPTIONS.find((s) => s.key === key)?.i18n_label;
|
||||
return (
|
||||
<div key={key} className="flex items-center gap-1 rounded px-1.5 py-1 text-xs bg-custom-background-80">
|
||||
{filterLabel}
|
||||
{filterLabel && t(filterLabel)}
|
||||
{editable && (
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { X } from "lucide-react";
|
||||
// i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { TProjectAppliedDisplayFilterKeys, TProjectFilters } from "@plane/types";
|
||||
// ui
|
||||
|
|
@ -30,6 +32,7 @@ const MEMBERS_FILTERS = ["lead", "members"];
|
|||
const DATE_FILTERS = ["created_at"];
|
||||
|
||||
export const ProjectAppliedFiltersList: React.FC<Props> = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
appliedFilters,
|
||||
appliedDisplayFilters,
|
||||
|
|
@ -95,7 +98,7 @@ export const ProjectAppliedFiltersList: React.FC<Props> = (props) => {
|
|||
{/* Applied display filters */}
|
||||
{appliedDisplayFilters.length > 0 && (
|
||||
<Tag key="project_display_filters">
|
||||
<span className="text-xs text-custom-text-300">Projects</span>
|
||||
<span className="text-xs text-custom-text-300">{t("projects.label", { count: 2 })}</span>
|
||||
<AppliedProjectDisplayFilters
|
||||
editable={isEditingAllowed}
|
||||
values={appliedDisplayFilters}
|
||||
|
|
@ -106,7 +109,7 @@ export const ProjectAppliedFiltersList: React.FC<Props> = (props) => {
|
|||
{isEditingAllowed && (
|
||||
<button type="button" onClick={handleClearAllFilters}>
|
||||
<Tag>
|
||||
Clear all
|
||||
{t("common.clear_all")}
|
||||
<X size={12} strokeWidth={2} />
|
||||
</Tag>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
// components
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel, EUserPermissions } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ContentWrapper } from "@plane/ui";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
// components
|
||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||
import { ProjectCard } from "@/components/project";
|
||||
import { ProjectsLoader } from "@/components/ui";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useProject, useProjectFilter } from "@/hooks/store";
|
||||
import { useCommandPalette, useEventTracker, useProject, useProjectFilter, useUserPermissions } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
// assets
|
||||
import AllFiltersImage from "@/public/empty-state/project/all-filters.svg";
|
||||
import NameFilterImage from "@/public/empty-state/project/name-filter.svg";
|
||||
|
|
@ -20,6 +22,8 @@ type TProjectCardListProps = {
|
|||
|
||||
export const ProjectCardList = observer((props: TProjectCardListProps) => {
|
||||
const { totalProjectIds: totalProjectIdsProps, filteredProjectIds: filteredProjectIdsProps } = props;
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
|
|
@ -31,21 +35,42 @@ export const ProjectCardList = observer((props: TProjectCardListProps) => {
|
|||
getProjectById,
|
||||
} = useProject();
|
||||
const { searchQuery, currentWorkspaceDisplayFilters } = useProjectFilter();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
// helper hooks
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/projects" });
|
||||
|
||||
// derived values
|
||||
const workspaceProjectIds = totalProjectIdsProps ?? storeWorkspaceProjectIds;
|
||||
const filteredProjectIds = filteredProjectIdsProps ?? storeFilteredProjectIds;
|
||||
|
||||
// permissions
|
||||
const canPerformEmptyStateActions = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
|
||||
if (!filteredProjectIds || !workspaceProjectIds || loader === "init-loader" || fetchStatus !== "complete")
|
||||
return <ProjectsLoader />;
|
||||
|
||||
if (workspaceProjectIds?.length === 0 && !currentWorkspaceDisplayFilters?.archived_projects)
|
||||
return (
|
||||
<EmptyState
|
||||
type={EmptyStateType.WORKSPACE_PROJECTS}
|
||||
primaryButtonOnClick={() => {
|
||||
setTrackElement("Project empty state");
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
<DetailedEmptyState
|
||||
title={t("workspace_projects.empty_state.general.title")}
|
||||
description={t("workspace_projects.empty_state.general.description")}
|
||||
assetPath={resolvedPath}
|
||||
customPrimaryButton={
|
||||
<ComicBoxButton
|
||||
label={t("workspace_projects.empty_state.general.primary_button.text")}
|
||||
title={t("workspace_projects.empty_state.general.primary_button.comic.title")}
|
||||
description={t("workspace_projects.empty_state.general.primary_button.comic.description")}
|
||||
onClick={() => {
|
||||
setTrackElement("Project empty state");
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
disabled={!canPerformEmptyStateActions}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
@ -58,11 +83,11 @@ export const ProjectCardList = observer((props: TProjectCardListProps) => {
|
|||
className="mx-auto h-36 w-36 sm:h-48 sm:w-48"
|
||||
alt="No matching projects"
|
||||
/>
|
||||
<h5 className="mb-1 mt-7 text-xl font-medium">No matching projects</h5>
|
||||
<h5 className="mb-1 mt-7 text-xl font-medium">{t("workspace_projects.empty_state.filter.title")}</h5>
|
||||
<p className="whitespace-pre-line text-base text-custom-text-400">
|
||||
{searchQuery.trim() === ""
|
||||
? "Remove the filters to see all projects"
|
||||
: "No projects detected with the matching criteria.\nCreate a new project instead"}
|
||||
? t("workspace_projects.empty_state.filter.description")
|
||||
: t("workspace_projects.empty_state.search.description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import Link from "next/link";
|
|||
import { useParams } from "next/navigation";
|
||||
import { ArchiveRestoreIcon, Check, ExternalLink, LinkIcon, Lock, Settings, Trash2, UserPlus } from "lucide-react";
|
||||
// types
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import type { IProject } from "@plane/types";
|
||||
// ui
|
||||
import {
|
||||
|
|
@ -33,7 +34,6 @@ import { useMember, useProject, useUserPermissions } from "@/hooks/store";
|
|||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane-web constants
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
type Props = {
|
||||
project: IProject;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { ChangeEvent } from "react";
|
||||
import { Controller, useFormContext, UseFormSetValue } from "react-hook-form";
|
||||
import { Info } from "lucide-react";
|
||||
// plane imports
|
||||
import { ETabIndices } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { Input, TextArea, Tooltip } from "@plane/ui";
|
||||
// plane utils
|
||||
import { cn } from "@plane/utils";
|
||||
// constants
|
||||
import { ETabIndices } from "@/constants/tab-indices";
|
||||
// helpers
|
||||
import { projectIdentifierSanitizer } from "@/helpers/project.helper";
|
||||
import { getTabIndex } from "@/helpers/tab-indices.helper";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { useState } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { X } from "lucide-react";
|
||||
// plane imports
|
||||
import { ETabIndices } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// plane types
|
||||
import { IProject } from "@plane/types";
|
||||
|
|
@ -8,8 +10,6 @@ import { IProject } from "@plane/types";
|
|||
import { CustomEmojiIconPicker, EmojiIconPickerTypes, Logo } from "@plane/ui";
|
||||
// components
|
||||
import { ImagePickerPopover } from "@/components/core";
|
||||
// constants
|
||||
import { ETabIndices } from "@/constants/tab-indices";
|
||||
// helpers
|
||||
import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper";
|
||||
import { getFileURL } from "@/helpers/file.helper";
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { useFormContext } from "react-hook-form";
|
||||
// plane imports
|
||||
import { ETabIndices } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IProject } from "@plane/types";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// constants
|
||||
import { ETabIndices } from "@/constants/tab-indices";
|
||||
// helpers
|
||||
import { getTabIndex } from "@/helpers/tab-indices.helper";
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ import { Controller, useForm } from "react-hook-form";
|
|||
import { AlertTriangle } from "lucide-react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// types
|
||||
import { PROJECT_DELETED } from "@plane/constants";
|
||||
import type { IProject } from "@plane/types";
|
||||
// ui
|
||||
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
import { PROJECT_DELETED } from "@/constants/event-tracker";
|
||||
// hooks
|
||||
import { useEventTracker, useProject } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { NETWORK_CHOICES } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// components
|
||||
import { FilterHeader, FilterOption } from "@/components/issues";
|
||||
// constants
|
||||
import { NETWORK_CHOICES } from "@/constants/project";
|
||||
import { ProjectNetworkIcon } from "@/components/project";
|
||||
|
||||
type Props = {
|
||||
appliedFilters: string[] | null;
|
||||
|
|
@ -15,9 +17,10 @@ export const FilterAccess: React.FC<Props> = observer((props) => {
|
|||
const { appliedFilters, handleUpdate, searchQuery } = props;
|
||||
// states
|
||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const appliedFiltersCount = appliedFilters?.length ?? 0;
|
||||
const filteredOptions = NETWORK_CHOICES.filter((a) => a.label.includes(searchQuery.toLowerCase()));
|
||||
const filteredOptions = NETWORK_CHOICES.filter((a) => a.i18n_label.includes(searchQuery.toLowerCase()));
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -34,8 +37,8 @@ export const FilterAccess: React.FC<Props> = observer((props) => {
|
|||
key={access.key}
|
||||
isChecked={appliedFilters?.includes(`${access.key}`) ? true : false}
|
||||
onClick={() => handleUpdate(`${access.key}`)}
|
||||
icon={<access.icon className="h-3 w-3" />}
|
||||
title={access.label}
|
||||
icon={<ProjectNetworkIcon iconKey={access.iconKey} />}
|
||||
title={t(access.i18n_label)}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// plane constants
|
||||
import { PROJECT_CREATED_AT_FILTER_OPTIONS } from "@plane/constants";
|
||||
// components
|
||||
import { DateFilterModal } from "@/components/core";
|
||||
import { FilterHeader, FilterOption } from "@/components/issues";
|
||||
// constants
|
||||
import { PROJECT_CREATED_AT_FILTER_OPTIONS } from "@/constants/filters";
|
||||
|
||||
// helpers
|
||||
import { isInDateFormat } from "@/helpers/date-time.helper";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
"use client";
|
||||
|
||||
import { ArrowDownWideNarrow, Check, ChevronDown } from "lucide-react";
|
||||
import { PROJECT_ORDER_BY_OPTIONS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TProjectOrderByOptions } from "@plane/types";
|
||||
// ui
|
||||
import { CustomMenu, getButtonStyling } from "@plane/ui";
|
||||
// helpers
|
||||
import { PROJECT_ORDER_BY_OPTIONS } from "@/constants/project";
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// types
|
||||
// constants
|
||||
|
|
@ -20,6 +21,7 @@ const DISABLED_ORDERING_OPTIONS = ["sort_order"];
|
|||
|
||||
export const ProjectOrderByDropdown: React.FC<Props> = (props) => {
|
||||
const { onChange, value, isMobile = false } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const orderByDetails = PROJECT_ORDER_BY_OPTIONS.find((option) => value?.includes(option.key));
|
||||
|
||||
|
|
@ -34,13 +36,13 @@ export const ProjectOrderByDropdown: React.FC<Props> = (props) => {
|
|||
{isMobile ? (
|
||||
<div className="flex text-sm items-center gap-2 neutral-primary text-custom-text-200">
|
||||
<ArrowDownWideNarrow className="h-3 w-3" />
|
||||
{orderByDetails?.label}
|
||||
{orderByDetails && t(orderByDetails?.i18n_label)}
|
||||
<ChevronDown className="h-3 w-3" strokeWidth={2} />
|
||||
</div>
|
||||
) : (
|
||||
<div className={cn(getButtonStyling("neutral-primary", "sm"), "px-2 text-custom-text-200")}>
|
||||
<ArrowDownWideNarrow className="h-3 w-3" />
|
||||
{orderByDetails?.label}
|
||||
{orderByDetails && t(orderByDetails?.i18n_label)}
|
||||
<ChevronDown className="h-3 w-3" strokeWidth={2} />
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -59,7 +61,7 @@ export const ProjectOrderByDropdown: React.FC<Props> = (props) => {
|
|||
else onChange(option.key);
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
{option && t(option?.i18n_label)}
|
||||
{value?.includes(option.key) && <Check className="h-3 w-3" />}
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import { useCallback } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { ListFilter } from "lucide-react";
|
||||
// i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// plane types
|
||||
import { TProjectFilters } from "@plane/types";
|
||||
// plane utils
|
||||
|
|
@ -22,6 +24,8 @@ type Props = {
|
|||
};
|
||||
|
||||
const HeaderFilters = observer(({ filterMenuButton, isMobile, classname = "", filterClassname = "" }: Props) => {
|
||||
// i18n
|
||||
const { t } = useTranslation();
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
const {
|
||||
|
|
@ -72,7 +76,7 @@ const HeaderFilters = observer(({ filterMenuButton, isMobile, classname = "", fi
|
|||
<div className={cn(filterClassname)}>
|
||||
<FiltersDropdown
|
||||
icon={<ListFilter className="h-3 w-3" />}
|
||||
title="Filters"
|
||||
title={t("common.filters")}
|
||||
placement="bottom-end"
|
||||
isFiltersApplied={isFiltersApplied}
|
||||
menuButton={filterMenuButton || null}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { FC, useEffect, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Info, Lock } from "lucide-react";
|
||||
import { NETWORK_CHOICES, PROJECT_UPDATED } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// plane types
|
||||
import { IProject, IWorkspace } from "@plane/types";
|
||||
|
|
@ -22,9 +23,7 @@ import {
|
|||
import { Logo } from "@/components/common";
|
||||
import { ImagePickerPopover } from "@/components/core";
|
||||
import { TimezoneSelect } from "@/components/global";
|
||||
// constants
|
||||
import { PROJECT_UPDATED } from "@/constants/event-tracker";
|
||||
import { NETWORK_CHOICES } from "@/constants/project";
|
||||
import { ProjectNetworkIcon } from "@/components/project";
|
||||
// helpers
|
||||
import { renderFormattedDate } from "@/helpers/date-time.helper";
|
||||
import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper";
|
||||
|
|
@ -52,6 +51,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
const { captureProjectEvent } = useEventTracker();
|
||||
const { updateProject } = useProject();
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
// form info
|
||||
const {
|
||||
handleSubmit,
|
||||
|
|
@ -107,8 +107,8 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: "Project updated successfully",
|
||||
title: t("toast.success"),
|
||||
message: t("project_settings.general.toast.success"),
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
@ -118,8 +118,8 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: error?.error ?? "Project could not be updated. Please try again.",
|
||||
title: t("toast.error"),
|
||||
message: error?.error ?? t("project_settings.general.toast.error"),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -146,7 +146,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
await projectService
|
||||
.checkProjectIdentifierAvailability(workspaceSlug as string, payload.identifier ?? "")
|
||||
.then(async (res) => {
|
||||
if (res.exists) setError("identifier", { message: "Identifier already exists" });
|
||||
if (res.exists) setError("identifier", { message: t("common.identifier_already_exists") });
|
||||
else await handleUpdateChange(payload);
|
||||
});
|
||||
else await handleUpdateChange(payload);
|
||||
|
|
@ -207,7 +207,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
<span>{watch("identifier")} .</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
{project.network === 0 && <Lock className="h-2.5 w-2.5 text-white " />}
|
||||
{currentNetwork?.label}
|
||||
{currentNetwork && t(currentNetwork?.i18n_label)}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -219,7 +219,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
name="cover_image_url"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<ImagePickerPopover
|
||||
label="Change cover"
|
||||
label={t("change_cover")}
|
||||
control={control}
|
||||
onChange={onChange}
|
||||
value={value ?? null}
|
||||
|
|
@ -234,12 +234,12 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
</div>
|
||||
<div className="my-8 flex flex-col gap-8">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">Project name</h4>
|
||||
<h4 className="text-sm">{t("common.project_name")}</h4>
|
||||
<Controller
|
||||
control={control}
|
||||
name="name"
|
||||
rules={{
|
||||
required: "Name is required",
|
||||
required: t("name_is_required"),
|
||||
maxLength: {
|
||||
value: 255,
|
||||
message: "Project name should be less than 255 characters",
|
||||
|
|
@ -255,7 +255,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
onChange={onChange}
|
||||
hasError={Boolean(errors.name)}
|
||||
className="rounded-md !p-3 font-medium"
|
||||
placeholder="Project name"
|
||||
placeholder={t("common.project_name")}
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -263,7 +263,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
<span className="text-xs text-red-500">{errors?.name?.message}</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">Summary</h4>
|
||||
<h4 className="text-sm">{t("description")}</h4>
|
||||
<Controller
|
||||
name="description"
|
||||
control={control}
|
||||
|
|
@ -272,7 +272,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
id="description"
|
||||
name="description"
|
||||
value={value}
|
||||
placeholder="Enter project summary"
|
||||
placeholder={t("project_description_placeholder")}
|
||||
onChange={onChange}
|
||||
className="min-h-[102px] text-sm font-medium"
|
||||
hasError={Boolean(errors?.description)}
|
||||
|
|
@ -289,17 +289,15 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
control={control}
|
||||
name="identifier"
|
||||
rules={{
|
||||
required: "Project ID is required",
|
||||
validate: (value) =>
|
||||
/^[ÇŞĞIİÖÜA-Z0-9]+$/.test(value.toUpperCase()) ||
|
||||
"Only Alphanumeric & Non-latin characters are allowed.",
|
||||
required: t("project_id_is_required"),
|
||||
validate: (value) => /^[ÇŞĞIİÖÜA-Z0-9]+$/.test(value.toUpperCase()) || t("project_id_allowed_char"),
|
||||
minLength: {
|
||||
value: 1,
|
||||
message: "Project ID must at least be of 1 character",
|
||||
message: t("project_id_min_char"),
|
||||
},
|
||||
maxLength: {
|
||||
value: 5,
|
||||
message: "Project ID must at most be of 5 characters",
|
||||
message: t("project_id_max_char"),
|
||||
},
|
||||
}}
|
||||
render={({ field: { value, ref } }) => (
|
||||
|
|
@ -311,7 +309,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
onChange={handleIdentifierChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.identifier)}
|
||||
placeholder="Enter project ID"
|
||||
placeholder={t("project_settings.general.enter_project_id")}
|
||||
className="w-full font-medium"
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
|
|
@ -319,7 +317,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
/>
|
||||
<Tooltip
|
||||
isMobile={isMobile}
|
||||
tooltipContent="Helps you identify issues in the project uniquely. Max 5 characters."
|
||||
tooltipContent="Helps you identify work items in the project uniquely. Max 5 characters."
|
||||
className="text-sm"
|
||||
position="right-top"
|
||||
>
|
||||
|
|
@ -331,7 +329,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">Network</h4>
|
||||
<h4 className="text-sm">{t("workspace_projects.network.label")}</h4>
|
||||
<Controller
|
||||
name="network"
|
||||
control={control}
|
||||
|
|
@ -345,11 +343,11 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
<div className="flex items-center gap-1">
|
||||
{selectedNetwork ? (
|
||||
<>
|
||||
<selectedNetwork.icon className="h-3.5 w-3.5" />
|
||||
{selectedNetwork.label}
|
||||
<ProjectNetworkIcon iconKey={selectedNetwork.iconKey} className="h-3.5 w-3.5" />
|
||||
{t(selectedNetwork.i18n_label)}
|
||||
</>
|
||||
) : (
|
||||
<span className="text-custom-text-400">Select network</span>
|
||||
<span className="text-custom-text-400">{t("select_network")}</span>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
|
|
@ -361,9 +359,9 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
{NETWORK_CHOICES.map((network) => (
|
||||
<CustomSelect.Option key={network.key} value={network.key}>
|
||||
<div className="flex items-start gap-2">
|
||||
<network.icon className="h-3.5 w-3.5" />
|
||||
<ProjectNetworkIcon iconKey={network.iconKey} className="h-3.5 w-3.5" />
|
||||
<div className="-mt-1">
|
||||
<p>{t(network.label)}</p>
|
||||
<p>{t(network.i18n_label)}</p>
|
||||
<p className="text-xs text-custom-text-400">{t(network.description)}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -375,11 +373,11 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 col-span-1 sm:col-span-2 xl:col-span-1">
|
||||
<h4 className="text-sm">Project Timezone</h4>
|
||||
<h4 className="text-sm">{t("common.project_timezone")}</h4>
|
||||
<Controller
|
||||
name="timezone"
|
||||
control={control}
|
||||
rules={{ required: "Please select a timezone" }}
|
||||
rules={{ required: t("project_settings.general.please_select_a_timezone") }}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<>
|
||||
<TimezoneSelect
|
||||
|
|
@ -399,10 +397,10 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
<div className="flex items-center justify-between py-2">
|
||||
<>
|
||||
<Button variant="primary" type="submit" loading={isLoading} disabled={!isAdmin}>
|
||||
{isLoading ? "Updating..." : "Update project"}
|
||||
{isLoading ? `${t("updating")}...` : t("common.update_project")}
|
||||
</Button>
|
||||
<span className="text-sm italic text-custom-sidebar-text-400">
|
||||
Created on {renderFormattedDate(project?.created_at)}
|
||||
{t("common.created_on")} {renderFormattedDate(project?.created_at)}
|
||||
</span>
|
||||
</>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Briefcase } from "lucide-react";
|
||||
// i18n
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { Breadcrumbs, Button, Header } from "@plane/ui";
|
||||
// components
|
||||
|
|
@ -10,12 +13,13 @@ import { BreadcrumbLink } from "@/components/common";
|
|||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useUserPermissions } from "@/hooks/store";
|
||||
// plane web constants
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
// components
|
||||
import HeaderFilters from "./filters";
|
||||
import { ProjectSearch } from "./search-projects";
|
||||
|
||||
export const ProjectsBaseHeader = observer(() => {
|
||||
// i18n
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
|
|
@ -35,7 +39,12 @@ export const ProjectsBaseHeader = observer(() => {
|
|||
<Breadcrumbs>
|
||||
<Breadcrumbs.BreadcrumbItem
|
||||
type="text"
|
||||
link={<BreadcrumbLink label="Projects" icon={<Briefcase className="h-4 w-4 text-custom-text-300" />} />}
|
||||
link={
|
||||
<BreadcrumbLink
|
||||
label={t("workspace_projects.label", { count: 2 })}
|
||||
icon={<Briefcase className="h-4 w-4 text-custom-text-300" />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{isArchived && <Breadcrumbs.BreadcrumbItem type="text" link={<BreadcrumbLink label="Archived" />} />}
|
||||
</Breadcrumbs>
|
||||
|
|
@ -54,7 +63,8 @@ export const ProjectsBaseHeader = observer(() => {
|
|||
}}
|
||||
className="items-center gap-1"
|
||||
>
|
||||
<span className="hidden sm:inline-block">Add</span> Project
|
||||
<span className="hidden sm:inline-block">{t("workspace_projects.create.label")}</span>
|
||||
<span className="inline-block sm:hidden">{t("workspace_projects.label", { count: 1 })}</span>
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
|
|
|
|||
|
|
@ -20,4 +20,5 @@ export * from "./send-project-invitation-modal";
|
|||
export * from "./confirm-project-member-remove";
|
||||
export * from "./multi-select-modal";
|
||||
export * from "./search-projects";
|
||||
export * from "./project-network-icon";
|
||||
export * from "@/plane-web/components/projects/create/root";
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ import { Controller, useForm } from "react-hook-form";
|
|||
import { AlertTriangleIcon } from "lucide-react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// types
|
||||
import { PROJECT_MEMBER_LEAVE } from "@plane/constants";
|
||||
import { IProject } from "@plane/types";
|
||||
// ui
|
||||
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
import { PROJECT_MEMBER_LEAVE } from "@/constants/event-tracker";
|
||||
// hooks
|
||||
import { useEventTracker, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
|
|
@ -144,7 +144,7 @@ export const LeaveProjectModal: FC<ILeaveProjectModal> = observer((props) => {
|
|||
<p className="text-sm leading-7 text-custom-text-200">
|
||||
Are you sure you want to leave the project -
|
||||
<span className="font-medium text-custom-text-100">{` "${project?.name}" `}</span>? All of the
|
||||
issues associated with you will become inaccessible.
|
||||
work items associated with you will become inaccessible.
|
||||
</p>
|
||||
</span>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
import { PROJECT_MEMBER_LEAVE } from "@plane/constants";
|
||||
import { TOAST_TYPE, Table, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { ConfirmProjectMemberRemove } from "@/components/project";
|
||||
// constants
|
||||
import { PROJECT_MEMBER_LEAVE } from "@/constants/event-tracker";
|
||||
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useProject, useUser, useUserPermissions } from "@/hooks/store";
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ import { useParams } from "next/navigation";
|
|||
import { Search } from "lucide-react";
|
||||
// hooks
|
||||
// components
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/ui";
|
||||
import { ProjectMemberListItem, SendProjectInvitationModal } from "@/components/project";
|
||||
// ui
|
||||
import { MembersSettingsLoader } from "@/components/ui";
|
||||
import { useEventTracker, useMember, useUserPermissions } from "@/hooks/store";
|
||||
// plane-web constants
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
export const ProjectMemberList: React.FC = observer(() => {
|
||||
// router
|
||||
|
|
@ -26,6 +26,9 @@ export const ProjectMemberList: React.FC = observer(() => {
|
|||
project: { projectMemberIds, getProjectMemberDetails },
|
||||
} = useMember();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const searchedMembers = (projectMemberIds ?? []).filter((userId) => {
|
||||
const memberDetails = projectId ? getProjectMemberDetails(userId, projectId.toString()) : null;
|
||||
|
||||
|
|
@ -47,7 +50,7 @@ export const ProjectMemberList: React.FC = observer(() => {
|
|||
<SendProjectInvitationModal isOpen={inviteModal} onClose={() => setInviteModal(false)} />
|
||||
|
||||
<div className="flex items-center justify-between gap-4 border-b border-custom-border-100 py-3.5 overflow-x-hidden">
|
||||
<h4 className="text-xl font-medium">Members</h4>
|
||||
<h4 className="text-xl font-medium">{t("members")}</h4>
|
||||
<div className="ml-auto flex items-center justify-start gap-1 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" />
|
||||
<input
|
||||
|
|
@ -66,7 +69,7 @@ export const ProjectMemberList: React.FC = observer(() => {
|
|||
setInviteModal(true);
|
||||
}}
|
||||
>
|
||||
Add member
|
||||
{t("add_member")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -77,7 +80,7 @@ export const ProjectMemberList: React.FC = observer(() => {
|
|||
{searchedMembers.length !== 0 && <ProjectMemberListItem memberDetails={memberDetails ?? []} />}
|
||||
|
||||
{searchedMembers.length === 0 && (
|
||||
<h4 className="text-sm mt-16 text-center text-custom-text-400">No matching members</h4>
|
||||
<h4 className="text-sm mt-16 text-center text-custom-text-400">{t("no_matching_members")}</h4>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ import { observer } from "mobx-react";
|
|||
import { useParams } from "next/navigation";
|
||||
import { Ban } from "lucide-react";
|
||||
// plane ui
|
||||
import { EUserPermissions } from "@plane/constants";
|
||||
import { Avatar, CustomSearchSelect } from "@plane/ui";
|
||||
// helpers
|
||||
import { getFileURL } from "@/helpers/file.helper";
|
||||
// hooks
|
||||
import { useMember } from "@/hooks/store";
|
||||
import { EUserPermissions } from "@/plane-web/constants";
|
||||
|
||||
type Props = {
|
||||
value: any;
|
||||
|
|
|
|||
|
|
@ -4,16 +4,16 @@ import { observer } from "mobx-react";
|
|||
import { Search, X } from "lucide-react";
|
||||
import { Combobox } from "@headlessui/react";
|
||||
// plane ui
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button, Checkbox, EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
|
||||
// components
|
||||
import { Logo } from "@/components/common";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
import { SimpleEmptyState } from "@/components/empty-state";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
|
|
@ -31,6 +31,8 @@ export const ProjectMultiSelectModal: React.FC<Props> = observer((props) => {
|
|||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
// refs
|
||||
const moveButtonRef = useRef<HTMLButtonElement>(null);
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { getProjectById } = useProject();
|
||||
// derived values
|
||||
|
|
@ -44,6 +46,9 @@ export const ProjectMultiSelectModal: React.FC<Props> = observer((props) => {
|
|||
const projectQuery = `${project?.identifier} ${project?.name}`.toLowerCase();
|
||||
return projectQuery.includes(searchTerm.toLowerCase());
|
||||
});
|
||||
const filteredProjectResolvedPath = useResolvedAssetPath({
|
||||
basePath: "/empty-state/search/project",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) setSelectedProjectIds(selectedProjectIdsProp);
|
||||
|
|
@ -114,7 +119,11 @@ export const ProjectMultiSelectModal: React.FC<Props> = observer((props) => {
|
|||
>
|
||||
{filteredProjectIds.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center px-3 py-8 text-center">
|
||||
<EmptyState type={EmptyStateType.PROJECTS_EMPTY_SEARCH} layout="screen-simple" />
|
||||
<SimpleEmptyState
|
||||
title={t("workspace_projects.empty_state.filter.title")}
|
||||
description={t("workspace_projects.empty_state.filter.description")}
|
||||
assetPath={filteredProjectResolvedPath}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<ul
|
||||
|
|
|
|||
|
|
@ -35,8 +35,9 @@ export const ProjectFeatureUpdate: FC<Props> = observer((props) => {
|
|||
</Row>
|
||||
<div className="flex items-center justify-between gap-2 mt-4 px-6 py-4 border-t border-custom-border-100">
|
||||
<div className="flex gap-1 text-sm text-custom-text-300 font-medium">
|
||||
{t("congrats")}! {t("project")} <Logo logo={currentProjectDetails.logo_props} />{" "}
|
||||
<p className="break-all">{currentProjectDetails.name}</p> {t("created").toLowerCase()}.
|
||||
{t("congrats")}! {t("workspace_projects.label", { count: 1 })}{" "}
|
||||
<Logo logo={currentProjectDetails.logo_props} /> <p className="break-all">{currentProjectDetails.name}</p>{" "}
|
||||
{t("created").toLowerCase()}.
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="neutral-primary" size="sm" onClick={onClose} tabIndex={1}>
|
||||
|
|
|
|||
30
web/core/components/project/project-network-icon.tsx
Normal file
30
web/core/components/project/project-network-icon.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { Lock, Globe2 } from "lucide-react";
|
||||
// plane imports
|
||||
import { TNetworkChoiceIconKey } from "@plane/constants";
|
||||
import { cn } from "@plane/utils";
|
||||
|
||||
type Props = {
|
||||
iconKey: TNetworkChoiceIconKey;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const ProjectNetworkIcon = (props: Props) => {
|
||||
const { iconKey, className } = props;
|
||||
// Get the icon key
|
||||
const getProjectNetworkIcon = () => {
|
||||
switch (iconKey) {
|
||||
case "Lock":
|
||||
return Lock;
|
||||
case "Globe2":
|
||||
return Globe2;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Get the icon
|
||||
const Icon = getProjectNetworkIcon();
|
||||
if (!Icon) return null;
|
||||
|
||||
return <Icon className={cn("h-3 w-3", className)} />;
|
||||
};
|
||||
|
|
@ -5,6 +5,8 @@ import { observer } from "mobx-react";
|
|||
import { useParams } from "next/navigation";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import useSWR from "swr";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IProject, IUserLite, IWorkspace } from "@plane/types";
|
||||
// ui
|
||||
import { Loader, TOAST_TYPE, ToggleSwitch, setToast } from "@plane/ui";
|
||||
|
|
@ -14,7 +16,6 @@ import { MemberSelect } from "@/components/project";
|
|||
import { PROJECT_MEMBERS } from "@/constants/fetch-keys";
|
||||
// hooks
|
||||
import { useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
// types
|
||||
|
||||
|
|
@ -29,6 +30,8 @@ export const ProjectSettingsMemberDefaults: React.FC = observer(() => {
|
|||
// store hooks
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { currentProjectDetails, fetchProjectDetails, updateProject } = useProject();
|
||||
// derived values
|
||||
const isAdmin = allowPermissions(
|
||||
|
|
@ -78,9 +81,9 @@ export const ProjectSettingsMemberDefaults: React.FC = observer(() => {
|
|||
})
|
||||
.then(() => {
|
||||
setToast({
|
||||
title: "Success!",
|
||||
title: `${t("success")}!`,
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
message: "Project updated successfully",
|
||||
message: t("project_settings.general.toast.success"),
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
|
|
@ -96,9 +99,9 @@ export const ProjectSettingsMemberDefaults: React.FC = observer(() => {
|
|||
})
|
||||
.then(() => {
|
||||
setToast({
|
||||
title: "Success!",
|
||||
title: `${t("success")}!`,
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
message: "Project updated successfully",
|
||||
message: t("project_settings.general.toast.success"),
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
|
|
@ -109,13 +112,13 @@ export const ProjectSettingsMemberDefaults: React.FC = observer(() => {
|
|||
return (
|
||||
<>
|
||||
<div className="flex items-center border-b border-custom-border-100 pb-3.5">
|
||||
<h3 className="text-xl font-medium">Defaults</h3>
|
||||
<h3 className="text-xl font-medium">{t("defaults")}</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full flex-col gap-2 pb-4">
|
||||
<div className="flex w-full items-center gap-4 py-4">
|
||||
<div className="flex w-1/2 flex-col gap-2">
|
||||
<h4 className="text-sm">Project lead</h4>
|
||||
<h4 className="text-sm">{t("project_settings.members.project_lead")}</h4>
|
||||
<div className="">
|
||||
{currentProjectDetails ? (
|
||||
<Controller
|
||||
|
|
@ -140,7 +143,7 @@ export const ProjectSettingsMemberDefaults: React.FC = observer(() => {
|
|||
</div>
|
||||
|
||||
<div className="flex w-1/2 flex-col gap-2">
|
||||
<h4 className="text-sm">Default assignee</h4>
|
||||
<h4 className="text-sm">{t("project_settings.members.default_assignee")}</h4>
|
||||
<div className="">
|
||||
{currentProjectDetails ? (
|
||||
<Controller
|
||||
|
|
@ -169,10 +172,10 @@ export const ProjectSettingsMemberDefaults: React.FC = observer(() => {
|
|||
<div className="relative pb-4 flex justify-between items-center gap-3">
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-lg font-medium text-custom-text-100">
|
||||
Grant view access to all issues for guest users:
|
||||
{t("project_settings.members.guest_super_permissions.title")}
|
||||
</h3>
|
||||
<p className="text-sm text-custom-text-200">
|
||||
This will allow guests to have view access to all the project issues.
|
||||
{t("project_settings.members.guest_super_permissions.sub_heading")}
|
||||
</p>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// types
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
// i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TProjectAppliedDisplayFilterKeys, TProjectFilters } from "@plane/types";
|
||||
// components
|
||||
import { PageHead } from "@/components/core";
|
||||
|
|
@ -17,6 +18,7 @@ const Root = observer(() => {
|
|||
const { currentWorkspace } = useWorkspace();
|
||||
const { workspaceSlug } = useParams();
|
||||
const pathname = usePathname();
|
||||
const { t } = useTranslation();
|
||||
// store
|
||||
const { totalProjectIds, filteredProjectIds } = useProject();
|
||||
const {
|
||||
|
|
@ -28,7 +30,9 @@ const Root = observer(() => {
|
|||
updateDisplayFilters,
|
||||
} = useProjectFilter();
|
||||
// derived values
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Projects` : undefined;
|
||||
const pageTitle = currentWorkspace?.name
|
||||
? `${currentWorkspace?.name} - ${t("workspace_projects.label", { count: 2 })}`
|
||||
: undefined;
|
||||
|
||||
const isArchived = pathname.includes("/archives");
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,16 @@ import { observer } from "mobx-react";
|
|||
import { Search, X } from "lucide-react";
|
||||
// plane hooks
|
||||
import { useOutsideClickDetector } from "@plane/hooks";
|
||||
// i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import { useProjectFilter } from "@/hooks/store";
|
||||
|
||||
export const ProjectSearch: FC = observer(() => {
|
||||
// i18n
|
||||
const { t } = useTranslation();
|
||||
// hooks
|
||||
const { searchQuery, updateSearchQuery } = useProjectFilter();
|
||||
// refs
|
||||
|
|
@ -55,7 +59,7 @@ export const ProjectSearch: FC = observer(() => {
|
|||
<input
|
||||
ref={inputRef}
|
||||
className="w-full max-w-[234px] border-none bg-transparent text-sm text-custom-text-100 placeholder:text-custom-text-400 focus:outline-none"
|
||||
placeholder="Search"
|
||||
placeholder={t("common.search.label")}
|
||||
value={searchQuery}
|
||||
onChange={(e) => updateSearchQuery(e.target.value)}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
|
|
|
|||
|
|
@ -6,17 +6,17 @@ import { useParams } from "next/navigation";
|
|||
import { useForm, Controller, useFieldArray } from "react-hook-form";
|
||||
import { ChevronDown, Plus, X } from "lucide-react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// plane imports
|
||||
import { ROLE, PROJECT_MEMBER_ADDED, EUserPermissions } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { Avatar, Button, CustomSelect, CustomSearchSelect, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
import { PROJECT_MEMBER_ADDED } from "@/constants/event-tracker";
|
||||
import { ROLE } from "@/constants/workspace";
|
||||
// helpers
|
||||
import { getFileURL } from "@/helpers/file.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useUserPermissions } from "@/hooks/store";
|
||||
// plane-web constants
|
||||
import { EUserPermissions } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
|
|
@ -68,6 +68,8 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
|
|||
name: "members",
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const currentProjectRole = projectUserInfo?.[workspaceSlug?.toString()]?.[projectId?.toString()]?.role;
|
||||
|
||||
const uninvitedPeople = workspaceMemberIds?.filter((userId) => {
|
||||
|
|
@ -175,7 +177,10 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
|
|||
const currentMemberWorkspaceRole = getWorkspaceMemberDetails(value)?.role;
|
||||
if (!value || !currentMemberWorkspaceRole) return ROLE;
|
||||
|
||||
const isGuestOROwner = [EUserPermissions.ADMIN, EUserPermissions.GUEST].includes(currentMemberWorkspaceRole);
|
||||
const isGuestOROwner = [EUserPermissions.ADMIN, EUserPermissions.GUEST].includes(
|
||||
currentMemberWorkspaceRole as EUserPermissions
|
||||
);
|
||||
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(ROLE).filter(([key]) => !isGuestOROwner || [currentMemberWorkspaceRole].includes(parseInt(key)))
|
||||
|
|
@ -212,10 +217,12 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
|
|||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="space-y-5">
|
||||
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
|
||||
Invite members
|
||||
{t("project_settings.members.invite_members.title")}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-custom-text-200">Invite members to work on your project.</p>
|
||||
<p className="text-sm text-custom-text-200">
|
||||
{t("project_settings.members.invite_members.sub_heading")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-3 space-y-4">
|
||||
|
|
@ -339,16 +346,16 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
|
|||
onClick={appendField}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add more
|
||||
{t("common.add_more")}
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
|
||||
Cancel
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
<Button variant="primary" size="sm" type="submit" loading={isSubmitting}>
|
||||
{isSubmitting
|
||||
? `${fields && fields.length > 1 ? "Adding members..." : "Adding member..."}`
|
||||
: `${fields && fields.length > 1 ? "Add members" : "Add member"}`}
|
||||
? `${fields && fields.length > 1 ? `${t("add_members")}...` : `${t("add_member")}...`}`
|
||||
: `${fields && fields.length > 1 ? t("add_members") : t("add_member")}`}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ export const ArchiveRestoreProjectModal: React.FC<Props> = (props) => {
|
|||
</h3>
|
||||
<p className="mt-3 text-sm text-custom-text-200">
|
||||
{archive
|
||||
? "This project and its issues, cycles, modules, and pages will be archived. Its issues won’t appear in search. Only project admins can restore the project."
|
||||
? "This project and its work items, cycles, modules, and pages will be archived. Its work items won’t appear in search. Only project admins can restore the project."
|
||||
: "Restoring a project will activate it and make it visible to all members of the project. Are you sure you want to continue?"}
|
||||
</p>
|
||||
<div className="mt-3 flex justify-end gap-2">
|
||||
|
|
|
|||
|
|
@ -3,18 +3,17 @@ import Link from "next/link";
|
|||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { Disclosure } from "@headlessui/react";
|
||||
// plane imports
|
||||
import { ROLE, EUserPermissions } from "@plane/constants";
|
||||
// plane types
|
||||
import { IUser, IWorkspaceMember } from "@plane/types";
|
||||
// plane ui
|
||||
import { CustomSelect, PopoverMenu, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
import { ROLE } from "@/constants/workspace";
|
||||
// helpers
|
||||
import { getFileURL } from "@/helpers/file.helper";
|
||||
// hooks
|
||||
import { useMember, useUser } from "@/hooks/store";
|
||||
// plane web constants
|
||||
import { EUserPermissions } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
export interface RowData {
|
||||
member: IWorkspaceMember;
|
||||
|
|
@ -132,7 +131,7 @@ export const AccountTypeColumn: React.FC<AccountTypeProps> = observer((props) =>
|
|||
<>
|
||||
{isRoleNonEditable ? (
|
||||
<div className="w-32 flex ">
|
||||
<span>{ROLE[rowData.role as keyof typeof ROLE]}</span>
|
||||
<span>{ROLE[rowData.role]}</span>
|
||||
</div>
|
||||
) : (
|
||||
<Controller
|
||||
|
|
@ -161,7 +160,7 @@ export const AccountTypeColumn: React.FC<AccountTypeProps> = observer((props) =>
|
|||
}}
|
||||
label={
|
||||
<div className="flex ">
|
||||
<span>{ROLE[rowData.role as keyof typeof ROLE]}</span>
|
||||
<span>{ROLE[rowData.role]}</span>
|
||||
</div>
|
||||
}
|
||||
buttonClassName={`!px-0 !justify-start hover:bg-custom-background-100 ${errors.role ? "border-red-500" : "border-none"}`}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue