Compare commits

..
Author SHA1 Message Date
Vamsi KuramaandGitHub 6394f517a2 Merge pull request #1413 from makeplane/stage-release
promote: stage-release to master
2023-06-28 19:19:59 +05:30
379d258375 fix: charts design and mutation (#1426) (#1427)
* fix: pie chart overlap issue

* fix: burndown chart mutation

* fix: burndown chart mutation

Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
2023-06-28 19:09:44 +05:30
pablohashescobarandGitHub 6a27827c16 Merge pull request #1425 from makeplane/develop
promote: develop to stage-release
2023-06-28 17:48:53 +05:30
pablohashescobarandGitHub 206ca98307 Merge pull request #1418 from makeplane/develop
promote: develop to stage-release
2023-06-28 11:14:41 +05:30
pablohashescobarandGitHub 473c32d3d4 fix: github importer issue (#1414) (#1415) 2023-06-27 22:56:06 +05:30
guru_sainathandGitHub 124d1c30aa Merge pull request #1410 from makeplane/develop
promote: develop to stage-release
2023-06-27 19:01:26 +05:30
guru_sainathandGitHub fd7274ba1c Merge pull request #1406 from makeplane/develop
promote: develop to stage-release
2023-06-27 16:48:34 +05:30
pablohashescobarandGitHub 46d93525be Merge pull request #1386 from makeplane/develop
promote: develop to stage-release
2023-06-24 01:34:34 +05:30
Vamsi KuramaandGitHub dbbce439ff Merge pull request #1311 from makeplane/stage-release
promote: staging to master
2023-06-16 21:34:03 +05:30
pablohashescobarandGitHub 4c0857233e fix: workspace member invite to avoid lower permission user to invite higher permission member (#1309) 2023-06-16 19:52:24 +05:30
424 changed files with 3859 additions and 6475 deletions
+6 -7
View File
@@ -9,11 +9,11 @@ NEXT_PUBLIC_GITHUB_ID=""
NEXT_PUBLIC_GITHUB_APP_NAME=""
# Sentry DSN for error monitoring
NEXT_PUBLIC_SENTRY_DSN=""
# Enable/Disable OAUTH - default 0 for selfhosted instance
# Enable/Disable OAUTH - default 0 for selfhosted instance
NEXT_PUBLIC_ENABLE_OAUTH=0
# Enable/Disable sentry
NEXT_PUBLIC_ENABLE_SENTRY=0
# Enable/Disable session recording
# Enable/Disable session recording
NEXT_PUBLIC_ENABLE_SESSION_RECORDER=0
# Enable/Disable event tracking
NEXT_PUBLIC_TRACK_EVENTS=0
@@ -59,16 +59,15 @@ AWS_S3_BUCKET_NAME="uploads"
FILE_SIZE_LIMIT=5242880
# GPT settings
OPENAI_API_BASE="https://api.openai.com/v1" # change if using a custom endpoint
OPENAI_API_KEY="sk-" # add your openai key here
GPT_ENGINE="gpt-3.5-turbo" # use "gpt-4" if you have access
OPENAI_API_KEY=""
GPT_ENGINE=""
# Github
GITHUB_CLIENT_SECRET="" # For fetching release notes
# Settings related to Docker
DOCKERIZED=1
# set to 1 If using the pre-configured minio setup
# set to 1 If using the pre-configured minio setup
USE_MINIO=1
# Nginx Configuration
@@ -80,4 +79,4 @@ DEFAULT_PASSWORD="password123"
# SignUps
ENABLE_SIGNUP="1"
# Auto generated and Required that will be generated from setup.sh
# Auto generated and Required that will be generated from setup.sh
+1
View File
@@ -23,6 +23,7 @@ You can open a new issue with this [issue form](https://github.com/makeplane/pla
- Python version 3.8+
- Postgres version v14
- Redis version v6.2.7
- pnpm version 7.22.0
### Setup the project
+8
View File
@@ -61,6 +61,14 @@ chmod +x setup.sh
> If running in a cloud env replace localhost with public facing IP address of the VM
- Export Environment Variables
```bash
set -a
source .env
set +a
```
- Run Docker compose up
```bash
+1 -2
View File
@@ -1,3 +1,2 @@
web: gunicorn -w 4 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:$PORT --config gunicorn.config.py --max-requests 10000 --max-requests-jitter 1000 --access-logfile -
worker: celery -A plane worker -l info
beat: celery -A plane beat -l INFO
worker: celery -A plane worker -l info
@@ -21,7 +21,6 @@ from .project import (
ProjectIdentifierSerializer,
ProjectFavoriteSerializer,
ProjectLiteSerializer,
ProjectMemberLiteSerializer,
)
from .state import StateSerializer, StateLiteSerializer
from .view import IssueViewSerializer, IssueViewFavoriteSerializer
@@ -42,7 +41,6 @@ from .issue import (
IssueLinkSerializer,
IssueLiteSerializer,
IssueAttachmentSerializer,
IssueSubscriberSerializer,
)
from .module import (
@@ -76,7 +74,4 @@ from .estimate import (
)
from .inbox import InboxSerializer, InboxIssueSerializer, IssueStateInboxSerializer
from .analytic import AnalyticViewSerializer
from .notification import NotificationSerializer
-12
View File
@@ -19,7 +19,6 @@ from plane.db.models import (
IssueProperty,
IssueBlocker,
IssueAssignee,
IssueSubscriber,
IssueLabel,
Label,
IssueBlocker,
@@ -531,14 +530,3 @@ class IssueLiteSerializer(BaseSerializer):
"created_at",
"updated_at",
]
class IssueSubscriberSerializer(BaseSerializer):
class Meta:
model = IssueSubscriber
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"issue",
]
@@ -1,10 +0,0 @@
# Module imports
from .base import BaseSerializer
from plane.db.models import Notification
class NotificationSerializer(BaseSerializer):
class Meta:
model = Notification
fields = "__all__"
+1 -18
View File
@@ -77,13 +77,6 @@ class ProjectSerializer(BaseSerializer):
raise serializers.ValidationError(detail="Project Identifier is already taken")
class ProjectLiteSerializer(BaseSerializer):
class Meta:
model = Project
fields = ["id", "identifier", "name"]
read_only_fields = fields
class ProjectDetailSerializer(BaseSerializer):
workspace = WorkSpaceSerializer(read_only=True)
default_assignee = UserLiteSerializer(read_only=True)
@@ -101,7 +94,7 @@ class ProjectDetailSerializer(BaseSerializer):
class ProjectMemberSerializer(BaseSerializer):
workspace = WorkSpaceSerializer(read_only=True)
project = ProjectLiteSerializer(read_only=True)
project = ProjectSerializer(read_only=True)
member = UserLiteSerializer(read_only=True)
class Meta:
@@ -141,13 +134,3 @@ class ProjectLiteSerializer(BaseSerializer):
model = Project
fields = ["id", "identifier", "name"]
read_only_fields = fields
class ProjectMemberLiteSerializer(BaseSerializer):
member = UserLiteSerializer(read_only=True)
is_subscribed = serializers.BooleanField(read_only=True)
class Meta:
model = ProjectMember
fields = ["member", "id", "is_subscribed"]
read_only_fields = fields
+1 -106
View File
@@ -76,9 +76,6 @@ from plane.api.views import (
IssueLinkViewSet,
BulkCreateIssueLabelsEndpoint,
IssueAttachmentEndpoint,
IssueSubscriberViewSet,
IssueArchiveViewSet,
IssueSubscriberViewSet,
## End Issues
# States
StateViewSet,
@@ -151,9 +148,6 @@ from plane.api.views import (
ExportAnalyticsEndpoint,
DefaultAnalyticsEndpoint,
## End Analytics
# Notification
NotificationViewSet,
## End Notification
)
@@ -301,6 +295,7 @@ urlpatterns = [
{
"delete": "destroy",
"get": "retrieve",
"get": "retrieve",
}
),
name="workspace",
@@ -803,34 +798,6 @@ urlpatterns = [
name="project-issue-comment",
),
## End IssueComments
# Issue Subscribers
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-subscribers/",
IssueSubscriberViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-issue-subscribers",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-subscribers/<uuid:subscriber_id>/",
IssueSubscriberViewSet.as_view({"delete": "destroy"}),
name="project-issue-subscribers",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/subscribe/",
IssueSubscriberViewSet.as_view(
{
"get": "subscription_status",
"post": "subscribe",
"delete": "unsubscribe",
}
),
name="project-issue-subscribers",
),
## End Issue Subscribers
## IssueProperty
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-properties/",
@@ -855,36 +822,6 @@ urlpatterns = [
name="project-issue-roadmap",
),
## IssueProperty Ebd
## Issue Archives
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-issues/",
IssueArchiveViewSet.as_view(
{
"get": "list",
}
),
name="project-issue-archive",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-issues/<uuid:pk>/",
IssueArchiveViewSet.as_view(
{
"get": "retrieve",
"delete": "destroy",
}
),
name="project-issue-archive",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/unarchive/<uuid:pk>/",
IssueArchiveViewSet.as_view(
{
"post": "unarchive",
}
),
name="project-issue-archive",
),
## End Issue Archives
## File Assets
path(
"workspaces/<str:slug>/file-assets/",
@@ -1337,46 +1274,4 @@ urlpatterns = [
name="default-analytics",
),
## End Analytics
# Notification
path(
"workspaces/<str:slug>/users/notifications/",
NotificationViewSet.as_view(
{
"get": "list",
}
),
name="notifications",
),
path(
"workspaces/<str:slug>/users/notifications/<uuid:pk>/",
NotificationViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="notifications",
),
path(
"workspaces/<str:slug>/users/notifications/<uuid:pk>/read/",
NotificationViewSet.as_view(
{
"post": "mark_read",
"delete": "mark_unread",
}
),
name="notifications",
),
path(
"workspaces/<str:slug>/users/notifications/<uuid:pk>/archive/",
NotificationViewSet.as_view(
{
"post": "archive",
"delete": "unarchive",
}
),
name="notifications",
),
## End Notification
]
-5
View File
@@ -65,8 +65,6 @@ from .issue import (
IssueLinkViewSet,
BulkCreateIssueLabelsEndpoint,
IssueAttachmentEndpoint,
IssueArchiveViewSet,
IssueSubscriberViewSet,
)
from .auth_extended import (
@@ -135,7 +133,6 @@ from .estimate import (
from .release import ReleaseNotesEndpoint
from .inbox import InboxViewSet, InboxIssueViewSet
from .analytic import (
AnalyticsEndpoint,
AnalyticViewViewset,
@@ -143,5 +140,3 @@ from .analytic import (
ExportAnalyticsEndpoint,
DefaultAnalyticsEndpoint,
)
from .notification import NotificationViewSet
+1 -1
View File
@@ -67,7 +67,7 @@ class GPTIntegrationEndpoint(BaseAPIView):
openai.api_key = settings.OPENAI_API_KEY
response = openai.Completion.create(
model=settings.GPT_ENGINE,
engine=settings.GPT_ENGINE,
prompt=final_text,
temperature=0.7,
max_tokens=1024,
+1 -352
View File
@@ -15,7 +15,6 @@ from django.db.models import (
Value,
CharField,
When,
Exists,
Max,
)
from django.core.serializers.json import DjangoJSONEncoder
@@ -44,15 +43,11 @@ from plane.api.serializers import (
IssueLinkSerializer,
IssueLiteSerializer,
IssueAttachmentSerializer,
IssueSubscriberSerializer,
ProjectMemberSerializer,
ProjectMemberLiteSerializer,
)
from plane.api.permissions import (
ProjectEntityPermission,
WorkSpaceAdminPermission,
ProjectMemberPermission,
ProjectLitePermission,
)
from plane.db.models import (
Project,
@@ -64,8 +59,6 @@ from plane.db.models import (
IssueLink,
IssueAttachment,
State,
IssueSubscriber,
ProjectMember,
)
from plane.bgtasks.issue_activites_task import issue_activity
from plane.utils.grouper import group_results
@@ -263,7 +256,7 @@ class IssueViewSet(BaseViewSet):
return Response(issues, status=status.HTTP_200_OK)
except Exception as e:
print(e)
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
@@ -912,347 +905,3 @@ class IssueAttachmentEndpoint(BaseAPIView):
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
class IssueArchiveViewSet(BaseViewSet):
permission_classes = [
ProjectEntityPermission,
]
serializer_class = IssueFlatSerializer
model = Issue
def get_queryset(self):
return (
Issue.objects.annotate(
sub_issues_count=Issue.objects.filter(parent=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.filter(archived_at__isnull=False)
.filter(project_id=self.kwargs.get("project_id"))
.filter(workspace__slug=self.kwargs.get("slug"))
.select_related("project")
.select_related("workspace")
.select_related("state")
.select_related("parent")
.prefetch_related("assignees")
.prefetch_related("labels")
)
@method_decorator(gzip_page)
def list(self, request, slug, project_id):
try:
filters = issue_filters(request.query_params, "GET")
show_sub_issues = request.GET.get("show_sub_issues", "true")
# Custom ordering for priority and state
priority_order = ["urgent", "high", "medium", "low", None]
state_order = ["backlog", "unstarted", "started", "completed", "cancelled"]
order_by_param = request.GET.get("order_by", "-created_at")
issue_queryset = (
self.get_queryset()
.filter(**filters)
.annotate(cycle_id=F("issue_cycle__id"))
.annotate(module_id=F("issue_module__id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
attachment_count=IssueAttachment.objects.filter(
issue=OuterRef("id")
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
)
# Priority Ordering
if order_by_param == "priority" or order_by_param == "-priority":
priority_order = (
priority_order
if order_by_param == "priority"
else priority_order[::-1]
)
issue_queryset = issue_queryset.annotate(
priority_order=Case(
*[
When(priority=p, then=Value(i))
for i, p in enumerate(priority_order)
],
output_field=CharField(),
)
).order_by("priority_order")
# State Ordering
elif order_by_param in [
"state__name",
"state__group",
"-state__name",
"-state__group",
]:
state_order = (
state_order
if order_by_param in ["state__name", "state__group"]
else state_order[::-1]
)
issue_queryset = issue_queryset.annotate(
state_order=Case(
*[
When(state__group=state_group, then=Value(i))
for i, state_group in enumerate(state_order)
],
default=Value(len(state_order)),
output_field=CharField(),
)
).order_by("state_order")
# assignee and label ordering
elif order_by_param in [
"labels__name",
"-labels__name",
"assignees__first_name",
"-assignees__first_name",
]:
issue_queryset = issue_queryset.annotate(
max_values=Max(
order_by_param[1::]
if order_by_param.startswith("-")
else order_by_param
)
).order_by(
"-max_values" if order_by_param.startswith("-") else "max_values"
)
else:
issue_queryset = issue_queryset.order_by(order_by_param)
issue_queryset = (
issue_queryset
if show_sub_issues == "true"
else issue_queryset.filter(parent__isnull=True)
)
issues = IssueLiteSerializer(issue_queryset, many=True).data
## Grouping the results
group_by = request.GET.get("group_by", False)
if group_by:
return Response(
group_results(issues, group_by), status=status.HTTP_200_OK
)
return Response(issues, status=status.HTTP_200_OK)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
def retrieve(self, request, slug, project_id, pk=None):
try:
issue = Issue.objects.get(
workspace__slug=slug,
project_id=project_id,
archived_at__isnull=False,
pk=pk,
)
return Response(IssueSerializer(issue).data, status=status.HTTP_200_OK)
except Issue.DoesNotExist:
return Response(
{"error": "Issue Does not exist"}, status=status.HTTP_404_NOT_FOUND
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
def unarchive(self, request, slug, project_id, pk=None):
try:
issue = Issue.objects.get(
workspace__slug=slug,
project_id=project_id,
archived_at__isnull=False,
pk=pk,
)
issue.archived_at = None
issue.save()
issue_activity.delay(
type="issue.activity.updated",
requested_data=json.dumps({"archived_in": None}),
actor_id=str(request.user.id),
issue_id=str(issue.id),
project_id=str(project_id),
current_instance=None,
)
return Response(IssueSerializer(issue).data, status=status.HTTP_200_OK)
except Issue.DoesNotExist:
return Response(
{"error": "Issue Does not exist"}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong, please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
class IssueSubscriberViewSet(BaseViewSet):
serializer_class = IssueSubscriberSerializer
model = IssueSubscriber
permission_classes = [
ProjectEntityPermission,
]
def get_permissions(self):
if self.action in ["subscribe", "unsubscribe", "subscription_status"]:
self.permission_classes = [
ProjectLitePermission,
]
else:
self.permission_classes = [
ProjectEntityPermission,
]
return super(IssueSubscriberViewSet, self).get_permissions()
def perform_create(self, serializer):
serializer.save(
project_id=self.kwargs.get("project_id"),
issue_id=self.kwargs.get("issue_id"),
)
def get_queryset(self):
return (
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(issue_id=self.kwargs.get("issue_id"))
.filter(project__project_projectmember__member=self.request.user)
.order_by("-created_at")
.distinct()
)
def list(self, request, slug, project_id, issue_id):
try:
members = ProjectMember.objects.filter(
workspace__slug=slug, project_id=project_id
).annotate(
is_subscribed=Exists(
IssueSubscriber.objects.filter(
workspace__slug=slug,
project_id=project_id,
issue_id=issue_id,
subscriber=OuterRef("member"),
)
)
).select_related("member")
serializer = ProjectMemberLiteSerializer(members, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
except Exception as e:
capture_exception(e)
return Response(
{"error": e},
status=status.HTTP_400_BAD_REQUEST,
)
def destroy(self, request, slug, project_id, issue_id, subscriber_id):
try:
issue_subscriber = IssueSubscriber.objects.get(
project=project_id,
subscriber=subscriber_id,
workspace__slug=slug,
issue=issue_id,
)
issue_subscriber.delete()
return Response(
status=status.HTTP_204_NO_CONTENT,
)
except IssueSubscriber.DoesNotExist:
return Response(
{"error": "User is not subscribed to this issue"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
def subscribe(self, request, slug, project_id, issue_id):
try:
if IssueSubscriber.objects.filter(
issue_id=issue_id,
subscriber=request.user,
workspace__slug=slug,
project=project_id,
).exists():
return Response(
{"message": "User already subscribed to the issue."},
status=status.HTTP_400_BAD_REQUEST,
)
subscriber = IssueSubscriber.objects.create(
issue_id=issue_id,
subscriber_id=request.user.id,
project_id=project_id,
)
serilaizer = IssueSubscriberSerializer(subscriber)
return Response(serilaizer.data, status=status.HTTP_201_CREATED)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong, please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
def unsubscribe(self, request, slug, project_id, issue_id):
try:
issue_subscriber = IssueSubscriber.objects.get(
project=project_id,
subscriber=request.user,
workspace__slug=slug,
issue=issue_id,
)
issue_subscriber.delete()
return Response(
status=status.HTTP_204_NO_CONTENT,
)
except IssueSubscriber.DoesNotExist:
return Response(
{"error": "User subscribed to this issue"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
def subscription_status(self, request, slug, project_id, issue_id):
try:
issue_subscriber = IssueSubscriber.objects.filter(
issue=issue_id,
subscriber=request.user,
workspace__slug=slug,
project=project_id,
).exists()
return Response({"subscribed": issue_subscriber}, status=status.HTTP_200_OK)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong, please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
-205
View File
@@ -1,205 +0,0 @@
# Django imports
from django.db.models import Q
from django.utils import timezone
# Third party imports
from rest_framework import status
from rest_framework.response import Response
from sentry_sdk import capture_exception
# Module imports
from .base import BaseViewSet
from plane.db.models import Notification, IssueAssignee, IssueSubscriber, Issue
from plane.api.serializers import NotificationSerializer
class NotificationViewSet(BaseViewSet):
model = Notification
serializer_class = NotificationSerializer
def get_queryset(self):
return (
super()
.get_queryset()
.filter(
workspace__slug=self.kwargs.get("slug"),
receiver_id=self.request.user.id,
)
.select_related("workspace")
)
def list(self, request, slug):
try:
order_by = request.GET.get("order_by", "-created_at")
snoozed = request.GET.get("snoozed", "false")
archived = request.GET.get("archived", "false")
# Filter type
type = request.GET.get("type", "all")
notifications = Notification.objects.filter(
workspace__slug=slug, receiver_id=request.user.id
).order_by(order_by)
# Filter for snoozed notifications
if snoozed == "false":
notifications = notifications.filter(
Q(snoozed_till__gte=timezone.now()) | Q(snoozed_till__isnull=True),
)
if snoozed == "true":
notifications = notifications.filter(
snoozed_till__lt=timezone.now(),
)
# Filter for archived or unarchive
if archived == "true":
notifications = notifications.filter(archived_at__isnull=True)
if archived == "false":
notifications = notifications.filter(archived_at__isnull=False)
# Subscribed issues
if type == "watching":
issue_ids = IssueSubscriber.objects.filter(
workspace__slug=slug, subsriber_id=request.user.id
).values_list("issue_id", flat=True)
notifications = notifications.filter(entity_identifier__in=issue_ids)
# Assigned Issues
if type == "assigned":
issue_ids = IssueAssignee.objects.filter(
workspace__slug=slug, assignee_id=request.user.id
).values_list("issue_id", flat=True)
notifications = notifications.filter(entity_identifier__in=issue_ids)
# Created issues
if type == "created":
issue_ids = Issue.objects.filter(
workspace__slug=slug, created_by=request.user
).values_list("pk", flat=True)
notifications = notifications.filter(entity_identifier__in=issue_ids)
serializer = NotificationSerializer(notifications, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
def partial_update(self, request, slug, pk):
try:
notification = Notification.objects.get(
workspace__slug=slug, pk=pk, receiver=request.user
)
# Only read_at and snoozed_till can be updated
notification_data = {
"snoozed_till": request.data.get("snoozed_till", None),
}
serializer = NotificationSerializer(
notification, data=notification_data, partial=True
)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except Notification.DoesNotExist:
return Response(
{"error": "Notification does not exists"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
def mark_read(self, request, slug, pk):
try:
notification = Notification.objects.get(
receiver=request.user, workspace__slug=slug, pk=pk
)
notification.read_at = timezone.now()
notification.save()
serializer = NotificationSerializer(notification)
return Response(serializer.data, status=status.HTTP_200_OK)
except Notification.DoesNotExist:
return Response(
{"error": "Notification does not exists"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
def mark_unread(self, request, slug, pk):
try:
notification = Notification.objects.get(
receiver=request.user, workspace__slug=slug, pk=pk
)
notification.read_at = None
notification.save()
serializer = NotificationSerializer(notification)
return Response(serializer.data, status=status.HTTP_200_OK)
except Notification.DoesNotExist:
return Response(
{"error": "Notification does not exists"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
def archive(self, request, slug, pk):
try:
notification = Notification.objects.get(
receiver=request.user, workspace__slug=slug, pk=pk
)
notification.archived_at = timezone.now()
notification.save()
serializer = NotificationSerializer(notification)
return Response(serializer.data, status=status.HTTP_200_OK)
except Notification.DoesNotExist:
return Response(
{"error": "Notification does not exists"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
def unarchive(self, request, slug, pk):
try:
notification = Notification.objects.get(
receiver=request.user, workspace__slug=slug, pk=pk
)
notification.archived_at = None
notification.save()
serializer = NotificationSerializer(notification)
return Response(serializer.data, status=status.HTTP_200_OK)
except Notification.DoesNotExist:
return Response(
{"error": "Notification does not exists"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
+31 -33
View File
@@ -259,7 +259,7 @@ class ProjectViewSet(BaseViewSet):
group="backlog",
description="Default state for managing all Inbox Issues",
project_id=pk,
color="#ff7700",
color="#ff7700"
)
return Response(serializer.data, status=status.HTTP_200_OK)
@@ -550,47 +550,45 @@ class AddMemberToProjectEndpoint(BaseAPIView):
def post(self, request, slug, project_id):
try:
members = request.data.get("members", [])
member_id = request.data.get("member_id", False)
role = request.data.get("role", False)
# get the project
project = Project.objects.get(pk=project_id, workspace__slug=slug)
if not len(members):
if not member_id or not role:
return Response(
{"error": "Atleast one member is required"},
{"error": "Member ID and role is required"},
status=status.HTTP_400_BAD_REQUEST,
)
project_members = ProjectMember.objects.bulk_create(
[
ProjectMember(
member_id=member.get("member_id"),
role=member.get("role", 10),
project_id=project_id,
workspace_id=project.workspace_id,
)
for member in members
],
batch_size=10,
ignore_conflicts=True,
# Check if the user is a member in the workspace
if not WorkspaceMember.objects.filter(
workspace__slug=slug, member_id=member_id
).exists():
# TODO: Update this error message - nk
return Response(
{
"error": "User is not a member of the workspace. Invite the user to the workspace to add him to project"
},
status=status.HTTP_400_BAD_REQUEST,
)
# Check if the user is already member of project
if ProjectMember.objects.filter(
project=project_id, member_id=member_id
).exists():
return Response(
{"error": "User is already a member of the project"},
status=status.HTTP_400_BAD_REQUEST,
)
# Add the user to project
project_member = ProjectMember.objects.create(
project_id=project_id, member_id=member_id, role=role
)
serializer = ProjectMemberSerializer(project_members, many=True)
serializer = ProjectMemberSerializer(project_member)
return Response(serializer.data, status=status.HTTP_201_CREATED)
except KeyError:
return Response(
{"error": "Incorrect data sent"}, status=status.HTTP_400_BAD_REQUEST
)
except Project.DoesNotExist:
return Response(
{"error": "Project does not exist"}, status=status.HTTP_400_BAD_REQUEST
)
except IntegrityError:
return Response(
{"error": "User not member of the workspace"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
+22 -90
View File
@@ -3,7 +3,6 @@ import jwt
from datetime import date, datetime
from dateutil.relativedelta import relativedelta
from uuid import uuid4
# Django imports
from django.db import IntegrityError
from django.db.models import Prefetch
@@ -22,7 +21,6 @@ from django.db.models import (
)
from django.db.models.functions import ExtractWeek, Cast, ExtractDay
from django.db.models.fields import DateField
from django.contrib.auth.hashers import make_password
# Third party modules
from rest_framework import status
@@ -95,33 +93,14 @@ class WorkSpaceViewSet(BaseViewSet):
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
return (
self.filter_queryset(super().get_queryset().select_related("owner"))
.order_by("name")
.filter(workspace_member__member=self.request.user)
.annotate(total_members=member_count)
.annotate(total_issues=issue_count)
)
return self.filter_queryset(
super().get_queryset().select_related("owner")
).order_by("name").filter(workspace_member__member=self.request.user).annotate(total_members=member_count).annotate(total_issues=issue_count)
def create(self, request):
try:
serializer = WorkSpaceSerializer(data=request.data)
slug = request.data.get("slug", False)
name = request.data.get("name", False)
if not name or not slug:
return Response(
{"error": "Both name and slug are required"},
status=status.HTTP_400_BAD_REQUEST,
)
if len(name) > 80 or len(slug) > 48:
return Response(
{"error": "The maximum length for name is 80 and for slug is 48"},
status=status.HTTP_400_BAD_REQUEST,
)
if serializer.is_valid():
serializer.save(owner=request.user)
# Create Workspace member
@@ -181,20 +160,14 @@ class UserWorkSpacesEndpoint(BaseAPIView):
)
workspace = (
(
Workspace.objects.prefetch_related(
Prefetch(
"workspace_member", queryset=WorkspaceMember.objects.all()
)
)
.filter(
workspace_member__member=request.user,
)
.select_related("owner")
Workspace.objects.prefetch_related(
Prefetch("workspace_member", queryset=WorkspaceMember.objects.all())
)
.annotate(total_members=member_count)
.annotate(total_issues=issue_count)
)
.filter(
workspace_member__member=request.user,
)
.select_related("owner")
).annotate(total_members=member_count).annotate(total_issues=issue_count)
serializer = WorkSpaceSerializer(self.filter_queryset(workspace), many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
@@ -243,20 +216,9 @@ class InviteWorkspaceEndpoint(BaseAPIView):
)
# check for role level
requesting_user = WorkspaceMember.objects.get(
workspace__slug=slug, member=request.user
)
if len(
[
email
for email in emails
if int(email.get("role", 10)) > requesting_user.role
]
):
return Response(
{"error": "You cannot invite a user with higher role"},
status=status.HTTP_400_BAD_REQUEST,
)
requesting_user = WorkspaceMember.objects.get(workspace__slug=slug, member=request.user)
if len([email for email in emails if int(email.get("role", 10)) > requesting_user.role]):
return Response({"error": "You cannot invite a user with higher role"}, status=status.HTTP_400_BAD_REQUEST)
workspace = Workspace.objects.get(slug=slug)
@@ -314,18 +276,14 @@ class InviteWorkspaceEndpoint(BaseAPIView):
# create the user if signup is disabled
if settings.DOCKERIZED and not settings.ENABLE_SIGNUP:
_ = User.objects.bulk_create(
[
User(
username=str(uuid4().hex),
email=invitation.email,
password=make_password(uuid4().hex),
is_password_autoset=True,
)
for invitation in workspace_invitations
],
batch_size=100,
)
_ = User.objects.bulk_create([
User(
email=email.get("email"),
password=str(uuid4().hex),
is_password_autoset=True
)
for email in emails
], batch_size=100)
for invitation in workspace_invitations:
workspace_invitation.delay(
@@ -442,30 +400,6 @@ class WorkspaceInvitationsViewset(BaseViewSet):
.select_related("workspace", "workspace__owner", "created_by")
)
def destroy(self, request, slug, pk):
try:
workspace_member_invite = WorkspaceMemberInvite.objects.get(
pk=pk, workspace__slug=slug
)
# delete the user if signup is disabled
if settings.DOCKERIZED and not settings.ENABLE_SIGNUP:
user = User.objects.filter(email=workspace_member_invite.email).first()
if user is not None:
user.delete()
workspace_member_invite.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except WorkspaceMemberInvite.DoesNotExist:
return Response(
{"error": "Workspace member invite does not exists"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
class UserWorkspaceInvitationsEndpoint(BaseViewSet):
serializer_class = WorkSpaceMemberInviteSerializer
@@ -931,9 +865,7 @@ class UserWorkspaceDashboardEndpoint(BaseAPIView):
)
state_distribution = (
Issue.issue_objects.filter(
workspace__slug=slug, assignees__in=[request.user]
)
Issue.issue_objects.filter(workspace__slug=slug, assignees__in=[request.user])
.annotate(state_group=F("state__group"))
.values("state_group")
.annotate(state_count=Count("state_group"))
+11 -100
View File
@@ -5,7 +5,6 @@ import requests
# Django imports
from django.conf import settings
from django.core.serializers.json import DjangoJSONEncoder
from django.utils import timezone
# Third Party imports
from celery import shared_task
@@ -21,9 +20,6 @@ from plane.db.models import (
State,
Cycle,
Module,
IssueSubscriber,
Notification,
IssueAssignee,
)
from plane.api.serializers import IssueActivitySerializer
@@ -558,24 +554,6 @@ def track_estimate_points(
)
def track_archive_in(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
issue_activities.append(
IssueActivity(
issue_id=issue_id,
project=project,
workspace=project.workspace,
comment=f"{actor.email} has restored the issue",
verb="updated",
actor=actor,
field="archived_at",
old_value="archive",
new_value="restore",
)
)
def update_issue_activity(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
@@ -592,7 +570,6 @@ def update_issue_activity(
"blocks_list": track_blocks,
"blockers_list": track_blockings,
"estimate_point": track_estimate_points,
"archived_in": track_archive_in,
}
requested_data = json.loads(requested_data) if requested_data is not None else None
@@ -970,7 +947,6 @@ def delete_attachment_activity(
)
# Receive message from room group
@shared_task
def issue_activity(
@@ -982,17 +958,6 @@ def issue_activity(
actor = User.objects.get(pk=actor_id)
project = Project.objects.get(pk=project_id)
issue = Issue.objects.filter(pk=issue_id).first()
if issue is not None:
issue.updated_at = timezone.now()
issue.save()
# add the user to issue subscriber
try:
_ = IssueSubscriber.objects.create(issue_id=issue_id, subscriber=actor)
except Exception as e:
pass
ACTIVITY_MAPPER = {
"issue.activity.created": create_issue_activity,
"issue.activity.updated": update_issue_activity,
@@ -1027,72 +992,18 @@ def issue_activity(
# Post the updates to segway for integrations and webhooks
if len(issue_activities_created):
# Don't send activities if the actor is a bot
try:
if settings.PROXY_BASE_URL:
for issue_activity in issue_activities_created:
headers = {"Content-Type": "application/json"}
issue_activity_json = json.dumps(
IssueActivitySerializer(issue_activity).data,
cls=DjangoJSONEncoder,
)
_ = requests.post(
f"{settings.PROXY_BASE_URL}/hooks/workspaces/{str(issue_activity.workspace_id)}/projects/{str(issue_activity.project_id)}/issues/{str(issue_activity.issue_id)}/issue-activity-hooks/",
json=issue_activity_json,
headers=headers,
)
except Exception as e:
capture_exception(e)
# Create Notifications
bulk_notifications = []
issue_subscribers = list(
IssueSubscriber.objects.filter(project=project, issue_id=issue_id)
.exclude(subscriber_id=actor_id)
.values_list("subscriber", flat=True)
)
issue_assignees = list(
IssueAssignee.objects.filter(project=project, issue_id=issue_id)
.exclude(assignee_id=actor_id)
.values_list("assignee", flat=True)
)
issue_subscribers = issue_subscribers + issue_assignees
if issue.created_by_id:
issue_subscribers = issue_subscribers + [issue.created_by_id]
issue = Issue.objects.get(project=project, pk=issue_id)
for subscriber in issue_subscribers:
for issue_activity in issue_activities_created:
bulk_notifications.append(
Notification(
workspace=project.workspace,
sender="in_app:issue_activities",
triggered_by_id=actor_id,
receiver_id=subscriber,
entity_identifier=issue_id,
entity_name="issue",
project=project,
title=issue_activity.comment,
data={
"issue": {
"id": str(issue_id),
"name": str(issue.name),
"identifier": str(project.identifier),
"sequence_id": issue.sequence_id,
"state_name": issue.state.name,
"state_group": issue.state.group,
},
"issue_activity": str(issue_activity.id),
},
if settings.PROXY_BASE_URL:
for issue_activity in issue_activities_created:
headers = {"Content-Type": "application/json"}
issue_activity_json = json.dumps(
IssueActivitySerializer(issue_activity).data,
cls=DjangoJSONEncoder,
)
_ = requests.post(
f"{settings.PROXY_BASE_URL}/hooks/workspaces/{str(issue_activity.workspace_id)}/projects/{str(issue_activity.project_id)}/issues/{str(issue_activity.issue_id)}/issue-activity-hooks/",
json=issue_activity_json,
headers=headers,
)
)
# Bulk create notifications
Notification.objects.bulk_create(bulk_notifications, batch_size=100)
return
except Exception as e:
# Print logs if in DEBUG mode
@@ -1,148 +0,0 @@
# Python improts
from datetime import timedelta
# Django imports
from django.utils import timezone
from django.db.models import Q
from django.conf import settings
# Third party imports
from celery import shared_task
from sentry_sdk import capture_exception
# Module imports
from plane.db.models import Issue, Project, IssueActivity, State
@shared_task
def archive_and_close_old_issues():
archive_old_issues()
close_old_issues()
def archive_old_issues():
try:
# Get all the projects whose archive_in is greater than 0
projects = Project.objects.filter(archive_in__gt=0)
for project in projects:
project_id = project.id
archive_in = project.archive_in
# Get all the issues whose updated_at in less that the archive_in month
issues = Issue.objects.filter(
Q(
project=project_id,
archived_at__isnull=True,
updated_at__lte=(timezone.now() - timedelta(days=archive_in * 30)),
state__group__in=["completed", "cancelled"],
),
Q(issue_cycle__isnull=True)
| (
Q(issue_cycle__cycle__end_date__lt=timezone.now().date())
& Q(issue_cycle__isnull=False)
),
Q(issue_module__isnull=True)
| (
Q(issue_module__module__target_date__lt=timezone.now().date())
& Q(issue_module__isnull=False)
),
)
# Check if Issues
if issues:
issues_to_update = []
for issue in issues:
issue.archived_at = timezone.now()
issues_to_update.append(issue)
# Bulk Update the issues and log the activity
Issue.objects.bulk_update(issues_to_update, ["archived_at"], batch_size=100)
IssueActivity.objects.bulk_create(
[
IssueActivity(
issue_id=issue.id,
actor=project.created_by,
verb="updated",
field="archived_at",
project=project,
workspace=project.workspace,
comment="Plane archived the issue",
new_value="archive",
old_value=""
)
for issue in issues_to_update
],
batch_size=100,
)
return
except Exception as e:
if settings.DEBUG:
print(e)
capture_exception(e)
return
def close_old_issues():
try:
# Get all the projects whose close_in is greater than 0
projects = Project.objects.filter(close_in__gt=0).select_related("default_state")
for project in projects:
project_id = project.id
close_in = project.close_in
# Get all the issues whose updated_at in less that the close_in month
issues = Issue.objects.filter(
Q(
project=project_id,
archived_at__isnull=True,
updated_at__lte=(timezone.now() - timedelta(days=close_in * 30)),
state__group__in=["backlog", "unstarted", "started"],
),
Q(issue_cycle__isnull=True)
| (
Q(issue_cycle__cycle__end_date__lt=timezone.now().date())
& Q(issue_cycle__isnull=False)
),
Q(issue_module__isnull=True)
| (
Q(issue_module__module__target_date__lt=timezone.now().date())
& Q(issue_module__isnull=False)
),
)
# Check if Issues
if issues:
if project.default_state is None:
close_state = project.default_state
else:
close_state = State.objects.filter(group="cancelled").first()
issues_to_update = []
for issue in issues:
issue.state = close_state
issues_to_update.append(issue)
# Bulk Update the issues and log the activity
Issue.objects.bulk_update(issues_to_update, ["state"], batch_size=100)
IssueActivity.objects.bulk_create(
[
IssueActivity(
issue_id=issue.id,
actor=project.created_by,
verb="updated",
field="state",
project=project,
workspace=project.workspace,
comment="Plane cancelled the issue",
)
for issue in issues_to_update
],
batch_size=100,
)
return
except Exception as e:
if settings.DEBUG:
print(e)
capture_exception(e)
return
-11
View File
@@ -1,7 +1,6 @@
import os
from celery import Celery
from plane.settings.redis import redis_instance
from celery.schedules import crontab
# Set the default Django settings module for the 'celery' program.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production")
@@ -14,15 +13,5 @@ app = Celery("plane")
# pickle the object when using Windows.
app.config_from_object("django.conf:settings", namespace="CELERY")
app.conf.beat_schedule = {
# Executes every day at 12 AM
"check-every-day-to-archive-and-close": {
"task": "plane.bgtasks.issue_automation_task.archive_and_close_old_issues",
"schedule": crontab(hour=0, minute=0),
},
}
# Load task modules from all registered Django app configs.
app.autodiscover_tasks()
app.conf.beat_scheduler = 'django_celery_beat.schedulers.DatabaseScheduler'
@@ -1,42 +0,0 @@
# Generated by Django 3.2.19 on 2023-07-04 16:55
from django.db import migrations, models
def update_company_organization_size(apps, schema_editor):
Model = apps.get_model("db", "Workspace")
updated_size = []
for obj in Model.objects.all():
obj.organization_size = str(obj.company_size)
updated_size.append(obj)
Model.objects.bulk_update(updated_size, ["organization_size"], batch_size=100)
class Migration(migrations.Migration):
dependencies = [
("db", "0034_auto_20230628_1046"),
]
operations = [
migrations.AddField(
model_name="workspace",
name="organization_size",
field=models.CharField(default="2-10", max_length=20),
),
migrations.RunPython(update_company_organization_size),
migrations.AlterField(
model_name="workspace",
name="name",
field=models.CharField(max_length=80, verbose_name="Workspace Name"),
),
migrations.AlterField(
model_name="workspace",
name="slug",
field=models.SlugField(max_length=48, unique=True),
),
migrations.RemoveField(
model_name="workspace",
name="company_size",
),
]
@@ -1,18 +0,0 @@
# Generated by Django 3.2.19 on 2023-07-05 07:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('db', '0035_auto_20230704_2225'),
]
operations = [
migrations.AlterField(
model_name='workspace',
name='organization_size',
field=models.CharField(max_length=20),
),
]
-4
View File
@@ -33,7 +33,6 @@ from .issue import (
IssueLink,
IssueSequence,
IssueAttachment,
IssueSubscriber,
)
from .asset import FileAsset
@@ -67,7 +66,4 @@ from .page import Page, PageBlock, PageFavorite, PageLabel
from .estimate import Estimate, EstimatePoint
from .inbox import Inbox, InboxIssue
from .analytic import AnalyticView
from .notification import Notification
-24
View File
@@ -28,8 +28,6 @@ class IssueManager(models.Manager):
| models.Q(issue_inbox__status=2)
| models.Q(issue_inbox__isnull=True)
)
.filter(archived_at__isnull=True)
.exclude(archived_at__isnull=False)
)
@@ -83,7 +81,6 @@ class Issue(ProjectBaseModel):
)
sort_order = models.FloatField(default=65535)
completed_at = models.DateTimeField(null=True)
archived_at = models.DateField(null=True)
objects = models.Manager()
issue_objects = IssueManager()
@@ -404,27 +401,6 @@ class IssueSequence(ProjectBaseModel):
ordering = ("-created_at",)
class IssueSubscriber(ProjectBaseModel):
issue = models.ForeignKey(
Issue, on_delete=models.CASCADE, related_name="issue_subscribers"
)
subscriber = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="issue_subscribers",
)
class Meta:
unique_together = ["issue", "subscriber"]
verbose_name = "Issue Subscriber"
verbose_name_plural = "Issue Subscribers"
db_table = "issue_subscribers"
ordering = ("-created_at",)
def __str__(self):
return f"{self.issue.name} {self.subscriber.email}"
# TODO: Find a better method to save the model
@receiver(post_save, sender=Issue)
def create_issue_sequence(sender, instance, created, **kwargs):
-37
View File
@@ -1,37 +0,0 @@
# Django imports
from django.db import models
# Third party imports
from .base import BaseModel
class Notification(BaseModel):
workspace = models.ForeignKey(
"db.Workspace", related_name="notifications", on_delete=models.CASCADE
)
project = models.ForeignKey(
"db.Project", related_name="notifications", on_delete=models.CASCADE, null=True
)
data = models.JSONField(null=True)
entity_identifier = models.UUIDField(null=True)
entity_name = models.CharField(max_length=255)
title = models.TextField()
message = models.JSONField(null=True)
message_html = models.TextField(blank=True, default="<p></p>")
message_stripped = models.TextField(blank=True, null=True)
sender = models.CharField(max_length=255)
triggered_by = models.ForeignKey("db.User", related_name="triggered_notifications", on_delete=models.SET_NULL, null=True)
receiver = models.ForeignKey("db.User", related_name="received_notifications", on_delete=models.CASCADE)
read_at = models.DateTimeField(null=True)
snoozed_till = models.DateTimeField(null=True)
archived_at = models.DateTimeField(null=True)
class Meta:
verbose_name = "Notification"
verbose_name_plural = "Notifications"
db_table = "notifications"
ordering = ("-created_at",)
def __str__(self):
"""Return name of the notifications"""
return f"{self.receiver.email} <{self.workspace.name}>"
-10
View File
@@ -4,7 +4,6 @@ from django.conf import settings
from django.template.defaultfilters import slugify
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.core.validators import MinValueValidator, MaxValueValidator
# Modeule imports
from plane.db.mixins import AuditModel
@@ -75,15 +74,6 @@ class Project(BaseModel):
estimate = models.ForeignKey(
"db.Estimate", on_delete=models.SET_NULL, related_name="projects", null=True
)
archive_in = models.IntegerField(
default=0, validators=[MinValueValidator(0), MaxValueValidator(12)]
)
close_in = models.IntegerField(
default=0, validators=[MinValueValidator(0), MaxValueValidator(12)]
)
default_state = models.ForeignKey(
"db.State", on_delete=models.SET_NULL, null=True, related_name="default_state"
)
def __str__(self):
"""Return name of the project"""
+3 -3
View File
@@ -15,15 +15,15 @@ ROLE_CHOICES = (
class Workspace(BaseModel):
name = models.CharField(max_length=80, verbose_name="Workspace Name")
name = models.CharField(max_length=255, verbose_name="Workspace Name")
logo = models.URLField(verbose_name="Logo", blank=True, null=True)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="owner_workspace",
)
slug = models.SlugField(max_length=48, db_index=True, unique=True)
organization_size = models.CharField(max_length=20)
slug = models.SlugField(max_length=100, db_index=True, unique=True)
company_size = models.PositiveIntegerField(default=10)
def __str__(self):
"""Return name of the Workspace"""
-2
View File
@@ -35,7 +35,6 @@ INSTALLED_APPS = [
"rest_framework_simplejwt.token_blacklist",
"corsheaders",
"taggit",
"django_celery_beat",
]
MIDDLEWARE = [
@@ -214,4 +213,3 @@ SIMPLE_JWT = {
CELERY_TIMEZONE = TIME_ZONE
CELERY_TASK_SERIALIZER = 'json'
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_IMPORTS = ("plane.bgtasks.issue_automation_task",)
+9 -28
View File
@@ -10,7 +10,9 @@ from sentry_sdk.integrations.redis import RedisIntegration
from .common import * # noqa
DEBUG = int(os.environ.get("DEBUG", 1)) == 1
DEBUG = int(os.environ.get(
"DEBUG", 1
)) == 1
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
@@ -25,11 +27,13 @@ DATABASES = {
}
}
DOCKERIZED = int(os.environ.get("DOCKERIZED", 0)) == 1
DOCKERIZED = int(os.environ.get(
"DOCKERIZED", 0
)) == 1
USE_MINIO = int(os.environ.get("USE_MINIO", 0)) == 1
FILE_SIZE_LIMIT = int(os.environ.get("FILE_SIZE_LIMIT", 5242880))
FILE_SIZE_LIMIT = int(os.environ.get("FILE_SIZE_LIMIT", 5242880))
if DOCKERIZED:
DATABASES["default"] = dj_database_url.config()
@@ -59,29 +63,7 @@ if os.environ.get("SENTRY_DSN", False):
send_default_pii=True,
environment="local",
traces_sample_rate=0.7,
profiles_sample_rate=1.0,
)
else:
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"console": {
"class": "logging.StreamHandler",
},
},
"root": {
"handlers": ["console"],
"level": "DEBUG",
},
"loggers": {
"*": {
"handlers": ["console"],
"level": "DEBUG",
"propagate": True,
},
},
}
REDIS_HOST = "localhost"
REDIS_PORT = 6379
@@ -100,9 +82,8 @@ PROXY_BASE_URL = os.environ.get("PROXY_BASE_URL", False)
ANALYTICS_SECRET_KEY = os.environ.get("ANALYTICS_SECRET_KEY", False)
ANALYTICS_BASE_API = os.environ.get("ANALYTICS_BASE_API", False)
OPENAI_API_BASE = os.environ.get("OPENAI_API_BASE", "https://api.openai.com/v1")
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", False)
GPT_ENGINE = os.environ.get("GPT_ENGINE", "gpt-3.5-turbo")
GPT_ENGINE = os.environ.get("GPT_ENGINE", "text-davinci-003")
SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN", False)
@@ -113,4 +94,4 @@ CELERY_BROKER_URL = os.environ.get("REDIS_URL")
GITHUB_ACCESS_TOKEN = os.environ.get("GITHUB_ACCESS_TOKEN", False)
ENABLE_SIGNUP = os.environ.get("ENABLE_SIGNUP", "1") == "1"
ENABLE_SIGNUP = os.environ.get("ENABLE_SIGNUP", "1") == "1"
+12 -18
View File
@@ -13,7 +13,9 @@ from sentry_sdk.integrations.redis import RedisIntegration
from .common import * # noqa
# Database
DEBUG = int(os.environ.get("DEBUG", 0)) == 1
DEBUG = int(os.environ.get(
"DEBUG", 0
)) == 1
DATABASES = {
"default": {
@@ -70,12 +72,8 @@ CORS_ALLOW_HEADERS = [
]
CORS_ALLOW_CREDENTIALS = True
STORAGES = {
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
}
# Simplified static file serving.
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
if bool(os.environ.get("SENTRY_DSN", False)):
sentry_sdk.init(
@@ -86,12 +84,11 @@ if bool(os.environ.get("SENTRY_DSN", False)):
traces_sample_rate=1,
send_default_pii=True,
environment="production",
profiles_sample_rate=1.0,
)
if DOCKERIZED and USE_MINIO:
INSTALLED_APPS += ("storages",)
STORAGES["default"] = {"BACKEND": "storages.backends.s3boto3.S3Boto3Storage"}
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
# The AWS access key to use.
AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID", "access-key")
# The AWS secret access key to use.
@@ -99,9 +96,7 @@ if DOCKERIZED and USE_MINIO:
# The name of the bucket to store files in.
AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_S3_BUCKET_NAME", "uploads")
# The full URL to the S3 endpoint. Leave blank to use the default region URL.
AWS_S3_ENDPOINT_URL = os.environ.get(
"AWS_S3_ENDPOINT_URL", "http://plane-minio:9000"
)
AWS_S3_ENDPOINT_URL = os.environ.get("AWS_S3_ENDPOINT_URL", "http://plane-minio:9000")
# Default permissions
AWS_DEFAULT_ACL = "public-read"
AWS_QUERYSTRING_AUTH = False
@@ -192,10 +187,7 @@ else:
# extra characters appended.
AWS_S3_FILE_OVERWRITE = False
STORAGES["default"] = {
"BACKEND": "django_s3_storage.storage.S3Storage",
}
DEFAULT_FILE_STORAGE = "django_s3_storage.storage.S3Storage"
# AWS Settings End
# Enable Connection Pooling (if desired)
@@ -210,6 +202,9 @@ ALLOWED_HOSTS = [
]
# Simplified static file serving.
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
@@ -246,9 +241,8 @@ PROXY_BASE_URL = os.environ.get("PROXY_BASE_URL", False)
ANALYTICS_SECRET_KEY = os.environ.get("ANALYTICS_SECRET_KEY", False)
ANALYTICS_BASE_API = os.environ.get("ANALYTICS_BASE_API", False)
OPENAI_API_BASE = os.environ.get("OPENAI_API_BASE", "https://api.openai.com/v1")
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", False)
GPT_ENGINE = os.environ.get("GPT_ENGINE", "gpt-3.5-turbo")
GPT_ENGINE = os.environ.get("GPT_ENGINE", "text-davinci-003")
SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN", False)
+16 -19
View File
@@ -11,9 +11,10 @@ from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.redis import RedisIntegration
from .common import * # noqa
# Database
DEBUG = int(os.environ.get("DEBUG", 1)) == 1
DEBUG = int(os.environ.get(
"DEBUG", 1
)) == 1
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql_psycopg2",
@@ -47,15 +48,13 @@ ALLOWED_HOSTS = ["*"]
# TODO: Make it FALSE and LIST DOMAINS IN FULL PROD.
CORS_ALLOW_ALL_ORIGINS = True
STORAGES = {
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
}
# Simplified static file serving.
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
# Make true if running in a docker environment
DOCKERIZED = int(os.environ.get("DOCKERIZED", 0)) == 1
DOCKERIZED = int(os.environ.get(
"DOCKERIZED", 0
)) == 1
FILE_SIZE_LIMIT = int(os.environ.get("FILE_SIZE_LIMIT", 5242880))
USE_MINIO = int(os.environ.get("USE_MINIO", 0)) == 1
@@ -67,7 +66,6 @@ sentry_sdk.init(
traces_sample_rate=1,
send_default_pii=True,
environment="staging",
profiles_sample_rate=1.0,
)
# The AWS region to connect to.
@@ -152,9 +150,7 @@ AWS_S3_SIGNATURE_VERSION = None
AWS_S3_FILE_OVERWRITE = False
# AWS Settings End
STORAGES["default"] = {
"BACKEND": "django_s3_storage.storage.S3Storage",
}
# Enable Connection Pooling (if desired)
# DATABASES['default']['ENGINE'] = 'django_postgrespool'
@@ -167,6 +163,11 @@ ALLOWED_HOSTS = [
"*",
]
DEFAULT_FILE_STORAGE = "django_s3_storage.storage.S3Storage"
# Simplified static file serving.
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
@@ -198,19 +199,15 @@ PROXY_BASE_URL = os.environ.get("PROXY_BASE_URL", False)
ANALYTICS_SECRET_KEY = os.environ.get("ANALYTICS_SECRET_KEY", False)
ANALYTICS_BASE_API = os.environ.get("ANALYTICS_BASE_API", False)
OPENAI_API_BASE = os.environ.get("OPENAI_API_BASE", "https://api.openai.com/v1")
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", False)
GPT_ENGINE = os.environ.get("GPT_ENGINE", "gpt-3.5-turbo")
GPT_ENGINE = os.environ.get("GPT_ENGINE", "text-davinci-003")
SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN", False)
LOGGER_BASE_URL = os.environ.get("LOGGER_BASE_URL", False)
redis_url = os.environ.get("REDIS_URL")
broker_url = (
f"{redis_url}?ssl_cert_reqs={ssl.CERT_NONE.name}&ssl_ca_certs={certifi.where()}"
)
broker_url = f"{redis_url}?ssl_cert_reqs={ssl.CERT_NONE.name}&ssl_ca_certs={certifi.where()}"
CELERY_RESULT_BACKEND = broker_url
CELERY_BROKER_URL = broker_url
+4 -2
View File
@@ -3,10 +3,11 @@
"""
# from django.contrib import admin
from django.urls import path, include, re_path
from django.urls import path
from django.views.generic import TemplateView
from django.conf import settings
from django.conf.urls import include, url, static
# from django.conf.urls.static import static
@@ -17,10 +18,11 @@ urlpatterns = [
path("", include("plane.web.urls")),
]
urlpatterns = urlpatterns + static.static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if settings.DEBUG:
import debug_toolbar
urlpatterns = [
re_path(r"^__debug__/", include(debug_toolbar.urls)),
url(r"^__debug__/", include(debug_toolbar.urls)),
] + urlpatterns
+4 -4
View File
@@ -166,16 +166,16 @@ def filter_target_date(params, filter, method):
for query in target_dates:
target_date_query = query.split(";")
if len(target_date_query) == 2 and "after" in target_date_query:
filter["target_date__gt"] = target_date_query[0]
filter["target_date__gte"] = target_date_query[0]
else:
filter["target_date__lt"] = target_date_query[0]
filter["target_date__lte"] = target_date_query[0]
else:
if params.get("target_date", None) and len(params.get("target_date")):
for query in params.get("target_date"):
if query.get("timeline", "after") == "after":
filter["target_date__gt"] = query.get("datetime")
filter["target_date__gte"] = query.get("datetime")
else:
filter["target_date__lt"] = query.get("datetime")
filter["target_date__lte"] = query.get("datetime")
return filter
+20 -21
View File
@@ -1,32 +1,31 @@
# base requirements
Django==4.2.3
Django==3.2.19
django-braces==1.15.0
django-taggit==4.0.0
psycopg2==2.9.6
django-oauth-toolkit==2.3.0
mistune==3.0.1
django-taggit==3.1.0
psycopg2==2.9.5
django-oauth-toolkit==2.2.0
mistune==2.0.4
djangorestframework==3.14.0
redis==4.6.0
redis==4.5.4
django-nested-admin==4.0.2
django-cors-headers==4.1.0
whitenoise==6.5.0
django-allauth==0.54.0
faker==18.11.2
django-filter==23.2
django-cors-headers==3.13.0
whitenoise==6.3.0
django-allauth==0.52.0
faker==13.4.0
django-filter==22.1
jsonmodels==2.6.0
djangorestframework-simplejwt==5.2.2
sentry-sdk==1.27.0
django-s3-storage==0.14.0
sentry-sdk==1.14.0
django-s3-storage==0.13.11
django-crum==0.7.9
django-guardian==2.4.0
dj_rest_auth==2.2.5
google-auth==2.21.0
google-api-python-client==2.92.0
django-redis==5.3.0
uvicorn==0.22.0
google-auth==2.16.0
google-api-python-client==2.75.0
django-redis==5.2.0
uvicorn==0.20.0
channels==4.0.0
openai==0.27.8
slack-sdk==3.21.3
celery==5.3.1
django_celery_beat==2.5.0
openai==0.27.2
slack-sdk==3.20.2
celery==5.2.7
+1 -1
View File
@@ -1,3 +1,3 @@
-r base.txt
django-debug-toolbar==4.1.0
django-debug-toolbar==3.8.1
+6 -5
View File
@@ -1,11 +1,12 @@
-r base.txt
dj-database-url==2.0.0
dj-database-url==1.2.0
gunicorn==20.1.0
whitenoise==6.5.0
whitenoise==6.3.0
django-storages==1.13.2
boto3==1.27.0
django-anymail==10.0
django-debug-toolbar==4.1.0
boto3==1.26.136
django-anymail==9.0
twilio==7.16.2
django-debug-toolbar==3.8.1
gevent==22.10.2
psycogreen==1.0.2
+1 -1
View File
@@ -20,7 +20,7 @@ ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
COPY .gitignore .gitignore
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/yarn.lock ./yarn.lock
RUN yarn install --network-timeout 500000
RUN yarn install
# Build the project
COPY --from=builder /app/out/full/ .
@@ -164,8 +164,8 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
type="button"
className={`mt-5 flex w-full justify-end text-xs outline-none ${
isResendDisabled
? "cursor-default text-custom-text-200"
: "cursor-pointer text-custom-primary"
? "cursor-default text-brand-secondary"
: "cursor-pointer text-brand-accent"
} `}
onClick={() => {
setIsCodeResending(true);
@@ -80,7 +80,7 @@ export const EmailPasswordForm: React.FC<Props> = ({ onSubmit }) => {
<div className="ml-auto text-sm">
{isSignUpPage ? (
<Link href="/">
<a className="font-medium text-custom-primary hover:text-custom-primary">
<a className="font-medium text-brand-accent hover:text-brand-accent">
Already have an account? Sign in.
</a>
</Link>
@@ -88,7 +88,7 @@ export const EmailPasswordForm: React.FC<Props> = ({ onSubmit }) => {
<button
type="button"
onClick={() => setIsResettingPassword(true)}
className="font-medium text-custom-primary hover:text-custom-primary"
className="font-medium text-brand-accent hover:text-brand-accent"
>
Forgot your password?
</button>
@@ -112,7 +112,7 @@ export const EmailPasswordForm: React.FC<Props> = ({ onSubmit }) => {
</SecondaryButton>
{!isSignUpPage && (
<Link href="/sign-up">
<a className="block font-medium text-custom-primary hover:text-custom-primary text-sm mt-1">
<a className="block font-medium text-brand-accent hover:text-brand-accent text-sm mt-1">
Don{"'"}t have an account? Sign up.
</a>
</Link>
@@ -39,7 +39,7 @@ export const GithubLoginButton: FC<GithubLoginButtonProps> = (props) => {
<Link
href={`https://github.com/login/oauth/authorize?client_id=${NEXT_PUBLIC_GITHUB_ID}&redirect_uri=${loginCallBackURL}&scope=read:user,user:email`}
>
<button className="flex w-full items-center justify-center gap-3 rounded border border-custom-border-100 p-2 text-sm font-medium text-custom-text-200 duration-300 hover:bg-custom-background-80">
<button className="flex w-full items-center justify-center gap-3 rounded border border-brand-base p-2 text-sm font-medium text-brand-secondary duration-300 hover:bg-brand-surface-2">
<Image src={githubImage} height={20} width={20} color="#000" alt="GitHub Logo" />
<span>Sign In with Github</span>
</button>
@@ -97,7 +97,7 @@ export const CreateUpdateAnalyticsModal: React.FC<Props> = ({ isOpen, handleClos
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-brand-backdrop bg-opacity-50 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
@@ -111,13 +111,10 @@ export const CreateUpdateAnalyticsModal: React.FC<Props> = ({ isOpen, handleClos
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-100 bg-custom-background-100 px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<Dialog.Panel className="relative transform rounded-lg border border-brand-base bg-brand-base px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-custom-text-100"
>
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-brand-base">
Save Analytics
</Dialog.Title>
<div className="mt-5">
@@ -61,7 +61,7 @@ export const CustomAnalytics: React.FC<Props> = ({
<AnalyticsSelectBar
control={control}
setValue={setValue}
projects={projects ?? []}
projects={projects}
params={params}
fullScreen={fullScreen}
isProjectLevel={isProjectLevel}
@@ -86,7 +86,7 @@ export const CustomAnalytics: React.FC<Props> = ({
</div>
) : (
<div className="grid h-full place-items-center p-5">
<div className="space-y-4 text-custom-text-200">
<div className="space-y-4 text-brand-secondary">
<p className="text-sm">No matching issues found. Try changing the parameters.</p>
</div>
</div>
@@ -104,7 +104,7 @@ export const CustomAnalytics: React.FC<Props> = ({
)
) : (
<div className="grid h-full place-items-center p-5">
<div className="space-y-4 text-custom-text-200">
<div className="space-y-4 text-brand-secondary">
<p className="text-sm">There was some error in fetching the data.</p>
<div className="flex items-center justify-center gap-2">
<PrimaryButton
@@ -31,7 +31,7 @@ export const CustomTooltip: React.FC<Props> = ({ datum, analytics, params }) =>
}
return (
<div className="flex items-center gap-2 rounded-md border border-custom-border-100 bg-custom-background-80 p-2 text-xs">
<div className="flex items-center gap-2 rounded-md border border-brand-base bg-brand-surface-2 p-2 text-xs">
<span
className="h-3 w-3 rounded"
style={{
@@ -39,7 +39,7 @@ export const CustomTooltip: React.FC<Props> = ({ datum, analytics, params }) =>
}}
/>
<span
className={`font-medium text-custom-text-200 ${
className={`font-medium text-brand-secondary ${
params.segment
? params.segment === "priority" || params.segment === "state__group"
? "capitalize"
@@ -111,6 +111,7 @@ export const AnalyticsGraph: React.FC<Props> = ({
: undefined,
}}
theme={{
background: "rgb(var(--color-bg-base))",
axis: {},
}}
/>
@@ -29,7 +29,7 @@ export const AnalyticsSelectBar: React.FC<Props> = ({
>
{!isProjectLevel && (
<div>
<h6 className="text-xs text-custom-text-200">Project</h6>
<h6 className="text-xs text-brand-secondary">Project</h6>
<Controller
name="project"
control={control}
@@ -40,7 +40,7 @@ export const AnalyticsSelectBar: React.FC<Props> = ({
</div>
)}
<div>
<h6 className="text-xs text-custom-text-200">Measure (y-axis)</h6>
<h6 className="text-xs text-brand-secondary">Measure (y-axis)</h6>
<Controller
name="y_axis"
control={control}
@@ -50,7 +50,7 @@ export const AnalyticsSelectBar: React.FC<Props> = ({
/>
</div>
<div>
<h6 className="text-xs text-custom-text-200">Dimension (x-axis)</h6>
<h6 className="text-xs text-brand-secondary">Dimension (x-axis)</h6>
<Controller
name="x_axis"
control={control}
@@ -67,7 +67,7 @@ export const AnalyticsSelectBar: React.FC<Props> = ({
/>
</div>
<div>
<h6 className="text-xs text-custom-text-200">Group</h6>
<h6 className="text-xs text-brand-secondary">Group</h6>
<Controller
name="segment"
control={control}
@@ -23,14 +23,13 @@ import {
import { ContrastIcon, LayerDiagonalIcon } from "components/icons";
// helpers
import { renderShortDate } from "helpers/date-time.helper";
import { renderEmoji } from "helpers/emoji.helper";
import { truncateText } from "helpers/string.helper";
// types
import {
IAnalyticsParams,
IAnalyticsResponse,
ICurrentUserResponse,
IExportAnalyticsFormData,
IProject,
IWorkspace,
} from "types";
// fetch-keys
@@ -179,23 +178,23 @@ export const AnalyticsSidebar: React.FC<Props> = ({
};
const selectedProjects =
params.project && params.project.length > 0 ? params.project : projects?.map((p) => p.id);
params.project && params.project.length > 0 ? params.project : projects.map((p) => p.id);
return (
<div
className={`px-5 py-2.5 flex items-center justify-between space-y-2 ${
fullScreen
? "border-l border-custom-border-100 md:h-full md:border-l md:border-custom-border-100 md:space-y-4 overflow-hidden md:flex-col md:items-start md:py-5"
? "border-l border-brand-base md:h-full md:border-l md:border-brand-base md:space-y-4 overflow-hidden md:flex-col md:items-start md:py-5"
: ""
}`}
>
<div className="flex items-center gap-2 flex-wrap">
<div className="flex items-center gap-1 bg-custom-background-80 rounded-md px-3 py-1 text-custom-text-200 text-xs">
<div className="flex items-center gap-1 bg-brand-surface-2 rounded-md px-3 py-1 text-brand-secondary text-xs">
<LayerDiagonalIcon height={14} width={14} />
{analytics ? analytics.total : "..."} Issues
</div>
{isProjectLevel && (
<div className="flex items-center gap-1 bg-custom-background-80 rounded-md px-3 py-1 text-custom-text-200 text-xs">
<div className="flex items-center gap-1 bg-brand-surface-2 rounded-md px-3 py-1 text-brand-secondary text-xs">
<CalendarDaysIcon className="h-3.5 w-3.5" />
{renderShortDate(
(cycleId
@@ -207,7 +206,7 @@ export const AnalyticsSidebar: React.FC<Props> = ({
</div>
)}
</div>
<div className="h-full w-full overflow-hidden">
<div className="h-full overflow-hidden">
{fullScreen ? (
<>
{!isProjectLevel && selectedProjects && selectedProjects.length > 0 && (
@@ -215,62 +214,61 @@ export const AnalyticsSidebar: React.FC<Props> = ({
<h4 className="font-medium">Selected Projects</h4>
<div className="space-y-6 mt-4 h-full overflow-y-auto">
{selectedProjects.map((projectId) => {
const project = projects?.find((p) => p.id === projectId);
const project: IProject = projects.find((p) => p.id === projectId);
if (project)
return (
<div key={project.id} className="w-full">
<div className="text-sm flex items-center gap-1">
{project.emoji ? (
<span className="grid h-6 w-6 flex-shrink-0 place-items-center">
{renderEmoji(project.emoji)}
return (
<div key={project.id}>
<div className="text-sm flex items-center gap-1">
{project.emoji ? (
<span className="grid h-6 w-6 flex-shrink-0 place-items-center">
{String.fromCodePoint(parseInt(project.emoji))}
</span>
) : project.icon_prop ? (
<div className="h-6 w-6 grid place-items-center flex-shrink-0">
<span
style={{ color: project.icon_prop.color }}
className="material-symbols-rounded text-lg"
>
{project.icon_prop.name}
</span>
) : project.icon_prop ? (
<div className="h-6 w-6 grid place-items-center flex-shrink-0">
<span
style={{ color: project.icon_prop.color }}
className="material-symbols-rounded text-lg"
>
{project.icon_prop.name}
</span>
</div>
) : (
<span className="grid h-6 w-6 mr-1 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
{project?.name.charAt(0)}
</span>
)}
<h5 className="flex items-center gap-1">
<p className="break-words">{truncateText(project.name, 20)}</p>
<span className="text-custom-text-200 text-xs ml-1">
({project.identifier})
</span>
</h5>
</div>
) : (
<span className="grid h-6 w-6 mr-1 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
{project?.name.charAt(0)}
</span>
)}
<h5 className="break-words">
{project.name}
<span className="text-brand-secondary text-xs ml-1">
({project.identifier})
</span>
</h5>
</div>
<div className="mt-4 space-y-3 pl-2">
<div className="flex items-center justify-between gap-2 text-xs">
<div className="flex items-center gap-2">
<UserGroupIcon className="h-4 w-4 text-brand-secondary" />
<h6>Total members</h6>
</div>
<span className="text-brand-secondary">{project.total_members}</span>
</div>
<div className="mt-4 space-y-3 pl-2 w-full">
<div className="flex items-center justify-between gap-2 text-xs">
<div className="flex items-center gap-2">
<UserGroupIcon className="h-4 w-4 text-custom-text-200" />
<h6>Total members</h6>
</div>
<span className="text-custom-text-200">{project.total_members}</span>
<div className="flex items-center justify-between gap-2 text-xs">
<div className="flex items-center gap-2">
<ContrastIcon height={16} width={16} />
<h6>Total cycles</h6>
</div>
<div className="flex items-center justify-between gap-2 text-xs">
<div className="flex items-center gap-2">
<ContrastIcon height={16} width={16} />
<h6>Total cycles</h6>
</div>
<span className="text-custom-text-200">{project.total_cycles}</span>
</div>
<div className="flex items-center justify-between gap-2 text-xs">
<div className="flex items-center gap-2">
<UserGroupIcon className="h-4 w-4 text-custom-text-200" />
<h6>Total modules</h6>
</div>
<span className="text-custom-text-200">{project.total_modules}</span>
<span className="text-brand-secondary">{project.total_cycles}</span>
</div>
<div className="flex items-center justify-between gap-2 text-xs">
<div className="flex items-center gap-2">
<UserGroupIcon className="h-4 w-4 text-brand-secondary" />
<h6>Total modules</h6>
</div>
<span className="text-brand-secondary">{project.total_modules}</span>
</div>
</div>
);
</div>
);
})}
</div>
</div>
@@ -281,13 +279,13 @@ export const AnalyticsSidebar: React.FC<Props> = ({
<h4 className="font-medium break-words">Analytics for {cycleDetails.name}</h4>
<div className="space-y-4 mt-4">
<div className="flex items-center gap-2 text-xs">
<h6 className="text-custom-text-200">Lead</h6>
<h6 className="text-brand-secondary">Lead</h6>
<span>
{cycleDetails.owned_by?.first_name} {cycleDetails.owned_by?.last_name}
</span>
</div>
<div className="flex items-center gap-2 text-xs">
<h6 className="text-custom-text-200">Start Date</h6>
<h6 className="text-brand-secondary">Start Date</h6>
<span>
{cycleDetails.start_date && cycleDetails.start_date !== ""
? renderShortDate(cycleDetails.start_date)
@@ -295,7 +293,7 @@ export const AnalyticsSidebar: React.FC<Props> = ({
</span>
</div>
<div className="flex items-center gap-2 text-xs">
<h6 className="text-custom-text-200">Target Date</h6>
<h6 className="text-brand-secondary">Target Date</h6>
<span>
{cycleDetails.end_date && cycleDetails.end_date !== ""
? renderShortDate(cycleDetails.end_date)
@@ -309,14 +307,14 @@ export const AnalyticsSidebar: React.FC<Props> = ({
<h4 className="font-medium break-words">Analytics for {moduleDetails.name}</h4>
<div className="space-y-4 mt-4">
<div className="flex items-center gap-2 text-xs">
<h6 className="text-custom-text-200">Lead</h6>
<h6 className="text-brand-secondary">Lead</h6>
<span>
{moduleDetails.lead_detail?.first_name}{" "}
{moduleDetails.lead_detail?.last_name}
</span>
</div>
<div className="flex items-center gap-2 text-xs">
<h6 className="text-custom-text-200">Start Date</h6>
<h6 className="text-brand-secondary">Start Date</h6>
<span>
{moduleDetails.start_date && moduleDetails.start_date !== ""
? renderShortDate(moduleDetails.start_date)
@@ -324,7 +322,7 @@ export const AnalyticsSidebar: React.FC<Props> = ({
</span>
</div>
<div className="flex items-center gap-2 text-xs">
<h6 className="text-custom-text-200">Target Date</h6>
<h6 className="text-brand-secondary">Target Date</h6>
<span>
{moduleDetails.target_date && moduleDetails.target_date !== ""
? renderShortDate(moduleDetails.target_date)
@@ -338,7 +336,7 @@ export const AnalyticsSidebar: React.FC<Props> = ({
<div className="flex items-center gap-1">
{projectDetails?.emoji ? (
<div className="grid h-6 w-6 flex-shrink-0 place-items-center">
{renderEmoji(projectDetails.emoji)}
{String.fromCodePoint(parseInt(projectDetails.emoji))}
</div>
) : projectDetails?.icon_prop ? (
<div className="h-6 w-6 grid place-items-center flex-shrink-0">
@@ -358,7 +356,7 @@ export const AnalyticsSidebar: React.FC<Props> = ({
</div>
<div className="space-y-4 mt-4">
<div className="flex items-center gap-2 text-xs">
<h6 className="text-custom-text-200">Network</h6>
<h6 className="text-brand-secondary">Network</h6>
<span>
{
NETWORK_CHOICES[
@@ -37,9 +37,9 @@ export const AnalyticsTable: React.FC<Props> = ({ analytics, barGraphData, param
<div className="flow-root">
<div className="overflow-x-auto">
<div className="inline-block min-w-full align-middle">
<table className="min-w-full divide-y divide-custom-border-100 whitespace-nowrap border-y border-custom-border-100">
<thead className="bg-custom-background-80">
<tr className="divide-x divide-custom-border-100 text-sm text-custom-text-100">
<table className="min-w-full divide-y divide-brand-base whitespace-nowrap border-y border-brand-base">
<thead className="bg-brand-surface-2">
<tr className="divide-x divide-brand-base text-sm text-brand-base">
<th scope="col" className="py-3 px-2.5 text-left font-medium">
{ANALYTICS_X_AXIS_VALUES.find((v) => v.value === params.x_axis)?.label}
</th>
@@ -80,11 +80,11 @@ export const AnalyticsTable: React.FC<Props> = ({ analytics, barGraphData, param
)}
</tr>
</thead>
<tbody className="divide-y divide-custom-border-100">
<tbody className="divide-y divide-brand-base">
{barGraphData.data.map((item, index) => (
<tr
key={`table-row-${index}`}
className="divide-x divide-custom-border-100 text-xs text-custom-text-200"
className="divide-x divide-brand-base text-xs text-brand-secondary"
>
<td
className={`flex items-center gap-2 whitespace-nowrap py-2 px-2.5 font-medium ${
@@ -150,16 +150,16 @@ export const AnalyticsProjectModal: React.FC<Props> = ({ isOpen, onClose }) => {
return (
<div
className={`absolute top-0 z-30 h-full bg-custom-background-90 ${
className={`absolute top-0 z-30 h-full bg-brand-surface-1 ${
fullScreen ? "p-2 w-full" : "w-1/2"
} ${isOpen ? "right-0" : "-right-full"} duration-300 transition-all`}
>
<div
className={`flex h-full flex-col overflow-hidden border-custom-border-100 bg-custom-background-100 text-left ${
className={`flex h-full flex-col overflow-hidden border-brand-base bg-brand-base text-left ${
fullScreen ? "rounded-lg border" : "border-l"
}`}
>
<div className="flex items-center justify-between gap-4 bg-custom-background-100 px-5 py-4 text-sm">
<div className="flex items-center justify-between gap-4 bg-brand-base px-5 py-4 text-sm">
<h3 className="break-words">
Analytics for{" "}
{cycleId ? cycleDetails?.name : moduleId ? moduleDetails?.name : projectDetails?.name}
@@ -167,7 +167,7 @@ export const AnalyticsProjectModal: React.FC<Props> = ({ isOpen, onClose }) => {
<div className="flex items-center gap-2">
<button
type="button"
className="grid place-items-center p-1 text-custom-text-200 hover:text-custom-text-100"
className="grid place-items-center p-1 text-brand-secondary hover:text-brand-base"
onClick={() => setFullScreen((prevData) => !prevData)}
>
{fullScreen ? (
@@ -178,7 +178,7 @@ export const AnalyticsProjectModal: React.FC<Props> = ({ isOpen, onClose }) => {
</button>
<button
type="button"
className="grid place-items-center p-1 text-custom-text-200 hover:text-custom-text-100"
className="grid place-items-center p-1 text-brand-secondary hover:text-brand-base"
onClick={handleClose}
>
<XMarkIcon className="h-4 w-4" />
@@ -186,13 +186,13 @@ export const AnalyticsProjectModal: React.FC<Props> = ({ isOpen, onClose }) => {
</div>
</div>
<Tab.Group as={Fragment}>
<Tab.List as="div" className="space-x-2 border-b border-custom-border-100 p-5 pt-0">
<Tab.List as="div" className="space-x-2 border-b border-brand-base p-5 pt-0">
{tabsList.map((tab) => (
<Tab
key={tab}
className={({ selected }) =>
`rounded-3xl border border-custom-border-100 px-4 py-2 text-xs hover:bg-custom-background-80 ${
selected ? "bg-custom-background-80" : ""
`rounded-3xl border border-brand-base px-4 py-2 text-xs hover:bg-brand-surface-2 ${
selected ? "bg-brand-surface-2" : ""
}`
}
onClick={() => trackAnalyticsEvent(tab)}
@@ -10,10 +10,10 @@ type Props = {
};
export const AnalyticsDemand: React.FC<Props> = ({ defaultAnalytics }) => (
<div className="space-y-3 rounded-[10px] border border-custom-border-100 p-3">
<div className="space-y-3 rounded-[10px] border border-brand-base p-3">
<h5 className="text-xs text-red-500">DEMAND</h5>
<div>
<h4 className="text-custom-text-100 text-base font-medium">Total open tasks</h4>
<h4 className="text-brand-bas text-base font-medium">Total open tasks</h4>
<h3 className="mt-1 text-xl font-semibold">{defaultAnalytics.open_issues}</h3>
</div>
<div className="space-y-6">
@@ -31,13 +31,13 @@ export const AnalyticsDemand: React.FC<Props> = ({ defaultAnalytics }) => (
}}
/>
<h6 className="capitalize">{group.state_group}</h6>
<span className="ml-1 rounded-3xl bg-custom-background-80 px-2 py-0.5 text-[0.65rem] text-custom-text-200">
<span className="ml-1 rounded-3xl bg-brand-surface-2 px-2 py-0.5 text-[0.65rem] text-brand-secondary">
{group.state_count}
</span>
</div>
<p className="text-custom-text-200">{percentage}%</p>
<p className="text-brand-secondary">{percentage}%</p>
</div>
<div className="bar relative h-1 w-full rounded bg-custom-background-80">
<div className="bar relative h-1 w-full rounded bg-brand-surface-2">
<div
className="absolute top-0 left-0 h-1 rounded duration-300"
style={{
@@ -50,8 +50,8 @@ export const AnalyticsDemand: React.FC<Props> = ({ defaultAnalytics }) => (
);
})}
</div>
<div className="!mt-6 flex w-min items-center gap-2 whitespace-nowrap rounded-md border border-custom-border-100 bg-custom-background-80 p-2 text-xs">
<p className="flex items-center gap-1 text-custom-text-200">
<div className="!mt-6 flex w-min items-center gap-2 whitespace-nowrap rounded-md border border-brand-base bg-brand-surface-2 p-2 text-xs">
<p className="flex items-center gap-1 text-brand-secondary">
<PlayIcon className="h-4 w-4 -rotate-90" aria-hidden="true" />
<span>Estimate Demand:</span>
</p>
@@ -10,7 +10,7 @@ type Props = {
};
export const AnalyticsLeaderboard: React.FC<Props> = ({ users, title }) => (
<div className="p-3 border border-custom-border-100 rounded-[10px]">
<div className="p-3 border border-brand-base rounded-[10px]">
<h6 className="text-base font-medium">{title}</h6>
{users.length > 0 ? (
<div className="mt-3 space-y-3">
@@ -33,7 +33,7 @@ export const AnalyticsLeaderboard: React.FC<Props> = ({ users, title }) => (
{user.firstName !== "" ? user.firstName[0] : "?"}
</div>
)}
<span className="break-words text-custom-text-200">
<span className="break-words text-brand-secondary">
{user.firstName !== "" ? `${user.firstName} ${user.lastName}` : "No assignee"}
</span>
</div>
@@ -42,7 +42,7 @@ export const AnalyticsLeaderboard: React.FC<Props> = ({ users, title }) => (
))}
</div>
) : (
<div className="text-custom-text-200 text-center text-sm py-8">No matching data found.</div>
<div className="text-brand-secondary text-center text-sm py-8">No matching data found.</div>
)}
</div>
);
@@ -88,7 +88,7 @@ export const ScopeAndDemand: React.FC<Props> = ({ fullScreen = true }) => {
)
) : (
<div className="grid h-full place-items-center p-5">
<div className="space-y-4 text-custom-text-200">
<div className="space-y-4 text-brand-secondary">
<p className="text-sm">There was some error in fetching the data.</p>
<div className="flex items-center justify-center gap-2">
<PrimaryButton onClick={() => mutateDefaultAnalytics()}>Refresh</PrimaryButton>
@@ -8,9 +8,9 @@ type Props = {
};
export const AnalyticsScope: React.FC<Props> = ({ defaultAnalytics }) => (
<div className="rounded-[10px] border border-custom-border-100">
<div className="rounded-[10px] border border-brand-base">
<h5 className="p-3 text-xs text-green-500">SCOPE</h5>
<div className="divide-y divide-custom-border-100">
<div className="divide-y divide-brand-base">
<div>
<h6 className="px-3 text-base font-medium">Pending issues</h6>
{defaultAnalytics.pending_issue_user.length > 0 ? (
@@ -27,8 +27,8 @@ export const AnalyticsScope: React.FC<Props> = ({ defaultAnalytics }) => (
);
return (
<div className="rounded-md border border-custom-border-100 bg-custom-background-80 p-2 text-xs">
<span className="font-medium text-custom-text-200">
<div className="rounded-md border border-brand-base bg-brand-surface-2 p-2 text-xs">
<span className="font-medium text-brand-secondary">
{assignee
? assignee.assignees__first_name + " " + assignee.assignees__last_name
: "No assignee"}
@@ -69,11 +69,12 @@ export const AnalyticsScope: React.FC<Props> = ({ defaultAnalytics }) => (
}}
margin={{ top: 20 }}
theme={{
background: "rgb(var(--color-bg-base))",
axis: {},
}}
/>
) : (
<div className="text-custom-text-200 text-center text-sm py-8">
<div className="text-brand-secondary text-center text-sm py-8">
No matching data found.
</div>
)}
@@ -15,14 +15,14 @@ export const AnalyticsYearWiseIssues: React.FC<Props> = ({ defaultAnalytics }) =
const quarterMonthsList = [startMonth, startMonth + 1, startMonth + 2];
return (
<div className="py-3 border border-custom-border-100 rounded-[10px]">
<div className="py-3 border border-brand-base rounded-[10px]">
<h1 className="px-3 text-base font-medium">Issues closed in a year</h1>
{defaultAnalytics.issue_completed_month_wise.length > 0 ? (
<LineGraph
data={[
{
id: "issues_closed",
color: "rgb(var(--color-primary-100))",
color: "rgb(var(--color-accent))",
data: MONTHS_LIST.map((month) => ({
x: month.label.substring(0, 3),
y:
@@ -43,19 +43,19 @@ export const AnalyticsYearWiseIssues: React.FC<Props> = ({ defaultAnalytics }) =
margin={{ top: 20 }}
enableSlices="x"
sliceTooltip={(datum) => (
<div className="rounded-md border border-custom-border-100 bg-custom-background-80 p-2 text-xs">
<div className="rounded-md border border-brand-base bg-brand-surface-2 p-2 text-xs">
{datum.slice.points[0].data.yFormatted}
<span className="text-custom-text-200"> issues closed in </span>
<span className="text-brand-secondary"> issues closed in </span>
{datum.slice.points[0].data.xFormatted}
</div>
)}
theme={{
background: "rgb(var(--color-background-100))",
background: "rgb(var(--color-bg-base))",
}}
enableArea
/>
) : (
<div className="text-custom-text-200 text-center text-sm py-8">No matching data found.</div>
<div className="text-brand-secondary text-center text-sm py-8">No matching data found.</div>
)}
</div>
);
@@ -15,7 +15,7 @@ export const SelectProject: React.FC<Props> = ({ value, onChange, projects }) =>
query: project.name + project.identifier,
content: (
<div className="flex items-center gap-2">
<span className="text-custom-text-200 text-[0.65rem]">{project.identifier}</span>
<span className="text-brand-secondary text-[0.65rem]">{project.identifier}</span>
{project.name}
</div>
),
@@ -23,7 +23,7 @@ export const SelectSegment: React.FC<Props> = ({ value, onChange, params }) => {
label={
<span>
{ANALYTICS_X_AXIS_VALUES.find((v) => v.value === value)?.label ?? (
<span className="text-custom-text-200">No value</span>
<span className="text-brand-secondary">No value</span>
)}
</span>
}
@@ -22,7 +22,7 @@ export const NotAuthorizedView: React.FC<Props> = ({ actionButton, type }) => {
return (
<DefaultLayout>
<div className="flex h-full w-full flex-col items-center justify-center gap-y-5 bg-custom-background-90 text-center">
<div className="flex h-full w-full flex-col items-center justify-center gap-y-5 bg-brand-surface-1 text-center">
<div className="h-44 w-72">
<Image
src={type === "project" ? ProjectNotAuthorizedImg : WorkspaceNotAuthorizedImg}
@@ -31,16 +31,16 @@ export const NotAuthorizedView: React.FC<Props> = ({ actionButton, type }) => {
alt="ProjectSettingImg"
/>
</div>
<h1 className="text-xl font-medium text-custom-text-100">
<h1 className="text-xl font-medium text-brand-base">
Oops! You are not authorized to view this page
</h1>
<div className="w-full max-w-md text-base text-custom-text-200">
<div className="w-full max-w-md text-base text-brand-secondary">
{user ? (
<p>
You have signed in as {user.email}. <br />
<Link href={`/?next=${currentPath}`}>
<a className="font-medium text-custom-text-100">Sign in</a>
<a className="font-medium text-brand-base">Sign in</a>
</Link>{" "}
with different account that has access to this page.
</p>
@@ -48,7 +48,7 @@ export const NotAuthorizedView: React.FC<Props> = ({ actionButton, type }) => {
<p>
You need to{" "}
<Link href={`/?next=${currentPath}`}>
<a className="font-medium text-custom-text-100">Sign in</a>
<a className="font-medium text-brand-base">Sign in</a>
</Link>{" "}
with an account that has access to this page.
</p>
@@ -41,15 +41,13 @@ export const JoinProject: React.FC = () => {
};
return (
<div className="flex h-full w-full flex-col items-center justify-center gap-y-5 bg-custom-background-90 text-center">
<div className="flex h-full w-full flex-col items-center justify-center gap-y-5 bg-brand-surface-1 text-center">
<div className="h-44 w-72">
<Image src={JoinProjectImg} height="176" width="288" alt="JoinProject" />
</div>
<h1 className="text-xl font-medium text-custom-text-100">
You are not a member of this project
</h1>
<h1 className="text-xl font-medium text-brand-base">You are not a member of this project</h1>
<div className="w-full max-w-md text-base text-custom-text-200">
<div className="w-full max-w-md text-base text-brand-secondary">
<p className="mx-auto w-full text-sm md:w-3/4">
You are not a member of this project, but you can join this project by clicking the button
below.
@@ -11,7 +11,7 @@ export const NotAWorkspaceMember = () => (
<div className="space-y-8 text-center">
<div className="space-y-2">
<h3 className="text-lg font-semibold">Not Authorized!</h3>
<p className="mx-auto w-1/2 text-sm text-custom-text-200">
<p className="mx-auto w-1/2 text-sm text-brand-secondary">
You{"'"}re not a member of this workspace. Please contact the workspace admin to get an
invitation or check your pending invitations.
</p>
+3 -3
View File
@@ -17,12 +17,12 @@ const Breadcrumbs = ({ children }: BreadcrumbsProps) => {
<div className="flex items-center">
<button
type="button"
className="group grid h-7 w-7 flex-shrink-0 cursor-pointer place-items-center rounded border border-custom-sidebar-border-100 text-center text-sm hover:bg-custom-sidebar-background-90"
className="group grid h-7 w-7 flex-shrink-0 cursor-pointer place-items-center rounded border border-brand-base text-center text-sm hover:bg-brand-surface-1"
onClick={() => router.back()}
>
<Icon
iconName="keyboard_backspace"
className="text-base leading-4 text-custom-sidebar-text-200 group-hover:text-custom-sidebar-text-100"
className="text-base leading-4 text-brand-secondary group-hover:text-brand-base"
/>
</button>
{children}
@@ -41,7 +41,7 @@ const BreadcrumbItem: React.FC<BreadcrumbItemProps> = ({ title, link, icon }) =>
<>
{link ? (
<Link href={link}>
<a className="border-r-2 border-custom-sidebar-border-100 px-3 text-sm">
<a className="border-r-2 border-brand-base px-3 text-sm">
<p className={`${icon ? "flex items-center gap-2" : ""}`}>
{icon ?? null}
{title}
@@ -34,8 +34,8 @@ export const ChangeInterfaceTheme: React.FC<Props> = ({ setIsPaletteOpen }) => {
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<SettingIcon className="h-4 w-4 text-custom-text-200" />
<div className="flex items-center gap-2 text-brand-secondary">
<SettingIcon className="h-4 w-4 text-brand-secondary" />
{theme.label}
</div>
</Command.Item>
@@ -408,7 +408,7 @@ export const CommandPalette: React.FC = () => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-brand-backdrop bg-opacity-50 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-30 overflow-y-auto p-4 sm:p-6 md:p-20">
@@ -421,7 +421,7 @@ export const CommandPalette: React.FC = () => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative mx-auto max-w-2xl transform divide-y divide-custom-border-100 divide-opacity-10 rounded-xl border border-custom-border-100 bg-custom-background-100 shadow-2xl transition-all">
<Dialog.Panel className="relative mx-auto max-w-2xl transform divide-y divide-brand-base divide-opacity-10 rounded-xl border border-brand-base bg-brand-base shadow-2xl transition-all">
<Command
filter={(value, search) => {
if (value.toLowerCase().includes(search.toLowerCase())) return 1;
@@ -444,7 +444,7 @@ export const CommandPalette: React.FC = () => {
>
{issueId && issueDetails && (
<div className="flex p-3">
<p className="overflow-hidden truncate rounded-md bg-custom-background-90 p-1 px-2 text-xs font-medium text-custom-text-200">
<p className="overflow-hidden truncate rounded-md bg-brand-surface-1 p-1 px-2 text-xs font-medium text-brand-secondary">
{issueDetails.project_detail?.identifier}-{issueDetails.sequence_id}{" "}
{issueDetails?.name}
</p>
@@ -452,11 +452,11 @@ export const CommandPalette: React.FC = () => {
)}
<div className="relative">
<MagnifyingGlassIcon
className="pointer-events-none absolute top-3.5 left-4 h-5 w-5 text-custom-text-200"
className="pointer-events-none absolute top-3.5 left-4 h-5 w-5 text-brand-secondary"
aria-hidden="true"
/>
<Command.Input
className="w-full border-0 border-b border-custom-border-100 bg-transparent p-4 pl-11 text-custom-text-100 outline-none focus:ring-0 sm:text-sm"
className="w-full border-0 border-b border-brand-base bg-transparent p-4 pl-11 text-brand-base placeholder-gray-500 outline-none focus:ring-0 sm:text-sm"
placeholder={placeholder}
value={searchTerm}
onValueChange={(e) => {
@@ -470,7 +470,7 @@ export const CommandPalette: React.FC = () => {
resultsCount === 0 &&
searchTerm !== "" &&
debouncedSearchTerm !== "" && (
<div className="my-4 text-center text-custom-text-200">
<div className="my-4 text-center text-brand-secondary">
No results found.
</div>
)}
@@ -533,9 +533,9 @@ export const CommandPalette: React.FC = () => {
value={value}
className="focus:outline-none"
>
<div className="flex items-center gap-2 overflow-hidden text-custom-text-200">
<div className="flex items-center gap-2 overflow-hidden text-brand-secondary">
<Icon
className="h-4 w-4 text-custom-text-200"
className="h-4 w-4 text-brand-secondary"
color="#6b7280"
/>
<p className="block flex-1 truncate">{item.name}</p>
@@ -562,8 +562,8 @@ export const CommandPalette: React.FC = () => {
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<Squares2X2Icon className="h-4 w-4 text-custom-text-200" />
<div className="flex items-center gap-2 text-brand-secondary">
<Squares2X2Icon className="h-4 w-4 text-brand-secondary" />
Change state...
</div>
</Command.Item>
@@ -575,8 +575,8 @@ export const CommandPalette: React.FC = () => {
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<ChartBarIcon className="h-4 w-4 text-custom-text-200" />
<div className="flex items-center gap-2 text-brand-secondary">
<ChartBarIcon className="h-4 w-4 text-brand-secondary" />
Change priority...
</div>
</Command.Item>
@@ -588,8 +588,8 @@ export const CommandPalette: React.FC = () => {
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<UsersIcon className="h-4 w-4 text-custom-text-200" />
<div className="flex items-center gap-2 text-brand-secondary">
<UsersIcon className="h-4 w-4 text-brand-secondary" />
Assign to...
</div>
</Command.Item>
@@ -600,15 +600,15 @@ export const CommandPalette: React.FC = () => {
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<div className="flex items-center gap-2 text-brand-secondary">
{issueDetails?.assignees.includes(user.id) ? (
<>
<UserMinusIcon className="h-4 w-4 text-custom-text-200" />
<UserMinusIcon className="h-4 w-4 text-brand-secondary" />
Un-assign from me
</>
) : (
<>
<UserPlusIcon className="h-4 w-4 text-custom-text-200" />
<UserPlusIcon className="h-4 w-4 text-brand-secondary" />
Assign to me
</>
)}
@@ -616,8 +616,8 @@ export const CommandPalette: React.FC = () => {
</Command.Item>
<Command.Item onSelect={deleteIssue} className="focus:outline-none">
<div className="flex items-center gap-2 text-custom-text-200">
<TrashIcon className="h-4 w-4 text-custom-text-200" />
<div className="flex items-center gap-2 text-brand-secondary">
<TrashIcon className="h-4 w-4 text-brand-secondary" />
Delete issue
</div>
</Command.Item>
@@ -628,8 +628,8 @@ export const CommandPalette: React.FC = () => {
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<LinkIcon className="h-4 w-4 text-custom-text-200" />
<div className="flex items-center gap-2 text-brand-secondary">
<LinkIcon className="h-4 w-4 text-brand-secondary" />
Copy issue URL to clipboard
</div>
</Command.Item>
@@ -638,9 +638,9 @@ export const CommandPalette: React.FC = () => {
<Command.Group heading="Issue">
<Command.Item
onSelect={createNewIssue}
className="focus:bg-custom-background-80"
className="focus:bg-brand-surface-2"
>
<div className="flex items-center gap-2 text-custom-text-200">
<div className="flex items-center gap-2 text-brand-secondary">
<LayerDiagonalIcon className="h-4 w-4" color="#6b7280" />
Create new issue
</div>
@@ -654,7 +654,7 @@ export const CommandPalette: React.FC = () => {
onSelect={createNewProject}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<div className="flex items-center gap-2 text-brand-secondary">
<AssignmentClipboardIcon className="h-4 w-4" color="#6b7280" />
Create new project
</div>
@@ -670,7 +670,7 @@ export const CommandPalette: React.FC = () => {
onSelect={createNewCycle}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<div className="flex items-center gap-2 text-brand-secondary">
<ContrastIcon className="h-4 w-4" color="#6b7280" />
Create new cycle
</div>
@@ -683,7 +683,7 @@ export const CommandPalette: React.FC = () => {
onSelect={createNewModule}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<div className="flex items-center gap-2 text-brand-secondary">
<PeopleGroupIcon className="h-4 w-4" color="#6b7280" />
Create new module
</div>
@@ -693,7 +693,7 @@ export const CommandPalette: React.FC = () => {
<Command.Group heading="View">
<Command.Item onSelect={createNewView} className="focus:outline-none">
<div className="flex items-center gap-2 text-custom-text-200">
<div className="flex items-center gap-2 text-brand-secondary">
<ViewListIcon className="h-4 w-4" color="#6b7280" />
Create new view
</div>
@@ -703,7 +703,7 @@ export const CommandPalette: React.FC = () => {
<Command.Group heading="Page">
<Command.Item onSelect={createNewPage} className="focus:outline-none">
<div className="flex items-center gap-2 text-custom-text-200">
<div className="flex items-center gap-2 text-brand-secondary">
<DocumentTextIcon className="h-4 w-4" color="#6b7280" />
Create new page
</div>
@@ -721,7 +721,7 @@ export const CommandPalette: React.FC = () => {
}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<div className="flex items-center gap-2 text-brand-secondary">
<InboxIcon className="h-4 w-4" color="#6b7280" />
Open inbox
</div>
@@ -740,7 +740,7 @@ export const CommandPalette: React.FC = () => {
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<div className="flex items-center gap-2 text-brand-secondary">
<SettingIcon className="h-4 w-4" color="#6b7280" />
Search settings...
</div>
@@ -751,8 +751,8 @@ export const CommandPalette: React.FC = () => {
onSelect={createNewWorkspace}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<FolderPlusIcon className="h-4 w-4 text-custom-text-200" />
<div className="flex items-center gap-2 text-brand-secondary">
<FolderPlusIcon className="h-4 w-4 text-brand-secondary" />
Create new workspace
</div>
</Command.Item>
@@ -764,8 +764,8 @@ export const CommandPalette: React.FC = () => {
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<SettingIcon className="h-4 w-4 text-custom-text-200" />
<div className="flex items-center gap-2 text-brand-secondary">
<SettingIcon className="h-4 w-4 text-brand-secondary" />
Change interface theme...
</div>
</Command.Item>
@@ -781,8 +781,8 @@ export const CommandPalette: React.FC = () => {
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<RocketLaunchIcon className="h-4 w-4 text-custom-text-200" />
<div className="flex items-center gap-2 text-brand-secondary">
<RocketLaunchIcon className="h-4 w-4 text-brand-secondary" />
Open keyboard shortcuts
</div>
</Command.Item>
@@ -793,8 +793,8 @@ export const CommandPalette: React.FC = () => {
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<DocumentIcon className="h-4 w-4 text-custom-text-200" />
<div className="flex items-center gap-2 text-brand-secondary">
<DocumentIcon className="h-4 w-4 text-brand-secondary" />
Open Plane documentation
</div>
</Command.Item>
@@ -805,7 +805,7 @@ export const CommandPalette: React.FC = () => {
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<div className="flex items-center gap-2 text-brand-secondary">
<DiscordIcon className="h-4 w-4" color="#6b7280" />
Join our Discord
</div>
@@ -820,7 +820,7 @@ export const CommandPalette: React.FC = () => {
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<div className="flex items-center gap-2 text-brand-secondary">
<GithubIcon className="h-4 w-4" color="#6b7280" />
Report a bug
</div>
@@ -832,8 +832,8 @@ export const CommandPalette: React.FC = () => {
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<ChatBubbleOvalLeftEllipsisIcon className="h-4 w-4 text-custom-text-200" />
<div className="flex items-center gap-2 text-brand-secondary">
<ChatBubbleOvalLeftEllipsisIcon className="h-4 w-4 text-brand-secondary" />
Chat with us
</div>
</Command.Item>
@@ -847,8 +847,8 @@ export const CommandPalette: React.FC = () => {
onSelect={() => redirect(`/${workspaceSlug}/settings`)}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<SettingIcon className="h-4 w-4 text-custom-text-200" />
<div className="flex items-center gap-2 text-brand-secondary">
<SettingIcon className="h-4 w-4 text-brand-secondary" />
General
</div>
</Command.Item>
@@ -856,8 +856,8 @@ export const CommandPalette: React.FC = () => {
onSelect={() => redirect(`/${workspaceSlug}/settings/members`)}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<SettingIcon className="h-4 w-4 text-custom-text-200" />
<div className="flex items-center gap-2 text-brand-secondary">
<SettingIcon className="h-4 w-4 text-brand-secondary" />
Members
</div>
</Command.Item>
@@ -865,8 +865,8 @@ export const CommandPalette: React.FC = () => {
onSelect={() => redirect(`/${workspaceSlug}/settings/billing`)}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<SettingIcon className="h-4 w-4 text-custom-text-200" />
<div className="flex items-center gap-2 text-brand-secondary">
<SettingIcon className="h-4 w-4 text-brand-secondary" />
Billing and Plans
</div>
</Command.Item>
@@ -874,8 +874,8 @@ export const CommandPalette: React.FC = () => {
onSelect={() => redirect(`/${workspaceSlug}/settings/integrations`)}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<SettingIcon className="h-4 w-4 text-custom-text-200" />
<div className="flex items-center gap-2 text-brand-secondary">
<SettingIcon className="h-4 w-4 text-brand-secondary" />
Integrations
</div>
</Command.Item>
@@ -883,8 +883,8 @@ export const CommandPalette: React.FC = () => {
onSelect={() => redirect(`/${workspaceSlug}/settings/import-export`)}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<SettingIcon className="h-4 w-4 text-custom-text-200" />
<div className="flex items-center gap-2 text-brand-secondary">
<SettingIcon className="h-4 w-4 text-brand-secondary" />
Import/Export
</div>
</Command.Item>
@@ -85,29 +85,29 @@ export const ShortcutsModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-80 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg">
<div className="bg-custom-background-80 p-5">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-brand-surface-2 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg">
<div className="bg-brand-surface-2 p-5">
<div className="sm:flex sm:items-start">
<div className="flex w-full flex-col gap-y-4 text-center sm:text-left">
<Dialog.Title
as="h3"
className="flex justify-between text-lg font-medium leading-6 text-custom-text-100"
className="flex justify-between text-lg font-medium leading-6 text-brand-base"
>
<span>Keyboard Shortcuts</span>
<span>
<button type="button" onClick={() => setIsOpen(false)}>
<XMarkIcon
className="h-6 w-6 text-custom-text-200 hover:text-custom-text-100"
className="h-6 w-6 text-gray-400 hover:text-brand-secondary"
aria-hidden="true"
/>
</button>
</span>
</Dialog.Title>
<div>
<div className="flex w-full items-center justify-start gap-1 rounded border-[0.6px] border-custom-border-100 bg-custom-background-90 px-3 py-2">
<MagnifyingGlassIcon className="h-3.5 w-3.5 text-custom-text-200" />
<div className="flex w-full items-center justify-start gap-1 rounded border-[0.6px] border-brand-base bg-brand-surface-1 px-3 py-2">
<MagnifyingGlassIcon className="h-3.5 w-3.5 text-brand-secondary" />
<Input
className="w-full border-none bg-transparent py-1 px-2 text-xs text-custom-text-200 focus:outline-none"
className="w-full border-none bg-transparent py-1 px-2 text-xs text-brand-secondary focus:outline-none"
id="search"
name="search"
type="text"
@@ -123,22 +123,22 @@ export const ShortcutsModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
<div key={shortcut.keys} className="flex w-full flex-col">
<div className="flex flex-col gap-y-3">
<div className="flex items-center justify-between">
<p className="text-sm text-custom-text-200">
<p className="text-sm text-brand-secondary">
{shortcut.description}
</p>
<div className="flex items-center gap-x-2.5">
{shortcut.keys.split(",").map((key, index) => (
<span key={index} className="flex items-center gap-1">
{key === "Ctrl" ? (
<span className="flex h-full items-center rounded-sm border border-custom-border-100 bg-custom-background-90 p-1.5">
<CommandIcon className="h-4 w-4 fill-current text-custom-text-200" />
<span className="flex h-full items-center rounded-sm border border-brand-base bg-brand-surface-1 p-1.5">
<CommandIcon className="h-4 w-4 fill-current text-brand-secondary" />
</span>
) : key === "Ctrl" ? (
<kbd className="rounded-sm border border-custom-border-100 bg-custom-background-90 p-1.5 text-sm font-medium text-custom-text-200">
<CommandIcon className="h-4 w-4 fill-current text-custom-text-200" />
<kbd className="rounded-sm border border-brand-base bg-brand-surface-1 p-1.5 text-sm font-medium text-brand-secondary">
<CommandIcon className="h-4 w-4 fill-current text-brand-secondary" />
</kbd>
) : (
<kbd className="rounded-sm border border-custom-border-100 bg-custom-background-90 px-2 py-1 text-sm font-medium text-custom-text-200">
<kbd className="rounded-sm border border-brand-base bg-brand-surface-1 px-2 py-1 text-sm font-medium text-brand-secondary">
{key}
</kbd>
)}
@@ -151,7 +151,7 @@ export const ShortcutsModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
))
) : (
<div className="flex flex-col gap-y-3">
<p className="text-sm text-custom-text-200">
<p className="text-sm text-brand-secondary">
No shortcuts found for{" "}
<span className="font-semibold italic">
{`"`}
@@ -168,20 +168,20 @@ export const ShortcutsModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
<div className="flex flex-col gap-y-3">
{shortcuts.map(({ keys, description }, index) => (
<div key={index} className="flex items-center justify-between">
<p className="text-sm text-custom-text-200">{description}</p>
<p className="text-sm text-brand-secondary">{description}</p>
<div className="flex items-center gap-x-2.5">
{keys.split(",").map((key, index) => (
<span key={index} className="flex items-center gap-1">
{key === "Ctrl" ? (
<span className="flex h-full items-center rounded-sm border border-custom-border-100 bg-custom-background-90 p-1.5 text-custom-text-200">
<CommandIcon className="h-4 w-4 fill-current text-custom-text-200" />
<span className="flex h-full items-center rounded-sm border border-brand-base bg-brand-surface-1 p-1.5 text-brand-secondary">
<CommandIcon className="h-4 w-4 fill-current text-brand-secondary" />
</span>
) : key === "Ctrl" ? (
<kbd className="rounded-sm border border-custom-border-100 bg-custom-background-90 p-1.5 text-sm font-medium text-custom-text-200">
<CommandIcon className="h-4 w-4 fill-current text-custom-text-200" />
<kbd className="rounded-sm border border-brand-base bg-brand-surface-1 p-1.5 text-sm font-medium text-brand-secondary">
<CommandIcon className="h-4 w-4 fill-current text-brand-secondary" />
</kbd>
) : (
<kbd className="rounded-sm border border-custom-border-100 bg-custom-background-90 px-2 py-1 text-sm font-medium text-custom-text-200">
<kbd className="rounded-sm border border-brand-base bg-brand-surface-1 px-2 py-1 text-sm font-medium text-brand-secondary">
{key}
</kbd>
)}
@@ -85,7 +85,7 @@ export const AllBoards: React.FC<Props> = ({
return (
<div
key={index}
className="flex items-center justify-between gap-2 rounded bg-custom-background-90 p-2 shadow"
className="flex items-center justify-between gap-2 rounded bg-brand-surface-1 p-2 shadow"
>
<div className="flex items-center gap-2">
{currentState &&
@@ -96,7 +96,7 @@ export const AllBoards: React.FC<Props> = ({
: addSpaceIfCamelCase(singleGroup)}
</h4>
</div>
<span className="text-xs text-custom-text-200">0</span>
<span className="text-xs text-brand-secondary">0</span>
</div>
);
})}
@@ -57,6 +57,18 @@ export const BoardHeader: React.FC<Props> = ({
: null
);
let bgColor = "#000000";
if (selectedGroup === "state") bgColor = currentState?.color ?? "#000000";
if (selectedGroup === "priority")
groupTitle === "high"
? (bgColor = "#dc2626")
: groupTitle === "medium"
? (bgColor = "#f97316")
: groupTitle === "low"
? (bgColor = "#22c55e")
: (bgColor = "#ff0000");
const getGroupTitle = () => {
let title = addSpaceIfCamelCase(groupTitle);
@@ -84,8 +96,7 @@ export const BoardHeader: React.FC<Props> = ({
switch (selectedGroup) {
case "state":
icon =
currentState && getStateGroupIcon(currentState.group, "16", "16", currentState.color);
icon = currentState && getStateGroupIcon(currentState.group, "16", "16", bgColor);
break;
case "priority":
icon = getPriorityIcon(groupTitle, "text-lg");
@@ -113,18 +124,18 @@ export const BoardHeader: React.FC<Props> = ({
return (
<div
className={`flex items-center justify-between px-1 ${
!isCollapsed ? "flex-col rounded-md bg-custom-background-90" : ""
!isCollapsed ? "flex-col rounded-md bg-brand-surface-1" : ""
}`}
>
<div className={`flex items-center ${!isCollapsed ? "flex-col gap-2" : "gap-1"}`}>
<div
className={`flex cursor-pointer items-center gap-x-3 max-w-[316px] ${
className={`flex cursor-pointer items-center gap-x-3 ${
!isCollapsed ? "mb-2 flex-col gap-y-2 py-2" : ""
}`}
>
<span className="flex items-center">{getGroupIcon()}</span>
<h2
className="text-lg font-semibold capitalize truncate"
className="text-lg font-semibold capitalize"
style={{
writingMode: !isCollapsed ? "vertical-rl" : "horizontal-tb",
}}
@@ -134,7 +145,7 @@ export const BoardHeader: React.FC<Props> = ({
<span
className={`${
isCollapsed ? "ml-0.5" : ""
} min-w-[2.5rem] rounded-full bg-custom-background-80 py-1 text-center text-xs`}
} min-w-[2.5rem] rounded-full bg-brand-surface-2 py-1 text-center text-xs`}
>
{groupedByIssues?.[groupTitle].length ?? 0}
</span>
@@ -144,7 +155,7 @@ export const BoardHeader: React.FC<Props> = ({
<div className={`flex items-center ${!isCollapsed ? "flex-col pb-2" : ""}`}>
<button
type="button"
className="grid h-7 w-7 place-items-center rounded p-1 text-custom-text-200 outline-none duration-300 hover:bg-custom-background-80"
className="grid h-7 w-7 place-items-center rounded p-1 text-brand-secondary outline-none duration-300 hover:bg-brand-surface-2"
onClick={() => {
setIsCollapsed((prevData) => !prevData);
}}
@@ -158,7 +169,7 @@ export const BoardHeader: React.FC<Props> = ({
{!isCompleted && selectedGroup !== "created_by" && (
<button
type="button"
className="grid h-7 w-7 place-items-center rounded p-1 text-custom-text-200 outline-none duration-300 hover:bg-custom-background-80"
className="grid h-7 w-7 place-items-center rounded p-1 text-brand-secondary outline-none duration-300 hover:bg-brand-surface-2"
onClick={addIssueToState}
>
<PlusIcon className="h-4 w-4" />
@@ -77,9 +77,7 @@ export const SingleBoard: React.FC<Props> = ({
{(provided, snapshot) => (
<div
className={`relative h-full ${
orderBy !== "sort_order" && snapshot.isDraggingOver
? "bg-custom-background-100/20"
: ""
orderBy !== "sort_order" && snapshot.isDraggingOver ? "bg-brand-base/20" : ""
} ${!isCollapsed ? "hidden" : "flex flex-col"}`}
ref={provided.innerRef}
{...provided.droppableProps}
@@ -89,12 +87,12 @@ export const SingleBoard: React.FC<Props> = ({
<div
className={`absolute ${
snapshot.isDraggingOver ? "block" : "hidden"
} pointer-events-none top-0 left-0 z-[99] h-full w-full bg-custom-background-90 opacity-50`}
} pointer-events-none top-0 left-0 z-[99] h-full w-full bg-brand-surface-1 opacity-50`}
/>
<div
className={`absolute ${
snapshot.isDraggingOver ? "block" : "hidden"
} pointer-events-none top-1/2 left-1/2 z-[99] -translate-y-1/2 -translate-x-1/2 whitespace-nowrap rounded bg-custom-background-100 p-2 text-xs`}
} pointer-events-none top-1/2 left-1/2 z-[99] -translate-y-1/2 -translate-x-1/2 whitespace-nowrap rounded bg-brand-base p-2 text-xs`}
>
This board is ordered by{" "}
{replaceUnderscoreIfSnakeCase(
@@ -152,7 +150,7 @@ export const SingleBoard: React.FC<Props> = ({
{type === "issue" ? (
<button
type="button"
className="flex items-center gap-2 font-medium text-custom-primary outline-none p-1"
className="flex items-center gap-2 font-medium text-brand-accent outline-none p-1"
onClick={addIssueToState}
>
<PlusIcon className="h-4 w-4" />
@@ -164,7 +162,7 @@ export const SingleBoard: React.FC<Props> = ({
customButton={
<button
type="button"
className="flex items-center gap-2 font-medium text-custom-primary outline-none"
className="flex items-center gap-2 font-medium text-brand-accent outline-none"
>
<PlusIcon className="h-4 w-4" />
Add Issue
@@ -43,7 +43,7 @@ import {
import { LayerDiagonalIcon } from "components/icons";
// helpers
import { handleIssuesMutation } from "constants/issue";
import { copyTextToClipboard } from "helpers/string.helper";
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
// types
import {
ICurrentUserResponse,
@@ -265,8 +265,8 @@ export const SingleBoardIssue: React.FC<Props> = ({
</a>
</ContextMenu>
<div
className={`mb-3 rounded bg-custom-background-90 shadow ${
snapshot.isDragging ? "border-2 border-custom-primary shadow-lg" : ""
className={`mb-3 rounded bg-brand-base shadow ${
snapshot.isDragging ? "border-2 border-brand-accent shadow-lg" : ""
}`}
ref={provided.innerRef}
{...provided.draggableProps}
@@ -290,7 +290,7 @@ export const SingleBoardIssue: React.FC<Props> = ({
<CustomMenu
customButton={
<button
className="flex w-full cursor-pointer items-center justify-between gap-1 rounded p-1 text-left text-xs duration-300 hover:bg-custom-background-80"
className="flex w-full cursor-pointer items-center justify-between gap-1 rounded p-1 text-left text-xs duration-300 hover:bg-brand-surface-2"
onClick={() => setIsMenuActive(!isMenuActive)}
>
<EllipsisHorizontalIcon className="h-4 w-4" />
@@ -330,11 +330,13 @@ export const SingleBoardIssue: React.FC<Props> = ({
<Link href={`/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`}>
<a>
{properties.key && (
<div className="mb-2.5 text-xs font-medium text-custom-text-200">
<div className="mb-2.5 text-xs font-medium text-brand-secondary">
{issue.project_detail.identifier}-{issue.sequence_id}
</div>
)}
<h5 className="text-sm break-words line-clamp-3">{issue.name}</h5>
<h5 className="text-sm group-hover:text-brand-accent break-words line-clamp-3">
{issue.name}
</h5>
</a>
</Link>
<div className="relative mt-2.5 flex flex-wrap items-center gap-2 text-xs">
@@ -392,9 +394,9 @@ export const SingleBoardIssue: React.FC<Props> = ({
/>
)}
{properties.sub_issue_count && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-100 px-2.5 py-1 text-xs shadow-sm">
<div className="flex cursor-default items-center rounded-md border border-brand-base px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Sub-issue" tooltipContent={`${issue.sub_issues_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<div className="flex items-center gap-1 text-brand-secondary">
<LayerDiagonalIcon className="h-3.5 w-3.5" />
{issue.sub_issues_count}
</div>
@@ -402,9 +404,9 @@ export const SingleBoardIssue: React.FC<Props> = ({
</div>
)}
{properties.link && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-100 px-2.5 py-1 text-xs shadow-sm">
<div className="flex cursor-default items-center rounded-md border border-brand-base px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Link" tooltipContent={`${issue.link_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<div className="flex items-center gap-1 text-brand-secondary">
<LinkIcon className="h-3.5 w-3.5" />
{issue.link_count}
</div>
@@ -412,9 +414,9 @@ export const SingleBoardIssue: React.FC<Props> = ({
</div>
)}
{properties.attachment_count && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-100 px-2.5 py-1 text-xs shadow-sm">
<div className="flex cursor-default items-center rounded-md border border-brand-base px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Attachment" tooltipContent={`${issue.attachment_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<div className="flex items-center gap-1 text-brand-secondary">
<PaperClipIcon className="h-3.5 w-3.5 -rotate-45" />
{issue.attachment_count}
</div>
@@ -173,7 +173,7 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen, user
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative mx-auto max-w-2xl transform rounded-xl border border-custom-border-100 bg-custom-background-100 shadow-2xl transition-all">
<Dialog.Panel className="relative mx-auto max-w-2xl transform divide-y divide-gray-500 rounded-xl border border-brand-base bg-brand-base shadow-2xl transition-all">
<form>
<Combobox
onChange={(val: string) => {
@@ -188,12 +188,12 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen, user
>
<div className="relative m-1">
<MagnifyingGlassIcon
className="pointer-events-none absolute top-3.5 left-4 h-5 w-5 text-custom-text-100 text-opacity-40"
className="pointer-events-none absolute top-3.5 left-4 h-5 w-5 text-brand-base text-opacity-40"
aria-hidden="true"
/>
<input
type="text"
className="h-12 w-full border-0 bg-transparent pl-11 pr-4 text-custom-text-100 outline-none focus:ring-0 sm:text-sm"
className="h-12 w-full border-0 bg-transparent pl-11 pr-4 text-brand-base placeholder-gray-500 outline-none focus:ring-0 sm:text-sm"
placeholder="Search..."
onChange={(event) => setQuery(event.target.value)}
/>
@@ -201,16 +201,16 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen, user
<Combobox.Options
static
className="max-h-80 scroll-py-2 divide-y divide-custom-border-100 overflow-y-auto"
className="max-h-80 scroll-py-2 divide-y divide-brand-base overflow-y-auto"
>
{filteredIssues.length > 0 ? (
<li className="p-2">
{query === "" && (
<h2 className="mt-4 mb-2 px-3 text-xs font-semibold text-custom-text-100">
<h2 className="mt-4 mb-2 px-3 text-xs font-semibold text-brand-base">
Select issues to delete
</h2>
)}
<ul className="text-sm text-custom-text-200">
<ul className="text-sm text-brand-secondary">
{filteredIssues.map((issue) => (
<Combobox.Option
key={issue.id}
@@ -218,7 +218,7 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen, user
value={issue.id}
className={({ active, selected }) =>
`flex cursor-pointer select-none items-center justify-between rounded-md px-3 py-2 ${
active ? "bg-custom-background-80 text-custom-text-100" : ""
active ? "bg-brand-surface-2 text-brand-base" : ""
}`
}
>
@@ -246,9 +246,9 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen, user
) : (
<div className="flex flex-col items-center justify-center gap-4 px-3 py-8 text-center">
<LayerDiagonalIcon height="56" width="56" />
<h3 className="text-custom-text-200">
<h3 className="text-brand-secondary">
No issues found. Create a new issue with{" "}
<pre className="inline rounded bg-custom-background-80 px-2 py-1">C</pre>.
<pre className="inline rounded bg-brand-surface-2 px-2 py-1">C</pre>.
</h3>
</div>
)}
@@ -62,7 +62,7 @@ export const CalendarHeader: React.FC<Props> = ({
{({ open }) => (
<>
<Popover.Button>
<div className="flex items-center justify-center gap-2 text-2xl font-semibold text-custom-text-100">
<div className="flex items-center justify-center gap-2 text-2xl font-semibold text-brand-base">
<span>{formatDate(currentDate, "Month")}</span>{" "}
<span>{formatDate(currentDate, "yyyy")}</span>
</div>
@@ -77,30 +77,30 @@ export const CalendarHeader: React.FC<Props> = ({
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute top-10 left-0 z-20 flex w-full max-w-xs transform flex-col overflow-hidden rounded-[10px] bg-custom-background-80 shadow-lg">
<Popover.Panel className="absolute top-10 left-0 z-20 flex w-full max-w-xs transform flex-col overflow-hidden rounded-[10px] bg-brand-surface-2 shadow-lg">
<div className="flex items-center justify-center gap-5 px-2 py-2 text-sm">
{YEARS_LIST.map((year) => (
<button
onClick={() => updateDate(updateDateWithYear(year.label, currentDate))}
className={` ${
isSameYear(year.value, currentDate)
? "text-sm font-medium text-custom-text-100"
: "text-xs text-custom-text-200 "
} hover:text-sm hover:font-medium hover:text-custom-text-100`}
? "text-sm font-medium text-brand-base"
: "text-xs text-brand-secondary "
} hover:text-sm hover:font-medium hover:text-brand-base`}
>
{year.label}
</button>
))}
</div>
<div className="grid grid-cols-4 border-t border-custom-border-100 px-2">
<div className="grid grid-cols-4 border-t border-brand-base px-2">
{MONTHS_LIST.map((month) => (
<button
onClick={() =>
updateDate(updateDateWithMonth(`${month.value}`, currentDate))
}
className={`px-2 py-2 text-xs text-custom-text-200 hover:font-medium hover:text-custom-text-100 ${
className={`px-2 py-2 text-xs text-brand-secondary hover:font-medium hover:text-brand-base ${
isSameMonth(`${month.value}`, currentDate)
? "font-medium text-custom-text-100"
? "font-medium text-brand-base"
: ""
}`}
>
@@ -152,7 +152,7 @@ export const CalendarHeader: React.FC<Props> = ({
<div className="flex w-full items-center justify-end gap-2">
<button
className="group flex cursor-pointer items-center gap-2 rounded-md border border-custom-border-100 px-3 py-1 text-sm hover:bg-custom-background-80 hover:text-custom-text-100 focus:outline-none"
className="group flex cursor-pointer items-center gap-2 rounded-md border border-brand-base px-3 py-1 text-sm hover:bg-brand-surface-2 hover:text-brand-base focus:outline-none"
onClick={() => {
if (isMonthlyView) {
updateDate(new Date());
@@ -170,7 +170,7 @@ export const CalendarHeader: React.FC<Props> = ({
<CustomMenu
customButton={
<div className="group flex cursor-pointer items-center gap-2 rounded-md border border-custom-border-100 px-3 py-1 text-sm hover:bg-custom-background-80 hover:text-custom-text-100 focus:outline-none ">
<div className="group flex cursor-pointer items-center gap-2 rounded-md border border-brand-base px-3 py-1 text-sm hover:bg-brand-surface-2 hover:text-brand-base focus:outline-none ">
{isMonthlyView ? "Monthly" : "Weekly"}
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
</div>
@@ -181,7 +181,7 @@ export const CalendarHeader: React.FC<Props> = ({
setIsMonthlyView(true);
changeDateRange(startOfWeek(currentDate), lastDayOfWeek(currentDate));
}}
className="w-52 text-sm text-custom-text-200"
className="w-52 text-sm text-brand-secondary"
>
<div className="flex w-full max-w-[260px] items-center justify-between gap-2">
<span className="flex items-center gap-2">Monthly View</span>
@@ -198,7 +198,7 @@ export const CalendarHeader: React.FC<Props> = ({
getCurrentWeekEndDate(currentDate)
);
}}
className="w-52 text-sm text-custom-text-200"
className="w-52 text-sm text-brand-secondary"
>
<div className="flex w-full items-center justify-between gap-2">
<span className="flex items-center gap-2">Weekly View</span>
@@ -207,7 +207,7 @@ export const CalendarHeader: React.FC<Props> = ({
/>
</div>
</CustomMenu.MenuItem>
<div className="mt-1 flex w-52 items-center justify-between border-t border-custom-border-100 py-2 px-1 text-sm text-custom-text-200">
<div className="mt-1 flex w-52 items-center justify-between border-t border-brand-base py-2 px-1 text-sm text-brand-secondary">
<h4>Show weekends</h4>
<ToggleSwitch value={showWeekEnds} onChange={() => setShowWeekEnds(!showWeekEnds)} />
</div>
@@ -170,9 +170,9 @@ export const CalendarView: React.FC<Props> = ({
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || isCompleted;
return calendarIssues ? (
<div className="h-full overflow-y-auto">
<div className="h-full">
<DragDropContext onDragEnd={onDragEnd}>
<div className="h-full rounded-lg p-8 text-custom-text-200">
<div className="h-full rounded-lg p-8 text-brand-secondary">
<CalendarHeader
isMonthlyView={isMonthlyView}
setIsMonthlyView={setIsMonthlyView}
@@ -191,7 +191,7 @@ export const CalendarView: React.FC<Props> = ({
{weeks.map((date, index) => (
<div
key={index}
className={`flex items-center justify-start gap-2 border-custom-border-100 bg-custom-background-90 p-1.5 text-base font-medium text-custom-text-200 ${
className={`flex items-center justify-start gap-2 border-brand-base bg-brand-surface-1 p-1.5 text-base font-medium text-brand-secondary ${
!isMonthlyView
? showWeekEnds
? (index + 1) % 7 === 0
@@ -49,7 +49,7 @@ export const SingleCalendarDate: React.FC<Props> = ({
key={index}
ref={provided.innerRef}
{...provided.droppableProps}
className={`group relative flex min-h-[150px] flex-col gap-1.5 border-t border-custom-border-100 p-2.5 text-left text-sm font-medium hover:bg-custom-background-90 ${
className={`group relative flex min-h-[150px] flex-col gap-1.5 border-t border-brand-base p-2.5 text-left text-sm font-medium hover:bg-brand-surface-1 ${
isMonthlyView ? "" : "pt-9"
} ${
showWeekEnds
@@ -83,7 +83,7 @@ export const SingleCalendarDate: React.FC<Props> = ({
{totalIssues > 4 && (
<button
type="button"
className="w-min whitespace-nowrap rounded-md border border-custom-border-100 bg-custom-background-80 px-1.5 py-1 text-xs"
className="w-min whitespace-nowrap rounded-md border border-brand-base bg-brand-surface-2 px-1.5 py-1 text-xs"
onClick={() => setShowAllIssues((prevData) => !prevData)}
>
{showAllIssues ? "Hide" : totalIssues - 4 + " more"}
@@ -91,13 +91,13 @@ export const SingleCalendarDate: React.FC<Props> = ({
)}
<div
className={`absolute top-2 right-2 flex items-center justify-center rounded-md bg-custom-background-80 p-1 text-xs text-custom-text-200 opacity-0 group-hover:opacity-100`}
className={`absolute top-2 right-2 flex items-center justify-center rounded-md bg-brand-surface-2 p-1 text-xs text-brand-secondary opacity-0 group-hover:opacity-100`}
>
<button
className="flex items-center justify-center gap-1 text-center"
onClick={() => addIssueToDate(date.date)}
>
<PlusSmallIcon className="h-4 w-4 text-custom-text-200" />
<PlusSmallIcon className="h-4 w-4 text-brand-secondary" />
Add issue
</button>
</div>
@@ -163,8 +163,8 @@ export const SingleCalendarIssue: React.FC<Props> = ({
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
className={`w-full relative cursor-pointer rounded border border-custom-border-100 px-1.5 py-1.5 text-xs duration-300 hover:cursor-move hover:bg-custom-background-80 ${
snapshot.isDragging ? "bg-custom-background-80 shadow-lg" : ""
className={`w-full relative cursor-pointer rounded border border-brand-base px-1.5 py-1.5 text-xs duration-300 hover:cursor-move hover:bg-brand-surface-2 ${
snapshot.isDragging ? "bg-brand-surface-2 shadow-lg" : ""
}`}
>
<div className="group/card flex w-full flex-col items-start justify-center gap-1.5 text-xs sm:w-auto ">
@@ -199,18 +199,18 @@ export const SingleCalendarIssue: React.FC<Props> = ({
tooltipHeading="Issue ID"
tooltipContent={`${issue.project_detail?.identifier}-${issue.sequence_id}`}
>
<span className="flex-shrink-0 text-xs text-custom-text-200">
<span className="flex-shrink-0 text-xs text-brand-secondary">
{issue.project_detail?.identifier}-{issue.sequence_id}
</span>
</Tooltip>
)}
<Tooltip position="top-left" tooltipHeading="Title" tooltipContent={issue.name}>
<span className="text-xs text-custom-text-100">{truncateText(issue.name, 25)}</span>
<span className="text-xs text-brand-base">{truncateText(issue.name, 25)}</span>
</Tooltip>
</a>
</Link>
{displayProperties && (
<div className="relative mt-1.5 w-full flex flex-wrap items-center gap-2 text-xs">
<div className="relative mt-1.5 flex flex-wrap items-center gap-2 text-xs">
{properties.priority && (
<ViewPrioritySelect
issue={issue}
@@ -225,7 +225,6 @@ export const SingleCalendarIssue: React.FC<Props> = ({
issue={issue}
partialUpdateIssue={partialUpdateIssue}
position="left"
className="max-w-full"
isNotAllowed={isNotAllowed}
user={user}
/>
@@ -267,9 +266,9 @@ export const SingleCalendarIssue: React.FC<Props> = ({
/>
)}
{properties.sub_issue_count && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-100 px-2.5 py-1 text-xs shadow-sm">
<div className="flex cursor-default items-center rounded-md border border-brand-base px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Sub-issue" tooltipContent={`${issue.sub_issues_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<div className="flex items-center gap-1 text-brand-secondary">
<LayerDiagonalIcon className="h-3.5 w-3.5" />
{issue.sub_issues_count}
</div>
@@ -277,9 +276,9 @@ export const SingleCalendarIssue: React.FC<Props> = ({
</div>
)}
{properties.link && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-100 px-2.5 py-1 text-xs shadow-sm">
<div className="flex cursor-default items-center rounded-md border border-brand-base px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Links" tooltipContent={`${issue.link_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<div className="flex items-center gap-1 text-brand-secondary">
<LinkIcon className="h-3.5 w-3.5" />
{issue.link_count}
</div>
@@ -287,9 +286,9 @@ export const SingleCalendarIssue: React.FC<Props> = ({
</div>
)}
{properties.attachment_count && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-100 px-2.5 py-1 text-xs shadow-sm">
<div className="flex cursor-default items-center rounded-md border border-brand-base px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Attachments" tooltipContent={`${issue.attachment_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<div className="flex items-center gap-1 text-brand-secondary">
<PaperClipIcon className="h-3.5 w-3.5 -rotate-45" />
{issue.attachment_count}
</div>
@@ -16,11 +16,9 @@ import { Popover, Transition } from "@headlessui/react";
import { Input } from "components/ui";
// icons
import { ColorPickerIcon } from "components/icons";
// types
import { ICustomTheme } from "types";
type Props = {
name: keyof ICustomTheme;
name: string;
watch: UseFormWatch<any>;
setValue: UseFormSetValue<any>;
error: FieldError | Merge<FieldError, FieldErrorsImpl<any>> | undefined;
@@ -33,25 +31,31 @@ export const ColorPickerInput: React.FC<Props> = ({ name, watch, setValue, error
setValue(name, hex);
};
const getColorText = (colorName: keyof ICustomTheme) => {
const getColorText = (colorName: string) => {
switch (colorName) {
case "background":
case "accent":
return "Accent";
case "bgBase":
return "Background";
case "text":
return "Text";
case "primary":
return "Primary(Theme)";
case "sidebarBackground":
return "Sidebar Background";
case "sidebarText":
return "Sidebar Text";
case "bgSurface1":
return "Background surface 1";
case "bgSurface2":
return "Background surface 2";
case "border":
return "Border";
case "sidebar":
return "Sidebar";
case "textBase":
return "Text primary";
case "textSecondary":
return "Text secondary";
default:
return "Color";
}
};
return (
<div className="relative">
<div className="relative ">
<Input
id={name}
name={name}
@@ -76,12 +80,12 @@ export const ColorPickerInput: React.FC<Props> = ({ name, watch, setValue, error
<Popover.Button
type="button"
className={`group inline-flex items-center outline-none ${
open ? "text-custom-text-100" : "text-custom-text-200"
open ? "text-brand-base" : "text-brand-secondary"
}`}
>
{watch(name) && watch(name) !== "" ? (
<span
className="h-4 w-4 rounded border border-custom-border-100"
className="h-4 w-4 rounded border border-brand-base"
style={{
backgroundColor: `${watch(name)}`,
}}
@@ -90,7 +94,7 @@ export const ColorPickerInput: React.FC<Props> = ({ name, watch, setValue, error
<ColorPickerIcon
height={14}
width={14}
className="fill-current text-custom-text-100"
className="fill-current text-brand-base"
/>
)}
</Popover.Button>
@@ -0,0 +1,198 @@
import React, { useEffect, useState } from "react";
import { useTheme } from "next-themes";
import { useForm } from "react-hook-form";
// hooks
import useUser from "hooks/use-user";
// ui
import { PrimaryButton } from "components/ui";
import { ColorPickerInput } from "components/core";
// services
import userService from "services/user.service";
// helper
import { applyTheme } from "helpers/theme.helper";
// types
import { ICustomTheme } from "types";
type Props = {
preLoadedData?: Partial<ICustomTheme> | null;
};
export const CustomThemeSelector: React.FC<Props> = ({ preLoadedData }) => {
const [darkPalette, setDarkPalette] = useState(false);
const defaultValues = {
accent: preLoadedData?.accent ?? "#FE5050",
bgBase: preLoadedData?.bgBase ?? "#FFF7F7",
bgSurface1: preLoadedData?.bgSurface1 ?? "#FFE0E0",
bgSurface2: preLoadedData?.bgSurface2 ?? "#FFF7F7",
border: preLoadedData?.border ?? "#FFC9C9",
darkPalette: preLoadedData?.darkPalette ?? false,
palette: preLoadedData?.palette ?? "",
sidebar: preLoadedData?.sidebar ?? "#FFFFFF",
textBase: preLoadedData?.textBase ?? "#430000",
textSecondary: preLoadedData?.textSecondary ?? "#323232",
};
const {
register,
formState: { errors, isSubmitting },
handleSubmit,
watch,
setValue,
reset,
} = useForm<any>({
defaultValues,
});
const { setTheme } = useTheme();
const { mutateUser } = useUser();
const handleFormSubmit = async (formData: any) => {
await userService
.updateUser({
theme: {
accent: formData.accent,
bgBase: formData.bgBase,
bgSurface1: formData.bgSurface1,
bgSurface2: formData.bgSurface2,
border: formData.border,
darkPalette: darkPalette,
palette: `${formData.bgBase},${formData.bgSurface1},${formData.bgSurface2},${formData.border},${formData.sidebar},${formData.accent},${formData.textBase},${formData.textSecondary}`,
sidebar: formData.sidebar,
textBase: formData.textBase,
textSecondary: formData.textSecondary,
},
})
.then((res) => {
mutateUser((prevData) => {
if (!prevData) return prevData;
return { ...prevData, user: res };
}, false);
applyTheme(formData.palette, darkPalette);
setTheme("custom");
})
.catch((err) => console.log(err));
};
const handleUpdateTheme = async (formData: any) => {
await handleFormSubmit({ ...formData, darkPalette });
reset({
...defaultValues,
});
};
useEffect(() => {
reset({
...defaultValues,
...preLoadedData,
});
}, [preLoadedData, reset]);
return (
<form onSubmit={handleSubmit(handleUpdateTheme)}>
<div className="space-y-5">
<h3 className="text-lg font-semibold text-brand-base">Customize your theme</h3>
<div className="space-y-4">
<div className="grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-3">
<div className="flex flex-col items-start gap-2">
<h3 className="text-left text-base text-brand-secondary">Background</h3>
<ColorPickerInput
name="bgBase"
error={errors.bgBase}
watch={watch}
setValue={setValue}
register={register}
/>
</div>
<div className="flex flex-col items-start gap-2">
<h3 className="text-left text-base text-brand-secondary">Background surface 1</h3>
<ColorPickerInput
name="bgSurface1"
error={errors.bgSurface1}
watch={watch}
setValue={setValue}
register={register}
/>
</div>
<div className="flex flex-col items-start gap-2">
<h3 className="text-left text-base text-brand-secondary">Background surface 2</h3>
<ColorPickerInput
name="bgSurface2"
error={errors.bgSurface2}
watch={watch}
setValue={setValue}
register={register}
/>
</div>
<div className="flex flex-col items-start gap-2">
<h3 className="text-left text-base text-brand-secondary">Border</h3>
<ColorPickerInput
name="border"
error={errors.border}
watch={watch}
setValue={setValue}
register={register}
/>
</div>
<div className="flex flex-col items-start gap-2">
<h3 className="text-left text-base text-brand-secondary">Sidebar</h3>
<ColorPickerInput
name="sidebar"
error={errors.sidebar}
watch={watch}
setValue={setValue}
register={register}
/>
</div>
<div className="flex flex-col items-start gap-2">
<h3 className="text-left text-base text-brand-secondary">Accent</h3>
<ColorPickerInput
name="accent"
error={errors.accent}
watch={watch}
setValue={setValue}
register={register}
/>
</div>
<div className="flex flex-col items-start gap-2">
<h3 className="text-left text-base text-brand-secondary">Text primary</h3>
<ColorPickerInput
name="textBase"
error={errors.textBase}
watch={watch}
setValue={setValue}
register={register}
/>
</div>
<div className="flex flex-col items-start gap-2">
<h3 className="text-left text-base text-brand-secondary">Text secondary</h3>
<ColorPickerInput
name="textSecondary"
error={errors.textSecondary}
watch={watch}
setValue={setValue}
register={register}
/>
</div>
</div>
</div>
</div>
<div className="mt-5 flex justify-end gap-2">
<PrimaryButton type="submit" loading={isSubmitting}>
{isSubmitting ? "Creating Theme..." : "Set Theme"}
</PrimaryButton>
</div>
</form>
);
};
@@ -141,7 +141,7 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-brand-backdrop bg-opacity-50 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto p-4 sm:p-6 md:p-20">
@@ -154,7 +154,7 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative mx-auto max-w-2xl transform rounded-xl border border-custom-border-100 bg-custom-background-100 shadow-2xl transition-all">
<Dialog.Panel className="relative mx-auto max-w-2xl transform rounded-xl border border-brand-base bg-brand-base shadow-2xl transition-all">
<Combobox
as="div"
onChange={(val: ISearchIssueResponse) => {
@@ -165,24 +165,24 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
>
<div className="relative m-1">
<MagnifyingGlassIcon
className="pointer-events-none absolute top-3.5 left-4 h-5 w-5 text-custom-text-100 text-opacity-40"
className="pointer-events-none absolute top-3.5 left-4 h-5 w-5 text-brand-base text-opacity-40"
aria-hidden="true"
/>
<Combobox.Input
className="h-12 w-full border-0 bg-transparent pl-11 pr-4 text-custom-text-100 outline-none focus:ring-0 sm:text-sm"
className="h-12 w-full border-0 bg-transparent pl-11 pr-4 text-brand-base placeholder-gray-500 outline-none focus:ring-0 sm:text-sm"
placeholder="Type to search..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<div className="text-custom-text-200 text-[0.825rem] p-2">
<div className="text-brand-secondary text-[0.825rem] p-2">
{selectedIssues.length > 0 ? (
<div className="flex items-center gap-2 flex-wrap mt-1">
{selectedIssues.map((issue) => (
<div
key={issue.id}
className="flex items-center gap-1 text-xs border border-custom-border-100 bg-custom-background-80 pl-2 py-1 rounded-md text-custom-text-100 whitespace-nowrap"
className="flex items-center gap-1 text-xs border border-brand-base bg-brand-surface-2 pl-2 py-1 rounded-md text-brand-base whitespace-nowrap"
>
{issue.project__identifier}-{issue.sequence_id}
<button
@@ -194,13 +194,13 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
)
}
>
<XMarkIcon className="h-3 w-3 text-custom-text-200 group-hover:text-custom-text-100" />
<XMarkIcon className="h-3 w-3 text-brand-secondary group-hover:text-brand-base" />
</button>
</div>
))}
</div>
) : (
<div className="w-min text-xs border border-custom-border-100 bg-custom-background-80 p-2 rounded-md whitespace-nowrap">
<div className="w-min text-xs border border-brand-base bg-brand-surface-2 p-2 rounded-md whitespace-nowrap">
No issues selected
</div>
)}
@@ -208,9 +208,9 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
<Combobox.Options static className="max-h-80 scroll-py-2 overflow-y-auto mt-2">
{debouncedSearchTerm !== "" && (
<h5 className="text-[0.825rem] text-custom-text-200 mx-2">
<h5 className="text-[0.825rem] text-brand-secondary mx-2">
Search results for{" "}
<span className="text-custom-text-100">
<span className="text-brand-base">
{'"'}
{debouncedSearchTerm}
{'"'}
@@ -225,9 +225,9 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
debouncedSearchTerm !== "" && (
<div className="flex flex-col items-center justify-center gap-4 px-3 py-8 text-center">
<LayerDiagonalIcon height="52" width="52" />
<h3 className="text-custom-text-200">
<h3 className="text-brand-secondary">
No issues found. Create a new issue with{" "}
<pre className="inline rounded bg-custom-background-80 px-2 py-1 text-sm">
<pre className="inline rounded bg-brand-surface-2 px-2 py-1 text-sm">
C
</pre>
.
@@ -243,9 +243,7 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
<Loader.Item height="40px" />
</Loader>
) : (
<ul
className={`text-sm text-custom-text-100 ${issues.length > 0 ? "p-2" : ""}`}
>
<ul className={`text-sm text-brand-base ${issues.length > 0 ? "p-2" : ""}`}>
{issues.map((issue) => {
const selected = selectedIssues.some((i) => i.id === issue.id);
@@ -256,9 +254,9 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
htmlFor={`issue-${issue.id}`}
value={issue}
className={({ active }) =>
`flex w-full cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-custom-text-200 ${
active ? "bg-custom-background-80 text-custom-text-100" : ""
} ${selected ? "text-custom-text-100" : ""}`
`flex w-full cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-brand-secondary ${
active ? "bg-brand-surface-2 text-brand-base" : ""
} ${selected ? "text-brand-base" : ""}`
}
>
<input type="checkbox" checked={selected} readOnly />
+23 -23
View File
@@ -19,7 +19,7 @@ import {
} from "@heroicons/react/24/outline";
import { BlockedIcon, BlockerIcon, CyclesIcon, TagIcon, UserGroupIcon } from "components/icons";
// helpers
import { renderShortDateWithYearFormat, timeAgo } from "helpers/date-time.helper";
import { renderShortNumericDateFormat, timeAgo } from "helpers/date-time.helper";
import { addSpaceIfCamelCase } from "helpers/string.helper";
// types
import RemirrorRichTextEditor from "components/rich-text-editor";
@@ -55,55 +55,55 @@ const activityDetails: {
},
modules: {
message: "set the module to",
icon: <RectangleGroupIcon className="h-3 w-3 text-custom-text-200" aria-hidden="true" />,
icon: <RectangleGroupIcon className="h-3 w-3 text-brand-secondary" aria-hidden="true" />,
},
state: {
message: "set the state to",
icon: <Squares2X2Icon className="h-3 w-3 text-custom-text-200" aria-hidden="true" />,
icon: <Squares2X2Icon className="h-3 w-3 text-brand-secondary" aria-hidden="true" />,
},
priority: {
message: "set the priority to",
icon: <ChartBarIcon className="h-3 w-3 text-custom-text-200" aria-hidden="true" />,
icon: <ChartBarIcon className="h-3 w-3 text-brand-secondary" aria-hidden="true" />,
},
name: {
message: "set the name to",
icon: (
<ChatBubbleBottomCenterTextIcon className="h-3 w-3 text-custom-text-200" aria-hidden="true" />
<ChatBubbleBottomCenterTextIcon className="h-3 w-3 text-brand-secondary" aria-hidden="true" />
),
},
description: {
message: "updated the description.",
icon: (
<ChatBubbleBottomCenterTextIcon className="h-3 w-3 text-custom-text-200" aria-hidden="true" />
<ChatBubbleBottomCenterTextIcon className="h-3 w-3 text-brand-secondary" aria-hidden="true" />
),
},
estimate_point: {
message: "set the estimate point to",
icon: <PlayIcon className="h-3 w-3 -rotate-90 text-custom-text-200" aria-hidden="true" />,
icon: <PlayIcon className="h-3 w-3 -rotate-90 text-brand-secondary" aria-hidden="true" />,
},
target_date: {
message: "set the due date to",
icon: <CalendarDaysIcon className="h-3 w-3 text-custom-text-200" aria-hidden="true" />,
icon: <CalendarDaysIcon className="h-3 w-3 text-brand-secondary" aria-hidden="true" />,
},
parent: {
message: "set the parent to",
icon: <UserIcon className="h-3 w-3 text-custom-text-200" aria-hidden="true" />,
icon: <UserIcon className="h-3 w-3 text-brand-secondary" aria-hidden="true" />,
},
issue: {
message: "deleted the issue.",
icon: <TrashIcon className="h-3 w-3 text-custom-text-200" aria-hidden="true" />,
icon: <TrashIcon className="h-3 w-3 text-brand-secondary" aria-hidden="true" />,
},
estimate: {
message: "updated the estimate",
icon: <PlayIcon className="h-3 w-3 -rotate-90 text-custom-text-200" aria-hidden="true" />,
icon: <PlayIcon className="h-3 w-3 -rotate-90 text-gray-500" aria-hidden="true" />,
},
link: {
message: "updated the link",
icon: <LinkIcon className="h-3 w-3 text-custom-text-200" aria-hidden="true" />,
icon: <LinkIcon className="h-3 w-3 text-gray-500" aria-hidden="true" />,
},
attachment: {
message: "updated the attachment",
icon: <PaperClipIcon className="h-3 w-3 text-custom-text-200 " aria-hidden="true" />,
icon: <PaperClipIcon className="h-3 w-3 text-gray-500 " aria-hidden="true" />,
},
};
@@ -157,7 +157,7 @@ export const Feeds: React.FC<any> = ({ activities }) => (
) {
const { workspace_detail, project, issue } = activity;
value = (
<span className="text-custom-text-200">
<span className="text-brand-secondary">
created{" "}
<Link href={`/${workspace_detail.slug}/projects/${project}/issues/${issue}`}>
<a className="inline-flex items-center hover:underline">
@@ -187,7 +187,7 @@ export const Feeds: React.FC<any> = ({ activities }) => (
activity.new_value && activity.new_value !== ""
? activity.new_value
: activity.old_value;
value = renderShortDateWithYearFormat(date as string);
value = renderShortNumericDateFormat(date as string);
} else if (activity.field === "description") {
value = "description";
} else if (activity.field === "attachment") {
@@ -221,9 +221,9 @@ export const Feeds: React.FC<any> = ({ activities }) => (
</div>
)}
<span className="absolute -bottom-0.5 -right-1 rounded-tl bg-custom-background-80 px-0.5 py-px">
<span className="absolute -bottom-0.5 -right-1 rounded-tl bg-brand-surface-2 px-0.5 py-px">
<ChatBubbleLeftEllipsisIcon
className="h-3.5 w-3.5 text-custom-text-200"
className="h-3.5 w-3.5 text-brand-secondary"
aria-hidden="true"
/>
</span>
@@ -234,7 +234,7 @@ export const Feeds: React.FC<any> = ({ activities }) => (
{activity.actor_detail.first_name}
{activity.actor_detail.is_bot ? "Bot" : " " + activity.actor_detail.last_name}
</div>
<p className="mt-0.5 text-xs text-custom-text-200">
<p className="mt-0.5 text-xs text-brand-secondary">
Commented {timeAgo(activity.created_at)}
</p>
</div>
@@ -247,7 +247,7 @@ export const Feeds: React.FC<any> = ({ activities }) => (
}
editable={false}
noBorder
customClassName="text-xs border border-custom-border-100 bg-custom-background-100"
customClassName="text-xs border border-brand-base bg-brand-base"
/>
</div>
</div>
@@ -262,7 +262,7 @@ export const Feeds: React.FC<any> = ({ activities }) => (
<div className="relative pb-1">
{activities.length > 1 && activityIdx !== activities.length - 1 ? (
<span
className="absolute top-5 left-5 -ml-px h-full w-0.5 bg-custom-background-80"
className="absolute top-5 left-5 -ml-px h-full w-0.5 bg-brand-surface-2"
aria-hidden="true"
/>
) : null}
@@ -271,7 +271,7 @@ export const Feeds: React.FC<any> = ({ activities }) => (
<div>
<div className="relative px-1.5">
<div className="mt-1.5">
<div className="ring-6 flex h-7 w-7 items-center justify-center rounded-full bg-custom-background-80 ring-white">
<div className="ring-6 flex h-7 w-7 items-center justify-center rounded-full bg-brand-surface-2 ring-white">
{activity.field ? (
activityDetails[activity.field as keyof typeof activityDetails]?.icon
) : activity.actor_detail.avatar &&
@@ -295,7 +295,7 @@ export const Feeds: React.FC<any> = ({ activities }) => (
</div>
</div>
<div className="min-w-0 flex-1 py-3">
<div className="text-xs text-custom-text-200">
<div className="text-xs text-brand-secondary">
<span className="text-gray font-medium">
{activity.actor_detail.first_name}
{activity.actor_detail.is_bot
@@ -303,7 +303,7 @@ export const Feeds: React.FC<any> = ({ activities }) => (
: " " + activity.actor_detail.last_name}
</span>
<span> {action} </span>
<span className="text-xs font-medium text-custom-text-100"> {value} </span>
<span className="text-xs font-medium text-brand-base"> {value} </span>
<span className="whitespace-nowrap">{timeAgo(activity.created_at)}</span>
</div>
</div>
@@ -17,7 +17,6 @@ import stateService from "services/state.service";
// types
import { PROJECT_ISSUE_LABELS, PROJECT_MEMBERS, STATES_LIST } from "constants/fetch-keys";
import { IIssueFilterOptions } from "types";
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
export const FilterList: React.FC<any> = ({ filters, setFilters }) => {
const router = useRouter();
@@ -58,10 +57,10 @@ export const FilterList: React.FC<any> = ({ filters, setFilters }) => {
return (
<div
key={key}
className="flex items-center gap-x-2 rounded-full border border-custom-border-100 bg-custom-background-80 px-2 py-1"
className="flex items-center gap-x-2 rounded-full border border-brand-base bg-brand-surface-2 px-2 py-1"
>
<span className="capitalize text-custom-text-200">
{key === "target_date" ? "Due Date" : replaceUnderscoreIfSnakeCase(key)}:
<span className="capitalize text-brand-secondary">
{replaceUnderscoreIfSnakeCase(key)}:
</span>
{filters[key as keyof IIssueFilterOptions] === null ||
(filters[key as keyof IIssueFilterOptions]?.length ?? 0) <= 0 ? (
@@ -132,7 +131,7 @@ export const FilterList: React.FC<any> = ({ filters, setFilters }) => {
? "bg-yellow-500/20 text-yellow-500"
: priority === "low"
? "bg-green-500/20 text-green-500"
: "bg-custom-background-90 text-custom-text-200"
: "bg-brand-surface-1 text-brand-secondary"
}`}
>
<span>{getPriorityIcon(priority)}</span>
@@ -171,7 +170,7 @@ export const FilterList: React.FC<any> = ({ filters, setFilters }) => {
return (
<div
key={memberId}
className="inline-flex items-center gap-x-1 rounded-full bg-custom-background-90 px-1 capitalize"
className="inline-flex items-center gap-x-1 rounded-full bg-brand-surface-1 px-1 capitalize"
>
<Avatar user={member} />
<span>{member?.first_name}</span>
@@ -212,7 +211,7 @@ export const FilterList: React.FC<any> = ({ filters, setFilters }) => {
return (
<div
key={`${memberId}-${key}`}
className="inline-flex items-center gap-x-1 rounded-full bg-custom-background-90 px-1 capitalize"
className="inline-flex items-center gap-x-1 rounded-full bg-brand-surface-1 px-1 capitalize"
>
<Avatar user={member} />
<span>{member?.first_name}</span>
@@ -300,51 +299,6 @@ export const FilterList: React.FC<any> = ({ filters, setFilters }) => {
<XMarkIcon className="h-3 w-3" />
</button>
</div>
) : key === "target_date" ? (
<div className="flex flex-wrap items-center gap-1">
{filters.target_date?.map((date: string) => {
if (filters.target_date.length <= 0) return null;
const splitDate = date.split(";");
return (
<div
key={date}
className="inline-flex items-center gap-x-1 rounded-full border border-custom-border-100 bg-custom-background-100 px-1 py-0.5"
>
<div className="h-1.5 w-1.5 rounded-full" />
<span className="capitalize">
{splitDate[1]} {renderShortDateWithYearFormat(splitDate[0])}
</span>
<span
className="cursor-pointer"
onClick={() =>
setFilters(
{
target_date: filters.target_date?.filter(
(d: any) => d !== date
),
},
!Boolean(viewId)
)
}
>
<XMarkIcon className="h-3 w-3" />
</span>
</div>
);
})}
<button
type="button"
onClick={() =>
setFilters({
target_date: null,
})
}
>
<XMarkIcon className="h-3 w-3" />
</button>
</div>
) : (
(filters[key as keyof IIssueFilterOptions] as any)?.join(", ")
)}
@@ -378,10 +332,9 @@ export const FilterList: React.FC<any> = ({ filters, setFilters }) => {
assignees: null,
labels: null,
created_by: null,
target_date: null,
})
}
className="flex items-center gap-x-1 rounded-full border border-custom-border-100 bg-custom-background-80 px-3 py-1.5 text-xs"
className="flex items-center gap-x-1 rounded-full border border-brand-base bg-brand-surface-2 px-3 py-1.5 text-xs"
>
<span>Clear all filters</span>
<XMarkIcon className="h-3 w-3" />
@@ -1,186 +0,0 @@
import { Fragment } from "react";
import { useRouter } from "next/router";
// react-hook-form
import { Controller, useForm } from "react-hook-form";
// react-datepicker
import DatePicker from "react-datepicker";
// headless ui
import { Dialog, Transition } from "@headlessui/react";
// hooks
import useIssuesView from "hooks/use-issues-view";
// components
import { DueDateFilterSelect } from "./due-date-filter-select";
// ui
import { PrimaryButton, SecondaryButton } from "components/ui";
// icons
import { XMarkIcon } from "@heroicons/react/20/solid";
// helpers
import { renderDateFormat, renderShortDateWithYearFormat } from "helpers/date-time.helper";
type Props = {
isOpen: boolean;
handleClose: () => void;
};
type TFormValues = {
filterType: "before" | "after" | "range";
date1: Date;
date2: Date;
};
const defaultValues: TFormValues = {
filterType: "range",
date1: new Date(),
date2: new Date(new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()),
};
export const DueDateFilterModal: React.FC<Props> = ({ isOpen, handleClose }) => {
const { filters, setFilters } = useIssuesView();
const router = useRouter();
const { viewId } = router.query;
const { handleSubmit, watch, control } = useForm<TFormValues>({
defaultValues,
});
const handleFormSubmit = (formData: TFormValues) => {
const { filterType, date1, date2 } = formData;
if (filterType === "range") {
setFilters(
{ target_date: [`${renderDateFormat(date1)};after`, `${renderDateFormat(date2)};before`] },
!Boolean(viewId)
);
} else {
const filteredArray = filters?.target_date?.filter((item) => {
if (item?.includes(filterType)) return false;
return true;
});
const filterOne = filteredArray && filteredArray?.length > 0 ? filteredArray[0] : null;
if (filterOne)
setFilters(
{ target_date: [filterOne, `${renderDateFormat(date1)};${filterType}`] },
!Boolean(viewId)
);
else
setFilters(
{
target_date: [`${renderDateFormat(date1)};${filterType}`],
},
!Boolean(viewId)
);
}
handleClose();
};
const isInvalid =
watch("filterType") === "range" ? new Date(watch("date1")) > new Date(watch("date2")) : false;
const nextDay = new Date(watch("date1"));
nextDay.setDate(nextDay.getDate() + 1);
return (
<Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 flex w-full justify-center overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative flex transform rounded-lg border border-custom-border-100 bg-custom-background-100 px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<form className="space-y-4" onSubmit={handleSubmit(handleFormSubmit)}>
<div className="flex w-full justify-between">
<Controller
control={control}
name="filterType"
render={({ field: { value, onChange } }) => (
<DueDateFilterSelect value={value} onChange={onChange} />
)}
/>
<XMarkIcon
className="border-base h-4 w-4 cursor-pointer"
onClick={handleClose}
/>
</div>
<div className="flex w-full justify-between gap-4">
<Controller
control={control}
name="date1"
render={({ field: { value, onChange } }) => (
<DatePicker
selected={value}
onChange={(val) => onChange(val)}
dateFormat="dd-MM-yyyy"
calendarClassName="h-full"
inline
/>
)}
/>
{watch("filterType") === "range" && (
<Controller
control={control}
name="date2"
render={({ field: { value, onChange } }) => (
<DatePicker
selected={value}
onChange={onChange}
dateFormat="dd-MM-yyyy"
calendarClassName="h-full"
minDate={nextDay}
inline
/>
)}
/>
)}
</div>
{watch("filterType") === "range" && (
<h6 className="text-xs flex items-center gap-1">
<span className="text-custom-text-200">After:</span>
<span>{renderShortDateWithYearFormat(watch("date1"))}</span>
<span className="text-custom-text-200 ml-1">Before:</span>
{!isInvalid && <span>{renderShortDateWithYearFormat(watch("date2"))}</span>}
</h6>
)}
<div className="flex justify-end gap-4">
<SecondaryButton className="flex items-center gap-2" onClick={handleClose}>
Cancel
</SecondaryButton>
<PrimaryButton
type="submit"
className="flex items-center gap-2"
disabled={isInvalid}
>
Apply
</PrimaryButton>
</div>
</form>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
};
@@ -1,58 +0,0 @@
import React from "react";
// ui
import { CustomSelect } from "components/ui";
// icons
import { CalendarBeforeIcon, CalendarAfterIcon, CalendarMonthIcon } from "components/icons";
// fetch-keys
type Props = {
value: string;
onChange: (value: string) => void;
};
type DueDate = {
name: string;
value: string;
icon: any;
};
const dueDateRange: DueDate[] = [
{
name: "Due date before",
value: "before",
icon: <CalendarBeforeIcon className="h-4 w-4 " />,
},
{
name: "Due date after",
value: "after",
icon: <CalendarAfterIcon className="h-4 w-4 " />,
},
{
name: "Due date range",
value: "range",
icon: <CalendarMonthIcon className="h-4 w-4 " />,
},
];
export const DueDateFilterSelect: React.FC<Props> = ({ value, onChange }) => (
<CustomSelect
value={value}
label={
<div className="flex items-center gap-2 text-xs">
{dueDateRange.find((item) => item.value === value)?.icon}
<span>{dueDateRange.find((item) => item.value === value)?.name}</span>
</div>
}
onChange={onChange}
>
{dueDateRange.map((option, index) => (
<CustomSelect.Option key={index} value={option.value}>
<>
<span>{option.icon}</span>
{option.name}
</>
</CustomSelect.Option>
))}
</CustomSelect>
);
@@ -1,4 +0,0 @@
export * from "./due-date-filter-modal";
export * from "./due-date-filter-select";
export * from "./filters-list";
export * from "./issues-view-filter";
@@ -143,7 +143,7 @@ export const GptAssistantModal: React.FC<Props> = ({
return (
<div
className={`absolute ${inset} z-20 w-full space-y-4 rounded-[10px] border border-custom-border-100 bg-custom-background-100 p-4 shadow ${
className={`absolute ${inset} z-20 w-full space-y-4 rounded-[10px] border border-brand-base bg-brand-base p-4 shadow ${
isOpen ? "block" : "hidden"
}`}
>
@@ -62,7 +62,7 @@ export const ImagePickerPopover: React.FC<Props> = ({ label, value, onChange })
return (
<Popover className="relative z-[2]" ref={ref}>
<Popover.Button
className="rounded-md border border-custom-border-100 bg-custom-background-80 px-2 py-1 text-xs text-custom-text-200"
className="rounded-md border border-brand-base bg-brand-surface-2 px-2 py-1 text-xs text-brand-secondary"
onClick={() => setIsOpen((prev) => !prev)}
>
{label}
@@ -76,16 +76,16 @@ export const ImagePickerPopover: React.FC<Props> = ({ label, value, onChange })
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Popover.Panel className="absolute right-0 z-10 mt-2 rounded-md border border-custom-border-100 bg-custom-background-80 shadow-lg">
<div className="h-96 w-80 overflow-auto rounded border border-custom-border-100 bg-custom-background-80 p-5 shadow-2xl sm:max-w-2xl md:w-96 lg:w-[40rem]">
<Popover.Panel className="absolute right-0 z-10 mt-2 rounded-md border border-brand-base bg-brand-surface-2 shadow-lg">
<div className="h-96 w-80 overflow-auto rounded border border-brand-base bg-brand-surface-2 p-5 shadow-2xl sm:max-w-2xl md:w-96 lg:w-[40rem]">
<Tab.Group>
<Tab.List as="span" className="inline-block rounded bg-custom-background-80 p-1">
<Tab.List as="span" className="inline-block rounded bg-brand-surface-2 p-1">
{tabOptions.map((tab) => (
<Tab
key={tab.key}
className={({ selected }) =>
`rounded py-1 px-4 text-center text-sm outline-none transition-colors ${
selected ? "bg-custom-primary text-white" : "text-custom-text-100"
selected ? "bg-brand-accent text-white" : "text-brand-base"
}`
}
>
@@ -104,7 +104,7 @@ export const ImageUploadModal: React.FC<Props> = ({
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-brand-backdrop bg-opacity-50 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-30 overflow-y-auto">
@@ -118,21 +118,18 @@ export const ImageUploadModal: React.FC<Props> = ({
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-100 bg-custom-background-100 px-5 py-8 text-left shadow-xl transition-all sm:w-full sm:max-w-xl sm:p-6">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-brand-base bg-brand-base px-5 py-8 text-left shadow-xl transition-all sm:w-full sm:max-w-xl sm:p-6">
<div className="space-y-5">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-custom-text-100"
>
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-brand-base">
Upload Image
</Dialog.Title>
<div className="space-y-3">
<div className="flex items-center gap-3">
<div
{...getRootProps()}
className={`relative grid h-80 w-full cursor-pointer place-items-center rounded-lg p-12 text-center focus:outline-none focus:ring-2 focus:ring-custom-primary focus:ring-offset-2 ${
className={`relative grid h-80 w-full cursor-pointer place-items-center rounded-lg p-12 text-center focus:outline-none focus:ring-2 focus:ring-brand-accent focus:ring-offset-2 ${
(image === null && isDragActive) || !value
? "border-2 border-dashed border-custom-border-100 hover:bg-custom-background-90"
? "border-2 border-dashed border-brand-base hover:bg-brand-surface-1"
: ""
}`}
>
@@ -140,7 +137,7 @@ export const ImageUploadModal: React.FC<Props> = ({
<>
<button
type="button"
className="absolute top-0 right-0 z-40 translate-x-1/2 -translate-y-1/2 rounded bg-custom-background-90 px-2 py-0.5 text-xs font-medium text-custom-text-200"
className="absolute top-0 right-0 z-40 translate-x-1/2 -translate-y-1/2 rounded bg-brand-surface-1 px-2 py-0.5 text-xs font-medium text-brand-secondary"
>
Edit
</button>
@@ -154,8 +151,8 @@ export const ImageUploadModal: React.FC<Props> = ({
</>
) : (
<div>
<UserCircleIcon className="mx-auto h-16 w-16 text-custom-text-200" />
<span className="mt-2 block text-sm font-medium text-custom-text-200">
<UserCircleIcon className="mx-auto h-16 w-16 text-brand-secondary" />
<span className="mt-2 block text-sm font-medium text-brand-secondary">
{isDragActive
? "Drop image here to upload"
: "Drag & drop image here"}
+10 -3
View File
@@ -1,12 +1,19 @@
export * from "./board-view";
export * from "./calendar-view";
export * from "./filters";
export * from "./gantt-chart-view";
export * from "./list-view";
export * from "./modals";
export * from "./spreadsheet-view";
export * from "./theme";
export * from "./sidebar";
export * from "./bulk-delete-issues-modal";
export * from "./existing-issues-list-modal";
export * from "./filters-list";
export * from "./gpt-assistant-modal";
export * from "./image-upload-modal";
export * from "./issues-view-filter";
export * from "./issues-view";
export * from "./link-modal";
export * from "./image-picker-popover";
export * from "./feeds";
export * from "./theme-switch";
export * from "./custom-theme-selector";
export * from "./color-picker-input";
@@ -2,12 +2,11 @@ import React from "react";
import { useRouter } from "next/router";
// headless ui
import { Popover, Transition } from "@headlessui/react";
// hooks
import useIssuesProperties from "hooks/use-issue-properties";
import useIssuesView from "hooks/use-issues-view";
import useEstimateOption from "hooks/use-estimate-option";
// headless ui
import { Popover, Transition } from "@headlessui/react";
// components
import { SelectFilters } from "components/views";
// ui
@@ -18,37 +17,15 @@ import {
ListBulletIcon,
Squares2X2Icon,
CalendarDaysIcon,
ChartBarIcon,
} from "@heroicons/react/24/outline";
// helpers
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
import { checkIfArraysHaveSameElements } from "helpers/array.helper";
// types
import { Properties, TIssueViewOptions } from "types";
import { Properties } from "types";
// constants
import { GROUP_BY_OPTIONS, ORDER_BY_OPTIONS, FILTER_ISSUE_OPTIONS } from "constants/issue";
const issueViewOptions: { type: TIssueViewOptions; icon: any }[] = [
{
type: "list",
icon: <ListBulletIcon className="h-4 w-4" />,
},
{
type: "kanban",
icon: <Squares2X2Icon className="h-4 w-4" />,
},
{
type: "calendar",
icon: <CalendarDaysIcon className="h-4 w-4" />,
},
{
type: "spreadsheet",
icon: <Icon iconName="table_chart" />,
},
{
type: "gantt_chart",
icon: <Icon iconName="waterfall_chart" className="rotate-90" />,
},
];
import useEstimateOption from "hooks/use-estimate-option";
export const IssuesFilterView: React.FC = () => {
const router = useRouter();
@@ -79,54 +56,79 @@ export const IssuesFilterView: React.FC = () => {
return (
<div className="flex items-center gap-2">
<div className="flex items-center gap-x-1">
{issueViewOptions.map((option) => (
<button
key={option.type}
type="button"
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80 duration-300 ${
issueView === option.type
? "bg-custom-sidebar-background-80"
: "text-custom-sidebar-text-200"
}`}
onClick={() => setIssueView(option.type)}
>
{option.icon}
</button>
))}
<button
type="button"
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none duration-300 hover:bg-brand-surface-2 ${
issueView === "list" ? "bg-brand-surface-2" : ""
}`}
onClick={() => setIssueView("list")}
>
<ListBulletIcon className="h-4 w-4 text-brand-secondary" />
</button>
<button
type="button"
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none duration-300 hover:bg-brand-surface-2 ${
issueView === "kanban" ? "bg-brand-surface-2" : ""
}`}
onClick={() => setIssueView("kanban")}
>
<Squares2X2Icon className="h-4 w-4 text-brand-secondary" />
</button>
<button
type="button"
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none duration-300 hover:bg-brand-surface-2 ${
issueView === "calendar" ? "bg-brand-surface-2" : ""
}`}
onClick={() => setIssueView("calendar")}
>
<CalendarDaysIcon className="h-4 w-4 text-brand-secondary" />
</button>
<button
type="button"
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none duration-300 hover:bg-brand-surface-2 ${
issueView === "spreadsheet" ? "bg-brand-surface-2" : ""
}`}
onClick={() => setIssueView("spreadsheet")}
>
<Icon iconName="table_chart" className="text-brand-secondary" />
</button>
<button
type="button"
className={`grid h-7 w-7 place-items-center rounded outline-none duration-300 hover:bg-brand-surface-2 ${
issueView === "gantt_chart" ? "bg-brand-surface-2" : ""
}`}
onClick={() => setIssueView("gantt_chart")}
>
<span className="material-symbols-rounded text-brand-secondary text-[18px] rotate-90">
waterfall_chart
</span>
</button>
</div>
<SelectFilters
filters={filters}
onSelect={(option) => {
const key = option.key as keyof typeof filters;
if (key === "target_date") {
const valueExists = checkIfArraysHaveSameElements(
filters.target_date ?? [],
option.value
const valueExists = filters[key]?.includes(option.value);
if (valueExists) {
setFilters(
{
...(filters ?? {}),
[option.key]: ((filters[key] ?? []) as any[])?.filter(
(val) => val !== option.value
),
},
!Boolean(viewId)
);
setFilters({
target_date: valueExists ? null : option.value,
});
} else {
const valueExists = filters[key]?.includes(option.value);
if (valueExists)
setFilters(
{
[option.key]: ((filters[key] ?? []) as any[])?.filter(
(val) => val !== option.value
),
},
!Boolean(viewId)
);
else
setFilters(
{
[option.key]: [...((filters[key] ?? []) as any[]), option.value],
},
!Boolean(viewId)
);
setFilters(
{
...(filters ?? {}),
[option.key]: [...((filters[key] ?? []) as any[]), option.value],
},
!Boolean(viewId)
);
}
}}
direction="left"
@@ -136,10 +138,8 @@ export const IssuesFilterView: React.FC = () => {
{({ open }) => (
<>
<Popover.Button
className={`group flex items-center gap-2 rounded-md border border-custom-sidebar-border-100 bg-transparent px-3 py-1.5 text-xs hover:bg-custom-sidebar-background-90 hover:text-custom-sidebar-text-100 focus:outline-none duration-300 ${
open
? "bg-custom-sidebar-background-90 text-custom-sidebar-text-100"
: "text-custom-sidebar-text-200"
className={`group flex items-center gap-2 rounded-md border border-brand-base bg-transparent px-3 py-1.5 text-xs hover:bg-brand-surface-1 hover:text-brand-base focus:outline-none ${
open ? "bg-brand-surface-1 text-brand-base" : "text-brand-secondary"
}`}
>
View
@@ -155,13 +155,13 @@ export const IssuesFilterView: React.FC = () => {
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute right-0 z-30 mt-1 w-screen max-w-xs transform rounded-lg border border-custom-border-100 bg-custom-background-90 p-3 shadow-lg">
<div className="relative divide-y-2 divide-custom-border-100">
<Popover.Panel className="absolute right-0 z-30 mt-1 w-screen max-w-xs transform rounded-lg border border-brand-base bg-brand-surface-1 p-3 shadow-lg">
<div className="relative divide-y-2 divide-brand-base">
<div className="space-y-4 pb-3 text-xs">
{issueView !== "calendar" && issueView !== "spreadsheet" && (
<>
<div className="flex items-center justify-between">
<h4 className="text-custom-text-200">Group by</h4>
<h4 className="text-brand-secondary">Group by</h4>
<CustomMenu
label={
GROUP_BY_OPTIONS.find((option) => option.key === groupByProperty)
@@ -182,7 +182,7 @@ export const IssuesFilterView: React.FC = () => {
</CustomMenu>
</div>
<div className="flex items-center justify-between">
<h4 className="text-custom-text-200">Order by</h4>
<h4 className="text-brand-secondary">Order by</h4>
<CustomMenu
label={
ORDER_BY_OPTIONS.find((option) => option.key === orderBy)?.name ??
@@ -207,7 +207,7 @@ export const IssuesFilterView: React.FC = () => {
</>
)}
<div className="flex items-center justify-between">
<h4 className="text-custom-text-200">Issue type</h4>
<h4 className="text-brand-secondary">Issue type</h4>
<CustomMenu
label={
FILTER_ISSUE_OPTIONS.find((option) => option.key === filters.type)
@@ -233,7 +233,7 @@ export const IssuesFilterView: React.FC = () => {
{issueView !== "calendar" && issueView !== "spreadsheet" && (
<>
<div className="flex items-center justify-between">
<h4 className="text-custom-text-200">Show empty states</h4>
<h4 className="text-brand-secondary">Show empty states</h4>
<ToggleSwitch
value={showEmptyGroups}
onChange={() => setShowEmptyGroups(!showEmptyGroups)}
@@ -245,7 +245,7 @@ export const IssuesFilterView: React.FC = () => {
</button>
<button
type="button"
className="font-medium text-custom-primary"
className="font-medium text-brand-accent"
onClick={() => setNewFilterDefaultView()}
>
Set as default
@@ -256,22 +256,15 @@ export const IssuesFilterView: React.FC = () => {
</div>
<div className="space-y-2 py-3">
<h4 className="text-sm text-custom-text-200">Display Properties</h4>
<h4 className="text-sm text-brand-secondary">Display Properties</h4>
<div className="flex flex-wrap items-center gap-2">
{Object.keys(properties).map((key) => {
if (key === "estimate" && !isEstimateActive) return null;
if (
issueView === "spreadsheet" &&
(key === "attachment_count" ||
key === "link" ||
key === "sub_issue_count")
)
return null;
if (
issueView !== "spreadsheet" &&
(key === "created_on" || key === "updated_on")
(issueView === "spreadsheet" && key === "sub_issue_count") ||
key === "attachment_count" ||
key === "link"
)
return null;
@@ -281,8 +274,8 @@ export const IssuesFilterView: React.FC = () => {
type="button"
className={`rounded border px-2 py-1 text-xs capitalize ${
properties[key as keyof Properties]
? "border-custom-primary bg-custom-primary text-white"
: "border-custom-border-100"
? "border-brand-accent bg-brand-accent text-white"
: "border-brand-base"
}`}
onClick={() => setProperties(key as keyof Properties)}
>
+52 -19
View File
@@ -29,13 +29,19 @@ import {
} from "components/core";
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
import { CreateUpdateViewModal } from "components/views";
import { TransferIssues, TransferIssuesModal } from "components/cycles";
import { CycleIssuesGanttChartView, TransferIssues, TransferIssuesModal } from "components/cycles";
import { IssueGanttChartView } from "components/issues/gantt-chart";
// ui
import { EmptyState, PrimaryButton, Spinner } from "components/ui";
import { EmptySpace, EmptySpaceItem, EmptyState, PrimaryButton, Spinner } from "components/ui";
// icons
import { PlusIcon, TrashIcon } from "@heroicons/react/24/outline";
import {
ListBulletIcon,
PlusIcon,
RectangleStackIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
// images
import emptyIssue from "public/empty-state/issue.svg";
import emptyIssue from "public/empty-state/empty-issue.svg";
// helpers
import { getStatesList } from "helpers/state.helper";
import { orderArrayBy } from "helpers/array.helper";
@@ -50,6 +56,7 @@ import {
PROJECT_ISSUES_LIST_WITH_PARAMS,
STATES_LIST,
} from "constants/fetch-keys";
import { ModuleIssuesGanttChartView } from "components/modules";
type Props = {
type?: "issue" | "cycle" | "module";
@@ -100,7 +107,7 @@ export const IssuesView: React.FC<Props> = ({
groupByProperty: selectedGroup,
orderBy,
filters,
isEmpty,
isNotEmpty,
setFilters,
params,
} = useIssuesView();
@@ -488,7 +495,7 @@ export const IssuesView: React.FC<Props> = ({
{viewId ? "Update" : "Save"} view
</PrimaryButton>
</div>
{<div className="mt-3 border-t border-custom-border-100" />}
{<div className="mt-3 border-t border-brand-base" />}
</>
)}
@@ -498,7 +505,7 @@ export const IssuesView: React.FC<Props> = ({
<div
className={`${
trashBox ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0"
} fixed top-4 left-1/2 -translate-x-1/2 z-40 w-72 flex items-center justify-center gap-2 rounded border-2 border-red-500/20 bg-custom-background-100 px-3 py-5 text-xs font-medium italic text-red-500 ${
} fixed top-4 left-1/2 -translate-x-1/2 z-40 w-72 flex items-center justify-center gap-2 rounded border-2 border-red-500/20 bg-brand-base px-3 py-5 text-xs font-medium italic text-red-500 ${
snapshot.isDraggingOver ? "bg-red-500 blur-2xl opacity-70" : ""
} transition duration-300`}
ref={provided.innerRef}
@@ -510,7 +517,7 @@ export const IssuesView: React.FC<Props> = ({
)}
</StrictModeDroppable>
{groupedByIssues ? (
!isEmpty || issueView === "kanban" || issueView === "calendar" ? (
isNotEmpty ? (
<>
{isCompleted && <TransferIssues handleClick={() => setTransferIssuesModal(true)} />}
{issueView === "list" ? (
@@ -577,20 +584,46 @@ export const IssuesView: React.FC<Props> = ({
issueView === "gantt_chart" && <GanttChartView />
)}
</>
) : (
) : type === "issue" ? (
<EmptyState
title="Project issues will appear here"
type="issue"
title="Create New Issue"
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
image={emptyIssue}
buttonText="New Issue"
buttonIcon={<PlusIcon className="h-4 w-4" />}
onClick={() => {
const e = new KeyboardEvent("keydown", {
key: "c",
});
document.dispatchEvent(e);
}}
imgURL={emptyIssue}
/>
) : (
<div className="grid h-full w-full place-items-center px-4 sm:px-0">
<EmptySpace
title="You don't have any issue yet."
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
Icon={RectangleStackIcon}
>
<EmptySpaceItem
title="Create a new issue"
description={
<span>
Use <pre className="inline rounded bg-brand-surface-2 px-2 py-1">C</pre>{" "}
shortcut to create a new issue
</span>
}
Icon={PlusIcon}
action={() => {
const e = new KeyboardEvent("keydown", {
key: "c",
});
document.dispatchEvent(e);
}}
/>
{openIssuesListModal && (
<EmptySpaceItem
title="Add an existing issue"
description="Open list"
Icon={ListBulletIcon}
action={openIssuesListModal}
/>
)}
</EmptySpace>
</div>
)
) : (
<div className="flex h-full w-full items-center justify-center">
@@ -70,14 +70,11 @@ export const LinkModal: React.FC<Props> = ({ isOpen, handleClose, onFormSubmit }
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-80 px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-brand-surface-2 px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<div className="space-y-5">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-custom-text-100"
>
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-brand-base">
Add Link
</Dialog.Title>
<div className="mt-2 space-y-3">
@@ -38,7 +38,7 @@ export const AllLists: React.FC<Props> = ({
return (
<>
{groupedByIssues && (
<div className="h-full overflow-y-auto">
<div>
{Object.keys(groupedByIssues).map((singleGroup) => {
const currentState =
selectedGroup === "state" ? states?.find((s) => s.id === singleGroup) : null;
@@ -218,7 +218,7 @@ export const SingleListIssue: React.FC<Props> = ({
</a>
</ContextMenu>
<div
className="flex flex-wrap items-center justify-between px-4 py-2.5 gap-2 border-b border-custom-border-100 bg-custom-background-100 last:border-b-0"
className="flex flex-wrap items-center justify-between px-4 py-2.5 gap-2 border-b border-brand-base bg-brand-base last:border-b-0"
onContextMenu={(e) => {
e.preventDefault();
setContextMenu(true);
@@ -233,13 +233,13 @@ export const SingleListIssue: React.FC<Props> = ({
tooltipHeading="Issue ID"
tooltipContent={`${issue.project_detail?.identifier}-${issue.sequence_id}`}
>
<span className="flex-shrink-0 text-xs text-custom-text-200">
<span className="flex-shrink-0 text-xs text-brand-secondary">
{issue.project_detail?.identifier}-{issue.sequence_id}
</span>
</Tooltip>
)}
<Tooltip position="top-left" tooltipHeading="Title" tooltipContent={issue.name}>
<span className="text-[0.825rem] text-custom-text-100">
<span className="text-[0.825rem] text-brand-base">
{truncateText(issue.name, 50)}
</span>
</Tooltip>
@@ -302,9 +302,9 @@ export const SingleListIssue: React.FC<Props> = ({
/>
)}
{properties.sub_issue_count && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-100 px-2.5 py-1 text-xs shadow-sm">
<div className="flex cursor-default items-center rounded-md border border-brand-base px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Sub-issue" tooltipContent={`${issue.sub_issues_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<div className="flex items-center gap-1 text-brand-secondary">
<LayerDiagonalIcon className="h-3.5 w-3.5" />
{issue.sub_issues_count}
</div>
@@ -312,9 +312,9 @@ export const SingleListIssue: React.FC<Props> = ({
</div>
)}
{properties.link && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-100 px-2.5 py-1 text-xs shadow-sm">
<div className="flex cursor-default items-center rounded-md border border-brand-base px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Links" tooltipContent={`${issue.link_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<div className="flex items-center gap-1 text-brand-secondary">
<LinkIcon className="h-3.5 w-3.5" />
{issue.link_count}
</div>
@@ -322,9 +322,9 @@ export const SingleListIssue: React.FC<Props> = ({
</div>
)}
{properties.attachment_count && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-100 px-2.5 py-1 text-xs shadow-sm">
<div className="flex cursor-default items-center rounded-md border border-brand-base px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Attachments" tooltipContent={`${issue.attachment_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<div className="flex items-center gap-1 text-brand-secondary">
<PaperClipIcon className="h-3.5 w-3.5 -rotate-45" />
{issue.attachment_count}
</div>
@@ -33,6 +33,7 @@ import { PROJECT_ISSUE_LABELS, PROJECT_MEMBERS } from "constants/fetch-keys";
type Props = {
type?: "issue" | "cycle" | "module";
currentState?: IState | null;
bgColor?: string;
groupTitle: string;
groupedByIssues: {
[key: string]: IIssue[];
@@ -52,6 +53,7 @@ type Props = {
export const SingleList: React.FC<Props> = ({
type,
currentState,
bgColor,
groupTitle,
groupedByIssues,
selectedGroup,
@@ -111,8 +113,7 @@ export const SingleList: React.FC<Props> = ({
switch (selectedGroup) {
case "state":
icon =
currentState && getStateGroupIcon(currentState.group, "16", "16", currentState.color);
icon = currentState && getStateGroupIcon(currentState.group, "16", "16", bgColor);
break;
case "priority":
icon = getPriorityIcon(groupTitle, "text-lg");
@@ -141,20 +142,20 @@ export const SingleList: React.FC<Props> = ({
<Disclosure as="div" defaultOpen>
{({ open }) => (
<div>
<div className="flex items-center justify-between px-4 py-2.5 bg-custom-background-90">
<div className="flex items-center justify-between px-4 py-2.5">
<Disclosure.Button>
<div className="flex items-center gap-x-3">
{selectedGroup !== null && (
<div className="flex items-center">{getGroupIcon()}</div>
)}
{selectedGroup !== null ? (
<h2 className="text-sm font-semibold capitalize leading-6 text-custom-text-100">
<h2 className="text-sm font-semibold capitalize leading-6 text-brand-base">
{getGroupTitle()}
</h2>
) : (
<h2 className="font-medium leading-5">All Issues</h2>
)}
<span className="text-custom-text-200 min-w-[2.5rem] rounded-full bg-custom-background-80 py-1 text-center text-xs">
<span className="text-brand-2 min-w-[2.5rem] rounded-full bg-brand-surface-2 py-1 text-center text-xs">
{groupedByIssues[groupTitle as keyof IIssue].length}
</span>
</div>
@@ -162,7 +163,7 @@ export const SingleList: React.FC<Props> = ({
{type === "issue" ? (
<button
type="button"
className="p-1 text-custom-text-200 hover:bg-custom-background-80"
className="p-1 text-brand-secondary hover:bg-brand-surface-2"
onClick={addIssueToState}
>
<PlusIcon className="h-4 w-4" />
@@ -221,7 +222,7 @@ export const SingleList: React.FC<Props> = ({
/>
))
) : (
<p className="bg-custom-background-100 px-4 py-2.5 text-sm text-custom-text-200">
<p className="bg-brand-base px-4 py-2.5 text-sm text-brand-secondary">
No issues.
</p>
)
-5
View File
@@ -1,5 +0,0 @@
export * from "./bulk-delete-issues-modal";
export * from "./existing-issues-list-modal";
export * from "./gpt-assistant-modal";
export * from "./image-upload-modal";
export * from "./link-modal";
@@ -32,15 +32,15 @@ export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, userAuth }
<div className="absolute top-1.5 right-1.5 z-[1] flex items-center gap-1">
<Link href={link.url}>
<a
className="grid h-7 w-7 place-items-center rounded bg-custom-background-90 p-1 outline-none hover:bg-custom-background-80"
className="grid h-7 w-7 place-items-center rounded bg-brand-surface-1 p-1 outline-none hover:bg-brand-surface-2"
target="_blank"
>
<ArrowTopRightOnSquareIcon className="h-4 w-4 text-custom-text-200" />
<ArrowTopRightOnSquareIcon className="h-4 w-4 text-brand-secondary" />
</a>
</Link>
<button
type="button"
className="grid h-7 w-7 place-items-center rounded bg-custom-background-90 p-1 text-red-500 outline-none duration-300 hover:bg-red-500/20"
className="grid h-7 w-7 place-items-center rounded bg-brand-surface-1 p-1 text-red-500 outline-none duration-300 hover:bg-red-500/20"
onClick={() => handleDeleteLink(link.id)}
>
<TrashIcon className="h-4 w-4" />
@@ -48,16 +48,13 @@ export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, userAuth }
</div>
)}
<Link href={link.url}>
<a
className="relative flex gap-2 rounded-md bg-custom-background-100 p-2"
target="_blank"
>
<a className="relative flex gap-2 rounded-md bg-brand-base p-2" target="_blank">
<div className="mt-0.5">
<LinkIcon className="h-3.5 w-3.5" />
</div>
<div>
<h5 className="w-4/5 break-words">{link.title}</h5>
<p className="mt-0.5 text-custom-text-200">
<p className="mt-0.5 text-brand-secondary">
Added {timeAgo(link.created_at)}
<br />
by{" "}
@@ -119,9 +119,9 @@ const ProgressChart: React.FC<Props> = ({ distribution, startDate, endDate, tota
gridXValues={chartData.map((item, index) => (index % 2 === 0 ? item.currentDate : ""))}
enableSlices="x"
sliceTooltip={(datum) => (
<div className="rounded-md border border-custom-border-100 bg-custom-background-80 p-2 text-xs">
<div className="rounded-md border border-brand-base bg-brand-surface-2 p-2 text-xs">
{datum.slice.points[0].data.yFormatted}
<span className="text-custom-text-200"> issues pending on </span>
<span className="text-brand-secondary"> issues pending on </span>
{datum.slice.points[0].data.xFormatted}
</div>
)}
@@ -80,16 +80,16 @@ export const SidebarProgressStats: React.FC<Props> = ({
<Tab.List
as="div"
className={`flex w-full items-center gap-2 justify-between rounded-md ${
noBackground ? "" : "bg-custom-background-90"
noBackground ? "" : "bg-brand-surface-1"
} px-1 py-1.5
${module ? "text-xs" : "text-sm"} `}
>
<Tab
className={({ selected }) =>
`w-full ${
roundedTab ? "rounded-3xl border border-custom-border-100" : "rounded"
} px-3 py-1 text-custom-text-100 ${
selected ? " bg-custom-primary text-white" : " hover:bg-custom-background-80"
roundedTab ? "rounded-3xl border border-brand-base" : "rounded"
} px-3 py-1 text-brand-base ${
selected ? " bg-brand-accent text-white" : " hover:bg-brand-surface-2"
}`
}
>
@@ -98,9 +98,9 @@ export const SidebarProgressStats: React.FC<Props> = ({
<Tab
className={({ selected }) =>
`w-full ${
roundedTab ? "rounded-3xl border border-custom-border-100" : "rounded"
} px-3 py-1 text-custom-text-100 ${
selected ? " bg-custom-primary text-white" : " hover:bg-custom-background-80"
roundedTab ? "rounded-3xl border border-brand-base" : "rounded"
} px-3 py-1 text-brand-base ${
selected ? " bg-brand-accent text-white" : " hover:bg-brand-surface-2"
}`
}
>
@@ -109,16 +109,16 @@ export const SidebarProgressStats: React.FC<Props> = ({
<Tab
className={({ selected }) =>
`w-full ${
roundedTab ? "rounded-3xl border border-custom-border-100" : "rounded"
} px-3 py-1 text-custom-text-100 ${
selected ? " bg-custom-primary text-white" : " hover:bg-custom-background-80"
roundedTab ? "rounded-3xl border border-brand-base" : "rounded"
} px-3 py-1 text-brand-base ${
selected ? " bg-brand-accent text-white" : " hover:bg-brand-surface-2"
}`
}
>
States
</Tab>
</Tab.List>
<Tab.Panels className="flex w-full items-center justify-between pt-1 text-custom-text-200">
<Tab.Panels className="flex w-full items-center justify-between pt-1 text-brand-secondary">
<Tab.Panel as="div" className="w-full space-y-1">
{distribution.assignees.map((assignee, index) => {
if (assignee.assignee_id)
@@ -159,7 +159,7 @@ export const SidebarProgressStats: React.FC<Props> = ({
key={`unassigned-${index}`}
title={
<div className="flex items-center gap-2">
<div className="h-5 w-5 rounded-full border-2 border-custom-border-100 bg-custom-background-80">
<div className="h-5 w-5 rounded-full border-2 border-brand-base bg-brand-surface-2">
<img
src="/user.png"
height="100%"
@@ -19,8 +19,8 @@ export const SingleProgressStats: React.FC<TSingleProgressStatsProps> = ({
}) => (
<div
className={`flex w-full items-center gap-4 justify-between rounded-sm p-1 text-xs ${
onClick ? "cursor-pointer hover:bg-custom-background-90" : ""
} ${selected ? "bg-custom-background-90" : ""}`}
onClick ? "cursor-pointer hover:bg-brand-surface-1" : ""
} ${selected ? "bg-brand-surface-1" : ""}`}
onClick={onClick}
>
<div className="w-1/2">{title}</div>
@@ -42,7 +42,6 @@ import {
import { ICurrentUserResponse, IIssue, ISubIssueResponse, Properties, UserAuth } from "types";
// helper
import { copyTextToClipboard } from "helpers/string.helper";
import { renderLongDetailDateFormat } from "helpers/date-time.helper";
type Props = {
issue: IIssue;
@@ -179,12 +178,12 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
return (
<div
className="relative group grid auto-rows-[minmax(44px,1fr)] hover:rounded-sm hover:bg-custom-background-80 border-b border-custom-border-100 w-full min-w-max"
className="relative group grid auto-rows-[minmax(44px,1fr)] hover:rounded-sm hover:bg-brand-surface-2 border-b border-brand-base w-full min-w-max"
style={{ gridTemplateColumns }}
>
<div className="flex gap-1.5 items-center px-4 sticky z-[1] left-0 text-custom-text-200 bg-custom-background-100 group-hover:text-custom-text-100 group-hover:bg-custom-background-80 border-custom-border-100 w-full">
<div className="flex gap-1.5 items-center px-4 sticky z-[1] left-0 text-brand-secondary bg-brand-base group-hover:text-brand-base group-hover:bg-brand-surface-2 border-brand-base w-full">
<div className="flex gap-1.5 items-center" style={issue.parent ? { paddingLeft } : {}}>
<div className="relative flex items-center cursor-pointer text-xs text-center hover:text-custom-text-100 w-14">
<div className="relative flex items-center cursor-pointer text-xs text-center hover:text-brand-base w-14">
{properties.key && (
<span className="flex items-center justify-center opacity-100 group-hover:opacity-0">
{issue.project_detail?.identifier}-{issue.sequence_id}
@@ -198,11 +197,11 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
onInteraction={(nextOpenState) => setIsOpen(nextOpenState)}
content={
<div
className={`flex flex-col gap-1.5 overflow-y-scroll whitespace-nowrap rounded-md border p-1 text-xs shadow-lg focus:outline-none max-h-44 min-w-full border-custom-border-100 bg-custom-background-90`}
className={`flex flex-col gap-1.5 overflow-y-scroll whitespace-nowrap rounded-md border p-1 text-xs shadow-lg focus:outline-none max-h-44 min-w-full border-brand-base bg-brand-surface-1`}
>
<button
type="button"
className="hover:text-custom-text-200 w-full select-none gap-2 truncate rounded px-1 py-1.5 text-left text-custom-text-200 hover:bg-custom-background-80"
className="hover:text-brand-muted-1 w-full select-none gap-2 truncate rounded px-1 py-1.5 text-left text-brand-secondary hover:bg-brand-surface-2"
onClick={() => {
handleEditIssue(issue);
setIsOpen(false);
@@ -216,7 +215,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
<button
type="button"
className="hover:text-custom-text-200 w-full select-none gap-2 truncate rounded px-1 py-1.5 text-left text-custom-text-200 hover:bg-custom-background-80"
className="hover:text-brand-muted-1 w-full select-none gap-2 truncate rounded px-1 py-1.5 text-left text-brand-secondary hover:bg-brand-surface-2"
onClick={() => {
handleDeleteIssue(issue);
setIsOpen(false);
@@ -230,7 +229,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
<button
type="button"
className="hover:text-custom-text-200 w-full select-none gap-2 truncate rounded px-1 py-1.5 text-left text-custom-text-200 hover:bg-custom-background-80"
className="hover:text-brand-muted-1 w-full select-none gap-2 truncate rounded px-1 py-1.5 text-left text-brand-secondary hover:bg-brand-surface-2"
onClick={() => {
handleCopyText();
setIsOpen(false);
@@ -245,7 +244,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
}
placement="bottom-start"
>
<EllipsisHorizontalIcon className="h-5 w-5 text-custom-text-200" />
<EllipsisHorizontalIcon className="h-5 w-5 text-brand-secondary" />
</Popover2>
</div>
)}
@@ -254,7 +253,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
{issue.sub_issues_count > 0 && (
<div className="h-6 w-6 flex justify-center items-center">
<button
className="h-5 w-5 hover:bg-custom-background-90 hover:text-custom-text-100 rounded-sm cursor-pointer"
className="h-5 w-5 hover:bg-brand-surface-1 hover:text-brand-base rounded-sm cursor-pointer"
onClick={() => handleToggleExpand(issue.id)}
>
<Icon iconName="chevron_right" className={`${expanded ? "rotate-90" : ""}`} />
@@ -264,18 +263,17 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
</div>
<Link href={`/${workspaceSlug}/projects/${issue?.project_detail?.id}/issues/${issue.id}`}>
<a className="truncate text-custom-text-100 cursor-pointer w-full text-[0.825rem]">
<a className="truncate text-brand-base cursor-pointer w-full text-[0.825rem]">
{issue.name}
</a>
</Link>
</div>
{properties.state && (
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-100">
<div className="flex items-center text-xs text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
<ViewStateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
position="left"
className="max-w-full"
tooltipPosition={tooltipPosition}
customButton
user={user}
@@ -284,7 +282,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
</div>
)}
{properties.priority && (
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-100">
<div className="flex items-center text-xs text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
<ViewPrioritySelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -297,7 +295,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
</div>
)}
{properties.assignee && (
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-100">
<div className="flex items-center text-xs text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
<ViewAssigneeSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -310,7 +308,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
</div>
)}
{properties.labels && (
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-100">
<div className="flex items-center text-xs text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
<ViewLabelSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -324,7 +322,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
)}
{properties.due_date && (
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-100">
<div className="flex items-center text-xs text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
<ViewDueDateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -336,7 +334,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
</div>
)}
{properties.estimate && (
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-100">
<div className="flex items-center text-xs text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
<ViewEstimateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -347,16 +345,6 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
/>
</div>
)}
{properties.created_on && (
<div className="flex items-center text-xs cursor-default text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
{renderLongDetailDateFormat(issue.created_at)}
</div>
)}
{properties.updated_on && (
<div className="flex items-center text-xs cursor-default text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
{renderLongDetailDateFormat(issue.updated_at)}
</div>
)}
</div>
);
};
@@ -39,15 +39,13 @@ export const SpreadsheetColumns: React.FC<Props> = ({ columnData, gridTemplateCo
if (col.isActive) {
return (
<div
className={`bg-custom-background-90 w-full ${
col.propertyName === "title"
? "sticky left-0 z-20 bg-custom-background-90 pl-24"
: ""
className={`bg-brand-surface-1 w-full ${
col.propertyName === "title" ? "sticky left-0 z-20 bg-brand-surface-1 pl-24" : ""
}`}
>
{col.propertyName === "title" ? (
<div
className={`flex items-center justify-start gap-1.5 cursor-default text-sm text-custom-text-200 text-current w-full py-2.5 px-2`}
className={`flex items-center justify-start gap-1.5 cursor-default text-sm text-brand-secondary text-current w-full py-2.5 px-2`}
>
{col.colName}
</div>
@@ -56,22 +54,22 @@ export const SpreadsheetColumns: React.FC<Props> = ({ columnData, gridTemplateCo
className="!w-full"
customButton={
<div
className={`relative group flex items-center justify-start gap-1.5 cursor-pointer text-sm text-custom-text-200 text-current hover:text-custom-text-100 w-full py-3 px-2 ${
activeSortingProperty === col.propertyName ? "bg-custom-background-80" : ""
className={`relative group flex items-center justify-start gap-1.5 cursor-pointer text-sm text-brand-secondary text-current hover:text-brand-base w-full py-3 px-2 ${
activeSortingProperty === col.propertyName ? "bg-brand-surface-2" : ""
}`}
>
{activeSortingProperty === col.propertyName && (
<div className="absolute top-1 right-1.5">
<Icon
iconName="filter_list"
className="flex items-center justify-center h-3.5 w-3.5 rounded-full bg-custom-primary text-xs text-white"
className="flex items-center justify-center h-3.5 w-3.5 rounded-full bg-brand-accent text-xs text-white"
/>
</div>
)}
{col.icon ? (
<col.icon
className={`text-custom-text-200 group-hover:text-custom-text-100 ${
className={`text-brand-secondary group-hover:text-brand-base ${
col.propertyName === "estimate" ? "-rotate-90" : ""
}`}
aria-hidden="true"
@@ -79,7 +77,7 @@ export const SpreadsheetColumns: React.FC<Props> = ({ columnData, gridTemplateCo
width="14"
/>
) : col.propertyName === "priority" ? (
<span className="text-sm material-symbols-rounded text-custom-text-200">
<span className="text-sm material-symbols-rounded text-brand-secondary">
signal_cellular_alt
</span>
) : (
@@ -96,7 +94,7 @@ export const SpreadsheetColumns: React.FC<Props> = ({ columnData, gridTemplateCo
<CustomMenu.MenuItem
className={`${
selectedMenuItem === `${col.ascendingOrder}_${col.propertyName}`
? "bg-custom-background-80"
? "bg-brand-surface-2"
: ""
}`}
key={col.propertyName}
@@ -107,8 +105,8 @@ export const SpreadsheetColumns: React.FC<Props> = ({ columnData, gridTemplateCo
<div
className={`group flex gap-1.5 px-1 items-center justify-between ${
selectedMenuItem === `${col.ascendingOrder}_${col.propertyName}`
? "text-custom-text-100"
: "text-custom-text-200 hover:text-custom-text-100"
? "text-brand-base"
: "text-brand-secondary hover:text-brand-base"
}`}
>
<div className="flex gap-2 items-center">
@@ -125,9 +123,7 @@ export const SpreadsheetColumns: React.FC<Props> = ({ columnData, gridTemplateCo
<Icon iconName="east" className="text-sm" />
<span>Z</span>
</>
) : col.propertyName === "due_date" ||
col.propertyName === "created_on" ||
col.propertyName === "updated_on" ? (
) : col.propertyName === "due_date" ? (
<>
<span className="relative flex items-center h-6 w-6">
<Icon
@@ -168,7 +164,7 @@ export const SpreadsheetColumns: React.FC<Props> = ({ columnData, gridTemplateCo
<CustomMenu.MenuItem
className={`mt-0.5 ${
selectedMenuItem === `${col.descendingOrder}_${col.propertyName}`
? "bg-custom-background-80"
? "bg-brand-surface-2"
: ""
}`}
key={col.property}
@@ -179,8 +175,8 @@ export const SpreadsheetColumns: React.FC<Props> = ({ columnData, gridTemplateCo
<div
className={`group flex gap-1.5 px-1 items-center justify-between ${
selectedMenuItem === `${col.descendingOrder}_${col.propertyName}`
? "text-custom-text-100"
: "text-custom-text-200 hover:text-custom-text-100"
? "text-brand-base"
: "text-brand-secondary hover:text-brand-base"
}`}
>
<div className="flex gap-2 items-center">
@@ -251,7 +247,7 @@ export const SpreadsheetColumns: React.FC<Props> = ({ columnData, gridTemplateCo
<CustomMenu.MenuItem
className={`mt-0.5${
selectedMenuItem === `-created_at_${col.propertyName}`
? "bg-custom-background-80"
? "bg-brand-surface-2"
: ""
}`}
key={col.property}
@@ -61,12 +61,12 @@ export const SpreadsheetView: React.FC<Props> = ({
.join(" ");
return (
<div className="h-full rounded-lg text-custom-text-200 overflow-x-auto whitespace-nowrap bg-custom-background-100">
<div className="sticky z-[2] top-0 border-b border-custom-border-100 bg-custom-background-90 w-full min-w-max">
<div className="h-full rounded-lg text-brand-secondary overflow-x-auto whitespace-nowrap bg-brand-base">
<div className="sticky z-[2] top-0 border-b border-brand-base bg-brand-surface-1 w-full min-w-max">
<SpreadsheetColumns columnData={columnData} gridTemplateColumns={gridTemplateColumns} />
</div>
{spreadsheetIssues ? (
<div className="flex flex-col h-full w-full bg-custom-background-100 rounded-sm ">
<div className="flex flex-col h-full w-full bg-brand-base rounded-sm ">
{spreadsheetIssues.map((issue: IIssue, index) => (
<SpreadsheetIssues
key={`${issue.id}_${index}`}
@@ -84,12 +84,12 @@ export const SpreadsheetView: React.FC<Props> = ({
/>
))}
<div
className="relative group grid auto-rows-[minmax(44px,1fr)] hover:rounded-sm hover:bg-custom-background-80 border-b border-custom-border-100 w-full min-w-max"
className="relative group grid auto-rows-[minmax(44px,1fr)] hover:rounded-sm hover:bg-brand-surface-2 border-b border-brand-base w-full min-w-max"
style={{ gridTemplateColumns }}
>
{type === "issue" ? (
<button
className="flex gap-1.5 items-center pl-7 py-2.5 text-sm sticky left-0 z-[1] text-custom-text-200 bg-custom-background-100 group-hover:text-custom-text-100 group-hover:bg-custom-background-80 border-custom-border-100 w-full"
className="flex gap-1.5 items-center pl-7 py-2.5 text-sm sticky left-0 z-[1] text-brand-secondary bg-brand-base group-hover:text-brand-base group-hover:bg-brand-surface-2 border-brand-base w-full"
onClick={() => {
const e = new KeyboardEvent("keydown", { key: "c" });
document.dispatchEvent(e);
@@ -104,7 +104,7 @@ export const SpreadsheetView: React.FC<Props> = ({
className="sticky left-0 z-[1]"
customButton={
<button
className="flex gap-1.5 items-center pl-7 py-2.5 text-sm sticky left-0 z-[1] text-custom-text-200 bg-custom-background-100 group-hover:text-custom-text-100 group-hover:bg-custom-background-80 border-custom-border-100 w-full"
className="flex gap-1.5 items-center pl-7 py-2.5 text-sm sticky left-0 z-[1] text-brand-secondary bg-brand-base group-hover:text-brand-base group-hover:bg-brand-surface-2 border-brand-base w-full"
type="button"
>
<PlusIcon className="h-4 w-4" />
@@ -6,6 +6,8 @@ import { useTheme } from "next-themes";
import { THEMES_OBJ } from "constants/themes";
// ui
import { CustomSelect } from "components/ui";
// helper
import { applyTheme } from "helpers/theme.helper";
// types
import { ICustomTheme, IUser } from "types";
@@ -77,14 +79,19 @@ export const ThemeSwitch: React.FC<Props> = ({
if (!customThemeSelectorOptions) setCustomThemeSelectorOptions(true);
} else {
if (customThemeSelectorOptions) setCustomThemeSelectorOptions(false);
const cssVars = [
"--color-bg-base",
"--color-bg-surface-1",
"--color-bg-surface-2",
for (let i = 10; i <= 900; i >= 100 ? (i += 100) : (i += 10)) {
document.documentElement.style.removeProperty(`--color-background-${i}`);
document.documentElement.style.removeProperty(`--color-text-${i}`);
document.documentElement.style.removeProperty(`--color-primary-${i}`);
document.documentElement.style.removeProperty(`--color-sidebar-background-${i}`);
document.documentElement.style.removeProperty(`--color-sidebar-text-${i}`);
}
"--color-border",
"--color-bg-sidebar",
"--color-accent",
"--color-text-base",
"--color-text-secondary",
];
cssVars.forEach((cssVar) => document.documentElement.style.removeProperty(cssVar));
}
setTheme(value);
document.documentElement.style.setProperty("color-scheme", type);
@@ -1,167 +0,0 @@
import React, { useEffect, useState } from "react";
import { useTheme } from "next-themes";
import { useForm } from "react-hook-form";
// hooks
import useUser from "hooks/use-user";
// ui
import { PrimaryButton } from "components/ui";
import { ColorPickerInput } from "components/core";
// services
import userService from "services/user.service";
// helper
import { applyTheme } from "helpers/theme.helper";
// types
import { ICustomTheme } from "types";
type Props = {
preLoadedData?: Partial<ICustomTheme> | null;
};
const defaultValues: ICustomTheme = {
background: "#fff7f7",
text: "#ffc9c9",
primary: "#fe5050",
sidebarBackground: "#ffffff",
sidebarText: "#000000",
darkPalette: false,
palette: "",
};
export const CustomThemeSelector: React.FC<Props> = ({ preLoadedData }) => {
const [darkPalette, setDarkPalette] = useState(false);
const {
register,
formState: { errors, isSubmitting },
handleSubmit,
watch,
setValue,
reset,
} = useForm<ICustomTheme>({
defaultValues,
});
const { setTheme } = useTheme();
const { mutateUser } = useUser();
const handleFormSubmit = async (formData: ICustomTheme) => {
const payload: ICustomTheme = {
background: formData.background,
text: formData.text,
primary: formData.primary,
sidebarBackground: formData.sidebarBackground,
sidebarText: formData.sidebarText,
darkPalette: darkPalette,
palette: `${formData.background},${formData.text},${formData.primary},${formData.sidebarBackground},${formData.sidebarText}`,
};
await userService
.updateUser({
theme: payload,
})
.then((res) => {
mutateUser((prevData) => {
if (!prevData) return prevData;
return { ...prevData, ...res };
}, false);
applyTheme(payload.palette, darkPalette);
setTheme("custom");
})
.catch((err) => console.log(err));
};
const handleUpdateTheme = async (formData: any) => {
await handleFormSubmit({ ...formData, darkPalette });
};
useEffect(() => {
reset({
...defaultValues,
...preLoadedData,
});
}, [preLoadedData, reset]);
return (
<form onSubmit={handleSubmit(handleUpdateTheme)}>
<div className="space-y-5">
<h3 className="text-lg font-semibold text-custom-text-100">Customize your theme</h3>
<div className="space-y-4">
<div className="grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-2 md:grid-cols-3">
<div className="flex flex-col items-start gap-2">
<h3 className="text-left text-sm font-medium text-custom-text-200">
Background color
</h3>
<ColorPickerInput
name="background"
error={errors.background}
watch={watch}
setValue={setValue}
register={register}
/>
</div>
<div className="flex flex-col items-start gap-2">
<h3 className="text-left text-sm font-medium text-custom-text-200">Text color</h3>
<ColorPickerInput
name="text"
error={errors.text}
watch={watch}
setValue={setValue}
register={register}
/>
</div>
<div className="flex flex-col items-start gap-2">
<h3 className="text-left text-sm font-medium text-custom-text-200">
Primary(Theme) color
</h3>
<ColorPickerInput
name="primary"
error={errors.primary}
watch={watch}
setValue={setValue}
register={register}
/>
</div>
<div className="flex flex-col items-start gap-2">
<h3 className="text-left text-sm font-medium text-custom-text-200">
Sidebar background color
</h3>
<ColorPickerInput
name="sidebarBackground"
error={errors.sidebarBackground}
watch={watch}
setValue={setValue}
register={register}
/>
</div>
<div className="flex flex-col items-start gap-2">
<h3 className="text-left text-sm font-medium text-custom-text-200">
Sidebar text color
</h3>
<ColorPickerInput
name="sidebarText"
error={errors.sidebarText}
watch={watch}
setValue={setValue}
register={register}
/>
</div>
</div>
</div>
</div>
<div className="mt-5 flex justify-end gap-2">
<PrimaryButton type="submit" loading={isSubmitting}>
{isSubmitting ? "Creating Theme..." : "Set Theme"}
</PrimaryButton>
</div>
</form>
);
};
-3
View File
@@ -1,3 +0,0 @@
export * from "./color-picker-input";
export * from "./custom-theme-selector";
export * from "./theme-switch";
@@ -110,8 +110,8 @@ export const ActiveCycleDetails: React.FC = () => {
if (!cycle)
return (
<div className="flex w-full items-center justify-start rounded-[10px] bg-custom-background-80 px-6 py-4">
<h3 className="text-base font-medium text-custom-text-100 ">No active cycle is present.</h3>
<div className="flex w-full items-center justify-start rounded-[10px] bg-brand-surface-2 px-6 py-4">
<h3 className="text-base font-medium text-brand-base ">No active cycle is present.</h3>
</div>
);
@@ -209,8 +209,8 @@ export const ActiveCycleDetails: React.FC = () => {
}));
return (
<div className="grid-row-2 grid rounded-[10px] shadow divide-y bg-custom-background-100 border border-custom-border-100">
<div className="grid grid-cols-1 divide-y border-custom-border-100 lg:divide-y-0 lg:divide-x lg:grid-cols-3">
<div className="grid-row-2 grid rounded-[10px] shadow divide-y bg-brand-base border border-brand-base">
<div className="grid grid-cols-1 divide-y border-brand-base lg:divide-y-0 lg:divide-x lg:grid-cols-3">
<div className="flex flex-col text-xs">
<div className="h-full w-full">
<div className="flex h-60 flex-col gap-5 justify-between rounded-b-[10px] p-4">
@@ -227,7 +227,7 @@ export const ActiveCycleDetails: React.FC = () => {
: cycleStatus === "completed"
? "#3F76FF"
: cycleStatus === "draft"
? "rgb(var(--color-text-200))"
? "#858E96"
: ""
}`}
/>
@@ -300,18 +300,18 @@ export const ActiveCycleDetails: React.FC = () => {
handleAddToFavorites();
}}
>
<StarIcon className="h-4 w-4 " color="rgb(var(--color-text-200))" />
<StarIcon className="h-4 w-4 " color="#858E96" />
</button>
)}
</span>
</div>
<div className="flex items-center justify-start gap-5 text-custom-text-200">
<div className="flex items-center justify-start gap-5 text-brand-secondary">
<div className="flex items-start gap-1">
<CalendarDaysIcon className="h-4 w-4" />
<span>{renderShortDateWithYearFormat(startDate)}</span>
</div>
<ArrowRightIcon className="h-4 w-4 text-custom-text-200" />
<ArrowRightIcon className="h-4 w-4 text-brand-secondary" />
<div className="flex items-start gap-1">
<TargetIcon className="h-4 w-4" />
<span>{renderShortDateWithYearFormat(endDate)}</span>
@@ -319,7 +319,7 @@ export const ActiveCycleDetails: React.FC = () => {
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2.5 text-custom-text-200">
<div className="flex items-center gap-2.5 text-brand-secondary">
{cycle.owned_by.avatar && cycle.owned_by.avatar !== "" ? (
<img
src={cycle.owned_by.avatar}
@@ -329,21 +329,21 @@ export const ActiveCycleDetails: React.FC = () => {
alt={cycle.owned_by.first_name}
/>
) : (
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-custom-background-100 capitalize">
<span className="bg-brand-secondary flex h-5 w-5 items-center justify-center rounded-full bg-brand-base capitalize">
{cycle.owned_by.first_name.charAt(0)}
</span>
)}
<span className="text-custom-text-200">{cycle.owned_by.first_name}</span>
<span className="text-brand-secondary">{cycle.owned_by.first_name}</span>
</div>
{cycle.assignees.length > 0 && (
<div className="flex items-center gap-1 text-custom-text-200">
<div className="flex items-center gap-1 text-brand-secondary">
<AssigneesList users={cycle.assignees} length={4} />
</div>
)}
</div>
<div className="flex items-center gap-4 text-custom-text-200">
<div className="flex items-center gap-4 text-brand-secondary">
<div className="flex gap-2">
<LayerDiagonalIcon className="h-4 w-4 flex-shrink-0" />
{cycle.total_issues} issues
@@ -355,16 +355,16 @@ export const ActiveCycleDetails: React.FC = () => {
</div>
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`}>
<a className="bg-custom-primary text-white px-4 rounded-md py-2 text-center text-sm font-medium w-full hover:bg-custom-primary/90">
<a className="bg-brand-accent text-white px-4 rounded-md py-2 text-center text-sm font-medium w-full hover:bg-brand-accent/90">
View Cycle
</a>
</Link>
</div>
</div>
</div>
<div className="grid col-span-2 grid-cols-1 divide-y border-custom-border-100 md:divide-y-0 md:divide-x md:grid-cols-2">
<div className="flex h-60 flex-col border-custom-border-100">
<div className="flex h-full w-full flex-col text-custom-text-200 p-4">
<div className="grid col-span-2 grid-cols-1 divide-y border-brand-base md:divide-y-0 md:divide-x md:grid-cols-2">
<div className="flex h-60 flex-col border-brand-base">
<div className="flex h-full w-full flex-col text-brand-secondary p-4">
<div className="flex w-full items-center gap-2 py-1">
<span>Progress</span>
<LinearProgressIndicator data={progressIndicatorData} />
@@ -391,22 +391,22 @@ export const ActiveCycleDetails: React.FC = () => {
</div>
</div>
</div>
<div className="border-custom-border-100 h-60 overflow-y-scroll">
<div className="border-brand-base h-60 overflow-y-scroll">
<ActiveCycleProgressStats cycle={cycle} />
</div>
</div>
</div>
<div className="grid grid-cols-1 divide-y border-custom-border-100 lg:divide-y-0 lg:divide-x lg:grid-cols-2">
<div className="grid grid-cols-1 divide-y border-brand-base lg:divide-y-0 lg:divide-x lg:grid-cols-2">
<div className="flex flex-col justify-between p-4">
<div>
<div className="text-custom-primary">High Priority Issues</div>
<div className="text-brand-primary">High Priority Issues</div>
<div className="my-3 flex max-h-[240px] min-h-[240px] flex-col gap-2.5 overflow-y-scroll rounded-md">
{issues ? (
issues.length > 0 ? (
issues.map((issue) => (
<div
key={issue.id}
className="flex flex-wrap rounded-md items-center justify-between gap-2 border border-custom-border-100 bg-custom-background-90 px-3 py-1.5"
className="flex flex-wrap rounded-md items-center justify-between gap-2 border border-brand-base bg-brand-surface-1 px-3 py-1.5"
>
<div className="flex flex-col gap-1">
<div>
@@ -414,7 +414,7 @@ export const ActiveCycleDetails: React.FC = () => {
tooltipHeading="Issue ID"
tooltipContent={`${issue.project_detail?.identifier}-${issue.sequence_id}`}
>
<span className="flex-shrink-0 text-xs text-custom-text-200">
<span className="flex-shrink-0 text-xs text-brand-secondary">
{issue.project_detail?.identifier}-{issue.sequence_id}
</span>
</Tooltip>
@@ -424,7 +424,7 @@ export const ActiveCycleDetails: React.FC = () => {
tooltipHeading="Title"
tooltipContent={issue.name}
>
<span className="text-[0.825rem] text-custom-text-100">
<span className="text-[0.825rem] text-brand-base">
{truncateText(issue.name, 30)}
</span>
</Tooltip>
@@ -444,7 +444,7 @@ export const ActiveCycleDetails: React.FC = () => {
{issue.label_details.map((label) => (
<span
key={label.id}
className="group flex items-center gap-1 rounded-2xl border border-custom-border-100 px-2 py-0.5 text-xs text-custom-text-200"
className="group flex items-center gap-1 rounded-2xl border border-brand-base px-2 py-0.5 text-xs text-brand-secondary"
>
<span
className="h-1.5 w-1.5 rounded-full"
@@ -460,7 +460,7 @@ export const ActiveCycleDetails: React.FC = () => {
) : (
""
)}
<div className={`flex items-center gap-2 text-custom-text-200`}>
<div className={`flex items-center gap-2 text-brand-secondary`}>
{issue.assignees &&
issue.assignees.length > 0 &&
Array.isArray(issue.assignees) ? (
@@ -479,7 +479,7 @@ export const ActiveCycleDetails: React.FC = () => {
</div>
))
) : (
<div className="grid place-items-center text-custom-text-200 text-sm text-center">
<div className="grid place-items-center text-brand-secondary text-sm text-center">
No issues present in the cycle.
</div>
)
@@ -495,7 +495,7 @@ export const ActiveCycleDetails: React.FC = () => {
{issues && issues.length > 0 && (
<div className="flex items-center justify-between gap-2">
<div className="h-1 w-full rounded-full bg-custom-background-80">
<div className="h-1 w-full rounded-full bg-brand-surface-2">
<div
className="h-1 rounded-full bg-green-600"
style={{
@@ -510,16 +510,16 @@ export const ActiveCycleDetails: React.FC = () => {
}}
/>
</div>
<div className="w-16 text-end text-xs text-custom-text-200">
<div className="w-16 text-end text-xs text-brand-secondary">
{issues?.filter((issue) => issue?.state_detail?.group === "completed")?.length} of{" "}
{issues?.length}
</div>
</div>
)}
</div>
<div className="flex flex-col justify-between border-custom-border-100 p-4">
<div className="flex flex-col justify-between border-brand-base p-4">
<div className="flex items-start justify-between gap-4 py-1.5 text-xs">
<div className="flex items-center gap-3 text-custom-text-100">
<div className="flex items-center gap-3 text-brand-base">
<div className="flex items-center justify-center gap-1">
<span className="h-2.5 w-2.5 rounded-full bg-[#a9bbd0]" />
<span>Ideal</span>
@@ -531,7 +531,7 @@ export const ActiveCycleDetails: React.FC = () => {
</div>
<div className="flex items-center gap-1">
<span>
<LayerDiagonalIcon className="h-5 w-5 flex-shrink-0 text-custom-text-200" />
<LayerDiagonalIcon className="h-5 w-5 flex-shrink-0 text-brand-secondary" />
</span>
<span>
Pending Issues -{" "}
@@ -48,12 +48,12 @@ export const ActiveCycleProgressStats: React.FC<Props> = ({ cycle }) => {
>
<Tab.List
as="div"
className="flex sticky top-0 z-10 bg-custom-background-100 w-full px-4 pt-4 pb-1 flex-wrap items-center justify-start gap-4 text-sm"
className="flex sticky top-0 z-10 bg-brand-base w-full px-4 pt-4 pb-1 flex-wrap items-center justify-start gap-4 text-sm"
>
<Tab
className={({ selected }) =>
`px-3 py-1 text-custom-text-100 rounded-3xl border border-custom-border-100 ${
selected ? " bg-custom-primary text-white" : " hover:bg-custom-background-80"
`px-3 py-1 text-brand-base rounded-3xl border border-brand-base ${
selected ? " bg-brand-accent text-white" : " hover:bg-brand-surface-2"
}`
}
>
@@ -61,8 +61,8 @@ export const ActiveCycleProgressStats: React.FC<Props> = ({ cycle }) => {
</Tab>
<Tab
className={({ selected }) =>
`px-3 py-1 text-custom-text-100 rounded-3xl border border-custom-border-100 ${
selected ? " bg-custom-primary text-white" : " hover:bg-custom-background-80"
`px-3 py-1 text-brand-base rounded-3xl border border-brand-base ${
selected ? " bg-brand-accent text-white" : " hover:bg-brand-surface-2"
}`
}
>
@@ -73,7 +73,7 @@ export const ActiveCycleProgressStats: React.FC<Props> = ({ cycle }) => {
<Tab.Panels as={Fragment}>
<Tab.Panel
as="div"
className="w-full gap-1 overflow-y-scroll items-center text-custom-text-200 p-4"
className="w-full gap-1 overflow-y-scroll items-center text-brand-secondary p-4"
>
{cycle.distribution.assignees.map((assignee, index) => {
if (assignee.assignee_id)
@@ -103,7 +103,7 @@ export const ActiveCycleProgressStats: React.FC<Props> = ({ cycle }) => {
key={`unassigned-${index}`}
title={
<div className="flex items-center gap-2">
<div className="h-5 w-5 rounded-full border-2 border-custom-border-100 bg-custom-background-80">
<div className="h-5 w-5 rounded-full border-2 border-brand-base bg-brand-surface-2">
<img
src="/user.png"
height="100%"
@@ -123,7 +123,7 @@ export const ActiveCycleProgressStats: React.FC<Props> = ({ cycle }) => {
</Tab.Panel>
<Tab.Panel
as="div"
className="w-full gap-1 overflow-y-scroll items-center text-custom-text-200 p-4"
className="w-full gap-1 overflow-y-scroll items-center text-brand-secondary p-4"
>
{cycle.distribution.labels.map((label, index) => (
<SingleProgressStats
@@ -146,7 +146,7 @@ export const ActiveCycleProgressStats: React.FC<Props> = ({ cycle }) => {
</Tab.Panel>
</Tab.Panels>
) : (
<div className="grid place-items-center text-custom-text-200 text-sm text-center mt-4">
<div className="grid place-items-center text-brand-secondary text-sm text-center mt-4">
No issues present in the cycle.
</div>
)}

Some files were not shown because too many files have changed in this diff Show More