mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge branch 'develop' of github.com:makeplane/plane into fix/project_creation
This commit is contained in:
commit
54c2a23a7e
@ -11,9 +11,9 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://discord.com/invite/A92xrEGCge">
|
<a href="https://discord.com/invite/A92xrEGCge">
|
||||||
<img alt="Discord" src="https://img.shields.io/discord/1031547764020084846?color=5865F2&label=Discord&style=for-the-badge" />
|
<img alt="Discord online members" src="https://img.shields.io/discord/1031547764020084846?color=5865F2&label=Discord&style=for-the-badge" />
|
||||||
</a>
|
</a>
|
||||||
<img alt="Discord" src="https://img.shields.io/github/commit-activity/m/makeplane/plane?style=for-the-badge" />
|
<img alt="Commit activity per month" src="https://img.shields.io/github/commit-activity/m/makeplane/plane?style=for-the-badge" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
from .workspace import WorkSpaceBasePermission, WorkSpaceAdminPermission, WorkspaceEntityPermission
|
from .workspace import WorkSpaceBasePermission, WorkSpaceAdminPermission, WorkspaceEntityPermission, WorkspaceViewerPermission
|
||||||
from .project import ProjectBasePermission, ProjectEntityPermission, ProjectMemberPermission, ProjectLitePermission
|
from .project import ProjectBasePermission, ProjectEntityPermission, ProjectMemberPermission, ProjectLitePermission
|
||||||
|
@ -61,3 +61,13 @@ class WorkspaceEntityPermission(BasePermission):
|
|||||||
return WorkspaceMember.objects.filter(
|
return WorkspaceMember.objects.filter(
|
||||||
member=request.user, workspace__slug=view.workspace_slug
|
member=request.user, workspace__slug=view.workspace_slug
|
||||||
).exists()
|
).exists()
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspaceViewerPermission(BasePermission):
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
if request.user.is_anonymous:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return WorkspaceMember.objects.filter(
|
||||||
|
member=request.user, workspace__slug=view.workspace_slug, role__gte=10
|
||||||
|
).exists()
|
||||||
|
@ -93,6 +93,7 @@ class ProjectDetailSerializer(BaseSerializer):
|
|||||||
total_cycles = serializers.IntegerField(read_only=True)
|
total_cycles = serializers.IntegerField(read_only=True)
|
||||||
total_modules = serializers.IntegerField(read_only=True)
|
total_modules = serializers.IntegerField(read_only=True)
|
||||||
is_member = serializers.BooleanField(read_only=True)
|
is_member = serializers.BooleanField(read_only=True)
|
||||||
|
sort_order = serializers.FloatField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Project
|
model = Project
|
||||||
|
@ -5,7 +5,7 @@ from datetime import datetime
|
|||||||
# Django imports
|
# Django imports
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
from django.db.models import Q, Exists, OuterRef, Func, F
|
from django.db.models import Q, Exists, OuterRef, Func, F, Min, Subquery
|
||||||
from django.core.validators import validate_email
|
from django.core.validators import validate_email
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
@ -120,9 +120,15 @@ class ProjectViewSet(BaseViewSet):
|
|||||||
project_id=OuterRef("pk"),
|
project_id=OuterRef("pk"),
|
||||||
workspace__slug=self.kwargs.get("slug"),
|
workspace__slug=self.kwargs.get("slug"),
|
||||||
)
|
)
|
||||||
|
sort_order_query = ProjectMember.objects.filter(
|
||||||
|
member=request.user,
|
||||||
|
project_id=OuterRef("pk"),
|
||||||
|
workspace__slug=self.kwargs.get("slug"),
|
||||||
|
).values("sort_order")
|
||||||
projects = (
|
projects = (
|
||||||
self.get_queryset()
|
self.get_queryset()
|
||||||
.annotate(is_favorite=Exists(subquery))
|
.annotate(is_favorite=Exists(subquery))
|
||||||
|
.annotate(sort_order=Subquery(sort_order_query))
|
||||||
.order_by("sort_order", "name")
|
.order_by("sort_order", "name")
|
||||||
.annotate(
|
.annotate(
|
||||||
total_members=ProjectMember.objects.filter(
|
total_members=ProjectMember.objects.filter(
|
||||||
@ -592,17 +598,26 @@ class AddMemberToProjectEndpoint(BaseAPIView):
|
|||||||
{"error": "Atleast one member is required"},
|
{"error": "Atleast one member is required"},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
bulk_project_members = []
|
||||||
|
|
||||||
project_members = ProjectMember.objects.bulk_create(
|
project_members = ProjectMember.objects.filter(
|
||||||
[
|
workspace=self.workspace, member_id__in=[member.get("member_id") for member in members]
|
||||||
|
).values("member_id").annotate(sort_order_min=Min("sort_order"))
|
||||||
|
|
||||||
|
for member in members:
|
||||||
|
sort_order = [project_member.get("sort_order") for project_member in project_members]
|
||||||
|
bulk_project_members.append(
|
||||||
ProjectMember(
|
ProjectMember(
|
||||||
member_id=member.get("member_id"),
|
member_id=member.get("member_id"),
|
||||||
role=member.get("role", 10),
|
role=member.get("role", 10),
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
workspace_id=project.workspace_id,
|
workspace_id=project.workspace_id,
|
||||||
|
sort_order=sort_order[0] - 10000 if len(sort_order) else 65535
|
||||||
)
|
)
|
||||||
for member in members
|
)
|
||||||
],
|
|
||||||
|
project_members = ProjectMember.objects.bulk_create(
|
||||||
|
bulk_project_members,
|
||||||
batch_size=10,
|
batch_size=10,
|
||||||
ignore_conflicts=True,
|
ignore_conflicts=True,
|
||||||
)
|
)
|
||||||
@ -845,12 +860,14 @@ class ProjectUserViewsEndpoint(BaseAPIView):
|
|||||||
view_props = project_member.view_props
|
view_props = project_member.view_props
|
||||||
default_props = project_member.default_props
|
default_props = project_member.default_props
|
||||||
preferences = project_member.preferences
|
preferences = project_member.preferences
|
||||||
|
sort_order = project_member.sort_order
|
||||||
|
|
||||||
project_member.view_props = request.data.get("view_props", view_props)
|
project_member.view_props = request.data.get("view_props", view_props)
|
||||||
project_member.default_props = request.data.get(
|
project_member.default_props = request.data.get(
|
||||||
"default_props", default_props
|
"default_props", default_props
|
||||||
)
|
)
|
||||||
project_member.preferences = request.data.get("preferences", preferences)
|
project_member.preferences = request.data.get("preferences", preferences)
|
||||||
|
project_member.sort_order = request.data.get("sort_order", sort_order)
|
||||||
|
|
||||||
project_member.save()
|
project_member.save()
|
||||||
|
|
||||||
|
@ -73,12 +73,14 @@ from plane.db.models import (
|
|||||||
IssueSubscriber,
|
IssueSubscriber,
|
||||||
Project,
|
Project,
|
||||||
Label,
|
Label,
|
||||||
State,
|
WorkspaceMember,
|
||||||
|
CycleIssue,
|
||||||
)
|
)
|
||||||
from plane.api.permissions import (
|
from plane.api.permissions import (
|
||||||
WorkSpaceBasePermission,
|
WorkSpaceBasePermission,
|
||||||
WorkSpaceAdminPermission,
|
WorkSpaceAdminPermission,
|
||||||
WorkspaceEntityPermission,
|
WorkspaceEntityPermission,
|
||||||
|
WorkspaceViewerPermission,
|
||||||
)
|
)
|
||||||
from plane.bgtasks.workspace_invitation_task import workspace_invitation
|
from plane.bgtasks.workspace_invitation_task import workspace_invitation
|
||||||
from plane.utils.issue_filters import issue_filters
|
from plane.utils.issue_filters import issue_filters
|
||||||
@ -1140,6 +1142,19 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
|
|||||||
.count()
|
.count()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
upcoming_cycles = CycleIssue.objects.filter(
|
||||||
|
workspace__slug=slug,
|
||||||
|
cycle__start_date__gt=timezone.now().date(),
|
||||||
|
issue__assignees__in=[user_id,]
|
||||||
|
).values("cycle__name", "cycle__id", "cycle__project_id")
|
||||||
|
|
||||||
|
present_cycle = CycleIssue.objects.filter(
|
||||||
|
workspace__slug=slug,
|
||||||
|
cycle__start_date__lt=timezone.now().date(),
|
||||||
|
cycle__end_date__gt=timezone.now().date(),
|
||||||
|
issue__assignees__in=[user_id,]
|
||||||
|
).values("cycle__name", "cycle__id", "cycle__project_id")
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"state_distribution": state_distribution,
|
"state_distribution": state_distribution,
|
||||||
@ -1149,6 +1164,8 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
|
|||||||
"completed_issues": completed_issues_count,
|
"completed_issues": completed_issues_count,
|
||||||
"pending_issues": pending_issues_count,
|
"pending_issues": pending_issues_count,
|
||||||
"subscribed_issues": subscribed_issues_count,
|
"subscribed_issues": subscribed_issues_count,
|
||||||
|
"present_cycles": present_cycle,
|
||||||
|
"upcoming_cycles": upcoming_cycles,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -1194,64 +1211,64 @@ class WorkspaceUserActivityEndpoint(BaseAPIView):
|
|||||||
|
|
||||||
|
|
||||||
class WorkspaceUserProfileEndpoint(BaseAPIView):
|
class WorkspaceUserProfileEndpoint(BaseAPIView):
|
||||||
permission_classes = [
|
|
||||||
WorkspaceEntityPermission,
|
|
||||||
]
|
|
||||||
|
|
||||||
def get(self, request, slug, user_id):
|
def get(self, request, slug, user_id):
|
||||||
try:
|
try:
|
||||||
user_data = User.objects.get(pk=user_id)
|
user_data = User.objects.get(pk=user_id)
|
||||||
|
|
||||||
projects = (
|
requesting_workspace_member = WorkspaceMember.objects.get(workspace__slug=slug, member=request.user)
|
||||||
Project.objects.filter(
|
projects = []
|
||||||
workspace__slug=slug,
|
if requesting_workspace_member.role >= 10:
|
||||||
project_projectmember__member=request.user,
|
projects = (
|
||||||
)
|
Project.objects.filter(
|
||||||
.annotate(
|
workspace__slug=slug,
|
||||||
created_issues=Count(
|
project_projectmember__member=request.user,
|
||||||
"project_issue", filter=Q(project_issue__created_by_id=user_id)
|
)
|
||||||
|
.annotate(
|
||||||
|
created_issues=Count(
|
||||||
|
"project_issue", filter=Q(project_issue__created_by_id=user_id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
assigned_issues=Count(
|
||||||
|
"project_issue",
|
||||||
|
filter=Q(project_issue__assignees__in=[user_id]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
completed_issues=Count(
|
||||||
|
"project_issue",
|
||||||
|
filter=Q(
|
||||||
|
project_issue__completed_at__isnull=False,
|
||||||
|
project_issue__assignees__in=[user_id],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
pending_issues=Count(
|
||||||
|
"project_issue",
|
||||||
|
filter=Q(
|
||||||
|
project_issue__state__group__in=[
|
||||||
|
"backlog",
|
||||||
|
"unstarted",
|
||||||
|
"started",
|
||||||
|
],
|
||||||
|
project_issue__assignees__in=[user_id],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.values(
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"identifier",
|
||||||
|
"emoji",
|
||||||
|
"icon_prop",
|
||||||
|
"created_issues",
|
||||||
|
"assigned_issues",
|
||||||
|
"completed_issues",
|
||||||
|
"pending_issues",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.annotate(
|
|
||||||
assigned_issues=Count(
|
|
||||||
"project_issue",
|
|
||||||
filter=Q(project_issue__assignees__in=[user_id]),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.annotate(
|
|
||||||
completed_issues=Count(
|
|
||||||
"project_issue",
|
|
||||||
filter=Q(
|
|
||||||
project_issue__completed_at__isnull=False,
|
|
||||||
project_issue__assignees__in=[user_id],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.annotate(
|
|
||||||
pending_issues=Count(
|
|
||||||
"project_issue",
|
|
||||||
filter=Q(
|
|
||||||
project_issue__state__group__in=[
|
|
||||||
"backlog",
|
|
||||||
"unstarted",
|
|
||||||
"started",
|
|
||||||
],
|
|
||||||
project_issue__assignees__in=[user_id],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.values(
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"identifier",
|
|
||||||
"emoji",
|
|
||||||
"icon_prop",
|
|
||||||
"created_issues",
|
|
||||||
"assigned_issues",
|
|
||||||
"completed_issues",
|
|
||||||
"pending_issues",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
@ -1268,6 +1285,8 @@ class WorkspaceUserProfileEndpoint(BaseAPIView):
|
|||||||
},
|
},
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
|
except WorkspaceMember.DoesNotExist:
|
||||||
|
return Response({"error": "Forbidden"}, status=status.HTTP_403_FORBIDDEN)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
capture_exception(e)
|
capture_exception(e)
|
||||||
return Response(
|
return Response(
|
||||||
@ -1278,7 +1297,7 @@ class WorkspaceUserProfileEndpoint(BaseAPIView):
|
|||||||
|
|
||||||
class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
|
class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
WorkspaceEntityPermission,
|
WorkspaceViewerPermission,
|
||||||
]
|
]
|
||||||
|
|
||||||
def get(self, request, slug, user_id):
|
def get(self, request, slug, user_id):
|
||||||
@ -1317,7 +1336,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
|
|||||||
.annotate(count=Func(F("id"), function="Count"))
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
.values("count")
|
.values("count")
|
||||||
)
|
)
|
||||||
)
|
).distinct()
|
||||||
|
|
||||||
# Priority Ordering
|
# Priority Ordering
|
||||||
if order_by_param == "priority" or order_by_param == "-priority":
|
if order_by_param == "priority" or order_by_param == "-priority":
|
||||||
@ -1394,9 +1413,10 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
|
|||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WorkspaceLabelsEndpoint(BaseAPIView):
|
class WorkspaceLabelsEndpoint(BaseAPIView):
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
WorkspaceEntityPermission,
|
WorkspaceViewerPermission,
|
||||||
]
|
]
|
||||||
|
|
||||||
def get(self, request, slug):
|
def get(self, request, slug):
|
||||||
|
@ -58,16 +58,16 @@ def update_workspace_member_props(apps, schema_editor):
|
|||||||
Model.objects.bulk_update(updated_workspace_member, ["view_props"], batch_size=100)
|
Model.objects.bulk_update(updated_workspace_member, ["view_props"], batch_size=100)
|
||||||
|
|
||||||
|
|
||||||
def update_project_sort_order(apps, schema_editor):
|
def update_project_member_sort_order(apps, schema_editor):
|
||||||
Model = apps.get_model("db", "Project")
|
Model = apps.get_model("db", "ProjectMember")
|
||||||
|
|
||||||
updated_projects = []
|
updated_project_members = []
|
||||||
|
|
||||||
for obj in Model.objects.all():
|
for obj in Model.objects.all():
|
||||||
obj.sort_order = random.randint(1, 65536)
|
obj.sort_order = random.randint(1, 65536)
|
||||||
updated_projects.append(obj)
|
updated_project_members.append(obj)
|
||||||
|
|
||||||
Model.objects.bulk_update(updated_projects, ["sort_order"], batch_size=100)
|
Model.objects.bulk_update(updated_project_members, ["sort_order"], batch_size=100)
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
@ -93,5 +93,5 @@ class Migration(migrations.Migration):
|
|||||||
name='sort_order',
|
name='sort_order',
|
||||||
field=models.FloatField(default=65535),
|
field=models.FloatField(default=65535),
|
||||||
),
|
),
|
||||||
migrations.RunPython(update_project_sort_order),
|
migrations.RunPython(update_project_member_sort_order),
|
||||||
]
|
]
|
||||||
|
@ -91,7 +91,6 @@ class Project(BaseModel):
|
|||||||
default_state = models.ForeignKey(
|
default_state = models.ForeignKey(
|
||||||
"db.State", on_delete=models.SET_NULL, null=True, related_name="default_state"
|
"db.State", on_delete=models.SET_NULL, null=True, related_name="default_state"
|
||||||
)
|
)
|
||||||
sort_order = models.FloatField(default=65535)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Return name of the project"""
|
"""Return name of the project"""
|
||||||
|
@ -2,7 +2,6 @@ import * as React from "react";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
// icons
|
// icons
|
||||||
import { ArrowLeftIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { Icon } from "components/ui";
|
import { Icon } from "components/ui";
|
||||||
|
|
||||||
type BreadcrumbsProps = {
|
type BreadcrumbsProps = {
|
||||||
@ -14,7 +13,7 @@ const Breadcrumbs = ({ children }: BreadcrumbsProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center flex-grow w-full whitespace-nowrap overflow-hidden overflow-ellipsis">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="group grid h-7 w-7 flex-shrink-0 cursor-pointer place-items-center rounded border border-custom-sidebar-border-200 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-custom-sidebar-border-200 text-center text-sm hover:bg-custom-sidebar-background-90"
|
||||||
@ -35,22 +34,36 @@ type BreadcrumbItemProps = {
|
|||||||
title: string;
|
title: string;
|
||||||
link?: string;
|
link?: string;
|
||||||
icon?: any;
|
icon?: any;
|
||||||
|
linkTruncate?: boolean;
|
||||||
|
unshrinkTitle?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BreadcrumbItem: React.FC<BreadcrumbItemProps> = ({ title, link, icon }) => (
|
const BreadcrumbItem: React.FC<BreadcrumbItemProps> = ({
|
||||||
|
title,
|
||||||
|
link,
|
||||||
|
icon,
|
||||||
|
linkTruncate = false,
|
||||||
|
unshrinkTitle = false,
|
||||||
|
}) => (
|
||||||
<>
|
<>
|
||||||
{link ? (
|
{link ? (
|
||||||
<Link href={link}>
|
<Link href={link}>
|
||||||
<a className="border-r-2 border-custom-sidebar-border-200 px-3 text-sm">
|
<a
|
||||||
<p className={`${icon ? "flex items-center gap-2" : ""}`}>
|
className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm ${
|
||||||
|
linkTruncate ? "truncate" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
className={`${linkTruncate ? "truncate" : ""}${icon ? "flex items-center gap-2" : ""}`}
|
||||||
|
>
|
||||||
{icon ?? null}
|
{icon ?? null}
|
||||||
{title}
|
{title}
|
||||||
</p>
|
</p>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<div className="max-w-64 px-3 text-sm">
|
<div className={`px-3 text-sm truncate ${unshrinkTitle ? "flex-shrink-0" : ""}`}>
|
||||||
<p className={`${icon ? "flex items-center gap-2" : ""}`}>
|
<p className={`truncate ${icon ? "flex items-center gap-2" : ""}`}>
|
||||||
{icon}
|
{icon}
|
||||||
<span className="break-words">{title}</span>
|
<span className="break-words">{title}</span>
|
||||||
</p>
|
</p>
|
||||||
|
@ -194,15 +194,15 @@ export const SingleListIssue: React.FC<Props> = ({
|
|||||||
</a>
|
</a>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
<div
|
<div
|
||||||
className="flex flex-wrap items-center justify-between px-4 py-2.5 gap-2 border-b border-custom-border-200 bg-custom-background-100 last:border-b-0"
|
className="flex items-center justify-between px-4 py-2.5 gap-10 border-b border-custom-border-200 bg-custom-background-100 last:border-b-0"
|
||||||
onContextMenu={(e) => {
|
onContextMenu={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setContextMenu(true);
|
setContextMenu(true);
|
||||||
setContextMenuPosition({ x: e.pageX, y: e.pageY });
|
setContextMenuPosition({ x: e.pageX, y: e.pageY });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Link href={singleIssuePath}>
|
<div className="flex-grow cursor-pointer min-w-[200px] whitespace-nowrap overflow-hidden overflow-ellipsis">
|
||||||
<div className="flex-grow cursor-pointer">
|
<Link href={singleIssuePath}>
|
||||||
<a className="group relative flex items-center gap-2">
|
<a className="group relative flex items-center gap-2">
|
||||||
{properties.key && (
|
{properties.key && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@ -215,16 +215,14 @@ export const SingleListIssue: React.FC<Props> = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<Tooltip position="top-left" tooltipHeading="Title" tooltipContent={issue.name}>
|
<Tooltip position="top-left" tooltipHeading="Title" tooltipContent={issue.name}>
|
||||||
<span className="text-[0.825rem] text-custom-text-100">
|
<span className="truncate text-[0.825rem] text-custom-text-100">{issue.name}</span>
|
||||||
{truncateText(issue.name, 50)}
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</Link>
|
||||||
</Link>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`flex w-full flex-shrink flex-wrap items-center gap-2 text-xs sm:w-auto ${
|
className={`flex flex-shrink-0 items-center gap-2 text-xs ${
|
||||||
isArchivedIssues ? "opacity-60" : ""
|
isArchivedIssues ? "opacity-60" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
@ -143,13 +144,17 @@ export const IssueActivitySection: React.FC<Props> = ({ issueId, user }) => {
|
|||||||
{activityItem.field === "archived_at" &&
|
{activityItem.field === "archived_at" &&
|
||||||
activityItem.new_value !== "restore" ? (
|
activityItem.new_value !== "restore" ? (
|
||||||
<span className="text-gray font-medium">Plane</span>
|
<span className="text-gray font-medium">Plane</span>
|
||||||
) : (
|
) : activityItem.actor_detail.is_bot ? (
|
||||||
<span className="text-gray font-medium">
|
<span className="text-gray font-medium">
|
||||||
{activityItem.actor_detail.first_name}
|
{activityItem.actor_detail.first_name} Bot
|
||||||
{activityItem.actor_detail.is_bot
|
|
||||||
? " Bot"
|
|
||||||
: " " + activityItem.actor_detail.last_name}
|
|
||||||
</span>
|
</span>
|
||||||
|
) : (
|
||||||
|
<Link href={`/${workspaceSlug}/profile/${activityItem.actor_detail.id}`}>
|
||||||
|
<a className="text-gray font-medium">
|
||||||
|
{activityItem.actor_detail.first_name}{" "}
|
||||||
|
{activityItem.actor_detail.last_name}
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
)}{" "}
|
)}{" "}
|
||||||
{message}{" "}
|
{message}{" "}
|
||||||
<span className="whitespace-nowrap">
|
<span className="whitespace-nowrap">
|
||||||
|
@ -41,7 +41,7 @@ export const ViewDueDateSelect: React.FC<Props> = ({
|
|||||||
position={tooltipPosition}
|
position={tooltipPosition}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`group relative max-w-[6.5rem] ${
|
className={`group flex-shrink-0 relative max-w-[6.5rem] ${
|
||||||
issue.target_date === null
|
issue.target_date === null
|
||||||
? ""
|
? ""
|
||||||
: issue.target_date < new Date().toISOString()
|
: issue.target_date < new Date().toISOString()
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
import useSWR, { mutate } from "swr";
|
|
||||||
|
|
||||||
// react-hook-form
|
// react-hook-form
|
||||||
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||||
// services
|
// services
|
||||||
import workspaceService from "services/workspace.service";
|
import workspaceService from "services/workspace.service";
|
||||||
import userService from "services/user.service";
|
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
@ -14,16 +11,15 @@ import { CustomSelect, Input, PrimaryButton, SecondaryButton } from "components/
|
|||||||
// icons
|
// icons
|
||||||
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import { ICurrentUserResponse, IWorkspace, OnboardingSteps } from "types";
|
import { ICurrentUserResponse, IWorkspace, TOnboardingSteps } from "types";
|
||||||
// fetch-keys
|
|
||||||
import { CURRENT_USER, USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys";
|
|
||||||
// constants
|
// constants
|
||||||
import { ROLE } from "constants/workspace";
|
import { ROLE } from "constants/workspace";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspace: IWorkspace | undefined;
|
finishOnboarding: () => Promise<void>;
|
||||||
|
stepChange: (steps: Partial<TOnboardingSteps>) => Promise<void>;
|
||||||
user: ICurrentUserResponse | undefined;
|
user: ICurrentUserResponse | undefined;
|
||||||
stepChange: (steps: Partial<OnboardingSteps>) => Promise<void>;
|
workspace: IWorkspace | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
type EmailRole = {
|
type EmailRole = {
|
||||||
@ -35,7 +31,12 @@ type FormValues = {
|
|||||||
emails: EmailRole[];
|
emails: EmailRole[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const InviteMembers: React.FC<Props> = ({ workspace, user, stepChange }) => {
|
export const InviteMembers: React.FC<Props> = ({
|
||||||
|
finishOnboarding,
|
||||||
|
stepChange,
|
||||||
|
user,
|
||||||
|
workspace,
|
||||||
|
}) => {
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -49,38 +50,14 @@ export const InviteMembers: React.FC<Props> = ({ workspace, user, stepChange })
|
|||||||
name: "emails",
|
name: "emails",
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: invitations } = useSWR(USER_WORKSPACE_INVITATIONS, () =>
|
|
||||||
workspaceService.userWorkspaceInvitations()
|
|
||||||
);
|
|
||||||
|
|
||||||
const nextStep = async () => {
|
const nextStep = async () => {
|
||||||
if (!user || !invitations) return;
|
const payload: Partial<TOnboardingSteps> = {
|
||||||
|
|
||||||
const payload: Partial<OnboardingSteps> = {
|
|
||||||
workspace_invite: true,
|
workspace_invite: true,
|
||||||
|
workspace_join: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// update onboarding status from this step if no invitations are present
|
|
||||||
if (invitations.length === 0) {
|
|
||||||
payload.workspace_join = true;
|
|
||||||
|
|
||||||
mutate<ICurrentUserResponse>(
|
|
||||||
CURRENT_USER,
|
|
||||||
(prevData) => {
|
|
||||||
if (!prevData) return prevData;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...prevData,
|
|
||||||
is_onboarded: true,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
await userService.updateUserOnBoard({ userRole: user.role }, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
await stepChange(payload);
|
await stepChange(payload);
|
||||||
|
await finishOnboarding();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async (formData: FormValues) => {
|
const onSubmit = async (formData: FormValues) => {
|
||||||
|
@ -4,7 +4,6 @@ import useSWR, { mutate } from "swr";
|
|||||||
|
|
||||||
// services
|
// services
|
||||||
import workspaceService from "services/workspace.service";
|
import workspaceService from "services/workspace.service";
|
||||||
import userService from "services/user.service";
|
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
// ui
|
// ui
|
||||||
@ -14,17 +13,23 @@ import { CheckCircleIcon } from "@heroicons/react/24/outline";
|
|||||||
// helpers
|
// helpers
|
||||||
import { truncateText } from "helpers/string.helper";
|
import { truncateText } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { ICurrentUserResponse, IUser, IWorkspaceMemberInvitation, OnboardingSteps } from "types";
|
import { IWorkspaceMemberInvitation, TOnboardingSteps } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { CURRENT_USER, USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys";
|
import { USER_WORKSPACES, USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys";
|
||||||
// constants
|
// constants
|
||||||
import { ROLE } from "constants/workspace";
|
import { ROLE } from "constants/workspace";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
stepChange: (steps: Partial<OnboardingSteps>) => Promise<void>;
|
finishOnboarding: () => Promise<void>;
|
||||||
|
stepChange: (steps: Partial<TOnboardingSteps>) => Promise<void>;
|
||||||
|
updateLastWorkspace: () => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const JoinWorkspaces: React.FC<Props> = ({ stepChange }) => {
|
export const JoinWorkspaces: React.FC<Props> = ({
|
||||||
|
finishOnboarding,
|
||||||
|
stepChange,
|
||||||
|
updateLastWorkspace,
|
||||||
|
}) => {
|
||||||
const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false);
|
const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false);
|
||||||
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
|
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
|
||||||
|
|
||||||
@ -47,25 +52,13 @@ export const JoinWorkspaces: React.FC<Props> = ({ stepChange }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// complete onboarding
|
const handleNextStep = async () => {
|
||||||
const finishOnboarding = async () => {
|
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
mutate<ICurrentUserResponse>(
|
|
||||||
CURRENT_USER,
|
|
||||||
(prevData) => {
|
|
||||||
if (!prevData) return prevData;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...prevData,
|
|
||||||
is_onboarded: true,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
await userService.updateUserOnBoard({ userRole: user.role }, user);
|
|
||||||
await stepChange({ workspace_join: true });
|
await stepChange({ workspace_join: true });
|
||||||
|
|
||||||
|
if (user.onboarding_step.workspace_create && user.onboarding_step.workspace_invite)
|
||||||
|
await finishOnboarding();
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitInvitations = async () => {
|
const submitInvitations = async () => {
|
||||||
@ -77,11 +70,12 @@ export const JoinWorkspaces: React.FC<Props> = ({ stepChange }) => {
|
|||||||
.joinWorkspaces({ invitations: invitationsRespond })
|
.joinWorkspaces({ invitations: invitationsRespond })
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
await mutateInvitations();
|
await mutateInvitations();
|
||||||
await finishOnboarding();
|
await mutate(USER_WORKSPACES);
|
||||||
|
await updateLastWorkspace();
|
||||||
|
|
||||||
setIsJoiningWorkspaces(false);
|
await handleNextStep();
|
||||||
})
|
})
|
||||||
.catch(() => setIsJoiningWorkspaces(false));
|
.finally(() => setIsJoiningWorkspaces(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -142,14 +136,15 @@ export const JoinWorkspaces: React.FC<Props> = ({ stepChange }) => {
|
|||||||
type="submit"
|
type="submit"
|
||||||
size="md"
|
size="md"
|
||||||
onClick={submitInvitations}
|
onClick={submitInvitations}
|
||||||
disabled={isJoiningWorkspaces || invitationsRespond.length === 0}
|
disabled={invitationsRespond.length === 0}
|
||||||
|
loading={isJoiningWorkspaces}
|
||||||
>
|
>
|
||||||
Accept & Join
|
Accept & Join
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
className="border border-none bg-transparent"
|
className="border border-none bg-transparent"
|
||||||
size="md"
|
size="md"
|
||||||
onClick={finishOnboarding}
|
onClick={handleNextStep}
|
||||||
>
|
>
|
||||||
Skip for now
|
Skip for now
|
||||||
</SecondaryButton>
|
</SecondaryButton>
|
||||||
|
@ -3,17 +3,25 @@ import { useState } from "react";
|
|||||||
// ui
|
// ui
|
||||||
import { SecondaryButton } from "components/ui";
|
import { SecondaryButton } from "components/ui";
|
||||||
// types
|
// types
|
||||||
import { ICurrentUserResponse, OnboardingSteps } from "types";
|
import { ICurrentUserResponse, IWorkspace, TOnboardingSteps } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { CreateWorkspaceForm } from "components/workspace";
|
import { CreateWorkspaceForm } from "components/workspace";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
user: ICurrentUserResponse | undefined;
|
finishOnboarding: () => Promise<void>;
|
||||||
|
stepChange: (steps: Partial<TOnboardingSteps>) => Promise<void>;
|
||||||
updateLastWorkspace: () => Promise<void>;
|
updateLastWorkspace: () => Promise<void>;
|
||||||
stepChange: (steps: Partial<OnboardingSteps>) => Promise<void>;
|
user: ICurrentUserResponse | undefined;
|
||||||
|
workspaces: IWorkspace[] | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Workspace: React.FC<Props> = ({ user, updateLastWorkspace, stepChange }) => {
|
export const Workspace: React.FC<Props> = ({
|
||||||
|
finishOnboarding,
|
||||||
|
stepChange,
|
||||||
|
updateLastWorkspace,
|
||||||
|
user,
|
||||||
|
workspaces,
|
||||||
|
}) => {
|
||||||
const [defaultValues, setDefaultValues] = useState({
|
const [defaultValues, setDefaultValues] = useState({
|
||||||
name: "",
|
name: "",
|
||||||
slug: "",
|
slug: "",
|
||||||
@ -23,12 +31,21 @@ export const Workspace: React.FC<Props> = ({ user, updateLastWorkspace, stepChan
|
|||||||
const completeStep = async () => {
|
const completeStep = async () => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
await stepChange({
|
const payload: Partial<TOnboardingSteps> = {
|
||||||
workspace_create: true,
|
workspace_create: true,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
await stepChange(payload);
|
||||||
await updateLastWorkspace();
|
await updateLastWorkspace();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const secondaryButtonAction = async () => {
|
||||||
|
if (workspaces && workspaces.length > 0) {
|
||||||
|
await stepChange({ workspace_create: true, workspace_invite: true, workspace_join: true });
|
||||||
|
await finishOnboarding();
|
||||||
|
} else await stepChange({ profile_complete: false, workspace_join: false });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full space-y-7 sm:space-y-10">
|
<div className="w-full space-y-7 sm:space-y-10">
|
||||||
<h4 className="text-xl sm:text-2xl font-semibold">Create your workspace</h4>
|
<h4 className="text-xl sm:text-2xl font-semibold">Create your workspace</h4>
|
||||||
@ -43,9 +60,11 @@ export const Workspace: React.FC<Props> = ({ user, updateLastWorkspace, stepChan
|
|||||||
default: "Continue",
|
default: "Continue",
|
||||||
}}
|
}}
|
||||||
secondaryButton={
|
secondaryButton={
|
||||||
<SecondaryButton onClick={() => stepChange({ profile_complete: false })}>
|
workspaces ? (
|
||||||
Back
|
<SecondaryButton onClick={secondaryButtonAction}>
|
||||||
</SecondaryButton>
|
{workspaces.length > 0 ? "Skip & continue" : "Back"}
|
||||||
|
</SecondaryButton>
|
||||||
|
) : undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,15 +1,26 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
// components
|
// components
|
||||||
import { ProfileIssuesViewOptions } from "components/profile";
|
import { ProfileIssuesViewOptions } from "components/profile";
|
||||||
|
// types
|
||||||
|
import { UserAuth } from "types";
|
||||||
|
|
||||||
const tabsList = [
|
type Props = {
|
||||||
|
memberRole: UserAuth;
|
||||||
|
};
|
||||||
|
|
||||||
|
const viewerTabs = [
|
||||||
{
|
{
|
||||||
route: "",
|
route: "",
|
||||||
label: "Overview",
|
label: "Overview",
|
||||||
selected: "/[workspaceSlug]/profile/[userId]",
|
selected: "/[workspaceSlug]/profile/[userId]",
|
||||||
},
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const adminTabs = [
|
||||||
{
|
{
|
||||||
route: "assigned",
|
route: "assigned",
|
||||||
label: "Assigned",
|
label: "Assigned",
|
||||||
@ -27,12 +38,17 @@ const tabsList = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ProfileNavbar = () => {
|
export const ProfileNavbar: React.FC<Props> = ({ memberRole }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, userId } = router.query;
|
const { workspaceSlug, userId } = router.query;
|
||||||
|
|
||||||
|
const tabsList =
|
||||||
|
memberRole.isOwner || memberRole.isMember || memberRole.isViewer
|
||||||
|
? [...viewerTabs, ...adminTabs]
|
||||||
|
: viewerTabs;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-4 sm:px-5 flex items-center justify-between gap-4 border-b border-custom-border-300">
|
<div className="sticky -top-0.5 z-[1] md:static px-4 sm:px-5 flex items-center justify-between gap-4 bg-custom-background-100 border-b border-custom-border-300">
|
||||||
<div className="flex items-center overflow-x-scroll">
|
<div className="flex items-center overflow-x-scroll">
|
||||||
{tabsList.map((tab) => (
|
{tabsList.map((tab) => (
|
||||||
<Link key={tab.route} href={`/${workspaceSlug}/profile/${userId}/${tab.route}`}>
|
<Link key={tab.route} href={`/${workspaceSlug}/profile/${userId}/${tab.route}`}>
|
||||||
|
91
apps/app/components/profile/overview/activity.tsx
Normal file
91
apps/app/components/profile/overview/activity.tsx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { useRouter } from "next/router";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
|
// services
|
||||||
|
import userService from "services/user.service";
|
||||||
|
// ui
|
||||||
|
import { Icon, Loader } from "components/ui";
|
||||||
|
// helpers
|
||||||
|
import { activityDetails } from "helpers/activity.helper";
|
||||||
|
import { timeAgo } from "helpers/date-time.helper";
|
||||||
|
// fetch-keys
|
||||||
|
import { USER_PROFILE_ACTIVITY } from "constants/fetch-keys";
|
||||||
|
|
||||||
|
export const ProfileActivity = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug, userId } = router.query;
|
||||||
|
|
||||||
|
const { data: userProfileActivity } = useSWR(
|
||||||
|
workspaceSlug && userId
|
||||||
|
? USER_PROFILE_ACTIVITY(workspaceSlug.toString(), userId.toString())
|
||||||
|
: null,
|
||||||
|
workspaceSlug && userId
|
||||||
|
? () => userService.getUserProfileActivity(workspaceSlug.toString(), userId.toString())
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="text-lg font-medium">Recent Activity</h3>
|
||||||
|
<div className="border border-custom-border-100 rounded p-6">
|
||||||
|
{userProfileActivity ? (
|
||||||
|
<div className="space-y-5">
|
||||||
|
{userProfileActivity.results.map((activity) => (
|
||||||
|
<div key={activity.id} className="flex gap-3">
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
{activity.actor_detail.avatar && activity.actor_detail.avatar !== "" ? (
|
||||||
|
<img
|
||||||
|
src={activity.actor_detail.avatar}
|
||||||
|
alt={activity.actor_detail.first_name}
|
||||||
|
height={24}
|
||||||
|
width={24}
|
||||||
|
className="rounded"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="grid h-6 w-6 place-items-center rounded border-2 bg-gray-700 text-xs text-white">
|
||||||
|
{activity.actor_detail.first_name.charAt(0)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="-mt-1 w-4/5 break-words">
|
||||||
|
<p className="text-sm text-custom-text-200">
|
||||||
|
<span className="font-medium text-custom-text-100">
|
||||||
|
{activity.actor_detail.first_name} {activity.actor_detail.last_name}{" "}
|
||||||
|
</span>
|
||||||
|
{activity.field ? (
|
||||||
|
activityDetails[activity.field]?.message(activity as any)
|
||||||
|
) : (
|
||||||
|
<span>
|
||||||
|
created this{" "}
|
||||||
|
<a
|
||||||
|
href={`/${activity.workspace_detail.slug}/projects/${activity.project}/issues/${activity.issue}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
|
||||||
|
>
|
||||||
|
Issue
|
||||||
|
<Icon iconName="launch" className="!text-xs" />
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-custom-text-200">{timeAgo(activity.created_at)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Loader className="space-y-5">
|
||||||
|
<Loader.Item height="40px" />
|
||||||
|
<Loader.Item height="40px" />
|
||||||
|
<Loader.Item height="40px" />
|
||||||
|
<Loader.Item height="40px" />
|
||||||
|
<Loader.Item height="40px" />
|
||||||
|
</Loader>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,3 +1,4 @@
|
|||||||
|
export * from "./activity";
|
||||||
export * from "./priority-distribution";
|
export * from "./priority-distribution";
|
||||||
export * from "./state-distribution";
|
export * from "./state-distribution";
|
||||||
export * from "./stats";
|
export * from "./stats";
|
||||||
|
@ -10,7 +10,7 @@ type Props = {
|
|||||||
export const ProfileWorkload: React.FC<Props> = ({ stateDistribution }) => (
|
export const ProfileWorkload: React.FC<Props> = ({ stateDistribution }) => (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="text-lg font-medium">Workload</h3>
|
<h3 className="text-lg font-medium">Workload</h3>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 xl:grid-cols-5 gap-4 justify-stretch">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-4 justify-stretch">
|
||||||
{stateDistribution.map((group) => (
|
{stateDistribution.map((group) => (
|
||||||
<div key={group.state_group}>
|
<div key={group.state_group}>
|
||||||
<a className="flex gap-2 p-4 rounded border border-custom-border-100 whitespace-nowrap">
|
<a className="flex gap-2 p-4 rounded border border-custom-border-100 whitespace-nowrap">
|
||||||
@ -21,7 +21,13 @@ export const ProfileWorkload: React.FC<Props> = ({ stateDistribution }) => (
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="space-y-1 -mt-1">
|
<div className="space-y-1 -mt-1">
|
||||||
<p className="text-custom-text-400 text-sm capitalize">{group.state_group}</p>
|
<p className="text-custom-text-400 text-sm capitalize">
|
||||||
|
{group.state_group === "unstarted"
|
||||||
|
? "Not Started"
|
||||||
|
: group.state_group === "started"
|
||||||
|
? "Working on"
|
||||||
|
: group.state_group}
|
||||||
|
</p>
|
||||||
<p className="text-xl font-semibold">{group.state_count}</p>
|
<p className="text-xl font-semibold">{group.state_count}</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -10,7 +10,7 @@ import useEstimateOption from "hooks/use-estimate-option";
|
|||||||
// components
|
// components
|
||||||
import { MyIssuesSelectFilters } from "components/issues";
|
import { MyIssuesSelectFilters } from "components/issues";
|
||||||
// ui
|
// ui
|
||||||
import { CustomMenu, ToggleSwitch, Tooltip } from "components/ui";
|
import { CustomMenu, CustomSearchSelect, ToggleSwitch, Tooltip } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
import { ChevronDownIcon } from "@heroicons/react/24/outline";
|
import { ChevronDownIcon } from "@heroicons/react/24/outline";
|
||||||
import { FormatListBulletedOutlined, GridViewOutlined } from "@mui/icons-material";
|
import { FormatListBulletedOutlined, GridViewOutlined } from "@mui/icons-material";
|
||||||
@ -21,6 +21,7 @@ import { checkIfArraysHaveSameElements } from "helpers/array.helper";
|
|||||||
import { Properties, TIssueViewOptions } from "types";
|
import { Properties, TIssueViewOptions } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { GROUP_BY_OPTIONS, ORDER_BY_OPTIONS, FILTER_ISSUE_OPTIONS } from "constants/issue";
|
import { GROUP_BY_OPTIONS, ORDER_BY_OPTIONS, FILTER_ISSUE_OPTIONS } from "constants/issue";
|
||||||
|
import useProjects from "hooks/use-projects";
|
||||||
|
|
||||||
const issueViewOptions: { type: TIssueViewOptions; Icon: any }[] = [
|
const issueViewOptions: { type: TIssueViewOptions; Icon: any }[] = [
|
||||||
{
|
{
|
||||||
@ -37,6 +38,8 @@ export const ProfileIssuesViewOptions: React.FC = () => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, userId } = router.query;
|
const { workspaceSlug, userId } = router.query;
|
||||||
|
|
||||||
|
const { projects } = useProjects();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
issueView,
|
issueView,
|
||||||
setIssueView,
|
setIssueView,
|
||||||
@ -54,12 +57,28 @@ export const ProfileIssuesViewOptions: React.FC = () => {
|
|||||||
|
|
||||||
const { isEstimateActive } = useEstimateOption();
|
const { isEstimateActive } = useEstimateOption();
|
||||||
|
|
||||||
|
const options = projects?.map((project) => ({
|
||||||
|
value: project.id,
|
||||||
|
query: project.name + " " + project.identifier,
|
||||||
|
content: project.name,
|
||||||
|
}));
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!router.pathname.includes("assigned") &&
|
!router.pathname.includes("assigned") &&
|
||||||
!router.pathname.includes("created") &&
|
!router.pathname.includes("created") &&
|
||||||
!router.pathname.includes("subscribed")
|
!router.pathname.includes("subscribed")
|
||||||
)
|
)
|
||||||
return null;
|
return null;
|
||||||
|
// return (
|
||||||
|
// <CustomSearchSelect
|
||||||
|
// value={projects ?? null}
|
||||||
|
// onChange={(val: string[] | null) => console.log(val)}
|
||||||
|
// label="Filters"
|
||||||
|
// options={options}
|
||||||
|
// position="right"
|
||||||
|
// multiple
|
||||||
|
// />
|
||||||
|
// );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
@ -8,10 +9,14 @@ import { useTheme } from "next-themes";
|
|||||||
import { Disclosure, Transition } from "@headlessui/react";
|
import { Disclosure, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import userService from "services/user.service";
|
import userService from "services/user.service";
|
||||||
|
// hooks
|
||||||
|
import useUser from "hooks/use-user";
|
||||||
// ui
|
// ui
|
||||||
import { Icon, Loader } from "components/ui";
|
import { Icon, Loader, Tooltip } from "components/ui";
|
||||||
|
// icons
|
||||||
|
import { EditOutlined } from "@mui/icons-material";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderLongDetailDateFormat } from "helpers/date-time.helper";
|
import { render12HourFormatTime, renderLongDetailDateFormat } from "helpers/date-time.helper";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
import { renderEmoji } from "helpers/emoji.helper";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { USER_PROFILE_PROJECT_SEGREGATION } from "constants/fetch-keys";
|
import { USER_PROFILE_PROJECT_SEGREGATION } from "constants/fetch-keys";
|
||||||
@ -22,6 +27,8 @@ export const ProfileSidebar = () => {
|
|||||||
|
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
|
const { user } = useUser();
|
||||||
|
|
||||||
const { data: userProjectsData } = useSWR(
|
const { data: userProjectsData } = useSWR(
|
||||||
workspaceSlug && userId
|
workspaceSlug && userId
|
||||||
? USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString())
|
? USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString())
|
||||||
@ -33,27 +40,24 @@ export const ProfileSidebar = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const userDetails = [
|
const userDetails = [
|
||||||
{
|
|
||||||
label: "Username",
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: "Joined on",
|
label: "Joined on",
|
||||||
value: renderLongDetailDateFormat(userProjectsData?.user_data.date_joined ?? ""),
|
value: renderLongDetailDateFormat(userProjectsData?.user_data.date_joined ?? ""),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Timezone",
|
label: "Timezone",
|
||||||
value: userProjectsData?.user_data.user_timezone,
|
value: (
|
||||||
},
|
<span>
|
||||||
{
|
{render12HourFormatTime(new Date())}{" "}
|
||||||
label: "Status",
|
<span className="text-custom-text-200">{userProjectsData?.user_data.user_timezone}</span>
|
||||||
value: "Online",
|
</span>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex-shrink-0 h-full w-80 overflow-y-auto"
|
className="flex-shrink-0 md:h-full w-full md:w-80 overflow-y-auto"
|
||||||
style={{
|
style={{
|
||||||
boxShadow:
|
boxShadow:
|
||||||
theme === "light"
|
theme === "light"
|
||||||
@ -64,10 +68,23 @@ export const ProfileSidebar = () => {
|
|||||||
{userProjectsData ? (
|
{userProjectsData ? (
|
||||||
<>
|
<>
|
||||||
<div className="relative h-32">
|
<div className="relative h-32">
|
||||||
|
{user?.id === userId && (
|
||||||
|
<div className="absolute top-3.5 right-3.5 h-5 w-5 bg-white rounded grid place-items-center">
|
||||||
|
<Link href={`/${workspaceSlug}/me/profile`}>
|
||||||
|
<a className="grid place-items-center text-black">
|
||||||
|
<EditOutlined
|
||||||
|
sx={{
|
||||||
|
fontSize: 12,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<img
|
<img
|
||||||
src={
|
src={
|
||||||
userProjectsData.user_data.cover_image ??
|
userProjectsData.user_data.cover_image ??
|
||||||
"https://images.unsplash.com/photo-1672243775941-10d763d9adef?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80"
|
"https://images.unsplash.com/photo-1506383796573-caf02b4a79ab"
|
||||||
}
|
}
|
||||||
alt={userProjectsData.user_data.first_name}
|
alt={userProjectsData.user_data.first_name}
|
||||||
className="h-32 w-full object-cover"
|
className="h-32 w-full object-cover"
|
||||||
@ -96,8 +113,8 @@ export const ProfileSidebar = () => {
|
|||||||
<div className="mt-6 space-y-5">
|
<div className="mt-6 space-y-5">
|
||||||
{userDetails.map((detail) => (
|
{userDetails.map((detail) => (
|
||||||
<div key={detail.label} className="flex items-center gap-4 text-sm">
|
<div key={detail.label} className="flex items-center gap-4 text-sm">
|
||||||
<div className="text-custom-text-200 w-2/5">{detail.label}</div>
|
<div className="flex-shrink-0 text-custom-text-200 w-2/5">{detail.label}</div>
|
||||||
<div className="font-medium">{detail.value}</div>
|
<div className="font-medium w-3/5 break-words">{detail.value}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -143,17 +160,19 @@ export const ProfileSidebar = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-shrink-0 flex items-center gap-2">
|
<div className="flex-shrink-0 flex items-center gap-2">
|
||||||
<div
|
<Tooltip tooltipContent="Completion percentage" position="left">
|
||||||
className={`px-1 py-0.5 text-xs font-medium rounded ${
|
<div
|
||||||
completedIssuePercentage <= 35
|
className={`px-1 py-0.5 text-xs font-medium rounded ${
|
||||||
? "bg-red-500/10 text-red-500"
|
completedIssuePercentage <= 35
|
||||||
: completedIssuePercentage <= 70
|
? "bg-red-500/10 text-red-500"
|
||||||
? "bg-yellow-500/10 text-yellow-500"
|
: completedIssuePercentage <= 70
|
||||||
: "bg-green-500/10 text-green-500"
|
? "bg-yellow-500/10 text-yellow-500"
|
||||||
}`}
|
: "bg-green-500/10 text-green-500"
|
||||||
>
|
}`}
|
||||||
{completedIssuePercentage}%
|
>
|
||||||
</div>
|
{completedIssuePercentage}%
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
<Icon iconName="arrow_drop_down" className="!text-lg" />
|
<Icon iconName="arrow_drop_down" className="!text-lg" />
|
||||||
</div>
|
</div>
|
||||||
</Disclosure.Button>
|
</Disclosure.Button>
|
||||||
|
@ -93,7 +93,6 @@ const SendProjectInvitationModal: React.FC<Props> = ({ isOpen, setIsOpen, member
|
|||||||
.inviteProject(workspaceSlug as string, projectId as string, payload, user)
|
.inviteProject(workspaceSlug as string, projectId as string, payload, user)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
mutate(PROJECT_MEMBERS(projectId as string));
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
title: "Success",
|
title: "Success",
|
||||||
type: "success",
|
type: "success",
|
||||||
@ -105,6 +104,7 @@ const SendProjectInvitationModal: React.FC<Props> = ({ isOpen, setIsOpen, member
|
|||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
reset(defaultValues);
|
reset(defaultValues);
|
||||||
|
mutate(PROJECT_MEMBERS(projectId.toString()));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import React, { useState, FC } from "react";
|
import React, { useState, FC } from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { mutate } from "swr";
|
||||||
|
|
||||||
|
// react-beautiful-dnd
|
||||||
|
import { DragDropContext, Draggable, DropResult, Droppable } from "react-beautiful-dnd";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useTheme from "hooks/use-theme";
|
import useTheme from "hooks/use-theme";
|
||||||
@ -9,12 +12,17 @@ import useUserAuth from "hooks/use-user-auth";
|
|||||||
import useProjects from "hooks/use-projects";
|
import useProjects from "hooks/use-projects";
|
||||||
// components
|
// components
|
||||||
import { DeleteProjectModal, SingleSidebarProject } from "components/project";
|
import { DeleteProjectModal, SingleSidebarProject } from "components/project";
|
||||||
|
// services
|
||||||
|
import projectService from "services/project.service";
|
||||||
// icons
|
// icons
|
||||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||||
// helpers
|
// helpers
|
||||||
import { copyTextToClipboard } from "helpers/string.helper";
|
import { copyTextToClipboard } from "helpers/string.helper";
|
||||||
|
import { orderArrayBy } from "helpers/array.helper";
|
||||||
// types
|
// types
|
||||||
import { IProject } from "types";
|
import { IProject } from "types";
|
||||||
|
// fetch-keys
|
||||||
|
import { PROJECTS_LIST } from "constants/fetch-keys";
|
||||||
|
|
||||||
export const ProjectSidebarList: FC = () => {
|
export const ProjectSidebarList: FC = () => {
|
||||||
const [deleteProjectModal, setDeleteProjectModal] = useState(false);
|
const [deleteProjectModal, setDeleteProjectModal] = useState(false);
|
||||||
@ -32,6 +40,14 @@ export const ProjectSidebarList: FC = () => {
|
|||||||
const { projects: allProjects } = useProjects();
|
const { projects: allProjects } = useProjects();
|
||||||
const favoriteProjects = allProjects?.filter((p) => p.is_favorite);
|
const favoriteProjects = allProjects?.filter((p) => p.is_favorite);
|
||||||
|
|
||||||
|
const orderedAllProjects = allProjects
|
||||||
|
? orderArrayBy(allProjects, "sort_order", "ascending")
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const orderedFavProjects = favoriteProjects
|
||||||
|
? orderArrayBy(favoriteProjects, "sort_order", "ascending")
|
||||||
|
: [];
|
||||||
|
|
||||||
const handleDeleteProject = (project: IProject) => {
|
const handleDeleteProject = (project: IProject) => {
|
||||||
setProjectToDelete(project);
|
setProjectToDelete(project);
|
||||||
setDeleteProjectModal(true);
|
setDeleteProjectModal(true);
|
||||||
@ -49,6 +65,54 @@ export const ProjectSidebarList: FC = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDragEnd = async (result: DropResult) => {
|
||||||
|
const { source, destination, draggableId } = result;
|
||||||
|
|
||||||
|
if (!destination || !workspaceSlug) return;
|
||||||
|
if (source.index === destination.index) return;
|
||||||
|
|
||||||
|
const projectList =
|
||||||
|
destination.droppableId === "all-projects" ? orderedAllProjects : orderedFavProjects;
|
||||||
|
|
||||||
|
let updatedSortOrder = projectList[source.index].sort_order;
|
||||||
|
if (destination.index === 0) {
|
||||||
|
updatedSortOrder = projectList[0].sort_order - 1000;
|
||||||
|
} else if (destination.index === projectList.length - 1) {
|
||||||
|
updatedSortOrder = projectList[projectList.length - 1].sort_order + 1000;
|
||||||
|
} else {
|
||||||
|
const destinationSortingOrder = projectList[destination.index].sort_order;
|
||||||
|
const relativeDestinationSortingOrder =
|
||||||
|
source.index < destination.index
|
||||||
|
? projectList[destination.index + 1].sort_order
|
||||||
|
: projectList[destination.index - 1].sort_order;
|
||||||
|
|
||||||
|
updatedSortOrder = Math.round(
|
||||||
|
(destinationSortingOrder + relativeDestinationSortingOrder) / 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
mutate<IProject[]>(
|
||||||
|
PROJECTS_LIST(workspaceSlug as string, { is_favorite: "all" }),
|
||||||
|
(prevData) => {
|
||||||
|
if (!prevData) return prevData;
|
||||||
|
return prevData.map((p) =>
|
||||||
|
p.id === draggableId ? { ...p, sort_order: updatedSortOrder } : p
|
||||||
|
);
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
await projectService
|
||||||
|
.setProjectView(workspaceSlug as string, draggableId, { sort_order: updatedSortOrder })
|
||||||
|
.catch(() => {
|
||||||
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
|
message: "Something went wrong. Please try again.",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DeleteProjectModal
|
<DeleteProjectModal
|
||||||
@ -58,39 +122,75 @@ export const ProjectSidebarList: FC = () => {
|
|||||||
user={user}
|
user={user}
|
||||||
/>
|
/>
|
||||||
<div className="h-full overflow-y-auto px-4">
|
<div className="h-full overflow-y-auto px-4">
|
||||||
{favoriteProjects && favoriteProjects.length > 0 && (
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
<div className="flex flex-col space-y-2 mt-5">
|
<Droppable droppableId="favorite-projects">
|
||||||
{!sidebarCollapse && (
|
{(provided) => (
|
||||||
<h5 className="text-sm font-medium text-custom-sidebar-text-200">Favorites</h5>
|
<div ref={provided.innerRef} {...provided.droppableProps}>
|
||||||
|
{orderedFavProjects && orderedFavProjects.length > 0 && (
|
||||||
|
<div className="flex flex-col space-y-2 mt-5">
|
||||||
|
{!sidebarCollapse && (
|
||||||
|
<h5 className="text-sm font-medium text-custom-sidebar-text-200">
|
||||||
|
Favorites
|
||||||
|
</h5>
|
||||||
|
)}
|
||||||
|
{orderedFavProjects.map((project, index) => (
|
||||||
|
<Draggable key={project.id} draggableId={project.id} index={index}>
|
||||||
|
{(provided, snapshot) => (
|
||||||
|
<div ref={provided.innerRef} {...provided.draggableProps}>
|
||||||
|
<SingleSidebarProject
|
||||||
|
key={project.id}
|
||||||
|
project={project}
|
||||||
|
sidebarCollapse={sidebarCollapse}
|
||||||
|
provided={provided}
|
||||||
|
snapshot={snapshot}
|
||||||
|
handleDeleteProject={() => handleDeleteProject(project)}
|
||||||
|
handleCopyText={() => handleCopyText(project.id)}
|
||||||
|
shortContextMenu
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
))}
|
||||||
|
{provided.placeholder}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{favoriteProjects.map((project) => (
|
</Droppable>
|
||||||
<SingleSidebarProject
|
</DragDropContext>
|
||||||
key={project.id}
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
project={project}
|
<Droppable droppableId="all-projects">
|
||||||
sidebarCollapse={sidebarCollapse}
|
{(provided) => (
|
||||||
handleDeleteProject={() => handleDeleteProject(project)}
|
<div ref={provided.innerRef} {...provided.droppableProps}>
|
||||||
handleCopyText={() => handleCopyText(project.id)}
|
{orderedAllProjects && orderedAllProjects.length > 0 && (
|
||||||
shortContextMenu
|
<div className="flex flex-col space-y-2 mt-5">
|
||||||
/>
|
{!sidebarCollapse && (
|
||||||
))}
|
<h5 className="text-sm font-medium text-custom-sidebar-text-200">Projects</h5>
|
||||||
</div>
|
)}
|
||||||
)}
|
{orderedAllProjects.map((project, index) => (
|
||||||
{allProjects && allProjects.length > 0 && (
|
<Draggable key={project.id} draggableId={project.id} index={index}>
|
||||||
<div className="flex flex-col space-y-2 mt-5">
|
{(provided, snapshot) => (
|
||||||
{!sidebarCollapse && (
|
<div ref={provided.innerRef} {...provided.draggableProps}>
|
||||||
<h5 className="text-sm font-medium text-custom-sidebar-text-200">Projects</h5>
|
<SingleSidebarProject
|
||||||
|
key={project.id}
|
||||||
|
project={project}
|
||||||
|
sidebarCollapse={sidebarCollapse}
|
||||||
|
provided={provided}
|
||||||
|
snapshot={snapshot}
|
||||||
|
handleDeleteProject={() => handleDeleteProject(project)}
|
||||||
|
handleCopyText={() => handleCopyText(project.id)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
))}
|
||||||
|
{provided.placeholder}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{allProjects.map((project) => (
|
</Droppable>
|
||||||
<SingleSidebarProject
|
</DragDropContext>
|
||||||
key={project.id}
|
|
||||||
project={project}
|
|
||||||
sidebarCollapse={sidebarCollapse}
|
|
||||||
handleDeleteProject={() => handleDeleteProject(project)}
|
|
||||||
handleCopyText={() => handleCopyText(project.id)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{allProjects && allProjects.length === 0 && (
|
{allProjects && allProjects.length === 0 && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -3,6 +3,8 @@ import { useRouter } from "next/router";
|
|||||||
|
|
||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
|
|
||||||
|
// react-beautiful-dnd
|
||||||
|
import { DraggableProvided, DraggableStateSnapshot } from "react-beautiful-dnd";
|
||||||
// headless ui
|
// headless ui
|
||||||
import { Disclosure, Transition } from "@headlessui/react";
|
import { Disclosure, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
@ -12,7 +14,7 @@ import useToast from "hooks/use-toast";
|
|||||||
// ui
|
// ui
|
||||||
import { CustomMenu, Tooltip } from "components/ui";
|
import { CustomMenu, Tooltip } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
import { LinkIcon, StarIcon, TrashIcon } from "@heroicons/react/24/outline";
|
import { EllipsisVerticalIcon, LinkIcon, StarIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||||
import {
|
import {
|
||||||
ArchiveOutlined,
|
ArchiveOutlined,
|
||||||
ArticleOutlined,
|
ArticleOutlined,
|
||||||
@ -34,6 +36,8 @@ import { PROJECTS_LIST } from "constants/fetch-keys";
|
|||||||
type Props = {
|
type Props = {
|
||||||
project: IProject;
|
project: IProject;
|
||||||
sidebarCollapse: boolean;
|
sidebarCollapse: boolean;
|
||||||
|
provided: DraggableProvided;
|
||||||
|
snapshot: DraggableStateSnapshot;
|
||||||
handleDeleteProject: () => void;
|
handleDeleteProject: () => void;
|
||||||
handleCopyText: () => void;
|
handleCopyText: () => void;
|
||||||
shortContextMenu?: boolean;
|
shortContextMenu?: boolean;
|
||||||
@ -75,6 +79,8 @@ const navigation = (workspaceSlug: string, projectId: string) => [
|
|||||||
export const SingleSidebarProject: React.FC<Props> = ({
|
export const SingleSidebarProject: React.FC<Props> = ({
|
||||||
project,
|
project,
|
||||||
sidebarCollapse,
|
sidebarCollapse,
|
||||||
|
provided,
|
||||||
|
snapshot,
|
||||||
handleDeleteProject,
|
handleDeleteProject,
|
||||||
handleCopyText,
|
handleCopyText,
|
||||||
shortContextMenu = false,
|
shortContextMenu = false,
|
||||||
@ -130,7 +136,21 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
|||||||
<Disclosure key={project?.id} defaultOpen={projectId === project?.id}>
|
<Disclosure key={project?.id} defaultOpen={projectId === project?.id}>
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-x-1 text-custom-sidebar-text-100">
|
<div
|
||||||
|
className={`group relative flex items-center gap-x-1 text-custom-sidebar-text-100 ${
|
||||||
|
snapshot.isDragging ? "opacity-60" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`absolute top-2 left-0 hidden rounded p-0.5 ${
|
||||||
|
sidebarCollapse ? "" : "group-hover:!flex"
|
||||||
|
}`}
|
||||||
|
{...provided.dragHandleProps}
|
||||||
|
>
|
||||||
|
<EllipsisVerticalIcon className="h-4" />
|
||||||
|
<EllipsisVerticalIcon className="-ml-5 h-4" />
|
||||||
|
</button>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
tooltipContent={`${project?.name}`}
|
tooltipContent={`${project?.name}`}
|
||||||
position="right"
|
position="right"
|
||||||
@ -140,7 +160,7 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
|||||||
<Disclosure.Button
|
<Disclosure.Button
|
||||||
as="div"
|
as="div"
|
||||||
className={`flex w-full cursor-pointer select-none items-center rounded-sm py-1 text-left text-sm font-medium ${
|
className={`flex w-full cursor-pointer select-none items-center rounded-sm py-1 text-left text-sm font-medium ${
|
||||||
sidebarCollapse ? "justify-center" : "justify-between"
|
sidebarCollapse ? "justify-center" : "justify-between ml-4"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-x-2">
|
<div className="flex items-center gap-x-2">
|
||||||
|
@ -22,7 +22,7 @@ export type CustomSearchSelectProps = DropdownProps & {
|
|||||||
| { multiple?: false; value: any } // if multiple is false, value can be anything
|
| { multiple?: false; value: any } // if multiple is false, value can be anything
|
||||||
| {
|
| {
|
||||||
multiple?: true;
|
multiple?: true;
|
||||||
value: any[]; // if multiple is true, value should be an array
|
value: any[] | null; // if multiple is true, value should be an array
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ export const CustomSearchSelect = ({
|
|||||||
className={`${selfPositioned ? "" : "relative"} flex-shrink-0 text-left ${className}`}
|
className={`${selfPositioned ? "" : "relative"} flex-shrink-0 text-left ${className}`}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{({ open }: any) => {
|
{({ open }: { open: boolean }) => {
|
||||||
if (open && onOpen) onOpen();
|
if (open && onOpen) onOpen();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
|
// swr
|
||||||
|
import { mutate } from "swr";
|
||||||
// react-hook-form
|
// react-hook-form
|
||||||
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||||
// headless
|
// headless
|
||||||
@ -13,9 +15,10 @@ import { CustomSelect, Input, PrimaryButton, SecondaryButton } from "components/
|
|||||||
// icons
|
// icons
|
||||||
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import { ICurrentUserResponse, IWorkspace, IWorkspaceMemberInvitation } from "types";
|
import { ICurrentUserResponse } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { ROLE } from "constants/workspace";
|
import { ROLE } from "constants/workspace";
|
||||||
|
import { WORKSPACE_INVITATIONS } from "constants/fetch-keys";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -94,7 +97,10 @@ const SendWorkspaceInvitationModal: React.FC<Props> = ({
|
|||||||
});
|
});
|
||||||
console.log(err);
|
console.log(err);
|
||||||
})
|
})
|
||||||
.finally(() => reset(defaultValues));
|
.finally(() => {
|
||||||
|
reset(defaultValues);
|
||||||
|
mutate(WORKSPACE_INVITATIONS);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const appendField = () => {
|
const appendField = () => {
|
||||||
|
@ -25,7 +25,7 @@ import { truncateText } from "helpers/string.helper";
|
|||||||
import { IWorkspace } from "types";
|
import { IWorkspace } from "types";
|
||||||
|
|
||||||
// Static Data
|
// Static Data
|
||||||
const userLinks = (workspaceSlug: string) => [
|
const userLinks = (workspaceSlug: string, userId: string) => [
|
||||||
{
|
{
|
||||||
name: "Workspace Settings",
|
name: "Workspace Settings",
|
||||||
href: `/${workspaceSlug}/settings`,
|
href: `/${workspaceSlug}/settings`,
|
||||||
@ -36,7 +36,7 @@ const userLinks = (workspaceSlug: string) => [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "My Profile",
|
name: "My Profile",
|
||||||
href: `/${workspaceSlug}/me/profile`,
|
href: `/${workspaceSlug}/profile/${userId}`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ export const WorkspaceSidebarDropdown = () => {
|
|||||||
</Menu.Button>
|
</Menu.Button>
|
||||||
|
|
||||||
{!sidebarCollapse && (
|
{!sidebarCollapse && (
|
||||||
<Link href={`/${workspaceSlug}/me/profile`}>
|
<Link href={`/${workspaceSlug}/profile/${user?.id}`}>
|
||||||
<a>
|
<a>
|
||||||
<div className="flex flex-grow justify-end">
|
<div className="flex flex-grow justify-end">
|
||||||
<Avatar user={user} height="28px" width="28px" fontSize="14px" />
|
<Avatar user={user} height="28px" width="28px" fontSize="14px" />
|
||||||
@ -215,7 +215,7 @@ export const WorkspaceSidebarDropdown = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full flex-col items-start justify-start gap-2 border-t border-custom-sidebar-border-200 px-3 py-2 text-sm">
|
<div className="flex w-full flex-col items-start justify-start gap-2 border-t border-custom-sidebar-border-200 px-3 py-2 text-sm">
|
||||||
{userLinks(workspaceSlug as string).map((link, index) => (
|
{userLinks(workspaceSlug?.toString() ?? "", user?.id ?? "").map((link, index) => (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
key={index}
|
key={index}
|
||||||
as="div"
|
as="div"
|
||||||
|
@ -12,6 +12,7 @@ import { profileIssuesContext } from "contexts/profile-issues-context";
|
|||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { USER_PROFILE_ISSUES } from "constants/fetch-keys";
|
import { USER_PROFILE_ISSUES } from "constants/fetch-keys";
|
||||||
|
import { useWorkspaceMyMembership } from "contexts/workspace-member.context";
|
||||||
|
|
||||||
const useProfileIssues = (workspaceSlug: string | undefined, userId: string | undefined) => {
|
const useProfileIssues = (workspaceSlug: string | undefined, userId: string | undefined) => {
|
||||||
const {
|
const {
|
||||||
@ -33,6 +34,8 @@ const useProfileIssues = (workspaceSlug: string | undefined, userId: string | un
|
|||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const { memberRole } = useWorkspaceMyMembership();
|
||||||
|
|
||||||
const params: any = {
|
const params: any = {
|
||||||
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
|
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
|
||||||
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
|
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
|
||||||
@ -47,14 +50,16 @@ const useProfileIssues = (workspaceSlug: string | undefined, userId: string | un
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { data: userProfileIssues, mutate: mutateProfileIssues } = useSWR(
|
const { data: userProfileIssues, mutate: mutateProfileIssues } = useSWR(
|
||||||
workspaceSlug && userId
|
workspaceSlug && userId && (memberRole.isOwner || memberRole.isMember || memberRole.isViewer)
|
||||||
? USER_PROFILE_ISSUES(workspaceSlug.toString(), userId.toString(), params)
|
? USER_PROFILE_ISSUES(workspaceSlug.toString(), userId.toString(), params)
|
||||||
: null,
|
: null,
|
||||||
workspaceSlug && userId
|
workspaceSlug && userId && (memberRole.isOwner || memberRole.isMember || memberRole.isViewer)
|
||||||
? () => userService.getUserProfileIssues(workspaceSlug.toString(), userId.toString(), params)
|
? () => userService.getUserProfileIssues(workspaceSlug.toString(), userId.toString(), params)
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log(memberRole);
|
||||||
|
|
||||||
const groupedIssues:
|
const groupedIssues:
|
||||||
| {
|
| {
|
||||||
[key: string]: IIssue[];
|
[key: string]: IIssue[];
|
||||||
@ -73,8 +78,6 @@ const useProfileIssues = (workspaceSlug: string | undefined, userId: string | un
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!userId || !filters) return;
|
if (!userId || !filters) return;
|
||||||
|
|
||||||
console.log("Triggered");
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
router.pathname.includes("assigned") &&
|
router.pathname.includes("assigned") &&
|
||||||
(!filters.assignees || !filters.assignees.includes(userId))
|
(!filters.assignees || !filters.assignees.includes(userId))
|
||||||
|
@ -11,13 +11,17 @@ import { IProject } from "types";
|
|||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECTS_LIST } from "constants/fetch-keys";
|
import { PROJECTS_LIST } from "constants/fetch-keys";
|
||||||
|
|
||||||
const useProjects = (type?: "all" | boolean) => {
|
const useProjects = (type?: "all" | boolean, fetchCondition?: boolean) => {
|
||||||
|
fetchCondition = fetchCondition ?? true;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
const { data: projects, mutate: mutateProjects } = useSWR(
|
const { data: projects, mutate: mutateProjects } = useSWR(
|
||||||
workspaceSlug ? PROJECTS_LIST(workspaceSlug as string, { is_favorite: type ?? "all" }) : null,
|
workspaceSlug && fetchCondition
|
||||||
workspaceSlug
|
? PROJECTS_LIST(workspaceSlug as string, { is_favorite: type ?? "all" })
|
||||||
|
: null,
|
||||||
|
workspaceSlug && fetchCondition
|
||||||
? () => projectService.getProjects(workspaceSlug as string, { is_favorite: type ?? "all" })
|
? () => projectService.getProjects(workspaceSlug as string, { is_favorite: type ?? "all" })
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
@ -11,11 +11,11 @@ type Props = {
|
|||||||
|
|
||||||
const Header: React.FC<Props> = ({ breadcrumbs, left, right, setToggleSidebar, noHeader }) => (
|
const Header: React.FC<Props> = ({ breadcrumbs, left, right, setToggleSidebar, noHeader }) => (
|
||||||
<div
|
<div
|
||||||
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 px-5 py-4 ${
|
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 px-5 py-4 ${
|
||||||
noHeader ? "md:hidden" : ""
|
noHeader ? "md:hidden" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
|
||||||
<div className="block md:hidden">
|
<div className="block md:hidden">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -26,9 +26,9 @@ const Header: React.FC<Props> = ({ breadcrumbs, left, right, setToggleSidebar, n
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{breadcrumbs}
|
{breadcrumbs}
|
||||||
{left}
|
<div className="flex-shrink-0">{left}</div>
|
||||||
</div>
|
</div>
|
||||||
{right}
|
<div className="flex-shrink-0">{right}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
45
apps/app/layouts/profile-layout.tsx
Normal file
45
apps/app/layouts/profile-layout.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// hooks
|
||||||
|
import { useWorkspaceMyMembership } from "contexts/workspace-member.context";
|
||||||
|
// layouts
|
||||||
|
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||||
|
// components
|
||||||
|
import { ProfileNavbar, ProfileSidebar } from "components/profile";
|
||||||
|
// ui
|
||||||
|
import { Breadcrumbs, BreadcrumbItem } from "components/breadcrumbs";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProfileAuthWrapper = (props: Props) => (
|
||||||
|
<WorkspaceAuthorizationLayout
|
||||||
|
breadcrumbs={
|
||||||
|
<Breadcrumbs>
|
||||||
|
<BreadcrumbItem title="User Profile" />
|
||||||
|
</Breadcrumbs>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ProfileLayout {...props} />
|
||||||
|
</WorkspaceAuthorizationLayout>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ProfileLayout: React.FC<Props> = ({ children, className }) => {
|
||||||
|
const { memberRole } = useWorkspaceMyMembership();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full w-full md:flex md:flex-row-reverse md:overflow-hidden">
|
||||||
|
<ProfileSidebar />
|
||||||
|
<div className="md:h-full w-full flex flex-col md:overflow-hidden">
|
||||||
|
<ProfileNavbar memberRole={memberRole} />
|
||||||
|
{memberRole.isOwner || memberRole.isMember || memberRole.isViewer ? (
|
||||||
|
<div className={`md:h-full w-full overflow-hidden ${className}`}>{children}</div>
|
||||||
|
) : (
|
||||||
|
<div className="h-full w-full grid place-items-center text-custom-text-200">
|
||||||
|
You do not have the permission to access this page.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,48 +1,19 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
// contexts
|
// contexts
|
||||||
import { ProfileIssuesContextProvider } from "contexts/profile-issues-context";
|
import { ProfileIssuesContextProvider } from "contexts/profile-issues-context";
|
||||||
// hooks
|
import { ProfileAuthWrapper } from "layouts/profile-layout";
|
||||||
import useUser from "hooks/use-user";
|
|
||||||
// layouts
|
|
||||||
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
|
||||||
// components
|
// components
|
||||||
import { ProfileIssuesView, ProfileNavbar, ProfileSidebar } from "components/profile";
|
import { ProfileIssuesView } from "components/profile";
|
||||||
// ui
|
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
|
||||||
// types
|
// types
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
|
|
||||||
const ProfileAssignedIssues: NextPage = () => {
|
const ProfileAssignedIssues: NextPage = () => (
|
||||||
const router = useRouter();
|
<ProfileIssuesContextProvider>
|
||||||
const { workspaceSlug } = router.query;
|
<ProfileAuthWrapper>
|
||||||
|
<ProfileIssuesView />
|
||||||
const { user } = useUser();
|
</ProfileAuthWrapper>
|
||||||
|
</ProfileIssuesContextProvider>
|
||||||
return (
|
);
|
||||||
<ProfileIssuesContextProvider>
|
|
||||||
<WorkspaceAuthorizationLayout
|
|
||||||
breadcrumbs={
|
|
||||||
<Breadcrumbs>
|
|
||||||
<BreadcrumbItem title="Settings" link={`/${workspaceSlug}/me/profile`} />
|
|
||||||
<BreadcrumbItem title={`${user?.first_name} ${user?.last_name}`} />
|
|
||||||
</Breadcrumbs>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="h-full w-full flex overflow-hidden">
|
|
||||||
<div className="h-full w-full flex flex-col overflow-hidden">
|
|
||||||
<ProfileNavbar />
|
|
||||||
<div className="h-full w-full flex flex-col overflow-hidden">
|
|
||||||
<ProfileIssuesView />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ProfileSidebar />
|
|
||||||
</div>
|
|
||||||
</WorkspaceAuthorizationLayout>
|
|
||||||
</ProfileIssuesContextProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProfileAssignedIssues;
|
export default ProfileAssignedIssues;
|
||||||
|
@ -1,48 +1,20 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
// contexts
|
// contexts
|
||||||
import { ProfileIssuesContextProvider } from "contexts/profile-issues-context";
|
import { ProfileIssuesContextProvider } from "contexts/profile-issues-context";
|
||||||
// hooks
|
|
||||||
import useUser from "hooks/use-user";
|
|
||||||
// layouts
|
// layouts
|
||||||
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
import { ProfileAuthWrapper } from "layouts/profile-layout";
|
||||||
// components
|
// components
|
||||||
import { ProfileIssuesView, ProfileNavbar, ProfileSidebar } from "components/profile";
|
import { ProfileIssuesView } from "components/profile";
|
||||||
// ui
|
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
|
||||||
// types
|
// types
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
|
|
||||||
const ProfileCreatedIssues: NextPage = () => {
|
const ProfileCreatedIssues: NextPage = () => (
|
||||||
const router = useRouter();
|
<ProfileIssuesContextProvider>
|
||||||
const { workspaceSlug } = router.query;
|
<ProfileAuthWrapper>
|
||||||
|
<ProfileIssuesView />
|
||||||
const { user } = useUser();
|
</ProfileAuthWrapper>
|
||||||
|
</ProfileIssuesContextProvider>
|
||||||
return (
|
);
|
||||||
<ProfileIssuesContextProvider>
|
|
||||||
<WorkspaceAuthorizationLayout
|
|
||||||
breadcrumbs={
|
|
||||||
<Breadcrumbs>
|
|
||||||
<BreadcrumbItem title="Settings" link={`/${workspaceSlug}/me/profile`} />
|
|
||||||
<BreadcrumbItem title={`${user?.first_name} ${user?.last_name}`} />
|
|
||||||
</Breadcrumbs>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="h-full w-full flex overflow-hidden">
|
|
||||||
<div className="h-full w-full flex flex-col overflow-hidden">
|
|
||||||
<ProfileNavbar />
|
|
||||||
<div className="h-full w-full flex flex-col overflow-hidden">
|
|
||||||
<ProfileIssuesView />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ProfileSidebar />
|
|
||||||
</div>
|
|
||||||
</WorkspaceAuthorizationLayout>
|
|
||||||
</ProfileIssuesContextProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProfileCreatedIssues;
|
export default ProfileCreatedIssues;
|
||||||
|
@ -1,34 +1,26 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// layouts
|
|
||||||
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
|
||||||
// services
|
// services
|
||||||
import userService from "services/user.service";
|
import userService from "services/user.service";
|
||||||
|
// layouts
|
||||||
|
import { ProfileAuthWrapper } from "layouts/profile-layout";
|
||||||
// components
|
// components
|
||||||
import {
|
import {
|
||||||
ProfileNavbar,
|
ProfileActivity,
|
||||||
ProfilePriorityDistribution,
|
ProfilePriorityDistribution,
|
||||||
ProfileSidebar,
|
|
||||||
ProfileStateDistribution,
|
ProfileStateDistribution,
|
||||||
ProfileStats,
|
ProfileStats,
|
||||||
ProfileWorkload,
|
ProfileWorkload,
|
||||||
} from "components/profile";
|
} from "components/profile";
|
||||||
// ui
|
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
|
||||||
import { Icon, Loader } from "components/ui";
|
|
||||||
// helpers
|
|
||||||
import { activityDetails } from "helpers/activity.helper";
|
|
||||||
import { timeAgo } from "helpers/date-time.helper";
|
|
||||||
// types
|
// types
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
import { IUserStateDistribution, TStateGroups } from "types";
|
import { IUserStateDistribution, TStateGroups } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { USER_PROFILE_DATA, USER_PROFILE_ACTIVITY } from "constants/fetch-keys";
|
import { USER_PROFILE_DATA } from "constants/fetch-keys";
|
||||||
import { GROUP_CHOICES } from "constants/project";
|
import { GROUP_CHOICES } from "constants/project";
|
||||||
|
|
||||||
const ProfileOverview: NextPage = () => {
|
const ProfileOverview: NextPage = () => {
|
||||||
@ -42,15 +34,6 @@ const ProfileOverview: NextPage = () => {
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: userProfileActivity } = useSWR(
|
|
||||||
workspaceSlug && userId
|
|
||||||
? USER_PROFILE_ACTIVITY(workspaceSlug.toString(), userId.toString())
|
|
||||||
: null,
|
|
||||||
workspaceSlug && userId
|
|
||||||
? () => userService.getUserProfileActivity(workspaceSlug.toString(), userId.toString())
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const stateDistribution: IUserStateDistribution[] = Object.keys(GROUP_CHOICES).map((key) => {
|
const stateDistribution: IUserStateDistribution[] = Object.keys(GROUP_CHOICES).map((key) => {
|
||||||
const group = userProfile?.state_distribution.find((g) => g.state_group === key);
|
const group = userProfile?.state_distribution.find((g) => g.state_group === key);
|
||||||
|
|
||||||
@ -59,93 +42,20 @@ const ProfileOverview: NextPage = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkspaceAuthorizationLayout
|
<ProfileAuthWrapper>
|
||||||
breadcrumbs={
|
<div className="h-full w-full px-5 md:px-9 py-5 space-y-7 overflow-y-auto">
|
||||||
<Breadcrumbs>
|
<ProfileStats userProfile={userProfile} />
|
||||||
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
<ProfileWorkload stateDistribution={stateDistribution} />
|
||||||
<BreadcrumbItem title={`User Name`} />
|
<div className="grid grid-cols-1 xl:grid-cols-2 items-stretch gap-5">
|
||||||
</Breadcrumbs>
|
<ProfilePriorityDistribution userProfile={userProfile} />
|
||||||
}
|
<ProfileStateDistribution
|
||||||
>
|
stateDistribution={stateDistribution}
|
||||||
<div className="h-full w-full flex overflow-hidden">
|
userProfile={userProfile}
|
||||||
<div className="h-full w-full flex flex-col overflow-hidden">
|
/>
|
||||||
<ProfileNavbar />
|
|
||||||
<div className="h-full w-full overflow-y-auto px-9 py-5 space-y-7">
|
|
||||||
<ProfileStats userProfile={userProfile} />
|
|
||||||
<ProfileWorkload stateDistribution={stateDistribution} />
|
|
||||||
<div className="grid grid-cols-1 xl:grid-cols-2 items-stretch gap-5">
|
|
||||||
<ProfilePriorityDistribution userProfile={userProfile} />
|
|
||||||
<ProfileStateDistribution
|
|
||||||
stateDistribution={stateDistribution}
|
|
||||||
userProfile={userProfile}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h3 className="text-lg font-medium">Recent Activity</h3>
|
|
||||||
<div className="border border-custom-border-100 rounded p-6">
|
|
||||||
{userProfileActivity ? (
|
|
||||||
<div className="space-y-5">
|
|
||||||
{userProfileActivity.results.map((activity) => (
|
|
||||||
<div key={activity.id} className="flex gap-3">
|
|
||||||
<div className="flex-shrink-0">
|
|
||||||
{activity.actor_detail.avatar && activity.actor_detail.avatar !== "" ? (
|
|
||||||
<img
|
|
||||||
src={activity.actor_detail.avatar}
|
|
||||||
alt={activity.actor_detail.first_name}
|
|
||||||
height={24}
|
|
||||||
width={24}
|
|
||||||
className="rounded"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="grid h-6 w-6 place-items-center rounded border-2 bg-gray-700 text-xs text-white">
|
|
||||||
{activity.actor_detail.first_name.charAt(0)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="-mt-1">
|
|
||||||
<p className="text-sm text-custom-text-200">
|
|
||||||
<span className="font-medium text-custom-text-100">
|
|
||||||
{activity.actor_detail.first_name} {activity.actor_detail.last_name}{" "}
|
|
||||||
</span>
|
|
||||||
{activity.field ? (
|
|
||||||
activityDetails[activity.field]?.message(activity as any)
|
|
||||||
) : (
|
|
||||||
<span>
|
|
||||||
created this{" "}
|
|
||||||
<Link
|
|
||||||
href={`/${activity.workspace_detail.slug}/projects/${activity.project}/issues/${activity.issue}`}
|
|
||||||
>
|
|
||||||
<a className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline">
|
|
||||||
Issue
|
|
||||||
<Icon iconName="launch" className="!text-xs" />
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-custom-text-200">
|
|
||||||
{timeAgo(activity.created_at)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Loader className="space-y-5">
|
|
||||||
<Loader.Item height="40px" />
|
|
||||||
<Loader.Item height="40px" />
|
|
||||||
<Loader.Item height="40px" />
|
|
||||||
<Loader.Item height="40px" />
|
|
||||||
<Loader.Item height="40px" />
|
|
||||||
</Loader>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<ProfileSidebar />
|
<ProfileActivity />
|
||||||
</div>
|
</div>
|
||||||
</WorkspaceAuthorizationLayout>
|
</ProfileAuthWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,48 +1,20 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
// contexts
|
// contexts
|
||||||
import { ProfileIssuesContextProvider } from "contexts/profile-issues-context";
|
import { ProfileIssuesContextProvider } from "contexts/profile-issues-context";
|
||||||
// hooks
|
|
||||||
import useUser from "hooks/use-user";
|
|
||||||
// layouts
|
// layouts
|
||||||
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
import { ProfileAuthWrapper } from "layouts/profile-layout";
|
||||||
// components
|
// components
|
||||||
import { ProfileIssuesView, ProfileNavbar, ProfileSidebar } from "components/profile";
|
import { ProfileIssuesView } from "components/profile";
|
||||||
// ui
|
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
|
||||||
// types
|
// types
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
|
|
||||||
const ProfileSubscribedIssues: NextPage = () => {
|
const ProfileSubscribedIssues: NextPage = () => (
|
||||||
const router = useRouter();
|
<ProfileIssuesContextProvider>
|
||||||
const { workspaceSlug } = router.query;
|
<ProfileAuthWrapper>
|
||||||
|
<ProfileIssuesView />
|
||||||
const { user } = useUser();
|
</ProfileAuthWrapper>
|
||||||
|
</ProfileIssuesContextProvider>
|
||||||
return (
|
);
|
||||||
<ProfileIssuesContextProvider>
|
|
||||||
<WorkspaceAuthorizationLayout
|
|
||||||
breadcrumbs={
|
|
||||||
<Breadcrumbs>
|
|
||||||
<BreadcrumbItem title="Settings" link={`/${workspaceSlug}/me/profile`} />
|
|
||||||
<BreadcrumbItem title={`${user?.first_name} ${user?.last_name}`} />
|
|
||||||
</Breadcrumbs>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="h-full w-full flex overflow-hidden">
|
|
||||||
<div className="h-full w-full flex flex-col overflow-hidden">
|
|
||||||
<ProfileNavbar />
|
|
||||||
<div className="h-full w-full flex flex-col overflow-hidden">
|
|
||||||
<ProfileIssuesView />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ProfileSidebar />
|
|
||||||
</div>
|
|
||||||
</WorkspaceAuthorizationLayout>
|
|
||||||
</ProfileIssuesContextProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProfileSubscribedIssues;
|
export default ProfileSubscribedIssues;
|
||||||
|
@ -23,6 +23,8 @@ import { IIssue } from "types";
|
|||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS } from "constants/fetch-keys";
|
import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS } from "constants/fetch-keys";
|
||||||
|
// helper
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
|
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
name: "",
|
name: "",
|
||||||
@ -146,13 +148,15 @@ const ArchivedIssueDetailsPage: NextPage = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<Breadcrumbs.BreadcrumbItem
|
<Breadcrumbs.BreadcrumbItem
|
||||||
title={`${issueDetails?.project_detail.name ?? "Project"} Issues`}
|
title={`${truncateText(issueDetails?.project_detail.name ?? "Project", 32)} Issues`}
|
||||||
link={`/${workspaceSlug}/projects/${projectId as string}/issues`}
|
link={`/${workspaceSlug}/projects/${projectId as string}/issues`}
|
||||||
|
linkTruncate
|
||||||
/>
|
/>
|
||||||
<Breadcrumbs.BreadcrumbItem
|
<Breadcrumbs.BreadcrumbItem
|
||||||
title={`Issue ${issueDetails?.project_detail.identifier ?? "Project"}-${
|
title={`Issue ${issueDetails?.project_detail.identifier ?? "Project"}-${
|
||||||
issueDetails?.sequence_id ?? "..."
|
issueDetails?.sequence_id ?? "..."
|
||||||
} Details`}
|
} Details`}
|
||||||
|
unshrinkTitle
|
||||||
/>
|
/>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ import useSWR from "swr";
|
|||||||
|
|
||||||
// services
|
// services
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
|
// hooks
|
||||||
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
// layouts
|
// layouts
|
||||||
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// contexts
|
// contexts
|
||||||
@ -21,8 +23,6 @@ import { XMarkIcon } from "@heroicons/react/24/outline";
|
|||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_DETAILS } from "constants/fetch-keys";
|
import { PROJECT_DETAILS } from "constants/fetch-keys";
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
const ProjectArchivedIssues: NextPage = () => {
|
const ProjectArchivedIssues: NextPage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -44,7 +44,7 @@ const ProjectArchivedIssues: NextPage = () => {
|
|||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
title={`${truncateText(projectDetails?.name ?? "Project", 12)} Archived Issues`}
|
title={`${truncateText(projectDetails?.name ?? "Project", 32)} Archived Issues`}
|
||||||
/>
|
/>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
|
@ -109,8 +109,9 @@ const SingleCycle: React.FC = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
title={`${cycleDetails?.project_detail.name ?? "Project"} Cycles`}
|
title={`${truncateText(cycleDetails?.project_detail.name ?? "Project", 32)} Cycles`}
|
||||||
link={`/${workspaceSlug}/projects/${projectId}/cycles`}
|
link={`/${workspaceSlug}/projects/${projectId}/cycles`}
|
||||||
|
linkTruncate
|
||||||
/>
|
/>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
@ -122,7 +123,7 @@ const SingleCycle: React.FC = () => {
|
|||||||
{cycleDetails?.name && truncateText(cycleDetails.name, 40)}
|
{cycleDetails?.name && truncateText(cycleDetails.name, 40)}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
className="ml-1.5"
|
className="ml-1.5 flex-shrink-0"
|
||||||
width="auto"
|
width="auto"
|
||||||
>
|
>
|
||||||
{cycles?.map((cycle) => (
|
{cycles?.map((cycle) => (
|
||||||
@ -137,7 +138,7 @@ const SingleCycle: React.FC = () => {
|
|||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
}
|
}
|
||||||
right={
|
right={
|
||||||
<div className={`flex items-center gap-2 duration-300`}>
|
<div className={`flex flex-shrink-0 items-center gap-2 duration-300`}>
|
||||||
<IssuesFilterView />
|
<IssuesFilterView />
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
onClick={() => setAnalyticsModal(true)}
|
onClick={() => setAnalyticsModal(true)}
|
||||||
|
@ -29,6 +29,8 @@ import emptyCycle from "public/empty-state/cycle.svg";
|
|||||||
// types
|
// types
|
||||||
import { SelectCycleType } from "types";
|
import { SelectCycleType } from "types";
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
|
// helper
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
|
|
||||||
const tabsList = ["All", "Active", "Upcoming", "Completed", "Drafts"];
|
const tabsList = ["All", "Active", "Upcoming", "Completed", "Drafts"];
|
||||||
|
|
||||||
@ -91,7 +93,7 @@ const ProjectCycles: NextPage = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
||||||
<BreadcrumbItem title={`${projectDetails?.name ?? "Project"} Cycles`} />
|
<BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Cycles`} />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
right={
|
right={
|
||||||
|
@ -31,7 +31,7 @@ const ProjectInbox: NextPage = () => {
|
|||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
title={`${truncateText(projectDetails?.name ?? "Project", 12)} Inbox`}
|
title={`${truncateText(projectDetails?.name ?? "Project", 32)} Inbox`}
|
||||||
/>
|
/>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,8 @@ import { IIssue } from "types";
|
|||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS } from "constants/fetch-keys";
|
import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS } from "constants/fetch-keys";
|
||||||
|
// helper
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
|
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
name: "",
|
name: "",
|
||||||
@ -110,13 +112,15 @@ const IssueDetailsPage: NextPage = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<Breadcrumbs.BreadcrumbItem
|
<Breadcrumbs.BreadcrumbItem
|
||||||
title={`${issueDetails?.project_detail.name ?? "Project"} Issues`}
|
title={`${truncateText(issueDetails?.project_detail.name ?? "Project", 32)} Issues`}
|
||||||
link={`/${workspaceSlug}/projects/${projectId as string}/issues`}
|
link={`/${workspaceSlug}/projects/${projectId as string}/issues`}
|
||||||
|
linkTruncate
|
||||||
/>
|
/>
|
||||||
<Breadcrumbs.BreadcrumbItem
|
<Breadcrumbs.BreadcrumbItem
|
||||||
title={`Issue ${issueDetails?.project_detail.identifier ?? "Project"}-${
|
title={`Issue ${issueDetails?.project_detail.identifier ?? "Project"}-${
|
||||||
issueDetails?.sequence_id ?? "..."
|
issueDetails?.sequence_id ?? "..."
|
||||||
} Details`}
|
} Details`}
|
||||||
|
unshrinkTitle
|
||||||
/>
|
/>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ const ProjectIssues: NextPage = () => {
|
|||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
title={`${truncateText(projectDetails?.name ?? "Project", 12)} Issues`}
|
title={`${truncateText(projectDetails?.name ?? "Project", 32)} Issues`}
|
||||||
/>
|
/>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
|
@ -112,8 +112,9 @@ const SingleModule: React.FC = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
title={`${moduleDetails?.project_detail.name ?? "Project"} Modules`}
|
title={`${truncateText(moduleDetails?.project_detail.name ?? "Project", 32)} Modules`}
|
||||||
link={`/${workspaceSlug}/projects/${projectId}/modules`}
|
link={`/${workspaceSlug}/projects/${projectId}/modules`}
|
||||||
|
linkTruncate
|
||||||
/>
|
/>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,8 @@ import { IModule, SelectModuleType } from "types/modules";
|
|||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { MODULE_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
import { MODULE_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
||||||
|
// helper
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
|
|
||||||
const ProjectModules: NextPage = () => {
|
const ProjectModules: NextPage = () => {
|
||||||
const [selectedModule, setSelectedModule] = useState<SelectModuleType>();
|
const [selectedModule, setSelectedModule] = useState<SelectModuleType>();
|
||||||
@ -73,7 +75,7 @@ const ProjectModules: NextPage = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
||||||
<BreadcrumbItem title={`${activeProject?.name ?? "Project"} Modules`} />
|
<BreadcrumbItem title={`${truncateText(activeProject?.name ?? "Project", 32)} Modules`} />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
right={
|
right={
|
||||||
|
@ -43,7 +43,7 @@ import {
|
|||||||
import { ColorPalletteIcon, ClipboardIcon } from "components/icons";
|
import { ColorPalletteIcon, ClipboardIcon } from "components/icons";
|
||||||
// helpers
|
// helpers
|
||||||
import { render24HourFormatTime, renderShortDate } from "helpers/date-time.helper";
|
import { render24HourFormatTime, renderShortDate } from "helpers/date-time.helper";
|
||||||
import { copyTextToClipboard } from "helpers/string.helper";
|
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
||||||
import { orderArrayBy } from "helpers/array.helper";
|
import { orderArrayBy } from "helpers/array.helper";
|
||||||
// types
|
// types
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
@ -346,7 +346,7 @@ const SinglePage: NextPage = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
||||||
<BreadcrumbItem title={`${projectDetails?.name ?? "Project"} Pages`} />
|
<BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project",32)} Pages`} />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -11,6 +11,7 @@ import { Tab } from "@headlessui/react";
|
|||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useLocalStorage from "hooks/use-local-storage";
|
import useLocalStorage from "hooks/use-local-storage";
|
||||||
|
import useUserAuth from "hooks/use-user-auth";
|
||||||
// icons
|
// icons
|
||||||
import { PlusIcon } from "components/icons";
|
import { PlusIcon } from "components/icons";
|
||||||
// layouts
|
// layouts
|
||||||
@ -27,7 +28,8 @@ import { TPageViewProps } from "types";
|
|||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_DETAILS } from "constants/fetch-keys";
|
import { PROJECT_DETAILS } from "constants/fetch-keys";
|
||||||
import useUserAuth from "hooks/use-user-auth";
|
// helper
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
|
|
||||||
const AllPagesList = dynamic<TPagesListProps>(
|
const AllPagesList = dynamic<TPagesListProps>(
|
||||||
() => import("components/pages").then((a) => a.AllPagesList),
|
() => import("components/pages").then((a) => a.AllPagesList),
|
||||||
@ -107,7 +109,9 @@ const ProjectPages: NextPage = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
||||||
<BreadcrumbItem title={`${projectDetails?.name ?? "Project"} Pages`} />
|
<BreadcrumbItem
|
||||||
|
title={`${truncateText(projectDetails?.name ?? "Project", 32)} Pages`}
|
||||||
|
/>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
right={
|
right={
|
||||||
|
@ -22,6 +22,8 @@ import type { NextPage } from "next";
|
|||||||
import { IProject } from "types";
|
import { IProject } from "types";
|
||||||
// constant
|
// constant
|
||||||
import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
||||||
|
// helper
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
|
|
||||||
const AutomationsSettings: NextPage = () => {
|
const AutomationsSettings: NextPage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -65,10 +67,11 @@ const AutomationsSettings: NextPage = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
title={`${projectDetails?.name ?? "Project"}`}
|
title={`${truncateText(projectDetails?.name ?? "Project", 32)}`}
|
||||||
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
|
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
|
||||||
|
linkTruncate
|
||||||
/>
|
/>
|
||||||
<BreadcrumbItem title="Automations Settings" />
|
<BreadcrumbItem title="Automations Settings" unshrinkTitle />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -23,6 +23,8 @@ import { IProject, IUserLite, IWorkspace } from "types";
|
|||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECTS_LIST, PROJECT_DETAILS, PROJECT_MEMBERS } from "constants/fetch-keys";
|
import { PROJECTS_LIST, PROJECT_DETAILS, PROJECT_MEMBERS } from "constants/fetch-keys";
|
||||||
|
// helper
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
|
|
||||||
const defaultValues: Partial<IProject> = {
|
const defaultValues: Partial<IProject> = {
|
||||||
project_lead: null,
|
project_lead: null,
|
||||||
@ -103,10 +105,11 @@ const ControlSettings: NextPage = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
title={`${projectDetails?.name ?? "Project"}`}
|
title={`${truncateText(projectDetails?.name ?? "Project", 32)}`}
|
||||||
link={`/${workspaceSlug}/projects/${projectId}/issues`}
|
link={`/${workspaceSlug}/projects/${projectId}/issues`}
|
||||||
|
linkTruncate
|
||||||
/>
|
/>
|
||||||
<BreadcrumbItem title="Control Settings" />
|
<BreadcrumbItem title="Control Settings" unshrinkTitle />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -29,6 +29,8 @@ import { IEstimate, IProject } from "types";
|
|||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { ESTIMATES_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
import { ESTIMATES_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
||||||
|
// helper
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
|
|
||||||
const EstimatesSettings: NextPage = () => {
|
const EstimatesSettings: NextPage = () => {
|
||||||
const [estimateFormOpen, setEstimateFormOpen] = useState(false);
|
const [estimateFormOpen, setEstimateFormOpen] = useState(false);
|
||||||
@ -115,10 +117,11 @@ const EstimatesSettings: NextPage = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
title={`${projectDetails?.name ?? "Project"}`}
|
title={`${truncateText(projectDetails?.name ?? "Project", 32)}`}
|
||||||
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
|
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
|
||||||
|
linkTruncate
|
||||||
/>
|
/>
|
||||||
<BreadcrumbItem title="Estimates Settings" />
|
<BreadcrumbItem title="Estimates Settings" unshrinkTitle />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -25,6 +25,8 @@ import { IProject } from "types";
|
|||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
||||||
|
// helper
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
|
|
||||||
const featuresList = [
|
const featuresList = [
|
||||||
{
|
{
|
||||||
@ -139,10 +141,11 @@ const FeaturesSettings: NextPage = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
title={`${projectDetails?.name ?? "Project"}`}
|
title={`${truncateText(projectDetails?.name ?? "Project", 32)}`}
|
||||||
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
|
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
|
||||||
|
linkTruncate
|
||||||
/>
|
/>
|
||||||
<BreadcrumbItem title="Features Settings" />
|
<BreadcrumbItem title="Features Settings" unshrinkTitle />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -29,6 +29,7 @@ import {
|
|||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
import { renderEmoji } from "helpers/emoji.helper";
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { IProject, IWorkspace } from "types";
|
import { IProject, IWorkspace } from "types";
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
@ -161,10 +162,11 @@ const GeneralSettings: NextPage = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
title={`${projectDetails?.name ?? "Project"}`}
|
title={`${truncateText(projectDetails?.name ?? "Project", 32)}`}
|
||||||
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
|
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
|
||||||
|
linkTruncate
|
||||||
/>
|
/>
|
||||||
<BreadcrumbItem title="General Settings" />
|
<BreadcrumbItem title="General Settings" unshrinkTitle />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -23,6 +23,8 @@ import { IProject } from "types";
|
|||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_DETAILS, WORKSPACE_INTEGRATIONS } from "constants/fetch-keys";
|
import { PROJECT_DETAILS, WORKSPACE_INTEGRATIONS } from "constants/fetch-keys";
|
||||||
|
// helper
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
|
|
||||||
const ProjectIntegrations: NextPage = () => {
|
const ProjectIntegrations: NextPage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -48,10 +50,11 @@ const ProjectIntegrations: NextPage = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
title={`${projectDetails?.name ?? "Project"}`}
|
title={`${truncateText(projectDetails?.name ?? "Project", 32)}`}
|
||||||
link={`/${workspaceSlug}/projects/${projectId}/issues`}
|
link={`/${workspaceSlug}/projects/${projectId}/issues`}
|
||||||
|
linkTruncate
|
||||||
/>
|
/>
|
||||||
<BreadcrumbItem title="Integrations" />
|
<BreadcrumbItem title="Integrations" unshrinkTitle />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -32,6 +32,8 @@ import { IIssueLabels } from "types";
|
|||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_DETAILS, PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
|
import { PROJECT_DETAILS, PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
|
||||||
|
// helper
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
|
|
||||||
const LabelsSettings: NextPage = () => {
|
const LabelsSettings: NextPage = () => {
|
||||||
// create/edit label form
|
// create/edit label form
|
||||||
@ -103,10 +105,11 @@ const LabelsSettings: NextPage = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
title={`${projectDetails?.name ?? "Project"}`}
|
title={`${truncateText(projectDetails?.name ?? "Project", 32)}`}
|
||||||
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
|
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
|
||||||
|
linkTruncate
|
||||||
/>
|
/>
|
||||||
<BreadcrumbItem title="Labels Settings" />
|
<BreadcrumbItem title="Labels Settings" unshrinkTitle />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
@ -28,6 +29,8 @@ import type { NextPage } from "next";
|
|||||||
import { PROJECT_INVITATIONS, PROJECT_MEMBERS, WORKSPACE_DETAILS } from "constants/fetch-keys";
|
import { PROJECT_INVITATIONS, PROJECT_MEMBERS, WORKSPACE_DETAILS } from "constants/fetch-keys";
|
||||||
// constants
|
// constants
|
||||||
import { ROLE } from "constants/workspace";
|
import { ROLE } from "constants/workspace";
|
||||||
|
// helper
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
|
|
||||||
const MembersSettings: NextPage = () => {
|
const MembersSettings: NextPage = () => {
|
||||||
const [inviteModal, setInviteModal] = useState(false);
|
const [inviteModal, setInviteModal] = useState(false);
|
||||||
@ -93,10 +96,11 @@ const MembersSettings: NextPage = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
title={`${projectDetails?.name ?? "Project"}`}
|
title={`${truncateText(projectDetails?.name ?? "Project", 32)}`}
|
||||||
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
|
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
|
||||||
|
linkTruncate
|
||||||
/>
|
/>
|
||||||
<BreadcrumbItem title="Members Settings" />
|
<BreadcrumbItem title="Members Settings" unshrinkTitle />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -187,9 +191,17 @@ const MembersSettings: NextPage = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm">
|
{member.member ? (
|
||||||
{member.first_name} {member.last_name}
|
<Link href={`/${workspaceSlug}/profile/${member.memberId}`}>
|
||||||
</h4>
|
<a className="text-sm">
|
||||||
|
{member.first_name} {member.last_name}
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<h4 className="text-sm">
|
||||||
|
{member.first_name} {member.last_name}
|
||||||
|
</h4>
|
||||||
|
)}
|
||||||
<p className="mt-0.5 text-xs text-custom-text-200">{member.email}</p>
|
<p className="mt-0.5 text-xs text-custom-text-200">{member.email}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,6 +26,7 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
|||||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||||
// helpers
|
// helpers
|
||||||
import { getStatesList, orderStateGroups } from "helpers/state.helper";
|
import { getStatesList, orderStateGroups } from "helpers/state.helper";
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
@ -64,10 +65,11 @@ const StatesSettings: NextPage = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
title={`${projectDetails?.name ?? "Project"}`}
|
title={`${truncateText(projectDetails?.name ?? "Project", 32)}`}
|
||||||
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
|
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
|
||||||
|
linkTruncate
|
||||||
/>
|
/>
|
||||||
<BreadcrumbItem title="States Settings" />
|
<BreadcrumbItem title="States Settings" unshrinkTitle />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -26,6 +26,8 @@ import emptyProject from "public/empty-state/project.svg";
|
|||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_MEMBERS } from "constants/fetch-keys";
|
import { PROJECT_MEMBERS } from "constants/fetch-keys";
|
||||||
|
// helper
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
|
|
||||||
const ProjectsPage: NextPage = () => {
|
const ProjectsPage: NextPage = () => {
|
||||||
// router
|
// router
|
||||||
@ -44,7 +46,10 @@ const ProjectsPage: NextPage = () => {
|
|||||||
<WorkspaceAuthorizationLayout
|
<WorkspaceAuthorizationLayout
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem title={`${activeWorkspace?.name ?? "Workspace"} Projects`} />
|
<BreadcrumbItem
|
||||||
|
title={`${truncateText(activeWorkspace?.name ?? "Workspace", 32)} Projects`}
|
||||||
|
unshrinkTitle={false}
|
||||||
|
/>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
right={
|
right={
|
||||||
|
@ -16,6 +16,8 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
|||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { WORKSPACE_DETAILS } from "constants/fetch-keys";
|
import { WORKSPACE_DETAILS } from "constants/fetch-keys";
|
||||||
|
// helper
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
|
|
||||||
const BillingSettings: NextPage = () => {
|
const BillingSettings: NextPage = () => {
|
||||||
const {
|
const {
|
||||||
@ -32,10 +34,11 @@ const BillingSettings: NextPage = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
title={`${activeWorkspace?.name ?? "Workspace"}`}
|
title={`${truncateText(activeWorkspace?.name ?? "Workspace", 32)}`}
|
||||||
link={`/${workspaceSlug}`}
|
link={`/${workspaceSlug}`}
|
||||||
|
linkTruncate
|
||||||
/>
|
/>
|
||||||
<BreadcrumbItem title="Billing & Plans Settings" />
|
<BreadcrumbItem title="Billing & Plans Settings" unshrinkTitle />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -1,26 +1,43 @@
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
|
// services
|
||||||
|
import workspaceService from "services/workspace.service";
|
||||||
// layouts
|
// layouts
|
||||||
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||||
import { SettingsHeader } from "components/workspace";
|
import { SettingsHeader } from "components/workspace";
|
||||||
// components
|
// components
|
||||||
import IntegrationGuide from "components/integration/guide";
|
import IntegrationGuide from "components/integration/guide";
|
||||||
|
import { IntegrationAndImportExportBanner } from "components/ui";
|
||||||
// ui
|
// ui
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
// types
|
// types
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
import { IntegrationAndImportExportBanner } from "components/ui";
|
// fetch-keys
|
||||||
|
import { WORKSPACE_DETAILS } from "constants/fetch-keys";
|
||||||
|
// helper
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
|
|
||||||
const ImportExport: NextPage = () => {
|
const ImportExport: NextPage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
|
const { data: activeWorkspace } = useSWR(
|
||||||
|
workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null,
|
||||||
|
() => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null)
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkspaceAuthorizationLayout
|
<WorkspaceAuthorizationLayout
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem title={`${workspaceSlug ?? "Workspace"}`} link={`/${workspaceSlug}`} />
|
<BreadcrumbItem
|
||||||
<BreadcrumbItem title="Import/ Export Settings" />
|
title={`${truncateText(activeWorkspace?.name ?? "Workspace", 32)}`}
|
||||||
|
link={`/${workspaceSlug}`}
|
||||||
|
linkTruncate
|
||||||
|
/>
|
||||||
|
<BreadcrumbItem title="Import/ Export Settings" unshrinkTitle />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -14,7 +14,6 @@ import useToast from "hooks/use-toast";
|
|||||||
import useUserAuth from "hooks/use-user-auth";
|
import useUserAuth from "hooks/use-user-auth";
|
||||||
// layouts
|
// layouts
|
||||||
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||||
import SettingsNavbar from "layouts/settings-navbar";
|
|
||||||
// components
|
// components
|
||||||
import { ImageUploadModal } from "components/core";
|
import { ImageUploadModal } from "components/core";
|
||||||
import { DeleteWorkspaceModal, SettingsHeader } from "components/workspace";
|
import { DeleteWorkspaceModal, SettingsHeader } from "components/workspace";
|
||||||
@ -24,7 +23,7 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
|||||||
// icons
|
// icons
|
||||||
import { LinkIcon } from "@heroicons/react/24/outline";
|
import { LinkIcon } from "@heroicons/react/24/outline";
|
||||||
// helpers
|
// helpers
|
||||||
import { copyTextToClipboard } from "helpers/string.helper";
|
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import type { IWorkspace } from "types";
|
import type { IWorkspace } from "types";
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
@ -147,7 +146,9 @@ const WorkspaceSettings: NextPage = () => {
|
|||||||
<WorkspaceAuthorizationLayout
|
<WorkspaceAuthorizationLayout
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem title={`${activeWorkspace?.name ?? "Workspace"} Settings`} />
|
<BreadcrumbItem
|
||||||
|
title={`${truncateText(activeWorkspace?.name ?? "Workspace", 32)} Settings`}
|
||||||
|
/>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -19,6 +19,8 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
|||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { WORKSPACE_DETAILS, APP_INTEGRATIONS } from "constants/fetch-keys";
|
import { WORKSPACE_DETAILS, APP_INTEGRATIONS } from "constants/fetch-keys";
|
||||||
|
// helper
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
|
|
||||||
const WorkspaceIntegrations: NextPage = () => {
|
const WorkspaceIntegrations: NextPage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -38,10 +40,11 @@ const WorkspaceIntegrations: NextPage = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
title={`${activeWorkspace?.name ?? "Workspace"}`}
|
title={`${truncateText(activeWorkspace?.name ?? "Workspace", 32)}`}
|
||||||
link={`/${workspaceSlug}`}
|
link={`/${workspaceSlug}`}
|
||||||
|
linkTruncate
|
||||||
/>
|
/>
|
||||||
<BreadcrumbItem title="Integrations" />
|
<BreadcrumbItem title="Integrations" unshrinkTitle />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import Image from "next/image";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
@ -27,6 +27,8 @@ import type { NextPage } from "next";
|
|||||||
import { WORKSPACE_DETAILS, WORKSPACE_INVITATIONS, WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
import { WORKSPACE_DETAILS, WORKSPACE_INVITATIONS, WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
||||||
// constants
|
// constants
|
||||||
import { ROLE } from "constants/workspace";
|
import { ROLE } from "constants/workspace";
|
||||||
|
// helper
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
|
|
||||||
const MembersSettings: NextPage = () => {
|
const MembersSettings: NextPage = () => {
|
||||||
const [selectedRemoveMember, setSelectedRemoveMember] = useState<string | null>(null);
|
const [selectedRemoveMember, setSelectedRemoveMember] = useState<string | null>(null);
|
||||||
@ -89,10 +91,11 @@ const MembersSettings: NextPage = () => {
|
|||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
title={`${activeWorkspace?.name ?? "Workspace"}`}
|
title={`${truncateText(activeWorkspace?.name ?? "Workspace", 32)}`}
|
||||||
link={`/${workspaceSlug}`}
|
link={`/${workspaceSlug}`}
|
||||||
|
linkTruncate
|
||||||
/>
|
/>
|
||||||
<BreadcrumbItem title="Members Settings" />
|
<BreadcrumbItem title="Members Settings" unshrinkTitle />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -187,9 +190,17 @@ const MembersSettings: NextPage = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm">
|
{member.member ? (
|
||||||
{member.first_name} {member.last_name}
|
<Link href={`/${workspaceSlug}/profile/${member.memberId}`}>
|
||||||
</h4>
|
<a className="text-sm">
|
||||||
|
{member.first_name} {member.last_name}
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<h4 className="text-sm">
|
||||||
|
{member.first_name} {member.last_name}
|
||||||
|
</h4>
|
||||||
|
)}
|
||||||
<p className="text-xs text-custom-text-200">{member.email}</p>
|
<p className="text-xs text-custom-text-200">{member.email}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,7 +24,7 @@ import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
|
|||||||
import BlackHorizontalLogo from "public/plane-logos/black-horizontal-with-blue-logo.svg";
|
import BlackHorizontalLogo from "public/plane-logos/black-horizontal-with-blue-logo.svg";
|
||||||
import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.svg";
|
import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.svg";
|
||||||
// types
|
// types
|
||||||
import { ICurrentUserResponse, IUser, OnboardingSteps } from "types";
|
import { ICurrentUserResponse, IUser, TOnboardingSteps } from "types";
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { CURRENT_USER, USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys";
|
import { CURRENT_USER, USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys";
|
||||||
@ -43,33 +43,35 @@ const Onboarding: NextPage = () => {
|
|||||||
workspaceService.userWorkspaceInvitations()
|
workspaceService.userWorkspaceInvitations()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// update last active workspace details
|
||||||
const updateLastWorkspace = async () => {
|
const updateLastWorkspace = async () => {
|
||||||
if (!userWorkspaces) return;
|
if (!workspaces) return;
|
||||||
|
|
||||||
mutate<ICurrentUserResponse>(
|
await mutate<ICurrentUserResponse>(
|
||||||
CURRENT_USER,
|
CURRENT_USER,
|
||||||
(prevData) => {
|
(prevData) => {
|
||||||
if (!prevData) return prevData;
|
if (!prevData) return prevData;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...prevData,
|
...prevData,
|
||||||
last_workspace_id: userWorkspaces[0]?.id,
|
last_workspace_id: workspaces[0]?.id,
|
||||||
workspace: {
|
workspace: {
|
||||||
...prevData.workspace,
|
...prevData.workspace,
|
||||||
fallback_workspace_id: userWorkspaces[0]?.id,
|
fallback_workspace_id: workspaces[0]?.id,
|
||||||
fallback_workspace_slug: userWorkspaces[0]?.slug,
|
fallback_workspace_slug: workspaces[0]?.slug,
|
||||||
last_workspace_id: userWorkspaces[0]?.id,
|
last_workspace_id: workspaces[0]?.id,
|
||||||
last_workspace_slug: userWorkspaces[0]?.slug,
|
last_workspace_slug: workspaces[0]?.slug,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
await userService.updateUser({ last_workspace_id: userWorkspaces?.[0]?.id });
|
await userService.updateUser({ last_workspace_id: workspaces?.[0]?.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
const stepChange = async (steps: Partial<OnboardingSteps>) => {
|
// handle step change
|
||||||
|
const stepChange = async (steps: Partial<TOnboardingSteps>) => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
const payload: Partial<IUser> = {
|
const payload: Partial<IUser> = {
|
||||||
@ -95,16 +97,44 @@ const Onboarding: NextPage = () => {
|
|||||||
await userService.updateUser(payload);
|
await userService.updateUser(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// complete onboarding
|
||||||
|
const finishOnboarding = async () => {
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
|
mutate<ICurrentUserResponse>(
|
||||||
|
CURRENT_USER,
|
||||||
|
(prevData) => {
|
||||||
|
if (!prevData) return prevData;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prevData,
|
||||||
|
is_onboarded: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
await userService.updateUserOnBoard({ userRole: user.role }, user);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleStepChange = async () => {
|
const handleStepChange = async () => {
|
||||||
if (!user || !userWorkspaces || !invitations) return;
|
if (!user || !invitations) return;
|
||||||
|
|
||||||
const onboardingStep = user.onboarding_step;
|
const onboardingStep = user.onboarding_step;
|
||||||
|
|
||||||
if (!onboardingStep.profile_complete && step !== 1) setStep(1);
|
if (!onboardingStep.profile_complete && step !== 1) setStep(1);
|
||||||
|
|
||||||
if (onboardingStep.profile_complete && !onboardingStep.workspace_create && step !== 2)
|
if (onboardingStep.profile_complete) {
|
||||||
setStep(2);
|
if (!onboardingStep.workspace_join && invitations.length > 0 && step !== 2 && step !== 4)
|
||||||
|
setStep(4);
|
||||||
|
else if (
|
||||||
|
!onboardingStep.workspace_create &&
|
||||||
|
(step !== 4 || onboardingStep.workspace_join) &&
|
||||||
|
step !== 2
|
||||||
|
)
|
||||||
|
setStep(2);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
onboardingStep.profile_complete &&
|
onboardingStep.profile_complete &&
|
||||||
@ -113,21 +143,10 @@ const Onboarding: NextPage = () => {
|
|||||||
step !== 3
|
step !== 3
|
||||||
)
|
)
|
||||||
setStep(3);
|
setStep(3);
|
||||||
|
|
||||||
if (
|
|
||||||
onboardingStep.profile_complete &&
|
|
||||||
onboardingStep.workspace_create &&
|
|
||||||
onboardingStep.workspace_invite &&
|
|
||||||
!onboardingStep.workspace_join &&
|
|
||||||
step !== 4
|
|
||||||
) {
|
|
||||||
if (invitations.length > 0) setStep(4);
|
|
||||||
else await Router.push("/");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleStepChange();
|
handleStepChange();
|
||||||
}, [user, invitations, userWorkspaces, step]);
|
}, [user, invitations, step]);
|
||||||
|
|
||||||
if (userLoading || step === null)
|
if (userLoading || step === null)
|
||||||
return (
|
return (
|
||||||
@ -167,14 +186,27 @@ const Onboarding: NextPage = () => {
|
|||||||
<UserDetails user={user} />
|
<UserDetails user={user} />
|
||||||
) : step === 2 ? (
|
) : step === 2 ? (
|
||||||
<Workspace
|
<Workspace
|
||||||
user={user}
|
finishOnboarding={finishOnboarding}
|
||||||
updateLastWorkspace={updateLastWorkspace}
|
|
||||||
stepChange={stepChange}
|
stepChange={stepChange}
|
||||||
|
updateLastWorkspace={updateLastWorkspace}
|
||||||
|
user={user}
|
||||||
|
workspaces={workspaces}
|
||||||
/>
|
/>
|
||||||
) : step === 3 ? (
|
) : step === 3 ? (
|
||||||
<InviteMembers workspace={userWorkspaces?.[0]} user={user} stepChange={stepChange} />
|
<InviteMembers
|
||||||
|
finishOnboarding={finishOnboarding}
|
||||||
|
stepChange={stepChange}
|
||||||
|
user={user}
|
||||||
|
workspace={userWorkspaces?.[0]}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
step === 4 && <JoinWorkspaces stepChange={stepChange} />
|
step === 4 && (
|
||||||
|
<JoinWorkspaces
|
||||||
|
finishOnboarding={finishOnboarding}
|
||||||
|
stepChange={stepChange}
|
||||||
|
updateLastWorkspace={updateLastWorkspace}
|
||||||
|
/>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{step !== 4 && (
|
{step !== 4 && (
|
||||||
|
@ -254,6 +254,7 @@ class ProjectServices extends APIService {
|
|||||||
view_props?: ProjectViewTheme;
|
view_props?: ProjectViewTheme;
|
||||||
default_props?: ProjectViewTheme;
|
default_props?: ProjectViewTheme;
|
||||||
preferences?: ProjectPreferences;
|
preferences?: ProjectPreferences;
|
||||||
|
sort_order?: number;
|
||||||
}
|
}
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
await this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/project-views/`, data)
|
await this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/project-views/`, data)
|
||||||
|
@ -23,6 +23,107 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
color-scheme: light !important;
|
||||||
|
|
||||||
|
--color-background-100: 255, 255, 255; /* primary bg */
|
||||||
|
--color-background-90: 250, 250, 250; /* secondary bg */
|
||||||
|
--color-background-80: 245, 245, 245; /* tertiary bg */
|
||||||
|
|
||||||
|
--color-text-100: 23, 23, 23; /* primary text */
|
||||||
|
--color-text-200: 58, 58, 58; /* secondary text */
|
||||||
|
--color-text-300: 82, 82, 82; /* tertiary text */
|
||||||
|
--color-text-400: 163, 163, 163; /* placeholder text */
|
||||||
|
|
||||||
|
--color-border-100: 245, 245, 245; /* subtle border= 1 */
|
||||||
|
--color-border-200: 229, 229, 229; /* subtle border- 2 */
|
||||||
|
--color-border-300: 212, 212, 212; /* strong border- 1 */
|
||||||
|
--color-border-400: 185, 185, 185; /* strong border- 2 */
|
||||||
|
|
||||||
|
--color-sidebar-background-100: var(--color-background-100); /* primary sidebar bg */
|
||||||
|
--color-sidebar-background-90: var(--color-background-90); /* secondary sidebar bg */
|
||||||
|
--color-sidebar-background-80: var(--color-background-80); /* tertiary sidebar bg */
|
||||||
|
|
||||||
|
--color-sidebar-text-100: var(--color-text-100); /* primary sidebar text */
|
||||||
|
--color-sidebar-text-200: var(--color-text-200); /* secondary sidebar text */
|
||||||
|
--color-sidebar-text-300: var(--color-text-300); /* tertiary sidebar text */
|
||||||
|
--color-sidebar-text-400: var(--color-text-400); /* sidebar placeholder text */
|
||||||
|
|
||||||
|
--color-sidebar-border-100: var(--color-border-100); /* subtle sidebar border= 1 */
|
||||||
|
--color-sidebar-border-200: var(--color-border-100); /* subtle sidebar border- 2 */
|
||||||
|
--color-sidebar-border-300: var(--color-border-100); /* strong sidebar border- 1 */
|
||||||
|
--color-sidebar-border-400: var(--color-border-100); /* strong sidebar border- 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="light"],
|
||||||
|
[data-theme="light-contrast"] {
|
||||||
|
color-scheme: light !important;
|
||||||
|
|
||||||
|
--color-background-100: 255, 255, 255; /* primary bg */
|
||||||
|
--color-background-90: 250, 250, 250; /* secondary bg */
|
||||||
|
--color-background-80: 245, 245, 245; /* tertiary bg */
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="light"] {
|
||||||
|
--color-text-100: 23, 23, 23; /* primary text */
|
||||||
|
--color-text-200: 58, 58, 58; /* secondary text */
|
||||||
|
--color-text-300: 82, 82, 82; /* tertiary text */
|
||||||
|
--color-text-400: 163, 163, 163; /* placeholder text */
|
||||||
|
|
||||||
|
--color-border-100: 245, 245, 245; /* subtle border= 1 */
|
||||||
|
--color-border-200: 229, 229, 229; /* subtle border- 2 */
|
||||||
|
--color-border-300: 212, 212, 212; /* strong border- 1 */
|
||||||
|
--color-border-400: 185, 185, 185; /* strong border- 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="light-contrast"] {
|
||||||
|
--color-text-100: 11, 11, 11; /* primary text */
|
||||||
|
--color-text-200: 38, 38, 38; /* secondary text */
|
||||||
|
--color-text-300: 58, 58, 58; /* tertiary text */
|
||||||
|
--color-text-400: 115, 115, 115; /* placeholder text */
|
||||||
|
|
||||||
|
--color-border-100: 34, 34, 34; /* subtle border= 1 */
|
||||||
|
--color-border-200: 38, 38, 38; /* subtle border- 2 */
|
||||||
|
--color-border-300: 46, 46, 46; /* strong border- 1 */
|
||||||
|
--color-border-400: 58, 58, 58; /* strong border- 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"],
|
||||||
|
[data-theme="dark-contrast"] {
|
||||||
|
color-scheme: dark !important;
|
||||||
|
|
||||||
|
--color-background-100: 7, 7, 7; /* primary bg */
|
||||||
|
--color-background-90: 11, 11, 11; /* secondary bg */
|
||||||
|
--color-background-80: 23, 23, 23; /* tertiary bg */
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--color-text-100: 229, 229, 229; /* primary text */
|
||||||
|
--color-text-200: 163, 163, 163; /* secondary text */
|
||||||
|
--color-text-300: 115, 115, 115; /* tertiary text */
|
||||||
|
--color-text-400: 82, 82, 82; /* placeholder text */
|
||||||
|
|
||||||
|
--color-border-100: 34, 34, 34; /* subtle border= 1 */
|
||||||
|
--color-border-200: 38, 38, 38; /* subtle border- 2 */
|
||||||
|
--color-border-300: 46, 46, 46; /* strong border- 1 */
|
||||||
|
--color-border-400: 58, 58, 58; /* strong border- 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark-contrast"] {
|
||||||
|
--color-text-100: 250, 250, 250; /* primary text */
|
||||||
|
--color-text-200: 241, 241, 241; /* secondary text */
|
||||||
|
--color-text-300: 212, 212, 212; /* tertiary text */
|
||||||
|
--color-text-400: 115, 115, 115; /* placeholder text */
|
||||||
|
|
||||||
|
--color-border-100: 245, 245, 245; /* subtle border= 1 */
|
||||||
|
--color-border-200: 229, 229, 229; /* subtle border- 2 */
|
||||||
|
--color-border-300: 212, 212, 212; /* strong border- 1 */
|
||||||
|
--color-border-400: 185, 185, 185; /* strong border- 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="light"],
|
||||||
|
[data-theme="dark"],
|
||||||
|
[data-theme="light-contrast"],
|
||||||
|
[data-theme="dark-contrast"] {
|
||||||
--color-primary-10: 236, 241, 255;
|
--color-primary-10: 236, 241, 255;
|
||||||
--color-primary-20: 217, 228, 255;
|
--color-primary-20: 217, 228, 255;
|
||||||
--color-primary-30: 197, 214, 255;
|
--color-primary-30: 197, 214, 255;
|
||||||
@ -42,122 +143,19 @@
|
|||||||
--color-primary-800: 19, 35, 76;
|
--color-primary-800: 19, 35, 76;
|
||||||
--color-primary-900: 13, 24, 51;
|
--color-primary-900: 13, 24, 51;
|
||||||
|
|
||||||
/* default theme- light */
|
--color-sidebar-background-100: var(--color-background-100); /* primary sidebar bg */
|
||||||
--color-background-100: 255, 255, 255; /* primary bg */
|
--color-sidebar-background-90: var(--color-background-90); /* secondary sidebar bg */
|
||||||
--color-background-90: 250, 250, 250; /* secondary bg */
|
--color-sidebar-background-80: var(--color-background-80); /* tertiary sidebar bg */
|
||||||
--color-background-80: 245, 245, 245; /* tertiary bg */
|
|
||||||
|
|
||||||
--color-text-100: 23, 23, 23; /* primary text */
|
--color-sidebar-text-100: var(--color-text-100); /* primary sidebar text */
|
||||||
--color-text-200: 82, 82, 82; /* secondary text */
|
--color-sidebar-text-200: var(--color-text-200); /* secondary sidebar text */
|
||||||
--color-text-300: 115, 115, 115; /* tertiary text */
|
--color-sidebar-text-300: var(--color-text-300); /* tertiary sidebar text */
|
||||||
--color-text-400: 163, 163, 163; /* placeholder text */
|
--color-sidebar-text-400: var(--color-text-400); /* sidebar placeholder text */
|
||||||
|
|
||||||
--color-border-100: 245, 245, 245; /* subtle border= 1 */
|
--color-sidebar-border-100: var(--color-border-100); /* subtle sidebar border= 1 */
|
||||||
--color-border-200: 229, 229, 229; /* subtle border- 2 */
|
--color-sidebar-border-200: var(--color-border-100); /* subtle sidebar border- 2 */
|
||||||
--color-border-300: 212, 212, 212; /* strong border- 1 */
|
--color-sidebar-border-300: var(--color-border-100); /* strong sidebar border- 1 */
|
||||||
--color-border-400: 185, 185, 185; /* strong border- 2 */
|
--color-sidebar-border-400: var(--color-border-100); /* strong sidebar border- 2 */
|
||||||
|
|
||||||
--color-sidebar-background-100: 255, 255, 255; /* primary sidebar bg */
|
|
||||||
--color-sidebar-background-90: 250, 250, 250; /* secondary sidebar bg */
|
|
||||||
--color-sidebar-background-80: 245, 245, 245; /* tertiary sidebar bg */
|
|
||||||
|
|
||||||
--color-sidebar-text-100: 23, 23, 23; /* primary sidebar text */
|
|
||||||
--color-sidebar-text-200: 82, 82, 82; /* secondary sidebar text */
|
|
||||||
--color-sidebar-text-300: 115, 115, 115; /* tertiary sidebar text */
|
|
||||||
--color-sidebar-text-400: 163, 163, 163; /* sidebar placeholder text */
|
|
||||||
|
|
||||||
--color-sidebar-border-100: 245, 245, 245; /* subtle sidebar border= 1 */
|
|
||||||
--color-sidebar-border-200: 229, 229, 229; /* subtle sidebar border- 2 */
|
|
||||||
--color-sidebar-border-300: 212, 212, 212; /* strong sidebar border- 1 */
|
|
||||||
--color-sidebar-border-400: 185, 185, 185; /* strong sidebar border- 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] {
|
|
||||||
color-scheme: dark !important;
|
|
||||||
|
|
||||||
--color-background-100: 7, 7, 7; /* primary bg */
|
|
||||||
--color-background-90: 11, 11, 11; /* secondary bg */
|
|
||||||
--color-background-80: 23, 23, 23; /* tertiary bg */
|
|
||||||
|
|
||||||
--color-text-100: 241, 241, 241; /* primary text */
|
|
||||||
--color-text-200: 115, 115, 115; /* secondary text */
|
|
||||||
--color-text-300: 163, 163, 163; /* tertiary text */
|
|
||||||
--color-text-400: 82, 82, 82; /* placeholder text */
|
|
||||||
|
|
||||||
--color-border-100: 34, 34, 34; /* subtle border= 1 */
|
|
||||||
--color-border-200: 38, 38, 38; /* subtle border- 2 */
|
|
||||||
--color-border-300: 46, 46, 46; /* strong border- 1 */
|
|
||||||
--color-border-400: 58, 58, 58; /* strong border- 2 */
|
|
||||||
|
|
||||||
--color-sidebar-background-100: 7, 7, 7; /* primary sidebar bg */
|
|
||||||
--color-sidebar-background-90: 11, 11, 11; /* secondary sidebar bg */
|
|
||||||
--color-sidebar-background-80: 23, 23, 23; /* tertiary sidebar bg */
|
|
||||||
|
|
||||||
--color-sidebar-text-100: 241, 241, 241; /* primary sidebar text */
|
|
||||||
--color-sidebar-text-200: 115, 115, 115; /* secondary sidebar text */
|
|
||||||
--color-sidebar-text-300: 163, 163, 163; /* tertiary sidebar text */
|
|
||||||
--color-sidebar-text-400: 82, 82, 82; /* sidebar placeholder text */
|
|
||||||
|
|
||||||
--color-sidebar-border-100: 34, 34, 34; /* subtle sidebar border= 1 */
|
|
||||||
--color-sidebar-border-200: 38, 38, 38; /* subtle sidebar border- 2 */
|
|
||||||
--color-sidebar-border-300: 46, 46, 46; /* strong sidebar border- 1 */
|
|
||||||
--color-sidebar-border-400: 58, 58, 58; /* strong sidebar border- 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="light-contrast"] {
|
|
||||||
color-scheme: light !important;
|
|
||||||
|
|
||||||
--color-text-100: 11, 11, 11; /* primary text */
|
|
||||||
--color-text-200: 38, 38, 38; /* secondary text */
|
|
||||||
--color-text-300: 58, 58, 58; /* tertiary text */
|
|
||||||
--color-text-400: 115, 115, 115; /* placeholder text */
|
|
||||||
|
|
||||||
--color-border-100: 34, 34, 34; /* subtle border= 1 */
|
|
||||||
--color-border-200: 38, 38, 38; /* subtle border- 2 */
|
|
||||||
--color-border-300: 46, 46, 46; /* strong border- 1 */
|
|
||||||
--color-border-400: 58, 58, 58; /* strong border- 2 */
|
|
||||||
|
|
||||||
--color-sidebar-text-100: 11, 11, 11; /* primary sidebar text */
|
|
||||||
--color-sidebar-text-200: 38, 38, 38; /* secondary sidebar text */
|
|
||||||
--color-sidebar-text-300: 58, 58, 58; /* tertiary sidebar text */
|
|
||||||
--color-sidebar-text-400: 115, 115, 115; /* sidebar placeholder text */
|
|
||||||
|
|
||||||
--color-sidebar-border-100: 34, 34, 34; /* subtle sidebar border= 1 */
|
|
||||||
--color-sidebar-border-200: 38, 38, 38; /* subtle sidebar border- 2 */
|
|
||||||
--color-sidebar-border-300: 46, 46, 46; /* strong sidebar border- 1 */
|
|
||||||
--color-sidebar-border-400: 58, 58, 58; /* strong sidebar border- 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark-contrast"] {
|
|
||||||
color-scheme: dark !important;
|
|
||||||
|
|
||||||
--color-background-100: 7, 7, 7; /* primary bg */
|
|
||||||
--color-background-90: 11, 11, 11; /* secondary bg */
|
|
||||||
--color-background-80: 23, 23, 23; /* tertiary bg */
|
|
||||||
|
|
||||||
--color-text-100: 250, 250, 250; /* primary text */
|
|
||||||
--color-text-200: 241, 241, 241; /* secondary text */
|
|
||||||
--color-text-300: 212, 212, 212; /* tertiary text */
|
|
||||||
--color-text-400: 115, 115, 115; /* placeholder text */
|
|
||||||
|
|
||||||
--color-border-100: 245, 245, 245; /* subtle border= 1 */
|
|
||||||
--color-border-200: 229, 229, 229; /* subtle border- 2 */
|
|
||||||
--color-border-300: 212, 212, 212; /* strong border- 1 */
|
|
||||||
--color-border-400: 185, 185, 185; /* strong border- 2 */
|
|
||||||
|
|
||||||
--color-sidebar-background-100: 7, 7, 7; /* primary sidebar bg */
|
|
||||||
--color-sidebar-background-90: 11, 11, 11; /* secondary sidebar bg */
|
|
||||||
--color-sidebar-background-80: 23, 23, 23; /* tertiary sidebar bg */
|
|
||||||
|
|
||||||
--color-sidebar-text-100: 250, 250, 250; /* primary sidebar text */
|
|
||||||
--color-sidebar-text-200: 241, 241, 241; /* secondary sidebar text */
|
|
||||||
--color-sidebar-text-300: 212, 212, 212; /* tertiary sidebar text */
|
|
||||||
--color-sidebar-text-400: 115, 115, 115; /* sidebar placeholder text */
|
|
||||||
|
|
||||||
--color-sidebar-border-100: 245, 245, 245; /* subtle sidebar border= 1 */
|
|
||||||
--color-sidebar-border-200: 229, 229, 229; /* subtle sidebar border- 2 */
|
|
||||||
--color-sidebar-border-300: 212, 212, 212; /* strong sidebar border- 1 */
|
|
||||||
--color-sidebar-border-400: 185, 185, 185; /* strong sidebar border- 2 */
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1
apps/app/types/projects.d.ts
vendored
1
apps/app/types/projects.d.ts
vendored
@ -45,6 +45,7 @@ export interface IProject {
|
|||||||
network: number;
|
network: number;
|
||||||
page_view: boolean;
|
page_view: boolean;
|
||||||
project_lead: IUserLite | string | null;
|
project_lead: IUserLite | string | null;
|
||||||
|
sort_order: number;
|
||||||
slug: string;
|
slug: string;
|
||||||
total_cycles: number;
|
total_cycles: number;
|
||||||
total_members: number;
|
total_members: number;
|
||||||
|
4
apps/app/types/users.d.ts
vendored
4
apps/app/types/users.d.ts
vendored
@ -27,7 +27,7 @@ export interface IUser {
|
|||||||
properties: Properties;
|
properties: Properties;
|
||||||
groupBy: NestedKeyOf<IIssue> | null;
|
groupBy: NestedKeyOf<IIssue> | null;
|
||||||
} | null;
|
} | null;
|
||||||
onboarding_step: OnboardingSteps;
|
onboarding_step: TOnboardingSteps;
|
||||||
role: string;
|
role: string;
|
||||||
token: string;
|
token: string;
|
||||||
theme: ICustomTheme;
|
theme: ICustomTheme;
|
||||||
@ -140,7 +140,7 @@ export type UserAuth = {
|
|||||||
isGuest: boolean;
|
isGuest: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OnboardingSteps = {
|
export type TOnboardingSteps = {
|
||||||
profile_complete: boolean;
|
profile_complete: boolean;
|
||||||
workspace_create: boolean;
|
workspace_create: boolean;
|
||||||
workspace_invite: boolean;
|
workspace_invite: boolean;
|
||||||
|
Loading…
Reference in New Issue
Block a user