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}
+
+
+
+
+
+
+));
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 (
-
-
-
- );
-};
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 = () => (
+
+);
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.
+
+
+ ) : 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(