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 ( -
- -
- router.back()} isLoading={loader}> - - - - ) - ) : ( - - - - ) - } - /> - } - /> - - } />} - /> - - {issuesCount && issuesCount > 0 ? ( - 1 ? "issues" : "issue"} in this project`} - position="bottom" - > - - - ) : null} -
- {currentProjectDetails?.anchor ? ( - - - Public - - - ) : ( - <> - )} -
- -
- -
- {canUserCreateIssue ? ( - - ) : ( - <> - )} -
-
- ); -}); +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 ( +
+ +
+ router.back()} isLoading={loader}> + + + + ) + ) : ( + + + + ) + } + /> + } + /> + + } />} + /> + + {issuesCount && issuesCount > 0 ? ( + 1 ? "issues" : "issue"} in this project`} + position="bottom" + > + + + ) : null} +
+ {currentProjectDetails?.anchor ? ( + + + Public + + + ) : ( + <> + )} +
+ +
+ +
+ {canUserCreateIssue ? ( + + ) : ( + <> + )} +
+
+ ); +}); 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 = () => {
- GithubLogo + GitHubLogo
{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";