diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index cdb7e41ed53..97a2b2d4105 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -4,6 +4,7 @@ FROM python:3.12.5-alpine AS backend ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 ENV PIP_DISABLE_PIP_VERSION_CHECK=1 +ENV INSTANCE_CHANGELOG_URL https://api.plane.so/api/public/anchor/8e1c2e4c7bc5493eb7731be3862f6960/pages/ WORKDIR /code diff --git a/apiserver/Dockerfile.dev b/apiserver/Dockerfile.dev index 3cde896b97d..c81966de4f2 100644 --- a/apiserver/Dockerfile.dev +++ b/apiserver/Dockerfile.dev @@ -4,6 +4,7 @@ FROM python:3.12.5-alpine AS backend ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 ENV PIP_DISABLE_PIP_VERSION_CHECK=1 +ENV INSTANCE_CHANGELOG_URL https://api.plane.so/api/public/anchor/8e1c2e4c7bc5493eb7731be3862f6960/pages/ RUN apk --no-cache add \ "bash~=5.2" \ diff --git a/apiserver/plane/license/api/views/__init__.py b/apiserver/plane/license/api/views/__init__.py index b10702b8a15..ad1aaa58d96 100644 --- a/apiserver/plane/license/api/views/__init__.py +++ b/apiserver/plane/license/api/views/__init__.py @@ -18,3 +18,5 @@ InstanceAdminSignOutEndpoint, InstanceAdminUserSessionEndpoint, ) + +from .changelog import ChangeLogEndpoint diff --git a/apiserver/plane/license/api/views/changelog.py b/apiserver/plane/license/api/views/changelog.py new file mode 100644 index 00000000000..a81504e4780 --- /dev/null +++ b/apiserver/plane/license/api/views/changelog.py @@ -0,0 +1,35 @@ +# Python imports +import requests + +# Django imports +from django.conf import settings + +# Third party imports +from rest_framework.response import Response +from rest_framework import status +from rest_framework.permissions import AllowAny + +# plane imports +from .base import BaseAPIView + + +class ChangeLogEndpoint(BaseAPIView): + permission_classes = [ + AllowAny, + ] + + def fetch_change_logs(self): + response = requests.get(settings.INSTANCE_CHANGELOG_URL) + response.raise_for_status() + return response.json() + + def get(self, request): + # Fetch the changelog + if settings.INSTANCE_CHANGELOG_URL: + data = self.fetch_change_logs() + return Response(data, status=status.HTTP_200_OK) + else: + return Response( + {"error": "could not fetch changelog please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) diff --git a/apiserver/plane/license/api/views/instance.py b/apiserver/plane/license/api/views/instance.py index 9aac3fb181f..c61b55bb611 100644 --- a/apiserver/plane/license/api/views/instance.py +++ b/apiserver/plane/license/api/views/instance.py @@ -177,6 +177,8 @@ def get(self, request): data["space_base_url"] = settings.SPACE_BASE_URL data["app_base_url"] = settings.APP_BASE_URL + data["instance_changelog_url"] = settings.INSTANCE_CHANGELOG_URL + instance_data = serializer.data instance_data["workspaces_exist"] = Workspace.objects.count() >= 1 diff --git a/apiserver/plane/license/urls.py b/apiserver/plane/license/urls.py index b4f19e52c35..444d2a11926 100644 --- a/apiserver/plane/license/urls.py +++ b/apiserver/plane/license/urls.py @@ -11,6 +11,7 @@ InstanceAdminUserMeEndpoint, InstanceAdminSignOutEndpoint, InstanceAdminUserSessionEndpoint, + ChangeLogEndpoint ) urlpatterns = [ @@ -19,6 +20,11 @@ InstanceEndpoint.as_view(), name="instance", ), + path( + "changelog/", + ChangeLogEndpoint.as_view(), + name="instance-changelog", + ), path( "admins/", InstanceAdminEndpoint.as_view(), diff --git a/apiserver/plane/settings/common.py b/apiserver/plane/settings/common.py index 007d8cecff5..96885f6f0fb 100644 --- a/apiserver/plane/settings/common.py +++ b/apiserver/plane/settings/common.py @@ -359,3 +359,6 @@ APP_BASE_URL = os.environ.get("APP_BASE_URL") HARD_DELETE_AFTER_DAYS = int(os.environ.get("HARD_DELETE_AFTER_DAYS", 60)) + +# Instance Changelog URL +INSTANCE_CHANGELOG_URL = os.environ.get("INSTANCE_CHANGELOG_URL", "") diff --git a/web/app/profile/page.tsx b/web/app/profile/page.tsx index 428be82728c..ab3fe76e8c8 100644 --- a/web/app/profile/page.tsx +++ b/web/app/profile/page.tsx @@ -286,9 +286,8 @@ const ProfileSettingsPage = observer(() => { ref={ref} hasError={Boolean(errors.email)} placeholder="Enter your email" - className={`w-full cursor-not-allowed rounded-md !bg-custom-background-80 ${ - errors.email ? "border-red-500" : "" - }`} + className={`w-full cursor-not-allowed rounded-md !bg-custom-background-80 ${errors.email ? "border-red-500" : "" + }`} autoComplete="on" disabled /> @@ -436,8 +435,4 @@ const ProfileSettingsPage = observer(() => { ); }); -// ProfileSettingsPage.getLayout = function getLayout(page: ReactElement) { -// return {page}; -// }; - export default ProfileSettingsPage; diff --git a/web/ce/components/global/index.ts b/web/ce/components/global/index.ts index 2d8930e19e1..c87c8ae0273 100644 --- a/web/ce/components/global/index.ts +++ b/web/ce/components/global/index.ts @@ -1,3 +1,2 @@ export * from "./version-number"; -export * from "./product-updates"; -export * from "./product-updates-modal"; +export * from "./product-updates-header"; diff --git a/web/ce/components/global/product-updates-header.tsx b/web/ce/components/global/product-updates-header.tsx new file mode 100644 index 00000000000..a5965bb2d14 --- /dev/null +++ b/web/ce/components/global/product-updates-header.tsx @@ -0,0 +1,26 @@ +import { observer } from "mobx-react"; +import Image from "next/image"; +// helpers +import { cn } from "@/helpers/common.helper"; +// assets +import PlaneLogo from "@/public/plane-logos/blue-without-text.png"; +// package.json +import packageJson from "package.json"; + +export const ProductUpdatesHeader = observer(() => ( +
+
+
What's new
+
+ Version: v{packageJson.version} +
+
+
+ Plane +
+
+)); diff --git a/web/ce/components/global/product-updates-modal.tsx b/web/ce/components/global/product-updates-modal.tsx deleted file mode 100644 index da279583502..00000000000 --- a/web/ce/components/global/product-updates-modal.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { FC } from "react"; -import { observer } from "mobx-react"; - -export type ProductUpdatesModalProps = { - isOpen: boolean; - handleClose: () => void; -}; - -export const ProductUpdatesModal: FC = observer(() => <>); diff --git a/web/ce/components/global/product-updates.tsx b/web/ce/components/global/product-updates.tsx deleted file mode 100644 index 700883574c0..00000000000 --- a/web/ce/components/global/product-updates.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { FC } from "react"; -import { observer } from "mobx-react"; -import Link from "next/link"; -// ui -import { CustomMenu } from "@plane/ui"; - -export type ProductUpdatesProps = { - setIsChangeLogOpen: (isOpen: boolean) => void; -}; - -export const ProductUpdates: FC = observer(() => ( - - - What's new - - -)); diff --git a/web/core/components/common/index.ts b/web/core/components/common/index.ts index d6d34858403..28b73238862 100644 --- a/web/core/components/common/index.ts +++ b/web/core/components/common/index.ts @@ -1,4 +1,3 @@ -export * from "./product-updates-modal"; export * from "./empty-state"; export * from "./latest-feature-block"; export * from "./breadcrumb-link"; diff --git a/web/core/components/common/product-updates-modal.tsx b/web/core/components/common/product-updates-modal.tsx deleted file mode 100644 index 9e1112db1f4..00000000000 --- a/web/core/components/common/product-updates-modal.tsx +++ /dev/null @@ -1,111 +0,0 @@ -"use client"; -import React from "react"; -import useSWR from "swr"; -// headless ui -import { X } from "lucide-react"; -import { Dialog, Transition } from "@headlessui/react"; -// services -// components -import { Loader } from "@plane/ui"; -import { MarkdownRenderer } from "@/components/ui"; -// icons -// helpers -import { renderFormattedDate } from "@/helpers/date-time.helper"; -import { WorkspaceService } from "@/plane-web/services"; - -type Props = { - isOpen: boolean; - setIsOpen: React.Dispatch>; -}; - -// services -const workspaceService = new WorkspaceService(); - -export const ProductUpdatesModal: React.FC = ({ isOpen, setIsOpen }) => { - const { data: updates } = useSWR("PRODUCT_UPDATES", () => workspaceService.getProductUpdates()); - - return ( - - - -
- - -
-
- - -
- - Product Updates - - - - - {updates && updates.length > 0 ? ( -
- {updates.map((item, index) => ( - -
- - {item.tag_name} - - {renderFormattedDate(item.published_at)} - {index === 0 && ( - - New - - )} -
- -
- ))} -
- ) : ( -
- -
- - - -
-
- - - -
-
- - - -
-
-
- )} -
-
-
-
-
-
-
- ); -}; diff --git a/web/core/components/global/index.ts b/web/core/components/global/index.ts new file mode 100644 index 00000000000..1230b8384fc --- /dev/null +++ b/web/core/components/global/index.ts @@ -0,0 +1 @@ +export * from "./product-updates"; diff --git a/web/core/components/global/product-updates/footer.tsx b/web/core/components/global/product-updates/footer.tsx new file mode 100644 index 00000000000..6dd2638332b --- /dev/null +++ b/web/core/components/global/product-updates/footer.tsx @@ -0,0 +1,62 @@ +import Image from "next/image"; +// ui +import { getButtonStyling } from "@plane/ui"; +// helpers +import { cn } from "@/helpers/common.helper"; +// assets +import PlaneLogo from "@/public/plane-logos/blue-without-text.png"; + +export const ProductUpdatesFooter = () => ( +
+ + + Plane + Powered by Plane Pages + +
+); diff --git a/web/core/components/global/product-updates/index.ts b/web/core/components/global/product-updates/index.ts new file mode 100644 index 00000000000..6886b550381 --- /dev/null +++ b/web/core/components/global/product-updates/index.ts @@ -0,0 +1,2 @@ +export * from "./modal"; +export * from "./footer"; diff --git a/web/core/components/global/product-updates/modal.tsx b/web/core/components/global/product-updates/modal.tsx new file mode 100644 index 00000000000..7ccdd57c168 --- /dev/null +++ b/web/core/components/global/product-updates/modal.tsx @@ -0,0 +1,81 @@ +import { FC, useRef } from "react"; +import { observer } from "mobx-react-lite"; +import useSWR from "swr"; +// editor +import { DocumentReadOnlyEditorWithRef, EditorRefApi } from "@plane/editor"; +// ui +import { EModalPosition, EModalWidth, ModalCore } from "@plane/ui"; +// helpers +import { LogoSpinner } from "@/components/common"; +import { ProductUpdatesFooter } from "@/components/global"; +// plane web components +import { ProductUpdatesHeader } from "@/plane-web/components/global"; +// services +import { InstanceService } from "@/services/instance.service"; + +const instanceService = new InstanceService(); + +export type ProductUpdatesModalProps = { + isOpen: boolean; + handleClose: () => void; +}; + +export const ProductUpdatesModal: FC = observer((props) => { + const { isOpen, handleClose } = props; + // refs + const editorRef = useRef(null); + // swr + const { data, isLoading, error } = useSWR(`INSTANCE_CHANGELOG`, () => instanceService.getInstanceChangeLog(), { + shouldRetryOnError: false, + revalidateIfStale: false, + revalidateOnFocus: false, + }); + + return ( + + +
+ {!isLoading && !!error ? ( +
+
We are having trouble fetching the updates.
+
+ Please visit{" "} + + our changelogs + {" "} + for the latest updates. +
+
+ ) : isLoading ? ( +
+ +
+ ) : ( +
+ {data?.id && ( +

"} + containerClassName="p-0 border-none" + mentionHandler={{ + highlights: () => Promise.resolve([]), + }} + embedHandler={{ + issue: { + widgetCallback: () => <>, + }, + }} + /> + )} +
+ )} +
+ +
+ ); +}); diff --git a/web/core/components/workspace/sidebar/help-section.tsx b/web/core/components/workspace/sidebar/help-section.tsx index ed4ee2ccf80..5570b5727a0 100644 --- a/web/core/components/workspace/sidebar/help-section.tsx +++ b/web/core/components/workspace/sidebar/help-section.tsx @@ -5,14 +5,16 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { FileText, HelpCircle, MessagesSquare, MoveLeft, User } from "lucide-react"; // ui -import { CustomMenu, ToggleSwitch, Tooltip } from "@plane/ui"; +import { CustomMenu, Tooltip, ToggleSwitch } from "@plane/ui"; +// components +import { ProductUpdatesModal } from "@/components/global"; // helpers import { cn } from "@/helpers/common.helper"; // hooks import { useAppTheme, useCommandPalette, useInstance, useTransient, useUserSettings } from "@/hooks/store"; import { usePlatformOS } from "@/hooks/use-platform-os"; // plane web components -import { PlaneVersionNumber, ProductUpdates, ProductUpdatesModal } from "@/plane-web/components/global"; +import { PlaneVersionNumber } from "@/plane-web/components/global"; import { WorkspaceEditionBadge } from "@/plane-web/components/workspace"; import { ENABLE_LOCAL_DB_CACHE } from "@/plane-web/constants/issues"; @@ -135,7 +137,15 @@ export const SidebarHelpSection: React.FC = observer( Keyboard shortcuts - + + + = observer(