From e2b704ea6f604876378af609fb328de569d49b14 Mon Sep 17 00:00:00 2001 From: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Date: Fri, 1 Dec 2023 12:21:17 +0530 Subject: [PATCH 01/17] chore: removed django settings module (#2953) --- ENV_SETUP.md | 1 - deploy/coolify/coolify-docker-compose.yml | 3 --- deploy/selfhost/docker-compose.yml | 1 - deploy/selfhost/variables.env | 1 - 4 files changed, 6 deletions(-) diff --git a/ENV_SETUP.md b/ENV_SETUP.md index f1cc7cb1e..3e03244c6 100644 --- a/ENV_SETUP.md +++ b/ENV_SETUP.md @@ -76,7 +76,6 @@ NEXT_PUBLIC_ENABLE_OAUTH=0 # Backend # Debug value for api server use it as 0 for production use DEBUG=0 -DJANGO_SETTINGS_MODULE="plane.settings.selfhosted" # deprecated ​ # Error logs SENTRY_DSN="" diff --git a/deploy/coolify/coolify-docker-compose.yml b/deploy/coolify/coolify-docker-compose.yml index 861ff3ab8..6dd361d34 100644 --- a/deploy/coolify/coolify-docker-compose.yml +++ b/deploy/coolify/coolify-docker-compose.yml @@ -35,7 +35,6 @@ services: command: ./bin/takeoff environment: - DEBUG=${DEBUG:-0} - - DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE:-plane.settings.selfhosted} - SENTRY_DSN=${SENTRY_DSN:-""} - PGUSER=${PGUSER:-plane} - PGPASSWORD=${PGPASSWORD:-plane} @@ -85,7 +84,6 @@ services: command: ./bin/worker environment: - DEBUG=${DEBUG:-0} - - DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE:-plane.settings.selfhosted} - SENTRY_DSN=${SENTRY_DSN:-""} - PGUSER=${PGUSER:-plane} - PGPASSWORD=${PGPASSWORD:-plane} @@ -133,7 +131,6 @@ services: command: ./bin/beat environment: - DEBUG=${DEBUG:-0} - - DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE:-plane.settings.selfhosted} - SENTRY_DSN=${SENTRY_DSN:-""} - PGUSER=${PGUSER:-plane} - PGPASSWORD=${PGPASSWORD:-plane} diff --git a/deploy/selfhost/docker-compose.yml b/deploy/selfhost/docker-compose.yml index c907c6c61..7eec3fd5a 100644 --- a/deploy/selfhost/docker-compose.yml +++ b/deploy/selfhost/docker-compose.yml @@ -5,7 +5,6 @@ x-app-env : &app-env - NGINX_PORT=${NGINX_PORT:-80} - WEB_URL=${WEB_URL:-http://localhost} - DEBUG=${DEBUG:-0} - - DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE:-plane.settings.selfhosted} # deprecated - NEXT_PUBLIC_ENABLE_OAUTH=${NEXT_PUBLIC_ENABLE_OAUTH:-0} - NEXT_PUBLIC_DEPLOY_URL=${NEXT_PUBLIC_DEPLOY_URL:-http://localhost/spaces} - SENTRY_DSN=${SENTRY_DSN:-""} diff --git a/deploy/selfhost/variables.env b/deploy/selfhost/variables.env index a90a546d7..10ca42879 100644 --- a/deploy/selfhost/variables.env +++ b/deploy/selfhost/variables.env @@ -7,7 +7,6 @@ API_REPLICAS=1 NGINX_PORT=80 WEB_URL=http://localhost DEBUG=0 -DJANGO_SETTINGS_MODULE=plane.settings.selfhosted # deprecated NEXT_PUBLIC_ENABLE_OAUTH=0 NEXT_PUBLIC_DEPLOY_URL=http://localhost/spaces SENTRY_DSN="" From 5db9b2b6383e761323b0e3c8ada83aa0680275de Mon Sep 17 00:00:00 2001 From: Manish Gupta <59428681+mguptahub@users.noreply.github.com> Date: Fri, 1 Dec 2023 12:46:18 +0530 Subject: [PATCH 02/17] branch build fix (#2954) --- .github/workflows/build-branch.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-branch.yml b/.github/workflows/build-branch.yml index a3ca1d688..26b3c58be 100644 --- a/.github/workflows/build-branch.yml +++ b/.github/workflows/build-branch.yml @@ -65,9 +65,9 @@ jobs: steps: - name: Set Frontend Docker Tag run: | - if [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" = "master" ]; then + if [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "master" ]; then TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-private:latest" - elif [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" = "release" ] || [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" = "preview" ]; then + elif [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "release" ] || [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "preview" ]; then TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-private:preview,${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:preview" else TAG=${{ env.FRONTEND_TAG }}" @@ -107,9 +107,9 @@ jobs: steps: - name: Set Space Docker Tag run: | - if [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" = "master" ]; then + if [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "master" ]; then TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space:latest" - elif [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" = "release" ] || [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" = "preview" ]; then + elif [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "release" ] || [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "preview" ]; then TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space-private:preview,${{ secrets.DOCKERHUB_USERNAME }}/plane-space:preview" else TAG=${{ env.SPACE_TAG }}" @@ -149,9 +149,9 @@ jobs: steps: - name: Set Backend Docker Tag run: | - if [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" = "master" ]; then + if [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "master" ]; then TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-private:latest" - elif [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" = "release" ] || [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" = "preview" ]; then + elif [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "release" ] || [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "preview" ]; then TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-private:preview",${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:preview" else TAG=${{ env.BACKEND_TAG }} @@ -191,9 +191,9 @@ jobs: steps: - name: Set Proxy Docker Tag run: | - if [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" = "master" ]; then + if [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "master" ]; then TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-private:latest" - elif [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" = "release" ] || [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" = "preview" ]; then + elif [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "release" ] || [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "preview" ]; then TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-private:preview,${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:preview" else TAG=${{ env.PROXY_TAG }} From 4a2f8d93b1c31dd739b3c50cb051222cce59f616 Mon Sep 17 00:00:00 2001 From: Manish Gupta <59428681+mguptahub@users.noreply.github.com> Date: Fri, 1 Dec 2023 13:23:50 +0530 Subject: [PATCH 03/17] fix: branch build 2 (#2957) * branch build fix * removed quotes --- .github/workflows/build-branch.yml | 54 +++++++++++++++--------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/build-branch.yml b/.github/workflows/build-branch.yml index 26b3c58be..e65035482 100644 --- a/.github/workflows/build-branch.yml +++ b/.github/workflows/build-branch.yml @@ -15,7 +15,7 @@ env: TARGET_BRANCH: ${{ github.event.pull_request.base.ref }} jobs: - branch_build_and_push: + branch_build_setup: if: ${{ (github.event_name == 'pull_request' && github.event.action =='closed' && github.event.pull_request.merged == true) }} name: Build-Push Web/Space/API/Proxy Docker Image runs-on: ubuntu-20.04 @@ -59,18 +59,18 @@ jobs: branch_build_push_frontend: runs-on: ubuntu-20.04 - needs: [branch_build_and_push] + needs: [branch_build_setup] env: - FRONTEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-private:${{ needs.branch_build_and_push.outputs.gh_branch_name }} + FRONTEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-private:${{ needs.branch_build_setup.outputs.gh_branch_name }} steps: - name: Set Frontend Docker Tag run: | - if [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "master" ]; then - TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-private:latest" - elif [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "release" ] || [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "preview" ]; then - TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-private:preview,${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:preview" + if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-private:latest + elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "release" ] || [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "preview" ]; then + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-private:preview,${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:preview else - TAG=${{ env.FRONTEND_TAG }}" + TAG=${{ env.FRONTEND_TAG }} fi echo "FRONTEND_TAG=${TAG}" >> $GITHUB_ENV - name: Set up Docker Buildx @@ -101,18 +101,18 @@ jobs: branch_build_push_space: runs-on: ubuntu-20.04 - needs: [branch_build_and_push] + needs: [branch_build_setup] env: - SPACE_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-space-private:${{ needs.branch_build_and_push.outputs.gh_branch_name }} + SPACE_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-space-private:${{ needs.branch_build_setup.outputs.gh_branch_name }} steps: - name: Set Space Docker Tag run: | - if [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "master" ]; then - TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space:latest" - elif [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "release" ] || [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "preview" ]; then - TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space-private:preview,${{ secrets.DOCKERHUB_USERNAME }}/plane-space:preview" + if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space:latest + elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "release" ] || [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "preview" ]; then + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space-private:preview,${{ secrets.DOCKERHUB_USERNAME }}/plane-space:preview else - TAG=${{ env.SPACE_TAG }}" + TAG=${{ env.SPACE_TAG }} fi echo "SPACE_TAG=${TAG}" >> $GITHUB_ENV - name: Set up Docker Buildx @@ -143,16 +143,16 @@ jobs: branch_build_push_backend: runs-on: ubuntu-20.04 - needs: [branch_build_and_push] + needs: [branch_build_setup] env: - BACKEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-private:${{ needs.branch_build_and_push.outputs.gh_branch_name }} + BACKEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-private:${{ needs.branch_build_setup.outputs.gh_branch_name }} steps: - name: Set Backend Docker Tag run: | - if [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "master" ]; then - TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-private:latest" - elif [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "release" ] || [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "preview" ]; then - TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-private:preview",${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:preview" + if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-private:latest + elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "release" ] || [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "preview" ]; then + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-private:preview",${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:preview else TAG=${{ env.BACKEND_TAG }} fi @@ -185,16 +185,16 @@ jobs: branch_build_push_proxy: runs-on: ubuntu-20.04 - needs: [branch_build_and_push] + needs: [branch_build_setup] env: - PROXY_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-private:${{ needs.branch_build_and_push.outputs.gh_branch_name }} + PROXY_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-private:${{ needs.branch_build_setup.outputs.gh_branch_name }} steps: - name: Set Proxy Docker Tag run: | - if [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "master" ]; then - TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-private:latest" - elif [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "release" ] || [ "${{ needs.branch_build_and_push.outputs.gh_branch_name }}" == "preview" ]; then - TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-private:preview,${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:preview" + if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-private:latest + elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "release" ] || [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "preview" ]; then + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-private:preview,${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:preview else TAG=${{ env.PROXY_TAG }} fi From c6764111a3098a67ea49a55ebc5425ee791f40fe Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Fri, 1 Dec 2023 13:25:48 +0530 Subject: [PATCH 04/17] fix: upgrading to nextjs 14 (#2959) --- .../accounts/email-reset-password-form.tsx | 19 +-- .../state/filter-state-block.tsx | 15 +-- space/components/issues/navbar/index.tsx | 24 ++-- .../issues/navbar/issue-board-view.tsx | 20 +-- space/components/issues/navbar/issue-view.tsx | 13 -- space/components/issues/navbar/search.tsx | 13 -- .../peek-overview/full-screen-peek-view.tsx | 13 +- .../issues/peek-overview/issue-activity.tsx | 22 ++-- .../issues/peek-overview/layout.tsx | 9 +- space/components/ui/tooltip.tsx | 5 +- space/components/views/login.tsx | 2 +- space/components/views/project-details.tsx | 21 ++- space/lib/mobx/store-init.tsx | 8 -- space/package.json | 4 +- space/pages/_app.tsx | 1 - space/pages/_document.tsx | 2 +- yarn.lock | 121 +----------------- 17 files changed, 69 insertions(+), 243 deletions(-) delete mode 100644 space/components/issues/navbar/issue-view.tsx delete mode 100644 space/components/issues/navbar/search.tsx diff --git a/space/components/accounts/email-reset-password-form.tsx b/space/components/accounts/email-reset-password-form.tsx index ee71890ec..e7752a00f 100644 --- a/space/components/accounts/email-reset-password-form.tsx +++ b/space/components/accounts/email-reset-password-form.tsx @@ -1,11 +1,5 @@ import React from "react"; - -// react hook form import { useForm } from "react-hook-form"; -// services -import userService from "services/user.service"; -// hooks -// import useToast from "hooks/use-toast"; // ui import { Input } from "components/ui"; import { Button } from "@plane/ui"; @@ -30,10 +24,9 @@ export const EmailResetPasswordForm: React.FC = ({ setIsResettingPassword }); const forgotPassword = async (formData: any) => { - const payload = { - email: formData.email, - }; - + // const payload = { + // email: formData.email, + // }; // await userService // .forgotPassword(payload) // .then(() => @@ -60,7 +53,7 @@ export const EmailResetPasswordForm: React.FC = ({ setIsResettingPassword }; return ( -
+
= ({ setIsResettingPassword ) || "Email address is not valid", })} placeholder="Enter registered email address.." - className="border-custom-border-300 h-[46px]" + className="h-[46px] border-custom-border-300" /> {errors.email &&
{errors.email.message}
}
-
+
diff --git a/space/components/issues/filters-render/state/filter-state-block.tsx b/space/components/issues/filters-render/state/filter-state-block.tsx index 9b6447cb6..b9c8ed4ec 100644 --- a/space/components/issues/filters-render/state/filter-state-block.tsx +++ b/space/components/issues/filters-render/state/filter-state-block.tsx @@ -1,19 +1,10 @@ -import { useRouter } from "next/router"; -// mobx react lite import { observer } from "mobx-react-lite"; -// mobx hook -import { useMobxStore } from "lib/mobx/store-provider"; // interfaces import { IIssueState } from "types/issue"; // constants import { issueGroupFilter } from "constants/data"; export const RenderIssueState = observer(({ state }: { state: IIssueState }) => { - const store = useMobxStore(); - - const router = useRouter(); - const { workspace_slug, project_slug } = router.query as { workspace_slug: string; project_slug: string }; - const stateGroup = issueGroupFilter(state.group); const removeStateFromFilter = () => { @@ -28,12 +19,12 @@ export const RenderIssueState = observer(({ state }: { state: IIssueState }) => if (stateGroup === null) return <>; return (
-
+
{/* */}
-
{state?.name}
+
{state?.name}
close diff --git a/space/components/issues/navbar/index.tsx b/space/components/issues/navbar/index.tsx index 8cc25090a..220991bd9 100644 --- a/space/components/issues/navbar/index.tsx +++ b/space/components/issues/navbar/index.tsx @@ -7,7 +7,7 @@ import { useRouter } from "next/router"; // mobx import { observer } from "mobx-react-lite"; // components -import { NavbarSearch } from "./search"; +// import { NavbarSearch } from "./search"; import { NavbarIssueBoardView } from "./issue-board-view"; import { NavbarTheme } from "./theme"; // ui @@ -83,45 +83,43 @@ const IssueNavbar = observer(() => { }, [board, workspace_slug, project_slug, router, projectStore, projectStore?.deploySettings]); return ( -
+
{/* project detail */} -
-
+
+
{projectStore?.project && projectStore?.project?.emoji ? ( renderEmoji(projectStore?.project?.emoji) ) : ( - plane logo + plane logo )}
-
+
{projectStore?.project?.name || `...`}
{/* issue search bar */} -
- -
+
{/* */}
{/* issue views */} -
+
{/* theming */} -
+
{user ? ( -
+
{user.avatar && user.avatar !== "" ? (
{/* eslint-disable-next-line @next/next/no-img-element */} {user.display_name
) : ( -
+
{(user.display_name ?? "A")[0]}
)} diff --git a/space/components/issues/navbar/issue-board-view.tsx b/space/components/issues/navbar/issue-board-view.tsx index 0ae71e8ee..16b09229a 100644 --- a/space/components/issues/navbar/issue-board-view.tsx +++ b/space/components/issues/navbar/issue-board-view.tsx @@ -7,28 +7,30 @@ import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; export const NavbarIssueBoardView = observer(() => { - const { project: projectStore, issue: issueStore }: RootStore = useMobxStore(); - + const { + project: { viewOptions, setActiveBoard, activeBoard }, + }: RootStore = useMobxStore(); + // router const router = useRouter(); const { workspace_slug, project_slug } = router.query as { workspace_slug: string; project_slug: string }; const handleCurrentBoardView = (boardView: string) => { - projectStore.setActiveBoard(boardView); + setActiveBoard(boardView); router.push(`/${workspace_slug}/${project_slug}?board=${boardView}`); }; return ( <> - {projectStore?.viewOptions && - Object.keys(projectStore?.viewOptions).map((viewKey: string) => { - if (projectStore?.viewOptions[viewKey]) { + {viewOptions && + Object.keys(viewOptions).map((viewKey: string) => { + if (viewOptions[viewKey]) { return (
handleCurrentBoardView(viewKey)} title={viewKey} diff --git a/space/components/issues/navbar/issue-view.tsx b/space/components/issues/navbar/issue-view.tsx deleted file mode 100644 index 0a8f5c860..000000000 --- a/space/components/issues/navbar/issue-view.tsx +++ /dev/null @@ -1,13 +0,0 @@ -"use client"; - -// mobx react lite -import { observer } from "mobx-react-lite"; -// mobx -import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; - -export const NavbarIssueView = observer(() => { - const store: RootStore = useMobxStore(); - - return
View
; -}); diff --git a/space/components/issues/navbar/search.tsx b/space/components/issues/navbar/search.tsx deleted file mode 100644 index d1cafea6a..000000000 --- a/space/components/issues/navbar/search.tsx +++ /dev/null @@ -1,13 +0,0 @@ -"use client"; - -// mobx react lite -import { observer } from "mobx-react-lite"; -// mobx -import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; - -export const NavbarSearch = observer(() => { - const store: RootStore = useMobxStore(); - - return
; -}); diff --git a/space/components/issues/peek-overview/full-screen-peek-view.tsx b/space/components/issues/peek-overview/full-screen-peek-view.tsx index a40e6b16a..3a66c9abe 100644 --- a/space/components/issues/peek-overview/full-screen-peek-view.tsx +++ b/space/components/issues/peek-overview/full-screen-peek-view.tsx @@ -1,7 +1,4 @@ -import { useEffect } from "react"; import { observer } from "mobx-react-lite"; -// lib -import { useMobxStore } from "lib/mobx/store-provider"; // components import { PeekOverviewHeader, @@ -22,19 +19,19 @@ export const FullScreenPeekView: React.FC = observer((props) => { const { handleClose, issueDetails } = props; return ( -
-
+
+
{issueDetails ? ( -
+
{/* issue title and description */}
{/* divider */} -
+
{/* issue activity/comments */}
@@ -43,7 +40,7 @@ export const FullScreenPeekView: React.FC = observer((props) => { ) : ( -
+
diff --git a/space/components/issues/peek-overview/issue-activity.tsx b/space/components/issues/peek-overview/issue-activity.tsx index 5bb846214..2d173487c 100644 --- a/space/components/issues/peek-overview/issue-activity.tsx +++ b/space/components/issues/peek-overview/issue-activity.tsx @@ -18,16 +18,18 @@ type Props = { issueDetails: IIssue; }; -export const PeekOverviewIssueActivity: React.FC = observer((props) => { +export const PeekOverviewIssueActivity: React.FC = observer(() => { + // router const router = useRouter(); const { workspace_slug } = router.query; - - const { issueDetails: issueDetailStore, project: projectStore, user: userStore } = useMobxStore(); - + // store + const { + issueDetails: issueDetailStore, + project: projectStore, + user: { currentUser }, + } = useMobxStore(); const comments = issueDetailStore.details[issueDetailStore.peekId || ""]?.comments || []; - const user = userStore?.currentUser; - return (

Activity

@@ -38,17 +40,17 @@ export const PeekOverviewIssueActivity: React.FC = observer((props) => { ))}
- {user ? ( + {currentUser ? ( <> {projectStore.deploySettings?.comments && (
- +
)} ) : ( -
-

+

+

Sign in to add your comment

diff --git a/space/components/issues/peek-overview/layout.tsx b/space/components/issues/peek-overview/layout.tsx index a3d7386eb..121bb9164 100644 --- a/space/components/issues/peek-overview/layout.tsx +++ b/space/components/issues/peek-overview/layout.tsx @@ -13,16 +13,15 @@ import { useMobxStore } from "lib/mobx/store-provider"; type Props = {}; -export const IssuePeekOverview: React.FC = observer((props) => { +export const IssuePeekOverview: React.FC = observer(() => { + // states const [isSidePeekOpen, setIsSidePeekOpen] = useState(false); const [isModalPeekOpen, setIsModalPeekOpen] = useState(false); - // router const router = useRouter(); const { workspace_slug, project_slug, peekId, board } = router.query; // store const { issueDetails: issueDetailStore, issue: issueStore } = useMobxStore(); - const issueDetails = issueDetailStore.peekId && peekId ? issueDetailStore.details[peekId.toString()] : undefined; useEffect(() => { @@ -75,7 +74,7 @@ export const IssuePeekOverview: React.FC = observer((props) => { leaveFrom="translate-x-0" leaveTo="translate-x-full" > - + @@ -105,7 +104,7 @@ export const IssuePeekOverview: React.FC = observer((props) => { >
diff --git a/space/components/ui/tooltip.tsx b/space/components/ui/tooltip.tsx index 994c0f32a..64876ffc0 100644 --- a/space/components/ui/tooltip.tsx +++ b/space/components/ui/tooltip.tsx @@ -1,5 +1,4 @@ import React from "react"; - // next-themes import { useTheme } from "next-themes"; // tooltip2 @@ -50,9 +49,9 @@ export const Tooltip: React.FC = ({ hoverCloseDelay={closeDelay} content={
{tooltipHeading && (
diff --git a/space/components/views/login.tsx b/space/components/views/login.tsx index 406d6be98..2f4d0946c 100644 --- a/space/components/views/login.tsx +++ b/space/components/views/login.tsx @@ -10,7 +10,7 @@ export const LoginView = observer(() => { return ( <> {userStore?.loader ? ( -
Loading
+
Loading
// TODO: Add spinner instead ) : ( <>{userStore.currentUser ? : } )} diff --git a/space/components/views/project-details.tsx b/space/components/views/project-details.tsx index 1c9c6ddc9..cd2658279 100644 --- a/space/components/views/project-details.tsx +++ b/space/components/views/project-details.tsx @@ -1,9 +1,6 @@ import { useEffect } from "react"; - import Image from "next/image"; import { useRouter } from "next/router"; - -// mobx import { observer } from "mobx-react-lite"; // components import { IssueListView } from "components/issues/board-views/list"; @@ -20,7 +17,7 @@ import SomethingWentWrongImage from "public/something-went-wrong.svg"; export const ProjectDetailsView = observer(() => { const router = useRouter(); - const { workspace_slug, project_slug, states, labels, priorities, board, peekId } = router.query; + const { workspace_slug, project_slug, states, labels, priorities, peekId } = router.query; const { issue: issueStore, @@ -53,22 +50,22 @@ export const ProjectDetailsView = observer(() => { }, [peekId, issueDetailStore, project_slug, workspace_slug]); return ( -
+
{workspace_slug && } {issueStore?.loader && !issueStore.issues ? ( -
Loading...
+
Loading...
) : ( <> {issueStore?.error ? ( -
+
-
-
+
+
Oops! Something went wrong
-

Oops! Something went wrong.

+

Oops! Something went wrong.

The public board does not exist. Please check the URL.

@@ -76,12 +73,12 @@ export const ProjectDetailsView = observer(() => { projectStore?.activeBoard && ( <> {projectStore?.activeBoard === "list" && ( -
+
)} {projectStore?.activeBoard === "kanban" && ( -
+
)} diff --git a/space/lib/mobx/store-init.tsx b/space/lib/mobx/store-init.tsx index 4fc761ad1..50bf1a853 100644 --- a/space/lib/mobx/store-init.tsx +++ b/space/lib/mobx/store-init.tsx @@ -1,5 +1,3 @@ -"use client"; - import { useEffect } from "react"; // next imports import { useRouter } from "next/router"; @@ -15,12 +13,6 @@ const MobxStoreInit = () => { const router = useRouter(); const { states, labels, priorities } = router.query as { states: string[]; labels: string[]; priorities: string[] }; - // useEffect(() => { - // store.issue.userSelectedLabels = labels || []; - // store.issue.userSelectedPriorities = priorities || []; - // store.issue.userSelectedStates = states || []; - // }, [store.issue]); - useEffect(() => { const authToken = Cookie.get("accessToken") || null; if (authToken) userStore.fetchCurrentUser(); diff --git a/space/package.json b/space/package.json index 9d2521e01..8ea815c58 100644 --- a/space/package.json +++ b/space/package.json @@ -17,10 +17,10 @@ "@emotion/styled": "^11.11.0", "@headlessui/react": "^1.7.13", "@mui/material": "^5.14.1", + "@plane/document-editor": "*", "@plane/lite-text-editor": "*", "@plane/rich-text-editor": "*", "@plane/ui": "*", - "@plane/document-editor": "*", "axios": "^1.3.4", "clsx": "^2.0.0", "js-cookie": "^3.0.1", @@ -28,7 +28,7 @@ "lucide-react": "^0.293.0", "mobx": "^6.10.0", "mobx-react-lite": "^4.0.3", - "next": "12.3.2", + "next": "^14.0.3", "next-images": "^1.8.5", "next-themes": "^0.2.1", "nprogress": "^0.2.0", diff --git a/space/pages/_app.tsx b/space/pages/_app.tsx index 7e00f4d8c..c5b66ca07 100644 --- a/space/pages/_app.tsx +++ b/space/pages/_app.tsx @@ -5,7 +5,6 @@ import { ThemeProvider } from "next-themes"; import "styles/globals.css"; import "styles/editor.css"; import "styles/table.css"; - // contexts import { ToastContextProvider } from "contexts/toast.context"; // mobx store provider diff --git a/space/pages/_document.tsx b/space/pages/_document.tsx index ca023f4a4..bf83a722c 100644 --- a/space/pages/_document.tsx +++ b/space/pages/_document.tsx @@ -5,7 +5,7 @@ class MyDocument extends Document { return ( - +
diff --git a/yarn.lock b/yarn.lock index c383db186..4f8f355cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1558,11 +1558,6 @@ prop-types "^15.8.1" react-is "^18.2.0" -"@next/env@12.3.2": - version "12.3.2" - resolved "https://registry.yarnpkg.com/@next/env/-/env-12.3.2.tgz#fb819366771f5721e9438ca3a42ad18684f0949b" - integrity sha512-upwtMaHxlv/udAWGq0kE+rg8huwmcxQPsKZFhS1R5iVO323mvxEBe1YrSXe1awLbg9sTIuEHbgxjLLt7JbeuAQ== - "@next/env@14.0.3": version "14.0.3" resolved "https://registry.yarnpkg.com/@next/env/-/env-14.0.3.tgz#9a58b296e7ae04ffebce8a4e5bd0f87f71de86bd" @@ -1596,111 +1591,46 @@ dependencies: glob "7.1.7" -"@next/swc-android-arm-eabi@12.3.2": - version "12.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.2.tgz#806e3be9741bc14aafdfad0f0c4c6a8de5b77ee1" - integrity sha512-r2rrz+DZ8YYGqzVrbRrpP6GKzwozpOrnFbErc4k36vUTSFMag9yQahZfaBe06JYdqu/e5yhm/saIDEaSVPRP4g== - -"@next/swc-android-arm64@12.3.2": - version "12.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-12.3.2.tgz#f9ec6b7fc746832a217ad6bb5478624d1a9a9822" - integrity sha512-B+TINJhCf+CrY1+b3/JWQlkecv53rAGa/gA7gi5B1cnBa/2Uvoe+Ue0JeCefTjfiyl1ScsyNx+NcESY8Ye2Ngg== - -"@next/swc-darwin-arm64@12.3.2": - version "12.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.2.tgz#97c532d35c66ce6b6941ae24b5b8b267b9b0d0d8" - integrity sha512-PTUfe1ZrwjsiuTmr3bOM9lsoy5DCmfYsLOUF9ZVhtbi5MNJVmUTy4VZ06GfrvnCO5hGCr48z3vpFE9QZ0qLcPw== - "@next/swc-darwin-arm64@14.0.3": version "14.0.3" resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.3.tgz#b1a0440ffbf69056451947c4aea5b6d887e9fbbc" integrity sha512-64JbSvi3nbbcEtyitNn2LEDS/hcleAFpHdykpcnrstITFlzFgB/bW0ER5/SJJwUPj+ZPY+z3e+1jAfcczRLVGw== -"@next/swc-darwin-x64@12.3.2": - version "12.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.2.tgz#e0cb4ff4b11faaff3a891bd1d18ed72f71e30ebe" - integrity sha512-1HkjmS9awwlaeEY8Y01nRSNkSv3y+qnC/mjMPe/W66hEh3QKa/LQHqHeS7NOdEs19B2mhZ7w+EgMRXdLQ0Su8w== - "@next/swc-darwin-x64@14.0.3": version "14.0.3" resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.3.tgz#48b527ef7eb5dbdcaf62fd107bc3a78371f36f09" integrity sha512-RkTf+KbAD0SgYdVn1XzqE/+sIxYGB7NLMZRn9I4Z24afrhUpVJx6L8hsRnIwxz3ERE2NFURNliPjJ2QNfnWicQ== -"@next/swc-freebsd-x64@12.3.2": - version "12.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.2.tgz#d7b93dd344cb67d1969565d0796c7b7d0217fccf" - integrity sha512-h5Mx0BKDCJ5Vu/U8e07esF6PjPv1EJgmRbYWTUZMAflu13MQpCJkKEJir7+BeRfTXRfgFf+llc7uocrpd7mcrg== - -"@next/swc-linux-arm-gnueabihf@12.3.2": - version "12.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.3.2.tgz#c2170a89effe00fdd65798c99684fd93a02b197c" - integrity sha512-EuRZAamoxfe/WoWRaC0zsCAoE4gs/mEhilcloNM4J5Mnb3PLY8PZV394W7t5tjBjItMCF7l2Ebwjwtm46tq2RA== - -"@next/swc-linux-arm64-gnu@12.3.2": - version "12.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.2.tgz#26df7d7cdc18cf413f12a408179ee4ac315f383a" - integrity sha512-T9GCFyOIb4S3acA9LqflUYD+QZ94iZketHCqKdoO0Nx0OCHIgGJV5rotDe8TDXwh/goYpIfyHU4j1qqw4w4VnA== - "@next/swc-linux-arm64-gnu@14.0.3": version "14.0.3" resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.3.tgz#0a36475a38b2855ab8ea0fe8b56899bc90184c0f" integrity sha512-3tBWGgz7M9RKLO6sPWC6c4pAw4geujSwQ7q7Si4d6bo0l6cLs4tmO+lnSwFp1Tm3lxwfMk0SgkJT7EdwYSJvcg== -"@next/swc-linux-arm64-musl@12.3.2": - version "12.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.2.tgz#fd42232a6b10d9f9a4f71433d59c280a4532d06f" - integrity sha512-hxNVZS6L3c2z3l9EH2GP0MGQ9exu6O8cohYNZyqC9WUl6C03sEn8xzDH1y+NgD3fVurvYkGU5F0PDddJJLfDIw== - "@next/swc-linux-arm64-musl@14.0.3": version "14.0.3" resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.3.tgz#25328a9f55baa09fde6364e7e47ade65c655034f" integrity sha512-v0v8Kb8j8T23jvVUWZeA2D8+izWspeyeDGNaT2/mTHWp7+37fiNfL8bmBWiOmeumXkacM/AB0XOUQvEbncSnHA== -"@next/swc-linux-x64-gnu@12.3.2": - version "12.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.2.tgz#5307579e3d8fbdb03adbe6cfc915b51548e0a103" - integrity sha512-fCPkLuwDwY8/QeXxciJJjDHG09liZym/Bhb4A+RLFQ877wUkwFsNWDUTSdUx0YXlYK/1gf67BKauqKkOKp6CYw== - "@next/swc-linux-x64-gnu@14.0.3": version "14.0.3" resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.3.tgz#594b747e3c8896b2da67bba54fcf8a6b5a410e5e" integrity sha512-VM1aE1tJKLBwMGtyBR21yy+STfl0MapMQnNrXkxeyLs0GFv/kZqXS5Jw/TQ3TSUnbv0QPDf/X8sDXuMtSgG6eg== -"@next/swc-linux-x64-musl@12.3.2": - version "12.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.3.2.tgz#d5cb920a825a8dc80ffba8a6b797fb845af0b84c" - integrity sha512-o+GifBIQ2K+/MEFxHsxUZoU3bsuVFLXZYWd3idimFHiVdDCVYiKsY6mYMmKDlucX+9xRyOCkKL9Tjf+3tuXJpw== - "@next/swc-linux-x64-musl@14.0.3": version "14.0.3" resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.3.tgz#a02da58fc6ecad8cf5c5a2a96a7f6030ec7f6215" integrity sha512-64EnmKy18MYFL5CzLaSuUn561hbO1Gk16jM/KHznYP3iCIfF9e3yULtHaMy0D8zbHfxset9LTOv6cuYKJgcOxg== -"@next/swc-win32-arm64-msvc@12.3.2": - version "12.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.3.2.tgz#2a0d619e5bc0cec17ed093afd1ca6b1c37c2690c" - integrity sha512-crii66irzGGMSUR0L8r9+A06eTv7FTXqw4rgzJ33M79EwQJOdpY7RVKXLQMurUhniEeQEEOfamiEdPIi/qxisw== - "@next/swc-win32-arm64-msvc@14.0.3": version "14.0.3" resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.3.tgz#bf2be23d3ba2ebd0d4a9376a31f783efdb677b48" integrity sha512-WRDp8QrmsL1bbGtsh5GqQ/KWulmrnMBgbnb+59qNTW1kVi1nG/2ndZLkcbs2GX7NpFLlToLRMWSQXmPzQm4tog== -"@next/swc-win32-ia32-msvc@12.3.2": - version "12.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.3.2.tgz#769bef60d0d678c3d7606a4dc7fee018d6199227" - integrity sha512-5hRUSvn3MdQ4nVRu1rmKxq5YJzpTtZfaC/NyGw6wa4NSF1noUn/pdQGUr+I5Qz3CZkd1gZzzC0eaXQHlrk0E2g== - "@next/swc-win32-ia32-msvc@14.0.3": version "14.0.3" resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.3.tgz#839f8de85a4bf2c3c69242483ab87cb916427551" integrity sha512-EKffQeqCrj+t6qFFhIFTRoqb2QwX1mU7iTOvMyLbYw3QtqTw9sMwjykyiMlZlrfm2a4fA84+/aeW+PMg1MjuTg== -"@next/swc-win32-x64-msvc@12.3.2": - version "12.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.2.tgz#45beb4b9d28e6dd6abf63cab1c5b92dc84323a6b" - integrity sha512-tpQJYUH+TzPMIsdVl9fH8uDg47iwiNjKY+8e9da3dXqlkztKzjSw0OwSADoqh3KrifplXeKSta+BBGLdBqg3sg== - "@next/swc-win32-x64-msvc@14.0.3": version "14.0.3" resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.3.tgz#27b623612b1d0cea6efe0a0d31aa1a335fc99647" @@ -2337,13 +2267,6 @@ magic-string "^0.25.0" string.prototype.matchall "^4.0.6" -"@swc/helpers@0.4.11": - version "0.4.11" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.4.11.tgz#db23a376761b3d31c26502122f349a21b592c8de" - integrity sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw== - dependencies: - tslib "^2.4.0" - "@swc/helpers@0.5.2": version "0.5.2" resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.2.tgz#85ea0c76450b61ad7d10a37050289eded783c27d" @@ -6504,7 +6427,7 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nanoid@^3.3.4, nanoid@^3.3.6: +nanoid@^3.3.6: version "3.3.7" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== @@ -6549,32 +6472,6 @@ next-themes@^0.2.1: resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.2.1.tgz#0c9f128e847979daf6c67f70b38e6b6567856e45" integrity sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A== -next@12.3.2: - version "12.3.2" - resolved "https://registry.yarnpkg.com/next/-/next-12.3.2.tgz#3a3356a8d752726128825a8bdf17f2a3b3f861cf" - integrity sha512-orzvvebCwOqaz1eA5ZA0R5dbKxqtJyw7yeig7kDspu6p8OrplfyelzpvMHcDTKscv/l0nn/0l0v3mSsE8w4k7A== - dependencies: - "@next/env" "12.3.2" - "@swc/helpers" "0.4.11" - caniuse-lite "^1.0.30001406" - postcss "8.4.14" - styled-jsx "5.0.7" - use-sync-external-store "1.2.0" - optionalDependencies: - "@next/swc-android-arm-eabi" "12.3.2" - "@next/swc-android-arm64" "12.3.2" - "@next/swc-darwin-arm64" "12.3.2" - "@next/swc-darwin-x64" "12.3.2" - "@next/swc-freebsd-x64" "12.3.2" - "@next/swc-linux-arm-gnueabihf" "12.3.2" - "@next/swc-linux-arm64-gnu" "12.3.2" - "@next/swc-linux-arm64-musl" "12.3.2" - "@next/swc-linux-x64-gnu" "12.3.2" - "@next/swc-linux-x64-musl" "12.3.2" - "@next/swc-win32-arm64-msvc" "12.3.2" - "@next/swc-win32-ia32-msvc" "12.3.2" - "@next/swc-win32-x64-msvc" "12.3.2" - next@^14.0.3: version "14.0.3" resolved "https://registry.yarnpkg.com/next/-/next-14.0.3.tgz#8d801a08eaefe5974203d71092fccc463103a03f" @@ -7010,15 +6907,6 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@8.4.14: - version "8.4.14" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf" - integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig== - dependencies: - nanoid "^3.3.4" - picocolors "^1.0.0" - source-map-js "^1.0.2" - postcss@8.4.31, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.29: version "8.4.31" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" @@ -8121,11 +8009,6 @@ style-to-object@^0.4.0: dependencies: inline-style-parser "0.1.1" -styled-jsx@5.0.7: - version "5.0.7" - resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.7.tgz#be44afc53771b983769ac654d355ca8d019dff48" - integrity sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA== - styled-jsx@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.1.tgz#839a1c3aaacc4e735fed0781b8619ea5d0009d1f" @@ -8797,7 +8680,7 @@ use-sidecar@^1.1.2: detect-node-es "^1.1.0" tslib "^2.0.0" -use-sync-external-store@1.2.0, use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.0: +use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== From ab37ce979a1b9b747538dfd5a507e408b7cb224c Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 1 Dec 2023 13:27:16 +0530 Subject: [PATCH 05/17] fix: view modal overlapping (#2956) --- .../command-palette/command-pallette.tsx | 14 +------------ web/components/headers/project-views.tsx | 11 ++++------ web/store/command-palette.store.ts | 20 ++++++++++++++++++- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/web/components/command-palette/command-pallette.tsx b/web/components/command-palette/command-pallette.tsx index a4f5279a2..6891279b1 100644 --- a/web/components/command-palette/command-pallette.tsx +++ b/web/components/command-palette/command-pallette.tsx @@ -55,22 +55,10 @@ export const CommandPalette: FC = observer(() => { toggleBulkDeleteIssueModal, isDeleteIssueModalOpen, toggleDeleteIssueModal, - + isAnyModalOpen, createIssueStoreType, } = commandPalette; - const isAnyModalOpen = Boolean( - isCreateIssueModalOpen || - isCreateCycleModalOpen || - isCreatePageModalOpen || - isCreateProjectModalOpen || - isCreateModuleModalOpen || - isCreateViewModalOpen || - isShortcutModalOpen || - isBulkDeleteIssueModalOpen || - isDeleteIssueModalOpen - ); - const { setToastAlert } = useToast(); const { data: issueDetails } = useSWR( diff --git a/web/components/headers/project-views.tsx b/web/components/headers/project-views.tsx index 66e320872..92e876557 100644 --- a/web/components/headers/project-views.tsx +++ b/web/components/headers/project-views.tsx @@ -1,4 +1,3 @@ -import { useState } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { Plus } from "lucide-react"; @@ -15,18 +14,16 @@ export const ProjectViewsHeader: React.FC = observer(() => { // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; - // states - const [createViewModal, setCreateViewModal] = useState(false); - const { project: projectStore } = useMobxStore(); + const { project: projectStore, commandPalette } = useMobxStore(); const { currentProjectDetails } = projectStore; return ( <> {workspaceSlug && projectId && ( setCreateViewModal(false)} + isOpen={commandPalette.isCreateViewModalOpen} + onClose={() => commandPalette.toggleCreateViewModal(false)} workspaceSlug={workspaceSlug.toString()} projectId={projectId.toString()} /> @@ -69,7 +66,7 @@ export const ProjectViewsHeader: React.FC = observer(() => { variant="primary" size="sm" prependIcon={} - onClick={() => setCreateViewModal(true)} + onClick={() => commandPalette.toggleCreateViewModal(true)} > Create View diff --git a/web/store/command-palette.store.ts b/web/store/command-palette.store.ts index 1c9fc785d..b2dac49c5 100644 --- a/web/store/command-palette.store.ts +++ b/web/store/command-palette.store.ts @@ -1,4 +1,4 @@ -import { observable, action, makeObservable } from "mobx"; +import { observable, action, makeObservable, computed } from "mobx"; // types import { RootStore } from "./root"; // services @@ -30,6 +30,9 @@ export interface ICommandPaletteStore { isDeleteIssueModalOpen: boolean; isBulkDeleteIssueModalOpen: boolean; + // computed + isAnyModalOpen: boolean; + toggleCommandPaletteModal: (value?: boolean) => void; toggleShortcutModal: (value?: boolean) => void; toggleCreateProjectModal: (value?: boolean) => void; @@ -77,6 +80,7 @@ class CommandPaletteStore implements ICommandPaletteStore { isDeleteIssueModalOpen: observable.ref, isBulkDeleteIssueModalOpen: observable.ref, // computed + isAnyModalOpen: computed, // projectPages: computed, // action toggleCommandPaletteModal: action, @@ -96,6 +100,20 @@ class CommandPaletteStore implements ICommandPaletteStore { this.pageService = new PageService(); } + get isAnyModalOpen() { + return Boolean( + this.isCreateIssueModalOpen || + this.isCreateCycleModalOpen || + this.isCreatePageModalOpen || + this.isCreateProjectModalOpen || + this.isCreateModuleModalOpen || + this.isCreateViewModalOpen || + this.isShortcutModalOpen || + this.isBulkDeleteIssueModalOpen || + this.isDeleteIssueModalOpen + ); + } + toggleCommandPaletteModal = (value?: boolean) => { if (value) { this.isCommandPaletteOpen = value; From 5993fc38f02c62fc104380986190fc315e643d52 Mon Sep 17 00:00:00 2001 From: Manish Gupta <59428681+mguptahub@users.noreply.github.com> Date: Fri, 1 Dec 2023 13:40:16 +0530 Subject: [PATCH 06/17] branch build fixes (#2961) --- .github/workflows/build-branch.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-branch.yml b/.github/workflows/build-branch.yml index e65035482..47bbb94c0 100644 --- a/.github/workflows/build-branch.yml +++ b/.github/workflows/build-branch.yml @@ -152,7 +152,7 @@ jobs: if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-private:latest elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "release" ] || [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "preview" ]; then - TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-private:preview",${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:preview + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-private:preview,${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:preview else TAG=${{ env.BACKEND_TAG }} fi From 2b63827caa6ee5a48d776f089c0c36eb59636bc8 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 1 Dec 2023 15:12:25 +0530 Subject: [PATCH 07/17] chore: create issue modal improvement (#2960) --- web/components/issues/select/cycle.tsx | 43 +++++++++++++++++-------- web/components/issues/select/module.tsx | 43 +++++++++++++++++-------- 2 files changed, 58 insertions(+), 28 deletions(-) diff --git a/web/components/issues/select/cycle.tsx b/web/components/issues/select/cycle.tsx index 04e251f01..ce1e89d99 100644 --- a/web/components/issues/select/cycle.tsx +++ b/web/components/issues/select/cycle.tsx @@ -104,24 +104,39 @@ export const IssueCycleSelect: React.FC = observer((props
{filteredOptions ? ( filteredOptions.length > 0 ? ( - filteredOptions.map((option) => ( + <> + {filteredOptions.map((option) => ( + + `flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${ + active && !selected ? "bg-custom-background-80" : "" + } w-full truncate ${selected ? "text-custom-text-100" : "text-custom-text-200"}` + } + > + {({ selected }) => ( + <> + {option.content} + {selected && } + + )} + + ))} - `flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${ - active && !selected ? "bg-custom-background-80" : "" - } w-full truncate ${selected ? "text-custom-text-100" : "text-custom-text-200"}` + key="none" + value="" + className={({ active }) => + `flex items-center justify-between gap-2 ${ + active ? "bg-custom-background-80" : "" + } cursor-pointer select-none truncate rounded px-1 py-1.5 w-full text-custom-text-100` } > - {({ selected }) => ( - <> - {option.content} - {selected && } - - )} +
+ None +
- )) + ) : (

No matching results

diff --git a/web/components/issues/select/module.tsx b/web/components/issues/select/module.tsx index 3174f32c1..98d242fbe 100644 --- a/web/components/issues/select/module.tsx +++ b/web/components/issues/select/module.tsx @@ -98,24 +98,39 @@ export const IssueModuleSelect: React.FC = observer((pro
{filteredOptions ? ( filteredOptions.length > 0 ? ( - filteredOptions.map((option) => ( + <> + {filteredOptions.map((option) => ( + + `flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${ + active && !selected ? "bg-custom-background-80" : "" + } w-full truncate ${selected ? "text-custom-text-100" : "text-custom-text-200"}` + } + > + {({ selected }) => ( + <> + {option.content} + {selected && } + + )} + + ))} - `flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${ - active && !selected ? "bg-custom-background-80" : "" - } w-full truncate ${selected ? "text-custom-text-100" : "text-custom-text-200"}` + key="none" + value="" + className={({ active }) => + `flex items-center justify-between gap-2 ${ + active ? "bg-custom-background-80" : "" + } cursor-pointer select-none truncate rounded px-1 py-1.5 w-full text-custom-text-100` } > - {({ selected }) => ( - <> - {option.content} - {selected && } - - )} +
+ None +
- )) + ) : (

No matching results

From dec1fb5a639f6cec746d7645fcb627de3ecdf7fe Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 1 Dec 2023 15:14:18 +0530 Subject: [PATCH 08/17] chore: profile issue display filters content updated (#2963) --- web/constants/issue.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/constants/issue.ts b/web/constants/issue.ts index 7979672fd..b47321881 100644 --- a/web/constants/issue.ts +++ b/web/constants/issue.ts @@ -113,7 +113,7 @@ export const ISSUE_EXTRA_OPTIONS: { title: string; }[] = [ { key: "sub_issue", title: "Show sub-issues" }, // in spreadsheet its always false - { key: "show_empty_groups", title: "Show empty states" }, // filter on front-end + { key: "show_empty_groups", title: "Show empty groups" }, // filter on front-end ]; export const ISSUE_LAYOUTS: { From 54464472316b2bdcb4a9097b305230319c8d7fcc Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Fri, 1 Dec 2023 15:16:36 +0530 Subject: [PATCH 09/17] chore: workspace global issues (#2964) * dev: global issues store * build-error: all issues render * build-error: build error resolved in global view store --- web/components/headers/global-issues.tsx | 86 ++-- .../applied-filters/roots/draft-issue.tsx | 2 - .../roots/global-view-root.tsx | 85 ++-- web/components/issues/issue-layouts/index.ts | 6 +- .../quick-action-dropdowns/all-issue.tsx | 114 +++++ .../quick-action-dropdowns/index.ts | 1 + .../roots/all-issue-layout-root.tsx | 134 ++++++ .../roots/global-view-layout-root.tsx | 118 ----- .../issues/issue-layouts/roots/index.ts | 2 +- .../roots/project-layout-root.tsx | 18 +- .../spreadsheet/spreadsheet-view.tsx | 2 - .../onboarding/onboarding-sidebar.tsx | 4 +- web/components/page-views/signin.tsx | 15 +- web/components/workspace/views/header.tsx | 7 - web/constants/issue.ts | 1 - .../settings-layout/profile/sidebar.tsx | 77 ++-- .../workspace-views/[globalViewId].tsx | 36 +- .../workspace-views/all-issues.tsx | 4 +- .../workspace-views/assigned.tsx | 4 +- .../workspace-views/created.tsx | 4 +- .../workspace-views/subscribed.tsx | 4 +- web/pages/accounts/password.tsx | 9 +- web/services/workspace.service.ts | 6 +- .../global-view/global_view_issues.store.ts | 4 +- web/store/issues/global/filter.store.ts | 402 +++++++++++------- web/store/issues/global/issue.store.ts | 281 ++++-------- web/store/issues/profile/filter.store.ts | 1 + 27 files changed, 732 insertions(+), 695 deletions(-) create mode 100644 web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx create mode 100644 web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx delete mode 100644 web/components/issues/issue-layouts/roots/global-view-layout-root.tsx diff --git a/web/components/headers/global-issues.tsx b/web/components/headers/global-issues.tsx index bb692b4f4..964e14c8b 100644 --- a/web/components/headers/global-issues.tsx +++ b/web/components/headers/global-issues.tsx @@ -2,8 +2,6 @@ import { useCallback, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; -import useSWR from "swr"; - // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components @@ -17,6 +15,7 @@ import { List, PlusIcon, Sheet } from "lucide-react"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TStaticViewTypes } from "types"; // constants import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; +import { EFilterType } from "store/issues/types"; const GLOBAL_VIEW_LAYOUTS = [ { key: "list", title: "List", link: "/workspace-views", icon: List }, @@ -27,73 +26,55 @@ type Props = { activeLayout: "list" | "spreadsheet"; }; -const STATIC_VIEW_TYPES: TStaticViewTypes[] = ["all-issues", "assigned", "created", "subscribed"]; - export const GlobalIssuesHeader: React.FC = observer((props) => { const { activeLayout } = props; const [createViewModal, setCreateViewModal] = useState(false); const router = useRouter(); - const { workspaceSlug, globalViewId } = router.query; + const { workspaceSlug } = router.query as { workspaceSlug: string }; const { - globalViewFilters: globalViewFiltersStore, - workspaceFilter: workspaceFilterStore, - workspace: workspaceStore, + workspace: { workspaceLabels }, workspaceMember: { workspaceMembers }, - project: projectStore, - } = useMobxStore(); + project: { workspaceProjects }, - const storedFilters = globalViewId ? globalViewFiltersStore.storedFilters[globalViewId.toString()] : undefined; + workspaceGlobalIssuesFilter: { issueFilters, updateFilters }, + } = useMobxStore(); const handleFiltersUpdate = useCallback( (key: keyof IIssueFilterOptions, value: string | string[]) => { - if (!workspaceSlug || !globalViewId) return; - - const newValues = storedFilters?.[key] ?? []; + if (!workspaceSlug) return; + const newValues = issueFilters?.filters?.[key] ?? []; if (Array.isArray(value)) { value.forEach((val) => { if (!newValues.includes(val)) newValues.push(val); }); } else { - if (storedFilters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1); + if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1); else newValues.push(value); } - globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), { - [key]: newValues, - }); + updateFilters(workspaceSlug, EFilterType.FILTERS, { [key]: newValues }); }, - [globalViewId, globalViewFiltersStore, storedFilters, workspaceSlug] + [workspaceSlug, issueFilters, updateFilters] ); - const handleDisplayFiltersUpdate = useCallback( + const handleDisplayFilters = useCallback( (updatedDisplayFilter: Partial) => { if (!workspaceSlug) return; - - workspaceFilterStore.updateWorkspaceFilters(workspaceSlug.toString(), { - display_filters: updatedDisplayFilter, - }); + updateFilters(workspaceSlug, EFilterType.DISPLAY_FILTERS, updatedDisplayFilter); }, - [workspaceFilterStore, workspaceSlug] + [workspaceSlug, updateFilters] ); - const handleDisplayPropertiesUpdate = useCallback( + const handleDisplayProperties = useCallback( (property: Partial) => { if (!workspaceSlug) return; - - workspaceFilterStore.updateWorkspaceFilters(workspaceSlug.toString(), { - display_properties: property, - }); + updateFilters(workspaceSlug, EFilterType.DISPLAY_PROPERTIES, property); }, - [workspaceFilterStore, workspaceSlug] - ); - - useSWR( - workspaceSlug ? "USER_WORKSPACE_DISPLAY_FILTERS" : null, - workspaceSlug ? () => workspaceFilterStore.fetchUserWorkspaceFilters(workspaceSlug.toString()) : null + [workspaceSlug, updateFilters] ); return ( @@ -137,32 +118,31 @@ export const GlobalIssuesHeader: React.FC = observer((props) => { ))}
+ {activeLayout === "spreadsheet" && ( <> - {!STATIC_VIEW_TYPES.some((word) => router.pathname.includes(word)) && ( - - m.member) ?? undefined} - projects={workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined} - /> - - )} - + + m.member)} + projects={workspaceProjects ?? undefined} + /> + )} + diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx index b2f4cf426..d69691426 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx @@ -25,9 +25,7 @@ export const DraftIssueAppliedFiltersRoot: React.FC = observer(() => { const appliedFilters: IIssueFilterOptions = {}; Object.entries(userFilters ?? {}).forEach(([key, value]) => { if (!value) return; - if (Array.isArray(value) && value.length === 0) return; - appliedFilters[key as keyof IIssueFilterOptions] = value; }); diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx index 9bc94b0c9..faf836d87 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx @@ -12,85 +12,73 @@ import { Button } from "@plane/ui"; import { areFiltersDifferent } from "helpers/filter.helper"; // types import { IIssueFilterOptions } from "types"; +import { EFilterType } from "store/issues/types"; export const GlobalViewsAppliedFiltersRoot = observer(() => { const router = useRouter(); - const { workspaceSlug, globalViewId } = router.query; + const { workspaceSlug, globalViewId } = router.query as { workspaceSlug: string; globalViewId: string }; const { globalViews: globalViewsStore, globalViewFilters: globalViewFiltersStore, - project: projectStore, - workspace: workspaceStore, + project: { workspaceProjects }, + workspace: { workspaceLabels }, workspaceMember: { workspaceMembers }, + workspaceGlobalIssuesFilter: { issueFilters, updateFilters }, } = useMobxStore(); const viewDetails = globalViewId ? globalViewsStore.globalViewDetails[globalViewId.toString()] : undefined; - const storedFilters = globalViewId ? globalViewFiltersStore.storedFilters[globalViewId.toString()] : undefined; + + const userFilters = issueFilters?.filters; // filters whose value not null or empty array const appliedFilters: IIssueFilterOptions = {}; - Object.entries(storedFilters ?? {}).forEach(([key, value]) => { + Object.entries(userFilters ?? {}).forEach(([key, value]) => { if (!value) return; - if (Array.isArray(value) && value.length === 0) return; - appliedFilters[key as keyof IIssueFilterOptions] = value; }); const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => { - if (!globalViewId) return; - - // remove all values of the key if value is null if (!value) { - globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), { - [key]: null, - }); + updateFilters(workspaceSlug, EFilterType.FILTERS, { [key]: null }); return; } - // remove the passed value from the key - let newValues = globalViewFiltersStore.storedFilters?.[globalViewId.toString()]?.[key] ?? []; + let newValues = userFilters?.[key] ?? []; newValues = newValues.filter((val) => val !== value); - - globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), { - [key]: newValues, - }); + updateFilters(workspaceSlug, EFilterType.FILTERS, { [key]: newValues }); }; const handleClearAllFilters = () => { - if (!globalViewId || !storedFilters) return; - + if (!workspaceSlug) return; const newFilters: IIssueFilterOptions = {}; - Object.keys(storedFilters).forEach((key) => { + Object.keys(userFilters ?? {}).forEach((key) => { newFilters[key as keyof IIssueFilterOptions] = null; }); - - globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), { - ...newFilters, - }); + updateFilters(workspaceSlug, EFilterType.FILTERS, { ...newFilters }); }; - const handleUpdateView = () => { - if (!workspaceSlug || !globalViewId || !viewDetails) return; + // const handleUpdateView = () => { + // if (!workspaceSlug || !globalViewId || !viewDetails) return; - globalViewsStore.updateGlobalView(workspaceSlug.toString(), globalViewId.toString(), { - query_data: { - ...viewDetails.query_data, - filters: { - ...(storedFilters ?? {}), - }, - }, - }); - }; + // globalViewsStore.updateGlobalView(workspaceSlug.toString(), globalViewId.toString(), { + // query_data: { + // ...viewDetails.query_data, + // filters: { + // ...(storedFilters ?? {}), + // }, + // }, + // }); + // }; // update stored filters when view details are fetched - useEffect(() => { - if (!globalViewId || !viewDetails) return; + // useEffect(() => { + // if (!globalViewId || !viewDetails) return; - if (!globalViewFiltersStore.storedFilters[globalViewId.toString()]) - globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), viewDetails?.query_data?.filters ?? {}); - }, [globalViewId, globalViewFiltersStore, viewDetails]); + // if (!globalViewFiltersStore.storedFilters[globalViewId.toString()]) + // globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), viewDetails?.query_data?.filters ?? {}); + // }, [globalViewId, globalViewFiltersStore, viewDetails]); // return if no filters are applied if (Object.keys(appliedFilters).length === 0) return null; @@ -98,18 +86,19 @@ export const GlobalViewsAppliedFiltersRoot = observer(() => { return (
m.member)} + projects={workspaceProjects ?? undefined} + appliedFilters={appliedFilters ?? {}} handleClearAllFilters={handleClearAllFilters} handleRemoveFilter={handleRemoveFilter} - labels={workspaceStore.workspaceLabels ?? undefined} - members={workspaceMembers?.map((m) => m.member)} - projects={workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined} /> - {storedFilters && viewDetails && areFiltersDifferent(storedFilters, viewDetails.query_data.filters ?? {}) && ( + + {/* {storedFilters && viewDetails && areFiltersDifferent(storedFilters, viewDetails.query_data.filters ?? {}) && ( - )} + )} */}
); }); diff --git a/web/components/issues/issue-layouts/index.ts b/web/components/issues/issue-layouts/index.ts index aa82dc686..3a8a2baac 100644 --- a/web/components/issues/issue-layouts/index.ts +++ b/web/components/issues/issue-layouts/index.ts @@ -3,6 +3,9 @@ export * from "./filters"; export * from "./empty-states"; export * from "./quick-action-dropdowns"; +// roots +export * from "./roots"; + // layouts export * from "./list"; export * from "./calendar"; @@ -10,6 +13,5 @@ export * from "./gantt"; export * from "./kanban"; export * from "./spreadsheet"; +// properties export * from "./properties"; - -export * from "./roots"; diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx new file mode 100644 index 000000000..f823a3e15 --- /dev/null +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx @@ -0,0 +1,114 @@ +import { useState } from "react"; +import { useRouter } from "next/router"; +import { CustomMenu } from "@plane/ui"; +import { Copy, Link, Pencil, Trash2 } from "lucide-react"; +// hooks +import useToast from "hooks/use-toast"; +// components +import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues"; +// helpers +import { copyUrlToClipboard } from "helpers/string.helper"; +// types +import { IIssue } from "types"; +import { IQuickActionProps } from "../list/list-view-types"; +import { EProjectStore } from "store/command-palette.store"; + +export const AllIssueQuickActions: React.FC = (props) => { + const { issue, handleDelete, handleUpdate } = props; + + const router = useRouter(); + const { workspaceSlug } = router.query; + + // states + const [createUpdateIssueModal, setCreateUpdateIssueModal] = useState(false); + const [issueToEdit, setIssueToEdit] = useState(null); + const [deleteIssueModal, setDeleteIssueModal] = useState(false); + + const { setToastAlert } = useToast(); + + const handleCopyIssueLink = () => { + copyUrlToClipboard(`/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() => + setToastAlert({ + type: "success", + title: "Link copied", + message: "Issue link copied to clipboard", + }) + ); + }; + + return ( + <> + setDeleteIssueModal(false)} + onSubmit={handleDelete} + /> + { + setCreateUpdateIssueModal(false); + setIssueToEdit(null); + }} + // pre-populate date only if not editing + prePopulateData={!issueToEdit && createUpdateIssueModal ? { ...issue, name: `${issue.name} (copy)` } : {}} + data={issueToEdit} + onSubmit={async (data) => { + if (issueToEdit && handleUpdate) handleUpdate({ ...issueToEdit, ...data }); + }} + currentStore={EProjectStore.PROJECT} + /> + + { + e.preventDefault(); + e.stopPropagation(); + handleCopyIssueLink(); + }} + > +
+ + Copy link +
+
+ { + e.preventDefault(); + e.stopPropagation(); + setIssueToEdit(issue); + setCreateUpdateIssueModal(true); + }} + > +
+ + Edit issue +
+
+ { + e.preventDefault(); + e.stopPropagation(); + setCreateUpdateIssueModal(true); + }} + > +
+ + Make a copy +
+
+ { + e.preventDefault(); + e.stopPropagation(); + setDeleteIssueModal(true); + }} + > +
+ + Delete issue +
+
+
+ + ); +}; diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/index.ts b/web/components/issues/issue-layouts/quick-action-dropdowns/index.ts index 642c39032..e38440017 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/index.ts +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/index.ts @@ -2,3 +2,4 @@ export * from "./cycle-issue"; export * from "./module-issue"; export * from "./project-issue"; export * from "./archived-issue"; +export * from "./all-issue"; diff --git a/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx new file mode 100644 index 000000000..a49af10ae --- /dev/null +++ b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx @@ -0,0 +1,134 @@ +import React, { useCallback } from "react"; +import { useRouter } from "next/router"; +import { observer } from "mobx-react-lite"; +import useSWR from "swr"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; +// components +import { GlobalViewEmptyState, GlobalViewsAppliedFiltersRoot } from "components/issues"; +import { SpreadsheetView } from "components/issues/issue-layouts"; +import { AllIssueQuickActions } from "components/issues/issue-layouts/quick-action-dropdowns"; +// ui +import { Spinner } from "@plane/ui"; +// types +import { IIssue, IIssueDisplayFilterOptions, TStaticViewTypes } from "types"; +import { IIssueUnGroupedStructure } from "store/issue"; +import { EIssueActions } from "../types"; + +import { EFilterType, TUnGroupedIssues } from "store/issues/types"; + +type Props = { + type?: TStaticViewTypes | null; +}; + +export const AllIssueLayoutRoot: React.FC = observer((props) => { + const { type = null } = props; + + const router = useRouter(); + const { workspaceSlug, globalViewId } = router.query as { workspaceSlug: string; globalViewId: string }; + + const currentIssueView = type ?? globalViewId; + + const { + workspaceMember: { workspaceMembers }, + workspace: { workspaceLabels }, + globalViews: { fetchAllGlobalViews }, + workspaceGlobalIssues: { loader, getIssues, getIssuesIds, fetchIssues, updateIssue, removeIssue }, + workspaceGlobalIssuesFilter: { currentView, issueFilters, fetchFilters, updateFilters, setCurrentView }, + } = useMobxStore(); + + useSWR(workspaceSlug ? `WORKSPACE_GLOBAL_VIEWS${workspaceSlug}` : null, async () => { + if (workspaceSlug) { + await fetchAllGlobalViews(workspaceSlug); + } + }); + + useSWR( + workspaceSlug && currentIssueView ? `WORKSPACE_GLOBAL_VIEW_ISSUES_${workspaceSlug}_${currentIssueView}` : null, + async () => { + if (workspaceSlug && currentIssueView) { + setCurrentView(currentIssueView); + await fetchAllGlobalViews(workspaceSlug); + await fetchFilters(workspaceSlug, currentIssueView); + await fetchIssues(workspaceSlug, currentIssueView, getIssues ? "mutation" : "init-loader"); + } + } + ); + + const isEditingAllowed = false; + + const issuesResponse = getIssues; + const issueIds = (getIssuesIds ?? []) as TUnGroupedIssues; + const issues = issueIds?.filter((id) => id && issuesResponse?.[id]).map((id) => issuesResponse?.[id]); + + const issueActions = { + [EIssueActions.UPDATE]: async (issue: IIssue) => { + if (!workspaceSlug) return; + + await updateIssue(workspaceSlug, issue.project, issue.id, issue); + }, + [EIssueActions.DELETE]: async (issue: IIssue) => { + if (!workspaceSlug) return; + + await removeIssue(workspaceSlug, issue.project, issue.id); + }, + }; + + const handleIssues = useCallback( + async (issue: IIssue, action: EIssueActions) => { + if (issueActions && action && issue) { + if (action === EIssueActions.UPDATE) await issueActions[action]!(issue); + if (action === EIssueActions.DELETE) await issueActions[action]!(issue); + } + }, + [getIssues] + ); + + const handleDisplayFiltersUpdate = useCallback( + (updatedDisplayFilter: Partial) => { + if (!workspaceSlug) return; + + updateFilters(workspaceSlug, EFilterType.DISPLAY_FILTERS, { ...updatedDisplayFilter }); + }, + [updateFilters, workspaceSlug] + ); + + return ( +
+ {currentView != currentIssueView && loader === "init-loader" ? ( +
+ +
+ ) : ( + <> + + + {Object.keys(getIssues ?? {}).length == 0 && !loader ? ( + <>{/* */} + ) : ( +
+ ( + handleIssues({ ...issue }, EIssueActions.UPDATE)} + handleDelete={async () => handleIssues(issue, EIssueActions.DELETE)} + /> + )} + members={workspaceMembers?.map((m) => m.member)} + labels={workspaceLabels || undefined} + handleIssues={handleIssues} + disableUserActions={isEditingAllowed} + viewId={currentIssueView} + /> +
+ )} + + )} +
+ ); +}); diff --git a/web/components/issues/issue-layouts/roots/global-view-layout-root.tsx b/web/components/issues/issue-layouts/roots/global-view-layout-root.tsx deleted file mode 100644 index 247a2e925..000000000 --- a/web/components/issues/issue-layouts/roots/global-view-layout-root.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import React, { useCallback } from "react"; -import { useRouter } from "next/router"; -import { observer } from "mobx-react-lite"; -import useSWR from "swr"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; -// components -import { GlobalViewEmptyState, GlobalViewsAppliedFiltersRoot, SpreadsheetView } from "components/issues"; -// ui -import { Spinner } from "@plane/ui"; -// types -import { IIssue, IIssueDisplayFilterOptions, TStaticViewTypes } from "types"; - -type Props = { - type?: TStaticViewTypes; -}; - -export const GlobalViewLayoutRoot: React.FC = observer((props) => { - const { type } = props; - - const router = useRouter(); - const { workspaceSlug, globalViewId } = router.query; - - const { - globalViews: globalViewsStore, - globalViewIssues: globalViewIssuesStore, - globalViewFilters: globalViewFiltersStore, - workspaceFilter: workspaceFilterStore, - workspace: workspaceStore, - workspaceMember: { workspaceMembers }, - issueDetail: issueDetailStore, - project: projectStore, - } = useMobxStore(); - - const viewDetails = globalViewId ? globalViewsStore.globalViewDetails[globalViewId.toString()] : undefined; - - const storedFilters = globalViewId ? globalViewFiltersStore.storedFilters[globalViewId.toString()] : undefined; - - const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null; - - useSWR( - workspaceSlug && globalViewId && viewDetails ? `GLOBAL_VIEW_ISSUES_${globalViewId.toString()}` : null, - workspaceSlug && globalViewId && viewDetails - ? () => { - globalViewIssuesStore.fetchViewIssues(workspaceSlug.toString(), globalViewId.toString(), storedFilters ?? {}); - } - : null - ); - - useSWR( - workspaceSlug && type ? `GLOBAL_VIEW_ISSUES_${type.toString()}` : null, - workspaceSlug && type - ? () => { - globalViewIssuesStore.fetchStaticIssues(workspaceSlug.toString(), type); - } - : null - ); - - const handleDisplayFiltersUpdate = useCallback( - (updatedDisplayFilter: Partial) => { - if (!workspaceSlug) return; - - workspaceFilterStore.updateWorkspaceFilters(workspaceSlug.toString(), { - display_filters: updatedDisplayFilter, - }); - }, - [workspaceFilterStore, workspaceSlug] - ); - - const handleUpdateIssue = useCallback( - (issue: IIssue, data: Partial) => { - if (!workspaceSlug) return; - - const payload = { - ...issue, - ...data, - }; - - globalViewIssuesStore.updateIssueStructure(type ?? globalViewId!.toString(), payload); - // issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, data); - }, - [globalViewId, globalViewIssuesStore, workspaceSlug, issueDetailStore] - ); - - const issues = type - ? globalViewIssuesStore.viewIssues?.[type] - : globalViewId - ? globalViewIssuesStore.viewIssues?.[globalViewId.toString()] - : undefined; - - if (!issues) - return ( -
- -
- ); - - return ( -
- - {issues?.length === 0 || !projects || projects?.length === 0 ? ( - - ) : ( -
- {/* m.member)} - labels={workspaceStore.workspaceLabels ? workspaceStore.workspaceLabels : undefined} - disableUserActions={false} - /> */} -
- )} -
- ); -}); diff --git a/web/components/issues/issue-layouts/roots/index.ts b/web/components/issues/issue-layouts/roots/index.ts index 515d73403..72f71aae2 100644 --- a/web/components/issues/issue-layouts/roots/index.ts +++ b/web/components/issues/issue-layouts/roots/index.ts @@ -1,5 +1,5 @@ export * from "./cycle-layout-root"; -export * from "./global-view-layout-root"; +export * from "./all-issue-layout-root"; export * from "./module-layout-root"; export * from "./project-layout-root"; export * from "./project-view-layout-root"; diff --git a/web/components/issues/issue-layouts/roots/project-layout-root.tsx b/web/components/issues/issue-layouts/roots/project-layout-root.tsx index 704e69db0..4255c9280 100644 --- a/web/components/issues/issue-layouts/roots/project-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/project-layout-root.tsx @@ -26,20 +26,12 @@ export const ProjectLayoutRoot: React.FC = observer(() => { projectIssuesFilter: { issueFilters, fetchFilters }, } = useMobxStore(); - useSWR( - workspaceSlug && projectId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null, - async () => { - if (workspaceSlug && projectId) { - await fetchFilters(workspaceSlug, projectId); - await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader"); - } - }, - { - onErrorRetry: (error) => { - if (error.status === 404) return; - }, + useSWR(workspaceSlug && projectId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null, async () => { + if (workspaceSlug && projectId) { + await fetchFilters(workspaceSlug, projectId); + await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader"); } - ); + }); const activeLayout = issueFilters?.displayFilters?.layout; diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx index 680198ac3..f18336e45 100644 --- a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx @@ -77,8 +77,6 @@ export const SpreadsheetView: React.FC = observer((props) => { }; }, []); - console.log("spreadsheet issues", issues); - return (
diff --git a/web/components/onboarding/onboarding-sidebar.tsx b/web/components/onboarding/onboarding-sidebar.tsx index feca84bf6..0a6b909ec 100644 --- a/web/components/onboarding/onboarding-sidebar.tsx +++ b/web/components/onboarding/onboarding-sidebar.tsx @@ -229,9 +229,7 @@ export const OnboardingSidebar: React.FC = (props) => {
diff --git a/web/components/page-views/signin.tsx b/web/components/page-views/signin.tsx index bf4db894a..4aa07147e 100644 --- a/web/components/page-views/signin.tsx +++ b/web/components/page-views/signin.tsx @@ -108,14 +108,13 @@ export const SignInView = observer(() => {

Pages gets a facelift! Write anything and use Galileo to help you start.{" "} - - - Learn more - + + Learn more

diff --git a/web/components/workspace/views/header.tsx b/web/components/workspace/views/header.tsx index aba978094..5cea9b683 100644 --- a/web/components/workspace/views/header.tsx +++ b/web/components/workspace/views/header.tsx @@ -2,8 +2,6 @@ import React, { useEffect, useState } from "react"; import { useRouter } from "next/router"; import Link from "next/link"; import { observer } from "mobx-react-lite"; -import useSWR from "swr"; - // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components @@ -21,11 +19,6 @@ export const GlobalViewsHeader: React.FC = observer(() => { const { globalViews: globalViewsStore } = useMobxStore(); - useSWR( - workspaceSlug ? `GLOBAL_VIEWS_LIST_${workspaceSlug.toString()}` : null, - workspaceSlug ? () => globalViewsStore.fetchAllGlobalViews(workspaceSlug.toString()) : null - ); - // bring the active view to the centre of the header useEffect(() => { if (!globalViewId) return; diff --git a/web/constants/issue.ts b/web/constants/issue.ts index b47321881..965ab69cc 100644 --- a/web/constants/issue.ts +++ b/web/constants/issue.ts @@ -297,7 +297,6 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { filters: ["priority", "state_group", "labels", "assignees", "created_by", "project", "start_date", "target_date"], display_properties: true, display_filters: { - order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"], type: [null, "active", "backlog"], }, extra_options: { diff --git a/web/layouts/settings-layout/profile/sidebar.tsx b/web/layouts/settings-layout/profile/sidebar.tsx index 487c53078..759c29429 100644 --- a/web/layouts/settings-layout/profile/sidebar.tsx +++ b/web/layouts/settings-layout/profile/sidebar.tsx @@ -102,19 +102,18 @@ export const ProfileLayoutSidebar = observer(() => { } ${sidebarCollapsed ? "left-0" : "-left-full md:left-0"}`} >
- - - - - - {!sidebarCollapsed && ( -

Profile settings

- )} -
+ + + + + {!sidebarCollapsed && ( +

Profile settings

+ )}
{!sidebarCollapsed && ( @@ -125,21 +124,19 @@ export const ProfileLayoutSidebar = observer(() => { if (link.key === "change-password" && currentUser?.is_password_autoset) return null; return ( - - - -
- {} - {!sidebarCollapsed && link.label} -
-
-
+ + +
+ {} + {!sidebarCollapsed && link.label} +
+
); })} @@ -189,19 +186,17 @@ export const ProfileLayoutSidebar = observer(() => { )}
{WORKSPACE_ACTION_LINKS.map((link) => ( - - - -
- {} - {!sidebarCollapsed && link.label} -
-
-
+ + +
+ {} + {!sidebarCollapsed && link.label} +
+
))}
diff --git a/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx b/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx index a7ccc4c3b..428f0a14c 100644 --- a/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx +++ b/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx @@ -1,41 +1,21 @@ import { ReactElement } from "react"; -import { useRouter } from "next/router"; -import useSWR from "swr"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; // layouts import { AppLayout } from "layouts/app-layout"; // components import { GlobalViewsHeader } from "components/workspace"; -import { GlobalViewLayoutRoot } from "components/issues"; +import { AllIssueLayoutRoot } from "components/issues"; import { GlobalIssuesHeader } from "components/headers"; // types import { NextPageWithLayout } from "types/app"; -const GlobalViewIssuesPage: NextPageWithLayout = () => { - const router = useRouter(); - const { workspaceSlug, globalViewId } = router.query; - - const { - globalViews: { fetchGlobalViewDetails }, - } = useMobxStore(); - - useSWR( - workspaceSlug && globalViewId ? `GLOBAL_VIEW_DETAILS_${globalViewId.toString()}` : null, - workspaceSlug && globalViewId - ? () => fetchGlobalViewDetails(workspaceSlug.toString(), globalViewId.toString()) - : null - ); - - return ( -
-
- - -
+const GlobalViewIssuesPage: NextPageWithLayout = () => ( +
+
+ +
- ); -}; +
+); GlobalViewIssuesPage.getLayout = function getLayout(page: ReactElement) { return }>{page}; diff --git a/web/pages/[workspaceSlug]/workspace-views/all-issues.tsx b/web/pages/[workspaceSlug]/workspace-views/all-issues.tsx index 3539beb47..756dde493 100644 --- a/web/pages/[workspaceSlug]/workspace-views/all-issues.tsx +++ b/web/pages/[workspaceSlug]/workspace-views/all-issues.tsx @@ -2,7 +2,7 @@ import { ReactElement } from "react"; // components import { GlobalViewsHeader } from "components/workspace"; import { GlobalIssuesHeader } from "components/headers"; -import { GlobalViewLayoutRoot } from "components/issues"; +import { AllIssueLayoutRoot } from "components/issues/issue-layouts"; // layouts import { AppLayout } from "layouts/app-layout"; // types @@ -12,7 +12,7 @@ const GlobalViewAllIssuesPage: NextPageWithLayout = () => (
- +
); diff --git a/web/pages/[workspaceSlug]/workspace-views/assigned.tsx b/web/pages/[workspaceSlug]/workspace-views/assigned.tsx index 7c1f56bb0..854f0ed6a 100644 --- a/web/pages/[workspaceSlug]/workspace-views/assigned.tsx +++ b/web/pages/[workspaceSlug]/workspace-views/assigned.tsx @@ -2,7 +2,7 @@ import { ReactElement } from "react"; // components import { GlobalViewsHeader } from "components/workspace"; import { GlobalIssuesHeader } from "components/headers"; -import { GlobalViewLayoutRoot } from "components/issues"; +import { AllIssueLayoutRoot } from "components/issues/issue-layouts"; // layouts import { AppLayout } from "layouts/app-layout"; // types @@ -12,7 +12,7 @@ const GlobalViewAssignedIssuesPage: NextPageWithLayout = () => (
- +
); diff --git a/web/pages/[workspaceSlug]/workspace-views/created.tsx b/web/pages/[workspaceSlug]/workspace-views/created.tsx index e8a33cf39..5f5fb46b5 100644 --- a/web/pages/[workspaceSlug]/workspace-views/created.tsx +++ b/web/pages/[workspaceSlug]/workspace-views/created.tsx @@ -2,7 +2,7 @@ import { ReactElement } from "react"; // components import { GlobalViewsHeader } from "components/workspace"; import { GlobalIssuesHeader } from "components/headers"; -import { GlobalViewLayoutRoot } from "components/issues"; +import { AllIssueLayoutRoot } from "components/issues"; // layouts import { AppLayout } from "layouts/app-layout"; // types @@ -12,7 +12,7 @@ const GlobalViewCreatedIssuesPage: NextPageWithLayout = () => (
- +
); diff --git a/web/pages/[workspaceSlug]/workspace-views/subscribed.tsx b/web/pages/[workspaceSlug]/workspace-views/subscribed.tsx index 02d980074..4bb5d4864 100644 --- a/web/pages/[workspaceSlug]/workspace-views/subscribed.tsx +++ b/web/pages/[workspaceSlug]/workspace-views/subscribed.tsx @@ -4,7 +4,7 @@ import { AppLayout } from "layouts/app-layout"; // components import { GlobalViewsHeader } from "components/workspace"; import { GlobalIssuesHeader } from "components/headers"; -import { GlobalViewLayoutRoot } from "components/issues"; +import { AllIssueLayoutRoot } from "components/issues"; // types import { NextPageWithLayout } from "types/app"; @@ -12,7 +12,7 @@ const GlobalViewSubscribedIssuesPage: NextPageWithLayout = () => (
- +
); diff --git a/web/pages/accounts/password.tsx b/web/pages/accounts/password.tsx index 47c74f784..8c031a408 100644 --- a/web/pages/accounts/password.tsx +++ b/web/pages/accounts/password.tsx @@ -142,8 +142,13 @@ const HomePage: NextPageWithLayout = () => {

When you click the button above, you agree with our{" "} - - terms and conditions of service. + + terms and conditions of service.

diff --git a/web/services/workspace.service.ts b/web/services/workspace.service.ts index 3fb3b4b2f..812e67735 100644 --- a/web/services/workspace.service.ts +++ b/web/services/workspace.service.ts @@ -17,6 +17,7 @@ import { import { IWorkspaceView } from "types/workspace-views"; // store import { IIssueGroupWithSubGroupsStructure, IIssueGroupedStructure, IIssueUnGroupedStructure } from "store/issue"; +import { IIssueResponse } from "store/issues/types"; export class WorkspaceService extends APIService { constructor() { @@ -245,10 +246,7 @@ export class WorkspaceService extends APIService { }); } - async getViewIssues( - workspaceSlug: string, - params: any - ): Promise { + async getViewIssues(workspaceSlug: string, params: any): Promise { return this.get(`/api/workspaces/${workspaceSlug}/issues/`, { params, }) diff --git a/web/store/global-view/global_view_issues.store.ts b/web/store/global-view/global_view_issues.store.ts index 006c9b380..313f7597d 100644 --- a/web/store/global-view/global_view_issues.store.ts +++ b/web/store/global-view/global_view_issues.store.ts @@ -118,7 +118,7 @@ export class GlobalViewIssuesStore implements IGlobalViewIssuesStore { this.loader = false; this.viewIssues = { ...this.viewIssues, - [viewId]: response as IIssue[], + [viewId]: Object.values(response) as IIssue[], }; }); @@ -163,7 +163,7 @@ export class GlobalViewIssuesStore implements IGlobalViewIssuesStore { this.loader = false; this.viewIssues = { ...this.viewIssues, - [type]: response as IIssue[], + [type]: Object.values(response) as IIssue[], }; }); diff --git a/web/store/issues/global/filter.store.ts b/web/store/issues/global/filter.store.ts index 81d661972..b495bb816 100644 --- a/web/store/issues/global/filter.store.ts +++ b/web/store/issues/global/filter.store.ts @@ -3,48 +3,55 @@ import { action, makeObservable, observable, runInAction } from "mobx"; import { RootStore } from "store/root"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; import { EFilterType } from "store/issues/types"; -import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; import { IssueFilterBaseStore } from "../project-issues/base-issue-filter.store"; -import { isEmpty } from "lodash"; +// helpers +import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; +// services +import { WorkspaceService } from "services/workspace.service"; -interface IProjectIssuesFiltersOptions { +interface IIssuesDisplayOptions { filters: IIssueFilterOptions; - displayFilters: IIssueDisplayFilterOptions; } -interface IProjectIssuesDisplayOptions { +type TIssueViewTypes = "all-issues" | "assigned" | "created" | "subscribed" | string; + +interface IIssueViewOptions { + "all-issues": IIssuesDisplayOptions; + assigned: IIssuesDisplayOptions; + created: IIssuesDisplayOptions; + subscribed: IIssuesDisplayOptions; + [view_id: string]: IIssuesDisplayOptions; +} + +interface IWorkspaceProperties { filters: IIssueFilterOptions; displayFilters: IIssueDisplayFilterOptions; displayProperties: IIssueDisplayProperties; } -interface IProjectIssuesFilters { - filters: IIssueFilterOptions | undefined; - displayFilters: IIssueDisplayFilterOptions | undefined; - displayProperties: IIssueDisplayProperties | undefined; -} - export interface IGlobalIssuesFilterStore { // observables - projectIssueFilters: { [workspaceId: string]: IProjectIssuesDisplayOptions } | undefined; + currentView: TIssueViewTypes; + workspaceProperties: { [workspaceId: string]: IWorkspaceProperties } | undefined; + workspaceViewFilters: { [workspaceId: string]: IIssueViewOptions } | undefined; // computed - issueFilters: IProjectIssuesFilters | undefined; + issueFilters: IWorkspaceProperties | undefined; appliedFilters: TIssueParams[] | undefined; // helpers - issueDisplayFilters: (workspaceId: string) => IProjectIssuesDisplayOptions | undefined; + issueDisplayFilters: (workspaceId: string) => IIssuesDisplayOptions | undefined; // actions - fetchDisplayFilters: (workspaceSlug: string) => Promise; - updateDisplayFilters: ( + setCurrentView: (view: TIssueViewTypes) => void; + fetchWorkspaceProperties: (workspaceSlug: string) => Promise; + updateWorkspaceProperties: ( workspaceSlug: string, type: EFilterType, - filters: IIssueFilterOptions | IIssueDisplayFilterOptions - ) => Promise; - fetchDisplayProperties: (workspaceSlug: string) => Promise; - updateDisplayProperties: ( - workspaceSlug: string, - properties: IIssueDisplayProperties - ) => Promise; - fetchFilters: (workspaceSlug: string) => Promise; + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties + ) => Promise; + + fetchWorkspaceViewFilters: (workspaceId: string, view: TIssueViewTypes) => Promise; + updateWorkspaceViewFilters: (workspaceId: string, filters: IIssueFilterOptions) => Promise; + + fetchFilters: (workspaceSlug: string, view: TIssueViewTypes) => Promise; updateFilters: ( workspaceSlug: string, filterType: EFilterType, @@ -54,89 +61,160 @@ export interface IGlobalIssuesFilterStore { export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGlobalIssuesFilterStore { // observables - projectIssueFilters: { [projectId: string]: IProjectIssuesDisplayOptions } | undefined = undefined; + currentView: TIssueViewTypes = "all-issues"; + workspaceProperties: { [workspaceId: string]: IWorkspaceProperties } | undefined = undefined; + workspaceViewFilters: { [workspaceId: string]: IIssueViewOptions } | undefined = undefined; // root store rootStore; + // service + workspaceService; constructor(_rootStore: RootStore) { super(_rootStore); makeObservable(this, { // observables - projectIssueFilters: observable.ref, + currentView: observable.ref, + workspaceProperties: observable.ref, + workspaceViewFilters: observable.ref, // computed // actions - fetchDisplayFilters: action, - updateDisplayFilters: action, - fetchDisplayProperties: action, - updateDisplayProperties: action, + setCurrentView: action, + fetchWorkspaceProperties: action, + updateWorkspaceProperties: action, + fetchWorkspaceViewFilters: action, + updateWorkspaceViewFilters: action, }); // root store this.rootStore = _rootStore; + // services + this.workspaceService = new WorkspaceService(); } // computed // helpers issueDisplayFilters = (workspaceId: string) => { - if (!workspaceId) return undefined; - return this.projectIssueFilters?.[workspaceId] || undefined; + if (!workspaceId || !this.currentView) return undefined; + const filters: IWorkspaceProperties = { + filters: this.workspaceProperties?.[workspaceId]?.filters || {}, + displayFilters: this.workspaceProperties?.[workspaceId]?.displayFilters || {}, + displayProperties: this.workspaceProperties?.[workspaceId]?.displayProperties || {}, + }; + + if (!["all-issues", "assigned", "created", "subscribed"].includes(this.currentView)) { + const viewFilters = this.workspaceViewFilters?.[workspaceId]?.[this.currentView]; + if (viewFilters) { + filters.filters = { ...filters.filters, ...viewFilters?.filters }; + } + } + + return filters; }; // actions - fetchDisplayFilters = async (workspaceSlug: string) => { + setCurrentView = (view: TIssueViewTypes) => { + this.currentView = view; + }; + + fetchWorkspaceProperties = async (workspaceSlug: string) => { try { - const filters: IIssueFilterOptions = { - assignees: null, - mentions: null, - created_by: null, - labels: null, - priority: null, - project: null, - start_date: null, - state: null, - state_group: null, - subscriber: null, - target_date: null, + let _filters: IWorkspaceProperties = {} as IWorkspaceProperties; + + const filtersResponse = await this.workspaceService.workspaceMemberMe(workspaceSlug); + _filters = { + filters: { ...filtersResponse?.view_props?.filters } || null, + displayFilters: { ...filtersResponse?.view_props?.display_filters } || null, + displayProperties: { ...filtersResponse?.view_props?.display_properties } || null, }; + let filters: IIssueFilterOptions = { + assignees: _filters?.filters?.assignees || null, + mentions: _filters?.filters?.mentions || null, + created_by: _filters?.filters?.created_by || null, + labels: _filters?.filters?.labels || null, + priority: _filters?.filters?.priority || null, + project: _filters?.filters?.project || null, + start_date: _filters?.filters?.start_date || null, + state: _filters?.filters?.state || null, + state_group: _filters?.filters?.state_group || null, + subscriber: _filters?.filters?.subscriber || null, + target_date: _filters?.filters?.target_date || null, + }; + + const currentUserId = this.rootStore.user.currentUser?.id; + if (currentUserId && this.currentView === "assigned") + filters = { + ...filters, + assignees: [currentUserId], + created_by: null, + subscriber: null, + }; + + if (currentUserId && this.currentView === "created") + filters = { + ...filters, + assignees: null, + created_by: [currentUserId], + subscriber: null, + }; + if (currentUserId && this.currentView === "subscribed") + filters = { + ...filters, + assignees: null, + created_by: null, + subscriber: [currentUserId], + }; + const displayFilters: IIssueDisplayFilterOptions = { calendar: { - show_weekends: false, - layout: "month", + show_weekends: _filters?.displayFilters?.calendar?.show_weekends || false, + layout: _filters?.displayFilters?.calendar?.layout || "month", }, - group_by: "state_detail.group", - sub_group_by: null, - layout: "list", - order_by: "-created_at", - show_empty_groups: false, - start_target_date: false, - sub_issue: false, - type: null, + group_by: _filters?.displayFilters?.group_by || null, + sub_group_by: _filters?.displayFilters?.sub_group_by || null, + layout: _filters?.displayFilters?.layout || "list", + order_by: _filters?.displayFilters?.order_by || "-created_at", + show_empty_groups: _filters?.displayFilters?.show_empty_groups || false, + start_target_date: _filters?.displayFilters?.start_target_date || false, + sub_issue: _filters?.displayFilters?.sub_issue || false, + type: _filters?.displayFilters?.type || null, }; - const issueFilters: IProjectIssuesFiltersOptions = { + const displayProperties: IIssueDisplayProperties = { + assignee: _filters?.displayProperties?.assignee || false, + start_date: _filters?.displayProperties?.start_date || false, + due_date: _filters?.displayProperties?.due_date || false, + labels: _filters?.displayProperties?.labels || false, + key: _filters?.displayProperties?.key || false, + priority: _filters?.displayProperties?.priority || false, + state: _filters?.displayProperties?.state || false, + sub_issue_count: _filters?.displayProperties?.sub_issue_count || false, + link: _filters?.displayProperties?.link || false, + attachment_count: _filters?.displayProperties?.attachment_count || false, + estimate: _filters?.displayProperties?.estimate || false, + created_on: _filters?.displayProperties?.created_on || false, + updated_on: _filters?.displayProperties?.updated_on || false, + }; + + const issueFilters: IWorkspaceProperties = { filters: filters, displayFilters: displayFilters, + displayProperties: displayProperties, }; - let _projectIssueFilters = this.projectIssueFilters; - if (!_projectIssueFilters) _projectIssueFilters = {}; - if (!_projectIssueFilters[workspaceSlug]) { - _projectIssueFilters[workspaceSlug] = { displayProperties: {} } as IProjectIssuesDisplayOptions; - } - if ( - isEmpty(_projectIssueFilters[workspaceSlug].filters) || - isEmpty(_projectIssueFilters[workspaceSlug].displayFilters) - ) { - _projectIssueFilters[workspaceSlug] = { - ..._projectIssueFilters[workspaceSlug], - ...issueFilters, + let _workspaceProperties = { ...this.workspaceProperties }; + if (!_workspaceProperties) _workspaceProperties = {}; + if (!_workspaceProperties[workspaceSlug]) + _workspaceProperties[workspaceSlug] = { + filters: {}, + displayFilters: {}, + displayProperties: {}, }; - } + _workspaceProperties[workspaceSlug] = { ...issueFilters }; runInAction(() => { - this.projectIssueFilters = _projectIssueFilters; + this.workspaceProperties = _workspaceProperties; }); return issueFilters; @@ -145,114 +223,126 @@ export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGl } }; - updateDisplayFilters = async ( + updateWorkspaceProperties = async ( workspaceSlug: string, type: EFilterType, - filters: IIssueFilterOptions | IIssueDisplayFilterOptions + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties ) => { try { - let _projectIssueFilters = { ...this.projectIssueFilters }; - if (!_projectIssueFilters) _projectIssueFilters = {}; - if (!_projectIssueFilters[workspaceSlug]) - _projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} }; + let _workspaceProperties = { ...this.workspaceProperties }; + if (!_workspaceProperties) _workspaceProperties = {}; + if (!_workspaceProperties[workspaceSlug]) + _workspaceProperties[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} }; const _filters = { - filters: { ..._projectIssueFilters[workspaceSlug].filters }, - displayFilters: { ..._projectIssueFilters[workspaceSlug].displayFilters }, + filters: { ..._workspaceProperties[workspaceSlug].filters }, + displayFilters: { ..._workspaceProperties[workspaceSlug].displayFilters }, + displayProperties: { ..._workspaceProperties[workspaceSlug].displayProperties }, }; - if (type === EFilterType.FILTERS) _filters.filters = { ..._filters.filters, ...filters }; - else if (type === EFilterType.DISPLAY_FILTERS) - _filters.displayFilters = { ..._filters.displayFilters, ...filters }; + switch (type) { + case EFilterType.FILTERS: + _filters.filters = { ..._filters.filters, ...(filters as IIssueFilterOptions) }; + break; + case EFilterType.DISPLAY_FILTERS: + _filters.displayFilters = { ..._filters.displayFilters, ...(filters as IIssueDisplayFilterOptions) }; + break; + case EFilterType.DISPLAY_PROPERTIES: + _filters.displayProperties = { ..._filters.displayProperties, ...(filters as IIssueDisplayProperties) }; + break; + } - // set sub_group_by to null if group_by is set to null - if (_filters.displayFilters.group_by === null) _filters.displayFilters.sub_group_by = null; - - // set sub_group_by to null if layout is switched to kanban group_by and sub_group_by are same - if ( - _filters.displayFilters.layout === "kanban" && - _filters.displayFilters.group_by === _filters.displayFilters.sub_group_by - ) - _filters.displayFilters.sub_group_by = null; - - // set group_by to state if layout is switched to kanban and group_by is null - if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) - _filters.displayFilters.group_by = "state"; - - _projectIssueFilters[workspaceSlug] = { - filters: _filters.filters, - displayFilters: _filters.displayFilters, - displayProperties: _projectIssueFilters[workspaceSlug].displayProperties, + _workspaceProperties[workspaceSlug] = { + ..._workspaceProperties[workspaceSlug], + filters: _filters?.filters, + displayFilters: _filters?.displayFilters, + displayProperties: _filters?.displayProperties, }; runInAction(() => { - this.projectIssueFilters = _projectIssueFilters; + this.workspaceProperties = _workspaceProperties; + }); + + await this.workspaceService.updateWorkspaceView(workspaceSlug, { + view_props: { + filters: _filters.filters, + display_filters: _filters.displayFilters, + display_properties: _filters.displayProperties, + }, }); return _filters; } catch (error) { - this.fetchDisplayFilters(workspaceSlug); + this.fetchWorkspaceProperties(workspaceSlug); throw error; } }; - fetchDisplayProperties = async (workspaceSlug: string) => { + fetchWorkspaceViewFilters = async (workspaceSlug: string, view: TIssueViewTypes) => { try { - const displayProperties: IIssueDisplayProperties = { - assignee: true, - start_date: true, - due_date: true, - labels: false, - key: false, - priority: true, - state: false, - sub_issue_count: true, - link: true, - attachment_count: false, - estimate: false, - created_on: false, - updated_on: false, + let _workspaceViewFilters = { ...this.workspaceViewFilters }; + if (!_workspaceViewFilters) _workspaceViewFilters = {}; + if (!_workspaceViewFilters[workspaceSlug]) _workspaceViewFilters[workspaceSlug] = {} as IIssueViewOptions; + if (!_workspaceViewFilters[workspaceSlug][view]) _workspaceViewFilters[workspaceSlug][view] = { filters: {} }; + + const filtersResponse = await this.workspaceService.getViewDetails(workspaceSlug, view); + + const _filters: IIssueFilterOptions = { + assignees: filtersResponse?.query_data?.filters?.assignees || null, + mentions: filtersResponse?.query_data?.filters?.mentions || null, + created_by: filtersResponse?.query_data?.filters?.created_by || null, + labels: filtersResponse?.query_data?.filters?.labels || null, + priority: filtersResponse?.query_data?.filters?.priority || null, + project: filtersResponse?.query_data?.filters?.project || null, + start_date: filtersResponse?.query_data?.filters?.start_date || null, + state: filtersResponse?.query_data?.filters?.state || null, + state_group: filtersResponse?.query_data?.filters?.state_group || null, + subscriber: filtersResponse?.query_data?.filters?.subscriber || null, + target_date: filtersResponse?.query_data?.filters?.target_date || null, }; - let _projectIssueFilters = { ...this.projectIssueFilters }; - if (!_projectIssueFilters) _projectIssueFilters = {}; - if (!_projectIssueFilters[workspaceSlug]) { - _projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {} } as IProjectIssuesDisplayOptions; - } - if (isEmpty(_projectIssueFilters[workspaceSlug].displayProperties)) { - _projectIssueFilters[workspaceSlug] = { - ..._projectIssueFilters[workspaceSlug], - displayProperties: displayProperties, - }; - } + _workspaceViewFilters[workspaceSlug][view].filters = { ..._filters }; runInAction(() => { - this.projectIssueFilters = _projectIssueFilters; + this.workspaceViewFilters = _workspaceViewFilters; }); - return displayProperties; + return _filters; } catch (error) { throw error; } }; - updateDisplayProperties = async (workspaceSlug: string, properties: IIssueDisplayProperties) => { + updateWorkspaceViewFilters = async (workspaceSlug: string, filters: IIssueFilterOptions) => { try { - let _issueFilters = { ...this.projectIssueFilters }; - if (!_issueFilters) _issueFilters = {}; - if (!_issueFilters[workspaceSlug]) - _issueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} }; + let _workspaceViewFilters = { ...this.workspaceViewFilters }; + if (!_workspaceViewFilters) _workspaceViewFilters = {}; + if (!_workspaceViewFilters[workspaceSlug]) _workspaceViewFilters[workspaceSlug] = {} as IIssueViewOptions; + if (!_workspaceViewFilters[workspaceSlug][this.currentView]) + _workspaceViewFilters[workspaceSlug][this.currentView] = { filters: {} }; - const updatedDisplayProperties = { ..._issueFilters[workspaceSlug].displayProperties, ...properties }; - _issueFilters[workspaceSlug] = { ..._issueFilters[workspaceSlug], displayProperties: updatedDisplayProperties }; + const _filters = { + filters: { ..._workspaceViewFilters[workspaceSlug][this.currentView].filters, ...filters }, + }; + + _workspaceViewFilters[workspaceSlug][this.currentView] = { + ..._workspaceViewFilters[workspaceSlug][this.currentView], + filters: _filters?.filters, + }; runInAction(() => { - this.projectIssueFilters = _issueFilters; + this.workspaceViewFilters = _workspaceViewFilters; }); - return properties; + await this.workspaceService.updateView(workspaceSlug, this.currentView, { + query_data: { + filters: _filters.filters, + } as any, + }); + + return _filters.filters; } catch (error) { - this.fetchDisplayProperties(workspaceSlug); + this.fetchWorkspaceViewFilters(workspaceSlug, this.currentView); throw error; } }; @@ -262,10 +352,10 @@ export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGl if (!workspaceSlug) return undefined; const displayFilters = this.issueDisplayFilters(workspaceSlug); - const _filters: IProjectIssuesFilters = { - filters: displayFilters?.filters, - displayFilters: displayFilters?.displayFilters, - displayProperties: displayFilters?.displayProperties, + const _filters: IWorkspaceProperties = { + filters: displayFilters?.filters || {}, + displayFilters: displayFilters?.displayFilters || {}, + displayProperties: displayFilters?.displayProperties || {}, }; return _filters; @@ -277,6 +367,7 @@ export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGl let filteredRouteParams: any = { priority: userFilters?.filters?.priority || undefined, + project: userFilters?.filters?.project || undefined, state_group: userFilters?.filters?.state_group || undefined, state: userFilters?.filters?.state || undefined, assignees: userFilters?.filters?.assignees || undefined, @@ -286,24 +377,20 @@ export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGl start_date: userFilters?.filters?.start_date || undefined, target_date: userFilters?.filters?.target_date || undefined, type: userFilters?.displayFilters?.type || undefined, - sub_issue: userFilters?.displayFilters?.sub_issue || true, - show_empty_groups: userFilters?.displayFilters?.show_empty_groups || true, - start_target_date: userFilters?.displayFilters?.start_target_date || true, + sub_issue: false, }; - const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "profile_issues"); + const filteredParams = handleIssueQueryParamsByLayout("spreadsheet", "my_issues"); if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); - if (userFilters?.displayFilters?.layout === "calendar") filteredRouteParams.group_by = "target_date"; - if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true; - return filteredRouteParams; } - fetchFilters = async (workspaceSlug: string) => { + fetchFilters = async (workspaceSlug: string, view: TIssueViewTypes) => { try { - await this.fetchDisplayFilters(workspaceSlug); - await this.fetchDisplayProperties(workspaceSlug); + await this.fetchWorkspaceProperties(workspaceSlug); + if (!["all-issues", "assigned", "created", "subscribed"].includes(view)) + await this.fetchWorkspaceViewFilters(workspaceSlug, view); return; } catch (error) { throw Error; @@ -312,20 +399,21 @@ export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGl updateFilters = async ( workspaceSlug: string, - filterType: EFilterType, filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties ) => { try { switch (filterType) { case EFilterType.FILTERS: - await this.updateDisplayFilters(workspaceSlug, filterType, filters as IIssueFilterOptions); + if (["all-issues", "assigned", "created", "subscribed"].includes(this.currentView)) + await this.updateWorkspaceProperties(workspaceSlug, filterType, filters as IIssueDisplayFilterOptions); + else await this.updateWorkspaceViewFilters(workspaceSlug, filters as IIssueFilterOptions); break; case EFilterType.DISPLAY_FILTERS: - await this.updateDisplayFilters(workspaceSlug, filterType, filters as IIssueDisplayFilterOptions); + await this.updateWorkspaceProperties(workspaceSlug, filterType, filters as IIssueDisplayFilterOptions); break; case EFilterType.DISPLAY_PROPERTIES: - await this.updateDisplayProperties(workspaceSlug, filters as IIssueDisplayProperties); + await this.updateWorkspaceProperties(workspaceSlug, filterType, filters as IIssueDisplayProperties); break; } diff --git a/web/store/issues/global/issue.store.ts b/web/store/issues/global/issue.store.ts index b6f9177dd..5610705b7 100644 --- a/web/store/issues/global/issue.store.ts +++ b/web/store/issues/global/issue.store.ts @@ -2,60 +2,60 @@ import { action, observable, makeObservable, computed, runInAction, autorun } fr // base class import { IssueBaseStore } from "store/issues"; // services -import { UserService } from "services/user.service"; +import { WorkspaceService } from "services/workspace.service"; +import { IssueService } from "services/issue"; // types -import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues, ViewFlags } from "../types"; +import { IIssue } from "types/issues"; +import { IIssueResponse, TLoader, TUnGroupedIssues, ViewFlags } from "../types"; import { RootStore } from "store/root"; -import { IIssue } from "types"; - -interface IProfileIssueTabTypes { - assigned: IIssueResponse; - created: IIssueResponse; - subscribed: IIssueResponse; -} +import isEmpty from "lodash/isEmpty"; export interface IGlobalIssuesStore { // observable loader: TLoader; - issues: { [user_id: string]: IProfileIssueTabTypes } | undefined; - currentUserId: string | null; - currentUserIssueTab: "assigned" | "created" | "subscribed" | null; + issues: { [workspace_view: string]: IIssueResponse } | undefined; // computed getIssues: IIssueResponse | undefined; - getIssuesIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined; + getIssuesIds: TUnGroupedIssues | undefined; // actions - fetchIssues: ( + fetchIssues: (workspaceSlug: string, workspaceViewId: string, loadType: TLoader) => Promise; + createIssue: ( workspaceSlug: string, - userId: string, - loadType: TLoader, - type: "assigned" | "created" | "subscribed" - ) => Promise; - createIssue: (workspaceSlug: string, userId: string, data: Partial) => Promise; + projectId: string, + data: Partial, + workspaceViewId?: string | undefined + ) => Promise; updateIssue: ( workspaceSlug: string, - userId: string, + projectId: string, issueId: string, - data: Partial + data: Partial, + workspaceViewId?: string | undefined ) => Promise; removeIssue: ( workspaceSlug: string, - userId: string, projectId: string, - issueId: string + issueId: string, + workspaceViewId?: string | undefined ) => Promise; - quickAddIssue: (workspaceSlug: string, userId: string, data: IIssue) => Promise; + viewFlags: ViewFlags; } export class GlobalIssuesStore extends IssueBaseStore implements IGlobalIssuesStore { loader: TLoader = "init-loader"; - issues: { [user_id: string]: IProfileIssueTabTypes } | undefined = undefined; - currentUserId: string | null = null; - currentUserIssueTab: "assigned" | "created" | "subscribed" | null = null; + issues: { [workspace_view: string]: IIssueResponse } | undefined = undefined; // root store rootStore; // service - userService; + workspaceService; + issueService; + //viewData + viewFlags = { + enableQuickAdd: true, + enableIssueCreation: true, + enableInlineEditing: true, + }; constructor(_rootStore: RootStore) { super(_rootStore); @@ -64,146 +64,91 @@ export class GlobalIssuesStore extends IssueBaseStore implements IGlobalIssuesSt // observable loader: observable.ref, issues: observable.ref, - currentUserId: observable.ref, - currentUserIssueTab: observable.ref, // computed getIssues: computed, getIssuesIds: computed, - viewFlags: computed, // action fetchIssues: action, createIssue: action, updateIssue: action, removeIssue: action, - quickAddIssue: action, }); this.rootStore = _rootStore; - this.userService = new UserService(); + this.workspaceService = new WorkspaceService(); + this.issueService = new IssueService(); autorun(() => { const workspaceSlug = this.rootStore.workspace.workspaceSlug; - if (!workspaceSlug || !this.currentUserId || !this.currentUserIssueTab) return; + const currentView = this.rootStore.workspaceGlobalIssuesFilter?.currentView; + if (!workspaceSlug || currentView === "") return; - const userFilters = this.rootStore?.workspaceProfileIssuesFilter?.issueFilters?.filters; - if (userFilters) this.fetchIssues(workspaceSlug, this.currentUserId, "mutation", this.currentUserIssueTab); + const userFilters = this.rootStore?.workspaceGlobalIssuesFilter?.issueFilters?.filters; + + if (!isEmpty(userFilters)) this.fetchIssues(workspaceSlug, currentView, "mutation"); }); } get getIssues() { - if (!this.currentUserId || !this.currentUserIssueTab || !this.issues || !this.issues[this.currentUserId]) - return undefined; + const currentView = this.rootStore.workspaceGlobalIssuesFilter?.currentView; + if (currentView === "" || !this.issues || !this.issues[currentView]) return undefined; - return this.issues[this.currentUserId][this.currentUserIssueTab]; + return this.issues[currentView]; } get getIssuesIds() { - const currentUserId = this.currentUserId; - const displayFilters = this.rootStore?.workspaceProfileIssuesFilter?.issueFilters?.displayFilters; + const currentView = this.rootStore.workspaceGlobalIssuesFilter?.currentView; + const displayFilters = this.rootStore?.workspaceGlobalIssuesFilter?.issueFilters?.displayFilters; if (!displayFilters) return undefined; - const groupBy = displayFilters?.group_by; const orderBy = displayFilters?.order_by; - const layout = displayFilters?.layout; - if (!currentUserId || !this.currentUserIssueTab || !this.issues || !this.issues[currentUserId]) return undefined; + if (currentView === "" || !this.issues || !this.issues[currentView]) return undefined; - let issues: IIssueResponse | IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined = undefined; + let issues: IIssueResponse | TUnGroupedIssues | undefined = undefined; - if (layout === "list" && orderBy) { - if (groupBy) issues = this.groupedIssues(groupBy, orderBy, this.issues[currentUserId][this.currentUserIssueTab]); - else issues = this.unGroupedIssues(orderBy, this.issues[currentUserId][this.currentUserIssueTab]); - } + issues = this.unGroupedIssues(orderBy ?? "-created_at", this.issues[currentView]); return issues; } - get viewFlags() { - if (this.currentUserIssueTab === "subscribed") { - return { - enableQuickAdd: false, - enableIssueCreation: false, - enableInlineEditing: false, - }; - } - - return { - enableQuickAdd: false, - enableIssueCreation: true, - enableInlineEditing: true, - }; - } - - fetchIssues = async ( - workspaceSlug: string, - userId: string, - loadType: TLoader = "init-loader", - type: "assigned" | "created" | "subscribed" - ) => { + fetchIssues = async (workspaceSlug: string, workspaceViewId: string, loadType: TLoader = "init-loader") => { try { this.loader = loadType; - this.currentUserId = userId; - if (type) this.currentUserIssueTab = type; - let params: any = this.rootStore?.workspaceProfileIssuesFilter?.appliedFilters; - params = { - ...params, - assignees: undefined, - created_by: undefined, - subscriber: undefined, - }; - if (this.currentUserIssueTab === "assigned") - params = params ? { ...params, assignees: userId } : { assignees: userId }; - else if (this.currentUserIssueTab === "created") - params = params ? { ...params, created_by: userId } : { created_by: userId }; - else if (this.currentUserIssueTab === "subscribed") - params = params ? { ...params, subscriber: userId } : { subscriber: userId }; + const params = this.rootStore?.workspaceGlobalIssuesFilter?.appliedFilters; + const response = await this.workspaceService.getViewIssues(workspaceSlug, params); - const response = await this.userService.getUserProfileIssues(workspaceSlug, userId, params); - - if (!this.currentUserIssueTab) return; - - const _issues: any = { - ...this.issues, - [userId]: { - ...this.issues?.[userId], - ...{ [this.currentUserIssueTab]: response }, - }, - }; + const _issues = { ...this.issues, [workspaceViewId]: { ...response } }; runInAction(() => { this.issues = _issues; this.loader = undefined; }); - return _issues; + return response; } catch (error) { + console.error(error); this.loader = undefined; throw error; } }; - createIssue = async (workspaceSlug: string, userId: string, data: Partial) => { + createIssue = async ( + workspaceSlug: string, + projectId: string, + data: Partial, + workspaceViewId: string | undefined = undefined + ) => { + if (!workspaceViewId) return; + try { - const projectId = data.project; - const moduleId = data.module_id; - const cycleId = data.cycle_id; - - if (!projectId) return; - - let response = {} as IIssue; - response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data); - - // if (moduleId) - // response = await this.rootStore.moduleIssues.addIssueToModule(workspaceSlug, projectId, moduleId, response); - - // if (cycleId) - // response = await this.rootStore.cycleIssues.addIssueToCycle(workspaceSlug, projectId, cycleId, response); + const response = await this.issueService.createIssue(workspaceSlug, projectId, data); let _issues = this.issues; if (!_issues) _issues = {}; - if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; - _issues[userId] = { ..._issues[userId], ...{ [response.id]: response } }; + if (!_issues[workspaceViewId]) _issues[workspaceViewId] = {}; + _issues[workspaceViewId] = { ..._issues[workspaceViewId], ...{ [response.id]: response } }; runInAction(() => { this.issues = _issues; @@ -211,116 +156,62 @@ export class GlobalIssuesStore extends IssueBaseStore implements IGlobalIssuesSt return response; } catch (error) { - if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab); + this.fetchIssues(workspaceSlug, workspaceViewId, "mutation"); throw error; } }; - updateIssue = async (workspaceSlug: string, userId: string, issueId: string, data: Partial) => { + updateIssue = async ( + workspaceSlug: string, + projectId: string, + issueId: string, + data: Partial, + workspaceViewId: string | undefined = undefined + ) => { + if (!workspaceViewId) return; + try { - const projectId = data.project; - const moduleId = data.module_id; - const cycleId = data.cycle_id; - - if (!projectId || !this.currentUserIssueTab) return; - let _issues = { ...this.issues }; if (!_issues) _issues = {}; - if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; - _issues[projectId][this.currentUserIssueTab][userId] = { - ..._issues[projectId][this.currentUserIssueTab][userId], - ...data, - }; + if (!_issues[workspaceViewId]) _issues[workspaceViewId] = {}; + _issues[workspaceViewId][issueId] = { ..._issues[workspaceViewId][issueId], ...data }; runInAction(() => { this.issues = _issues; }); - let response = data as IIssue | undefined; - response = await this.rootStore.projectIssues.updateIssue( - workspaceSlug, - projectId, - data.id as keyof IIssue, - data - ); - - if (moduleId) - response = await this.rootStore.moduleIssues.updateIssue( - workspaceSlug, - projectId, - response.id as keyof IIssue, - response, - moduleId - ); - - if (cycleId) - response = await this.rootStore.cycleIssues.updateIssue( - workspaceSlug, - projectId, - data.id as keyof IIssue, - data, - cycleId - ); + const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data); return response; } catch (error) { - if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab); + this.fetchIssues(workspaceSlug, workspaceViewId, "mutation"); throw error; } }; - removeIssue = async (workspaceSlug: string, userId: string, projectId: string, issueId: string) => { + removeIssue = async ( + workspaceSlug: string, + projectId: string, + issueId: string, + workspaceViewId: string | undefined = undefined + ) => { + if (!workspaceViewId) return; + try { let _issues = { ...this.issues }; if (!_issues) _issues = {}; - if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; - - if (this.currentUserIssueTab) delete _issues?.[userId]?.[this.currentUserIssueTab]?.[issueId]; + if (!_issues[workspaceViewId]) _issues[workspaceViewId] = {}; + delete _issues?.[workspaceViewId]?.[issueId]; runInAction(() => { this.issues = _issues; }); - const response = await this.rootStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); + const response = await this.issueService.deleteIssue(workspaceSlug, projectId, issueId); return response; } catch (error) { - if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab); - throw error; - } - }; - - quickAddIssue = async (workspaceSlug: string, userId: string, data: IIssue) => { - try { - const projectId = data.project; - - let _issues = { ...this.issues }; - if (!_issues) _issues = {}; - if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; - _issues[userId] = { ..._issues[userId], ...{ [data.id as keyof IIssue]: data } }; - - runInAction(() => { - this.issues = _issues; - }); - - const response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data); - - if (this.issues && this.currentUserIssueTab) { - delete this.issues[userId][this.currentUserIssueTab][data.id as keyof IIssue]; - - let _issues = { ...this.issues }; - if (!_issues) _issues = {}; - if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; - _issues[userId] = { ..._issues[userId], ...{ [response.id as keyof IIssue]: response } }; - - runInAction(() => { - this.issues = _issues; - }); - } - - return response; - } catch (error) { - if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab); + this.fetchIssues(workspaceSlug, workspaceViewId, "mutation"); throw error; } }; diff --git a/web/store/issues/profile/filter.store.ts b/web/store/issues/profile/filter.store.ts index e8567b042..0b684f7a7 100644 --- a/web/store/issues/profile/filter.store.ts +++ b/web/store/issues/profile/filter.store.ts @@ -125,6 +125,7 @@ export class ProfileIssuesFilterStore extends IssueFilterBaseStore implements IP if (!_projectIssueFilters[workspaceSlug]) { _projectIssueFilters[workspaceSlug] = { displayProperties: {} } as IProjectIssuesDisplayOptions; } + if ( isEmpty(_projectIssueFilters[workspaceSlug].filters) || isEmpty(_projectIssueFilters[workspaceSlug].displayFilters) From eca96da83725fcc2020ef2b61663c696b003f431 Mon Sep 17 00:00:00 2001 From: Lakhan Baheti <94619783+1akhanBaheti@users.noreply.github.com> Date: Fri, 1 Dec 2023 15:18:48 +0530 Subject: [PATCH 10/17] fix: create workspace form validation (#2958) * fix: create workspace form validation * fix: textfield placeholder typo * fix: name field onchange --- .../account/sign-in-forms/unique-code.tsx | 2 +- web/components/onboarding/join-workspaces.tsx | 2 +- web/components/onboarding/workspace.tsx | 29 +++++++------------ 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/web/components/account/sign-in-forms/unique-code.tsx b/web/components/account/sign-in-forms/unique-code.tsx index 71ed93f0c..0091819f8 100644 --- a/web/components/account/sign-in-forms/unique-code.tsx +++ b/web/components/account/sign-in-forms/unique-code.tsx @@ -196,7 +196,7 @@ export const UniqueCodeForm: React.FC = (props) => { value={value} onChange={onChange} hasError={Boolean(errors.token)} - placeholder="gets-sets-fays" + placeholder="gets-sets-flys" className="w-full h-[46px] placeholder:text-onboarding-text-400 border border-onboarding-border-100 pr-12" /> )} diff --git a/web/components/onboarding/join-workspaces.tsx b/web/components/onboarding/join-workspaces.tsx index 214f3b709..66f88fb49 100644 --- a/web/components/onboarding/join-workspaces.tsx +++ b/web/components/onboarding/join-workspaces.tsx @@ -24,7 +24,7 @@ export const JoinWorkspaces: React.FC = ({ stepChange, setTryDiffAccount } = useForm({ defaultValues: { name: "", - slug: `${window.location.host}/`, + slug: "", }, mode: "onChange", }); diff --git a/web/components/onboarding/workspace.tsx b/web/components/onboarding/workspace.tsx index cb126adfd..e14356e5a 100644 --- a/web/components/onboarding/workspace.tsx +++ b/web/components/onboarding/workspace.tsx @@ -40,8 +40,6 @@ export const Workspace: React.FC = (props) => { const handleCreateWorkspace = async (formData: IWorkspace) => { if (isSubmitting) return; - const slug = formData.slug.split("/"); - formData.slug = slug[slug.length - 1]; await workspaceService .workspaceSlugCheck(formData.slug) @@ -118,11 +116,7 @@ export const Workspace: React.FC = (props) => { onChange={(event) => { onChange(event.target.value); setValue("name", event.target.value); - if (window && window.location.host) { - const host = window.location.host; - const slug = event.currentTarget.value.split("/"); - setValue("slug", `${host}/${slug[slug.length - 1].toLocaleLowerCase().trim().replace(/ /g, "-")}`); - } + setValue("slug", event.target.value.toLocaleLowerCase().trim().replace(/ /g, "-")); }} placeholder="Enter workspace name..." ref={ref} @@ -137,26 +131,25 @@ export const Workspace: React.FC = (props) => { ( -
+ render={({ field: { value, ref, onChange } }) => ( +
+ {window && window.location.host}/ { - const host = window.location.host; - const slug = e.currentTarget.value.split("/"); - if (slug.length > 1) { - /^[a-zA-Z0-9_-]+$/.test(slug[slug.length - 1]) ? setInvalidSlug(false) : setInvalidSlug(true); - setValue("slug", `${host}/${slug[slug.length - 1].toLocaleLowerCase().trim().replace(/ /g, "-")}`); - } else { - setValue("slug", `${host}/`); - } + /^[a-zA-Z0-9_-]+$/.test(e.target.value) ? setInvalidSlug(false) : setInvalidSlug(true); + onChange(e.target.value.toLowerCase()); }} ref={ref} hasError={Boolean(errors.slug)} - className="w-full h-[46px] border-onboarding-border-100" + className="w-full h-[46px] !px-0 border-none" />
)} From 332e56bb0d81936983d4567167b870bce88c452f Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 1 Dec 2023 15:50:01 +0530 Subject: [PATCH 11/17] style: updated the UI of the instance admin setup and the sign in workflow (#2962) * style: updated the UI of the signin and instance setups * fix: form validations and mutations * fix: updated Link tags in accordance to next v14 * chore: latest features image, reset password redirection --- .../account/sign-in-forms/create-password.tsx | 12 +- .../account/sign-in-forms/email-form.tsx | 4 +- .../sign-in-forms/optional-set-password.tsx | 15 +- .../account/sign-in-forms/password.tsx | 23 +- web/components/account/sign-in-forms/root.tsx | 4 +- .../sign-in-forms/set-password-link.tsx | 46 +-- .../account/sign-in-forms/unique-code.tsx | 49 ++-- .../common/latest-feature-block.tsx | 35 +-- web/components/cycles/cycles-board-card.tsx | 9 +- web/components/cycles/cycles-list-item.tsx | 4 +- web/components/inbox/issue-card.tsx | 126 +++++---- web/components/instance/help-section.tsx | 4 +- .../instance/instance-admin-restriction.tsx | 10 +- web/components/instance/not-ready-view.tsx | 18 +- web/components/instance/setup-done-view.tsx | 81 ++++-- .../instance/setup-form/email-code-form.tsx | 135 +++++---- .../instance/setup-form/email-form.tsx | 94 +++---- .../instance/setup-form/password-form.tsx | 24 +- web/components/instance/setup-form/root.tsx | 10 +- web/components/instance/setup-view.tsx | 2 +- web/components/instance/sidebar-menu.tsx | 4 +- .../issues/attachment/attachments.tsx | 46 ++- web/components/issues/main-content.tsx | 4 +- web/components/modules/module-card-item.tsx | 4 +- web/components/modules/module-list-item.tsx | 4 +- web/components/page-views/signin.tsx | 29 +- web/components/pages/pages-list/list-item.tsx | 262 +++++++++--------- web/components/views/view-list-item.tsx | 4 +- .../views/default-view-list-item.tsx | 4 +- .../workspace/views/view-list-item.tsx | 4 +- web/layouts/admin-layout/layout.tsx | 13 +- web/layouts/auth-layout/user-wrapper.tsx | 1 - web/layouts/instance-layout/index.tsx | 4 +- .../settings-layout/profile/sidebar.tsx | 25 +- .../settings-layout/project/sidebar.tsx | 20 +- web/pages/accounts/password.tsx | 93 ++++--- web/public/onboarding/onboarding-pages.svg | 62 +++++ web/services/auth.service.ts | 2 +- web/store/instance/instance.store.ts | 2 +- 39 files changed, 717 insertions(+), 575 deletions(-) create mode 100644 web/public/onboarding/onboarding-pages.svg diff --git a/web/components/account/sign-in-forms/create-password.tsx b/web/components/account/sign-in-forms/create-password.tsx index 9494d1518..bc8fb300c 100644 --- a/web/components/account/sign-in-forms/create-password.tsx +++ b/web/components/account/sign-in-forms/create-password.tsx @@ -1,4 +1,5 @@ import React from "react"; +import Link from "next/link"; import { Controller, useForm } from "react-hook-form"; // services import { AuthService } from "services/auth.service"; @@ -74,7 +75,7 @@ export const CreatePasswordForm: React.FC = (props) => { return ( <> -

+

Let{"'"}s get a new password

@@ -117,16 +118,23 @@ export const CreatePasswordForm: React.FC = (props) => { hasError={Boolean(errors.password)} placeholder="Create password" className="w-full h-[46px] placeholder:text-onboarding-text-400 border border-onboarding-border-100 pr-12" + minLength={8} /> )} /> -

+

Whatever you choose now will be your account{"'"}s password until you change it.

+

+ When you click the button above, you agree with our{" "} + + terms and conditions of service. + +

); diff --git a/web/components/account/sign-in-forms/email-form.tsx b/web/components/account/sign-in-forms/email-form.tsx index c8e138a49..f704d4134 100644 --- a/web/components/account/sign-in-forms/email-form.tsx +++ b/web/components/account/sign-in-forms/email-form.tsx @@ -83,10 +83,10 @@ export const EmailForm: React.FC = (props) => { return ( <> -

+

Get on your flight deck!

-

+

Sign in with the email you used to sign up for Plane

diff --git a/web/components/account/sign-in-forms/optional-set-password.tsx b/web/components/account/sign-in-forms/optional-set-password.tsx index 372dc3947..57161054b 100644 --- a/web/components/account/sign-in-forms/optional-set-password.tsx +++ b/web/components/account/sign-in-forms/optional-set-password.tsx @@ -1,4 +1,5 @@ import React, { useState } from "react"; +import Link from "next/link"; import { Controller, useForm } from "react-hook-form"; // ui import { Button, Input } from "@plane/ui"; @@ -37,12 +38,12 @@ export const OptionalSetPasswordForm: React.FC = (props) => { return ( <> -

Set a password

+

Set a password

- If you{"'"}d to do away with codes, set a password here. + If you{"'"}d to do away with codes, set a password here

-
+ = (props) => { /> )} /> -
+
+

+ When you click Go to workspace above, you agree with our{" "} + + terms and conditions of service. + +

); diff --git a/web/components/account/sign-in-forms/password.tsx b/web/components/account/sign-in-forms/password.tsx index bdaa34b22..547f59e9d 100644 --- a/web/components/account/sign-in-forms/password.tsx +++ b/web/components/account/sign-in-forms/password.tsx @@ -1,4 +1,5 @@ import React from "react"; +import Link from "next/link"; import { Controller, useForm } from "react-hook-form"; import { XCircle } from "lucide-react"; // services @@ -184,17 +185,25 @@ export const PasswordForm: React.FC = (props) => { /> )} /> - +
+ +
+

+ When you click the button above, you agree with our{" "} + + terms and conditions of service. + +

); diff --git a/web/components/account/sign-in-forms/root.tsx b/web/components/account/sign-in-forms/root.tsx index d2d6ef753..9797c8573 100644 --- a/web/components/account/sign-in-forms/root.tsx +++ b/web/components/account/sign-in-forms/root.tsx @@ -23,6 +23,8 @@ type Props = { handleSignInRedirection: () => Promise; }; +const OAUTH_HIDDEN_STEPS = [ESignInSteps.OPTIONAL_SET_PASSWORD, ESignInSteps.CREATE_PASSWORD]; + export const SignInRoot: React.FC = (props) => { const { handleSignInRedirection } = props; // states @@ -72,7 +74,7 @@ export const SignInRoot: React.FC = (props) => { /> )}
- {signInStep !== ESignInSteps.OPTIONAL_SET_PASSWORD && ( + {!OAUTH_HIDDEN_STEPS.includes(signInStep) && ( setEmail(newEmail)} handleStepChange={(step: ESignInSteps) => setSignInStep(step)} diff --git a/web/components/account/sign-in-forms/set-password-link.tsx b/web/components/account/sign-in-forms/set-password-link.tsx index 34e643b92..f683a26fc 100644 --- a/web/components/account/sign-in-forms/set-password-link.tsx +++ b/web/components/account/sign-in-forms/set-password-link.tsx @@ -1,4 +1,5 @@ import React, { useState } from "react"; +import Link from "next/link"; import { Controller, useForm } from "react-hook-form"; import { XCircle } from "lucide-react"; // services @@ -48,6 +49,13 @@ export const SetPasswordLink: React.FC = (props) => { await authService .emailCheck(payload) + .then(() => + setToastAlert({ + type: "success", + title: "Success!", + message: "We have sent a new link to your email.", + }) + ) .catch((err) => setToastAlert({ type: "error", @@ -60,15 +68,15 @@ export const SetPasswordLink: React.FC = (props) => { return ( <> -

- Get on your flight deck! +

+ Get on your flight deck

- We have sent a link to {email}, so you can set a + We have sent a link to {email}, so you can set a password

-
+
= (props) => { )} />
-
- -
+ +

+ When you click the button above, you agree with our{" "} + + terms and conditions of service. + +

); diff --git a/web/components/account/sign-in-forms/unique-code.tsx b/web/components/account/sign-in-forms/unique-code.tsx index 0091819f8..43e183fd5 100644 --- a/web/components/account/sign-in-forms/unique-code.tsx +++ b/web/components/account/sign-in-forms/unique-code.tsx @@ -1,4 +1,5 @@ import React, { useState } from "react"; +import Link from "next/link"; import { Controller, useForm } from "react-hook-form"; import { CornerDownLeft, XCircle } from "lucide-react"; // services @@ -44,7 +45,7 @@ export const UniqueCodeForm: React.FC = (props) => { // toast alert const { setToastAlert } = useToast(); // timer - const { timer: resendTimerCode, setTimer: setResendCodeTimer } = useTimer(); + const { timer: resendTimerCode, setTimer: setResendCodeTimer } = useTimer(30); // form info const { control, @@ -131,11 +132,9 @@ export const UniqueCodeForm: React.FC = (props) => { return ( <> -

- Moving to the runway -

-

- Paste the code you got at {email} below. +

Moving to the runway

+

+ Paste the code you got at {email} below

@@ -201,20 +200,24 @@ export const UniqueCodeForm: React.FC = (props) => { /> )} /> - +
+ +
+

+ When you click Confirm above, you agree with our{" "} + + terms and conditions of service. + +

); diff --git a/web/components/common/latest-feature-block.tsx b/web/components/common/latest-feature-block.tsx index 46309f6da..bade52d62 100644 --- a/web/components/common/latest-feature-block.tsx +++ b/web/components/common/latest-feature-block.tsx @@ -1,34 +1,35 @@ import Image from "next/image"; +import Link from "next/link"; import { useTheme } from "next-themes"; // icons import { Lightbulb } from "lucide-react"; // images -import signInIssues from "public/onboarding/onboarding-issues.svg"; +import latestFeatures from "public/onboarding/onboarding-pages.svg"; export const LatestFeatureBlock = () => { const { resolvedTheme } = useTheme(); return ( <> -
+
-

- Try the latest features, like Tiptap editor, to write compelling responses.{" "} - {}}> - See new features - +

+ Pages gets a facelift! Write anything and use Galileo to help you start.{" "} + + Learn more +

-
- Plane Issues +
+
+ Plane Issues +
); diff --git a/web/components/cycles/cycles-board-card.tsx b/web/components/cycles/cycles-board-card.tsx index 8a74565d5..22b59fc5f 100644 --- a/web/components/cycles/cycles-board-card.tsx +++ b/web/components/cycles/cycles-board-card.tsx @@ -33,7 +33,10 @@ export interface ICyclesBoardCard { export const CyclesBoardCard: FC = (props) => { const { cycle, workspaceSlug, projectId } = props; // store - const { cycle: cycleStore, trackEvent: { setTrackElement } } = useMobxStore(); + const { + cycle: cycleStore, + trackEvent: { setTrackElement }, + } = useMobxStore(); // toast const { setToastAlert } = useToast(); // states @@ -152,7 +155,7 @@ export const CyclesBoardCard: FC = (props) => { /> - +
@@ -268,7 +271,7 @@ export const CyclesBoardCard: FC = (props) => {
-
+
); diff --git a/web/components/cycles/cycles-list-item.tsx b/web/components/cycles/cycles-list-item.tsx index 531a637aa..30fcda221 100644 --- a/web/components/cycles/cycles-list-item.tsx +++ b/web/components/cycles/cycles-list-item.tsx @@ -153,7 +153,7 @@ export const CyclesListItem: FC = (props) => { projectId={projectId} /> - +
@@ -262,7 +262,7 @@ export const CyclesListItem: FC = (props) => {
- +
); diff --git a/web/components/inbox/issue-card.tsx b/web/components/inbox/issue-card.tsx index abf82208e..24f267506 100644 --- a/web/components/inbox/issue-card.tsx +++ b/web/components/inbox/issue-card.tsx @@ -27,71 +27,69 @@ export const InboxIssueCard: React.FC = (props) => { return ( - -
-
-

- {issue.project_detail?.identifier}-{issue.sequence_id} -

-
{issue.name}
-
-
- - - - -
- - {renderShortDateWithYearFormat(issue.created_at ?? "")} -
-
-
-
s.value === issueStatus)?.textColor - }`} - > - {issueStatus === -2 ? ( - <> - - Pending - - ) : issueStatus === -1 ? ( - <> - - Declined - - ) : issueStatus === 0 ? ( - <> - - - {new Date(issue.issue_inbox[0].snoozed_till ?? "") < new Date() ? "Snoozed date passed" : "Snoozed"} - - - ) : issueStatus === 1 ? ( - <> - - Accepted - - ) : ( - <> - - Duplicate - - )} -
+
+
+

+ {issue.project_detail?.identifier}-{issue.sequence_id} +

+
{issue.name}
- +
+ + + + +
+ + {renderShortDateWithYearFormat(issue.created_at ?? "")} +
+
+
+
s.value === issueStatus)?.textColor + }`} + > + {issueStatus === -2 ? ( + <> + + Pending + + ) : issueStatus === -1 ? ( + <> + + Declined + + ) : issueStatus === 0 ? ( + <> + + + {new Date(issue.issue_inbox[0].snoozed_till ?? "") < new Date() ? "Snoozed date passed" : "Snoozed"} + + + ) : issueStatus === 1 ? ( + <> + + Accepted + + ) : ( + <> + + Duplicate + + )} +
+
); }; diff --git a/web/components/instance/help-section.tsx b/web/components/instance/help-section.tsx index f880688f4..5d1e48c9c 100644 --- a/web/components/instance/help-section.tsx +++ b/web/components/instance/help-section.tsx @@ -98,12 +98,12 @@ export const InstanceHelpSection: FC = () => { if (href) return ( - +
{name} - +
); else diff --git a/web/components/instance/instance-admin-restriction.tsx b/web/components/instance/instance-admin-restriction.tsx index c1e8a47db..5551c0786 100644 --- a/web/components/instance/instance-admin-restriction.tsx +++ b/web/components/instance/instance-admin-restriction.tsx @@ -67,12 +67,10 @@ export const InstanceAdminRestriction: FC = ({ re
- - - +
diff --git a/web/components/instance/not-ready-view.tsx b/web/components/instance/not-ready-view.tsx index 501ab4d41..1333d65a6 100644 --- a/web/components/instance/not-ready-view.tsx +++ b/web/components/instance/not-ready-view.tsx @@ -1,29 +1,29 @@ import React from "react"; import Image from "next/image"; import { useTheme } from "next-themes"; -// image +// images import instanceNotReady from "public/instance/plane-instance-not-ready.webp"; import PlaneWhiteLogo from "public/plane-logos/white-horizontal-with-blue-logo.svg"; -import PlaneDarkLogo from "public/plane-logos/black-horizontal-with-blue-logo.svg"; +import PlaneBlackLogo from "public/plane-logos/black-horizontal-with-blue-logo.svg"; export const InstanceNotReady = () => { const { resolvedTheme } = useTheme(); - const planeLogo = resolvedTheme === "dark" ? PlaneWhiteLogo : PlaneDarkLogo; + const planeLogo = resolvedTheme === "dark" ? PlaneWhiteLogo : PlaneBlackLogo; return (
-
-
-
+
+
+
- image + Plane logo
- image + Instance not ready
-

Your Plane instance isn’t ready yet

+

Your Plane instance isn{"'"}t ready yet

Ask your Instance Admin to complete set-up first.

diff --git a/web/components/instance/setup-done-view.tsx b/web/components/instance/setup-done-view.tsx index 4885e006a..4eab4013f 100644 --- a/web/components/instance/setup-done-view.tsx +++ b/web/components/instance/setup-done-view.tsx @@ -1,43 +1,66 @@ -import React from "react"; +import React, { useState } from "react"; import Image from "next/image"; - +import { useTheme } from "next-themes"; // ui import { Button } from "@plane/ui"; import { UserCog2 } from "lucide-react"; -// image +// images import instanceSetupDone from "public/instance-setup-done.svg"; -import PlaneLogo from "public/plane-logos/blue-without-text.png"; +import PlaneBlackLogo from "public/plane-logos/black-horizontal-with-blue-logo.svg"; +import PlaneWhiteLogo from "public/plane-logos/white-horizontal-with-blue-logo.svg"; +import { useMobxStore } from "lib/mobx/store-provider"; -export const InstanceSetupDone = () => ( -
-
-
-
-
- image - To the stratosphere now! -
+export const InstanceSetupDone = () => { + // states + const [isRedirecting, setIsRedirecting] = useState(false); + // next-themes + const { resolvedTheme } = useTheme(); + // mobx store + const { + instance: { fetchInstanceInfo }, + } = useMobxStore(); -
- image -
+ const planeLogo = resolvedTheme === "dark" ? PlaneWhiteLogo : PlaneBlackLogo; -
- - Your instance is now ready for more security, more controls, and more intelligence. - - + const redirectToGodMode = async () => { + setIsRedirecting(true); -
- Use this wisely. Remember, with great power comes great responsibility.🕷️ + await fetchInstanceInfo().finally(() => setIsRedirecting(false)); + }; + + return ( +
+
+
+
+
+
+ Plane logo +
+
+ +
+
+ image +
+
+ +
+
+
+ Your instance is now ready for more security, more controls, and more intelligence. +
+

+ Use this wisely. Remember, with great power comes great responsibility. +

+
+
-
-); + ); +}; diff --git a/web/components/instance/setup-form/email-code-form.tsx b/web/components/instance/setup-form/email-code-form.tsx index 6264ba20f..24643d833 100644 --- a/web/components/instance/setup-form/email-code-form.tsx +++ b/web/components/instance/setup-form/email-code-form.tsx @@ -1,4 +1,4 @@ -import { FC } from "react"; +import { FC, useState } from "react"; import { useForm, Controller } from "react-hook-form"; // ui import { Input, Button } from "@plane/ui"; @@ -24,6 +24,8 @@ export interface IInstanceSetupEmailCodeForm { export const InstanceSetupEmailCodeForm: FC = (props) => { const { handleNextStep, email, moveBack } = props; + // states + const [isResendingCode, setIsResendingCode] = useState(false); // form info const { control, @@ -40,10 +42,10 @@ export const InstanceSetupEmailCodeForm: FC = (prop const { setToastAlert } = useToast(); const { timer, setTimer } = useTimer(30); // computed - const isResendDisabled = timer > 0 || isSubmitting; + const isResendDisabled = timer > 0 || isResendingCode; - const handleEmailCodeFormSubmit = (formValues: InstanceSetupEmailCodeFormValues) => - authService + const handleEmailCodeFormSubmit = async (formValues: InstanceSetupEmailCodeFormValues) => + await authService .instanceMagicSignIn({ key: `magic_${formValues.email}`, token: formValues.token }) .then(() => { reset(); @@ -51,42 +53,40 @@ export const InstanceSetupEmailCodeForm: FC = (prop }) .catch((err) => { setToastAlert({ - title: "Oops!", type: "error", - message: err?.error, + title: "Error!", + message: err?.error ?? "Something went wrong. Please try again.", }); }); - const resendMagicCode = () => { - setTimer(30); - authService + const resendMagicCode = async () => { + setIsResendingCode(true); + + await authService .instanceAdminEmailCode({ email }) - .then(() => { - // setCodeResending(false); - setTimer(30); - }) + .then(() => setTimer(30)) .catch((err) => { setToastAlert({ - title: "Oops!", type: "error", - message: err?.error, + title: "Error!", + message: err?.error ?? "Something went wrong. Please try again.", }); - }); + }) + .finally(() => setIsResendingCode(false)); }; return (
-
-

- Let’s secure your instance -

-
-

Paste the code you got at

- {email} - below. -
- -
+

+ Let{"'"}s secure your instance +

+

+ Paste the code you got at +
+ {email} below. +

+
+
= (prop ) || "Email address is not valid", }} render={({ field: { value, onChange } }) => ( -
+
= (prop
)} /> -
- {timer > 0 ? ( - Request new code in {timer}s - ) : isSubmitting ? ( - "Sending new code..." - ) : ( -
- -
- )} +
+
- ( -
- -
- )} - /> - -
+ ( +
+ +
+ )} + /> +
); diff --git a/web/components/instance/setup-form/email-form.tsx b/web/components/instance/setup-form/email-form.tsx index b2c336695..7305095a9 100644 --- a/web/components/instance/setup-form/email-form.tsx +++ b/web/components/instance/setup-form/email-form.tsx @@ -44,61 +44,59 @@ export const InstanceSetupEmailForm: FC = (props) => { }) .catch((err) => { setToastAlert({ - title: "Oops!", type: "error", - message: err?.error, + title: "Error!", + message: err?.error ?? "Something went wrong. Please try again.", }); }); return (
-
-

- Let’s secure your instance -

-

- Explore privacy options. Get AI features. Secure access.
Takes 2 minutes. -

- -
- - /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test( - value - ) || "Email address is not valid", - }} - render={({ field: { value, onChange } }) => ( -
- + Let{"'"}s secure your instance +
+

+ Explore privacy options. Get AI features. Secure access. +
+ Takes 2 minutes. +

+
+ + /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test( + value + ) || "Email address is not valid", + }} + render={({ field: { value, onChange } }) => ( +
+ + {value.length > 0 && ( + setValue("email", "")} /> - {value.length > 0 && ( - setValue("email", "")} - /> - )} -
- )} - /> -

- Use your email address if you are the instance admin.
Use your admin’s e-mail if you are not. -

- - -
+ )} +
+ )} + /> +

+ Use your email address if you are the instance admin.
Use your admin’s e-mail if you are not. +

+
); diff --git a/web/components/instance/setup-form/password-form.tsx b/web/components/instance/setup-form/password-form.tsx index f963b960e..86053cfd7 100644 --- a/web/components/instance/setup-form/password-form.tsx +++ b/web/components/instance/setup-form/password-form.tsx @@ -1,4 +1,5 @@ import React from "react"; +import Link from "next/link"; import { useForm, Controller } from "react-hook-form"; // ui import { Input, Button } from "@plane/ui"; @@ -43,14 +44,14 @@ export const InstanceSetupPasswordForm: React.FC = ( return (
-

+

Moving to the runway

- {"Let's set a password so you can do away with codes."} + Let{"'"}s set a password so you can do away with codes.

-
+
= (
)} /> - -
+
= (
)} /> +

+ Whatever you choose now will be your account{"'"}s password +

-

- {"Whatever you choose now will be your account's password"} -

+

+ When you click the button above, you agree with our{" "} + + terms and conditions of service. + +

diff --git a/web/components/instance/setup-form/root.tsx b/web/components/instance/setup-form/root.tsx index ebc6c5649..46f5493ba 100644 --- a/web/components/instance/setup-form/root.tsx +++ b/web/components/instance/setup-form/root.tsx @@ -21,13 +21,11 @@ export const InstanceSetupFormRoot = () => { return ( <> {setupStep === EInstanceSetupSteps.DONE ? ( -
- -
+ ) : ( -
-
-
+
+
+
{setupStep === EInstanceSetupSteps.EMAIL && ( { diff --git a/web/components/instance/setup-view.tsx b/web/components/instance/setup-view.tsx index 07d4455dd..e79f6321e 100644 --- a/web/components/instance/setup-view.tsx +++ b/web/components/instance/setup-view.tsx @@ -24,7 +24,7 @@ export const InstanceSetupView = observer(() => { return ( <> -
+
Plane Logo diff --git a/web/components/instance/sidebar-menu.tsx b/web/components/instance/sidebar-menu.tsx index 8feaa44c2..54a04529f 100644 --- a/web/components/instance/sidebar-menu.tsx +++ b/web/components/instance/sidebar-menu.tsx @@ -54,7 +54,7 @@ export const InstanceAdminSidebarMenu = () => { return ( - +
{ )}
- +
); })} diff --git a/web/components/issues/attachment/attachments.tsx b/web/components/issues/attachment/attachments.tsx index d2f592f2e..86cafd7a9 100644 --- a/web/components/issues/attachment/attachments.tsx +++ b/web/components/issues/attachment/attachments.tsx @@ -59,33 +59,31 @@ export const IssueAttachments = () => { key={file.id} className="flex h-[60px] items-center justify-between gap-1 rounded-md border-[2px] border-custom-border-200 bg-custom-background-100 px-4 py-2 text-sm" > - - -
-
{getFileIcon(getFileExtension(file.asset))}
-
-
- - {truncateText(`${getFileName(file.attributes.name)}`, 10)} - - person.member.id === file.updated_by)?.member.display_name ?? "" - } uploaded on ${renderLongDateFormat(file.updated_at)}`} - > - - - - -
+ +
+
{getFileIcon(getFileExtension(file.asset))}
+
+
+ + {truncateText(`${getFileName(file.attributes.name)}`, 10)} + + person.member.id === file.updated_by)?.member.display_name ?? "" + } uploaded on ${renderLongDateFormat(file.updated_at)}`} + > + + + + +
-
- {getFileExtension(file.asset).toUpperCase()} - {convertBytesToSize(file.attributes.size)} -
+
+ {getFileExtension(file.asset).toUpperCase()} + {convertBytesToSize(file.attributes.size)}
- +
- ) : ( - - )} - - )} - {!page.archived_at && userCanEdit && ( - - {page.access ? ( - - ) : ( - - )} - - )} + /> + {label.name} +
+ ))} +
+
+ {page.archived_at ? ( projectMember.member.id === page.created_by)?.member - .display_name ?? "" - } on ${renderLongDateFormat(`${page.created_at}`)}`} + tooltipContent={`Archived at ${render24HourFormatTime(page.archived_at)} on ${renderShortDate( + page.archived_at + )}`} > - +

{render24HourFormatTime(page.archived_at)}

- {page.archived_at ? ( - - {userCanEdit && ( - <> - -
- - Restore page -
-
- -
- - Delete page -
-
- - )} - -
- - Copy page link -
-
-
- ) : ( - - {userCanEdit && ( - <> - -
- - Edit page -
-
- -
- - Archive page -
-
- - )} - -
- - Copy page link -
-
-
- )} -
+ ) : ( + +

{render24HourFormatTime(page.updated_at)}

+
+ )} + {!page.archived_at && userCanEdit && ( + + {page.is_favorite ? ( + + ) : ( + + )} + + )} + {!page.archived_at && userCanEdit && ( + + {page.access ? ( + + ) : ( + + )} + + )} + projectMember.member.id === page.created_by)?.member + .display_name ?? "" + } on ${renderLongDateFormat(`${page.created_at}`)}`} + > + + + {page.archived_at ? ( + + {userCanEdit && ( + <> + +
+ + Restore page +
+
+ +
+ + Delete page +
+
+ + )} + +
+ + Copy page link +
+
+
+ ) : ( + + {userCanEdit && ( + <> + +
+ + Edit page +
+
+ +
+ + Archive page +
+
+ + )} + +
+ + Copy page link +
+
+
+ )}
- +
diff --git a/web/components/views/view-list-item.tsx b/web/components/views/view-list-item.tsx index 7ed182c84..65d2e4e14 100644 --- a/web/components/views/view-list-item.tsx +++ b/web/components/views/view-list-item.tsx @@ -57,7 +57,7 @@ export const ProjectViewListItem: React.FC = observer((props) => { setDeleteViewModal(false)} />
- +
@@ -128,7 +128,7 @@ export const ProjectViewListItem: React.FC = observer((props) => {
- +
diff --git a/web/components/workspace/views/default-view-list-item.tsx b/web/components/workspace/views/default-view-list-item.tsx index a168f20ea..d75d8271c 100644 --- a/web/components/workspace/views/default-view-list-item.tsx +++ b/web/components/workspace/views/default-view-list-item.tsx @@ -18,7 +18,7 @@ export const GlobalDefaultViewListItem: React.FC = observer((props) => { return (
- +
@@ -29,7 +29,7 @@ export const GlobalDefaultViewListItem: React.FC = observer((props) => {
- +
); diff --git a/web/components/workspace/views/view-list-item.tsx b/web/components/workspace/views/view-list-item.tsx index 29a4627a0..a84c2d558 100644 --- a/web/components/workspace/views/view-list-item.tsx +++ b/web/components/workspace/views/view-list-item.tsx @@ -32,7 +32,7 @@ export const GlobalViewListItem: React.FC = observer((props) => { setDeleteViewModal(false)} />
- +
@@ -77,7 +77,7 @@ export const GlobalViewListItem: React.FC = observer((props) => {
- +
diff --git a/web/layouts/admin-layout/layout.tsx b/web/layouts/admin-layout/layout.tsx index 1a5c188eb..044a2b77b 100644 --- a/web/layouts/admin-layout/layout.tsx +++ b/web/layouts/admin-layout/layout.tsx @@ -1,4 +1,5 @@ import { FC, ReactNode } from "react"; +import { observer } from "mobx-react-lite"; // layouts import { AdminAuthWrapper, UserAuthWrapper } from "layouts/auth-layout"; // components @@ -12,20 +13,14 @@ export interface IInstanceAdminLayout { children: ReactNode; } -export const InstanceAdminLayout: FC = (props) => { +export const InstanceAdminLayout: FC = observer((props) => { const { children } = props; // store const { instance: { instance }, - user: { currentUser }, } = useMobxStore(); - // fetch - console.log("instance", instance); - - if (instance?.is_setup_done === false) { - return ; - } + if (instance?.is_setup_done === false) return ; return ( <> @@ -46,4 +41,4 @@ export const InstanceAdminLayout: FC = (props) => { ); -}; +}); diff --git a/web/layouts/auth-layout/user-wrapper.tsx b/web/layouts/auth-layout/user-wrapper.tsx index 2bd6ac8ac..0f2ccf5e1 100644 --- a/web/layouts/auth-layout/user-wrapper.tsx +++ b/web/layouts/auth-layout/user-wrapper.tsx @@ -21,7 +21,6 @@ export const UserAuthWrapper: FC = observer((props) => { fetchCurrentUser, fetchCurrentUserInstanceAdminStatus, fetchCurrentUserSettings, - }, workspace: { fetchWorkspaces }, } = useMobxStore(); diff --git a/web/layouts/instance-layout/index.tsx b/web/layouts/instance-layout/index.tsx index 75aa374c1..858f30c8d 100644 --- a/web/layouts/instance-layout/index.tsx +++ b/web/layouts/instance-layout/index.tsx @@ -24,7 +24,9 @@ const InstanceLayout: FC = observer(({ children }) => { const router = useRouter(); const isGodMode = router.pathname.includes("god-mode"); - useSWR("INSTANCE_INFO", () => fetchInstanceInfo()); + useSWR("INSTANCE_INFO", () => fetchInstanceInfo(), { + revalidateOnFocus: false, + }); useEffect(() => { if (instance?.is_activated === false) { diff --git a/web/layouts/settings-layout/profile/sidebar.tsx b/web/layouts/settings-layout/profile/sidebar.tsx index 759c29429..6ee161fbd 100644 --- a/web/layouts/settings-layout/profile/sidebar.tsx +++ b/web/layouts/settings-layout/profile/sidebar.tsx @@ -102,18 +102,19 @@ export const ProfileLayoutSidebar = observer(() => { } ${sidebarCollapsed ? "left-0" : "-left-full md:left-0"}`} >
- - - - - {!sidebarCollapsed && ( -

Profile settings

- )} + +
+ + + + {!sidebarCollapsed && ( +

Profile settings

+ )} +
{!sidebarCollapsed && ( diff --git a/web/layouts/settings-layout/project/sidebar.tsx b/web/layouts/settings-layout/project/sidebar.tsx index dfd975cd3..b025a53d2 100644 --- a/web/layouts/settings-layout/project/sidebar.tsx +++ b/web/layouts/settings-layout/project/sidebar.tsx @@ -50,17 +50,15 @@ export const ProjectSettingsSidebar = () => {
{projectLinks.map((link) => ( - -
- {link.label} -
-
+
+ {link.label} +
))}
diff --git a/web/pages/accounts/password.tsx b/web/pages/accounts/password.tsx index 8c031a408..f85840cda 100644 --- a/web/pages/accounts/password.tsx +++ b/web/pages/accounts/password.tsx @@ -1,4 +1,4 @@ -import { ReactElement } from "react"; +import { ReactElement, useCallback } from "react"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; @@ -15,11 +15,14 @@ import DefaultLayout from "layouts/default-layout"; import { Button, Input } from "@plane/ui"; // images import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; -import signInIssues from "public/onboarding/onboarding-issues.svg"; +import latestFeatures from "public/onboarding/onboarding-pages.svg"; // helpers import { checkEmailValidity } from "helpers/string.helper"; // type import { NextPageWithLayout } from "types/app"; +import { useMobxStore } from "lib/mobx/store-provider"; +// types +import { IUser, IUserSettings } from "types"; type TResetPasswordFormValues = { email: string; @@ -42,6 +45,10 @@ const HomePage: NextPageWithLayout = () => { const { resolvedTheme } = useTheme(); // toast const { setToastAlert } = useToast(); + // mobx store + const { + user: { fetchCurrentUser, fetchCurrentUserSettings }, + } = useMobxStore(); // form info const { control, @@ -54,6 +61,31 @@ const HomePage: NextPageWithLayout = () => { }, }); + const handleSignInRedirection = useCallback( + async (user: IUser) => { + // if the user is not onboarded, redirect them to the onboarding page + if (!user.is_onboarded) { + router.push("/onboarding"); + return; + } + + // if the user is onboarded, fetch their last workspace details + await fetchCurrentUserSettings().then((userSettings: IUserSettings) => { + const workspaceSlug = + userSettings?.workspace?.last_workspace_slug || userSettings?.workspace?.fallback_workspace_slug; + if (workspaceSlug) router.push(`/${workspaceSlug}`); + else router.push("/profile"); + }); + }, + [fetchCurrentUserSettings, router] + ); + + const mutateUserInfo = useCallback(async () => { + await fetchCurrentUser().then(async (user) => { + await handleSignInRedirection(user); + }); + }, [fetchCurrentUser, handleSignInRedirection]); + const handleResetPassword = async (formData: TResetPasswordFormValues) => { if (!uidb64 || !token || !email) return; @@ -61,13 +93,16 @@ const HomePage: NextPageWithLayout = () => { new_password: formData.password, }; - await authService.resetPassword(uidb64.toString(), token.toString(), payload).catch((err) => - setToastAlert({ - type: "error", - title: "Error!", - message: err?.error ?? "Something went wrong. Please try again.", - }) - ); + await authService + .resetPassword(uidb64.toString(), token.toString(), payload) + .then(() => mutateUserInfo()) + .catch((err) => + setToastAlert({ + type: "error", + title: "Error!", + message: err?.error ?? "Something went wrong. Please try again.", + }) + ); }; return ( @@ -82,7 +117,7 @@ const HomePage: NextPageWithLayout = () => {
-

+

Let{"'"}s get a new password

@@ -123,6 +158,7 @@ const HomePage: NextPageWithLayout = () => { hasError={Boolean(errors.password)} placeholder="Choose password" className="w-full h-[46px] placeholder:text-onboarding-text-400 border border-onboarding-border-100 pr-12" + minLength={8} /> )} /> @@ -142,13 +178,8 @@ const HomePage: NextPageWithLayout = () => {

When you click the button above, you agree with our{" "} - - terms and conditions of service. + + terms and conditions of service.

@@ -157,25 +188,21 @@ const HomePage: NextPageWithLayout = () => {

Try the latest features, like Tiptap editor, to write compelling responses.{" "} - - - See new features - + + See new features

-
- Plane Issues +
+
+ Plane Issues +
diff --git a/web/public/onboarding/onboarding-pages.svg b/web/public/onboarding/onboarding-pages.svg new file mode 100644 index 000000000..93118ebe0 --- /dev/null +++ b/web/public/onboarding/onboarding-pages.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/services/auth.service.ts b/web/services/auth.service.ts index 43511d33d..47325cf30 100644 --- a/web/services/auth.service.ts +++ b/web/services/auth.service.ts @@ -55,7 +55,7 @@ export class AuthService extends APIService { new_password: string; } ): Promise { - return this.post(`/api/reset-password/${uidb64}/${token}/`, data) + return this.post(`/api/reset-password/${uidb64}/${token}/`, data, { headers: {} }) .then((response) => { if (response?.status === 200) { this.setAccessToken(response?.data?.access_token); diff --git a/web/store/instance/instance.store.ts b/web/store/instance/instance.store.ts index 81db85ae8..a846b8e2e 100644 --- a/web/store/instance/instance.store.ts +++ b/web/store/instance/instance.store.ts @@ -71,7 +71,7 @@ export class InstanceStore implements IInstanceStore { } /** - * fetch instace info from API + * fetch instance info from API */ fetchInstanceInfo = async () => { try { From 8c462f96ee08e731a88ac0fd71490649044f3114 Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Fri, 1 Dec 2023 17:35:33 +0530 Subject: [PATCH 12/17] fix: corrected rendering of workspace-level labels, members, and states in project view (#2966) * fix: dynamic issue properties filters in project, workspace and profile level * clean-up: removed logs from the project store --- .../issue-layouts/kanban/properties.tsx | 3 ++ .../issues/issue-layouts/list/properties.tsx | 3 ++ .../issue-layouts/properties/assignee.tsx | 30 +++++++++---------- .../issue-layouts/properties/labels.tsx | 22 +++++++++----- .../issues/issue-layouts/properties/state.tsx | 11 +++---- .../spreadsheet/columns/assignee-column.tsx | 1 + .../spreadsheet/columns/label-column.tsx | 1 + .../spreadsheet/columns/state-column.tsx | 1 + 8 files changed, 44 insertions(+), 28 deletions(-) diff --git a/web/components/issues/issue-layouts/kanban/properties.tsx b/web/components/issues/issue-layouts/kanban/properties.tsx index c3c677208..929311f31 100644 --- a/web/components/issues/issue-layouts/kanban/properties.tsx +++ b/web/components/issues/issue-layouts/kanban/properties.tsx @@ -89,6 +89,7 @@ export const KanBanProperties: React.FC = observer((props) => = observer((props) => = observer((props) => = observer((props) => { = observer((props) => { = observer((props) => { void; disabled?: boolean; hideDropdownArrow?: boolean; @@ -27,6 +29,7 @@ export const IssuePropertyAssignee: React.FC = observer( const { projectId, value, + defaultOptions = [], onChange, disabled = false, hideDropdownArrow = false, @@ -40,8 +43,7 @@ export const IssuePropertyAssignee: React.FC = observer( // store const { workspace: workspaceStore, - project: projectStore, - workspaceMember: { workspaceMembers, fetchWorkspaceMembers }, + projectMember: { projectMembers: _projectMembers, fetchProjectMembers }, } = useMobxStore(); const workspaceSlug = workspaceStore?.workspaceSlug; // states @@ -50,20 +52,16 @@ export const IssuePropertyAssignee: React.FC = observer( const [popperElement, setPopperElement] = useState(null); const [isLoading, setIsLoading] = useState(false); - // const fetchProjectMembers = () => { - // setIsLoading(true); - // if (workspaceSlug && projectId) - // workspaceSlug && - // projectId && - // projectStore.fetchProjectMembers(workspaceSlug, projectId).then(() => setIsLoading(false)); - // }; - const getWorkspaceMembers = () => { setIsLoading(true); - if (workspaceSlug) workspaceSlug && fetchWorkspaceMembers(workspaceSlug).then(() => setIsLoading(false)); + if (workspaceSlug && projectId) fetchProjectMembers(workspaceSlug, projectId).then(() => setIsLoading(false)); }; - const options = (workspaceMembers ?? [])?.map((member) => ({ + const updatedDefaultOptions: IProjectMember[] = + defaultOptions.map((member: any) => ({ member: { ...member } })) ?? []; + const projectMembers = _projectMembers ?? updatedDefaultOptions; + + const options = projectMembers?.map((member) => ({ value: member.member.id, query: member.member.display_name, content: ( @@ -82,7 +80,7 @@ export const IssuePropertyAssignee: React.FC = observer( // if multiple assignees if (Array.isArray(value)) { - const assignees = workspaceMembers?.filter((m) => value.includes(m.member.id)); + const assignees = projectMembers?.filter((m) => value.includes(m.member.id)); if (!assignees || assignees.length === 0) return "No Assignee"; @@ -93,7 +91,7 @@ export const IssuePropertyAssignee: React.FC = observer( } // if single assignee - const assignee = workspaceMembers?.find((m) => m.member.id === value)?.member; + const assignee = projectMembers?.find((m) => m.member.id === value)?.member; if (!assignee) return "No Assignee"; @@ -107,7 +105,7 @@ export const IssuePropertyAssignee: React.FC = observer( {value && value.length > 0 && Array.isArray(value) ? ( {value.map((assigneeId) => { - const member = workspaceMembers?.find((m) => m.member.id === assigneeId)?.member; + const member = projectMembers?.find((m) => m.member.id === assigneeId)?.member; if (!member) return null; return ; })} @@ -149,7 +147,7 @@ export const IssuePropertyAssignee: React.FC = observer( className={`flex items-center justify-between gap-1 w-full text-xs ${ disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer hover:bg-custom-background-80" } ${buttonClassName}`} - onClick={() => !workspaceMembers && getWorkspaceMembers()} + onClick={() => !projectMembers && getWorkspaceMembers()} > {label} {!hideDropdownArrow && !disabled &&
) : ( <> - , - text: "New Project", + text: "Create Cycles and Modules first", onClick: () => { setTrackElement("ANALYTICS_EMPTY_STATE"); toggleCreateProjectModal(true); diff --git a/web/public/empty-state/empty_analytics.webp b/web/public/empty-state/empty_analytics.webp new file mode 100644 index 0000000000000000000000000000000000000000..46174fff27e9979eb4a64aed3a32f0e59f00761c GIT binary patch literal 12182 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 WeidlOD_8mZB}odtrRSdyKmQLA`lEmV literal 0 HcmV?d00001 diff --git a/web/public/empty-state/empty_modules.webp b/web/public/empty-state/empty_modules.webp index 745dce48db9880929ddfe1a8d7f82bca6d4c281d..49050b53bfdaba6abba35b138bcc00f041e508f2 100644 GIT binary patch literal 18618 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 literal 0 HcmV?d00001 From d22c258c00c941872e37cf02e48941c3c7c91a8c Mon Sep 17 00:00:00 2001 From: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Date: Fri, 1 Dec 2023 18:51:52 +0530 Subject: [PATCH 14/17] fix: V3 release blocker bugs (#2968) * fix add subgroup issue FED-1101 * fix subgroup by None assignee FED-1100 * fix grouping by asignee or labels FED-1096 * fix create view popup FED-1093 * fix subgroup exception in swimlanes --- web/components/issues/form.tsx | 3 ++- .../issue-layouts/kanban/base-kanban-root.tsx | 1 + .../issues/issue-layouts/kanban/swimlanes.tsx | 23 +++++++++++++++++++ web/components/views/modal.tsx | 7 +++++- web/store/command-palette.store.ts | 20 ++++++++-------- .../issues/project-issues/base-issue.store.ts | 11 +++++---- 6 files changed, 49 insertions(+), 16 deletions(-) diff --git a/web/components/issues/form.tsx b/web/components/issues/form.tsx index e58bed089..643b60460 100644 --- a/web/components/issues/form.tsx +++ b/web/components/issues/form.tsx @@ -226,9 +226,10 @@ export const IssueForm: FC = observer((props) => { reset({ ...defaultValues, + project: projectId, ...initialData, }); - }, [setFocus, reset]); + }, [setFocus, initialData, reset]); // update projectId in form when projectId changes useEffect(() => { diff --git a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx index aa3ba503e..e63b6e745 100644 --- a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx +++ b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx @@ -267,6 +267,7 @@ export const BaseKanBanRoot: React.FC = observer((props: IBas enableQuickIssueCreate={enableQuickAdd} isReadOnly={!enableInlineEditing || !isEditingAllowed} currentStore={currentStore} + quickAddCallback={issueStore?.quickAddIssue} addIssuesToView={(issues) => { console.log("kanban existingIds", issues); diff --git a/web/components/issues/issue-layouts/kanban/swimlanes.tsx b/web/components/issues/issue-layouts/kanban/swimlanes.tsx index e599a9e59..1c6d23162 100644 --- a/web/components/issues/issue-layouts/kanban/swimlanes.tsx +++ b/web/components/issues/issue-layouts/kanban/swimlanes.tsx @@ -91,6 +91,12 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader { currentStore?: EProjectStore; enableQuickIssueCreate: boolean; isReadOnly: boolean; + quickAddCallback?: ( + workspaceSlug: string, + projectId: string, + data: IIssue, + viewId?: string + ) => Promise; } const SubGroupSwimlane: React.FC = observer((props) => { const { @@ -118,6 +124,7 @@ const SubGroupSwimlane: React.FC = observer((props) => { enableQuickIssueCreate, isReadOnly, addIssuesToView, + quickAddCallback, } = props; const calculateIssueCount = (column_id: string) => { @@ -176,6 +183,7 @@ const SubGroupSwimlane: React.FC = observer((props) => { isDragStarted={isDragStarted} isReadOnly={isReadOnly} addIssuesToView={addIssuesToView} + quickAddCallback={quickAddCallback} />

)} @@ -208,6 +216,12 @@ export interface IKanBanSwimLanes { currentStore?: EProjectStore; addIssuesToView?: (issueIds: string[]) => Promise; enableQuickIssueCreate: boolean; + quickAddCallback?: ( + workspaceSlug: string, + projectId: string, + data: IIssue, + viewId?: string + ) => Promise; isReadOnly: boolean; } @@ -236,6 +250,7 @@ export const KanBanSwimLanes: React.FC = observer((props) => { isReadOnly, currentStore, addIssuesToView, + quickAddCallback, } = props; return ( @@ -378,6 +393,7 @@ export const KanBanSwimLanes: React.FC = observer((props) => { disableIssueCreation={disableIssueCreation} enableQuickIssueCreate={enableQuickIssueCreate} isReadOnly={isReadOnly} + quickAddCallback={quickAddCallback} /> )} @@ -406,6 +422,7 @@ export const KanBanSwimLanes: React.FC = observer((props) => { disableIssueCreation={disableIssueCreation} enableQuickIssueCreate={enableQuickIssueCreate} isReadOnly={isReadOnly} + quickAddCallback={quickAddCallback} /> )} @@ -434,6 +451,7 @@ export const KanBanSwimLanes: React.FC = observer((props) => { disableIssueCreation={disableIssueCreation} enableQuickIssueCreate={enableQuickIssueCreate} isReadOnly={isReadOnly} + quickAddCallback={quickAddCallback} /> )} @@ -462,6 +480,7 @@ export const KanBanSwimLanes: React.FC = observer((props) => { disableIssueCreation={disableIssueCreation} enableQuickIssueCreate={enableQuickIssueCreate} isReadOnly={isReadOnly} + quickAddCallback={quickAddCallback} /> )} @@ -490,6 +509,7 @@ export const KanBanSwimLanes: React.FC = observer((props) => { disableIssueCreation={disableIssueCreation} enableQuickIssueCreate={enableQuickIssueCreate} isReadOnly={isReadOnly} + quickAddCallback={quickAddCallback} /> )} @@ -518,6 +538,7 @@ export const KanBanSwimLanes: React.FC = observer((props) => { disableIssueCreation={disableIssueCreation} enableQuickIssueCreate={enableQuickIssueCreate} isReadOnly={isReadOnly} + quickAddCallback={quickAddCallback} /> )} @@ -546,6 +567,7 @@ export const KanBanSwimLanes: React.FC = observer((props) => { disableIssueCreation={disableIssueCreation} enableQuickIssueCreate={enableQuickIssueCreate} isReadOnly={isReadOnly} + quickAddCallback={quickAddCallback} /> )} @@ -574,6 +596,7 @@ export const KanBanSwimLanes: React.FC = observer((props) => { disableIssueCreation={disableIssueCreation} enableQuickIssueCreate={enableQuickIssueCreate} isReadOnly={isReadOnly} + quickAddCallback={quickAddCallback} /> )}
diff --git a/web/components/views/modal.tsx b/web/components/views/modal.tsx index 8cc1a5981..0d1d6802d 100644 --- a/web/components/views/modal.tsx +++ b/web/components/views/modal.tsx @@ -8,6 +8,7 @@ import useToast from "hooks/use-toast"; import { ProjectViewForm } from "components/views"; // types import { IProjectView } from "types"; +import { debounce } from "lodash"; type Props = { data?: IProjectView | null; @@ -33,7 +34,9 @@ export const CreateUpdateProjectViewModal: FC = observer((props) => { await projectViewsStore .createView(workspaceSlug, projectId, payload) .then(() => { + console.log("after calling store"); handleClose(); + console.log("after closing"); setToastAlert({ type: "success", title: "Success!", @@ -67,6 +70,8 @@ export const CreateUpdateProjectViewModal: FC = observer((props) => { else await updateView(formData); }; + const debouncedFormSubmit = debounce(handleFormSubmit, 10, { leading: false, trailing: true }); + return ( @@ -97,7 +102,7 @@ export const CreateUpdateProjectViewModal: FC = observer((props) => { Promise} preLoadedData={preLoadedData} /> diff --git a/web/store/command-palette.store.ts b/web/store/command-palette.store.ts index b2dac49c5..b2a1be116 100644 --- a/web/store/command-palette.store.ts +++ b/web/store/command-palette.store.ts @@ -115,7 +115,7 @@ class CommandPaletteStore implements ICommandPaletteStore { } toggleCommandPaletteModal = (value?: boolean) => { - if (value) { + if (value !== undefined) { this.isCommandPaletteOpen = value; } else { this.isCommandPaletteOpen = !this.isCommandPaletteOpen; @@ -123,7 +123,7 @@ class CommandPaletteStore implements ICommandPaletteStore { }; toggleShortcutModal = (value?: boolean) => { - if (value) { + if (value !== undefined) { this.isShortcutModalOpen = value; } else { this.isShortcutModalOpen = !this.isShortcutModalOpen; @@ -131,7 +131,7 @@ class CommandPaletteStore implements ICommandPaletteStore { }; toggleCreateProjectModal = (value?: boolean) => { - if (value) { + if (value !== undefined) { this.isCreateProjectModalOpen = value; } else { this.isCreateProjectModalOpen = !this.isCreateProjectModalOpen; @@ -139,7 +139,7 @@ class CommandPaletteStore implements ICommandPaletteStore { }; toggleCreateCycleModal = (value?: boolean) => { - if (value) { + if (value !== undefined) { this.isCreateCycleModalOpen = value; } else { this.isCreateCycleModalOpen = !this.isCreateCycleModalOpen; @@ -147,7 +147,7 @@ class CommandPaletteStore implements ICommandPaletteStore { }; toggleCreateViewModal = (value?: boolean) => { - if (value) { + if (value !== undefined) { this.isCreateViewModalOpen = value; } else { this.isCreateViewModalOpen = !this.isCreateViewModalOpen; @@ -155,7 +155,7 @@ class CommandPaletteStore implements ICommandPaletteStore { }; toggleCreatePageModal = (value?: boolean) => { - if (value) { + if (value !== undefined) { this.isCreatePageModalOpen = value; } else { this.isCreatePageModalOpen = !this.isCreatePageModalOpen; @@ -163,7 +163,7 @@ class CommandPaletteStore implements ICommandPaletteStore { }; toggleCreateIssueModal = (value?: boolean, storeType?: EProjectStore) => { - if (value) { + if (value !== undefined) { this.isCreateIssueModalOpen = value; this.createIssueStoreType = storeType || EProjectStore.PROJECT; } else { @@ -173,7 +173,7 @@ class CommandPaletteStore implements ICommandPaletteStore { }; toggleDeleteIssueModal = (value?: boolean) => { - if (value) { + if (value !== undefined) { this.isDeleteIssueModalOpen = value; } else { this.isDeleteIssueModalOpen = !this.isDeleteIssueModalOpen; @@ -181,7 +181,7 @@ class CommandPaletteStore implements ICommandPaletteStore { }; toggleCreateModuleModal = (value?: boolean) => { - if (value) { + if (value !== undefined) { this.isCreateModuleModalOpen = value; } else { this.isCreateModuleModalOpen = !this.isCreateModuleModalOpen; @@ -189,7 +189,7 @@ class CommandPaletteStore implements ICommandPaletteStore { }; toggleBulkDeleteIssueModal = (value?: boolean) => { - if (value) { + if (value !== undefined) { this.isBulkDeleteIssueModalOpen = value; } else { this.isBulkDeleteIssueModalOpen = !this.isBulkDeleteIssueModalOpen; diff --git a/web/store/issues/project-issues/base-issue.store.ts b/web/store/issues/project-issues/base-issue.store.ts index c483f8784..c4940337f 100644 --- a/web/store/issues/project-issues/base-issue.store.ts +++ b/web/store/issues/project-issues/base-issue.store.ts @@ -91,8 +91,9 @@ export class IssueBaseStore implements IIssueBaseStore { for (const subGroup of subGroupArray) { for (const group of groupArray) { - if (subGroup && group && _issues[subGroup][group]) _issues[subGroup][group].push(_issue.id); - else if (subGroup && group) _issues[subGroup][group] = [_issue.id]; + if (subGroup && group && _issues?.[subGroup]?.[group]) _issues[subGroup][group].push(_issue.id); + else if (subGroup && group && _issues[subGroup]) _issues[subGroup][group] = [_issue.id]; + else if (subGroup && group) _issues[subGroup] = { [group]: [_issue.id] }; } } } @@ -197,8 +198,10 @@ export class IssueBaseStore implements IIssueBaseStore { }; getGroupArray(value: string[] | string | null, isDate: boolean = false) { - if (Array.isArray(value)) return value; - else if (isDate) return [renderDateFormat(value) || "None"]; + if (Array.isArray(value)) { + if (value.length) return value; + else return ["None"]; + } else if (isDate) return [renderDateFormat(value) || "None"]; else return [value || "None"]; } } From f4aa059da6682a44ad32ce15b285b2bd0d72edd4 Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Sat, 2 Dec 2023 18:19:29 +0530 Subject: [PATCH 15/17] fix: global issues properties updation issue resolved (#2974) --- .../roots/all-issue-layout-root.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx index a49af10ae..9f7fbdb7c 100644 --- a/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx @@ -63,23 +63,23 @@ export const AllIssueLayoutRoot: React.FC = observer((props) => { const issueActions = { [EIssueActions.UPDATE]: async (issue: IIssue) => { - if (!workspaceSlug) return; + const projectId = issue.project; + if (!workspaceSlug || !projectId) return; - await updateIssue(workspaceSlug, issue.project, issue.id, issue); + await updateIssue(workspaceSlug, projectId, issue.id, issue, currentIssueView); }, [EIssueActions.DELETE]: async (issue: IIssue) => { - if (!workspaceSlug) return; + const projectId = issue.project; + if (!workspaceSlug || !projectId) return; - await removeIssue(workspaceSlug, issue.project, issue.id); + await removeIssue(workspaceSlug, projectId, issue.id, currentIssueView); }, }; const handleIssues = useCallback( async (issue: IIssue, action: EIssueActions) => { - if (issueActions && action && issue) { - if (action === EIssueActions.UPDATE) await issueActions[action]!(issue); - if (action === EIssueActions.DELETE) await issueActions[action]!(issue); - } + if (action === EIssueActions.UPDATE) await issueActions[action]!(issue); + if (action === EIssueActions.DELETE) await issueActions[action]!(issue); }, [getIssues] ); From 9756abece42cd43452f724f6719aabe9369a12c6 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Sat, 2 Dec 2023 18:19:59 +0530 Subject: [PATCH 16/17] chore: update instance admin sign in endpoint (#2973) --- web/services/auth.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/services/auth.service.ts b/web/services/auth.service.ts index 47325cf30..175fe8a76 100644 --- a/web/services/auth.service.ts +++ b/web/services/auth.service.ts @@ -135,7 +135,7 @@ export class AuthService extends APIService { } async instanceMagicSignIn(data: any): Promise { - const response = await this.post("/api/licenses/instances/admins/magic-sign-in/", data); + const response = await this.post("/api/licenses/instances/admins/magic-sign-in/", data, { headers: {} }); if (response?.status === 200) { this.setAccessToken(response?.data?.access_token); this.setRefreshToken(response?.data?.refresh_token); From 59110c7205eda8084618074495b84ea8ae83b23a Mon Sep 17 00:00:00 2001 From: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Date: Sat, 2 Dec 2023 18:23:07 +0530 Subject: [PATCH 17/17] fix: sub display filter params for fetching issues (#2972) * fix add subgroup issue FED-1101 * fix subgroup by None assignee FED-1100 * fix grouping by asignee or labels FED-1096 * fix create view popup FED-1093 * fix subgroup exception in swimlanes * fix show sub issue filter FED-1102 --- web/store/issues/global/filter.store.ts | 7 +++++++ web/store/issues/profile/filter.store.ts | 11 ++++++++--- .../issues/project-issues/archived/filter.store.ts | 11 ++++++++--- web/store/issues/project-issues/cycle/filter.store.ts | 11 ++++++++--- web/store/issues/project-issues/draft/filter.store.ts | 11 ++++++++--- .../issues/project-issues/module/filter.store.ts | 11 ++++++++--- .../project-issues/project-view/filter.store.ts | 11 ++++++++--- .../issues/project-issues/project/filter.store.ts | 11 ++++++++--- web/store/issues/project-issues/utils.ts | 5 +++++ 9 files changed, 68 insertions(+), 21 deletions(-) create mode 100644 web/store/issues/project-issues/utils.ts diff --git a/web/store/issues/global/filter.store.ts b/web/store/issues/global/filter.store.ts index b495bb816..4302acc76 100644 --- a/web/store/issues/global/filter.store.ts +++ b/web/store/issues/global/filter.store.ts @@ -6,6 +6,7 @@ import { EFilterType } from "store/issues/types"; import { IssueFilterBaseStore } from "../project-issues/base-issue-filter.store"; // helpers import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; +import { isNil } from "../project-issues/utils"; // services import { WorkspaceService } from "services/workspace.service"; @@ -377,6 +378,12 @@ export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGl start_date: userFilters?.filters?.start_date || undefined, target_date: userFilters?.filters?.target_date || undefined, type: userFilters?.displayFilters?.type || undefined, + show_empty_groups: isNil(userFilters?.displayFilters?.show_empty_groups) + ? true + : userFilters?.displayFilters?.show_empty_groups, + start_target_date: isNil(userFilters?.displayFilters?.start_target_date) + ? true + : userFilters?.displayFilters?.start_target_date, sub_issue: false, }; diff --git a/web/store/issues/profile/filter.store.ts b/web/store/issues/profile/filter.store.ts index 0b684f7a7..dcb760d1a 100644 --- a/web/store/issues/profile/filter.store.ts +++ b/web/store/issues/profile/filter.store.ts @@ -6,6 +6,7 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption import { EFilterType } from "store/issues/types"; import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; import { IssueFilterBaseStore } from "../project-issues/base-issue-filter.store"; +import { isNil } from "../project-issues/utils"; interface IProjectIssuesFiltersOptions { filters: IIssueFilterOptions; @@ -285,9 +286,13 @@ export class ProfileIssuesFilterStore extends IssueFilterBaseStore implements IP start_date: userFilters?.filters?.start_date || undefined, target_date: userFilters?.filters?.target_date || undefined, type: userFilters?.displayFilters?.type || undefined, - sub_issue: userFilters?.displayFilters?.sub_issue || true, - show_empty_groups: userFilters?.displayFilters?.show_empty_groups || true, - start_target_date: userFilters?.displayFilters?.start_target_date || true, + sub_issue: isNil(userFilters?.displayFilters?.sub_issue) ? true : userFilters?.displayFilters?.sub_issue, + show_empty_groups: isNil(userFilters?.displayFilters?.show_empty_groups) + ? true + : userFilters?.displayFilters?.show_empty_groups, + start_target_date: isNil(userFilters?.displayFilters?.start_target_date) + ? true + : userFilters?.displayFilters?.start_target_date, }; const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "profile_issues"); diff --git a/web/store/issues/project-issues/archived/filter.store.ts b/web/store/issues/project-issues/archived/filter.store.ts index 77e30c4fc..0b3ce15a9 100644 --- a/web/store/issues/project-issues/archived/filter.store.ts +++ b/web/store/issues/project-issues/archived/filter.store.ts @@ -8,6 +8,7 @@ import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; import { RootStore } from "store/root"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; import { EFilterType } from "store/issues/types"; +import { isNil } from "../utils"; interface IProjectIssuesFilters { filters: IIssueFilterOptions | undefined; @@ -78,9 +79,13 @@ export class ProjectArchivedIssuesFilterStore start_date: userFilters?.filters?.start_date || undefined, target_date: userFilters?.filters?.target_date || undefined, type: userFilters?.displayFilters?.type || undefined, - sub_issue: userFilters?.displayFilters?.sub_issue || true, - show_empty_groups: userFilters?.displayFilters?.show_empty_groups || true, - start_target_date: userFilters?.displayFilters?.start_target_date || true, + sub_issue: isNil(userFilters?.displayFilters?.sub_issue) ? true : userFilters?.displayFilters?.sub_issue, + show_empty_groups: isNil(userFilters?.displayFilters?.show_empty_groups) + ? true + : userFilters?.displayFilters?.show_empty_groups, + start_target_date: isNil(userFilters?.displayFilters?.start_target_date) + ? true + : userFilters?.displayFilters?.start_target_date, }; const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues"); diff --git a/web/store/issues/project-issues/cycle/filter.store.ts b/web/store/issues/project-issues/cycle/filter.store.ts index 6e73d7613..b78999d7e 100644 --- a/web/store/issues/project-issues/cycle/filter.store.ts +++ b/web/store/issues/project-issues/cycle/filter.store.ts @@ -11,6 +11,7 @@ import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; import { RootStore } from "store/root"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; import { EFilterType } from "store/issues/types"; +import { isNil } from "../utils"; interface ICycleIssuesFilterOptions { filters: IIssueFilterOptions; @@ -118,9 +119,13 @@ export class CycleIssuesFilterStore extends IssueFilterBaseStore implements ICyc start_date: userFilters?.filters?.start_date || undefined, target_date: userFilters?.filters?.target_date || undefined, type: userFilters?.displayFilters?.type || undefined, - sub_issue: userFilters?.displayFilters?.sub_issue || true, - show_empty_groups: userFilters?.displayFilters?.show_empty_groups || true, - start_target_date: userFilters?.displayFilters?.start_target_date || true, + sub_issue: isNil(userFilters?.displayFilters?.sub_issue) ? true : userFilters?.displayFilters?.sub_issue, + show_empty_groups: isNil(userFilters?.displayFilters?.show_empty_groups) + ? true + : userFilters?.displayFilters?.show_empty_groups, + start_target_date: isNil(userFilters?.displayFilters?.start_target_date) + ? true + : userFilters?.displayFilters?.start_target_date, }; const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues"); diff --git a/web/store/issues/project-issues/draft/filter.store.ts b/web/store/issues/project-issues/draft/filter.store.ts index 7cfca229b..0c0a3ba37 100644 --- a/web/store/issues/project-issues/draft/filter.store.ts +++ b/web/store/issues/project-issues/draft/filter.store.ts @@ -8,6 +8,7 @@ import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; import { RootStore } from "store/root"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; import { EFilterType } from "store/issues/types"; +import { isNil } from "../utils"; interface IProjectIssuesFilters { filters: IIssueFilterOptions | undefined; @@ -75,9 +76,13 @@ export class ProjectDraftIssuesFilterStore extends IssueFilterBaseStore implemen start_date: userFilters?.filters?.start_date || undefined, target_date: userFilters?.filters?.target_date || undefined, type: userFilters?.displayFilters?.type || undefined, - sub_issue: userFilters?.displayFilters?.sub_issue || true, - show_empty_groups: userFilters?.displayFilters?.show_empty_groups || true, - start_target_date: userFilters?.displayFilters?.start_target_date || true, + sub_issue: isNil(userFilters?.displayFilters?.sub_issue) ? true : userFilters?.displayFilters?.sub_issue, + show_empty_groups: isNil(userFilters?.displayFilters?.show_empty_groups) + ? true + : userFilters?.displayFilters?.show_empty_groups, + start_target_date: isNil(userFilters?.displayFilters?.start_target_date) + ? true + : userFilters?.displayFilters?.start_target_date, }; const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues"); diff --git a/web/store/issues/project-issues/module/filter.store.ts b/web/store/issues/project-issues/module/filter.store.ts index a30103314..103528ba8 100644 --- a/web/store/issues/project-issues/module/filter.store.ts +++ b/web/store/issues/project-issues/module/filter.store.ts @@ -11,6 +11,7 @@ import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; import { RootStore } from "store/root"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; import { EFilterType } from "store/issues/types"; +import { isNil } from "../utils"; interface IModuleIssuesFilterOptions { filters: IIssueFilterOptions; @@ -118,9 +119,13 @@ export class ModuleIssuesFilterStore extends IssueFilterBaseStore implements IMo start_date: userFilters?.filters?.start_date || undefined, target_date: userFilters?.filters?.target_date || undefined, type: userFilters?.displayFilters?.type || undefined, - sub_issue: userFilters?.displayFilters?.sub_issue || true, - show_empty_groups: userFilters?.displayFilters?.show_empty_groups || true, - start_target_date: userFilters?.displayFilters?.start_target_date || true, + sub_issue: isNil(userFilters?.displayFilters?.sub_issue) ? true : userFilters?.displayFilters?.sub_issue, + show_empty_groups: isNil(userFilters?.displayFilters?.show_empty_groups) + ? true + : userFilters?.displayFilters?.show_empty_groups, + start_target_date: isNil(userFilters?.displayFilters?.start_target_date) + ? true + : userFilters?.displayFilters?.start_target_date, }; const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues"); diff --git a/web/store/issues/project-issues/project-view/filter.store.ts b/web/store/issues/project-issues/project-view/filter.store.ts index 5aa25f604..215e3749f 100644 --- a/web/store/issues/project-issues/project-view/filter.store.ts +++ b/web/store/issues/project-issues/project-view/filter.store.ts @@ -11,6 +11,7 @@ import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; import { RootStore } from "store/root"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; import { EFilterType } from "store/issues/types"; +import { isNil } from "../utils"; interface IViewIssuesFilterOptions { filters: IIssueFilterOptions; @@ -118,9 +119,13 @@ export class ViewIssuesFilterStore extends IssueFilterBaseStore implements IView start_date: userFilters?.filters?.start_date || undefined, target_date: userFilters?.filters?.target_date || undefined, type: userFilters?.displayFilters?.type || undefined, - sub_issue: userFilters?.displayFilters?.sub_issue || true, - show_empty_groups: userFilters?.displayFilters?.show_empty_groups || true, - start_target_date: userFilters?.displayFilters?.start_target_date || true, + sub_issue: isNil(userFilters?.displayFilters?.sub_issue) ? true : userFilters?.displayFilters?.sub_issue, + show_empty_groups: isNil(userFilters?.displayFilters?.show_empty_groups) + ? true + : userFilters?.displayFilters?.show_empty_groups, + start_target_date: isNil(userFilters?.displayFilters?.start_target_date) + ? true + : userFilters?.displayFilters?.start_target_date, }; const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues"); diff --git a/web/store/issues/project-issues/project/filter.store.ts b/web/store/issues/project-issues/project/filter.store.ts index bb64b5784..8caf189ad 100644 --- a/web/store/issues/project-issues/project/filter.store.ts +++ b/web/store/issues/project-issues/project/filter.store.ts @@ -8,6 +8,7 @@ import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; import { RootStore } from "store/root"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; import { EFilterType } from "store/issues/types"; +import { isNil } from "../utils"; interface IProjectIssuesFilters { filters: IIssueFilterOptions | undefined; @@ -75,9 +76,13 @@ export class ProjectIssuesFilterStore extends IssueFilterBaseStore implements IP start_date: userFilters?.filters?.start_date || undefined, target_date: userFilters?.filters?.target_date || undefined, type: userFilters?.displayFilters?.type || undefined, - sub_issue: userFilters?.displayFilters?.sub_issue || true, - show_empty_groups: userFilters?.displayFilters?.show_empty_groups || true, - start_target_date: userFilters?.displayFilters?.start_target_date || true, + sub_issue: isNil(userFilters?.displayFilters?.sub_issue) ? true : userFilters?.displayFilters?.sub_issue, + show_empty_groups: isNil(userFilters?.displayFilters?.show_empty_groups) + ? true + : userFilters?.displayFilters?.show_empty_groups, + start_target_date: isNil(userFilters?.displayFilters?.start_target_date) + ? true + : userFilters?.displayFilters?.start_target_date, }; const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues"); diff --git a/web/store/issues/project-issues/utils.ts b/web/store/issues/project-issues/utils.ts new file mode 100644 index 000000000..11eb6b90d --- /dev/null +++ b/web/store/issues/project-issues/utils.ts @@ -0,0 +1,5 @@ +export const isNil = (value: any) => { + if (value === undefined || value === null) return true; + + return false; +};