diff --git a/.gitignore b/.gitignore
index ad72521ff..4933d309e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -62,3 +62,6 @@ yarn-error.log
*.sln
package-lock.json
.vscode
+
+# Sentry
+.sentryclirc
\ No newline at end of file
diff --git a/README.md b/README.md
index 0480ee4fd..6af8396ac 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
-
+
@@ -48,4 +48,4 @@ Our [Code of Conduct](https://github.com/makeplane/plane/blob/master/CODE_OF_CON
## Security
-If you believe you have found a security vulnerability in Plane, we encourage you to responsibly disclose this and not open a public issue. We will investigate all legitimate reports. Email security@plane.so to disclose any security vulnerabilities.
\ No newline at end of file
+If you believe you have found a security vulnerability in Plane, we encourage you to responsibly disclose this and not open a public issue. We will investigate all legitimate reports. Email security@plane.so to disclose any security vulnerabilities.
diff --git a/apiserver/back_migration.py b/apiserver/back_migration.py
index 57ded0ba4..9613412a3 100644
--- a/apiserver/back_migration.py
+++ b/apiserver/back_migration.py
@@ -1,11 +1,13 @@
# All the python scripts that are used for back migrations
+import uuid
from plane.db.models import ProjectIdentifier
-from plane.db.models import Issue, IssueComment
+from plane.db.models import Issue, IssueComment, User
+from django.contrib.auth.hashers import make_password
+
# Update description and description html values for old descriptions
def update_description():
try:
-
issues = Issue.objects.all()
updated_issues = []
@@ -25,7 +27,6 @@ def update_description():
def update_comments():
try:
-
issue_comments = IssueComment.objects.all()
updated_issue_comments = []
@@ -44,9 +45,11 @@ def update_comments():
def update_project_identifiers():
try:
- project_identifiers = ProjectIdentifier.objects.filter(workspace_id=None).select_related("project", "project__workspace")
+ project_identifiers = ProjectIdentifier.objects.filter(
+ workspace_id=None
+ ).select_related("project", "project__workspace")
updated_identifiers = []
-
+
for identifier in project_identifiers:
identifier.workspace_id = identifier.project.workspace_id
updated_identifiers.append(identifier)
@@ -58,3 +61,21 @@ def update_project_identifiers():
except Exception as e:
print(e)
print("Failed")
+
+
+def update_user_empty_password():
+ try:
+ users = User.objects.filter(password="")
+ updated_users = []
+
+ for user in users:
+ user.password = make_password(uuid.uuid4().hex)
+ user.is_password_autoset = True
+ updated_users.append(user)
+
+ User.objects.bulk_update(updated_users, ["password"], batch_size=50)
+ print("Success")
+
+ except Exception as e:
+ print(e)
+ print("Failed")
diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py
index a148cbfb5..3add8f965 100644
--- a/apiserver/plane/api/serializers/issue.py
+++ b/apiserver/plane/api/serializers/issue.py
@@ -40,12 +40,12 @@ class IssueFlatSerializer(BaseSerializer):
"start_date",
"target_date",
"sequence_id",
+ "sort_order",
]
# Issue Serializer with state details
class IssueStateSerializer(BaseSerializer):
-
state_detail = StateSerializer(read_only=True, source="state")
project_detail = ProjectSerializer(read_only=True, source="project")
@@ -57,7 +57,6 @@ class IssueStateSerializer(BaseSerializer):
##TODO: Find a better way to write this serializer
## Find a better approach to save manytomany?
class IssueCreateSerializer(BaseSerializer):
-
state_detail = StateSerializer(read_only=True, source="state")
created_by_detail = UserLiteSerializer(read_only=True, source="created_by")
project_detail = ProjectSerializer(read_only=True, source="project")
@@ -176,7 +175,6 @@ class IssueCreateSerializer(BaseSerializer):
return issue
def update(self, instance, validated_data):
-
blockers = validated_data.pop("blockers_list", None)
assignees = validated_data.pop("assignees_list", None)
labels = validated_data.pop("labels_list", None)
@@ -254,7 +252,6 @@ class IssueCreateSerializer(BaseSerializer):
class IssueActivitySerializer(BaseSerializer):
-
actor_detail = UserLiteSerializer(read_only=True, source="actor")
class Meta:
@@ -263,7 +260,6 @@ class IssueActivitySerializer(BaseSerializer):
class IssueCommentSerializer(BaseSerializer):
-
actor_detail = UserLiteSerializer(read_only=True, source="actor")
issue_detail = IssueFlatSerializer(read_only=True, source="issue")
project_detail = ProjectSerializer(read_only=True, source="project")
@@ -319,7 +315,6 @@ class LabelSerializer(BaseSerializer):
class IssueLabelSerializer(BaseSerializer):
-
# label_details = LabelSerializer(read_only=True, source="label")
class Meta:
@@ -332,7 +327,6 @@ class IssueLabelSerializer(BaseSerializer):
class BlockedIssueSerializer(BaseSerializer):
-
blocked_issue_detail = IssueFlatSerializer(source="block", read_only=True)
class Meta:
@@ -341,7 +335,6 @@ class BlockedIssueSerializer(BaseSerializer):
class BlockerIssueSerializer(BaseSerializer):
-
blocker_issue_detail = IssueFlatSerializer(source="blocked_by", read_only=True)
class Meta:
@@ -350,7 +343,6 @@ class BlockerIssueSerializer(BaseSerializer):
class IssueAssigneeSerializer(BaseSerializer):
-
assignee_details = UserLiteSerializer(read_only=True, source="assignee")
class Meta:
@@ -373,7 +365,6 @@ class CycleBaseSerializer(BaseSerializer):
class IssueCycleDetailSerializer(BaseSerializer):
-
cycle_detail = CycleBaseSerializer(read_only=True, source="cycle")
class Meta:
@@ -404,7 +395,6 @@ class ModuleBaseSerializer(BaseSerializer):
class IssueModuleDetailSerializer(BaseSerializer):
-
module_detail = ModuleBaseSerializer(read_only=True, source="module")
class Meta:
diff --git a/apiserver/plane/api/views/api_token.py b/apiserver/plane/api/views/api_token.py
index 4ed3d9de0..2508b06ac 100644
--- a/apiserver/plane/api/views/api_token.py
+++ b/apiserver/plane/api/views/api_token.py
@@ -15,12 +15,16 @@ from plane.api.serializers import APITokenSerializer
class ApiTokenEndpoint(BaseAPIView):
def post(self, request):
try:
-
label = request.data.get("label", str(uuid4().hex))
+ workspace = request.data.get("workspace", False)
+
+ if not workspace:
+ return Response(
+ {"error": "Workspace is required"}, status=status.HTTP_200_OK
+ )
api_token = APIToken.objects.create(
- label=label,
- user=request.user,
+ label=label, user=request.user, workspace_id=workspace
)
serializer = APITokenSerializer(api_token)
diff --git a/apiserver/plane/api/views/authentication.py b/apiserver/plane/api/views/authentication.py
index c77bdd160..ac218837d 100644
--- a/apiserver/plane/api/views/authentication.py
+++ b/apiserver/plane/api/views/authentication.py
@@ -9,6 +9,7 @@ from django.utils import timezone
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.conf import settings
+from django.contrib.auth.hashers import make_password
# Third party imports
from rest_framework.response import Response
@@ -35,12 +36,10 @@ def get_tokens_for_user(user):
class SignUpEndpoint(BaseAPIView):
-
permission_classes = (AllowAny,)
def post(self, request):
try:
-
email = request.data.get("email", False)
password = request.data.get("password", False)
@@ -216,14 +215,12 @@ class SignOutEndpoint(BaseAPIView):
class MagicSignInGenerateEndpoint(BaseAPIView):
-
permission_classes = [
AllowAny,
]
def post(self, request):
try:
-
email = request.data.get("email", False)
if not email:
@@ -269,7 +266,6 @@ class MagicSignInGenerateEndpoint(BaseAPIView):
ri.set(key, json.dumps(value), ex=expiry)
else:
-
value = {"current_attempt": 0, "email": email, "token": token}
expiry = 600
@@ -293,14 +289,12 @@ class MagicSignInGenerateEndpoint(BaseAPIView):
class MagicSignInEndpoint(BaseAPIView):
-
permission_classes = [
AllowAny,
]
def post(self, request):
try:
-
user_token = request.data.get("token", "").strip().lower()
key = request.data.get("key", False)
@@ -313,19 +307,20 @@ class MagicSignInEndpoint(BaseAPIView):
ri = redis_instance()
if ri.exists(key):
-
data = json.loads(ri.get(key))
token = data["token"]
email = data["email"]
if str(token) == str(user_token):
-
if User.objects.filter(email=email).exists():
user = User.objects.get(email=email)
else:
user = User.objects.create(
- email=email, username=uuid.uuid4().hex
+ email=email,
+ username=uuid.uuid4().hex,
+ password=make_password(uuid.uuid4().hex),
+ is_password_autoset=True,
)
user.last_active = timezone.now()
diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py
index d1b291d9a..2b18aab96 100644
--- a/apiserver/plane/api/views/cycle.py
+++ b/apiserver/plane/api/views/cycle.py
@@ -1,5 +1,9 @@
+# Python imports
+import json
+
# Django imports
from django.db.models import OuterRef, Func, F
+from django.core import serializers
# Third party imports
from rest_framework.response import Response
@@ -11,10 +15,10 @@ from . import BaseViewSet
from plane.api.serializers import CycleSerializer, CycleIssueSerializer
from plane.api.permissions import ProjectEntityPermission
from plane.db.models import Cycle, CycleIssue, Issue
+from plane.bgtasks.issue_activites_task import issue_activity
class CycleViewSet(BaseViewSet):
-
serializer_class = CycleSerializer
model = Cycle
permission_classes = [
@@ -41,7 +45,6 @@ class CycleViewSet(BaseViewSet):
class CycleIssueViewSet(BaseViewSet):
-
serializer_class = CycleIssueSerializer
model = CycleIssue
@@ -79,7 +82,6 @@ class CycleIssueViewSet(BaseViewSet):
def create(self, request, slug, project_id, cycle_id):
try:
-
issues = request.data.get("issues", [])
if not len(issues):
@@ -91,29 +93,77 @@ class CycleIssueViewSet(BaseViewSet):
workspace__slug=slug, project_id=project_id, pk=cycle_id
)
- issues = Issue.objects.filter(
- pk__in=issues, workspace__slug=slug, project_id=project_id
- )
+ # Get all CycleIssues already created
+ cycle_issues = list(CycleIssue.objects.filter(issue_id__in=issues))
+ records_to_update = []
+ update_cycle_issue_activity = []
+ record_to_create = []
- # Delete old records in order to maintain the database integrity
- CycleIssue.objects.filter(issue_id__in=issues).delete()
+ for issue in issues:
+ cycle_issue = [
+ cycle_issue
+ for cycle_issue in cycle_issues
+ if str(cycle_issue.issue_id) in issues
+ ]
+ # Update only when cycle changes
+ if len(cycle_issue):
+ if cycle_issue[0].cycle_id != cycle_id:
+ update_cycle_issue_activity.append(
+ {
+ "old_cycle_id": str(cycle_issue[0].cycle_id),
+ "new_cycle_id": str(cycle_id),
+ "issue_id": str(cycle_issue[0].issue_id),
+ }
+ )
+ cycle_issue[0].cycle_id = cycle_id
+ records_to_update.append(cycle_issue[0])
+ else:
+ record_to_create.append(
+ CycleIssue(
+ project_id=project_id,
+ workspace=cycle.workspace,
+ created_by=request.user,
+ updated_by=request.user,
+ cycle=cycle,
+ issue_id=issue,
+ )
+ )
CycleIssue.objects.bulk_create(
- [
- CycleIssue(
- project_id=project_id,
- workspace=cycle.workspace,
- created_by=request.user,
- updated_by=request.user,
- cycle=cycle,
- issue=issue,
- )
- for issue in issues
- ],
+ record_to_create,
batch_size=10,
ignore_conflicts=True,
)
- return Response({"message": "Success"}, status=status.HTTP_200_OK)
+ CycleIssue.objects.bulk_update(
+ records_to_update,
+ ["cycle"],
+ batch_size=10,
+ )
+
+ # Capture Issue Activity
+ issue_activity.delay(
+ {
+ "type": "issue.activity",
+ "requested_data": json.dumps({"cycles_list": issues}),
+ "actor_id": str(self.request.user.id),
+ "issue_id": str(self.kwargs.get("pk", None)),
+ "project_id": str(self.kwargs.get("project_id", None)),
+ "current_instance": json.dumps(
+ {
+ "updated_cycle_issues": update_cycle_issue_activity,
+ "created_cycle_issues": serializers.serialize(
+ "json", record_to_create
+ ),
+ }
+ ),
+ },
+ )
+
+ # Return all Cycle Issues
+ return Response(
+ CycleIssueSerializer(self.get_queryset(), many=True).data,
+ status=status.HTTP_200_OK,
+ )
except Cycle.DoesNotExist:
return Response(
diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py
index 9955ded76..a1cda9834 100644
--- a/apiserver/plane/api/views/module.py
+++ b/apiserver/plane/api/views/module.py
@@ -1,6 +1,10 @@
+# Python imports
+import json
+
# Django Imports
from django.db import IntegrityError
from django.db.models import Prefetch, F, OuterRef, Func
+from django.core import serializers
# Third party imports
from rest_framework.response import Response
@@ -22,10 +26,10 @@ from plane.db.models import (
Issue,
ModuleLink,
)
+from plane.bgtasks.issue_activites_task import issue_activity
class ModuleViewSet(BaseViewSet):
-
model = Module
permission_classes = [
ProjectEntityPermission,
@@ -95,7 +99,6 @@ class ModuleViewSet(BaseViewSet):
class ModuleIssueViewSet(BaseViewSet):
-
serializer_class = ModuleIssueSerializer
model = ModuleIssue
@@ -148,29 +151,77 @@ class ModuleIssueViewSet(BaseViewSet):
workspace__slug=slug, project_id=project_id, pk=module_id
)
- issues = Issue.objects.filter(
- pk__in=issues, workspace__slug=slug, project_id=project_id
- )
+ module_issues = list(ModuleIssue.objects.filter(issue_id__in=issues))
- # Delete old records in order to maintain the database integrity
- ModuleIssue.objects.filter(issue_id__in=issues).delete()
+ update_module_issue_activity = []
+ records_to_update = []
+ record_to_create = []
+
+ for issue in issues:
+ module_issue = [
+ module_issue
+ for module_issue in module_issues
+ if str(module_issue.issue_id) in issues
+ ]
+
+ if len(module_issue):
+ if module_issue[0].module_id != module_id:
+ update_module_issue_activity.append(
+ {
+ "old_module_id": str(module_issue[0].module_id),
+ "new_module_id": str(module_id),
+ "issue_id": str(module_issue[0].issue_id),
+ }
+ )
+ module_issue[0].module_id = module_id
+ records_to_update.append(module_issue[0])
+ else:
+ record_to_create.append(
+ ModuleIssue(
+ module=module,
+ issue_id=issue,
+ project_id=project_id,
+ workspace=module.workspace,
+ created_by=request.user,
+ updated_by=request.user,
+ )
+ )
ModuleIssue.objects.bulk_create(
- [
- ModuleIssue(
- module=module,
- issue=issue,
- project_id=project_id,
- workspace=module.workspace,
- created_by=request.user,
- updated_by=request.user,
- )
- for issue in issues
- ],
+ record_to_create,
batch_size=10,
ignore_conflicts=True,
)
- return Response({"message": "Success"}, status=status.HTTP_200_OK)
+
+ ModuleIssue.objects.bulk_update(
+ records_to_update,
+ ["module"],
+ batch_size=10,
+ )
+
+ # Capture Issue Activity
+ issue_activity.delay(
+ {
+ "type": "issue.activity",
+ "requested_data": json.dumps({"modules_list": issues}),
+ "actor_id": str(self.request.user.id),
+ "issue_id": str(self.kwargs.get("pk", None)),
+ "project_id": str(self.kwargs.get("project_id", None)),
+ "current_instance": json.dumps(
+ {
+ "updated_module_issues": update_module_issue_activity,
+ "created_module_issues": serializers.serialize(
+ "json", record_to_create
+ ),
+ }
+ ),
+ },
+ )
+
+ return Response(
+ ModuleIssueSerializer(self.get_queryset(), many=True).data,
+ status=status.HTTP_200_OK,
+ )
except Module.DoesNotExist:
return Response(
{"error": "Module Does not exists"}, status=status.HTTP_400_BAD_REQUEST
diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py
index f6debc921..7e0e3f6ff 100644
--- a/apiserver/plane/bgtasks/issue_activites_task.py
+++ b/apiserver/plane/bgtasks/issue_activites_task.py
@@ -6,7 +6,16 @@ from django_rq import job
from sentry_sdk import capture_exception
# Module imports
-from plane.db.models import User, Issue, Project, Label, IssueActivity, State
+from plane.db.models import (
+ User,
+ Issue,
+ Project,
+ Label,
+ IssueActivity,
+ State,
+ Cycle,
+ Module,
+)
# Track Chnages in name
@@ -44,7 +53,6 @@ def track_parent(
issue_activities,
):
if current_instance.get("parent") != requested_data.get("parent"):
-
if requested_data.get("parent") == None:
old_parent = Issue.objects.get(pk=current_instance.get("parent"))
issue_activities.append(
@@ -134,7 +142,6 @@ def track_state(
issue_activities,
):
if current_instance.get("state") != requested_data.get("state"):
-
new_state = State.objects.get(pk=requested_data.get("state", None))
old_state = State.objects.get(pk=current_instance.get("state", None))
@@ -167,7 +174,6 @@ def track_description(
if current_instance.get("description_html") != requested_data.get(
"description_html"
):
-
issue_activities.append(
IssueActivity(
issue_id=issue_id,
@@ -274,7 +280,6 @@ def track_labels(
):
# Label Addition
if len(requested_data.get("labels_list")) > len(current_instance.get("labels")):
-
for label in requested_data.get("labels_list"):
if label not in current_instance.get("labels"):
label = Label.objects.get(pk=label)
@@ -296,7 +301,6 @@ def track_labels(
# Label Removal
if len(requested_data.get("labels_list")) < len(current_instance.get("labels")):
-
for label in current_instance.get("labels"):
if label not in requested_data.get("labels_list"):
label = Label.objects.get(pk=label)
@@ -326,12 +330,10 @@ def track_assignees(
actor,
issue_activities,
):
-
# Assignee Addition
if len(requested_data.get("assignees_list")) > len(
current_instance.get("assignees")
):
-
for assignee in requested_data.get("assignees_list"):
if assignee not in current_instance.get("assignees"):
assignee = User.objects.get(pk=assignee)
@@ -354,7 +356,6 @@ def track_assignees(
if len(requested_data.get("assignees_list")) < len(
current_instance.get("assignees")
):
-
for assignee in current_instance.get("assignees"):
if assignee not in requested_data.get("assignees_list"):
assignee = User.objects.get(pk=assignee)
@@ -386,7 +387,6 @@ def track_blocks(
if len(requested_data.get("blocks_list")) > len(
current_instance.get("blocked_issues")
):
-
for block in requested_data.get("blocks_list"):
if (
len(
@@ -418,7 +418,6 @@ def track_blocks(
if len(requested_data.get("blocks_list")) < len(
current_instance.get("blocked_issues")
):
-
for blocked in current_instance.get("blocked_issues"):
if blocked.get("block") not in requested_data.get("blocks_list"):
issue = Issue.objects.get(pk=blocked.get("block"))
@@ -450,7 +449,6 @@ def track_blockings(
if len(requested_data.get("blockers_list")) > len(
current_instance.get("blocker_issues")
):
-
for block in requested_data.get("blockers_list"):
if (
len(
@@ -482,7 +480,6 @@ def track_blockings(
if len(requested_data.get("blockers_list")) < len(
current_instance.get("blocker_issues")
):
-
for blocked in current_instance.get("blocker_issues"):
if blocked.get("blocked_by") not in requested_data.get("blockers_list"):
issue = Issue.objects.get(pk=blocked.get("blocked_by"))
@@ -502,6 +499,119 @@ def track_blockings(
)
+def track_cycles(
+ requested_data,
+ current_instance,
+ issue_id,
+ project,
+ actor,
+ issue_activities,
+):
+ # Updated Records:
+ updated_records = current_instance.get("updated_cycle_issues", [])
+ created_records = json.loads(current_instance.get("created_cycle_issues", []))
+
+ for updated_record in updated_records:
+ old_cycle = Cycle.objects.filter(
+ pk=updated_record.get("old_cycle_id", None)
+ ).first()
+ new_cycle = Cycle.objects.filter(
+ pk=updated_record.get("new_cycle_id", None)
+ ).first()
+
+ issue_activities.append(
+ IssueActivity(
+ issue_id=updated_record.get("issue_id"),
+ actor=actor,
+ verb="updated",
+ old_value=old_cycle.name,
+ new_value=new_cycle.name,
+ field="cycles",
+ project=project,
+ workspace=project.workspace,
+ comment=f"{actor.email} updated cycle from {old_cycle.name} to {new_cycle.name}",
+ old_identifier=old_cycle.id,
+ new_identifier=new_cycle.id,
+ )
+ )
+
+ for created_record in created_records:
+ cycle = Cycle.objects.filter(
+ pk=created_record.get("fields").get("cycle")
+ ).first()
+
+ issue_activities.append(
+ IssueActivity(
+ issue_id=created_record.get("fields").get("issue"),
+ actor=actor,
+ verb="created",
+ old_value="",
+ new_value=cycle.name,
+ field="cycles",
+ project=project,
+ workspace=project.workspace,
+ comment=f"{actor.email} added cycle {cycle.name}",
+ new_identifier=cycle.id,
+ )
+ )
+
+
+def track_modules(
+ requested_data,
+ current_instance,
+ issue_id,
+ project,
+ actor,
+ issue_activities,
+):
+ # Updated Records:
+ updated_records = current_instance.get("updated_module_issues", [])
+ created_records = json.loads(current_instance.get("created_module_issues", []))
+
+ for updated_record in updated_records:
+ old_module = Module.objects.filter(
+ pk=updated_record.get("old_module_id", None)
+ ).first()
+ new_module = Module.objects.filter(
+ pk=updated_record.get("new_module_id", None)
+ ).first()
+
+ issue_activities.append(
+ IssueActivity(
+ issue_id=updated_record.get("issue_id"),
+ actor=actor,
+ verb="updated",
+ old_value=old_module.name,
+ new_value=new_module.name,
+ field="modules",
+ project=project,
+ workspace=project.workspace,
+ comment=f"{actor.email} updated module from {old_module.name} to {new_module.name}",
+ old_identifier=old_module.id,
+ new_identifier=new_module.id,
+ )
+ )
+
+ for created_record in created_records:
+ module = Module.objects.filter(
+ pk=created_record.get("fields").get("module")
+ ).first()
+ issue_activities.append(
+ IssueActivity(
+ issue_id=created_record.get("fields").get("issue"),
+ actor=actor,
+ verb="created",
+ old_value="",
+ new_value=module.name,
+ field="modules",
+ project=project,
+ workspace=project.workspace,
+ comment=f"{actor.email} added module {module.name}",
+ new_identifier=module.id,
+ )
+ )
+
+
# Receive message from room group
@job("default")
def issue_activity(event):
@@ -510,7 +620,7 @@ def issue_activity(event):
requested_data = json.loads(event.get("requested_data"))
current_instance = json.loads(event.get("current_instance"))
- issue_id = event.get("issue_id")
+ issue_id = event.get("issue_id", None)
actor_id = event.get("actor_id")
project_id = event.get("project_id")
@@ -530,6 +640,8 @@ def issue_activity(event):
"assignees_list": track_assignees,
"blocks_list": track_blocks,
"blockers_list": track_blockings,
+ "cycles_list": track_cycles,
+ "modules_list": track_modules,
}
for key in requested_data:
diff --git a/apiserver/plane/db/models/api_token.py b/apiserver/plane/db/models/api_token.py
index 32ba013bc..b4009e6eb 100644
--- a/apiserver/plane/db/models/api_token.py
+++ b/apiserver/plane/db/models/api_token.py
@@ -17,7 +17,6 @@ def generate_token():
class APIToken(BaseModel):
-
token = models.CharField(max_length=255, unique=True, default=generate_token)
label = models.CharField(max_length=255, default=generate_label_token)
user = models.ForeignKey(
@@ -28,6 +27,9 @@ class APIToken(BaseModel):
user_type = models.PositiveSmallIntegerField(
choices=((0, "Human"), (1, "Bot")), default=0
)
+ workspace = models.ForeignKey(
+ "db.Workspace", related_name="api_tokens", on_delete=models.CASCADE, null=True
+ )
class Meta:
verbose_name = "API Token"
diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py
index c3984b3d2..3331b0832 100644
--- a/apiserver/plane/db/models/issue.py
+++ b/apiserver/plane/db/models/issue.py
@@ -9,6 +9,7 @@ from django.dispatch import receiver
from . import ProjectBaseModel
from plane.utils.html_processor import strip_tags
+
# TODO: Handle identifiers for Bulk Inserts - nk
class Issue(ProjectBaseModel):
PRIORITY_CHOICES = (
@@ -32,8 +33,8 @@ class Issue(ProjectBaseModel):
related_name="state_issue",
)
name = models.CharField(max_length=255, verbose_name="Issue Name")
- description = models.JSONField(blank=True, null=True)
- description_html = models.TextField(blank=True, null=True)
+ description = models.JSONField(blank=True, default=dict)
+ description_html = models.TextField(blank=True, default="
")
description_stripped = models.TextField(blank=True, null=True)
priority = models.CharField(
max_length=30,
@@ -56,6 +57,7 @@ class Issue(ProjectBaseModel):
labels = models.ManyToManyField(
"db.Label", blank=True, related_name="labels", through="IssueLabel"
)
+ sort_order = models.FloatField(default=65535)
class Meta:
verbose_name = "Issue"
@@ -196,8 +198,8 @@ class TimelineIssue(ProjectBaseModel):
class IssueComment(ProjectBaseModel):
comment_stripped = models.TextField(verbose_name="Comment", blank=True)
- comment_json = models.JSONField(blank=True, null=True)
- comment_html = models.TextField(blank=True)
+ comment_json = models.JSONField(blank=True, default=dict)
+ comment_html = models.TextField(blank=True, default="
")
attachments = ArrayField(models.URLField(), size=10, blank=True, default=list)
issue = models.ForeignKey(Issue, on_delete=models.CASCADE)
# System can also create comment
@@ -246,7 +248,6 @@ class IssueProperty(ProjectBaseModel):
class Label(ProjectBaseModel):
-
parent = models.ForeignKey(
"self",
on_delete=models.CASCADE,
@@ -256,7 +257,7 @@ class Label(ProjectBaseModel):
)
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
- colour = models.CharField(max_length=255, blank=True)
+ color = models.CharField(max_length=255, blank=True)
class Meta:
verbose_name = "Label"
@@ -269,7 +270,6 @@ class Label(ProjectBaseModel):
class IssueLabel(ProjectBaseModel):
-
issue = models.ForeignKey(
"db.Issue", on_delete=models.CASCADE, related_name="label_issue"
)
@@ -288,7 +288,6 @@ class IssueLabel(ProjectBaseModel):
class IssueSequence(ProjectBaseModel):
-
issue = models.ForeignKey(
Issue, on_delete=models.SET_NULL, related_name="issue_sequence", null=True
)
@@ -305,7 +304,6 @@ class IssueSequence(ProjectBaseModel):
# TODO: Find a better method to save the model
@receiver(post_save, sender=Issue)
def create_issue_sequence(sender, instance, created, **kwargs):
-
if created:
IssueSequence.objects.create(
issue=instance, sequence=instance.sequence_id, project=instance.project
diff --git a/apiserver/plane/db/models/project.py b/apiserver/plane/db/models/project.py
index 545bcd8a6..4a180642b 100644
--- a/apiserver/plane/db/models/project.py
+++ b/apiserver/plane/db/models/project.py
@@ -29,7 +29,6 @@ def get_default_props():
class Project(BaseModel):
-
NETWORK_CHOICES = ((0, "Secret"), (2, "Public"))
name = models.CharField(max_length=255, verbose_name="Project Name")
description = models.TextField(verbose_name="Project Description", blank=True)
@@ -63,6 +62,8 @@ class Project(BaseModel):
blank=True,
)
icon = models.CharField(max_length=255, null=True, blank=True)
+ module_view = models.BooleanField(default=True)
+ cycle_view = models.BooleanField(default=True)
def __str__(self):
"""Return name of the project"""
@@ -82,7 +83,6 @@ class Project(BaseModel):
class ProjectBaseModel(BaseModel):
-
project = models.ForeignKey(
Project, on_delete=models.CASCADE, related_name="project_%(class)s"
)
@@ -117,7 +117,6 @@ class ProjectMemberInvite(ProjectBaseModel):
class ProjectMember(ProjectBaseModel):
-
member = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
@@ -141,9 +140,9 @@ class ProjectMember(ProjectBaseModel):
"""Return members of the project"""
return f"{self.member.email} <{self.project.name}>"
+
# TODO: Remove workspace relation later
class ProjectIdentifier(AuditModel):
-
workspace = models.ForeignKey(
"db.Workspace", models.CASCADE, related_name="project_identifiers", null=True
)
diff --git a/apiserver/requirements/base.txt b/apiserver/requirements/base.txt
index 578235003..e9ca677db 100644
--- a/apiserver/requirements/base.txt
+++ b/apiserver/requirements/base.txt
@@ -1,6 +1,6 @@
# base requirements
-Django==3.2.16
+Django==3.2.17
django-braces==1.15.0
django-taggit==2.1.0
psycopg2==2.9.3
diff --git a/apps/app/components/account/email-code-form.tsx b/apps/app/components/account/email-code-form.tsx
index 03f9ea822..93298b4e0 100644
--- a/apps/app/components/account/email-code-form.tsx
+++ b/apps/app/components/account/email-code-form.tsx
@@ -35,7 +35,6 @@ export const EmailCodeForm = ({ onSuccess }: any) => {
});
const onSubmit = ({ email }: EmailCodeFormValues) => {
- console.log(email);
authenticationService
.emailCode({ email })
.then((res) => {
diff --git a/apps/app/components/command-palette/index.tsx b/apps/app/components/command-palette/index.tsx
index 441fb31fa..e6138da94 100644
--- a/apps/app/components/command-palette/index.tsx
+++ b/apps/app/components/command-palette/index.tsx
@@ -1,37 +1,38 @@
// TODO: Refactor this component: into a different file, use this file to export the components
import React, { useState, useCallback, useEffect } from "react";
-// next
+
import { useRouter } from "next/router";
-// swr
+
import useSWR from "swr";
-// hooks
+
+// headless ui
import { Combobox, Dialog, Transition } from "@headlessui/react";
+// services
+import userService from "services/user.service";
+// hooks
+import useTheme from "hooks/use-theme";
+import useToast from "hooks/use-toast";
+import useUser from "hooks/use-user";
+// components
+import ShortcutsModal from "components/command-palette/shortcuts";
+import { BulkDeleteIssuesModal } from "components/core";
+import { CreateProjectModal } from "components/project";
+import { CreateUpdateIssueModal } from "components/issues";
+import { CreateUpdateModuleModal } from "components/modules";
+import CreateUpdateCycleModal from "components/project/cycles/create-update-cycle-modal";
+// ui
+import { Button } from "components/ui";
+// icons
import {
FolderIcon,
RectangleStackIcon,
ClipboardDocumentListIcon,
MagnifyingGlassIcon,
} from "@heroicons/react/24/outline";
-import useTheme from "hooks/use-theme";
-import useToast from "hooks/use-toast";
-import useUser from "hooks/use-user";
-// services
-import userService from "services/user.service";
-// components
-import ShortcutsModal from "components/command-palette/shortcuts";
-import { CreateProjectModal } from "components/project";
-import { CreateUpdateIssueModal } from "components/issues/modal";
-import CreateUpdateCycleModal from "components/project/cycles/create-update-cycle-modal";
-import CreateUpdateModuleModal from "components/project/modules/create-update-module-modal";
-import BulkDeleteIssuesModal from "components/common/bulk-delete-issues-modal";
-// headless ui
// helpers
import { copyTextToClipboard } from "helpers/string.helper";
// types
import { IIssue } from "types";
-// ui
-import { Button } from "components/ui";
-// icons
// fetch-keys
import { USER_ISSUE } from "constants/fetch-keys";
@@ -74,7 +75,7 @@ const CommandPalette: React.FC = () => {
name: "Add new issue...",
icon: RectangleStackIcon,
hide: !projectId,
- shortcut: "I",
+ shortcut: "C",
onClick: () => {
setIsIssueModalOpen(true);
},
@@ -111,7 +112,6 @@ const CommandPalette: React.FC = () => {
if (!router.query.issueId) return;
const url = new URL(window.location.href);
- console.log(url);
copyTextToClipboard(url.href)
.then(() => {
setToastAlert({
@@ -179,7 +179,6 @@ const CommandPalette: React.FC = () => {
>
)}
@@ -330,7 +329,6 @@ const CommandPalette: React.FC = () => {
/>
{action.name}
- ⌘
{action.shortcut}
>
diff --git a/apps/app/components/common/board-view/single-board.tsx b/apps/app/components/common/board-view/single-board.tsx
deleted file mode 100644
index aedc969b5..000000000
--- a/apps/app/components/common/board-view/single-board.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-const SingleBoard = () => <>>;
-
-export default SingleBoard;
diff --git a/apps/app/components/common/board-view/single-issue.tsx b/apps/app/components/common/board-view/single-issue.tsx
deleted file mode 100644
index cd84697e9..000000000
--- a/apps/app/components/common/board-view/single-issue.tsx
+++ /dev/null
@@ -1,464 +0,0 @@
-import React from "react";
-
-import Link from "next/link";
-import Image from "next/image";
-import { useRouter } from "next/router";
-
-import useSWR, { mutate } from "swr";
-
-// react-beautiful-dnd
-import { DraggableStateSnapshot } from "react-beautiful-dnd";
-// react-datepicker
-import DatePicker from "react-datepicker";
-import "react-datepicker/dist/react-datepicker.css";
-// headless ui
-import { Listbox, Transition } from "@headlessui/react";
-// constants
-import { TrashIcon } from "@heroicons/react/24/outline";
-// services
-import issuesService from "services/issues.service";
-import stateService from "services/state.service";
-import projectService from "services/project.service";
-// components
-import { AssigneesList, CustomDatePicker } from "components/ui";
-// helpers
-import { renderShortNumericDateFormat, findHowManyDaysLeft } from "helpers/date-time.helper";
-import { addSpaceIfCamelCase } from "helpers/string.helper";
-// types
-import {
- CycleIssueResponse,
- IIssue,
- IssueResponse,
- IUserLite,
- IWorkspaceMember,
- ModuleIssueResponse,
- Properties,
- UserAuth,
-} from "types";
-// common
-import { PRIORITIES } from "constants/";
-import {
- STATE_LIST,
- PROJECT_DETAILS,
- CYCLE_ISSUES,
- MODULE_ISSUES,
- PROJECT_ISSUES_LIST,
-} from "constants/fetch-keys";
-import { getPriorityIcon } from "constants/global";
-
-type Props = {
- type?: string;
- typeId?: string;
- issue: IIssue;
- properties: Properties;
- snapshot?: DraggableStateSnapshot;
- assignees: Partial[] | (Partial | undefined)[];
- people: IWorkspaceMember[] | undefined;
- handleDeleteIssue?: React.Dispatch>;
- userAuth: UserAuth;
-};
-
-const SingleBoardIssue: React.FC = ({
- type,
- typeId,
- issue,
- properties,
- snapshot,
- assignees,
- people,
- handleDeleteIssue,
- userAuth,
-}) => {
- const router = useRouter();
- const { workspaceSlug, projectId } = router.query;
-
- const { data: states } = useSWR(
- workspaceSlug && projectId ? STATE_LIST(projectId as string) : null,
- workspaceSlug && projectId
- ? () => stateService.getStates(workspaceSlug as string, projectId as string)
- : null
- );
-
- const { data: projectDetails } = useSWR(
- workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
- workspaceSlug && projectId
- ? () => projectService.getProject(workspaceSlug as string, projectId as string)
- : null
- );
-
- const partialUpdateIssue = (formData: Partial) => {
- if (!workspaceSlug || !projectId) return;
-
- if (typeId) {
- mutate(
- CYCLE_ISSUES(typeId ?? ""),
- (prevData) => {
- const updatedIssues = (prevData ?? []).map((p) => {
- if (p.issue_detail.id === issue.id) {
- return {
- ...p,
- issue_detail: {
- ...p.issue_detail,
- ...formData,
- },
- };
- }
- return p;
- });
- return [...updatedIssues];
- },
- false
- );
-
- mutate(
- MODULE_ISSUES(typeId ?? ""),
- (prevData) => {
- const updatedIssues = (prevData ?? []).map((p) => {
- if (p.issue_detail.id === issue.id) {
- return {
- ...p,
- issue_detail: {
- ...p.issue_detail,
- ...formData,
- },
- };
- }
- return p;
- });
- return [...updatedIssues];
- },
- false
- );
- }
-
- mutate(
- PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
- (prevData) => ({
- ...(prevData as IssueResponse),
- results: (prevData?.results ?? []).map((p) => {
- if (p.id === issue.id) return { ...p, ...formData };
- return p;
- }),
- }),
- false
- );
-
- issuesService
- .patchIssue(workspaceSlug as string, projectId as string, issue.id, formData)
- .then((res) => {
- if (typeId) {
- mutate(CYCLE_ISSUES(typeId ?? ""));
- mutate(MODULE_ISSUES(typeId ?? ""));
- }
-
- mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string));
- })
- .catch((error) => {
- console.log(error);
- });
- };
-
- const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
-
- return (
-
-
- {handleDeleteIssue && !isNotAllowed && (
-
- handleDeleteIssue(issue.id)}
- >
-
-
-
- )}
-
-
- {properties.key && (
-
- {projectDetails?.identifier}-{issue.sequence_id}
-
- )}
-
- {issue.name}
-
-
-
-
- {properties.priority && (
-
{
- partialUpdateIssue({ priority: data });
- }}
- className="group relative flex-shrink-0"
- disabled={isNotAllowed}
- >
- {({ open }) => (
- <>
-
-
- {getPriorityIcon(issue?.priority ?? "None")}
-
-
-
-
- {PRIORITIES?.map((priority) => (
-
- `flex cursor-pointer select-none items-center gap-2 px-3 py-2 capitalize ${
- active ? "bg-indigo-50" : "bg-white"
- }`
- }
- value={priority}
- >
- {getPriorityIcon(priority)}
- {priority}
-
- ))}
-
-
-
- >
- )}
-
- )}
- {properties.state && (
-
{
- partialUpdateIssue({ state: data });
- }}
- className="group relative flex-shrink-0"
- disabled={isNotAllowed}
- >
- {({ open }) => (
- <>
-
-
-
- {addSpaceIfCamelCase(issue.state_detail.name)}
-
-
-
-
- {states?.map((state) => (
-
- `flex cursor-pointer select-none items-center gap-2 px-3 py-2 ${
- active ? "bg-indigo-50" : "bg-white"
- }`
- }
- value={state.id}
- >
-
- {addSpaceIfCamelCase(state.name)}
-
- ))}
-
-
-
- >
- )}
-
- )}
- {/* {properties.cycle && !typeId && (
-
- {issue.issue_cycle ? issue.issue_cycle.cycle_detail.name : "None"}
-
- )} */}
- {properties.due_date && (
-
-
- partialUpdateIssue({
- target_date: val,
- })
- }
- className={issue?.target_date ? "w-[6.5rem]" : "w-[3rem] text-center"}
- />
- {/* {
- partialUpdateIssue({
- target_date: val
- ? `${val.getFullYear()}-${val.getMonth() + 1}-${val.getDate()}`
- : null,
- });
- }}
- dateFormat="dd-MM-yyyy"
- className={`cursor-pointer rounded-md border px-2 py-[3px] text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${
- issue?.target_date ? "w-[4.5rem]" : "w-[3rem] text-center"
- }`}
- isClearable
- /> */}
-
- )}
- {properties.sub_issue_count && (
-
- {issue.sub_issues_count} {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
-
- )}
- {properties.assignee && (
-
{
- const newData = issue.assignees ?? [];
-
- if (newData.includes(data)) newData.splice(newData.indexOf(data), 1);
- else newData.push(data);
-
- partialUpdateIssue({ assignees_list: newData });
- }}
- className="group relative flex-shrink-0"
- disabled={isNotAllowed}
- >
- {({ open }) => (
-
-
-
-
-
-
-
- {people?.map((person) => (
-
- `cursor-pointer select-none p-2 ${active ? "bg-indigo-50" : "bg-white"}`
- }
- value={person.member.id}
- >
-
- {person.member.avatar && person.member.avatar !== "" ? (
-
-
-
- ) : (
-
- {person.member.first_name && person.member.first_name !== ""
- ? person.member.first_name.charAt(0)
- : person.member.email.charAt(0)}
-
- )}
-
- {person.member.first_name && person.member.first_name !== ""
- ? person.member.first_name
- : person.member.email}
-
-
-
- ))}
-
-
-
- )}
-
- )}
-
-
-
- );
-};
-
-export default SingleBoardIssue;
diff --git a/apps/app/components/common/list-view/single-issue.tsx b/apps/app/components/common/list-view/single-issue.tsx
deleted file mode 100644
index 262f332b5..000000000
--- a/apps/app/components/common/list-view/single-issue.tsx
+++ /dev/null
@@ -1,434 +0,0 @@
-import React, { useState } from "react";
-
-import Link from "next/link";
-import { useRouter } from "next/router";
-
-import useSWR, { mutate } from "swr";
-
-// services
-import issuesService from "services/issues.service";
-import workspaceService from "services/workspace.service";
-import stateService from "services/state.service";
-// headless ui
-import { Listbox, Transition } from "@headlessui/react";
-// ui
-import { CustomMenu, CustomSelect, AssigneesList, Avatar, CustomDatePicker } from "components/ui";
-// components
-import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deletion";
-// helpers
-import { renderShortNumericDateFormat, findHowManyDaysLeft } from "helpers/date-time.helper";
-import { addSpaceIfCamelCase } from "helpers/string.helper";
-// types
-import {
- CycleIssueResponse,
- IIssue,
- IssueResponse,
- IWorkspaceMember,
- ModuleIssueResponse,
- Properties,
- UserAuth,
-} from "types";
-// fetch-keys
-import {
- CYCLE_ISSUES,
- MODULE_ISSUES,
- PROJECT_ISSUES_LIST,
- STATE_LIST,
- WORKSPACE_MEMBERS,
-} from "constants/fetch-keys";
-// constants
-import { getPriorityIcon } from "constants/global";
-import { PRIORITIES } from "constants/";
-
-type Props = {
- type?: string;
- typeId?: string;
- issue: IIssue;
- properties: Properties;
- editIssue: () => void;
- removeIssue?: () => void;
- userAuth: UserAuth;
-};
-
-const SingleListIssue: React.FC = ({
- type,
- typeId,
- issue,
- properties,
- editIssue,
- removeIssue,
- userAuth,
-}) => {
- const [deleteIssue, setDeleteIssue] = useState();
-
- const router = useRouter();
- const { workspaceSlug, projectId } = router.query;
-
- const { data: states } = useSWR(
- workspaceSlug && projectId ? STATE_LIST(projectId as string) : null,
- workspaceSlug && projectId
- ? () => stateService.getStates(workspaceSlug as string, projectId as string)
- : null
- );
-
- const { data: people } = useSWR(
- workspaceSlug ? WORKSPACE_MEMBERS : null,
- workspaceSlug ? () => workspaceService.workspaceMembers(workspaceSlug as string) : null
- );
-
- const partialUpdateIssue = (formData: Partial) => {
- if (!workspaceSlug || !projectId) return;
-
- if (typeId) {
- mutate(
- CYCLE_ISSUES(typeId ?? ""),
- (prevData) => {
- const updatedIssues = (prevData ?? []).map((p) => {
- if (p.issue_detail.id === issue.id) {
- return {
- ...p,
- issue_detail: {
- ...p.issue_detail,
- ...formData,
- },
- };
- }
- return p;
- });
- return [...updatedIssues];
- },
- false
- );
-
- mutate(
- MODULE_ISSUES(typeId ?? ""),
- (prevData) => {
- const updatedIssues = (prevData ?? []).map((p) => {
- if (p.issue_detail.id === issue.id) {
- return {
- ...p,
- issue_detail: {
- ...p.issue_detail,
- ...formData,
- },
- };
- }
- return p;
- });
- return [...updatedIssues];
- },
- false
- );
- }
-
- mutate(
- PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
- (prevData) => ({
- ...(prevData as IssueResponse),
- results: (prevData?.results ?? []).map((p) => {
- if (p.id === issue.id) return { ...p, ...formData };
- return p;
- }),
- }),
- false
- );
-
- issuesService
- .patchIssue(workspaceSlug as string, projectId as string, issue.id, formData)
- .then((res) => {
- if (typeId) {
- mutate(CYCLE_ISSUES(typeId ?? ""));
- mutate(MODULE_ISSUES(typeId ?? ""));
- }
-
- mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string));
- })
- .catch((error) => {
- console.log(error);
- });
- };
-
- const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
-
- return (
- <>
- setDeleteIssue(undefined)}
- isOpen={!!deleteIssue}
- data={deleteIssue}
- />
-
-
-
- {properties.priority && (
-
{
- partialUpdateIssue({ priority: data });
- }}
- className="group relative flex-shrink-0"
- disabled={isNotAllowed}
- >
- {({ open }) => (
- <>
-
-
- {getPriorityIcon(
- issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None",
- "text-sm"
- )}
-
-
-
-
- {PRIORITIES?.map((priority) => (
-
- `flex cursor-pointer select-none items-center gap-x-2 px-3 py-2 capitalize ${
- active ? "bg-indigo-50" : "bg-white"
- }`
- }
- value={priority}
- >
- {getPriorityIcon(priority, "text-sm")}
- {priority ?? "None"}
-
- ))}
-
-
-
-
-
Priority
-
- {issue.priority ?? "None"}
-
-
- >
- )}
-
- )}
- {properties.state && (
-
-
- {addSpaceIfCamelCase(issue.state_detail.name)}
- >
- }
- value={issue.state}
- onChange={(data: string) => {
- partialUpdateIssue({ state: data });
- }}
- maxHeight="md"
- noChevron
- disabled={isNotAllowed}
- >
- {states?.map((state) => (
-
- <>
-
- {addSpaceIfCamelCase(state.name)}
- >
-
- ))}
-
- )}
- {/* {properties.cycle && !typeId && (
-
- {issue.issue_cycle ? issue.issue_cycle.cycle_detail.name : "None"}
-
- )} */}
- {properties.due_date && (
-
-
- partialUpdateIssue({
- target_date: val,
- })
- }
- className={issue?.target_date ? "w-[6.5rem]" : "w-[3rem] text-center"}
- />
-
-
Due date
-
{renderShortNumericDateFormat(issue.target_date ?? "")}
-
- {issue.target_date
- ? issue.target_date < new Date().toISOString()
- ? `Due date has passed by ${findHowManyDaysLeft(issue.target_date)} days`
- : findHowManyDaysLeft(issue.target_date) <= 3
- ? `Due date is in ${findHowManyDaysLeft(issue.target_date)} days`
- : "Due date"
- : "N/A"}
-
-
-
- )}
- {properties.sub_issue_count && (
-
- {issue.sub_issues_count} {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
-
- )}
- {properties.assignee && (
-
{
- const newData = issue.assignees ?? [];
-
- if (newData.includes(data)) newData.splice(newData.indexOf(data), 1);
- else newData.push(data);
-
- partialUpdateIssue({ assignees_list: newData });
- }}
- className="group relative flex-shrink-0"
- disabled={isNotAllowed}
- >
- {({ open }) => (
- <>
-
-
-
-
-
-
-
- {people?.map((person) => (
-
- `flex items-center gap-x-1 cursor-pointer select-none p-2 ${
- active ? "bg-indigo-50" : ""
- } ${
- selected || issue.assignees?.includes(person.member.id)
- ? "bg-indigo-50 font-medium"
- : "font-normal"
- }`
- }
- value={person.member.id}
- >
-
-
- {person.member.first_name && person.member.first_name !== ""
- ? person.member.first_name
- : person.member.email}
-
-
- ))}
-
-
-
-
-
Assigned to
-
- {issue.assignee_details?.length > 0
- ? issue.assignee_details.map((assignee) => assignee.first_name).join(", ")
- : "No one"}
-
-
- >
- )}
-
- )}
- {type && !isNotAllowed && (
-
- Edit
- {type !== "issue" && (
-
- <>Remove from {type}>
-
- )}
- setDeleteIssue(issue)}>
- Delete permanently
-
-
- )}
-
-
- >
- );
-};
-
-export default SingleListIssue;
diff --git a/apps/app/components/core/board-view/all-boards.tsx b/apps/app/components/core/board-view/all-boards.tsx
new file mode 100644
index 000000000..77c36e548
--- /dev/null
+++ b/apps/app/components/core/board-view/all-boards.tsx
@@ -0,0 +1,82 @@
+// react-beautiful-dnd
+import { DragDropContext, DropResult } from "react-beautiful-dnd";
+// hooks
+import useIssueView from "hooks/use-issue-view";
+// components
+import StrictModeDroppable from "components/dnd/StrictModeDroppable";
+import { SingleBoard } from "components/core/board-view/single-board";
+// types
+import { IIssue, IProjectMember, IState, UserAuth } from "types";
+
+type Props = {
+ type: "issue" | "cycle" | "module";
+ issues: IIssue[];
+ states: IState[] | undefined;
+ members: IProjectMember[] | undefined;
+ addIssueToState: (groupTitle: string, stateId: string | null) => void;
+ openIssuesListModal?: (() => void) | null;
+ handleDeleteIssue: (issue: IIssue) => void;
+ handleOnDragEnd: (result: DropResult) => void;
+ userAuth: UserAuth;
+};
+
+export const AllBoards: React.FC = ({
+ type,
+ issues,
+ states,
+ members,
+ addIssueToState,
+ openIssuesListModal,
+ handleDeleteIssue,
+ handleOnDragEnd,
+ userAuth,
+}) => {
+ const { groupedByIssues, groupByProperty: selectedGroup, orderBy } = useIssueView(issues);
+
+ return (
+ <>
+ {groupedByIssues ? (
+
+
+
+
+
+ {Object.keys(groupedByIssues).map((singleGroup, index) => {
+ const stateId =
+ selectedGroup === "state_detail.name"
+ ? states?.find((s) => s.name === singleGroup)?.id ?? null
+ : null;
+
+ const bgColor =
+ selectedGroup === "state_detail.name"
+ ? states?.find((s) => s.name === singleGroup)?.color
+ : "#000000";
+
+ return (
+ addIssueToState(singleGroup, stateId)}
+ handleDeleteIssue={handleDeleteIssue}
+ openIssuesListModal={openIssuesListModal ?? null}
+ orderBy={orderBy}
+ userAuth={userAuth}
+ />
+ );
+ })}
+
+
+
+
+
+ ) : (
+ Loading...
+ )}
+ >
+ );
+};
diff --git a/apps/app/components/common/board-view/board-header.tsx b/apps/app/components/core/board-view/board-header.tsx
similarity index 76%
rename from apps/app/components/common/board-view/board-header.tsx
rename to apps/app/components/core/board-view/board-header.tsx
index c04bc95d5..3a7753366 100644
--- a/apps/app/components/common/board-view/board-header.tsx
+++ b/apps/app/components/core/board-view/board-header.tsx
@@ -12,27 +12,23 @@ import {
// helpers
import { addSpaceIfCamelCase } from "helpers/string.helper";
// types
-import { IIssue, NestedKeyOf } from "types";
+import { IIssue } from "types";
type Props = {
isCollapsed: boolean;
setIsCollapsed: React.Dispatch>;
groupedByIssues: {
[key: string]: IIssue[];
};
- selectedGroup: NestedKeyOf | null;
groupTitle: string;
createdBy: string | null;
- bgColor: string;
+ bgColor?: string;
addIssueToState: () => void;
- provided?: DraggableProvided;
};
-const BoardHeader: React.FC = ({
+export const BoardHeader: React.FC = ({
isCollapsed,
setIsCollapsed,
- provided,
groupedByIssues,
- selectedGroup,
groupTitle,
createdBy,
bgColor,
@@ -44,18 +40,6 @@ const BoardHeader: React.FC = ({
}`}
>
- {provided && (
-
-
-
-
- )}
= ({
);
-
-export default BoardHeader;
diff --git a/apps/app/components/core/board-view/index.ts b/apps/app/components/core/board-view/index.ts
new file mode 100644
index 000000000..6e5cdf8bf
--- /dev/null
+++ b/apps/app/components/core/board-view/index.ts
@@ -0,0 +1,4 @@
+export * from "./all-boards";
+export * from "./board-header";
+export * from "./single-board";
+export * from "./single-issue";
diff --git a/apps/app/components/core/board-view/single-board.tsx b/apps/app/components/core/board-view/single-board.tsx
new file mode 100644
index 000000000..82d789a07
--- /dev/null
+++ b/apps/app/components/core/board-view/single-board.tsx
@@ -0,0 +1,146 @@
+import { useState } from "react";
+
+import { useRouter } from "next/router";
+
+// react-beautiful-dnd
+import StrictModeDroppable from "components/dnd/StrictModeDroppable";
+import { Draggable } from "react-beautiful-dnd";
+// hooks
+import useIssuesProperties from "hooks/use-issue-properties";
+// components
+import { BoardHeader, SingleBoardIssue } from "components/core";
+// ui
+import { CustomMenu } from "components/ui";
+// icons
+import { PlusIcon } from "@heroicons/react/24/outline";
+// types
+import { IIssue, IProjectMember, NestedKeyOf, UserAuth } from "types";
+
+type Props = {
+ type?: "issue" | "cycle" | "module";
+ bgColor?: string;
+ groupTitle: string;
+ groupedByIssues: {
+ [key: string]: IIssue[];
+ };
+ selectedGroup: NestedKeyOf | null;
+ members: IProjectMember[] | undefined;
+ addIssueToState: () => void;
+ handleDeleteIssue: (issue: IIssue) => void;
+ openIssuesListModal?: (() => void) | null;
+ orderBy: NestedKeyOf | "manual" | null;
+ userAuth: UserAuth;
+};
+
+export const SingleBoard: React.FC = ({
+ type,
+ bgColor,
+ groupTitle,
+ groupedByIssues,
+ selectedGroup,
+ members,
+ addIssueToState,
+ handleDeleteIssue,
+ openIssuesListModal,
+ orderBy,
+ userAuth,
+}) => {
+ // collapse/expand
+ const [isCollapsed, setIsCollapsed] = useState(true);
+
+ const router = useRouter();
+ const { workspaceSlug, projectId } = router.query;
+
+ const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string);
+
+ const createdBy =
+ selectedGroup === "created_by"
+ ? members?.find((m) => m.member.id === groupTitle)?.member.first_name ?? "loading..."
+ : null;
+
+ if (selectedGroup === "priority")
+ groupTitle === "high"
+ ? (bgColor = "#dc2626")
+ : groupTitle === "medium"
+ ? (bgColor = "#f97316")
+ : groupTitle === "low"
+ ? (bgColor = "#22c55e")
+ : (bgColor = "#ff0000");
+
+ return (
+
+
+
+
+ {(provided, snapshot) => (
+
+ {groupedByIssues[groupTitle].map((issue, index: number) => (
+
+ ))}
+
+ {provided.placeholder}
+
+ {type === "issue" ? (
+
+
+ Create
+
+ ) : (
+
+
+ Add issue
+
+ }
+ className="mt-1"
+ optionsPosition="left"
+ noBorder
+ >
+ Create new
+ {openIssuesListModal && (
+
+ Add an existing issue
+
+ )}
+
+ )}
+
+ )}
+
+
+
+ );
+};
diff --git a/apps/app/components/core/board-view/single-issue.tsx b/apps/app/components/core/board-view/single-issue.tsx
new file mode 100644
index 000000000..59786ea7c
--- /dev/null
+++ b/apps/app/components/core/board-view/single-issue.tsx
@@ -0,0 +1,240 @@
+import React, { useCallback } from "react";
+
+import Link from "next/link";
+import { useRouter } from "next/router";
+
+import useSWR, { mutate } from "swr";
+
+// react-beautiful-dnd
+import {
+ Draggable,
+ DraggableStateSnapshot,
+ DraggingStyle,
+ NotDraggingStyle,
+} from "react-beautiful-dnd";
+// constants
+import { TrashIcon } from "@heroicons/react/24/outline";
+// services
+import issuesService from "services/issues.service";
+// components
+import {
+ ViewAssigneeSelect,
+ ViewDueDateSelect,
+ ViewPrioritySelect,
+ ViewStateSelect,
+} from "components/issues/view-select";
+// types
+import {
+ CycleIssueResponse,
+ IIssue,
+ IssueResponse,
+ ModuleIssueResponse,
+ NestedKeyOf,
+ Properties,
+ UserAuth,
+} from "types";
+// fetch-keys
+import { CYCLE_ISSUES, MODULE_ISSUES, PROJECT_ISSUES_LIST } from "constants/fetch-keys";
+
+type Props = {
+ index: number;
+ type?: string;
+ issue: IIssue;
+ selectedGroup: NestedKeyOf | null;
+ properties: Properties;
+ handleDeleteIssue: (issue: IIssue) => void;
+ orderBy: NestedKeyOf | "manual" | null;
+ userAuth: UserAuth;
+};
+
+export const SingleBoardIssue: React.FC = ({
+ index,
+ type,
+ issue,
+ selectedGroup,
+ properties,
+ handleDeleteIssue,
+ orderBy,
+ userAuth,
+}) => {
+ const router = useRouter();
+ const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
+
+ const partialUpdateIssue = useCallback(
+ (formData: Partial) => {
+ if (!workspaceSlug || !projectId) return;
+
+ if (cycleId)
+ mutate(
+ CYCLE_ISSUES(cycleId as string),
+ (prevData) => {
+ const updatedIssues = (prevData ?? []).map((p) => {
+ if (p.issue_detail.id === issue.id) {
+ return {
+ ...p,
+ issue_detail: {
+ ...p.issue_detail,
+ ...formData,
+ },
+ };
+ }
+ return p;
+ });
+ return [...updatedIssues];
+ },
+ false
+ );
+
+ if (moduleId)
+ mutate(
+ MODULE_ISSUES(moduleId as string),
+ (prevData) => {
+ const updatedIssues = (prevData ?? []).map((p) => {
+ if (p.issue_detail.id === issue.id) {
+ return {
+ ...p,
+ issue_detail: {
+ ...p.issue_detail,
+ ...formData,
+ },
+ };
+ }
+ return p;
+ });
+ return [...updatedIssues];
+ },
+ false
+ );
+
+ mutate(
+ PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
+ (prevData) => ({
+ ...(prevData as IssueResponse),
+ results: (prevData?.results ?? []).map((p) => {
+ if (p.id === issue.id) return { ...p, ...formData };
+ return p;
+ }),
+ }),
+ false
+ );
+
+ issuesService
+ .patchIssue(workspaceSlug as string, projectId as string, issue.id, formData)
+ .then((res) => {
+ if (cycleId) mutate(CYCLE_ISSUES(cycleId as string));
+ if (moduleId) mutate(MODULE_ISSUES(moduleId as string));
+
+ mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string));
+ })
+ .catch((error) => {
+ console.log(error);
+ });
+ },
+ [workspaceSlug, projectId, cycleId, moduleId, issue]
+ );
+
+ function getStyle(
+ style: DraggingStyle | NotDraggingStyle | undefined,
+ snapshot: DraggableStateSnapshot
+ ) {
+ if (orderBy === "manual") return style;
+ if (!snapshot.isDragging) return {};
+ if (!snapshot.isDropAnimating) {
+ return style;
+ }
+
+ return {
+ ...style,
+ transitionDuration: `0.001s`,
+ };
+ }
+
+ const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
+
+ return (
+
+ {(provided, snapshot) => (
+
+ )}
+
+ );
+};
diff --git a/apps/app/components/common/bulk-delete-issues-modal.tsx b/apps/app/components/core/bulk-delete-issues-modal.tsx
similarity index 92%
rename from apps/app/components/common/bulk-delete-issues-modal.tsx
rename to apps/app/components/core/bulk-delete-issues-modal.tsx
index 64a65c22a..6e879d885 100644
--- a/apps/app/components/common/bulk-delete-issues-modal.tsx
+++ b/apps/app/components/core/bulk-delete-issues-modal.tsx
@@ -1,27 +1,26 @@
-// react
import React, { useState } from "react";
-// next
+
import { useRouter } from "next/router";
-// swr
+
import useSWR, { mutate } from "swr";
+
// react hook form
import { SubmitHandler, useForm } from "react-hook-form";
-// services
+// headless ui
import { Combobox, Dialog, Transition } from "@headlessui/react";
-import { FolderIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
+// services
import issuesServices from "services/issues.service";
-import projectService from "services/project.service";
// hooks
import useToast from "hooks/use-toast";
-// headless ui
// ui
import { Button } from "components/ui";
// icons
+import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { LayerDiagonalIcon } from "components/icons";
// types
import { IIssue, IssueResponse } from "types";
// fetch keys
-import { PROJECT_ISSUES_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
+import { PROJECT_ISSUES_LIST } from "constants/fetch-keys";
type FormInput = {
delete_issue_ids: string[];
@@ -32,7 +31,7 @@ type Props = {
setIsOpen: React.Dispatch>;
};
-const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen }) => {
+export const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen }) => {
const [query, setQuery] = useState("");
const router = useRouter();
@@ -50,13 +49,6 @@ const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen }) => {
: null
);
- const { data: projectDetails } = useSWR(
- workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
- workspaceSlug && projectId
- ? () => projectService.getProject(workspaceSlug as string, projectId as string)
- : null
- );
-
const { setToastAlert } = useToast();
const {
@@ -213,7 +205,7 @@ const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen }) => {
}}
/>
- {projectDetails?.identifier}-{issue.sequence_id}
+ {issue.project_detail.identifier}-{issue.sequence_id}
{issue.name}
@@ -226,7 +218,7 @@ const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen }) => {
No issues found. Create a new issue with{" "}
- C .
+ C .
)}
@@ -256,5 +248,3 @@ const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen }) => {
);
};
-
-export default BulkDeleteIssuesModal;
diff --git a/apps/app/components/common/existing-issues-list-modal.tsx b/apps/app/components/core/existing-issues-list-modal.tsx
similarity index 91%
rename from apps/app/components/common/existing-issues-list-modal.tsx
rename to apps/app/components/core/existing-issues-list-modal.tsx
index 5179facd4..15a313cb0 100644
--- a/apps/app/components/common/existing-issues-list-modal.tsx
+++ b/apps/app/components/core/existing-issues-list-modal.tsx
@@ -1,24 +1,17 @@
import React, { useState } from "react";
-import { useRouter } from "next/router";
-
-import useSWR from "swr";
// react-hook-form
import { Controller, SubmitHandler, useForm } from "react-hook-form";
// hooks
import { Combobox, Dialog, Transition } from "@headlessui/react";
import { MagnifyingGlassIcon, RectangleStackIcon } from "@heroicons/react/24/outline";
import useToast from "hooks/use-toast";
-// services
-import projectService from "services/project.service";
// headless ui
// ui
import { Button } from "components/ui";
import { LayerDiagonalIcon } from "components/icons";
// types
import { IIssue } from "types";
-// fetch-keys
-import { PROJECT_DETAILS } from "constants/fetch-keys";
type FormInput = {
issues: string[];
@@ -32,7 +25,7 @@ type Props = {
handleOnSubmit: any;
};
-const ExistingIssuesListModal: React.FC = ({
+export const ExistingIssuesListModal: React.FC = ({
isOpen,
handleClose: onClose,
issues,
@@ -41,16 +34,6 @@ const ExistingIssuesListModal: React.FC = ({
}) => {
const [query, setQuery] = useState("");
- const router = useRouter();
- const { workspaceSlug, projectId } = router.query;
-
- const { data: projectDetails } = useSWR(
- workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
- workspaceSlug && projectId
- ? () => projectService.getProject(workspaceSlug as string, projectId as string)
- : null
- );
-
const { setToastAlert } = useToast();
const handleClose = () => {
@@ -175,7 +158,7 @@ const ExistingIssuesListModal: React.FC = ({
}}
/>
- {projectDetails?.identifier}-{issue.sequence_id}
+ {issue.project_detail.identifier}-{issue.sequence_id}
{issue.name}
>
@@ -189,7 +172,7 @@ const ExistingIssuesListModal: React.FC = ({
No issues found. Create a new issue with{" "}
- C .
+ C .
)}
@@ -233,5 +216,3 @@ const ExistingIssuesListModal: React.FC = ({
>
);
};
-
-export default ExistingIssuesListModal;
diff --git a/apps/app/components/common/image-upload-modal.tsx b/apps/app/components/core/image-upload-modal.tsx
similarity index 100%
rename from apps/app/components/common/image-upload-modal.tsx
rename to apps/app/components/core/image-upload-modal.tsx
diff --git a/apps/app/components/core/index.ts b/apps/app/components/core/index.ts
index 8266a5111..0865ea441 100644
--- a/apps/app/components/core/index.ts
+++ b/apps/app/components/core/index.ts
@@ -1 +1,8 @@
+export * from "./board-view";
+export * from "./list-view";
+export * from "./bulk-delete-issues-modal";
+export * from "./existing-issues-list-modal";
+export * from "./image-upload-modal";
+export * from "./issues-view-filter";
+export * from "./issues-view";
export * from "./not-authorized-view";
diff --git a/apps/app/components/core/view.tsx b/apps/app/components/core/issues-view-filter.tsx
similarity index 87%
rename from apps/app/components/core/view.tsx
rename to apps/app/components/core/issues-view-filter.tsx
index 1fe147f22..7225f5148 100644
--- a/apps/app/components/core/view.tsx
+++ b/apps/app/components/core/issues-view-filter.tsx
@@ -17,13 +17,13 @@ import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
// types
import { IIssue, Properties } from "types";
// common
-import { filterIssueOptions, groupByOptions, orderByOptions } from "constants/";
+import { GROUP_BY_OPTIONS, ORDER_BY_OPTIONS, FILTER_ISSUE_OPTIONS } from "constants/issue";
type Props = {
issues?: IIssue[];
};
-const View: React.FC = ({ issues }) => {
+export const IssuesFilterView: React.FC = ({ issues }) => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
@@ -99,31 +99,33 @@ const View: React.FC = ({ issues }) => {
Group by
option.key === groupByProperty)
+ GROUP_BY_OPTIONS.find((option) => option.key === groupByProperty)
?.name ?? "Select"
}
width="lg"
>
- {groupByOptions.map((option) => (
- setGroupByProperty(option.key)}
- >
- {option.name}
-
- ))}
+ {GROUP_BY_OPTIONS.map((option) =>
+ issueView === "kanban" && option.key === null ? null : (
+ setGroupByProperty(option.key)}
+ >
+ {option.name}
+
+ )
+ )}
Order by
option.key === orderBy)?.name ??
+ ORDER_BY_OPTIONS.find((option) => option.key === orderBy)?.name ??
"Select"
}
width="lg"
>
- {orderByOptions.map((option) =>
+ {ORDER_BY_OPTIONS.map((option) =>
groupByProperty === "priority" &&
option.key === "priority" ? null : (
= ({ issues }) => {
Issue type
option.key === filterIssue)
+ FILTER_ISSUE_OPTIONS.find((option) => option.key === filterIssue)
?.name ?? "Select"
}
width="lg"
>
- {filterIssueOptions.map((option) => (
+ {FILTER_ISSUE_OPTIONS.map((option) => (
setFilterIssue(option.key)}
@@ -203,5 +205,3 @@ const View: React.FC = ({ issues }) => {
>
);
};
-
-export default View;
diff --git a/apps/app/components/core/issues-view.tsx b/apps/app/components/core/issues-view.tsx
new file mode 100644
index 000000000..5f1d2c289
--- /dev/null
+++ b/apps/app/components/core/issues-view.tsx
@@ -0,0 +1,405 @@
+import { useCallback, useState } from "react";
+
+import { useRouter } from "next/router";
+
+import useSWR, { mutate } from "swr";
+
+// react-beautiful-dnd
+import { DropResult } from "react-beautiful-dnd";
+// services
+import issuesService from "services/issues.service";
+import stateService from "services/state.service";
+import projectService from "services/project.service";
+import modulesService from "services/modules.service";
+// hooks
+import useIssueView from "hooks/use-issue-view";
+// components
+import { AllLists, AllBoards, ExistingIssuesListModal } from "components/core";
+import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
+// types
+import {
+ CycleIssueResponse,
+ IIssue,
+ IssueResponse,
+ IState,
+ ModuleIssueResponse,
+ UserAuth,
+} from "types";
+// fetch-keys
+import {
+ CYCLE_ISSUES,
+ MODULE_ISSUES,
+ PROJECT_ISSUES_LIST,
+ PROJECT_MEMBERS,
+ STATE_LIST,
+} from "constants/fetch-keys";
+
+type Props = {
+ type?: "issue" | "cycle" | "module";
+ issues: IIssue[];
+ openIssuesListModal?: () => void;
+ userAuth: UserAuth;
+};
+
+export const IssuesView: React.FC = ({
+ type = "issue",
+ issues,
+ openIssuesListModal,
+ userAuth,
+}) => {
+ // create issue modal
+ const [createIssueModal, setCreateIssueModal] = useState(false);
+ const [preloadedData, setPreloadedData] = useState<
+ (Partial & { actionType: "createIssue" | "edit" | "delete" }) | undefined
+ >(undefined);
+
+ // updates issue modal
+ const [editIssueModal, setEditIssueModal] = useState(false);
+ const [issueToEdit, setIssueToEdit] = useState<
+ (IIssue & { actionType: "edit" | "delete" }) | undefined
+ >(undefined);
+
+ // delete issue modal
+ const [deleteIssueModal, setDeleteIssueModal] = useState(false);
+ const [issueToDelete, setIssueToDelete] = useState(null);
+
+ const router = useRouter();
+ const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
+
+ const { issueView, groupedByIssues, groupByProperty: selectedGroup } = useIssueView(issues);
+
+ const { data: states } = useSWR(
+ workspaceSlug && projectId ? STATE_LIST(projectId as string) : null,
+ workspaceSlug
+ ? () => stateService.getStates(workspaceSlug as string, projectId as string)
+ : null
+ );
+
+ const { data: members } = useSWR(
+ projectId ? PROJECT_MEMBERS(projectId as string) : null,
+ workspaceSlug && projectId
+ ? () => projectService.projectMembers(workspaceSlug as string, projectId as string)
+ : null
+ );
+
+ const handleOnDragEnd = useCallback(
+ (result: DropResult) => {
+ if (!result.destination || !workspaceSlug || !projectId) return;
+
+ const { source, destination } = result;
+
+ const draggedItem = groupedByIssues[source.droppableId][source.index];
+
+ if (source.droppableId !== destination.droppableId) {
+ const sourceGroup = source.droppableId; // source group id
+ const destinationGroup = destination.droppableId; // destination group id
+
+ if (!sourceGroup || !destinationGroup) return;
+
+ if (selectedGroup === "priority") {
+ // update the removed item for mutation
+ draggedItem.priority = destinationGroup;
+
+ if (cycleId)
+ mutate(
+ CYCLE_ISSUES(cycleId as string),
+ (prevData) => {
+ if (!prevData) return prevData;
+ const updatedIssues = prevData.map((issue) => {
+ if (issue.issue_detail.id === draggedItem.id) {
+ return {
+ ...issue,
+ issue_detail: {
+ ...draggedItem,
+ priority: destinationGroup,
+ },
+ };
+ }
+ return issue;
+ });
+ return [...updatedIssues];
+ },
+ false
+ );
+
+ if (moduleId)
+ mutate(
+ MODULE_ISSUES(moduleId as string),
+ (prevData) => {
+ if (!prevData) return prevData;
+ const updatedIssues = prevData.map((issue) => {
+ if (issue.issue_detail.id === draggedItem.id) {
+ return {
+ ...issue,
+ issue_detail: {
+ ...draggedItem,
+ priority: destinationGroup,
+ },
+ };
+ }
+ return issue;
+ });
+ return [...updatedIssues];
+ },
+ false
+ );
+
+ mutate(
+ PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
+ (prevData) => {
+ if (!prevData) return prevData;
+
+ const updatedIssues = prevData.results.map((issue) => {
+ if (issue.id === draggedItem.id)
+ return {
+ ...draggedItem,
+ priority: destinationGroup,
+ };
+
+ return issue;
+ });
+
+ return {
+ ...prevData,
+ results: updatedIssues,
+ };
+ },
+ false
+ );
+
+ // patch request
+ issuesService
+ .patchIssue(workspaceSlug as string, projectId as string, draggedItem.id, {
+ priority: destinationGroup,
+ })
+ .then((res) => {
+ if (cycleId) mutate(CYCLE_ISSUES(cycleId as string));
+ if (moduleId) mutate(MODULE_ISSUES(moduleId as string));
+
+ mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string));
+ });
+ } else if (selectedGroup === "state_detail.name") {
+ const destinationState = states?.find((s) => s.name === destinationGroup);
+ const destinationStateId = destinationState?.id;
+
+ // update the removed item for mutation
+ if (!destinationStateId || !destinationState) return;
+ draggedItem.state = destinationStateId;
+ draggedItem.state_detail = destinationState;
+
+ if (cycleId)
+ mutate(
+ CYCLE_ISSUES(cycleId as string),
+ (prevData) => {
+ if (!prevData) return prevData;
+ const updatedIssues = prevData.map((issue) => {
+ if (issue.issue_detail.id === draggedItem.id) {
+ return {
+ ...issue,
+ issue_detail: {
+ ...draggedItem,
+ state_detail: destinationState,
+ state: destinationStateId,
+ },
+ };
+ }
+ return issue;
+ });
+ return [...updatedIssues];
+ },
+ false
+ );
+
+ if (moduleId)
+ mutate(
+ MODULE_ISSUES(moduleId as string),
+ (prevData) => {
+ if (!prevData) return prevData;
+ const updatedIssues = prevData.map((issue) => {
+ if (issue.issue_detail.id === draggedItem.id) {
+ return {
+ ...issue,
+ issue_detail: {
+ ...draggedItem,
+ state_detail: destinationState,
+ state: destinationStateId,
+ },
+ };
+ }
+ return issue;
+ });
+ return [...updatedIssues];
+ },
+ false
+ );
+
+ mutate(
+ PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
+ (prevData) => {
+ if (!prevData) return prevData;
+
+ const updatedIssues = prevData.results.map((issue) => {
+ if (issue.id === draggedItem.id)
+ return {
+ ...draggedItem,
+ state_detail: destinationState,
+ state: destinationStateId,
+ };
+
+ return issue;
+ });
+
+ return {
+ ...prevData,
+ results: updatedIssues,
+ };
+ },
+ false
+ );
+
+ // patch request
+ issuesService
+ .patchIssue(workspaceSlug as string, projectId as string, draggedItem.id, {
+ state: destinationStateId,
+ })
+ .then((res) => {
+ if (cycleId) mutate(CYCLE_ISSUES(cycleId as string));
+ if (moduleId) mutate(MODULE_ISSUES(moduleId as string));
+
+ mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string));
+ });
+ }
+ }
+ },
+ [workspaceSlug, cycleId, moduleId, groupedByIssues, projectId, selectedGroup, states]
+ );
+
+ const addIssueToState = (groupTitle: string, stateId: string | null) => {
+ setCreateIssueModal(true);
+ if (selectedGroup)
+ setPreloadedData({
+ state: stateId ?? undefined,
+ [selectedGroup]: groupTitle,
+ actionType: "createIssue",
+ });
+ else setPreloadedData({ actionType: "createIssue" });
+ };
+
+ const handleEditIssue = (issue: IIssue) => {
+ setEditIssueModal(true);
+ setIssueToEdit({
+ ...issue,
+ actionType: "edit",
+ cycle: issue.issue_cycle ? issue.issue_cycle.cycle : null,
+ module: issue.issue_module ? issue.issue_module.module : null,
+ });
+ };
+
+ const handleDeleteIssue = (issue: IIssue) => {
+ setDeleteIssueModal(true);
+ setIssueToDelete(issue);
+ };
+
+ const removeIssueFromCycle = (bridgeId: string) => {
+ if (!workspaceSlug || !projectId) return;
+
+ mutate(
+ CYCLE_ISSUES(cycleId as string),
+ (prevData) => prevData?.filter((p) => p.id !== bridgeId),
+ false
+ );
+
+ issuesService
+ .removeIssueFromCycle(
+ workspaceSlug as string,
+ projectId as string,
+ cycleId as string,
+ bridgeId
+ )
+ .then((res) => {
+ console.log(res);
+ })
+ .catch((e) => {
+ console.log(e);
+ });
+ };
+
+ const removeIssueFromModule = (bridgeId: string) => {
+ if (!workspaceSlug || !projectId) return;
+
+ mutate(
+ MODULE_ISSUES(moduleId as string),
+ (prevData) => prevData?.filter((p) => p.id !== bridgeId),
+ false
+ );
+
+ modulesService
+ .removeIssueFromModule(
+ workspaceSlug as string,
+ projectId as string,
+ moduleId as string,
+ bridgeId
+ )
+ .then((res) => {
+ console.log(res);
+ })
+ .catch((e) => {
+ console.log(e);
+ });
+ };
+
+ return (
+ <>
+ setCreateIssueModal(false)}
+ prePopulateData={{
+ ...preloadedData,
+ }}
+ />
+ setEditIssueModal(false)}
+ data={issueToEdit}
+ />
+ setDeleteIssueModal(false)}
+ isOpen={deleteIssueModal}
+ data={issueToDelete}
+ />
+ {issueView === "list" ? (
+
+ ) : (
+
+ )}
+ >
+ );
+};
diff --git a/apps/app/components/core/list-view/all-lists.tsx b/apps/app/components/core/list-view/all-lists.tsx
new file mode 100644
index 000000000..c2b6c498a
--- /dev/null
+++ b/apps/app/components/core/list-view/all-lists.tsx
@@ -0,0 +1,63 @@
+// hooks
+import useIssueView from "hooks/use-issue-view";
+// components
+import { SingleList } from "components/core/list-view/single-list";
+// types
+import { IIssue, IProjectMember, IState, UserAuth } from "types";
+
+// types
+type Props = {
+ type: "issue" | "cycle" | "module";
+ issues: IIssue[];
+ states: IState[] | undefined;
+ members: IProjectMember[] | undefined;
+ addIssueToState: (groupTitle: string, stateId: string | null) => void;
+ handleEditIssue: (issue: IIssue) => void;
+ handleDeleteIssue: (issue: IIssue) => void;
+ openIssuesListModal?: (() => void) | null;
+ removeIssue: ((bridgeId: string) => void) | null;
+ userAuth: UserAuth;
+};
+
+export const AllLists: React.FC = ({
+ type,
+ issues,
+ states,
+ members,
+ addIssueToState,
+ openIssuesListModal,
+ handleEditIssue,
+ handleDeleteIssue,
+ removeIssue,
+ userAuth,
+}) => {
+ const { groupedByIssues, groupByProperty: selectedGroup } = useIssueView(issues);
+
+ return (
+
+ {Object.keys(groupedByIssues).map((singleGroup) => {
+ const stateId =
+ selectedGroup === "state_detail.name"
+ ? states?.find((s) => s.name === singleGroup)?.id ?? null
+ : null;
+
+ return (
+ addIssueToState(singleGroup, stateId)}
+ handleEditIssue={handleEditIssue}
+ handleDeleteIssue={handleDeleteIssue}
+ openIssuesListModal={type !== "issue" ? openIssuesListModal : null}
+ removeIssue={removeIssue}
+ userAuth={userAuth}
+ />
+ );
+ })}
+
+ );
+};
diff --git a/apps/app/components/core/list-view/index.ts b/apps/app/components/core/list-view/index.ts
new file mode 100644
index 000000000..c515ed1c2
--- /dev/null
+++ b/apps/app/components/core/list-view/index.ts
@@ -0,0 +1,3 @@
+export * from "./all-lists";
+export * from "./single-issue";
+export * from "./single-list";
diff --git a/apps/app/components/core/list-view/single-issue.tsx b/apps/app/components/core/list-view/single-issue.tsx
new file mode 100644
index 000000000..b779db594
--- /dev/null
+++ b/apps/app/components/core/list-view/single-issue.tsx
@@ -0,0 +1,198 @@
+import React, { useCallback } from "react";
+
+import Link from "next/link";
+import { useRouter } from "next/router";
+
+import { mutate } from "swr";
+
+// services
+import issuesService from "services/issues.service";
+// components
+import {
+ ViewAssigneeSelect,
+ ViewDueDateSelect,
+ ViewPrioritySelect,
+ ViewStateSelect,
+} from "components/issues/view-select";
+// ui
+import { CustomMenu } from "components/ui";
+// types
+import {
+ CycleIssueResponse,
+ IIssue,
+ IssueResponse,
+ ModuleIssueResponse,
+ Properties,
+ UserAuth,
+} from "types";
+// fetch-keys
+import { CYCLE_ISSUES, MODULE_ISSUES, PROJECT_ISSUES_LIST, STATE_LIST } from "constants/fetch-keys";
+
+type Props = {
+ type?: string;
+ issue: IIssue;
+ properties: Properties;
+ editIssue: () => void;
+ removeIssue?: (() => void) | null;
+ handleDeleteIssue: (issue: IIssue) => void;
+ userAuth: UserAuth;
+};
+
+export const SingleListIssue: React.FC = ({
+ type,
+ issue,
+ properties,
+ editIssue,
+ removeIssue,
+ handleDeleteIssue,
+ userAuth,
+}) => {
+ const router = useRouter();
+ const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
+
+ const partialUpdateIssue = useCallback(
+ (formData: Partial) => {
+ if (!workspaceSlug || !projectId) return;
+
+ if (cycleId)
+ mutate(
+ CYCLE_ISSUES(cycleId as string),
+ (prevData) => {
+ const updatedIssues = (prevData ?? []).map((p) => {
+ if (p.issue_detail.id === issue.id) {
+ return {
+ ...p,
+ issue_detail: {
+ ...p.issue_detail,
+ ...formData,
+ },
+ };
+ }
+ return p;
+ });
+ return [...updatedIssues];
+ },
+ false
+ );
+
+ if (moduleId)
+ mutate(
+ MODULE_ISSUES(moduleId as string),
+ (prevData) => {
+ const updatedIssues = (prevData ?? []).map((p) => {
+ if (p.issue_detail.id === issue.id) {
+ return {
+ ...p,
+ issue_detail: {
+ ...p.issue_detail,
+ ...formData,
+ },
+ };
+ }
+ return p;
+ });
+ return [...updatedIssues];
+ },
+ false
+ );
+
+ mutate(
+ PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
+ (prevData) => ({
+ ...(prevData as IssueResponse),
+ results: (prevData?.results ?? []).map((p) => {
+ if (p.id === issue.id) return { ...p, ...formData };
+ return p;
+ }),
+ }),
+ false
+ );
+
+ issuesService
+ .patchIssue(workspaceSlug as string, projectId as string, issue.id, formData)
+ .then((res) => {
+ if (cycleId) mutate(CYCLE_ISSUES(cycleId as string));
+ if (moduleId) mutate(MODULE_ISSUES(moduleId as string));
+
+ mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string));
+ })
+ .catch((error) => {
+ console.log(error);
+ });
+ },
+ [workspaceSlug, projectId, cycleId, moduleId, issue]
+ );
+
+ const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
+
+ return (
+
+
+
+ {properties.priority && (
+
+ )}
+ {properties.state && (
+
+ )}
+ {properties.due_date && (
+
+ )}
+ {properties.sub_issue_count && (
+
+ {issue.sub_issues_count} {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
+
+ )}
+ {properties.assignee && (
+
+ )}
+ {type && !isNotAllowed && (
+
+ Edit
+ {type !== "issue" && removeIssue && (
+
+ <>Remove from {type}>
+
+ )}
+ handleDeleteIssue(issue)}>
+ Delete permanently
+
+
+ )}
+
+
+ );
+};
diff --git a/apps/app/components/core/list-view/single-list.tsx b/apps/app/components/core/list-view/single-list.tsx
new file mode 100644
index 000000000..4309b2d33
--- /dev/null
+++ b/apps/app/components/core/list-view/single-list.tsx
@@ -0,0 +1,155 @@
+import { useRouter } from "next/router";
+
+// headless ui
+import { Disclosure, Transition } from "@headlessui/react";
+// hooks
+import useIssuesProperties from "hooks/use-issue-properties";
+// components
+import { SingleListIssue } from "components/core";
+// icons
+import { ChevronDownIcon, PlusIcon } from "@heroicons/react/24/outline";
+// helpers
+import { addSpaceIfCamelCase } from "helpers/string.helper";
+// types
+import { IIssue, IProjectMember, NestedKeyOf, UserAuth } from "types";
+import { CustomMenu } from "components/ui";
+
+type Props = {
+ type?: "issue" | "cycle" | "module";
+ groupTitle: string;
+ groupedByIssues: {
+ [key: string]: IIssue[];
+ };
+ selectedGroup: NestedKeyOf | null;
+ members: IProjectMember[] | undefined;
+ addIssueToState: () => void;
+ handleEditIssue: (issue: IIssue) => void;
+ handleDeleteIssue: (issue: IIssue) => void;
+ openIssuesListModal?: (() => void) | null;
+ removeIssue: ((bridgeId: string) => void) | null;
+ userAuth: UserAuth;
+};
+
+export const SingleList: React.FC = ({
+ type,
+ groupTitle,
+ groupedByIssues,
+ selectedGroup,
+ members,
+ addIssueToState,
+ handleEditIssue,
+ handleDeleteIssue,
+ openIssuesListModal,
+ removeIssue,
+ userAuth,
+}) => {
+ const router = useRouter();
+ const { workspaceSlug, projectId } = router.query;
+
+ const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string);
+
+ const createdBy =
+ selectedGroup === "created_by"
+ ? members?.find((m) => m.member.id === groupTitle)?.member.first_name ?? "loading..."
+ : null;
+
+ return (
+
+ {({ open }) => (
+
+
+
+
+
+
+
+ {selectedGroup !== null ? (
+
+ {groupTitle === null || groupTitle === "null"
+ ? "None"
+ : createdBy
+ ? createdBy
+ : addSpaceIfCamelCase(groupTitle)}
+
+ ) : (
+
All Issues
+ )}
+
+ {groupedByIssues[groupTitle as keyof IIssue].length}
+
+
+
+
+
+
+
+ {groupedByIssues[groupTitle] ? (
+ groupedByIssues[groupTitle].length > 0 ? (
+ groupedByIssues[groupTitle].map((issue: IIssue) => (
+
handleEditIssue(issue)}
+ handleDeleteIssue={handleDeleteIssue}
+ removeIssue={() => {
+ removeIssue && removeIssue(issue.bridge);
+ }}
+ userAuth={userAuth}
+ />
+ ))
+ ) : (
+ No issues.
+ )
+ ) : (
+ Loading...
+ )}
+
+
+
+
+ {type === "issue" ? (
+
+
+ Add issue
+
+ ) : (
+
+
+ Add issue
+
+ }
+ optionsPosition="left"
+ noBorder
+ >
+ Create new
+ {openIssuesListModal && (
+
+ Add an existing issue
+
+ )}
+
+ )}
+
+
+ )}
+
+ );
+};
diff --git a/apps/app/components/cycles/modal.tsx b/apps/app/components/cycles/modal.tsx
index 9a53a2949..76e1c5ad1 100644
--- a/apps/app/components/cycles/modal.tsx
+++ b/apps/app/components/cycles/modal.tsx
@@ -61,9 +61,8 @@ export const CycleModal: React.FC = (props) => {
if (workspaceSlug && projectId) {
const payload = {
...formValues,
- start_date: formValues.start_date ? renderDateFormat(formValues.start_date) : null,
- end_date: formValues.end_date ? renderDateFormat(formValues.end_date) : null,
};
+
if (initialData) {
updateCycle(initialData.id, payload);
} else {
diff --git a/apps/app/components/dnd/StrictModeDroppable.tsx b/apps/app/components/dnd/StrictModeDroppable.tsx
index e63cab246..9ed01d3bf 100644
--- a/apps/app/components/dnd/StrictModeDroppable.tsx
+++ b/apps/app/components/dnd/StrictModeDroppable.tsx
@@ -1,4 +1,5 @@
import React, { useState, useEffect } from "react";
+
// react beautiful dnd
import { Droppable, DroppableProps } from "react-beautiful-dnd";
@@ -14,9 +15,7 @@ const StrictModeDroppable = ({ children, ...props }: DroppableProps) => {
};
}, []);
- if (!enabled) {
- return null;
- }
+ if (!enabled) return null;
return {children} ;
};
diff --git a/apps/app/components/emoji-icon-picker/index.tsx b/apps/app/components/emoji-icon-picker/index.tsx
index c2930e95b..a441cd4cb 100644
--- a/apps/app/components/emoji-icon-picker/index.tsx
+++ b/apps/app/components/emoji-icon-picker/index.tsx
@@ -7,7 +7,7 @@ import { Props } from "./types";
import emojis from "./emojis.json";
// helpers
import { getRecentEmojis, saveRecentEmoji } from "./helpers";
-import { getRandomEmoji } from "helpers/functions.helper";
+import { getRandomEmoji } from "helpers/common.helper";
// hooks
import useOutsideClickDetector from "hooks/use-outside-click-detector";
diff --git a/apps/app/constants/global.tsx b/apps/app/components/icons/priority-icon.tsx
similarity index 100%
rename from apps/app/constants/global.tsx
rename to apps/app/components/icons/priority-icon.tsx
diff --git a/apps/app/components/project/issues/issue-detail/activity/index.tsx b/apps/app/components/issues/activity.tsx
similarity index 92%
rename from apps/app/components/project/issues/issue-detail/activity/index.tsx
rename to apps/app/components/issues/activity.tsx
index 3594bf7a8..2bcc3853d 100644
--- a/apps/app/components/project/issues/issue-detail/activity/index.tsx
+++ b/apps/app/components/issues/activity.tsx
@@ -8,17 +8,18 @@ import {
CalendarDaysIcon,
ChartBarIcon,
ChatBubbleBottomCenterTextIcon,
+ RectangleGroupIcon,
Squares2X2Icon,
UserIcon,
} from "@heroicons/react/24/outline";
// services
import issuesServices from "services/issues.service";
// components
-import CommentCard from "components/project/issues/issue-detail/comment/issue-comment-card";
+import { CommentCard } from "components/issues/comment";
// ui
import { Loader } from "components/ui";
// icons
-import { BlockedIcon, BlockerIcon, TagIcon, UserGroupIcon } from "components/icons";
+import { BlockedIcon, BlockerIcon, CyclesIcon, TagIcon, UserGroupIcon } from "components/icons";
// helpers
import { renderShortNumericDateFormat, timeAgo } from "helpers/date-time.helper";
import { addSpaceIfCamelCase } from "helpers/string.helper";
@@ -47,9 +48,17 @@ const activityDetails: {
message: "marked this issue is blocking",
icon: ,
},
+ cycles: {
+ message: "set the cycle to",
+ icon: ,
+ },
labels: {
icon: ,
},
+ modules: {
+ message: "set the module to",
+ icon: ,
+ },
state: {
message: "set the state to",
icon: ,
@@ -76,10 +85,12 @@ const activityDetails: {
},
};
-const IssueActivitySection: React.FC<{
+type Props = {
issueActivities: IIssueActivity[];
mutate: KeyedMutator;
-}> = ({ issueActivities, mutate }) => {
+};
+
+export const IssueActivitySection: React.FC = ({ issueActivities, mutate }) => {
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
@@ -183,7 +194,9 @@ const IssueActivitySection: React.FC<{
?.message}{" "}
- {activity.verb === "created" ? (
+ {activity.verb === "created" &&
+ activity.field !== "cycles" &&
+ activity.field !== "modules" ? (
created this issue.
) : activity.field === "description" ? null : activity.field === "state" ? (
activity.new_value ? (
@@ -216,7 +229,7 @@ const IssueActivitySection: React.FC<{
);
- } else if ("comment_json" in activity) {
+ } else if ("comment_json" in activity)
return (
);
- }
})}
) : (
@@ -247,5 +259,3 @@ const IssueActivitySection: React.FC<{
>
);
};
-
-export default IssueActivitySection;
diff --git a/apps/app/components/project/issues/issue-detail/comment/issue-comment-section.tsx b/apps/app/components/issues/comment/add-comment.tsx
similarity index 95%
rename from apps/app/components/project/issues/issue-detail/comment/issue-comment-section.tsx
rename to apps/app/components/issues/comment/add-comment.tsx
index 5ac7e061a..9b31a2423 100644
--- a/apps/app/components/project/issues/issue-detail/comment/issue-comment-section.tsx
+++ b/apps/app/components/issues/comment/add-comment.tsx
@@ -10,7 +10,7 @@ import issuesServices from "services/issues.service";
// ui
import { Loader } from "components/ui";
// helpers
-import { debounce } from "helpers/functions.helper";
+import { debounce } from "helpers/common.helper";
// types
import type { IIssueActivity, IIssueComment } from "types";
import type { KeyedMutator } from "swr";
@@ -28,7 +28,8 @@ const defaultValues: Partial = {
comment_html: "",
comment_json: "",
};
-const AddIssueComment: React.FC<{
+
+export const AddComment: React.FC<{
mutate: KeyedMutator;
}> = ({ mutate }) => {
const {
@@ -111,5 +112,3 @@ const AddIssueComment: React.FC<{
);
};
-
-export default AddIssueComment;
diff --git a/apps/app/components/project/issues/issue-detail/comment/issue-comment-card.tsx b/apps/app/components/issues/comment/comment-card.tsx
similarity index 97%
rename from apps/app/components/project/issues/issue-detail/comment/issue-comment-card.tsx
rename to apps/app/components/issues/comment/comment-card.tsx
index ec270ff25..79582df3f 100644
--- a/apps/app/components/project/issues/issue-detail/comment/issue-comment-card.tsx
+++ b/apps/app/components/issues/comment/comment-card.tsx
@@ -24,7 +24,7 @@ type Props = {
handleCommentDeletion: (comment: string) => void;
};
-const CommentCard: React.FC = ({ comment, onSubmit, handleCommentDeletion }) => {
+export const CommentCard: React.FC = ({ comment, onSubmit, handleCommentDeletion }) => {
const { user } = useUser();
const [isEditing, setIsEditing] = useState(false);
@@ -130,5 +130,3 @@ const CommentCard: React.FC = ({ comment, onSubmit, handleCommentDeletion
);
};
-
-export default CommentCard;
diff --git a/apps/app/components/issues/comment/index.ts b/apps/app/components/issues/comment/index.ts
new file mode 100644
index 000000000..cf13ca91e
--- /dev/null
+++ b/apps/app/components/issues/comment/index.ts
@@ -0,0 +1,2 @@
+export * from "./add-comment";
+export * from "./comment-card";
diff --git a/apps/app/components/project/issues/confirm-issue-deletion.tsx b/apps/app/components/issues/delete-issue-modal.tsx
similarity index 92%
rename from apps/app/components/project/issues/confirm-issue-deletion.tsx
rename to apps/app/components/issues/delete-issue-modal.tsx
index 12b8c63e9..bbc6552ba 100644
--- a/apps/app/components/project/issues/confirm-issue-deletion.tsx
+++ b/apps/app/components/issues/delete-issue-modal.tsx
@@ -17,20 +17,20 @@ import { Button } from "components/ui";
// types
import type { CycleIssueResponse, IIssue, IssueResponse, ModuleIssueResponse } from "types";
// fetch-keys
-import { CYCLE_ISSUES, PROJECT_ISSUES_LIST, MODULE_ISSUES } from "constants/fetch-keys";
+import { CYCLE_ISSUES, PROJECT_ISSUES_LIST, MODULE_ISSUES, USER_ISSUE } from "constants/fetch-keys";
type Props = {
isOpen: boolean;
handleClose: () => void;
- data?: IIssue;
+ data: IIssue | null;
};
-const ConfirmIssueDeletion: React.FC = ({ isOpen, handleClose, data }) => {
+export const DeleteIssueModal: React.FC = ({ isOpen, handleClose, data }) => {
const cancelButtonRef = useRef(null);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const router = useRouter();
- const { workspaceSlug } = router.query;
+ const { workspaceSlug, projectId: queryProjectId } = router.query;
const { setToastAlert } = useToast();
@@ -43,15 +43,40 @@ const ConfirmIssueDeletion: React.FC = ({ isOpen, handleClose, data }) =>
handleClose();
};
- console.log(data);
-
const handleDeletion = async () => {
setIsDeleteLoading(true);
if (!data || !workspaceSlug) return;
+
const projectId = data.project;
await issueServices
.deleteIssue(workspaceSlug as string, projectId, data.id)
.then(() => {
+ const cycleId = data?.cycle;
+ const moduleId = data?.module;
+
+ if (cycleId) {
+ mutate(
+ CYCLE_ISSUES(cycleId),
+ (prevData) => prevData?.filter((i) => i.issue !== data.id),
+ false
+ );
+ }
+
+ if (moduleId) {
+ mutate(
+ MODULE_ISSUES(moduleId),
+ (prevData) => prevData?.filter((i) => i.issue !== data.id),
+ false
+ );
+ }
+
+ if (!queryProjectId)
+ mutate(
+ USER_ISSUE(workspaceSlug as string),
+ (prevData) => prevData?.filter((i) => i.id !== data.id),
+ false
+ );
+
mutate(
PROJECT_ISSUES_LIST(workspaceSlug as string, projectId),
(prevData) => ({
@@ -62,24 +87,6 @@ const ConfirmIssueDeletion: React.FC = ({ isOpen, handleClose, data }) =>
false
);
- const moduleId = data?.module;
- const cycleId = data?.cycle;
-
- if (moduleId) {
- mutate(
- MODULE_ISSUES(moduleId),
- (prevData) => prevData?.filter((i) => i.issue !== data.id),
- false
- );
- }
- if (cycleId) {
- mutate(
- CYCLE_ISSUES(cycleId),
- (prevData) => prevData?.filter((i) => i.issue !== data.id),
- false
- );
- }
-
handleClose();
setToastAlert({
title: "Success",
@@ -173,5 +180,3 @@ const ConfirmIssueDeletion: React.FC = ({ isOpen, handleClose, data }) =>
);
};
-
-export default ConfirmIssueDeletion;
diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx
index a2233fc52..5e034822a 100644
--- a/apps/app/components/issues/form.tsx
+++ b/apps/app/components/issues/form.tsx
@@ -16,7 +16,7 @@ import {
IssueStateSelect,
} from "components/issues/select";
import { CycleSelect as IssueCycleSelect } from "components/cycles/select";
-import CreateUpdateStateModal from "components/project/issues/BoardView/state/create-update-state-modal";
+import { CreateUpdateStateModal } from "components/states";
import CreateUpdateCycleModal from "components/project/cycles/create-update-cycle-modal";
// ui
import { Button, CustomDatePicker, CustomMenu, Input, Loader } from "components/ui";
diff --git a/apps/app/components/issues/index.ts b/apps/app/components/issues/index.ts
index 5608866d4..ab62034b5 100644
--- a/apps/app/components/issues/index.ts
+++ b/apps/app/components/issues/index.ts
@@ -1,5 +1,12 @@
-export * from "./list-item";
+export * from "./comment";
+export * from "./sidebar-select";
+export * from "./activity";
+export * from "./delete-issue-modal";
export * from "./description-form";
-export * from "./sub-issue-list";
export * from "./form";
export * from "./modal";
+export * from "./my-issues-list-item";
+export * from "./parent-issues-list-modal";
+export * from "./sidebar";
+export * from "./sub-issues-list";
+export * from "./sub-issues-list-modal";
diff --git a/apps/app/components/issues/list-item.tsx b/apps/app/components/issues/list-item.tsx
deleted file mode 100644
index 603d8d299..000000000
--- a/apps/app/components/issues/list-item.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import React from "react";
-
-import Link from "next/link";
-import { useRouter } from "next/router";
-
-// components
-import { AssigneesList } from "components/ui/avatar";
-// icons
-import { CalendarDaysIcon } from "@heroicons/react/24/outline";
-// helpers
-import { renderShortNumericDateFormat, findHowManyDaysLeft } from "helpers/date-time.helper";
-import { addSpaceIfCamelCase } from "helpers/string.helper";
-// types
-import { IIssue, Properties } from "types";
-// constants
-import { getPriorityIcon } from "constants/global";
-
-type Props = {
- type?: string;
- issue: IIssue;
- properties: Properties;
- editIssue?: () => void;
- handleDeleteIssue?: () => void;
- removeIssue?: () => void;
-};
-
-export const IssueListItem: React.FC = (props) => {
- // const { type, issue, properties, editIssue, handleDeleteIssue, removeIssue } = props;
- const { issue, properties } = props;
- // router
- const router = useRouter();
- const { workspaceSlug } = router.query;
-
- return (
-
-
-
- {properties.priority && (
-
- {getPriorityIcon(issue.priority)}
-
-
Priority
-
- {issue.priority ?? "None"}
-
-
-
- )}
- {properties.state && (
-
-
- {addSpaceIfCamelCase(issue?.state_detail.name)}
-
-
State
-
{issue?.state_detail.name}
-
-
- )}
- {properties.due_date && (
-
-
- {issue.target_date ? renderShortNumericDateFormat(issue.target_date) : "N/A"}
-
-
Due date
-
{renderShortNumericDateFormat(issue.target_date ?? "")}
-
- {issue.target_date &&
- (issue.target_date < new Date().toISOString()
- ? `Due date has passed by ${findHowManyDaysLeft(issue.target_date)} days`
- : findHowManyDaysLeft(issue.target_date) <= 3
- ? `Due date is in ${findHowManyDaysLeft(issue.target_date)} days`
- : "Due date")}
-
-
-
- )}
- {properties.sub_issue_count && (
-
- {issue?.sub_issues_count} {issue?.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
-
- )}
- {properties.assignee && (
-
- )}
-
-
- );
-};
diff --git a/apps/app/components/issues/modal.tsx b/apps/app/components/issues/modal.tsx
index 3f7555435..c7ff0c2d7 100644
--- a/apps/app/components/issues/modal.tsx
+++ b/apps/app/components/issues/modal.tsx
@@ -16,11 +16,7 @@ import issuesService from "services/issues.service";
import useUser from "hooks/use-user";
import useToast from "hooks/use-toast";
// components
-import CreateUpdateStateModal from "components/project/issues/BoardView/state/create-update-state-modal";
-import CreateUpdateCycleModal from "components/project/cycles/create-update-cycle-modal";
import { IssueForm } from "components/issues";
-// common
-import { renderDateFormat } from "helpers/date-time.helper";
// types
import type { IIssue, IssueResponse } from "types";
// fetch keys
@@ -54,7 +50,10 @@ export const CreateUpdateIssueModal: React.FC = ({
const [activeProject, setActiveProject] = useState(null);
const router = useRouter();
- const { workspaceSlug, projectId } = router.query;
+ const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
+
+ if (cycleId) prePopulateData = { ...prePopulateData, cycle: cycleId as string };
+ if (moduleId) prePopulateData = { ...prePopulateData, module: moduleId as string };
const { user } = useUser();
const { setToastAlert } = useToast();
@@ -176,7 +175,7 @@ export const CreateUpdateIssueModal: React.FC = ({
.then((res) => {
if (isUpdatingSingleIssue) {
mutate(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false);
- } else
+ } else {
mutate(
PROJECT_ISSUES_LIST(workspaceSlug as string, activeProject ?? ""),
(prevData) => ({
@@ -187,8 +186,10 @@ export const CreateUpdateIssueModal: React.FC = ({
}),
})
);
+ }
if (payload.cycle && payload.cycle !== "") addIssueToCycle(res.id, payload.cycle);
+ if (payload.module && payload.module !== "") addIssueToModule(res.id, payload.module);
if (!createMore) handleClose();
@@ -206,15 +207,16 @@ export const CreateUpdateIssueModal: React.FC = ({
};
const handleFormSubmit = async (formData: Partial) => {
- if (workspaceSlug && activeProject) {
- const payload: Partial = {
- ...formData,
- target_date: formData.target_date ? renderDateFormat(formData.target_date ?? "") : null,
- };
+ if (!workspaceSlug || !activeProject) return;
- if (!data) await createIssue(payload);
- else await updateIssue(payload);
- }
+ const payload: Partial = {
+ ...formData,
+ description: formData.description ? formData.description : "",
+ description_html: formData.description_html ? formData.description_html : "
",
+ };
+
+ if (!data) await createIssue(payload);
+ else await updateIssue(payload);
};
return (
diff --git a/apps/app/components/issues/my-issues-list-item.tsx b/apps/app/components/issues/my-issues-list-item.tsx
new file mode 100644
index 000000000..130c777af
--- /dev/null
+++ b/apps/app/components/issues/my-issues-list-item.tsx
@@ -0,0 +1,127 @@
+import React, { useCallback } from "react";
+
+import Link from "next/link";
+import { useRouter } from "next/router";
+
+import { mutate } from "swr";
+
+// services
+import issuesService from "services/issues.service";
+// components
+import {
+ ViewDueDateSelect,
+ ViewPrioritySelect,
+ ViewStateSelect,
+} from "components/issues/view-select";
+// ui
+import { AssigneesList } from "components/ui/avatar";
+import { CustomMenu } from "components/ui";
+// types
+import { IIssue, Properties } from "types";
+// fetch-keys
+import { USER_ISSUE } from "constants/fetch-keys";
+
+type Props = {
+ issue: IIssue;
+ properties: Properties;
+ projectId: string;
+ handleDeleteIssue: () => void;
+};
+
+export const MyIssuesListItem: React.FC = ({
+ issue,
+ properties,
+ projectId,
+ handleDeleteIssue,
+}) => {
+ const router = useRouter();
+ const { workspaceSlug } = router.query;
+
+ const partialUpdateIssue = useCallback(
+ (formData: Partial) => {
+ if (!workspaceSlug) return;
+
+ mutate(
+ USER_ISSUE(workspaceSlug as string),
+ (prevData) =>
+ prevData?.map((p) => {
+ if (p.id === issue.id) return { ...p, ...formData };
+
+ return p;
+ }),
+ false
+ );
+
+ issuesService
+ .patchIssue(workspaceSlug as string, projectId as string, issue.id, formData)
+ .then((res) => {
+ mutate(USER_ISSUE(workspaceSlug as string));
+ })
+ .catch((error) => {
+ console.log(error);
+ });
+ },
+ [workspaceSlug, projectId, issue]
+ );
+
+ const isNotAllowed = false;
+
+ return (
+
+
+
+ {properties.priority && (
+
+ )}
+ {properties.state && (
+
+ )}
+ {properties.due_date && (
+
+ )}
+ {properties.sub_issue_count && (
+
+ {issue?.sub_issues_count} {issue?.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
+
+ )}
+ {properties.assignee && (
+
+ )}
+
+ Delete permanently
+
+
+
+ );
+};
diff --git a/apps/app/components/project/issues/issues-list-modal.tsx b/apps/app/components/issues/parent-issues-list-modal.tsx
similarity index 98%
rename from apps/app/components/project/issues/issues-list-modal.tsx
rename to apps/app/components/issues/parent-issues-list-modal.tsx
index 9c56d6406..dc4de5329 100644
--- a/apps/app/components/project/issues/issues-list-modal.tsx
+++ b/apps/app/components/issues/parent-issues-list-modal.tsx
@@ -21,7 +21,7 @@ type Props = {
customDisplay?: JSX.Element;
};
-const IssuesListModal: React.FC = ({
+export const ParentIssuesListModal: React.FC = ({
isOpen,
handleClose: onClose,
value,
@@ -212,7 +212,7 @@ const IssuesListModal: React.FC = ({
No issues found. Create a new issue with{" "}
- C .
+ C .
)}
@@ -227,5 +227,3 @@ const IssuesListModal: React.FC = ({
>
);
};
-
-export default IssuesListModal;
diff --git a/apps/app/components/issues/select/index.ts b/apps/app/components/issues/select/index.ts
index de43d9b0e..4338b3162 100644
--- a/apps/app/components/issues/select/index.ts
+++ b/apps/app/components/issues/select/index.ts
@@ -1,6 +1,6 @@
export * from "./assignee";
export * from "./label";
-export * from "./parent-issue";
+export * from "./parent";
export * from "./priority";
export * from "./project";
export * from "./state";
diff --git a/apps/app/components/issues/select/label.tsx b/apps/app/components/issues/select/label.tsx
index 42f441cba..b1b1c4338 100644
--- a/apps/app/components/issues/select/label.tsx
+++ b/apps/app/components/issues/select/label.tsx
@@ -72,7 +72,7 @@ export const IssueLabelSelect: React.FC = ({ value, onChange, projectId }
const options = issueLabels?.map((label) => ({
value: label.id,
display: label.name,
- color: label.colour,
+ color: label.color,
}));
const filteredOptions =
diff --git a/apps/app/components/issues/select/parent-issue.tsx b/apps/app/components/issues/select/parent.tsx
similarity index 86%
rename from apps/app/components/issues/select/parent-issue.tsx
rename to apps/app/components/issues/select/parent.tsx
index d08020d20..c04e89b92 100644
--- a/apps/app/components/issues/select/parent-issue.tsx
+++ b/apps/app/components/issues/select/parent.tsx
@@ -1,7 +1,7 @@
import React from "react";
import { Controller, Control } from "react-hook-form";
// components
-import IssuesListModal from "components/project/issues/issues-list-modal";
+import { ParentIssuesListModal } from "components/issues";
// types
import type { IIssue } from "types";
@@ -17,7 +17,7 @@ export const IssueParentSelect: React.FC = ({ control, isOpen, setIsOpen,
control={control}
name="parent"
render={({ field: { onChange } }) => (
- setIsOpen(false)}
onChange={onChange}
diff --git a/apps/app/components/issues/select/priority.tsx b/apps/app/components/issues/select/priority.tsx
index e85f2deac..1347e2765 100644
--- a/apps/app/components/issues/select/priority.tsx
+++ b/apps/app/components/issues/select/priority.tsx
@@ -2,9 +2,10 @@ import React from "react";
// headless ui
import { Listbox, Transition } from "@headlessui/react";
+// icons
+import { getPriorityIcon } from "components/icons/priority-icon";
// constants
-import { getPriorityIcon } from "constants/global";
-import { PRIORITIES } from "constants/";
+import { PRIORITIES } from "constants/project";
type Props = {
value: string | null;
diff --git a/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-assignee.tsx b/apps/app/components/issues/sidebar-select/assignee.tsx
similarity index 98%
rename from apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-assignee.tsx
rename to apps/app/components/issues/sidebar-select/assignee.tsx
index ceefa5e7a..369d03368 100644
--- a/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-assignee.tsx
+++ b/apps/app/components/issues/sidebar-select/assignee.tsx
@@ -27,7 +27,7 @@ type Props = {
userAuth: UserAuth;
};
-const SelectAssignee: React.FC = ({ control, submitChanges, userAuth }) => {
+export const SidebarAssigneeSelect: React.FC = ({ control, submitChanges, userAuth }) => {
const router = useRouter();
const { workspaceSlug } = router.query;
@@ -143,5 +143,3 @@ const SelectAssignee: React.FC = ({ control, submitChanges, userAuth }) =
);
};
-
-export default SelectAssignee;
diff --git a/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-blocked.tsx b/apps/app/components/issues/sidebar-select/blocked.tsx
similarity index 97%
rename from apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-blocked.tsx
rename to apps/app/components/issues/sidebar-select/blocked.tsx
index 0e8ec0881..38da455bd 100644
--- a/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-blocked.tsx
+++ b/apps/app/components/issues/sidebar-select/blocked.tsx
@@ -16,7 +16,7 @@ import issuesService from "services/issues.service";
// ui
import { Button } from "components/ui";
// icons
-import { FolderIcon, MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/outline";
+import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { BlockedIcon, LayerDiagonalIcon } from "components/icons";
// types
import { IIssue, UserAuth } from "types";
@@ -34,7 +34,12 @@ type Props = {
userAuth: UserAuth;
};
-const SelectBlocked: React.FC = ({ submitChanges, issuesList, watch, userAuth }) => {
+export const SidebarBlockedSelect: React.FC = ({
+ submitChanges,
+ issuesList,
+ watch,
+ userAuth,
+}) => {
const [query, setQuery] = useState("");
const [isBlockedModalOpen, setIsBlockedModalOpen] = useState(false);
@@ -172,7 +177,6 @@ const SelectBlocked: React.FC = ({ submitChanges, issuesList, watch, user
-
+
);
};
diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx
new file mode 100644
index 000000000..b75adef7b
--- /dev/null
+++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx
@@ -0,0 +1,187 @@
+import React from "react";
+
+import { useRouter } from "next/router";
+
+import useSWR, { mutate } from "swr";
+
+// services
+import projectService from "services/project.service";
+// lib
+import { requiredAdmin } from "lib/auth";
+// layouts
+import AppLayout from "layouts/app-layout";
+// hooks
+import useToast from "hooks/use-toast";
+// ui
+import { Button } from "components/ui";
+import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
+// types
+import { IProject, UserAuth } from "types";
+import type { NextPage, NextPageContext } from "next";
+// fetch-keys
+import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
+
+const FeaturesSettings: NextPage = (props) => {
+ const router = useRouter();
+ const { workspaceSlug, projectId } = router.query;
+
+ const { setToastAlert } = useToast();
+
+ const { data: projectDetails } = useSWR(
+ workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
+ workspaceSlug && projectId
+ ? () => projectService.getProject(workspaceSlug as string, projectId as string)
+ : null
+ );
+
+ const handleSubmit = async (formData: Partial) => {
+ if (!workspaceSlug || !projectId) return;
+
+ mutate(
+ PROJECT_DETAILS(projectId as string),
+ (prevData) => ({ ...(prevData as IProject), ...formData }),
+ false
+ );
+
+ mutate(
+ PROJECTS_LIST(workspaceSlug as string),
+ (prevData) =>
+ prevData?.map((p) => {
+ if (p.id === projectId)
+ return {
+ ...p,
+ ...formData,
+ };
+
+ return p;
+ }),
+ false
+ );
+
+ await projectService
+ .updateProject(workspaceSlug as string, projectId as string, formData)
+ .then((res) => {
+ mutate(PROJECT_DETAILS(projectId as string));
+ mutate(PROJECTS_LIST(workspaceSlug as string));
+
+ setToastAlert({
+ title: "Success!",
+ type: "success",
+ message: "Project features updated successfully.",
+ });
+ })
+ .catch((err) => {
+ console.error(err);
+ });
+ };
+
+ return (
+
+
+
+
+ }
+ >
+
+
+
Project Features
+
+
+
+
+
Use cycles
+
+ Cycles are enabled for all the projects in this workspace. Access it from the
+ navigation bar.
+
+
+
+ handleSubmit({ cycle_view: !projectDetails?.cycle_view })}
+ >
+ Use cycles
+
+
+
+
+
+
+
Use modules
+
+ Modules are enabled for all the projects in this workspace. Access it from the
+ navigation bar.
+
+
+
+ handleSubmit({ module_view: !projectDetails?.module_view })}
+ >
+ Use cycles
+
+
+
+
+
+
+
+
+ );
+};
+
+export const getServerSideProps = async (ctx: NextPageContext) => {
+ const projectId = ctx.query.projectId as string;
+ const workspaceSlug = ctx.query.workspaceSlug as string;
+
+ const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
+
+ return {
+ props: {
+ isOwner: memberDetail?.role === 20,
+ isMember: memberDetail?.role === 15,
+ isViewer: memberDetail?.role === 10,
+ isGuest: memberDetail?.role === 5,
+ },
+ };
+};
+
+export default FeaturesSettings;
diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx
index 1e9cc6e16..e85000f11 100644
--- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx
+++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx
@@ -6,11 +6,11 @@ import useSWR, { mutate } from "swr";
// react-hook-form
import { Controller, useForm } from "react-hook-form";
-import { IProject, IWorkspace } from "types";
+import { IProject, IWorkspace, UserAuth } from "types";
// lib
import { requiredAdmin } from "lib/auth";
// layouts
-import SettingsLayout from "layouts/settings-layout";
+import AppLayout from "layouts/app-layout";
// services
import projectService from "services/project.service";
import workspaceService from "services/workspace.service";
@@ -24,13 +24,13 @@ import { Button, Input, TextArea, Loader, CustomSelect } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
import OutlineButton from "components/ui/outline-button";
// helpers
-import { debounce } from "helpers/functions.helper";
+import { debounce } from "helpers/common.helper";
// types
import type { NextPage, NextPageContext } from "next";
// fetch-keys
import { PROJECTS_LIST, PROJECT_DETAILS, WORKSPACE_DETAILS } from "constants/fetch-keys";
// constants
-import { NETWORK_CHOICES } from "constants/";
+import { NETWORK_CHOICES } from "constants/project";
const defaultValues: Partial = {
name: "",
@@ -39,14 +39,7 @@ const defaultValues: Partial = {
network: 0,
};
-type TGeneralSettingsProps = {
- isMember: boolean;
- isOwner: boolean;
- isViewer: boolean;
- isGuest: boolean;
-};
-
-const GeneralSettings: NextPage = (props) => {
+const GeneralSettings: NextPage = (props) => {
const { isMember, isOwner, isViewer, isGuest } = props;
const [selectProject, setSelectedProject] = useState(null);
@@ -100,6 +93,7 @@ const GeneralSettings: NextPage = (props) => {
const onSubmit = async (formData: IProject) => {
if (!activeWorkspace || !projectDetails) return;
+
const payload: Partial = {
name: formData.name,
network: formData.network,
@@ -109,6 +103,7 @@ const GeneralSettings: NextPage = (props) => {
project_lead: formData.project_lead,
icon: formData.icon,
};
+
await projectService
.updateProject(activeWorkspace.slug, projectDetails.id, payload)
.then((res) => {
@@ -130,9 +125,9 @@ const GeneralSettings: NextPage = (props) => {
};
return (
-
= (props) => {
-
+
);
};
diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx
index 568f62392..b82f76d9e 100644
--- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx
+++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx
@@ -1,11 +1,15 @@
import React, { useState } from "react";
+
import { useRouter } from "next/router";
+
import useSWR from "swr";
+
+// react-hook-form
import { Controller, SubmitHandler, useForm } from "react-hook-form";
-import { PlusIcon } from "@heroicons/react/24/outline";
-import { Popover, Transition } from "@headlessui/react";
+// react-color
import { TwitterPicker } from "react-color";
-import type { NextPageContext, NextPage } from "next";
+// headless ui
+import { Popover, Transition } from "@headlessui/react";
// services
import projectService from "services/project.service";
import workspaceService from "services/workspace.service";
@@ -13,20 +17,23 @@ import issuesService from "services/issues.service";
// lib
import { requiredAdmin } from "lib/auth";
// layouts
-import SettingsLayout from "layouts/settings-layout";
+import AppLayout from "layouts/app-layout";
// components
import SingleLabel from "components/project/settings/single-label";
// ui
import { Button, Input, Loader } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
-// fetch-keys
-import { PROJECT_DETAILS, PROJECT_ISSUE_LABELS, WORKSPACE_DETAILS } from "constants/fetch-keys";
+// icons
+import { PlusIcon } from "@heroicons/react/24/outline";
// types
import { IIssueLabels } from "types";
+import type { NextPageContext, NextPage } from "next";
+// fetch-keys
+import { PROJECT_DETAILS, PROJECT_ISSUE_LABELS, WORKSPACE_DETAILS } from "constants/fetch-keys";
const defaultValues: Partial = {
name: "",
- colour: "#ff0000",
+ color: "#ff0000",
};
type TLabelSettingsProps = {
@@ -52,7 +59,7 @@ const LabelsSettings: NextPage = (props) => {
() => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null)
);
- const { data: activeProject } = useSWR(
+ const { data: projectDetails } = useSWR(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
workspaceSlug && projectId
? () => projectService.getProject(workspaceSlug as string, projectId as string)
@@ -77,9 +84,9 @@ const LabelsSettings: NextPage = (props) => {
);
const handleNewLabel: SubmitHandler = async (formData) => {
- if (!activeWorkspace || !activeProject || isSubmitting) return;
+ if (!activeWorkspace || !projectDetails || isSubmitting) return;
await issuesService
- .createIssueLabel(activeWorkspace.slug, activeProject.id, formData)
+ .createIssueLabel(activeWorkspace.slug, projectDetails.id, formData)
.then((res) => {
reset(defaultValues);
mutate((prevData) => [...(prevData ?? []), res], false);
@@ -89,16 +96,17 @@ const LabelsSettings: NextPage = (props) => {
const editLabel = (label: IIssueLabels) => {
setNewLabelForm(true);
- setValue("colour", label.colour);
+ setValue("color", label.color);
setValue("name", label.name);
setIsUpdating(true);
setLabelIdForUpdate(label.id);
};
const handleLabelUpdate: SubmitHandler = async (formData) => {
- if (!activeWorkspace || !activeProject || isSubmitting) return;
+ if (!activeWorkspace || !projectDetails || isSubmitting) return;
+
await issuesService
- .patchIssueLabel(activeWorkspace.slug, activeProject.id, labelIdForUpdate ?? "", formData)
+ .patchIssueLabel(activeWorkspace.slug, projectDetails.id, labelIdForUpdate ?? "", formData)
.then((res) => {
console.log(res);
reset(defaultValues);
@@ -112,10 +120,10 @@ const LabelsSettings: NextPage = (props) => {
};
const handleLabelDelete = (labelId: string) => {
- if (activeWorkspace && activeProject) {
+ if (activeWorkspace && projectDetails) {
mutate((prevData) => prevData?.filter((p) => p.id !== labelId), false);
issuesService
- .deleteIssueLabel(activeWorkspace.slug, activeProject.id, labelId)
+ .deleteIssueLabel(activeWorkspace.slug, projectDetails.id, labelId)
.then((res) => {
console.log(res);
})
@@ -126,14 +134,14 @@ const LabelsSettings: NextPage = (props) => {
};
return (
-
@@ -170,13 +178,13 @@ const LabelsSettings: NextPage = (props) => {
open ? "text-gray-900" : "text-gray-500"
}`}
>
- {watch("colour") && watch("colour") !== "" && (
+ {watch("color") && watch("color") !== "" && (
+ />
)}
@@ -191,7 +199,7 @@ const LabelsSettings: NextPage = (props) => {
>
(
= (props) => {
>
-
+
);
};
diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx
index 0c9e90029..f8510b73a 100644
--- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx
+++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx
@@ -2,9 +2,8 @@ import { useState } from "react";
import Image from "next/image";
import { useRouter } from "next/router";
+
import useSWR from "swr";
-import { PlusIcon } from "@heroicons/react/24/outline";
-import type { NextPage, NextPageContext } from "next";
// services
import projectService from "services/project.service";
@@ -13,10 +12,8 @@ import workspaceService from "services/workspace.service";
import { requiredAdmin } from "lib/auth";
// hooks
import useToast from "hooks/use-toast";
-// constants
-import { ROLE } from "constants/";
// layouts
-import SettingsLayout from "layouts/settings-layout";
+import AppLayout from "layouts/app-layout";
// components
import ConfirmProjectMemberRemove from "components/project/confirm-project-member-remove";
import SendProjectInvitationModal from "components/project/send-project-invitation-modal";
@@ -24,6 +21,9 @@ import SendProjectInvitationModal from "components/project/send-project-invitati
import { Button, CustomListbox, CustomMenu, Loader } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons
+import { PlusIcon } from "@heroicons/react/24/outline";
+// types
+import type { NextPage, NextPageContext } from "next";
// fetch-keys
import {
PROJECT_DETAILS,
@@ -31,6 +31,8 @@ import {
PROJECT_MEMBERS,
WORKSPACE_DETAILS,
} from "constants/fetch-keys";
+// constants
+import { ROLE } from "constants/workspace";
type TMemberSettingsProps = {
isMember: boolean;
@@ -58,7 +60,7 @@ const MembersSettings: NextPage = (props) => {
() => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null)
);
- const { data: activeProject } = useSWR(
+ const { data: projectDetails } = useSWR(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
workspaceSlug && projectId
? () => projectService.getProject(workspaceSlug as string, projectId as string)
@@ -120,11 +122,11 @@ const MembersSettings: NextPage = (props) => {
(item) => item.id === selectedRemoveMember || item.id === selectedInviteRemoveMember
)}
handleDelete={async () => {
- if (!activeWorkspace || !activeProject) return;
+ if (!activeWorkspace || !projectDetails) return;
if (selectedRemoveMember) {
await projectService.deleteProjectMember(
activeWorkspace.slug,
- activeProject.id,
+ projectDetails.id,
selectedRemoveMember
);
mutateMembers(
@@ -135,7 +137,7 @@ const MembersSettings: NextPage = (props) => {
if (selectedInviteRemoveMember) {
await projectService.deleteProjectInvitation(
activeWorkspace.slug,
- activeProject.id,
+ projectDetails.id,
selectedInviteRemoveMember
);
mutateInvitations(
@@ -155,14 +157,14 @@ const MembersSettings: NextPage = (props) => {
setIsOpen={setInviteModal}
members={members}
/>
-
@@ -235,11 +237,11 @@ const MembersSettings: NextPage = (props) => {
title={ROLE[member.role as keyof typeof ROLE] ?? "Select Role"}
value={member.role}
onChange={(value) => {
- if (!activeWorkspace || !activeProject) return;
+ if (!activeWorkspace || !projectDetails) return;
projectService
.updateProjectMember(
activeWorkspace.slug,
- activeProject.id,
+ projectDetails.id,
member.id,
{
role: value,
@@ -306,7 +308,7 @@ const MembersSettings: NextPage = (props) => {
)}
-
+
>
);
};
diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx
index 1234b7063..52a095b2a 100644
--- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx
+++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx
@@ -11,13 +11,9 @@ import projectService from "services/project.service";
// lib
import { requiredAdmin } from "lib/auth";
// layouts
-import SettingsLayout from "layouts/settings-layout";
+import AppLayout from "layouts/app-layout";
// components
-import ConfirmStateDeletion from "components/project/issues/BoardView/state/confirm-state-delete";
-import {
- CreateUpdateStateInline,
- StateGroup,
-} from "components/project/issues/BoardView/state/create-update-state-inline";
+import { CreateUpdateStateInline, DeleteStateModal, StateGroup } from "components/states";
// ui
import { Loader } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
@@ -47,7 +43,7 @@ const StatesSettings: NextPage = (props) => {
query: { workspaceSlug, projectId },
} = useRouter();
- const { data: activeProject } = useSWR(
+ const { data: projectDetails } = useSWR(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
workspaceSlug && projectId
? () => projectService.getProject(workspaceSlug as string, projectId as string)
@@ -67,19 +63,19 @@ const StatesSettings: NextPage = (props) => {
return (
<>
- state.id === selectDeleteState) ?? null}
onClose={() => setSelectDeleteState(null)}
/>
-
@@ -91,7 +87,7 @@ const StatesSettings: NextPage = (props) => {
Manage the state of this project.
- {states && activeProject ? (
+ {states && projectDetails ? (
Object.keys(groupedStates).map((key) => (
@@ -108,7 +104,7 @@ const StatesSettings: NextPage
= (props) => {
{key === activeGroup && (
{
setActiveGroup(null);
setSelectedState(null);
@@ -147,7 +143,7 @@ const StatesSettings: NextPage = (props) => {
) : (
{
setActiveGroup(null);
setSelectedState(null);
@@ -172,7 +168,7 @@ const StatesSettings: NextPage = (props) => {
)}
-
+
>
);
};
diff --git a/apps/app/pages/[workspaceSlug]/projects/index.tsx b/apps/app/pages/[workspaceSlug]/projects/index.tsx
index 794ab433d..8b8d0408a 100644
--- a/apps/app/pages/[workspaceSlug]/projects/index.tsx
+++ b/apps/app/pages/[workspaceSlug]/projects/index.tsx
@@ -88,7 +88,7 @@ const ProjectsPage: NextPage = () => {
title="Create a new project"
description={
- Use P shortcut to
+ Use P shortcut to
create a new project
}
diff --git a/apps/app/pages/[workspaceSlug]/settings/billing.tsx b/apps/app/pages/[workspaceSlug]/settings/billing.tsx
index b1af24365..38758ffdc 100644
--- a/apps/app/pages/[workspaceSlug]/settings/billing.tsx
+++ b/apps/app/pages/[workspaceSlug]/settings/billing.tsx
@@ -1,19 +1,21 @@
import React from "react";
import { useRouter } from "next/router";
+
import useSWR from "swr";
// lib
-import type { NextPage, GetServerSideProps } from "next";
import { requiredWorkspaceAdmin } from "lib/auth";
-// constants
// services
import workspaceService from "services/workspace.service";
// layouts
-import SettingsLayout from "layouts/settings-layout";
+import AppLayout from "layouts/app-layout";
// ui
import { Button } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
+// types
+import type { NextPage, GetServerSideProps } from "next";
+// fetch-keys
import { WORKSPACE_DETAILS } from "constants/fetch-keys";
type TBillingSettingsProps = {
@@ -35,9 +37,9 @@ const BillingSettings: NextPage = (props) => {
return (
<>
-
= (props) => {
-
+
>
);
};
diff --git a/apps/app/pages/[workspaceSlug]/settings/features.tsx b/apps/app/pages/[workspaceSlug]/settings/features.tsx
deleted file mode 100644
index a5deede27..000000000
--- a/apps/app/pages/[workspaceSlug]/settings/features.tsx
+++ /dev/null
@@ -1,173 +0,0 @@
-import React from "react";
-
-import { useRouter } from "next/router";
-import useSWR from "swr";
-
-// lib
-import type { GetServerSideProps, NextPage } from "next";
-import { requiredWorkspaceAdmin } from "lib/auth";
-// constants
-// services
-import workspaceService from "services/workspace.service";
-// layouts
-import SettingsLayout from "layouts/settings-layout";
-// ui
-import { Button } from "components/ui";
-import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
-import { WORKSPACE_DETAILS } from "constants/fetch-keys";
-
-type TFeatureSettingsProps = {
- isOwner: boolean;
- isMember: boolean;
- isViewer: boolean;
- isGuest: boolean;
-};
-
-const FeaturesSettings: NextPage
= (props) => {
- const {
- query: { workspaceSlug },
- } = useRouter();
-
- const { data: activeWorkspace } = useSWR(
- workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null,
- () => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null)
- );
-
- return (
- <>
-
-
-
-
- }
- >
-
-
-
Workspace Features
-
-
-
-
-
Use modules
-
- Modules are enabled for all the projects in this workspace. Access it from the
- navigation bar.
-
-
-
- {/* Disabled- bg-gray-200, translate-x-0 */}
-
- Use setting
-
-
-
-
-
-
-
Use cycles
-
- Cycles are enabled for all the projects in this workspace. Access it from the
- navigation bar.
-
-
-
- {/* Disabled- bg-gray-200, translate-x-0 */}
-
- Use setting
-
-
-
-
-
-
-
Use backlogs
-
- Backlog are enabled for all the projects in this workspace. Access it from the
- navigation bar.
-
-
-
- {/* Disabled- bg-gray-200, translate-x-0 */}
-
- Use setting
-
-
-
-
-
-
-
-
- >
- );
-};
-
-export const getServerSideProps: GetServerSideProps = async (ctx) => {
- const workspaceSlug = ctx.params?.workspaceSlug as string;
-
- const memberDetail = await requiredWorkspaceAdmin(workspaceSlug, ctx.req.headers.cookie);
-
- if (memberDetail === null) {
- return {
- redirect: {
- destination: "/",
- permanent: false,
- },
- };
- }
-
- return {
- props: {
- isOwner: memberDetail?.role === 20,
- isMember: memberDetail?.role === 15,
- isViewer: memberDetail?.role === 10,
- isGuest: memberDetail?.role === 5,
- },
- };
-};
-
-export default FeaturesSettings;
diff --git a/apps/app/pages/[workspaceSlug]/settings/index.tsx b/apps/app/pages/[workspaceSlug]/settings/index.tsx
index e44d8d0ba..5ee704bce 100644
--- a/apps/app/pages/[workspaceSlug]/settings/index.tsx
+++ b/apps/app/pages/[workspaceSlug]/settings/index.tsx
@@ -17,11 +17,11 @@ import { requiredWorkspaceAdmin } from "lib/auth";
import workspaceService from "services/workspace.service";
import fileServices from "services/file.service";
// layouts
-import SettingsLayout from "layouts/settings-layout";
+import AppLayout from "layouts/app-layout";
// hooks
import useToast from "hooks/use-toast";
// components
-import { ImageUploadModal } from "components/common/image-upload-modal";
+import { ImageUploadModal } from "components/core";
import ConfirmWorkspaceDeletion from "components/workspace/confirm-workspace-deletion";
// ui
import { Spinner, Button, Input, CustomSelect } from "components/ui";
@@ -35,7 +35,7 @@ import type { GetServerSideProps, NextPage } from "next";
// fetch-keys
import { WORKSPACE_DETAILS, USER_WORKSPACES } from "constants/fetch-keys";
// constants
-import { companySize } from "constants/";
+import { COMPANY_SIZE } from "constants/workspace";
const defaultValues: Partial = {
name: "",
@@ -85,11 +85,13 @@ const WorkspaceSettings: NextPage = (props) => {
const onSubmit = async (formData: IWorkspace) => {
if (!activeWorkspace) return;
+
const payload: Partial = {
logo: formData.logo,
name: formData.name,
company_size: formData.company_size,
};
+
await workspaceService
.updateWorkspace(activeWorkspace.slug, payload)
.then((res) => {
@@ -106,9 +108,9 @@ const WorkspaceSettings: NextPage = (props) => {
};
return (
- = (props) => {
label={value ? value.toString() : "Select company size"}
input
>
- {companySize?.map((item) => (
+ {COMPANY_SIZE?.map((item) => (
{item.label}
@@ -315,7 +317,7 @@ const WorkspaceSettings: NextPage = (props) => {
)}
-
+
);
};
diff --git a/apps/app/pages/[workspaceSlug]/settings/members.tsx b/apps/app/pages/[workspaceSlug]/settings/members.tsx
index a4e8c9166..e3bbcd2fc 100644
--- a/apps/app/pages/[workspaceSlug]/settings/members.tsx
+++ b/apps/app/pages/[workspaceSlug]/settings/members.tsx
@@ -2,29 +2,31 @@ import { useState } from "react";
import Image from "next/image";
import { useRouter } from "next/router";
+
import useSWR from "swr";
-import { PlusIcon } from "@heroicons/react/24/outline";
// lib
-import type { GetServerSideProps, NextPage } from "next";
import { requiredWorkspaceAdmin } from "lib/auth";
// hooks
import useToast from "hooks/use-toast";
// services
import workspaceService from "services/workspace.service";
-// constants
// layouts
-import SettingsLayout from "layouts/settings-layout";
+import AppLayout from "layouts/app-layout";
// components
import ConfirmWorkspaceMemberRemove from "components/workspace/confirm-workspace-member-remove";
import SendWorkspaceInvitationModal from "components/workspace/send-workspace-invitation-modal";
// ui
import { Button, CustomListbox, CustomMenu, Loader } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
-import { ROLE } from "constants/";
// icons
+import { PlusIcon } from "@heroicons/react/24/outline";
+// types
+import type { GetServerSideProps, NextPage } from "next";
// fetch-keys
import { WORKSPACE_DETAILS, WORKSPACE_INVITATIONS, WORKSPACE_MEMBERS } from "constants/fetch-keys";
+// constants
+import { ROLE } from "constants/workspace";
type TMembersSettingsProps = {
isOwner: boolean;
@@ -135,11 +137,9 @@ const MembersSettings: NextPage = (props) => {
workspace_slug={workspaceSlug as string}
members={members}
/>
-
= (props) => {
)}
-
+
>
);
};
diff --git a/apps/app/pages/_app.tsx b/apps/app/pages/_app.tsx
index d838868a6..f10a6f722 100644
--- a/apps/app/pages/_app.tsx
+++ b/apps/app/pages/_app.tsx
@@ -10,15 +10,13 @@ import type { AppProps } from "next/app";
function MyApp({ Component, pageProps }: AppProps) {
return (
- <>
-
-
-
-
-
-
-
- >
+
+
+
+
+
+
+
);
}
diff --git a/apps/app/pages/_error.js b/apps/app/pages/_error.js
new file mode 100644
index 000000000..f1ee86ca7
--- /dev/null
+++ b/apps/app/pages/_error.js
@@ -0,0 +1,33 @@
+/**
+ * NOTE: This requires `@sentry/nextjs` version 7.3.0 or higher.
+ *
+ * NOTE: If using this with `next` version 12.2.0 or lower, uncomment the
+ * penultimate line in `CustomErrorComponent`.
+ *
+ * This page is loaded by Nextjs:
+ * - on the server, when data-fetching methods throw or reject
+ * - on the client, when `getInitialProps` throws or rejects
+ * - on the client, when a React lifecycle method throws or rejects, and it's
+ * caught by the built-in Nextjs error boundary
+ *
+ * See:
+ * - https://nextjs.org/docs/basic-features/data-fetching/overview
+ * - https://nextjs.org/docs/api-reference/data-fetching/get-initial-props
+ * - https://reactjs.org/docs/error-boundaries.html
+ */
+
+import * as Sentry from "@sentry/nextjs";
+import NextErrorComponent from "next/error";
+
+const CustomErrorComponent = (props) => ;
+
+CustomErrorComponent.getInitialProps = async (contextData) => {
+ // In case this is running in a serverless function, await this in order to give Sentry
+ // time to send the error before the lambda exits
+ await Sentry.captureUnderscoreErrorException(contextData);
+
+ // This will contain the status code of the response
+ return NextErrorComponent.getInitialProps(contextData);
+};
+
+export default CustomErrorComponent;
diff --git a/apps/app/pages/create-workspace.tsx b/apps/app/pages/create-workspace.tsx
index fcacbcedf..2c4b1a6a4 100644
--- a/apps/app/pages/create-workspace.tsx
+++ b/apps/app/pages/create-workspace.tsx
@@ -25,7 +25,7 @@ import type { NextPage, NextPageContext } from "next";
// fetch-keys
import { USER_WORKSPACES } from "constants/fetch-keys";
// constants
-import { companySize } from "constants/";
+import { COMPANY_SIZE } from "constants/workspace";
const defaultValues = {
name: "",
@@ -145,7 +145,7 @@ const CreateWorkspace: NextPage = () => {
label={value ? value.toString() : "Select company size"}
input
>
- {companySize?.map((item) => (
+ {COMPANY_SIZE?.map((item) => (
{item.label}
diff --git a/apps/app/pages/error.tsx b/apps/app/pages/error.tsx
index fd41671d9..3909a1268 100644
--- a/apps/app/pages/error.tsx
+++ b/apps/app/pages/error.tsx
@@ -1,21 +1,21 @@
import React from "react";
-import type { NextPage } from "next";
-
// layouts
import DefaultLayout from "layouts/default-layout";
+// types
+import type { NextPage } from "next";
const ErrorPage: NextPage = () => (
-
-
-
Error!
-
-
- );
+
+
+
Error!
+
+
+);
export default ErrorPage;
diff --git a/apps/app/pages/index.tsx b/apps/app/pages/index.tsx
index 20bbc6661..a795211ff 100644
--- a/apps/app/pages/index.tsx
+++ b/apps/app/pages/index.tsx
@@ -1,7 +1,7 @@
-import type { NextPage, NextPageContext } from "next";
-
// lib
import { homePageRedirect } from "lib/auth";
+// types
+import type { NextPage, NextPageContext } from "next";
const Home: NextPage = () => null;
diff --git a/apps/app/pages/magic-sign-in/index.tsx b/apps/app/pages/magic-sign-in/index.tsx
index ccfc13268..521373685 100644
--- a/apps/app/pages/magic-sign-in/index.tsx
+++ b/apps/app/pages/magic-sign-in/index.tsx
@@ -4,15 +4,16 @@ import { useRouter } from "next/router";
import { mutate } from "swr";
+// layouts
+import DefaultLayout from "layouts/default-layout";
// services
-import type { NextPage } from "next";
import authenticationService from "services/authentication.service";
-// constants
// hooks
import useUser from "hooks/use-user";
import useToast from "hooks/use-toast";
-// layouts
-import DefaultLayout from "layouts/default-layout";
+// types
+import type { NextPage } from "next";
+// constants
import { USER_WORKSPACES } from "constants/fetch-keys";
const MagicSignIn: NextPage = () => {
diff --git a/apps/app/pages/onboarding/index.tsx b/apps/app/pages/onboarding/index.tsx
index 68d8f70f9..48f01462b 100644
--- a/apps/app/pages/onboarding/index.tsx
+++ b/apps/app/pages/onboarding/index.tsx
@@ -2,11 +2,13 @@ import { useState } from "react";
import Image from "next/image";
import { useRouter } from "next/router";
-// hooks
-import type { NextPage, NextPageContext } from "next";
-import useUser from "hooks/use-user";
+
// lib
import { requiredAuth } from "lib/auth";
+// services
+import userService from "services/user.service";
+// hooks
+import useUser from "hooks/use-user";
// layouts
import DefaultLayout from "layouts/default-layout";
// components
@@ -20,7 +22,8 @@ import InviteMembers from "components/onboarding/invite-members";
import CommandMenu from "components/onboarding/command-menu";
// images
import Logo from "public/onboarding/logo.svg";
-import userService from "services/user.service";
+// types
+import type { NextPage, NextPageContext } from "next";
const Onboarding: NextPage = () => {
const [step, setStep] = useState(1);
diff --git a/apps/app/pages/workspace-member-invitation/[invitationId].tsx b/apps/app/pages/workspace-member-invitation/[invitationId].tsx
index 7afe4c7e7..cbbb2fd41 100644
--- a/apps/app/pages/workspace-member-invitation/[invitationId].tsx
+++ b/apps/app/pages/workspace-member-invitation/[invitationId].tsx
@@ -13,7 +13,6 @@ import {
} from "@heroicons/react/24/outline";
// swr
// services
-import type { NextPage } from "next";
import workspaceService from "services/workspace.service";
// hooks
import useUser from "hooks/use-user";
@@ -23,6 +22,8 @@ import DefaultLayout from "layouts/default-layout";
import { Spinner } from "components/ui";
// icons
import { EmptySpace, EmptySpaceItem } from "components/ui/empty-space";
+// types
+import type { NextPage } from "next";
// constants
import { WORKSPACE_INVITATION } from "constants/fetch-keys";
diff --git a/apps/app/sentry.client.config.js b/apps/app/sentry.client.config.js
new file mode 100644
index 000000000..8816c47b1
--- /dev/null
+++ b/apps/app/sentry.client.config.js
@@ -0,0 +1,17 @@
+// This file configures the initialization of Sentry on the browser.
+// The config you add here will be used whenever a page is visited.
+// https://docs.sentry.io/platforms/javascript/guides/nextjs/
+
+import * as Sentry from "@sentry/nextjs";
+
+const SENTRY_DSN = process.env.NEXT_PUBLIC_SENTRY_DSN;
+
+Sentry.init({
+ dsn: SENTRY_DSN,
+ // Adjust this value in production, or use tracesSampler for greater control
+ tracesSampleRate: 1.0,
+ // ...
+ // Note: if you want to override the automatic release value, do not set a
+ // `release` value here - use the environment variable `SENTRY_RELEASE`, so
+ // that it will also get attached to your source maps
+});
diff --git a/apps/app/sentry.edge.config.js b/apps/app/sentry.edge.config.js
new file mode 100644
index 000000000..e6347794f
--- /dev/null
+++ b/apps/app/sentry.edge.config.js
@@ -0,0 +1,17 @@
+// This file configures the initialization of Sentry on the server.
+// The config you add here will be used whenever middleware or an Edge route handles a request.
+// https://docs.sentry.io/platforms/javascript/guides/nextjs/
+
+import * as Sentry from "@sentry/nextjs";
+
+const SENTRY_DSN = process.env.NEXT_PUBLIC_SENTRY_DSN;
+
+Sentry.init({
+ dsn: SENTRY_DSN,
+ // Adjust this value in production, or use tracesSampler for greater control
+ tracesSampleRate: 1.0,
+ // ...
+ // Note: if you want to override the automatic release value, do not set a
+ // `release` value here - use the environment variable `SENTRY_RELEASE`, so
+ // that it will also get attached to your source maps
+});
diff --git a/apps/app/sentry.properties b/apps/app/sentry.properties
new file mode 100644
index 000000000..6796827bd
--- /dev/null
+++ b/apps/app/sentry.properties
@@ -0,0 +1,3 @@
+defaults.url=https://sentry.io/
+defaults.org=plane
+defaults.project=plane-web
diff --git a/apps/app/sentry.server.config.js b/apps/app/sentry.server.config.js
new file mode 100644
index 000000000..db1fbc4b1
--- /dev/null
+++ b/apps/app/sentry.server.config.js
@@ -0,0 +1,17 @@
+// This file configures the initialization of Sentry on the server.
+// The config you add here will be used whenever the server handles a request.
+// https://docs.sentry.io/platforms/javascript/guides/nextjs/
+
+import * as Sentry from "@sentry/nextjs";
+
+const SENTRY_DSN = process.env.NEXT_PUBLIC_SENTRY_DSN;
+
+Sentry.init({
+ dsn: SENTRY_DSN,
+ // Adjust this value in production, or use tracesSampler for greater control
+ tracesSampleRate: 1.0,
+ // ...
+ // Note: if you want to override the automatic release value, do not set a
+ // `release` value here - use the environment variable `SENTRY_RELEASE`, so
+ // that it will also get attached to your source maps
+});
diff --git a/apps/app/services/cycles.service.ts b/apps/app/services/cycles.service.ts
index 07f39d3a5..109b62108 100644
--- a/apps/app/services/cycles.service.ts
+++ b/apps/app/services/cycles.service.ts
@@ -26,6 +26,18 @@ class ProjectCycleServices extends APIService {
});
}
+ async getCycleDetails(
+ workspaceSlug: string,
+ projectId: string,
+ cycleId: string
+ ): Promise {
+ return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/`)
+ .then((res) => res?.data)
+ .catch((err) => {
+ throw err?.response?.data;
+ });
+ }
+
async getCycleIssues(workspaceSlug: string, projectId: string, cycleId: string): Promise {
return this.get(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/cycle-issues/`
diff --git a/apps/app/styles/editor.css b/apps/app/styles/editor.css
index 45deb0dc6..319f3207d 100644
--- a/apps/app/styles/editor.css
+++ b/apps/app/styles/editor.css
@@ -401,6 +401,19 @@ img.ProseMirror-separator {
/* end table styling */
/* link styling */
+.remirror-floating-popover {
+ z-index: 20 !important;
+}
+
+.remirror-floating-popover input {
+ font-size: 0.75rem;
+ border-radius: 5px;
+ padding: 5px;
+ border: 1px solid #a8a6a6;
+ box-shadow: 1px 1px 5px #c0bebe;
+ outline: none;
+}
+
.remirror-editor-wrapper a {
color: blue;
text-decoration: underline;
diff --git a/apps/app/types/issues.d.ts b/apps/app/types/issues.d.ts
index 4ac7ef9c4..3dcb8c918 100644
--- a/apps/app/types/issues.d.ts
+++ b/apps/app/types/issues.d.ts
@@ -171,7 +171,7 @@ export interface IIssueLabels {
updated_at: Date;
name: string;
description: string;
- colour: string;
+ color: string;
created_by: string;
updated_by: string;
project: string;
diff --git a/apps/app/types/projects.d.ts b/apps/app/types/projects.d.ts
index 4a83b90ba..603b8a527 100644
--- a/apps/app/types/projects.d.ts
+++ b/apps/app/types/projects.d.ts
@@ -1,20 +1,22 @@
import type { IUserLite, IWorkspace } from "./";
export interface IProject {
- id: string;
- workspace: IWorkspace | string;
- default_assignee: IUser | string | null;
- project_lead: IUser | string | null;
created_at: Date;
- updated_at: Date;
- name: string;
- description: string;
- network: number;
- identifier: string;
- slug: string;
created_by: string;
- updated_by: string;
+ cycle_view: boolean;
+ default_assignee: IUser | string | null;
+ description: string;
icon: string;
+ id: string;
+ identifier: string;
+ module_view: boolean;
+ name: string;
+ network: number;
+ project_lead: IUser | string | null;
+ slug: string;
+ updated_at: Date;
+ updated_by: string;
+ workspace: IWorkspace | string;
}
type ProjectViewTheme = {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2a0f52745..492366154 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -10,7 +10,7 @@ importers:
devDependencies:
config: link:packages/config
prettier: 2.8.3
- turbo: 1.7.0
+ turbo: 1.7.2
apps/app:
specifiers:
@@ -20,6 +20,7 @@ importers:
'@remirror/extension-react-tables': ^2.2.11
'@remirror/pm': ^2.0.3
'@remirror/react': ^2.0.24
+ '@sentry/nextjs': ^7.36.0
'@types/js-cookie': ^3.0.2
'@types/lodash.debounce': ^4.0.7
'@types/node': 18.0.6
@@ -62,6 +63,7 @@ importers:
'@remirror/extension-react-tables': 2.2.11_primzaudxtd4wvxl4iqmlhjvbm
'@remirror/pm': 2.0.3
'@remirror/react': 2.0.24_primzaudxtd4wvxl4iqmlhjvbm
+ '@sentry/nextjs': 7.36.0_next@12.3.2+react@18.2.0
'@types/lodash.debounce': 4.0.7
'@types/react-datepicker': 4.8.0_biqbaboplfbrettd7655fr4n2y
axios: 1.2.0
@@ -3754,6 +3756,24 @@ packages:
rollup: 2.79.1
dev: false
+ /@rollup/plugin-commonjs/24.0.0_rollup@2.78.0:
+ resolution: {integrity: sha512-0w0wyykzdyRRPHOb0cQt14mIBLujfAv6GgP6g8nvg/iBxEm112t3YPPq+Buqe2+imvElTka+bjNlJ/gB56TD8g==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^2.68.0||^3.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+ dependencies:
+ '@rollup/pluginutils': 5.0.2_rollup@2.78.0
+ commondir: 1.0.1
+ estree-walker: 2.0.2
+ glob: 8.1.0
+ is-reference: 1.2.1
+ magic-string: 0.27.0
+ rollup: 2.78.0
+ dev: false
+
/@rollup/plugin-node-resolve/11.2.1_rollup@2.79.1:
resolution: {integrity: sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==}
engines: {node: '>= 10.0.0'}
@@ -3791,9 +3811,174 @@ packages:
rollup: 2.79.1
dev: false
+ /@rollup/pluginutils/5.0.2_rollup@2.78.0:
+ resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0||^3.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+ dependencies:
+ '@types/estree': 1.0.0
+ estree-walker: 2.0.2
+ picomatch: 2.3.1
+ rollup: 2.78.0
+ dev: false
+
/@rushstack/eslint-patch/1.2.0:
resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==}
+ /@sentry/browser/7.36.0:
+ resolution: {integrity: sha512-Mu0OpisCZFICBGxVXdHWjUDgSvuQKjnO9acNcXR1+68IU08iX+cU6f2kq6VzI4mW/pNieI20FDFbx9KA0YZ4+A==}
+ engines: {node: '>=8'}
+ dependencies:
+ '@sentry/core': 7.36.0
+ '@sentry/replay': 7.36.0
+ '@sentry/types': 7.36.0
+ '@sentry/utils': 7.36.0
+ tslib: 1.14.1
+ dev: false
+
+ /@sentry/cli/1.74.6:
+ resolution: {integrity: sha512-pJ7JJgozyjKZSTjOGi86chIngZMLUlYt2HOog+OJn+WGvqEkVymu8m462j1DiXAnex9NspB4zLLNuZ/R6rTQHg==}
+ engines: {node: '>= 8'}
+ hasBin: true
+ requiresBuild: true
+ dependencies:
+ https-proxy-agent: 5.0.1
+ mkdirp: 0.5.6
+ node-fetch: 2.6.9
+ npmlog: 4.1.2
+ progress: 2.0.3
+ proxy-from-env: 1.1.0
+ which: 2.0.2
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+ dev: false
+
+ /@sentry/core/7.36.0:
+ resolution: {integrity: sha512-lq1MlcMhvm7QIwUOknFeufkg4M6QREY3s61y6pm1o+o3vSqB7Hz0D19xlyEpP62qMn8OyuttVKOVK1UfGc2EyQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ '@sentry/types': 7.36.0
+ '@sentry/utils': 7.36.0
+ tslib: 1.14.1
+ dev: false
+
+ /@sentry/integrations/7.36.0:
+ resolution: {integrity: sha512-wrRoUqdeGi64NNimGVk8U8DBiXamxTYPBux0/faFDyau8EJyQFcv8zOyB78Za4W2Ss3ZXNaE/WtFF8UxalHzBQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ '@sentry/types': 7.36.0
+ '@sentry/utils': 7.36.0
+ localforage: 1.10.0
+ tslib: 1.14.1
+ dev: false
+
+ /@sentry/nextjs/7.36.0_next@12.3.2+react@18.2.0:
+ resolution: {integrity: sha512-7IUwBjCjo3rWuvEG16D1wKb0D+aMyCU920VGCAQVZaqTZAgrgAKfpTa1Sk0fmDxYglW1EBI9QM+WEnOa9RleLw==}
+ engines: {node: '>=8'}
+ peerDependencies:
+ next: ^10.0.8 || ^11.0 || ^12.0 || ^13.0
+ react: 16.x || 17.x || 18.x
+ webpack: '>= 4.0.0'
+ peerDependenciesMeta:
+ webpack:
+ optional: true
+ dependencies:
+ '@rollup/plugin-commonjs': 24.0.0_rollup@2.78.0
+ '@sentry/core': 7.36.0
+ '@sentry/integrations': 7.36.0
+ '@sentry/node': 7.36.0
+ '@sentry/react': 7.36.0_react@18.2.0
+ '@sentry/tracing': 7.36.0
+ '@sentry/types': 7.36.0
+ '@sentry/utils': 7.36.0
+ '@sentry/webpack-plugin': 1.20.0
+ chalk: 3.0.0
+ next: 12.3.2_biqbaboplfbrettd7655fr4n2y
+ react: 18.2.0
+ rollup: 2.78.0
+ tslib: 1.14.1
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+ dev: false
+
+ /@sentry/node/7.36.0:
+ resolution: {integrity: sha512-nAHAY+Rbn5OlTpNX/i6wYrmw3hT/BtwPZ/vNU52cKgw7CpeE1UrCeFjnPn18rQPB7lIh7x0vNvoaPrfemRzpSQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ '@sentry/core': 7.36.0
+ '@sentry/types': 7.36.0
+ '@sentry/utils': 7.36.0
+ cookie: 0.4.2
+ https-proxy-agent: 5.0.1
+ lru_map: 0.3.3
+ tslib: 1.14.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /@sentry/react/7.36.0_react@18.2.0:
+ resolution: {integrity: sha512-ttrRqbgeqvkV3DwkDRZC/V8OEnBKGpQf4dKpG8oMlfdVbMTINzrxYUgkhi9xAkxkH9O+vj3Md8L3Rdqw/SDwKQ==}
+ engines: {node: '>=8'}
+ peerDependencies:
+ react: 15.x || 16.x || 17.x || 18.x
+ dependencies:
+ '@sentry/browser': 7.36.0
+ '@sentry/types': 7.36.0
+ '@sentry/utils': 7.36.0
+ hoist-non-react-statics: 3.3.2
+ react: 18.2.0
+ tslib: 1.14.1
+ dev: false
+
+ /@sentry/replay/7.36.0:
+ resolution: {integrity: sha512-wNbME74/2GtkqdDXz7NaStyfPWVLjYmN9TFWvu6E9sNl9pkDDvii/Qc8F6ps1wa7bozkKcWRHgNvYiGCxUBHcg==}
+ engines: {node: '>=12'}
+ dependencies:
+ '@sentry/core': 7.36.0
+ '@sentry/types': 7.36.0
+ '@sentry/utils': 7.36.0
+ dev: false
+
+ /@sentry/tracing/7.36.0:
+ resolution: {integrity: sha512-5R5mfWMDncOcTMmmyYMjgus1vZJzIFw4LHaSbrX7e1IRNT/6vFyNeVxATa2ePXb9mI3XHo5f2p7YrnreAtaSXw==}
+ engines: {node: '>=8'}
+ dependencies:
+ '@sentry/core': 7.36.0
+ '@sentry/types': 7.36.0
+ '@sentry/utils': 7.36.0
+ tslib: 1.14.1
+ dev: false
+
+ /@sentry/types/7.36.0:
+ resolution: {integrity: sha512-uvfwUn3okAWSZ948D/xqBrkc3Sn6TeHUgi3+p/dTTNGAXXskzavgfgQ4rSW7f3YD4LL+boZojpoIARVLodMGuA==}
+ engines: {node: '>=8'}
+ dev: false
+
+ /@sentry/utils/7.36.0:
+ resolution: {integrity: sha512-mgDi5X5Bm0sydCzXpnyKD/sD98yc2qnKXyRdNX4HRRwruhC/P53LT0hGhZXsyqsB/l8OAMl0zWXJLg0xONQsEw==}
+ engines: {node: '>=8'}
+ dependencies:
+ '@sentry/types': 7.36.0
+ tslib: 1.14.1
+ dev: false
+
+ /@sentry/webpack-plugin/1.20.0:
+ resolution: {integrity: sha512-Ssj1mJVFsfU6vMCOM2d+h+KQR7QHSfeIP16t4l20Uq/neqWXZUQ2yvQfe4S3BjdbJXz/X4Rw8Hfy1Sd0ocunYw==}
+ engines: {node: '>= 8'}
+ dependencies:
+ '@sentry/cli': 1.74.6
+ webpack-sources: 3.2.3
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+ dev: false
+
/@seznam/compose-react-refs/1.0.6:
resolution: {integrity: sha512-izzOXQfeQLonzrIQb8u6LQ8dk+ymz3WXTIXjvOlTXHq6sbzROg3NWU+9TTAOpEoK9Bth24/6F/XrfHJ5yR5n6Q==}
dev: false
@@ -4402,6 +4587,15 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
+ /agent-base/6.0.2:
+ resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
+ engines: {node: '>= 6.0.0'}
+ dependencies:
+ debug: 4.3.4
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
/ajv-keywords/3.5.2_ajv@6.12.6:
resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==}
peerDependencies:
@@ -4449,6 +4643,11 @@ packages:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'}
+ /ansi-regex/2.1.1:
+ resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
/ansi-regex/5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
@@ -4472,6 +4671,17 @@ packages:
normalize-path: 3.0.0
picomatch: 2.3.1
+ /aproba/1.2.0:
+ resolution: {integrity: sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==}
+ dev: false
+
+ /are-we-there-yet/1.1.7:
+ resolution: {integrity: sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==}
+ dependencies:
+ delegates: 1.0.0
+ readable-stream: 2.3.7
+ dev: false
+
/arg/5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
@@ -4766,6 +4976,14 @@ packages:
escape-string-regexp: 1.0.5
supports-color: 5.5.0
+ /chalk/3.0.0:
+ resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==}
+ engines: {node: '>=8'}
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+ dev: false
+
/chalk/4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
@@ -4836,6 +5054,11 @@ packages:
engines: {node: '>=6'}
dev: false
+ /code-point-at/1.1.0:
+ resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
/codemirror/5.65.11:
resolution: {integrity: sha512-Gp62g2eKSCHYt10axmGhKq3WoJSvVpvhXmowNq7pZdRVowwtvBR/hi2LSP5srtctKkRT33T6/n8Kv1UGp7JW4A==}
dev: false
@@ -4896,10 +5119,19 @@ packages:
/concat-map/0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+ /console-control-strings/1.1.0:
+ resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
+ dev: false
+
/convert-source-map/1.9.0:
resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
dev: false
+ /cookie/0.4.2:
+ resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
/copy-to-clipboard/3.3.3:
resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==}
dependencies:
@@ -4916,6 +5148,10 @@ packages:
resolution: {integrity: sha512-VVXcDpp/xJ21KdULRq/lXdLzQAtX7+37LzpyfFM973il0tWSsDEoyzG38G14AjTpK9VTfiNM9jnFauq/CpaWGQ==}
requiresBuild: true
+ /core-util-is/1.0.3:
+ resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
+ dev: false
+
/cosmiconfig/7.1.0:
resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
engines: {node: '>=10'}
@@ -5067,6 +5303,10 @@ packages:
engines: {node: '>=0.4.0'}
dev: false
+ /delegates/1.0.0:
+ resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
+ dev: false
+
/dequal/2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
@@ -6062,6 +6302,10 @@ packages:
resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==}
dev: false
+ /estree-walker/2.0.2:
+ resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+ dev: false
+
/estree-walker/3.0.1:
resolution: {integrity: sha512-woY0RUD87WzMBUiZLx8NsYr23N5BKsOMZHhu2hoNRVh6NXGfoiT1KOL8G3UHlJAnEDGmfa5ubNA/AacfG+Kb0g==}
dev: false
@@ -6257,6 +6501,19 @@ packages:
/functions-have-names/1.2.3:
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
+ /gauge/2.7.4:
+ resolution: {integrity: sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==}
+ dependencies:
+ aproba: 1.2.0
+ console-control-strings: 1.1.0
+ has-unicode: 2.0.1
+ object-assign: 4.1.1
+ signal-exit: 3.0.7
+ string-width: 1.0.2
+ strip-ansi: 3.0.1
+ wide-align: 1.1.5
+ dev: false
+
/gensync/1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
@@ -6321,6 +6578,17 @@ packages:
once: 1.4.0
path-is-absolute: 1.0.1
+ /glob/8.1.0:
+ resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
+ engines: {node: '>=12'}
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 5.1.4
+ once: 1.4.0
+ dev: false
+
/globals/11.12.0:
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
engines: {node: '>=4'}
@@ -6394,6 +6662,10 @@ packages:
dependencies:
has-symbols: 1.0.3
+ /has-unicode/2.0.1:
+ resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
+ dev: false
+
/has/1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'}
@@ -6456,6 +6728,16 @@ packages:
react-is: 16.13.1
dev: false
+ /https-proxy-agent/5.0.1:
+ resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
+ engines: {node: '>= 6'}
+ dependencies:
+ agent-base: 6.0.2
+ debug: 4.3.4
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
/hyphenate-style-name/1.0.4:
resolution: {integrity: sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==}
dev: false
@@ -6478,6 +6760,10 @@ packages:
resolution: {integrity: sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==}
engines: {node: '>= 4'}
+ /immediate/3.0.6:
+ resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
+ dev: false
+
/import-fresh/3.3.0:
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
engines: {node: '>=6'}
@@ -6607,6 +6893,13 @@ packages:
number-is-nan: 1.0.1
dev: false
+ /is-fullwidth-code-point/1.0.0:
+ resolution: {integrity: sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ number-is-nan: 1.0.1
+ dev: false
+
/is-fullwidth-code-point/3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
@@ -6690,6 +6983,12 @@ packages:
isobject: 3.0.1
dev: false
+ /is-reference/1.2.1:
+ resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
+ dependencies:
+ '@types/estree': 1.0.0
+ dev: false
+
/is-reference/3.0.0:
resolution: {integrity: sha512-Eo1W3wUoHWoCoVM4GVl/a+K0IgiqE5aIo4kJABFyMum1ZORlPkC+UC357sSQUL5w5QCE5kCC9upl75b7+7CY/Q==}
dependencies:
@@ -6735,6 +7034,10 @@ packages:
dependencies:
call-bind: 1.0.2
+ /isarray/1.0.0:
+ resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
+ dev: false
+
/isexe/2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
@@ -6908,6 +7211,12 @@ packages:
isomorphic.js: 0.2.5
dev: false
+ /lie/3.1.1:
+ resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==}
+ dependencies:
+ immediate: 3.0.6
+ dev: false
+
/lilconfig/2.0.6:
resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==}
engines: {node: '>=10'}
@@ -6925,6 +7234,12 @@ packages:
json5: 2.2.3
dev: false
+ /localforage/1.10.0:
+ resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==}
+ dependencies:
+ lie: 3.1.1
+ dev: false
+
/locate-path/5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
@@ -6991,12 +7306,23 @@ packages:
dependencies:
yallist: 4.0.0
+ /lru_map/0.3.3:
+ resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==}
+ dev: false
+
/magic-string/0.25.9:
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
dependencies:
sourcemap-codec: 1.4.8
dev: false
+ /magic-string/0.27.0:
+ resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
+ engines: {node: '>=12'}
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.4.14
+ dev: false
+
/make-dir/3.1.0:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
engines: {node: '>=8'}
@@ -7467,6 +7793,13 @@ packages:
/minimist/1.2.7:
resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==}
+ /mkdirp/0.5.6:
+ resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
+ hasBin: true
+ dependencies:
+ minimist: 1.2.7
+ dev: false
+
/mri/1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
@@ -7695,6 +8028,18 @@ packages:
- babel-plugin-macros
dev: false
+ /node-fetch/2.6.9:
+ resolution: {integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==}
+ engines: {node: 4.x || >=6.0.0}
+ peerDependencies:
+ encoding: ^0.1.0
+ peerDependenciesMeta:
+ encoding:
+ optional: true
+ dependencies:
+ whatwg-url: 5.0.0
+ dev: false
+
/node-releases/2.0.6:
resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==}
@@ -7706,6 +8051,15 @@ packages:
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
engines: {node: '>=0.10.0'}
+ /npmlog/4.1.2:
+ resolution: {integrity: sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==}
+ dependencies:
+ are-we-there-yet: 1.1.7
+ console-control-strings: 1.1.0
+ gauge: 2.7.4
+ set-blocking: 2.0.0
+ dev: false
+
/number-is-nan/1.0.1:
resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==}
engines: {node: '>=0.10.0'}
@@ -8131,6 +8485,10 @@ packages:
engines: {node: '>=6'}
dev: false
+ /process-nextick-args/2.0.1:
+ resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
+ dev: false
+
/progress/2.0.3:
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
engines: {node: '>=0.4.0'}
@@ -8561,6 +8919,18 @@ packages:
dependencies:
pify: 2.3.0
+ /readable-stream/2.3.7:
+ resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==}
+ dependencies:
+ core-util-is: 1.0.3
+ inherits: 2.0.4
+ isarray: 1.0.0
+ process-nextick-args: 2.0.1
+ safe-buffer: 5.1.2
+ string_decoder: 1.1.1
+ util-deprecate: 1.0.2
+ dev: false
+
/readdirp/3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
@@ -8834,6 +9204,14 @@ packages:
terser: 5.16.1
dev: false
+ /rollup/2.78.0:
+ resolution: {integrity: sha512-4+YfbQC9QEVvKTanHhIAFVUFSRsezvQF8vFOJwtGfb9Bb+r014S+qryr9PSmw8x6sMnPkmFBGAvIFVQxvJxjtg==}
+ engines: {node: '>=10.0.0'}
+ hasBin: true
+ optionalDependencies:
+ fsevents: 2.3.2
+ dev: false
+
/rollup/2.79.1:
resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==}
engines: {node: '>=10.0.0'}
@@ -8883,6 +9261,10 @@ packages:
resolution: {integrity: sha512-oTEQOdMwRX+uCtWCKT1nx2gAeSdpr8elg/2gcaKUH00SJU2xWESfkx11nmXwTRHy7xfQoj1o4TTQvdmuBosTnA==}
dev: false
+ /safe-buffer/5.1.2:
+ resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
+ dev: false
+
/safe-buffer/5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: false
@@ -8946,6 +9328,10 @@ packages:
randombytes: 2.1.0
dev: false
+ /set-blocking/2.0.0:
+ resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
+ dev: false
+
/set-harmonic-interval/1.0.1:
resolution: {integrity: sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==}
engines: {node: '>=6.9'}
@@ -8976,6 +9362,10 @@ packages:
get-intrinsic: 1.1.3
object-inspect: 1.12.2
+ /signal-exit/3.0.7:
+ resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
+ dev: false
+
/slash/3.0.0:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
engines: {node: '>=8'}
@@ -9071,6 +9461,15 @@ packages:
stacktrace-gps: 3.1.2
dev: false
+ /string-width/1.0.2:
+ resolution: {integrity: sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ code-point-at: 1.1.0
+ is-fullwidth-code-point: 1.0.0
+ strip-ansi: 3.0.1
+ dev: false
+
/string-width/4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -9105,6 +9504,12 @@ packages:
define-properties: 1.1.4
es-abstract: 1.20.4
+ /string_decoder/1.1.1:
+ resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
+ dependencies:
+ safe-buffer: 5.1.2
+ dev: false
+
/stringify-entities/4.0.3:
resolution: {integrity: sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==}
dependencies:
@@ -9121,6 +9526,13 @@ packages:
is-regexp: 1.0.0
dev: false
+ /strip-ansi/3.0.1:
+ resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ ansi-regex: 2.1.1
+ dev: false
+
/strip-ansi/6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
@@ -9386,6 +9798,10 @@ packages:
resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
dev: false
+ /tr46/0.0.3:
+ resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
+ dev: false
+
/tr46/1.0.1:
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
dependencies:
@@ -9451,65 +9867,65 @@ packages:
typescript: 4.9.4
dev: false
- /turbo-darwin-64/1.7.0:
- resolution: {integrity: sha512-hSGAueSf5Ko8J67mpqjpt9FsP6ePn1nMcl7IVPoJq5dHsgX3anCP/BPlexJ502bNK+87DDyhQhJ/LPSJXKrSYQ==}
+ /turbo-darwin-64/1.7.2:
+ resolution: {integrity: sha512-Sml3WR8MSu80W+gS8SnoKNImcDOlIX7zlvezzds65mW11yGniIFfZ18aKWGOm92Nj2SvXCQ2+UmyGghbFaHNmQ==}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
- /turbo-darwin-arm64/1.7.0:
- resolution: {integrity: sha512-BLLOW5W6VZxk5+0ZOj5AO1qjM0P5isIgjbEuyAl8lHZ4s9antUbY4CtFrspT32XxPTYoDl4UjviPMcSsbcl3WQ==}
+ /turbo-darwin-arm64/1.7.2:
+ resolution: {integrity: sha512-JnlgGLScboUJGJxvmSsF+5xkImEDTMPg2FHzX4n8AMB9az9ZlPQAMtc+xu4p6Xp9eaykKiV2RG81YS3H0fxDLA==}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
- /turbo-linux-64/1.7.0:
- resolution: {integrity: sha512-aw2qxmfZa+kT87SB3GNUoFimqEPzTlzlRqhPgHuAAT6Uf0JHnmebPt4K+ZPtDNl5yfVmtB05bhHPqw+5QV97Yg==}
+ /turbo-linux-64/1.7.2:
+ resolution: {integrity: sha512-vbLJw6ovG+lpiPqxniscBjljKJ2jbsHuKp8uK4j/wqgp68wAVKeAZW77GGDAUgDb88XH6Kvhh2hcizL+iWduww==}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
- /turbo-linux-arm64/1.7.0:
- resolution: {integrity: sha512-AJEx2jX+zO5fQtJpO3r6uhTabj4oSA5ZhB7zTs/rwu/XqoydsvStA4X8NDW4poTbOjF7DcSHizqwi04tSMzpJw==}
+ /turbo-linux-arm64/1.7.2:
+ resolution: {integrity: sha512-zLnuS8WdHonKL74KqOopOH/leBOWumlVGF8/8hldbDPq0mwY+6myRR5/5LdveB51rkG4UJh/sQ94xV67tjBoyw==}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
- /turbo-windows-64/1.7.0:
- resolution: {integrity: sha512-ewj7PPv2uxqv0r31hgnBa3E5qwUu7eyVRP5M1gB/TJXfSHduU79gbxpKCyxIZv2fL/N2/3U7EPOQPSZxBAoljA==}
+ /turbo-windows-64/1.7.2:
+ resolution: {integrity: sha512-oE5PMoXjmR09okvVzteFb6FjA6yo+nMsacsgKH2yLNq4sOrVo9tG98JkRurOv5+L6ZQ3yGXPxWHiqeH7hLkAVQ==}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
- /turbo-windows-arm64/1.7.0:
- resolution: {integrity: sha512-LzjOUzveWkvTD0jP8DBMYiAnYemmydsvqxdSmsUapHHJkl6wKZIOQNSO7pxsy+9XM/1/+0f9Y9F9ZNl5lePTEA==}
+ /turbo-windows-arm64/1.7.2:
+ resolution: {integrity: sha512-mdTUJk23acRv5qxA/yEstYhM1VFenVE3FDrssxGRFq7S80smtCGK1xUd4BEDDzDlVXOqBohmM5jRh9516rcjPQ==}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
- /turbo/1.7.0:
- resolution: {integrity: sha512-cwympNwQNnQZ/TffBd8yT0i0O10Cf/hlxccCYgUcwhcGEb9rDjE5thDbHoHw1hlJQUF/5ua7ERJe7Zr0lNE/ww==}
+ /turbo/1.7.2:
+ resolution: {integrity: sha512-YR/x3GZEx0C1RV6Yvuw/HB1Ixx3upM6ZTTa4WqKz9WtLWN8u2g+u2h5KpG5YtjCS3wl/8zVXgHf2WiMK6KIghg==}
hasBin: true
requiresBuild: true
optionalDependencies:
- turbo-darwin-64: 1.7.0
- turbo-darwin-arm64: 1.7.0
- turbo-linux-64: 1.7.0
- turbo-linux-arm64: 1.7.0
- turbo-windows-64: 1.7.0
- turbo-windows-arm64: 1.7.0
+ turbo-darwin-64: 1.7.2
+ turbo-darwin-arm64: 1.7.2
+ turbo-linux-64: 1.7.2
+ turbo-linux-arm64: 1.7.2
+ turbo-windows-64: 1.7.2
+ turbo-windows-arm64: 1.7.2
dev: true
/turndown-plugin-gfm/1.0.2:
@@ -9789,6 +10205,10 @@ packages:
loose-envify: 1.4.0
dev: false
+ /webidl-conversions/3.0.1:
+ resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+ dev: false
+
/webidl-conversions/4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
dev: false
@@ -9800,6 +10220,18 @@ packages:
source-map: 0.6.1
dev: false
+ /webpack-sources/3.2.3:
+ resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==}
+ engines: {node: '>=10.13.0'}
+ dev: false
+
+ /whatwg-url/5.0.0:
+ resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
+ dependencies:
+ tr46: 0.0.3
+ webidl-conversions: 3.0.1
+ dev: false
+
/whatwg-url/7.1.0:
resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
dependencies:
@@ -9824,6 +10256,12 @@ packages:
dependencies:
isexe: 2.0.0
+ /wide-align/1.1.5:
+ resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
+ dependencies:
+ string-width: 4.2.3
+ dev: false
+
/word-wrap/1.2.3:
resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==}
engines: {node: '>=0.10.0'}
diff --git a/turbo.json b/turbo.json
index ddc3b9cc5..b4658a0df 100644
--- a/turbo.json
+++ b/turbo.json
@@ -6,7 +6,8 @@
"NEXT_PUBLIC_API_BASE_URL",
"NEXT_PUBLIC_DOCSEARCH_API_KEY",
"NEXT_PUBLIC_DOCSEARCH_APP_ID",
- "NEXT_PUBLIC_DOCSEARCH_INDEX_NAME"
+ "NEXT_PUBLIC_DOCSEARCH_INDEX_NAME",
+ "NEXT_PUBLIC_SENTRY_DSN"
],
"pipeline": {
"build": {