From a55253d242bdb3e13b61c5c626655aa9b096b787 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Tue, 9 Dec 2025 23:35:52 +0530 Subject: [PATCH 1/6] chore: update package version --- apps/admin/package.json | 2 +- apps/api/package.json | 2 +- apps/live/package.json | 2 +- apps/space/package.json | 2 +- apps/web/package.json | 2 +- package.json | 2 +- packages/codemods/package.json | 2 +- packages/constants/package.json | 2 +- packages/editor/package.json | 2 +- packages/hooks/package.json | 2 +- packages/i18n/package.json | 2 +- packages/logger/package.json | 2 +- packages/propel/package.json | 2 +- packages/services/package.json | 2 +- packages/shared-state/package.json | 2 +- packages/tailwind-config/package.json | 2 +- packages/types/package.json | 2 +- packages/typescript-config/package.json | 2 +- packages/ui/package.json | 2 +- packages/utils/package.json | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/admin/package.json b/apps/admin/package.json index 0261d3485d..e81a3e073d 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -1,7 +1,7 @@ { "name": "admin", "description": "Admin UI for Plane", - "version": "1.1.0", + "version": "1.2.0", "license": "AGPL-3.0", "private": true, "type": "module", diff --git a/apps/api/package.json b/apps/api/package.json index ffecb3a73e..6e62f2bd11 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -1,6 +1,6 @@ { "name": "plane-api", - "version": "1.1.0", + "version": "1.2.0", "license": "AGPL-3.0", "private": true, "description": "API server powering Plane's backend" diff --git a/apps/live/package.json b/apps/live/package.json index ffe3c2bd44..b773efb444 100644 --- a/apps/live/package.json +++ b/apps/live/package.json @@ -1,6 +1,6 @@ { "name": "live", - "version": "1.1.0", + "version": "1.2.0", "license": "AGPL-3.0", "description": "A realtime collaborative server powers Plane's rich text editor", "main": "./dist/start.mjs", diff --git a/apps/space/package.json b/apps/space/package.json index fb2a68c4f9..73101a7f68 100644 --- a/apps/space/package.json +++ b/apps/space/package.json @@ -1,6 +1,6 @@ { "name": "space", - "version": "1.1.0", + "version": "1.2.0", "private": true, "license": "AGPL-3.0", "type": "module", diff --git a/apps/web/package.json b/apps/web/package.json index 96ed391df6..ed9730543e 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "web", - "version": "1.1.0", + "version": "1.2.0", "private": true, "license": "AGPL-3.0", "type": "module", diff --git a/package.json b/package.json index e838e41cab..b73f0acc85 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "plane", "description": "Open-source project management that unlocks customer value", "repository": "https://github.com/makeplane/plane.git", - "version": "1.1.0", + "version": "1.2.0", "license": "AGPL-3.0", "private": true, "scripts": { diff --git a/packages/codemods/package.json b/packages/codemods/package.json index 9d9c832708..e2bac61cd8 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -1,6 +1,6 @@ { "name": "@plane/codemods", - "version": "1.1.0", + "version": "1.2.0", "private": true, "scripts": { "test": "vitest run", diff --git a/packages/constants/package.json b/packages/constants/package.json index bc0a38ff60..9e1a2ca699 100644 --- a/packages/constants/package.json +++ b/packages/constants/package.json @@ -1,6 +1,6 @@ { "name": "@plane/constants", - "version": "1.1.0", + "version": "1.2.0", "private": true, "license": "AGPL-3.0", "type": "module", diff --git a/packages/editor/package.json b/packages/editor/package.json index 26717948e8..26faa7c78e 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@plane/editor", - "version": "1.1.0", + "version": "1.2.0", "description": "Core Editor that powers Plane", "license": "AGPL-3.0", "private": true, diff --git a/packages/hooks/package.json b/packages/hooks/package.json index b373c994e3..c78d17b3cc 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -1,6 +1,6 @@ { "name": "@plane/hooks", - "version": "1.1.0", + "version": "1.2.0", "license": "AGPL-3.0", "description": "React hooks that are shared across multiple apps internally", "private": true, diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 526dd16c8c..21fac2cbde 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@plane/i18n", - "version": "1.1.0", + "version": "1.2.0", "license": "AGPL-3.0", "description": "I18n shared across multiple apps internally", "private": true, diff --git a/packages/logger/package.json b/packages/logger/package.json index 1f6e721241..339a4c2f1c 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -1,6 +1,6 @@ { "name": "@plane/logger", - "version": "1.1.0", + "version": "1.2.0", "license": "AGPL-3.0", "description": "Logger shared across multiple apps internally", "private": true, diff --git a/packages/propel/package.json b/packages/propel/package.json index 31091544e3..74ca9036d6 100644 --- a/packages/propel/package.json +++ b/packages/propel/package.json @@ -1,6 +1,6 @@ { "name": "@plane/propel", - "version": "1.1.0", + "version": "1.2.0", "private": true, "license": "AGPL-3.0", "type": "module", diff --git a/packages/services/package.json b/packages/services/package.json index 3a317e1ad5..4972157479 100644 --- a/packages/services/package.json +++ b/packages/services/package.json @@ -1,6 +1,6 @@ { "name": "@plane/services", - "version": "1.1.0", + "version": "1.2.0", "license": "AGPL-3.0", "private": true, "type": "module", diff --git a/packages/shared-state/package.json b/packages/shared-state/package.json index ddbe5338fe..b3f7c25060 100644 --- a/packages/shared-state/package.json +++ b/packages/shared-state/package.json @@ -1,6 +1,6 @@ { "name": "@plane/shared-state", - "version": "1.1.0", + "version": "1.2.0", "license": "AGPL-3.0", "description": "Shared state shared across multiple apps internally", "private": true, diff --git a/packages/tailwind-config/package.json b/packages/tailwind-config/package.json index ada4cc5e81..9deb622cca 100644 --- a/packages/tailwind-config/package.json +++ b/packages/tailwind-config/package.json @@ -1,6 +1,6 @@ { "name": "@plane/tailwind-config", - "version": "1.1.0", + "version": "1.2.0", "license": "AGPL-3.0", "description": "common tailwind configuration across monorepo", "main": "tailwind.config.js", diff --git a/packages/types/package.json b/packages/types/package.json index 72251f3f8c..020b98bbe4 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@plane/types", - "version": "1.1.0", + "version": "1.2.0", "license": "AGPL-3.0", "private": true, "type": "module", diff --git a/packages/typescript-config/package.json b/packages/typescript-config/package.json index 45c6ac232a..c8d21d9ae5 100644 --- a/packages/typescript-config/package.json +++ b/packages/typescript-config/package.json @@ -1,6 +1,6 @@ { "name": "@plane/typescript-config", - "version": "1.1.0", + "version": "1.2.0", "license": "AGPL-3.0", "private": true, "files": [ diff --git a/packages/ui/package.json b/packages/ui/package.json index 40335a4b40..51fb33f6cf 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -2,7 +2,7 @@ "name": "@plane/ui", "description": "UI components shared across multiple apps internally", "private": true, - "version": "1.1.0", + "version": "1.2.0", "sideEffects": false, "license": "AGPL-3.0", "type": "module", diff --git a/packages/utils/package.json b/packages/utils/package.json index 94cbd39e35..1adaf323be 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@plane/utils", - "version": "1.1.0", + "version": "1.2.0", "description": "Helper functions shared across multiple apps internally", "license": "AGPL-3.0", "private": true, From 0bfb74d4c00e004d17b454ec2a99a5c4c1d3efd9 Mon Sep 17 00:00:00 2001 From: Vipin Chaudhary Date: Wed, 10 Dec 2025 00:24:36 +0530 Subject: [PATCH 2/6] [WIKI-830] fix: copy clipboard functionality in the editor (#8229) * feat: enhance clipboard functionality for markdown and HTML content * fix: improve error handling and state management in CustomImageNodeView component * fix: correct asset retrieval query by removing workspace filter in DuplicateAssetEndpoint * fix: update meta tag creation in PasteAssetPlugin for clipboard HTML content * feat: implement copyMarkdownToClipboard utility for enhanced clipboard functionality * refactor: replace copyMarkdownToClipboard utility with copyTextToClipboard for simplified clipboard operations * refactor: streamline clipboard operations by replacing copyTextToClipboard with copyMarkdownToClipboard in editor components * refactor: simplify PasteAssetPlugin by removing unnecessary meta tag handling and streamlining HTML processing * feat: implement asset duplication processing on paste for enhanced clipboard functionality * chore:remove async from copy markdown method * chore: add paste html * remove:prevent default * refactor: remove hasChanges from processAssetDuplication return type for simplified asset processing * fix: format options-dropdown.tsx --- apps/api/plane/app/views/asset/v2.py | 4 +- .../core/description-versions/modal.tsx | 13 ++-- .../pages/editor/toolbar/options-dropdown.tsx | 13 ++-- .../custom-image/components/node-view.tsx | 29 ++++--- .../editor/src/core/extensions/utility.ts | 3 - .../editor/src/core/helpers/editor-ref.ts | 21 +++++ .../editor/src/core/helpers/paste-asset.ts | 28 +++++++ .../src/core/plugins/markdown-clipboard.ts | 1 + .../editor/src/core/plugins/paste-asset.ts | 77 ------------------- packages/editor/src/core/props.ts | 13 ++++ packages/editor/src/core/types/editor.ts | 1 + 11 files changed, 96 insertions(+), 107 deletions(-) create mode 100644 packages/editor/src/core/helpers/paste-asset.ts delete mode 100644 packages/editor/src/core/plugins/paste-asset.ts diff --git a/apps/api/plane/app/views/asset/v2.py b/apps/api/plane/app/views/asset/v2.py index c0580c1149..b8b27eeae0 100644 --- a/apps/api/plane/app/views/asset/v2.py +++ b/apps/api/plane/app/views/asset/v2.py @@ -766,7 +766,7 @@ class DuplicateAssetEndpoint(BaseAPIView): return {} - @allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE") + @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def post(self, request, slug, asset_id): project_id = request.data.get("project_id", None) entity_id = request.data.get("entity_id", None) @@ -792,7 +792,7 @@ class DuplicateAssetEndpoint(BaseAPIView): storage = S3Storage(request=request) original_asset = FileAsset.objects.filter( - workspace=workspace, id=asset_id, is_uploaded=True + id=asset_id, is_uploaded=True ).first() if not original_asset: diff --git a/apps/web/core/components/core/description-versions/modal.tsx b/apps/web/core/components/core/description-versions/modal.tsx index fbb04b5c09..b393c80311 100644 --- a/apps/web/core/components/core/description-versions/modal.tsx +++ b/apps/web/core/components/core/description-versions/modal.tsx @@ -59,13 +59,12 @@ export const DescriptionVersionsModal = observer(function DescriptionVersionsMod const handleCopyMarkdown = useCallback(() => { if (!editorRef.current) return; - copyTextToClipboard(editorRef.current.getMarkDown()).then(() => - setToast({ - type: TOAST_TYPE.SUCCESS, - title: t("toast.success"), - message: "Markdown copied to clipboard.", - }) - ); + editorRef.current.copyMarkdownToClipboard(); + setToast({ + type: TOAST_TYPE.SUCCESS, + title: t("toast.success"), + message: "Markdown copied to clipboard.", + }); }, [t]); if (!workspaceId) return null; diff --git a/apps/web/core/components/pages/editor/toolbar/options-dropdown.tsx b/apps/web/core/components/pages/editor/toolbar/options-dropdown.tsx index 572f0babec..5655469ccd 100644 --- a/apps/web/core/components/pages/editor/toolbar/options-dropdown.tsx +++ b/apps/web/core/components/pages/editor/toolbar/options-dropdown.tsx @@ -71,13 +71,12 @@ export const PageOptionsDropdown = observer(function PageOptionsDropdown(props: key: "copy-markdown", action: () => { if (!editorRef) return; - copyTextToClipboard(editorRef.getMarkDown()).then(() => - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Markdown copied to clipboard.", - }) - ); + editorRef.copyMarkdownToClipboard(); + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Success!", + message: "Markdown copied to clipboard.", + }); }, title: "Copy markdown", icon: Clipboard, diff --git a/packages/editor/src/core/extensions/custom-image/components/node-view.tsx b/packages/editor/src/core/extensions/custom-image/components/node-view.tsx index f8143c52c8..3ad4e46187 100644 --- a/packages/editor/src/core/extensions/custom-image/components/node-view.tsx +++ b/packages/editor/src/core/extensions/custom-image/components/node-view.tsx @@ -56,16 +56,24 @@ export function CustomImageNodeView(props: CustomImageNodeViewProps) { return; } + setResolvedSrc(undefined); + setResolvedDownloadSrc(undefined); + setFailedToLoadImage(false); + const getImageSource = async () => { - const url = await extension.options.getImageSource?.(imgNodeSrc); - setResolvedSrc(url); - const downloadUrl = await extension.options.getImageDownloadSource?.(imgNodeSrc); - setResolvedDownloadSrc(downloadUrl); + try { + const url = await extension.options.getImageSource?.(imgNodeSrc); + setResolvedSrc(url); + const downloadUrl = await extension.options.getImageDownloadSource?.(imgNodeSrc); + setResolvedDownloadSrc(downloadUrl); + } catch (error) { + console.error("Error fetching image source:", error); + setFailedToLoadImage(true); + } }; getImageSource(); }, [imgNodeSrc, extension.options]); - // Handle image duplication when status is duplicating useEffect(() => { const handleDuplication = async () => { if (status !== ECustomImageStatus.DUPLICATING || !extension.options.duplicateImage || !imgNodeSrc) { @@ -87,11 +95,8 @@ export function CustomImageNodeView(props: CustomImageNodeViewProps) { throw new Error("Duplication returned invalid asset ID"); } - // Update node with new source and success status - updateAttributes({ - src: newAssetId, - status: ECustomImageStatus.UPLOADED, - }); + setFailedToLoadImage(false); + updateAttributes({ src: newAssetId, status: ECustomImageStatus.UPLOADED }); } catch (error: unknown) { console.error("Failed to duplicate image:", error); // Update status to failed @@ -115,11 +120,13 @@ export function CustomImageNodeView(props: CustomImageNodeViewProps) { useEffect(() => { if (status === ECustomImageStatus.UPLOADED) { hasRetriedOnMount.current = false; + setFailedToLoadImage(false); } }, [status]); const hasDuplicationFailed = hasImageDuplicationFailed(status); - const shouldShowBlock = (isUploaded || imageFromFileSystem) && !failedToLoadImage; + const hasValidImageSource = imageFromFileSystem || (isUploaded && resolvedSrc); + const shouldShowBlock = hasValidImageSource && !failedToLoadImage && !hasDuplicationFailed; return ( diff --git a/packages/editor/src/core/extensions/utility.ts b/packages/editor/src/core/extensions/utility.ts index 167ba298e0..558136347e 100644 --- a/packages/editor/src/core/extensions/utility.ts +++ b/packages/editor/src/core/extensions/utility.ts @@ -8,8 +8,6 @@ import type { TAdditionalActiveDropbarExtensions } from "@/plane-editor/types/ut import { DropHandlerPlugin } from "@/plugins/drop"; import { FilePlugins } from "@/plugins/file/root"; import { MarkdownClipboardPlugin } from "@/plugins/markdown-clipboard"; -// types -import { PasteAssetPlugin } from "@/plugins/paste-asset"; import type { IEditorProps, TEditorAsset, TFileHandler } from "@/types"; type TActiveDropbarExtensions = @@ -82,7 +80,6 @@ export const UtilityExtension = (props: Props) => { flaggedExtensions, editor: this.editor, }), - PasteAssetPlugin(), ]; }, diff --git a/packages/editor/src/core/helpers/editor-ref.ts b/packages/editor/src/core/helpers/editor-ref.ts index bc81364af1..c7be617bcc 100644 --- a/packages/editor/src/core/helpers/editor-ref.ts +++ b/packages/editor/src/core/helpers/editor-ref.ts @@ -89,6 +89,27 @@ export const getEditorRefHelpers = (args: TArgs): EditorRefApi => { }); return markdown; }, + copyMarkdownToClipboard: () => { + if (!editor) return; + + const html = editor.getHTML(); + const metaData = getEditorMetaData(html); + const markdown = convertHTMLToMarkdown({ + description_html: html, + metaData, + }); + + const copyHandler = (event: ClipboardEvent) => { + event.preventDefault(); + event.clipboardData?.setData("text/plain", markdown); + event.clipboardData?.setData("text/html", html); + event.clipboardData?.setData("text/plane-editor-html", html); + document.removeEventListener("copy", copyHandler); + }; + + document.addEventListener("copy", copyHandler); + document.execCommand("copy"); + }, isAnyDropbarOpen: () => { if (!editor) return false; const utilityStorage = editor.storage.utility; diff --git a/packages/editor/src/core/helpers/paste-asset.ts b/packages/editor/src/core/helpers/paste-asset.ts new file mode 100644 index 0000000000..c60e23aaa5 --- /dev/null +++ b/packages/editor/src/core/helpers/paste-asset.ts @@ -0,0 +1,28 @@ +import { assetDuplicationHandlers } from "@/plane-editor/helpers/asset-duplication"; + +// Utility function to process HTML content with all registered handlers +export const processAssetDuplication = (htmlContent: string): { processedHtml: string } => { + const tempDiv = document.createElement("div"); + tempDiv.innerHTML = htmlContent; + + let processedHtml = htmlContent; + + // Process each registered component type + for (const [componentName, handler] of Object.entries(assetDuplicationHandlers)) { + const elements = tempDiv.querySelectorAll(componentName); + + if (elements.length > 0) { + elements.forEach((element) => { + const result = handler({ element, originalHtml: processedHtml }); + if (result.shouldProcess) { + processedHtml = result.modifiedHtml; + } + }); + + // Update tempDiv with processed HTML for next iteration + tempDiv.innerHTML = processedHtml; + } + } + + return { processedHtml }; +}; diff --git a/packages/editor/src/core/plugins/markdown-clipboard.ts b/packages/editor/src/core/plugins/markdown-clipboard.ts index de34a027b4..a97e065890 100644 --- a/packages/editor/src/core/plugins/markdown-clipboard.ts +++ b/packages/editor/src/core/plugins/markdown-clipboard.ts @@ -32,6 +32,7 @@ export const MarkdownClipboardPlugin = (args: TArgs): Plugin => { }); event.clipboardData?.setData("text/plain", markdown); event.clipboardData?.setData("text/html", clipboardHTML); + event.clipboardData?.setData("text/plane-editor-html", clipboardHTML); return true; } catch (error) { console.error("Failed to copy markdown content to clipboard:", error); diff --git a/packages/editor/src/core/plugins/paste-asset.ts b/packages/editor/src/core/plugins/paste-asset.ts deleted file mode 100644 index 67ab9056d3..0000000000 --- a/packages/editor/src/core/plugins/paste-asset.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Plugin, PluginKey } from "@tiptap/pm/state"; -import { assetDuplicationHandlers } from "@/plane-editor/helpers/asset-duplication"; - -export const PasteAssetPlugin = (): Plugin => - new Plugin({ - key: new PluginKey("paste-asset-duplication"), - props: { - handlePaste: (view, event) => { - if (!event.clipboardData) return false; - - const htmlContent = event.clipboardData.getData("text/html"); - if (!htmlContent || htmlContent.includes('data-uploaded="true"')) return false; - - // Process the HTML content using the registry - const { processedHtml, hasChanges } = processAssetDuplication(htmlContent); - - if (!hasChanges) return false; - - event.preventDefault(); - event.stopPropagation(); - - // Mark the content as already processed to avoid infinite loops - const tempDiv = document.createElement("div"); - tempDiv.innerHTML = processedHtml; - const metaTag = tempDiv.querySelector("meta[charset='utf-8']"); - if (metaTag) { - metaTag.setAttribute("data-uploaded", "true"); - } - const finalHtml = tempDiv.innerHTML; - - const newDataTransfer = new DataTransfer(); - newDataTransfer.setData("text/html", finalHtml); - if (event.clipboardData) { - newDataTransfer.setData("text/plain", event.clipboardData.getData("text/plain")); - } - - const pasteEvent = new ClipboardEvent("paste", { - clipboardData: newDataTransfer, - bubbles: true, - cancelable: true, - }); - - view.dom.dispatchEvent(pasteEvent); - - return true; - }, - }, - }); - -// Utility function to process HTML content with all registered handlers -const processAssetDuplication = (htmlContent: string): { processedHtml: string; hasChanges: boolean } => { - const tempDiv = document.createElement("div"); - tempDiv.innerHTML = htmlContent; - - let processedHtml = htmlContent; - let hasChanges = false; - - // Process each registered component type - for (const [componentName, handler] of Object.entries(assetDuplicationHandlers)) { - const elements = tempDiv.querySelectorAll(componentName); - - if (elements.length > 0) { - elements.forEach((element) => { - const result = handler({ element, originalHtml: processedHtml }); - if (result.shouldProcess) { - processedHtml = result.modifiedHtml; - hasChanges = true; - } - }); - - // Update tempDiv with processed HTML for next iteration - tempDiv.innerHTML = processedHtml; - } - } - - return { processedHtml, hasChanges }; -}; diff --git a/packages/editor/src/core/props.ts b/packages/editor/src/core/props.ts index 98821d67d2..d20b5372d4 100644 --- a/packages/editor/src/core/props.ts +++ b/packages/editor/src/core/props.ts @@ -1,6 +1,9 @@ +import { DOMParser } from "@tiptap/pm/model"; import type { EditorProps } from "@tiptap/pm/view"; // plane utils import { cn } from "@plane/utils"; +// helpers +import { processAssetDuplication } from "@/helpers/paste-asset"; type TArgs = { editorClassName: string; @@ -27,5 +30,15 @@ export const CoreEditorProps = (props: TArgs): EditorProps => { } }, }, + handlePaste: (view, event) => { + if (!event.clipboardData) return false; + + const htmlContent = event.clipboardData.getData("text/plane-editor-html"); + if (!htmlContent) return false; + + const { processedHtml } = processAssetDuplication(htmlContent); + view.pasteHTML(processedHtml); + return true; + }, }; }; diff --git a/packages/editor/src/core/types/editor.ts b/packages/editor/src/core/types/editor.ts index 2c7f9bbf3d..44b6388f25 100644 --- a/packages/editor/src/core/types/editor.ts +++ b/packages/editor/src/core/types/editor.ts @@ -118,6 +118,7 @@ export type EditorRefApi = { getDocumentInfo: () => TDocumentInfo; getHeadings: () => IMarking[]; getMarkDown: () => string; + copyMarkdownToClipboard: () => void; getSelectedText: () => string | null; insertText: (contentHTML: string, insertOnNextLine?: boolean) => void; isAnyDropbarOpen: () => boolean; From 079a6240064203b2dd0ce552c89bbf2ba70088c2 Mon Sep 17 00:00:00 2001 From: b-saikrishnakanth <130811169+b-saikrishnakanth@users.noreply.github.com> Date: Wed, 10 Dec 2025 00:59:39 +0530 Subject: [PATCH 3/6] feat: add timezone selection to workspace settings (#8248) * feat: add timezone selection to workspace onboarding, creation and settings * refactor: remove timezone selection from workspace creation and onboarding forms --- apps/web/app/(all)/create-workspace/page.tsx | 6 +- .../onboarding/steps/workspace/create.tsx | 91 +++++++------- .../workspace/create-workspace-form.tsx | 82 ++++++------ .../workspace/settings/workspace-details.tsx | 117 +++++++++++------- packages/i18n/src/locales/cs/translations.ts | 1 + packages/i18n/src/locales/de/translations.ts | 1 + packages/i18n/src/locales/en/translations.ts | 1 + packages/i18n/src/locales/es/translations.ts | 1 + packages/i18n/src/locales/fr/translations.ts | 1 + packages/i18n/src/locales/id/translations.ts | 1 + packages/i18n/src/locales/it/translations.ts | 1 + packages/i18n/src/locales/ja/translations.ts | 1 + packages/i18n/src/locales/ko/translations.ts | 1 + packages/i18n/src/locales/pl/translations.ts | 1 + .../i18n/src/locales/pt-BR/translations.ts | 1 + packages/i18n/src/locales/ro/translations.ts | 1 + packages/i18n/src/locales/ru/translations.ts | 1 + packages/i18n/src/locales/sk/translations.ts | 1 + .../i18n/src/locales/tr-TR/translations.ts | 1 + packages/i18n/src/locales/ua/translations.ts | 1 + .../i18n/src/locales/vi-VN/translations.ts | 1 + .../i18n/src/locales/zh-CN/translations.ts | 1 + .../i18n/src/locales/zh-TW/translations.ts | 1 + packages/types/src/workspace.ts | 3 +- 24 files changed, 187 insertions(+), 131 deletions(-) diff --git a/apps/web/app/(all)/create-workspace/page.tsx b/apps/web/app/(all)/create-workspace/page.tsx index 7d8cfa82a8..15ce00a165 100644 --- a/apps/web/app/(all)/create-workspace/page.tsx +++ b/apps/web/app/(all)/create-workspace/page.tsx @@ -18,7 +18,7 @@ import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper"; // plane web helpers import { getIsWorkspaceCreationDisabled } from "@/plane-web/helpers/instance.helper"; -function CreateWorkspacePage() { +const CreateWorkspacePage = observer(function CreateWorkspacePage() { const { t } = useTranslation(); // router const router = useAppRouter(); @@ -104,6 +104,6 @@ function CreateWorkspacePage() { ); -} +}); -export default observer(CreateWorkspacePage); +export default CreateWorkspacePage; diff --git a/apps/web/core/components/onboarding/steps/workspace/create.tsx b/apps/web/core/components/onboarding/steps/workspace/create.tsx index 949144a375..5c44923e14 100644 --- a/apps/web/core/components/onboarding/steps/workspace/create.tsx +++ b/apps/web/core/components/onboarding/steps/workspace/create.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import { useState } from "react"; import { observer } from "mobx-react"; import { Controller, useForm } from "react-hook-form"; import { CircleCheck } from "lucide-react"; @@ -71,47 +71,46 @@ export const WorkspaceCreateStep = observer(function WorkspaceCreateStep({ const handleCreateWorkspace = async (formData: IWorkspace) => { if (isSubmitting) return; - await workspaceService - .workspaceSlugCheck(formData.slug) - .then(async (res) => { - if (res.status === true && !RESTRICTED_URLS.includes(formData.slug)) { - setSlugError(false); - await createWorkspace(formData) - .then(async (workspaceResponse) => { - setToast({ - type: TOAST_TYPE.SUCCESS, - title: t("workspace_creation.toast.success.title"), - message: t("workspace_creation.toast.success.message"), - }); - captureSuccess({ - eventName: WORKSPACE_TRACKER_EVENTS.create, - payload: { slug: formData.slug }, - }); - await fetchWorkspaces(); - await completeStep(workspaceResponse.id); - onComplete(formData.organization_size === "Just myself"); - }) - .catch(() => { - captureError({ - eventName: WORKSPACE_TRACKER_EVENTS.create, - payload: { slug: formData.slug }, - error: new Error("Error creating workspace"), - }); - setToast({ - type: TOAST_TYPE.ERROR, - title: t("workspace_creation.toast.error.title"), - message: t("workspace_creation.toast.error.message"), - }); - }); - } else setSlugError(true); - }) - .catch(() => - setToast({ - type: TOAST_TYPE.ERROR, - title: t("workspace_creation.toast.error.title"), - message: t("workspace_creation.toast.error.message"), - }) - ); + try { + const res = (await workspaceService.workspaceSlugCheck(formData.slug)) as { status: boolean }; + if (res.status === true && !RESTRICTED_URLS.includes(formData.slug)) { + setSlugError(false); + try { + const workspaceResponse = await createWorkspace(formData); + setToast({ + type: TOAST_TYPE.SUCCESS, + title: t("workspace_creation.toast.success.title"), + message: t("workspace_creation.toast.success.message"), + }); + captureSuccess({ + eventName: WORKSPACE_TRACKER_EVENTS.create, + payload: { slug: formData.slug }, + }); + await fetchWorkspaces(); + await completeStep(workspaceResponse.id); + onComplete(formData.organization_size === "Just myself"); + } catch { + captureError({ + eventName: WORKSPACE_TRACKER_EVENTS.create, + payload: { slug: formData.slug }, + error: new Error("Error creating workspace"), + }); + setToast({ + type: TOAST_TYPE.ERROR, + title: t("workspace_creation.toast.error.title"), + message: t("workspace_creation.toast.error.message"), + }); + } + } else { + setSlugError(true); + } + } catch { + setToast({ + type: TOAST_TYPE.ERROR, + title: t("workspace_creation.toast.error.title"), + message: t("workspace_creation.toast.error.message"), + }); + } }; const completeStep = async (workspaceId: string) => { @@ -136,7 +135,12 @@ export const WorkspaceCreateStep = observer(function WorkspaceCreateStep({ ); } return ( -
+ { + void handleSubmit(handleCreateWorkspace)(e); + }} + >
@@ -181,6 +185,7 @@ export const WorkspaceCreateStep = observer(function WorkspaceCreateStep({ "border-red-500": errors.name, } )} + // eslint-disable-next-line jsx-a11y/no-autofocus autoFocus />
diff --git a/apps/web/core/components/workspace/create-workspace-form.tsx b/apps/web/core/components/workspace/create-workspace-form.tsx index 4a85cd03e8..028feddf85 100644 --- a/apps/web/core/components/workspace/create-workspace-form.tsx +++ b/apps/web/core/components/workspace/create-workspace-form.tsx @@ -67,47 +67,46 @@ export const CreateWorkspaceForm = observer(function CreateWorkspaceForm(props: } = useForm({ defaultValues, mode: "onChange" }); const handleCreateWorkspace = async (formData: IWorkspace) => { - await workspaceService - .workspaceSlugCheck(formData.slug) - .then(async (res) => { - if (res.status === true && !RESTRICTED_URLS.includes(formData.slug)) { - setSlugError(false); + try { + const res = (await workspaceService.workspaceSlugCheck(formData.slug)) as { status: boolean }; + if (res.status === true && !RESTRICTED_URLS.includes(formData.slug)) { + setSlugError(false); - await createWorkspace(formData) - .then(async (res) => { - captureSuccess({ - eventName: WORKSPACE_TRACKER_EVENTS.create, - payload: { slug: formData.slug }, - }); - setToast({ - type: TOAST_TYPE.SUCCESS, - title: t("workspace_creation.toast.success.title"), - message: t("workspace_creation.toast.success.message"), - }); + try { + const workspaceResponse = await createWorkspace(formData); + captureSuccess({ + eventName: WORKSPACE_TRACKER_EVENTS.create, + payload: { slug: formData.slug }, + }); + setToast({ + type: TOAST_TYPE.SUCCESS, + title: t("workspace_creation.toast.success.title"), + message: t("workspace_creation.toast.success.message"), + }); - if (onSubmit) await onSubmit(res); - }) - .catch(() => { - captureError({ - eventName: WORKSPACE_TRACKER_EVENTS.create, - payload: { slug: formData.slug }, - error: new Error("Error creating workspace"), - }); - setToast({ - type: TOAST_TYPE.ERROR, - title: t("workspace_creation.toast.error.title"), - message: t("workspace_creation.toast.error.message"), - }); - }); - } else setSlugError(true); - }) - .catch(() => { - setToast({ - type: TOAST_TYPE.ERROR, - title: t("workspace_creation.toast.error.title"), - message: t("workspace_creation.toast.error.message"), - }); + if (onSubmit) await onSubmit(workspaceResponse); + } catch { + captureError({ + eventName: WORKSPACE_TRACKER_EVENTS.create, + payload: { slug: formData.slug }, + error: new Error("Error creating workspace"), + }); + setToast({ + type: TOAST_TYPE.ERROR, + title: t("workspace_creation.toast.error.title"), + message: t("workspace_creation.toast.error.message"), + }); + } + } else { + setSlugError(true); + } + } catch { + setToast({ + type: TOAST_TYPE.ERROR, + title: t("workspace_creation.toast.error.title"), + message: t("workspace_creation.toast.error.message"), }); + } }; useEffect( @@ -119,7 +118,12 @@ export const CreateWorkspaceForm = observer(function CreateWorkspaceForm(props: ); return ( - + { + void handleSubmit(handleCreateWorkspace)(e); + }} + >
+ +
+

{t("workspace_settings.settings.general.workspace_timezone")}

+ ( + <> + + + )} + /> +
{isAdmin && ( @@ -277,7 +302,9 @@ export const WorkspaceDetails = observer(function WorkspaceDetails() {