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:
Prateek Shourya 2025-02-06 20:41:31 +05:30 committed by GitHub
parent e244f48776
commit d36c3acbf7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
693 changed files with 18182 additions and 10485 deletions

View file

@ -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"

View file

@ -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

View file

@ -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"

View file

@ -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>

View file

@ -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>

View file

@ -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;

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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)}
/>
))
) : (

View file

@ -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";

View file

@ -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>
))}

View file

@ -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}

View file

@ -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>

View file

@ -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>
) : (
<></>

View file

@ -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";

View file

@ -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>

View file

@ -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";

View file

@ -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>
)}

View file

@ -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;

View file

@ -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

View file

@ -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}>

View 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)} />;
};

View file

@ -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

View file

@ -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");

View file

@ -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}

View file

@ -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>

View file

@ -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 wont 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 wont 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">

View file

@ -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"}`}