From 3aa334c58dace6c398a3eaeed65f9a3e98efa38c Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Tue, 20 Aug 2024 17:34:27 +0530 Subject: [PATCH 1/2] Revert "release: v1.3.0-rc32" --- .env.example | 3 - admin/app/general/form.tsx | 1 - admin/core/components/instance/setup-form.tsx | 4 - admin/core/components/login/sign-in-form.tsx | 2 - admin/package.json | 2 +- apiserver/.env.example | 2 +- apiserver/Dockerfile.api | 24 +- apiserver/Dockerfile.dev | 22 +- apiserver/bin/docker-entrypoint-api-local.sh | 1 + apiserver/plane/api/rate_limit.py | 41 - apiserver/plane/api/serializers/issue.py | 17 - apiserver/plane/api/serializers/state.py | 1 - apiserver/plane/api/views/base.py | 27 +- apiserver/plane/api/views/cycle.py | 11 + apiserver/plane/api/views/inbox.py | 7 - apiserver/plane/api/views/issue.py | 118 - apiserver/plane/api/views/module.py | 1 + apiserver/plane/app/permissions/__init__.py | 1 - apiserver/plane/app/permissions/base.py | 61 - apiserver/plane/app/serializers/issue.py | 19 - apiserver/plane/app/serializers/project.py | 6 - apiserver/plane/app/urls/inbox.py | 2 +- apiserver/plane/app/views/analytic/base.py | 34 +- apiserver/plane/app/views/cycle/archive.py | 9 +- apiserver/plane/app/views/cycle/base.py | 40 +- apiserver/plane/app/views/cycle/issue.py | 20 +- apiserver/plane/app/views/dashboard/base.py | 109 +- apiserver/plane/app/views/estimate/base.py | 16 +- apiserver/plane/app/views/exporter/base.py | 7 +- apiserver/plane/app/views/external/base.py | 12 +- apiserver/plane/app/views/inbox/base.py | 66 +- apiserver/plane/app/views/issue/activity.py | 3 +- apiserver/plane/app/views/issue/archive.py | 13 +- apiserver/plane/app/views/issue/attachment.py | 24 +- apiserver/plane/app/views/issue/base.py | 94 +- apiserver/plane/app/views/issue/comment.py | 14 +- apiserver/plane/app/views/issue/draft.py | 1 - apiserver/plane/app/views/issue/label.py | 10 +- apiserver/plane/app/views/issue/relation.py | 4 +- apiserver/plane/app/views/issue/sub_issue.py | 1 - apiserver/plane/app/views/module/base.py | 44 +- apiserver/plane/app/views/module/issue.py | 14 +- .../plane/app/views/notification/base.py | 36 - apiserver/plane/app/views/page/base.py | 39 +- apiserver/plane/app/views/project/base.py | 159 +- apiserver/plane/app/views/project/member.py | 43 +- apiserver/plane/app/views/search/base.py | 2 - apiserver/plane/app/views/search/issue.py | 15 +- apiserver/plane/app/views/search/workspace.py | 1 - apiserver/plane/app/views/view/base.py | 126 +- apiserver/plane/app/views/webhook/base.py | 17 +- .../plane/app/views/workspace/favorite.py | 23 +- apiserver/plane/app/views/workspace/label.py | 2 +- .../plane/bgtasks/recent_visited_task.py | 61 - apiserver/plane/celery.py | 4 - apiserver/plane/db/models/__init__.py | 4 +- .../bgtasks/issue_property_activity_task.py | 435 -- apiserver/plane/ee/models/__init__.py | 3 - apiserver/plane/ee/models/issue_properties.py | 5 +- apiserver/plane/ee/serializers/__init__.py | 9 - .../ee/serializers/app/issue_property.py | 67 - apiserver/plane/ee/serializers/app/project.py | 17 - .../ee/serializers/app/workspace/feature.py | 8 - .../app/workspace/project_state.py | 21 - apiserver/plane/ee/urls/app/__init__.py | 2 - apiserver/plane/ee/urls/app/ai.py | 1 - apiserver/plane/ee/urls/app/issue_property.py | 98 - apiserver/plane/ee/urls/app/workspace.py | 23 - apiserver/plane/ee/utils/__init__.py | 0 .../ee/utils/issue_property_validators.py | 399 -- apiserver/plane/ee/utils/workspace_feature.py | 19 - apiserver/plane/ee/views/__init__.py | 5 - apiserver/plane/ee/views/app/__init__.py | 2 +- .../ee/views/app/issue_property/__init__.py | 5 - .../ee/views/app/issue_property/activity.py | 42 - .../plane/ee/views/app/issue_property/base.py | 328 -- .../ee/views/app/issue_property/option.py | 150 - .../plane/ee/views/app/issue_property/type.py | 363 -- .../ee/views/app/issue_property/value.py | 299 -- .../plane/ee/views/app/workspace/__init__.py | 5 - .../plane/ee/views/app/workspace/feature.py | 131 - .../ee/views/app/workspace/project_state.py | 138 - apiserver/plane/graphql/mutations/cycle.py | 157 - apiserver/plane/graphql/mutations/issue.py | 24 +- apiserver/plane/graphql/mutations/module.py | 117 - apiserver/plane/graphql/queries/cycle.py | 82 - apiserver/plane/graphql/queries/issue.py | 11 +- apiserver/plane/graphql/queries/module.py | 82 - apiserver/plane/graphql/schema.py | 11 +- apiserver/plane/graphql/types/cycle.py | 76 - apiserver/plane/graphql/types/issue.py | 35 +- apiserver/plane/graphql/types/module.py | 72 - apiserver/plane/graphql/types/project.py | 24 +- .../plane/license/api/serializers/instance.py | 1 - apiserver/plane/license/api/views/instance.py | 6 - .../workspace_subscription_sync_task.py | 99 - apiserver/plane/payment/flags/flag.py | 2 - apiserver/plane/payment/flags/provider.py | 4 +- .../utils/workspace_license_request.py | 2 +- apiserver/plane/settings/common.py | 2 - apiserver/plane/space/serializer/issue.py | 20 +- apiserver/plane/space/views/inbox.py | 6 - apiserver/plane/space/views/issue.py | 2 +- apiserver/plane/utils/grouper.py | 8 +- apiserver/plane/utils/issue_filters.py | 17 - deploy/selfhost/docker-compose.yml | 2 +- deploy/selfhost/install.sh | 17 +- docker-compose-local.yml | 2 +- docker-compose.yml | 2 +- package.json | 2 +- .../src/ce/extensions/document-extensions.tsx | 6 +- .../components/editors/document/editor.tsx | 33 +- .../editors/document/page-renderer.tsx | 18 +- .../editors/document/read-only-editor.tsx | 22 +- .../components/editors/editor-container.tsx | 21 +- .../components/editors/editor-wrapper.tsx | 7 +- .../components/editors/lite-text/editor.tsx | 2 +- .../editors/read-only-editor-wrapper.tsx | 21 +- .../components/editors/rich-text/editor.tsx | 27 +- .../src/core/components/menus/ai-menu.tsx | 95 - .../src/core/components/menus/block-menu.tsx | 2 +- .../editor/src/core/components/menus/index.ts | 1 - packages/editor/src/core/constants/config.ts | 7 - .../extensions/code/code-block-node-view.tsx | 2 +- .../drag-drop.tsx} | 301 +- packages/editor/src/core/extensions/index.ts | 2 +- .../editor/src/core/extensions/side-menu.tsx | 205 - .../src/core/hooks/use-document-editor.ts | 21 +- packages/editor/src/core/hooks/use-editor.ts | 113 +- .../src/core/hooks/use-read-only-editor.ts | 4 +- packages/editor/src/core/plugins/ai-handle.ts | 153 - packages/editor/src/core/props/props.tsx | 10 +- packages/editor/src/core/props/read-only.tsx | 22 +- packages/editor/src/core/types/ai.ts | 7 - packages/editor/src/core/types/config.ts | 17 - packages/editor/src/core/types/editor.ts | 8 +- packages/editor/src/core/types/extensions.ts | 1 - packages/editor/src/core/types/index.ts | 3 - .../core/types/slash-commands-suggestion.ts | 3 +- .../src/ee/extensions/ai-features/handle.ts | 13 - .../src/ee/extensions/ai-features/index.ts | 1 - .../src/ee/extensions/document-extensions.tsx | 51 +- packages/editor/src/ee/extensions/index.ts | 2 - packages/editor/src/index.ts | 2 +- packages/editor/src/styles/drag-drop.css | 65 +- packages/editor/src/styles/editor.css | 132 +- packages/editor/src/styles/table.css | 5 + packages/types/src/common.d.ts | 1 - packages/types/src/favorite/favorite.d.ts | 1 - packages/types/src/instance/base.d.ts | 2 - packages/types/src/issues.d.ts | 1 - packages/types/src/issues/activity/base.d.ts | 5 - packages/types/src/issues/base.d.ts | 9 +- packages/types/src/issues/issue.d.ts | 23 +- packages/types/src/project/projects.d.ts | 15 +- packages/types/src/view-props.d.ts | 8 +- .../types/src/workspace-notifications.d.ts | 2 +- packages/types/src/workspace.d.ts | 1 - packages/ui/src/collapsible/collapsible.tsx | 6 +- .../ui/src/dropdowns/custom-search-select.tsx | 157 +- packages/ui/src/dropdowns/helper.tsx | 1 - .../ui/src/emoji/emoji-icon-picker-new.tsx | 4 +- packages/ui/src/emoji/icons.ts | 6 - .../ui/src/form-fields/checkbox-select.tsx | 83 - packages/ui/src/form-fields/index.ts | 1 - packages/ui/src/form-fields/input.tsx | 17 +- packages/ui/src/form-fields/textarea.tsx | 17 +- .../src/hooks/use-outside-click-detector.tsx | 23 - packages/ui/src/icons/index.ts | 3 - packages/ui/src/icons/monospace-icon.tsx | 16 - packages/ui/src/icons/sans-serif-icon.tsx | 16 - packages/ui/src/icons/serif-icon.tsx | 16 - packages/ui/src/sortable/sortable.tsx | 50 +- packages/ui/src/toast/index.tsx | 3 +- setup.sh | 2 +- .../components/account/auth-forms/email.tsx | 1 - .../account/auth-forms/password.tsx | 1 - .../issues/filters/applied-filters/state.tsx | 2 +- .../core/components/issues/filters/state.tsx | 2 +- .../issues/peek-overview/layout.tsx | 7 +- space/package.json | 2 +- space/public/onboarding/onboarding-pages.svg | 73 + .../public/onboarding/profile-setup-dark.svg | 448 ++ .../public/onboarding/profile-setup-light.svg | 407 ++ .../[workspaceSlug]/(pages)/pages/layout.tsx | 10 +- .../[projectId]/pages/(list)/page.tsx | 16 +- .../[projectId]/settings/automations/page.tsx | 11 +- .../[projectId]/settings/estimates/page.tsx | 20 +- .../[projectId]/settings/features/page.tsx | 21 +- .../(detail)/[projectId]/settings/header.tsx | 25 +- .../[projectId]/settings/issue-types/page.tsx | 31 - .../[projectId]/settings/labels/page.tsx | 12 +- .../(detail)/[projectId]/settings/layout.tsx | 39 +- .../[projectId]/settings/members/page.tsx | 11 +- .../(detail)/[projectId]/settings/sidebar.tsx | 9 +- .../[projectId]/settings/states/page.tsx | 11 +- .../[projectId]/views/(list)/header.tsx | 21 +- .../projects/(detail)/archives/layout.tsx | 16 - .../projects/(detail)/archives/page.tsx | 4 - .../(projects)/projects/(list)}/header.tsx | 8 +- .../(projects)/projects/(list)/layout.tsx | 5 +- .../projects/(list)}/mobile-header.tsx | 2 +- .../(projects)/projects/(list)/page.tsx | 84 +- .../(projects)/settings/api-tokens/page.tsx | 23 +- .../(projects)/settings/billing/page.tsx | 17 +- .../(projects)/settings/exports/page.tsx | 30 +- .../(projects)/settings/imports/page.tsx | 113 +- .../imports/provider/[providerSlug]/page.tsx | 40 - .../(projects)/settings/integrations/page.tsx | 2 +- .../(projects)/settings/layout.tsx | 2 +- .../(projects)/settings/members/page.tsx | 22 +- .../settings/project-states/page.tsx | 96 - .../(projects)/settings/webhooks/page.tsx | 22 +- .../(projects)/settings/worklogs/page.tsx | 6 +- .../[workspaceSlug]/(projects)/sidebar.tsx | 5 +- .../(projects)/workspace-views/header.tsx | 17 +- web/app/accounts/forgot-password/page.tsx | 1 - web/app/accounts/reset-password/page.tsx | 2 - web/app/accounts/set-password/page.tsx | 2 - web/app/profile/page.tsx | 4 +- web/ce/components/issue-types/index.ts | 1 - web/ce/components/issue-types/values/index.ts | 1 - .../components/issue-types/values/update.tsx | 8 - .../issues/filters/applied-filters/index.ts | 1 - .../filters/applied-filters/issue-types.tsx | 11 - web/ce/components/issues/filters/index.ts | 2 - .../components/issues/filters/issue-types.tsx | 12 - web/ce/components/issues/index.ts | 4 - .../components/issues/issue-details/index.ts | 2 - .../issues/issue-details/issue-identifier.tsx | 45 - .../issue-properties-activity/index.ts | 1 - .../issue-properties-activity/root.tsx | 8 - .../issue-modal/additional-properties.tsx | 8 - web/ce/components/issues/issue-modal/index.ts | 3 - .../issues/issue-modal/issue-type-select.tsx | 12 - .../issues/issue-modal/provider.tsx | 28 - web/ce/components/issues/quick-add/index.ts | 1 - web/ce/components/issues/quick-add/root.tsx | 74 - .../pages/editor/ai/ask-pi-menu.tsx | 103 - web/ce/components/pages/editor/ai/index.ts | 2 - web/ce/components/pages/editor/ai/menu.tsx | 290 - web/ce/components/pages/editor/index.ts | 1 - .../components/projects/create/attributes.tsx | 80 - web/ce/components/projects/create/root.tsx | 139 - web/ce/components/projects/header.tsx | 5 - web/ce/components/projects/page.tsx | 3 - web/ce/constants/ai.ts | 9 - web/ce/constants/project/settings/index.ts | 1 - web/ce/constants/project/settings/tabs.ts | 82 - web/ce/constants/workspace.ts | 4 +- web/ce/hooks/use-editor-flagging.ts | 13 - web/ce/hooks/use-issue-embed.tsx | 21 +- web/ce/types/index.ts | 2 - web/ce/types/issue-types/index.ts | 1 - .../issue-types/issue-property-values.d.ts | 2 - web/ce/types/projects/index.ts | 1 - web/ce/types/projects/projects.ts | 3 - .../components/account/auth-forms/email.tsx | 1 - .../account/auth-forms/password.tsx | 1 - .../account/auth-forms/unique-code.tsx | 1 - .../custom-analytics/select/project.tsx | 1 + .../auth-screens/not-authorized-view.tsx | 47 +- .../command-palette/command-modal.tsx | 34 +- .../command-palette/command-palette.tsx | 34 +- .../components/command-palette/helpers.tsx | 23 +- .../common/latest-feature-block.tsx | 2 +- web/core/components/core/activity.tsx | 44 +- .../modals/bulk-delete-issues-modal-item.tsx | 11 +- .../core/modals/bulk-delete-issues-modal.tsx | 19 +- .../modals/existing-issues-list-modal.tsx | 22 +- .../cycles/active-cycle/cycle-stats.tsx | 22 +- web/core/components/cycles/delete-modal.tsx | 3 +- .../dashboard/project-empty-state.tsx | 2 +- .../widgets/issue-panels/issue-list-item.tsx | 148 +- .../widgets/issue-panels/issues-list.tsx | 19 +- web/core/components/dropdowns/date.tsx | 55 +- .../components/dropdowns/member/index.tsx | 31 +- .../dropdowns/member/member-options.tsx | 14 +- .../components/dropdowns/member/types.d.ts | 1 - .../components/estimates/radio-select.tsx | 8 +- .../gantt-chart/blocks/blocks-list.tsx | 20 +- .../components/gantt-chart/chart/header.tsx | 16 +- .../gantt-chart/chart/main-content.tsx | 12 +- .../components/gantt-chart/chart/root.tsx | 15 +- web/core/components/gantt-chart/root.tsx | 15 +- .../components/gantt-chart/sidebar/root.tsx | 6 +- .../inbox/content/inbox-issue-header.tsx | 2 +- .../components/inbox/content/issue-root.tsx | 2 +- .../integration/github/import-data.tsx | 2 +- .../integration/github/select-repository.tsx | 2 +- .../integration/github/single-user-select.tsx | 2 +- .../integration/jira/import-users.tsx | 2 +- .../components/issues/delete-issue-modal.tsx | 15 +- .../issue-detail-widgets/relations/helper.tsx | 11 +- .../sub-issues/helper.tsx | 13 +- .../issue-activity/activity-comment-root.tsx | 8 +- .../activity/actions/archived-at.tsx | 4 +- .../activity/actions/assignee.tsx | 2 +- .../activity/actions/attachment.tsx | 2 +- .../issue-activity/activity/actions/cycle.tsx | 2 +- .../activity/actions/default.tsx | 2 +- .../activity/actions/description.tsx | 2 +- .../activity/actions/estimate.tsx | 2 +- .../issue-activity/activity/actions/inbox.tsx | 2 +- .../issue-activity/activity/actions/label.tsx | 2 +- .../issue-activity/activity/actions/link.tsx | 2 +- .../activity/actions/module.tsx | 2 +- .../issue-activity/activity/actions/name.tsx | 2 +- .../activity/actions/parent.tsx | 2 +- .../activity/actions/priority.tsx | 2 +- .../activity/actions/start_date.tsx | 2 +- .../issue-activity/activity/actions/state.tsx | 2 +- .../activity/actions/target_date.tsx | 2 +- .../issue-activity/comments/comment-block.tsx | 2 +- .../issue-detail/issue-activity/root.tsx | 17 +- .../issue-detail-quick-actions.tsx | 20 +- .../issues/issue-detail/main-content.tsx | 21 +- .../issues/issue-detail/parent-select.tsx | 21 +- .../issues/issue-detail/parent/root.tsx | 17 +- .../issue-detail/parent/sibling-item.tsx | 17 +- .../issues/issue-detail/parent/siblings.tsx | 2 +- .../issues/issue-detail/relation-select.tsx | 8 +- .../issues/issue-detail/sidebar.tsx | 10 - .../issues/issue-layouts/calendar/index.ts | 2 +- .../issue-layouts/calendar/issue-block.tsx | 24 +- .../issue-layouts/calendar/issue-blocks.tsx | 6 +- .../calendar/quick-add-issue-actions.tsx | 133 - .../calendar/quick-add-issue-form.tsx | 264 + .../filters/applied-filters/filters-list.tsx | 9 - .../filters/header/display-filters/index.ts | 2 +- .../{issue-grouping.tsx => issue-type.tsx} | 10 +- .../header/filters/filters-selection.tsx | 22 +- .../issue-layouts/gantt/base-gantt-root.tsx | 18 +- .../issues/issue-layouts/gantt/blocks.tsx | 23 +- .../issues/issue-layouts/gantt/index.ts | 2 +- .../gantt/quick-add-issue-form.tsx | 179 + .../components/issues/issue-layouts/index.ts | 4 - .../issues/issue-layouts/kanban/block.tsx | 35 +- .../issues/issue-layouts/kanban/index.ts | 1 + .../issue-layouts/kanban/kanban-group.tsx | 11 +- .../kanban/quick-add-issue-form.tsx | 162 + .../issues/issue-layouts/list/block.tsx | 15 +- .../issues/issue-layouts/list/index.ts | 1 + .../issues/issue-layouts/list/list-group.tsx | 9 +- .../list/quick-add-issue-form.tsx | 168 + .../properties/all-properties.tsx | 4 +- .../quick-action-dropdowns/all-issue.tsx | 6 +- .../quick-action-dropdowns/cycle-issue.tsx | 7 +- .../quick-action-dropdowns/module-issue.tsx | 6 +- .../quick-action-dropdowns/project-issue.tsx | 6 +- .../issue-layouts/quick-add/button/gantt.tsx | 19 - .../issue-layouts/quick-add/button/index.ts | 4 - .../issue-layouts/quick-add/button/kanban.tsx | 18 - .../issue-layouts/quick-add/button/list.tsx | 18 - .../quick-add/button/spreadsheet.tsx | 21 - .../issue-layouts/quick-add/form/calendar.tsx | 31 - .../issue-layouts/quick-add/form/gantt.tsx | 32 - .../issue-layouts/quick-add/form/index.ts | 5 - .../issue-layouts/quick-add/form/kanban.tsx | 26 - .../issue-layouts/quick-add/form/list.tsx | 31 - .../quick-add/form/spreadsheet.tsx | 31 - .../issues/issue-layouts/quick-add/index.ts | 3 - .../issues/issue-layouts/quick-add/root.tsx | 185 - .../issues/issue-layouts/spreadsheet/index.ts | 1 + .../issue-layouts/spreadsheet/issue-row.tsx | 10 +- .../spreadsheet/quick-add-issue-form.tsx | 238 + .../spreadsheet/spreadsheet-view.tsx | 9 +- .../components/issues/issue-modal/base.tsx | 331 -- .../components/default-properties.tsx | 325 -- .../components/description-editor.tsx | 230 - .../issues/issue-modal/components/index.ts | 5 - .../issue-modal/components/parent-tag.tsx | 66 - .../issue-modal/components/project-select.tsx | 55 - .../issue-modal/components/title-input.tsx | 57 - .../issues/issue-modal/context/index.ts | 1 - .../issue-modal/context/issue-modal.tsx | 37 - .../issues/issue-modal/draft-issue-layout.tsx | 28 +- .../components/issues/issue-modal/form.tsx | 816 ++- .../components/issues/issue-modal/index.ts | 4 +- .../components/issues/issue-modal/modal.tsx | 313 +- .../components/issues/issue-update-status.tsx | 13 +- .../issues/parent-issues-list-modal.tsx | 28 +- .../issues/peek-overview/issue-detail.tsx | 11 +- .../issues/peek-overview/properties.tsx | 10 - .../components/issues/peek-overview/root.tsx | 23 +- .../components/issues/peek-overview/view.tsx | 4 +- .../issues/relations/issue-list-item.tsx | 16 +- .../issues/sub-issues/issue-list-item.tsx | 21 +- web/core/components/issues/title-input.tsx | 2 +- .../onboarding/create-or-join-workspaces.tsx | 4 +- .../components/onboarding/profile-setup.tsx | 7 +- .../components/pages/editor/editor-body.tsx | 33 +- .../pages/editor/header/options-dropdown.tsx | 1 - web/core/components/pages/editor/title.tsx | 27 +- .../pages/list/block-item-action.tsx | 27 +- web/core/components/project/card.tsx | 2 +- .../project/create-project-form.tsx | 405 ++ .../project/create-project-modal.tsx | 68 +- .../project/create/common-attributes.tsx | 133 - web/core/components/project/create/header.tsx | 85 - .../project/create/project-create-buttons.tsx | 26 - web/core/components/project/index.ts | 2 +- web/core/components/project/root.tsx | 92 - .../project/send-project-invitation-modal.tsx | 31 +- .../project/settings/features-list.tsx | 4 +- .../project/settings/member-columns.tsx | 27 +- .../sidebar/notification-card/item.tsx | 52 +- .../sidebar/favorites/favorite-folder.tsx | 5 +- .../sidebar/favorites/favorite-item.tsx | 220 + .../common/favorite-item-drag-handle.tsx | 41 - .../common/favorite-item-quick-action.tsx | 48 - .../common/favorite-item-title.tsx | 33 - .../common/favorite-item-wrapper.tsx | 34 - .../favorite-items/common/helper.tsx | 49 - .../favorites/favorite-items/common/index.ts | 5 - .../sidebar/favorites/favorite-items/index.ts | 2 - .../sidebar/favorites/favorite-items/root.tsx | 112 - .../sidebar/favorites/favorites-menu.tsx | 5 +- .../workspace/sidebar/favorites/index.ts | 5 - .../sidebar/favorites/new-fav-folder.tsx | 40 +- .../components/workspace/sidebar/index.ts | 1 - .../workspace/sidebar/projects-list-item.tsx | 105 +- .../workspace/sidebar/projects-list.tsx | 1 + .../workspace/sidebar/workspace-menu.tsx | 89 +- web/core/constants/cycle.ts | 24 + web/core/constants/dashboard.ts | 4 +- web/core/constants/editor.ts | 26 +- web/core/constants/empty-state.ts | 2 +- web/core/constants/issue-modal.ts | 22 - web/core/constants/issue.ts | 28 +- web/core/constants/project.ts | 77 + web/core/hooks/context/use-issue-modal.tsx | 9 - web/core/hooks/use-favorite-item-details.tsx | 60 - web/core/hooks/use-outside-click-detector.tsx | 23 - web/core/hooks/use-page-filters.ts | 61 +- .../hooks/use-peek-overview-outside-click.tsx | 25 - .../layouts/auth-layout/project-wrapper.tsx | 23 +- .../layouts/auth-layout/workspace-wrapper.tsx | 41 +- web/core/services/ai.service.ts | 24 - web/core/store/inbox/project-inbox.store.ts | 30 +- .../helpers/issue-filter-helper.store.ts | 1 - .../store/issue/issue-details/issue.store.ts | 1 - web/core/store/member/project-member.store.ts | 18 +- web/core/store/project/project.store.ts | 32 +- web/core/store/user/index.ts | 80 +- web/ee/components/active-cycles/progress.tsx | 6 +- web/ee/components/active-cycles/root.tsx | 20 +- .../cycles/active-cycles/priority-issues.tsx | 17 +- .../cycles/active-cycles/progress.tsx | 8 +- .../feature-flags/with-feature-flag-hoc.tsx | 11 +- web/ee/components/iframe/silo-iframe.tsx | 79 - web/ee/components/integration/guide.tsx | 59 - .../issue-types/common/icon-picker.tsx | 107 - web/ee/components/issue-types/common/index.ts | 2 - .../issue-types/common/issue-type-logo.tsx | 72 - .../issue-types/common/lucide-icons-list.tsx | 138 - .../issue-types/create-update/form.tsx | 104 - .../issue-types/create-update/index.ts | 2 - .../issue-types/create-update/modal.tsx | 134 - .../components/issue-types/dropdowns/index.ts | 1 - .../issue-types/dropdowns/issue-type.tsx | 67 - web/ee/components/issue-types/empty-state.tsx | 93 - web/ee/components/issue-types/index.ts | 9 - .../issue-types/issue-type-list-item.tsx | 107 - .../issue-types/issue-types-list.tsx | 62 - .../properties/attributes/boolean.tsx | 63 - .../properties/attributes/common/index.ts | 2 - .../common/property-multi-select.tsx | 45 - .../property-settings-configuration.tsx | 67 - .../properties/attributes/date-picker.tsx | 44 - .../properties/attributes/dropdown.tsx | 90 - .../properties/attributes/index.ts | 8 - .../properties/attributes/member-picker.tsx | 85 - .../properties/attributes/number.tsx | 58 - .../attributes/options/create-option-item.tsx | 31 - .../options/default-option-select.tsx | 106 - .../properties/attributes/options/index.ts | 4 - .../properties/attributes/options/option.tsx | 84 - .../properties/attributes/options/root.tsx | 131 - .../properties/attributes/text.tsx | 63 - .../issue-types/properties/common/index.ts | 1 - .../properties/common/issue-property-logo.tsx | 34 - .../properties/delete-confirmation-modal.tsx | 85 - .../issue-types/properties/dropdowns/index.ts | 4 - .../dropdowns/property-attributes.tsx | 105 - .../properties/dropdowns/property-title.tsx | 106 - .../properties/dropdowns/property-type.tsx | 79 - .../selected-attribute-properties.tsx | 87 - .../issue-types/properties/empty-state.tsx | 12 - .../issue-types/properties/index.ts | 11 - .../properties/mandatory-field-toggle.tsx | 72 - .../properties/property-create-list-item.tsx | 32 - .../properties/property-list-item.tsx | 432 -- .../issue-types/properties/property-list.tsx | 82 - .../issue-types/properties/quick-actions.tsx | 101 - .../issue-types/properties/root.tsx | 127 - .../components/issue-types/quick-actions.tsx | 89 - web/ee/components/issue-types/root.tsx | 60 - .../values/components/boolean-input.tsx | 27 - .../values/components/date-select.tsx | 83 - .../issue-types/values/components/index.ts | 6 - .../values/components/member-select.tsx | 121 - .../values/components/number-input.tsx | 87 - .../values/components/option-select.tsx | 166 - .../values/components/text-input.tsx | 140 - .../components/issue-types/values/create.tsx | 90 - web/ee/components/issue-types/values/index.ts | 5 - web/ee/components/issue-types/values/root.tsx | 62 - .../components/issue-types/values/update.tsx | 115 - .../issue-types/values/value-select.tsx | 205 - .../issues/bulk-operations/properties.tsx | 2 +- .../issues/bulk-operations/root.tsx | 5 +- .../issues/filters/applied-filters/index.ts | 1 - .../filters/applied-filters/issue-types.tsx | 59 - web/ee/components/issues/filters/index.ts | 2 - .../components/issues/filters/issue-types.tsx | 114 - web/ee/components/issues/index.ts | 4 - .../components/issues/issue-details/index.ts | 2 - .../issues/issue-details/issue-identifier.tsx | 92 - .../issue-properties-activity/boolean.tsx | 30 - .../common/activity-block.tsx | 66 - .../issue-properties-activity/common/index.ts | 2 - .../common/issue-user.tsx | 34 - .../issue-properties-activity/date.tsx | 38 - .../issue-properties-activity/dropdown.tsx | 55 - .../issue-properties-activity/index.ts | 8 - .../issue-properties-activity/member.tsx | 64 - .../issue-properties-activity/number.tsx | 35 - .../issue-properties-activity/root.tsx | 82 - .../issue-properties-activity/text.tsx | 30 - .../issue-modal/additional-properties.tsx | 63 - web/ee/components/issues/issue-modal/index.ts | 3 - .../issues/issue-modal/issue-type-select.tsx | 65 - .../issues/issue-modal/provider.tsx | 159 - web/ee/components/issues/quick-add/index.ts | 1 - web/ee/components/issues/quick-add/root.tsx | 63 - .../pages/editor/embed/issue-embed.tsx | 16 +- web/ee/components/pages/extra-actions.tsx | 4 +- .../projects/applied-filters/access.tsx | 36 - .../projects/applied-filters/date.tsx | 55 - .../projects/applied-filters/index.ts | 5 - .../projects/applied-filters/members.tsx | 48 - .../project-display-filters.tsx | 39 - .../projects/applied-filters/root.tsx | 141 - .../projects/common/join-button.tsx | 66 - .../components/projects/create/attributes.tsx | 171 - web/ee/components/projects/create/root.tsx | 187 - web/ee/components/projects/dropdowns/index.ts | 3 - .../projects/dropdowns/members-dropdown.tsx | 42 - .../projects/dropdowns/priority-dropdown.tsx | 481 -- .../projects/dropdowns/scope-dropdown.tsx | 87 - .../projects/dropdowns/state-dropdown.tsx | 68 - .../header/attributes-dropdown/access.tsx | 66 - .../header/attributes-dropdown/index.ts | 4 - .../header/attributes-dropdown/priority.tsx | 66 - .../header/attributes-dropdown/root.tsx | 106 - .../header/attributes-dropdown/state.tsx | 103 - .../header/attributes-dropdown/users.tsx | 111 - .../projects/header/create-project-button.tsx | 35 - .../display-filters-dropdown/group-by.tsx | 42 - .../header/display-filters-dropdown/index.ts | 4 - .../header/display-filters-dropdown/root.tsx | 54 - .../display-filters-dropdown/sort-by.tsx | 42 - .../display-filters-dropdown/sort-order.tsx | 42 - web/ee/components/projects/header/index.ts | 6 - .../projects/header/layout-selection.tsx | 73 - web/ee/components/projects/header/root.tsx | 80 - .../projects/header/search-projects.tsx | 77 - web/ee/components/projects/index.ts | 6 - .../projects/layouts/attributes.tsx | 167 - .../layouts/board/group-drag-overlay.tsx | 67 - .../layouts/board/group-item-header/index.ts | 1 - .../layouts/board/group-item-header/root.tsx | 103 - .../projects/layouts/board/group-item.tsx | 115 - .../projects/layouts/board/group.tsx | 38 - .../projects/layouts/board/index.ts | 6 - .../projects/layouts/board/list-item.tsx | 69 - .../projects/layouts/board/list.tsx | 22 - .../projects/layouts/board/root.tsx | 25 - .../projects/layouts/board/utils.tsx | 93 - .../layouts/gallery/base-gallery-root.tsx | 31 - .../projects/layouts/gallery/card.tsx | 117 - .../projects/layouts/gallery/details.tsx | 229 - .../layouts/gantt/base-gantt-root.tsx | 89 - .../projects/layouts/gantt/blocks.tsx | 112 - .../projects/layouts/gantt/index.ts | 2 - .../projects/layouts/gantt/sidebar.tsx | 52 - web/ee/components/projects/layouts/index.ts | 2 - .../projects/layouts/list/base-list-root.tsx | 37 - .../projects/layouts/list/block.tsx | 131 - .../projects/layouts/list/blocks-list.tsx | 19 - .../projects/layouts/list/default.tsx | 45 - .../layouts/list/headers/group-by-card.tsx | 34 - .../components/projects/layouts/list/index.ts | 2 - .../projects/layouts/list/list-group.tsx | 87 - .../projects/layouts/project-layout-HOC.tsx | 69 - web/ee/components/projects/layouts/root.tsx | 37 - .../spreadsheet/base-spreadsheet-root.tsx | 43 - .../spreadsheet/columns/header-column.tsx | 110 - .../layouts/spreadsheet/columns/index.ts | 6 - .../spreadsheet/columns/issue-column.tsx | 47 - .../spreadsheet/columns/lead-column.tsx | 58 - .../spreadsheet/columns/members-column.tsx | 27 - .../spreadsheet/columns/priority-column.tsx | 30 - .../spreadsheet/columns/state-column.tsx | 41 - .../spreadsheet/columns/updated-on-column.tsx | 47 - .../projects/layouts/spreadsheet/index.ts | 4 - .../layouts/spreadsheet/project-column.tsx | 44 - .../layouts/spreadsheet/project-row.tsx | 158 - .../spreadsheet/spreadsheet-header-column.tsx | 37 - .../spreadsheet/spreadsheet-header.tsx | 43 - .../layouts/spreadsheet/spreadsheet-table.tsx | 103 - .../layouts/spreadsheet/spreadsheet-view.tsx | 69 - web/ee/components/projects/mobile-header.tsx | 50 - web/ee/components/projects/page.tsx | 56 - web/ee/components/projects/quick-actions.tsx | 151 - web/ee/components/projects/root.tsx | 25 - web/ee/components/sidebar/app-switcher.tsx | 2 +- web/ee/components/views/access-controller.tsx | 12 - web/ee/components/views/publish/modal.tsx | 2 +- .../views/publish/use-view-publish.tsx | 6 +- .../workspace/download/download-item/root.tsx | 19 +- .../workspace/download/download-list.tsx | 6 +- .../components/worklogs/workspace/empty.tsx | 2 +- .../workspace-project-states/index.ts | 3 - .../project-state-icon/cancelled.tsx | 42 - .../project-state-icon/completed.tsx | 36 - .../project-state-icon/draft.tsx | 31 - .../project-state-icon/execution.tsx | 36 - .../project-state-icon/index.ts | 12 - .../project-state-icon/monitoring.tsx | 30 - .../project-state-icon/planning.tsx | 36 - .../project-state-icon/root.tsx | 50 - .../project-state-icon/types.ts | 8 - .../settings/create-update/create.tsx | 74 - .../settings/create-update/form.tsx | 108 - .../settings/create-update/index.ts | 3 - .../settings/create-update/update.tsx | 73 - .../settings/empty-state.tsx | 40 - .../settings/group-list-item.tsx | 103 - .../settings/group-list.tsx | 62 - .../settings/index.ts | 14 - .../settings/loader.tsx | 14 - .../settings/options/delete.tsx | 101 - .../settings/options/index.ts | 2 - .../settings/options/mark-as-default.tsx | 44 - .../settings/root.tsx | 49 - .../settings/state-list-item.tsx | 204 - .../settings/state-list.tsx | 35 - .../settings/upgrade.tsx | 75 - web/ee/components/workspace/upgrade-badge.tsx | 5 +- web/ee/constants/issue-properties.ts | 249 - web/ee/constants/issues.ts | 10 +- web/ee/constants/project/default-root.ts | 129 - web/ee/constants/project/index.ts | 1 - web/ee/constants/project/settings/index.ts | 1 - web/ee/constants/project/settings/tabs.ts | 38 - web/ee/constants/project/spreadsheet.ts | 103 - web/ee/constants/workspace-project-states.ts | 69 - web/ee/constants/workspace.ts | 27 +- web/ee/helpers/issue-properties.helper.ts | 91 - web/ee/helpers/issue-type.helper.ts | 14 - web/ee/hooks/store/index.ts | 5 - web/ee/hooks/store/issue-types/index.ts | 5 - .../use-issue-properties-activity.ts | 11 - .../store/issue-types/use-issue-property.ts | 24 - .../hooks/store/issue-types/use-issue-type.ts | 19 - .../store/issue-types/use-issue-types.ts | 12 - .../issue-types/use-property-options.tsx | 9 - web/ee/hooks/store/use-flag.ts | 10 +- web/ee/hooks/store/use-workspace-features.ts | 11 - .../store/workspace-project-states/index.ts | 3 - .../use-project-filters.ts | 11 - .../use-project-state.ts | 13 - .../use-workspace-project-states.ts | 11 - web/ee/hooks/use-editor-flagging.ts | 31 - web/ee/hooks/use-issue-embed.tsx | 32 +- web/ee/lib/context/index.ts | 1 - web/ee/lib/context/issue-property-option.tsx | 113 - web/ee/lib/index.ts | 1 - web/ee/services/api_token.service.ts | 16 - web/ee/services/index.ts | 2 - web/ee/services/issue-types/index.ts | 5 - .../issue-properties-activity.service.ts | 31 - .../issue-types/issue-properties.service.ts | 68 - .../issue-property-options.service.ts | 68 - .../issue-property-values.service.ts | 54 - .../issue-types/issue-types.service.ts | 65 - web/ee/services/workspace-feature.service.ts | 50 - .../workspace-project-states.service.ts | 95 - .../feature-flags/feature-flags.store.ts | 11 +- web/ee/store/issue-types/index.ts | 6 - .../issue-properties-activity.store.ts | 133 - .../issue-types/issue-properties-activity.ts | 122 - .../issue-types/issue-property-option.ts | 114 - web/ee/store/issue-types/issue-property.ts | 291 - web/ee/store/issue-types/issue-type.ts | 250 - web/ee/store/issue-types/issue-types.store.ts | 343 -- .../issue/issue-details/activity.store.ts | 36 +- .../projects/project-filter-helper.store.ts | 328 -- web/ee/store/projects/projects.ts | 94 - web/ee/store/root.store.ts | 28 - web/ee/store/workspace-feature.store.ts | 142 - .../filters/filters.ts | 303 -- .../filters/helpers.ts | 249 - .../workspace-project-states/filters/index.ts | 2 - .../store/workspace-project-states/index.ts | 3 - .../workspace-project-states/project-state.ts | 149 - .../workspace-project-states.store.ts | 322 -- .../workspace-worklog.store.ts | 4 +- web/ee/types/index.ts | 10 +- web/ee/types/issue-types/index.ts | 6 - .../issue-properties-activity.d.ts | 24 - .../types/issue-types/issue-properties.d.ts | 57 - .../issue-property-configurations.d.ts | 78 - .../issue-types/issue-property-option.d.ts | 29 - .../issue-types/issue-property-values.d.ts | 17 - web/ee/types/issue-types/issue-types.d.ts | 20 - web/ee/types/projects/index.ts | 1 - web/ee/types/projects/projects.ts | 1 - web/ee/types/projects/types.ts | 10 - web/ee/types/workspace-feature.ts | 19 - web/ee/types/workspace-project-filters.ts | 77 - web/ee/types/workspace-project-states.ts | 57 - web/ee/types/workspace-worklog.d.ts | 4 +- web/helpers/date-time.helper.ts | 19 +- web/helpers/emoji.helper.tsx | 6 +- web/helpers/issue-modal.helper.ts | 3 - web/package.json | 2 +- web/public/auth/unauthorized.svg | 44 - web/public/empty-state/dashboard/project.svg | 333 ++ .../issue-types/issue-type-dark.svg | 810 --- .../issue-types/issue-type-light.svg | 192 - web/public/manifest.json | 2 +- .../onboarding/create-join-workspace-dark.svg | 278 + .../create-join-workspace-dark.webp | Bin 27102 -> 0 bytes .../create-join-workspace-light.webp | Bin 34658 -> 0 bytes .../onboarding/create-join-workspace.svg | 331 ++ web/public/onboarding/onboarding-pages.svg | 73 + web/public/onboarding/onboarding-pages.webp | Bin 45508 -> 0 bytes web/public/onboarding/profile-setup-dark.svg | 448 ++ web/public/onboarding/profile-setup-dark.webp | Bin 43108 -> 0 bytes web/public/onboarding/profile-setup-light.svg | 407 ++ .../onboarding/profile-setup-light.webp | Bin 56108 -> 0 bytes web/public/projects/dark-upgrade.svg | 36 - web/public/projects/light-upgrade.svg | 42 - web/public/projects/project-states-dark.svg | 730 --- web/public/projects/project-states-light.svg | 103 - web/public/projects/states-dark.svg | 46 - web/public/projects/states-light.svg | 46 - web/public/sw.js | 2 +- web/public/sw.js.map | 32 + .../workspace-active-cycles/cta-r-2-dark.webp | Bin 207310 -> 386292 bytes web/styles/globals.css | 9 - yarn.lock | 4748 ++++++++++------- 755 files changed, 10523 insertions(+), 32383 deletions(-) delete mode 100644 apiserver/plane/app/permissions/base.py delete mode 100644 apiserver/plane/bgtasks/recent_visited_task.py delete mode 100644 apiserver/plane/ee/bgtasks/issue_property_activity_task.py delete mode 100644 apiserver/plane/ee/serializers/app/issue_property.py delete mode 100644 apiserver/plane/ee/serializers/app/project.py delete mode 100644 apiserver/plane/ee/serializers/app/workspace/feature.py delete mode 100644 apiserver/plane/ee/serializers/app/workspace/project_state.py delete mode 100644 apiserver/plane/ee/urls/app/issue_property.py delete mode 100644 apiserver/plane/ee/utils/__init__.py delete mode 100644 apiserver/plane/ee/utils/issue_property_validators.py delete mode 100644 apiserver/plane/ee/utils/workspace_feature.py delete mode 100644 apiserver/plane/ee/views/app/issue_property/__init__.py delete mode 100644 apiserver/plane/ee/views/app/issue_property/activity.py delete mode 100644 apiserver/plane/ee/views/app/issue_property/base.py delete mode 100644 apiserver/plane/ee/views/app/issue_property/option.py delete mode 100644 apiserver/plane/ee/views/app/issue_property/type.py delete mode 100644 apiserver/plane/ee/views/app/issue_property/value.py delete mode 100644 apiserver/plane/ee/views/app/workspace/feature.py delete mode 100644 apiserver/plane/ee/views/app/workspace/project_state.py delete mode 100644 apiserver/plane/graphql/mutations/cycle.py delete mode 100644 apiserver/plane/graphql/mutations/module.py delete mode 100644 apiserver/plane/graphql/queries/cycle.py delete mode 100644 apiserver/plane/graphql/queries/module.py delete mode 100644 apiserver/plane/graphql/types/cycle.py delete mode 100644 apiserver/plane/graphql/types/module.py delete mode 100644 apiserver/plane/payment/bgtasks/workspace_subscription_sync_task.py delete mode 100644 packages/editor/src/core/components/menus/ai-menu.tsx delete mode 100644 packages/editor/src/core/constants/config.ts rename packages/editor/src/core/{plugins/drag-handle.ts => extensions/drag-drop.tsx} (50%) delete mode 100644 packages/editor/src/core/extensions/side-menu.tsx delete mode 100644 packages/editor/src/core/plugins/ai-handle.ts delete mode 100644 packages/editor/src/core/types/ai.ts delete mode 100644 packages/editor/src/core/types/config.ts delete mode 100644 packages/editor/src/core/types/extensions.ts delete mode 100644 packages/editor/src/ee/extensions/ai-features/handle.ts delete mode 100644 packages/editor/src/ee/extensions/ai-features/index.ts delete mode 100644 packages/ui/src/form-fields/checkbox-select.tsx delete mode 100644 packages/ui/src/icons/monospace-icon.tsx delete mode 100644 packages/ui/src/icons/sans-serif-icon.tsx delete mode 100644 packages/ui/src/icons/serif-icon.tsx create mode 100644 space/public/onboarding/onboarding-pages.svg create mode 100644 space/public/onboarding/profile-setup-dark.svg create mode 100644 space/public/onboarding/profile-setup-light.svg delete mode 100644 web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/issue-types/page.tsx delete mode 100644 web/app/[workspaceSlug]/(projects)/projects/(detail)/archives/layout.tsx delete mode 100644 web/app/[workspaceSlug]/(projects)/projects/(detail)/archives/page.tsx rename web/{core/components/project => app/[workspaceSlug]/(projects)/projects/(list)}/header.tsx (95%) rename web/{ce/components/projects => app/[workspaceSlug]/(projects)/projects/(list)}/mobile-header.tsx (99%) delete mode 100644 web/app/[workspaceSlug]/(projects)/settings/imports/provider/[providerSlug]/page.tsx delete mode 100644 web/app/[workspaceSlug]/(projects)/settings/project-states/page.tsx delete mode 100644 web/ce/components/issue-types/index.ts delete mode 100644 web/ce/components/issue-types/values/index.ts delete mode 100644 web/ce/components/issue-types/values/update.tsx delete mode 100644 web/ce/components/issues/filters/applied-filters/index.ts delete mode 100644 web/ce/components/issues/filters/applied-filters/issue-types.tsx delete mode 100644 web/ce/components/issues/filters/index.ts delete mode 100644 web/ce/components/issues/filters/issue-types.tsx delete mode 100644 web/ce/components/issues/issue-details/index.ts delete mode 100644 web/ce/components/issues/issue-details/issue-identifier.tsx delete mode 100644 web/ce/components/issues/issue-details/issue-properties-activity/index.ts delete mode 100644 web/ce/components/issues/issue-details/issue-properties-activity/root.tsx delete mode 100644 web/ce/components/issues/issue-modal/additional-properties.tsx delete mode 100644 web/ce/components/issues/issue-modal/index.ts delete mode 100644 web/ce/components/issues/issue-modal/issue-type-select.tsx delete mode 100644 web/ce/components/issues/issue-modal/provider.tsx delete mode 100644 web/ce/components/issues/quick-add/index.ts delete mode 100644 web/ce/components/issues/quick-add/root.tsx delete mode 100644 web/ce/components/pages/editor/ai/ask-pi-menu.tsx delete mode 100644 web/ce/components/pages/editor/ai/index.ts delete mode 100644 web/ce/components/pages/editor/ai/menu.tsx delete mode 100644 web/ce/components/projects/create/attributes.tsx delete mode 100644 web/ce/components/projects/create/root.tsx delete mode 100644 web/ce/components/projects/header.tsx delete mode 100644 web/ce/components/projects/page.tsx delete mode 100644 web/ce/constants/ai.ts delete mode 100644 web/ce/constants/project/settings/tabs.ts delete mode 100644 web/ce/hooks/use-editor-flagging.ts delete mode 100644 web/ce/types/index.ts delete mode 100644 web/ce/types/issue-types/index.ts delete mode 100644 web/ce/types/issue-types/issue-property-values.d.ts delete mode 100644 web/ce/types/projects/index.ts delete mode 100644 web/ce/types/projects/projects.ts delete mode 100644 web/core/components/issues/issue-layouts/calendar/quick-add-issue-actions.tsx create mode 100644 web/core/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx rename web/core/components/issues/issue-layouts/filters/header/display-filters/{issue-grouping.tsx => issue-type.tsx} (79%) create mode 100644 web/core/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx create mode 100644 web/core/components/issues/issue-layouts/kanban/quick-add-issue-form.tsx create mode 100644 web/core/components/issues/issue-layouts/list/quick-add-issue-form.tsx delete mode 100644 web/core/components/issues/issue-layouts/quick-add/button/gantt.tsx delete mode 100644 web/core/components/issues/issue-layouts/quick-add/button/index.ts delete mode 100644 web/core/components/issues/issue-layouts/quick-add/button/kanban.tsx delete mode 100644 web/core/components/issues/issue-layouts/quick-add/button/list.tsx delete mode 100644 web/core/components/issues/issue-layouts/quick-add/button/spreadsheet.tsx delete mode 100644 web/core/components/issues/issue-layouts/quick-add/form/calendar.tsx delete mode 100644 web/core/components/issues/issue-layouts/quick-add/form/gantt.tsx delete mode 100644 web/core/components/issues/issue-layouts/quick-add/form/index.ts delete mode 100644 web/core/components/issues/issue-layouts/quick-add/form/kanban.tsx delete mode 100644 web/core/components/issues/issue-layouts/quick-add/form/list.tsx delete mode 100644 web/core/components/issues/issue-layouts/quick-add/form/spreadsheet.tsx delete mode 100644 web/core/components/issues/issue-layouts/quick-add/index.ts delete mode 100644 web/core/components/issues/issue-layouts/quick-add/root.tsx create mode 100644 web/core/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx delete mode 100644 web/core/components/issues/issue-modal/base.tsx delete mode 100644 web/core/components/issues/issue-modal/components/default-properties.tsx delete mode 100644 web/core/components/issues/issue-modal/components/description-editor.tsx delete mode 100644 web/core/components/issues/issue-modal/components/index.ts delete mode 100644 web/core/components/issues/issue-modal/components/parent-tag.tsx delete mode 100644 web/core/components/issues/issue-modal/components/project-select.tsx delete mode 100644 web/core/components/issues/issue-modal/components/title-input.tsx delete mode 100644 web/core/components/issues/issue-modal/context/index.ts delete mode 100644 web/core/components/issues/issue-modal/context/issue-modal.tsx create mode 100644 web/core/components/project/create-project-form.tsx delete mode 100644 web/core/components/project/create/common-attributes.tsx delete mode 100644 web/core/components/project/create/header.tsx delete mode 100644 web/core/components/project/create/project-create-buttons.tsx delete mode 100644 web/core/components/project/root.tsx create mode 100644 web/core/components/workspace/sidebar/favorites/favorite-item.tsx delete mode 100644 web/core/components/workspace/sidebar/favorites/favorite-items/common/favorite-item-drag-handle.tsx delete mode 100644 web/core/components/workspace/sidebar/favorites/favorite-items/common/favorite-item-quick-action.tsx delete mode 100644 web/core/components/workspace/sidebar/favorites/favorite-items/common/favorite-item-title.tsx delete mode 100644 web/core/components/workspace/sidebar/favorites/favorite-items/common/favorite-item-wrapper.tsx delete mode 100644 web/core/components/workspace/sidebar/favorites/favorite-items/common/helper.tsx delete mode 100644 web/core/components/workspace/sidebar/favorites/favorite-items/common/index.ts delete mode 100644 web/core/components/workspace/sidebar/favorites/favorite-items/index.ts delete mode 100644 web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx delete mode 100644 web/core/components/workspace/sidebar/favorites/index.ts delete mode 100644 web/core/constants/issue-modal.ts delete mode 100644 web/core/hooks/context/use-issue-modal.tsx delete mode 100644 web/core/hooks/use-favorite-item-details.tsx delete mode 100644 web/ee/components/iframe/silo-iframe.tsx delete mode 100644 web/ee/components/integration/guide.tsx delete mode 100644 web/ee/components/issue-types/common/icon-picker.tsx delete mode 100644 web/ee/components/issue-types/common/index.ts delete mode 100644 web/ee/components/issue-types/common/issue-type-logo.tsx delete mode 100644 web/ee/components/issue-types/common/lucide-icons-list.tsx delete mode 100644 web/ee/components/issue-types/create-update/form.tsx delete mode 100644 web/ee/components/issue-types/create-update/index.ts delete mode 100644 web/ee/components/issue-types/create-update/modal.tsx delete mode 100644 web/ee/components/issue-types/dropdowns/index.ts delete mode 100644 web/ee/components/issue-types/dropdowns/issue-type.tsx delete mode 100644 web/ee/components/issue-types/empty-state.tsx delete mode 100644 web/ee/components/issue-types/index.ts delete mode 100644 web/ee/components/issue-types/issue-type-list-item.tsx delete mode 100644 web/ee/components/issue-types/issue-types-list.tsx delete mode 100644 web/ee/components/issue-types/properties/attributes/boolean.tsx delete mode 100644 web/ee/components/issue-types/properties/attributes/common/index.ts delete mode 100644 web/ee/components/issue-types/properties/attributes/common/property-multi-select.tsx delete mode 100644 web/ee/components/issue-types/properties/attributes/common/property-settings-configuration.tsx delete mode 100644 web/ee/components/issue-types/properties/attributes/date-picker.tsx delete mode 100644 web/ee/components/issue-types/properties/attributes/dropdown.tsx delete mode 100644 web/ee/components/issue-types/properties/attributes/index.ts delete mode 100644 web/ee/components/issue-types/properties/attributes/member-picker.tsx delete mode 100644 web/ee/components/issue-types/properties/attributes/number.tsx delete mode 100644 web/ee/components/issue-types/properties/attributes/options/create-option-item.tsx delete mode 100644 web/ee/components/issue-types/properties/attributes/options/default-option-select.tsx delete mode 100644 web/ee/components/issue-types/properties/attributes/options/index.ts delete mode 100644 web/ee/components/issue-types/properties/attributes/options/option.tsx delete mode 100644 web/ee/components/issue-types/properties/attributes/options/root.tsx delete mode 100644 web/ee/components/issue-types/properties/attributes/text.tsx delete mode 100644 web/ee/components/issue-types/properties/common/index.ts delete mode 100644 web/ee/components/issue-types/properties/common/issue-property-logo.tsx delete mode 100644 web/ee/components/issue-types/properties/delete-confirmation-modal.tsx delete mode 100644 web/ee/components/issue-types/properties/dropdowns/index.ts delete mode 100644 web/ee/components/issue-types/properties/dropdowns/property-attributes.tsx delete mode 100644 web/ee/components/issue-types/properties/dropdowns/property-title.tsx delete mode 100644 web/ee/components/issue-types/properties/dropdowns/property-type.tsx delete mode 100644 web/ee/components/issue-types/properties/dropdowns/selected-attribute-properties.tsx delete mode 100644 web/ee/components/issue-types/properties/empty-state.tsx delete mode 100644 web/ee/components/issue-types/properties/index.ts delete mode 100644 web/ee/components/issue-types/properties/mandatory-field-toggle.tsx delete mode 100644 web/ee/components/issue-types/properties/property-create-list-item.tsx delete mode 100644 web/ee/components/issue-types/properties/property-list-item.tsx delete mode 100644 web/ee/components/issue-types/properties/property-list.tsx delete mode 100644 web/ee/components/issue-types/properties/quick-actions.tsx delete mode 100644 web/ee/components/issue-types/properties/root.tsx delete mode 100644 web/ee/components/issue-types/quick-actions.tsx delete mode 100644 web/ee/components/issue-types/root.tsx delete mode 100644 web/ee/components/issue-types/values/components/boolean-input.tsx delete mode 100644 web/ee/components/issue-types/values/components/date-select.tsx delete mode 100644 web/ee/components/issue-types/values/components/index.ts delete mode 100644 web/ee/components/issue-types/values/components/member-select.tsx delete mode 100644 web/ee/components/issue-types/values/components/number-input.tsx delete mode 100644 web/ee/components/issue-types/values/components/option-select.tsx delete mode 100644 web/ee/components/issue-types/values/components/text-input.tsx delete mode 100644 web/ee/components/issue-types/values/create.tsx delete mode 100644 web/ee/components/issue-types/values/index.ts delete mode 100644 web/ee/components/issue-types/values/root.tsx delete mode 100644 web/ee/components/issue-types/values/update.tsx delete mode 100644 web/ee/components/issue-types/values/value-select.tsx delete mode 100644 web/ee/components/issues/filters/applied-filters/index.ts delete mode 100644 web/ee/components/issues/filters/applied-filters/issue-types.tsx delete mode 100644 web/ee/components/issues/filters/index.ts delete mode 100644 web/ee/components/issues/filters/issue-types.tsx delete mode 100644 web/ee/components/issues/issue-details/index.ts delete mode 100644 web/ee/components/issues/issue-details/issue-identifier.tsx delete mode 100644 web/ee/components/issues/issue-details/issue-properties-activity/boolean.tsx delete mode 100644 web/ee/components/issues/issue-details/issue-properties-activity/common/activity-block.tsx delete mode 100644 web/ee/components/issues/issue-details/issue-properties-activity/common/index.ts delete mode 100644 web/ee/components/issues/issue-details/issue-properties-activity/common/issue-user.tsx delete mode 100644 web/ee/components/issues/issue-details/issue-properties-activity/date.tsx delete mode 100644 web/ee/components/issues/issue-details/issue-properties-activity/dropdown.tsx delete mode 100644 web/ee/components/issues/issue-details/issue-properties-activity/index.ts delete mode 100644 web/ee/components/issues/issue-details/issue-properties-activity/member.tsx delete mode 100644 web/ee/components/issues/issue-details/issue-properties-activity/number.tsx delete mode 100644 web/ee/components/issues/issue-details/issue-properties-activity/root.tsx delete mode 100644 web/ee/components/issues/issue-details/issue-properties-activity/text.tsx delete mode 100644 web/ee/components/issues/issue-modal/additional-properties.tsx delete mode 100644 web/ee/components/issues/issue-modal/index.ts delete mode 100644 web/ee/components/issues/issue-modal/issue-type-select.tsx delete mode 100644 web/ee/components/issues/issue-modal/provider.tsx delete mode 100644 web/ee/components/issues/quick-add/index.ts delete mode 100644 web/ee/components/issues/quick-add/root.tsx delete mode 100644 web/ee/components/projects/applied-filters/access.tsx delete mode 100644 web/ee/components/projects/applied-filters/date.tsx delete mode 100644 web/ee/components/projects/applied-filters/index.ts delete mode 100644 web/ee/components/projects/applied-filters/members.tsx delete mode 100644 web/ee/components/projects/applied-filters/project-display-filters.tsx delete mode 100644 web/ee/components/projects/applied-filters/root.tsx delete mode 100644 web/ee/components/projects/common/join-button.tsx delete mode 100644 web/ee/components/projects/create/attributes.tsx delete mode 100644 web/ee/components/projects/create/root.tsx delete mode 100644 web/ee/components/projects/dropdowns/index.ts delete mode 100644 web/ee/components/projects/dropdowns/members-dropdown.tsx delete mode 100644 web/ee/components/projects/dropdowns/priority-dropdown.tsx delete mode 100644 web/ee/components/projects/dropdowns/scope-dropdown.tsx delete mode 100644 web/ee/components/projects/dropdowns/state-dropdown.tsx delete mode 100644 web/ee/components/projects/header/attributes-dropdown/access.tsx delete mode 100644 web/ee/components/projects/header/attributes-dropdown/index.ts delete mode 100644 web/ee/components/projects/header/attributes-dropdown/priority.tsx delete mode 100644 web/ee/components/projects/header/attributes-dropdown/root.tsx delete mode 100644 web/ee/components/projects/header/attributes-dropdown/state.tsx delete mode 100644 web/ee/components/projects/header/attributes-dropdown/users.tsx delete mode 100644 web/ee/components/projects/header/create-project-button.tsx delete mode 100644 web/ee/components/projects/header/display-filters-dropdown/group-by.tsx delete mode 100644 web/ee/components/projects/header/display-filters-dropdown/index.ts delete mode 100644 web/ee/components/projects/header/display-filters-dropdown/root.tsx delete mode 100644 web/ee/components/projects/header/display-filters-dropdown/sort-by.tsx delete mode 100644 web/ee/components/projects/header/display-filters-dropdown/sort-order.tsx delete mode 100644 web/ee/components/projects/header/index.ts delete mode 100644 web/ee/components/projects/header/layout-selection.tsx delete mode 100644 web/ee/components/projects/header/root.tsx delete mode 100644 web/ee/components/projects/header/search-projects.tsx delete mode 100644 web/ee/components/projects/index.ts delete mode 100644 web/ee/components/projects/layouts/attributes.tsx delete mode 100644 web/ee/components/projects/layouts/board/group-drag-overlay.tsx delete mode 100644 web/ee/components/projects/layouts/board/group-item-header/index.ts delete mode 100644 web/ee/components/projects/layouts/board/group-item-header/root.tsx delete mode 100644 web/ee/components/projects/layouts/board/group-item.tsx delete mode 100644 web/ee/components/projects/layouts/board/group.tsx delete mode 100644 web/ee/components/projects/layouts/board/index.ts delete mode 100644 web/ee/components/projects/layouts/board/list-item.tsx delete mode 100644 web/ee/components/projects/layouts/board/list.tsx delete mode 100644 web/ee/components/projects/layouts/board/root.tsx delete mode 100644 web/ee/components/projects/layouts/board/utils.tsx delete mode 100644 web/ee/components/projects/layouts/gallery/base-gallery-root.tsx delete mode 100644 web/ee/components/projects/layouts/gallery/card.tsx delete mode 100644 web/ee/components/projects/layouts/gallery/details.tsx delete mode 100644 web/ee/components/projects/layouts/gantt/base-gantt-root.tsx delete mode 100644 web/ee/components/projects/layouts/gantt/blocks.tsx delete mode 100644 web/ee/components/projects/layouts/gantt/index.ts delete mode 100644 web/ee/components/projects/layouts/gantt/sidebar.tsx delete mode 100644 web/ee/components/projects/layouts/index.ts delete mode 100644 web/ee/components/projects/layouts/list/base-list-root.tsx delete mode 100644 web/ee/components/projects/layouts/list/block.tsx delete mode 100644 web/ee/components/projects/layouts/list/blocks-list.tsx delete mode 100644 web/ee/components/projects/layouts/list/default.tsx delete mode 100644 web/ee/components/projects/layouts/list/headers/group-by-card.tsx delete mode 100644 web/ee/components/projects/layouts/list/index.ts delete mode 100644 web/ee/components/projects/layouts/list/list-group.tsx delete mode 100644 web/ee/components/projects/layouts/project-layout-HOC.tsx delete mode 100644 web/ee/components/projects/layouts/root.tsx delete mode 100644 web/ee/components/projects/layouts/spreadsheet/base-spreadsheet-root.tsx delete mode 100644 web/ee/components/projects/layouts/spreadsheet/columns/header-column.tsx delete mode 100644 web/ee/components/projects/layouts/spreadsheet/columns/index.ts delete mode 100644 web/ee/components/projects/layouts/spreadsheet/columns/issue-column.tsx delete mode 100644 web/ee/components/projects/layouts/spreadsheet/columns/lead-column.tsx delete mode 100644 web/ee/components/projects/layouts/spreadsheet/columns/members-column.tsx delete mode 100644 web/ee/components/projects/layouts/spreadsheet/columns/priority-column.tsx delete mode 100644 web/ee/components/projects/layouts/spreadsheet/columns/state-column.tsx delete mode 100644 web/ee/components/projects/layouts/spreadsheet/columns/updated-on-column.tsx delete mode 100644 web/ee/components/projects/layouts/spreadsheet/index.ts delete mode 100644 web/ee/components/projects/layouts/spreadsheet/project-column.tsx delete mode 100644 web/ee/components/projects/layouts/spreadsheet/project-row.tsx delete mode 100644 web/ee/components/projects/layouts/spreadsheet/spreadsheet-header-column.tsx delete mode 100644 web/ee/components/projects/layouts/spreadsheet/spreadsheet-header.tsx delete mode 100644 web/ee/components/projects/layouts/spreadsheet/spreadsheet-table.tsx delete mode 100644 web/ee/components/projects/layouts/spreadsheet/spreadsheet-view.tsx delete mode 100644 web/ee/components/projects/mobile-header.tsx delete mode 100644 web/ee/components/projects/page.tsx delete mode 100644 web/ee/components/projects/quick-actions.tsx delete mode 100644 web/ee/components/projects/root.tsx delete mode 100644 web/ee/components/workspace-project-states/index.ts delete mode 100644 web/ee/components/workspace-project-states/project-state-icon/cancelled.tsx delete mode 100644 web/ee/components/workspace-project-states/project-state-icon/completed.tsx delete mode 100644 web/ee/components/workspace-project-states/project-state-icon/draft.tsx delete mode 100644 web/ee/components/workspace-project-states/project-state-icon/execution.tsx delete mode 100644 web/ee/components/workspace-project-states/project-state-icon/index.ts delete mode 100644 web/ee/components/workspace-project-states/project-state-icon/monitoring.tsx delete mode 100644 web/ee/components/workspace-project-states/project-state-icon/planning.tsx delete mode 100644 web/ee/components/workspace-project-states/project-state-icon/root.tsx delete mode 100644 web/ee/components/workspace-project-states/project-state-icon/types.ts delete mode 100644 web/ee/components/workspace-project-states/settings/create-update/create.tsx delete mode 100644 web/ee/components/workspace-project-states/settings/create-update/form.tsx delete mode 100644 web/ee/components/workspace-project-states/settings/create-update/index.ts delete mode 100644 web/ee/components/workspace-project-states/settings/create-update/update.tsx delete mode 100644 web/ee/components/workspace-project-states/settings/empty-state.tsx delete mode 100644 web/ee/components/workspace-project-states/settings/group-list-item.tsx delete mode 100644 web/ee/components/workspace-project-states/settings/group-list.tsx delete mode 100644 web/ee/components/workspace-project-states/settings/index.ts delete mode 100644 web/ee/components/workspace-project-states/settings/loader.tsx delete mode 100644 web/ee/components/workspace-project-states/settings/options/delete.tsx delete mode 100644 web/ee/components/workspace-project-states/settings/options/index.ts delete mode 100644 web/ee/components/workspace-project-states/settings/options/mark-as-default.tsx delete mode 100644 web/ee/components/workspace-project-states/settings/root.tsx delete mode 100644 web/ee/components/workspace-project-states/settings/state-list-item.tsx delete mode 100644 web/ee/components/workspace-project-states/settings/state-list.tsx delete mode 100644 web/ee/components/workspace-project-states/settings/upgrade.tsx delete mode 100644 web/ee/constants/issue-properties.ts delete mode 100644 web/ee/constants/project/default-root.ts delete mode 100644 web/ee/constants/project/settings/tabs.ts delete mode 100644 web/ee/constants/project/spreadsheet.ts delete mode 100644 web/ee/constants/workspace-project-states.ts delete mode 100644 web/ee/helpers/issue-properties.helper.ts delete mode 100644 web/ee/helpers/issue-type.helper.ts delete mode 100644 web/ee/hooks/store/issue-types/index.ts delete mode 100644 web/ee/hooks/store/issue-types/use-issue-properties-activity.ts delete mode 100644 web/ee/hooks/store/issue-types/use-issue-property.ts delete mode 100644 web/ee/hooks/store/issue-types/use-issue-type.ts delete mode 100644 web/ee/hooks/store/issue-types/use-issue-types.ts delete mode 100644 web/ee/hooks/store/issue-types/use-property-options.tsx delete mode 100644 web/ee/hooks/store/use-workspace-features.ts delete mode 100644 web/ee/hooks/store/workspace-project-states/index.ts delete mode 100644 web/ee/hooks/store/workspace-project-states/use-project-filters.ts delete mode 100644 web/ee/hooks/store/workspace-project-states/use-project-state.ts delete mode 100644 web/ee/hooks/store/workspace-project-states/use-workspace-project-states.ts delete mode 100644 web/ee/hooks/use-editor-flagging.ts delete mode 100644 web/ee/lib/context/index.ts delete mode 100644 web/ee/lib/context/issue-property-option.tsx delete mode 100644 web/ee/lib/index.ts delete mode 100644 web/ee/services/api_token.service.ts delete mode 100644 web/ee/services/issue-types/index.ts delete mode 100644 web/ee/services/issue-types/issue-properties-activity.service.ts delete mode 100644 web/ee/services/issue-types/issue-properties.service.ts delete mode 100644 web/ee/services/issue-types/issue-property-options.service.ts delete mode 100644 web/ee/services/issue-types/issue-property-values.service.ts delete mode 100644 web/ee/services/issue-types/issue-types.service.ts delete mode 100644 web/ee/services/workspace-feature.service.ts delete mode 100644 web/ee/services/workspace-project-states.service.ts delete mode 100644 web/ee/store/issue-types/index.ts delete mode 100644 web/ee/store/issue-types/issue-properties-activity.store.ts delete mode 100644 web/ee/store/issue-types/issue-properties-activity.ts delete mode 100644 web/ee/store/issue-types/issue-property-option.ts delete mode 100644 web/ee/store/issue-types/issue-property.ts delete mode 100644 web/ee/store/issue-types/issue-type.ts delete mode 100644 web/ee/store/issue-types/issue-types.store.ts delete mode 100644 web/ee/store/projects/project-filter-helper.store.ts delete mode 100644 web/ee/store/projects/projects.ts delete mode 100644 web/ee/store/workspace-feature.store.ts delete mode 100644 web/ee/store/workspace-project-states/filters/filters.ts delete mode 100644 web/ee/store/workspace-project-states/filters/helpers.ts delete mode 100644 web/ee/store/workspace-project-states/filters/index.ts delete mode 100644 web/ee/store/workspace-project-states/index.ts delete mode 100644 web/ee/store/workspace-project-states/project-state.ts delete mode 100644 web/ee/store/workspace-project-states/workspace-project-states.store.ts delete mode 100644 web/ee/types/issue-types/index.ts delete mode 100644 web/ee/types/issue-types/issue-properties-activity.d.ts delete mode 100644 web/ee/types/issue-types/issue-properties.d.ts delete mode 100644 web/ee/types/issue-types/issue-property-configurations.d.ts delete mode 100644 web/ee/types/issue-types/issue-property-option.d.ts delete mode 100644 web/ee/types/issue-types/issue-property-values.d.ts delete mode 100644 web/ee/types/issue-types/issue-types.d.ts delete mode 100644 web/ee/types/projects/index.ts delete mode 100644 web/ee/types/projects/projects.ts delete mode 100644 web/ee/types/projects/types.ts delete mode 100644 web/ee/types/workspace-feature.ts delete mode 100644 web/ee/types/workspace-project-filters.ts delete mode 100644 web/ee/types/workspace-project-states.ts delete mode 100644 web/helpers/issue-modal.helper.ts delete mode 100644 web/public/auth/unauthorized.svg create mode 100644 web/public/empty-state/dashboard/project.svg delete mode 100644 web/public/empty-state/issue-types/issue-type-dark.svg delete mode 100644 web/public/empty-state/issue-types/issue-type-light.svg create mode 100644 web/public/onboarding/create-join-workspace-dark.svg delete mode 100644 web/public/onboarding/create-join-workspace-dark.webp delete mode 100644 web/public/onboarding/create-join-workspace-light.webp create mode 100644 web/public/onboarding/create-join-workspace.svg create mode 100644 web/public/onboarding/onboarding-pages.svg delete mode 100644 web/public/onboarding/onboarding-pages.webp create mode 100644 web/public/onboarding/profile-setup-dark.svg delete mode 100644 web/public/onboarding/profile-setup-dark.webp create mode 100644 web/public/onboarding/profile-setup-light.svg delete mode 100644 web/public/onboarding/profile-setup-light.webp delete mode 100644 web/public/projects/dark-upgrade.svg delete mode 100644 web/public/projects/light-upgrade.svg delete mode 100644 web/public/projects/project-states-dark.svg delete mode 100644 web/public/projects/project-states-light.svg delete mode 100644 web/public/projects/states-dark.svg delete mode 100644 web/public/projects/states-light.svg create mode 100644 web/public/sw.js.map diff --git a/.env.example b/.env.example index c893d7c25e..9965fe79ec 100644 --- a/.env.example +++ b/.env.example @@ -33,7 +33,4 @@ USE_MINIO=1 # Nginx Configuration NGINX_PORT=80 -# Imports Config -SILO_BASE_URL= - MONGO_DB_URL="mongodb://plane-mongodb:27017/" diff --git a/admin/app/general/form.tsx b/admin/app/general/form.tsx index 4422ee91ff..380f39357f 100644 --- a/admin/app/general/form.tsx +++ b/admin/app/general/form.tsx @@ -86,7 +86,6 @@ export const GeneralConfigurationForm: FC = observer( value={instanceAdmins[0]?.user_detail?.email ?? ""} placeholder="Admin email" className="w-full cursor-not-allowed !text-custom-text-400" - autoComplete="on" disabled /> diff --git a/admin/core/components/instance/setup-form.tsx b/admin/core/components/instance/setup-form.tsx index 7e987dbdf1..ec3919896d 100644 --- a/admin/core/components/instance/setup-form.tsx +++ b/admin/core/components/instance/setup-form.tsx @@ -174,7 +174,6 @@ export const InstanceSetupForm: FC = (props) => { placeholder="Wilber" value={formData.first_name} onChange={(e) => handleFormChange("first_name", e.target.value)} - autoComplete="on" autoFocus /> @@ -191,7 +190,6 @@ export const InstanceSetupForm: FC = (props) => { placeholder="Wright" value={formData.last_name} onChange={(e) => handleFormChange("last_name", e.target.value)} - autoComplete="on" /> @@ -210,7 +208,6 @@ export const InstanceSetupForm: FC = (props) => { value={formData.email} onChange={(e) => handleFormChange("email", e.target.value)} hasError={errorData.type && errorData.type === EErrorCodes.INVALID_EMAIL ? true : false} - autoComplete="on" /> {errorData.type && errorData.type === EErrorCodes.INVALID_EMAIL && errorData.message && (

{errorData.message}

@@ -250,7 +247,6 @@ export const InstanceSetupForm: FC = (props) => { hasError={errorData.type && errorData.type === EErrorCodes.INVALID_PASSWORD ? true : false} onFocus={() => setIsPasswordInputFocused(true)} onBlur={() => setIsPasswordInputFocused(false)} - autoComplete="on" /> {showPassword.password ? ( )} - {isOpen && - createPortal( - -
-
- - setQuery(e.target.value)} - placeholder="Search" - displayValue={(assigned: any) => assigned?.name} - /> -
-
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( - filteredOptions.map((option) => ( - - cn( - "w-full truncate flex items-center justify-between gap-2 rounded px-1 py-1.5 cursor-pointer select-none", - { - "bg-custom-background-80": active, - } - ) - } - onClick={() => { - if (!multiple) closeDropdown(); - }} - > - {({ selected }) => ( - <> - {option.content} - {selected && } - - )} - - )) - ) : ( -

No matches found

- ) - ) : ( -

Loading...

- )} -
- {footerOption} + {isOpen && ( + +
+
+ + setQuery(e.target.value)} + placeholder="Search" + displayValue={(assigned: any) => assigned?.name} + />
- , - document.body - )} +
+ {filteredOptions ? ( + filteredOptions.length > 0 ? ( + filteredOptions.map((option) => ( + + cn( + "w-full truncate flex items-center justify-between gap-2 rounded px-1 py-1.5 cursor-pointer select-none", + { + "bg-custom-background-80": active, + } + ) + } + onClick={() => { + if (!multiple) closeDropdown(); + }} + > + {({ selected }) => ( + <> + {option.content} + {selected && } + + )} + + )) + ) : ( +

No matches found

+ ) + ) : ( +

Loading...

+ )} +
+ {footerOption} +
+
+ )} ); }} diff --git a/packages/ui/src/dropdowns/helper.tsx b/packages/ui/src/dropdowns/helper.tsx index 8566f183b2..0eda5ac5c9 100644 --- a/packages/ui/src/dropdowns/helper.tsx +++ b/packages/ui/src/dropdowns/helper.tsx @@ -12,7 +12,6 @@ export interface IDropdownProps { label?: string | JSX.Element; maxHeight?: "sm" | "rg" | "md" | "lg"; noChevron?: boolean; - chevronClassName?: string; onOpen?: () => void; optionsClassName?: string; placement?: Placement; diff --git a/packages/ui/src/emoji/emoji-icon-picker-new.tsx b/packages/ui/src/emoji/emoji-icon-picker-new.tsx index 41ad696820..a8651b349e 100644 --- a/packages/ui/src/emoji/emoji-icon-picker-new.tsx +++ b/packages/ui/src/emoji/emoji-icon-picker-new.tsx @@ -101,7 +101,7 @@ export const EmojiIconPicker: React.FC = (props) => { type: EmojiIconPickerTypes.EMOJI, value: val, }); - if (closeOnSelect) handleToggle(false); + if (closeOnSelect) close(); }} height="20rem" width="100%" @@ -120,7 +120,7 @@ export const EmojiIconPicker: React.FC = (props) => { type: EmojiIconPickerTypes.ICON, value: val, }); - if (closeOnSelect) handleToggle(false); + if (closeOnSelect) close(); }} /> diff --git a/packages/ui/src/emoji/icons.ts b/packages/ui/src/emoji/icons.ts index ab9aa69b3f..3d650e244e 100644 --- a/packages/ui/src/emoji/icons.ts +++ b/packages/ui/src/emoji/icons.ts @@ -149,9 +149,6 @@ import { Minus, MinusCircle, MinusSquare, - CircleChevronDown, - UsersRound, - ToggleLeft, } from "lucide-react"; export const MATERIAL_ICONS_LIST = [ @@ -794,7 +791,6 @@ export const LUCIDE_ICONS_LIST = [ { name: "Camera", element: Camera }, { name: "CameraOff", element: CameraOff }, { name: "Cast", element: Cast }, - { name: "CircleChevronDown", element: CircleChevronDown }, { name: "Check", element: Check }, { name: "CheckCircle", element: CheckCircle }, { name: "CheckSquare", element: CheckSquare }, @@ -912,6 +908,4 @@ export const LUCIDE_ICONS_LIST = [ { name: "Minus", element: Minus }, { name: "MinusCircle", element: MinusCircle }, { name: "MinusSquare", element: MinusSquare }, - { name: "ToggleLeft", element: ToggleLeft }, - { name: "UsersRound", element: UsersRound }, ]; diff --git a/packages/ui/src/form-fields/checkbox-select.tsx b/packages/ui/src/form-fields/checkbox-select.tsx deleted file mode 100644 index a5e8f3926c..0000000000 --- a/packages/ui/src/form-fields/checkbox-select.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from "react"; -// helpers -import { cn } from "../../helpers"; -// components -import { Checkbox } from "./checkbox"; - -type CheckboxSelectProps = { - name?: string; - label?: string | React.ReactNode | undefined; - selected: T; - options: { label: string | React.ReactNode; key: T; indeterminate?: boolean; disabled?: boolean }[]; - onChange: (key: T) => void; - className?: string; - labelClassName?: string; - checkboxClassName?: { input?: string; icon?: string; label?: string; container?: string }; - fieldClassName?: string; - wrapperClassName?: string; - vertical?: boolean; -}; - -// TODO: refactor this to allow multiple selection -export const CheckboxSelect = ( - props: CheckboxSelectProps -) => { - const { - name = "checkbox-select", - label: inputLabel, - labelClassName = "", - wrapperClassName = "", - fieldClassName = "", - checkboxClassName: { - input: inputClassName = "", - icon: iconClassName = "", - label: checkboxLabelClassName = "", - container: containerClassName = "", - } = {}, - options, - vertical, - selected, - onChange, - className, - } = props; - - const wrapperClass = vertical ? "flex flex-col gap-1" : "flex gap-2"; - - const setSelected = (value: T) => { - onChange(value); - }; - - return ( -
- {inputLabel &&
{inputLabel}
} -
- {options.map(({ key, label, indeterminate, disabled }, index) => ( -
!disabled && setSelected(key)} - className={cn( - "flex items-center gap-2", - disabled ? `bg-custom-background-200 border-custom-border-200 cursor-not-allowed` : ``, - fieldClassName - )} - > - - -
- ))} -
-
- ); -}; diff --git a/packages/ui/src/form-fields/index.ts b/packages/ui/src/form-fields/index.ts index 38e6690a93..f19adcdc5c 100644 --- a/packages/ui/src/form-fields/index.ts +++ b/packages/ui/src/form-fields/index.ts @@ -2,4 +2,3 @@ export * from "./input"; export * from "./textarea"; export * from "./input-color-picker"; export * from "./checkbox"; -export * from "./checkbox-select"; diff --git a/packages/ui/src/form-fields/input.tsx b/packages/ui/src/form-fields/input.tsx index 4e71ea89a0..10f9fd85c1 100644 --- a/packages/ui/src/form-fields/input.tsx +++ b/packages/ui/src/form-fields/input.tsx @@ -4,24 +4,13 @@ import { cn } from "../../helpers"; export interface InputProps extends React.InputHTMLAttributes { mode?: "primary" | "transparent" | "true-transparent"; - inputSize?: "xs" | "sm" | "md"; + inputSize?: "sm" | "md"; hasError?: boolean; className?: string; - autoComplete?: "on" | "off"; } const Input = React.forwardRef((props, ref) => { - const { - id, - type, - name, - mode = "primary", - inputSize = "sm", - hasError = false, - className = "", - autoComplete = "off", - ...rest - } = props; + const { id, type, name, mode = "primary", inputSize = "sm", hasError = false, className = "", ...rest } = props; return ( ((props, ref) => { mode === "transparent", "rounded border-none bg-transparent ring-0": mode === "true-transparent", "border-red-500": hasError, - "px-1.5 py-1": inputSize === "xs", "px-3 py-2": inputSize === "sm", "p-3": inputSize === "md", }, className )} - autoComplete={autoComplete} {...rest} /> ); diff --git a/packages/ui/src/form-fields/textarea.tsx b/packages/ui/src/form-fields/textarea.tsx index 48cc311e38..e6927a9682 100644 --- a/packages/ui/src/form-fields/textarea.tsx +++ b/packages/ui/src/form-fields/textarea.tsx @@ -6,22 +6,12 @@ import { useAutoResizeTextArea } from "../hooks/use-auto-resize-textarea"; export interface TextAreaProps extends React.TextareaHTMLAttributes { mode?: "primary" | "transparent"; - textAreaSize?: "xs" | "sm" | "md"; hasError?: boolean; className?: string; } const TextArea = React.forwardRef((props, ref) => { - const { - id, - name, - value = "", - mode = "primary", - textAreaSize = "sm", - hasError = false, - className = "", - ...rest - } = props; + const { id, name, value = "", mode = "primary", hasError = false, className = "", ...rest } = props; // refs const textAreaRef = useRef(ref); // auto re-size @@ -34,14 +24,11 @@ const TextArea = React.forwardRef((props, re ref={textAreaRef} value={value} className={cn( - "no-scrollbar w-full bg-transparent placeholder-custom-text-400 outline-none", + "no-scrollbar w-full bg-transparent px-3 py-2 placeholder-custom-text-400 outline-none", { "rounded-md border-[0.5px] border-custom-border-200": mode === "primary", "focus:ring-theme rounded border-none bg-transparent ring-0 transition-all focus:ring-1": mode === "transparent", - "px-1.5 py-1": textAreaSize === "xs", - "px-3 py-2": textAreaSize === "sm", - "p-3": textAreaSize === "md", "border-red-500": hasError, "bg-red-100": hasError && mode === "primary", }, diff --git a/packages/ui/src/hooks/use-outside-click-detector.tsx b/packages/ui/src/hooks/use-outside-click-detector.tsx index c1a4778037..5331d11c88 100644 --- a/packages/ui/src/hooks/use-outside-click-detector.tsx +++ b/packages/ui/src/hooks/use-outside-click-detector.tsx @@ -1,31 +1,8 @@ import React, { useEffect } from "react"; -// TODO: move it to helpers package const useOutsideClickDetector = (ref: React.RefObject, callback: () => void) => { const handleClick = (event: MouseEvent) => { if (ref.current && !ref.current.contains(event.target as Node)) { - // get all the element with attribute name data-prevent-outside-click - const preventOutsideClickElements = document.querySelectorAll("[data-prevent-outside-click]"); - // check if the click target is any of the elements with attribute name data-prevent-outside-click - for (let i = 0; i < preventOutsideClickElements.length; i++) { - if (preventOutsideClickElements[i].contains(event.target as Node)) { - // if the click target is any of the elements with attribute name data-prevent-outside-click, return - return; - } - } - // get all the element with attribute name data-delay-outside-click - const delayOutsideClickElements = document.querySelectorAll("[data-delay-outside-click]"); - // check if the click target is any of the elements with attribute name data-delay-outside-click - for (let i = 0; i < delayOutsideClickElements.length; i++) { - if (delayOutsideClickElements[i].contains(event.target as Node)) { - // if the click target is any of the elements with attribute name data-delay-outside-click, delay the callback - setTimeout(() => { - callback(); - }, 1); - return; - } - } - // else, call the callback immediately callback(); } }; diff --git a/packages/ui/src/icons/index.ts b/packages/ui/src/icons/index.ts index 0c8186ac4c..f7fbd24dcb 100644 --- a/packages/ui/src/icons/index.ts +++ b/packages/ui/src/icons/index.ts @@ -16,12 +16,9 @@ export * from "./gitlab-icon"; export * from "./info-icon"; export * from "./layer-stack"; export * from "./layers-icon"; -export * from "./monospace-icon"; export * from "./photo-filter-icon"; export * from "./priority-icon"; export * from "./related-icon"; -export * from "./sans-serif-icon"; -export * from "./serif-icon"; export * from "./side-panel-icon"; export * from "./subscribe-icon"; export * from "./transfer-icon"; diff --git a/packages/ui/src/icons/monospace-icon.tsx b/packages/ui/src/icons/monospace-icon.tsx deleted file mode 100644 index 8eb64f01c5..0000000000 --- a/packages/ui/src/icons/monospace-icon.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import * as React from "react"; - -import { ISvgIcons } from "./type"; - -export const MonospaceIcon: React.FC = ({ className = "text-current", ...rest }) => ( - - - - -); diff --git a/packages/ui/src/icons/sans-serif-icon.tsx b/packages/ui/src/icons/sans-serif-icon.tsx deleted file mode 100644 index 2260130a7f..0000000000 --- a/packages/ui/src/icons/sans-serif-icon.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import * as React from "react"; - -import { ISvgIcons } from "./type"; - -export const SansSerifIcon: React.FC = ({ className = "text-current", ...rest }) => ( - - - - -); diff --git a/packages/ui/src/icons/serif-icon.tsx b/packages/ui/src/icons/serif-icon.tsx deleted file mode 100644 index fab5c12d33..0000000000 --- a/packages/ui/src/icons/serif-icon.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import * as React from "react"; - -import { ISvgIcons } from "./type"; - -export const SerifIcon: React.FC = ({ className = "text-current", ...rest }) => ( - - - - -); diff --git a/packages/ui/src/sortable/sortable.tsx b/packages/ui/src/sortable/sortable.tsx index b437f1df1c..b495d535ec 100644 --- a/packages/ui/src/sortable/sortable.tsx +++ b/packages/ui/src/sortable/sortable.tsx @@ -2,32 +2,27 @@ import React, { Fragment, useEffect, useMemo } from "react"; import { monitorForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; import { Draggable } from "./draggable"; -type TEnhancedData = T & { __uuid__?: string }; - type Props = { - data: TEnhancedData[]; + data: T[]; render: (item: T, index: number) => React.ReactNode; - onChange: (data: T[], movedItem?: T) => void; + onChange: (data: T[]) => void; keyExtractor: (item: T, index: number) => string; containerClassName?: string; id?: string; }; const moveItem = ( - data: TEnhancedData[], - source: TEnhancedData, - destination: TEnhancedData & Record, + data: T[], + source: T, + destination: T & Record, keyExtractor: (item: T, index: number) => string -): { - newData: T[]; - movedItem: T | undefined; -} => { +) => { const sourceIndex = data.findIndex((item, index) => keyExtractor(item, index) === keyExtractor(source, 0)); - if (sourceIndex === -1) return { newData: data, movedItem: undefined }; + if (sourceIndex === -1) return data; const destinationIndex = data.findIndex((item, index) => keyExtractor(item, index) === keyExtractor(destination, 0)); - if (destinationIndex === -1) return { newData: data, movedItem: undefined }; + if (destinationIndex === -1) return data; const symbolKey = Reflect.ownKeys(destination).find((key) => key.toString() === "Symbol(closestEdge)"); const position = symbolKey ? destination[symbolKey as symbol] : "bottom"; // Add 'as symbol' to cast symbolKey to symbol @@ -46,16 +41,7 @@ const moveItem = ( newData.splice(adjustedDestinationIndex, 0, movedItem); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { __uuid__: movedItemId, ...movedItemData } = movedItem; - return { - newData: newData.map((item) => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { __uuid__: uuid, ...rest } = item; - return rest as T; - }), - movedItem: movedItemData as T, - }; + return newData; }; export const Sortable = ({ data, render, onChange, keyExtractor, containerClassName, id }: Props) => { @@ -64,13 +50,7 @@ export const Sortable = ({ data, render, onChange, keyExtractor, containerCl onDrop({ source, location }) { const destination = location?.current?.dropTargets[0]; if (!destination) return; - const { newData, movedItem } = moveItem( - data, - source.data as TEnhancedData, - destination.data as TEnhancedData & { closestEdge: string }, - keyExtractor - ); - onChange(newData, movedItem); + onChange(moveItem(data, source.data as T, destination.data as T & { closestEdge: string }, keyExtractor)); }, }); @@ -87,13 +67,9 @@ export const Sortable = ({ data, render, onChange, keyExtractor, containerCl return ( <> - {data.map((item, index) => ( - - {render(item, index)} + {enhancedData.map((item, index) => ( + + {render(item, index)} ))} diff --git a/packages/ui/src/toast/index.tsx b/packages/ui/src/toast/index.tsx index 94cf1fa28c..ce2d05ef78 100644 --- a/packages/ui/src/toast/index.tsx +++ b/packages/ui/src/toast/index.tsx @@ -69,7 +69,7 @@ export const setToast = (props: SetToastProps) => { borderColorClassName, }: ToastContentProps) => props.type === TOAST_TYPE.LOADING ? ( -
+
{ e.stopPropagation(); @@ -96,7 +96,6 @@ export const setToast = (props: SetToastProps) => {
) : (
{ e.stopPropagation(); e.preventDefault(); diff --git a/setup.sh b/setup.sh index 9b84edeb59..838f5bbac9 100755 --- a/setup.sh +++ b/setup.sh @@ -11,4 +11,4 @@ cp ./space/.env.example ./space/.env cp ./admin/.env.example ./admin/.env # Generate the SECRET_KEY that will be used by django -echo -e "\nSECRET_KEY=\"$(tr -dc 'a-z0-9' < /dev/urandom | head -c50)\"" >> ./apiserver/.env +echo "SECRET_KEY=\"$(tr -dc 'a-z0-9' < /dev/urandom | head -c50)\"" >> ./apiserver/.env \ No newline at end of file diff --git a/space/core/components/account/auth-forms/email.tsx b/space/core/components/account/auth-forms/email.tsx index 6721aedab6..ec29a429dc 100644 --- a/space/core/components/account/auth-forms/email.tsx +++ b/space/core/components/account/auth-forms/email.tsx @@ -63,7 +63,6 @@ export const AuthEmailForm: FC = observer((props) => { className={`disable-autofill-style h-[46px] w-full placeholder:text-onboarding-text-400 autofill:bg-red-500 border-0 focus:bg-none active:bg-transparent`} onFocus={() => setFocused(true)} onBlur={() => setFocused(false)} - autoComplete="on" autoFocus /> {email.length > 0 && ( diff --git a/space/core/components/account/auth-forms/password.tsx b/space/core/components/account/auth-forms/password.tsx index 3d87cded59..4d4467d63e 100644 --- a/space/core/components/account/auth-forms/password.tsx +++ b/space/core/components/account/auth-forms/password.tsx @@ -157,7 +157,6 @@ export const AuthPasswordForm: React.FC = observer((props: Props) => { className="disable-autofill-style h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400" onFocus={() => setIsPasswordInputFocused(true)} onBlur={() => setIsPasswordInputFocused(false)} - autoComplete="on" autoFocus /> {showPassword?.password ? ( diff --git a/space/core/components/issues/filters/applied-filters/state.tsx b/space/core/components/issues/filters/applied-filters/state.tsx index 23bfc87e62..6eeabd71ff 100644 --- a/space/core/components/issues/filters/applied-filters/state.tsx +++ b/space/core/components/issues/filters/applied-filters/state.tsx @@ -15,7 +15,7 @@ type Props = { export const AppliedStateFilters: React.FC = observer((props) => { const { handleRemove, values } = props; - const { sortedStates: states } = useStates(); + const { states } = useStates(); return ( <> diff --git a/space/core/components/issues/filters/state.tsx b/space/core/components/issues/filters/state.tsx index 00cf4c0ff3..dcd76797a9 100644 --- a/space/core/components/issues/filters/state.tsx +++ b/space/core/components/issues/filters/state.tsx @@ -18,7 +18,7 @@ type Props = { export const FilterState: React.FC = observer((props) => { const { appliedFilters, handleUpdate, searchQuery } = props; - const { sortedStates: states } = useStates(); + const { states } = useStates(); const [itemsToRender, setItemsToRender] = useState(5); const [previewEnabled, setPreviewEnabled] = useState(true); diff --git a/space/core/components/issues/peek-overview/layout.tsx b/space/core/components/issues/peek-overview/layout.tsx index 0e4d0b85f2..e649a3279a 100644 --- a/space/core/components/issues/peek-overview/layout.tsx +++ b/space/core/components/issues/peek-overview/layout.tsx @@ -7,7 +7,7 @@ import { Dialog, Transition } from "@headlessui/react"; // components import { FullScreenPeekView, SidePeekView } from "@/components/issues/peek-overview"; // hooks -import { useIssueDetails } from "@/hooks/store"; +import { useIssue, useIssueDetails } from "@/hooks/store"; type TIssuePeekOverview = { anchor: string; @@ -29,14 +29,15 @@ export const IssuePeekOverview: FC = observer((props) => { const [isModalPeekOpen, setIsModalPeekOpen] = useState(false); // store const issueDetailStore = useIssueDetails(); + const issueStore = useIssue(); const issueDetails = issueDetailStore.peekId && peekId ? issueDetailStore.details[peekId.toString()] : undefined; useEffect(() => { - if (anchor && peekId) { + if (anchor && peekId && issueStore.groupedIssueIds) { issueDetailStore.fetchIssueDetails(anchor, peekId.toString()); } - }, [anchor, issueDetailStore, peekId]); + }, [anchor, issueDetailStore, peekId, issueStore.groupedIssueIds]); const handleClose = () => { // if close logic is passed down, call that instead of the below logic diff --git a/space/package.json b/space/package.json index d3bdc39175..e47ca51484 100644 --- a/space/package.json +++ b/space/package.json @@ -23,7 +23,7 @@ "@plane/types": "*", "@plane/ui": "*", "@sentry/nextjs": "^8", - "axios": "^1.7.4", + "axios": "^1.3.4", "clsx": "^2.0.0", "date-fns": "^3.6.0", "dompurify": "^3.0.11", diff --git a/space/public/onboarding/onboarding-pages.svg b/space/public/onboarding/onboarding-pages.svg new file mode 100644 index 0000000000..5ed5d44c2d --- /dev/null +++ b/space/public/onboarding/onboarding-pages.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/space/public/onboarding/profile-setup-dark.svg b/space/public/onboarding/profile-setup-dark.svg new file mode 100644 index 0000000000..69cceb716e --- /dev/null +++ b/space/public/onboarding/profile-setup-dark.svg @@ -0,0 +1,448 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/space/public/onboarding/profile-setup-light.svg b/space/public/onboarding/profile-setup-light.svg new file mode 100644 index 0000000000..1a290780dc --- /dev/null +++ b/space/public/onboarding/profile-setup-light.svg @@ -0,0 +1,407 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/[workspaceSlug]/(pages)/pages/layout.tsx b/web/app/[workspaceSlug]/(pages)/pages/layout.tsx index 7cec2731cc..9b14a5c094 100644 --- a/web/app/[workspaceSlug]/(pages)/pages/layout.tsx +++ b/web/app/[workspaceSlug]/(pages)/pages/layout.tsx @@ -1,7 +1,6 @@ "use client"; // layouts -import { useParams } from "next/navigation"; import { WorkspaceAuthWrapper } from "@/layouts/auth-layout"; // wrappers import { AuthenticationWrapper } from "@/lib/wrappers"; @@ -13,17 +12,10 @@ import { WorkspacePagesUpgrade } from "@/plane-web/components/pages"; import { PagesAppSidebar } from "./sidebar"; export default function WorkspacePagesLayout({ children }: { children: React.ReactNode }) { - // router - const { workspaceSlug } = useParams(); - return ( - } - > + }> <>
diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/page.tsx index 4171e1f332..b3ac8980f8 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/page.tsx @@ -6,10 +6,7 @@ import { useParams, useSearchParams } from "next/navigation"; import { TPageNavigationTabs } from "@plane/types"; // components import { PageHead } from "@/components/core"; -import { EmptyState } from "@/components/empty-state"; import { PagesListRoot, PagesListView } from "@/components/pages"; -// constants -import { EmptyStateType } from "@/constants/empty-state"; // hooks import { useProject } from "@/hooks/store"; @@ -19,7 +16,7 @@ const ProjectPagesPage = observer(() => { const type = searchParams.get("type"); const { workspaceSlug, projectId } = useParams(); // store hooks - const { getProjectById, currentProjectDetails } = useProject(); + const { getProjectById } = useProject(); // derived values const project = projectId ? getProjectById(projectId.toString()) : undefined; const pageTitle = project?.name ? `${project?.name} - Pages` : undefined; @@ -32,17 +29,6 @@ const ProjectPagesPage = observer(() => { }; if (!workspaceSlug || !projectId) return <>; - - // No access to cycle - if (currentProjectDetails?.page_view === false) - return ( -
- -
- ); return ( <> diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/automations/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/automations/page.tsx index bcf6258dd5..1676b25aa1 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/automations/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/automations/page.tsx @@ -7,9 +7,10 @@ import { IProject } from "@plane/types"; // ui import { TOAST_TYPE, setToast } from "@plane/ui"; // components -import { NotAuthorizedView } from "@/components/auth-screens"; import { AutoArchiveAutomation, AutoCloseAutomation } from "@/components/automation"; import { PageHead } from "@/components/core"; +// constants +import { EUserProjectRoles } from "@/constants/project"; // hooks import { useProject, useUser } from "@/hooks/store"; @@ -18,7 +19,6 @@ const AutomationSettingsPage = observer(() => { const { workspaceSlug, projectId } = useParams(); // store hooks const { - canPerformProjectAdminActions, membership: { currentProjectRole }, } = useUser(); const { currentProjectDetails: projectDetails, updateProject } = useProject(); @@ -36,16 +36,13 @@ const AutomationSettingsPage = observer(() => { }; // derived values + const isAdmin = currentProjectRole === EUserProjectRoles.ADMIN; const pageTitle = projectDetails?.name ? `${projectDetails?.name} - Automations` : undefined; - if (currentProjectRole && !canPerformProjectAdminActions) { - return ; - } - return ( <> -
+

Automations

diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/estimates/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/estimates/page.tsx index 84b01b86ab..f292bd6d99 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/estimates/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/estimates/page.tsx @@ -3,40 +3,30 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // components -import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; import { EstimateRoot } from "@/components/estimates"; +// constants +import { EUserProjectRoles } from "@/constants/project"; // hooks import { useUser, useProject } from "@/hooks/store"; const EstimatesSettingsPage = observer(() => { const { workspaceSlug, projectId } = useParams(); const { - canPerformProjectAdminActions, membership: { currentProjectRole }, } = useUser(); const { currentProjectDetails } = useProject(); // derived values + const isAdmin = currentProjectRole === EUserProjectRoles.ADMIN; const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Estimates` : undefined; if (!workspaceSlug || !projectId) return <>; - - if (currentProjectRole && !canPerformProjectAdminActions) { - return ; - } - return ( <> -
- +
+
); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/features/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/features/page.tsx index 8aba7c88ae..f09ae985b1 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/features/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/features/page.tsx @@ -2,10 +2,12 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; +import useSWR from "swr"; // components -import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; import { ProjectFeaturesList } from "@/components/project"; +// constants +import { EUserProjectRoles } from "@/constants/project"; // hooks import { useProject, useUser } from "@/hooks/store"; @@ -13,27 +15,28 @@ const FeaturesSettingsPage = observer(() => { const { workspaceSlug, projectId } = useParams(); // store const { - canPerformProjectAdminActions, - membership: { currentProjectRole }, + membership: { fetchUserProjectInfo }, } = useUser(); const { currentProjectDetails } = useProject(); + // fetch the project details + const { data: memberDetails } = useSWR( + workspaceSlug && projectId ? `PROJECT_MEMBERS_ME_${workspaceSlug}_${projectId}` : null, + workspaceSlug && projectId ? () => fetchUserProjectInfo(workspaceSlug.toString(), projectId.toString()) : null + ); // derived values + const isAdmin = memberDetails?.role === EUserProjectRoles.ADMIN; const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Features` : undefined; if (!workspaceSlug || !projectId) return null; - if (currentProjectRole && !canPerformProjectAdminActions) { - return ; - } - return ( <> -
+
diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/header.tsx index 6b6939d132..be9e3781a3 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/header.tsx @@ -9,12 +9,10 @@ import { Breadcrumbs, CustomMenu } from "@plane/ui"; // components import { BreadcrumbLink, Logo } from "@/components/common"; // constants -import { EUserProjectRoles } from "@/constants/project"; +import { EUserProjectRoles, PROJECT_SETTINGS_LINKS } from "@/constants/project"; // hooks import { useProject, useUser } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; -// plane web constants -import { PROJECT_SETTINGS_LINKS } from "@/plane-web/constants/project"; export const ProjectSettingHeader: FC = observer(() => { // router @@ -26,7 +24,7 @@ export const ProjectSettingHeader: FC = observer(() => { } = useUser(); const { currentProjectDetails, loader } = useProject(); - const projectMemberInfo = currentProjectRole || EUserProjectRoles.GUEST; + if (currentProjectRole && currentProjectRole <= EUserProjectRoles.VIEWER) return null; return (
@@ -72,17 +70,14 @@ export const ProjectSettingHeader: FC = observer(() => { placement="bottom-start" closeOnSelect > - {PROJECT_SETTINGS_LINKS.map( - (item) => - projectMemberInfo >= item.access && ( - router.push(`/${workspaceSlug}/projects/${projectId}${item.href}`)} - > - {item.label} - - ) - )} + {PROJECT_SETTINGS_LINKS.map((item) => ( + router.push(`/${workspaceSlug}/projects/${projectId}${item.href}`)} + > + {item.label} + + ))}
diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/issue-types/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/issue-types/page.tsx deleted file mode 100644 index ec8fd3236a..0000000000 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/issue-types/page.tsx +++ /dev/null @@ -1,31 +0,0 @@ -"use client"; - -import { observer } from "mobx-react"; -// components -import { PageHead } from "@/components/core"; -// hooks -import { EUserProjectRoles } from "@/constants/project"; -import { useProject, useUser } from "@/hooks/store"; -import { IssueTypesRoot } from "@/plane-web/components/issue-types"; - -const IssueTypesSettingsPage = observer(() => { - // store hooks - const { - membership: { currentProjectRole }, - } = useUser(); - const { currentProjectDetails } = useProject(); - // derived values - const isAdmin = currentProjectRole === EUserProjectRoles.ADMIN; - const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Issue Types` : undefined; - - return ( - <> - -
- -
- - ); -}); - -export default IssueTypesSettingsPage; diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/labels/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/labels/page.tsx index 0e88b9c5d5..192d7147f7 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/labels/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/labels/page.tsx @@ -5,19 +5,13 @@ import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element"; import { observer } from "mobx-react"; // components -import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; import { ProjectSettingsLabelList } from "@/components/labels"; // hooks -import { useProject, useUser } from "@/hooks/store"; +import { useProject } from "@/hooks/store"; const LabelsSettingsPage = observer(() => { - // store hooks const { currentProjectDetails } = useProject(); - const { - canPerformProjectMemberActions, - membership: { currentProjectRole }, - } = useUser(); const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Labels` : undefined; const scrollableContainerRef = useRef(null); @@ -35,10 +29,6 @@ const LabelsSettingsPage = observer(() => { ); }, [scrollableContainerRef?.current]); - if (currentProjectRole && !canPerformProjectMemberActions) { - return ; - } - return ( <> diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/layout.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/layout.tsx index 743c14d103..8553d86aa5 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/layout.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/layout.tsx @@ -1,8 +1,18 @@ "use client"; import { FC, ReactNode } from "react"; +import { observer } from "mobx-react"; +import Link from "next/link"; +import { useParams } from "next/navigation"; +// ui +import { Button, LayersIcon } from "@plane/ui"; // components +import { NotAuthorizedView } from "@/components/auth-screens"; import { AppHeader, ContentWrapper } from "@/components/core"; +// constants +import { EUserProjectRoles } from "@/constants/project"; +// hooks +import { useUser } from "@/hooks/store"; // local components import { ProjectSettingHeader } from "./header"; import { ProjectSettingsSidebar } from "./sidebar"; @@ -11,8 +21,33 @@ export interface IProjectSettingLayout { children: ReactNode; } -const ProjectSettingLayout: FC = (props) => { +const ProjectSettingLayout: FC = observer((props) => { const { children } = props; + // router + const { workspaceSlug, projectId } = useParams(); + // store hooks + const { + membership: { currentProjectRole }, + } = useUser(); + + const restrictViewSettings = currentProjectRole && currentProjectRole <= EUserProjectRoles.VIEWER; + + if (restrictViewSettings) { + return ( + + + + } + /> + ); + } + return ( <> } /> @@ -28,6 +63,6 @@ const ProjectSettingLayout: FC = (props) => { ); -}; +}); export default ProjectSettingLayout; diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/members/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/members/page.tsx index 28c65f680f..af1c82e12a 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/members/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/members/page.tsx @@ -2,26 +2,17 @@ import { observer } from "mobx-react"; // components -import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; import { ProjectMemberList, ProjectSettingsMemberDefaults } from "@/components/project"; // hooks -import { useProject, useUser } from "@/hooks/store"; +import { useProject } from "@/hooks/store"; const MembersSettingsPage = observer(() => { // store const { currentProjectDetails } = useProject(); - const { - canPerformProjectViewerActions, - membership: { currentProjectRole }, - } = useUser(); // derived values const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Members` : undefined; - if (currentProjectRole && !canPerformProjectViewerActions) { - return ; - } - return ( <> diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/sidebar.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/sidebar.tsx index 91630e9229..0b9a94f82e 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/sidebar.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/sidebar.tsx @@ -1,7 +1,6 @@ "use client"; import React from "react"; -import { observer } from "mobx-react"; import Link from "next/link"; import { useParams, usePathname } from "next/navigation"; // ui @@ -9,13 +8,11 @@ import { Loader } from "@plane/ui"; // components import { SidebarNavItem } from "@/components/sidebar"; // constants -import { EUserProjectRoles } from "@/constants/project"; +import { EUserProjectRoles, PROJECT_SETTINGS_LINKS } from "@/constants/project"; // hooks import { useUser } from "@/hooks/store"; -// plane web constants -import { PROJECT_SETTINGS_LINKS } from "@/plane-web/constants/project"; -export const ProjectSettingsSidebar = observer(() => { +export const ProjectSettingsSidebar = () => { const { workspaceSlug, projectId } = useParams(); const pathname = usePathname(); // mobx store @@ -63,4 +60,4 @@ export const ProjectSettingsSidebar = observer(() => {
); -}); +}; diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/states/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/states/page.tsx index b03310a091..4132662bf0 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/states/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/states/page.tsx @@ -3,27 +3,18 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // components -import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; import { ProjectStateRoot } from "@/components/project-states"; // hook -import { useProject, useUser } from "@/hooks/store"; +import { useProject } from "@/hooks/store"; const StatesSettingsPage = observer(() => { const { workspaceSlug, projectId } = useParams(); // store const { currentProjectDetails } = useProject(); - const { - canPerformProjectMemberActions, - membership: { currentProjectRole }, - } = useUser(); // derived values const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - States` : undefined; - if (currentProjectRole && !canPerformProjectMemberActions) { - return ; - } - return ( <> 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 27f84d94ef..26d5fd873c 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 @@ -12,17 +12,21 @@ import { BreadcrumbLink, Logo } from "@/components/common"; import { ViewListHeader } from "@/components/views"; import { ViewAppliedFiltersList } from "@/components/views/applied-filters"; // constants +import { EUserProjectRoles } from "@/constants/project"; import { EViewAccess } from "@/constants/views"; // helpers import { calculateTotalFilters } from "@/helpers/filter.helper"; // hooks -import { useCommandPalette, useProject, useProjectView } from "@/hooks/store"; +import { useCommandPalette, useProject, useProjectView, useUser } from "@/hooks/store"; export const ProjectViewsHeader = observer(() => { // router const { workspaceSlug } = useParams(); // store hooks const { toggleCreateViewModal } = useCommandPalette(); + const { + membership: { currentProjectRole }, + } = useUser(); const { currentProjectDetails, loader } = useProject(); const { filters, updateFilters, clearAllFilters } = useProjectView(); @@ -45,6 +49,9 @@ export const ProjectViewsHeader = observer(() => { const isFiltersApplied = calculateTotalFilters(filters?.filters ?? {}) !== 0; + const canUserCreateView = + currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole); + return ( <>
@@ -76,11 +83,13 @@ export const ProjectViewsHeader = observer(() => {
-
- -
+ {canUserCreateView && ( +
+ +
+ )}
{isFiltersApplied && ( diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/archives/layout.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/archives/layout.tsx deleted file mode 100644 index a308e19789..0000000000 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/archives/layout.tsx +++ /dev/null @@ -1,16 +0,0 @@ -"use client"; - -import { ReactNode } from "react"; -// components -import { AppHeader, ContentWrapper } from "@/components/core"; -// local components -import { ProjectsListHeader } from "@/plane-web/components/projects/header"; -import { ProjectsListMobileHeader } from "@/plane-web/components/projects/mobile-header"; -export default function ProjectListLayout({ children }: { children: ReactNode }) { - return ( - <> - } mobileHeader={} /> - {children} - - ); -} diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/archives/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/archives/page.tsx deleted file mode 100644 index ac6e5c3cd9..0000000000 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/archives/page.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import { ProjectPageRoot } from "@/plane-web/components/projects/page"; - -const ProjectsPage = () => ; -export default ProjectsPage; diff --git a/web/core/components/project/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(list)/header.tsx similarity index 95% rename from web/core/components/project/header.tsx rename to web/app/[workspaceSlug]/(projects)/projects/(list)/header.tsx index 5593ddb10b..04c6977819 100644 --- a/web/core/components/project/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(list)/header.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { observer } from "mobx-react"; -import { useParams, usePathname } from "next/navigation"; +import { useParams } from "next/navigation"; import { Search, Briefcase, X, ListFilter } from "lucide-react"; // types import { TProjectFilters } from "@plane/types"; @@ -21,7 +21,7 @@ import { calculateTotalFilters } from "@/helpers/filter.helper"; import { useCommandPalette, useEventTracker, useMember, useProjectFilter, useUser } from "@/hooks/store"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; -export const ProjectsBaseHeader = observer(() => { +export const ProjectsListHeader = observer(() => { // router const { workspaceSlug } = useParams(); // states @@ -34,8 +34,6 @@ export const ProjectsBaseHeader = observer(() => { const { membership: { currentWorkspaceRole }, } = useUser(); - const pathname = usePathname(); - const { currentWorkspaceDisplayFilters: displayFilters, currentWorkspaceFilters: filters, @@ -53,7 +51,6 @@ export const ProjectsBaseHeader = observer(() => { }); // auth const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; - const isArchived = pathname.includes("/archives"); const handleFilters = useCallback( (key: keyof TProjectFilters, value: string | string[]) => { @@ -100,7 +97,6 @@ export const ProjectsBaseHeader = observer(() => { type="text" link={} />} /> - {isArchived && } />}
diff --git a/web/app/[workspaceSlug]/(projects)/projects/(list)/layout.tsx b/web/app/[workspaceSlug]/(projects)/projects/(list)/layout.tsx index a308e19789..259c412dcb 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(list)/layout.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(list)/layout.tsx @@ -4,8 +4,9 @@ import { ReactNode } from "react"; // components import { AppHeader, ContentWrapper } from "@/components/core"; // local components -import { ProjectsListHeader } from "@/plane-web/components/projects/header"; -import { ProjectsListMobileHeader } from "@/plane-web/components/projects/mobile-header"; +import { ProjectsListHeader } from "./header"; +import { ProjectsListMobileHeader } from "./mobile-header"; + export default function ProjectListLayout({ children }: { children: ReactNode }) { return ( <> diff --git a/web/ce/components/projects/mobile-header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(list)/mobile-header.tsx similarity index 99% rename from web/ce/components/projects/mobile-header.tsx rename to web/app/[workspaceSlug]/(projects)/projects/(list)/mobile-header.tsx index 3804721600..cd8eb9dfe0 100644 --- a/web/ce/components/projects/mobile-header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(list)/mobile-header.tsx @@ -1,4 +1,3 @@ -"use client"; import { useCallback } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; @@ -24,6 +23,7 @@ export const ProjectsListMobileHeader = observer(() => { updateFilters, } = useProjectFilter(); + const { workspace: { workspaceMemberIds }, } = useMember(); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(list)/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(list)/page.tsx index ac6e5c3cd9..40e7f30a2a 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(list)/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(list)/page.tsx @@ -1,4 +1,84 @@ -import { ProjectPageRoot } from "@/plane-web/components/projects/page"; +"use client"; + +import { useCallback } from "react"; +import { observer } from "mobx-react"; +import { useParams } from "next/navigation"; +// types +import { TProjectAppliedDisplayFilterKeys, TProjectFilters } from "@plane/types"; +// components +import { PageHead } from "@/components/core"; +import { ProjectAppliedFiltersList, ProjectCardList } from "@/components/project"; +// helpers +import { calculateTotalFilters } from "@/helpers/filter.helper"; +// hooks +import { useProject, useProjectFilter, useWorkspace } from "@/hooks/store"; + +const ProjectsPage = observer(() => { + // store + const { workspaceSlug } = useParams(); + const { currentWorkspace } = useWorkspace(); + const { totalProjectIds, filteredProjectIds } = useProject(); + const { + currentWorkspaceFilters, + currentWorkspaceAppliedDisplayFilters, + clearAllFilters, + clearAllAppliedDisplayFilters, + updateFilters, + updateDisplayFilters, + } = useProjectFilter(); + // derived values + const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Projects` : undefined; + + const handleRemoveFilter = useCallback( + (key: keyof TProjectFilters, value: string | null) => { + if (!workspaceSlug) return; + let newValues = currentWorkspaceFilters?.[key] ?? []; + + if (!value) newValues = []; + else newValues = newValues.filter((val) => val !== value); + + updateFilters(workspaceSlug.toString(), { [key]: newValues }); + }, + [currentWorkspaceFilters, updateFilters, workspaceSlug] + ); + + const handleRemoveDisplayFilter = useCallback( + (key: TProjectAppliedDisplayFilterKeys) => { + if (!workspaceSlug) return; + updateDisplayFilters(workspaceSlug.toString(), { [key]: false }); + }, + [updateDisplayFilters, workspaceSlug] + ); + + const handleClearAllFilters = useCallback(() => { + if (!workspaceSlug) return; + clearAllFilters(workspaceSlug.toString()); + clearAllAppliedDisplayFilters(workspaceSlug.toString()); + }, [clearAllFilters, clearAllAppliedDisplayFilters, workspaceSlug]); + + return ( + <> + +
+ {(calculateTotalFilters(currentWorkspaceFilters ?? {}) !== 0 || + currentWorkspaceAppliedDisplayFilters?.length !== 0) && ( +
+ +
+ )} + +
+ + ); +}); -const ProjectsPage = () => ; export default ProjectsPage; diff --git a/web/app/[workspaceSlug]/(projects)/settings/api-tokens/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/api-tokens/page.tsx index bfc583b785..db0fd329d7 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/api-tokens/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/api-tokens/page.tsx @@ -8,13 +8,13 @@ import useSWR from "swr"; import { Button } from "@plane/ui"; // component import { ApiTokenListItem, CreateApiTokenModal } from "@/components/api-token"; -import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; import { EmptyState } from "@/components/empty-state"; import { APITokenSettingsLoader } from "@/components/ui"; // constants import { EmptyStateType } from "@/constants/empty-state"; import { API_TOKENS_LIST } from "@/constants/fetch-keys"; +import { EUserWorkspaceRoles } from "@/constants/workspace"; // store hooks import { useUser, useWorkspace } from "@/hooks/store"; // services @@ -29,22 +29,27 @@ const ApiTokensPage = observer(() => { const { workspaceSlug } = useParams(); // store hooks const { - canPerformWorkspaceAdminActions, membership: { currentWorkspaceRole }, } = useUser(); const { currentWorkspace } = useWorkspace(); - const { data: tokens } = useSWR( - workspaceSlug && canPerformWorkspaceAdminActions ? API_TOKENS_LIST(workspaceSlug.toString()) : null, - () => - workspaceSlug && canPerformWorkspaceAdminActions ? apiTokenService.getApiTokens(workspaceSlug.toString()) : null + const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN; + + const { data: tokens } = useSWR(workspaceSlug && isAdmin ? API_TOKENS_LIST(workspaceSlug.toString()) : null, () => + workspaceSlug && isAdmin ? apiTokenService.getApiTokens(workspaceSlug.toString()) : null ); const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - API Tokens` : undefined; - if (currentWorkspaceRole && !canPerformWorkspaceAdminActions) { - return ; - } + if (!isAdmin) + return ( + <> + +
+

You are not authorized to access this page.

+
+ + ); if (!tokens) { return ; diff --git a/web/app/[workspaceSlug]/(projects)/settings/billing/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/billing/page.tsx index 0158c3c98e..3e3c586c47 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/billing/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/billing/page.tsx @@ -2,8 +2,9 @@ import { observer } from "mobx-react"; // component -import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; +// constants +import { EUserWorkspaceRoles } from "@/constants/workspace"; // hooks import { useUser, useWorkspace } from "@/hooks/store"; // plane web components @@ -12,16 +13,22 @@ import { BillingRoot } from "@/plane-web/components/workspace"; const BillingSettingsPage = observer(() => { // store hooks const { - canPerformWorkspaceAdminActions, membership: { currentWorkspaceRole }, } = useUser(); const { currentWorkspace } = useWorkspace(); // derived values + const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN; const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Billing & Plans` : undefined; - if (currentWorkspaceRole && !canPerformWorkspaceAdminActions) { - return ; - } + if (!isAdmin) + return ( + <> + +
+

You are not authorized to access this page.

+
+ + ); return ( <> diff --git a/web/app/[workspaceSlug]/(projects)/settings/exports/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/exports/page.tsx index 85b8cd6442..59fd4d2c75 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/exports/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/exports/page.tsx @@ -2,39 +2,39 @@ import { observer } from "mobx-react"; // components -import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; import ExportGuide from "@/components/exporter/guide"; -// helpers -import { cn } from "@/helpers/common.helper"; +// constants +import { EUserWorkspaceRoles } from "@/constants/workspace"; // hooks import { useUser, useWorkspace } from "@/hooks/store"; const ExportsPage = observer(() => { // store hooks const { - canPerformWorkspaceViewerActions, - canPerformWorkspaceMemberActions, membership: { currentWorkspaceRole }, } = useUser(); const { currentWorkspace } = useWorkspace(); // derived values + const hasPageAccess = + currentWorkspaceRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentWorkspaceRole); const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Exports` : undefined; - // if user is not authorized to view this page - if (currentWorkspaceRole && !canPerformWorkspaceViewerActions) { - return ; - } + if (!hasPageAccess) + return ( + <> + +
+

You are not authorized to access this page.

+
+ + ); return ( <> -
+

Exports

@@ -44,4 +44,4 @@ const ExportsPage = observer(() => { ); }); -export default ExportsPage; +export default ExportsPage; \ No newline at end of file diff --git a/web/app/[workspaceSlug]/(projects)/settings/imports/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/imports/page.tsx index fb12275a67..100275f64d 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/imports/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/imports/page.tsx @@ -2,24 +2,18 @@ import { observer } from "mobx-react"; import Image from "next/image"; -import { useParams } from "next/navigation"; -import { getButtonStyling } from "@plane/ui"; // components +import { getButtonStyling } from "@plane/ui"; import { PageHead } from "@/components/core"; +import IntegrationGuide from "@/components/integration/guide"; // constants import { EUserWorkspaceRoles } from "@/constants/workspace"; // helpers import { cn } from "@/helpers/common.helper"; // hooks import { useUser, useWorkspace } from "@/hooks/store"; -// plane web components -import IntegrationGuide from "@/plane-web/components/integration/guide"; -// plane web hooks -import { useFlag } from "@/plane-web/hooks/store"; const ImportsPage = observer(() => { - // router - const { workspaceSlug } = useParams(); // store hooks const { userProfile: { data: userProfile }, @@ -32,8 +26,6 @@ const ImportsPage = observer(() => { const isDarkMode = userProfile?.theme.theme === "dark"; const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Imports` : undefined; - const isSiloIntegrationEnabled = useFlag(workspaceSlug?.toString(), "SILO_INTEGRATION"); - if (!isAdmin) return ( <> @@ -44,67 +36,64 @@ const ImportsPage = observer(() => { ); - if (!isSiloIntegrationEnabled) + if (true) return ( - <> - -
-
-

Imports

+
+
+

Imports

+
+
+
+
+

Imports are coming soon!

+

+ Get your issues, sprints, components, docs, and anything else with imports from Jira, GitHub, and + more—coming very soon to all editions and plans. +

+
+
-
-
-
-

Imports are coming soon!

-

- Get your issues, sprints, components, docs, and anything else with imports from Jira, GitHub, and - more—coming very soon to all editions and plans. -

-
- -
-
- - cta-1 - - - cta-2 - -
+
+ + cta-1 + + + cta-2 +
- +
); return ( <> -
-
+
+

Imports

diff --git a/web/app/[workspaceSlug]/(projects)/settings/imports/provider/[providerSlug]/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/imports/provider/[providerSlug]/page.tsx deleted file mode 100644 index 61d152ed2d..0000000000 --- a/web/app/[workspaceSlug]/(projects)/settings/imports/provider/[providerSlug]/page.tsx +++ /dev/null @@ -1,40 +0,0 @@ -"use client"; -import { useEffect } from "react"; -import { observer } from "mobx-react"; -import { useParams, useRouter } from "next/navigation"; -import { useInstance, useWorkspace } from "@/hooks/store"; -import SiloIframe from "@/plane-web/components/iframe/silo-iframe"; - -const ImportProviderPage = observer(() => { - const { workspaceSlug, providerSlug } = useParams(); - - const router = useRouter(); - - const { config } = useInstance(); - - const { currentWorkspace } = useWorkspace(); - - useEffect(() => { - const handleMessage = (e: MessageEvent) => { - if (e.data.msg === "created-migration") { - router.push(`/${workspaceSlug}/settings/imports`); - } - }; - - window.addEventListener("message", handleMessage, false); - - return () => { - window.removeEventListener("message", handleMessage, false); - }; - }, [router, workspaceSlug]); - - if (config?.silo_base_url) { - return ( - - ); - } -}); - -export default ImportProviderPage; diff --git a/web/app/[workspaceSlug]/(projects)/settings/integrations/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/integrations/page.tsx index 7a46dbc57e..43ae97ec9b 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/integrations/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/integrations/page.tsx @@ -51,7 +51,7 @@ const WorkspaceIntegrationsPage = observer(() => { if (true) return ( -
+

Integrations

diff --git a/web/app/[workspaceSlug]/(projects)/settings/layout.tsx b/web/app/[workspaceSlug]/(projects)/settings/layout.tsx index 64d83ddb7c..bd04e2d6ee 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/layout.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/layout.tsx @@ -25,7 +25,7 @@ export default function WorkspaceSettingLayout(props: IWorkspaceSettingLayout) {
-
+
{children}
diff --git a/web/app/[workspaceSlug]/(projects)/settings/members/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/members/page.tsx index 0d48bef73f..52b2256398 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/members/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/members/page.tsx @@ -9,13 +9,12 @@ import { IWorkspaceBulkInviteFormData } from "@plane/types"; // ui import { Button, TOAST_TYPE, setToast } from "@plane/ui"; // components -import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; import { SendWorkspaceInvitationModal, WorkspaceMembersList } from "@/components/workspace"; // constants import { MEMBER_INVITED } from "@/constants/event-tracker"; +import { EUserWorkspaceRoles } from "@/constants/workspace"; // helpers -import { cn } from "@/helpers/common.helper"; import { getUserRole } from "@/helpers/user.helper"; // hooks import { useEventTracker, useMember, useUser, useWorkspace } from "@/hooks/store"; @@ -29,9 +28,6 @@ const WorkspaceMembersSettingsPage = observer(() => { // store hooks const { captureEvent } = useEventTracker(); const { - canPerformWorkspaceAdminActions, - canPerformWorkspaceViewerActions, - canPerformWorkspaceMemberActions, membership: { currentWorkspaceRole }, } = useUser(); const { @@ -83,13 +79,9 @@ const WorkspaceMembersSettingsPage = observer(() => { }; // derived values + const isAdmin = currentWorkspaceRole && [EUserWorkspaceRoles.ADMIN].includes(currentWorkspaceRole); const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Members` : undefined; - // if user is not authorized to view this page - if (currentWorkspaceRole && !canPerformWorkspaceViewerActions) { - return ; - } - return ( <> @@ -98,11 +90,7 @@ const WorkspaceMembersSettingsPage = observer(() => { onClose={() => setInviteModal(false)} onSubmit={handleWorkspaceInvite} /> -
+

Members

@@ -115,13 +103,13 @@ const WorkspaceMembersSettingsPage = observer(() => { onChange={(e) => setSearchQuery(e.target.value)} />
- {canPerformWorkspaceAdminActions && ( + {isAdmin && ( )}
- +
); diff --git a/web/app/[workspaceSlug]/(projects)/settings/project-states/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/project-states/page.tsx deleted file mode 100644 index 3e0bd272ba..0000000000 --- a/web/app/[workspaceSlug]/(projects)/settings/project-states/page.tsx +++ /dev/null @@ -1,96 +0,0 @@ -"use client"; - -import React from "react"; -import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; -// component -import { ToggleSwitch } from "@plane/ui"; -import { PageHead } from "@/components/core"; -// constants -import { EUserWorkspaceRoles } from "@/constants/workspace"; -// store hooks -import { useUser, useWorkspace } from "@/hooks/store"; -// plane web components -import { WithFeatureFlagHOC } from "@/plane-web/components/feature-flags"; -import { - WorkspaceProjectStatesUpgrade, - WorkspaceProjectStatesRoot, -} from "@/plane-web/components/workspace-project-states"; -// plane web hooks -import { useFlag, useWorkspaceFeatures } from "@/plane-web/hooks/store"; -import { E_FEATURE_FLAGS } from "@/plane-web/hooks/store/use-flag"; -import { EWorkspaceFeatures } from "@/plane-web/types/workspace-feature"; - -const WorklogsPage = observer(() => { - // router - const { workspaceSlug } = useParams(); - // store hooks - const { - membership: { currentWorkspaceRole }, - } = useUser(); - const { currentWorkspace } = useWorkspace(); - const { isWorkspaceFeatureEnabled, updateWorkspaceFeature } = useWorkspaceFeatures(); - const isFeatureEnabled = useFlag(workspaceSlug?.toString(), E_FEATURE_FLAGS.PROJECT_GROUPING); - - // derived values - const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Project States` : undefined; - const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN; - const isProjectGroupingEnabled = isWorkspaceFeatureEnabled(EWorkspaceFeatures.IS_PROJECT_GROUPING_ENABLED); - - if (!workspaceSlug || !currentWorkspace?.id) return <>; - - if (!isAdmin) - return ( - <> - -
-

You are not authorized to access this page.

-
- - ); - - const toggleProjectGroupingFeature = async () => { - try { - const payload = { - [EWorkspaceFeatures.IS_PROJECT_GROUPING_ENABLED]: !isProjectGroupingEnabled, - }; - await updateWorkspaceFeature(workspaceSlug.toString(), payload); - } catch (error) { - console.error(error); - } - }; - - return ( - <> - - } - workspaceSlug={workspaceSlug?.toString()} - > -
-
-
-

See progress overview for all projects.

- - Projects States is a Plane-only feature for tracking progress of all your projects by any project - property. - -
- {isFeatureEnabled && ( - - )} -
- -
-
- - ); -}); - -export default WorklogsPage; diff --git a/web/app/[workspaceSlug]/(projects)/settings/webhooks/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/webhooks/page.tsx index a887c41440..695f1f16b2 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/webhooks/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/webhooks/page.tsx @@ -7,7 +7,6 @@ import useSWR from "swr"; // ui import { Button } from "@plane/ui"; // components -import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; import { EmptyState } from "@/components/empty-state"; import { WebhookSettingsLoader } from "@/components/ui"; @@ -24,15 +23,16 @@ const WebhooksListPage = observer(() => { const { workspaceSlug } = useParams(); // mobx store const { - canPerformWorkspaceAdminActions, membership: { currentWorkspaceRole }, } = useUser(); const { fetchWebhooks, webhooks, clearSecretKey, webhookSecretKey, createWebhook } = useWebhook(); const { currentWorkspace } = useWorkspace(); + const isAdmin = currentWorkspaceRole === 20; + useSWR( - workspaceSlug && canPerformWorkspaceAdminActions ? `WEBHOOKS_LIST_${workspaceSlug}` : null, - workspaceSlug && canPerformWorkspaceAdminActions ? () => fetchWebhooks(workspaceSlug.toString()) : null + workspaceSlug && isAdmin ? `WEBHOOKS_LIST_${workspaceSlug}` : null, + workspaceSlug && isAdmin ? () => fetchWebhooks(workspaceSlug.toString()) : null ); const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Webhooks` : undefined; @@ -42,9 +42,15 @@ const WebhooksListPage = observer(() => { if (!showCreateWebhookModal && webhookSecretKey) clearSecretKey(); }, [showCreateWebhookModal, webhookSecretKey, clearSecretKey]); - if (currentWorkspaceRole && !canPerformWorkspaceAdminActions) { - return ; - } + if (!isAdmin) + return ( + <> + +
+

You are not authorized to access this page.

+
+ + ); if (!webhooks) return ; @@ -89,4 +95,4 @@ const WebhooksListPage = observer(() => { ); }); -export default WebhooksListPage; +export default WebhooksListPage; \ No newline at end of file diff --git a/web/app/[workspaceSlug]/(projects)/settings/worklogs/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/worklogs/page.tsx index f1b57f32b9..71d2d5b010 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/worklogs/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/worklogs/page.tsx @@ -43,11 +43,7 @@ const WorklogsPage = observer(() => { return ( <> - } - > + }> diff --git a/web/app/[workspaceSlug]/(projects)/sidebar.tsx b/web/app/[workspaceSlug]/(projects)/sidebar.tsx index 338a8488b1..be09caa9b4 100644 --- a/web/app/[workspaceSlug]/(projects)/sidebar.tsx +++ b/web/app/[workspaceSlug]/(projects)/sidebar.tsx @@ -13,7 +13,7 @@ import { import { SidebarFavoritesMenu } from "@/components/workspace/sidebar/favorites/favorites-menu"; import { cn } from "@/helpers/common.helper"; // hooks -import { useAppTheme, useUser } from "@/hooks/store"; +import { useAppTheme } from "@/hooks/store"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; // plane web components import useSize from "@/hooks/use-window-size"; @@ -23,7 +23,6 @@ export interface IAppSidebar {} export const AppSidebar: FC = observer(() => { // store hooks - const { canPerformWorkspaceMemberActions } = useUser(); const { toggleSidebar, sidebarCollapsed } = useAppTheme(); const windowSize = useSize(); // refs @@ -86,7 +85,7 @@ export const AppSidebar: FC = observer(() => { "opacity-0": !sidebarCollapsed, })} /> - {canPerformWorkspaceMemberActions && } +
diff --git a/web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx b/web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx index 82e11b2083..686e4f7b60 100644 --- a/web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx @@ -14,10 +14,11 @@ import { DisplayFiltersSelection, FiltersDropdown, FilterSelection } from "@/com import { CreateUpdateWorkspaceViewModal } from "@/components/workspace"; // constants import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue"; +import { EUserWorkspaceRoles } from "@/constants/workspace"; // helpers import { isIssueFilterActive } from "@/helpers/filter.helper"; // hooks -import { useLabel, useMember, useIssues, useGlobalView } from "@/hooks/store"; +import { useLabel, useMember, useUser, useIssues, useGlobalView } from "@/hooks/store"; export const GlobalIssuesHeader = observer(() => { // states @@ -29,6 +30,9 @@ export const GlobalIssuesHeader = observer(() => { issuesFilter: { filters, updateFilters }, } = useIssues(EIssuesStoreType.GLOBAL); const { getViewDetailsById } = useGlobalView(); + const { + membership: { currentWorkspaceRole }, + } = useUser(); const { workspaceLabels } = useLabel(); const { workspace: { workspaceMemberIds }, @@ -93,6 +97,8 @@ export const GlobalIssuesHeader = observer(() => { [workspaceSlug, updateFilters, globalViewId] ); + const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; + const isLocked = viewDetails?.is_locked; return ( @@ -136,10 +142,11 @@ export const GlobalIssuesHeader = observer(() => { )} - - + {isAuthorizedUser && ( + + )}
diff --git a/web/app/accounts/forgot-password/page.tsx b/web/app/accounts/forgot-password/page.tsx index 91516c5b95..3e86e8eff5 100644 --- a/web/app/accounts/forgot-password/page.tsx +++ b/web/app/accounts/forgot-password/page.tsx @@ -154,7 +154,6 @@ export default function ForgotPasswordPage() { hasError={Boolean(errors.email)} placeholder="name@company.com" className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400" - autoComplete="on" disabled={resendTimerCode > 0} /> )} diff --git a/web/app/accounts/reset-password/page.tsx b/web/app/accounts/reset-password/page.tsx index 04d6e3115b..4d5303b985 100644 --- a/web/app/accounts/reset-password/page.tsx +++ b/web/app/accounts/reset-password/page.tsx @@ -153,7 +153,6 @@ export default function ResetPasswordPage() { //hasError={Boolean(errors.email)} placeholder="name@company.com" className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 text-onboarding-text-400 cursor-not-allowed" - autoComplete="on" disabled />
@@ -174,7 +173,6 @@ export default function ResetPasswordPage() { minLength={8} onFocus={() => setIsPasswordInputFocused(true)} onBlur={() => setIsPasswordInputFocused(false)} - autoComplete="on" autoFocus /> {showPassword.password ? ( diff --git a/web/app/accounts/set-password/page.tsx b/web/app/accounts/set-password/page.tsx index f3ac35b76f..df1b5d5675 100644 --- a/web/app/accounts/set-password/page.tsx +++ b/web/app/accounts/set-password/page.tsx @@ -147,7 +147,6 @@ const SetPasswordPage = observer(() => { //hasError={Boolean(errors.email)} placeholder="name@company.com" className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 text-onboarding-text-400 cursor-not-allowed" - autoComplete="on" disabled />
@@ -168,7 +167,6 @@ const SetPasswordPage = observer(() => { minLength={8} onFocus={() => setIsPasswordInputFocused(true)} onBlur={() => setIsPasswordInputFocused(false)} - autoComplete="on" autoFocus /> {showPassword.password ? ( diff --git a/web/app/profile/page.tsx b/web/app/profile/page.tsx index c9929db1e4..ebad3bc584 100644 --- a/web/app/profile/page.tsx +++ b/web/app/profile/page.tsx @@ -245,7 +245,6 @@ const ProfileSettingsPage = observer(() => { placeholder="Enter your first name" className={`w-full rounded-md ${errors.first_name ? "border-red-500" : ""}`} maxLength={24} - autoComplete="on" /> )} /> @@ -270,7 +269,6 @@ const ProfileSettingsPage = observer(() => { placeholder="Enter your last name" className="w-full rounded-md" maxLength={24} - autoComplete="on" /> )} /> @@ -298,7 +296,6 @@ const ProfileSettingsPage = observer(() => { className={`w-full cursor-not-allowed rounded-md !bg-custom-background-80 ${ errors.email ? "border-red-500" : "" }`} - autoComplete="on" disabled /> )} @@ -390,6 +387,7 @@ const ProfileSettingsPage = observer(() => { label={value ? TIME_ZONES.find((t) => t.value === value)?.label ?? value : "Select a timezone"} options={timeZoneOptions} onChange={onChange} + optionsClassName="w-full" buttonClassName={errors.user_timezone ? "border-red-500" : "border-none"} className="rounded-md border-[0.5px] !border-custom-border-200" input diff --git a/web/ce/components/issue-types/index.ts b/web/ce/components/issue-types/index.ts deleted file mode 100644 index 11413e4c19..0000000000 --- a/web/ce/components/issue-types/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./values"; diff --git a/web/ce/components/issue-types/values/index.ts b/web/ce/components/issue-types/values/index.ts deleted file mode 100644 index 635be6440d..0000000000 --- a/web/ce/components/issue-types/values/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./update"; diff --git a/web/ce/components/issue-types/values/update.tsx b/web/ce/components/issue-types/values/update.tsx deleted file mode 100644 index 52ccb4bd80..0000000000 --- a/web/ce/components/issue-types/values/update.tsx +++ /dev/null @@ -1,8 +0,0 @@ -type TIssueAdditionalPropertyValuesUpdateProps = { - issueId: string; - issueTypeId: string; - projectId: string; - workspaceSlug: string; -}; - -export const IssueAdditionalPropertyValuesUpdate: React.FC = () => <>; diff --git a/web/ce/components/issues/filters/applied-filters/index.ts b/web/ce/components/issues/filters/applied-filters/index.ts deleted file mode 100644 index 592325823c..0000000000 --- a/web/ce/components/issues/filters/applied-filters/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./issue-types"; diff --git a/web/ce/components/issues/filters/applied-filters/issue-types.tsx b/web/ce/components/issues/filters/applied-filters/issue-types.tsx deleted file mode 100644 index fd2daf9c82..0000000000 --- a/web/ce/components/issues/filters/applied-filters/issue-types.tsx +++ /dev/null @@ -1,11 +0,0 @@ -"use client"; - -import { observer } from "mobx-react"; - -type Props = { - handleRemove: (val: string) => void; - values: string[]; - editable: boolean | undefined; -}; - -export const AppliedIssueTypeFilters: React.FC = observer(() => null); diff --git a/web/ce/components/issues/filters/index.ts b/web/ce/components/issues/filters/index.ts deleted file mode 100644 index 2cd80e3a7e..0000000000 --- a/web/ce/components/issues/filters/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./applied-filters"; -export * from "./issue-types"; diff --git a/web/ce/components/issues/filters/issue-types.tsx b/web/ce/components/issues/filters/issue-types.tsx deleted file mode 100644 index bc364c8f80..0000000000 --- a/web/ce/components/issues/filters/issue-types.tsx +++ /dev/null @@ -1,12 +0,0 @@ -"use client"; - -import React from "react"; -import { observer } from "mobx-react"; - -type Props = { - appliedFilters: string[] | null; - handleUpdate: (val: string) => void; - searchQuery: string; -}; - -export const FilterIssueTypes: React.FC = observer(() => null); diff --git a/web/ce/components/issues/index.ts b/web/ce/components/issues/index.ts index 97b57af4b0..1c463b9b98 100644 --- a/web/ce/components/issues/index.ts +++ b/web/ce/components/issues/index.ts @@ -1,6 +1,2 @@ export * from "./bulk-operations"; export * from "./worklog"; -export * from "./issue-modal"; -export * from "./issue-details"; -export * from "./quick-add"; -export * from "./filters"; diff --git a/web/ce/components/issues/issue-details/index.ts b/web/ce/components/issues/issue-details/index.ts deleted file mode 100644 index a465ed4b3b..0000000000 --- a/web/ce/components/issues/issue-details/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./issue-identifier"; -export * from "./issue-properties-activity"; diff --git a/web/ce/components/issues/issue-details/issue-identifier.tsx b/web/ce/components/issues/issue-details/issue-identifier.tsx deleted file mode 100644 index 019acbac57..0000000000 --- a/web/ce/components/issues/issue-details/issue-identifier.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { observer } from "mobx-react"; -// helpers -import { cn } from "@/helpers/common.helper"; -// hooks -import { useIssueDetail, useProject } from "@/hooks/store"; - -type TIssueIdentifierBaseProps = { - projectId: string; - size?: "xs" | "sm" | "md" | "lg"; - textContainerClassName?: string; -}; - -type TIssueIdentifierFromStore = TIssueIdentifierBaseProps & { - issueId: string; -}; - -type TIssueIdentifierWithDetails = TIssueIdentifierBaseProps & { - issueTypeId?: string | null; - projectIdentifier: string; - issueSequenceId: string | number; -}; - -type TIssueIdentifierProps = TIssueIdentifierFromStore | TIssueIdentifierWithDetails; -export const IssueIdentifier: React.FC = observer((props) => { - const { projectId, textContainerClassName } = props; - // store hooks - const { getProjectIdentifierById } = useProject(); - const { - issue: { getIssueById }, - } = useIssueDetail(); - // Determine if the component is using store data or not - const isUsingStoreData = "issueId" in props; - // derived values - const issue = isUsingStoreData ? getIssueById(props.issueId) : null; - const projectIdentifier = isUsingStoreData ? getProjectIdentifierById(projectId) : props.projectIdentifier; - const issueSequenceId = isUsingStoreData ? issue?.sequence_id : props.issueSequenceId; - - return ( -
- - {projectIdentifier}-{issueSequenceId} - -
- ); -}); diff --git a/web/ce/components/issues/issue-details/issue-properties-activity/index.ts b/web/ce/components/issues/issue-details/issue-properties-activity/index.ts deleted file mode 100644 index 1efe34c51e..0000000000 --- a/web/ce/components/issues/issue-details/issue-properties-activity/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./root"; diff --git a/web/ce/components/issues/issue-details/issue-properties-activity/root.tsx b/web/ce/components/issues/issue-details/issue-properties-activity/root.tsx deleted file mode 100644 index cac7556764..0000000000 --- a/web/ce/components/issues/issue-details/issue-properties-activity/root.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { FC } from "react"; - -type TIssueAdditionalPropertiesActivity = { - activityId: string; - ends: "top" | "bottom" | undefined; -}; - -export const IssueAdditionalPropertiesActivity: FC = () => <>; diff --git a/web/ce/components/issues/issue-modal/additional-properties.tsx b/web/ce/components/issues/issue-modal/additional-properties.tsx deleted file mode 100644 index 228ab51e85..0000000000 --- a/web/ce/components/issues/issue-modal/additional-properties.tsx +++ /dev/null @@ -1,8 +0,0 @@ -type TIssueAdditionalPropertiesProps = { - issueId: string | undefined; - issueTypeId: string | null; - projectId: string; - workspaceSlug: string; -}; - -export const IssueAdditionalProperties: React.FC = () => <>; diff --git a/web/ce/components/issues/issue-modal/index.ts b/web/ce/components/issues/issue-modal/index.ts deleted file mode 100644 index f2c8494163..0000000000 --- a/web/ce/components/issues/issue-modal/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./provider"; -export * from "./issue-type-select"; -export * from "./additional-properties"; diff --git a/web/ce/components/issues/issue-modal/issue-type-select.tsx b/web/ce/components/issues/issue-modal/issue-type-select.tsx deleted file mode 100644 index a4b60103d6..0000000000 --- a/web/ce/components/issues/issue-modal/issue-type-select.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { Control } from "react-hook-form"; -// types -import { TIssue } from "@plane/types"; - -type TIssueTypeSelectProps = { - control: Control; - projectId: string | null; - disabled?: boolean; - handleFormChange: () => void; -}; - -export const IssueTypeSelect: React.FC = () => <>; diff --git a/web/ce/components/issues/issue-modal/provider.tsx b/web/ce/components/issues/issue-modal/provider.tsx deleted file mode 100644 index f387feb5a8..0000000000 --- a/web/ce/components/issues/issue-modal/provider.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import { observer } from "mobx-react-lite"; -// components -import { IssueModalContext } from "@/components/issues"; - -type TIssueModalProviderProps = { - children: React.ReactNode; -}; - -export const IssueModalProvider = observer((props: TIssueModalProviderProps) => { - const { children } = props; - return ( - {}, - issuePropertyValueErrors: {}, - setIssuePropertyValueErrors: () => {}, - getIssueTypeIdOnProjectChange: () => null, - getActiveAdditionalPropertiesLength: () => 0, - handlePropertyValuesValidation: () => true, - handleCreateUpdatePropertyValues: () => Promise.resolve(), - }} - > - {children} - - ); -}); diff --git a/web/ce/components/issues/quick-add/index.ts b/web/ce/components/issues/quick-add/index.ts deleted file mode 100644 index 1efe34c51e..0000000000 --- a/web/ce/components/issues/quick-add/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./root"; diff --git a/web/ce/components/issues/quick-add/root.tsx b/web/ce/components/issues/quick-add/root.tsx deleted file mode 100644 index 8ca22c3862..0000000000 --- a/web/ce/components/issues/quick-add/root.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { FC, useEffect, useRef } from "react"; -import { observer } from "mobx-react"; -import { UseFormRegister, UseFormSetFocus } from "react-hook-form"; -// types -import { TIssue } from "@plane/types"; -// components -import { - CalendarQuickAddIssueForm, - GanttQuickAddIssueForm, - KanbanQuickAddIssueForm, - ListQuickAddIssueForm, - SpreadsheetQuickAddIssueForm, - TQuickAddIssueForm, -} from "@/components/issues/issue-layouts"; -// constants -import { EIssueLayoutTypes } from "@/constants/issue"; -// hooks -import { useProject } from "@/hooks/store"; -import useKeypress from "@/hooks/use-keypress"; -import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; - -export type TQuickAddIssueFormRoot = { - isOpen: boolean; - layout: EIssueLayoutTypes; - prePopulatedData?: Partial; - projectId: string; - hasError?: boolean; - setFocus: UseFormSetFocus; - register: UseFormRegister; - onSubmit: () => void; - onClose: () => void; -}; - -export const QuickAddIssueFormRoot: FC = observer((props) => { - const { isOpen, layout, projectId, hasError = false, setFocus, register, onSubmit, onClose } = props; - // store hooks - const { getProjectById } = useProject(); - // derived values - const projectDetail = getProjectById(projectId); - // refs - const ref = useRef(null); - // click detection - useKeypress("Escape", onClose); - useOutsideClickDetector(ref, onClose); - // set focus on name input - useEffect(() => { - setFocus("name"); - }, [setFocus]); - - if (!projectDetail) return <>; - - const QUICK_ADD_ISSUE_FORMS: Record> = { - [EIssueLayoutTypes.LIST]: ListQuickAddIssueForm, - [EIssueLayoutTypes.KANBAN]: KanbanQuickAddIssueForm, - [EIssueLayoutTypes.CALENDAR]: CalendarQuickAddIssueForm, - [EIssueLayoutTypes.GANTT]: GanttQuickAddIssueForm, - [EIssueLayoutTypes.SPREADSHEET]: SpreadsheetQuickAddIssueForm, - }; - - const CurrentLayoutQuickAddIssueForm = QUICK_ADD_ISSUE_FORMS[layout] ?? null; - - if (!CurrentLayoutQuickAddIssueForm) return <>; - - return ( - - ); -}); diff --git a/web/ce/components/pages/editor/ai/ask-pi-menu.tsx b/web/ce/components/pages/editor/ai/ask-pi-menu.tsx deleted file mode 100644 index d3440ea479..0000000000 --- a/web/ce/components/pages/editor/ai/ask-pi-menu.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { useState } from "react"; -import { CircleArrowUp, CornerDownRight, RefreshCcw, Sparkles } from "lucide-react"; -// ui -import { Tooltip } from "@plane/ui"; -// components -import { RichTextReadOnlyEditor } from "@/components/editor"; -// helpers -import { cn } from "@/helpers/common.helper"; - -type Props = { - handleInsertText: (insertOnNextLine: boolean) => void; - handleRegenerate: () => Promise; - isRegenerating: boolean; - response: string | undefined; -}; - -export const AskPiMenu: React.FC = (props) => { - const { handleInsertText, handleRegenerate, isRegenerating, response } = props; - // states - const [query, setQuery] = useState(""); - - return ( - <> -
- - - - {response ? ( -
- -
- - - - - - - -
-
- ) : ( -

Pi is answering...

- )} -
-
-
- - - - setQuery(e.target.value)} - placeholder="Tell Pi what to do..." - /> - - - -
-
- - ); -}; diff --git a/web/ce/components/pages/editor/ai/index.ts b/web/ce/components/pages/editor/ai/index.ts deleted file mode 100644 index d21eb63d70..0000000000 --- a/web/ce/components/pages/editor/ai/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./ask-pi-menu"; -export * from "./menu"; diff --git a/web/ce/components/pages/editor/ai/menu.tsx b/web/ce/components/pages/editor/ai/menu.tsx deleted file mode 100644 index 5f8cb21835..0000000000 --- a/web/ce/components/pages/editor/ai/menu.tsx +++ /dev/null @@ -1,290 +0,0 @@ -"use client"; - -import React, { RefObject, useRef, useState } from "react"; -import { useParams } from "next/navigation"; -import { ChevronRight, CornerDownRight, LucideIcon, RefreshCcw, Sparkles, TriangleAlert } from "lucide-react"; -// plane editor -import { EditorRefApi } from "@plane/editor"; -// plane ui -import { Tooltip } from "@plane/ui"; -// plane web constants -import { AI_EDITOR_TASKS, LOADING_TEXTS } from "@/ce/constants/ai"; -// components -import { RichTextReadOnlyEditor } from "@/components/editor"; -// helpers -import { cn } from "@/helpers/common.helper"; -// plane web services -import { AIService, TTaskPayload } from "@/services/ai.service"; -import { AskPiMenu } from "./ask-pi-menu"; -const aiService = new AIService(); - -type Props = { - editorRef: RefObject; - onClose: () => void; -}; - -const MENU_ITEMS: { - icon: LucideIcon; - key: AI_EDITOR_TASKS; - label: string; -}[] = [ - { - key: AI_EDITOR_TASKS.ASK_ANYTHING, - icon: Sparkles, - label: "Ask Pi", - }, -]; - -const TONES_LIST = [ - { - key: "default", - label: "Default", - casual_score: 5, - formal_score: 5, - }, - { - key: "professional", - label: "💼 Professional", - casual_score: 0, - formal_score: 10, - }, - { - key: "casual", - label: "😃 Casual", - casual_score: 10, - formal_score: 0, - }, -]; - -export const EditorAIMenu: React.FC = (props) => { - const { editorRef, onClose } = props; - // states - const [activeTask, setActiveTask] = useState(null); - const [response, setResponse] = useState(undefined); - const [isRegenerating, setIsRegenerating] = useState(false); - // refs - const responseContainerRef = useRef(null); - // params - const { workspaceSlug } = useParams(); - const handleGenerateResponse = async (payload: TTaskPayload) => { - if (!workspaceSlug) return; - await aiService.performEditorTask(workspaceSlug.toString(), payload).then((res) => setResponse(res.response)); - }; - // handle task click - const handleClick = async (key: AI_EDITOR_TASKS) => { - const selection = editorRef.current?.getSelectedText(); - if (!selection || activeTask === key) return; - setActiveTask(key); - if (key === AI_EDITOR_TASKS.ASK_ANYTHING) return; - setResponse(undefined); - setIsRegenerating(false); - await handleGenerateResponse({ - task: key, - text_input: selection, - }); - }; - // handle re-generate response - const handleRegenerate = async () => { - const selection = editorRef.current?.getSelectedText(); - if (!selection || !activeTask) return; - setIsRegenerating(true); - await handleGenerateResponse({ - task: activeTask, - text_input: selection, - }) - .then(() => - responseContainerRef.current?.scrollTo({ - top: 0, - behavior: "smooth", - }) - ) - .finally(() => setIsRegenerating(false)); - }; - // handle re-generate response - const handleToneChange = async (key: string) => { - const selectedTone = TONES_LIST.find((t) => t.key === key); - const selection = editorRef.current?.getSelectedText(); - if (!selectedTone || !selection || !activeTask) return; - setResponse(undefined); - setIsRegenerating(false); - await handleGenerateResponse({ - casual_score: selectedTone.casual_score, - formal_score: selectedTone.formal_score, - task: activeTask, - text_input: selection, - }).then(() => - responseContainerRef.current?.scrollTo({ - top: 0, - behavior: "smooth", - }) - ); - }; - // handle replace selected text with the response - const handleInsertText = (insertOnNextLine: boolean) => { - if (!response) return; - editorRef.current?.insertText(response, insertOnNextLine); - onClose(); - }; - - return ( -
-
-
- {MENU_ITEMS.map((item) => { - const isActiveTask = activeTask === item.key; - - return ( - - ); - })} -
-
- {activeTask === AI_EDITOR_TASKS.ASK_ANYTHING ? ( - - ) : ( - <> -
- - - - {response ? ( -
- -
- - - - - - - -
-
- ) : ( -

- {activeTask ? LOADING_TEXTS[activeTask] : "Pi is writing"}... -

- )} -
-
- {TONES_LIST.map((tone) => ( - - ))} -
- - )} -
-
- {activeTask && ( -
- - - -

- By using this feature, you consent to sharing the message with a 3rd party service. -

-
- )} -
- ); -}; diff --git a/web/ce/components/pages/editor/index.ts b/web/ce/components/pages/editor/index.ts index 88b26fa277..12b3c5295b 100644 --- a/web/ce/components/pages/editor/index.ts +++ b/web/ce/components/pages/editor/index.ts @@ -1,2 +1 @@ -export * from "./ai"; export * from "./embed"; diff --git a/web/ce/components/projects/create/attributes.tsx b/web/ce/components/projects/create/attributes.tsx deleted file mode 100644 index ead92208f4..0000000000 --- a/web/ce/components/projects/create/attributes.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { Controller, useFormContext } from "react-hook-form"; -import { IProject } from "@plane/types"; -import { CustomSelect } from "@plane/ui"; -import { MemberDropdown } from "@/components/dropdowns"; -import { NETWORK_CHOICES } from "@/constants/project"; - -const ProjectAttributes = () => { - const { control } = useFormContext(); - return ( -
- { - const currentNetwork = NETWORK_CHOICES.find((n) => n.key === value); - - return ( -
- - {currentNetwork ? ( - <> - - {currentNetwork.label} - - ) : ( - Select network - )} -
- } - placement="bottom-start" - className="h-full" - buttonClassName="h-full" - noChevron - tabIndex={4} - > - {NETWORK_CHOICES.map((network) => ( - -
- -
-

{network.label}

-

{network.description}

-
-
-
- ))} - -
- ); - }} - /> - { - if (value === undefined || value === null || typeof value === "string") - return ( -
- onChange(lead === value ? null : lead)} - placeholder="Lead" - multiple={false} - buttonVariant="border-with-text" - tabIndex={5} - /> -
- ); - else return <>; - }} - /> -
- ); -}; - -export default ProjectAttributes; diff --git a/web/ce/components/projects/create/root.tsx b/web/ce/components/projects/create/root.tsx deleted file mode 100644 index 76fea48f79..0000000000 --- a/web/ce/components/projects/create/root.tsx +++ /dev/null @@ -1,139 +0,0 @@ -"use client"; - -import { useState, FC } from "react"; -import { observer } from "mobx-react"; -import { FormProvider, useForm } from "react-hook-form"; -// ui -import { setToast, TOAST_TYPE } from "@plane/ui"; -// constants -import ProjectCommonAttributes from "@/components/project/create/common-attributes"; -import ProjectCreateHeader from "@/components/project/create/header"; -import ProjectCreateButtons from "@/components/project/create/project-create-buttons"; -import { PROJECT_CREATED } from "@/constants/event-tracker"; -import { PROJECT_UNSPLASH_COVERS } from "@/constants/project"; -// helpers -import { getRandomEmoji } from "@/helpers/emoji.helper"; -// hooks -import { useEventTracker, useProject } from "@/hooks/store"; -import { usePlatformOS } from "@/hooks/use-platform-os"; -import { TProject } from "@/plane-web/types/projects"; -import ProjectAttributes from "./attributes"; - -type Props = { - setToFavorite?: boolean; - workspaceSlug: string; - onClose: () => void; - handleNextStep: (projectId: string) => void; - data?: Partial; -}; - -const defaultValues: Partial = { - cover_image: PROJECT_UNSPLASH_COVERS[Math.floor(Math.random() * PROJECT_UNSPLASH_COVERS.length)], - description: "", - logo_props: { - in_use: "emoji", - emoji: { - value: getRandomEmoji(), - }, - }, - identifier: "", - name: "", - network: 2, - project_lead: null, -}; - -export const CreateProjectForm: FC = observer((props) => { - const { setToFavorite, workspaceSlug, onClose, handleNextStep } = props; - // store - const { captureProjectEvent } = useEventTracker(); - const { addProjectToFavorites, createProject } = useProject(); - // states - const [isChangeInIdentifierRequired, setIsChangeInIdentifierRequired] = useState(true); - // form info - const methods = useForm({ - defaultValues, - reValidateMode: "onChange", - }); - const { handleSubmit, reset, setValue } = methods; - const { isMobile } = usePlatformOS(); - const handleAddToFavorites = (projectId: string) => { - if (!workspaceSlug) return; - - addProjectToFavorites(workspaceSlug.toString(), projectId).catch(() => { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Couldn't remove the project from favorites. Please try again.", - }); - }); - }; - - const onSubmit = async (formData: Partial) => { - // Upper case identifier - formData.identifier = formData.identifier?.toUpperCase(); - - return createProject(workspaceSlug.toString(), formData) - .then((res) => { - const newPayload = { - ...res, - state: "SUCCESS", - }; - captureProjectEvent({ - eventName: PROJECT_CREATED, - payload: newPayload, - }); - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Project created successfully.", - }); - if (setToFavorite) { - handleAddToFavorites(res.id); - } - handleNextStep(res.id); - }) - .catch((err) => { - Object.keys(err.data).map((key) => { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: err.data[key], - }); - captureProjectEvent({ - eventName: PROJECT_CREATED, - payload: { - ...formData, - state: "FAILED", - }, - }); - }); - }); - }; - - const handleClose = () => { - onClose(); - setIsChangeInIdentifierRequired(true); - setTimeout(() => { - reset(); - }, 300); - }; - - return ( - - - -
-
- - -
- - -
- ); -}); diff --git a/web/ce/components/projects/header.tsx b/web/ce/components/projects/header.tsx deleted file mode 100644 index 08871ec9b6..0000000000 --- a/web/ce/components/projects/header.tsx +++ /dev/null @@ -1,5 +0,0 @@ -"use client"; - -import { ProjectsBaseHeader } from "@/components/project/header"; - -export const ProjectsListHeader = () => ; diff --git a/web/ce/components/projects/page.tsx b/web/ce/components/projects/page.tsx deleted file mode 100644 index a44ab7df40..0000000000 --- a/web/ce/components/projects/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import Root from "@/components/project/root"; - -export const ProjectPageRoot = () => ; diff --git a/web/ce/constants/ai.ts b/web/ce/constants/ai.ts deleted file mode 100644 index c5c1b04fa9..0000000000 --- a/web/ce/constants/ai.ts +++ /dev/null @@ -1,9 +0,0 @@ -export enum AI_EDITOR_TASKS { - ASK_ANYTHING = "ASK_ANYTHING", -} - -export const LOADING_TEXTS: { - [key in AI_EDITOR_TASKS]: string; -} = { - [AI_EDITOR_TASKS.ASK_ANYTHING]: "Pi is generating response", -}; diff --git a/web/ce/constants/project/settings/index.ts b/web/ce/constants/project/settings/index.ts index a6a842e7be..0e849261ac 100644 --- a/web/ce/constants/project/settings/index.ts +++ b/web/ce/constants/project/settings/index.ts @@ -1,2 +1 @@ export * from "./features"; -export * from "./tabs"; diff --git a/web/ce/constants/project/settings/tabs.ts b/web/ce/constants/project/settings/tabs.ts deleted file mode 100644 index d067fba92e..0000000000 --- a/web/ce/constants/project/settings/tabs.ts +++ /dev/null @@ -1,82 +0,0 @@ -// icons -import { SettingIcon } from "@/components/icons/attachment"; -// types -import { Props } from "@/components/icons/types"; -// constants -import { EUserProjectRoles } from "@/constants/project"; - -export const PROJECT_SETTINGS = { - general: { - key: "general", - label: "General", - href: `/settings`, - access: EUserProjectRoles.GUEST, - highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/`, - Icon: SettingIcon, - }, - members: { - key: "members", - label: "Members", - href: `/settings/members`, - access: EUserProjectRoles.VIEWER, - highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/members/`, - Icon: SettingIcon, - }, - features: { - key: "features", - label: "Features", - href: `/settings/features`, - access: EUserProjectRoles.ADMIN, - highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/features/`, - Icon: SettingIcon, - }, - states: { - key: "states", - label: "States", - href: `/settings/states`, - access: EUserProjectRoles.MEMBER, - highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/states/`, - Icon: SettingIcon, - }, - labels: { - key: "labels", - label: "Labels", - href: `/settings/labels`, - access: EUserProjectRoles.MEMBER, - highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/labels/`, - Icon: SettingIcon, - }, - estimates: { - key: "estimates", - label: "Estimates", - href: `/settings/estimates`, - access: EUserProjectRoles.ADMIN, - highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/estimates/`, - Icon: SettingIcon, - }, - automations: { - key: "automations", - label: "Automations", - href: `/settings/automations`, - access: EUserProjectRoles.ADMIN, - highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/automations/`, - Icon: SettingIcon, - }, -}; - -export const PROJECT_SETTINGS_LINKS: { - key: string; - label: string; - href: string; - access: EUserProjectRoles; - highlight: (pathname: string, baseUrl: string) => boolean; - Icon: React.FC; -}[] = [ - PROJECT_SETTINGS["general"], - PROJECT_SETTINGS["members"], - PROJECT_SETTINGS["features"], - PROJECT_SETTINGS["states"], - PROJECT_SETTINGS["labels"], - PROJECT_SETTINGS["estimates"], - PROJECT_SETTINGS["automations"], -]; diff --git a/web/ce/constants/workspace.ts b/web/ce/constants/workspace.ts index 109a7a4d14..b89ced416d 100644 --- a/web/ce/constants/workspace.ts +++ b/web/ce/constants/workspace.ts @@ -17,7 +17,7 @@ export const WORKSPACE_SETTINGS = { key: "members", label: "Members", href: `/settings/members`, - access: EUserWorkspaceRoles.VIEWER, + access: EUserWorkspaceRoles.GUEST, highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/members/`, Icon: SettingIcon, }, @@ -33,7 +33,7 @@ export const WORKSPACE_SETTINGS = { key: "export", label: "Exports", href: `/settings/exports`, - access: EUserWorkspaceRoles.VIEWER, + access: EUserWorkspaceRoles.MEMBER, highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/exports/`, Icon: SettingIcon, }, diff --git a/web/ce/hooks/use-editor-flagging.ts b/web/ce/hooks/use-editor-flagging.ts deleted file mode 100644 index 9077d216e6..0000000000 --- a/web/ce/hooks/use-editor-flagging.ts +++ /dev/null @@ -1,13 +0,0 @@ -// editor -import { TExtensions } from "@plane/editor"; - -/** - * @description extensions disabled in various editors - */ -export const useEditorFlagging = (): { - documentEditor: TExtensions[]; - richTextEditor: TExtensions[]; -} => ({ - documentEditor: ["ai"], - richTextEditor: ["ai"], -}); diff --git a/web/ce/hooks/use-issue-embed.tsx b/web/ce/hooks/use-issue-embed.tsx index 5d02d978fa..9f085fa41f 100644 --- a/web/ce/hooks/use-issue-embed.tsx +++ b/web/ce/hooks/use-issue-embed.tsx @@ -1,19 +1,2 @@ -// editor -import { TEmbedConfig } from "@plane/editor"; -// types -import { TPageEmbedType } from "@plane/types"; -// plane web components -import { IssueEmbedUpgradeCard } from "@/plane-web/components/pages"; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export const useIssueEmbed = (workspaceSlug: string, projectId: string, queryType: TPageEmbedType = "issue") => { - const widgetCallback = () => ; - - const issueEmbedProps: TEmbedConfig["issue"] = { - widgetCallback, - }; - - return { - issueEmbedProps, - }; -}; +// Removed the implementation for the CE hook as it was using an overridden type causing the build to fail. +export * from "ee/hooks/use-issue-embed"; diff --git a/web/ce/types/index.ts b/web/ce/types/index.ts deleted file mode 100644 index 0d4b66523e..0000000000 --- a/web/ce/types/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./projects"; -export * from "./issue-types"; diff --git a/web/ce/types/issue-types/index.ts b/web/ce/types/issue-types/index.ts deleted file mode 100644 index 7259fa3518..0000000000 --- a/web/ce/types/issue-types/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./issue-property-values.d"; diff --git a/web/ce/types/issue-types/issue-property-values.d.ts b/web/ce/types/issue-types/issue-property-values.d.ts deleted file mode 100644 index e1d94dbc84..0000000000 --- a/web/ce/types/issue-types/issue-property-values.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export type TIssuePropertyValues = object; -export type TIssuePropertyValueErrors = object; diff --git a/web/ce/types/projects/index.ts b/web/ce/types/projects/index.ts deleted file mode 100644 index 244d8c4df3..0000000000 --- a/web/ce/types/projects/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./projects"; diff --git a/web/ce/types/projects/projects.ts b/web/ce/types/projects/projects.ts deleted file mode 100644 index 567c9488db..0000000000 --- a/web/ce/types/projects/projects.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { IProject } from "@plane/types"; - -export type TProject = IProject; diff --git a/web/core/components/account/auth-forms/email.tsx b/web/core/components/account/auth-forms/email.tsx index ab328a7e1b..9acfcc5cc2 100644 --- a/web/core/components/account/auth-forms/email.tsx +++ b/web/core/components/account/auth-forms/email.tsx @@ -63,7 +63,6 @@ export const AuthEmailForm: FC = observer((props) => { className={`disable-autofill-style h-[46px] w-full placeholder:text-onboarding-text-400 autofill:bg-red-500 border-0 focus:bg-none active:bg-transparent`} onFocus={() => setFocused(true)} onBlur={() => setFocused(false)} - autoComplete="on" autoFocus /> {email.length > 0 && ( diff --git a/web/core/components/account/auth-forms/password.tsx b/web/core/components/account/auth-forms/password.tsx index 088dc31949..618c578b0d 100644 --- a/web/core/components/account/auth-forms/password.tsx +++ b/web/core/components/account/auth-forms/password.tsx @@ -208,7 +208,6 @@ export const AuthPasswordForm: React.FC = observer((props: Props) => { className="disable-autofill-style h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400" onFocus={() => setIsPasswordInputFocused(true)} onBlur={() => setIsPasswordInputFocused(false)} - autoComplete="on" autoFocus /> {showPassword?.password ? ( diff --git a/web/core/components/account/auth-forms/unique-code.tsx b/web/core/components/account/auth-forms/unique-code.tsx index 530874eb9c..8c7b8a60fd 100644 --- a/web/core/components/account/auth-forms/unique-code.tsx +++ b/web/core/components/account/auth-forms/unique-code.tsx @@ -107,7 +107,6 @@ export const AuthUniqueCodeForm: React.FC = (props) => { onChange={(e) => handleFormChange("email", e.target.value)} placeholder="name@company.com" className={`disable-autofill-style h-[46px] w-full placeholder:text-onboarding-text-400 border-0`} - autoComplete="on" disabled /> {uniqueCodeFormData.email.length > 0 && ( diff --git a/web/core/components/analytics/custom-analytics/select/project.tsx b/web/core/components/analytics/custom-analytics/select/project.tsx index e9d226a7e6..4562cff554 100644 --- a/web/core/components/analytics/custom-analytics/select/project.tsx +++ b/web/core/components/analytics/custom-analytics/select/project.tsx @@ -46,6 +46,7 @@ export const SelectProject: React.FC = observer((props) => { : "All projects"}
} + optionsClassName="w-48" multiple /> ); diff --git a/web/core/components/auth-screens/not-authorized-view.tsx b/web/core/components/auth-screens/not-authorized-view.tsx index fe344f468f..f8f101dd30 100644 --- a/web/core/components/auth-screens/not-authorized-view.tsx +++ b/web/core/components/auth-screens/not-authorized-view.tsx @@ -1,33 +1,62 @@ import React from "react"; import { observer } from "mobx-react"; import Image from "next/image"; +import Link from "next/link"; +import { useSearchParams } from "next/navigation"; +// hooks +import { useUser } from "@/hooks/store"; // layouts import DefaultLayout from "@/layouts/default-layout"; // images import ProjectNotAuthorizedImg from "@/public/auth/project-not-authorized.svg"; -import Unauthorized from "@/public/auth/unauthorized.svg"; import WorkspaceNotAuthorizedImg from "@/public/auth/workspace-not-authorized.svg"; type Props = { actionButton?: React.ReactNode; - section?: "settings" | "general"; - isProjectView?: boolean; + type: "project" | "workspace"; }; export const NotAuthorizedView: React.FC = observer((props) => { - const { actionButton, section = "general", isProjectView = false } = props; - - // assets - const settingAsset = isProjectView ? ProjectNotAuthorizedImg : WorkspaceNotAuthorizedImg; - const asset = section === "settings" ? settingAsset : Unauthorized; + const { actionButton, type } = props; + // router + const searchParams = useSearchParams(); + const next_path = searchParams.get("next_path"); + // hooks + const { data: currentUser } = useUser(); return (
- ProjectSettingImg + ProjectSettingImg

Oops! You are not authorized to view this page

+ +
+ {currentUser ? ( +

+ You have signed in as {currentUser.email}.
+ + Sign in + {" "} + with different account that has access to this page. +

+ ) : ( +

+ You need to{" "} + + Sign in + {" "} + with an account that has access to this page. +

+ )} +
+ {actionButton}
diff --git a/web/core/components/command-palette/command-modal.tsx b/web/core/components/command-palette/command-modal.tsx index e7b65b4490..f0c779c55a 100644 --- a/web/core/components/command-palette/command-modal.tsx +++ b/web/core/components/command-palette/command-modal.tsx @@ -7,11 +7,10 @@ import { useParams } from "next/navigation"; import useSWR from "swr"; import { FolderPlus, Search, Settings } from "lucide-react"; import { Dialog, Transition } from "@headlessui/react"; -// types +// icons import { IWorkspaceSearchResults } from "@plane/types"; -// ui +// hooks import { LayersIcon, Loader, ToggleSwitch, Tooltip } from "@plane/ui"; -// components import { ChangeIssueAssignee, ChangeIssuePriority, @@ -24,28 +23,28 @@ import { CommandPaletteWorkspaceSettingsActions, } from "@/components/command-palette"; import { EmptyState } from "@/components/empty-state"; -// constants import { EmptyStateType } from "@/constants/empty-state"; -// fetch-keys import { ISSUE_DETAILS } from "@/constants/fetch-keys"; -// hooks import { useCommandPalette, useEventTracker, useProject, useUser } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; import useDebounce from "@/hooks/use-debounce"; import { usePlatformOS } from "@/hooks/use-platform-os"; -// plane web components -import { IssueIdentifier } from "@/plane-web/components/issues"; -// plane web services -import { WorkspaceService } from "@/plane-web/services"; // services +import { WorkspaceService } from "@/plane-web/services"; import { IssueService } from "@/services/issue"; +// ui +// components +// types +// fetch-keys +// constants + const workspaceService = new WorkspaceService(); const issueService = new IssueService(); export const CommandModal: React.FC = observer(() => { // hooks - const { workspaceProjectIds } = useProject(); + const { getProjectById, workspaceProjectIds } = useProject(); const { isMobile } = usePlatformOS(); const { canPerformAnyCreateAction } = useUser(); // states @@ -142,6 +141,8 @@ export const CommandModal: React.FC = observer(() => { [debouncedSearchTerm, isWorkspaceLevel, projectId, workspaceSlug] // Only call effect if debounced search term changes ); + const projectDetails = getProjectById(issueDetails?.project_id ?? ""); + return ( setSearchTerm("")} as={React.Fragment}> closePalette()}> @@ -197,15 +198,8 @@ export const CommandModal: React.FC = observer(() => { }`} > {issueDetails && ( -
- {issueDetails.project_id && ( - - )} - {issueDetails.name} +
+ {projectDetails?.identifier}-{issueDetails.sequence_id} {issueDetails.name}
)} {projectId && ( diff --git a/web/core/components/command-palette/command-palette.tsx b/web/core/components/command-palette/command-palette.tsx index 62f0491450..099d53f909 100644 --- a/web/core/components/command-palette/command-palette.tsx +++ b/web/core/components/command-palette/command-palette.tsx @@ -43,8 +43,8 @@ export const CommandPalette: FC = observer(() => { const { platform } = usePlatformOS(); const { data: currentUser, - canPerformProjectMemberActions, - canPerformWorkspaceMemberActions, + canPerformProjectCreateActions, + canPerformWorkspaceCreateActions, canPerformAnyCreateAction, canPerformProjectAdminActions, } = useUser(); @@ -103,15 +103,15 @@ export const CommandPalette: FC = observer(() => { // auth const performProjectCreateActions = useCallback( (showToast: boolean = true) => { - if (!canPerformProjectMemberActions && showToast) + if (!canPerformProjectCreateActions && showToast) setToast({ type: TOAST_TYPE.ERROR, title: "You don't have permission to perform this action.", }); - return canPerformProjectMemberActions; + return canPerformProjectCreateActions; }, - [canPerformProjectMemberActions] + [canPerformProjectCreateActions] ); const performProjectBulkDeleteActions = useCallback( @@ -129,14 +129,14 @@ export const CommandPalette: FC = observer(() => { const performWorkspaceCreateActions = useCallback( (showToast: boolean = true) => { - if (!canPerformWorkspaceMemberActions && showToast) + if (!canPerformWorkspaceCreateActions && showToast) setToast({ type: TOAST_TYPE.ERROR, title: "You don't have permission to perform this action.", }); - return canPerformWorkspaceMemberActions; + return canPerformWorkspaceCreateActions; }, - [canPerformWorkspaceMemberActions] + [canPerformWorkspaceCreateActions] ); const performAnyProjectCreateActions = useCallback( @@ -212,6 +212,7 @@ export const CommandPalette: FC = observer(() => { toggleCreatePageModal, toggleCreateProjectModal, toggleCreateViewModal, + toggleShortcutModal, ] ); @@ -260,18 +261,15 @@ export const CommandPalette: FC = observer(() => { if ( Object.keys(shortcutsList.global).includes(keyPressed) && ((!projectId && performAnyProjectCreateActions()) || performProjectCreateActions()) - ) { + ) shortcutsList.global[keyPressed].action(); - } // workspace authorized actions else if ( Object.keys(shortcutsList.workspace).includes(keyPressed) && workspaceSlug && performWorkspaceCreateActions() - ) { - e.preventDefault(); + ) shortcutsList.workspace[keyPressed].action(); - } // project authorized actions else if ( Object.keys(shortcutsList.project).includes(keyPressed) && @@ -285,18 +283,16 @@ export const CommandPalette: FC = observer(() => { } }, [ + performAnyProjectCreateActions, + performProjectCreateActions, + performProjectBulkDeleteActions, + performWorkspaceCreateActions, copyIssueUrlToClipboard, isAnyModalOpen, - platform, - performAnyProjectCreateActions, - performProjectBulkDeleteActions, - performProjectCreateActions, - performWorkspaceCreateActions, projectId, setTrackElement, shortcutsList, toggleCommandPaletteModal, - toggleShortcutModal, toggleSidebar, workspaceSlug, ] diff --git a/web/core/components/command-palette/helpers.tsx b/web/core/components/command-palette/helpers.tsx index 99c8c310e4..cc3e80bb7c 100644 --- a/web/core/components/command-palette/helpers.tsx +++ b/web/core/components/command-palette/helpers.tsx @@ -9,14 +9,11 @@ import { IWorkspaceProjectSearchResult, IWorkspaceSearchResult, } from "@plane/types"; -// ui -import { ContrastIcon, DiceIcon } from "@plane/ui"; -// plane web components -import { IssueIdentifier } from "@/plane-web/components/issues"; +import { ContrastIcon, DiceIcon, LayersIcon } from "@plane/ui"; export const commandGroups: { [key: string]: { - icon: JSX.Element | null; + icon: JSX.Element; itemName: (item: any) => React.ReactNode; path: (item: any, projectId: string | undefined) => string; title: string; @@ -34,18 +31,14 @@ export const commandGroups: { title: "Cycles", }, issue: { - icon: null, + icon: , itemName: (issue: IWorkspaceIssueSearchResult) => ( -
- {" "} +
+ + {issue.project__identifier}-{issue.sequence_id} + {" "} {issue.name} -
+ ), path: (issue: IWorkspaceIssueSearchResult) => `/${issue?.workspace__slug}/projects/${issue?.project_id}/issues/${issue?.id}`, diff --git a/web/core/components/common/latest-feature-block.tsx b/web/core/components/common/latest-feature-block.tsx index a108e05bf2..48595241b1 100644 --- a/web/core/components/common/latest-feature-block.tsx +++ b/web/core/components/common/latest-feature-block.tsx @@ -4,7 +4,7 @@ import { useTheme } from "next-themes"; // icons import { Lightbulb } from "lucide-react"; // images -import latestFeatures from "@/public/onboarding/onboarding-pages.webp"; +import latestFeatures from "@/public/onboarding/onboarding-pages.svg"; export const LatestFeatureBlock = () => { const { resolvedTheme } = useTheme(); diff --git a/web/core/components/core/activity.tsx b/web/core/components/core/activity.tsx index 9a06b336b4..94273f4a9a 100644 --- a/web/core/components/core/activity.tsx +++ b/web/core/components/core/activity.tsx @@ -165,7 +165,7 @@ const activityDetails: { ); }, - icon:
diff --git a/web/core/components/core/modals/bulk-delete-issues-modal.tsx b/web/core/components/core/modals/bulk-delete-issues-modal.tsx index 1d397a315e..fc1a4cd9aa 100644 --- a/web/core/components/core/modals/bulk-delete-issues-modal.tsx +++ b/web/core/components/core/modals/bulk-delete-issues-modal.tsx @@ -6,21 +6,22 @@ import { useParams } from "next/navigation"; import { SubmitHandler, useForm } from "react-hook-form"; import { Search } from "lucide-react"; import { Combobox, Dialog, Transition } from "@headlessui/react"; -// types +//plane import { ISearchIssueResponse, IUser } from "@plane/types"; -// ui import { Button, Loader, TOAST_TYPE, setToast } from "@plane/ui"; -// components +//components import { EmptyState } from "@/components/empty-state"; -// constants +//constants import { EmptyStateType } from "@/constants/empty-state"; import { EIssuesStoreType } from "@/constants/issue"; -// hooks -import { useIssues } from "@/hooks/store"; +//hooks +import { useIssues, useProject } from "@/hooks/store"; import useDebounce from "@/hooks/use-debounce"; // services import { ProjectService } from "@/services/project"; -// local components +// ui +// icons +// components import { BulkDeleteIssuesModalItem } from "./bulk-delete-issues-modal-item"; type FormInput = { @@ -40,6 +41,7 @@ export const BulkDeleteIssuesModal: React.FC = observer((props) => { // router params const { workspaceSlug, projectId } = useParams(); // hooks + const { getProjectById } = useProject(); const { issues: { removeBulkIssues }, } = useIssues(EIssuesStoreType.PROJECT); @@ -113,6 +115,8 @@ export const BulkDeleteIssuesModal: React.FC = observer((props) => { ); }; + const projectDetails = getProjectById(projectId as string); + const issueList = issues.length > 0 ? (
  • @@ -123,6 +127,7 @@ export const BulkDeleteIssuesModal: React.FC = observer((props) => { {issues.map((issue) => ( diff --git a/web/core/components/core/modals/existing-issues-list-modal.tsx b/web/core/components/core/modals/existing-issues-list-modal.tsx index 780a8642d0..a07582b8af 100644 --- a/web/core/components/core/modals/existing-issues-list-modal.tsx +++ b/web/core/components/core/modals/existing-issues-list-modal.tsx @@ -10,8 +10,6 @@ import { Button, Loader, ToggleSwitch, Tooltip, TOAST_TYPE, setToast } from "@pl // hooks import useDebounce from "@/hooks/use-debounce"; import { usePlatformOS } from "@/hooks/use-platform-os"; -// plane web components -import { IssueIdentifier } from "@/plane-web/components/issues"; // services import { ProjectService } from "@/services/project"; // components @@ -151,13 +149,7 @@ export const ExistingIssuesListModal: React.FC = (props) => { key={issue.id} className="flex items-center gap-1 whitespace-nowrap rounded-md border border-custom-border-200 bg-custom-background-80 py-1 pl-2 text-xs text-custom-text-100" > - + {issue.project__identifier}-{issue.sequence_id}
  • diff --git a/web/core/components/cycles/active-cycle/cycle-stats.tsx b/web/core/components/cycles/active-cycle/cycle-stats.tsx index e0a4e02e8a..164020d764 100644 --- a/web/core/components/cycles/active-cycle/cycle-stats.tsx +++ b/web/core/components/cycles/active-cycle/cycle-stats.tsx @@ -22,11 +22,9 @@ import { EIssuesStoreType } from "@/constants/issue"; import { cn } from "@/helpers/common.helper"; import { renderFormattedDate, renderFormattedDateWithoutYear } from "@/helpers/date-time.helper"; // hooks -import { useIssueDetail, useIssues } from "@/hooks/store"; +import { useIssueDetail, useIssues, useProject } from "@/hooks/store"; import { useIntersectionObserver } from "@/hooks/use-intersection-observer"; import useLocalStorage from "@/hooks/use-local-storage"; -// plane web components -import { IssueIdentifier } from "@/plane-web/components/issues"; export type ActiveCycleStatsProps = { workspaceSlug: string; @@ -64,6 +62,8 @@ export const ActiveCycleStats: FC = observer((props) => { setPeekIssue, } = useIssueDetail(); + const { currentProjectDetails } = useProject(); + useSWR( workspaceSlug && projectId && cycleId ? CYCLE_ISSUES_WITH_PARAMS(cycleId, { priority: "urgent,high" }) : null, workspaceSlug && projectId && cycleId ? () => fetchActiveCycleIssues(workspaceSlug, projectId, 30, cycleId) : null, @@ -183,16 +183,20 @@ export const ActiveCycleStats: FC = observer((props) => { }} >
    - + + + + + {currentProjectDetails?.identifier}-{issue.sequence_id} + + {issue.name}
    -
    = observer((props) => { try { await deleteCycle(workspaceSlug, projectId, cycle.id) .then(() => { - if (cycleId || peekCycle) router.push(`/${workspaceSlug}/projects/${projectId}/cycles`); setToast({ type: TOAST_TYPE.SUCCESS, title: "Success!", @@ -81,6 +80,8 @@ export const CycleDeleteModal: React.FC = observer((props) => { }); }) .finally(() => handleClose()); + + if (cycleId || peekCycle) router.push(`/${workspaceSlug}/projects/${projectId}/cycles`); } catch (error) { setToast({ type: TOAST_TYPE.ERROR, diff --git a/web/core/components/dashboard/project-empty-state.tsx b/web/core/components/dashboard/project-empty-state.tsx index a42f5a5b07..ed8623735a 100644 --- a/web/core/components/dashboard/project-empty-state.tsx +++ b/web/core/components/dashboard/project-empty-state.tsx @@ -9,7 +9,7 @@ import { EUserWorkspaceRoles } from "@/constants/workspace"; // hooks import { useCommandPalette, useEventTracker, useUser } from "@/hooks/store"; // assets -import ProjectEmptyStateImage from "@/public/empty-state/onboarding/dashboard-light.webp"; +import ProjectEmptyStateImage from "@/public/empty-state/dashboard/project.svg"; export const DashboardProjectEmptyState = observer(() => { // store hooks diff --git a/web/core/components/dashboard/widgets/issue-panels/issue-list-item.tsx b/web/core/components/dashboard/widgets/issue-panels/issue-list-item.tsx index e41d755045..b5e04d9b44 100644 --- a/web/core/components/dashboard/widgets/issue-panels/issue-list-item.tsx +++ b/web/core/components/dashboard/widgets/issue-panels/issue-list-item.tsx @@ -2,16 +2,14 @@ import isToday from "date-fns/isToday"; import { observer } from "mobx-react"; -// types import { TIssue, TWidgetIssue } from "@plane/types"; +// hooks // ui import { Avatar, AvatarGroup, ControlLink, PriorityIcon } from "@plane/ui"; // helpers import { findTotalDaysInRange, getDate, renderFormattedDate } from "@/helpers/date-time.helper"; -// hooks import { useIssueDetail, useMember, useProject } from "@/hooks/store"; -// plane web components -import { IssueIdentifier } from "@/plane-web/components/issues"; +// types export type IssueListItemProps = { issueId: string; @@ -44,35 +42,23 @@ export const AssignedUpcomingIssueListItem: React.FC = obser onClick(issueDetails)} - className="grid grid-cols-12 gap-1 rounded px-3 py-2 hover:bg-custom-background-80" + className="grid grid-cols-6 gap-1 rounded px-3 py-2 hover:bg-custom-background-80" > -
    - {projectDetails && ( - - )} +
    + + + {projectDetails?.identifier} {issueDetails.sequence_id} +
    {issueDetails.name}
    -
    - -
    -
    +
    {targetDate ? (isToday(targetDate) ? "Today" : renderFormattedDate(targetDate)) : "-"}
    -
    +
    {blockedByIssues.length > 0 ? blockedByIssues.length > 1 ? `${blockedByIssues.length} blockers` - : blockedByIssueProjectDetails && ( - - ) + : `${blockedByIssueProjectDetails?.identifier} ${blockedByIssues[0]?.sequence_id}` : "-"}
    @@ -103,35 +89,23 @@ export const AssignedOverdueIssueListItem: React.FC = observ onClick(issueDetails)} - className="grid grid-cols-12 gap-1 rounded px-3 py-2 hover:bg-custom-background-80" + className="grid grid-cols-6 gap-1 rounded px-3 py-2 hover:bg-custom-background-80" > -
    - {projectDetails && ( - - )} +
    + + + {projectDetails?.identifier} {issueDetails.sequence_id} +
    {issueDetails.name}
    -
    - -
    -
    +
    {dueBy} {`day${dueBy > 1 ? "s" : ""}`}
    -
    +
    {blockedByIssues.length > 0 ? blockedByIssues.length > 1 ? `${blockedByIssues.length} blockers` - : blockedByIssueProjectDetails && ( - - ) + : `${blockedByIssueProjectDetails?.identifier} ${blockedByIssues[0]?.sequence_id}` : "-"}
    @@ -156,21 +130,15 @@ export const AssignedCompletedIssueListItem: React.FC = obse onClick(issueDetails)} - className="grid grid-cols-12 gap-1 rounded px-3 py-2 hover:bg-custom-background-80" + className="grid grid-cols-6 gap-1 rounded px-3 py-2 hover:bg-custom-background-80" > -
    - {projectDetails && ( - - )} +
    + + + {projectDetails?.identifier} {issueDetails.sequence_id} +
    {issueDetails.name}
    -
    - -
    ); }); @@ -195,25 +163,19 @@ export const CreatedUpcomingIssueListItem: React.FC = observ onClick(issue)} - className="grid grid-cols-12 gap-1 rounded px-3 py-2 hover:bg-custom-background-80" + className="grid grid-cols-6 gap-1 rounded px-3 py-2 hover:bg-custom-background-80" > -
    - {projectDetails && ( - - )} +
    + + + {projectDetails?.identifier} {issue.sequence_id} +
    {issue.name}
    -
    - -
    -
    +
    {targetDate ? (isToday(targetDate) ? "Today" : renderFormattedDate(targetDate)) : "-"}
    -
    +
    {issue.assignee_ids && issue.assignee_ids?.length > 0 ? ( {issue.assignee_ids?.map((assigneeId) => { @@ -253,25 +215,19 @@ export const CreatedOverdueIssueListItem: React.FC = observe onClick(issue)} - className="grid grid-cols-12 gap-1 rounded px-3 py-2 hover:bg-custom-background-80" + className="grid grid-cols-6 gap-1 rounded px-3 py-2 hover:bg-custom-background-80" > -
    - {projectDetails && ( - - )} +
    + + + {projectDetails?.identifier} {issue.sequence_id} +
    {issue.name}
    -
    - -
    -
    +
    {dueBy} {`day${dueBy > 1 ? "s" : ""}`}
    -
    +
    {issue.assignee_ids.length > 0 ? ( {issue.assignee_ids?.map((assigneeId) => { @@ -309,22 +265,16 @@ export const CreatedCompletedIssueListItem: React.FC = obser onClick(issue)} - className="grid grid-cols-12 gap-1 rounded px-3 py-2 hover:bg-custom-background-80" + className="grid grid-cols-6 gap-1 rounded px-3 py-2 hover:bg-custom-background-80" > -
    - {projectDetails && ( - - )} +
    + + + {projectDetails?.identifier} {issue.sequence_id} +
    {issue.name}
    -
    - -
    -
    +
    {issue.assignee_ids.length > 0 ? ( {issue.assignee_ids?.map((assigneeId) => { diff --git a/web/core/components/dashboard/widgets/issue-panels/issues-list.tsx b/web/core/components/dashboard/widgets/issue-panels/issues-list.tsx index dc099167ec..6eda81fcb0 100644 --- a/web/core/components/dashboard/widgets/issue-panels/issues-list.tsx +++ b/web/core/components/dashboard/widgets/issue-panels/issues-list.tsx @@ -74,23 +74,22 @@ export const WidgetIssuesList: React.FC = (props) => { ) : issuesList.length > 0 ? ( <> -
    +
    Issues - + {widgetStats.count}
    -
    Priority
    - {["upcoming", "pending"].includes(tab) &&
    Due date
    } - {tab === "overdue" &&
    Due by
    } - {type === "assigned" && tab !== "completed" &&
    Blocked by
    } - {type === "created" &&
    Assigned to
    } + {["upcoming", "pending"].includes(tab) &&
    Due date
    } + {tab === "overdue" &&
    Due by
    } + {type === "assigned" && tab !== "completed" &&
    Blocked by
    } + {type === "created" &&
    Assigned to
    }
    {issuesList.map((issue) => { diff --git a/web/core/components/dropdowns/date.tsx b/web/core/components/dropdowns/date.tsx index 1977d06620..327bb45980 100644 --- a/web/core/components/dropdowns/date.tsx +++ b/web/core/components/dropdowns/date.tsx @@ -1,6 +1,5 @@ import React, { useRef, useState } from "react"; import { DayPicker, Matcher } from "react-day-picker"; -import { createPortal } from "react-dom"; import { usePopper } from "react-popper"; import { CalendarDays, X } from "lucide-react"; import { Combobox } from "@headlessui/react"; @@ -26,7 +25,6 @@ type Props = TDropdownProps & { onClose?: () => void; value: Date | string | null; closeOnSelect?: boolean; - formatToken?: string; }; export const DateDropdown: React.FC = (props) => { @@ -50,7 +48,6 @@ export const DateDropdown: React.FC = (props) => { showTooltip = false, tabIndex, value, - formatToken, } = props; // states const [isOpen, setIsOpen] = useState(false); @@ -129,15 +126,13 @@ export const DateDropdown: React.FC = (props) => { className={buttonClassName} isActive={isOpen} tooltipHeading={placeholder} - tooltipContent={value ? renderFormattedDate(value, formatToken) : "None"} + tooltipContent={value ? renderFormattedDate(value) : "None"} showTooltip={showTooltip} variant={buttonVariant} > {!hideIcon && icon} {BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && ( - - {value ? renderFormattedDate(value, formatToken) : placeholder} - + {value ? renderFormattedDate(value) : placeholder} )} {isClearable && !disabled && isDateSelected && ( = (props) => { - {isOpen && - createPortal( - -
    - { - dropdownOnChange(date ?? null); - }} - showOutsideDays - initialFocus - disabled={disabledDays} - mode="single" - /> -
    -
    , - document.body - )} + {isOpen && ( + +
    + { + dropdownOnChange(date ?? null); + }} + showOutsideDays + initialFocus + disabled={disabledDays} + mode="single" + /> +
    +
    + )} ); }; diff --git a/web/core/components/dropdowns/member/index.tsx b/web/core/components/dropdowns/member/index.tsx index 64dc566353..f68f5a6fa9 100644 --- a/web/core/components/dropdowns/member/index.tsx +++ b/web/core/components/dropdowns/member/index.tsx @@ -42,7 +42,6 @@ export const MemberDropdown: React.FC = observer((props) => { placement, projectId, showTooltip = false, - showUserDetails = false, tabIndex, value, icon, @@ -76,26 +75,6 @@ export const MemberDropdown: React.FC = observer((props) => { if (!multiple) handleClose(); }; - const getDisplayName = (value: string | string[] | null, showUserDetails: boolean, placeholder: string = "") => { - if (Array.isArray(value)) { - if (value.length > 0) { - if (value.length === 1) { - return getUserDetails(value[0])?.display_name || placeholder; - } else { - return showUserDetails ? `${value.length} members` : ""; - } - } else { - return placeholder; - } - } else { - if (showUserDetails && value) { - return getUserDetails(value)?.display_name || placeholder; - } else { - return placeholder; - } - } - }; - return ( = observer((props) => { onClick={handleOnClick} > = observer((props) => { > {!hideIcon && } {BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && ( - - {getDisplayName(value, showUserDetails, placeholder)} + + {Array.isArray(value) && value.length > 0 + ? value.length === 1 + ? getUserDetails(value[0])?.display_name + : "" + : placeholder} )} {dropdownArrow && ( diff --git a/web/core/components/dropdowns/member/member-options.tsx b/web/core/components/dropdowns/member/member-options.tsx index 8e7003f24a..360b85f11d 100644 --- a/web/core/components/dropdowns/member/member-options.tsx +++ b/web/core/components/dropdowns/member/member-options.tsx @@ -4,7 +4,6 @@ import { useEffect, useRef, useState } from "react"; import { Placement } from "@popperjs/core"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; -import { createPortal } from "react-dom"; import { usePopper } from "react-popper"; import { Check, Search } from "lucide-react"; import { Combobox } from "@headlessui/react"; @@ -85,14 +84,12 @@ export const MemberOptions = observer((props: Props) => { const filteredOptions = query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase())); - return createPortal( - + return ( +
    @@ -137,7 +134,6 @@ export const MemberOptions = observer((props: Props) => { )}
    -
    , - document.body +
    ); }); diff --git a/web/core/components/dropdowns/member/types.d.ts b/web/core/components/dropdowns/member/types.d.ts index 9bdc5192c3..758389650a 100644 --- a/web/core/components/dropdowns/member/types.d.ts +++ b/web/core/components/dropdowns/member/types.d.ts @@ -7,7 +7,6 @@ export type MemberDropdownProps = TDropdownProps & { placeholder?: string; tooltipContent?: string; onClose?: () => void; - showUserDetails?: boolean; } & ( | { multiple: false; diff --git a/web/core/components/estimates/radio-select.tsx b/web/core/components/estimates/radio-select.tsx index 0515b2c8a4..6a35f14e32 100644 --- a/web/core/components/estimates/radio-select.tsx +++ b/web/core/components/estimates/radio-select.tsx @@ -4,7 +4,7 @@ import { cn } from "@/helpers/common.helper"; type RadioInputProps = { name?: string; - label?: string | React.ReactNode; + label: string | React.ReactNode | undefined; wrapperClassName?: string; fieldClassName?: string; buttonClassName?: string; @@ -46,14 +46,14 @@ export const RadioInput = ({ return (
    - {inputLabel &&
    {inputLabel}
    } +
    {inputLabel}
    {options.map(({ value, label, disabled }, index) => (
    !disabled && setSelected(value)} className={cn( - "flex items-center gap-2 text-base", + "flex items-center gap-2", disabled ? `bg-custom-background-200 border-custom-border-200 cursor-not-allowed` : ``, inputFieldClassName )} @@ -72,7 +72,7 @@ export const RadioInput = ({ disabled={disabled} checked={selected === value} /> -
    diff --git a/web/core/components/gantt-chart/blocks/blocks-list.tsx b/web/core/components/gantt-chart/blocks/blocks-list.tsx index c4ffae1387..8c94b07d03 100644 --- a/web/core/components/gantt-chart/blocks/blocks-list.tsx +++ b/web/core/components/gantt-chart/blocks/blocks-list.tsx @@ -14,10 +14,10 @@ export type GanttChartBlocksProps = { getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock; blockToRender: (data: any) => React.ReactNode; blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void; - enableBlockLeftResize: boolean | ((blockId: string) => boolean); - enableBlockRightResize: boolean | ((blockId: string) => boolean); - enableBlockMove: boolean | ((blockId: string) => boolean); - enableAddBlock: boolean | ((blockId: string) => boolean); + enableBlockLeftResize: boolean; + enableBlockRightResize: boolean; + enableBlockMove: boolean; + enableAddBlock: boolean; ganttContainerRef: React.RefObject; showAllBlocks: boolean; selectionHelpers: TSelectionHelper; @@ -55,14 +55,10 @@ export const GanttChartBlocksList: FC = (props) => { showAllBlocks={showAllBlocks} blockToRender={blockToRender} blockUpdateHandler={blockUpdateHandler} - enableBlockLeftResize={ - typeof enableBlockLeftResize === "function" ? enableBlockLeftResize(blockId) : enableBlockLeftResize - } - enableBlockRightResize={ - typeof enableBlockRightResize === "function" ? enableBlockRightResize(blockId) : enableBlockRightResize - } - enableBlockMove={typeof enableBlockMove === "function" ? enableBlockMove(blockId) : enableBlockMove} - enableAddBlock={typeof enableAddBlock === "function" ? enableAddBlock(blockId) : enableAddBlock} + enableBlockLeftResize={enableBlockLeftResize} + enableBlockRightResize={enableBlockRightResize} + enableBlockMove={enableBlockMove} + enableAddBlock={enableAddBlock} ganttContainerRef={ganttContainerRef} selectionHelpers={selectionHelpers} /> diff --git a/web/core/components/gantt-chart/chart/header.tsx b/web/core/components/gantt-chart/chart/header.tsx index 4e16436df4..8756e200fd 100644 --- a/web/core/components/gantt-chart/chart/header.tsx +++ b/web/core/components/gantt-chart/chart/header.tsx @@ -16,12 +16,10 @@ type Props = { handleToday: () => void; loaderTitle: string; toggleFullScreenMode: () => void; - showToday: boolean; }; export const GanttChartHeader: React.FC = observer((props) => { - const { blockIds, fullScreenMode, handleChartView, handleToday, loaderTitle, toggleFullScreenMode, showToday } = - props; + const { blockIds, fullScreenMode, handleChartView, handleToday, loaderTitle, toggleFullScreenMode } = props; // chart hook const { currentView } = useGanttChart(); @@ -48,15 +46,9 @@ export const GanttChartHeader: React.FC = observer((props) => { ))}
    - {showToday && ( - - )} +
    {quickAdd ? quickAdd : null} diff --git a/web/core/components/inbox/content/inbox-issue-header.tsx b/web/core/components/inbox/content/inbox-issue-header.tsx index 8d4561fc12..1b18f063ab 100644 --- a/web/core/components/inbox/content/inbox-issue-header.tsx +++ b/web/core/components/inbox/content/inbox-issue-header.tsx @@ -140,7 +140,7 @@ export const InboxIssueActionsHeader: FC = observer((p const handleInboxIssueDelete = async () => { if (!inboxIssue || !currentInboxIssueId) return; - await deleteInboxIssue(workspaceSlug, projectId, currentInboxIssueId).then(() => { + await deleteInboxIssue(workspaceSlug, projectId, currentInboxIssueId).finally(() => { if (!isNotificationEmbed) router.push(`/${workspaceSlug}/projects/${projectId}/inbox`); }); }; diff --git a/web/core/components/inbox/content/issue-root.tsx b/web/core/components/inbox/content/issue-root.tsx index a95be412d8..3db2f9fa01 100644 --- a/web/core/components/inbox/content/issue-root.tsx +++ b/web/core/components/inbox/content/issue-root.tsx @@ -160,7 +160,7 @@ export const InboxIssueMainContent: React.FC = observer((props) => { />
    - +
    ); diff --git a/web/core/components/integration/github/import-data.tsx b/web/core/components/integration/github/import-data.tsx index ca2dac9559..bae8207a30 100644 --- a/web/core/components/integration/github/import-data.tsx +++ b/web/core/components/integration/github/import-data.tsx @@ -84,7 +84,7 @@ export const GithubImportData: FC = observer((props) => { } onChange={onChange} options={options} - optionsClassName="w-48" + optionsClassName="w-full" /> )} /> diff --git a/web/core/components/integration/github/select-repository.tsx b/web/core/components/integration/github/select-repository.tsx index 122e534dfe..d04d33b36a 100644 --- a/web/core/components/integration/github/select-repository.tsx +++ b/web/core/components/integration/github/select-repository.tsx @@ -81,7 +81,7 @@ export const SelectRepository: React.FC = (props) => { )} } - optionsClassName="w-48" + optionsClassName="w-full" /> ); }; diff --git a/web/core/components/integration/github/single-user-select.tsx b/web/core/components/integration/github/single-user-select.tsx index a936db6302..0f50a5a128 100644 --- a/web/core/components/integration/github/single-user-select.tsx +++ b/web/core/components/integration/github/single-user-select.tsx @@ -124,7 +124,7 @@ export const SingleUserSelect: React.FC = ({ collaborator, index, users, newUsers[index].email = val; setUsers(newUsers); }} - optionsClassName="w-48" + optionsClassName="w-full" /> )}
    diff --git a/web/core/components/integration/jira/import-users.tsx b/web/core/components/integration/jira/import-users.tsx index 3b7a7cd737..0f8f82165d 100644 --- a/web/core/components/integration/jira/import-users.tsx +++ b/web/core/components/integration/jira/import-users.tsx @@ -137,7 +137,7 @@ export const JiraImportUsers: FC = () => { label={value !== "" ? value : "Select user from project"} options={options} onChange={onChange} - optionsClassName="w-48" + optionsClassName="w-full" /> )} /> diff --git a/web/core/components/issues/delete-issue-modal.tsx b/web/core/components/issues/delete-issue-modal.tsx index e6109a028a..06fa4cafa4 100644 --- a/web/core/components/issues/delete-issue-modal.tsx +++ b/web/core/components/issues/delete-issue-modal.tsx @@ -8,7 +8,7 @@ import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui"; // constants import { PROJECT_ERROR_MESSAGES } from "@/constants/project"; // hooks -import { useIssues, useProject, useUser } from "@/hooks/store"; +import { useIssues, useProject } from "@/hooks/store"; type Props = { isOpen: boolean; @@ -26,7 +26,6 @@ export const DeleteIssueModal: React.FC = (props) => { // store hooks const { issueMap } = useIssues(); const { getProjectById } = useProject(); - const { data: currentUser, canPerformProjectAdminActions } = useUser(); useEffect(() => { setIsDeleting(false); @@ -37,8 +36,6 @@ export const DeleteIssueModal: React.FC = (props) => { // derived values const issue = data ? data : issueMap[dataId!]; const projectDetails = getProjectById(issue?.project_id); - const isIssueCreator = issue?.created_by === currentUser?.id; - const authorized = isIssueCreator || canPerformProjectAdminActions; const onClose = () => { setIsDeleting(false); @@ -47,16 +44,6 @@ export const DeleteIssueModal: React.FC = (props) => { const handleIssueDelete = async () => { setIsDeleting(true); - - if (!authorized) { - setToast({ - title: PROJECT_ERROR_MESSAGES.permissionError.title, - type: TOAST_TYPE.ERROR, - message: PROJECT_ERROR_MESSAGES.permissionError.message, - }); - onClose(); - return; - } if (onSubmit) await onSubmit() .then(() => { diff --git a/web/core/components/issues/issue-detail-widgets/relations/helper.tsx b/web/core/components/issues/issue-detail-widgets/relations/helper.tsx index fe3be8ca4a..f2366f7d43 100644 --- a/web/core/components/issues/issue-detail-widgets/relations/helper.tsx +++ b/web/core/components/issues/issue-detail-widgets/relations/helper.tsx @@ -70,12 +70,11 @@ export const useRelationOperations = (): TRelationIssueOperations => { }, remove: async (workspaceSlug: string, projectId: string, issueId: string) => { try { - return removeIssue(workspaceSlug, projectId, issueId).then(() => { - captureIssueEvent({ - eventName: ISSUE_DELETED, - payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" }, - path: pathname, - }); + await removeIssue(workspaceSlug, projectId, issueId); + captureIssueEvent({ + eventName: ISSUE_DELETED, + payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" }, + path: pathname, }); } catch (error) { captureIssueEvent({ diff --git a/web/core/components/issues/issue-detail-widgets/sub-issues/helper.tsx b/web/core/components/issues/issue-detail-widgets/sub-issues/helper.tsx index 7df432d5d2..dbf295a000 100644 --- a/web/core/components/issues/issue-detail-widgets/sub-issues/helper.tsx +++ b/web/core/components/issues/issue-detail-widgets/sub-issues/helper.tsx @@ -150,14 +150,13 @@ export const useSubIssueOperations = (): TSubIssueOperations => { deleteSubIssue: async (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => { try { setSubIssueHelpers(parentIssueId, "issue_loader", issueId); - return deleteSubIssue(workspaceSlug, projectId, parentIssueId, issueId).then(() => { - captureIssueEvent({ - eventName: "Sub-issue deleted", - payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" }, - path: pathname, - }); - setSubIssueHelpers(parentIssueId, "issue_loader", issueId); + await deleteSubIssue(workspaceSlug, projectId, parentIssueId, issueId); + captureIssueEvent({ + eventName: "Sub-issue deleted", + payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" }, + path: pathname, }); + setSubIssueHelpers(parentIssueId, "issue_loader", issueId); } catch (error) { captureIssueEvent({ eventName: "Sub-issue removed", diff --git a/web/core/components/issues/issue-detail/issue-activity/activity-comment-root.tsx b/web/core/components/issues/issue-detail/issue-activity/activity-comment-root.tsx index 49747d2b3a..a26dbc9c9a 100644 --- a/web/core/components/issues/issue-detail/issue-activity/activity-comment-root.tsx +++ b/web/core/components/issues/issue-detail/issue-activity/activity-comment-root.tsx @@ -2,8 +2,7 @@ import { FC } from "react"; import { observer } from "mobx-react"; // hooks import { useIssueDetail } from "@/hooks/store"; -// plane web components -import { IssueAdditionalPropertiesActivity } from "@/plane-web/components/issues"; +// plane wev components import { IssueActivityWorklog } from "@/plane-web/components/issues/worklog/activity/root"; // plane web constants import { TActivityFilters, filterActivityOnSelectedFilters } from "@/plane-web/constants/issues"; @@ -57,11 +56,6 @@ export const IssueActivityCommentRoot: FC = observer( activityId={activityComment.id} ends={index === 0 ? "top" : index === filteredActivityComments.length - 1 ? "bottom" : undefined} /> - ) : activityComment.activity_type === "ISSUE_ADDITIONAL_PROPERTIES_ACTIVITY" ? ( - ) : activityComment.activity_type === "WORKLOG" ? ( = observer((p
    diff --git a/web/core/components/issues/issue-detail/issue-activity/root.tsx b/web/core/components/issues/issue-detail/issue-activity/root.tsx index 3af87e7704..dad83881dc 100644 --- a/web/core/components/issues/issue-detail/issue-activity/root.tsx +++ b/web/core/components/issues/issue-detail/issue-activity/root.tsx @@ -21,7 +21,6 @@ type TIssueActivity = { projectId: string; issueId: string; disabled?: boolean; - isIntakeIssue?: boolean; }; export type TActivityOperations = { @@ -31,7 +30,7 @@ export type TActivityOperations = { }; export const IssueActivity: FC = observer((props) => { - const { workspaceSlug, projectId, issueId, disabled = false, isIntakeIssue = false } = props; + const { workspaceSlug, projectId, issueId, disabled = false } = props; // hooks const { createComment, updateComment, removeComment } = useIssueDetail(); const { getProjectById } = useProject(); @@ -115,14 +114,12 @@ export const IssueActivity: FC = observer((props) => {
    Activity
    - {!isIntakeIssue && ( - - )} +
    diff --git a/web/core/components/issues/issue-detail/issue-detail-quick-actions.tsx b/web/core/components/issues/issue-detail/issue-detail-quick-actions.tsx index 54cce976fd..8c720261ee 100644 --- a/web/core/components/issues/issue-detail/issue-detail-quick-actions.tsx +++ b/web/core/components/issues/issue-detail/issue-detail-quick-actions.tsx @@ -78,18 +78,14 @@ export const IssueDetailQuickActions: FC = observer((props) => { const handleDeleteIssue = async () => { try { - if (issue?.archived_at) { - return removeArchivedIssue(workspaceSlug, projectId, issueId).then(() => { - router.push(`/${workspaceSlug}/projects/${projectId}/issues`); - captureIssueEvent({ - eventName: ISSUE_DELETED, - payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" }, - path: pathname, - }); - }); - } else { - return removeIssue(workspaceSlug, projectId, issueId); - } + if (issue?.archived_at) await removeArchivedIssue(workspaceSlug, projectId, issueId); + else await removeIssue(workspaceSlug, projectId, issueId); + router.push(`/${workspaceSlug}/projects/${projectId}/issues`); + captureIssueEvent({ + eventName: ISSUE_DELETED, + payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" }, + path: pathname, + }); } catch (error) { setToast({ title: "Error!", diff --git a/web/core/components/issues/issue-detail/main-content.tsx b/web/core/components/issues/issue-detail/main-content.tsx index 386f4ab4e8..0236e5f189 100644 --- a/web/core/components/issues/issue-detail/main-content.tsx +++ b/web/core/components/issues/issue-detail/main-content.tsx @@ -4,6 +4,8 @@ import { useEffect, useState } from "react"; import { observer } from "mobx-react"; // types import { TIssue } from "@plane/types"; +// ui +import { StateGroupIcon } from "@plane/ui"; // components import { IssueActivity, @@ -15,10 +17,8 @@ import { IssueDetailWidgets, } from "@/components/issues"; // hooks -import { useIssueDetail, useUser } from "@/hooks/store"; +import { useIssueDetail, useProjectState, useUser } from "@/hooks/store"; import useReloadConfirmations from "@/hooks/use-reload-confirmation"; -// plane web components -import { IssueIdentifier } from "@/plane-web/components/issues"; // types import { TIssueOperations } from "./root"; @@ -38,6 +38,7 @@ export const IssueMainContent: React.FC = observer((props) => { const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved"); // hooks const { data: currentUser } = useUser(); + const { projectStates } = useProjectState(); const { issue: { getIssueById }, } = useIssueDetail(); @@ -53,6 +54,8 @@ export const IssueMainContent: React.FC = observer((props) => { const issue = issueId ? getIssueById(issueId) : undefined; if (!issue || !issue.project_id) return <>; + const currentIssueState = projectStates?.find((s) => s.id === issue.state_id); + return ( <>
    @@ -66,9 +69,15 @@ export const IssueMainContent: React.FC = observer((props) => { /> )} -
    - - +
    + {currentIssueState && ( + + )} +
    = observer((props) disabled={disabled} > {issue.parent_id && parentIssue ? ( -
    +
    e.stopPropagation()} > - {parentIssue?.project_id && parentIssueProjectDetails && ( - - )} + {parentIssueProjectDetails?.identifier}-{parentIssue.sequence_id} diff --git a/web/core/components/issues/issue-detail/parent/root.tsx b/web/core/components/issues/issue-detail/parent/root.tsx index edf80814cd..b64033830c 100644 --- a/web/core/components/issues/issue-detail/parent/root.tsx +++ b/web/core/components/issues/issue-detail/parent/root.tsx @@ -9,9 +9,7 @@ import { TIssue } from "@plane/types"; // ui import { CustomMenu } from "@plane/ui"; // hooks -import { useIssues, useProjectState } from "@/hooks/store"; -// plane web components -import { IssueIdentifier } from "@/plane-web/components/issues"; +import { useIssues, useProject, useProjectState } from "@/hooks/store"; // types import { TIssueOperations } from "../root"; import { IssueParentSiblings } from "./siblings"; @@ -28,6 +26,7 @@ export const IssueParentDetail: FC = observer((props) => { const { workspaceSlug, projectId, issueId, issue, issueOperations } = props; // hooks const { issueMap } = useIssues(); + const { getProjectById } = useProject(); const { getProjectStates } = useProjectState(); const parentIssue = issueMap?.[issue.parent_id || ""] || undefined; @@ -43,16 +42,12 @@ export const IssueParentDetail: FC = observer((props) => { <>
    -
    +
    - {parentIssue.project_id && ( - - )} + + {getProjectById(parentIssue.project_id)?.identifier}-{parentIssue?.sequence_id} +
    {(parentIssue?.name ?? "").substring(0, 50)}
    diff --git a/web/core/components/issues/issue-detail/parent/sibling-item.tsx b/web/core/components/issues/issue-detail/parent/sibling-item.tsx index da7adefa96..e54a306ecc 100644 --- a/web/core/components/issues/issue-detail/parent/sibling-item.tsx +++ b/web/core/components/issues/issue-detail/parent/sibling-item.tsx @@ -4,11 +4,9 @@ import { FC } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; // ui -import { CustomMenu } from "@plane/ui"; +import { CustomMenu, LayersIcon } from "@plane/ui"; // hooks import { useIssueDetail, useProject } from "@/hooks/store"; -// plane web components -import { IssueIdentifier } from "@/plane-web/components/issues"; type TIssueParentSiblingItem = { workspaceSlug: string; @@ -33,17 +31,10 @@ export const IssueParentSiblingItem: FC = observer((pro - {issueDetail.project_id && projectDetails?.identifier && ( - - )} + + {projectDetails?.identifier}-{issueDetail.sequence_id} diff --git a/web/core/components/issues/issue-detail/parent/siblings.tsx b/web/core/components/issues/issue-detail/parent/siblings.tsx index 30998c9aae..f1991e8050 100644 --- a/web/core/components/issues/issue-detail/parent/siblings.tsx +++ b/web/core/components/issues/issue-detail/parent/siblings.tsx @@ -34,7 +34,7 @@ export const IssueParentSiblings: FC = observer((props) => const subIssueIds = (parentIssue && subIssuesByIssueId(parentIssue.id)) || undefined; return ( -
    +
    {isLoading ? (
    Loading diff --git a/web/core/components/issues/issue-detail/relation-select.tsx b/web/core/components/issues/issue-detail/relation-select.tsx index c787c5c644..4923d72d08 100644 --- a/web/core/components/issues/issue-detail/relation-select.tsx +++ b/web/core/components/issues/issue-detail/relation-select.tsx @@ -21,22 +21,22 @@ export type TRelationObject = { className: string; icon: (size: number) => React export const issueRelationObject: Record = { relates_to: { className: "bg-custom-background-80 text-custom-text-200", - icon: (size) => , + icon: (size) => , placeholder: "Add related issues", }, blocking: { className: "bg-yellow-500/20 text-yellow-700", - icon: (size) => , + icon: (size) => , placeholder: "None", }, blocked_by: { className: "bg-red-500/20 text-red-700", - icon: (size) => , + icon: (size) => , placeholder: "None", }, duplicate: { className: "bg-custom-background-80 text-custom-text-200", - icon: (size) => , + icon: (size) => , placeholder: "None", }, }; diff --git a/web/core/components/issues/issue-detail/sidebar.tsx b/web/core/components/issues/issue-detail/sidebar.tsx index 7d64403cf1..076cc5a038 100644 --- a/web/core/components/issues/issue-detail/sidebar.tsx +++ b/web/core/components/issues/issue-detail/sidebar.tsx @@ -22,7 +22,6 @@ import { shouldHighlightIssueDueDate } from "@/helpers/issue.helper"; // hooks import { useProjectEstimates, useIssueDetail, useProject, useProjectState, useMember } from "@/hooks/store"; // plane web components -import { IssueAdditionalPropertyValuesUpdate } from "@/plane-web/components/issue-types/values"; import { IssueWorklogProperty } from "@/plane-web/components/issues"; // components import type { TIssueOperations } from "./root"; @@ -289,15 +288,6 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { issueId={issueId} disabled={!isEditable} /> - - {issue.type_id && ( - - )}
    diff --git a/web/core/components/issues/issue-layouts/calendar/index.ts b/web/core/components/issues/issue-layouts/calendar/index.ts index d6028f4f50..527a9eff48 100644 --- a/web/core/components/issues/issue-layouts/calendar/index.ts +++ b/web/core/components/issues/issue-layouts/calendar/index.ts @@ -9,4 +9,4 @@ export * from "./issue-block-root"; export * from "./issue-block"; export * from "./week-days"; export * from "./week-header"; -export * from "./quick-add-issue-actions"; +export * from "./quick-add-issue-form"; diff --git a/web/core/components/issues/issue-layouts/calendar/issue-block.tsx b/web/core/components/issues/issue-layouts/calendar/issue-block.tsx index ec77933ced..747425d41d 100644 --- a/web/core/components/issues/issue-layouts/calendar/issue-block.tsx +++ b/web/core/components/issues/issue-layouts/calendar/issue-block.tsx @@ -5,19 +5,16 @@ import { useState, useRef, forwardRef } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { MoreHorizontal } from "lucide-react"; -// types import { TIssue } from "@plane/types"; -// ui +// components import { Tooltip, ControlLink } from "@plane/ui"; -// helpers -import { cn } from "@/helpers/common.helper"; // hooks -import { useIssueDetail, useProjectState } from "@/hooks/store"; +import { cn } from "@/helpers/common.helper"; +import { useIssueDetail, useProject, useProjectState } from "@/hooks/store"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; +// helpers +// types import { usePlatformOS } from "@/hooks/use-platform-os"; -// plane web components -import { IssueIdentifier } from "@/plane-web/components/issues/issue-details"; -// local components import { TRenderQuickActions } from "../list/list-view-types"; type Props = { @@ -36,6 +33,7 @@ export const CalendarIssueBlock = observer( const menuActionRef = useRef(null); // hooks const { workspaceSlug, projectId } = useParams(); + const { getProjectIdentifierById } = useProject(); const { getProjectStates } = useProjectState(); const { getIsIssuePeeked, setPeekIssue } = useIssueDetail(); const { isMobile } = usePlatformOS(); @@ -101,13 +99,9 @@ export const CalendarIssueBlock = observer( backgroundColor: stateColor, }} /> - {issue.project_id && ( - - )} +
    + {getProjectIdentifierById(issue?.project_id)}-{issue.sequence_id} +
    {issue.name}
    diff --git a/web/core/components/issues/issue-layouts/calendar/issue-blocks.tsx b/web/core/components/issues/issue-layouts/calendar/issue-blocks.tsx index 8860fcb6ad..6be0a21e26 100644 --- a/web/core/components/issues/issue-layouts/calendar/issue-blocks.tsx +++ b/web/core/components/issues/issue-layouts/calendar/issue-blocks.tsx @@ -1,7 +1,7 @@ import { observer } from "mobx-react"; import { TIssue, TPaginationData } from "@plane/types"; // components -import { CalendarQuickAddIssueActions, CalendarIssueBlockRoot } from "@/components/issues"; +import { CalendarQuickAddIssueForm, CalendarIssueBlockRoot } from "@/components/issues"; // helpers import { renderFormattedPayloadDate } from "@/helpers/date-time.helper"; import { useIssuesStore } from "@/hooks/use-issue-layout-store"; @@ -75,7 +75,9 @@ export const CalendarIssueBlocks: React.FC = observer((props) => { {enableQuickIssueCreate && !disableIssueCreation && !readOnly && (
    - ; - quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise; - addIssuesToView?: (issueIds: string[]) => Promise; - onOpen?: () => void; -}; - -export const CalendarQuickAddIssueActions: FC = observer((props) => { - const { prePopulatedData, quickAddCallback, addIssuesToView, onOpen } = props; - // router - const { workspaceSlug, projectId, moduleId } = useParams(); - // states - const [isOpen, setIsOpen] = useState(false); - const [isMenuOpen, setIsMenuOpen] = useState(false); - const [isExistingIssueModalOpen, setIsExistingIssueModalOpen] = useState(false); - const { updateIssue } = useIssueDetail(); - // derived values - const ExistingIssuesListModalPayload = addIssuesToView - ? moduleId - ? { module: moduleId.toString(), target_date: "none" } - : { cycle: true, target_date: "none" } - : { target_date: "none" }; - - const handleAddIssuesToView = async (data: ISearchIssueResponse[]) => { - if (!workspaceSlug || !projectId) return; - - const issueIds = data.map((i) => i.id); - - try { - // To handle all updates in parallel - await Promise.all( - data.map((issue) => - updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, prePopulatedData ?? {}) - ) - ); - await addIssuesToView?.(issueIds); - } catch (error) { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Something went wrong. Please try again.", - }); - } - }; - - const handleNewIssue = () => { - setIsOpen(true); - if (onOpen) onOpen(); - }; - const handleExistingIssue = () => { - setIsExistingIssueModalOpen(true); - }; - - if (!projectId) return null; - - return ( - <> - {workspaceSlug && projectId && ( - setIsExistingIssueModalOpen(false)} - searchParams={ExistingIssuesListModalPayload} - handleOnSubmit={handleAddIssuesToView} - shouldHideIssue={(issue) => { - if (issue.start_date && prePopulatedData?.target_date) { - const issueStartDate = new Date(issue.start_date); - const targetDate = new Date(prePopulatedData.target_date); - const diffInDays = differenceInCalendarDays(targetDate, issueStartDate); - if (diffInDays < 0) return true; - } - return false; - }} - /> - )} - setIsOpen(isOpen)} - layout={EIssueLayoutTypes.CALENDAR} - prePopulatedData={prePopulatedData} - quickAddCallback={quickAddCallback} - customQuickAddButton={ -
    - setIsMenuOpen(true)} - onMenuClose={() => setIsMenuOpen(false)} - className="w-full" - customButtonClassName="w-full" - customButton={ -
    - - New issue -
    - } - > - New issue - Add existing issue -
    -
    - } - /> - - ); -}); diff --git a/web/core/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx b/web/core/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx new file mode 100644 index 0000000000..4cc870be9f --- /dev/null +++ b/web/core/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx @@ -0,0 +1,264 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { differenceInCalendarDays } from "date-fns"; +import { observer } from "mobx-react"; +import { useParams, usePathname } from "next/navigation"; +import { useForm } from "react-hook-form"; +import { PlusIcon } from "lucide-react"; +// types +import { ISearchIssueResponse, TIssue } from "@plane/types"; +// ui +import { TOAST_TYPE, setPromiseToast, setToast, CustomMenu } from "@plane/ui"; +// components +import { ExistingIssuesListModal } from "@/components/core"; +import { CreateIssueToastActionItems } from "@/components/issues"; +// constants +import { ISSUE_CREATED } from "@/constants/event-tracker"; +// helpers +import { cn } from "@/helpers/common.helper"; +import { createIssuePayload } from "@/helpers/issue.helper"; +// hooks +import { useEventTracker, useIssueDetail, useProject } from "@/hooks/store"; +import useKeypress from "@/hooks/use-keypress"; +import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; + +type Props = { + formKey: keyof TIssue; + groupId?: string; + subGroupId?: string | null; + prePopulatedData?: Partial; + quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise; + addIssuesToView?: (issueIds: string[]) => Promise; + onOpen?: () => void; +}; + +const defaultValues: Partial = { + name: "", +}; + +const Inputs = (props: any) => { + const { register, setFocus, projectDetails } = props; + + useEffect(() => { + setFocus("name"); + }, [setFocus]); + + return ( + <> +

    {projectDetails?.identifier ?? "..."}

    + + + ); +}; + +export const CalendarQuickAddIssueForm: React.FC = observer((props) => { + const { formKey, prePopulatedData, quickAddCallback, addIssuesToView, onOpen } = props; + + // router + const { workspaceSlug, projectId, moduleId } = useParams(); + const pathname = usePathname(); + // store hooks + const { getProjectById } = useProject(); + const { captureIssueEvent } = useEventTracker(); + const { updateIssue } = useIssueDetail(); + // refs + const ref = useRef(null); + // states + const [isOpen, setIsOpen] = useState(false); + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [isExistingIssueModalOpen, setIsExistingIssueModalOpen] = useState(false); + // derived values + const projectDetail = projectId ? getProjectById(projectId.toString()) : null; + const ExistingIssuesListModalPayload = addIssuesToView + ? moduleId + ? { module: moduleId.toString(), target_date: "none" } + : { cycle: true, target_date: "none" } + : { target_date: "none" }; + + const { + reset, + handleSubmit, + register, + setFocus, + formState: { errors, isSubmitting }, + } = useForm({ defaultValues }); + + const handleClose = () => { + setIsOpen(false); + }; + + useKeypress("Escape", handleClose); + useOutsideClickDetector(ref, handleClose); + + useEffect(() => { + if (!isOpen) reset({ ...defaultValues }); + }, [isOpen, reset]); + + useEffect(() => { + if (!errors) return; + + Object.keys(errors).forEach((key) => { + const error = errors[key as keyof TIssue]; + + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: error?.message?.toString() || "Some error occurred. Please try again.", + }); + }); + }, [errors]); + + const onSubmitHandler = async (formData: TIssue) => { + if (isSubmitting || !workspaceSlug || !projectId) return; + + reset({ ...defaultValues }); + + const payload = createIssuePayload(projectId.toString(), { + ...(prePopulatedData ?? {}), + ...formData, + }); + + if (quickAddCallback) { + const quickAddPromise = quickAddCallback(projectId.toString(), { + ...payload, + }); + setPromiseToast(quickAddPromise, { + loading: "Adding issue...", + success: { + title: "Success!", + message: () => "Issue created successfully.", + actionItems: (data) => ( + + ), + }, + error: { + title: "Error!", + message: (err) => err?.message || "Some error occurred. Please try again.", + }, + }); + + await quickAddPromise + .then((res) => { + captureIssueEvent({ + eventName: ISSUE_CREATED, + payload: { ...res, state: "SUCCESS", element: "Calendar quick add" }, + path: pathname, + }); + }) + .catch(() => { + captureIssueEvent({ + eventName: ISSUE_CREATED, + payload: { ...payload, state: "FAILED", element: "Calendar quick add" }, + path: pathname, + }); + }); + } + }; + + const handleAddIssuesToView = async (data: ISearchIssueResponse[]) => { + if (!workspaceSlug || !projectId) return; + + const issueIds = data.map((i) => i.id); + + try { + // To handle all updates in parallel + await Promise.all( + data.map((issue) => + updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, prePopulatedData ?? {}) + ) + ); + await addIssuesToView?.(issueIds); + } catch (error) { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Something went wrong. Please try again.", + }); + } + }; + + const handleNewIssue = () => { + setIsOpen(true); + if (onOpen) onOpen(); + }; + const handleExistingIssue = () => { + setIsExistingIssueModalOpen(true); + }; + + return ( + <> + {workspaceSlug && projectId && ( + setIsExistingIssueModalOpen(false)} + searchParams={ExistingIssuesListModalPayload} + handleOnSubmit={handleAddIssuesToView} + shouldHideIssue={(issue) => { + if (issue.start_date && prePopulatedData?.target_date) { + const issueStartDate = new Date(issue.start_date); + const targetDate = new Date(prePopulatedData.target_date); + const diffInDays = differenceInCalendarDays(targetDate, issueStartDate); + if (diffInDays < 0) return true; + } + return false; + }} + /> + )} + {isOpen && ( +
    +
    + + +
    + )} + + {!isOpen && ( +
    + setIsMenuOpen(true)} + onMenuClose={() => setIsMenuOpen(false)} + className="w-full" + customButtonClassName="w-full" + customButton={ +
    + + New issue +
    + } + > + New issue + Add existing issue +
    +
    + )} + + ); +}); diff --git a/web/core/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx b/web/core/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx index 1227d0d4ac..c0d94dcbc6 100644 --- a/web/core/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx +++ b/web/core/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx @@ -20,8 +20,6 @@ import { EUserProjectRoles } from "@/constants/project"; import { replaceUnderscoreIfSnakeCase } from "@/helpers/string.helper"; // hooks import { useUser } from "@/hooks/store"; -// plane web components -import { AppliedIssueTypeFilters } from "@/plane-web/components/issues"; type Props = { appliedFilters: IIssueFilterOptions; @@ -133,13 +131,6 @@ export const AppliedFiltersList: React.FC = observer((props) => { values={value} /> )} - {filterKey === "issue_type" && ( - handleRemoveFilter("issue_type", val)} - values={value} - /> - )} {isEditingAllowed && (
    )} - {/* issue type */} - {isFilterEnabled("issue_type") && ( - handleFiltersUpdate("issue_type", val)} - searchQuery={filtersSearchQuery} - /> - )} - {/* assignees */} {isFilterEnabled("assignees") && (
    @@ -208,7 +198,7 @@ export const FilterSelection: React.FC = observer((props) => { {/* issue type */} {isDisplayFilterEnabled("type") && displayFilters && handleDisplayFiltersUpdate && (
    - handleDisplayFiltersUpdate({ diff --git a/web/core/components/issues/issue-layouts/gantt/base-gantt-root.tsx b/web/core/components/issues/issue-layouts/gantt/base-gantt-root.tsx index d60373312d..82720a3be6 100644 --- a/web/core/components/issues/issue-layouts/gantt/base-gantt-root.tsx +++ b/web/core/components/issues/issue-layouts/gantt/base-gantt-root.tsx @@ -7,12 +7,10 @@ import { TIssue } from "@plane/types"; // hooks import { ChartDataType, GanttChartRoot, IBlockUpdateData, IssueGanttSidebar } from "@/components/gantt-chart"; import { getMonthChartItemPositionWidthInMonth } from "@/components/gantt-chart/views"; -import { QuickAddIssueRoot, IssueGanttBlock, GanttQuickAddIssueButton } from "@/components/issues"; +import { GanttQuickAddIssueForm, IssueGanttBlock } from "@/components/issues"; //constants import { EIssueLayoutTypes, EIssuesStoreType } from "@/constants/issue"; import { EUserProjectRoles } from "@/constants/project"; -// helpers -import { renderFormattedPayloadDate } from "@/helpers/date-time.helper"; import { getIssueBlocksStructure } from "@/helpers/issue.helper"; //hooks import { useIssues, useUser } from "@/hooks/store"; @@ -49,9 +47,6 @@ export const BaseGanttRoot: React.FC = observer((props: IBaseGan const appliedDisplayFilters = issuesFilter.issueFilters?.displayFilters; // plane web hooks const isBulkOperationsEnabled = useBulkOperationStatus(); - // derived values - const targetDate = new Date(); - targetDate.setDate(targetDate.getDate() + 1); useEffect(() => { fetchIssues("init-loader", { canGroup: false, perPageCount: 100 }, viewId); @@ -94,16 +89,7 @@ export const BaseGanttRoot: React.FC = observer((props: IBaseGan const quickAdd = enableIssueCreation && isAllowed && !isCompletedCycle ? ( - + ) : undefined; return ( diff --git a/web/core/components/issues/issue-layouts/gantt/blocks.tsx b/web/core/components/issues/issue-layouts/gantt/blocks.tsx index 98d795477e..69be0d2a2d 100644 --- a/web/core/components/issues/issue-layouts/gantt/blocks.tsx +++ b/web/core/components/issues/issue-layouts/gantt/blocks.tsx @@ -2,15 +2,13 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; +// hooks // ui -import { Tooltip, ControlLink } from "@plane/ui"; +import { Tooltip, StateGroupIcon, ControlLink } from "@plane/ui"; // helpers import { renderFormattedDate } from "@/helpers/date-time.helper"; -// hooks -import { useIssueDetail, useProjectState } from "@/hooks/store"; +import { useIssueDetail, useProject, useProjectState } from "@/hooks/store"; import { usePlatformOS } from "@/hooks/use-platform-os"; -// plane web components -import { IssueIdentifier } from "@/plane-web/components/issues"; type Props = { issueId: string; @@ -80,12 +78,16 @@ export const IssueGanttSidebarBlock: React.FC = observer((props) => { const { workspaceSlug: routerWorkspaceSlug } = useParams(); const workspaceSlug = routerWorkspaceSlug?.toString(); // store hooks + const { getStateById } = useProjectState(); + const { getProjectIdentifierById } = useProject(); const { issue: { getIssueById }, setPeekIssue, } = useIssueDetail(); // derived values const issueDetails = getIssueById(issueId); + const projectIdentifier = issueDetails && getProjectIdentifierById(issueDetails?.project_id); + const stateDetails = issueDetails && getStateById(issueDetails?.state_id); const handleIssuePeekOverview = () => workspaceSlug && @@ -102,13 +104,10 @@ export const IssueGanttSidebarBlock: React.FC = observer((props) => { disabled={!!issueDetails?.tempId} >
    - {issueDetails?.project_id && ( - - )} + {stateDetails && } +
    + {projectIdentifier} {issueDetails?.sequence_id} +
    {issueDetails?.name} diff --git a/web/core/components/issues/issue-layouts/gantt/index.ts b/web/core/components/issues/issue-layouts/gantt/index.ts index 1cd0649fcc..5e741d30db 100644 --- a/web/core/components/issues/issue-layouts/gantt/index.ts +++ b/web/core/components/issues/issue-layouts/gantt/index.ts @@ -1,3 +1,3 @@ export * from "./blocks"; export * from "./base-gantt-root"; - +export * from "./quick-add-issue-form"; diff --git a/web/core/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx b/web/core/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx new file mode 100644 index 0000000000..f5996af691 --- /dev/null +++ b/web/core/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx @@ -0,0 +1,179 @@ +"use client"; + +import { useEffect, useState, useRef, FC } from "react"; +import { observer } from "mobx-react"; +import { useParams, usePathname } from "next/navigation"; +import { useForm } from "react-hook-form"; +import { PlusIcon } from "lucide-react"; +import { IProject, TIssue } from "@plane/types"; +// hooks +import { setPromiseToast } from "@plane/ui"; +import { CreateIssueToastActionItems } from "@/components/issues"; +import { ISSUE_CREATED } from "@/constants/event-tracker"; +import { cn } from "@/helpers/common.helper"; +import { renderFormattedPayloadDate } from "@/helpers/date-time.helper"; +import { createIssuePayload } from "@/helpers/issue.helper"; +import { useEventTracker, useProject } from "@/hooks/store"; +import useKeypress from "@/hooks/use-keypress"; +import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; +// helpers +// ui +// types +// constants + +interface IInputProps { + formKey: string; + register: any; + setFocus: any; + projectDetail: IProject | null; +} +const Inputs: FC = (props) => { + const { formKey, register, setFocus, projectDetail } = props; + + useEffect(() => { + setFocus(formKey); + }, [formKey, setFocus]); + + return ( +
    +
    {projectDetail?.identifier ?? "..."}
    + +
    + ); +}; + +type IGanttQuickAddIssueForm = { + prePopulatedData?: Partial; + onSuccess?: (data: TIssue) => Promise | void; + quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise; +}; + +const defaultValues: Partial = { + name: "", +}; + +export const GanttQuickAddIssueForm: React.FC = observer((props) => { + const { prePopulatedData, quickAddCallback } = props; + // router + const { workspaceSlug, projectId } = useParams(); + const pathname = usePathname(); + // hooks + const { getProjectById } = useProject(); + const { captureIssueEvent } = useEventTracker(); + + const projectDetail = (projectId && getProjectById(projectId.toString())) || undefined; + + const ref = useRef(null); + + const [isOpen, setIsOpen] = useState(false); + const handleClose = () => setIsOpen(false); + + useKeypress("Escape", handleClose); + useOutsideClickDetector(ref, handleClose); + + // form info + const { + reset, + handleSubmit, + setFocus, + register, + formState: { errors, isSubmitting }, + } = useForm({ defaultValues }); + + useEffect(() => { + if (!isOpen) reset({ ...defaultValues }); + }, [isOpen, reset]); + + const onSubmitHandler = async (formData: TIssue) => { + if (isSubmitting || !workspaceSlug || !projectId) return; + + reset({ ...defaultValues }); + + const targetDate = new Date(); + targetDate.setDate(targetDate.getDate() + 1); + + const payload = createIssuePayload(projectId.toString(), { + ...(prePopulatedData ?? {}), + ...formData, + start_date: renderFormattedPayloadDate(new Date()), + target_date: renderFormattedPayloadDate(targetDate), + }); + + if (quickAddCallback) { + const quickAddPromise = quickAddCallback(projectId.toString(), { ...payload }); + setPromiseToast(quickAddPromise, { + loading: "Adding issue...", + success: { + title: "Success!", + message: () => "Issue created successfully.", + actionItems: (data) => ( + + ), + }, + error: { + title: "Error!", + message: (err) => err?.message || "Some error occurred. Please try again.", + }, + }); + + await quickAddPromise + .then((res) => { + captureIssueEvent({ + eventName: ISSUE_CREATED, + payload: { ...res, state: "SUCCESS", element: "Gantt quick add" }, + path: pathname, + }); + }) + .catch(() => { + captureIssueEvent({ + eventName: ISSUE_CREATED, + payload: { ...payload, state: "FAILED", element: "Gantt quick add" }, + path: pathname, + }); + }); + } + }; + return ( + <> + {isOpen ? ( +
    +
    +
    + + +
    {`Press 'Enter' to add another issue`}
    +
    +
    + ) : ( + + )} + + ); +}); diff --git a/web/core/components/issues/issue-layouts/index.ts b/web/core/components/issues/issue-layouts/index.ts index 7877eeaf53..fc294b0847 100644 --- a/web/core/components/issues/issue-layouts/index.ts +++ b/web/core/components/issues/issue-layouts/index.ts @@ -18,7 +18,3 @@ export * from "./properties"; // save view export * from "./save-filter-view"; - -// quick add -export * from "./quick-add"; - diff --git a/web/core/components/issues/issue-layouts/kanban/block.tsx b/web/core/components/issues/issue-layouts/kanban/block.tsx index c7c9ed834b..0d9ea8a13f 100644 --- a/web/core/components/issues/issue-layouts/kanban/block.tsx +++ b/web/core/components/issues/issue-layouts/kanban/block.tsx @@ -5,26 +5,24 @@ import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; -// types import { TIssue, IIssueDisplayProperties, IIssueMap } from "@plane/types"; -// ui +// hooks import { ControlLink, DropIndicator, TOAST_TYPE, Tooltip, setToast } from "@plane/ui"; -// components import RenderIfVisible from "@/components/core/render-if-visible-HOC"; import { HIGHLIGHT_CLASS } from "@/components/issues/issue-layouts/utils"; -// helpers import { cn } from "@/helpers/common.helper"; // hooks -import { useIssueDetail, useKanbanView } from "@/hooks/store"; +import { useIssueDetail, useProject, useKanbanView } from "@/hooks/store"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; import { usePlatformOS } from "@/hooks/use-platform-os"; -// plane web components -import { IssueIdentifier } from "@/plane-web/components/issues"; -// local components +// components import { TRenderQuickActions } from "../list/list-view-types"; import { IssueProperties } from "../properties/all-properties"; import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC"; import { getIssueBlockId } from "../utils"; +// ui +// types +// helper interface IssueBlockProps { issueId: string; @@ -53,6 +51,7 @@ const KanbanIssueDetailsBlock: React.FC = observer((prop const { cardRef, issue, updateIssue, quickActions, isReadOnly, displayProperties } = props; // hooks const { isMobile } = usePlatformOS(); + const { getProjectIdentifierById } = useProject(); const handleEventPropagation = (e: React.MouseEvent) => { e.stopPropagation(); @@ -63,13 +62,9 @@ const KanbanIssueDetailsBlock: React.FC = observer((prop <>
    - {issue.project_id && ( - - )} +
    + {getProjectIdentifierById(issue.project_id)}-{issue.sequence_id} +
    = observer((prop
    -
    + {issue?.is_draft ? ( {issue.name} -
    + ) : ( +
    + + {issue.name} + +
    + )} { {enableQuickIssueCreate && !disableIssueCreation && (
    - { + const { register, setFocus, projectDetail } = props; + + useEffect(() => { + setFocus("name"); + }, [setFocus]); + + return ( +
    +

    {projectDetail?.identifier ?? "..."}

    + +
    + ); +}; + +interface IKanBanQuickAddIssueForm { + formKey: keyof TIssue; + groupId?: string; + subGroupId?: string | null; + prePopulatedData?: Partial; + quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise; +} + +const defaultValues: Partial = { + name: "", +}; + +export const KanBanQuickAddIssueForm: React.FC = observer((props) => { + const { formKey, prePopulatedData, quickAddCallback } = props; + // router + const { workspaceSlug, projectId } = useParams(); + const pathname = usePathname(); + // store hooks + const { getProjectById } = useProject(); + const { captureIssueEvent } = useEventTracker(); + + const projectDetail = projectId ? getProjectById(projectId.toString()) : null; + + const ref = useRef(null); + + const [isOpen, setIsOpen] = useState(false); + const handleClose = () => setIsOpen(false); + + useKeypress("Escape", handleClose); + useOutsideClickDetector(ref, handleClose); + + const { + reset, + handleSubmit, + setFocus, + register, + formState: { isSubmitting }, + } = useForm({ defaultValues }); + + useEffect(() => { + if (!isOpen) reset({ ...defaultValues }); + }, [isOpen, reset]); + + const onSubmitHandler = async (formData: TIssue) => { + if (isSubmitting || !workspaceSlug || !projectId) return; + + reset({ ...defaultValues }); + + const payload = createIssuePayload(projectId.toString(), { + ...(prePopulatedData ?? {}), + ...formData, + }); + + if (quickAddCallback) { + const quickAddPromise = quickAddCallback(projectId.toString(), { + ...payload, + }); + setPromiseToast(quickAddPromise, { + loading: "Adding issue...", + success: { + title: "Success!", + message: () => "Issue created successfully.", + actionItems: (data) => ( + + ), + }, + error: { + title: "Error!", + message: (err) => err?.message || "Some error occurred. Please try again.", + }, + }); + + await quickAddPromise + .then((res) => { + captureIssueEvent({ + eventName: ISSUE_CREATED, + payload: { ...res, state: "SUCCESS", element: "Kanban quick add" }, + path: pathname, + }); + }) + .catch(() => { + captureIssueEvent({ + eventName: ISSUE_CREATED, + payload: { ...payload, state: "FAILED", element: "Kanban quick add" }, + path: pathname, + }); + }); + } + }; + + return ( + <> + {isOpen ? ( +
    +
    + + +
    {`Press 'Enter' to add another issue`}
    +
    + ) : ( +
    setIsOpen(true)} + > + + New Issue +
    + )} + + ); +}); diff --git a/web/core/components/issues/issue-layouts/list/block.tsx b/web/core/components/issues/issue-layouts/list/block.tsx index b568f82bfa..a07a156848 100644 --- a/web/core/components/issues/issue-layouts/list/block.tsx +++ b/web/core/components/issues/issue-layouts/list/block.tsx @@ -19,8 +19,6 @@ import { cn } from "@/helpers/common.helper"; import { useAppTheme, useIssueDetail, useProject } from "@/hooks/store"; import { TSelectionHelper } from "@/hooks/use-multiple-select"; import { usePlatformOS } from "@/hooks/use-platform-os"; -// plane web components -import { IssueIdentifier } from "@/plane-web/components/issues"; // types import { TRenderQuickActions } from "./list-view-types"; @@ -189,14 +187,11 @@ export const IssueBlock = observer((props: IssueBlockProps) => { )} {displayProperties && displayProperties?.key && ( -
    - {issue.project_id && ( - - )} +
    + {projectIdentifier}-{issue.sequence_id}
    )} diff --git a/web/core/components/issues/issue-layouts/list/index.ts b/web/core/components/issues/issue-layouts/list/index.ts index 2171c18731..26c125b515 100644 --- a/web/core/components/issues/issue-layouts/list/index.ts +++ b/web/core/components/issues/issue-layouts/list/index.ts @@ -3,3 +3,4 @@ export * from "./block-root"; export * from "./block"; export * from "./roots"; export * from "./blocks-list"; +export * from "./quick-add-issue-form"; diff --git a/web/core/components/issues/issue-layouts/list/list-group.tsx b/web/core/components/issues/issue-layouts/list/list-group.tsx index 1b5d88529e..0f1b806945 100644 --- a/web/core/components/issues/issue-layouts/list/list-group.tsx +++ b/web/core/components/issues/issue-layouts/list/list-group.tsx @@ -18,7 +18,7 @@ import { setToast, TOAST_TYPE } from "@plane/ui"; // components import { ListLoaderItemRow } from "@/components/ui"; // constants -import { DRAG_ALLOWED_GROUPS, EIssueLayoutTypes } from "@/constants/issue"; +import { DRAG_ALLOWED_GROUPS } from "@/constants/issue"; // hooks import { useProjectState } from "@/hooks/store"; import { useIntersectionObserver } from "@/hooks/use-intersection-observer"; @@ -26,7 +26,6 @@ import { useIssuesStore } from "@/hooks/use-issue-layout-store"; import { TSelectionHelper } from "@/hooks/use-multiple-select"; // components import { GroupDragOverlay } from "../group-drag-overlay"; -import { ListQuickAddIssueButton, QuickAddIssueRoot } from "../quick-add"; import { GroupDropLocation, getDestinationFromDropPayload, @@ -37,6 +36,7 @@ import { import { IssueBlocksList } from "./blocks-list"; import { HeaderGroupByCard } from "./headers/group-by-card"; import { TRenderQuickActions } from "./list-view-types"; +import { ListQuickAddIssueForm } from "./quick-add-issue-form"; interface Props { groupIssueIds: string[] | undefined; @@ -274,11 +274,8 @@ export const ListGroup = observer((props: Props) => { {enableIssueQuickAdd && !disableIssueCreation && !isGroupByCreatedBy && !isCompletedCycle && (
    -
    diff --git a/web/core/components/issues/issue-layouts/list/quick-add-issue-form.tsx b/web/core/components/issues/issue-layouts/list/quick-add-issue-form.tsx new file mode 100644 index 0000000000..0025f2dfc1 --- /dev/null +++ b/web/core/components/issues/issue-layouts/list/quick-add-issue-form.tsx @@ -0,0 +1,168 @@ +"use client"; + +import { FC, useEffect, useState, useRef } from "react"; +import { observer } from "mobx-react"; +import { useParams, usePathname } from "next/navigation"; +import { useForm } from "react-hook-form"; +import { PlusIcon } from "lucide-react"; +import { TIssue, IProject } from "@plane/types"; +// hooks +import { setPromiseToast } from "@plane/ui"; +import { CreateIssueToastActionItems } from "@/components/issues"; +import { ISSUE_CREATED } from "@/constants/event-tracker"; +import { createIssuePayload } from "@/helpers/issue.helper"; +import { useEventTracker, useProject } from "@/hooks/store"; +import useKeypress from "@/hooks/use-keypress"; +import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; +// ui +// types +// helper +// constants + +interface IInputProps { + formKey: string; + register: any; + setFocus: any; + projectDetail: IProject | null; +} +const Inputs: FC = (props) => { + const { formKey, register, setFocus, projectDetail } = props; + + useEffect(() => { + setFocus(formKey); + }, [formKey, setFocus]); + + return ( +
    +
    {projectDetail?.identifier ?? "..."}
    + +
    + ); +}; + +interface IListQuickAddIssueForm { + prePopulatedData?: Partial; + quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise; +} + +const defaultValues: Partial = { + name: "", +}; + +export const ListQuickAddIssueForm: FC = observer((props) => { + const { prePopulatedData, quickAddCallback } = props; + // router + const { workspaceSlug, projectId } = useParams(); + const pathname = usePathname(); + // hooks + const { getProjectById } = useProject(); + const { captureIssueEvent } = useEventTracker(); + + const projectDetail = (projectId && getProjectById(projectId.toString())) || undefined; + + const ref = useRef(null); + + const [isOpen, setIsOpen] = useState(false); + const handleClose = () => setIsOpen(false); + + useKeypress("Escape", handleClose); + useOutsideClickDetector(ref, handleClose); + + const { + reset, + handleSubmit, + setFocus, + register, + formState: { errors, isSubmitting }, + } = useForm({ defaultValues }); + + useEffect(() => { + if (!isOpen) reset({ ...defaultValues }); + }, [isOpen, reset]); + + const onSubmitHandler = async (formData: TIssue) => { + if (isSubmitting || !workspaceSlug || !projectId) return; + + reset({ ...defaultValues }); + + const payload = createIssuePayload(projectId.toString(), { + ...(prePopulatedData ?? {}), + ...formData, + }); + + if (quickAddCallback) { + const quickAddPromise = quickAddCallback(projectId.toString(), { ...payload }); + setPromiseToast(quickAddPromise, { + loading: "Adding issue...", + success: { + title: "Success!", + message: () => "Issue created successfully.", + actionItems: (data) => ( + + ), + }, + error: { + title: "Error!", + message: (err) => err?.message || "Some error occurred. Please try again.", + }, + }); + + await quickAddPromise + .then((res) => { + captureIssueEvent({ + eventName: ISSUE_CREATED, + payload: { ...res, state: "SUCCESS", element: "List quick add" }, + path: pathname, + }); + }) + .catch(() => { + captureIssueEvent({ + eventName: ISSUE_CREATED, + payload: { ...payload, state: "FAILED", element: "List quick add" }, + path: pathname, + }); + }); + } + }; + + return ( +
    + {isOpen ? ( +
    +
    + + +
    {`Press 'Enter' to add another issue`}
    +
    + ) : ( +
    setIsOpen(true)} + > + + New Issue +
    + )} +
    + ); +}); diff --git a/web/core/components/issues/issue-layouts/properties/all-properties.tsx b/web/core/components/issues/issue-layouts/properties/all-properties.tsx index b926a4b7e6..14a100cce7 100644 --- a/web/core/components/issues/issue-layouts/properties/all-properties.tsx +++ b/web/core/components/issues/issue-layouts/properties/all-properties.tsx @@ -32,8 +32,8 @@ import { useAppRouter } from "@/hooks/use-app-router"; import { useIssueStoreType } from "@/hooks/use-issue-layout-store"; import { usePlatformOS } from "@/hooks/use-platform-os"; // local components -import { IssuePropertyLabels } from "./labels"; -import { WithDisplayPropertiesHOC } from "./with-display-properties-HOC"; +import { IssuePropertyLabels } from "../properties/labels"; +import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC"; export interface IIssueProperties { issue: TIssue; diff --git a/web/core/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx b/web/core/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx index f0d998f2b1..cc1f9d1fb3 100644 --- a/web/core/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx +++ b/web/core/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx @@ -20,7 +20,6 @@ import { copyUrlToClipboard } from "@/helpers/string.helper"; // hooks import { useEventTracker, useProjectState } from "@/hooks/store"; // types -import { useIssueType } from "@/plane-web/hooks/store"; import { IQuickActionProps } from "../list/list-view-types"; export const AllIssueQuickActions: React.FC = observer((props) => { @@ -45,8 +44,6 @@ export const AllIssueQuickActions: React.FC = observer((props // store hooks const { setTrackElement } = useEventTracker(); const { getStateById } = useProjectState(); - // plane web hooks - const issueTypeDetail = useIssueType(issue.type_id); // derived values const stateDetails = getStateById(issue.state_id); const isEditingAllowed = !readOnly; @@ -70,7 +67,6 @@ export const AllIssueQuickActions: React.FC = observer((props { ...issue, name: `${issue.name} (copy)`, - sourceIssueId: issue.id, }, ["id"] ); @@ -95,7 +91,7 @@ export const AllIssueQuickActions: React.FC = observer((props setTrackElement("Global issues"); setCreateUpdateIssueModal(true); }, - shouldRender: isEditingAllowed && issueTypeDetail?.is_active, + shouldRender: isEditingAllowed, }, { key: "open-in-new-tab", diff --git a/web/core/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx b/web/core/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx index 842a91bebd..e53a4c2a44 100644 --- a/web/core/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx +++ b/web/core/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx @@ -20,8 +20,6 @@ import { cn } from "@/helpers/common.helper"; import { copyUrlToClipboard } from "@/helpers/string.helper"; // hooks import { useEventTracker, useIssues, useProjectState, useUser } from "@/hooks/store"; -// plane web hooks -import { useIssueType } from "@/plane-web/hooks/store"; // types import { IQuickActionProps } from "../list/list-view-types"; @@ -52,8 +50,6 @@ export const CycleIssueQuickActions: React.FC = observer((pro membership: { currentProjectRole }, } = useUser(); const { getStateById } = useProjectState(); - // plane web hooks - const issueTypeDetail = useIssueType(issue.type_id); // derived values const stateDetails = getStateById(issue.state_id); // auth @@ -81,7 +77,6 @@ export const CycleIssueQuickActions: React.FC = observer((pro { ...issue, name: `${issue.name} (copy)`, - sourceIssueId: issue.id, }, ["id"] ); @@ -109,7 +104,7 @@ export const CycleIssueQuickActions: React.FC = observer((pro setTrackElement(activeLayout); setCreateUpdateIssueModal(true); }, - shouldRender: isEditingAllowed && issueTypeDetail?.is_active, + shouldRender: isEditingAllowed, }, { key: "open-in-new-tab", diff --git a/web/core/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx b/web/core/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx index 17d56c8f50..2170a24999 100644 --- a/web/core/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx +++ b/web/core/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx @@ -21,7 +21,6 @@ import { copyUrlToClipboard } from "@/helpers/string.helper"; // hooks import { useIssues, useEventTracker, useUser, useProjectState } from "@/hooks/store"; // types -import { useIssueType } from "@/plane-web/hooks/store"; import { IQuickActionProps } from "../list/list-view-types"; export const ModuleIssueQuickActions: React.FC = observer((props) => { @@ -51,8 +50,6 @@ export const ModuleIssueQuickActions: React.FC = observer((pr membership: { currentProjectRole }, } = useUser(); const { getStateById } = useProjectState(); - // plane web hooks - const issueTypeDetail = useIssueType(issue.type_id); // derived values const stateDetails = getStateById(issue.state_id); // auth @@ -80,7 +77,6 @@ export const ModuleIssueQuickActions: React.FC = observer((pr { ...issue, name: `${issue.name} (copy)`, - sourceIssueId: issue.id, }, ["id"] ); @@ -105,7 +101,7 @@ export const ModuleIssueQuickActions: React.FC = observer((pr setTrackElement(activeLayout); setCreateUpdateIssueModal(true); }, - shouldRender: isEditingAllowed && issueTypeDetail?.is_active, + shouldRender: isEditingAllowed, }, { key: "open-in-new-tab", diff --git a/web/core/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx b/web/core/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx index 4b191fb5cb..943b72c345 100644 --- a/web/core/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx +++ b/web/core/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx @@ -21,7 +21,6 @@ import { copyUrlToClipboard } from "@/helpers/string.helper"; // hooks import { useEventTracker, useIssues, useProjectState, useUser } from "@/hooks/store"; // types -import { useIssueType } from "@/plane-web/hooks/store"; import { IQuickActionProps } from "../list/list-view-types"; export const ProjectIssueQuickActions: React.FC = observer((props) => { @@ -51,8 +50,6 @@ export const ProjectIssueQuickActions: React.FC = observer((p const { setTrackElement } = useEventTracker(); const { issuesFilter } = useIssues(EIssuesStoreType.PROJECT); const { getStateById } = useProjectState(); - // plane web hooks - const issueTypeDetail = useIssueType(issue.type_id); // derived values const activeLayout = `${issuesFilter.issueFilters?.displayFilters?.layout} layout`; const stateDetails = getStateById(issue.state_id); @@ -80,7 +77,6 @@ export const ProjectIssueQuickActions: React.FC = observer((p ...issue, name: `${issue.name} (copy)`, is_draft: isDraftIssue ? false : issue.is_draft, - sourceIssueId: issue.id, }, ["id"] ); @@ -105,7 +101,7 @@ export const ProjectIssueQuickActions: React.FC = observer((p setTrackElement(activeLayout); setCreateUpdateIssueModal(true); }, - shouldRender: isEditingAllowed && issueTypeDetail?.is_active, + shouldRender: isEditingAllowed, }, { key: "open-in-new-tab", diff --git a/web/core/components/issues/issue-layouts/quick-add/button/gantt.tsx b/web/core/components/issues/issue-layouts/quick-add/button/gantt.tsx deleted file mode 100644 index 41c5e12f98..0000000000 --- a/web/core/components/issues/issue-layouts/quick-add/button/gantt.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { FC } from "react"; -import { observer } from "mobx-react"; -import { PlusIcon } from "lucide-react"; -import { TQuickAddIssueButton } from "../root"; - -export const GanttQuickAddIssueButton: FC = observer((props) => { - const { onClick } = props; - - return ( - - ); -}); diff --git a/web/core/components/issues/issue-layouts/quick-add/button/index.ts b/web/core/components/issues/issue-layouts/quick-add/button/index.ts deleted file mode 100644 index 91477c053d..0000000000 --- a/web/core/components/issues/issue-layouts/quick-add/button/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./list"; -export * from "./kanban"; -export * from "./gantt"; -export * from "./spreadsheet"; diff --git a/web/core/components/issues/issue-layouts/quick-add/button/kanban.tsx b/web/core/components/issues/issue-layouts/quick-add/button/kanban.tsx deleted file mode 100644 index 90411e74d2..0000000000 --- a/web/core/components/issues/issue-layouts/quick-add/button/kanban.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { FC } from "react"; -import { observer } from "mobx-react"; -import { PlusIcon } from "lucide-react"; -import { TQuickAddIssueButton } from "../root"; - -export const KanbanQuickAddIssueButton: FC = observer((props) => { - const { onClick } = props; - - return ( -
    - - New Issue -
    - ); -}); diff --git a/web/core/components/issues/issue-layouts/quick-add/button/list.tsx b/web/core/components/issues/issue-layouts/quick-add/button/list.tsx deleted file mode 100644 index c5b77e1f09..0000000000 --- a/web/core/components/issues/issue-layouts/quick-add/button/list.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { FC } from "react"; -import { observer } from "mobx-react"; -import { PlusIcon } from "lucide-react"; -import { TQuickAddIssueButton } from "../root"; - -export const ListQuickAddIssueButton: FC = observer((props) => { - const { onClick } = props; - - return ( -
    - - New Issue -
    - ); -}); diff --git a/web/core/components/issues/issue-layouts/quick-add/button/spreadsheet.tsx b/web/core/components/issues/issue-layouts/quick-add/button/spreadsheet.tsx deleted file mode 100644 index b5663bb569..0000000000 --- a/web/core/components/issues/issue-layouts/quick-add/button/spreadsheet.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { FC } from "react"; -import { observer } from "mobx-react"; -import { PlusIcon } from "lucide-react"; -import { TQuickAddIssueButton } from "../root"; - -export const SpreadsheetAddIssueButton: FC = observer((props) => { - const { onClick } = props; - - return ( -
    - -
    - ); -}); diff --git a/web/core/components/issues/issue-layouts/quick-add/form/calendar.tsx b/web/core/components/issues/issue-layouts/quick-add/form/calendar.tsx deleted file mode 100644 index 66e7990569..0000000000 --- a/web/core/components/issues/issue-layouts/quick-add/form/calendar.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { FC } from "react"; -import { observer } from "mobx-react"; -import { TQuickAddIssueForm } from "../root"; - -export const CalendarQuickAddIssueForm: FC = observer((props) => { - const { ref, isOpen, projectDetail, register, onSubmit } = props; - - return ( -
    -
    -

    {projectDetail?.identifier ?? "..."}

    - -
    -
    - ); -}); diff --git a/web/core/components/issues/issue-layouts/quick-add/form/gantt.tsx b/web/core/components/issues/issue-layouts/quick-add/form/gantt.tsx deleted file mode 100644 index d0afadb098..0000000000 --- a/web/core/components/issues/issue-layouts/quick-add/form/gantt.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { FC } from "react"; -import { observer } from "mobx-react"; -import { cn } from "@/helpers/common.helper"; -import { TQuickAddIssueForm } from "../root"; - -export const GanttQuickAddIssueForm: FC = observer((props) => { - const { ref, projectDetail, hasError, register, onSubmit } = props; - - return ( -
    -
    -
    -
    {projectDetail?.identifier ?? "..."}
    - -
    -
    -
    {`Press 'Enter' to add another issue`}
    -
    - ); -}); diff --git a/web/core/components/issues/issue-layouts/quick-add/form/index.ts b/web/core/components/issues/issue-layouts/quick-add/form/index.ts deleted file mode 100644 index d0a45c124c..0000000000 --- a/web/core/components/issues/issue-layouts/quick-add/form/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./list"; -export * from "./kanban"; -export * from "./gantt"; -export * from "./calendar"; -export * from "./spreadsheet"; diff --git a/web/core/components/issues/issue-layouts/quick-add/form/kanban.tsx b/web/core/components/issues/issue-layouts/quick-add/form/kanban.tsx deleted file mode 100644 index 8111f539e4..0000000000 --- a/web/core/components/issues/issue-layouts/quick-add/form/kanban.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { FC } from "react"; -import { observer } from "mobx-react"; -import { TQuickAddIssueForm } from "../root"; - -export const KanbanQuickAddIssueForm: FC = observer((props) => { - const { ref, projectDetail, register, onSubmit } = props; - - return ( -
    -
    -
    -

    {projectDetail?.identifier ?? "..."}

    - -
    -
    -
    {`Press 'Enter' to add another issue`}
    -
    - ); -}); diff --git a/web/core/components/issues/issue-layouts/quick-add/form/list.tsx b/web/core/components/issues/issue-layouts/quick-add/form/list.tsx deleted file mode 100644 index 0a894511ff..0000000000 --- a/web/core/components/issues/issue-layouts/quick-add/form/list.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { FC } from "react"; -import { observer } from "mobx-react"; -import { TQuickAddIssueForm } from "../root"; - -export const ListQuickAddIssueForm: FC = observer((props) => { - const { ref, projectDetail, register, onSubmit } = props; - - return ( -
    -
    -
    -
    {projectDetail?.identifier ?? "..."}
    - -
    -
    -
    {`Press 'Enter' to add another issue`}
    -
    - ); -}); diff --git a/web/core/components/issues/issue-layouts/quick-add/form/spreadsheet.tsx b/web/core/components/issues/issue-layouts/quick-add/form/spreadsheet.tsx deleted file mode 100644 index b4317f3593..0000000000 --- a/web/core/components/issues/issue-layouts/quick-add/form/spreadsheet.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { FC } from "react"; -import { observer } from "mobx-react"; -import { TQuickAddIssueForm } from "../root"; - -export const SpreadsheetQuickAddIssueForm: FC = observer((props) => { - const { ref, projectDetail, register, onSubmit } = props; - - return ( -
    -
    -

    {projectDetail?.identifier ?? "..."}

    - -
    -

    - Press {"'"}Enter{"'"} to add another issue -

    -
    - ); -}); diff --git a/web/core/components/issues/issue-layouts/quick-add/index.ts b/web/core/components/issues/issue-layouts/quick-add/index.ts deleted file mode 100644 index a82947248f..0000000000 --- a/web/core/components/issues/issue-layouts/quick-add/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./root"; -export * from "./form"; -export * from "./button"; diff --git a/web/core/components/issues/issue-layouts/quick-add/root.tsx b/web/core/components/issues/issue-layouts/quick-add/root.tsx deleted file mode 100644 index 4cdf2e8c09..0000000000 --- a/web/core/components/issues/issue-layouts/quick-add/root.tsx +++ /dev/null @@ -1,185 +0,0 @@ -"use client"; - -import { FC, useEffect, useState } from "react"; -import { observer } from "mobx-react"; -import { useParams, usePathname } from "next/navigation"; -import { useForm, UseFormRegister } from "react-hook-form"; -import { PlusIcon } from "lucide-react"; -// types -import { IProject, TIssue } from "@plane/types"; -// ui -import { setPromiseToast } from "@plane/ui"; -// components -import { CreateIssueToastActionItems } from "@/components/issues"; -// constants -import { ISSUE_CREATED } from "@/constants/event-tracker"; -import { EIssueLayoutTypes } from "@/constants/issue"; -// helpers -import { cn } from "@/helpers/common.helper"; -import { createIssuePayload } from "@/helpers/issue.helper"; -// hooks -import { useEventTracker } from "@/hooks/store"; -// plane web components -import { QuickAddIssueFormRoot } from "@/plane-web/components/issues"; - -export type TQuickAddIssueForm = { - ref: React.RefObject; - isOpen: boolean; - projectDetail: IProject; - hasError: boolean; - register: UseFormRegister; - onSubmit: () => void; -}; - -export type TQuickAddIssueButton = { - onClick: () => void; -}; - -type TQuickAddIssueRoot = { - isQuickAddOpen?: boolean; - layout: EIssueLayoutTypes; - prePopulatedData?: Partial; - QuickAddButton?: FC; - customQuickAddButton?: JSX.Element; - containerClassName?: string; - setIsQuickAddOpen?: (isOpen: boolean) => void; - quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise; -}; - -const defaultValues: Partial = { - name: "", -}; - -export const QuickAddIssueRoot: FC = observer((props) => { - const { - isQuickAddOpen, - layout, - prePopulatedData, - QuickAddButton, - customQuickAddButton, - containerClassName = "", - setIsQuickAddOpen, - quickAddCallback, - } = props; - // router - const { workspaceSlug, projectId } = useParams(); - const pathname = usePathname(); - // states - const [isOpen, setIsOpen] = useState(isQuickAddOpen ?? false); - // store hooks - const { captureIssueEvent } = useEventTracker(); - // form info - const { - reset, - handleSubmit, - setFocus, - register, - formState: { errors, isSubmitting }, - } = useForm({ defaultValues }); - - useEffect(() => { - if (isQuickAddOpen !== undefined) { - setIsOpen(isQuickAddOpen); - } - }, [isQuickAddOpen]); - - useEffect(() => { - if (!isOpen) reset({ ...defaultValues }); - }, [isOpen, reset]); - - const handleIsOpen = (isOpen: boolean) => { - if (isQuickAddOpen !== undefined && setIsQuickAddOpen) { - setIsQuickAddOpen(isOpen); - } else { - setIsOpen(isOpen); - } - }; - - const onSubmitHandler = async (formData: TIssue) => { - if (isSubmitting || !workspaceSlug || !projectId) return; - - reset({ ...defaultValues }); - - const payload = createIssuePayload(projectId.toString(), { - ...(prePopulatedData ?? {}), - ...formData, - }); - - if (quickAddCallback) { - const quickAddPromise = quickAddCallback(projectId.toString(), { ...payload }); - setPromiseToast(quickAddPromise, { - loading: "Adding issue...", - success: { - title: "Success!", - message: () => "Issue created successfully.", - actionItems: (data) => ( - - ), - }, - error: { - title: "Error!", - message: (err) => err?.message || "Some error occurred. Please try again.", - }, - }); - - await quickAddPromise - .then((res) => { - captureIssueEvent({ - eventName: ISSUE_CREATED, - payload: { ...res, state: "SUCCESS", element: ` ${layout} quick add` }, - path: pathname, - }); - }) - .catch(() => { - captureIssueEvent({ - eventName: ISSUE_CREATED, - payload: { ...payload, state: "FAILED", element: `${layout} quick ad` }, - path: pathname, - }); - }); - } - }; - - if (!projectId) return null; - - return ( -
    - {isOpen ? ( - handleIsOpen(false)} - /> - ) : ( - <> - {QuickAddButton && handleIsOpen(true)} />} - {customQuickAddButton && <>{customQuickAddButton}} - {!QuickAddButton && !customQuickAddButton && ( -
    handleIsOpen(true)} - > - - New Issue -
    - )} - - )} -
    - ); -}); diff --git a/web/core/components/issues/issue-layouts/spreadsheet/index.ts b/web/core/components/issues/issue-layouts/spreadsheet/index.ts index 75d40695ff..8fa49e851e 100644 --- a/web/core/components/issues/issue-layouts/spreadsheet/index.ts +++ b/web/core/components/issues/issue-layouts/spreadsheet/index.ts @@ -1,4 +1,5 @@ export * from "./columns"; export * from "./roots"; export * from "./spreadsheet-view"; +export * from "./quick-add-issue-form"; export * from "./spreadsheet-header-column"; diff --git a/web/core/components/issues/issue-layouts/spreadsheet/issue-row.tsx b/web/core/components/issues/issue-layouts/spreadsheet/issue-row.tsx index 764021abde..189fdb9065 100644 --- a/web/core/components/issues/issue-layouts/spreadsheet/issue-row.tsx +++ b/web/core/components/issues/issue-layouts/spreadsheet/issue-row.tsx @@ -20,8 +20,6 @@ import { useIssueDetail, useProject } from "@/hooks/store"; import { TSelectionHelper } from "@/hooks/use-multiple-select"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; import { usePlatformOS } from "@/hooks/use-platform-os"; -// plane web components -import { IssueIdentifier } from "@/plane-web/components/issues"; // local components import { TRenderQuickActions } from "../list/list-view-types"; import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC"; @@ -289,13 +287,7 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => {

    - {issueDetail.project_id && ( - - )} + {getProjectIdentifierById(issueDetail.project_id)}-{issueDetail.sequence_id}

    diff --git a/web/core/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx b/web/core/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx new file mode 100644 index 0000000000..e88887b437 --- /dev/null +++ b/web/core/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx @@ -0,0 +1,238 @@ +"use client"; + +import { useEffect, useState, useRef } from "react"; +import { observer } from "mobx-react"; +import { usePathname } from "next/navigation"; +import { useForm } from "react-hook-form"; +import { PlusIcon } from "lucide-react"; +import { TIssue } from "@plane/types"; +// hooks +import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui"; +import { CreateIssueToastActionItems } from "@/components/issues"; +import { ISSUE_CREATED } from "@/constants/event-tracker"; +import { createIssuePayload } from "@/helpers/issue.helper"; +import { useEventTracker, useProject, useWorkspace } from "@/hooks/store"; +import useKeypress from "@/hooks/use-keypress"; +import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; +// helpers +// ui +// types +// constants + +type Props = { + formKey: keyof TIssue; + groupId?: string; + subGroupId?: string | null; + prePopulatedData?: Partial; + quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise; +}; + +const defaultValues: Partial = { + name: "", +}; + +const Inputs = (props: any) => { + const { register, setFocus, projectDetails } = props; + + useEffect(() => { + setFocus("name"); + }, [setFocus]); + + return ( + <> +

    {projectDetails?.identifier ?? "..."}

    + + + ); +}; + +export const SpreadsheetQuickAddIssueForm: React.FC = observer((props) => { + const { formKey, prePopulatedData, quickAddCallback } = props; + // store hooks + const { currentWorkspace } = useWorkspace(); + const { currentProjectDetails } = useProject(); + const { captureIssueEvent } = useEventTracker(); + // router + const pathname = usePathname(); + // form info + const { + reset, + handleSubmit, + setFocus, + register, + formState: { errors, isSubmitting }, + } = useForm({ defaultValues }); + + // ref + const ref = useRef(null); + + // states + const [isOpen, setIsOpen] = useState(false); + + const handleClose = () => setIsOpen(false); + + // hooks + useKeypress("Escape", handleClose); + useOutsideClickDetector(ref, handleClose); + + useEffect(() => { + setFocus("name"); + }, [setFocus, isOpen]); + + useEffect(() => { + if (!isOpen) reset({ ...defaultValues }); + }, [isOpen, reset]); + + useEffect(() => { + if (!errors) return; + + Object.keys(errors).forEach((key) => { + const error = errors[key as keyof TIssue]; + + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: error?.message?.toString() || "Some error occurred. Please try again.", + }); + }); + }, [errors]); + + // const onSubmitHandler = async (formData: TIssue) => { + // if (isSubmitting || !workspaceSlug || !projectId) return; + + // // resetting the form so that user can add another issue quickly + // reset({ ...defaultValues }); + + // const payload = createIssuePayload(workspaceDetail!, projectDetails!, { + // ...(prePopulatedData ?? {}), + // ...formData, + // }); + + // try { + // quickAddStore.createIssue( + // workspaceSlug.toString(), + // projectId.toString(), + // { + // group_id: groupId ?? null, + // sub_group_id: null, + // }, + // payload + // ); + + // setToast({ + // type: TOAST_TYPE.SUCCESS, + // title: "Success!", + // message: "Issue created successfully.", + // }); + // } catch (err: any) { + // Object.keys(err || {}).forEach((key) => { + // const error = err?.[key]; + // const errorTitle = error ? (Array.isArray(error) ? error.join(", ") : error) : null; + + // setToast({ + // type: TOAST_TYPE.ERROR, + // title: "Error!", + // message: errorTitle || "Some error occurred. Please try again.", + // }); + // }); + // } + // }; + + const onSubmitHandler = async (formData: TIssue) => { + if (isSubmitting || !currentWorkspace || !currentProjectDetails) return; + + reset({ ...defaultValues }); + + const payload = createIssuePayload(currentProjectDetails.id, { + ...(prePopulatedData ?? {}), + ...formData, + }); + + if (quickAddCallback) { + const quickAddPromise = quickAddCallback(currentProjectDetails.id, { ...payload } as TIssue); + setPromiseToast(quickAddPromise, { + loading: "Adding issue...", + success: { + title: "Success!", + message: () => "Issue created successfully.", + actionItems: (data) => ( + + ), + }, + error: { + title: "Error!", + message: (err) => err?.message || "Some error occurred. Please try again.", + }, + }); + + await quickAddPromise + .then((res) => { + captureIssueEvent({ + eventName: ISSUE_CREATED, + payload: { ...res, state: "SUCCESS", element: "Spreadsheet quick add" }, + path: pathname, + }); + }) + .catch((err) => { + captureIssueEvent({ + eventName: ISSUE_CREATED, + payload: { ...payload, state: "FAILED", element: "Spreadsheet quick add" }, + path: pathname, + }); + console.error(err); + }); + } + }; + + return ( +
    + {isOpen && ( +
    +
    + + +
    + )} + + {isOpen && ( +

    + Press {"'"}Enter{"'"} to add another issue +

    + )} + + {!isOpen && ( +
    + +
    + )} +
    + ); +}); diff --git a/web/core/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx b/web/core/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx index 167aa46f42..32b8e287ab 100644 --- a/web/core/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx +++ b/web/core/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx @@ -5,9 +5,8 @@ import { TIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@pl // components import { LogoSpinner } from "@/components/common"; import { MultipleSelectGroup } from "@/components/core"; -import { QuickAddIssueRoot, SpreadsheetAddIssueButton } from "@/components/issues"; +import { SpreadsheetQuickAddIssueForm } from "@/components/issues"; // constants -import { EIssueLayoutTypes } from "@/constants/issue"; import { SPREADSHEET_PROPERTY_LIST, SPREADSHEET_SELECT_GROUP } from "@/constants/spreadsheet"; // hooks import { useProject } from "@/hooks/store"; @@ -110,11 +109,7 @@ export const SpreadsheetView: React.FC = observer((props) => {
    {enableQuickCreateIssue && !disableIssueCreation && ( - + )}
    diff --git a/web/core/components/issues/issue-modal/base.tsx b/web/core/components/issues/issue-modal/base.tsx deleted file mode 100644 index 92c0be51c6..0000000000 --- a/web/core/components/issues/issue-modal/base.tsx +++ /dev/null @@ -1,331 +0,0 @@ -"use client"; - -import React, { useEffect, useRef, useState } from "react"; -import { observer } from "mobx-react"; -import { useParams, usePathname } from "next/navigation"; -// types -import type { TIssue } from "@plane/types"; -// ui -import { EModalPosition, EModalWidth, ModalCore, TOAST_TYPE, setToast } from "@plane/ui"; -import { CreateIssueToastActionItems, IssuesModalProps } from "@/components/issues"; -// constants -import { ISSUE_CREATED, ISSUE_UPDATED } from "@/constants/event-tracker"; -import { EIssuesStoreType } from "@/constants/issue"; -// hooks -import { useIssueModal } from "@/hooks/context/use-issue-modal"; -import { useEventTracker, useCycle, useIssues, useModule, useProject, useIssueDetail } from "@/hooks/store"; -import { useIssueStoreType } from "@/hooks/use-issue-layout-store"; -import { useIssuesActions } from "@/hooks/use-issues-actions"; -import useLocalStorage from "@/hooks/use-local-storage"; -// local components -import { DraftIssueLayout } from "./draft-issue-layout"; -import { IssueFormRoot } from "./form"; - -export const CreateUpdateIssueModalBase: React.FC = observer((props) => { - const { - data, - isOpen, - onClose, - onSubmit, - withDraftIssueWrapper = true, - storeType: issueStoreFromProps, - isDraft = false, - } = props; - const issueStoreType = useIssueStoreType(); - - const storeType = issueStoreFromProps ?? issueStoreType; - // ref - const issueTitleRef = useRef(null); - // states - const [changesMade, setChangesMade] = useState | null>(null); - const [createMore, setCreateMore] = useState(false); - const [activeProjectId, setActiveProjectId] = useState(null); - const [description, setDescription] = useState(undefined); - // store hooks - const { captureIssueEvent } = useEventTracker(); - const { workspaceSlug, projectId, cycleId, moduleId } = useParams(); - const { workspaceProjectIds } = useProject(); - const { fetchCycleDetails } = useCycle(); - const { fetchModuleDetails } = useModule(); - const { issues } = useIssues(storeType); - const { issues: projectIssues } = useIssues(EIssuesStoreType.PROJECT); - const { issues: draftIssues } = useIssues(EIssuesStoreType.DRAFT); - const { fetchIssue } = useIssueDetail(); - const { handleCreateUpdatePropertyValues } = useIssueModal(); - // pathname - const pathname = usePathname(); - // local storage - const { storedValue: localStorageDraftIssues, setValue: setLocalStorageDraftIssue } = useLocalStorage< - Record> - >("draftedIssue", {}); - // current store details - const { createIssue, updateIssue } = useIssuesActions(storeType); - - const fetchIssueDetail = async (issueId: string | undefined) => { - setDescription(undefined); - if (!workspaceSlug) return; - - if (!projectId || issueId === undefined) { - setDescription(data?.description_html || "

    "); - return; - } - const response = await fetchIssue( - workspaceSlug.toString(), - projectId.toString(), - issueId, - isDraft ? "DRAFT" : "DEFAULT" - ); - if (response) setDescription(response?.description_html || "

    "); - }; - - useEffect(() => { - // fetching issue details - if (isOpen) fetchIssueDetail(data?.id); - - // if modal is closed, reset active project to null - // and return to avoid activeProjectId being set to some other project - if (!isOpen) { - setActiveProjectId(null); - return; - } - - // if data is present, set active project to the project of the - // issue. This has more priority than the project in the url. - if (data && data.project_id) { - setActiveProjectId(data.project_id); - return; - } - - // if data is not present, set active project to the project - // in the url. This has the least priority. - if (workspaceProjectIds && workspaceProjectIds.length > 0 && !activeProjectId) - setActiveProjectId(projectId?.toString() ?? workspaceProjectIds?.[0]); - - // clearing up the description state when we leave the component - return () => setDescription(undefined); - }, [data, projectId, isOpen, activeProjectId]); - - const addIssueToCycle = async (issue: TIssue, cycleId: string) => { - if (!workspaceSlug || !issue.project_id) return; - - await issues.addIssueToCycle(workspaceSlug.toString(), issue.project_id, cycleId, [issue.id]); - fetchCycleDetails(workspaceSlug.toString(), issue.project_id, cycleId); - }; - - const addIssueToModule = async (issue: TIssue, moduleIds: string[]) => { - if (!workspaceSlug || !activeProjectId) return; - - await issues.changeModulesInIssue(workspaceSlug.toString(), activeProjectId, issue.id, moduleIds, []); - moduleIds.forEach((moduleId) => fetchModuleDetails(workspaceSlug.toString(), activeProjectId, moduleId)); - }; - - const handleCreateMoreToggleChange = (value: boolean) => { - setCreateMore(value); - }; - - const handleClose = (saveDraftIssueInLocalStorage?: boolean) => { - if (changesMade && saveDraftIssueInLocalStorage) { - // updating the current edited issue data in the local storage - let draftIssues = localStorageDraftIssues ? localStorageDraftIssues : {}; - if (workspaceSlug) { - draftIssues = { ...draftIssues, [workspaceSlug.toString()]: changesMade }; - setLocalStorageDraftIssue(draftIssues); - } - } - - setActiveProjectId(null); - setChangesMade(null); - onClose(); - }; - - const handleCreateIssue = async ( - payload: Partial, - is_draft_issue: boolean = false - ): Promise => { - if (!workspaceSlug || !payload.project_id) return; - - try { - let response; - - // if draft issue, use draft issue store to create issue - if (is_draft_issue) { - response = await draftIssues.createIssue(workspaceSlug.toString(), payload.project_id, payload); - } - // if cycle id in payload does not match the cycleId in url - // or if the moduleIds in Payload does not match the moduleId in url - // use the project issue store to create issues - else if ( - (payload.cycle_id !== cycleId && storeType === EIssuesStoreType.CYCLE) || - (!payload.module_ids?.includes(moduleId?.toString()) && storeType === EIssuesStoreType.MODULE) - ) { - response = await projectIssues.createIssue(workspaceSlug.toString(), payload.project_id, payload); - } // else just use the existing store type's create method - else if (createIssue) { - response = await createIssue(payload.project_id, payload); - } - - if (!response) throw new Error(); - - // check if we should add issue to cycle/module - if ( - payload.cycle_id && - payload.cycle_id !== "" && - (payload.cycle_id !== cycleId || storeType !== EIssuesStoreType.CYCLE) - ) { - await addIssueToCycle(response, payload.cycle_id); - } - if ( - payload.module_ids && - payload.module_ids.length > 0 && - (!payload.module_ids.includes(moduleId?.toString()) || storeType !== EIssuesStoreType.MODULE) - ) { - await addIssueToModule(response, payload.module_ids); - } - - // add other property values - if (response.id && response.project_id) { - await handleCreateUpdatePropertyValues({ - issueId: response.id, - projectId: response.project_id, - workspaceSlug: workspaceSlug.toString(), - }); - } - - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: `${is_draft_issue ? "Draft issue" : "Issue"} created successfully.`, - actionItems: !is_draft_issue && response?.project_id && ( - - ), - }); - captureIssueEvent({ - eventName: ISSUE_CREATED, - payload: { ...response, state: "SUCCESS" }, - path: pathname, - }); - !createMore && handleClose(); - if (createMore) issueTitleRef && issueTitleRef?.current?.focus(); - setDescription("

    "); - setChangesMade(null); - return response; - } catch (error) { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: `${is_draft_issue ? "Draft issue" : "Issue"} could not be created. Please try again.`, - }); - captureIssueEvent({ - eventName: ISSUE_CREATED, - payload: { ...payload, state: "FAILED" }, - path: pathname, - }); - } - }; - - const handleUpdateIssue = async (payload: Partial): Promise => { - if (!workspaceSlug || !payload.project_id || !data?.id) return; - - try { - isDraft - ? await draftIssues.updateIssue(workspaceSlug.toString(), payload.project_id, data.id, payload) - : updateIssue && (await updateIssue(payload.project_id, data.id, payload)); - - // add other property values - await handleCreateUpdatePropertyValues({ - issueId: data.id, - projectId: payload.project_id, - workspaceSlug: workspaceSlug.toString(), - }); - - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Issue updated successfully.", - }); - captureIssueEvent({ - eventName: ISSUE_UPDATED, - payload: { ...payload, issueId: data.id, state: "SUCCESS" }, - path: pathname, - }); - handleClose(); - } catch (error) { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Issue could not be updated. Please try again.", - }); - captureIssueEvent({ - eventName: ISSUE_UPDATED, - payload: { ...payload, state: "FAILED" }, - path: pathname, - }); - } - }; - - const handleFormSubmit = async (payload: Partial, is_draft_issue: boolean = false) => { - if (!workspaceSlug || !payload.project_id || !storeType) return; - // remove sourceIssueId from payload since it is not needed - if (data?.sourceIssueId) delete data.sourceIssueId; - - let response: TIssue | undefined = undefined; - if (!data?.id) response = await handleCreateIssue(payload, is_draft_issue); - else response = await handleUpdateIssue(payload); - - if (response != undefined && onSubmit) await onSubmit(response); - }; - - const handleFormChange = (formData: Partial | null) => setChangesMade(formData); - - // don't open the modal if there are no projects - if (!workspaceProjectIds || workspaceProjectIds.length === 0 || !activeProjectId) return null; - - return ( - handleClose(true)} - position={EModalPosition.TOP} - width={EModalWidth.XXXXL} - > - {withDraftIssueWrapper ? ( - - ) : ( - handleClose(false)} - isCreateMoreToggleEnabled={createMore} - onCreateMoreToggleChange={handleCreateMoreToggleChange} - onSubmit={handleFormSubmit} - projectId={activeProjectId} - isDraft={isDraft} - /> - )} - - ); -}); diff --git a/web/core/components/issues/issue-modal/components/default-properties.tsx b/web/core/components/issues/issue-modal/components/default-properties.tsx deleted file mode 100644 index e0012d9b83..0000000000 --- a/web/core/components/issues/issue-modal/components/default-properties.tsx +++ /dev/null @@ -1,325 +0,0 @@ -"use client"; - -import React, { useState } from "react"; -import { observer } from "mobx-react"; -import { Control, Controller } from "react-hook-form"; -import { LayoutPanelTop } from "lucide-react"; -// types -import { ISearchIssueResponse, TIssue } from "@plane/types"; -// ui -import { CustomMenu } from "@plane/ui"; -// components -import { - CycleDropdown, - DateDropdown, - EstimateDropdown, - ModuleDropdown, - PriorityDropdown, - MemberDropdown, - StateDropdown, -} from "@/components/dropdowns"; -import { ParentIssuesListModal } from "@/components/issues"; -import { IssueLabelSelect } from "@/components/issues/select"; -// helpers -import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper"; -import { getTabIndex } from "@/helpers/issue-modal.helper"; -// hooks -import { useProjectEstimates, useProject } from "@/hooks/store"; -// plane web components -import { IssueIdentifier } from "@/plane-web/components/issues"; - -type TIssueDefaultPropertiesProps = { - control: Control; - id: string | undefined; - projectId: string | null; - workspaceSlug: string; - selectedParentIssue: ISearchIssueResponse | null; - startDate: string | null; - targetDate: string | null; - parentId: string | null; - isDraft: boolean; - handleFormChange: () => void; - setLabelModal: React.Dispatch>; - setSelectedParentIssue: (issue: ISearchIssueResponse) => void; -}; - -export const IssueDefaultProperties: React.FC = observer((props) => { - const { - control, - id, - projectId, - workspaceSlug, - selectedParentIssue, - startDate, - targetDate, - parentId, - isDraft, - handleFormChange, - setLabelModal, - setSelectedParentIssue, - } = props; - // states - const [parentIssueListModalOpen, setParentIssueListModalOpen] = useState(false); - // store hooks - const { areEstimateEnabledByProjectId } = useProjectEstimates(); - const { getProjectById } = useProject(); - // derived values - const projectDetails = getProjectById(projectId); - - const minDate = getDate(startDate); - minDate?.setDate(minDate.getDate()); - - const maxDate = getDate(targetDate); - maxDate?.setDate(maxDate.getDate()); - - return ( -
    - ( -
    - { - onChange(stateId); - handleFormChange(); - }} - projectId={projectId ?? undefined} - buttonVariant="border-with-text" - tabIndex={getTabIndex("state_id")} - /> -
    - )} - /> - ( -
    - { - onChange(priority); - handleFormChange(); - }} - buttonVariant="border-with-text" - tabIndex={getTabIndex("priority")} - /> -
    - )} - /> - ( -
    - { - onChange(assigneeIds); - handleFormChange(); - }} - buttonVariant={value?.length > 0 ? "transparent-without-text" : "border-with-text"} - buttonClassName={value?.length > 0 ? "hover:bg-transparent" : ""} - placeholder="Assignees" - multiple - tabIndex={getTabIndex("assignee_ids")} - /> -
    - )} - /> - ( -
    - { - onChange(labelIds); - handleFormChange(); - }} - projectId={projectId ?? undefined} - tabIndex={getTabIndex("label_ids")} - /> -
    - )} - /> - ( -
    - { - onChange(date ? renderFormattedPayloadDate(date) : null); - handleFormChange(); - }} - buttonVariant="border-with-text" - maxDate={maxDate ?? undefined} - placeholder="Start date" - tabIndex={getTabIndex("start_date")} - /> -
    - )} - /> - ( -
    - { - onChange(date ? renderFormattedPayloadDate(date) : null); - handleFormChange(); - }} - buttonVariant="border-with-text" - minDate={minDate ?? undefined} - placeholder="Due date" - tabIndex={getTabIndex("target_date")} - /> -
    - )} - /> - {projectDetails?.cycle_view && ( - ( -
    - { - onChange(cycleId); - handleFormChange(); - }} - placeholder="Cycle" - value={value} - buttonVariant="border-with-text" - tabIndex={getTabIndex("cycle_id")} - /> -
    - )} - /> - )} - {projectDetails?.module_view && workspaceSlug && ( - ( -
    - { - onChange(moduleIds); - handleFormChange(); - }} - placeholder="Modules" - buttonVariant="border-with-text" - tabIndex={getTabIndex("module_ids")} - multiple - showCount - /> -
    - )} - /> - )} - {projectId && areEstimateEnabledByProjectId(projectId) && ( - ( -
    - { - onChange(estimatePoint); - handleFormChange(); - }} - projectId={projectId} - buttonVariant="border-with-text" - tabIndex={getTabIndex("estimate_point")} - placeholder="Estimate" - /> -
    - )} - /> - )} - {parentId ? ( - - {selectedParentIssue?.project_id && ( - - )} - - } - placement="bottom-start" - tabIndex={getTabIndex("parent_id")} - > - <> - setParentIssueListModalOpen(true)}> - Change parent issue - - ( - { - onChange(null); - handleFormChange(); - }} - > - Remove parent issue - - )} - /> - - - ) : ( - - )} - ( - setParentIssueListModalOpen(false)} - onChange={(issue) => { - onChange(issue.id); - handleFormChange(); - setSelectedParentIssue(issue); - }} - projectId={projectId ?? undefined} - issueId={isDraft ? undefined : id} - /> - )} - /> -
    - ); -}); diff --git a/web/core/components/issues/issue-modal/components/description-editor.tsx b/web/core/components/issues/issue-modal/components/description-editor.tsx deleted file mode 100644 index 0a16f22dde..0000000000 --- a/web/core/components/issues/issue-modal/components/description-editor.tsx +++ /dev/null @@ -1,230 +0,0 @@ -"use client"; - -import React, { useEffect, useState } from "react"; -import { observer } from "mobx-react"; -import { Control, Controller } from "react-hook-form"; -import { Sparkle } from "lucide-react"; -// editor -import { EditorRefApi } from "@plane/editor"; -// types -import { TIssue } from "@plane/types"; -// ui -import { Loader, setToast, TOAST_TYPE } from "@plane/ui"; -// components -import { GptAssistantPopover } from "@/components/core"; -import { RichTextEditor } from "@/components/editor"; -// helpers -import { getTabIndex } from "@/helpers/issue-modal.helper"; -import { getDescriptionPlaceholder } from "@/helpers/issue.helper"; -// hooks -import { useInstance, useWorkspace } from "@/hooks/store"; -import useKeypress from "@/hooks/use-keypress"; -// services -import { AIService } from "@/services/ai.service"; - -type TIssueDescriptionEditorProps = { - control: Control; - issueName: string; - descriptionHtmlData: string | undefined; - editorRef: React.MutableRefObject; - submitBtnRef: React.MutableRefObject; - gptAssistantModal: boolean; - workspaceSlug: string; - projectId: string | null; - handleFormChange: () => void; - handleDescriptionHTMLDataChange: (descriptionHtmlData: string) => void; - setGptAssistantModal: React.Dispatch>; - handleGptAssistantClose: () => void; - onClose: () => void; -}; - -// services -const aiService = new AIService(); - -export const IssueDescriptionEditor: React.FC = observer((props) => { - const { - control, - issueName, - descriptionHtmlData, - editorRef, - submitBtnRef, - gptAssistantModal, - workspaceSlug, - projectId, - handleFormChange, - handleDescriptionHTMLDataChange, - setGptAssistantModal, - handleGptAssistantClose, - onClose, - } = props; - // states - const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false); - // store hooks - const { getWorkspaceBySlug } = useWorkspace(); - const workspaceId = getWorkspaceBySlug(workspaceSlug?.toString())?.id as string; - const { config } = useInstance(); - - useEffect(() => { - if (descriptionHtmlData) handleDescriptionHTMLDataChange(descriptionHtmlData); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [descriptionHtmlData]); - - const handleKeyDown = (event: KeyboardEvent) => { - if (editorRef.current?.isEditorReadyToDiscard()) { - onClose(); - } else { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Editor is still processing changes. Please wait before proceeding.", - }); - event.preventDefault(); // Prevent default action if editor is not ready to discard - } - }; - - useKeypress("Escape", handleKeyDown); - - // handlers - const handleAiAssistance = async (response: string) => { - if (!workspaceSlug || !projectId) return; - - editorRef.current?.setEditorValueAtCursorPosition(response); - }; - - const handleAutoGenerateDescription = async () => { - if (!workspaceSlug || !projectId) return; - - setIAmFeelingLucky(true); - - aiService - .createGptTask(workspaceSlug.toString(), { - prompt: issueName, - task: "Generate a proper description for this issue.", - }) - .then((res) => { - if (res.response === "") - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: - "Issue title isn't informative enough to generate the description. Please try with a different title.", - }); - else handleAiAssistance(res.response_html); - }) - .catch((err) => { - const error = err?.data?.error; - - if (err.status === 429) - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: error || "You have reached the maximum number of requests of 50 requests per month per user.", - }); - else - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: error || "Some error occurred. Please try again.", - }); - }) - .finally(() => setIAmFeelingLucky(false)); - }; - - return ( -
    - {descriptionHtmlData === undefined || !projectId ? ( - - -
    - - -
    -
    - - -
    - -
    - -
    -
    - - -
    -
    - ) : ( - <> - ( - { - onChange(description_html); - handleFormChange(); - }} - onEnterKeyPress={() => submitBtnRef?.current?.click()} - ref={editorRef} - tabIndex={getTabIndex("description_html")} - placeholder={getDescriptionPlaceholder} - containerClassName="pt-3 min-h-[120px]" - /> - )} - /> -
    - {issueName && issueName.trim() !== "" && config?.has_openai_configured && ( - - )} - {config?.has_openai_configured && projectId && ( - { - setGptAssistantModal((prevData) => !prevData); - // this is done so that the title do not reset after gpt popover closed - handleGptAssistantClose(); - }} - onResponse={(response) => { - handleAiAssistance(response); - }} - placement="top-end" - button={ - - } - /> - )} -
    - - )} -
    - ); -}); diff --git a/web/core/components/issues/issue-modal/components/index.ts b/web/core/components/issues/issue-modal/components/index.ts deleted file mode 100644 index 3e0c1d4d9a..0000000000 --- a/web/core/components/issues/issue-modal/components/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./project-select"; -export * from "./parent-tag"; -export * from "./title-input"; -export * from "./description-editor"; -export * from "./default-properties"; diff --git a/web/core/components/issues/issue-modal/components/parent-tag.tsx b/web/core/components/issues/issue-modal/components/parent-tag.tsx deleted file mode 100644 index 74772b08c7..0000000000 --- a/web/core/components/issues/issue-modal/components/parent-tag.tsx +++ /dev/null @@ -1,66 +0,0 @@ -"use client"; - -import React from "react"; -import { observer } from "mobx-react"; -import { Control, Controller } from "react-hook-form"; -import { X } from "lucide-react"; -// types -import { ISearchIssueResponse, TIssue } from "@plane/types"; -// helpers -import { getTabIndex } from "@/helpers/issue-modal.helper"; -// plane web components -import { IssueIdentifier } from "@/plane-web/components/issues"; - -type TIssueParentTagProps = { - control: Control; - selectedParentIssue: ISearchIssueResponse; - handleFormChange: () => void; - setSelectedParentIssue: (issue: ISearchIssueResponse | null) => void; -}; - -export const IssueParentTag: React.FC = observer((props) => { - const { control, selectedParentIssue, handleFormChange, setSelectedParentIssue } = props; - - return ( - ( -
    -
    - - - {selectedParentIssue?.project_id && ( - - )} - - {selectedParentIssue.name.substring(0, 50)} - -
    -
    - )} - /> - ); -}); diff --git a/web/core/components/issues/issue-modal/components/project-select.tsx b/web/core/components/issues/issue-modal/components/project-select.tsx deleted file mode 100644 index ada1e012b8..0000000000 --- a/web/core/components/issues/issue-modal/components/project-select.tsx +++ /dev/null @@ -1,55 +0,0 @@ -"use client"; - -import React from "react"; -import { observer } from "mobx-react"; -import { Control, Controller } from "react-hook-form"; -// types -import { TIssue } from "@plane/types"; -// components -import { ProjectDropdown } from "@/components/dropdowns"; -// helpers -import { getTabIndex } from "@/helpers/issue-modal.helper"; -import { shouldRenderProject } from "@/helpers/project.helper"; -// store hooks -import { useUser } from "@/hooks/store"; - -type TIssueProjectSelectProps = { - control: Control; - disabled?: boolean; - handleFormChange: () => void; -}; - -export const IssueProjectSelect: React.FC = observer((props) => { - const { control, disabled = false, handleFormChange } = props; - // store hooks - const { projectsWithCreatePermissions } = useUser(); - - return ( - - projectsWithCreatePermissions && projectsWithCreatePermissions[value!] ? ( -
    - { - onChange(projectId); - handleFormChange(); - }} - buttonVariant="border-with-text" - renderCondition={(project) => shouldRenderProject(project)} - tabIndex={getTabIndex("project_id")} - disabled={disabled} - /> -
    - ) : ( - <> - ) - } - /> - ); -}); diff --git a/web/core/components/issues/issue-modal/components/title-input.tsx b/web/core/components/issues/issue-modal/components/title-input.tsx deleted file mode 100644 index 99f6cbf4aa..0000000000 --- a/web/core/components/issues/issue-modal/components/title-input.tsx +++ /dev/null @@ -1,57 +0,0 @@ -"use client"; - -import React from "react"; -import { observer } from "mobx-react"; -import { Control, Controller, FieldErrors } from "react-hook-form"; -// types -import { TIssue } from "@plane/types"; -// ui -import { Input } from "@plane/ui"; -// helpers -import { getTabIndex } from "@/helpers/issue-modal.helper"; - -type TIssueTitleInputProps = { - control: Control; - issueTitleRef: React.MutableRefObject; - errors: FieldErrors; - handleFormChange: () => void; -}; - -export const IssueTitleInput: React.FC = observer((props) => { - const { control, issueTitleRef, errors, handleFormChange } = props; - - return ( - <> - ( - { - onChange(e.target.value); - handleFormChange(); - }} - ref={issueTitleRef || ref} - hasError={Boolean(errors.name)} - placeholder="Title" - className="w-full text-base" - tabIndex={getTabIndex("name")} - autoFocus - /> - )} - /> - {errors?.name?.message} - - ); -}); diff --git a/web/core/components/issues/issue-modal/context/index.ts b/web/core/components/issues/issue-modal/context/index.ts deleted file mode 100644 index 61ad8c43aa..0000000000 --- a/web/core/components/issues/issue-modal/context/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./issue-modal"; diff --git a/web/core/components/issues/issue-modal/context/issue-modal.tsx b/web/core/components/issues/issue-modal/context/issue-modal.tsx deleted file mode 100644 index 845aec5525..0000000000 --- a/web/core/components/issues/issue-modal/context/issue-modal.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { createContext } from "react"; -import { UseFormWatch } from "react-hook-form"; -// types -import { TIssue } from "@plane/types"; -// plane web types -import { TIssuePropertyValueErrors, TIssuePropertyValues } from "@/plane-web/types"; - -export type TPropertyValuesValidationProps = { - projectId: string | null; - workspaceSlug: string; - watch: UseFormWatch; -}; - -export type TActiveAdditionalPropertiesProps = { - projectId: string | null; - workspaceSlug: string; - watch: UseFormWatch; -}; - -export type TCreateUpdatePropertyValuesProps = { - issueId: string; - projectId: string; - workspaceSlug: string; -}; - -export type TIssueModalContext = { - issuePropertyValues: TIssuePropertyValues; - setIssuePropertyValues: React.Dispatch>; - issuePropertyValueErrors: TIssuePropertyValueErrors; - setIssuePropertyValueErrors: React.Dispatch>; - getIssueTypeIdOnProjectChange: (projectId: string) => string | null; - getActiveAdditionalPropertiesLength: (props: TActiveAdditionalPropertiesProps) => number; - handlePropertyValuesValidation: (props: TPropertyValuesValidationProps) => boolean; - handleCreateUpdatePropertyValues: (props: TCreateUpdatePropertyValuesProps) => Promise; -}; - -export const IssueModalContext = createContext(undefined); diff --git a/web/core/components/issues/issue-modal/draft-issue-layout.tsx b/web/core/components/issues/issue-modal/draft-issue-layout.tsx index da8622dc8d..c22d2a19ff 100644 --- a/web/core/components/issues/issue-modal/draft-issue-layout.tsx +++ b/web/core/components/issues/issue-modal/draft-issue-layout.tsx @@ -4,21 +4,15 @@ import React, { useState } from "react"; import isEmpty from "lodash/isEmpty"; import { observer } from "mobx-react"; import { useParams, usePathname } from "next/navigation"; -// types import type { TIssue } from "@plane/types"; -// ui -import { TOAST_TYPE, setToast } from "@plane/ui"; -// components -import { ConfirmIssueDiscard } from "@/components/issues"; -// helpers -import { isEmptyHtmlString } from "@/helpers/string.helper"; // hooks -import { useIssueModal } from "@/hooks/context/use-issue-modal"; +import { TOAST_TYPE, setToast } from "@plane/ui"; +import { ConfirmIssueDiscard } from "@/components/issues"; +import { IssueFormRoot } from "@/components/issues/issue-modal/form"; +import { isEmptyHtmlString } from "@/helpers/string.helper"; import { useEventTracker } from "@/hooks/store"; // services import { IssueDraftService } from "@/services/issue"; -// local components -import { IssueFormRoot } from "./form"; export interface DraftIssueProps { changesMade: Partial | null; @@ -28,7 +22,7 @@ export interface DraftIssueProps { onCreateMoreToggleChange: (value: boolean) => void; onChange: (formData: Partial | null) => void; onClose: (saveDraftIssueInLocalStorage?: boolean) => void; - onSubmit: (formData: Partial, is_draft_issue?: boolean) => Promise; + onSubmit: (formData: Partial) => Promise; projectId: string; isDraft: boolean; } @@ -56,7 +50,6 @@ export const DraftIssueLayout: React.FC = observer((props) => { const pathname = usePathname(); // store hooks const { captureIssueEvent } = useEventTracker(); - const { handleCreateUpdatePropertyValues } = useIssueModal(); const handleClose = () => { if (data?.id) { @@ -97,7 +90,7 @@ export const DraftIssueLayout: React.FC = observer((props) => { name: changesMade?.name && changesMade?.name?.trim() !== "" ? changesMade.name?.trim() : "Untitled", }; - const response = await issueDraftService + await issueDraftService .createDraftIssue(workspaceSlug.toString(), projectId.toString(), payload) .then((res) => { setToast({ @@ -113,7 +106,6 @@ export const DraftIssueLayout: React.FC = observer((props) => { onChange(null); setIssueDiscardModal(false); onClose(false); - return res; }) .catch(() => { setToast({ @@ -127,14 +119,6 @@ export const DraftIssueLayout: React.FC = observer((props) => { path: pathname, }); }); - - if (response && handleCreateUpdatePropertyValues) { - handleCreateUpdatePropertyValues({ - issueId: response.id, - projectId, - workspaceSlug: workspaceSlug?.toString(), - }); - } }; return ( diff --git a/web/core/components/issues/issue-modal/form.tsx b/web/core/components/issues/issue-modal/form.tsx index e6a3f22194..524d2eff62 100644 --- a/web/core/components/issues/issue-modal/form.tsx +++ b/web/core/components/issues/issue-modal/form.tsx @@ -3,36 +3,43 @@ import React, { FC, useState, useRef, useEffect } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; -import { useForm } from "react-hook-form"; +import { Controller, useForm } from "react-hook-form"; +import { LayoutPanelTop, Sparkle, X } from "lucide-react"; // editor import { EditorRefApi } from "@plane/editor"; // types import type { TIssue, ISearchIssueResponse } from "@plane/types"; // hooks -import { Button, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui"; +import { Button, CustomMenu, Input, Loader, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui"; // components +import { GptAssistantPopover } from "@/components/core"; import { - IssueDefaultProperties, - IssueDescriptionEditor, - IssueParentTag, - IssueProjectSelect, - IssueTitleInput, -} from "@/components/issues/issue-modal/components"; + CycleDropdown, + DateDropdown, + EstimateDropdown, + ModuleDropdown, + PriorityDropdown, + ProjectDropdown, + MemberDropdown, + StateDropdown, +} from "@/components/dropdowns"; +import { RichTextEditor } from "@/components/editor/rich-text-editor/rich-text-editor"; +import { ParentIssuesListModal } from "@/components/issues"; +import { IssueLabelSelect } from "@/components/issues/select"; import { CreateLabelModal } from "@/components/labels"; // helpers -import { cn } from "@/helpers/common.helper"; -import { getTabIndex } from "@/helpers/issue-modal.helper"; -import { getChangedIssuefields } from "@/helpers/issue.helper"; +import { renderFormattedPayloadDate, getDate } from "@/helpers/date-time.helper"; +import { getChangedIssuefields, getDescriptionPlaceholder } from "@/helpers/issue.helper"; +import { shouldRenderProject } from "@/helpers/project.helper"; // hooks -import { useIssueModal } from "@/hooks/context/use-issue-modal"; -import { useIssueDetail, useProject } from "@/hooks/store"; +import { useProjectEstimates, useInstance, useIssueDetail, useProject, useWorkspace, useUser } from "@/hooks/store"; +import useKeypress from "@/hooks/use-keypress"; import { useProjectIssueProperties } from "@/hooks/use-project-issue-properties"; -// plane web components -import { IssueAdditionalProperties, IssueTypeSelect } from "@/plane-web/components/issues/issue-modal"; +// services +import { AIService } from "@/services/ai.service"; const defaultValues: Partial = { project_id: "", - type_id: null, name: "", description_html: "", estimate_point: null, @@ -59,6 +66,34 @@ export interface IssueFormProps { isDraft: boolean; } +// services +const aiService = new AIService(); + +const TAB_INDICES = [ + "name", + "description_html", + "feeling_lucky", + "ai_assistant", + "state_id", + "priority", + "assignee_ids", + "label_ids", + "start_date", + "target_date", + "cycle_id", + "module_ids", + "estimate_point", + "parent_id", + "create_more", + "discard_button", + "draft_button", + "submit_button", + "project_id", + "remove_parent", +]; + +const getTabIndex = (key: string) => TAB_INDICES.findIndex((tabIndex) => tabIndex === key) + 1; + export const IssueFormRoot: FC = observer((props) => { const { data, @@ -73,17 +108,38 @@ export const IssueFormRoot: FC = observer((props) => { } = props; // states const [labelModal, setLabelModal] = useState(false); + const [parentIssueListModalOpen, setParentIssueListModalOpen] = useState(false); const [selectedParentIssue, setSelectedParentIssue] = useState(null); const [gptAssistantModal, setGptAssistantModal] = useState(false); + const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false); // refs const editorRef = useRef(null); const submitBtnRef = useRef(null); // router const { workspaceSlug, projectId: routeProjectId } = useParams(); // store hooks + const workspaceStore = useWorkspace(); + const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug?.toString())?.id as string; + const { config } = useInstance(); + const { projectsWithCreatePermissions } = useUser(); + const { getProjectById } = useProject(); - const { getIssueTypeIdOnProjectChange, getActiveAdditionalPropertiesLength, handlePropertyValuesValidation } = - useIssueModal(); + const { areEstimateEnabledByProjectId } = useProjectEstimates(); + + const handleKeyDown = (event: KeyboardEvent) => { + if (editorRef.current?.isEditorReadyToDiscard()) { + onClose(); + } else { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Editor is still processing changes. Please wait before proceeding.", + }); + event.preventDefault(); // Prevent default action if editor is not ready to discard + } + }; + + useKeypress("Escape", handleKeyDown); const { issue: { getIssueById }, @@ -104,11 +160,6 @@ export const IssueFormRoot: FC = observer((props) => { }); const projectId = watch("project_id"); - const activeAdditionalPropertiesLength = getActiveAdditionalPropertiesLength({ - projectId: projectId, - workspaceSlug: workspaceSlug?.toString(), - watch: watch, - }); //reset few fields on projectId change useEffect(() => { @@ -127,29 +178,14 @@ export const IssueFormRoot: FC = observer((props) => { }); } if (projectId && routeProjectId !== projectId) fetchCycles(workspaceSlug?.toString(), projectId); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [projectId]); - // Update the issue type id when the project id changes useEffect(() => { - const issueTypeId = watch("type_id"); + if (data?.description_html) setValue<"description_html">("description_html", data?.description_html); + }, [data?.description_html]); - // if data is present, set active type id to the type id of the issue - if (data && data.type_id) { - setValue("type_id", data.type_id, { shouldValidate: true }); - return; - } - - // if issue type id is present or project not available, return - if (issueTypeId || !projectId) return; - - // get issue type id on project change - const issueTypeIdOnProjectChange = getIssueTypeIdOnProjectChange(projectId); - if (issueTypeIdOnProjectChange) setValue("type_id", issueTypeIdOnProjectChange, { shouldValidate: true }); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [data, projectId]); + const issueName = watch("name"); const handleFormSubmit = async (formData: Partial, is_draft_issue = false) => { // Check if the editor is ready to discard @@ -162,16 +198,6 @@ export const IssueFormRoot: FC = observer((props) => { return; } - // check for required properties validation - if ( - !handlePropertyValuesValidation({ - projectId: projectId, - workspaceSlug: workspaceSlug?.toString(), - watch: watch, - }) - ) - return; - const submitData = !data?.id ? formData : { @@ -192,12 +218,56 @@ export const IssueFormRoot: FC = observer((props) => { ...defaultValues, ...(isCreateMoreToggleEnabled ? { ...data } : {}), project_id: getValues<"project_id">("project_id"), - type_id: getValues<"type_id">("type_id"), description_html: data?.description_html ?? "

    ", }); editorRef?.current?.clearEditor(); }; + const handleAiAssistance = async (response: string) => { + if (!workspaceSlug || !projectId) return; + + editorRef.current?.setEditorValueAtCursorPosition(response); + }; + + const handleAutoGenerateDescription = async () => { + if (!workspaceSlug || !projectId) return; + + setIAmFeelingLucky(true); + + aiService + .createGptTask(workspaceSlug.toString(), { + prompt: issueName, + task: "Generate a proper description for this issue.", + }) + .then((res) => { + if (res.response === "") + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: + "Issue title isn't informative enough to generate the description. Please try with a different title.", + }); + else handleAiAssistance(res.response_html); + }) + .catch((err) => { + const error = err?.data?.error; + + if (err.status === 429) + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: error || "You have reached the maximum number of requests of 50 requests per month per user.", + }); + else + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: error || "Some error occurred. Please try again.", + }); + }) + .finally(() => setIAmFeelingLucky(false)); + }; + const condition = (watch("name") && watch("name") !== "") || (watch("description_html") && watch("description_html") !== "

    "); @@ -208,6 +278,17 @@ export const IssueFormRoot: FC = observer((props) => { else onChange(null); }; + const startDate = watch("start_date"); + const targetDate = watch("target_date"); + + const minDate = getDate(startDate); + minDate?.setDate(minDate.getDate()); + + const maxDate = getDate(targetDate); + maxDate?.setDate(maxDate.getDate()); + + const projectDetails = getProjectById(projectId); + // executing this useEffect when the parent_id coming from the component prop useEffect(() => { const parentId = watch("parent_id") || undefined; @@ -236,7 +317,6 @@ export const IssueFormRoot: FC = observer((props) => { if (isDirty && condition) onChange(watch()); else onChange(null); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [isDirty]); return ( @@ -253,104 +333,448 @@ export const IssueFormRoot: FC = observer((props) => { /> )}
    handleFormSubmit(data))}> -
    -

    {data?.id ? "Update" : "Create new"} issue

    - {/* Disable project selection if editing an issue */} -
    - - {projectId && ( - +
    + {/* Don't show project selection if editing an issue */} + {!data?.id && ( + + projectsWithCreatePermissions && projectsWithCreatePermissions[value!] ? ( +
    + { + onChange(projectId); + handleFormChange(); + }} + buttonVariant="border-with-text" + renderCondition={(project) => shouldRenderProject(project)} + tabIndex={getTabIndex("project_id")} + /> +
    + ) : ( + <> + ) + } /> )} +

    {data?.id ? "Update" : "Create"} issue

    {watch("parent_id") && selectedParentIssue && ( -
    - ( +
    +
    + + + {selectedParentIssue.project__identifier}-{selectedParentIssue.sequence_id} + + {selectedParentIssue.name.substring(0, 50)} + +
    +
    + )} + /> + )} +
    +
    + ( + { + onChange(e.target.value); + handleFormChange(); + }} + ref={issueTitleRef || ref} + hasError={Boolean(errors.name)} + placeholder="Title" + className="w-full text-base" + tabIndex={getTabIndex("name")} + autoFocus + /> + )} + /> + {errors?.name?.message} +
    +
    + {data?.description_html === undefined || !projectId ? ( + + +
    + + +
    +
    + + +
    + +
    + +
    +
    + + +
    +
    + ) : ( + <> + ( + { + onChange(description_html); + handleFormChange(); + }} + onEnterKeyPress={() => submitBtnRef?.current?.click()} + ref={editorRef} + tabIndex={getTabIndex("description_html")} + placeholder={getDescriptionPlaceholder} + containerClassName="pt-3 min-h-[150px]" + /> + )} + /> +
    + {issueName && issueName.trim() !== "" && config?.has_openai_configured && ( + + )} + {config?.has_openai_configured && projectId && ( + { + setGptAssistantModal((prevData) => !prevData); + // this is done so that the title do not reset after gpt popover closed + reset(getValues()); + }} + onResponse={(response) => { + handleAiAssistance(response); + }} + placement="top-end" + button={ + + } + /> + )} +
    + + )} +
    +
    + ( +
    + { + onChange(stateId); + handleFormChange(); + }} + projectId={projectId ?? undefined} + buttonVariant="border-with-text" + tabIndex={getTabIndex("state_id")} + /> +
    + )} + /> + ( +
    + { + onChange(priority); + handleFormChange(); + }} + buttonVariant="border-with-text" + tabIndex={getTabIndex("priority")} + /> +
    + )} + /> + ( +
    + { + onChange(assigneeIds); + handleFormChange(); + }} + buttonVariant={value?.length > 0 ? "transparent-without-text" : "border-with-text"} + buttonClassName={value?.length > 0 ? "hover:bg-transparent" : ""} + placeholder="Assignees" + multiple + tabIndex={getTabIndex("assignee_ids")} + /> +
    + )} + /> + ( +
    + { + onChange(labelIds); + handleFormChange(); + }} + projectId={projectId ?? undefined} + tabIndex={getTabIndex("label_ids")} + /> +
    + )} + /> + ( +
    + { + onChange(date ? renderFormattedPayloadDate(date) : null); + handleFormChange(); + }} + buttonVariant="border-with-text" + maxDate={maxDate ?? undefined} + placeholder="Start date" + tabIndex={getTabIndex("start_date")} + /> +
    + )} + /> + ( +
    + { + onChange(date ? renderFormattedPayloadDate(date) : null); + handleFormChange(); + }} + buttonVariant="border-with-text" + minDate={minDate ?? undefined} + placeholder="Due date" + tabIndex={getTabIndex("target_date")} + /> +
    + )} + /> + {projectDetails?.cycle_view && ( + ( +
    + { + onChange(cycleId); + handleFormChange(); + }} + placeholder="Cycle" + value={value} + buttonVariant="border-with-text" + tabIndex={getTabIndex("cycle_id")} + /> +
    + )} + /> + )} + {projectDetails?.module_view && workspaceSlug && ( + ( +
    + { + onChange(moduleIds); + handleFormChange(); + }} + placeholder="Modules" + buttonVariant="border-with-text" + tabIndex={getTabIndex("module_ids")} + multiple + showCount + /> +
    + )} + /> + )} + {projectId && areEstimateEnabledByProjectId(projectId) && ( + ( +
    + { + onChange(estimatePoint); + handleFormChange(); + }} + projectId={projectId} + buttonVariant="border-with-text" + tabIndex={getTabIndex("estimate_point")} + placeholder="Estimate" + /> +
    + )} + /> + )} + {watch("parent_id") ? ( + + + + {selectedParentIssue && + `${selectedParentIssue.project__identifier}-${selectedParentIssue.sequence_id}`} + + + } + placement="bottom-start" + tabIndex={getTabIndex("parent_id")} + > + <> + setParentIssueListModalOpen(true)}> + Change parent issue + + ( + { + onChange(null); + handleFormChange(); + }} + > + Remove parent issue + + )} + /> + + + ) : ( + + )} + ( + setParentIssueListModalOpen(false)} + onChange={(issue) => { + onChange(issue.id); + handleFormChange(); + setSelectedParentIssue(issue); + }} + projectId={projectId ?? undefined} + issueId={isDraft ? undefined : data?.id} + /> + )} />
    - )} -
    -
    -
    4 && - "max-h-[45vh] overflow-hidden overflow-y-auto vertical-scrollbar scrollbar-sm" - )} - > -
    - - setValue<"description_html">("description_html", description_html) - } - setGptAssistantModal={setGptAssistantModal} - handleGptAssistantClose={() => reset(getValues())} - onClose={onClose} - /> -
    -
    - {projectId && ( - - )} -
    -
    -
    -
    - -
    -
    +
    +
    {!data?.id && (
    = observer((props) => { Create more
    )} -
    - - {isDraft && ( - <> - {data?.id ? ( - - ) : ( - - )} - - )} - -
    +
    +
    + + {isDraft && ( + <> + {data?.id ? ( + + ) : ( + + )} + + )} +
    diff --git a/web/core/components/issues/issue-modal/index.ts b/web/core/components/issues/issue-modal/index.ts index 4899229491..feac885d4c 100644 --- a/web/core/components/issues/issue-modal/index.ts +++ b/web/core/components/issues/issue-modal/index.ts @@ -1,5 +1,3 @@ -export * from "./form"; -export * from "./base"; export * from "./draft-issue-layout"; +export * from "./form"; export * from "./modal"; -export * from "./context"; diff --git a/web/core/components/issues/issue-modal/modal.tsx b/web/core/components/issues/issue-modal/modal.tsx index 0f2d3ce000..1520ec767b 100644 --- a/web/core/components/issues/issue-modal/modal.tsx +++ b/web/core/components/issues/issue-modal/modal.tsx @@ -1,15 +1,24 @@ "use client"; -import React from "react"; +import React, { useEffect, useRef, useState } from "react"; import { observer } from "mobx-react"; +import { useParams, usePathname } from "next/navigation"; // types import type { TIssue } from "@plane/types"; -// components -import { CreateUpdateIssueModalBase } from "@/components/issues"; +// ui +import { EModalPosition, EModalWidth, ModalCore, TOAST_TYPE, setToast } from "@plane/ui"; +import { CreateIssueToastActionItems } from "@/components/issues"; // constants +import { ISSUE_CREATED, ISSUE_UPDATED } from "@/constants/event-tracker"; import { EIssuesStoreType } from "@/constants/issue"; -// plane web providers -import { IssueModalProvider } from "@/plane-web/components/issues"; +// hooks +import { useEventTracker, useCycle, useIssues, useModule, useProject, useIssueDetail } from "@/hooks/store"; +import { useIssueStoreType } from "@/hooks/use-issue-layout-store"; +import { useIssuesActions } from "@/hooks/use-issues-actions"; +import useLocalStorage from "@/hooks/use-local-storage"; +// components +import { DraftIssueLayout } from "./draft-issue-layout"; +import { IssueFormRoot } from "./form"; export interface IssuesModalProps { data?: Partial; @@ -21,8 +30,292 @@ export interface IssuesModalProps { isDraft?: boolean; } -export const CreateUpdateIssueModal: React.FC = observer((props) => ( - - - -)); +export const CreateUpdateIssueModal: React.FC = observer((props) => { + const { + data, + isOpen, + onClose, + onSubmit, + withDraftIssueWrapper = true, + storeType: issueStoreFromProps, + isDraft = false, + } = props; + const issueStoreType = useIssueStoreType(); + + const storeType = issueStoreFromProps ?? issueStoreType; + // ref + const issueTitleRef = useRef(null); + // states + const [changesMade, setChangesMade] = useState | null>(null); + const [createMore, setCreateMore] = useState(false); + const [activeProjectId, setActiveProjectId] = useState(null); + const [description, setDescription] = useState(undefined); + // store hooks + const { captureIssueEvent } = useEventTracker(); + const { workspaceSlug, projectId, cycleId, moduleId } = useParams(); + const { workspaceProjectIds } = useProject(); + const { fetchCycleDetails } = useCycle(); + const { fetchModuleDetails } = useModule(); + const { issues } = useIssues(storeType); + const { issues: projectIssues } = useIssues(EIssuesStoreType.PROJECT); + const { issues: draftIssues } = useIssues(EIssuesStoreType.DRAFT); + const { fetchIssue } = useIssueDetail(); + // pathname + const pathname = usePathname(); + // local storage + const { storedValue: localStorageDraftIssues, setValue: setLocalStorageDraftIssue } = useLocalStorage< + Record> + >("draftedIssue", {}); + // current store details + const { createIssue, updateIssue } = useIssuesActions(storeType); + + const fetchIssueDetail = async (issueId: string | undefined) => { + setDescription(undefined); + if (!workspaceSlug) return; + + if (!projectId || issueId === undefined) { + setDescription(data?.description_html || "

    "); + return; + } + const response = await fetchIssue( + workspaceSlug.toString(), + projectId.toString(), + issueId, + isDraft ? "DRAFT" : "DEFAULT" + ); + if (response) setDescription(response?.description_html || "

    "); + }; + + useEffect(() => { + // fetching issue details + if (isOpen) fetchIssueDetail(data?.id); + + // if modal is closed, reset active project to null + // and return to avoid activeProjectId being set to some other project + if (!isOpen) { + setActiveProjectId(null); + return; + } + + // if data is present, set active project to the project of the + // issue. This has more priority than the project in the url. + if (data && data.project_id) { + setActiveProjectId(data.project_id); + return; + } + + // if data is not present, set active project to the project + // in the url. This has the least priority. + if (workspaceProjectIds && workspaceProjectIds.length > 0 && !activeProjectId) + setActiveProjectId(projectId?.toString() ?? workspaceProjectIds?.[0]); + + // clearing up the description state when we leave the component + return () => setDescription(undefined); + }, [data, projectId, isOpen, activeProjectId]); + + const addIssueToCycle = async (issue: TIssue, cycleId: string) => { + if (!workspaceSlug || !issue.project_id) return; + + await issues.addIssueToCycle(workspaceSlug.toString(), issue.project_id, cycleId, [issue.id]); + fetchCycleDetails(workspaceSlug.toString(), issue.project_id, cycleId); + }; + + const addIssueToModule = async (issue: TIssue, moduleIds: string[]) => { + if (!workspaceSlug || !activeProjectId) return; + + await issues.changeModulesInIssue(workspaceSlug.toString(), activeProjectId, issue.id, moduleIds, []); + moduleIds.forEach((moduleId) => fetchModuleDetails(workspaceSlug.toString(), activeProjectId, moduleId)); + }; + + const handleCreateMoreToggleChange = (value: boolean) => { + setCreateMore(value); + }; + + const handleClose = (saveDraftIssueInLocalStorage?: boolean) => { + if (changesMade && saveDraftIssueInLocalStorage) { + // updating the current edited issue data in the local storage + let draftIssues = localStorageDraftIssues ? localStorageDraftIssues : {}; + if (workspaceSlug) { + draftIssues = { ...draftIssues, [workspaceSlug.toString()]: changesMade }; + setLocalStorageDraftIssue(draftIssues); + } + } + + setActiveProjectId(null); + setChangesMade(null); + onClose(); + }; + + const handleCreateIssue = async ( + payload: Partial, + is_draft_issue: boolean = false + ): Promise => { + if (!workspaceSlug || !payload.project_id) return; + + try { + let response; + + // if draft issue, use draft issue store to create issue + if (is_draft_issue) { + response = await draftIssues.createIssue(workspaceSlug.toString(), payload.project_id, payload); + } + // if cycle id in payload does not match the cycleId in url + // or if the moduleIds in Payload does not match the moduleId in url + // use the project issue store to create issues + else if ( + (payload.cycle_id !== cycleId && storeType === EIssuesStoreType.CYCLE) || + (!payload.module_ids?.includes(moduleId?.toString()) && storeType === EIssuesStoreType.MODULE) + ) { + response = await projectIssues.createIssue(workspaceSlug.toString(), payload.project_id, payload); + } // else just use the existing store type's create method + else if (createIssue) { + response = await createIssue(payload.project_id, payload); + } + + if (!response) throw new Error(); + + // check if we should add issue to cycle/module + if ( + payload.cycle_id && + payload.cycle_id !== "" && + (payload.cycle_id !== cycleId || storeType !== EIssuesStoreType.CYCLE) + ) { + await addIssueToCycle(response, payload.cycle_id); + } + if ( + payload.module_ids && + payload.module_ids.length > 0 && + (!payload.module_ids.includes(moduleId?.toString()) || storeType !== EIssuesStoreType.MODULE) + ) { + await addIssueToModule(response, payload.module_ids); + } + + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Success!", + message: `${is_draft_issue ? "Draft issue" : "Issue"} created successfully.`, + actionItems: !is_draft_issue && response?.project_id && ( + + ), + }); + captureIssueEvent({ + eventName: ISSUE_CREATED, + payload: { ...response, state: "SUCCESS" }, + path: pathname, + }); + !createMore && handleClose(); + if (createMore) issueTitleRef && issueTitleRef?.current?.focus(); + setDescription("

    "); + setChangesMade(null); + return response; + } catch (error) { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: `${is_draft_issue ? "Draft issue" : "Issue"} could not be created. Please try again.`, + }); + captureIssueEvent({ + eventName: ISSUE_CREATED, + payload: { ...payload, state: "FAILED" }, + path: pathname, + }); + } + }; + + const handleUpdateIssue = async (payload: Partial): Promise => { + if (!workspaceSlug || !payload.project_id || !data?.id) return; + + try { + isDraft + ? await draftIssues.updateIssue(workspaceSlug.toString(), payload.project_id, data.id, payload) + : updateIssue && (await updateIssue(payload.project_id, data.id, payload)); + + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Success!", + message: "Issue updated successfully.", + }); + captureIssueEvent({ + eventName: ISSUE_UPDATED, + payload: { ...payload, issueId: data.id, state: "SUCCESS" }, + path: pathname, + }); + handleClose(); + } catch (error) { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Issue could not be updated. Please try again.", + }); + captureIssueEvent({ + eventName: ISSUE_UPDATED, + payload: { ...payload, state: "FAILED" }, + path: pathname, + }); + } + }; + + const handleFormSubmit = async (payload: Partial, is_draft_issue: boolean = false) => { + if (!workspaceSlug || !payload.project_id || !storeType) return; + + let response: TIssue | undefined = undefined; + if (!data?.id) response = await handleCreateIssue(payload, is_draft_issue); + else response = await handleUpdateIssue(payload); + + if (response != undefined && onSubmit) await onSubmit(response); + }; + + const handleFormChange = (formData: Partial | null) => setChangesMade(formData); + + // don't open the modal if there are no projects + if (!workspaceProjectIds || workspaceProjectIds.length === 0 || !activeProjectId) return null; + + return ( + handleClose(true)} + position={EModalPosition.TOP} + width={EModalWidth.XXXXL} + > + {withDraftIssueWrapper ? ( + + ) : ( + handleClose(false)} + isCreateMoreToggleEnabled={createMore} + onCreateMoreToggleChange={handleCreateMoreToggleChange} + onSubmit={handleFormSubmit} + projectId={activeProjectId} + isDraft={isDraft} + /> + )} + + ); +}); diff --git a/web/core/components/issues/issue-update-status.tsx b/web/core/components/issues/issue-update-status.tsx index 6eb064b529..018e5754aa 100644 --- a/web/core/components/issues/issue-update-status.tsx +++ b/web/core/components/issues/issue-update-status.tsx @@ -1,16 +1,27 @@ import React from "react"; import { observer } from "mobx-react"; import { RefreshCw } from "lucide-react"; +import { TIssue } from "@plane/types"; +// types +import { useProject } from "@/hooks/store"; type Props = { isSubmitting: "submitting" | "submitted" | "saved"; + issueDetail?: TIssue; }; export const IssueUpdateStatus: React.FC = observer((props) => { - const { isSubmitting } = props; + const { isSubmitting, issueDetail } = props; + // hooks + const { getProjectById } = useProject(); return ( <> + {issueDetail && ( +

    + {getProjectById(issueDetail.project_id)?.identifier}-{issueDetail.sequence_id} +

    + )}
    = ({ key={issue.id} value={issue} className={({ active, selected }) => - `group flex w-full cursor-pointer select-none items-center justify-between gap-2 rounded-md px-3 py-2 my-0.5 text-custom-text-200 ${ + `group flex w-full cursor-pointer select-none items-center justify-between gap-2 rounded-md px-3 py-2 text-custom-text-200 ${ active ? "bg-custom-background-80 text-custom-text-100" : "" } ${selected ? "text-custom-text-100" : ""}` } @@ -196,14 +194,8 @@ export const ParentIssuesListModal: React.FC = ({ backgroundColor: issue.state__color, }} /> - - + + {issue.project__identifier}-{issue.sequence_id} {" "} {issue.name}
    diff --git a/web/core/components/issues/peek-overview/issue-detail.tsx b/web/core/components/issues/peek-overview/issue-detail.tsx index cfc5208a3f..53b9e6045c 100644 --- a/web/core/components/issues/peek-overview/issue-detail.tsx +++ b/web/core/components/issues/peek-overview/issue-detail.tsx @@ -2,11 +2,9 @@ import { FC, useEffect } from "react"; import { observer } from "mobx-react"; // store hooks import { TIssueOperations } from "@/components/issues"; -import { useIssueDetail, useUser } from "@/hooks/store"; +import { useIssueDetail, useProject, useUser } from "@/hooks/store"; // hooks import useReloadConfirmations from "@/hooks/use-reload-confirmation"; -// plane web components -import { IssueIdentifier } from "@/plane-web/components/issues"; // components import { IssueDescriptionInput } from "../description-input"; import { IssueReaction } from "../issue-detail/reactions"; @@ -26,6 +24,7 @@ interface IPeekOverviewIssueDetails { export const PeekOverviewIssueDetails: FC = observer((props) => { const { workspaceSlug, issueId, issueOperations, disabled, isArchived, isSubmitting, setIsSubmitting } = props; // store hooks + const { getProjectById } = useProject(); const { data: currentUser } = useUser(); const { issue: { getIssueById }, @@ -47,6 +46,8 @@ export const PeekOverviewIssueDetails: FC = observer( const issue = issueId ? getIssueById(issueId) : undefined; if (!issue || !issue.project_id) return <>; + const projectDetails = getProjectById(issue.project_id); + const issueDescription = issue.description_html !== undefined || issue.description_html !== null ? issue.description_html != "" @@ -56,7 +57,9 @@ export const PeekOverviewIssueDetails: FC = observer( return (
    - + + {projectDetails?.identifier}-{issue?.sequence_id} + = observer((pro issueId={issueId} disabled={disabled} /> - - {issue.type_id && ( - - )}
    ); diff --git a/web/core/components/issues/peek-overview/root.tsx b/web/core/components/issues/peek-overview/root.tsx index a55b0d5464..0f46f8f680 100644 --- a/web/core/components/issues/peek-overview/root.tsx +++ b/web/core/components/issues/peek-overview/root.tsx @@ -34,7 +34,6 @@ export const IssuePeekOverview: FC = observer((props) => { } = useIssues(EIssuesStoreType.ARCHIVED); const { peekIssue, - setPeekIssue, issue: { fetchIssue }, fetchActivities, } = useIssueDetail(); @@ -45,11 +44,6 @@ export const IssuePeekOverview: FC = observer((props) => { const [loader, setLoader] = useState(true); const [error, setError] = useState(false); - const removeRoutePeekId = () => { - setPeekIssue(undefined); - if (embedIssue) embedRemoveCurrentNotification && embedRemoveCurrentNotification(); - }; - const issueOperations: TIssueOperations = useMemo( () => ({ fetch: async (workspaceSlug: string, projectId: string, issueId: string, loader = true) => { @@ -101,13 +95,16 @@ export const IssuePeekOverview: FC = observer((props) => { }, remove: async (workspaceSlug: string, projectId: string, issueId: string) => { try { - return issues?.removeIssue(workspaceSlug, projectId, issueId).then(() => { - captureIssueEvent({ - eventName: ISSUE_DELETED, - payload: { id: issueId, state: "SUCCESS", element: "Issue peek-overview" }, - path: pathname, - }); - removeRoutePeekId(); + issues?.removeIssue(workspaceSlug, projectId, issueId); + setToast({ + title: "Success!", + type: TOAST_TYPE.SUCCESS, + message: "Issue deleted successfully", + }); + captureIssueEvent({ + eventName: ISSUE_DELETED, + payload: { id: issueId, state: "SUCCESS", element: "Issue peek-overview" }, + path: pathname, }); } catch (error) { setToast({ diff --git a/web/core/components/issues/peek-overview/view.tsx b/web/core/components/issues/peek-overview/view.tsx index f71b21b064..865e93fbf1 100644 --- a/web/core/components/issues/peek-overview/view.tsx +++ b/web/core/components/issues/peek-overview/view.tsx @@ -131,7 +131,7 @@ export const IssueView: FC = observer((props) => { toggleDeleteIssueModal(null); }} data={issue} - onSubmit={async () => issueOperations.remove(workspaceSlug, projectId, issueId)} + onSubmit={() => issueOperations.remove(workspaceSlug, projectId, issueId).then(() => removeRoutePeekId())} /> )} @@ -241,7 +241,7 @@ export const IssueView: FC = observer((props) => {
    diff --git a/web/core/components/issues/relations/issue-list-item.tsx b/web/core/components/issues/relations/issue-list-item.tsx index f63a1dae38..3f9386c653 100644 --- a/web/core/components/issues/relations/issue-list-item.tsx +++ b/web/core/components/issues/relations/issue-list-item.tsx @@ -10,8 +10,6 @@ import { RelationIssueProperty } from "@/components/issues/relations"; // hooks import { useIssueDetail, useProject, useProjectState } from "@/hooks/store"; import { usePlatformOS } from "@/hooks/use-platform-os"; -// plane web components -import { IssueIdentifier } from "@/plane-web/components/issues"; // types import { TRelationIssueOperations } from "../issue-detail-widgets/relations/helper"; @@ -93,23 +91,15 @@ export const RelationIssueListItem: FC = observer((props) => { {issue && (
    -
    +
    -
    - {projectDetail && ( - - )} +
    + {projectDetail?.identifier}-{issue?.sequence_id}
    = observer((props) => { )}
    -
    +
    -
    - {projectDetail && ( - - )} +
    + {projectDetail?.identifier}-{issue?.sequence_id}
    = observer((props) => { /255
    - {title?.length === 0 && Title is required} + {title?.length === 0 && Title is required}
    ); }); diff --git a/web/core/components/onboarding/create-or-join-workspaces.tsx b/web/core/components/onboarding/create-or-join-workspaces.tsx index bec44a85ea..5ee55116ad 100644 --- a/web/core/components/onboarding/create-or-join-workspaces.tsx +++ b/web/core/components/onboarding/create-or-join-workspaces.tsx @@ -10,8 +10,8 @@ import { Invitations, OnboardingHeader, SwitchAccountDropdown, CreateWorkspace } // hooks import { useUser } from "@/hooks/store"; // assets -import CreateJoinWorkspaceDark from "@/public/onboarding/create-join-workspace-dark.webp"; -import CreateJoinWorkspace from "@/public/onboarding/create-join-workspace-light.webp"; +import CreateJoinWorkspaceDark from "@/public/onboarding/create-join-workspace-dark.svg"; +import CreateJoinWorkspace from "@/public/onboarding/create-join-workspace.svg"; import { LogoSpinner } from "../common"; export enum ECreateOrJoinWorkspaceViews { diff --git a/web/core/components/onboarding/profile-setup.tsx b/web/core/components/onboarding/profile-setup.tsx index 648ec73635..134f6e7a69 100644 --- a/web/core/components/onboarding/profile-setup.tsx +++ b/web/core/components/onboarding/profile-setup.tsx @@ -22,8 +22,8 @@ import { E_PASSWORD_STRENGTH, getPasswordStrength } from "@/helpers/password.hel import { useEventTracker, useUser, useUserProfile } from "@/hooks/store"; // services // assets -import ProfileSetupDark from "@/public/onboarding/profile-setup-dark.webp"; -import ProfileSetupLight from "@/public/onboarding/profile-setup-light.webp"; +import ProfileSetupDark from "@/public/onboarding/profile-setup-dark.svg"; +import ProfileSetupLight from "@/public/onboarding/profile-setup-light.svg"; import UserPersonalizationDark from "@/public/onboarding/user-personalization-dark.svg"; import UserPersonalizationLight from "@/public/onboarding/user-personalization-light.svg"; import { AuthService } from "@/services/auth.service"; @@ -372,7 +372,6 @@ export const ProfileSetup: React.FC = observer((props) => { hasError={Boolean(errors.first_name)} placeholder="Wilbur" className="w-full border-onboarding-border-100" - autoComplete="on" /> )} /> @@ -406,7 +405,6 @@ export const ProfileSetup: React.FC = observer((props) => { hasError={Boolean(errors.last_name)} placeholder="Wright" className="w-full border-onboarding-border-100" - autoComplete="on" /> )} /> @@ -440,7 +438,6 @@ export const ProfileSetup: React.FC = observer((props) => { className="w-full border-[0.5px] border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400" onFocus={() => setIsPasswordInputFocused(true)} onBlur={() => setIsPasswordInputFocused(false)} - autoComplete="on" /> {showPassword.password ? ( = observer((props) => { project: { getProjectMemberIds }, } = useMember(); // derived values - const workspaceId = workspaceSlug ? (getWorkspaceBySlug(workspaceSlug.toString())?.id ?? "") : ""; + const workspaceId = workspaceSlug ? getWorkspaceBySlug(workspaceSlug.toString())?.id ?? "" : ""; const pageId = page?.id; const pageTitle = page?.name ?? ""; const pageDescription = page?.description_html; const { isContentEditable, updateTitle, setIsSubmitting } = page; const projectMemberIds = projectId ? getProjectMemberIds(projectId.toString()) : []; const projectMemberDetails = projectMemberIds?.map((id) => getUserDetails(id) as IUserLite); + // use-mention const { mentionHighlights, mentionSuggestions } = useMention({ workspaceSlug: workspaceSlug?.toString() ?? "", @@ -82,17 +80,14 @@ export const PageEditorBody: React.FC = observer((props) => { members: projectMemberDetails, user: currentUser ?? undefined, }); - // editor flaggings - const { documentEditor } = useEditorFlagging(workspaceSlug?.toString()); - // page filters - const { fontSize, fontStyle, isFullWidth } = usePageFilters(); - // issue-embed - const { issueEmbedProps } = useIssueEmbed(workspaceSlug?.toString() ?? "", projectId?.toString() ?? ""); - const displayConfig: TDisplayConfig = { - fontSize, - fontStyle, - }; + // page filters + const { isFullWidth } = usePageFilters(); + // issue-embed + const { issueEmbedProps, issueEmbedReadOnlyProps } = useIssueEmbed( + workspaceSlug?.toString() ?? "", + projectId?.toString() ?? "" + ); useEffect(() => { updateMarkings(pageDescription ?? "

    "); @@ -145,7 +140,6 @@ export const PageEditorBody: React.FC = observer((props) => { value={pageDescriptionYJS} ref={editorRef} containerClassName="p-0 pb-64" - displayConfig={displayConfig} editorClassName="pl-10" onChange={handleDescriptionChange} mentionHandler={{ @@ -155,10 +149,6 @@ export const PageEditorBody: React.FC = observer((props) => { embedHandler={{ issue: issueEmbedProps, }} - disabledExtensions={documentEditor} - aiHandler={{ - menu: ({ onClose }) => , - }} /> ) : ( = observer((props) => { initialValue={pageDescription ?? "

    "} handleEditorReady={handleReadOnlyEditorReady} containerClassName="p-0 pb-64 border-none" - displayConfig={displayConfig} editorClassName="pl-10" mentionHandler={{ highlights: mentionHighlights, }} embedHandler={{ - issue: { - widgetCallback: issueEmbedProps.widgetCallback, - }, + issue: issueEmbedReadOnlyProps, }} /> )} diff --git a/web/core/components/pages/editor/header/options-dropdown.tsx b/web/core/components/pages/editor/header/options-dropdown.tsx index 016ca8eb57..af2f2e6af6 100644 --- a/web/core/components/pages/editor/header/options-dropdown.tsx +++ b/web/core/components/pages/editor/header/options-dropdown.tsx @@ -40,7 +40,6 @@ export const PageOptionsDropdown: React.FC = observer((props) => { const { workspaceSlug, projectId } = useParams(); // page filters const { isFullWidth, handleFullWidth } = usePageFilters(); - const handleArchivePage = async () => await archive().catch(() => setToast({ diff --git a/web/core/components/pages/editor/title.tsx b/web/core/components/pages/editor/title.tsx index 56d2f18f5e..59502ba817 100644 --- a/web/core/components/pages/editor/title.tsx +++ b/web/core/components/pages/editor/title.tsx @@ -1,6 +1,6 @@ "use client"; -import { CSSProperties, useState } from "react"; +import { useState } from "react"; import { observer } from "mobx-react"; // editor import { EditorRefApi } from "@plane/editor"; @@ -8,8 +8,6 @@ import { EditorRefApi } from "@plane/editor"; import { TextArea } from "@plane/ui"; // helpers import { cn } from "@/helpers/common.helper"; -// hooks -import { usePageFilters } from "@/hooks/use-page-filters"; type Props = { editorRef: React.RefObject; @@ -22,28 +20,25 @@ export const PageEditorTitle: React.FC = observer((props) => { const { editorRef, readOnly, title, updateTitle } = props; // states const [isLengthVisible, setIsLengthVisible] = useState(false); - // page filters - const { fontSize, fontStyle } = usePageFilters(); - // ui - const titleClassName = cn("bg-transparent tracking-[-2%] font-semibold", { - "text-[1.6rem] leading-[1.8rem]": fontSize === "small-font", - "text-[2rem] leading-[2.25rem]": fontSize === "large-font", - }); - const titleStyle: CSSProperties = { - fontFamily: fontStyle, - }; return ( <> {readOnly ? ( -
    +
    {title}
    ) : ( <>