diff --git a/admin/app/authentication/github/page.tsx b/admin/app/authentication/github/page.tsx
index 7991fb95f7..03e5c97f22 100644
--- a/admin/app/authentication/github/page.tsx
+++ b/admin/app/authentication/github/page.tsx
@@ -44,7 +44,7 @@ const InstanceGithubAuthenticationPage = observer(() => {
loading: "Saving Configuration...",
success: {
title: "Configuration saved",
- message: () => `Github authentication is now ${value ? "active" : "disabled"}.`,
+ message: () => `GitHub authentication is now ${value ? "active" : "disabled"}.`,
},
error: {
title: "Error",
@@ -67,8 +67,8 @@ const InstanceGithubAuthenticationPage = observer(() => {
{
case "google":
return "Google";
case "github":
- return "Github";
+ return "GitHub";
case "gitlab":
return "GitLab";
case "workspace":
diff --git a/apiserver/plane/api/serializers/cycle.py b/apiserver/plane/api/serializers/cycle.py
index f4f06c324a..d394dc9bd6 100644
--- a/apiserver/plane/api/serializers/cycle.py
+++ b/apiserver/plane/api/serializers/cycle.py
@@ -4,7 +4,7 @@ from rest_framework import serializers
# Module imports
from .base import BaseSerializer
from plane.db.models import Cycle, CycleIssue
-
+from plane.utils.timezone_converter import convert_to_utc
class CycleSerializer(BaseSerializer):
total_issues = serializers.IntegerField(read_only=True)
@@ -24,6 +24,18 @@ class CycleSerializer(BaseSerializer):
and data.get("start_date", None) > data.get("end_date", None)
):
raise serializers.ValidationError("Start date cannot exceed end date")
+
+ if (
+ data.get("start_date", None) is not None
+ and data.get("end_date", None) is not None
+ ):
+ project_id = self.initial_data.get("project_id") or self.instance.project_id
+ data["start_date"] = convert_to_utc(
+ str(data.get("start_date").date()), project_id, is_start_date=True
+ )
+ data["end_date"] = convert_to_utc(
+ str(data.get("end_date", None).date()), project_id
+ )
return data
class Meta:
diff --git a/apiserver/plane/app/serializers/cycle.py b/apiserver/plane/app/serializers/cycle.py
index 9c54bee4e7..09349556a9 100644
--- a/apiserver/plane/app/serializers/cycle.py
+++ b/apiserver/plane/app/serializers/cycle.py
@@ -24,10 +24,10 @@ class CycleWriteSerializer(BaseSerializer):
):
project_id = self.initial_data.get("project_id") or self.instance.project_id
data["start_date"] = convert_to_utc(
- str(data.get("start_date").date()), project_id
+ str(data.get("start_date").date()), project_id, is_start_date=True
)
data["end_date"] = convert_to_utc(
- str(data.get("end_date", None).date()), project_id, is_end_date=True
+ str(data.get("end_date", None).date()), project_id
)
return data
diff --git a/apiserver/plane/app/views/cycle/base.py b/apiserver/plane/app/views/cycle/base.py
index 2a4391d64b..959ab10f66 100644
--- a/apiserver/plane/app/views/cycle/base.py
+++ b/apiserver/plane/app/views/cycle/base.py
@@ -267,7 +267,7 @@ class CycleViewSet(BaseViewSet):
"created_by",
)
datetime_fields = ["start_date", "end_date"]
- data = user_timezone_converter(data, datetime_fields, project_timezone)
+ data = user_timezone_converter(data, datetime_fields, request.user.user_timezone)
return Response(data, status=status.HTTP_200_OK)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
@@ -412,7 +412,6 @@ class CycleViewSet(BaseViewSet):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
def retrieve(self, request, slug, project_id, pk):
- project = Project.objects.get(id=project_id)
queryset = self.get_queryset().filter(archived_at__isnull=True).filter(pk=pk)
data = (
self.get_queryset()
@@ -466,7 +465,7 @@ class CycleViewSet(BaseViewSet):
queryset = queryset.first()
datetime_fields = ["start_date", "end_date"]
- data = user_timezone_converter(data, datetime_fields, project.timezone)
+ data = user_timezone_converter(data, datetime_fields, request.user.user_timezone)
recent_visited_task.delay(
slug=slug,
@@ -542,8 +541,8 @@ class CycleDateCheckEndpoint(BaseAPIView):
status=status.HTTP_400_BAD_REQUEST,
)
- start_date = convert_to_utc(str(start_date), project_id)
- end_date = convert_to_utc(str(end_date), project_id, is_end_date=True)
+ start_date = convert_to_utc(str(start_date), project_id, is_start_date=True)
+ end_date = convert_to_utc(str(end_date), project_id)
# Check if any cycle intersects in the given interval
cycles = Cycle.objects.filter(
diff --git a/apiserver/plane/app/views/workspace/cycle.py b/apiserver/plane/app/views/workspace/cycle.py
index ec08f47c95..a9398a91db 100644
--- a/apiserver/plane/app/views/workspace/cycle.py
+++ b/apiserver/plane/app/views/workspace/cycle.py
@@ -10,7 +10,7 @@ from plane.app.views.base import BaseAPIView
from plane.db.models import Cycle
from plane.app.permissions import WorkspaceViewerPermission
from plane.app.serializers.cycle import CycleSerializer
-
+from plane.utils.timezone_converter import user_timezone_converter
class WorkspaceCyclesEndpoint(BaseAPIView):
permission_classes = [WorkspaceViewerPermission]
diff --git a/apiserver/plane/utils/timezone_converter.py b/apiserver/plane/utils/timezone_converter.py
index dc8e20b8c7..46a864b62d 100644
--- a/apiserver/plane/utils/timezone_converter.py
+++ b/apiserver/plane/utils/timezone_converter.py
@@ -28,7 +28,7 @@ def user_timezone_converter(queryset, datetime_fields, user_timezone):
return queryset_values
-def convert_to_utc(date, project_id, is_end_date=False):
+def convert_to_utc(date, project_id, is_start_date=False):
"""
Converts a start date string to the project's local timezone at 12:00 AM
and then converts it to UTC for storage.
@@ -58,9 +58,9 @@ def convert_to_utc(date, project_id, is_end_date=False):
# Localize the datetime to the project's timezone
localized_datetime = local_tz.localize(local_datetime)
- # If it's an end date, subtract one minute
- if is_end_date:
- localized_datetime -= timedelta(minutes=1)
+ # If it's an start date, add one minute
+ if is_start_date:
+ localized_datetime += timedelta(minutes=1)
# Convert the localized datetime to UTC
utc_datetime = localized_datetime.astimezone(pytz.utc)
diff --git a/app.json b/app.json
index bc5789078e..600b524d2f 100644
--- a/app.json
+++ b/app.json
@@ -70,7 +70,7 @@
"value": ""
},
"GITHUB_CLIENT_SECRET": {
- "description": "Github Client Secret",
+ "description": "GitHub Client Secret",
"value": ""
},
"NEXT_PUBLIC_API_BASE_URL": {
diff --git a/packages/types/src/project/index.ts b/packages/types/src/project/index.ts
index ef7308bf7d..f5478051eb 100644
--- a/packages/types/src/project/index.ts
+++ b/packages/types/src/project/index.ts
@@ -1,2 +1,3 @@
export * from "./project_filters";
export * from "./projects";
+export * from "./project_link";
diff --git a/packages/types/src/project/project_link.d.ts b/packages/types/src/project/project_link.d.ts
new file mode 100644
index 0000000000..45b9dfc6ac
--- /dev/null
+++ b/packages/types/src/project/project_link.d.ts
@@ -0,0 +1,22 @@
+export type TProjectLinkEditableFields = {
+ title: string;
+ url: string;
+};
+
+export type TProjectLink = TProjectLinkEditableFields & {
+ created_by_id: string;
+ id: string;
+ metadata: any;
+ project_id: string;
+
+ //need
+ created_at: Date;
+};
+
+export type TProjectLinkMap = {
+ [project_id: string]: TProjectLink;
+};
+
+export type TProjectLinkIdMap = {
+ [project_id: string]: string[];
+};
diff --git a/packages/ui/src/icons/activity-icon.tsx b/packages/ui/src/icons/activity-icon.tsx
new file mode 100644
index 0000000000..2ac4828360
--- /dev/null
+++ b/packages/ui/src/icons/activity-icon.tsx
@@ -0,0 +1,22 @@
+import * as React from "react";
+
+import { ISvgIcons } from "./type";
+
+export const ActivityIcon: React.FC = ({ className = "text-current", ...rest }) => (
+
+);
diff --git a/packages/ui/src/icons/at-risk-icon.tsx b/packages/ui/src/icons/at-risk-icon.tsx
new file mode 100644
index 0000000000..bb4437e6d9
--- /dev/null
+++ b/packages/ui/src/icons/at-risk-icon.tsx
@@ -0,0 +1,30 @@
+import * as React from "react";
+
+import { ISvgIcons } from "./type";
+
+export const AtRiskIcon: React.FC = ({ width = "16", height = "16" }) => (
+
+);
diff --git a/packages/ui/src/icons/index.ts b/packages/ui/src/icons/index.ts
index cc7295fd47..1903e7ccf9 100644
--- a/packages/ui/src/icons/index.ts
+++ b/packages/ui/src/icons/index.ts
@@ -44,5 +44,11 @@ export * from "./pi-chat";
export * from "./workspace-icon";
export * from "./teams";
export * from "./lead-icon";
+export * from "./activity-icon";
+export * from "./updates-icon";
+export * from "./overview-icon";
+export * from "./on-track-icon";
+export * from "./off-track-icon";
+export * from "./at-risk-icon";
export * from "./plane-ai-icon";
export * from "./epic-icon";
diff --git a/packages/ui/src/icons/off-track-icon.tsx b/packages/ui/src/icons/off-track-icon.tsx
new file mode 100644
index 0000000000..0d93d1b605
--- /dev/null
+++ b/packages/ui/src/icons/off-track-icon.tsx
@@ -0,0 +1,30 @@
+import * as React from "react";
+
+import { ISvgIcons } from "./type";
+
+export const OffTrackIcon: React.FC = ({ width = "16", height = "16" }) => (
+
+);
diff --git a/packages/ui/src/icons/on-track-icon.tsx b/packages/ui/src/icons/on-track-icon.tsx
new file mode 100644
index 0000000000..c384d4c8d7
--- /dev/null
+++ b/packages/ui/src/icons/on-track-icon.tsx
@@ -0,0 +1,48 @@
+import * as React from "react";
+
+import { ISvgIcons } from "./type";
+
+export const OnTrackIcon: React.FC = ({ width = "16", height = "16" }) => (
+
+);
diff --git a/packages/ui/src/icons/overview-icon.tsx b/packages/ui/src/icons/overview-icon.tsx
new file mode 100644
index 0000000000..81b07224b6
--- /dev/null
+++ b/packages/ui/src/icons/overview-icon.tsx
@@ -0,0 +1,21 @@
+import * as React from "react";
+
+import { ISvgIcons } from "./type";
+
+export const OverviewIcon: React.FC = ({ width = "16", height = "16", className = "" }) => (
+
+);
diff --git a/packages/ui/src/icons/updates-icon.tsx b/packages/ui/src/icons/updates-icon.tsx
new file mode 100644
index 0000000000..978eb5f057
--- /dev/null
+++ b/packages/ui/src/icons/updates-icon.tsx
@@ -0,0 +1,18 @@
+import * as React from "react";
+
+import { ISvgIcons } from "./type";
+
+export const UpdatesIcon: React.FC = ({ className = "text-current", ...rest }) => (
+
+);
diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/header.tsx
index 1b1cffcc64..c3aacaebb0 100644
--- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/header.tsx
+++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/header.tsx
@@ -1,130 +1,3 @@
-"use client";
+import { IssuesHeader } from "@/plane-web/components/issues";
-import { observer } from "mobx-react";
-import { useParams } from "next/navigation";
-// icons
-import { Briefcase, Circle, ExternalLink } from "lucide-react";
-// ui
-import { Breadcrumbs, Button, LayersIcon, Tooltip, Header } from "@plane/ui";
-// components
-import { BreadcrumbLink, CountChip, Logo } from "@/components/common";
-// constants
-import HeaderFilters from "@/components/issues/filters";
-import { EIssuesStoreType } from "@/constants/issue";
-// helpers
-import { SPACE_BASE_PATH, SPACE_BASE_URL } from "@/helpers/common.helper";
-// hooks
-import { useEventTracker, useProject, useCommandPalette, useUserPermissions } from "@/hooks/store";
-import { useIssues } from "@/hooks/store/use-issues";
-import { useAppRouter } from "@/hooks/use-app-router";
-import { usePlatformOS } from "@/hooks/use-platform-os";
-import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
-
-export const ProjectIssuesHeader = observer(() => {
- // router
- const router = useAppRouter();
- const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string };
- // store hooks
- const {
- issues: { getGroupIssueCount },
- } = useIssues(EIssuesStoreType.PROJECT);
-
- const { currentProjectDetails, loader } = useProject();
-
- const { toggleCreateIssueModal } = useCommandPalette();
- const { setTrackElement } = useEventTracker();
- const { allowPermissions } = useUserPermissions();
- const { isMobile } = usePlatformOS();
-
- const SPACE_APP_URL = (SPACE_BASE_URL.trim() === "" ? window.location.origin : SPACE_BASE_URL) + SPACE_BASE_PATH;
- const publishedURL = `${SPACE_APP_URL}/issues/${currentProjectDetails?.anchor}`;
-
- const issuesCount = getGroupIssueCount(undefined, undefined, false);
- const canUserCreateIssue = allowPermissions(
- [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
- EUserPermissionsLevel.PROJECT
- );
-
- return (
-
- );
-});
+export const ProjectIssuesHeader = () => ;
diff --git a/web/ce/components/issues/header.tsx b/web/ce/components/issues/header.tsx
new file mode 100644
index 0000000000..a4c750061d
--- /dev/null
+++ b/web/ce/components/issues/header.tsx
@@ -0,0 +1,130 @@
+"use client";
+
+import { observer } from "mobx-react";
+import { useParams } from "next/navigation";
+// icons
+import { Briefcase, Circle, ExternalLink } from "lucide-react";
+// ui
+import { Breadcrumbs, Button, LayersIcon, Tooltip, Header } from "@plane/ui";
+// components
+import { BreadcrumbLink, CountChip, Logo } from "@/components/common";
+// constants
+import HeaderFilters from "@/components/issues/filters";
+import { EIssuesStoreType } from "@/constants/issue";
+// helpers
+import { SPACE_BASE_PATH, SPACE_BASE_URL } from "@/helpers/common.helper";
+// hooks
+import { useEventTracker, useProject, useCommandPalette, useUserPermissions } from "@/hooks/store";
+import { useIssues } from "@/hooks/store/use-issues";
+import { useAppRouter } from "@/hooks/use-app-router";
+import { usePlatformOS } from "@/hooks/use-platform-os";
+import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
+
+export const IssuesHeader = observer(() => {
+ // router
+ const router = useAppRouter();
+ const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string };
+ // store hooks
+ const {
+ issues: { getGroupIssueCount },
+ } = useIssues(EIssuesStoreType.PROJECT);
+
+ const { currentProjectDetails, loader } = useProject();
+
+ const { toggleCreateIssueModal } = useCommandPalette();
+ const { setTrackElement } = useEventTracker();
+ const { allowPermissions } = useUserPermissions();
+ const { isMobile } = usePlatformOS();
+
+ const SPACE_APP_URL = (SPACE_BASE_URL.trim() === "" ? window.location.origin : SPACE_BASE_URL) + SPACE_BASE_PATH;
+ const publishedURL = `${SPACE_APP_URL}/issues/${currentProjectDetails?.anchor}`;
+
+ const issuesCount = getGroupIssueCount(undefined, undefined, false);
+ const canUserCreateIssue = allowPermissions(
+ [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
+ EUserPermissionsLevel.PROJECT
+ );
+
+ return (
+
+ );
+});
diff --git a/web/ce/components/issues/index.ts b/web/ce/components/issues/index.ts
index 97b57af4b0..01fc1d9acf 100644
--- a/web/ce/components/issues/index.ts
+++ b/web/ce/components/issues/index.ts
@@ -4,3 +4,4 @@ export * from "./issue-modal";
export * from "./issue-details";
export * from "./quick-add";
export * from "./filters";
+export * from "./header";
diff --git a/web/core/components/dropdowns/member/avatar.tsx b/web/core/components/dropdowns/member/avatar.tsx
index 0a7a92d438..9907de3599 100644
--- a/web/core/components/dropdowns/member/avatar.tsx
+++ b/web/core/components/dropdowns/member/avatar.tsx
@@ -3,6 +3,7 @@
import { observer } from "mobx-react";
import { LucideIcon, Users } from "lucide-react";
// plane ui
+import { cn } from "@plane/editor";
import { Avatar, AvatarGroup } from "@plane/ui";
// helpers
import { getFileURL } from "@/helpers/file.helper";
@@ -13,17 +14,18 @@ type AvatarProps = {
showTooltip: boolean;
userIds: string | string[] | null;
icon?: LucideIcon;
+ size?: "sm" | "md" | "base" | "lg" | number;
};
export const ButtonAvatars: React.FC = observer((props) => {
- const { showTooltip, userIds, icon: Icon } = props;
+ const { showTooltip, userIds, icon: Icon, size = "md" } = props;
// store hooks
const { getUserDetails } = useMember();
if (Array.isArray(userIds)) {
if (userIds.length > 0)
return (
-
+
{userIds.map((userId) => {
const userDetails = getUserDetails(userId);
@@ -39,12 +41,12 @@ export const ButtonAvatars: React.FC = observer((props) => {
);
}
}
- return Icon ? : ;
+ return Icon ? : ;
});
diff --git a/web/core/components/integration/github/root.tsx b/web/core/components/integration/github/root.tsx
index 8fa528d8f6..c1160ba58d 100644
--- a/web/core/components/integration/github/root.tsx
+++ b/web/core/components/integration/github/root.tsx
@@ -168,7 +168,7 @@ export const GithubImporterRoot: React.FC = () => {
-
+
{integrationWorkflowData.map((integration, index) => (
diff --git a/web/core/constants/event-tracker.ts b/web/core/constants/event-tracker.ts
index c0ac86935c..cd2e1c3bec 100644
--- a/web/core/constants/event-tracker.ts
+++ b/web/core/constants/event-tracker.ts
@@ -8,6 +8,8 @@ export type IssueEventProps = {
export type EventProps = {
eventName: string;
payload: any;
+ updates?: any;
+ path?: string;
};
export const getWorkspaceEventPayload = (payload: any) => ({
@@ -206,7 +208,7 @@ export const PRODUCT_TOUR_COMPLETED = "Product tour completed";
export const PRODUCT_TOUR_SKIPPED = "Product tour skipped";
// Dashboard Events
export const CHANGELOG_REDIRECTED = "Changelog redirected";
-export const GITHUB_REDIRECTED = "Github redirected";
+export const GITHUB_REDIRECTED = "GitHub redirected";
// Sidebar Events
export const SIDEBAR_CLICKED = "Sidenav clicked";
// Global View Events
diff --git a/web/ee/components/issues/header.tsx b/web/ee/components/issues/header.tsx
new file mode 100644
index 0000000000..8049127f42
--- /dev/null
+++ b/web/ee/components/issues/header.tsx
@@ -0,0 +1 @@
+export * from "ce/components/issues/header";
diff --git a/web/ee/components/issues/index.ts b/web/ee/components/issues/index.ts
index f83b98d068..e4587ce004 100644
--- a/web/ee/components/issues/index.ts
+++ b/web/ee/components/issues/index.ts
@@ -4,4 +4,5 @@ export * from "./issue-modal";
export * from "./issue-details";
export * from "./quick-add";
export * from "./filters";
+export * from "./header";
export * from "./issue-layouts";