diff --git a/.github/ISSUE_TEMPLATE/--bug-report.yaml b/.github/ISSUE_TEMPLATE/--bug-report.yaml
index 5d19be11c..3adaa4230 100644
--- a/.github/ISSUE_TEMPLATE/--bug-report.yaml
+++ b/.github/ISSUE_TEMPLATE/--bug-report.yaml
@@ -2,7 +2,7 @@ name: Bug report
description: Create a bug report to help us improve Plane
title: "[bug]: "
labels: [🐛bug]
-assignees: [srinivaspendem, pushya-plane]
+assignees: [srinivaspendem, pushya22]
body:
- type: markdown
attributes:
@@ -45,7 +45,7 @@ body:
- Deploy preview
validations:
required: true
- type: dropdown
+- type: dropdown
id: browser
attributes:
label: Browser
diff --git a/.github/ISSUE_TEMPLATE/--feature-request.yaml b/.github/ISSUE_TEMPLATE/--feature-request.yaml
index 941fbef87..ff9cdd238 100644
--- a/.github/ISSUE_TEMPLATE/--feature-request.yaml
+++ b/.github/ISSUE_TEMPLATE/--feature-request.yaml
@@ -2,7 +2,7 @@ name: Feature request
description: Suggest a feature to improve Plane
title: "[feature]: "
labels: [✨feature]
-assignees: [srinivaspendem, pushya-plane]
+assignees: [srinivaspendem, pushya22]
body:
- type: markdown
attributes:
diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py
index 6f66c373e..84931f46b 100644
--- a/apiserver/plane/api/views/cycle.py
+++ b/apiserver/plane/api/views/cycle.py
@@ -45,7 +45,10 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
return (
Cycle.objects.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.select_related("project")
.select_related("workspace")
.select_related("owned_by")
@@ -390,7 +393,10 @@ class CycleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
)
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.filter(cycle_id=self.kwargs.get("cycle_id"))
.select_related("project")
.select_related("workspace")
diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py
index 50269fe07..0905ae1f7 100644
--- a/apiserver/plane/api/views/issue.py
+++ b/apiserver/plane/api/views/issue.py
@@ -352,7 +352,10 @@ class LabelAPIEndpoint(BaseAPIView):
return (
Label.objects.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.select_related("project")
.select_related("workspace")
.select_related("parent")
@@ -481,7 +484,10 @@ class IssueLinkAPIEndpoint(BaseAPIView):
IssueLink.objects.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(issue_id=self.kwargs.get("issue_id"))
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.order_by(self.kwargs.get("order_by", "-created_at"))
.distinct()
)
@@ -607,11 +613,11 @@ class IssueCommentAPIEndpoint(WebhookMixin, BaseAPIView):
)
.filter(project_id=self.kwargs.get("project_id"))
.filter(issue_id=self.kwargs.get("issue_id"))
- .filter(project__project_projectmember__member=self.request.user)
- .select_related("project")
- .select_related("workspace")
- .select_related("issue")
- .select_related("actor")
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
+ .select_related("workspace", "project", "issue", "actor")
.annotate(
is_member=Exists(
ProjectMember.objects.filter(
@@ -784,6 +790,7 @@ class IssueActivityAPIEndpoint(BaseAPIView):
.filter(
~Q(field__in=["comment", "vote", "reaction", "draft"]),
project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
)
.select_related("actor", "workspace", "issue", "project")
).order_by(request.GET.get("order_by", "created_at"))
diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py
index d509a53c7..2e5bb85e2 100644
--- a/apiserver/plane/api/views/module.py
+++ b/apiserver/plane/api/views/module.py
@@ -273,7 +273,10 @@ class ModuleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(module_id=self.kwargs.get("module_id"))
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.select_related("project")
.select_related("workspace")
.select_related("module")
diff --git a/apiserver/plane/api/views/state.py b/apiserver/plane/api/views/state.py
index dedc15ccd..ec10f9bab 100644
--- a/apiserver/plane/api/views/state.py
+++ b/apiserver/plane/api/views/state.py
@@ -24,7 +24,10 @@ class StateAPIEndpoint(BaseAPIView):
return (
State.objects.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.filter(~Q(name="Triage"))
.select_related("project")
.select_related("workspace")
diff --git a/apiserver/plane/app/views/cycle.py b/apiserver/plane/app/views/cycle.py
index 866396655..85e1e9f2e 100644
--- a/apiserver/plane/app/views/cycle.py
+++ b/apiserver/plane/app/views/cycle.py
@@ -85,7 +85,10 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.select_related("project", "workspace", "owned_by")
.prefetch_related(
Prefetch(
@@ -689,7 +692,10 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
)
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.filter(cycle_id=self.kwargs.get("cycle_id"))
.select_related("project")
.select_related("workspace")
diff --git a/apiserver/plane/app/views/integration/slack.py b/apiserver/plane/app/views/integration/slack.py
index 410e6b332..c22ee3e52 100644
--- a/apiserver/plane/app/views/integration/slack.py
+++ b/apiserver/plane/app/views/integration/slack.py
@@ -36,7 +36,10 @@ class SlackProjectSyncViewSet(BaseViewSet):
workspace__slug=self.kwargs.get("slug"),
project_id=self.kwargs.get("project_id"),
)
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
)
def create(self, request, slug, project_id, workspace_integration_id):
diff --git a/apiserver/plane/app/views/issue.py b/apiserver/plane/app/views/issue.py
index 66cc3caf7..6d50a2aba 100644
--- a/apiserver/plane/app/views/issue.py
+++ b/apiserver/plane/app/views/issue.py
@@ -773,7 +773,10 @@ class WorkSpaceIssuesEndpoint(BaseAPIView):
def get(self, request, slug):
issues = (
Issue.issue_objects.filter(workspace__slug=slug)
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.order_by("-created_at")
)
serializer = IssueSerializer(issues, many=True)
@@ -796,6 +799,7 @@ class IssueActivityEndpoint(BaseAPIView):
.filter(
~Q(field__in=["comment", "vote", "reaction", "draft"]),
project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
workspace__slug=slug,
)
.filter(**filters)
@@ -805,6 +809,7 @@ class IssueActivityEndpoint(BaseAPIView):
IssueComment.objects.filter(issue_id=issue_id)
.filter(
project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
workspace__slug=slug,
)
.filter(**filters)
@@ -856,7 +861,10 @@ class IssueCommentViewSet(WebhookMixin, BaseViewSet):
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(issue_id=self.kwargs.get("issue_id"))
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.select_related("project")
.select_related("workspace")
.select_related("issue")
@@ -1018,7 +1026,10 @@ class LabelViewSet(BaseViewSet):
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.select_related("project")
.select_related("workspace")
.select_related("parent")
@@ -1231,7 +1242,10 @@ class IssueLinkViewSet(BaseViewSet):
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(issue_id=self.kwargs.get("issue_id"))
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.order_by("-created_at")
.distinct()
)
@@ -1692,7 +1706,10 @@ class IssueSubscriberViewSet(BaseViewSet):
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(issue_id=self.kwargs.get("issue_id"))
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.order_by("-created_at")
.distinct()
)
@@ -1776,7 +1793,10 @@ class IssueReactionViewSet(BaseViewSet):
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(issue_id=self.kwargs.get("issue_id"))
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.order_by("-created_at")
.distinct()
)
@@ -1845,7 +1865,10 @@ class CommentReactionViewSet(BaseViewSet):
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(comment_id=self.kwargs.get("comment_id"))
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.order_by("-created_at")
.distinct()
)
@@ -1915,7 +1938,10 @@ class IssueRelationViewSet(BaseViewSet):
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(issue_id=self.kwargs.get("issue_id"))
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.select_related("project")
.select_related("workspace")
.select_related("issue")
diff --git a/apiserver/plane/app/views/module.py b/apiserver/plane/app/views/module.py
index 5ac244dda..3b52db64f 100644
--- a/apiserver/plane/app/views/module.py
+++ b/apiserver/plane/app/views/module.py
@@ -673,7 +673,10 @@ class ModuleLinkViewSet(BaseViewSet):
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(module_id=self.kwargs.get("module_id"))
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.order_by("-created_at")
.distinct()
)
diff --git a/apiserver/plane/app/views/page.py b/apiserver/plane/app/views/page.py
index 1d8ff1fbb..7ecf22fa8 100644
--- a/apiserver/plane/app/views/page.py
+++ b/apiserver/plane/app/views/page.py
@@ -60,7 +60,10 @@ class PageViewSet(BaseViewSet):
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.filter(parent__isnull=True)
.filter(Q(owned_by=self.request.user) | Q(access=0))
.select_related("project")
diff --git a/apiserver/plane/app/views/search.py b/apiserver/plane/app/views/search.py
index ccef3d18f..a2ed1c015 100644
--- a/apiserver/plane/app/views/search.py
+++ b/apiserver/plane/app/views/search.py
@@ -48,8 +48,8 @@ class GlobalSearchEndpoint(BaseAPIView):
return (
Project.objects.filter(
q,
- Q(project_projectmember__member=self.request.user)
- | Q(network=2),
+ project_projectmember__member=self.request.user,
+ project_projectmember__is_active=True,
workspace__slug=slug,
)
.distinct()
@@ -71,6 +71,7 @@ class GlobalSearchEndpoint(BaseAPIView):
issues = Issue.issue_objects.filter(
q,
project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
workspace__slug=slug,
)
@@ -95,6 +96,7 @@ class GlobalSearchEndpoint(BaseAPIView):
cycles = Cycle.objects.filter(
q,
project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
workspace__slug=slug,
)
@@ -118,6 +120,7 @@ class GlobalSearchEndpoint(BaseAPIView):
modules = Module.objects.filter(
q,
project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
workspace__slug=slug,
)
@@ -141,6 +144,7 @@ class GlobalSearchEndpoint(BaseAPIView):
pages = Page.objects.filter(
q,
project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
workspace__slug=slug,
)
@@ -164,6 +168,7 @@ class GlobalSearchEndpoint(BaseAPIView):
issue_views = IssueView.objects.filter(
q,
project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
workspace__slug=slug,
)
@@ -236,6 +241,7 @@ class IssueSearchEndpoint(BaseAPIView):
issues = Issue.issue_objects.filter(
workspace__slug=slug,
project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
)
if workspace_search == "false":
diff --git a/apiserver/plane/app/views/state.py b/apiserver/plane/app/views/state.py
index 242061e18..34b3d1dcc 100644
--- a/apiserver/plane/app/views/state.py
+++ b/apiserver/plane/app/views/state.py
@@ -31,7 +31,10 @@ class StateViewSet(BaseViewSet):
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.filter(~Q(name="Triage"))
.select_related("project")
.select_related("workspace")
diff --git a/apiserver/plane/app/views/view.py b/apiserver/plane/app/views/view.py
index 97a0f036f..ade445fae 100644
--- a/apiserver/plane/app/views/view.py
+++ b/apiserver/plane/app/views/view.py
@@ -86,6 +86,10 @@ class GlobalViewIssuesViewSet(BaseViewSet):
.values("count")
)
.filter(workspace__slug=self.kwargs.get("slug"))
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(cycle_id=F("issue_cycle__cycle_id"))
@@ -163,7 +167,6 @@ class GlobalViewIssuesViewSet(BaseViewSet):
issue_queryset = (
self.get_queryset()
.filter(**filters)
- .filter(project__project_projectmember__member=self.request.user)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
)
@@ -284,7 +287,10 @@ class IssueViewViewSet(BaseViewSet):
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
- .filter(project__project_projectmember__member=self.request.user)
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
.select_related("project")
.select_related("workspace")
.annotate(is_favorite=Exists(subquery))
diff --git a/apiserver/plane/app/views/workspace.py b/apiserver/plane/app/views/workspace.py
index 6677b4c4b..47de86a1c 100644
--- a/apiserver/plane/app/views/workspace.py
+++ b/apiserver/plane/app/views/workspace.py
@@ -1086,6 +1086,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
workspace__slug=slug,
assignees__in=[user_id],
project__project_projectmember__member=request.user,
+ project__project_projectmember__is_active=True
)
.filter(**filters)
.annotate(state_group=F("state__group"))
@@ -1101,6 +1102,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
workspace__slug=slug,
assignees__in=[user_id],
project__project_projectmember__member=request.user,
+ project__project_projectmember__is_active=True
)
.filter(**filters)
.values("priority")
@@ -1123,6 +1125,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
Issue.issue_objects.filter(
workspace__slug=slug,
project__project_projectmember__member=request.user,
+ project__project_projectmember__is_active=True,
created_by_id=user_id,
)
.filter(**filters)
@@ -1134,6 +1137,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
workspace__slug=slug,
assignees__in=[user_id],
project__project_projectmember__member=request.user,
+ project__project_projectmember__is_active=True,
)
.filter(**filters)
.count()
@@ -1145,6 +1149,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
workspace__slug=slug,
assignees__in=[user_id],
project__project_projectmember__member=request.user,
+ project__project_projectmember__is_active=True,
)
.filter(**filters)
.count()
@@ -1156,6 +1161,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
assignees__in=[user_id],
state__group="completed",
project__project_projectmember__member=request.user,
+ project__project_projectmember__is_active=True
)
.filter(**filters)
.count()
@@ -1166,6 +1172,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
workspace__slug=slug,
subscriber_id=user_id,
project__project_projectmember__member=request.user,
+ project__project_projectmember__is_active=True
)
.filter(**filters)
.count()
@@ -1215,6 +1222,7 @@ class WorkspaceUserActivityEndpoint(BaseAPIView):
~Q(field__in=["comment", "vote", "reaction", "draft"]),
workspace__slug=slug,
project__project_projectmember__member=request.user,
+ project__project_projectmember__is_active=True,
actor=user_id,
).select_related("actor", "workspace", "issue", "project")
@@ -1355,6 +1363,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
| Q(issue_subscribers__subscriber_id=user_id),
workspace__slug=slug,
project__project_projectmember__member=request.user,
+ project__project_projectmember__is_active=True
)
.filter(**filters)
.select_related("workspace", "project", "state", "parent")
@@ -1486,6 +1495,7 @@ class WorkspaceLabelsEndpoint(BaseAPIView):
labels = Label.objects.filter(
workspace__slug=slug,
project__project_projectmember__member=request.user,
+ project__project_projectmember__is_active=True
)
serializer = LabelSerializer(labels, many=True).data
return Response(serializer, status=status.HTTP_200_OK)
@@ -1500,6 +1510,7 @@ class WorkspaceStatesEndpoint(BaseAPIView):
states = State.objects.filter(
workspace__slug=slug,
project__project_projectmember__member=request.user,
+ project__project_projectmember__is_active=True
)
serializer = StateSerializer(states, many=True).data
return Response(serializer, status=status.HTTP_200_OK)
diff --git a/apiserver/plane/bgtasks/export_task.py b/apiserver/plane/bgtasks/export_task.py
index b99e4b1d9..d8522e769 100644
--- a/apiserver/plane/bgtasks/export_task.py
+++ b/apiserver/plane/bgtasks/export_task.py
@@ -292,6 +292,7 @@ def issue_export_task(
workspace__id=workspace_id,
project_id__in=project_ids,
project__project_projectmember__member=exporter_instance.initiated_by_id,
+ project__project_projectmember__is_active=True
)
.select_related(
"project", "workspace", "state", "parent", "created_by"
diff --git a/web/components/command-palette/command-modal.tsx b/web/components/command-palette/command-modal.tsx
index bd489f4c4..b52976aa8 100644
--- a/web/components/command-palette/command-modal.tsx
+++ b/web/components/command-palette/command-modal.tsx
@@ -154,237 +154,239 @@ export const CommandModal: React.FC = observer(() => {
-
-
-
-
-
{
- if (value.toLowerCase().includes(search.toLowerCase())) return 1;
- return 0;
- }}
- onKeyDown={(e) => {
- // when search is empty and page is undefined
- // when user tries to close the modal with esc
- if (e.key === "Escape" && !page && !searchTerm) closePalette();
+
+
+
+
+
+
{
+ if (value.toLowerCase().includes(search.toLowerCase())) return 1;
+ return 0;
+ }}
+ onKeyDown={(e) => {
+ // when search is empty and page is undefined
+ // when user tries to close the modal with esc
+ if (e.key === "Escape" && !page && !searchTerm) closePalette();
- // Escape goes to previous page
- // Backspace goes to previous page when search is empty
- if (e.key === "Escape" || (e.key === "Backspace" && !searchTerm)) {
- e.preventDefault();
- setPages((pages) => pages.slice(0, -1));
- setPlaceholder("Type a command or search...");
- }
- }}
- >
- pages.slice(0, -1));
+ setPlaceholder("Type a command or search...");
+ }
+ }}
>
- {issueDetails && (
-
- {projectDetails?.identifier}-{issueDetails.sequence_id} {issueDetails.name}
-
- )}
- {projectId && (
-
-
-
setIsWorkspaceLevel((prevData) => !prevData)}
- className="flex-shrink-0"
- >
- Workspace Level
-
-
setIsWorkspaceLevel((prevData) => !prevData)}
- />
+
+ {issueDetails && (
+
+ {projectDetails?.identifier}-{issueDetails.sequence_id} {issueDetails.name}
-
- )}
-
-
-
- setSearchTerm(e)}
- autoFocus
- tabIndex={1}
- />
-
+ )}
+ {projectId && (
+
+
+ setIsWorkspaceLevel((prevData) => !prevData)}
+ className="flex-shrink-0"
+ >
+ Workspace Level
+
+ setIsWorkspaceLevel((prevData) => !prevData)}
+ />
+
+
+ )}
+
+
+
+ setSearchTerm(e)}
+ autoFocus
+ tabIndex={1}
+ />
+
-
- {searchTerm !== "" && (
-
- Search results for{" "}
-
- {'"'}
- {searchTerm}
- {'"'}
- {" "}
- in {!projectId || isWorkspaceLevel ? "workspace" : "project"}:
-
- )}
+
+ {searchTerm !== "" && (
+
+ Search results for{" "}
+
+ {'"'}
+ {searchTerm}
+ {'"'}
+ {" "}
+ in {!projectId || isWorkspaceLevel ? "workspace" : "project"}:
+
+ )}
- {!isLoading && resultsCount === 0 && searchTerm !== "" && debouncedSearchTerm !== "" && (
- No results found.
- )}
+ {!isLoading && resultsCount === 0 && searchTerm !== "" && debouncedSearchTerm !== "" && (
+ No results found.
+ )}
- {(isLoading || isSearching) && (
-
-
-
-
-
-
-
-
- )}
+ {(isLoading || isSearching) && (
+
+
+
+
+
+
+
+
+ )}
- {debouncedSearchTerm !== "" && (
-
- )}
+ {debouncedSearchTerm !== "" && (
+
+ )}
- {!page && (
- <>
- {/* issue actions */}
- {issueId && (
- setPages(newPages)}
- setPlaceholder={(newPlaceholder) => setPlaceholder(newPlaceholder)}
- setSearchTerm={(newSearchTerm) => setSearchTerm(newSearchTerm)}
- />
- )}
-
- {
- closePalette();
- setTrackElement("Command Palette");
- toggleCreateIssueModal(true);
- }}
- className="focus:bg-custom-background-80"
- >
-
-
- Create new issue
-
- C
-
-
-
- {workspaceSlug && (
-
+ {!page && (
+ <>
+ {/* issue actions */}
+ {issueId && (
+ setPages(newPages)}
+ setPlaceholder={(newPlaceholder) => setPlaceholder(newPlaceholder)}
+ setSearchTerm={(newSearchTerm) => setSearchTerm(newSearchTerm)}
+ />
+ )}
+
{
closePalette();
- setTrackElement("Command palette");
- toggleCreateProjectModal(true);
+ setTrackElement("Command Palette");
+ toggleCreateIssueModal(true);
+ }}
+ className="focus:bg-custom-background-80"
+ >
+
+
+ Create new issue
+
+ C
+
+
+
+ {workspaceSlug && (
+
+ {
+ closePalette();
+ setTrackElement("Command palette");
+ toggleCreateProjectModal(true);
+ }}
+ className="focus:outline-none"
+ >
+
+
+ Create new project
+
+ P
+
+
+ )}
+
+ {/* project actions */}
+ {projectId && }
+
+
+ {
+ setPlaceholder("Search workspace settings...");
+ setSearchTerm("");
+ setPages([...pages, "settings"]);
}}
className="focus:outline-none"
>
-
- Create new project
+
+ Search settings...
+
+
+
+
+
+
+
+ Create new workspace
+
+
+ {
+ setPlaceholder("Change interface theme...");
+ setSearchTerm("");
+ setPages([...pages, "change-interface-theme"]);
+ }}
+ className="focus:outline-none"
+ >
+
+
+ Change interface theme...
- P
- )}
- {/* project actions */}
- {projectId && }
+ {/* help options */}
+
+ >
+ )}
-
- {
- setPlaceholder("Search workspace settings...");
- setSearchTerm("");
- setPages([...pages, "settings"]);
- }}
- className="focus:outline-none"
- >
-
-
- Search settings...
-
-
-
-
-
-
-
- Create new workspace
-
-
- {
- setPlaceholder("Change interface theme...");
- setSearchTerm("");
- setPages([...pages, "change-interface-theme"]);
- }}
- className="focus:outline-none"
- >
-
-
- Change interface theme...
-
-
-
+ {/* workspace settings actions */}
+ {page === "settings" && workspaceSlug && (
+
+ )}
- {/* help options */}
-
- >
- )}
+ {/* issue details page actions */}
+ {page === "change-issue-state" && issueDetails && (
+
+ )}
+ {page === "change-issue-priority" && issueDetails && (
+
+ )}
+ {page === "change-issue-assignee" && issueDetails && (
+
+ )}
- {/* workspace settings actions */}
- {page === "settings" && workspaceSlug && (
-
- )}
-
- {/* issue details page actions */}
- {page === "change-issue-state" && issueDetails && (
-
- )}
- {page === "change-issue-priority" && issueDetails && (
-
- )}
- {page === "change-issue-assignee" && issueDetails && (
-
- )}
-
- {/* theme actions */}
- {page === "change-interface-theme" && (
- {
- closePalette();
- setPages((pages) => pages.slice(0, -1));
- }}
- />
- )}
-
-
-
-
-
+ {/* theme actions */}
+ {page === "change-interface-theme" && (
+ {
+ closePalette();
+ setPages((pages) => pages.slice(0, -1));
+ }}
+ />
+ )}
+
+
+
+
+
+
diff --git a/web/components/core/modals/bulk-delete-issues-modal.tsx b/web/components/core/modals/bulk-delete-issues-modal.tsx
index f5eab83ef..39be2872b 100644
--- a/web/components/core/modals/bulk-delete-issues-modal.tsx
+++ b/web/components/core/modals/bulk-delete-issues-modal.tsx
@@ -49,8 +49,10 @@ export const BulkDeleteIssuesModal: React.FC = observer((props) => {
const [query, setQuery] = useState("");
// fetching project issues.
const { data: issues } = useSWR(
- workspaceSlug && projectId ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) : null,
- workspaceSlug && projectId ? () => issueService.getIssues(workspaceSlug as string, projectId as string) : null
+ workspaceSlug && projectId && isOpen ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) : null,
+ workspaceSlug && projectId && isOpen
+ ? () => issueService.getIssues(workspaceSlug as string, projectId as string)
+ : null
);
const { setToastAlert } = useToast();
diff --git a/web/components/dropdowns/cycle.tsx b/web/components/dropdowns/cycle.tsx
deleted file mode 100644
index d1f552a99..000000000
--- a/web/components/dropdowns/cycle.tsx
+++ /dev/null
@@ -1,277 +0,0 @@
-import { Fragment, ReactNode, useEffect, useRef, useState } from "react";
-import { observer } from "mobx-react-lite";
-import { Combobox } from "@headlessui/react";
-import { usePopper } from "react-popper";
-import { Check, ChevronDown, Search } from "lucide-react";
-// hooks
-import { useApplication, useCycle } from "hooks/store";
-import { useDropdownKeyDown } from "hooks/use-dropdown-key-down";
-import useOutsideClickDetector from "hooks/use-outside-click-detector";
-// components
-import { DropdownButton } from "./buttons";
-// icons
-import { ContrastIcon, CycleGroupIcon } from "@plane/ui";
-// helpers
-import { cn } from "helpers/common.helper";
-// types
-import { TDropdownProps } from "./types";
-import { TCycleGroups } from "@plane/types";
-// constants
-import { BUTTON_VARIANTS_WITH_TEXT } from "./constants";
-
-type Props = TDropdownProps & {
- button?: ReactNode;
- dropdownArrow?: boolean;
- dropdownArrowClassName?: string;
- onChange: (val: string | null) => void;
- onClose?: () => void;
- projectId: string;
- value: string | null;
-};
-
-type DropdownOptions =
- | {
- value: string | null;
- query: string;
- content: JSX.Element;
- }[]
- | undefined;
-
-export const CycleDropdown: React.FC = observer((props) => {
- const {
- button,
- buttonClassName,
- buttonContainerClassName,
- buttonVariant,
- className = "",
- disabled = false,
- dropdownArrow = false,
- dropdownArrowClassName = "",
- hideIcon = false,
- onChange,
- onClose,
- placeholder = "Cycle",
- placement,
- projectId,
- showTooltip = false,
- tabIndex,
- value,
- } = props;
- // states
- const [query, setQuery] = useState("");
- const [isOpen, setIsOpen] = useState(false);
- // refs
- const dropdownRef = useRef(null);
- const inputRef = useRef(null);
- // popper-js refs
- const [referenceElement, setReferenceElement] = useState(null);
- const [popperElement, setPopperElement] = useState(null);
- // popper-js init
- const { styles, attributes } = usePopper(referenceElement, popperElement, {
- placement: placement ?? "bottom-start",
- modifiers: [
- {
- name: "preventOverflow",
- options: {
- padding: 12,
- },
- },
- ],
- });
- // store hooks
- const {
- router: { workspaceSlug },
- } = useApplication();
- const { getProjectCycleIds, fetchAllCycles, getCycleById } = useCycle();
-
- const cycleIds = (getProjectCycleIds(projectId) ?? [])?.filter((cycleId) => {
- const cycleDetails = getCycleById(cycleId);
- return cycleDetails?.status ? (cycleDetails?.status.toLowerCase() != "completed" ? true : false) : true;
- });
-
- const options: DropdownOptions = cycleIds?.map((cycleId) => {
- const cycleDetails = getCycleById(cycleId);
- const cycleStatus = cycleDetails?.status ? (cycleDetails.status.toLocaleLowerCase() as TCycleGroups) : "draft";
-
- return {
- value: cycleId,
- query: `${cycleDetails?.name}`,
- content: (
-
-
- {cycleDetails?.name}
-
- ),
- };
- });
- options?.unshift({
- value: null,
- query: "No cycle",
- content: (
-
-
- No cycle
-
- ),
- });
-
- const filteredOptions =
- query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase()));
-
- const selectedCycle = value ? getCycleById(value) : null;
-
- const onOpen = () => {
- if (workspaceSlug && !cycleIds) fetchAllCycles(workspaceSlug, projectId);
- };
-
- const handleClose = () => {
- if (!isOpen) return;
- setIsOpen(false);
- onClose && onClose();
- };
-
- const toggleDropdown = () => {
- if (!isOpen) onOpen();
- setIsOpen((prevIsOpen) => !prevIsOpen);
- };
-
- const dropdownOnChange = (val: string | null) => {
- onChange(val);
- handleClose();
- };
-
- const handleKeyDown = useDropdownKeyDown(toggleDropdown, handleClose);
-
- const handleOnClick = (e: React.MouseEvent) => {
- e.stopPropagation();
- e.preventDefault();
- toggleDropdown();
- };
-
- const searchInputKeyDown = (e: React.KeyboardEvent) => {
- if (query !== "" && e.key === "Escape") {
- e.stopPropagation();
- setQuery("");
- }
- };
-
- useOutsideClickDetector(dropdownRef, handleClose);
-
- useEffect(() => {
- if (isOpen && inputRef.current) {
- inputRef.current.focus();
- }
- }, [isOpen]);
-
- return (
-
-
- {button ? (
-
- {button}
-
- ) : (
-
-
- {!hideIcon && }
- {BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
- {selectedCycle?.name ?? placeholder}
- )}
- {dropdownArrow && (
-
- )}
-
-
- )}
-
- {isOpen && (
-
-
-
-
- setQuery(e.target.value)}
- placeholder="Search"
- displayValue={(assigned: any) => assigned?.name}
- onKeyDown={searchInputKeyDown}
- />
-
-
- {filteredOptions ? (
- filteredOptions.length > 0 ? (
- filteredOptions.map((option) => (
-
- `w-full truncate flex items-center justify-between gap-2 rounded px-1 py-1.5 cursor-pointer select-none ${
- active ? "bg-custom-background-80" : ""
- } ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
- }
- >
- {({ selected }) => (
- <>
- {option.content}
- {selected && }
- >
- )}
-
- ))
- ) : (
-
No matches found
- )
- ) : (
-
Loading...
- )}
-
-
-
- )}
-
- );
-});
diff --git a/web/components/dropdowns/cycle/cycle-options.tsx b/web/components/dropdowns/cycle/cycle-options.tsx
new file mode 100644
index 000000000..e691569b7
--- /dev/null
+++ b/web/components/dropdowns/cycle/cycle-options.tsx
@@ -0,0 +1,162 @@
+import { useEffect, useRef, useState } from "react";
+import { Combobox } from "@headlessui/react";
+import { observer } from "mobx-react";
+//components
+import { ContrastIcon, CycleGroupIcon } from "@plane/ui";
+//store
+import { useApplication, useCycle } from "hooks/store";
+//hooks
+import { usePopper } from "react-popper";
+//icon
+import { Check, Search } from "lucide-react";
+//types
+import { Placement } from "@popperjs/core";
+import { TCycleGroups } from "@plane/types";
+
+type DropdownOptions =
+ | {
+ value: string | null;
+ query: string;
+ content: JSX.Element;
+ }[]
+ | undefined;
+
+interface Props {
+ projectId: string;
+ referenceElement: HTMLButtonElement | null;
+ placement: Placement | undefined;
+ isOpen: boolean;
+}
+
+export const CycleOptions = observer((props: any) => {
+ const { projectId, isOpen, referenceElement, placement } = props;
+
+ //state hooks
+ const [query, setQuery] = useState("");
+ const [popperElement, setPopperElement] = useState(null);
+ const inputRef = useRef(null);
+
+ // store hooks
+ const {
+ router: { workspaceSlug },
+ } = useApplication();
+ const { getProjectCycleIds, fetchAllCycles, getCycleById } = useCycle();
+
+ useEffect(() => {
+ if (isOpen) {
+ onOpen();
+ inputRef.current && inputRef.current.focus();
+ }
+ }, [isOpen]);
+
+ // popper-js init
+ const { styles, attributes } = usePopper(referenceElement, popperElement, {
+ placement: placement ?? "bottom-start",
+ modifiers: [
+ {
+ name: "preventOverflow",
+ options: {
+ padding: 12,
+ },
+ },
+ ],
+ });
+
+ const cycleIds = (getProjectCycleIds(projectId) ?? [])?.filter((cycleId) => {
+ const cycleDetails = getCycleById(cycleId);
+ return cycleDetails?.status ? (cycleDetails?.status.toLowerCase() != "completed" ? true : false) : true;
+ });
+
+ const onOpen = () => {
+ if (workspaceSlug && !cycleIds) fetchAllCycles(workspaceSlug, projectId);
+ };
+
+ const searchInputKeyDown = (e: React.KeyboardEvent) => {
+ if (query !== "" && e.key === "Escape") {
+ e.stopPropagation();
+ setQuery("");
+ }
+ };
+
+ const options: DropdownOptions = cycleIds?.map((cycleId) => {
+ const cycleDetails = getCycleById(cycleId);
+ const cycleStatus = cycleDetails?.status ? (cycleDetails.status.toLocaleLowerCase() as TCycleGroups) : "draft";
+
+ return {
+ value: cycleId,
+ query: `${cycleDetails?.name}`,
+ content: (
+
+
+ {cycleDetails?.name}
+
+ ),
+ };
+ });
+ options?.unshift({
+ value: null,
+ query: "No cycle",
+ content: (
+
+
+ No cycle
+
+ ),
+ });
+
+ const filteredOptions =
+ query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase()));
+
+ return (
+
+
+
+
+ setQuery(e.target.value)}
+ placeholder="Search"
+ displayValue={(assigned: any) => assigned?.name}
+ onKeyDown={searchInputKeyDown}
+ />
+
+
+ {filteredOptions ? (
+ filteredOptions.length > 0 ? (
+ filteredOptions.map((option) => (
+
+ `w-full truncate flex items-center justify-between gap-2 rounded px-1 py-1.5 cursor-pointer select-none ${
+ active ? "bg-custom-background-80" : ""
+ } ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
+ }
+ >
+ {({ selected }) => (
+ <>
+ {option.content}
+ {selected && }
+ >
+ )}
+
+ ))
+ ) : (
+
No matches found
+ )
+ ) : (
+
Loading...
+ )}
+
+
+
+ );
+});
diff --git a/web/components/dropdowns/cycle/index.tsx b/web/components/dropdowns/cycle/index.tsx
new file mode 100644
index 000000000..465eb3e2a
--- /dev/null
+++ b/web/components/dropdowns/cycle/index.tsx
@@ -0,0 +1,149 @@
+import { Fragment, ReactNode, useRef, useState } from "react";
+import { observer } from "mobx-react-lite";
+import { Combobox } from "@headlessui/react";
+import { ChevronDown } from "lucide-react";
+// hooks
+import { useCycle } from "hooks/store";
+import { useDropdownKeyDown } from "hooks/use-dropdown-key-down";
+import useOutsideClickDetector from "hooks/use-outside-click-detector";
+// components
+import { DropdownButton } from "../buttons";
+// icons
+import { ContrastIcon } from "@plane/ui";
+// helpers
+import { cn } from "helpers/common.helper";
+// types
+import { TDropdownProps } from "../types";
+// constants
+import { BUTTON_VARIANTS_WITH_TEXT } from "../constants";
+import { CycleOptions } from "./cycle-options";
+
+type Props = TDropdownProps & {
+ button?: ReactNode;
+ dropdownArrow?: boolean;
+ dropdownArrowClassName?: string;
+ onChange: (val: string | null) => void;
+ onClose?: () => void;
+ projectId: string;
+ value: string | null;
+};
+
+export const CycleDropdown: React.FC = observer((props) => {
+ const {
+ button,
+ buttonClassName,
+ buttonContainerClassName,
+ buttonVariant,
+ className = "",
+ disabled = false,
+ dropdownArrow = false,
+ dropdownArrowClassName = "",
+ hideIcon = false,
+ onChange,
+ onClose,
+ placeholder = "Cycle",
+ placement,
+ projectId,
+ showTooltip = false,
+ tabIndex,
+ value,
+ } = props;
+ // states
+
+ const [isOpen, setIsOpen] = useState(false);
+ const { getCycleNameById } = useCycle();
+ // refs
+ const dropdownRef = useRef(null);
+ // popper-js refs
+ const [referenceElement, setReferenceElement] = useState(null);
+
+ const selectedName = value ? getCycleNameById(value) : null;
+
+ const handleClose = () => {
+ if (!isOpen) return;
+ setIsOpen(false);
+ onClose && onClose();
+ };
+
+ const toggleDropdown = () => {
+ setIsOpen((prevIsOpen) => !prevIsOpen);
+ };
+
+ const dropdownOnChange = (val: string | null) => {
+ onChange(val);
+ handleClose();
+ };
+
+ const handleKeyDown = useDropdownKeyDown(toggleDropdown, handleClose);
+
+ const handleOnClick = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ toggleDropdown();
+ };
+
+ useOutsideClickDetector(dropdownRef, handleClose);
+
+ return (
+
+
+ {button ? (
+
+ {button}
+
+ ) : (
+
+
+ {!hideIcon && }
+ {BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
+ {selectedName ?? placeholder}
+ )}
+ {dropdownArrow && (
+
+ )}
+
+
+ )}
+
+ {isOpen && (
+
+ )}
+
+ );
+});
diff --git a/web/components/dropdowns/member/index.ts b/web/components/dropdowns/member/index.ts
deleted file mode 100644
index a9f7e09c8..000000000
--- a/web/components/dropdowns/member/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from "./project-member";
-export * from "./workspace-member";
diff --git a/web/components/dropdowns/member/index.tsx b/web/components/dropdowns/member/index.tsx
new file mode 100644
index 000000000..332f2227a
--- /dev/null
+++ b/web/components/dropdowns/member/index.tsx
@@ -0,0 +1,156 @@
+import { Fragment, useRef, useState } from "react";
+import { observer } from "mobx-react-lite";
+import { Combobox } from "@headlessui/react";
+import { ChevronDown } from "lucide-react";
+// hooks
+import { useMember } from "hooks/store";
+import { useDropdownKeyDown } from "hooks/use-dropdown-key-down";
+import useOutsideClickDetector from "hooks/use-outside-click-detector";
+// components
+import { ButtonAvatars } from "./avatar";
+import { DropdownButton } from "../buttons";
+// helpers
+import { cn } from "helpers/common.helper";
+// types
+import { MemberDropdownProps } from "./types";
+// constants
+import { BUTTON_VARIANTS_WITH_TEXT } from "../constants";
+import { MemberOptions } from "./member-options";
+
+type Props = {
+ projectId?: string;
+ onClose?: () => void;
+} & MemberDropdownProps;
+
+export const MemberDropdown: React.FC = observer((props) => {
+ const {
+ button,
+ buttonClassName,
+ buttonContainerClassName,
+ buttonVariant,
+ className = "",
+ disabled = false,
+ dropdownArrow = false,
+ dropdownArrowClassName = "",
+ hideIcon = false,
+ multiple,
+ onChange,
+ onClose,
+ placeholder = "Members",
+ placement,
+ projectId,
+ showTooltip = false,
+ tabIndex,
+ value,
+ } = props;
+ // states
+ const [isOpen, setIsOpen] = useState(false);
+ // refs
+ const dropdownRef = useRef(null);
+ // popper-js refs
+ const [referenceElement, setReferenceElement] = useState(null);
+
+ const { getUserDetails } = useMember();
+
+ const comboboxProps: any = {
+ value,
+ onChange,
+ disabled,
+ };
+ if (multiple) comboboxProps.multiple = true;
+
+ const handleClose = () => {
+ if (!isOpen) return;
+ setIsOpen(false);
+ onClose && onClose();
+ };
+
+ const toggleDropdown = () => {
+ setIsOpen((prevIsOpen) => !prevIsOpen);
+ };
+
+ const dropdownOnChange = (val: string & string[]) => {
+ onChange(val);
+ if (!multiple) handleClose();
+ };
+
+ const handleKeyDown = useDropdownKeyDown(toggleDropdown, handleClose);
+
+ const handleOnClick = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ toggleDropdown();
+ };
+
+ useOutsideClickDetector(dropdownRef, handleClose);
+
+ return (
+
+
+ {button ? (
+
+ {button}
+
+ ) : (
+
+
+ {!hideIcon && }
+ {BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
+
+ {Array.isArray(value) && value.length > 0
+ ? value.length === 1
+ ? getUserDetails(value[0])?.display_name
+ : ""
+ : placeholder}
+
+ )}
+ {dropdownArrow && (
+
+ )}
+
+
+ )}
+
+ {isOpen && (
+
+ )}
+
+ );
+});
diff --git a/web/components/dropdowns/member/member-options.tsx b/web/components/dropdowns/member/member-options.tsx
new file mode 100644
index 000000000..46a0b9cba
--- /dev/null
+++ b/web/components/dropdowns/member/member-options.tsx
@@ -0,0 +1,142 @@
+import { useEffect, useRef, useState } from "react";
+import { Combobox } from "@headlessui/react";
+import { observer } from "mobx-react";
+//components
+import { Avatar } from "@plane/ui";
+//store
+import { useApplication, useMember, useUser } from "hooks/store";
+//hooks
+import { usePopper } from "react-popper";
+//icon
+import { Check, Search } from "lucide-react";
+//types
+import { Placement } from "@popperjs/core";
+
+interface Props {
+ projectId?: string;
+ referenceElement: HTMLButtonElement | null;
+ placement: Placement | undefined;
+ isOpen: boolean;
+}
+
+export const MemberOptions = observer((props: Props) => {
+ const { projectId, referenceElement, placement, isOpen } = props;
+
+ const [query, setQuery] = useState("");
+ const [popperElement, setPopperElement] = useState(null);
+ const inputRef = useRef(null);
+
+ // store hooks
+ const {
+ router: { workspaceSlug },
+ } = useApplication();
+ const {
+ getUserDetails,
+ project: { getProjectMemberIds, fetchProjectMembers },
+ workspace: { workspaceMemberIds },
+ } = useMember();
+ const { currentUser } = useUser();
+
+ // popper-js init
+ const { styles, attributes } = usePopper(referenceElement, popperElement, {
+ placement: placement ?? "bottom-start",
+ modifiers: [
+ {
+ name: "preventOverflow",
+ options: {
+ padding: 12,
+ },
+ },
+ ],
+ });
+
+ useEffect(() => {
+ if (isOpen) {
+ onOpen();
+ inputRef.current && inputRef.current.focus();
+ }
+ }, [isOpen]);
+
+ const memberIds = projectId ? getProjectMemberIds(projectId) : workspaceMemberIds;
+ const onOpen = () => {
+ if (!memberIds && workspaceSlug && projectId) fetchProjectMembers(workspaceSlug, projectId);
+ };
+
+ const searchInputKeyDown = (e: React.KeyboardEvent) => {
+ if (query !== "" && e.key === "Escape") {
+ e.stopPropagation();
+ setQuery("");
+ }
+ };
+
+ const options = memberIds?.map((userId) => {
+ const userDetails = getUserDetails(userId);
+
+ return {
+ value: userId,
+ query: `${userDetails?.display_name} ${userDetails?.first_name} ${userDetails?.last_name}`,
+ content: (
+
+
+
{currentUser?.id === userId ? "You" : userDetails?.display_name}
+
+ ),
+ };
+ });
+
+ const filteredOptions =
+ query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase()));
+
+ return (
+
+
+
+
+ setQuery(e.target.value)}
+ placeholder="Search"
+ displayValue={(assigned: any) => assigned?.name}
+ onKeyDown={searchInputKeyDown}
+ />
+
+
+ {filteredOptions ? (
+ filteredOptions.length > 0 ? (
+ filteredOptions.map((option) => (
+
+ `w-full truncate flex items-center justify-between gap-2 rounded px-1 py-1.5 cursor-pointer select-none ${
+ active ? "bg-custom-background-80" : ""
+ } ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
+ }
+ >
+ {({ selected }) => (
+ <>
+ {option.content}
+ {selected && }
+ >
+ )}
+
+ ))
+ ) : (
+
No matching results
+ )
+ ) : (
+
Loading...
+ )}
+
+
+
+ );
+});
diff --git a/web/components/dropdowns/member/project-member.tsx b/web/components/dropdowns/member/project-member.tsx
deleted file mode 100644
index db8702535..000000000
--- a/web/components/dropdowns/member/project-member.tsx
+++ /dev/null
@@ -1,261 +0,0 @@
-import { Fragment, useEffect, useRef, useState } from "react";
-import { observer } from "mobx-react-lite";
-import { Combobox } from "@headlessui/react";
-import { usePopper } from "react-popper";
-import { Check, ChevronDown, Search } from "lucide-react";
-// hooks
-import { useApplication, useMember, useUser } from "hooks/store";
-import { useDropdownKeyDown } from "hooks/use-dropdown-key-down";
-import useOutsideClickDetector from "hooks/use-outside-click-detector";
-// components
-import { ButtonAvatars } from "./avatar";
-import { DropdownButton } from "../buttons";
-// icons
-import { Avatar } from "@plane/ui";
-// helpers
-import { cn } from "helpers/common.helper";
-// types
-import { MemberDropdownProps } from "./types";
-// constants
-import { BUTTON_VARIANTS_WITH_TEXT } from "../constants";
-
-type Props = {
- projectId: string;
- onClose?: () => void;
-} & MemberDropdownProps;
-
-export const ProjectMemberDropdown: React.FC = observer((props) => {
- const {
- button,
- buttonClassName,
- buttonContainerClassName,
- buttonVariant,
- className = "",
- disabled = false,
- dropdownArrow = false,
- dropdownArrowClassName = "",
- hideIcon = false,
- multiple,
- onChange,
- onClose,
- placeholder = "Members",
- placement,
- projectId,
- showTooltip = false,
- tabIndex,
- value,
- } = props;
- // states
- const [query, setQuery] = useState("");
- const [isOpen, setIsOpen] = useState(false);
- // refs
- const dropdownRef = useRef(null);
- const inputRef = useRef(null);
- // popper-js refs
- const [referenceElement, setReferenceElement] = useState(null);
- const [popperElement, setPopperElement] = useState(null);
- // popper-js init
- const { styles, attributes } = usePopper(referenceElement, popperElement, {
- placement: placement ?? "bottom-start",
- modifiers: [
- {
- name: "preventOverflow",
- options: {
- padding: 12,
- },
- },
- ],
- });
- // store hooks
- const {
- router: { workspaceSlug },
- } = useApplication();
- const { currentUser } = useUser();
- const {
- getUserDetails,
- project: { getProjectMemberIds, fetchProjectMembers },
- } = useMember();
- const projectMemberIds = getProjectMemberIds(projectId);
-
- const options = projectMemberIds?.map((userId) => {
- const userDetails = getUserDetails(userId);
-
- return {
- value: userId,
- query: `${userDetails?.display_name} ${userDetails?.first_name} ${userDetails?.last_name}`,
- content: (
-
-
-
{currentUser?.id === userId ? "You" : userDetails?.display_name}
-
- ),
- };
- });
-
- const filteredOptions =
- query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase()));
-
- const comboboxProps: any = {
- value,
- onChange,
- disabled,
- };
- if (multiple) comboboxProps.multiple = true;
-
- const onOpen = () => {
- if (!projectMemberIds && workspaceSlug) fetchProjectMembers(workspaceSlug, projectId);
- };
-
- const handleClose = () => {
- if (!isOpen) return;
- setIsOpen(false);
- onClose && onClose();
- };
-
- const toggleDropdown = () => {
- if (!isOpen) onOpen();
- setIsOpen((prevIsOpen) => !prevIsOpen);
- };
-
- const dropdownOnChange = (val: string & string[]) => {
- onChange(val);
- if (!multiple) handleClose();
- };
-
- const handleKeyDown = useDropdownKeyDown(toggleDropdown, handleClose);
-
- const handleOnClick = (e: React.MouseEvent) => {
- e.stopPropagation();
- e.preventDefault();
- toggleDropdown();
- };
-
- const searchInputKeyDown = (e: React.KeyboardEvent) => {
- if (query !== "" && e.key === "Escape") {
- e.stopPropagation();
- setQuery("");
- }
- };
-
- useOutsideClickDetector(dropdownRef, handleClose);
-
- useEffect(() => {
- if (isOpen && inputRef.current) {
- inputRef.current.focus();
- }
- }, [isOpen]);
-
- return (
-
-
- {button ? (
-
- {button}
-
- ) : (
-
-
- {!hideIcon && }
- {BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
-
- {Array.isArray(value) && value.length > 0
- ? value.length === 1
- ? getUserDetails(value[0])?.display_name
- : ""
- : placeholder}
-
- )}
- {dropdownArrow && (
-
- )}
-
-
- )}
-
- {isOpen && (
-
-
-
-
- setQuery(e.target.value)}
- placeholder="Search"
- displayValue={(assigned: any) => assigned?.name}
- onKeyDown={searchInputKeyDown}
- />
-
-
- {filteredOptions ? (
- filteredOptions.length > 0 ? (
- filteredOptions.map((option) => (
-
- `w-full truncate flex items-center justify-between gap-2 rounded px-1 py-1.5 cursor-pointer select-none ${
- active ? "bg-custom-background-80" : ""
- } ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
- }
- >
- {({ selected }) => (
- <>
- {option.content}
- {selected && }
- >
- )}
-
- ))
- ) : (
-
No matching results
- )
- ) : (
-
Loading...
- )}
-
-
-
- )}
-
- );
-});
diff --git a/web/components/dropdowns/member/workspace-member.tsx b/web/components/dropdowns/member/workspace-member.tsx
deleted file mode 100644
index e7a679750..000000000
--- a/web/components/dropdowns/member/workspace-member.tsx
+++ /dev/null
@@ -1,238 +0,0 @@
-import { Fragment, useEffect, useRef, useState } from "react";
-import { observer } from "mobx-react-lite";
-import { Combobox } from "@headlessui/react";
-import { usePopper } from "react-popper";
-import { Check, ChevronDown, Search } from "lucide-react";
-// hooks
-import { useMember, useUser } from "hooks/store";
-import { useDropdownKeyDown } from "hooks/use-dropdown-key-down";
-import useOutsideClickDetector from "hooks/use-outside-click-detector";
-// components
-import { ButtonAvatars } from "./avatar";
-import { DropdownButton } from "../buttons";
-// icons
-import { Avatar } from "@plane/ui";
-// helpers
-import { cn } from "helpers/common.helper";
-// types
-import { MemberDropdownProps } from "./types";
-// constants
-import { BUTTON_VARIANTS_WITH_TEXT } from "../constants";
-
-export const WorkspaceMemberDropdown: React.FC = observer((props) => {
- const {
- button,
- buttonClassName,
- buttonContainerClassName,
- buttonVariant,
- className = "",
- disabled = false,
- dropdownArrow = false,
- dropdownArrowClassName = "",
- hideIcon = false,
- multiple,
- onChange,
- onClose,
- placeholder = "Members",
- placement,
- showTooltip = false,
- tabIndex,
- value,
- } = props;
- // states
- const [query, setQuery] = useState("");
- const [isOpen, setIsOpen] = useState(false);
- // refs
- const dropdownRef = useRef(null);
- const inputRef = useRef(null);
- // popper-js refs
- const [referenceElement, setReferenceElement] = useState(null);
- const [popperElement, setPopperElement] = useState(null);
- // popper-js init
- const { styles, attributes } = usePopper(referenceElement, popperElement, {
- placement: placement ?? "bottom-start",
- modifiers: [
- {
- name: "preventOverflow",
- options: {
- padding: 12,
- },
- },
- ],
- });
- // store hooks
- const { currentUser } = useUser();
- const {
- getUserDetails,
- workspace: { workspaceMemberIds },
- } = useMember();
-
- const options = workspaceMemberIds?.map((userId) => {
- const userDetails = getUserDetails(userId);
-
- return {
- value: userId,
- query: `${userDetails?.display_name} ${userDetails?.first_name} ${userDetails?.last_name}`,
- content: (
-
-
-
{currentUser?.id === userId ? "You" : userDetails?.display_name}
-
- ),
- };
- });
-
- const filteredOptions =
- query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase()));
-
- const comboboxProps: any = {
- value,
- onChange,
- disabled,
- };
- if (multiple) comboboxProps.multiple = true;
-
- const handleClose = () => {
- if (!isOpen) return;
- setIsOpen(false);
- onClose && onClose();
- };
-
- const toggleDropdown = () => {
- setIsOpen((prevIsOpen) => !prevIsOpen);
- };
-
- const dropdownOnChange = (val: string & string[]) => {
- onChange(val);
- if (!multiple) handleClose();
- };
-
- const handleKeyDown = useDropdownKeyDown(toggleDropdown, handleClose);
-
- const handleOnClick = (e: React.MouseEvent) => {
- e.stopPropagation();
- e.preventDefault();
- toggleDropdown();
- };
-
- useOutsideClickDetector(dropdownRef, handleClose);
-
- useEffect(() => {
- if (isOpen && inputRef.current) {
- inputRef.current.focus();
- }
- }, [isOpen]);
-
- return (
-
-
- {button ? (
-
- {button}
-
- ) : (
-
-
- {!hideIcon && }
- {BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
-
- {Array.isArray(value) && value.length > 0
- ? value.length === 1
- ? getUserDetails(value[0])?.display_name
- : ""
- : placeholder}
-
- )}
- {dropdownArrow && (
-
- )}
-
-
- )}
-
- {isOpen && (
-
-
-
-
- setQuery(e.target.value)}
- placeholder="Search"
- displayValue={(assigned: any) => assigned?.name}
- />
-
-
- {filteredOptions ? (
- filteredOptions.length > 0 ? (
- filteredOptions.map((option) => (
-
- `w-full truncate flex items-center justify-between gap-2 rounded px-1 py-1.5 cursor-pointer select-none ${
- active ? "bg-custom-background-80" : ""
- } ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
- }
- >
- {({ selected }) => (
- <>
- {option.content}
- {selected && }
- >
- )}
-
- ))
- ) : (
-
No matching results
- )
- ) : (
-
Loading...
- )}
-
-
-
- )}
-
- );
-});
diff --git a/web/components/dropdowns/module.tsx b/web/components/dropdowns/module/index.tsx
similarity index 61%
rename from web/components/dropdowns/module.tsx
rename to web/components/dropdowns/module/index.tsx
index cee0ed9f3..ee2d746d9 100644
--- a/web/components/dropdowns/module.tsx
+++ b/web/components/dropdowns/module/index.tsx
@@ -1,22 +1,22 @@
import { Fragment, ReactNode, useEffect, useRef, useState } from "react";
import { observer } from "mobx-react-lite";
import { Combobox } from "@headlessui/react";
-import { usePopper } from "react-popper";
-import { Check, ChevronDown, Search, X } from "lucide-react";
+import { ChevronDown, X } from "lucide-react";
// hooks
-import { useApplication, useModule } from "hooks/store";
+import { useModule } from "hooks/store";
import { useDropdownKeyDown } from "hooks/use-dropdown-key-down";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
// components
-import { DropdownButton } from "./buttons";
+import { DropdownButton } from "../buttons";
// icons
import { DiceIcon, Tooltip } from "@plane/ui";
// helpers
import { cn } from "helpers/common.helper";
// types
-import { TDropdownProps } from "./types";
+import { TDropdownProps } from "../types";
// constants
-import { BUTTON_VARIANTS_WITHOUT_TEXT } from "./constants";
+import { BUTTON_VARIANTS_WITHOUT_TEXT } from "../constants";
+import { ModuleOptions } from "./module-options";
type Props = TDropdownProps & {
button?: ReactNode;
@@ -38,14 +38,6 @@ type Props = TDropdownProps & {
}
);
-type DropdownOptions =
- | {
- value: string | null;
- query: string;
- content: JSX.Element;
- }[]
- | undefined;
-
type ButtonContentProps = {
disabled: boolean;
dropdownArrow: boolean;
@@ -166,64 +158,14 @@ export const ModuleDropdown: React.FC = observer((props) => {
value,
} = props;
// states
- const [query, setQuery] = useState("");
const [isOpen, setIsOpen] = useState(false);
// refs
const dropdownRef = useRef(null);
const inputRef = useRef(null);
// popper-js refs
const [referenceElement, setReferenceElement] = useState(null);
- const [popperElement, setPopperElement] = useState(null);
- // popper-js init
- const { styles, attributes } = usePopper(referenceElement, popperElement, {
- placement: placement ?? "bottom-start",
- modifiers: [
- {
- name: "preventOverflow",
- options: {
- padding: 12,
- },
- },
- ],
- });
- // store hooks
- const {
- router: { workspaceSlug },
- } = useApplication();
- const { getProjectModuleIds, fetchModules, getModuleById } = useModule();
- const moduleIds = getProjectModuleIds(projectId);
- const options: DropdownOptions = moduleIds?.map((moduleId) => {
- const moduleDetails = getModuleById(moduleId);
- return {
- value: moduleId,
- query: `${moduleDetails?.name}`,
- content: (
-
-
- {moduleDetails?.name}
-
- ),
- };
- });
- if (!multiple)
- options?.unshift({
- value: null,
- query: "No module",
- content: (
-
-
- No module
-
- ),
- });
-
- const filteredOptions =
- query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase()));
-
- const onOpen = () => {
- if (!moduleIds && workspaceSlug) fetchModules(workspaceSlug, projectId);
- };
+ const { getModuleNameById } = useModule();
const handleClose = () => {
if (!isOpen) return;
@@ -232,7 +174,6 @@ export const ModuleDropdown: React.FC = observer((props) => {
};
const toggleDropdown = () => {
- if (!isOpen) onOpen();
setIsOpen((prevIsOpen) => !prevIsOpen);
};
@@ -249,13 +190,6 @@ export const ModuleDropdown: React.FC = observer((props) => {
toggleDropdown();
};
- const searchInputKeyDown = (e: React.KeyboardEvent) => {
- if (query !== "" && e.key === "Escape") {
- e.stopPropagation();
- setQuery("");
- }
- };
-
useOutsideClickDetector(dropdownRef, handleClose);
const comboboxProps: any = {
@@ -314,7 +248,7 @@ export const ModuleDropdown: React.FC = observer((props) => {
tooltipContent={
Array.isArray(value)
? `${value
- .map((moduleId) => getModuleById(moduleId)?.name)
+ .map((moduleId) => getModuleNameById(moduleId))
.toString()
.replaceAll(",", ", ")}`
: ""
@@ -339,61 +273,13 @@ export const ModuleDropdown: React.FC = observer((props) => {
)}
{isOpen && (
-
-
-
-
- setQuery(e.target.value)}
- placeholder="Search"
- displayValue={(assigned: any) => assigned?.name}
- onKeyDown={searchInputKeyDown}
- />
-
-
- {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,
- "text-custom-text-100": selected,
- "text-custom-text-200": !selected,
- }
- )
- }
- >
- {({ selected }) => (
- <>
- {option.content}
- {selected && }
- >
- )}
-
- ))
- ) : (
-
No matching results
- )
- ) : (
-
Loading...
- )}
-
-
-
+
)}
);
diff --git a/web/components/dropdowns/module/module-options.tsx b/web/components/dropdowns/module/module-options.tsx
new file mode 100644
index 000000000..e7d205b12
--- /dev/null
+++ b/web/components/dropdowns/module/module-options.tsx
@@ -0,0 +1,163 @@
+import { useEffect, useRef, useState } from "react";
+import { Combobox } from "@headlessui/react";
+import { observer } from "mobx-react";
+//components
+import { DiceIcon } from "@plane/ui";
+//store
+import { useApplication, useModule } from "hooks/store";
+//hooks
+import { usePopper } from "react-popper";
+import { cn } from "helpers/common.helper";
+//icon
+import { Check, Search } from "lucide-react";
+//types
+import { Placement } from "@popperjs/core";
+
+type DropdownOptions =
+ | {
+ value: string | null;
+ query: string;
+ content: JSX.Element;
+ }[]
+ | undefined;
+
+interface Props {
+ projectId: string;
+ referenceElement: HTMLButtonElement | null;
+ placement: Placement | undefined;
+ isOpen: boolean;
+ multiple: boolean;
+}
+
+export const ModuleOptions = observer((props: Props) => {
+ const { projectId, isOpen, referenceElement, placement, multiple } = props;
+
+ const [query, setQuery] = useState("");
+ const [popperElement, setPopperElement] = useState(null);
+ const inputRef = useRef(null);
+
+ // store hooks
+ const {
+ router: { workspaceSlug },
+ } = useApplication();
+ const { getProjectModuleIds, fetchModules, getModuleById } = useModule();
+
+ useEffect(() => {
+ if (isOpen) {
+ onOpen();
+ inputRef.current && inputRef.current.focus();
+ }
+ }, [isOpen]);
+
+ // popper-js init
+ const { styles, attributes } = usePopper(referenceElement, popperElement, {
+ placement: placement ?? "bottom-start",
+ modifiers: [
+ {
+ name: "preventOverflow",
+ options: {
+ padding: 12,
+ },
+ },
+ ],
+ });
+
+ const moduleIds = getProjectModuleIds(projectId);
+
+ const onOpen = () => {
+ if (workspaceSlug && !moduleIds) fetchModules(workspaceSlug, projectId);
+ };
+
+ const searchInputKeyDown = (e: React.KeyboardEvent) => {
+ if (query !== "" && e.key === "Escape") {
+ e.stopPropagation();
+ setQuery("");
+ }
+ };
+
+ const options: DropdownOptions = moduleIds?.map((moduleId) => {
+ const moduleDetails = getModuleById(moduleId);
+ return {
+ value: moduleId,
+ query: `${moduleDetails?.name}`,
+ content: (
+
+
+ {moduleDetails?.name}
+
+ ),
+ };
+ });
+ if (!multiple)
+ options?.unshift({
+ value: null,
+ query: "No module",
+ content: (
+
+
+ No module
+
+ ),
+ });
+
+ const filteredOptions =
+ query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase()));
+
+ return (
+
+
+
+
+ setQuery(e.target.value)}
+ placeholder="Search"
+ displayValue={(assigned: any) => assigned?.name}
+ onKeyDown={searchInputKeyDown}
+ />
+
+
+ {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,
+ "text-custom-text-100": selected,
+ "text-custom-text-200": !selected,
+ }
+ )
+ }
+ >
+ {({ selected }) => (
+ <>
+ {option.content}
+ {selected && }
+ >
+ )}
+
+ ))
+ ) : (
+
No matching results
+ )
+ ) : (
+
Loading...
+ )}
+
+
+
+ );
+});
diff --git a/web/components/issues/issue-detail/inbox/root.tsx b/web/components/issues/issue-detail/inbox/root.tsx
index d7b2b4b92..d96b36efa 100644
--- a/web/components/issues/issue-detail/inbox/root.tsx
+++ b/web/components/issues/issue-detail/inbox/root.tsx
@@ -30,6 +30,8 @@ export const InboxIssueDetailRoot: FC = (props) => {
} = useInboxIssues();
const {
issue: { getIssueById },
+ fetchActivities,
+ fetchComments,
} = useIssueDetail();
const { captureIssueEvent } = useEventTracker();
const { setToastAlert } = useToast();
@@ -125,6 +127,8 @@ export const InboxIssueDetailRoot: FC = (props) => {
async () => {
if (workspaceSlug && projectId && inboxId && issueId) {
await issueOperations.fetch(workspaceSlug, projectId, issueId);
+ await fetchActivities(workspaceSlug, projectId, issueId);
+ await fetchComments(workspaceSlug, projectId, issueId);
}
}
);
diff --git a/web/components/issues/issue-detail/inbox/sidebar.tsx b/web/components/issues/issue-detail/inbox/sidebar.tsx
index 6373ecd17..592791a85 100644
--- a/web/components/issues/issue-detail/inbox/sidebar.tsx
+++ b/web/components/issues/issue-detail/inbox/sidebar.tsx
@@ -5,7 +5,7 @@ import { CalendarCheck2, Signal, Tag } from "lucide-react";
import { useIssueDetail, useProject, useProjectState } from "hooks/store";
// components
import { IssueLabel, TIssueOperations } from "components/issues";
-import { DateDropdown, PriorityDropdown, ProjectMemberDropdown, StateDropdown } from "components/dropdowns";
+import { DateDropdown, PriorityDropdown, MemberDropdown, StateDropdown } from "components/dropdowns";
// icons
import { DoubleCircleIcon, StateGroupIcon, UserGroupIcon } from "@plane/ui";
// helper
@@ -80,7 +80,7 @@ export const InboxIssueDetailsSidebar: React.FC = observer((props) => {
Assignees
- issueOperations.update(workspaceSlug, projectId, issueId, { assignee_ids: val })}
disabled={!is_editable}
diff --git a/web/components/issues/issue-detail/issue-activity/comments/comment-card.tsx b/web/components/issues/issue-detail/issue-activity/comments/comment-card.tsx
index 2000721ee..722fd80a5 100644
--- a/web/components/issues/issue-detail/issue-activity/comments/comment-card.tsx
+++ b/web/components/issues/issue-detail/issue-activity/comments/comment-card.tsx
@@ -14,6 +14,8 @@ import { FileService } from "services/file.service";
// types
import { TIssueComment } from "@plane/types";
import { TActivityOperations } from "../root";
+// helpers
+import { isEmptyHtmlString } from "helpers/string.helper";
const fileService = new FileService();
@@ -67,6 +69,12 @@ export const IssueCommentCard: FC = (props) => {
isEditing && setFocus("comment_html");
}, [isEditing, setFocus]);
+ const isEmpty =
+ watch("comment_html") === "" ||
+ watch("comment_html")?.trim() === "" ||
+ watch("comment_html") === "
" ||
+ isEmptyHtmlString(watch("comment_html") ?? "");
+
if (!comment || !currentUser) return <>>;
return (
= (props) => {
>
<>