From 00c15da2087e11aee508feb43b5978a4ac887a92 Mon Sep 17 00:00:00 2001 From: Vamsi krishna Date: Mon, 16 Dec 2024 18:09:45 +0530 Subject: [PATCH 1/7] enhancement:added functionality to add labels directly from dropdown --- .../issues/issue-detail/label/root.tsx | 17 +------ .../label/select/label-select.tsx | 48 +++++++++++++++---- .../issues/issue-detail/label/select/root.tsx | 1 + .../issue-layouts/properties/labels.tsx | 47 ++++++++++++++---- 4 files changed, 81 insertions(+), 32 deletions(-) diff --git a/web/core/components/issues/issue-detail/label/root.tsx b/web/core/components/issues/issue-detail/label/root.tsx index f71e9ba3ce2..f31defafd44 100644 --- a/web/core/components/issues/issue-detail/label/root.tsx +++ b/web/core/components/issues/issue-detail/label/root.tsx @@ -7,13 +7,12 @@ import { IIssueLabel, TIssue, TIssueServiceType } from "@plane/types"; // components import { TOAST_TYPE, setToast } from "@plane/ui"; // hooks -import { useIssueDetail, useLabel, useProjectInbox, useUserPermissions } from "@/hooks/store"; +import { useIssueDetail, useLabel, useProjectInbox } from "@/hooks/store"; // ui // types -import { LabelList, LabelCreate, IssueLabelSelectRoot } from "./"; +import { LabelList, IssueLabelSelectRoot } from "./"; // TODO: Fix this import statement, as core should not import from ee // eslint-disable-next-line import/order -import { EUserPermissions, EUserPermissionsLevel } from "ee/constants/user-permissions"; export type TIssueLabel = { workspaceSlug: string; @@ -47,9 +46,7 @@ export const IssueLabel: FC = observer((props) => { issue: { getIssueById }, } = useIssueDetail(issueServiceType); const { getIssueInboxByIssueId } = useProjectInbox(); - const { allowPermissions } = useUserPermissions(); - const canCreateLabel = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT); const issue = isInboxIssue ? getIssueInboxByIssueId(issueId)?.issue : getIssueById(issueId); const labelOperations: TLabelOperations = useMemo( @@ -113,16 +110,6 @@ export const IssueLabel: FC = observer((props) => { labelOperations={labelOperations} /> )} - - {!disabled && canCreateLabel && ( - - )} ); }); diff --git a/web/core/components/issues/issue-detail/label/select/label-select.tsx b/web/core/components/issues/issue-detail/label/select/label-select.tsx index 7fd700398c1..523a2453c69 100644 --- a/web/core/components/issues/issue-detail/label/select/label-select.tsx +++ b/web/core/components/issues/issue-detail/label/select/label-select.tsx @@ -1,33 +1,40 @@ import { Fragment, useState } from "react"; import { observer } from "mobx-react"; import { usePopper } from "react-popper"; -import { Check, Search, Tag } from "lucide-react"; +import { Check, Loader, Search, Tag } from "lucide-react"; import { Combobox } from "@headlessui/react"; // helpers +import { IIssueLabel } from "@plane/types"; +import { getRandomLabelColor } from "@/constants/label"; import { getTabIndex } from "@/helpers/tab-indices.helper"; // hooks -import { useLabel } from "@/hooks/store"; +import { useLabel, useUserPermissions } from "@/hooks/store"; import { usePlatformOS } from "@/hooks/use-platform-os"; -// components - +//constants +import { EUserPermissions, EUserPermissionsLevel } from "ee/constants/user-permissions"; export interface IIssueLabelSelect { workspaceSlug: string; projectId: string; issueId: string; values: string[]; onSelect: (_labelIds: string[]) => void; + onAddLabel: (workspaceSlug: string, projectId: string, data: Partial) => Promise; } export const IssueLabelSelect: React.FC = observer((props) => { - const { workspaceSlug, projectId, issueId, values, onSelect } = props; + const { workspaceSlug, projectId, issueId, values, onSelect, onAddLabel } = props; // store hooks const { isMobile } = usePlatformOS(); const { fetchProjectLabels, getProjectLabels } = useLabel(); + const { allowPermissions } = useUserPermissions(); // states const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); const [isLoading, setIsLoading] = useState(false); const [query, setQuery] = useState(""); + const [submitting, setSubmitting] = useState(false); + + const canCreateLabel = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT); const projectLabels = getProjectLabels(projectId); @@ -83,11 +90,25 @@ export const IssueLabelSelect: React.FC = observer((props) => ); - const searchInputKeyDown = (e: React.KeyboardEvent) => { + const searchInputKeyDown = async (e: React.KeyboardEvent) => { if (query !== "" && e.key === "Escape") { e.stopPropagation(); setQuery(""); } + + if (query !== "" && e.key === "Enter") { + e.stopPropagation(); + e.preventDefault(); + await handleAddLabel(query); + } + }; + + const handleAddLabel = async (labelName: string) => { + setSubmitting(true); + const label = await onAddLabel(workspaceSlug, projectId, { name: labelName, color: getRandomLabelColor() }); + onSelect([...values, label.id]); + setQuery(""); + setSubmitting(false); }; if (!issueId || !values) return <>; @@ -159,10 +180,19 @@ export const IssueLabelSelect: React.FC = observer((props) => )} )) + ) : submitting ? ( + + ) : canCreateLabel ? ( +

{ + handleAddLabel(query); + }} + className="text-left text-custom-text-200 cursor-pointer" + > + + Add "{query}" to labels +

) : ( - -

No matching results

-
+

No matching results.

)} diff --git a/web/core/components/issues/issue-detail/label/select/root.tsx b/web/core/components/issues/issue-detail/label/select/root.tsx index 00f96522b1c..a57a58742c2 100644 --- a/web/core/components/issues/issue-detail/label/select/root.tsx +++ b/web/core/components/issues/issue-detail/label/select/root.tsx @@ -26,6 +26,7 @@ export const IssueLabelSelectRoot: FC = (props) => { issueId={issueId} values={values} onSelect={handleLabel} + onAddLabel={labelOperations.createLabel} /> ); }; diff --git a/web/core/components/issues/issue-layouts/properties/labels.tsx b/web/core/components/issues/issue-layouts/properties/labels.tsx index 823137cb88f..a2f18c72ff2 100644 --- a/web/core/components/issues/issue-layouts/properties/labels.tsx +++ b/web/core/components/issues/issue-layouts/properties/labels.tsx @@ -1,11 +1,11 @@ "use client"; -import { Fragment, useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { Placement } from "@popperjs/core"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { usePopper } from "react-popper"; -import { Check, ChevronDown, Search, Tags } from "lucide-react"; +import { Check, ChevronDown, Loader, Search, Tags } from "lucide-react"; import { Combobox } from "@headlessui/react"; // plane helpers import { useOutsideClickDetector } from "@plane/hooks"; @@ -14,9 +14,12 @@ import { IIssueLabel } from "@plane/types"; // ui import { ComboDropDown, Tooltip } from "@plane/ui"; // hooks -import { useLabel } from "@/hooks/store"; +import { getRandomLabelColor } from "@/constants/label"; +import { useLabel, useUserPermissions } from "@/hooks/store"; import { useDropdownKeyDown } from "@/hooks/use-dropdown-key-down"; import { usePlatformOS } from "@/hooks/use-platform-os"; +// constants +import { EUserPermissions, EUserPermissionsLevel } from "ee/constants/user-permissions"; export interface IIssuePropertyLabels { projectId: string | null; @@ -62,6 +65,7 @@ export const IssuePropertyLabels: React.FC = observer((pro // states const [query, setQuery] = useState(""); const [isOpen, setIsOpen] = useState(false); + const [submitting, setSubmitting] = useState(false); // refs const dropdownRef = useRef(null); const inputRef = useRef(null); @@ -70,9 +74,12 @@ export const IssuePropertyLabels: React.FC = observer((pro const [popperElement, setPopperElement] = useState(null); const [isLoading, setIsLoading] = useState(false); // store hooks - const { fetchProjectLabels, getProjectLabels } = useLabel(); + const { fetchProjectLabels, getProjectLabels, createLabel } = useLabel(); const { isMobile } = usePlatformOS(); const storeLabels = getProjectLabels(projectId); + const { allowPermissions } = useUserPermissions(); + + const canCreateLabel = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT); const onOpen = () => { if (!storeLabels && workspaceSlug && projectId) @@ -102,11 +109,17 @@ export const IssuePropertyLabels: React.FC = observer((pro useOutsideClickDetector(dropdownRef, handleClose); - const searchInputKeyDown = (e: React.KeyboardEvent) => { + const searchInputKeyDown = async (e: React.KeyboardEvent) => { if (query !== "" && e.key === "Escape") { e.stopPropagation(); setQuery(""); } + + if (query !== "" && e.key === "Enter") { + e.stopPropagation(); + e.preventDefault(); + await handleAddLabel(query); + } }; useEffect(() => { @@ -249,6 +262,15 @@ export const IssuePropertyLabels: React.FC = observer((pro ); + const handleAddLabel = async (labelName: string) => { + if (!projectId) return; + setSubmitting(true); + const label = await createLabel(workspaceSlug, projectId, { name: labelName, color: getRandomLabelColor() }); + onChange([...value, label.id]); + setQuery(""); + setSubmitting(false); + }; + return ( = observer((pro )} )) + ) : submitting ? ( + + ) : canCreateLabel ? ( +

{ + handleAddLabel(query); + }} + className="text-left text-custom-text-200 cursor-pointer" + > + + Add "{query}" to labels +

) : ( - -

No matching results

-
+

No matching results.

)} From 8b178a54e271053a143ba62aef966ec4f9d565d4 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Sun, 15 Dec 2024 10:51:50 +0530 Subject: [PATCH 2/7] fix: adding language support package --- packages/i18n/.eslintignore | 3 ++ packages/i18n/.eslintrc.js | 9 +++++ packages/i18n/.prettierignore | 4 ++ packages/i18n/.prettierrc | 5 +++ packages/i18n/package.json | 20 ++++++++++ packages/i18n/src/config/index.ts | 16 ++++++++ packages/i18n/src/hooks/index.ts | 1 + packages/i18n/src/hooks/use-translation.ts | 39 +++++++++++++++++++ packages/i18n/src/index.ts | 2 + .../i18n/src/locales/en/translations.json | 35 +++++++++++++++++ .../i18n/src/locales/fr/translations.json | 38 ++++++++++++++++++ packages/i18n/tsconfig.json | 10 +++++ web/app/profile/page.tsx | 22 +++++++---- web/next.config.js | 1 + web/package.json | 1 + 15 files changed, 199 insertions(+), 7 deletions(-) create mode 100644 packages/i18n/.eslintignore create mode 100644 packages/i18n/.eslintrc.js create mode 100644 packages/i18n/.prettierignore create mode 100644 packages/i18n/.prettierrc create mode 100644 packages/i18n/package.json create mode 100644 packages/i18n/src/config/index.ts create mode 100644 packages/i18n/src/hooks/index.ts create mode 100644 packages/i18n/src/hooks/use-translation.ts create mode 100644 packages/i18n/src/index.ts create mode 100644 packages/i18n/src/locales/en/translations.json create mode 100644 packages/i18n/src/locales/fr/translations.json create mode 100644 packages/i18n/tsconfig.json diff --git a/packages/i18n/.eslintignore b/packages/i18n/.eslintignore new file mode 100644 index 00000000000..6019047c3e5 --- /dev/null +++ b/packages/i18n/.eslintignore @@ -0,0 +1,3 @@ +build/* +dist/* +out/* \ No newline at end of file diff --git a/packages/i18n/.eslintrc.js b/packages/i18n/.eslintrc.js new file mode 100644 index 00000000000..558b8f76ed4 --- /dev/null +++ b/packages/i18n/.eslintrc.js @@ -0,0 +1,9 @@ +/** @type {import("eslint").Linter.Config} */ +module.exports = { + root: true, + extends: ["@plane/eslint-config/library.js"], + parser: "@typescript-eslint/parser", + parserOptions: { + project: true, + }, +}; diff --git a/packages/i18n/.prettierignore b/packages/i18n/.prettierignore new file mode 100644 index 00000000000..d5be669c5e0 --- /dev/null +++ b/packages/i18n/.prettierignore @@ -0,0 +1,4 @@ +.turbo +out/ +dist/ +build/ \ No newline at end of file diff --git a/packages/i18n/.prettierrc b/packages/i18n/.prettierrc new file mode 100644 index 00000000000..87d988f1b26 --- /dev/null +++ b/packages/i18n/.prettierrc @@ -0,0 +1,5 @@ +{ + "printWidth": 120, + "tabWidth": 2, + "trailingComma": "es5" +} diff --git a/packages/i18n/package.json b/packages/i18n/package.json new file mode 100644 index 00000000000..0a4d0562797 --- /dev/null +++ b/packages/i18n/package.json @@ -0,0 +1,20 @@ +{ + "name": "@plane/i18n", + "version": "0.24.1", + "description": "I18n shared across multiple apps internally", + "private": true, + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "lint": "eslint src --ext .ts,.tsx", + "lint:errors": "eslint src --ext .ts,.tsx --quiet" + }, + "dependencies": { + "@plane/utils": "*" + }, + "devDependencies": { + "@plane/eslint-config": "*", + "@types/node": "^22.5.4", + "typescript": "^5.3.3" + } +} diff --git a/packages/i18n/src/config/index.ts b/packages/i18n/src/config/index.ts new file mode 100644 index 00000000000..edd62071bd6 --- /dev/null +++ b/packages/i18n/src/config/index.ts @@ -0,0 +1,16 @@ +import en from "../locales/en/translations.json"; +import fr from "../locales/fr/translations.json"; + +export type Language = (typeof languages)[number]; +export type Translations = { + [key: string]: { + [key: string]: string; + }; +}; + +export const fallbackLng = "en"; +export const languages = ["en", "fr"] as const; +export const translations: Translations = { + en, + fr, +}; diff --git a/packages/i18n/src/hooks/index.ts b/packages/i18n/src/hooks/index.ts new file mode 100644 index 00000000000..fb4e297e216 --- /dev/null +++ b/packages/i18n/src/hooks/index.ts @@ -0,0 +1 @@ +export * from "./use-translation"; diff --git a/packages/i18n/src/hooks/use-translation.ts b/packages/i18n/src/hooks/use-translation.ts new file mode 100644 index 00000000000..6400b01c21f --- /dev/null +++ b/packages/i18n/src/hooks/use-translation.ts @@ -0,0 +1,39 @@ +import { useState, useEffect } from "react"; +import { translations, Language, fallbackLng, languages } from "../config"; + +export function useTranslation() { + const [currentLocale, setCurrentLocale] = useState(fallbackLng); + + useEffect(() => { + // Try to get language from localStorage + const savedLocale = localStorage.getItem("userLanguage") as Language; + if (savedLocale && languages.includes(savedLocale)) { + setCurrentLocale(savedLocale); + } else { + // Get browser language + const browserLang = navigator.language.split("-")[0] as Language; + const newLocale = languages.includes(browserLang) ? browserLang : fallbackLng; + localStorage.setItem("userLanguage", newLocale); + setCurrentLocale(newLocale); + } + }, []); + + const t = (key: string) => { + console.log("key", key); + const translatedValue = translations[currentLocale]?.[key] || translations[fallbackLng][key] || key; + console.log("translatedValue", translatedValue); + return translatedValue; + }; + + const changeLanguage = (lng: Language) => { + localStorage.setItem("userLanguage", lng); + setCurrentLocale(lng); + }; + + return { + t, + currentLocale, + changeLanguage, + languages, + }; +} diff --git a/packages/i18n/src/index.ts b/packages/i18n/src/index.ts new file mode 100644 index 00000000000..b784e73e91a --- /dev/null +++ b/packages/i18n/src/index.ts @@ -0,0 +1,2 @@ +export * from "./config"; +export * from "./hooks"; diff --git a/packages/i18n/src/locales/en/translations.json b/packages/i18n/src/locales/en/translations.json new file mode 100644 index 00000000000..df0640ea306 --- /dev/null +++ b/packages/i18n/src/locales/en/translations.json @@ -0,0 +1,35 @@ +{ + "submit": "Submit", + "cancel": "Cancel", + "loading": "Loading", + "error": "Error", + "success": "Success", + "warning": "Warning", + "info": "Info", + "close": "Close", + "yes": "Yes", + "no": "No", + "ok": "OK", + "name": "Name", + "description": "Description", + "search": "Search", + "add_member": "Add member", + "remove_member": "Remove member", + "add_members": "Add members", + "remove_members": "Remove members", + "add": "Add", + "remove": "Remove", + "add_new": "Add new", + "remove_selected": "Remove selected", + "first_name": "First name", + "last_name": "Last name", + "email": "Email", + "display_name": "Display name", + "role": "Role", + "timezone": "Timezone", + "avatar": "Avatar", + "cover_image": "Cover image", + "password": "Password", + "change_cover": "Change cover", + "language": "Language" +} diff --git a/packages/i18n/src/locales/fr/translations.json b/packages/i18n/src/locales/fr/translations.json new file mode 100644 index 00000000000..73624ff72ee --- /dev/null +++ b/packages/i18n/src/locales/fr/translations.json @@ -0,0 +1,38 @@ +{ + "submit": "Soumettre", + "cancel": "Annuler", + "loading": "Chargement", + "error": "Erreur", + "success": "Succès", + "warning": "Avertissement", + "info": "Info", + "close": "Fermer", + "yes": "Oui", + "no": "Non", + "ok": "OK", + "name": "Nom", + "description": "Description", + "search": "Rechercher", + "add_member": "Ajouter un membre", + "remove_member": "Supprimer le membre", + "add_members": "Ajouter des membres", + "remove_members": "Supprimer des membres", + "add": "Ajouter", + "remove": "Supprimer", + "add_new": "Ajouter un nouveau", + "remove_selected": "Supprimer les sélectionnés", + "first_name": "Prénom", + "last_name": "Nom", + "email": "Email", + "display_name": "Nom d'affichage", + "role": "Rôle", + "timezone": "Fuseau horaire", + "avatar": "Avatar", + "member": "Membre", + "members": "Membres", + "invite": "Inviter", + "invite_members": "Inviter des membres", + "invite_new_members": "Inviter des nouveaux membres", + "change_cover": "Changer la couverture", + "language": "Langue" +} diff --git a/packages/i18n/tsconfig.json b/packages/i18n/tsconfig.json new file mode 100644 index 00000000000..6599e6e82aa --- /dev/null +++ b/packages/i18n/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@plane/typescript-config/react-library.json", + "compilerOptions": { + "jsx": "react", + "lib": ["esnext", "dom"], + "resolveJsonModule": true + }, + "include": ["./src"], + "exclude": ["dist", "build", "node_modules"] +} diff --git a/web/app/profile/page.tsx b/web/app/profile/page.tsx index 1dd9702a36b..c8a5a8cfc55 100644 --- a/web/app/profile/page.tsx +++ b/web/app/profile/page.tsx @@ -5,6 +5,7 @@ import { observer } from "mobx-react"; import { Controller, useForm } from "react-hook-form"; import { ChevronDown, CircleUserRound } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; +import { useTranslation } from "@plane/i18n"; import type { IUser } from "@plane/types"; import { Button, @@ -45,6 +46,8 @@ const ProfileSettingsPage = observer(() => { const [isLoading, setIsLoading] = useState(false); const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false); const [deactivateAccountModal, setDeactivateAccountModal] = useState(false); + // language support + const { t } = useTranslation(); // form info const { handleSubmit, @@ -224,7 +227,8 @@ const ProfileSettingsPage = observer(() => {

- First name* + {t("first_name")} + *

{ {errors.first_name && {errors.first_name.message}}
-

Last name

+

{t("last_name")}

{

- Display name* + {t("display_name")} + *

{

- Email* + {t("email")} + *

{

- Role* + {t("role")} + *

{

- Timezone* + {t("timezone")} + *

{
-

Language

+

{t("language")}

{ // if (!isServer) { // // Ensure that all imports of 'yjs' resolve to the same instance diff --git a/web/package.json b/web/package.json index cd0a5ddcca5..d37ae8aaf97 100644 --- a/web/package.json +++ b/web/package.json @@ -31,6 +31,7 @@ "@plane/constants": "*", "@plane/editor": "*", "@plane/hooks": "*", + "@plane/i18n": "*", "@plane/types": "*", "@plane/ui": "*", "@plane/utils": "*", From f00a7c6f2571f211c886489ddc4f37aeb4f8ab05 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Mon, 16 Dec 2024 17:56:15 +0530 Subject: [PATCH 3/7] fix: language support implementation using mobx --- packages/i18n/src/components/index.tsx | 29 ++ packages/i18n/src/components/store.ts | 38 ++ packages/i18n/src/config/index.ts | 11 + packages/i18n/src/hooks/use-translation.ts | 44 +- packages/i18n/src/index.ts | 1 + .../i18n/src/locales/en/translations.json | 4 +- .../i18n/src/locales/fr/translations.json | 4 +- packages/types/src/users.d.ts | 2 +- web/app/profile/page.tsx | 445 +--------------- web/app/provider.tsx | 23 +- web/core/components/profile/form.tsx | 493 ++++++++++++++++++ web/core/components/profile/index.ts | 5 +- web/core/lib/wrappers/store-wrapper.tsx | 11 +- 13 files changed, 618 insertions(+), 492 deletions(-) create mode 100644 packages/i18n/src/components/index.tsx create mode 100644 packages/i18n/src/components/store.ts create mode 100644 web/core/components/profile/form.tsx diff --git a/packages/i18n/src/components/index.tsx b/packages/i18n/src/components/index.tsx new file mode 100644 index 00000000000..e94246d081a --- /dev/null +++ b/packages/i18n/src/components/index.tsx @@ -0,0 +1,29 @@ +import React, { createContext, useEffect } from "react"; +import { observer } from "mobx-react"; +import { TranslationStore } from "./store"; +import { Language, languages } from "../config"; + +// Create the store instance +const translationStore = new TranslationStore(); + +// Create Context +export const TranslationContext = createContext(translationStore); + +export const TranslationProvider = observer(({ children }: any) => { + // Handle storage events for cross-tab synchronization + useEffect(() => { + const handleStorageChange = (event: StorageEvent) => { + if (event.key === "userLanguage" && event.newValue) { + const newLang = event.newValue as Language; + if (languages.includes(newLang)) { + translationStore.setLanguage(newLang); + } + } + }; + + window.addEventListener("storage", handleStorageChange); + return () => window.removeEventListener("storage", handleStorageChange); + }, []); + + return {children}; +}); diff --git a/packages/i18n/src/components/store.ts b/packages/i18n/src/components/store.ts new file mode 100644 index 00000000000..bf1934a6535 --- /dev/null +++ b/packages/i18n/src/components/store.ts @@ -0,0 +1,38 @@ +import { makeObservable, observable } from "mobx"; +import { Language, fallbackLng, languages, translations } from "../config"; + +export class TranslationStore { + currentLocale: Language = fallbackLng; + + constructor() { + makeObservable(this, { + currentLocale: observable.ref, + }); + this.initializeLanguage(); + } + + get availableLanguages() { + return languages; + } + + t(key: string) { + return translations[this.currentLocale]?.[key] || translations[fallbackLng][key] || key; + } + + setLanguage(lng: Language) { + localStorage.setItem("userLanguage", lng); + this.currentLocale = lng; + } + + initializeLanguage() { + if (typeof window === "undefined") return; + const savedLocale = localStorage.getItem("userLanguage") as Language; + if (savedLocale && languages.includes(savedLocale)) { + this.setLanguage(savedLocale); + } else { + const browserLang = navigator.language.split("-")[0] as Language; + const newLocale = languages.includes(browserLang as Language) ? (browserLang as Language) : fallbackLng; + this.setLanguage(newLocale); + } + } +} diff --git a/packages/i18n/src/config/index.ts b/packages/i18n/src/config/index.ts index edd62071bd6..a789825ec91 100644 --- a/packages/i18n/src/config/index.ts +++ b/packages/i18n/src/config/index.ts @@ -14,3 +14,14 @@ export const translations: Translations = { en, fr, }; + +export const SUPPORTED_LANGUAGES = [ + { + label: "English", + value: "en", + }, + { + label: "French", + value: "fr", + }, +]; diff --git a/packages/i18n/src/hooks/use-translation.ts b/packages/i18n/src/hooks/use-translation.ts index 6400b01c21f..f947d1d5eb5 100644 --- a/packages/i18n/src/hooks/use-translation.ts +++ b/packages/i18n/src/hooks/use-translation.ts @@ -1,39 +1,17 @@ -import { useState, useEffect } from "react"; -import { translations, Language, fallbackLng, languages } from "../config"; +import { useContext } from "react"; +import { TranslationContext } from "../components"; +import { Language } from "../config"; export function useTranslation() { - const [currentLocale, setCurrentLocale] = useState(fallbackLng); - - useEffect(() => { - // Try to get language from localStorage - const savedLocale = localStorage.getItem("userLanguage") as Language; - if (savedLocale && languages.includes(savedLocale)) { - setCurrentLocale(savedLocale); - } else { - // Get browser language - const browserLang = navigator.language.split("-")[0] as Language; - const newLocale = languages.includes(browserLang) ? browserLang : fallbackLng; - localStorage.setItem("userLanguage", newLocale); - setCurrentLocale(newLocale); - } - }, []); - - const t = (key: string) => { - console.log("key", key); - const translatedValue = translations[currentLocale]?.[key] || translations[fallbackLng][key] || key; - console.log("translatedValue", translatedValue); - return translatedValue; - }; - - const changeLanguage = (lng: Language) => { - localStorage.setItem("userLanguage", lng); - setCurrentLocale(lng); - }; + const store = useContext(TranslationContext); + if (!store) { + throw new Error("useTranslation must be used within a TranslationProvider"); + } return { - t, - currentLocale, - changeLanguage, - languages, + t: (key: string) => store.t(key), + currentLocale: store.currentLocale, + changeLanguage: (lng: Language) => store.setLanguage(lng), + languages: store.availableLanguages, }; } diff --git a/packages/i18n/src/index.ts b/packages/i18n/src/index.ts index b784e73e91a..639ef4b59a4 100644 --- a/packages/i18n/src/index.ts +++ b/packages/i18n/src/index.ts @@ -1,2 +1,3 @@ export * from "./config"; +export * from "./components"; export * from "./hooks"; diff --git a/packages/i18n/src/locales/en/translations.json b/packages/i18n/src/locales/en/translations.json index df0640ea306..7eec69af328 100644 --- a/packages/i18n/src/locales/en/translations.json +++ b/packages/i18n/src/locales/en/translations.json @@ -31,5 +31,7 @@ "cover_image": "Cover image", "password": "Password", "change_cover": "Change cover", - "language": "Language" + "language": "Language", + "saving": "Saving...", + "save_changes": "Save changes" } diff --git a/packages/i18n/src/locales/fr/translations.json b/packages/i18n/src/locales/fr/translations.json index 73624ff72ee..683a429958a 100644 --- a/packages/i18n/src/locales/fr/translations.json +++ b/packages/i18n/src/locales/fr/translations.json @@ -34,5 +34,7 @@ "invite_members": "Inviter des membres", "invite_new_members": "Inviter des nouveaux membres", "change_cover": "Changer la couverture", - "language": "Langue" + "language": "Langue", + "saving": "Enregistrement...", + "save_changes": "Enregistrer les modifications" } diff --git a/packages/types/src/users.d.ts b/packages/types/src/users.d.ts index 452bc23c238..c562e7c246b 100644 --- a/packages/types/src/users.d.ts +++ b/packages/types/src/users.d.ts @@ -25,7 +25,6 @@ export interface IUser extends IUserLite { is_password_autoset: boolean; is_tour_completed: boolean; mobile_number: string | null; - role: string | null; last_workspace_id: string; user_timezone: string; username: string; @@ -62,6 +61,7 @@ export type TUserProfile = { billing_address_country: string | undefined; billing_address: string | undefined; has_billing_address: boolean; + language: string; created_at: Date | string; updated_at: Date | string; }; diff --git a/web/app/profile/page.tsx b/web/app/profile/page.tsx index c8a5a8cfc55..1a4470ea331 100644 --- a/web/app/profile/page.tsx +++ b/web/app/profile/page.tsx @@ -1,143 +1,16 @@ "use client"; -import React, { useEffect, useState } from "react"; import { observer } from "mobx-react"; -import { Controller, useForm } from "react-hook-form"; -import { ChevronDown, CircleUserRound } from "lucide-react"; -import { Disclosure, Transition } from "@headlessui/react"; -import { useTranslation } from "@plane/i18n"; -import type { IUser } from "@plane/types"; -import { - Button, - CustomSelect, - CustomSearchSelect, - Input, - TOAST_TYPE, - setPromiseToast, - setToast, - Tooltip, -} from "@plane/ui"; // components -import { DeactivateAccountModal } from "@/components/account"; import { LogoSpinner } from "@/components/common"; -import { ImagePickerPopover, UserImageUploadModal, PageHead } from "@/components/core"; -import { ProfileSettingContentWrapper } from "@/components/profile"; -// constants -import { TIME_ZONES, TTimezone } from "@/constants/timezones"; -import { USER_ROLES } from "@/constants/workspace"; -// helpers -import { getFileURL } from "@/helpers/file.helper"; +import { PageHead } from "@/components/core"; +import { ProfileSettingContentWrapper, ProfileForm } from "@/components/profile"; // hooks import { useUser } from "@/hooks/store"; -const defaultValues: Partial = { - avatar_url: "", - cover_image_url: "", - first_name: "", - last_name: "", - display_name: "", - email: "", - role: "Product / Project Manager", - user_timezone: "Asia/Kolkata", -}; - const ProfileSettingsPage = observer(() => { - // states - const [isLoading, setIsLoading] = useState(false); - const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false); - const [deactivateAccountModal, setDeactivateAccountModal] = useState(false); - // language support - const { t } = useTranslation(); - // form info - const { - handleSubmit, - reset, - watch, - control, - setValue, - formState: { errors }, - } = useForm({ defaultValues }); - // derived values - const userAvatar = watch("avatar_url"); - const userCover = watch("cover_image_url"); // store hooks - const { data: currentUser, updateCurrentUser } = useUser(); - - useEffect(() => { - reset({ ...defaultValues, ...currentUser }); - }, [currentUser, reset]); - - const onSubmit = async (formData: IUser) => { - setIsLoading(true); - const payload: Partial = { - first_name: formData.first_name, - last_name: formData.last_name, - avatar_url: formData.avatar_url, - role: formData.role, - display_name: formData?.display_name, - user_timezone: formData.user_timezone, - }; - // if unsplash or a pre-defined image is uploaded, delete the old uploaded asset - if (formData.cover_image_url?.startsWith("http")) { - payload.cover_image = formData.cover_image_url; - payload.cover_image_asset = null; - } - - const updateCurrentUserDetail = updateCurrentUser(payload).finally(() => setIsLoading(false)); - setPromiseToast(updateCurrentUserDetail, { - loading: "Updating...", - success: { - title: "Success!", - message: () => `Profile updated successfully.`, - }, - error: { - title: "Error!", - message: () => `There was some error in updating your profile. Please try again.`, - }, - }); - }; - - const handleDelete = async (url: string | null | undefined) => { - if (!url) return; - - await updateCurrentUser({ - avatar_url: "", - }) - .then(() => { - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Profile picture deleted successfully.", - }); - setValue("avatar_url", ""); - }) - .catch(() => { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "There was some error in deleting your profile picture. Please try again.", - }); - }) - .finally(() => { - setIsImageUploadModalOpen(false); - }); - }; - - const getTimeZoneLabel = (timezone: TTimezone | undefined) => { - if (!timezone) return undefined; - return ( -
- {timezone.gmtOffset} - {timezone.name} -
- ); - }; - - const timeZoneOptions = TIME_ZONES.map((timeZone) => ({ - value: timeZone.value, - query: timeZone.name + " " + timeZone.gmtOffset + " " + timeZone.value, - content: getTimeZoneLabel(timeZone), - })); + const { data: currentUser, userProfile } = useUser(); if (!currentUser) return ( @@ -150,317 +23,7 @@ const ProfileSettingsPage = observer(() => { <> - ( - setIsImageUploadModalOpen(false)} - handleRemove={async () => await handleDelete(currentUser?.avatar_url)} - onSuccess={(url) => { - onChange(url); - handleSubmit(onSubmit)(); - setIsImageUploadModalOpen(false); - }} - value={value && value.trim() !== "" ? value : null} - /> - )} - /> - setDeactivateAccountModal(false)} /> -
-
-
- {currentUser?.first_name -
-
-
- -
-
-
-
- ( - onChange(imageUrl)} - control={control} - value={value ?? "https://images.unsplash.com/photo-1506383796573-caf02b4a79ab"} - isProfileCover - /> - )} - /> -
-
-
-
-
- {`${watch("first_name")} ${watch("last_name")}`} -
- {watch("email")} -
-
-
-
-
-

- {t("first_name")} - * -

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

{t("last_name")}

- ( - - )} - /> -
-
-

- {t("display_name")} - * -

- { - if (value.trim().length < 1) return "Display name can't be empty."; - if (value.split(" ").length > 1) return "Display name can't have two consecutive spaces."; - if (value.replace(/\s/g, "").length < 1) - return "Display name must be at least 1 character long."; - if (value.replace(/\s/g, "").length > 20) - return "Display name must be less than 20 characters long."; - return true; - }, - }} - render={({ field: { value, onChange, ref } }) => ( - - )} - /> - {errors?.display_name && ( - {errors?.display_name?.message} - )} -
-
-

- {t("email")} - * -

- ( - - )} - /> -
-
-

- {t("role")} - * -

- ( - - {USER_ROLES.map((item) => ( - - {item.label} - - ))} - - )} - /> - {errors.role && Please select a role} -
-
-
-
-
-
-

- {t("timezone")} - * -

- ( - t.value === value)) ?? value) - : "Select a timezone" - } - options={timeZoneOptions} - onChange={onChange} - buttonClassName={errors.user_timezone ? "border-red-500" : ""} - className="rounded-md border-[0.5px] !border-custom-border-200" - optionsClassName="w-72" - input - /> - )} - /> - {errors.user_timezone && {errors.user_timezone.message}} -
- -
-

{t("language")}

- {}} - className="rounded-md bg-custom-background-90" - input - disabled - /> -
-
-
-
- -
-
-
-
- - {({ open }) => ( - <> - - Deactivate account - - - - -
- - When deactivating an account, all of the data and resources within that account will be - permanently removed and cannot be recovered. - -
- -
-
-
-
- - )} -
+
); diff --git a/web/app/provider.tsx b/web/app/provider.tsx index dba975a6373..34526ffd14c 100644 --- a/web/app/provider.tsx +++ b/web/app/provider.tsx @@ -4,7 +4,8 @@ import { FC, ReactNode } from "react"; import dynamic from "next/dynamic"; import { useTheme, ThemeProvider } from "next-themes"; import { SWRConfig } from "swr"; -// ui +// Plane Imports +import { TranslationProvider } from "@plane/i18n"; import { Toast } from "@plane/ui"; // constants import { SWR_CONFIG } from "@/constants/swr-config"; @@ -41,15 +42,17 @@ export const AppProvider: FC = (props) => { - - - - - {children} - - - - + + + + + + {children} + + + + + diff --git a/web/core/components/profile/form.tsx b/web/core/components/profile/form.tsx new file mode 100644 index 00000000000..5b3fbcaf05e --- /dev/null +++ b/web/core/components/profile/form.tsx @@ -0,0 +1,493 @@ +import React, { useState } from "react"; +import { observer } from "mobx-react"; +import { Controller, useForm } from "react-hook-form"; +import { ChevronDown, CircleUserRound } from "lucide-react"; +import { Disclosure, Transition } from "@headlessui/react"; +import { useTranslation, SUPPORTED_LANGUAGES } from "@plane/i18n"; +import type { IUser, TUserProfile } from "@plane/types"; +import { + Button, + CustomSelect, + CustomSearchSelect, + Input, + TOAST_TYPE, + setPromiseToast, + setToast, + Tooltip, +} from "@plane/ui"; +// components +import { DeactivateAccountModal } from "@/components/account"; +import { ImagePickerPopover, UserImageUploadModal } from "@/components/core"; +// constants +import { TIME_ZONES, TTimezone } from "@/constants/timezones"; +import { USER_ROLES } from "@/constants/workspace"; +// helpers +import { getFileURL } from "@/helpers/file.helper"; +// hooks +import { useUser, useUserProfile } from "@/hooks/store"; + +type TUserProfileForm = { + avatar_url: string; + cover_image: string; + cover_image_asset: any; + cover_image_url: string; + first_name: string; + last_name: string; + display_name: string; + email: string; + role: string; + language: string; + user_timezone: string; +}; + +export type TProfileFormProps = { + user: IUser; + profile: TUserProfile; +}; + +export const ProfileForm = observer((props: TProfileFormProps) => { + const { user, profile } = props; + // states + const [isLoading, setIsLoading] = useState(false); + const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false); + const [deactivateAccountModal, setDeactivateAccountModal] = useState(false); + // language support + const { t } = useTranslation(); + // form info + const { + handleSubmit, + watch, + control, + setValue, + formState: { errors }, + } = useForm({ + defaultValues: { + avatar_url: user.avatar_url || "", + cover_image_asset: null, + cover_image_url: user.cover_image_url || "", + first_name: user.first_name || "", + last_name: user.last_name || "", + display_name: user.display_name || "", + email: user.email || "", + role: profile.role || "Product / Project Manager", + language: profile.language || "en", + user_timezone: "Asia/Kolkata", + }, + }); + // derived values + const userAvatar = watch("avatar_url"); + const userCover = watch("cover_image_url"); + // store hooks + const { data: currentUser, updateCurrentUser } = useUser(); + const { updateUserProfile } = useUserProfile(); + + const getLanguageLabel = (value: string) => { + const selectedLanguage = SUPPORTED_LANGUAGES.find((l) => l.value === value); + if (!selectedLanguage) return value; + return selectedLanguage.label; + }; + + const getTimeZoneLabel = (timezone: TTimezone | undefined) => { + if (!timezone) return undefined; + return ( +
+ {timezone.gmtOffset} + {timezone.name} +
+ ); + }; + + const timeZoneOptions = TIME_ZONES.map((timeZone) => ({ + value: timeZone.value, + query: timeZone.name + " " + timeZone.gmtOffset + " " + timeZone.value, + content: getTimeZoneLabel(timeZone), + })); + + const handleProfilePictureDelete = async (url: string | null | undefined) => { + if (!url) return; + await updateCurrentUser({ + avatar_url: "", + }) + .then(() => { + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Success!", + message: "Profile picture deleted successfully.", + }); + setValue("avatar_url", ""); + }) + .catch(() => { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "There was some error in deleting your profile picture. Please try again.", + }); + }) + .finally(() => { + setIsImageUploadModalOpen(false); + }); + }; + + const onSubmit = async (formData: TUserProfileForm) => { + setIsLoading(true); + const userPayload: Partial = { + first_name: formData.first_name, + last_name: formData.last_name, + avatar_url: formData.avatar_url, + display_name: formData?.display_name, + user_timezone: formData.user_timezone, + }; + // if unsplash or a pre-defined image is uploaded, delete the old uploaded asset + if (formData.cover_image_url?.startsWith("http")) { + userPayload.cover_image = formData.cover_image_url; + userPayload.cover_image_asset = null; + } + + const profilePayload: Partial = { + role: formData.role, + language: formData.language, + }; + + const updateCurrentUserDetail = updateCurrentUser(userPayload).finally(() => setIsLoading(false)); + const updateCurrentUserProfile = updateUserProfile(profilePayload).finally(() => setIsLoading(false)); + + const promises = [updateCurrentUserDetail, updateCurrentUserProfile]; + const updateUserAndProfile = Promise.all(promises); + + setPromiseToast(updateUserAndProfile, { + loading: "Updating...", + success: { + title: "Success!", + message: () => `Profile updated successfully.`, + }, + error: { + title: "Error!", + message: () => `There was some error in updating your profile. Please try again.`, + }, + }); + }; + + return ( + <> + setDeactivateAccountModal(false)} /> + ( + setIsImageUploadModalOpen(false)} + handleRemove={async () => await handleProfilePictureDelete(currentUser?.avatar_url)} + onSuccess={(url) => { + onChange(url); + handleSubmit(onSubmit)(); + setIsImageUploadModalOpen(false); + }} + value={value && value.trim() !== "" ? value : null} + /> + )} + /> +
+
+
+ {currentUser?.first_name +
+
+
+ +
+
+
+
+ ( + onChange(imageUrl)} + control={control} + value={value ?? "https://images.unsplash.com/photo-1506383796573-caf02b4a79ab"} + isProfileCover + /> + )} + /> +
+
+
+
+
+ {`${watch("first_name")} ${watch("last_name")}`} +
+ {watch("email")} +
+
+
+
+
+

+ {t("first_name")}  + * +

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

{t("last_name")}

+ ( + + )} + /> +
+
+

+ {t("display_name")}  + * +

+ { + if (value.trim().length < 1) return "Display name can't be empty."; + if (value.split(" ").length > 1) return "Display name can't have two consecutive spaces."; + if (value.replace(/\s/g, "").length < 1) return "Display name must be at least 1 character long."; + if (value.replace(/\s/g, "").length > 20) + return "Display name must be less than 20 characters long."; + return true; + }, + }} + render={({ field: { value, onChange, ref } }) => ( + + )} + /> + {errors?.display_name && {errors?.display_name?.message}} +
+
+

+ {t("email")}  + * +

+ ( + + )} + /> +
+
+

+ {t("role")}  + * +

+ ( + + {USER_ROLES.map((item) => ( + + {item.label} + + ))} + + )} + /> + {errors.role && Please select a role} +
+
+
+
+
+
+

+ {t("timezone")}  + * +

+ ( + t.value === value)) ?? value) + : "Select a timezone" + } + options={timeZoneOptions} + onChange={onChange} + buttonClassName={errors.user_timezone ? "border-red-500" : ""} + className="rounded-md border-[0.5px] !border-custom-border-200" + optionsClassName="w-72" + input + /> + )} + /> + {errors.user_timezone && {errors.user_timezone.message}} +
+
+

{t("language")}

+ ( + + {SUPPORTED_LANGUAGES.map((item) => ( + + {item.label} + + ))} + + )} + /> +
+
+
+ +
+
+
+
+ + {({ open }) => ( + <> + + Deactivate account + + + + +
+ + When deactivating an account, all of the data and resources within that account will be permanently + removed and cannot be recovered. + +
+ +
+
+
+
+ + )} +
+ + ); +}); diff --git a/web/core/components/profile/index.ts b/web/core/components/profile/index.ts index d0d33af96ae..e5495aba1f5 100644 --- a/web/core/components/profile/index.ts +++ b/web/core/components/profile/index.ts @@ -3,5 +3,6 @@ export * from "./overview"; export * from "./profile-issues-filter"; export * from "./sidebar"; export * from "./time"; -export * from "./profile-setting-content-wrapper" -export * from "./profile-setting-content-header" \ No newline at end of file +export * from "./profile-setting-content-wrapper"; +export * from "./profile-setting-content-header"; +export * from "./form"; diff --git a/web/core/lib/wrappers/store-wrapper.tsx b/web/core/lib/wrappers/store-wrapper.tsx index fa60c354e3c..eb8f7325ba1 100644 --- a/web/core/lib/wrappers/store-wrapper.tsx +++ b/web/core/lib/wrappers/store-wrapper.tsx @@ -1,7 +1,8 @@ -import { ReactNode, useEffect, FC, useState } from "react"; +import { ReactNode, useEffect, FC } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { useTheme } from "next-themes"; +import { useTranslation, Language } from "@plane/i18n"; // helpers import { applyTheme, unsetCustomCssVariables } from "@/helpers/theme.helper"; // hooks @@ -21,6 +22,7 @@ const StoreWrapper: FC = observer((props) => { const { setQuery } = useRouterParams(); const { sidebarCollapsed, toggleSidebar } = useAppTheme(); const { data: userProfile } = useUserProfile(); + const { changeLanguage } = useTranslation(); /** * Sidebar collapsed fetching from local storage @@ -28,7 +30,6 @@ const StoreWrapper: FC = observer((props) => { useEffect(() => { const localValue = localStorage && localStorage.getItem("app_sidebar_collapsed"); const localBoolValue = localValue ? (localValue === "true" ? true : false) : false; - if (localValue && sidebarCollapsed === undefined) toggleSidebar(localBoolValue); }, [sidebarCollapsed, setTheme, toggleSidebar]); @@ -37,7 +38,6 @@ const StoreWrapper: FC = observer((props) => { */ useEffect(() => { if (!userProfile?.theme?.theme) return; - const currentTheme = userProfile?.theme?.theme || "system"; const currentThemePalette = userProfile?.theme?.palette; if (currentTheme) { @@ -51,6 +51,11 @@ const StoreWrapper: FC = observer((props) => { } }, [userProfile?.theme?.theme, userProfile?.theme?.palette, setTheme]); + useEffect(() => { + if (!userProfile?.language) return; + changeLanguage(userProfile?.language as Language); + }, [userProfile?.language, changeLanguage]); + useEffect(() => { if (!params) return; setQuery(params); From fd1011c7f37accb0da8f3747bff2f36d2c917a98 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Mon, 16 Dec 2024 18:34:07 +0530 Subject: [PATCH 4/7] fix: adding more languages for support --- packages/i18n/src/config/index.ts | 14 ++++++- .../i18n/src/locales/en/translations.json | 4 +- .../i18n/src/locales/es/translations.json | 39 +++++++++++++++++++ .../i18n/src/locales/fr/translations.json | 27 +++++++------ .../i18n/src/locales/ja/translations.json | 39 +++++++++++++++++++ web/core/components/profile/form.tsx | 9 ++--- 6 files changed, 110 insertions(+), 22 deletions(-) create mode 100644 packages/i18n/src/locales/es/translations.json create mode 100644 packages/i18n/src/locales/ja/translations.json diff --git a/packages/i18n/src/config/index.ts b/packages/i18n/src/config/index.ts index a789825ec91..3f55d8cf6f0 100644 --- a/packages/i18n/src/config/index.ts +++ b/packages/i18n/src/config/index.ts @@ -1,5 +1,7 @@ import en from "../locales/en/translations.json"; import fr from "../locales/fr/translations.json"; +import es from "../locales/es/translations.json"; +import ja from "../locales/ja/translations.json"; export type Language = (typeof languages)[number]; export type Translations = { @@ -9,10 +11,12 @@ export type Translations = { }; export const fallbackLng = "en"; -export const languages = ["en", "fr"] as const; +export const languages = ["en", "fr", "es", "ja"] as const; export const translations: Translations = { en, fr, + es, + ja, }; export const SUPPORTED_LANGUAGES = [ @@ -24,4 +28,12 @@ export const SUPPORTED_LANGUAGES = [ label: "French", value: "fr", }, + { + label: "Spanish", + value: "es", + }, + { + label: "Japanese", + value: "ja", + }, ]; diff --git a/packages/i18n/src/locales/en/translations.json b/packages/i18n/src/locales/en/translations.json index 7eec69af328..8b798a6aa96 100644 --- a/packages/i18n/src/locales/en/translations.json +++ b/packages/i18n/src/locales/en/translations.json @@ -33,5 +33,7 @@ "change_cover": "Change cover", "language": "Language", "saving": "Saving...", - "save_changes": "Save changes" + "save_changes": "Save changes", + "deactivate_account": "Deactivate account", + "deactivate_account_description": "When deactivating an account, all of the data and resources within that account will be permanently removed and cannot be recovered." } diff --git a/packages/i18n/src/locales/es/translations.json b/packages/i18n/src/locales/es/translations.json new file mode 100644 index 00000000000..b49c1c060f5 --- /dev/null +++ b/packages/i18n/src/locales/es/translations.json @@ -0,0 +1,39 @@ +{ + "submit": "Enviar", + "cancel": "Cancelar", + "loading": "Cargando", + "error": "Error", + "success": "Éxito", + "warning": "Advertencia", + "info": "Información", + "close": "Cerrar", + "yes": "Sí", + "no": "No", + "ok": "OK", + "name": "Nombre", + "description": "Descripción", + "search": "Buscar", + "add_member": "Agregar miembro", + "remove_member": "Eliminar miembro", + "add_members": "Agregar miembros", + "remove_members": "Eliminar miembros", + "add": "Agregar", + "remove": "Eliminar", + "add_new": "Agregar nuevo", + "remove_selected": "Eliminar seleccionados", + "first_name": "Nombre", + "last_name": "Apellido", + "email": "Correo electrónico", + "display_name": "Nombre para mostrar", + "role": "Rol", + "timezone": "Zona horaria", + "avatar": "Avatar", + "cover_image": "Imagen de portada", + "password": "Contraseña", + "change_cover": "Cambiar portada", + "language": "Idioma", + "saving": "Guardando...", + "save_changes": "Guardar cambios", + "deactivate_account": "Desactivar cuenta", + "deactivate_account_description": "Al desactivar una cuenta, todos los datos y recursos dentro de esa cuenta se eliminarán permanentemente y no se podrán recuperar." +} diff --git a/packages/i18n/src/locales/fr/translations.json b/packages/i18n/src/locales/fr/translations.json index 683a429958a..14fc0bbdc01 100644 --- a/packages/i18n/src/locales/fr/translations.json +++ b/packages/i18n/src/locales/fr/translations.json @@ -3,7 +3,7 @@ "cancel": "Annuler", "loading": "Chargement", "error": "Erreur", - "success": "Succès", + "success": "Succès", "warning": "Avertissement", "info": "Info", "close": "Fermer", @@ -14,27 +14,26 @@ "description": "Description", "search": "Rechercher", "add_member": "Ajouter un membre", - "remove_member": "Supprimer le membre", + "remove_member": "Supprimer un membre", "add_members": "Ajouter des membres", "remove_members": "Supprimer des membres", "add": "Ajouter", "remove": "Supprimer", - "add_new": "Ajouter un nouveau", - "remove_selected": "Supprimer les sélectionnés", - "first_name": "Prénom", - "last_name": "Nom", + "add_new": "Ajouter nouveau", + "remove_selected": "Supprimer la sélection", + "first_name": "Prénom", + "last_name": "Nom de famille", "email": "Email", "display_name": "Nom d'affichage", - "role": "Rôle", + "role": "Rôle", "timezone": "Fuseau horaire", "avatar": "Avatar", - "member": "Membre", - "members": "Membres", - "invite": "Inviter", - "invite_members": "Inviter des membres", - "invite_new_members": "Inviter des nouveaux membres", - "change_cover": "Changer la couverture", + "cover_image": "Image de couverture", + "password": "Mot de passe", + "change_cover": "Modifier la couverture", "language": "Langue", "saving": "Enregistrement...", - "save_changes": "Enregistrer les modifications" + "save_changes": "Enregistrer les modifications", + "deactivate_account": "Désactiver le compte", + "deactivate_account_description": "Lors de la désactivation d'un compte, toutes les données et ressources de ce compte seront définitivement supprimées et ne pourront pas être récupérées." } diff --git a/packages/i18n/src/locales/ja/translations.json b/packages/i18n/src/locales/ja/translations.json new file mode 100644 index 00000000000..ff3a6d12026 --- /dev/null +++ b/packages/i18n/src/locales/ja/translations.json @@ -0,0 +1,39 @@ +{ + "submit": "送信", + "cancel": "キャンセル", + "loading": "読み込み中", + "error": "エラー", + "success": "成功", + "warning": "警告", + "info": "情報", + "close": "閉じる", + "yes": "はい", + "no": "いいえ", + "ok": "OK", + "name": "名前", + "description": "説明", + "search": "検索", + "add_member": "メンバーを追加", + "remove_member": "メンバーを削除", + "add_members": "メンバーを追加", + "remove_members": "メンバーを削除", + "add": "追加", + "remove": "削除", + "add_new": "新規追加", + "remove_selected": "選択項目を削除", + "first_name": "名", + "last_name": "姓", + "email": "メールアドレス", + "display_name": "表示名", + "role": "役割", + "timezone": "タイムゾーン", + "avatar": "アバター", + "cover_image": "カバー画像", + "password": "パスワード", + "change_cover": "カバーを変更", + "language": "言語", + "saving": "保存中...", + "save_changes": "変更を保存", + "deactivate_account": "アカウントを無効化", + "deactivate_account_description": "アカウントを無効化すると、そのアカウント内のすべてのデータとリソースが完全に削除され、復元することはできません。" +} diff --git a/web/core/components/profile/form.tsx b/web/core/components/profile/form.tsx index 5b3fbcaf05e..ae2679c6b77 100644 --- a/web/core/components/profile/form.tsx +++ b/web/core/components/profile/form.tsx @@ -459,7 +459,7 @@ export const ProfileForm = observer((props: TProfileFormProps) => { {({ open }) => ( <> - Deactivate account + {t("deactivate_account")} { >
- - When deactivating an account, all of the data and resources within that account will be permanently - removed and cannot be recovered. - + {t("deactivate_account_description")}
From 6172c64e0268d5c584cc73c3ffab2bdadf3b88d7 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Mon, 16 Dec 2024 18:52:23 +0530 Subject: [PATCH 5/7] fix: profile settings translations --- .../i18n/src/locales/en/translations.json | 12 ++++++++++- .../i18n/src/locales/es/translations.json | 12 ++++++++++- .../i18n/src/locales/fr/translations.json | 12 ++++++++++- .../i18n/src/locales/ja/translations.json | 12 ++++++++++- web/app/profile/sidebar.tsx | 21 ++++++++++--------- 5 files changed, 55 insertions(+), 14 deletions(-) diff --git a/packages/i18n/src/locales/en/translations.json b/packages/i18n/src/locales/en/translations.json index 8b798a6aa96..98b5377a087 100644 --- a/packages/i18n/src/locales/en/translations.json +++ b/packages/i18n/src/locales/en/translations.json @@ -35,5 +35,15 @@ "saving": "Saving...", "save_changes": "Save changes", "deactivate_account": "Deactivate account", - "deactivate_account_description": "When deactivating an account, all of the data and resources within that account will be permanently removed and cannot be recovered." + "deactivate_account_description": "When deactivating an account, all of the data and resources within that account will be permanently removed and cannot be recovered.", + "profile_settings": "Profile settings", + "your_account": "Your account", + "profile": "Profile", + "security": "Security", + "activity": "Activity", + "appearance": "Appearance", + "notifications": "Notifications", + "workspaces": "Workspaces", + "create_workspace": "Create workspace", + "invitations": "Invitations" } diff --git a/packages/i18n/src/locales/es/translations.json b/packages/i18n/src/locales/es/translations.json index b49c1c060f5..47d98d2d0a9 100644 --- a/packages/i18n/src/locales/es/translations.json +++ b/packages/i18n/src/locales/es/translations.json @@ -35,5 +35,15 @@ "saving": "Guardando...", "save_changes": "Guardar cambios", "deactivate_account": "Desactivar cuenta", - "deactivate_account_description": "Al desactivar una cuenta, todos los datos y recursos dentro de esa cuenta se eliminarán permanentemente y no se podrán recuperar." + "deactivate_account_description": "Al desactivar una cuenta, todos los datos y recursos dentro de esa cuenta se eliminarán permanentemente y no se podrán recuperar.", + "profile_settings": "Configuración de perfil", + "your_account": "Tu cuenta", + "profile": "Perfil", + "security": "Seguridad", + "activity": "Actividad", + "appearance": "Apariencia", + "notifications": "Notificaciones", + "workspaces": "Workspaces", + "create_workspace": "Crear workspace", + "invitations": "Invitaciones" } diff --git a/packages/i18n/src/locales/fr/translations.json b/packages/i18n/src/locales/fr/translations.json index 14fc0bbdc01..5acc0f457c9 100644 --- a/packages/i18n/src/locales/fr/translations.json +++ b/packages/i18n/src/locales/fr/translations.json @@ -35,5 +35,15 @@ "saving": "Enregistrement...", "save_changes": "Enregistrer les modifications", "deactivate_account": "Désactiver le compte", - "deactivate_account_description": "Lors de la désactivation d'un compte, toutes les données et ressources de ce compte seront définitivement supprimées et ne pourront pas être récupérées." + "deactivate_account_description": "Lors de la désactivation d'un compte, toutes les données et ressources de ce compte seront définitivement supprimées et ne pourront pas être récupérées.", + "profile_settings": "Paramètres du profil", + "your_account": "Votre compte", + "profile": "Profil", + "security": " Sécurité", + "activity": "Activité", + "appearance": "Apparence", + "notifications": "Notifications", + "workspaces": "Workspaces", + "create_workspace": "Créer un workspace", + "invitations": "Invitations" } diff --git a/packages/i18n/src/locales/ja/translations.json b/packages/i18n/src/locales/ja/translations.json index ff3a6d12026..8a12214d019 100644 --- a/packages/i18n/src/locales/ja/translations.json +++ b/packages/i18n/src/locales/ja/translations.json @@ -35,5 +35,15 @@ "saving": "保存中...", "save_changes": "変更を保存", "deactivate_account": "アカウントを無効化", - "deactivate_account_description": "アカウントを無効化すると、そのアカウント内のすべてのデータとリソースが完全に削除され、復元することはできません。" + "deactivate_account_description": "アカウントを無効化すると、そのアカウント内のすべてのデータとリソースが完全に削除され、復元することはできません。", + "profile_settings": "プロフィール設定", + "your_account": "アカウント", + "profile": "プロフィール", + "security": "セキュリティ", + "activity": "アクティビティ", + "appearance": "アピアンス", + "notifications": "通知", + "workspaces": "ワークスペース", + "create_workspace": "ワークスペースを作成", + "invitations": "招待" } diff --git a/web/app/profile/sidebar.tsx b/web/app/profile/sidebar.tsx index 479ef21f515..2f456415760 100644 --- a/web/app/profile/sidebar.tsx +++ b/web/app/profile/sidebar.tsx @@ -6,9 +6,9 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; // icons import { ChevronLeft, LogOut, MoveLeft, Plus, UserPlus } from "lucide-react"; -// plane helpers +// plane imports import { useOutsideClickDetector } from "@plane/hooks"; -// ui +import { useTranslation } from "@plane/i18n"; import { TOAST_TYPE, Tooltip, setToast } from "@plane/ui"; // components import { SidebarNavItem } from "@/components/sidebar"; @@ -23,7 +23,7 @@ import { usePlatformOS } from "@/hooks/use-platform-os"; const WORKSPACE_ACTION_LINKS = [ { - key: "create-workspace", + key: "create_workspace", Icon: Plus, label: "Create workspace", href: "/create-workspace", @@ -47,6 +47,7 @@ export const ProfileLayoutSidebar = observer(() => { const { data: currentUserSettings } = useUserSettings(); const { workspaces } = useWorkspace(); const { isMobile } = usePlatformOS(); + const { t } = useTranslation(); const workspacesList = Object.values(workspaces ?? {}); @@ -117,13 +118,13 @@ export const ProfileLayoutSidebar = observer(() => { {!sidebarCollapsed && ( -

Profile settings

+

{t("profile_settings")}

)}
{!sidebarCollapsed && ( -
Your account
+
{t("your_account")}
)}
{PROFILE_ACTION_LINKS.map((link) => { @@ -132,7 +133,7 @@ export const ProfileLayoutSidebar = observer(() => { return ( { >
- {!sidebarCollapsed &&

{link.label}

} + {!sidebarCollapsed &&

{t(link.key)}

}
@@ -156,7 +157,7 @@ export const ProfileLayoutSidebar = observer(() => {
{!sidebarCollapsed && ( -
Workspaces
+
{t("workspaces")}
)} {workspacesList && workspacesList.length > 0 && (
{ {WORKSPACE_ACTION_LINKS.map((link) => ( { }`} > {} - {!sidebarCollapsed && link.label} + {!sidebarCollapsed && t(link.key)}
From d38b637f6fbc1bac6a2259d1f3f591185a256f95 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Mon, 16 Dec 2024 19:01:37 +0530 Subject: [PATCH 6/7] refactor: enhance workspace and project wrapper modularity (#6207) --- packages/ui/src/modals/constants.ts | 1 + web/app/[workspaceSlug]/(projects)/layout.tsx | 3 +- .../(projects)/projects/(detail)/layout.tsx | 4 +-- web/ce/layouts/project-wrapper.tsx | 15 +++++++++ web/ce/layouts/workspace-wrapper.tsx | 15 +++++++++ .../settings/invitations-list-item.tsx | 4 +-- .../layouts/auth-layout/project-wrapper.tsx | 31 +++++++++---------- .../layouts/auth-layout/workspace-wrapper.tsx | 17 +++++----- web/ee/layouts/project-wrapper.tsx | 1 + web/ee/layouts/workspace-wrapper.tsx | 1 + 10 files changed, 62 insertions(+), 30 deletions(-) create mode 100644 web/ce/layouts/project-wrapper.tsx create mode 100644 web/ce/layouts/workspace-wrapper.tsx create mode 100644 web/ee/layouts/project-wrapper.tsx create mode 100644 web/ee/layouts/workspace-wrapper.tsx diff --git a/packages/ui/src/modals/constants.ts b/packages/ui/src/modals/constants.ts index fe72ef7aea1..e1eccd94129 100644 --- a/packages/ui/src/modals/constants.ts +++ b/packages/ui/src/modals/constants.ts @@ -13,4 +13,5 @@ export enum EModalWidth { XXXXL = "sm:max-w-4xl", VXL = "sm:max-w-5xl", VIXL = "sm:max-w-6xl", + VIIXL = "sm:max-w-7xl", } diff --git a/web/app/[workspaceSlug]/(projects)/layout.tsx b/web/app/[workspaceSlug]/(projects)/layout.tsx index f8fe0f8f9e0..340ec57d0d0 100644 --- a/web/app/[workspaceSlug]/(projects)/layout.tsx +++ b/web/app/[workspaceSlug]/(projects)/layout.tsx @@ -1,8 +1,9 @@ "use client"; import { CommandPalette } from "@/components/command-palette"; -import { WorkspaceAuthWrapper } from "@/layouts/auth-layout"; import { AuthenticationWrapper } from "@/lib/wrappers"; +// plane web components +import { WorkspaceAuthWrapper } from "@/plane-web/layouts/workspace-wrapper"; import { AppSidebar } from "./sidebar"; export default function WorkspaceLayout({ children }: { children: React.ReactNode }) { diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/layout.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/layout.tsx index fc2ec0075f3..cdd4f708077 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/layout.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/layout.tsx @@ -1,8 +1,8 @@ "use client"; import { ReactNode } from "react"; -// layouts -import { ProjectAuthWrapper } from "@/layouts/auth-layout"; +// plane web layouts +import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper"; const ProjectDetailLayout = ({ children }: { children: ReactNode }) => ( {children} diff --git a/web/ce/layouts/project-wrapper.tsx b/web/ce/layouts/project-wrapper.tsx new file mode 100644 index 00000000000..a9223210994 --- /dev/null +++ b/web/ce/layouts/project-wrapper.tsx @@ -0,0 +1,15 @@ +import { FC } from "react"; +import { observer } from "mobx-react"; +// layouts +import { ProjectAuthWrapper as CoreProjectAuthWrapper } from "@/layouts/auth-layout"; + +export type IProjectAuthWrapper = { + children: React.ReactNode; +}; + +export const ProjectAuthWrapper: FC = observer((props) => { + // props + const { children } = props; + + return {children}; +}); diff --git a/web/ce/layouts/workspace-wrapper.tsx b/web/ce/layouts/workspace-wrapper.tsx new file mode 100644 index 00000000000..fcde83e7f32 --- /dev/null +++ b/web/ce/layouts/workspace-wrapper.tsx @@ -0,0 +1,15 @@ +import { FC } from "react"; +import { observer } from "mobx-react"; +// layouts +import { WorkspaceAuthWrapper as CoreWorkspaceAuthWrapper } from "@/layouts/auth-layout"; + +export type IWorkspaceAuthWrapper = { + children: React.ReactNode; +}; + +export const WorkspaceAuthWrapper: FC = observer((props) => { + // props + const { children } = props; + + return {children}; +}); diff --git a/web/core/components/workspace/settings/invitations-list-item.tsx b/web/core/components/workspace/settings/invitations-list-item.tsx index 09ca653f349..bbbbcd2670f 100644 --- a/web/core/components/workspace/settings/invitations-list-item.tsx +++ b/web/core/components/workspace/settings/invitations-list-item.tsx @@ -120,11 +120,11 @@ export const WorkspaceInvitationsListItem: FC = observer((props) => { updateMemberInvitation(workspaceSlug.toString(), invitationDetails.id, { role: value, - }).catch(() => { + }).catch((error) => { setToast({ type: TOAST_TYPE.ERROR, title: "Error!", - message: "An error occurred while updating member role. Please try again.", + message: error?.error || "An error occurred while updating member role. Please try again.", }); }); }} diff --git a/web/core/layouts/auth-layout/project-wrapper.tsx b/web/core/layouts/auth-layout/project-wrapper.tsx index 4cca052573a..dcba2a8fef2 100644 --- a/web/core/layouts/auth-layout/project-wrapper.tsx +++ b/web/core/layouts/auth-layout/project-wrapper.tsx @@ -34,12 +34,14 @@ import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/u interface IProjectAuthWrapper { children: ReactNode; + isLoading?: boolean; } export const ProjectAuthWrapper: FC = observer((props) => { - const { children } = props; - // store - // const { fetchInboxes } = useInbox(); + const { children, isLoading: isParentLoading = false } = props; + // router + const { workspaceSlug, projectId } = useParams(); + // store hooks const { toggleCreateProjectModal } = useCommandPalette(); const { setTrackElement } = useEventTracker(); const { fetchUserProjectInfo, allowPermissions, projectUserInfo } = useUserPermissions(); @@ -54,14 +56,20 @@ export const ProjectAuthWrapper: FC = observer((props) => { const { fetchProjectStates, fetchProjectStateTransitions } = useProjectState(); const { fetchProjectLabels } = useLabel(); const { getProjectEstimates } = useProjectEstimates(); - // router - const { workspaceSlug, projectId } = useParams(); - + // derived values + const projectExists = projectId ? getProjectById(projectId.toString()) : null; const projectMemberInfo = projectUserInfo?.[workspaceSlug?.toString()]?.[projectId?.toString()]; + const hasPermissionToCurrentProject = allowPermissions( + [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST], + EUserPermissionsLevel.PROJECT, + workspaceSlug.toString(), + projectId?.toString() + ); // Initialize module timeline chart useEffect(() => { initGantt(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useSWR( @@ -143,17 +151,8 @@ export const ProjectAuthWrapper: FC = observer((props) => { { revalidateIfStale: false, revalidateOnFocus: false } ); - // derived values - const projectExists = projectId ? getProjectById(projectId.toString()) : null; - const hasPermissionToCurrentProject = allowPermissions( - [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST], - EUserPermissionsLevel.PROJECT, - workspaceSlug.toString(), - projectId?.toString() - ); - // check if the project member apis is loading - if (!projectMemberInfo && projectId && hasPermissionToCurrentProject === null) + if (isParentLoading || (!projectMemberInfo && projectId && hasPermissionToCurrentProject === null)) return (
diff --git a/web/core/layouts/auth-layout/workspace-wrapper.tsx b/web/core/layouts/auth-layout/workspace-wrapper.tsx index 2dd658704e3..ab39dd76147 100644 --- a/web/core/layouts/auth-layout/workspace-wrapper.tsx +++ b/web/core/layouts/auth-layout/workspace-wrapper.tsx @@ -8,11 +8,12 @@ import { useParams } from "next/navigation"; import { useTheme } from "next-themes"; import useSWR from "swr"; import useSWRImmutable from "swr/immutable"; - +// ui import { LogOut } from "lucide-react"; -// hooks import { Button, setToast, TOAST_TYPE, Tooltip } from "@plane/ui"; +// components import { LogoSpinner } from "@/components/common"; +// hooks import { useMember, useProject, useUser, useUserPermissions, useWorkspace } from "@/hooks/store"; import { useFavorite } from "@/hooks/store/use-favorite"; import { usePlatformOS } from "@/hooks/use-platform-os"; @@ -25,12 +26,13 @@ import PlaneBlackLogo from "@/public/plane-logos/black-horizontal-with-blue-logo import PlaneWhiteLogo from "@/public/plane-logos/white-horizontal-with-blue-logo.png"; import WorkSpaceNotAvailable from "@/public/workspace/workspace-not-available.png"; -export interface IWorkspaceAuthWrapper { +interface IWorkspaceAuthWrapper { children: ReactNode; + isLoading?: boolean; } export const WorkspaceAuthWrapper: FC = observer((props) => { - const { children } = props; + const { children, isLoading: isParentLoading = false } = props; // router params const { workspaceSlug } = useParams(); // next themes @@ -51,11 +53,11 @@ export const WorkspaceAuthWrapper: FC = observer((props) [EUserPermissions.ADMIN, EUserPermissions.MEMBER], EUserPermissionsLevel.WORKSPACE ); - const planeLogo = resolvedTheme === "dark" ? PlaneWhiteLogo : PlaneBlackLogo; const allWorkspaces = workspaces ? Object.values(workspaces) : undefined; const currentWorkspace = (allWorkspaces && allWorkspaces.find((workspace) => workspace?.slug === workspaceSlug)) || undefined; + const currentWorkspaceInfo = workspaceSlug && workspaceInfoBySlug(workspaceSlug.toString()); // fetching user workspace information useSWR( @@ -116,11 +118,8 @@ export const WorkspaceAuthWrapper: FC = observer((props) ); }; - // derived values - const currentWorkspaceInfo = workspaceSlug && workspaceInfoBySlug(workspaceSlug.toString()); - // if list of workspaces are not there then we have to render the spinner - if (allWorkspaces === undefined || loader || isDBInitializing) { + if (isParentLoading || allWorkspaces === undefined || loader || isDBInitializing) { return (
diff --git a/web/ee/layouts/project-wrapper.tsx b/web/ee/layouts/project-wrapper.tsx new file mode 100644 index 00000000000..911665c9786 --- /dev/null +++ b/web/ee/layouts/project-wrapper.tsx @@ -0,0 +1 @@ +export * from "ce/layouts/project-wrapper"; diff --git a/web/ee/layouts/workspace-wrapper.tsx b/web/ee/layouts/workspace-wrapper.tsx new file mode 100644 index 00000000000..186266fc1ad --- /dev/null +++ b/web/ee/layouts/workspace-wrapper.tsx @@ -0,0 +1 @@ +export * from "ce/layouts/workspace-wrapper"; From afd2d6b453b7a359b7f52637f7a9e64424d4711f Mon Sep 17 00:00:00 2001 From: Vamsi krishna Date: Tue, 17 Dec 2024 13:45:27 +0530 Subject: [PATCH 7/7] fix:lint errors --- .../projects/(detail)/[projectId]/cycles/(list)/header.tsx | 2 -- .../projects/(detail)/[projectId]/modules/(list)/header.tsx | 2 -- .../projects/(detail)/[projectId]/views/(list)/header.tsx | 2 -- web/app/onboarding/page.tsx | 2 +- web/ce/components/de-dupe/de-dupe-button.tsx | 5 +---- web/core/components/issues/issue-layouts/kanban/block.tsx | 2 +- 6 files changed, 3 insertions(+), 12 deletions(-) diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx index ca3189a1fc7..c75f93d3e3b 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx @@ -2,7 +2,6 @@ import { FC } from "react"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; // ui import { Breadcrumbs, Button, ContrastIcon, Header } from "@plane/ui"; // components @@ -17,7 +16,6 @@ import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/u export const CyclesListHeader: FC = observer(() => { // router const router = useAppRouter(); - const { workspaceSlug } = useParams(); // store hooks const { toggleCreateCycleModal } = useCommandPalette(); const { setTrackElement } = useEventTracker(); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx index 6da84e56a7a..f8ed7104f11 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx @@ -1,7 +1,6 @@ "use client"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; // ui import { Breadcrumbs, Button, DiceIcon, Header } from "@plane/ui"; // components @@ -16,7 +15,6 @@ import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/u export const ModulesListHeader: React.FC = observer(() => { // router const router = useAppRouter(); - const { workspaceSlug } = useParams(); // store hooks const { toggleCreateModuleModal } = useCommandPalette(); const { setTrackElement } = useEventTracker(); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/header.tsx index 3cb73a33ec0..edc2fcf65f1 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/header.tsx @@ -1,7 +1,6 @@ "use client"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; import { Layers } from "lucide-react"; // ui import { Breadcrumbs, Button, Header } from "@plane/ui"; @@ -13,7 +12,6 @@ import { useCommandPalette, useProject } from "@/hooks/store"; export const ProjectViewsHeader = observer(() => { // router - const { workspaceSlug } = useParams(); // store hooks const { toggleCreateViewModal } = useCommandPalette(); const { currentProjectDetails, loader } = useProject(); diff --git a/web/app/onboarding/page.tsx b/web/app/onboarding/page.tsx index 6d915dfcbce..34415645cd9 100644 --- a/web/app/onboarding/page.tsx +++ b/web/app/onboarding/page.tsx @@ -44,7 +44,7 @@ const OnboardingPage = observer(() => { const workspacesList = Object.values(workspaces ?? {}); // fetching workspaces list const { isLoading: workspaceListLoader } = useSWR(USER_WORKSPACES_LIST, () => { - user?.id && fetchWorkspaces(); + if (user?.id) fetchWorkspaces(); }); // fetching user workspace invitations const { isLoading: invitationsLoader, data: invitations } = useSWR( diff --git a/web/ce/components/de-dupe/de-dupe-button.tsx b/web/ce/components/de-dupe/de-dupe-button.tsx index eaa4e3b7c8f..a37e6eb4a39 100644 --- a/web/ce/components/de-dupe/de-dupe-button.tsx +++ b/web/ce/components/de-dupe/de-dupe-button.tsx @@ -9,7 +9,4 @@ type TDeDupeButtonRoot = { label: string; }; -export const DeDupeButtonRoot: FC = (props) => { - const { workspaceSlug, isDuplicateModalOpen, label, handleOnClick } = props; - return <>; -}; +export const DeDupeButtonRoot: FC = () => <>; diff --git a/web/core/components/issues/issue-layouts/kanban/block.tsx b/web/core/components/issues/issue-layouts/kanban/block.tsx index d8f4307e855..5fbee1c0a73 100644 --- a/web/core/components/issues/issue-layouts/kanban/block.tsx +++ b/web/core/components/issues/issue-layouts/kanban/block.tsx @@ -6,6 +6,7 @@ import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-d import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // plane helpers +import { EIssueServiceType } from "@plane/constants"; import { useOutsideClickDetector } from "@plane/hooks"; // types import { TIssue, IIssueDisplayProperties, IIssueMap } from "@plane/types"; @@ -26,7 +27,6 @@ import { IssueIdentifier } from "@/plane-web/components/issues"; import { TRenderQuickActions } from "../list/list-view-types"; import { IssueProperties } from "../properties/all-properties"; import { getIssueBlockId } from "../utils"; -import { EIssueServiceType } from "@plane/constants"; interface IssueBlockProps { issueId: string;