diff --git a/README.md b/README.md index 8141bf588..20e34b673 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@

-Discord +Discord online members -Discord +Commit activity per month

diff --git a/apiserver/plane/api/permissions/__init__.py b/apiserver/plane/api/permissions/__init__.py index 91b3aea35..8b15a9373 100644 --- a/apiserver/plane/api/permissions/__init__.py +++ b/apiserver/plane/api/permissions/__init__.py @@ -1,2 +1,2 @@ -from .workspace import WorkSpaceBasePermission, WorkSpaceAdminPermission, WorkspaceEntityPermission +from .workspace import WorkSpaceBasePermission, WorkSpaceAdminPermission, WorkspaceEntityPermission, WorkspaceViewerPermission from .project import ProjectBasePermission, ProjectEntityPermission, ProjectMemberPermission, ProjectLitePermission diff --git a/apiserver/plane/api/permissions/workspace.py b/apiserver/plane/api/permissions/workspace.py index 7fccc455e..d01b545ee 100644 --- a/apiserver/plane/api/permissions/workspace.py +++ b/apiserver/plane/api/permissions/workspace.py @@ -61,3 +61,13 @@ class WorkspaceEntityPermission(BasePermission): return WorkspaceMember.objects.filter( member=request.user, workspace__slug=view.workspace_slug ).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() diff --git a/apiserver/plane/api/serializers/project.py b/apiserver/plane/api/serializers/project.py index 641edb07c..fa97c5a6d 100644 --- a/apiserver/plane/api/serializers/project.py +++ b/apiserver/plane/api/serializers/project.py @@ -93,6 +93,7 @@ class ProjectDetailSerializer(BaseSerializer): total_cycles = serializers.IntegerField(read_only=True) total_modules = serializers.IntegerField(read_only=True) is_member = serializers.BooleanField(read_only=True) + sort_order = serializers.FloatField(read_only=True) class Meta: model = Project diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index d8da4f7dd..26064d331 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -5,7 +5,7 @@ from datetime import datetime # Django imports from django.core.exceptions import ValidationError 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.conf import settings @@ -120,9 +120,15 @@ class ProjectViewSet(BaseViewSet): project_id=OuterRef("pk"), 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 = ( self.get_queryset() .annotate(is_favorite=Exists(subquery)) + .annotate(sort_order=Subquery(sort_order_query)) .order_by("sort_order", "name") .annotate( total_members=ProjectMember.objects.filter( @@ -592,17 +598,26 @@ class AddMemberToProjectEndpoint(BaseAPIView): {"error": "Atleast one member is required"}, 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( member_id=member.get("member_id"), role=member.get("role", 10), project_id=project_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, ignore_conflicts=True, ) @@ -845,12 +860,14 @@ class ProjectUserViewsEndpoint(BaseAPIView): view_props = project_member.view_props default_props = project_member.default_props preferences = project_member.preferences + sort_order = project_member.sort_order project_member.view_props = request.data.get("view_props", view_props) project_member.default_props = request.data.get( "default_props", default_props ) project_member.preferences = request.data.get("preferences", preferences) + project_member.sort_order = request.data.get("sort_order", sort_order) project_member.save() diff --git a/apiserver/plane/api/views/workspace.py b/apiserver/plane/api/views/workspace.py index 3404bbf19..51db47c3d 100644 --- a/apiserver/plane/api/views/workspace.py +++ b/apiserver/plane/api/views/workspace.py @@ -73,12 +73,14 @@ from plane.db.models import ( IssueSubscriber, Project, Label, - State, + WorkspaceMember, + CycleIssue, ) from plane.api.permissions import ( WorkSpaceBasePermission, WorkSpaceAdminPermission, WorkspaceEntityPermission, + WorkspaceViewerPermission, ) from plane.bgtasks.workspace_invitation_task import workspace_invitation from plane.utils.issue_filters import issue_filters @@ -1140,6 +1142,19 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView): .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( { "state_distribution": state_distribution, @@ -1149,6 +1164,8 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView): "completed_issues": completed_issues_count, "pending_issues": pending_issues_count, "subscribed_issues": subscribed_issues_count, + "present_cycles": present_cycle, + "upcoming_cycles": upcoming_cycles, } ) except Exception as e: @@ -1194,64 +1211,64 @@ class WorkspaceUserActivityEndpoint(BaseAPIView): class WorkspaceUserProfileEndpoint(BaseAPIView): - permission_classes = [ - WorkspaceEntityPermission, - ] def get(self, request, slug, user_id): try: user_data = User.objects.get(pk=user_id) - projects = ( - Project.objects.filter( - workspace__slug=slug, - project_projectmember__member=request.user, - ) - .annotate( - created_issues=Count( - "project_issue", filter=Q(project_issue__created_by_id=user_id) + requesting_workspace_member = WorkspaceMember.objects.get(workspace__slug=slug, member=request.user) + projects = [] + if requesting_workspace_member.role >= 10: + projects = ( + Project.objects.filter( + workspace__slug=slug, + project_projectmember__member=request.user, + ) + .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( { @@ -1268,6 +1285,8 @@ class WorkspaceUserProfileEndpoint(BaseAPIView): }, status=status.HTTP_200_OK, ) + except WorkspaceMember.DoesNotExist: + return Response({"error": "Forbidden"}, status=status.HTTP_403_FORBIDDEN) except Exception as e: capture_exception(e) return Response( @@ -1278,7 +1297,7 @@ class WorkspaceUserProfileEndpoint(BaseAPIView): class WorkspaceUserProfileIssuesEndpoint(BaseAPIView): permission_classes = [ - WorkspaceEntityPermission, + WorkspaceViewerPermission, ] def get(self, request, slug, user_id): @@ -1317,7 +1336,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView): .annotate(count=Func(F("id"), function="Count")) .values("count") ) - ) + ).distinct() # Priority Ordering if order_by_param == "priority" or order_by_param == "-priority": @@ -1394,9 +1413,10 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView): status=status.HTTP_400_BAD_REQUEST, ) + class WorkspaceLabelsEndpoint(BaseAPIView): permission_classes = [ - WorkspaceEntityPermission, + WorkspaceViewerPermission, ] def get(self, request, slug): diff --git a/apiserver/plane/db/migrations/0039_auto_20230723_2203.py b/apiserver/plane/db/migrations/0039_auto_20230723_2203.py index 78b77521c..8f8700b5d 100644 --- a/apiserver/plane/db/migrations/0039_auto_20230723_2203.py +++ b/apiserver/plane/db/migrations/0039_auto_20230723_2203.py @@ -58,16 +58,16 @@ def update_workspace_member_props(apps, schema_editor): Model.objects.bulk_update(updated_workspace_member, ["view_props"], batch_size=100) -def update_project_sort_order(apps, schema_editor): - Model = apps.get_model("db", "Project") +def update_project_member_sort_order(apps, schema_editor): + Model = apps.get_model("db", "ProjectMember") - updated_projects = [] + updated_project_members = [] for obj in Model.objects.all(): 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): @@ -93,5 +93,5 @@ class Migration(migrations.Migration): name='sort_order', field=models.FloatField(default=65535), ), - migrations.RunPython(update_project_sort_order), + migrations.RunPython(update_project_member_sort_order), ] diff --git a/apiserver/plane/db/models/project.py b/apiserver/plane/db/models/project.py index 721b7b698..d700ab5e2 100644 --- a/apiserver/plane/db/models/project.py +++ b/apiserver/plane/db/models/project.py @@ -91,7 +91,6 @@ class Project(BaseModel): default_state = models.ForeignKey( "db.State", on_delete=models.SET_NULL, null=True, related_name="default_state" ) - sort_order = models.FloatField(default=65535) def __str__(self): """Return name of the project""" diff --git a/apps/app/components/breadcrumbs/index.tsx b/apps/app/components/breadcrumbs/index.tsx index 8a67c92b9..0e5cfb9c4 100644 --- a/apps/app/components/breadcrumbs/index.tsx +++ b/apps/app/components/breadcrumbs/index.tsx @@ -2,7 +2,6 @@ import * as React from "react"; import { useRouter } from "next/router"; import Link from "next/link"; // icons -import { ArrowLeftIcon } from "@heroicons/react/24/outline"; import { Icon } from "components/ui"; type BreadcrumbsProps = { @@ -14,7 +13,7 @@ const Breadcrumbs = ({ children }: BreadcrumbsProps) => { return ( <> -

+
= ({
diff --git a/apps/app/components/ui/dropdowns/custom-search-select.tsx b/apps/app/components/ui/dropdowns/custom-search-select.tsx index e40976d13..afbd27e4a 100644 --- a/apps/app/components/ui/dropdowns/custom-search-select.tsx +++ b/apps/app/components/ui/dropdowns/custom-search-select.tsx @@ -22,7 +22,7 @@ export type CustomSearchSelectProps = DropdownProps & { | { multiple?: false; value: any } // if multiple is false, value can be anything | { 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}`} {...props} > - {({ open }: any) => { + {({ open }: { open: boolean }) => { if (open && onOpen) onOpen(); return ( diff --git a/apps/app/components/workspace/send-workspace-invitation-modal.tsx b/apps/app/components/workspace/send-workspace-invitation-modal.tsx index a5ff08652..5bffa3264 100644 --- a/apps/app/components/workspace/send-workspace-invitation-modal.tsx +++ b/apps/app/components/workspace/send-workspace-invitation-modal.tsx @@ -1,5 +1,7 @@ import React, { useEffect } from "react"; +// swr +import { mutate } from "swr"; // react-hook-form import { Controller, useFieldArray, useForm } from "react-hook-form"; // headless @@ -13,9 +15,10 @@ import { CustomSelect, Input, PrimaryButton, SecondaryButton } from "components/ // icons import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline"; // types -import { ICurrentUserResponse, IWorkspace, IWorkspaceMemberInvitation } from "types"; +import { ICurrentUserResponse } from "types"; // constants import { ROLE } from "constants/workspace"; +import { WORKSPACE_INVITATIONS } from "constants/fetch-keys"; type Props = { isOpen: boolean; @@ -94,7 +97,10 @@ const SendWorkspaceInvitationModal: React.FC = ({ }); console.log(err); }) - .finally(() => reset(defaultValues)); + .finally(() => { + reset(defaultValues); + mutate(WORKSPACE_INVITATIONS); + }); }; const appendField = () => { diff --git a/apps/app/components/workspace/sidebar-dropdown.tsx b/apps/app/components/workspace/sidebar-dropdown.tsx index 19b537384..b8f68cf9a 100644 --- a/apps/app/components/workspace/sidebar-dropdown.tsx +++ b/apps/app/components/workspace/sidebar-dropdown.tsx @@ -25,7 +25,7 @@ import { truncateText } from "helpers/string.helper"; import { IWorkspace } from "types"; // Static Data -const userLinks = (workspaceSlug: string) => [ +const userLinks = (workspaceSlug: string, userId: string) => [ { name: "Workspace Settings", href: `/${workspaceSlug}/settings`, @@ -36,7 +36,7 @@ const userLinks = (workspaceSlug: string) => [ }, { name: "My Profile", - href: `/${workspaceSlug}/me/profile`, + href: `/${workspaceSlug}/profile/${userId}`, }, ]; @@ -119,7 +119,7 @@ export const WorkspaceSidebarDropdown = () => { {!sidebarCollapse && ( - +
@@ -215,7 +215,7 @@ export const WorkspaceSidebarDropdown = () => { )}
- {userLinks(workspaceSlug as string).map((link, index) => ( + {userLinks(workspaceSlug?.toString() ?? "", user?.id ?? "").map((link, index) => ( { const { @@ -33,6 +34,8 @@ const useProfileIssues = (workspaceSlug: string | undefined, userId: string | un const router = useRouter(); + const { memberRole } = useWorkspaceMyMembership(); + const params: any = { assignees: filters?.assignees ? filters?.assignees.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( - workspaceSlug && userId + workspaceSlug && userId && (memberRole.isOwner || memberRole.isMember || memberRole.isViewer) ? USER_PROFILE_ISSUES(workspaceSlug.toString(), userId.toString(), params) : null, - workspaceSlug && userId + workspaceSlug && userId && (memberRole.isOwner || memberRole.isMember || memberRole.isViewer) ? () => userService.getUserProfileIssues(workspaceSlug.toString(), userId.toString(), params) : null ); + console.log(memberRole); + const groupedIssues: | { [key: string]: IIssue[]; @@ -73,8 +78,6 @@ const useProfileIssues = (workspaceSlug: string | undefined, userId: string | un useEffect(() => { if (!userId || !filters) return; - console.log("Triggered"); - if ( router.pathname.includes("assigned") && (!filters.assignees || !filters.assignees.includes(userId)) diff --git a/apps/app/hooks/use-projects.tsx b/apps/app/hooks/use-projects.tsx index c537e3a62..3363ff278 100644 --- a/apps/app/hooks/use-projects.tsx +++ b/apps/app/hooks/use-projects.tsx @@ -11,13 +11,17 @@ import { IProject } from "types"; // 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 { workspaceSlug } = router.query; const { data: projects, mutate: mutateProjects } = useSWR( - workspaceSlug ? PROJECTS_LIST(workspaceSlug as string, { is_favorite: type ?? "all" }) : null, - workspaceSlug + workspaceSlug && fetchCondition + ? PROJECTS_LIST(workspaceSlug as string, { is_favorite: type ?? "all" }) + : null, + workspaceSlug && fetchCondition ? () => projectService.getProjects(workspaceSlug as string, { is_favorite: type ?? "all" }) : null ); diff --git a/apps/app/layouts/app-layout/app-header.tsx b/apps/app/layouts/app-layout/app-header.tsx index b8eff97a9..f63d65650 100644 --- a/apps/app/layouts/app-layout/app-header.tsx +++ b/apps/app/layouts/app-layout/app-header.tsx @@ -11,11 +11,11 @@ type Props = { const Header: React.FC = ({ breadcrumbs, left, right, setToggleSidebar, noHeader }) => (
-
+
{breadcrumbs} - {left} +
{left}
- {right} +
{right}
); diff --git a/apps/app/layouts/profile-layout.tsx b/apps/app/layouts/profile-layout.tsx new file mode 100644 index 000000000..46ac24d8e --- /dev/null +++ b/apps/app/layouts/profile-layout.tsx @@ -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) => ( + + + + } + > + + +); + +const ProfileLayout: React.FC = ({ children, className }) => { + const { memberRole } = useWorkspaceMyMembership(); + + return ( +
+ +
+ + {memberRole.isOwner || memberRole.isMember || memberRole.isViewer ? ( +
{children}
+ ) : ( +
+ You do not have the permission to access this page. +
+ )} +
+
+ ); +}; diff --git a/apps/app/pages/[workspaceSlug]/profile/[userId]/assigned.tsx b/apps/app/pages/[workspaceSlug]/profile/[userId]/assigned.tsx index 0f0c31829..a60116503 100644 --- a/apps/app/pages/[workspaceSlug]/profile/[userId]/assigned.tsx +++ b/apps/app/pages/[workspaceSlug]/profile/[userId]/assigned.tsx @@ -1,48 +1,19 @@ import React from "react"; -import { useRouter } from "next/router"; - // contexts import { ProfileIssuesContextProvider } from "contexts/profile-issues-context"; -// hooks -import useUser from "hooks/use-user"; -// layouts -import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; +import { ProfileAuthWrapper } from "layouts/profile-layout"; // components -import { ProfileIssuesView, ProfileNavbar, ProfileSidebar } from "components/profile"; -// ui -import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; +import { ProfileIssuesView } from "components/profile"; // types import type { NextPage } from "next"; -const ProfileAssignedIssues: NextPage = () => { - const router = useRouter(); - const { workspaceSlug } = router.query; - - const { user } = useUser(); - - return ( - - - - - - } - > -
-
- -
- -
-
- -
-
-
- ); -}; +const ProfileAssignedIssues: NextPage = () => ( + + + + + +); export default ProfileAssignedIssues; diff --git a/apps/app/pages/[workspaceSlug]/profile/[userId]/created.tsx b/apps/app/pages/[workspaceSlug]/profile/[userId]/created.tsx index 73113eeb2..1cac31e62 100644 --- a/apps/app/pages/[workspaceSlug]/profile/[userId]/created.tsx +++ b/apps/app/pages/[workspaceSlug]/profile/[userId]/created.tsx @@ -1,48 +1,20 @@ import React from "react"; -import { useRouter } from "next/router"; - // contexts import { ProfileIssuesContextProvider } from "contexts/profile-issues-context"; -// hooks -import useUser from "hooks/use-user"; // layouts -import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; +import { ProfileAuthWrapper } from "layouts/profile-layout"; // components -import { ProfileIssuesView, ProfileNavbar, ProfileSidebar } from "components/profile"; -// ui -import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; +import { ProfileIssuesView } from "components/profile"; // types import type { NextPage } from "next"; -const ProfileCreatedIssues: NextPage = () => { - const router = useRouter(); - const { workspaceSlug } = router.query; - - const { user } = useUser(); - - return ( - - - - - - } - > -
-
- -
- -
-
- -
-
-
- ); -}; +const ProfileCreatedIssues: NextPage = () => ( + + + + + +); export default ProfileCreatedIssues; diff --git a/apps/app/pages/[workspaceSlug]/profile/[userId]/index.tsx b/apps/app/pages/[workspaceSlug]/profile/[userId]/index.tsx index c29c78490..0cf54bb0d 100644 --- a/apps/app/pages/[workspaceSlug]/profile/[userId]/index.tsx +++ b/apps/app/pages/[workspaceSlug]/profile/[userId]/index.tsx @@ -1,34 +1,26 @@ import React from "react"; import { useRouter } from "next/router"; -import Link from "next/link"; import useSWR from "swr"; -// layouts -import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; // services import userService from "services/user.service"; +// layouts +import { ProfileAuthWrapper } from "layouts/profile-layout"; // components import { - ProfileNavbar, + ProfileActivity, ProfilePriorityDistribution, - ProfileSidebar, ProfileStateDistribution, ProfileStats, ProfileWorkload, } 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 import type { NextPage } from "next"; import { IUserStateDistribution, TStateGroups } from "types"; // 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"; const ProfileOverview: NextPage = () => { @@ -42,15 +34,6 @@ const ProfileOverview: NextPage = () => { : 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 group = userProfile?.state_distribution.find((g) => g.state_group === key); @@ -59,93 +42,20 @@ const ProfileOverview: NextPage = () => { }); return ( - - - - - } - > -
-
- - + +
+ + +
+ +
- +
- +
); }; diff --git a/apps/app/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx b/apps/app/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx index c04681990..3a1ca01ee 100644 --- a/apps/app/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx +++ b/apps/app/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx @@ -1,48 +1,20 @@ import React from "react"; -import { useRouter } from "next/router"; - // contexts import { ProfileIssuesContextProvider } from "contexts/profile-issues-context"; -// hooks -import useUser from "hooks/use-user"; // layouts -import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; +import { ProfileAuthWrapper } from "layouts/profile-layout"; // components -import { ProfileIssuesView, ProfileNavbar, ProfileSidebar } from "components/profile"; -// ui -import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; +import { ProfileIssuesView } from "components/profile"; // types import type { NextPage } from "next"; -const ProfileSubscribedIssues: NextPage = () => { - const router = useRouter(); - const { workspaceSlug } = router.query; - - const { user } = useUser(); - - return ( - - - - - - } - > -
-
- -
- -
-
- -
-
-
- ); -}; +const ProfileSubscribedIssues: NextPage = () => ( + + + + + +); export default ProfileSubscribedIssues; diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx index d66575282..a48207db3 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx @@ -23,6 +23,8 @@ import { IIssue } from "types"; import type { NextPage } from "next"; // fetch-keys import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const defaultValues = { name: "", @@ -146,13 +148,15 @@ const ArchivedIssueDetailsPage: NextPage = () => { breadcrumbs={ } diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/archived-issues/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/archived-issues/index.tsx index e944301d3..b2c0ab428 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/archived-issues/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/archived-issues/index.tsx @@ -4,6 +4,8 @@ import useSWR from "swr"; // services import projectService from "services/project.service"; +// hooks +import useIssuesView from "hooks/use-issues-view"; // layouts import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; // contexts @@ -21,8 +23,6 @@ import { XMarkIcon } from "@heroicons/react/24/outline"; import type { NextPage } from "next"; // fetch-keys import { PROJECT_DETAILS } from "constants/fetch-keys"; -import useIssuesView from "hooks/use-issues-view"; -import { useEffect } from "react"; const ProjectArchivedIssues: NextPage = () => { const router = useRouter(); @@ -44,7 +44,7 @@ const ProjectArchivedIssues: NextPage = () => { } diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx index 73d6b4f80..0da8f797c 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx @@ -109,8 +109,9 @@ const SingleCycle: React.FC = () => { breadcrumbs={ } @@ -122,7 +123,7 @@ const SingleCycle: React.FC = () => { {cycleDetails?.name && truncateText(cycleDetails.name, 40)} } - className="ml-1.5" + className="ml-1.5 flex-shrink-0" width="auto" > {cycles?.map((cycle) => ( @@ -137,7 +138,7 @@ const SingleCycle: React.FC = () => { } right={ -
+
setAnalyticsModal(true)} diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx index 744d636fa..0ea9e73e3 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx @@ -29,6 +29,8 @@ import emptyCycle from "public/empty-state/cycle.svg"; // types import { SelectCycleType } from "types"; import type { NextPage } from "next"; +// helper +import { truncateText } from "helpers/string.helper"; const tabsList = ["All", "Active", "Upcoming", "Completed", "Drafts"]; @@ -91,7 +93,7 @@ const ProjectCycles: NextPage = () => { breadcrumbs={ - + } right={ diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx index cad3c3830..7a82ac61f 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx @@ -31,7 +31,7 @@ const ProjectInbox: NextPage = () => { } diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index 0c70ff2e9..d5f7c8ec6 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -22,6 +22,8 @@ import { IIssue } from "types"; import type { NextPage } from "next"; // fetch-keys import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const defaultValues = { name: "", @@ -110,13 +112,15 @@ const IssueDetailsPage: NextPage = () => { breadcrumbs={ } diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx index 63d7ffb51..91da78757 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx @@ -54,7 +54,7 @@ const ProjectIssues: NextPage = () => { } diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx index 44f6d5d23..71c3bb655 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx @@ -112,8 +112,9 @@ const SingleModule: React.FC = () => { breadcrumbs={ } diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx index ba952be7a..b5b1e3806 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx @@ -29,6 +29,8 @@ import { IModule, SelectModuleType } from "types/modules"; import type { NextPage } from "next"; // fetch-keys import { MODULE_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const ProjectModules: NextPage = () => { const [selectedModule, setSelectedModule] = useState(); @@ -73,7 +75,7 @@ const ProjectModules: NextPage = () => { breadcrumbs={ - + } right={ diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index f15b9aa1d..b26be661e 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -43,7 +43,7 @@ import { import { ColorPalletteIcon, ClipboardIcon } from "components/icons"; // helpers 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"; // types import type { NextPage } from "next"; @@ -346,7 +346,7 @@ const SinglePage: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx index a07bcce26..f6f8d3d86 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx @@ -11,6 +11,7 @@ import { Tab } from "@headlessui/react"; import projectService from "services/project.service"; // hooks import useLocalStorage from "hooks/use-local-storage"; +import useUserAuth from "hooks/use-user-auth"; // icons import { PlusIcon } from "components/icons"; // layouts @@ -27,7 +28,8 @@ import { TPageViewProps } from "types"; import type { NextPage } from "next"; // 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( () => import("components/pages").then((a) => a.AllPagesList), @@ -107,7 +109,9 @@ const ProjectPages: NextPage = () => { breadcrumbs={ - + } right={ diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx index ca4ac770d..a65222af5 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx @@ -22,6 +22,8 @@ import type { NextPage } from "next"; import { IProject } from "types"; // constant import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const AutomationsSettings: NextPage = () => { const router = useRouter(); @@ -65,10 +67,11 @@ const AutomationsSettings: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/control.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/control.tsx index d0a102e05..9e8d437e8 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/control.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/control.tsx @@ -23,6 +23,8 @@ import { IProject, IUserLite, IWorkspace } from "types"; import type { NextPage } from "next"; // fetch-keys import { PROJECTS_LIST, PROJECT_DETAILS, PROJECT_MEMBERS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const defaultValues: Partial = { project_lead: null, @@ -103,10 +105,11 @@ const ControlSettings: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx index 39efc12c3..7c82f7dd4 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx @@ -29,6 +29,8 @@ import { IEstimate, IProject } from "types"; import type { NextPage } from "next"; // fetch-keys import { ESTIMATES_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const EstimatesSettings: NextPage = () => { const [estimateFormOpen, setEstimateFormOpen] = useState(false); @@ -115,10 +117,11 @@ const EstimatesSettings: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx index 517004dac..be0c2198a 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx @@ -25,6 +25,8 @@ import { IProject } from "types"; import type { NextPage } from "next"; // fetch-keys import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const featuresList = [ { @@ -139,10 +141,11 @@ const FeaturesSettings: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx index 390dc206b..7de91c823 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx @@ -29,6 +29,7 @@ import { import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; // helpers import { renderEmoji } from "helpers/emoji.helper"; +import { truncateText } from "helpers/string.helper"; // types import { IProject, IWorkspace } from "types"; import type { NextPage } from "next"; @@ -161,10 +162,11 @@ const GeneralSettings: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx index 7726df02a..0ede7d216 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx @@ -23,6 +23,8 @@ import { IProject } from "types"; import type { NextPage } from "next"; // fetch-keys import { PROJECT_DETAILS, WORKSPACE_INTEGRATIONS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const ProjectIntegrations: NextPage = () => { const router = useRouter(); @@ -48,10 +50,11 @@ const ProjectIntegrations: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx index 228c8ed44..d8c425ee5 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx @@ -32,6 +32,8 @@ import { IIssueLabels } from "types"; import type { NextPage } from "next"; // fetch-keys import { PROJECT_DETAILS, PROJECT_ISSUE_LABELS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const LabelsSettings: NextPage = () => { // create/edit label form @@ -103,10 +105,11 @@ const LabelsSettings: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx index 61e285542..d828b3912 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { useRouter } from "next/router"; +import Link from "next/link"; import useSWR from "swr"; @@ -28,6 +29,8 @@ import type { NextPage } from "next"; import { PROJECT_INVITATIONS, PROJECT_MEMBERS, WORKSPACE_DETAILS } from "constants/fetch-keys"; // constants import { ROLE } from "constants/workspace"; +// helper +import { truncateText } from "helpers/string.helper"; const MembersSettings: NextPage = () => { const [inviteModal, setInviteModal] = useState(false); @@ -93,10 +96,11 @@ const MembersSettings: NextPage = () => { breadcrumbs={ - + } > @@ -187,9 +191,17 @@ const MembersSettings: NextPage = () => { )}
diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx index 0b7b0fb72..73337c6bb 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx @@ -26,6 +26,7 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { PlusIcon } from "@heroicons/react/24/outline"; // helpers import { getStatesList, orderStateGroups } from "helpers/state.helper"; +import { truncateText } from "helpers/string.helper"; // types import type { NextPage } from "next"; // fetch-keys @@ -64,10 +65,11 @@ const StatesSettings: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/projects/index.tsx b/apps/app/pages/[workspaceSlug]/projects/index.tsx index e6bf7a28a..5f3fbcc07 100644 --- a/apps/app/pages/[workspaceSlug]/projects/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/index.tsx @@ -26,6 +26,8 @@ import emptyProject from "public/empty-state/project.svg"; import type { NextPage } from "next"; // fetch-keys import { PROJECT_MEMBERS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const ProjectsPage: NextPage = () => { // router @@ -44,7 +46,10 @@ const ProjectsPage: NextPage = () => { - + } right={ diff --git a/apps/app/pages/[workspaceSlug]/settings/billing.tsx b/apps/app/pages/[workspaceSlug]/settings/billing.tsx index 2367b94b4..6731cd33f 100644 --- a/apps/app/pages/[workspaceSlug]/settings/billing.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/billing.tsx @@ -16,6 +16,8 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import type { NextPage } from "next"; // fetch-keys import { WORKSPACE_DETAILS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const BillingSettings: NextPage = () => { const { @@ -32,10 +34,11 @@ const BillingSettings: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/settings/import-export.tsx b/apps/app/pages/[workspaceSlug]/settings/import-export.tsx index efd72af24..ae31aa9f8 100644 --- a/apps/app/pages/[workspaceSlug]/settings/import-export.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/import-export.tsx @@ -1,26 +1,43 @@ import { useRouter } from "next/router"; +import useSWR from "swr"; + +// services +import workspaceService from "services/workspace.service"; // layouts import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; import { SettingsHeader } from "components/workspace"; // components import IntegrationGuide from "components/integration/guide"; +import { IntegrationAndImportExportBanner } from "components/ui"; // ui import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; // types 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 router = useRouter(); const { workspaceSlug } = router.query; + const { data: activeWorkspace } = useSWR( + workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, + () => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null) + ); + return ( - - + + } > diff --git a/apps/app/pages/[workspaceSlug]/settings/index.tsx b/apps/app/pages/[workspaceSlug]/settings/index.tsx index 66284382a..af46e0884 100644 --- a/apps/app/pages/[workspaceSlug]/settings/index.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/index.tsx @@ -14,7 +14,6 @@ import useToast from "hooks/use-toast"; import useUserAuth from "hooks/use-user-auth"; // layouts import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; -import SettingsNavbar from "layouts/settings-navbar"; // components import { ImageUploadModal } from "components/core"; import { DeleteWorkspaceModal, SettingsHeader } from "components/workspace"; @@ -24,7 +23,7 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; // icons import { LinkIcon } from "@heroicons/react/24/outline"; // helpers -import { copyTextToClipboard } from "helpers/string.helper"; +import { copyTextToClipboard, truncateText } from "helpers/string.helper"; // types import type { IWorkspace } from "types"; import type { NextPage } from "next"; @@ -147,7 +146,9 @@ const WorkspaceSettings: NextPage = () => { - + } > diff --git a/apps/app/pages/[workspaceSlug]/settings/integrations.tsx b/apps/app/pages/[workspaceSlug]/settings/integrations.tsx index b94057bea..1b7a540c4 100644 --- a/apps/app/pages/[workspaceSlug]/settings/integrations.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/integrations.tsx @@ -19,6 +19,8 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import type { NextPage } from "next"; // fetch-keys import { WORKSPACE_DETAILS, APP_INTEGRATIONS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; const WorkspaceIntegrations: NextPage = () => { const router = useRouter(); @@ -38,10 +40,11 @@ const WorkspaceIntegrations: NextPage = () => { breadcrumbs={ - + } > diff --git a/apps/app/pages/[workspaceSlug]/settings/members.tsx b/apps/app/pages/[workspaceSlug]/settings/members.tsx index 35d7a03bd..c9c4a2eac 100644 --- a/apps/app/pages/[workspaceSlug]/settings/members.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/members.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; -import Image from "next/image"; +import Link from "next/link"; import { useRouter } from "next/router"; import useSWR from "swr"; @@ -27,6 +27,8 @@ import type { NextPage } from "next"; import { WORKSPACE_DETAILS, WORKSPACE_INVITATIONS, WORKSPACE_MEMBERS } from "constants/fetch-keys"; // constants import { ROLE } from "constants/workspace"; +// helper +import { truncateText } from "helpers/string.helper"; const MembersSettings: NextPage = () => { const [selectedRemoveMember, setSelectedRemoveMember] = useState(null); @@ -89,10 +91,11 @@ const MembersSettings: NextPage = () => { breadcrumbs={ - + } > @@ -187,9 +190,17 @@ const MembersSettings: NextPage = () => { )}
-

- {member.first_name} {member.last_name} -

+ {member.member ? ( + + + {member.first_name} {member.last_name} + + + ) : ( +

+ {member.first_name} {member.last_name} +

+ )}

{member.email}

diff --git a/apps/app/pages/onboarding.tsx b/apps/app/pages/onboarding.tsx index 57b121648..217584dd0 100644 --- a/apps/app/pages/onboarding.tsx +++ b/apps/app/pages/onboarding.tsx @@ -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 WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.svg"; // types -import { ICurrentUserResponse, IUser, OnboardingSteps } from "types"; +import { ICurrentUserResponse, IUser, TOnboardingSteps } from "types"; import type { NextPage } from "next"; // fetch-keys import { CURRENT_USER, USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys"; @@ -43,33 +43,35 @@ const Onboarding: NextPage = () => { workspaceService.userWorkspaceInvitations() ); + // update last active workspace details const updateLastWorkspace = async () => { - if (!userWorkspaces) return; + if (!workspaces) return; - mutate( + await mutate( CURRENT_USER, (prevData) => { if (!prevData) return prevData; return { ...prevData, - last_workspace_id: userWorkspaces[0]?.id, + last_workspace_id: workspaces[0]?.id, workspace: { ...prevData.workspace, - fallback_workspace_id: userWorkspaces[0]?.id, - fallback_workspace_slug: userWorkspaces[0]?.slug, - last_workspace_id: userWorkspaces[0]?.id, - last_workspace_slug: userWorkspaces[0]?.slug, + fallback_workspace_id: workspaces[0]?.id, + fallback_workspace_slug: workspaces[0]?.slug, + last_workspace_id: workspaces[0]?.id, + last_workspace_slug: workspaces[0]?.slug, }, }; }, false ); - await userService.updateUser({ last_workspace_id: userWorkspaces?.[0]?.id }); + await userService.updateUser({ last_workspace_id: workspaces?.[0]?.id }); }; - const stepChange = async (steps: Partial) => { + // handle step change + const stepChange = async (steps: Partial) => { if (!user) return; const payload: Partial = { @@ -95,16 +97,44 @@ const Onboarding: NextPage = () => { await userService.updateUser(payload); }; + // complete onboarding + const finishOnboarding = async () => { + if (!user) return; + + mutate( + CURRENT_USER, + (prevData) => { + if (!prevData) return prevData; + + return { + ...prevData, + is_onboarded: true, + }; + }, + false + ); + + await userService.updateUserOnBoard({ userRole: user.role }, user); + }; + useEffect(() => { const handleStepChange = async () => { - if (!user || !userWorkspaces || !invitations) return; + if (!user || !invitations) return; const onboardingStep = user.onboarding_step; if (!onboardingStep.profile_complete && step !== 1) setStep(1); - if (onboardingStep.profile_complete && !onboardingStep.workspace_create && step !== 2) - setStep(2); + if (onboardingStep.profile_complete) { + 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 ( onboardingStep.profile_complete && @@ -113,21 +143,10 @@ const Onboarding: NextPage = () => { step !== 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(); - }, [user, invitations, userWorkspaces, step]); + }, [user, invitations, step]); if (userLoading || step === null) return ( @@ -167,14 +186,27 @@ const Onboarding: NextPage = () => { ) : step === 2 ? ( ) : step === 3 ? ( - + ) : ( - step === 4 && + step === 4 && ( + + ) )}
{step !== 4 && ( diff --git a/apps/app/services/project.service.ts b/apps/app/services/project.service.ts index 03e1b9eed..d25bcd9a6 100644 --- a/apps/app/services/project.service.ts +++ b/apps/app/services/project.service.ts @@ -254,6 +254,7 @@ class ProjectServices extends APIService { view_props?: ProjectViewTheme; default_props?: ProjectViewTheme; preferences?: ProjectPreferences; + sort_order?: number; } ): Promise { await this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/project-views/`, data) diff --git a/apps/app/styles/globals.css b/apps/app/styles/globals.css index aba03a248..36d87edf2 100644 --- a/apps/app/styles/globals.css +++ b/apps/app/styles/globals.css @@ -23,6 +23,107 @@ } :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-20: 217, 228, 255; --color-primary-30: 197, 214, 255; @@ -42,122 +143,19 @@ --color-primary-800: 19, 35, 76; --color-primary-900: 13, 24, 51; - /* default theme- light */ - --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-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-text-100: 23, 23, 23; /* primary text */ - --color-text-200: 82, 82, 82; /* secondary text */ - --color-text-300: 115, 115, 115; /* tertiary text */ - --color-text-400: 163, 163, 163; /* placeholder text */ + --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-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: 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 */ + --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 */ } } diff --git a/apps/app/types/projects.d.ts b/apps/app/types/projects.d.ts index 0ac3b1d91..6ea201391 100644 --- a/apps/app/types/projects.d.ts +++ b/apps/app/types/projects.d.ts @@ -45,6 +45,7 @@ export interface IProject { network: number; page_view: boolean; project_lead: IUserLite | string | null; + sort_order: number; slug: string; total_cycles: number; total_members: number; diff --git a/apps/app/types/users.d.ts b/apps/app/types/users.d.ts index 3d72f3300..8731d29ad 100644 --- a/apps/app/types/users.d.ts +++ b/apps/app/types/users.d.ts @@ -27,7 +27,7 @@ export interface IUser { properties: Properties; groupBy: NestedKeyOf | null; } | null; - onboarding_step: OnboardingSteps; + onboarding_step: TOnboardingSteps; role: string; token: string; theme: ICustomTheme; @@ -140,7 +140,7 @@ export type UserAuth = { isGuest: boolean; }; -export type OnboardingSteps = { +export type TOnboardingSteps = { profile_complete: boolean; workspace_create: boolean; workspace_invite: boolean;