diff --git a/README.md b/README.md index 20e34b673..2bc2764f3 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,16 @@ chmod +x setup.sh > If running in a cloud env replace localhost with public facing IP address of the VM +- Setup Tiptap Pro + + Visit [Tiptap Pro](https://collab.tiptap.dev/pro-extensions) and signup (it is free). + + Create a **`.npmrc`** file, copy the following and replace your registry token generated from Tiptap Pro. + +``` +@tiptap-pro:registry=https://registry.tiptap.dev/ +//registry.tiptap.dev/:_authToken=YOUR_REGISTRY_TOKEN +``` - Run Docker compose up ```bash diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 77432e1e0..6f0f1e6ae 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -370,7 +370,7 @@ class UserWorkSpaceIssues(BaseAPIView): ) ) .filter(**filters) - ) + ).distinct() # Priority Ordering if order_by_param == "priority" or order_by_param == "-priority": diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py index 9150d7c94..1cc6c85cc 100644 --- a/apiserver/plane/bgtasks/issue_activites_task.py +++ b/apiserver/plane/bgtasks/issue_activites_task.py @@ -184,19 +184,24 @@ def track_description( if current_instance.get("description_html") != requested_data.get( "description_html" ): - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value=current_instance.get("description_html"), - new_value=requested_data.get("description_html"), - field="description", - project=project, - workspace=project.workspace, - comment=f"updated the description to {requested_data.get('description_html')}", - ) - ) + last_activity = IssueActivity.objects.filter(issue_id=issue_id).order_by("-created_at").first() + if(last_activity is not None and last_activity.field == "description" and actor.id == last_activity.actor_id): + last_activity.created_at = timezone.now() + last_activity.save(update_fields=["created_at"]) + else: + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value=current_instance.get("description_html"), + new_value=requested_data.get("description_html"), + field="description", + project=project, + workspace=project.workspace, + comment=f"updated the description to {requested_data.get('description_html')}", + ) + ) # Track changes in issue target date diff --git a/apps/app/components/command-palette/change-interface-theme.tsx b/apps/app/components/command-palette/change-interface-theme.tsx index 34ebf3562..87d1289ae 100644 --- a/apps/app/components/command-palette/change-interface-theme.tsx +++ b/apps/app/components/command-palette/change-interface-theme.tsx @@ -9,12 +9,18 @@ import userService from "services/user.service"; import useUser from "hooks/use-user"; // helper import { unsetCustomCssVariables } from "helpers/theme.helper"; +// mobx react lite +import { observer } from "mobx-react-lite"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; type Props = { setIsPaletteOpen: Dispatch>; }; -export const ChangeInterfaceTheme: React.FC = ({ setIsPaletteOpen }) => { +export const ChangeInterfaceTheme: React.FC = observer(({ setIsPaletteOpen }) => { + const store: any = useMobxStore(); + const [mounted, setMounted] = useState(false); const { setTheme } = useTheme(); @@ -23,29 +29,11 @@ export const ChangeInterfaceTheme: React.FC = ({ setIsPaletteOpen }) => { const updateUserTheme = (newTheme: string) => { if (!user) return; - - unsetCustomCssVariables(); - setTheme(newTheme); - - mutateUser((prevData: any) => { - if (!prevData) return prevData; - - return { - ...prevData, - theme: { - ...prevData?.theme, - theme: newTheme, - }, - }; - }, false); - - userService.updateUser({ - theme: { - ...user.theme, - theme: newTheme, - }, - }); + return store.user + .updateCurrentUserSettings({ theme: { ...user.theme, theme: newTheme } }) + .then((response: any) => response) + .catch((error: any) => error); }; // useEffect only runs on the client, so now we can safely show the UI @@ -74,4 +62,4 @@ export const ChangeInterfaceTheme: React.FC = ({ setIsPaletteOpen }) => { ))} ); -}; +}); diff --git a/apps/app/components/core/modals/gpt-assistant-modal.tsx b/apps/app/components/core/modals/gpt-assistant-modal.tsx index 6b06256cf..a3b748d66 100644 --- a/apps/app/components/core/modals/gpt-assistant-modal.tsx +++ b/apps/app/components/core/modals/gpt-assistant-modal.tsx @@ -145,7 +145,7 @@ export const GptAssistantModal: React.FC = ({ }`} > {((content && content !== "") || (htmlContent && htmlContent !== "

")) && ( -
+
Content: ${content}

`} diff --git a/apps/app/components/core/theme/theme-switch.tsx b/apps/app/components/core/theme/theme-switch.tsx index 8b4d0da69..687998c25 100644 --- a/apps/app/components/core/theme/theme-switch.tsx +++ b/apps/app/components/core/theme/theme-switch.tsx @@ -45,18 +45,26 @@ export const ThemeSwitch: React.FC = observer( currentThemeObj ? (
-
+ > +
+
+
+ {currentThemeObj.label}
) : ( "Select your theme" @@ -98,18 +106,26 @@ export const ThemeSwitch: React.FC = observer(
-
+ > +
+
+
+ {label}
))} diff --git a/apps/app/components/issues/comment/add-comment.tsx b/apps/app/components/issues/comment/add-comment.tsx index 6f49e900a..1dc75a7a6 100644 --- a/apps/app/components/issues/comment/add-comment.tsx +++ b/apps/app/components/issues/comment/add-comment.tsx @@ -87,7 +87,7 @@ export const AddComment: React.FC = ({ issueId, user, disabled = false }) return (
-
+
= ({ comment, onSubmit, handleCommentD className={`flex-col gap-2 ${isEditing ? "flex" : "hidden"}`} onSubmit={handleSubmit(onEnter)} > -
+
= ({ {characterLimit && (
255 ? "text-red-500" : "" - }`} + className={`${ + watch("name").length === 0 || watch("name").length > 255 ? "text-red-500" : "" + }`} > {watch("name").length} @@ -122,7 +123,7 @@ export const IssueDescriptionForm: FC = ({ )}
{errors.name ? errors.name.message : null} -
+
= ({ = ({ ); }} /> -
- {isSubmitting === 'submitting' ? 'Saving...' : 'Saved'} +
+ {isSubmitting === "submitting" ? "Saving..." : "Saved"}
diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index a7913b3cf..0e73ca349 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -333,7 +333,7 @@ export const IssueForm: FC = ({
)} {(fieldsToShow.includes("all") || fieldsToShow.includes("description")) && ( -
+
{issueName && issueName !== "" && (
-
+
{
-

{userProjectsData.user_data.display_name}

+

+ {userProjectsData.user_data.first_name} {userProjectsData.user_data.last_name} +

- {userProjectsData.user_data.display_name} + ({userProjectsData.user_data.display_name})
diff --git a/apps/app/components/project/publish-project/modal.tsx b/apps/app/components/project/publish-project/modal.tsx index d91719497..5f9d9ae2c 100644 --- a/apps/app/components/project/publish-project/modal.tsx +++ b/apps/app/components/project/publish-project/modal.tsx @@ -14,6 +14,9 @@ import { observer } from "mobx-react-lite"; import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; import { IProjectPublishSettingsViews } from "store/project-publish"; +// hooks +import useToast from "hooks/use-toast"; +import useProjectDetails from "hooks/use-project-details"; type Props = { // user: ICurrentUserResponse | undefined; @@ -25,7 +28,7 @@ const defaultValues: Partial = { reactions: false, votes: false, inbox: null, - views: [], + views: ["list", "kanban"], }; const viewOptions = [ @@ -40,6 +43,17 @@ export const PublishProjectModal: React.FC = observer(() => { const store: RootStore = useMobxStore(); const { projectPublish } = store; + const { projectDetails, mutateProjectDetails } = useProjectDetails(); + + const { setToastAlert } = useToast(); + const handleToastAlert = (title: string, type: string, message: string) => { + setToastAlert({ + title: title || "Title", + type: "error" || "warning", + message: message || "Message", + }); + }; + const { NEXT_PUBLIC_DEPLOY_URL } = process.env; const plane_deploy_url = NEXT_PUBLIC_DEPLOY_URL ? NEXT_PUBLIC_DEPLOY_URL @@ -111,32 +125,41 @@ export const PublishProjectModal: React.FC = observer(() => { }, [workspaceSlug, projectPublish, projectPublish.projectPublishModal]); const onSettingsPublish = async (formData: any) => { - const payload = { - comments: formData.comments || false, - reactions: formData.reactions || false, - votes: formData.votes || false, - inbox: formData.inbox || null, - views: { - list: formData.views.includes("list") || false, - kanban: formData.views.includes("kanban") || false, - calendar: formData.views.includes("calendar") || false, - gantt: formData.views.includes("gantt") || false, - spreadsheet: formData.views.includes("spreadsheet") || false, - }, - }; + if (formData.views && formData.views.length > 0) { + const payload = { + comments: formData.comments || false, + reactions: formData.reactions || false, + votes: formData.votes || false, + inbox: formData.inbox || null, + views: { + list: formData.views.includes("list") || false, + kanban: formData.views.includes("kanban") || false, + calendar: formData.views.includes("calendar") || false, + gantt: formData.views.includes("gantt") || false, + spreadsheet: formData.views.includes("spreadsheet") || false, + }, + }; - return projectPublish - .createProjectSettingsAsync( - workspaceSlug as string, - projectPublish.project_id as string, - payload, - null - ) - .then((response) => response) - .catch((error) => { - console.error("error", error); - return error; - }); + const _workspaceSlug = workspaceSlug; + const _projectId = projectPublish.project_id; + + return projectPublish + .createProjectSettingsAsync(_workspaceSlug as string, _projectId as string, payload, null) + .then((response) => { + mutateProjectDetails(); + handleClose(); + console.log("_projectId", _projectId); + if (_projectId) + window.open(`${plane_deploy_url}/${_workspaceSlug}/${_projectId}`, "_blank"); + return response; + }) + .catch((error) => { + console.error("error", error); + return error; + }); + } else { + handleToastAlert("Missing fields", "warning", "Please select at least one view to publish"); + } }; const onSettingsUpdate = async (key: string, value: any) => { @@ -171,7 +194,10 @@ export const PublishProjectModal: React.FC = observer(() => { payload, null ) - .then((response) => response) + .then((response) => { + mutateProjectDetails(); + return response; + }) .catch((error) => { console.log("error", error); return error; @@ -187,7 +213,9 @@ export const PublishProjectModal: React.FC = observer(() => { null ) .then((response) => { + mutateProjectDetails(); reset({ ...defaultValues }); + handleClose(); return response; }) .catch((error) => { diff --git a/apps/app/components/tiptap/bubble-menu/index.tsx b/apps/app/components/tiptap/bubble-menu/index.tsx index 590dfab5e..259b5ecea 100644 --- a/apps/app/components/tiptap/bubble-menu/index.tsx +++ b/apps/app/components/tiptap/bubble-menu/index.tsx @@ -97,10 +97,14 @@ export const EditorBubbleMenu: FC = (props) => { {items.map((item, index) => ( +const { NEXT_PUBLIC_DEPLOY_URL } = process.env; +const plane_deploy_url = NEXT_PUBLIC_DEPLOY_URL ? NEXT_PUBLIC_DEPLOY_URL : "http://localhost:3001"; + +const Header: React.FC = ({ breadcrumbs, left, right, setToggleSidebar, noHeader }) => { + const { projectDetails } = useProjectDetails(); + + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + return ( +
+
+
+ +
+
{breadcrumbs}
+ + {projectDetails && projectDetails?.is_deployed && ( + + + +
+
+ + radio_button_checked + +
+
Public
+
+ open_in_new +
+
+
+
+ + )} + +
{left}
- {breadcrumbs} -
{left}
+
{right}
-
{right}
-
-); + ); +}; export default Header; diff --git a/apps/app/store/project-publish.tsx b/apps/app/store/project-publish.tsx index d1b4c58a7..1b27d5fff 100644 --- a/apps/app/store/project-publish.tsx +++ b/apps/app/store/project-publish.tsx @@ -259,15 +259,13 @@ class ProjectPublishStore implements IProjectPublishStore { user ); - if (response) { - runInAction(() => { - this.projectPublishSettings = "not-initialized"; - this.loader = false; - this.error = null; - }); + runInAction(() => { + this.projectPublishSettings = "not-initialized"; + this.loader = false; + this.error = null; + }); - return response; - } + return response; } catch (error) { this.loader = false; this.error = error; diff --git a/apps/app/types/projects.d.ts b/apps/app/types/projects.d.ts index d620a24b7..a3d8b997a 100644 --- a/apps/app/types/projects.d.ts +++ b/apps/app/types/projects.d.ts @@ -57,6 +57,7 @@ export interface IProject { updated_by: string; workspace: IWorkspace | string; workspace_detail: IWorkspaceLite; + is_deployed: boolean; } export interface IProjectLite { diff --git a/apps/space/app/404/page.tsx b/apps/space/app/404/page.tsx new file mode 100644 index 000000000..d10d95fb9 --- /dev/null +++ b/apps/space/app/404/page.tsx @@ -0,0 +1,30 @@ +// next imports +import Image from "next/image"; + +const Custom404Error = () => ( +
+
+
+
+ 404- Page not found +
+
Oops! Something went wrong.
+
+ Sorry, the page you are looking for cannot be found. It may have been removed, had its name changed, or is + temporarily unavailable. +
+
+ + +
+
+); + +export default Custom404Error; diff --git a/apps/space/app/[workspace_slug]/[project_slug]/layout.tsx b/apps/space/app/[workspace_slug]/[project_slug]/layout.tsx index fde4f5d99..7b4ed6142 100644 --- a/apps/space/app/[workspace_slug]/[project_slug]/layout.tsx +++ b/apps/space/app/[workspace_slug]/[project_slug]/layout.tsx @@ -7,6 +7,7 @@ import IssueNavbar from "components/issues/navbar"; import IssueFilter from "components/issues/filters-render"; // service import ProjectService from "services/project.service"; +import { redirect } from "next/navigation"; type LayoutProps = { params: { workspace_slug: string; project_slug: string }; @@ -17,17 +18,26 @@ export async function generateMetadata({ params }: LayoutProps): Promise${ - typeof project?.project_details?.emoji != "object" - ? String.fromCodePoint(parseInt(project?.project_details?.emoji)) - : "✈️" - }`, - }; + return { + title: `${project?.project_details?.name} | ${workspace_slug}`, + description: `${ + project?.project_details?.description || `${project?.project_details?.name} | ${workspace_slug}` + }`, + icons: `data:image/svg+xml,${ + typeof project?.project_details?.emoji != "object" + ? String.fromCodePoint(parseInt(project?.project_details?.emoji)) + : "✈️" + }`, + }; + } catch (error: any) { + if (error?.data?.error) { + redirect(`/project-not-published`); + } + return {}; + } } const RootLayout = ({ children }: { children: React.ReactNode }) => ( diff --git a/apps/space/app/project-not-published/page.tsx b/apps/space/app/project-not-published/page.tsx new file mode 100644 index 000000000..82a2ff5da --- /dev/null +++ b/apps/space/app/project-not-published/page.tsx @@ -0,0 +1,31 @@ +// next imports +import Image from "next/image"; + +const CustomProjectNotPublishedError = () => ( +
+
+
+
+ 404- Page not found +
+
+ Oops! The page you{`'`}re looking for isn{`'`}t live at the moment. +
+
+ If this is your project, login to your workspace to adjust its visibility settings and make it public. +
+
+ + +
+
+); + +export default CustomProjectNotPublishedError; diff --git a/apps/space/components/issues/board-views/kanban/block.tsx b/apps/space/components/issues/board-views/kanban/block.tsx index 304e05612..22af77568 100644 --- a/apps/space/components/issues/board-views/kanban/block.tsx +++ b/apps/space/components/issues/board-views/kanban/block.tsx @@ -27,7 +27,7 @@ export const IssueListBlock = ({ issue }: { issue: IIssue }) => {
{issue.name}
{/* priority */} -
+
{issue?.priority && (
diff --git a/apps/space/package.json b/apps/space/package.json index c2eaa8af1..dd7a3058c 100644 --- a/apps/space/package.json +++ b/apps/space/package.json @@ -20,7 +20,7 @@ "js-cookie": "^3.0.1", "mobx": "^6.10.0", "mobx-react-lite": "^4.0.3", - "next": "^13.4.13", + "next": "^13.4.16", "nprogress": "^0.2.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/apps/space/public/404.svg b/apps/space/public/404.svg new file mode 100644 index 000000000..4c298417d --- /dev/null +++ b/apps/space/public/404.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/apps/space/public/project-not-published.svg b/apps/space/public/project-not-published.svg new file mode 100644 index 000000000..db4b404df --- /dev/null +++ b/apps/space/public/project-not-published.svg @@ -0,0 +1,4 @@ + + + + diff --git a/yarn.lock b/yarn.lock index 005c9e563..705ce0362 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5963,7 +5963,7 @@ next@12.3.2: "@next/swc-win32-ia32-msvc" "12.3.2" "@next/swc-win32-x64-msvc" "12.3.2" -next@^13.4.13: +next@^13.4.16: version "13.4.16" resolved "https://registry.yarnpkg.com/next/-/next-13.4.16.tgz#327ef6885b22161ed001cd5943c20b5e409a9406" integrity sha512-1xaA/5DrfpPu0eV31Iro7JfPeqO8uxQWb1zYNTe+KDKdzqkAGapLcDYHMLNKXKB7lHjZ7LfKUOf9dyuzcibrhA==