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

@ -15,6 +15,8 @@ import {
MoveRight,
Copy,
} from "lucide-react";
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { TNameDescriptionLoader } from "@plane/types";
import { Button, ControlLink, CustomMenu, Row, TOAST_TYPE, setToast } from "@plane/ui";
// components
@ -34,7 +36,6 @@ import { copyUrlToClipboard } from "@/helpers/string.helper";
// hooks
import { useUser, useProjectInbox, useProject, useUserPermissions } from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router";
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
// store types
import type { IInboxIssueStore } from "@/store/inbox/inbox-issue.store";
@ -71,6 +72,7 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
const { data: currentUser } = useUser();
const { allowPermissions } = useUserPermissions();
const { currentProjectDetails } = useProject();
const { t } = useTranslation();
const router = useAppRouter();
const { getProjectById } = useProject();
@ -172,8 +174,8 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
copyUrlToClipboard(path).then(() =>
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Link copied",
message: "Issue link copied to clipboard",
title: t("common.link_copied"),
message: t("common.copied_to_clipboard"),
})
);
@ -243,10 +245,12 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
beforeFormSubmit={handleInboxIssueAccept}
withDraftIssueWrapper={false}
fetchIssueDetails={false}
modalTitle={`Move ${currentProjectDetails?.identifier}-${issue?.sequence_id} to project issues`}
modalTitle={t("inbox_issue.actions.move", {
value: `${currentProjectDetails?.identifier}-${issue?.sequence_id}`,
})}
primaryButtonText={{
default: "Add to project",
loading: "Adding",
default: t("add_to_project"),
loading: t("adding"),
}}
/>
<DeclineIssueModal
@ -319,11 +323,11 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
handleActionWithPermission(
isProjectAdmin,
() => setAcceptIssueModal(true),
"Only project admins can accept issues"
t("inbox_issue.errors.accept_permission")
)
}
>
Accept
{t("inbox_issue.actions.accept")}
</Button>
</div>
)}
@ -339,11 +343,11 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
handleActionWithPermission(
isProjectAdmin,
() => setDeclineIssueModal(true),
"Only project admins can deny issues"
t("inbox_issue.errors.decline_permission")
)
}
>
Decline
{t("inbox_issue.actions.decline")}
</Button>
</div>
)}
@ -356,7 +360,7 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
size="sm"
onClick={() => handleCopyIssueLink(issueLink)}
>
Copy issue link
{t("inbox_issue.actions.copy")}
</Button>
<ControlLink
href={`/${workspaceSlug}/projects/${issue?.project_id}/issues/${currentInboxIssueId}`}
@ -366,7 +370,7 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
target="_self"
>
<Button variant="neutral-primary" prependIcon={<ExternalLink className="h-2.5 w-2.5" />} size="sm">
Open issue
{t("inbox_issue.actions.open")}
</Button>
</ControlLink>
</div>
@ -380,15 +384,15 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
handleActionWithPermission(
isProjectAdmin,
handleIssueSnoozeAction,
"Only project admins can snooze/Un-snooze issues"
t("inbox_issue.errors.snooze_permission")
)
}
>
<div className="flex items-center gap-2">
<Clock size={14} strokeWidth={2} />
{inboxIssue?.snoozed_till && numberOfDaysLeft && numberOfDaysLeft > 0
? "Un-snooze"
: "Snooze"}
? t("inbox_issue.actions.unsnooze")
: t("inbox_issue.actions.snooze")}
</div>
</CustomMenu.MenuItem>
)}
@ -398,27 +402,27 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
handleActionWithPermission(
isProjectAdmin,
() => setSelectDuplicateIssue(true),
"Only project admins can mark issues as duplicate"
"Only project admins can mark work item as duplicate"
)
}
>
<div className="flex items-center gap-2">
<FileStack size={14} strokeWidth={2} />
Mark as duplicate
{t("inbox_issue.actions.mark_as_duplicate")}
</div>
</CustomMenu.MenuItem>
)}
<CustomMenu.MenuItem onClick={() => handleCopyIssueLink(intakeIssueLink)}>
<div className="flex items-center gap-2">
<Copy size={14} strokeWidth={2} />
Copy issue link
{t("inbox_issue.actions.copy")}
</div>
</CustomMenu.MenuItem>
{canDelete && (
<CustomMenu.MenuItem onClick={() => setDeleteIssueModal(true)}>
<div className="flex items-center gap-2">
<Trash2 size={14} strokeWidth={2} />
Delete
{t("inbox_issue.actions.delete")}
</div>
</CustomMenu.MenuItem>
)}

View file

@ -127,7 +127,7 @@ export const InboxIssueActionsMobileHeader: React.FC<Props> = observer((props) =
<CustomMenu.MenuItem onClick={handleCopyIssueLink}>
<div className="flex items-center gap-2">
<Link size={14} strokeWidth={2} />
Copy issue link
Copy work item link
</div>
</CustomMenu.MenuItem>
)}
@ -139,7 +139,7 @@ export const InboxIssueActionsMobileHeader: React.FC<Props> = observer((props) =
>
<div className="flex items-center gap-2">
<ExternalLink size={14} strokeWidth={2} />
Open issue
Open work item
</div>
</CustomMenu.MenuItem>
)}
@ -149,7 +149,7 @@ export const InboxIssueActionsMobileHeader: React.FC<Props> = observer((props) =
handleActionWithPermission(
isProjectAdmin,
handleIssueSnoozeAction,
"Only project admins can snooze/Un-snooze issues"
"Only project admins can snooze/Un-snooze work items"
)
}
>
@ -165,7 +165,7 @@ export const InboxIssueActionsMobileHeader: React.FC<Props> = observer((props) =
handleActionWithPermission(
isProjectAdmin,
() => setSelectDuplicateIssue(true),
"Only project admins can mark issues as duplicate"
"Only project admins can mark work items as duplicate"
)
}
>
@ -181,7 +181,7 @@ export const InboxIssueActionsMobileHeader: React.FC<Props> = observer((props) =
handleActionWithPermission(
isProjectAdmin,
() => setAcceptIssueModal(true),
"Only project admins can accept issues"
"Only project admins can accept work items"
)
}
>
@ -197,7 +197,7 @@ export const InboxIssueActionsMobileHeader: React.FC<Props> = observer((props) =
handleActionWithPermission(
isProjectAdmin,
() => setDeclineIssueModal(true),
"Only project admins can deny issues"
"Only project admins can deny work items"
)
}
>

View file

@ -4,6 +4,7 @@ import { Dispatch, SetStateAction, useEffect, useMemo } from "react";
import { observer } from "mobx-react";
import { usePathname } from "next/navigation";
// plane types
import { ISSUE_ARCHIVED, ISSUE_DELETED } from "@plane/constants";
import { TIssue, TNameDescriptionLoader } from "@plane/types";
// plane ui
import { Loader, TOAST_TYPE, setToast } from "@plane/ui";
@ -18,7 +19,6 @@ import {
IssueAttachmentRoot,
} from "@/components/issues";
// constants
import { ISSUE_ARCHIVED, ISSUE_DELETED } from "@/constants/event-tracker";
// helpers
import { getTextContent } from "@/helpers/editor.helper";
// hooks
@ -91,23 +91,23 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
setToast({
title: "Success!",
type: TOAST_TYPE.SUCCESS,
message: "Issue deleted successfully",
message: "Work item deleted successfully",
});
captureIssueEvent({
eventName: ISSUE_DELETED,
payload: { id: _issueId, state: "SUCCESS", element: "Issue detail page" },
payload: { id: _issueId, state: "SUCCESS", element: "Work item detail page" },
path: pathname,
});
} catch (error) {
console.log("Error in deleting issue:", error);
console.log("Error in deleting work item:", error);
setToast({
title: "Error!",
type: TOAST_TYPE.ERROR,
message: "Issue delete failed",
message: "Work item delete failed",
});
captureIssueEvent({
eventName: ISSUE_DELETED,
payload: { id: _issueId, state: "FAILED", element: "Issue detail page" },
payload: { id: _issueId, state: "FAILED", element: "Work item detail page" },
path: pathname,
});
}
@ -116,7 +116,7 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
try {
await inboxIssue.updateIssue(data);
captureIssueEvent({
eventName: "Inbox issue updated",
eventName: "Inbox work item updated",
payload: { ...data, state: "SUCCESS", element: "Inbox" },
updates: {
changed_property: Object.keys(data).join(","),
@ -126,12 +126,12 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
});
} catch (error) {
setToast({
title: "Issue update failed",
title: "Work item update failed",
type: TOAST_TYPE.ERROR,
message: "Issue update failed",
message: "Work item update failed",
});
captureIssueEvent({
eventName: "Inbox issue updated",
eventName: "Inbox work item updated",
payload: { state: "SUCCESS", element: "Inbox" },
updates: {
changed_property: Object.keys(data).join(","),
@ -146,14 +146,14 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
await archiveIssue(workspaceSlug, projectId, issueId);
captureIssueEvent({
eventName: ISSUE_ARCHIVED,
payload: { id: issueId, state: "SUCCESS", element: "Issue details page" },
payload: { id: issueId, state: "SUCCESS", element: "Work item details page" },
path: pathname,
});
} catch (error) {
console.log("Error in archiving issue:", error);
captureIssueEvent({
eventName: ISSUE_ARCHIVED,
payload: { id: issueId, state: "FAILED", element: "Issue details page" },
payload: { id: issueId, state: "FAILED", element: "Work item details page" },
path: pathname,
});
}

View file

@ -1,6 +1,7 @@
import { FC, useEffect, useState } from "react";
import { observer } from "mobx-react";
import useSWR from "swr";
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { TNameDescriptionLoader } from "@plane/types";
// components
import { ContentWrapper } from "@plane/ui";
@ -8,7 +9,6 @@ import { InboxIssueActionsHeader, InboxIssueMainContent } from "@/components/inb
// hooks
import { useProjectInbox, useUser, useUserPermissions } from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router";
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
type TInboxContentRoot = {
workspaceSlug: string;

View file

@ -3,15 +3,16 @@
import { FC } from "react";
import { observer } from "mobx-react";
import { X } from "lucide-react";
import { ISSUE_PRIORITIES } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { TIssuePriorities } from "@plane/types";
import { PriorityIcon, Tag } from "@plane/ui";
// constants
import { ISSUE_PRIORITIES } from "@/constants/issue";
// hooks
import { useProjectInbox } from "@/hooks/store";
export const InboxIssueAppliedFiltersPriority: FC = observer(() => {
// hooks
const { t } = useTranslation();
const { inboxFilters, handleInboxIssueFilters } = useProjectInbox();
// derived values
const filteredValues = inboxFilters?.priority || [];
@ -26,7 +27,7 @@ export const InboxIssueAppliedFiltersPriority: FC = observer(() => {
if (filteredValues.length === 0) return <></>;
return (
<Tag>
<div className="text-xs text-custom-text-200">Priority</div>
<div className="text-xs text-custom-text-200">{t("common.priority")}</div>
{filteredValues.map((value) => {
const optionDetail = currentOptionDetail(value);
if (!optionDetail) return <></>;

View file

@ -1,16 +1,18 @@
import { FC } from "react";
import { observer } from "mobx-react";
import { X } from "lucide-react";
import { TInboxIssueStatus } from "@plane/types";
import { INBOX_STATUS, TInboxIssueStatus } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
// constants
import { Tag } from "@plane/ui";
import { INBOX_STATUS } from "@/constants/inbox";
// hooks
import { useProjectInbox } from "@/hooks/store";
import { InboxStatusIcon } from "../../inbox-status-icon";
export const InboxIssueAppliedFiltersStatus: FC = observer(() => {
// hooks
const { inboxFilters, handleInboxIssueFilters } = useProjectInbox();
const { t } = useTranslation();
// derived values
const filteredValues = inboxFilters?.status || [];
const currentOptionDetail = (status: TInboxIssueStatus) => INBOX_STATUS.find((s) => s.status === status) || undefined;
@ -28,9 +30,9 @@ export const InboxIssueAppliedFiltersStatus: FC = observer(() => {
return (
<div key={value} className="relative flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs">
<div className="w-3 h-3 flex-shrink-0 relative flex justify-center items-center overflow-hidden">
<optionDetail.icon className={`w-3 h-3 ${optionDetail?.textColor(false)}`} />
<InboxStatusIcon type={optionDetail?.status} />
</div>
<div className="text-xs truncate">{optionDetail?.title}</div>
<div className="text-xs truncate">{t(optionDetail?.i18n_title)}</div>
{handleFilterValue(optionDetail?.status).length >= 1 && (
<div
className="w-3 h-3 flex-shrink-0 relative flex justify-center items-center overflow-hidden cursor-pointer text-custom-text-300 hover:text-custom-text-200 transition-all"

View file

@ -2,12 +2,13 @@
import { FC, useState } from "react";
import { observer } from "mobx-react";
import { ISSUE_PRIORITIES } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { TIssuePriorities } from "@plane/types";
import { PriorityIcon } from "@plane/ui";
// plane constants
// components
import { FilterHeader, FilterOption } from "@/components/issues";
// constants
import { ISSUE_PRIORITIES } from "@/constants/issue";
// hooks
import { useProjectInbox } from "@/hooks/store/use-project-inbox";
@ -18,6 +19,7 @@ type Props = {
export const FilterPriority: FC<Props> = observer((props) => {
const { searchQuery } = props;
// hooks
const { t } = useTranslation();
const { inboxFilters, handleInboxIssueFilters } = useProjectInbox();
// states
const [previewEnabled, setPreviewEnabled] = useState(true);
@ -32,7 +34,7 @@ export const FilterPriority: FC<Props> = observer((props) => {
return (
<>
<FilterHeader
title={`Priority${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
title={`${t("common.priority")}${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
@ -49,7 +51,7 @@ export const FilterPriority: FC<Props> = observer((props) => {
/>
))
) : (
<p className="text-xs italic text-custom-text-400">No matches found</p>
<p className="text-xs italic text-custom-text-400">{t("common.search.no_matches_found")}</p>
)}
</div>
)}

View file

@ -1,13 +1,14 @@
import { FC, useState } from "react";
import { observer } from "mobx-react";
// types
import { TInboxIssueStatus } from "@plane/types";
import { INBOX_STATUS, TInboxIssueStatus } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
// components
import { FilterHeader, FilterOption } from "@/components/issues";
// constants
import { INBOX_STATUS } from "@/constants/inbox";
// hooks
import { useProjectInbox } from "@/hooks/store/use-project-inbox";
import { InboxStatusIcon } from "../../inbox-status-icon";
type Props = {
searchQuery: string;
@ -17,6 +18,7 @@ export const FilterStatus: FC<Props> = observer((props) => {
const { searchQuery } = props;
// hooks
const { currentTab, inboxFilters, handleInboxIssueFilters } = useProjectInbox();
const { t } = useTranslation();
// states
const [previewEnabled, setPreviewEnabled] = useState(true);
// derived values
@ -52,8 +54,8 @@ export const FilterStatus: FC<Props> = observer((props) => {
key={status.key}
isChecked={filterValue?.includes(status.status) ? true : false}
onClick={() => handleStatusFilterSelect(status.status)}
icon={<status.icon className={`h-3.5 w-3.5 ${status?.textColor(false)}`} />}
title={status.title}
icon={<InboxStatusIcon type={status.status} className={`h-3.5 w-3.5`} />}
title={t(status.i18n_title)}
/>
))
) : (

View file

@ -3,9 +3,11 @@
import { FC } from "react";
import { observer } from "mobx-react";
import { ArrowDownWideNarrow, ArrowUpWideNarrow, Check, ChevronDown } from "lucide-react";
import { INBOX_ISSUE_ORDER_BY_OPTIONS, INBOX_ISSUE_SORT_BY_OPTIONS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { TInboxIssueSortingOrderByKeys, TInboxIssueSortingSortByKeys } from "@plane/types";
import { CustomMenu, getButtonStyling } from "@plane/ui";
// constants
import { INBOX_ISSUE_ORDER_BY_OPTIONS, INBOX_ISSUE_SORT_BY_OPTIONS } from "@/constants/inbox";
// helpers
import { cn } from "@/helpers/common.helper";
// hooks
@ -14,6 +16,7 @@ import useSize from "@/hooks/use-window-size";
export const InboxIssueOrderByDropdown: FC = observer(() => {
// hooks
const { t } = useTranslation();
const windowSize = useSize();
const { inboxSorting, handleInboxIssueSorting } = useProjectInbox();
const orderByDetails =
@ -31,8 +34,7 @@ export const InboxIssueOrderByDropdown: FC = observer(() => {
) : (
<ArrowDownWideNarrow className="size-3 " />
)}
{orderByDetails?.label || "Order By"}
{t(orderByDetails?.i18n_label || "inbox_issue.order_by.created_at")}
<ChevronDown className="size-3" strokeWidth={2} />
</div>
);
@ -47,9 +49,9 @@ export const InboxIssueOrderByDropdown: FC = observer(() => {
<CustomMenu.MenuItem
key={option.key}
className="flex items-center justify-between gap-2"
onClick={() => handleInboxIssueSorting("order_by", option.key)}
onClick={() => handleInboxIssueSorting("order_by", option.key as TInboxIssueSortingOrderByKeys)}
>
{option.label}
{t(option.i18n_label)}
{inboxSorting?.order_by?.includes(option.key) && <Check className="size-3" />}
</CustomMenu.MenuItem>
))}
@ -58,9 +60,9 @@ export const InboxIssueOrderByDropdown: FC = observer(() => {
<CustomMenu.MenuItem
key={option.key}
className="flex items-center justify-between gap-2"
onClick={() => handleInboxIssueSorting("sort_by", option.key)}
onClick={() => handleInboxIssueSorting("sort_by", option.key as TInboxIssueSortingSortByKeys)}
>
{option.label}
{t(option.i18n_label)}
{inboxSorting?.sort_by?.includes(option.key) && <Check className="size-3" />}
</CustomMenu.MenuItem>
))}

View file

@ -1,11 +1,14 @@
import React from "react";
import { observer } from "mobx-react";
// constants
import { INBOX_STATUS } from "@/constants/inbox";
// helpers
import { INBOX_STATUS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { cn } from "@/helpers/common.helper";
import { findHowManyDaysLeft } from "@/helpers/date-time.helper";
// store
import { IInboxIssueStore } from "@/store/inbox/inbox-issue.store";
import { ICON_PROPERTIES, InboxStatusIcon } from "./inbox-status-icon";
type Props = {
inboxIssue: IInboxIssueStore;
@ -15,28 +18,31 @@ type Props = {
export const InboxIssueStatus: React.FC<Props> = observer((props) => {
const { inboxIssue, iconSize = 16, showDescription = false } = props;
//hooks
const { t } = useTranslation();
// derived values
const inboxIssueStatusDetail = INBOX_STATUS.find((s) => s.status === inboxIssue.status);
const isSnoozedDatePassed = inboxIssue.status === 0 && new Date(inboxIssue.snoozed_till ?? "") < new Date();
if (!inboxIssueStatusDetail || isSnoozedDatePassed) return <></>;
const description = inboxIssueStatusDetail.description(new Date(inboxIssue.snoozed_till ?? ""));
const description = t(inboxIssueStatusDetail.i18n_description(), {
days: findHowManyDaysLeft(new Date(inboxIssue.snoozed_till ?? "")),
});
const statusIcon = ICON_PROPERTIES[inboxIssue?.status];
return (
<div
className={cn(
`relative flex flex-col gap-1 p-1.5 py-0.5 rounded ${inboxIssueStatusDetail.textColor(
`relative flex flex-col gap-1 p-1.5 py-0.5 rounded ${statusIcon.textColor(
isSnoozedDatePassed
)} ${inboxIssueStatusDetail.bgColor(isSnoozedDatePassed)}`
)} ${statusIcon.bgColor(isSnoozedDatePassed)}`
)}
>
<div className={`flex items-center gap-1`}>
<inboxIssueStatusDetail.icon size={iconSize} className="flex-shrink-0" />
<InboxStatusIcon type={inboxIssue?.status} size={iconSize} className="flex-shrink-0" renderColor={false} />
<div className="font-medium text-xs whitespace-nowrap">
{inboxIssue?.status === 0 && inboxIssue?.snoozed_till
? inboxIssueStatusDetail.description(inboxIssue?.snoozed_till)
: inboxIssueStatusDetail.title}
{inboxIssue?.status === 0 && inboxIssue?.snoozed_till ? description : t(inboxIssueStatusDetail.i18n_title)}
</div>
</div>
{showDescription && <div className="text-sm whitespace-nowrap">{description}</div>}

View file

@ -0,0 +1,46 @@
import { AlertTriangle, CheckCircle2, Clock, Copy, XCircle } from "lucide-react";
import { TInboxIssueStatus, EInboxIssueStatus } from "@plane/constants";
import { cn } from "@plane/utils";
export const ICON_PROPERTIES = {
[EInboxIssueStatus.PENDING]: {
icon: AlertTriangle,
textColor: (snoozeDatePassed: boolean = false) => (snoozeDatePassed ? "" : "text-[#AB6400]"),
bgColor: (snoozeDatePassed: boolean = false) => (snoozeDatePassed ? "" : "bg-[#FFF7C2]"),
},
[EInboxIssueStatus.DECLINED]: {
icon: XCircle,
textColor: (snoozeDatePassed: boolean = false) => (snoozeDatePassed ? "" : "text-[#CE2C31]"),
bgColor: (snoozeDatePassed: boolean = false) => (snoozeDatePassed ? "" : "bg-[#FEEBEC]"),
},
[EInboxIssueStatus.SNOOZED]: {
icon: Clock,
textColor: (snoozeDatePassed: boolean = false) => (snoozeDatePassed ? "text-red-500" : "text-custom-text-400"),
bgColor: (snoozeDatePassed: boolean = false) => (snoozeDatePassed ? "bg-red-500/10" : "bg-[#E0E1E6]"),
},
[EInboxIssueStatus.ACCEPTED]: {
icon: CheckCircle2,
textColor: (snoozeDatePassed: boolean = false) => (snoozeDatePassed ? "" : "text-[#3E9B4F]"),
bgColor: (snoozeDatePassed: boolean = false) => (snoozeDatePassed ? "" : "bg-[#E9F6E9]"),
},
[EInboxIssueStatus.DUPLICATE]: {
icon: Copy,
textColor: (snoozeDatePassed: boolean = false) => (snoozeDatePassed ? "" : "text-custom-text-200"),
bgColor: (snoozeDatePassed: boolean = false) => (snoozeDatePassed ? "" : "bg-gray-500/10"),
},
};
export const InboxStatusIcon = ({
type,
size,
className,
renderColor = true,
}: {
type: TInboxIssueStatus;
size?: number;
className?: string;
renderColor?: boolean;
}) => {
if (type === undefined) return null;
const Icon = ICON_PROPERTIES[type];
return <Icon.icon size={size} className={cn(`w-3 h-3 ${renderColor && Icon?.textColor(false)}`, className)} />;
};

View file

@ -3,16 +3,16 @@
import { FC, FormEvent, useCallback, useEffect, useRef, useState } from "react";
import { observer } from "mobx-react";
import { usePathname } from "next/navigation";
// editor
// plane imports
import { ETabIndices, ISSUE_CREATED } from "@plane/constants";
import { EditorRefApi } from "@plane/editor";
// types
import { useTranslation } from "@plane/i18n";
import { TIssue } from "@plane/types";
import { Button, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { InboxIssueTitle, InboxIssueDescription, InboxIssueProperties } from "@/components/inbox/modals/create-modal";
// constants
import { ISSUE_CREATED } from "@/constants/event-tracker";
import { ETabIndices } from "@/constants/tab-indices";
// helpers
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
import { getTabIndex } from "@/helpers/tab-indices.helper";
@ -67,6 +67,7 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id;
const { isMobile } = usePlatformOS();
const { getProjectById } = useProject();
const { t } = useTranslation();
// states
const [createMore, setCreateMore] = useState<boolean>(false);
const [formSubmitting, setFormSubmitting] = useState(false);
@ -179,7 +180,7 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
setToast({
type: TOAST_TYPE.SUCCESS,
title: `Success!`,
message: "Issue created successfully.",
message: "Work item created successfully.",
});
})
.catch((error) => {
@ -213,7 +214,7 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
<form ref={formRef} onSubmit={handleFormSubmit} className="flex flex-col w-full">
<div className="space-y-5 p-5 rounded-t-lg bg-custom-background-100">
<div className="flex items-center justify-between gap-2">
<h3 className="text-xl font-medium text-custom-text-200">Create intake issue</h3>
<h3 className="text-xl font-medium text-custom-text-200">{t("inbox_issue.modal.title")}</h3>
{duplicateIssues?.length > 0 && (
<DeDupeButtonRoot
workspaceSlug={workspaceSlug}
@ -251,7 +252,7 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
tabIndex={getIndex("create_more")}
>
<ToggleSwitch value={createMore} onChange={() => {}} size="sm" />
<span className="text-xs">Create more</span>
<span className="text-xs">{t("create_more")}</span>
</div>
<div className="flex items-center gap-3">
<Button
@ -271,7 +272,7 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
}}
tabIndex={getIndex("discard_button")}
>
Discard
{t("discard")}
</Button>
<Button
variant="primary"
@ -282,7 +283,7 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
disabled={isTitleLengthMoreThan255Character}
tabIndex={getIndex("submit_button")}
>
{formSubmitting ? "Creating" : "Create Issue"}
{formSubmitting ? t("creating") : t("create_work_item")}
</Button>
</div>
</div>

View file

@ -2,8 +2,12 @@
import { FC, RefObject } from "react";
import { observer } from "mobx-react";
// plane imports
import { ETabIndices } from "@plane/constants";
// editor
import { EditorRefApi } from "@plane/editor";
// i18n
import { useTranslation } from "@plane/i18n";
// types
import { TIssue } from "@plane/types";
import { EFileAssetType } from "@plane/types/src/enums";
@ -11,10 +15,8 @@ import { EFileAssetType } from "@plane/types/src/enums";
import { Loader } from "@plane/ui";
// components
import { RichTextEditor } from "@/components/editor/rich-text-editor/rich-text-editor";
// constants
import { ETabIndices } from "@/constants/tab-indices";
// helpers
import { getDescriptionPlaceholder } from "@/helpers/issue.helper";
import { getDescriptionPlaceholderI18n } from "@/helpers/issue.helper";
import { getTabIndex } from "@/helpers/tab-indices.helper";
// hooks
import { useProjectInbox } from "@/hooks/store";
@ -51,6 +53,10 @@ export const InboxIssueDescription: FC<TInboxIssueDescription> = observer((props
onEnterKeyPress,
onAssetUpload,
} = props;
// i18n
const { t } = useTranslation();
// hooks
const { loader } = useProjectInbox();
const { isMobile } = usePlatformOS();
@ -74,7 +80,7 @@ export const InboxIssueDescription: FC<TInboxIssueDescription> = observer((props
projectId={projectId}
dragDropEnabled={false}
onChange={(_description: object, description_html: string) => handleData("description_html", description_html)}
placeholder={getDescriptionPlaceholder}
placeholder={(isFocused, description) => t(`${getDescriptionPlaceholderI18n(isFocused, description)}`)}
searchMentionCallback={async (payload) =>
await workspaceService.searchEntity(workspaceSlug?.toString() ?? "", {
...payload,
@ -98,7 +104,7 @@ export const InboxIssueDescription: FC<TInboxIssueDescription> = observer((props
onAssetUpload?.(asset_id);
return asset_id;
} catch (error) {
console.log("Error in uploading issue asset:", error);
console.log("Error in uploading work item asset:", error);
throw new Error("Asset upload failed. Please try again later.");
}
}}

View file

@ -1,6 +1,8 @@
import { FC, useState } from "react";
import { observer } from "mobx-react";
import { LayoutPanelTop } from "lucide-react";
// plane imports
import { ETabIndices } from "@plane/constants";
import { ISearchIssueResponse, TIssue } from "@plane/types";
import { CustomMenu } from "@plane/ui";
// components
@ -15,8 +17,6 @@ import {
} from "@/components/dropdowns";
import { ParentIssuesListModal } from "@/components/issues";
import { IssueLabelSelect } from "@/components/issues/select";
// constants
import { ETabIndices } from "@/constants/tab-indices";
// helpers
import { renderFormattedPayloadDate, getDate } from "@/helpers/date-time.helper";
import { getTabIndex } from "@/helpers/tab-indices.helper";
@ -194,7 +194,7 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
>
<>
<CustomMenu.MenuItem className="!p-1" onClick={() => setParentIssueModalOpen(true)}>
Change parent issue
Change parent work item
</CustomMenu.MenuItem>
<CustomMenu.MenuItem
className="!p-1"
@ -203,7 +203,7 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
setSelectedParentIssue(undefined);
}}
>
Remove parent issue
Remove parent work item
</CustomMenu.MenuItem>
</>
</CustomMenu>

View file

@ -2,10 +2,11 @@
import { FC } from "react";
import { observer } from "mobx-react";
// plane imports
import { ETabIndices } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { TIssue } from "@plane/types";
import { Input } from "@plane/ui";
// constants
import { ETabIndices } from "@/constants/tab-indices";
// helpers
import { getTabIndex } from "@/helpers/tab-indices.helper";
// hooks
@ -23,6 +24,7 @@ export const InboxIssueTitle: FC<TInboxIssueTitle> = observer((props) => {
const { isMobile } = usePlatformOS();
const { getIndex } = getTabIndex(ETabIndices.INTAKE_ISSUE_FORM, isMobile);
const { t } = useTranslation();
return (
<div className="space-y-1">
<Input
@ -31,13 +33,13 @@ export const InboxIssueTitle: FC<TInboxIssueTitle> = observer((props) => {
type="text"
value={data?.name}
onChange={(e) => handleData("name", e.target.value)}
placeholder="Title"
placeholder={t("title")}
className="w-full text-base"
tabIndex={getIndex("name")}
required
/>
{isTitleLengthMoreThan255Character && (
<span className="text-xs text-red-500">Title should be less than 255 characters</span>
<span className="text-xs text-red-500">{t("title_should_be_less_than_255_characters")}</span>
)}
</div>
);

View file

@ -1,5 +1,6 @@
import React, { useState } from "react";
// types
import { useTranslation } from "@plane/i18n";
import type { TIssue } from "@plane/types";
// ui
import { AlertModalCore } from "@plane/ui";
@ -19,6 +20,7 @@ export const DeclineIssueModal: React.FC<Props> = (props) => {
const [isDeclining, setIsDeclining] = useState(false);
// store hooks
const { getProjectById } = useProject();
const { t } = useTranslation();
// derived values
const projectDetails = data.project_id ? getProjectById(data?.project_id) : undefined;
@ -38,11 +40,11 @@ export const DeclineIssueModal: React.FC<Props> = (props) => {
handleSubmit={handleDecline}
isSubmitting={isDeclining}
isOpen={isOpen}
title="Decline issue"
title={t("inbox_issue.modals.decline.title")}
// TODO: Need to translate the confirmation message
content={
<>
{" "}
Are you sure you want to decline issue{" "}
Are you sure you want to decline work item{" "}
<span className="break-words font-medium text-custom-text-100">
{projectDetails?.identifier}-{data?.sequence_id}
</span>
@ -50,8 +52,8 @@ export const DeclineIssueModal: React.FC<Props> = (props) => {
</>
}
primaryButtonText={{
loading: "Declining",
default: "Decline",
loading: t("declining"),
default: t("decline"),
}}
/>
);

View file

@ -1,11 +1,12 @@
import React, { useState } from "react";
import { observer } from "mobx-react";
// types
import { PROJECT_ERROR_MESSAGES } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import type { TIssue } from "@plane/types";
// ui
import { AlertModalCore, setToast, TOAST_TYPE } from "@plane/ui";
// constants
import { PROJECT_ERROR_MESSAGES } from "@/constants/project";
// hooks
import { useProject } from "@/hooks/store";
@ -21,6 +22,7 @@ export const DeleteInboxIssueModal: React.FC<Props> = observer(({ isOpen, onClos
const [isDeleting, setIsDeleting] = useState(false);
// store hooks
const { getProjectById } = useProject();
const { t } = useTranslation();
// derived values
const projectDetails = data.project_id ? getProjectById(data?.project_id) : undefined;
@ -35,19 +37,19 @@ export const DeleteInboxIssueModal: React.FC<Props> = observer(({ isOpen, onClos
.then(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: `Issue deleted successfully`,
title: `${t("success")!}`,
message: `${t("inbox_issue.modals.delete.success")!}`,
});
})
.catch((errors) => {
const isPermissionError = errors?.error === "Only admin or creator can delete the issue";
const isPermissionError = errors?.error === "Only admin or creator can delete the work item";
const currentError = isPermissionError
? PROJECT_ERROR_MESSAGES.permissionError
: PROJECT_ERROR_MESSAGES.issueDeleteError;
setToast({
title: currentError.title,
title: t(currentError.i18n_title),
type: TOAST_TYPE.ERROR,
message: currentError.message,
message: currentError.i18n_message && t(currentError.i18n_message),
});
})
.finally(() => handleClose());
@ -59,14 +61,15 @@ export const DeleteInboxIssueModal: React.FC<Props> = observer(({ isOpen, onClos
handleSubmit={handleDelete}
isSubmitting={isDeleting}
isOpen={isOpen}
title="Delete issue"
title={t("inbox_issue.modals.delete.title")}
// TODO: Need to translate the confirmation message
content={
<>
Are you sure you want to delete issue{" "}
Are you sure you want to delete work item{" "}
<span className="break-words font-medium text-custom-text-100">
{projectDetails?.identifier}-{data?.sequence_id}
</span>
{""}? The issue will only be deleted from the intake and this action cannot be undone.
{""}? The work item will only be deleted from the intake and this action cannot be undone.
</>
}
/>

View file

@ -4,18 +4,16 @@ import React, { useEffect, useState } from "react";
import { useParams } from "next/navigation";
import { Search } from "lucide-react";
import { Combobox, Dialog, Transition } from "@headlessui/react";
// icons
// components
// types
// plane imports
import { useTranslation } from "@plane/i18n";
import { ISearchIssueResponse } from "@plane/types";
// ui
import { Loader, TOAST_TYPE, setToast } from "@plane/ui";
import { EmptyState } from "@/components/empty-state";
// constants
import { EmptyStateType } from "@/constants/empty-state";
// components
import { SimpleEmptyState } from "@/components/empty-state";
// hooks
import { useProject } from "@/hooks/store";
import useDebounce from "@/hooks/use-debounce";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
// services
import { ProjectService } from "@/services/project";
@ -30,18 +28,19 @@ const projectService = new ProjectService();
export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
const { isOpen, onClose, onSubmit, value } = props;
const [query, setQuery] = useState("");
// router
const { workspaceSlug, projectId, issueId } = useParams();
// hooks
const { getProjectById } = useProject();
// states
const [query, setQuery] = useState("");
const [issues, setIssues] = useState<ISearchIssueResponse[]>([]);
const [isSearching, setIsSearching] = useState(false);
// hooks
const { getProjectById } = useProject();
const { t } = useTranslation();
// derived values
const debouncedSearchTerm: string = useDebounce(query, 500);
const searchResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/search" });
const issuesResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/issues" });
useEffect(() => {
if (!isOpen || !workspaceSlug || !projectId) return;
@ -75,7 +74,9 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
const issueList =
filteredIssues.length > 0 ? (
<li className="p-2">
{query === "" && <h2 className="mb-2 mt-4 px-3 text-xs font-semibold text-custom-text-100">Select issue</h2>}
{query === "" && (
<h2 className="mb-2 mt-4 px-3 text-xs font-semibold text-custom-text-100">Select work item</h2>
)}
<ul className="text-sm text-custom-text-100">
{filteredIssues.map((issue) => {
const stateColor = issue.state__color || "";
@ -110,12 +111,11 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
</li>
) : (
<div className="flex flex-col items-center justify-center px-3 py-8 text-center">
<EmptyState
type={
query === "" ? EmptyStateType.ISSUE_RELATION_EMPTY_STATE : EmptyStateType.ISSUE_RELATION_SEARCH_EMPTY_STATE
}
layout="screen-simple"
/>
{query === "" ? (
<SimpleEmptyState title={t("issue_relation.empty_state.no_issues.title")} assetPath={issuesResolvedPath} />
) : (
<SimpleEmptyState title={t("issue_relation.empty_state.search.title")} assetPath={searchResolvedPath} />
)}
</div>
);

View file

@ -4,6 +4,7 @@ import { FC, Fragment, useState } from "react";
import { DayPicker, getDefaultClassNames } from "react-day-picker";
import { Dialog, Transition } from "@headlessui/react";
// ui
import { useTranslation } from "@plane/i18n";
import { Button } from "@plane/ui";
export type InboxIssueSnoozeModalProps = {
@ -17,6 +18,8 @@ export const InboxIssueSnoozeModal: FC<InboxIssueSnoozeModalProps> = (props) =>
const { isOpen, handleClose, value, onConfirm } = props;
// states
const [date, setDate] = useState(value || new Date());
//hooks
const { t } = useTranslation();
const defaultClassNames = getDefaultClassNames();
@ -70,7 +73,7 @@ export const InboxIssueSnoozeModal: FC<InboxIssueSnoozeModalProps> = (props) =>
onConfirm(date);
}}
>
Snooze
{t("inbox_issue.actions.snooze")}
</Button>
</div>
</Dialog.Panel>

View file

@ -1,18 +1,19 @@
import { FC, useEffect, useState } from "react";
import { observer } from "mobx-react";
import { PanelLeft } from "lucide-react";
// plane imports
import { useTranslation } from "@plane/i18n";
import { Intake } from "@plane/ui";
// components
import { EmptyState } from "@/components/empty-state";
import { SimpleEmptyState } from "@/components/empty-state";
import { InboxSidebar, InboxContentRoot } from "@/components/inbox";
import { InboxLayoutLoader } from "@/components/ui";
// constants
import { EmptyStateType } from "@/constants/empty-state";
// helpers
import { cn } from "@/helpers/common.helper";
import { EInboxIssueCurrentTab } from "@/helpers/inbox.helper";
// hooks
import { useProjectInbox } from "@/hooks/store";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
type TInboxIssueRoot = {
workspaceSlug: string;
@ -26,8 +27,12 @@ export const InboxIssueRoot: FC<TInboxIssueRoot> = observer((props) => {
const { workspaceSlug, projectId, inboxIssueId, inboxAccessible, navigationTab } = props;
// states
const [isMobileSidebar, setIsMobileSidebar] = useState(true);
// plane hooks
const { t } = useTranslation();
// hooks
const { loader, error, currentTab, handleCurrentTab, fetchInboxIssues } = useProjectInbox();
// derived values
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/intake/issue-detail" });
useEffect(() => {
if (!inboxAccessible || !workspaceSlug || !projectId) return;
@ -96,7 +101,7 @@ export const InboxIssueRoot: FC<TInboxIssueRoot> = observer((props) => {
/>
) : (
<div className="w-full h-full relative flex justify-center items-center">
<EmptyState type={EmptyStateType.INBOX_DETAIL_EMPTY_STATE} layout="screen-simple" />
<SimpleEmptyState title={t("inbox_issue.empty_state.detail.title")} assetPath={resolvedPath} />
</div>
)}
</div>

View file

@ -2,14 +2,14 @@
import { FC, useCallback, useEffect, useRef, useState } from "react";
import { observer } from "mobx-react";
import { TInboxIssueCurrentTab } from "@plane/types";
// plane imports
import { TInboxIssueCurrentTab } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { Header, Loader, EHeaderVariant } from "@plane/ui";
// components
import { EmptyState } from "@/components/empty-state";
import { SimpleEmptyState } from "@/components/empty-state";
import { FiltersRoot, InboxIssueAppliedFilters, InboxIssueList } from "@/components/inbox";
import { InboxSidebarLoader } from "@/components/ui";
// constants
import { EmptyStateType } from "@/constants/empty-state";
// helpers
import { cn } from "@/helpers/common.helper";
import { EInboxIssueCurrentTab } from "@/helpers/inbox.helper";
@ -17,6 +17,7 @@ import { EInboxIssueCurrentTab } from "@/helpers/inbox.helper";
import { useProject, useProjectInbox } from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router";
import { useIntersectionObserver } from "@/hooks/use-intersection-observer";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
type IInboxSidebarProps = {
workspaceSlug: string;
@ -25,22 +26,26 @@ type IInboxSidebarProps = {
setIsMobileSidebar: (value: boolean) => void;
};
const tabNavigationOptions: { key: TInboxIssueCurrentTab; label: string }[] = [
const tabNavigationOptions: { key: TInboxIssueCurrentTab; i18n_label: string }[] = [
{
key: EInboxIssueCurrentTab.OPEN,
label: "Open",
i18n_label: "inbox_issue.tabs.open",
},
{
key: EInboxIssueCurrentTab.CLOSED,
label: "Closed",
i18n_label: "inbox_issue.tabs.closed",
},
];
export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
const { workspaceSlug, projectId, inboxIssueId, setIsMobileSidebar } = props;
// router
const router = useAppRouter();
// ref
const containerRef = useRef<HTMLDivElement>(null);
const [elementRef, setElementRef] = useState<HTMLDivElement | null>(null);
// plane hooks
const { t } = useTranslation();
// store
const { currentProjectDetails } = useProject();
const {
@ -52,8 +57,11 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
fetchInboxPaginationIssues,
getAppliedFiltersCount,
} = useProjectInbox();
const router = useAppRouter();
// derived values
const sidebarAssetPath = useResolvedAssetPath({ basePath: "/empty-state/intake/intake-issue" });
const sidebarFilterAssetPath = useResolvedAssetPath({
basePath: "/empty-state/intake/filter-issue",
});
const fetchNextPages = useCallback(() => {
if (!workspaceSlug || !projectId) return;
@ -91,7 +99,7 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
}
}}
>
<div>{option?.label}</div>
<div>{t(option?.i18n_label)}</div>
{option?.key === "open" && currentTab === option?.key && (
<div className="rounded-full p-1.5 py-0.5 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-semibold">
{inboxIssuePaginationInfo?.total_results || 0}
@ -128,16 +136,25 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
/>
) : (
<div className="flex items-center justify-center h-full w-full">
<EmptyState
type={
getAppliedFiltersCount > 0
? EmptyStateType.INBOX_SIDEBAR_FILTER_EMPTY_STATE
: currentTab === EInboxIssueCurrentTab.OPEN
? EmptyStateType.INBOX_SIDEBAR_OPEN_TAB
: EmptyStateType.INBOX_SIDEBAR_CLOSED_TAB
}
layout="screen-simple"
/>
{getAppliedFiltersCount > 0 ? (
<SimpleEmptyState
title={t("inbox_issue.empty_state.sidebar_filter.title")}
description={t("inbox_issue.empty_state.sidebar_filter.description")}
assetPath={sidebarFilterAssetPath}
/>
) : currentTab === EInboxIssueCurrentTab.OPEN ? (
<SimpleEmptyState
title={t("inbox_issue.empty_state.sidebar_open_tab.title")}
description={t("inbox_issue.empty_state.sidebar_open_tab.description")}
assetPath={sidebarAssetPath}
/>
) : (
<SimpleEmptyState
title={t("inbox_issue.empty_state.sidebar_closed_tab.title")}
description={t("inbox_issue.empty_state.sidebar_closed_tab.description")}
assetPath={sidebarAssetPath}
/>
)}
</div>
)}
<div ref={setElementRef}>