[WEB-2500] feat: Product updates modal (What's new in Plane) (#5690)
* [WEB-2500] feat: Product updates modal (What's new in Plane) * fix: build errors. * fix: lint errors resolved. * chore: minor improvements. * chore: minor fixes
This commit is contained in:
parent
c423d7d9df
commit
4bc751b7ab
19 changed files with 250 additions and 151 deletions
|
|
@ -4,6 +4,7 @@ FROM python:3.12.5-alpine AS backend
|
||||||
ENV PYTHONDONTWRITEBYTECODE 1
|
ENV PYTHONDONTWRITEBYTECODE 1
|
||||||
ENV PYTHONUNBUFFERED 1
|
ENV PYTHONUNBUFFERED 1
|
||||||
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
|
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
|
||||||
|
ENV INSTANCE_CHANGELOG_URL https://api.plane.so/api/public/anchor/8e1c2e4c7bc5493eb7731be3862f6960/pages/
|
||||||
|
|
||||||
WORKDIR /code
|
WORKDIR /code
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ FROM python:3.12.5-alpine AS backend
|
||||||
ENV PYTHONDONTWRITEBYTECODE 1
|
ENV PYTHONDONTWRITEBYTECODE 1
|
||||||
ENV PYTHONUNBUFFERED 1
|
ENV PYTHONUNBUFFERED 1
|
||||||
ENV PIP_DISABLE_PIP_VERSION_CHECK=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 \
|
RUN apk --no-cache add \
|
||||||
"bash~=5.2" \
|
"bash~=5.2" \
|
||||||
|
|
|
||||||
|
|
@ -18,3 +18,5 @@ from .admin import (
|
||||||
InstanceAdminSignOutEndpoint,
|
InstanceAdminSignOutEndpoint,
|
||||||
InstanceAdminUserSessionEndpoint,
|
InstanceAdminUserSessionEndpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from .changelog import ChangeLogEndpoint
|
||||||
|
|
|
||||||
35
apiserver/plane/license/api/views/changelog.py
Normal file
35
apiserver/plane/license/api/views/changelog.py
Normal file
|
|
@ -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,
|
||||||
|
)
|
||||||
|
|
@ -177,6 +177,8 @@ class InstanceEndpoint(BaseAPIView):
|
||||||
data["space_base_url"] = settings.SPACE_BASE_URL
|
data["space_base_url"] = settings.SPACE_BASE_URL
|
||||||
data["app_base_url"] = settings.APP_BASE_URL
|
data["app_base_url"] = settings.APP_BASE_URL
|
||||||
|
|
||||||
|
data["instance_changelog_url"] = settings.INSTANCE_CHANGELOG_URL
|
||||||
|
|
||||||
instance_data = serializer.data
|
instance_data = serializer.data
|
||||||
instance_data["workspaces_exist"] = Workspace.objects.count() >= 1
|
instance_data["workspaces_exist"] = Workspace.objects.count() >= 1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ from plane.license.api.views import (
|
||||||
InstanceAdminUserMeEndpoint,
|
InstanceAdminUserMeEndpoint,
|
||||||
InstanceAdminSignOutEndpoint,
|
InstanceAdminSignOutEndpoint,
|
||||||
InstanceAdminUserSessionEndpoint,
|
InstanceAdminUserSessionEndpoint,
|
||||||
|
ChangeLogEndpoint
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|
@ -19,6 +20,11 @@ urlpatterns = [
|
||||||
InstanceEndpoint.as_view(),
|
InstanceEndpoint.as_view(),
|
||||||
name="instance",
|
name="instance",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"changelog/",
|
||||||
|
ChangeLogEndpoint.as_view(),
|
||||||
|
name="instance-changelog",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"admins/",
|
"admins/",
|
||||||
InstanceAdminEndpoint.as_view(),
|
InstanceAdminEndpoint.as_view(),
|
||||||
|
|
|
||||||
|
|
@ -384,6 +384,9 @@ APP_BASE_URL = os.environ.get("APP_BASE_URL")
|
||||||
|
|
||||||
HARD_DELETE_AFTER_DAYS = int(os.environ.get("HARD_DELETE_AFTER_DAYS", 60))
|
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", "")
|
||||||
|
|
||||||
ATTACHMENT_MIME_TYPES = [
|
ATTACHMENT_MIME_TYPES = [
|
||||||
# Images
|
# Images
|
||||||
"image/jpeg",
|
"image/jpeg",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,2 @@
|
||||||
export * from "./version-number";
|
export * from "./version-number";
|
||||||
export * from "./product-updates";
|
export * from "./product-updates-header";
|
||||||
export * from "./product-updates-modal";
|
|
||||||
|
|
|
||||||
26
web/ce/components/global/product-updates-header.tsx
Normal file
26
web/ce/components/global/product-updates-header.tsx
Normal file
|
|
@ -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(() => (
|
||||||
|
<div className="flex gap-2 mx-6 my-4 items-center justify-between flex-shrink-0">
|
||||||
|
<div className="flex w-full items-center">
|
||||||
|
<div className="flex gap-2 text-xl font-medium">What's new</div>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"px-2 mx-2 py-0.5 text-center text-xs font-medium rounded-full bg-custom-primary-100/20 text-custom-primary-100"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Version: v{packageJson.version}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-shrink-0 items-center gap-8">
|
||||||
|
<Image src={PlaneLogo} alt="Plane" width={24} height={24} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
import { FC } from "react";
|
|
||||||
import { observer } from "mobx-react";
|
|
||||||
|
|
||||||
export type ProductUpdatesModalProps = {
|
|
||||||
isOpen: boolean;
|
|
||||||
handleClose: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ProductUpdatesModal: FC<ProductUpdatesModalProps> = observer(() => <></>);
|
|
||||||
|
|
@ -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<ProductUpdatesProps> = observer(() => (
|
|
||||||
<CustomMenu.MenuItem>
|
|
||||||
<Link
|
|
||||||
href="https://go.plane.so/p-changelog"
|
|
||||||
target="_blank"
|
|
||||||
className="flex w-full items-center justify-start text-xs hover:bg-custom-background-80"
|
|
||||||
>
|
|
||||||
<span className="text-xs">What's new</span>
|
|
||||||
</Link>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
));
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
export * from "./product-updates-modal";
|
|
||||||
export * from "./empty-state";
|
export * from "./empty-state";
|
||||||
export * from "./latest-feature-block";
|
export * from "./latest-feature-block";
|
||||||
export * from "./breadcrumb-link";
|
export * from "./breadcrumb-link";
|
||||||
|
|
|
||||||
|
|
@ -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<React.SetStateAction<boolean>>;
|
|
||||||
};
|
|
||||||
|
|
||||||
// services
|
|
||||||
const workspaceService = new WorkspaceService();
|
|
||||||
|
|
||||||
export const ProductUpdatesModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
|
|
||||||
const { data: updates } = useSWR("PRODUCT_UPDATES", () => workspaceService.getProductUpdates());
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Transition.Root show={isOpen} as={React.Fragment}>
|
|
||||||
<Dialog as="div" className="relative z-20" onClose={setIsOpen}>
|
|
||||||
<Transition.Child
|
|
||||||
as={React.Fragment}
|
|
||||||
enter="ease-out duration-300"
|
|
||||||
enterFrom="opacity-0"
|
|
||||||
enterTo="opacity-100"
|
|
||||||
leave="ease-in duration-200"
|
|
||||||
leaveFrom="opacity-100"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
|
|
||||||
</Transition.Child>
|
|
||||||
|
|
||||||
<div className="fixed inset-0 z-20 h-full w-full">
|
|
||||||
<div className="grid h-full w-full place-items-center p-4">
|
|
||||||
<Transition.Child
|
|
||||||
as={React.Fragment}
|
|
||||||
enter="ease-out duration-300"
|
|
||||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
||||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
|
||||||
leave="ease-in duration-200"
|
|
||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
||||||
>
|
|
||||||
<Dialog.Panel className="relative min-w-[100%] overflow-hidden rounded-lg bg-custom-background-100 shadow-custom-shadow-md sm:min-w-[50%] sm:max-w-[50%]">
|
|
||||||
<div className="flex max-h-[90vh] w-full flex-col p-4">
|
|
||||||
<Dialog.Title as="h3" className="flex items-center justify-between text-lg font-semibold">
|
|
||||||
<span>Product Updates</span>
|
|
||||||
<span>
|
|
||||||
<button type="button" onClick={() => setIsOpen(false)}>
|
|
||||||
<X className="h-6 w-6 text-custom-text-200 hover:text-custom-text-100" aria-hidden="true" />
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</Dialog.Title>
|
|
||||||
{updates && updates.length > 0 ? (
|
|
||||||
<div className="mt-4 h-full space-y-4 overflow-y-auto">
|
|
||||||
{updates.map((item, index) => (
|
|
||||||
<React.Fragment key={item.id}>
|
|
||||||
<div className="flex items-center gap-3 text-xs text-custom-text-200">
|
|
||||||
<span className="flex items-center rounded-full border border-custom-border-200 bg-custom-background-90 px-3 py-1.5 text-xs">
|
|
||||||
{item.tag_name}
|
|
||||||
</span>
|
|
||||||
<span>{renderFormattedDate(item.published_at)}</span>
|
|
||||||
{index === 0 && (
|
|
||||||
<span className="flex items-center rounded-full border border-custom-border-200 bg-custom-primary px-3 py-1.5 text-xs text-white">
|
|
||||||
New
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<MarkdownRenderer markdown={item.body} />
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="mt-4 grid w-full place-items-center">
|
|
||||||
<Loader className="w-full space-y-6">
|
|
||||||
<div className="space-y-3">
|
|
||||||
<Loader.Item height="30px" />
|
|
||||||
<Loader.Item height="20px" width="80%" />
|
|
||||||
<Loader.Item height="20px" width="80%" />
|
|
||||||
</div>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<Loader.Item height="30px" />
|
|
||||||
<Loader.Item height="20px" width="80%" />
|
|
||||||
<Loader.Item height="20px" width="80%" />
|
|
||||||
</div>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<Loader.Item height="30px" />
|
|
||||||
<Loader.Item height="20px" width="80%" />
|
|
||||||
<Loader.Item height="20px" width="80%" />
|
|
||||||
</div>
|
|
||||||
</Loader>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Dialog.Panel>
|
|
||||||
</Transition.Child>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</Transition.Root>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
1
web/core/components/global/index.ts
Normal file
1
web/core/components/global/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./product-updates";
|
||||||
62
web/core/components/global/product-updates/footer.tsx
Normal file
62
web/core/components/global/product-updates/footer.tsx
Normal file
|
|
@ -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 = () => (
|
||||||
|
<div className="flex items-center justify-between flex-shrink-0 gap-4 m-6 mb-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<a
|
||||||
|
href="https://go.plane.so/p-docs"
|
||||||
|
target="_blank"
|
||||||
|
className="text-sm text-custom-text-200 hover:text-custom-text-100 hover:underline underline-offset-1 outline-none"
|
||||||
|
>
|
||||||
|
Docs
|
||||||
|
</a>
|
||||||
|
<svg viewBox="0 0 2 2" className="h-0.5 w-0.5 fill-current">
|
||||||
|
<circle cx={1} cy={1} r={1} />
|
||||||
|
</svg>
|
||||||
|
<a
|
||||||
|
href="https://go.plane.so/p-changelog"
|
||||||
|
target="_blank"
|
||||||
|
className="text-sm text-custom-text-200 hover:text-custom-text-100 hover:underline underline-offset-1 outline-none"
|
||||||
|
>
|
||||||
|
Full changelog
|
||||||
|
</a>
|
||||||
|
<svg viewBox="0 0 2 2" className="h-0.5 w-0.5 fill-current">
|
||||||
|
<circle cx={1} cy={1} r={1} />
|
||||||
|
</svg>
|
||||||
|
<a
|
||||||
|
href="mailto:support@plane.so"
|
||||||
|
target="_blank"
|
||||||
|
className="text-sm text-custom-text-200 hover:text-custom-text-100 hover:underline underline-offset-1 outline-none"
|
||||||
|
>
|
||||||
|
Support
|
||||||
|
</a>
|
||||||
|
<svg viewBox="0 0 2 2" className="h-0.5 w-0.5 fill-current">
|
||||||
|
<circle cx={1} cy={1} r={1} />
|
||||||
|
</svg>
|
||||||
|
<a
|
||||||
|
href="https://go.plane.so/p-discord"
|
||||||
|
target="_blank"
|
||||||
|
className="text-sm text-custom-text-200 hover:text-custom-text-100 hover:underline underline-offset-1 outline-none"
|
||||||
|
>
|
||||||
|
Discord
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href="https://plane.so/pages"
|
||||||
|
target="_blank"
|
||||||
|
className={cn(
|
||||||
|
getButtonStyling("accent-primary", "sm"),
|
||||||
|
"flex gap-1.5 items-center text-center font-medium hover:underline underline-offset-2 outline-none"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Image src={PlaneLogo} alt="Plane" width={12} height={12} />
|
||||||
|
Powered by Plane Pages
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
2
web/core/components/global/product-updates/index.ts
Normal file
2
web/core/components/global/product-updates/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./modal";
|
||||||
|
export * from "./footer";
|
||||||
84
web/core/components/global/product-updates/modal.tsx
Normal file
84
web/core/components/global/product-updates/modal.tsx
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
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<ProductUpdatesModalProps> = observer((props) => {
|
||||||
|
const { isOpen, handleClose } = props;
|
||||||
|
// refs
|
||||||
|
const editorRef = useRef<EditorRefApi>(null);
|
||||||
|
// swr
|
||||||
|
const { data, isLoading, error } = useSWR(`INSTANCE_CHANGELOG`, () => instanceService.getInstanceChangeLog(), {
|
||||||
|
shouldRetryOnError: false,
|
||||||
|
revalidateIfStale: false,
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.CENTER} width={EModalWidth.XXL}>
|
||||||
|
<ProductUpdatesHeader />
|
||||||
|
<div className="flex flex-col h-[60vh] vertical-scrollbar scrollbar-xs overflow-hidden overflow-y-scroll px-6 mx-0.5">
|
||||||
|
{!isLoading && !!error ? (
|
||||||
|
<div className="flex flex-col items-center justify-center w-full h-full mb-8">
|
||||||
|
<div className="text-lg font-medium">We are having trouble fetching the updates.</div>
|
||||||
|
<div className="text-sm text-custom-text-200">
|
||||||
|
Please visit{" "}
|
||||||
|
<a
|
||||||
|
href="https://go.plane.so/p-changelog"
|
||||||
|
target="_blank"
|
||||||
|
className="text-sm text-custom-primary-100 font-medium hover:text-custom-primary-200 underline underline-offset-1 outline-none"
|
||||||
|
>
|
||||||
|
our changelogs
|
||||||
|
</a>{" "}
|
||||||
|
for the latest updates.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : isLoading ? (
|
||||||
|
<div className="flex items-center justify-center w-full h-full">
|
||||||
|
<LogoSpinner />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="ml-5">
|
||||||
|
{data?.id && (
|
||||||
|
<DocumentReadOnlyEditorWithRef
|
||||||
|
ref={editorRef}
|
||||||
|
id={data.id}
|
||||||
|
initialValue={data.description_html ?? "<p></p>"}
|
||||||
|
containerClassName="p-0 border-none"
|
||||||
|
mentionHandler={{
|
||||||
|
highlights: () => Promise.resolve([]),
|
||||||
|
}}
|
||||||
|
embedHandler={{
|
||||||
|
issue: {
|
||||||
|
widgetCallback: () => <></>,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
fileHandler={{
|
||||||
|
getAssetSrc: () => "",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<ProductUpdatesFooter />
|
||||||
|
</ModalCore>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
@ -5,14 +5,16 @@ import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { FileText, HelpCircle, MessagesSquare, MoveLeft, User } from "lucide-react";
|
import { FileText, HelpCircle, MessagesSquare, MoveLeft, User } from "lucide-react";
|
||||||
// ui
|
// ui
|
||||||
import { CustomMenu, ToggleSwitch, Tooltip } from "@plane/ui";
|
import { CustomMenu, Tooltip, ToggleSwitch } from "@plane/ui";
|
||||||
|
// components
|
||||||
|
import { ProductUpdatesModal } from "@/components/global";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useAppTheme, useCommandPalette, useInstance, useTransient, useUserSettings } from "@/hooks/store";
|
import { useAppTheme, useCommandPalette, useInstance, useTransient, useUserSettings } from "@/hooks/store";
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
// plane web components
|
// 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 { WorkspaceEditionBadge } from "@/plane-web/components/workspace";
|
||||||
import { ENABLE_LOCAL_DB_CACHE } from "@/plane-web/constants/issues";
|
import { ENABLE_LOCAL_DB_CACHE } from "@/plane-web/constants/issues";
|
||||||
|
|
||||||
|
|
@ -135,7 +137,15 @@ export const SidebarHelpSection: React.FC<WorkspaceHelpSectionProps> = observer(
|
||||||
<span className="text-xs">Keyboard shortcuts</span>
|
<span className="text-xs">Keyboard shortcuts</span>
|
||||||
</button>
|
</button>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
<ProductUpdates setIsChangeLogOpen={setIsChangeLogOpen} />
|
<CustomMenu.MenuItem>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setIsChangeLogOpen(true)}
|
||||||
|
className="flex w-full items-center justify-start text-xs hover:bg-custom-background-80"
|
||||||
|
>
|
||||||
|
<span className="text-xs">What's new</span>
|
||||||
|
</button>
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
<CustomMenu.MenuItem>
|
<CustomMenu.MenuItem>
|
||||||
<a
|
<a
|
||||||
href="https://go.plane.so/p-discord"
|
href="https://go.plane.so/p-discord"
|
||||||
|
|
@ -163,9 +173,8 @@ export const SidebarHelpSection: React.FC<WorkspaceHelpSectionProps> = observer(
|
||||||
<Tooltip tooltipContent={`${isCollapsed ? "Expand" : "Hide"}`} isMobile={isMobile}>
|
<Tooltip tooltipContent={`${isCollapsed ? "Expand" : "Hide"}`} isMobile={isMobile}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`grid place-items-center rounded-md p-1 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 ${
|
className={`grid place-items-center rounded-md p-1 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 ${isCollapsed ? "w-full" : ""
|
||||||
isCollapsed ? "w-full" : ""
|
}`}
|
||||||
}`}
|
|
||||||
onClick={() => toggleSidebar()}
|
onClick={() => toggleSidebar()}
|
||||||
>
|
>
|
||||||
<MoveLeft className={`h-4 w-4 duration-300 ${isCollapsed ? "rotate-180" : ""}`} />
|
<MoveLeft className={`h-4 w-4 duration-300 ${isCollapsed ? "rotate-180" : ""}`} />
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// types
|
// types
|
||||||
import type { IInstanceInfo } from "@plane/types";
|
import type { IInstanceInfo, TPage } from "@plane/types";
|
||||||
// helpers
|
// helpers
|
||||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||||
// services
|
// services
|
||||||
|
|
@ -25,4 +25,12 @@ export class InstanceService extends APIService {
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getInstanceChangeLog(): Promise<TPage> {
|
||||||
|
return this.get("/api/instances/changelog/")
|
||||||
|
.then((response) => response.data)
|
||||||
|
.catch((error) => {
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue