From febf19ccc03544e1ecafdccfabd2b4aa4980d7c0 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Tue, 14 May 2024 14:26:54 +0530 Subject: [PATCH] feat: converting space app to use nextjs app dir (#4451) * feat: changemod space * fix: space app dir fixes * fix: build errors --- packages/eslint-config-custom/index.js | 2 +- .../[workspace_slug]/[project_id]/error.tsx | 5 + .../[workspace_slug]/[project_id]/layout.tsx | 36 +++ .../[project_id]/not-found.tsx | 3 + .../[workspace_slug]/[project_id]/page.tsx | 8 + space/app/error.tsx | 40 ++++ space/app/layout.tsx | 35 +++ space/app/not-found.tsx | 39 ++++ space/app/page.tsx | 43 ++++ .../components/accounts/auth-forms/email.tsx | 2 + .../auth-forms/forgot-password-popover.tsx | 1 + .../accounts/auth-forms/password.tsx | 13 +- space/components/accounts/auth-forms/root.tsx | 14 +- .../accounts/auth-forms/unique-code.tsx | 9 +- .../accounts/oauth/github-button.tsx | 2 + .../accounts/oauth/google-button.tsx | 2 + .../accounts/oauth/oauth-options.tsx | 8 +- space/components/accounts/onboarding-form.tsx | 8 +- .../accounts/password-strength-meter.tsx | 2 + .../accounts/terms-and-conditions.tsx | 2 + .../accounts/user-image-upload-modal.tsx | 5 +- space/components/accounts/user-logged-in.tsx | 2 + .../instance/instance-failure-view.tsx | 12 +- space/components/instance/not-ready-view.tsx | 14 +- .../issues/board-views/kanban/block.tsx | 49 ++--- .../issues/board-views/kanban/header.tsx | 20 +- .../issues/board-views/kanban/index.tsx | 39 ++-- .../issues/board-views/list/block.tsx | 43 ++-- .../issues/board-views/list/header.tsx | 13 +- .../issues/board-views/list/index.tsx | 33 +-- .../filters/applied-filters/filters-list.tsx | 8 +- .../issues/filters/applied-filters/root.tsx | 45 ++-- space/components/issues/filters/root.tsx | 50 ++--- space/components/issues/filters/selection.tsx | 9 +- space/components/issues/navbar/index.tsx | 107 +++++---- .../issues/navbar/issue-board-view.tsx | 46 ++-- space/components/issues/navbar/theme.tsx | 3 +- .../peek-overview/comment/add-comment.tsx | 25 +-- .../comment/comment-detail-card.tsx | 26 +-- .../comment/comment-reactions.tsx | 38 +--- .../peek-overview/full-screen-peek-view.tsx | 11 +- .../issues/peek-overview/header.tsx | 28 +-- .../issues/peek-overview/issue-activity.tsx | 28 ++- .../peek-overview/issue-emoji-reactions.tsx | 27 ++- .../issues/peek-overview/issue-properties.tsx | 2 +- .../issues/peek-overview/issue-reaction.tsx | 18 +- .../peek-overview/issue-vote-reactions.tsx | 21 +- .../issues/peek-overview/layout.tsx | 57 +++-- .../issues/peek-overview/side-peek-view.tsx | 14 +- space/components/views/auth.tsx | 47 ++-- space/components/views/project-details.tsx | 67 +++--- space/constants/issue.ts | 20 ++ space/hooks/store/index.ts | 9 +- space/hooks/store/use-instance.ts | 5 +- space/hooks/store/use-issue-details.tsx | 11 + space/hooks/store/use-issue-filter.ts | 11 + space/hooks/store/use-issue.ts | 11 + space/hooks/store/use-project.ts | 11 + .../store/{user => }/use-user-profile.ts | 7 +- space/hooks/store/{user => }/use-user.ts | 5 +- space/hooks/store/user-mobx-provider.ts | 10 - space/hooks/store/user/index.ts | 2 - space/hooks/use-editor-suggestions.tsx | 13 -- space/layouts/project-layout.tsx | 30 --- space/lib/app-providers.tsx | 38 ++++ space/lib/index.ts | 1 - space/lib/store-context.tsx | 19 -- space/lib/wrappers/auth-wrapper.tsx | 2 +- space/lib/wrappers/index.ts | 1 - space/lib/wrappers/instance-wrapper.tsx | 39 ---- space/pages/404.tsx | 42 ---- .../[workspace_slug]/[project_slug]/index.tsx | 47 ---- space/pages/[workspace_slug]/index.tsx | 5 - space/pages/_app.tsx | 47 ---- space/pages/_document.tsx | 18 -- space/pages/accounts/forgot-password.tsx | 166 -------------- space/pages/accounts/reset-password.tsx | 205 ------------------ space/pages/index.tsx | 16 -- space/pages/onboarding/index.tsx | 130 ----------- space/pages/project-not-published/index.tsx | 49 ----- space/services/api.service.ts | 18 +- ...hentication.service.ts => auth.service.ts} | 0 space/services/issue.service.ts | 2 +- space/services/project.service.ts | 2 +- space/store/instance.store.ts | 21 +- ...issue_details.ts => issue-detail.store.ts} | 4 +- .../store/{issues => }/issue-filters.store.ts | 67 ++++-- space/store/{issue.ts => issue.store.ts} | 22 +- space/store/issues/base-issue-filter.store.ts | 29 --- space/store/issues/helpers.ts | 52 ----- space/store/issues/types.ts | 36 --- space/store/{user => }/profile.store.ts | 0 space/store/{project.ts => project.store.ts} | 65 ++++-- space/store/root.store.ts | 43 ++-- space/store/{user/index.ts => user.store.ts} | 21 +- space/tsconfig.json | 15 +- space/types/issue-filters.d.ts | 6 + space/types/issue.d.ts | 35 +++ 98 files changed, 1038 insertions(+), 1551 deletions(-) create mode 100644 space/app/[workspace_slug]/[project_id]/error.tsx create mode 100644 space/app/[workspace_slug]/[project_id]/layout.tsx create mode 100644 space/app/[workspace_slug]/[project_id]/not-found.tsx create mode 100644 space/app/[workspace_slug]/[project_id]/page.tsx create mode 100644 space/app/error.tsx create mode 100644 space/app/layout.tsx create mode 100644 space/app/not-found.tsx create mode 100644 space/app/page.tsx create mode 100644 space/constants/issue.ts create mode 100644 space/hooks/store/use-issue-details.tsx create mode 100644 space/hooks/store/use-issue-filter.ts create mode 100644 space/hooks/store/use-issue.ts create mode 100644 space/hooks/store/use-project.ts rename space/hooks/store/{user => }/use-user-profile.ts (62%) rename space/hooks/store/{user => }/use-user.ts (69%) delete mode 100644 space/hooks/store/user-mobx-provider.ts delete mode 100644 space/hooks/store/user/index.ts delete mode 100644 space/hooks/use-editor-suggestions.tsx delete mode 100644 space/layouts/project-layout.tsx create mode 100644 space/lib/app-providers.tsx delete mode 100644 space/lib/index.ts delete mode 100644 space/lib/store-context.tsx delete mode 100644 space/lib/wrappers/instance-wrapper.tsx delete mode 100644 space/pages/404.tsx delete mode 100644 space/pages/[workspace_slug]/[project_slug]/index.tsx delete mode 100644 space/pages/[workspace_slug]/index.tsx delete mode 100644 space/pages/_app.tsx delete mode 100644 space/pages/_document.tsx delete mode 100644 space/pages/accounts/forgot-password.tsx delete mode 100644 space/pages/accounts/reset-password.tsx delete mode 100644 space/pages/index.tsx delete mode 100644 space/pages/onboarding/index.tsx delete mode 100644 space/pages/project-not-published/index.tsx rename space/services/{authentication.service.ts => auth.service.ts} (100%) rename space/store/{issue_details.ts => issue-detail.store.ts} (99%) rename space/store/{issues => }/issue-filters.store.ts (55%) rename space/store/{issue.ts => issue.store.ts} (82%) delete mode 100644 space/store/issues/base-issue-filter.store.ts delete mode 100644 space/store/issues/helpers.ts delete mode 100644 space/store/issues/types.ts rename space/store/{user => }/profile.store.ts (100%) rename space/store/{project.ts => project.store.ts} (57%) rename space/store/{user/index.ts => user.store.ts} (90%) create mode 100644 space/types/issue-filters.d.ts diff --git a/packages/eslint-config-custom/index.js b/packages/eslint-config-custom/index.js index 9eae2e3f9..39d657d93 100644 --- a/packages/eslint-config-custom/index.js +++ b/packages/eslint-config-custom/index.js @@ -13,7 +13,7 @@ module.exports = { plugins: ["react", "@typescript-eslint", "import"], settings: { next: { - rootDir: ["web/", "space/", "packages/*/"], + rootDir: ["web/", "space/", "admin/", "packages/*/"], }, }, rules: { diff --git a/space/app/[workspace_slug]/[project_id]/error.tsx b/space/app/[workspace_slug]/[project_id]/error.tsx new file mode 100644 index 000000000..b666762d1 --- /dev/null +++ b/space/app/[workspace_slug]/[project_id]/error.tsx @@ -0,0 +1,5 @@ +"use client"; + +export default function ProjectError() { + return <>Project Error; +} diff --git a/space/app/[workspace_slug]/[project_id]/layout.tsx b/space/app/[workspace_slug]/[project_id]/layout.tsx new file mode 100644 index 000000000..37fc7c544 --- /dev/null +++ b/space/app/[workspace_slug]/[project_id]/layout.tsx @@ -0,0 +1,36 @@ +import Image from "next/image"; +// components +import IssueNavbar from "@/components/issues/navbar"; +// services +import ProjectService from "@/services/project.service"; +// assets +import planeLogo from "public/plane-logo.svg"; + +const projectService = new ProjectService(); + +export default async function ProjectLayout({ children, params }: { children: React.ReactNode; params: any }) { + const { workspace_slug, project_id } = params; + const projectSettings = await projectService.getProjectSettings(workspace_slug, project_id); + + return ( +
+
+ +
+
{children}
+ +
+ Plane logo +
+
+ Powered by Plane Deploy +
+
+
+ ); +} diff --git a/space/app/[workspace_slug]/[project_id]/not-found.tsx b/space/app/[workspace_slug]/[project_id]/not-found.tsx new file mode 100644 index 000000000..9da31ad5e --- /dev/null +++ b/space/app/[workspace_slug]/[project_id]/not-found.tsx @@ -0,0 +1,3 @@ +export default function ProjectSettingsNotFound() { + return <>Project Settings not found; +} diff --git a/space/app/[workspace_slug]/[project_id]/page.tsx b/space/app/[workspace_slug]/[project_id]/page.tsx new file mode 100644 index 000000000..25893ed86 --- /dev/null +++ b/space/app/[workspace_slug]/[project_id]/page.tsx @@ -0,0 +1,8 @@ +// components +import { ProjectDetailsView } from "@/components/views"; + +export default async function WorkspaceProjectPage({ params }: { params: any }) { + const { workspace_slug, project_id, peekId } = params; + + return ; +} diff --git a/space/app/error.tsx b/space/app/error.tsx new file mode 100644 index 000000000..5b7c6ecfb --- /dev/null +++ b/space/app/error.tsx @@ -0,0 +1,40 @@ +"use client"; + +import Image from "next/image"; +import { useTheme } from "next-themes"; +import { Button } from "@plane/ui"; +// assets +import InstanceFailureDarkImage from "@/public/instance/instance-failure-dark.svg"; +import InstanceFailureImage from "@/public/instance/instance-failure.svg"; + +export default function InstanceError() { + const { resolvedTheme } = useTheme(); + + const instanceImage = resolvedTheme === "dark" ? InstanceFailureDarkImage : InstanceFailureImage; + + const handleRetry = () => { + window.location.reload(); + }; + + return ( +
+
+
+
+ Plane Logo +

Unable to fetch instance details.

+

+ We were unable to fetch the details of the instance.
+ Fret not, it might just be a connectivity issue. +

+
+
+ +
+
+
+
+ ); +} diff --git a/space/app/layout.tsx b/space/app/layout.tsx new file mode 100644 index 000000000..b5501957e --- /dev/null +++ b/space/app/layout.tsx @@ -0,0 +1,35 @@ +import { Metadata } from "next"; +// styles +import "@/styles/globals.css"; +// helpers +import { ASSET_PREFIX } from "@/helpers/common.helper"; + +export const metadata: Metadata = { + title: "Plane Deploy | Make your Plane boards public with one-click", + description: "Plane Deploy is a customer feedback management tool built on top of plane.so", + openGraph: { + title: "Plane Deploy | Make your Plane boards public with one-click", + description: "Plane Deploy is a customer feedback management tool built on top of plane.so", + url: "https://sites.plane.so/", + }, + keywords: + "software development, customer feedback, software, accelerate, code management, release management, project management, issue tracking, agile, scrum, kanban, collaboration", + twitter: { + site: "@planepowers", + }, +}; + +export default async function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + + + + + + + {children} + + ); +} diff --git a/space/app/not-found.tsx b/space/app/not-found.tsx new file mode 100644 index 000000000..98c4f95ca --- /dev/null +++ b/space/app/not-found.tsx @@ -0,0 +1,39 @@ +"use client"; +import Image from "next/image"; +import { useTheme } from "next-themes"; +import { Button } from "@plane/ui"; +// assets +import InstanceFailureDarkImage from "@/public/instance/instance-failure-dark.svg"; +import InstanceFailureImage from "@/public/instance/instance-failure.svg"; + +export default function InstanceNotFound() { + const { resolvedTheme } = useTheme(); + + const instanceImage = resolvedTheme === "dark" ? InstanceFailureDarkImage : InstanceFailureImage; + + const handleRetry = () => { + window.location.reload(); + }; + + return ( +
+
+
+
+ Plane Logo +

Unable to fetch instance details.

+

+ We were unable to fetch the details of the instance.
+ Fret not, it might just be a connectivity issue. +

+
+
+ +
+
+
+
+ ); +} diff --git a/space/app/page.tsx b/space/app/page.tsx new file mode 100644 index 000000000..b2c032287 --- /dev/null +++ b/space/app/page.tsx @@ -0,0 +1,43 @@ +// components +import { UserLoggedIn } from "@/components/accounts"; +import { InstanceNotReady, InstanceFailureView } from "@/components/instance"; +import { AuthView } from "@/components/views"; +// helpers +// import { EPageTypes } from "@/helpers/authentication.helper"; +// import { useInstance, useUser } from "@/hooks/store"; +// wrapper +// import { AuthWrapper } from "@/lib/wrappers"; +// lib +import { AppProvider } from "@/lib/app-providers"; +// services +import { InstanceService } from "@/services/instance.service"; +import { UserService } from "@/services/user.service"; + +const userServices = new UserService(); +const instanceService = new InstanceService(); + +export default async function HomePage() { + const instanceDetails = await instanceService.getInstanceInfo().catch(() => undefined); + const user = await userServices + .currentUser() + .then((user) => ({ ...user, isAuthenticated: true })) + .catch(() => ({ isAuthenticated: false })); + + if (!instanceDetails) { + return ; + } + + if (!instanceDetails?.instance?.is_setup_done) { + ; + } + + if (user.isAuthenticated) { + return ; + } + + return ( + + + + ); +} diff --git a/space/components/accounts/auth-forms/email.tsx b/space/components/accounts/auth-forms/email.tsx index 550dea2bf..8f40b74d5 100644 --- a/space/components/accounts/auth-forms/email.tsx +++ b/space/components/accounts/auth-forms/email.tsx @@ -1,3 +1,5 @@ +"use client"; + import React from "react"; import { Controller, useForm } from "react-hook-form"; // icons diff --git a/space/components/accounts/auth-forms/forgot-password-popover.tsx b/space/components/accounts/auth-forms/forgot-password-popover.tsx index 31bafce26..8e0f2064c 100644 --- a/space/components/accounts/auth-forms/forgot-password-popover.tsx +++ b/space/components/accounts/auth-forms/forgot-password-popover.tsx @@ -1,3 +1,4 @@ +"use client"; import { Fragment, useState } from "react"; import { usePopper } from "react-popper"; import { X } from "lucide-react"; diff --git a/space/components/accounts/auth-forms/password.tsx b/space/components/accounts/auth-forms/password.tsx index 35d8703b6..b9d614155 100644 --- a/space/components/accounts/auth-forms/password.tsx +++ b/space/components/accounts/auth-forms/password.tsx @@ -1,7 +1,9 @@ +"use client"; + import React, { useEffect, useMemo, useState } from "react"; // icons import Link from "next/link"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import { Eye, EyeOff, XCircle } from "lucide-react"; // ui import { Button, Input, Spinner } from "@plane/ui"; @@ -12,7 +14,7 @@ import { API_BASE_URL } from "@/helpers/common.helper"; import { getPasswordStrength } from "@/helpers/password.helper"; // hooks import { useInstance } from "@/hooks/store"; -import { AuthService } from "@/services/authentication.service"; +import { AuthService } from "@/services/auth.service"; type Props = { email: string; @@ -43,12 +45,11 @@ export const PasswordForm: React.FC = (props) => { const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); // hooks - const { instance } = useInstance(); + const { data: instance, config: instanceConfig } = useInstance(); // router - const router = useRouter(); - const { next_path } = router.query; + const { next_path } = useParams(); // derived values - const isSmtpConfigured = instance?.config?.is_smtp_configured; + const isSmtpConfigured = instanceConfig?.is_smtp_configured; const handleFormChange = (key: keyof TPasswordFormValues, value: string) => setPasswordFormData((prev) => ({ ...prev, [key]: value })); diff --git a/space/components/accounts/auth-forms/root.tsx b/space/components/accounts/auth-forms/root.tsx index 1dac88655..273a438bf 100644 --- a/space/components/accounts/auth-forms/root.tsx +++ b/space/components/accounts/auth-forms/root.tsx @@ -1,3 +1,5 @@ +"use client"; + import React, { useState } from "react"; import { observer } from "mobx-react-lite"; // components @@ -7,7 +9,7 @@ import { EmailForm, UniqueCodeForm, PasswordForm, OAuthOptions, TermsAndConditio import { useInstance } from "@/hooks/store"; import useToast from "@/hooks/use-toast"; // services -import { AuthService } from "@/services/authentication.service"; +import { AuthService } from "@/services/auth.service"; export enum EAuthSteps { EMAIL = "EMAIL", @@ -60,9 +62,9 @@ export const AuthRoot = observer(() => { const [authStep, setAuthStep] = useState(EAuthSteps.EMAIL); const [email, setEmail] = useState(""); // hooks - const { instance } = useInstance(); + const { config: instanceConfig } = useInstance(); // derived values - const isSmtpConfigured = instance?.config?.is_smtp_configured; + const isSmtpConfigured = instanceConfig?.is_smtp_configured; const { header, subHeader } = getHeaderSubHeader(authMode); @@ -112,8 +114,8 @@ export const AuthRoot = observer(() => { ); }; - const isOAuthEnabled = - instance?.config && (instance?.config?.is_google_enabled || instance?.config?.is_github_enabled); + const isOAuthEnabled = instanceConfig && (instanceConfig?.is_google_enabled || instanceConfig?.is_github_enabled); + return (
@@ -149,7 +151,7 @@ export const AuthRoot = observer(() => { )} )} - {isOAuthEnabled && } + {isOAuthEnabled !== undefined && }
); diff --git a/space/components/accounts/auth-forms/unique-code.tsx b/space/components/accounts/auth-forms/unique-code.tsx index bf76acdb9..fc04938bc 100644 --- a/space/components/accounts/auth-forms/unique-code.tsx +++ b/space/components/accounts/auth-forms/unique-code.tsx @@ -1,5 +1,7 @@ +"use client"; + import React, { useEffect, useState } from "react"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; // icons import { CircleCheck, XCircle } from "lucide-react"; // ui @@ -10,7 +12,7 @@ import { API_BASE_URL } from "@/helpers/common.helper"; import useTimer from "@/hooks/use-timer"; import useToast from "@/hooks/use-toast"; // services -import { AuthService } from "@/services/authentication.service"; +import { AuthService } from "@/services/auth.service"; // types import { IEmailCheckData } from "@/types/auth"; import { EAuthModes } from "./root"; @@ -43,8 +45,7 @@ export const UniqueCodeForm: React.FC = (props) => { const [csrfToken, setCsrfToken] = useState(undefined); const [isSubmitting, setIsSubmitting] = useState(false); // router - const router = useRouter(); - const { next_path } = router.query; + const { next_path } = useParams(); // toast alert const { setToastAlert } = useToast(); // timer diff --git a/space/components/accounts/oauth/github-button.tsx b/space/components/accounts/oauth/github-button.tsx index 740d59aaf..9a907f03a 100644 --- a/space/components/accounts/oauth/github-button.tsx +++ b/space/components/accounts/oauth/github-button.tsx @@ -1,3 +1,5 @@ +"use client"; + import { FC } from "react"; import Image from "next/image"; import { useTheme } from "next-themes"; diff --git a/space/components/accounts/oauth/google-button.tsx b/space/components/accounts/oauth/google-button.tsx index d31c6f59f..035a541a5 100644 --- a/space/components/accounts/oauth/google-button.tsx +++ b/space/components/accounts/oauth/google-button.tsx @@ -1,3 +1,5 @@ +"use client"; + import { FC } from "react"; import Image from "next/image"; import { useTheme } from "next-themes"; diff --git a/space/components/accounts/oauth/oauth-options.tsx b/space/components/accounts/oauth/oauth-options.tsx index b8e86c2ca..13b8c7d27 100644 --- a/space/components/accounts/oauth/oauth-options.tsx +++ b/space/components/accounts/oauth/oauth-options.tsx @@ -1,3 +1,5 @@ +"use client"; + import { observer } from "mobx-react-lite"; // components import { GithubOAuthButton, GoogleOAuthButton } from "@/components/accounts"; @@ -6,7 +8,7 @@ import { useInstance } from "@/hooks/store"; export const OAuthOptions: React.FC = observer(() => { // hooks - const { instance } = useInstance(); + const { config: instanceConfig } = useInstance(); return ( <> @@ -16,12 +18,12 @@ export const OAuthOptions: React.FC = observer(() => {
- {instance?.config?.is_google_enabled && ( + {instanceConfig?.is_google_enabled && (
)} - {instance?.config?.is_github_enabled && } + {instanceConfig?.is_github_enabled && }
); diff --git a/space/components/accounts/onboarding-form.tsx b/space/components/accounts/onboarding-form.tsx index 768d5160f..50e5f0f0d 100644 --- a/space/components/accounts/onboarding-form.tsx +++ b/space/components/accounts/onboarding-form.tsx @@ -1,3 +1,5 @@ +"use client"; + import React, { useMemo, useState } from "react"; import { observer } from "mobx-react-lite"; import { Controller, useForm } from "react-hook-form"; @@ -8,7 +10,7 @@ import { Button, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui"; // components import { UserImageUploadModal } from "@/components/accounts"; // hooks -import { useMobxStore } from "@/hooks/store"; +import { useUser } from "@/hooks/store"; // services import fileService from "@/services/file.service"; @@ -35,9 +37,7 @@ export const OnBoardingForm: React.FC = observer((props) => { const [isRemoving, setIsRemoving] = useState(false); const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false); // store hooks - const { - user: { updateCurrentUser }, - } = useMobxStore(); + const { updateCurrentUser } = useUser(); // form info const { getValues, diff --git a/space/components/accounts/password-strength-meter.tsx b/space/components/accounts/password-strength-meter.tsx index 86ee814c8..c12d78421 100644 --- a/space/components/accounts/password-strength-meter.tsx +++ b/space/components/accounts/password-strength-meter.tsx @@ -1,3 +1,5 @@ +"use client"; + // icons import { CircleCheck } from "lucide-react"; // helpers diff --git a/space/components/accounts/terms-and-conditions.tsx b/space/components/accounts/terms-and-conditions.tsx index fbb16fd21..4bbde51f3 100644 --- a/space/components/accounts/terms-and-conditions.tsx +++ b/space/components/accounts/terms-and-conditions.tsx @@ -1,3 +1,5 @@ +"use client"; + import React, { FC } from "react"; import Link from "next/link"; import { EAuthModes } from "./auth-forms"; diff --git a/space/components/accounts/user-image-upload-modal.tsx b/space/components/accounts/user-image-upload-modal.tsx index fc0f4393a..802a6af62 100644 --- a/space/components/accounts/user-image-upload-modal.tsx +++ b/space/components/accounts/user-image-upload-modal.tsx @@ -1,3 +1,4 @@ +"use client"; import React, { useState } from "react"; import { observer } from "mobx-react-lite"; import { useDropzone } from "react-dropzone"; @@ -27,7 +28,7 @@ export const UserImageUploadModal: React.FC = observer((props) => { const [image, setImage] = useState(null); const [isImageUploading, setIsImageUploading] = useState(false); // store hooks - const { instance } = useInstance(); + const { config: instanceConfig } = useInstance(); const onDrop = (acceptedFiles: File[]) => setImage(acceptedFiles[0]); @@ -36,7 +37,7 @@ export const UserImageUploadModal: React.FC = observer((props) => { accept: { "image/*": [".png", ".jpg", ".jpeg", ".svg", ".webp"], }, - maxSize: instance?.config?.file_size_limit ?? MAX_FILE_SIZE, + maxSize: (instanceConfig?.file_size_limit as number) ?? MAX_FILE_SIZE, multiple: false, }); diff --git a/space/components/accounts/user-logged-in.tsx b/space/components/accounts/user-logged-in.tsx index 7bf864431..773d934ad 100644 --- a/space/components/accounts/user-logged-in.tsx +++ b/space/components/accounts/user-logged-in.tsx @@ -1,3 +1,5 @@ +"use client"; + import Image from "next/image"; // hooks import { useUser } from "@/hooks/store"; diff --git a/space/components/instance/instance-failure-view.tsx b/space/components/instance/instance-failure-view.tsx index 0b875382a..1173a6894 100644 --- a/space/components/instance/instance-failure-view.tsx +++ b/space/components/instance/instance-failure-view.tsx @@ -1,3 +1,4 @@ +"use client"; import { FC } from "react"; import Image from "next/image"; import { useTheme } from "next-themes"; @@ -7,15 +8,18 @@ import InstanceFailureDarkImage from "public/instance/instance-failure-dark.svg" import InstanceFailureImage from "public/instance/instance-failure.svg"; type InstanceFailureViewProps = { - mutate: () => void; + // mutate: () => void; }; -export const InstanceFailureView: FC = (props) => { - const { mutate } = props; +export const InstanceFailureView: FC = () => { const { resolvedTheme } = useTheme(); const instanceImage = resolvedTheme === "dark" ? InstanceFailureDarkImage : InstanceFailureImage; + const handleRetry = () => { + window.location.reload(); + }; + return (
@@ -28,7 +32,7 @@ export const InstanceFailureView: FC = (props) => {

-
diff --git a/space/components/instance/not-ready-view.tsx b/space/components/instance/not-ready-view.tsx index 815e0d1fe..5b94d92ed 100644 --- a/space/components/instance/not-ready-view.tsx +++ b/space/components/instance/not-ready-view.tsx @@ -1,18 +1,19 @@ +"use client"; + import { FC } from "react"; import Image from "next/image"; -import Link from "next/link"; // ui import { Button } from "@plane/ui"; -// helpers +// helper import { ADMIN_BASE_URL, ADMIN_BASE_PATH } from "@/helpers/common.helper"; // images import PlaneTakeOffImage from "@/public/instance/plane-takeoff.png"; export const InstanceNotReady: FC = () => { - const GOD_MODE_URL = encodeURI(ADMIN_BASE_URL + ADMIN_BASE_PATH + "/setup/?auth_enabled=0"); + const GOD_MODE_URL = encodeURI(ADMIN_BASE_URL + ADMIN_BASE_PATH); return ( -
+

Welcome aboard Plane!

@@ -21,13 +22,12 @@ export const InstanceNotReady: FC = () => { Get started by setting up your instance and workspace

-
diff --git a/space/components/issues/board-views/kanban/block.tsx b/space/components/issues/board-views/kanban/block.tsx index 6c2aa5279..4ecee992c 100644 --- a/space/components/issues/board-views/kanban/block.tsx +++ b/space/components/issues/board-views/kanban/block.tsx @@ -1,53 +1,48 @@ "use client"; -// mobx react lite +import { FC } from "react"; import { observer } from "mobx-react-lite"; -import { useRouter } from "next/router"; +import { useRouter, useSearchParams } from "next/navigation"; +// components import { IssueBlockDueDate } from "@/components/issues/board-views/block-due-date"; import { IssueBlockPriority } from "@/components/issues/board-views/block-priority"; import { IssueBlockState } from "@/components/issues/board-views/block-state"; -import { useMobxStore } from "@/hooks/store"; - -// components +// hooks +import { useIssueDetails, useProject } from "@/hooks/store"; // interfaces -import { RootStore } from "@/store/root.store"; -import { IIssue } from "types/issue"; +import { IIssue } from "@/types/issue"; -export const IssueKanBanBlock = observer(({ issue }: { issue: IIssue }) => { - const { project: projectStore, issueDetails: issueDetailStore }: RootStore = useMobxStore(); +type IssueKanBanBlockProps = { + issue: IIssue; + workspaceSlug: string; + projectId: string; + params: any; +}; +export const IssueKanBanBlock: FC = observer((props) => { + const { workspaceSlug, projectId, params, issue } = props; + const { board, priorities, states, labels } = params; + // store + const { project } = useProject(); + const { setPeekId } = useIssueDetails(); // router const router = useRouter(); - const { workspace_slug, project_slug, board, priorities, states, labels } = router.query as { - workspace_slug: string; - project_slug: string; - board: string; - priorities: string; - states: string; - labels: string; - }; + const searchParams = useSearchParams(); const handleBlockClick = () => { - issueDetailStore.setPeekId(issue.id); + setPeekId(issue.id); const params: any = { board: board, peekId: issue.id }; if (states && states.length > 0) params.states = states; if (priorities && priorities.length > 0) params.priorities = priorities; if (labels && labels.length > 0) params.labels = labels; - router.push( - { - pathname: `/${workspace_slug}/${project_slug}`, - query: { ...params }, - }, - undefined, - { shallow: true } - ); + router.push(`/${workspaceSlug}/${projectId}?${searchParams}`); }; return (
{/* id */}
- {projectStore?.project?.identifier}-{issue?.sequence_id} + {project?.identifier}-{issue?.sequence_id}
{/* name */} diff --git a/space/components/issues/board-views/kanban/header.tsx b/space/components/issues/board-views/kanban/header.tsx index c08f89975..5a4d9a226 100644 --- a/space/components/issues/board-views/kanban/header.tsx +++ b/space/components/issues/board-views/kanban/header.tsx @@ -1,18 +1,16 @@ // mobx react lite import { observer } from "mobx-react-lite"; -// interfaces -// constants -import { StateGroupIcon } from "@plane/ui"; -import { issueGroupFilter } from "@/constants/data"; // ui +import { StateGroupIcon } from "@plane/ui"; +// constants +import { issueGroupFilter } from "@/constants/data"; // mobx hook -import { useMobxStore } from "@/hooks/store"; -import { RootStore } from "@/store/root.store"; -import { IIssueState } from "types/issue"; +import { useIssue } from "@/hooks/store"; +// interfaces +import { IIssueState } from "@/types/issue"; export const IssueKanBanHeader = observer(({ state }: { state: IIssueState }) => { - const store: RootStore = useMobxStore(); - + const { getCountOfIssuesByState } = useIssue(); const stateGroup = issueGroupFilter(state.group); if (stateGroup === null) return <>; @@ -23,9 +21,7 @@ export const IssueKanBanHeader = observer(({ state }: { state: IIssueState }) =>
{state?.name}
- - {store.issue.getCountOfIssuesByState(state.id)} - + {getCountOfIssuesByState(state.id)}
); }); diff --git a/space/components/issues/board-views/kanban/index.tsx b/space/components/issues/board-views/kanban/index.tsx index d1a9fe709..e2e4e9900 100644 --- a/space/components/issues/board-views/kanban/index.tsx +++ b/space/components/issues/board-views/kanban/index.tsx @@ -1,36 +1,47 @@ "use client"; -// mobx react lite +import { FC } from "react"; import { observer } from "mobx-react-lite"; // components import { IssueKanBanBlock } from "@/components/issues/board-views/kanban/block"; import { IssueKanBanHeader } from "@/components/issues/board-views/kanban/header"; // ui import { Icon } from "@/components/ui"; -// interfaces // mobx hook -import { useMobxStore } from "@/hooks/store"; -import { RootStore } from "@/store/root.store"; -import { IIssueState, IIssue } from "types/issue"; +import { useIssue } from "@/hooks/store"; +// interfaces +import { IIssueState, IIssue } from "@/types/issue"; -export const IssueKanbanView = observer(() => { - const store: RootStore = useMobxStore(); +type IssueKanbanViewProps = { + workspaceSlug: string; + projectId: string; +}; + +export const IssueKanbanView: FC = observer((props) => { + const { workspaceSlug, projectId } = props; + // store hooks + const { states, getFilteredIssuesByState } = useIssue(); return (
- {store?.issue?.states && - store?.issue?.states.length > 0 && - store?.issue?.states.map((_state: IIssueState) => ( + {states && + states.length > 0 && + states.map((_state: IIssueState) => (
- {store.issue.getFilteredIssuesByState(_state.id) && - store.issue.getFilteredIssuesByState(_state.id).length > 0 ? ( + {getFilteredIssuesByState(_state.id) && getFilteredIssuesByState(_state.id).length > 0 ? (
- {store.issue.getFilteredIssuesByState(_state.id).map((_issue: IIssue) => ( - + {getFilteredIssuesByState(_state.id).map((_issue: IIssue) => ( + ))}
) : ( diff --git a/space/components/issues/board-views/list/block.tsx b/space/components/issues/board-views/list/block.tsx index 63b589066..e05ebcb2d 100644 --- a/space/components/issues/board-views/list/block.tsx +++ b/space/components/issues/board-views/list/block.tsx @@ -1,47 +1,40 @@ import { FC } from "react"; import { observer } from "mobx-react-lite"; -import { useRouter } from "next/router"; +import { useParams, useRouter, useSearchParams } from "next/navigation"; // components import { IssueBlockDueDate } from "@/components/issues/board-views/block-due-date"; import { IssueBlockLabels } from "@/components/issues/board-views/block-labels"; import { IssueBlockPriority } from "@/components/issues/board-views/block-priority"; import { IssueBlockState } from "@/components/issues/board-views/block-state"; // mobx hook -import { useMobxStore } from "@/hooks/store"; +import { useIssueDetails, useProject } from "@/hooks/store"; // interfaces -import { RootStore } from "@/store/root.store"; -import { IIssue } from "types/issue"; +import { IIssue } from "@/types/issue"; // store -export const IssueListBlock: FC<{ issue: IIssue }> = observer((props) => { - const { issue } = props; +type IssueListBlockProps = { + issue: IIssue; + workspaceSlug: string; + projectId: string; +}; + +export const IssueListBlock: FC = observer((props) => { + const { workspaceSlug, projectId, issue } = props; + const { board, states, priorities, labels } = useParams(); + const searchParams = useSearchParams(); // store - const { project: projectStore, issueDetails: issueDetailStore }: RootStore = useMobxStore(); + const { project } = useProject(); + const { setPeekId } = useIssueDetails(); // router const router = useRouter(); - const { workspace_slug, project_slug, board, priorities, states, labels } = router.query as { - workspace_slug: string; - project_slug: string; - board: string; - priorities: string; - states: string; - labels: string; - }; const handleBlockClick = () => { - issueDetailStore.setPeekId(issue.id); + setPeekId(issue.id); const params: any = { board: board, peekId: issue.id }; if (states && states.length > 0) params.states = states; if (priorities && priorities.length > 0) params.priorities = priorities; if (labels && labels.length > 0) params.labels = labels; - router.push( - { - pathname: `/${workspace_slug}/${project_slug}`, - query: { ...params }, - }, - undefined, - { shallow: true } - ); + router.push(`/${workspaceSlug}/${projectId}?${searchParams}`); // router.push(`/${workspace_slug?.toString()}/${project_slug}?board=${board?.toString()}&peekId=${issue.id}`); }; @@ -50,7 +43,7 @@ export const IssueListBlock: FC<{ issue: IIssue }> = observer((props) => {
{/* id */}
- {projectStore?.project?.identifier}-{issue?.sequence_id} + {project?.identifier}-{issue?.sequence_id}
{/* name */}
diff --git a/space/components/issues/board-views/list/header.tsx b/space/components/issues/board-views/list/header.tsx index 6266c9cef..22a902474 100644 --- a/space/components/issues/board-views/list/header.tsx +++ b/space/components/issues/board-views/list/header.tsx @@ -1,18 +1,15 @@ -// mobx react lite import { observer } from "mobx-react-lite"; -// interfaces // ui import { StateGroupIcon } from "@plane/ui"; // constants import { issueGroupFilter } from "@/constants/data"; // mobx hook -import { useMobxStore } from "@/hooks/store"; -import { RootStore } from "@/store/root.store"; -import { IIssueState } from "types/issue"; +import { useIssue } from "@/hooks/store"; +// types +import { IIssueState } from "@/types/issue"; export const IssueListHeader = observer(({ state }: { state: IIssueState }) => { - const store: RootStore = useMobxStore(); - + const { getCountOfIssuesByState } = useIssue(); const stateGroup = issueGroupFilter(state.group); if (stateGroup === null) return <>; @@ -23,7 +20,7 @@ export const IssueListHeader = observer(({ state }: { state: IIssueState }) => {
{state?.name}
-
{store.issue.getCountOfIssuesByState(state.id)}
+
{getCountOfIssuesByState(state.id)}
); }); diff --git a/space/components/issues/board-views/list/index.tsx b/space/components/issues/board-views/list/index.tsx index 03ca07998..7740bfd58 100644 --- a/space/components/issues/board-views/list/index.tsx +++ b/space/components/issues/board-views/list/index.tsx @@ -1,29 +1,34 @@ +import { FC } from "react"; import { observer } from "mobx-react-lite"; // components import { IssueListBlock } from "@/components/issues/board-views/list/block"; import { IssueListHeader } from "@/components/issues/board-views/list/header"; -// interfaces // mobx hook -import { useMobxStore } from "@/hooks/store"; -// store -import { RootStore } from "@/store/root.store"; -import { IIssueState, IIssue } from "types/issue"; +import { useIssue } from "@/hooks/store"; +// types +import { IIssueState, IIssue } from "@/types/issue"; -export const IssueListView = observer(() => { - const { issue: issueStore }: RootStore = useMobxStore(); +type IssueListViewProps = { + workspaceSlug: string; + projectId: string; +}; + +export const IssueListView: FC = observer((props) => { + const { workspaceSlug, projectId } = props; + // store hooks + const { states, getFilteredIssuesByState } = useIssue(); return ( <> - {issueStore?.states && - issueStore?.states.length > 0 && - issueStore?.states.map((_state: IIssueState) => ( + {states && + states.length > 0 && + states.map((_state: IIssueState) => (
- {issueStore.getFilteredIssuesByState(_state.id) && - issueStore.getFilteredIssuesByState(_state.id).length > 0 ? ( + {getFilteredIssuesByState(_state.id) && getFilteredIssuesByState(_state.id).length > 0 ? (
- {issueStore.getFilteredIssuesByState(_state.id).map((_issue: IIssue) => ( - + {getFilteredIssuesByState(_state.id).map((_issue: IIssue) => ( + ))}
) : ( diff --git a/space/components/issues/filters/applied-filters/filters-list.tsx b/space/components/issues/filters/applied-filters/filters-list.tsx index e0d41341b..a0d703c2c 100644 --- a/space/components/issues/filters/applied-filters/filters-list.tsx +++ b/space/components/issues/filters/applied-filters/filters-list.tsx @@ -1,12 +1,10 @@ -// components // icons import { X } from "lucide-react"; -// helpers -import { IIssueFilterOptions } from "@/store/issues/types"; -import { IIssueLabel, IIssueState } from "types/issue"; +// types +import { IIssueLabel, IIssueState, IIssueFilterOptions } from "@/types/issue"; +// components import { AppliedPriorityFilters } from "./priority"; import { AppliedStateFilters } from "./state"; -// types type Props = { appliedFilters: IIssueFilterOptions; diff --git a/space/components/issues/filters/applied-filters/root.tsx b/space/components/issues/filters/applied-filters/root.tsx index 7362e8787..1e32ea363 100644 --- a/space/components/issues/filters/applied-filters/root.tsx +++ b/space/components/issues/filters/applied-filters/root.tsx @@ -1,33 +1,31 @@ +"use client"; + import { FC, useCallback } from "react"; import { observer } from "mobx-react-lite"; -import { useRouter } from "next/router"; -// components +import { useRouter } from "next/navigation"; +// hooks +import { useIssue, useProject, useIssueFilter } from "@/hooks/store"; // store -import { useMobxStore } from "@/hooks/store"; -import { IIssueFilterOptions } from "@/store/issues/types"; -import { RootStore } from "@/store/root.store"; +import { IIssueFilterOptions } from "@/types/issue"; +// components import { AppliedFiltersList } from "./filters-list"; -export const IssueAppliedFilters: FC = observer(() => { +// TODO: fix component types +export const IssueAppliedFilters: FC = observer((props: any) => { const router = useRouter(); - const { workspace_slug: workspaceSlug, project_slug: projectId } = router.query as { - workspace_slug: string; - project_slug: string; - }; - - const { - issuesFilter: { issueFilters, updateFilters }, - issue: { states, labels }, - project: { activeBoard }, - }: RootStore = useMobxStore(); + const { workspaceSlug, projectId } = props; + const { states, labels } = useIssue(); + const { activeLayout } = useProject(); + const { issueFilters, updateFilters } = useIssueFilter(); const userFilters = issueFilters?.filters || {}; - const appliedFilters: IIssueFilterOptions = {}; + const appliedFilters: any = {}; + Object.entries(userFilters).forEach(([key, value]) => { if (!value) return; if (Array.isArray(value) && value.length === 0) return; - appliedFilters[key as keyof IIssueFilterOptions] = value; + appliedFilters[key] = value; }); const updateRouteParams = useCallback( @@ -36,16 +34,17 @@ export const IssueAppliedFilters: FC = observer(() => { const priority = key === "priority" ? value || [] : issueFilters?.filters?.priority ?? []; const labels = key === "labels" ? value || [] : issueFilters?.filters?.labels ?? []; - let params: any = { board: activeBoard || "list" }; + let params: any = { board: activeLayout || "list" }; if (!clearFields) { if (priority.length > 0) params = { ...params, priorities: priority.join(",") }; if (state.length > 0) params = { ...params, states: state.join(",") }; if (labels.length > 0) params = { ...params, labels: labels.join(",") }; } - - router.push({ pathname: `/${workspaceSlug}/${projectId}`, query: { ...params } }, undefined, { shallow: true }); + console.log("params", params); + // TODO: fix this redirection + // router.push({ pathname: `/${workspaceSlug}/${projectId}`, query: { ...params } }, undefined, { shallow: true }); }, - [workspaceSlug, projectId, activeBoard, issueFilters, router] + [workspaceSlug, projectId, activeLayout, issueFilters, router] ); const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => { @@ -80,7 +79,7 @@ export const IssueAppliedFilters: FC = observer(() => {
{ +type IssueFiltersDropdownProps = { + workspaceSlug: string; + projectId: string; +}; + +export const IssueFiltersDropdown: FC = observer((props) => { + const { workspaceSlug, projectId } = props; + const searchParams = useSearchParams(); const router = useRouter(); - const { workspace_slug: workspaceSlug, project_slug: projectId } = router.query as { - workspace_slug: string; - project_slug: string; - }; - - const { - project: { activeBoard }, - issue: { states, labels }, - issuesFilter: { issueFilters, updateFilters }, - }: RootStore = useMobxStore(); + // store hooks + const { activeLayout } = useProject(); + const { states, labels } = useIssue(); + const { issueFilters, updateFilters } = useIssueFilter(); const updateRouteParams = useCallback( (key: keyof IIssueFilterOptions, value: string[]) => { @@ -31,14 +31,14 @@ export const IssueFiltersDropdown: FC = observer(() => { const priority = key === "priority" ? value : issueFilters?.filters?.priority ?? []; const labels = key === "labels" ? value : issueFilters?.filters?.labels ?? []; - let params: any = { board: activeBoard || "list" }; + let params: any = { board: activeLayout || "list" }; if (priority.length > 0) params = { ...params, priorities: priority.join(",") }; if (state.length > 0) params = { ...params, states: state.join(",") }; if (labels.length > 0) params = { ...params, labels: labels.join(",") }; - - router.push({ pathname: `/${workspaceSlug}/${projectId}`, query: { ...params } }, undefined, { shallow: true }); + console.log("params", params); + router.push(`/${workspaceSlug}/${projectId}?${searchParams}`); }, - [workspaceSlug, projectId, activeBoard, issueFilters, router] + [workspaceSlug, projectId, activeLayout, issueFilters, router] ); const handleFilters = useCallback( @@ -66,8 +66,8 @@ export const IssueFiltersDropdown: FC = observer(() => { diff --git a/space/components/issues/filters/selection.tsx b/space/components/issues/filters/selection.tsx index 71f0c5f1b..9d7d89666 100644 --- a/space/components/issues/filters/selection.tsx +++ b/space/components/issues/filters/selection.tsx @@ -1,13 +1,10 @@ import React, { useState } from "react"; import { observer } from "mobx-react-lite"; import { Search, X } from "lucide-react"; -// components // types - -// filter helpers -import { ILayoutDisplayFiltersOptions } from "@/store/issues/helpers"; -import { IIssueFilterOptions } from "@/store/issues/types"; -import { IIssueState, IIssueLabel } from "types/issue"; +import { IIssueState, IIssueLabel, IIssueFilterOptions } from "@/types/issue"; +import { ILayoutDisplayFiltersOptions } from "@/types/issue-filters"; +// components import { FilterPriority, FilterState } from "./"; type Props = { diff --git a/space/components/issues/navbar/index.tsx b/space/components/issues/navbar/index.tsx index de8633100..97cd0c500 100644 --- a/space/components/issues/navbar/index.tsx +++ b/space/components/issues/navbar/index.tsx @@ -1,54 +1,52 @@ -import { useEffect } from "react"; +"use client"; + +import { useEffect, FC } from "react"; import { observer } from "mobx-react-lite"; import Link from "next/link"; -import { useRouter } from "next/router"; -// components +import { useRouter, useParams, useSearchParams, usePathname } from "next/navigation"; import { Briefcase } from "lucide-react"; import { Avatar, Button } from "@plane/ui"; +// components import { ProjectLogo } from "@/components/common"; import { IssueFiltersDropdown } from "@/components/issues/filters"; // hooks -import { useMobxStore, useUser } from "@/hooks/store"; -// store -import { RootStore } from "@/store/root.store"; +import { useProject, useUser, useIssueFilter } from "@/hooks/store"; +// types import { TIssueBoardKeys } from "@/types/issue"; +// components import { NavbarIssueBoardView } from "./issue-board-view"; import { NavbarTheme } from "./theme"; -const IssueNavbar = observer(() => { - const { - project: projectStore, - issuesFilter: { updateFilters }, - }: RootStore = useMobxStore(); - const { data: user } = useUser(); - // router +type IssueNavbarProps = { + projectSettings: any; + workspaceSlug: string; + projectId: string; +}; + +const IssueNavbar: FC = observer((props) => { + const { projectSettings, workspaceSlug, projectId } = props; + const { project_details, views } = projectSettings; + const { board, labels, states, priorities, peekId } = useParams(); + const searchParams = useSearchParams(); + const pathName = usePathname(); + // hooks const router = useRouter(); - const { workspace_slug, project_slug, board, peekId, states, priorities, labels } = router.query as { - workspace_slug: string; - project_slug: string; - peekId: string; - board: string; - states: string; - priorities: string; - labels: string; - }; + // store + const { settings, activeLayout, hydrate, setActiveLayout } = useProject(); + const { data: user } = useUser(); + const { updateFilters } = useIssueFilter(); + hydrate(projectSettings); useEffect(() => { - if (workspace_slug && project_slug) { - projectStore.fetchProjectSettings(workspace_slug.toString(), project_slug.toString()); - } - }, [projectStore, workspace_slug, project_slug]); - - useEffect(() => { - if (workspace_slug && project_slug && projectStore?.deploySettings) { + if (workspaceSlug && projectId && settings) { const viewsAcceptable: string[] = []; let currentBoard: TIssueBoardKeys | null = null; - if (projectStore?.deploySettings?.views?.list) viewsAcceptable.push("list"); - if (projectStore?.deploySettings?.views?.kanban) viewsAcceptable.push("kanban"); - if (projectStore?.deploySettings?.views?.calendar) viewsAcceptable.push("calendar"); - if (projectStore?.deploySettings?.views?.gantt) viewsAcceptable.push("gantt"); - if (projectStore?.deploySettings?.views?.spreadsheet) viewsAcceptable.push("spreadsheet"); + if (settings?.views?.list) viewsAcceptable.push("list"); + if (settings?.views?.kanban) viewsAcceptable.push("kanban"); + if (settings?.views?.calendar) viewsAcceptable.push("calendar"); + if (settings?.views?.gantt) viewsAcceptable.push("gantt"); + if (settings?.views?.spreadsheet) viewsAcceptable.push("spreadsheet"); if (board) { if (viewsAcceptable.includes(board.toString())) { @@ -65,49 +63,47 @@ const IssueNavbar = observer(() => { } if (currentBoard) { - if (projectStore?.activeBoard === null || projectStore?.activeBoard !== currentBoard) { + if (activeLayout === null || activeLayout !== currentBoard) { let params: any = { board: currentBoard }; if (peekId && peekId.length > 0) params = { ...params, peekId: peekId }; if (priorities && priorities.length > 0) params = { ...params, priorities: priorities }; if (states && states.length > 0) params = { ...params, states: states }; if (labels && labels.length > 0) params = { ...params, labels: labels }; - + console.log("params", params); let storeParams: any = {}; if (priorities && priorities.length > 0) storeParams = { ...storeParams, priority: priorities.split(",") }; if (states && states.length > 0) storeParams = { ...storeParams, state: states.split(",") }; if (labels && labels.length > 0) storeParams = { ...storeParams, labels: labels.split(",") }; - if (storeParams) updateFilters(project_slug, storeParams); - - projectStore.setActiveBoard(currentBoard); - router.push({ - pathname: `/${workspace_slug}/${project_slug}`, - query: { ...params }, - }); + if (storeParams) updateFilters(projectId, storeParams); + setActiveLayout(currentBoard); + router.push(`/${workspaceSlug}/${projectId}?${searchParams}`); } } } }, [ board, - workspace_slug, - project_slug, + workspaceSlug, + projectId, router, - projectStore, - projectStore?.deploySettings, updateFilters, labels, states, priorities, peekId, + settings, + activeLayout, + setActiveLayout, + searchParams, ]); return (
{/* project detail */}
- {projectStore.project ? ( + {project_details ? ( - + ) : ( @@ -115,21 +111,18 @@ const IssueNavbar = observer(() => { )}
- {projectStore?.project?.name || `...`} + {project_details?.name || `...`}
- {/* issue search bar */} -
{/* */}
- {/* issue views */}
- +
{/* issue filters */}
- +
{/* theming */} @@ -137,14 +130,14 @@ const IssueNavbar = observer(() => {
- {user ? ( + {user?.id ? (
{user.display_name}
) : (
- +
diff --git a/space/components/issues/navbar/issue-board-view.tsx b/space/components/issues/navbar/issue-board-view.tsx index 12574bef8..d2eb53398 100644 --- a/space/components/issues/navbar/issue-board-view.tsx +++ b/space/components/issues/navbar/issue-board-view.tsx @@ -1,47 +1,49 @@ +"use client"; + +import { FC } from "react"; import { observer } from "mobx-react-lite"; -import { useRouter } from "next/router"; // constants import { issueViews } from "@/constants/data"; +// hooks +import { useProject } from "@/hooks/store"; // mobx -import { useMobxStore } from "@/hooks/store"; -import { RootStore } from "@/store/root.store"; -import { TIssueBoardKeys } from "types/issue"; +import { TIssueBoardKeys } from "@/types/issue"; -export const NavbarIssueBoardView = observer(() => { - 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 }; +type NavbarIssueBoardViewProps = { + layouts: Record; +}; + +export const NavbarIssueBoardView: FC = observer((props) => { + const { layouts } = props; + + const { activeLayout, setActiveLayout } = useProject(); const handleCurrentBoardView = (boardView: string) => { - setActiveBoard(boardView as TIssueBoardKeys); - router.push(`/${workspace_slug}/${project_slug}?board=${boardView}`); + setActiveLayout(boardView as TIssueBoardKeys); }; return ( <> - {viewOptions && - Object.keys(viewOptions).map((viewKey: string) => { - if (viewOptions[viewKey]) { + {layouts && + Object.keys(layouts).map((layoutKey: string) => { + if (layouts[layoutKey as TIssueBoardKeys]) { return (
handleCurrentBoardView(viewKey)} - title={viewKey} + onClick={() => handleCurrentBoardView(layoutKey)} + title={layoutKey} > - {issueViews[viewKey]?.icon} + {issueViews[layoutKey]?.icon}
); diff --git a/space/components/issues/navbar/theme.tsx b/space/components/issues/navbar/theme.tsx index 1d45625c7..e09bdda60 100644 --- a/space/components/issues/navbar/theme.tsx +++ b/space/components/issues/navbar/theme.tsx @@ -1,3 +1,5 @@ +"use client"; + // next theme import { useEffect, useState } from "react"; import { observer } from "mobx-react-lite"; @@ -16,7 +18,6 @@ export const NavbarTheme = observer(() => { useEffect(() => { if (!theme) return; - setAppTheme(theme); }, [theme]); diff --git a/space/components/issues/peek-overview/comment/add-comment.tsx b/space/components/issues/peek-overview/comment/add-comment.tsx index dadcc1747..96366eadd 100644 --- a/space/components/issues/peek-overview/comment/add-comment.tsx +++ b/space/components/issues/peek-overview/comment/add-comment.tsx @@ -1,12 +1,11 @@ import React, { useRef } from "react"; import { observer } from "mobx-react-lite"; -import { useRouter } from "next/router"; import { useForm, Controller } from "react-hook-form"; // components import { EditorRefApi } from "@plane/lite-text-editor"; import { LiteTextEditor } from "@/components/editor/lite-text-editor"; // hooks -import { useMobxStore, useUser } from "@/hooks/store"; +import { useIssueDetails, useProject, useUser } from "@/hooks/store"; import useToast from "@/hooks/use-toast"; // types import { Comment } from "@/types/issue"; @@ -17,22 +16,21 @@ const defaultValues: Partial = { type Props = { disabled?: boolean; + workspaceSlug: string; + projectId: string; }; -export const AddComment: React.FC = observer(() => { +export const AddComment: React.FC = observer((props) => { // const { disabled = false } = props; + const { workspaceSlug, projectId } = props; // refs const editorRef = useRef(null); - // router - const router = useRouter(); - const { workspace_slug, project_slug } = router.query; // store hooks - const { project } = useMobxStore(); - const { issueDetails: issueDetailStore } = useMobxStore(); + const { workspace } = useProject(); + const { peekId: issueId, addIssueComment } = useIssueDetails(); const { data: currentUser } = useUser(); // derived values - const workspaceId = project.workspace?.id; - const issueId = issueDetailStore.peekId; + const workspaceId = workspace?.id; // form info const { handleSubmit, @@ -45,10 +43,9 @@ export const AddComment: React.FC = observer(() => { const { setToastAlert } = useToast(); const onSubmit = async (formData: Comment) => { - if (!workspace_slug || !project_slug || !issueId || isSubmitting || !formData.comment_html) return; + if (!workspaceSlug || !projectId || !issueId || isSubmitting || !formData.comment_html) return; - await issueDetailStore - .addIssueComment(workspace_slug.toString(), project_slug.toString(), issueId, formData) + await addIssueComment(workspaceSlug, projectId, issueId, formData) .then(() => { reset(defaultValues); editorRef.current?.clearEditor(); @@ -75,7 +72,7 @@ export const AddComment: React.FC = observer(() => { if (currentUser) handleSubmit(onSubmit)(e); }} workspaceId={workspaceId as string} - workspaceSlug={workspace_slug as string} + workspaceSlug={workspaceSlug} ref={editorRef} initialValue={ !value || value === "" || (typeof value === "object" && Object.keys(value).length === 0) diff --git a/space/components/issues/peek-overview/comment/comment-detail-card.tsx b/space/components/issues/peek-overview/comment/comment-detail-card.tsx index e3962c5e4..449b7883c 100644 --- a/space/components/issues/peek-overview/comment/comment-detail-card.tsx +++ b/space/components/issues/peek-overview/comment/comment-detail-card.tsx @@ -10,9 +10,7 @@ import { CommentReactions } from "@/components/issues/peek-overview"; // helpers import { timeAgo } from "@/helpers/date-time.helper"; // hooks -import { useMobxStore, useUser } from "@/hooks/store"; -// store -import { RootStore } from "@/store/root.store"; +import { useIssueDetails, useProject, useUser } from "@/hooks/store"; // types import { Comment } from "@/types/issue"; @@ -23,12 +21,13 @@ type Props = { export const CommentCard: React.FC = observer((props) => { const { comment, workspaceSlug } = props; - const { project }: RootStore = useMobxStore(); - const workspaceId = project.workspace?.id; - - // store - const { issueDetails: issueDetailStore } = useMobxStore(); + // store hooks + const { workspace } = useProject(); + const { peekId, deleteIssueComment, updateIssueComment } = useIssueDetails(); const { data: currentUser } = useUser(); + // derived values + const workspaceId = workspace?.id; + // states const [isEditing, setIsEditing] = useState(false); // refs @@ -44,15 +43,14 @@ export const CommentCard: React.FC = observer((props) => { }); const handleDelete = () => { - if (!workspaceSlug || !issueDetailStore.peekId) return; - issueDetailStore.deleteIssueComment(workspaceSlug, comment.project, issueDetailStore.peekId, comment.id); + if (!workspaceSlug || !peekId) return; + deleteIssueComment(workspaceSlug, comment.project, peekId, comment.id); }; const handleCommentUpdate = async (formData: Comment) => { - if (!workspaceSlug || !issueDetailStore.peekId) return; - issueDetailStore.updateIssueComment(workspaceSlug, comment.project, issueDetailStore.peekId, comment.id, formData); + if (!workspaceSlug || !peekId) return; + updateIssueComment(workspaceSlug, comment.project, peekId, comment.id, formData); setIsEditing(false); - editorRef.current?.setEditorValue(formData.comment_html); showEditorRef.current?.setEditorValue(formData.comment_html); }; @@ -135,7 +133,7 @@ export const CommentCard: React.FC = observer((props) => {
- +
diff --git a/space/components/issues/peek-overview/comment/comment-reactions.tsx b/space/components/issues/peek-overview/comment/comment-reactions.tsx index ca451f7b4..a19502e53 100644 --- a/space/components/issues/peek-overview/comment/comment-reactions.tsx +++ b/space/components/issues/peek-overview/comment/comment-reactions.tsx @@ -1,58 +1,38 @@ import React from "react"; import { observer } from "mobx-react-lite"; -import { useRouter } from "next/router"; import { Tooltip } from "@plane/ui"; // ui import { ReactionSelector } from "@/components/ui"; // helpers import { groupReactions, renderEmoji } from "@/helpers/emoji.helper"; // hooks -import { useMobxStore, useUser } from "@/hooks/store"; +import { useIssueDetails, useUser } from "@/hooks/store"; type Props = { commentId: string; projectId: string; + workspaceSlug: string; }; export const CommentReactions: React.FC = observer((props) => { - const { commentId, projectId } = props; - - const router = useRouter(); - const { workspace_slug } = router.query; + const { commentId, projectId, workspaceSlug } = props; // hooks - const { issueDetails: issueDetailsStore } = useMobxStore(); + const { addCommentReaction, removeCommentReaction, details, peekId } = useIssueDetails(); const { data: user } = useUser(); - const peekId = issueDetailsStore.peekId; - const commentReactions = peekId - ? issueDetailsStore.details[peekId].comments.find((c) => c.id === commentId)?.comment_reactions - : []; + const commentReactions = peekId ? details[peekId].comments.find((c) => c.id === commentId)?.comment_reactions : []; const groupedReactions = peekId ? groupReactions(commentReactions ?? [], "reaction") : {}; const userReactions = commentReactions?.filter((r) => r.actor_detail.id === user?.id); const handleAddReaction = (reactionHex: string) => { - if (!workspace_slug || !projectId || !peekId) return; - - issueDetailsStore.addCommentReaction( - workspace_slug.toString(), - projectId.toString(), - peekId, - commentId, - reactionHex - ); + if (!workspaceSlug || !projectId || !peekId) return; + addCommentReaction(workspaceSlug, projectId, peekId, commentId, reactionHex); }; const handleRemoveReaction = (reactionHex: string) => { - if (!workspace_slug || !projectId || !peekId) return; - - issueDetailsStore.removeCommentReaction( - workspace_slug.toString(), - projectId.toString(), - peekId, - commentId, - reactionHex - ); + if (!workspaceSlug || !projectId || !peekId) return; + removeCommentReaction(workspaceSlug, projectId, peekId, commentId, reactionHex); }; const handleReactionClick = (reactionHex: string) => { 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 5ab4dffd7..f5918de43 100644 --- a/space/components/issues/peek-overview/full-screen-peek-view.tsx +++ b/space/components/issues/peek-overview/full-screen-peek-view.tsx @@ -13,11 +13,12 @@ import { IIssue } from "@/types/issue"; type Props = { handleClose: () => void; issueDetails: IIssue | undefined; - workspace_slug: string; + workspaceSlug: string; + projectId: string; }; export const FullScreenPeekView: React.FC = observer((props) => { - const { handleClose, issueDetails } = props; + const { handleClose, issueDetails, workspaceSlug, projectId } = props; return (
@@ -35,7 +36,11 @@ export const FullScreenPeekView: React.FC = observer((props) => {
{/* issue activity/comments */}
- +
) : ( diff --git a/space/components/issues/peek-overview/header.tsx b/space/components/issues/peek-overview/header.tsx index 7aba40305..3b8e2960f 100644 --- a/space/components/issues/peek-overview/header.tsx +++ b/space/components/issues/peek-overview/header.tsx @@ -2,19 +2,17 @@ import React from "react"; import { observer } from "mobx-react-lite"; import { MoveRight } from "lucide-react"; import { Listbox, Transition } from "@headlessui/react"; -// hooks // ui import { Icon } from "@/components/ui"; // helpers import { copyTextToClipboard } from "@/helpers/string.helper"; +// hooks +import { useIssueDetails } from "@/hooks/store"; +import useToast from "@/hooks/use-toast"; // store -import { useMobxStore } from "@/hooks/store"; -import { IPeekMode } from "@/store/issue_details"; -import { RootStore } from "@/store/root.store"; -// lib -import useToast from "hooks/use-toast"; +import { IPeekMode } from "@/store/issue-detail.store"; // types -import { IIssue } from "types/issue"; +import { IIssue } from "@/types/issue"; type Props = { handleClose: () => void; @@ -42,7 +40,7 @@ const peekModes: { export const PeekOverviewHeader: React.FC = observer((props) => { const { handleClose } = props; - const { issueDetails: issueDetailStore }: RootStore = useMobxStore(); + const { peekMode, setPeekMode } = useIssueDetails(); const { setToastAlert } = useToast(); @@ -62,21 +60,19 @@ export const PeekOverviewHeader: React.FC = observer((props) => { <>
- {issueDetailStore.peekMode === "side" && ( + {peekMode === "side" && ( )} issueDetailStore.setPeekMode(val)} + value={peekMode} + onChange={(val) => setPeekMode(val)} className="relative flex-shrink-0 text-left" > - - m.key === issueDetailStore.peekMode)?.icon ?? ""} /> + + m.key === peekMode)?.icon ?? ""} /> = observer((props) => {
- {(issueDetailStore.peekMode === "side" || issueDetailStore.peekMode === "modal") && ( + {(peekMode === "side" || peekMode === "modal") && (
diff --git a/space/components/issues/peek-overview/issue-emoji-reactions.tsx b/space/components/issues/peek-overview/issue-emoji-reactions.tsx index 7e568461b..ef5688c57 100644 --- a/space/components/issues/peek-overview/issue-emoji-reactions.tsx +++ b/space/components/issues/peek-overview/issue-emoji-reactions.tsx @@ -1,20 +1,22 @@ import { useEffect } from "react"; import { observer } from "mobx-react-lite"; -import { useRouter } from "next/router"; // lib import { Tooltip } from "@plane/ui"; import { ReactionSelector } from "@/components/ui"; // helpers import { groupReactions, renderEmoji } from "@/helpers/emoji.helper"; // hooks -import { useMobxStore, useUser } from "@/hooks/store"; +import { useIssueDetails, useUser } from "@/hooks/store"; -export const IssueEmojiReactions: React.FC = observer(() => { - // router - const router = useRouter(); - const { workspace_slug, project_slug } = router.query; +type IssueEmojiReactionsProps = { + workspaceSlug: string; + projectId: string; +}; + +export const IssueEmojiReactions: React.FC = observer((props) => { + const { workspaceSlug, projectId } = props; // store - const { issueDetails: issueDetailsStore } = useMobxStore(); + const issueDetailsStore = useIssueDetails(); const { data: user, fetchCurrentUser } = useUser(); const issueId = issueDetailsStore.peekId; @@ -24,20 +26,17 @@ export const IssueEmojiReactions: React.FC = observer(() => { const userReactions = reactions?.filter((r) => r.actor_detail.id === user?.id); const handleAddReaction = (reactionHex: string) => { - if (!workspace_slug || !project_slug || !issueId) return; - - issueDetailsStore.addIssueReaction(workspace_slug.toString(), project_slug.toString(), issueId, reactionHex); + if (!workspaceSlug || !projectId || !issueId) return; + issueDetailsStore.addIssueReaction(workspaceSlug.toString(), projectId.toString(), issueId, reactionHex); }; const handleRemoveReaction = (reactionHex: string) => { - if (!workspace_slug || !project_slug || !issueId) return; - - issueDetailsStore.removeIssueReaction(workspace_slug.toString(), project_slug.toString(), issueId, reactionHex); + if (!workspaceSlug || !projectId || !issueId) return; + issueDetailsStore.removeIssueReaction(workspaceSlug.toString(), projectId.toString(), issueId, reactionHex); }; const handleReactionClick = (reactionHex: string) => { const userReaction = userReactions?.find((r) => r.actor_detail.id === user?.id && r.reaction === reactionHex); - if (userReaction) handleRemoveReaction(reactionHex); else handleAddReaction(reactionHex); }; diff --git a/space/components/issues/peek-overview/issue-properties.tsx b/space/components/issues/peek-overview/issue-properties.tsx index 1018c22f7..d31e8dd6d 100644 --- a/space/components/issues/peek-overview/issue-properties.tsx +++ b/space/components/issues/peek-overview/issue-properties.tsx @@ -8,7 +8,7 @@ import { issueGroupFilter, issuePriorityFilter } from "@/constants/data"; import { renderFullDate } from "@/helpers/date-time.helper"; import { copyTextToClipboard, addSpaceIfCamelCase } from "@/helpers/string.helper"; // types -import { IPeekMode } from "@/store/issue_details"; +import { IPeekMode } from "@/store/issue-detail.store"; // constants import useToast from "hooks/use-toast"; import { IIssue } from "types/issue"; diff --git a/space/components/issues/peek-overview/issue-reaction.tsx b/space/components/issues/peek-overview/issue-reaction.tsx index 5bc60cb34..ae569a52e 100644 --- a/space/components/issues/peek-overview/issue-reaction.tsx +++ b/space/components/issues/peek-overview/issue-reaction.tsx @@ -1,12 +1,20 @@ +import { useParams } from "next/navigation"; import { IssueEmojiReactions, IssueVotes } from "@/components/issues/peek-overview"; -import { useMobxStore } from "@/hooks/store"; +import { useProject } from "@/hooks/store"; + +// type IssueReactionsProps = { +// workspaceSlug: string; +// projectId: string; +// }; export const IssueReactions: React.FC = () => { - const { project: projectStore } = useMobxStore(); + const { workspace_slug: workspaceSlug, project_id: projectId } = useParams(); + + const { canVote, canReact } = useProject(); return (
- {projectStore?.deploySettings?.votes && ( + {canVote && ( <>
@@ -14,9 +22,9 @@ export const IssueReactions: React.FC = () => {
)} - {projectStore?.deploySettings?.reactions && ( + {canReact && (
- +
)}
diff --git a/space/components/issues/peek-overview/issue-vote-reactions.tsx b/space/components/issues/peek-overview/issue-vote-reactions.tsx index 64568f66c..cab6f73ad 100644 --- a/space/components/issues/peek-overview/issue-vote-reactions.tsx +++ b/space/components/issues/peek-overview/issue-vote-reactions.tsx @@ -1,18 +1,17 @@ +"use client"; + import { useState, useEffect } from "react"; import { observer } from "mobx-react-lite"; -import { useRouter } from "next/router"; import { Tooltip } from "@plane/ui"; // hooks -import { useMobxStore, useUser } from "@/hooks/store"; +import { useIssueDetails, useUser } from "@/hooks/store"; -export const IssueVotes: React.FC = observer(() => { +export const IssueVotes: React.FC = observer((props: any) => { + const { workspaceSlug, projectId } = props; + // states const [isSubmitting, setIsSubmitting] = useState(false); - const router = useRouter(); - - const { workspace_slug, project_slug } = router.query; - - const { issueDetails: issueDetailsStore } = useMobxStore(); + const issueDetailsStore = useIssueDetails(); const { data: user, fetchCurrentUser } = useUser(); const issueId = issueDetailsStore.peekId; @@ -26,16 +25,16 @@ export const IssueVotes: React.FC = observer(() => { const isDownVotedByUser = allDownVotes?.some((vote) => vote.actor === user?.id); const handleVote = async (e: any, voteValue: 1 | -1) => { - if (!workspace_slug || !project_slug || !issueId) return; + if (!workspaceSlug || !projectId || !issueId) return; setIsSubmitting(true); const actionPerformed = votes?.find((vote) => vote.actor === user?.id && vote.vote === voteValue); if (actionPerformed) - await issueDetailsStore.removeIssueVote(workspace_slug.toString(), project_slug.toString(), issueId); + await issueDetailsStore.removeIssueVote(workspaceSlug.toString(), projectId.toString(), issueId); else - await issueDetailsStore.addIssueVote(workspace_slug.toString(), project_slug.toString(), issueId, { + await issueDetailsStore.addIssueVote(workspaceSlug.toString(), projectId.toString(), issueId, { vote: voteValue, }); diff --git a/space/components/issues/peek-overview/layout.tsx b/space/components/issues/peek-overview/layout.tsx index 01183fb2d..aa4163610 100644 --- a/space/components/issues/peek-overview/layout.tsx +++ b/space/components/issues/peek-overview/layout.tsx @@ -1,42 +1,32 @@ +"use client"; + import React, { useEffect, useState } from "react"; - import { observer } from "mobx-react-lite"; -import { useRouter } from "next/router"; - -// mobx // headless ui import { Dialog, Transition } from "@headlessui/react"; // components import { FullScreenPeekView, SidePeekView } from "@/components/issues/peek-overview"; -// lib -import { useMobxStore } from "@/hooks/store"; +// store +import { useIssue, useIssueDetails } from "@/hooks/store"; -export const IssuePeekOverview: React.FC = observer(() => { +export const IssuePeekOverview: React.FC = observer((props: any) => { + const { workspaceSlug, projectId, peekId, board, priorities, states, labels } = props; // states const [isSidePeekOpen, setIsSidePeekOpen] = useState(false); const [isModalPeekOpen, setIsModalPeekOpen] = useState(false); - // router - const router = useRouter(); - const { workspace_slug, project_slug, peekId, board, priorities, states, labels } = router.query as { - workspace_slug: string; - project_slug: string; - peekId: string; - board: string; - priorities: string; - states: string; - labels: string; - }; // store - const { issueDetails: issueDetailStore, issue: issueStore } = useMobxStore(); + const issueDetailStore = useIssueDetails(); + const issueStore = useIssue(); + const issueDetails = issueDetailStore.peekId && peekId ? issueDetailStore.details[peekId.toString()] : undefined; useEffect(() => { - if (workspace_slug && project_slug && peekId && issueStore.issues && issueStore.issues.length > 0) { + if (workspaceSlug && projectId && peekId && issueStore.issues && issueStore.issues.length > 0) { if (!issueDetails) { - issueDetailStore.fetchIssueDetails(workspace_slug.toString(), project_slug.toString(), peekId.toString()); + issueDetailStore.fetchIssueDetails(workspaceSlug.toString(), projectId.toString(), peekId.toString()); } } - }, [workspace_slug, project_slug, issueDetailStore, issueDetails, peekId, issueStore.issues]); + }, [workspaceSlug, projectId, issueDetailStore, issueDetails, peekId, issueStore.issues]); const handleClose = () => { issueDetailStore.setPeekId(null); @@ -45,10 +35,8 @@ export const IssuePeekOverview: React.FC = observer(() => { if (states && states.length > 0) params.states = states; if (priorities && priorities.length > 0) params.priorities = priorities; if (labels && labels.length > 0) params.labels = labels; - - router.replace({ pathname: `/${workspace_slug?.toString()}/${project_slug}`, query: { ...params } }, undefined, { - shallow: true, - }); + // TODO: fix this redirection + // router.push( encodeURI(`/${workspaceSlug?.toString()}/${projectId}`, ) { pathname: `/${workspaceSlug?.toString()}/${projectId}`, query: { ...params } }); }; useEffect(() => { @@ -80,7 +68,12 @@ export const IssuePeekOverview: React.FC = observer(() => { leaveTo="translate-x-full" > - + @@ -114,13 +107,19 @@ export const IssuePeekOverview: React.FC = observer(() => { }`} > {issueDetailStore.peekMode === "modal" && ( - + )} {issueDetailStore.peekMode === "full" && ( )}
diff --git a/space/components/issues/peek-overview/side-peek-view.tsx b/space/components/issues/peek-overview/side-peek-view.tsx index 8a8636edc..baa2b4ee5 100644 --- a/space/components/issues/peek-overview/side-peek-view.tsx +++ b/space/components/issues/peek-overview/side-peek-view.tsx @@ -7,16 +7,18 @@ import { PeekOverviewIssueDetails, PeekOverviewIssueProperties, } from "@/components/issues/peek-overview"; - -import { IIssue } from "types/issue"; +// types +import { IIssue } from "@/types/issue"; type Props = { handleClose: () => void; issueDetails: IIssue | undefined; + workspaceSlug: string; + projectId: string; }; export const SidePeekView: React.FC = observer((props) => { - const { handleClose, issueDetails } = props; + const { handleClose, issueDetails, workspaceSlug, projectId } = props; return (
@@ -37,7 +39,11 @@ export const SidePeekView: React.FC = observer((props) => {
{/* issue activity/comments */}
- +
) : ( diff --git a/space/components/views/auth.tsx b/space/components/views/auth.tsx index cb36a6146..87ff8c366 100644 --- a/space/components/views/auth.tsx +++ b/space/components/views/auth.tsx @@ -1,3 +1,5 @@ +"use client"; + import { observer } from "mobx-react-lite"; import Image from "next/image"; // ui @@ -5,7 +7,7 @@ import { useTheme } from "next-themes"; import useSWR from "swr"; import { Spinner } from "@plane/ui"; // components -import { AuthRoot, UserLoggedIn } from "@/components/accounts"; +import { AuthRoot } from "@/components/accounts"; // hooks import { useUser } from "@/hooks/store"; // images @@ -17,12 +19,15 @@ export const AuthView = observer(() => { // hooks const { resolvedTheme } = useTheme(); // store - const { data: currentUser, fetchCurrentUser, isLoading } = useUser(); + const { fetchCurrentUser, isLoading } = useUser(); // fetching user information const { isLoading: isSWRLoading } = useSWR("CURRENT_USER_DETAILS", () => fetchCurrentUser(), { shouldRetryOnError: false, revalidateOnFocus: false, + revalidateIfStale: false, + revalidateOnReconnect: true, + errorRetryCount: 1, }); return ( @@ -33,30 +38,26 @@ export const AuthView = observer(() => {
) : ( <> - {currentUser ? ( - - ) : ( -
-
- Plane background pattern +
+
+ Plane background pattern +
+
+
+
+ Plane Logo + Plane +
-
-
-
- Plane Logo - Plane -
-
-
- -
+
+
- )} +
)} diff --git a/space/components/views/project-details.tsx b/space/components/views/project-details.tsx index ef51a4512..f0fff758c 100644 --- a/space/components/views/project-details.tsx +++ b/space/components/views/project-details.tsx @@ -1,7 +1,10 @@ -import { useEffect } from "react"; +"use client"; + +import { FC, useEffect } from "react"; import { observer } from "mobx-react-lite"; import Image from "next/image"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; +import useSWR from "swr"; // components import { IssueCalendarView } from "@/components/issues/board-views/calendar"; import { IssueGanttView } from "@/components/issues/board-views/gantt"; @@ -11,16 +14,31 @@ import { IssueSpreadsheetView } from "@/components/issues/board-views/spreadshee import { IssueAppliedFilters } from "@/components/issues/filters/applied-filters/root"; import { IssuePeekOverview } from "@/components/issues/peek-overview"; // mobx store -import { useMobxStore, useUser } from "@/hooks/store"; -import { RootStore } from "@/store/root.store"; +import { useIssue, useUser, useProject, useIssueDetails } from "@/hooks/store"; // assets import SomethingWentWrongImage from "public/something-went-wrong.svg"; -export const ProjectDetailsView = observer(() => { - const router = useRouter(); - const { workspace_slug, project_slug, states, labels, priorities, peekId } = router.query; +type ProjectDetailsViewProps = { + workspaceSlug: string; + projectId: string; + peekId: string; +}; - const { issue: issueStore, project: projectStore, issueDetails: issueDetailStore }: RootStore = useMobxStore(); +export const ProjectDetailsView: FC = observer((props) => { + const { workspaceSlug, projectId, peekId } = props; + // router + const params = useParams(); + // store hooks + const { fetchPublicIssues } = useIssue(); + const { activeLayout } = useProject(); + // fetching public issues + useSWR( + workspaceSlug && projectId ? "PROJECT_PUBLIC_ISSUES" : null, + workspaceSlug && projectId ? () => fetchPublicIssues(workspaceSlug, projectId, params) : null + ); + // store hooks + const issueStore = useIssue(); + const issueDetailStore = useIssueDetails(); const { data: currentUser, fetchCurrentUser } = useUser(); useEffect(() => { @@ -30,25 +48,14 @@ export const ProjectDetailsView = observer(() => { }, [currentUser, fetchCurrentUser]); useEffect(() => { - if (workspace_slug && project_slug) { - const params = { - state: states || null, - labels: labels || null, - priority: priorities || null, - }; - issueStore.fetchPublicIssues(workspace_slug?.toString(), project_slug.toString(), params); - } - }, [workspace_slug, project_slug, issueStore, states, labels, priorities]); - - useEffect(() => { - if (peekId && workspace_slug && project_slug) { + if (peekId && workspaceSlug && projectId) { issueDetailStore.setPeekId(peekId.toString()); } - }, [peekId, issueDetailStore, project_slug, workspace_slug]); + }, [peekId, issueDetailStore, projectId, workspaceSlug]); return (
- {workspace_slug && } + {workspaceSlug && } {issueStore?.loader && !issueStore.issues ? (
Loading...
@@ -67,24 +74,24 @@ export const ProjectDetailsView = observer(() => {
) : ( - projectStore?.activeBoard && ( + activeLayout && (
{/* applied filters */} - {projectStore?.activeBoard === "list" && ( + {activeLayout === "list" && (
- +
)} - {projectStore?.activeBoard === "kanban" && ( + {activeLayout === "kanban" && (
- +
)} - {projectStore?.activeBoard === "calendar" && } - {projectStore?.activeBoard === "spreadsheet" && } - {projectStore?.activeBoard === "gantt" && } + {activeLayout === "calendar" && } + {activeLayout === "spreadsheet" && } + {activeLayout === "gantt" && }
) )} diff --git a/space/constants/issue.ts b/space/constants/issue.ts new file mode 100644 index 000000000..147d840fc --- /dev/null +++ b/space/constants/issue.ts @@ -0,0 +1,20 @@ +import { ILayoutDisplayFiltersOptions } from "@/types/issue-filters"; + +export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { + [pageType: string]: { [layoutType: string]: ILayoutDisplayFiltersOptions }; +} = { + issues: { + list: { + filters: ["priority", "state", "labels"], + display_properties: null, + display_filters: null, + extra_options: null, + }, + kanban: { + filters: ["priority", "state", "labels"], + display_properties: null, + display_filters: null, + extra_options: null, + }, + }, +}; diff --git a/space/hooks/store/index.ts b/space/hooks/store/index.ts index 3b7ef07c9..76b6f9315 100644 --- a/space/hooks/store/index.ts +++ b/space/hooks/store/index.ts @@ -1,4 +1,7 @@ -export * from "./user-mobx-provider"; - export * from "./use-instance"; -export * from "./user"; +export * from "./use-project"; +export * from "./use-issue"; +export * from "./use-user"; +export * from "./use-user-profile"; +export * from "./use-issue-details"; +export * from "./use-issue-filter"; diff --git a/space/hooks/store/use-instance.ts b/space/hooks/store/use-instance.ts index 92165e2bb..62aa0baae 100644 --- a/space/hooks/store/use-instance.ts +++ b/space/hooks/store/use-instance.ts @@ -1,10 +1,11 @@ import { useContext } from "react"; +// lib +import { StoreContext } from "@/lib/app-providers"; // store -import { StoreContext } from "@/lib/store-context"; import { IInstanceStore } from "@/store/instance.store"; export const useInstance = (): IInstanceStore => { const context = useContext(StoreContext); - if (context === undefined) throw new Error("useInstance must be used within StoreProvider"); + if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider"); return context.instance; }; diff --git a/space/hooks/store/use-issue-details.tsx b/space/hooks/store/use-issue-details.tsx new file mode 100644 index 000000000..56ee48627 --- /dev/null +++ b/space/hooks/store/use-issue-details.tsx @@ -0,0 +1,11 @@ +import { useContext } from "react"; +// lib +import { StoreContext } from "@/lib/app-providers"; +// store +import { IIssueDetailStore } from "@/store/issue-detail.store"; + +export const useIssueDetails = (): IIssueDetailStore => { + const context = useContext(StoreContext); + if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider"); + return context.issueDetail; +}; diff --git a/space/hooks/store/use-issue-filter.ts b/space/hooks/store/use-issue-filter.ts new file mode 100644 index 000000000..a80d9761b --- /dev/null +++ b/space/hooks/store/use-issue-filter.ts @@ -0,0 +1,11 @@ +import { useContext } from "react"; +// lib +import { StoreContext } from "@/lib/app-providers"; +// store +import { IIssueFilterStore } from "@/store/issue-filters.store"; + +export const useIssueFilter = (): IIssueFilterStore => { + const context = useContext(StoreContext); + if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider"); + return context.issueFilter; +}; diff --git a/space/hooks/store/use-issue.ts b/space/hooks/store/use-issue.ts new file mode 100644 index 000000000..8ccd95ac4 --- /dev/null +++ b/space/hooks/store/use-issue.ts @@ -0,0 +1,11 @@ +import { useContext } from "react"; +// lib +import { StoreContext } from "@/lib/app-providers"; +// store +import { IIssueStore } from "@/store/issue.store"; + +export const useIssue = (): IIssueStore => { + const context = useContext(StoreContext); + if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider"); + return context.issue; +}; diff --git a/space/hooks/store/use-project.ts b/space/hooks/store/use-project.ts new file mode 100644 index 000000000..0bc7d8f8a --- /dev/null +++ b/space/hooks/store/use-project.ts @@ -0,0 +1,11 @@ +import { useContext } from "react"; +// lib +import { StoreContext } from "@/lib/app-providers"; +// store +import { IProjectStore } from "@/store/project.store"; + +export const useProject = (): IProjectStore => { + const context = useContext(StoreContext); + if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider"); + return context.project; +}; diff --git a/space/hooks/store/user/use-user-profile.ts b/space/hooks/store/use-user-profile.ts similarity index 62% rename from space/hooks/store/user/use-user-profile.ts rename to space/hooks/store/use-user-profile.ts index 5b1d8149d..042f16c0d 100644 --- a/space/hooks/store/user/use-user-profile.ts +++ b/space/hooks/store/use-user-profile.ts @@ -1,10 +1,11 @@ import { useContext } from "react"; +// lib +import { StoreContext } from "@/lib/app-providers"; // store -import { StoreContext } from "@/lib/store-context"; -import { IProfileStore } from "@/store/user/profile.store"; +import { IProfileStore } from "@/store/profile.store"; export const useUserProfile = (): IProfileStore => { const context = useContext(StoreContext); if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider"); - return context.user.userProfile; + return context.user.profile; }; diff --git a/space/hooks/store/user/use-user.ts b/space/hooks/store/use-user.ts similarity index 69% rename from space/hooks/store/user/use-user.ts rename to space/hooks/store/use-user.ts index e491d88a2..c935946f8 100644 --- a/space/hooks/store/user/use-user.ts +++ b/space/hooks/store/use-user.ts @@ -1,7 +1,8 @@ import { useContext } from "react"; +// lib +import { StoreContext } from "@/lib/app-providers"; // store -import { StoreContext } from "@/lib/store-context"; -import { IUserStore } from "@/store/user"; +import { IUserStore } from "@/store/user.store"; export const useUser = (): IUserStore => { const context = useContext(StoreContext); diff --git a/space/hooks/store/user-mobx-provider.ts b/space/hooks/store/user-mobx-provider.ts deleted file mode 100644 index 4fbc5591f..000000000 --- a/space/hooks/store/user-mobx-provider.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { useContext } from "react"; -// store -import { StoreContext } from "@/lib/store-context"; -import { RootStore } from "@/store/root.store"; - -export const useMobxStore = (): RootStore => { - const context = useContext(StoreContext); - if (context === undefined) throw new Error("useMobxStore must be used within StoreProvider"); - return context; -}; diff --git a/space/hooks/store/user/index.ts b/space/hooks/store/user/index.ts deleted file mode 100644 index 72660f100..000000000 --- a/space/hooks/store/user/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./use-user"; -export * from "./use-user-profile"; diff --git a/space/hooks/use-editor-suggestions.tsx b/space/hooks/use-editor-suggestions.tsx deleted file mode 100644 index 937306f7b..000000000 --- a/space/hooks/use-editor-suggestions.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { useMobxStore } from "@/hooks/store"; -import { RootStore } from "@/store/root.store"; - -const useEditorSuggestions = () => { - const { mentionsStore }: RootStore = useMobxStore(); - - return { - // mentionSuggestions: mentionsStore.mentionSuggestions, - mentionHighlights: mentionsStore.mentionHighlights, - }; -}; - -export default useEditorSuggestions; diff --git a/space/layouts/project-layout.tsx b/space/layouts/project-layout.tsx deleted file mode 100644 index 0411bcbcc..000000000 --- a/space/layouts/project-layout.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { observer } from "mobx-react-lite"; -import Image from "next/image"; -// components -import IssueNavbar from "@/components/issues/navbar"; -// logo -import planeLogo from "public/plane-logo.svg"; - -const ProjectLayout = ({ children }: { children: React.ReactNode }) => ( - -); - -export default observer(ProjectLayout); diff --git a/space/lib/app-providers.tsx b/space/lib/app-providers.tsx new file mode 100644 index 000000000..389d68ab2 --- /dev/null +++ b/space/lib/app-providers.tsx @@ -0,0 +1,38 @@ +"use client"; + +import { ReactNode, createContext } from "react"; +import { ThemeProvider } from "next-themes"; +// store +import { RootStore } from "@/store/root.store"; + +let rootStore = new RootStore(); + +export const StoreContext = createContext(rootStore); + +function initializeStore(initialData = {}) { + const singletonRootStore = rootStore ?? new RootStore(); + // If your page has Next.js data fetching methods that use a Mobx store, it will + // get hydrated here, check `pages/ssg.js` and `pages/ssr.js` for more details + if (initialData) { + singletonRootStore.hydrate(initialData); + } + // For SSG and SSR always create a new store + if (typeof window === "undefined") return singletonRootStore; + // Create the store once in the client + if (!rootStore) rootStore = singletonRootStore; + return singletonRootStore; +} + +export type AppProviderProps = { + children: ReactNode; + initialState: any; +}; + +export const AppProvider = ({ children, initialState = {} }: AppProviderProps) => { + const store = initializeStore(initialState); + return ( + + {children} + + ); +}; diff --git a/space/lib/index.ts b/space/lib/index.ts deleted file mode 100644 index a10356821..000000000 --- a/space/lib/index.ts +++ /dev/null @@ -1 +0,0 @@ -export const init = {}; diff --git a/space/lib/store-context.tsx b/space/lib/store-context.tsx deleted file mode 100644 index 1eff1ddde..000000000 --- a/space/lib/store-context.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { ReactElement, createContext } from "react"; -// mobx store -import { RootStore } from "@/store/root.store"; - -export let rootStore = new RootStore(); - -export const StoreContext = createContext(rootStore); - -const initializeStore = () => { - const singletonRootStore = rootStore ?? new RootStore(); - if (typeof window === "undefined") return singletonRootStore; - if (!rootStore) rootStore = singletonRootStore; - return singletonRootStore; -}; - -export const StoreProvider = ({ children }: { children: ReactElement }) => { - const store = initializeStore(); - return {children}; -}; diff --git a/space/lib/wrappers/auth-wrapper.tsx b/space/lib/wrappers/auth-wrapper.tsx index 3b49e80d5..ba1fae2e5 100644 --- a/space/lib/wrappers/auth-wrapper.tsx +++ b/space/lib/wrappers/auth-wrapper.tsx @@ -1,6 +1,6 @@ import { FC, ReactNode } from "react"; import { observer } from "mobx-react-lite"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; import useSWR from "swr"; import { Spinner } from "@plane/ui"; // helpers diff --git a/space/lib/wrappers/index.ts b/space/lib/wrappers/index.ts index 51fab70a6..d40c4c886 100644 --- a/space/lib/wrappers/index.ts +++ b/space/lib/wrappers/index.ts @@ -1,2 +1 @@ -export * from "./instance-wrapper"; export * from "./auth-wrapper"; diff --git a/space/lib/wrappers/instance-wrapper.tsx b/space/lib/wrappers/instance-wrapper.tsx deleted file mode 100644 index 3be92ed05..000000000 --- a/space/lib/wrappers/instance-wrapper.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { FC, ReactNode } from "react"; -import { observer } from "mobx-react-lite"; -import useSWR from "swr"; -// ui -import { Spinner } from "@plane/ui"; -// components -import { InstanceNotReady, InstanceFailureView } from "@/components/instance"; -// hooks -import { useInstance } from "@/hooks/store"; - -type TInstanceWrapper = { - children: ReactNode; -}; - -export const InstanceWrapper: FC = observer((props) => { - const { children } = props; - // hooks - const { isLoading, instance, fetchInstanceInfo } = useInstance(); - - const { isLoading: isSWRLoading, mutate } = useSWR("INSTANCE_INFORMATION", () => fetchInstanceInfo(), { - revalidateOnFocus: false, - revalidateIfStale: false, - revalidateOnReconnect: false, - errorRetryCount: 0, - }); - - if (isSWRLoading || isLoading) - return ( -
- -
- ); - - if (!instance) return ; - - if (instance?.instance?.is_setup_done === false) return ; - - return <>{children}; -}); diff --git a/space/pages/404.tsx b/space/pages/404.tsx deleted file mode 100644 index 4591f71f8..000000000 --- a/space/pages/404.tsx +++ /dev/null @@ -1,42 +0,0 @@ -// next imports -import { observer } from "mobx-react-lite"; -import Image from "next/image"; -// hooks -import { useInstance } from "@/hooks/store"; -// images -import notFoundImage from "public/404.svg"; - -const Custom404Error = observer(() => { - // hooks - const { instance } = useInstance(); - - const redirectionUrl = instance?.config?.app_base_url || "/"; - - return ( -
-
-
-
- 404- Page not found -
-
Oops! Something went wrong.
-
- Sorry, the page you are looking for cannot be found. It may have been removed, had its name changed, or is - temporarily unavailable. -
-
- - -
-
- ); -}); - -export default Custom404Error; diff --git a/space/pages/[workspace_slug]/[project_slug]/index.tsx b/space/pages/[workspace_slug]/[project_slug]/index.tsx deleted file mode 100644 index aaec7672e..000000000 --- a/space/pages/[workspace_slug]/[project_slug]/index.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import Head from "next/head"; -import { useRouter } from "next/router"; -import useSWR from "swr"; -// components -import { ProjectDetailsView } from "@/components/views"; -// helpers -import { EPageTypes } from "@/helpers/authentication.helper"; -// hooks -import { useMobxStore } from "@/hooks/store"; -// layouts -import ProjectLayout from "@/layouts/project-layout"; -// wrappers -import { AuthWrapper } from "@/lib/wrappers"; - -const WorkspaceProjectPage = (props: any) => { - const SITE_TITLE = props?.project_settings?.project_details?.name || "Plane | Deploy"; - - const router = useRouter(); - const { workspace_slug, project_slug, states, labels, priorities } = router.query; - - const { project: projectStore, issue: issueStore } = useMobxStore(); - - useSWR("REVALIDATE_ALL", () => { - if (workspace_slug && project_slug) { - projectStore.fetchProjectSettings(workspace_slug.toString(), project_slug.toString()); - const params = { - state: states || null, - labels: labels || null, - priority: priorities || null, - }; - issueStore.fetchPublicIssues(workspace_slug.toString(), project_slug.toString(), params); - } - }); - - return ( - - - - {SITE_TITLE} - - - - - ); -}; - -export default WorkspaceProjectPage; diff --git a/space/pages/[workspace_slug]/index.tsx b/space/pages/[workspace_slug]/index.tsx deleted file mode 100644 index 635f3fdf9..000000000 --- a/space/pages/[workspace_slug]/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -const WorkspaceProjectPage = () => ( -
Plane Workspace Space
-); - -export default WorkspaceProjectPage; diff --git a/space/pages/_app.tsx b/space/pages/_app.tsx deleted file mode 100644 index 363b61510..000000000 --- a/space/pages/_app.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import type { AppProps } from "next/app"; -import Head from "next/head"; -import { ThemeProvider } from "next-themes"; -// styles -import "@/styles/globals.css"; -// contexts -import { SITE_NAME, SITE_DESCRIPTION, SITE_URL, TWITTER_USER_NAME, SITE_KEYWORDS, SITE_TITLE } from "@/constants/seo"; -import { ToastContextProvider } from "@/contexts/toast.context"; -// mobx store provider -import { StoreProvider } from "@/lib/store-context"; -// wrappers -import { InstanceWrapper } from "@/lib/wrappers"; - -const prefix = "/spaces/"; - -function MyApp({ Component, pageProps }: AppProps) { - return ( - <> - - {SITE_TITLE} - - - - - - - - - - - - - - - - - - - - - - - - ); -} - -export default MyApp; diff --git a/space/pages/_document.tsx b/space/pages/_document.tsx deleted file mode 100644 index ae4455438..000000000 --- a/space/pages/_document.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import Document, { Html, Head, Main, NextScript } from "next/document"; - -class MyDocument extends Document { - render() { - return ( - - - -
-
- - - - ); - } -} - -export default MyDocument; diff --git a/space/pages/accounts/forgot-password.tsx b/space/pages/accounts/forgot-password.tsx deleted file mode 100644 index 494eae9d3..000000000 --- a/space/pages/accounts/forgot-password.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import { NextPage } from "next"; -import Image from "next/image"; -import Link from "next/link"; -import { useRouter } from "next/router"; -import { useTheme } from "next-themes"; -import { Controller, useForm } from "react-hook-form"; -// icons -import { CircleCheck } from "lucide-react"; -// ui -import { Button, Input, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui"; -// helpers -import { EPageTypes } from "@/helpers/authentication.helper"; -import { cn } from "@/helpers/common.helper"; -import { checkEmailValidity } from "@/helpers/string.helper"; -// hooks -import useTimer from "@/hooks/use-timer"; -// wrappers -import { AuthWrapper } from "@/lib/wrappers"; -// services -import { AuthService } from "@/services/authentication.service"; -// images -import PlaneBackgroundPatternDark from "public/auth/background-pattern-dark.svg"; -import PlaneBackgroundPattern from "public/auth/background-pattern.svg"; -import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; - -type TForgotPasswordFormValues = { - email: string; -}; - -const defaultValues: TForgotPasswordFormValues = { - email: "", -}; - -// services -const authService = new AuthService(); - -const ForgotPasswordPage: NextPage = () => { - // router - const router = useRouter(); - const { email } = router.query; - // hooks - const { resolvedTheme } = useTheme(); - // timer - const { timer: resendTimerCode, setTimer: setResendCodeTimer } = useTimer(0); - - // form info - const { - control, - formState: { errors, isSubmitting, isValid }, - handleSubmit, - } = useForm({ - defaultValues: { - ...defaultValues, - email: email?.toString() ?? "", - }, - }); - - const handleForgotPassword = async (formData: TForgotPasswordFormValues) => { - await authService - .sendResetPasswordLink({ - email: formData.email, - }) - .then(() => { - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Email sent", - message: - "Check your inbox for a link to reset your password. If it doesn't appear within a few minutes, check your spam folder.", - }); - setResendCodeTimer(30); - }) - .catch((err: any) => { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: err?.error ?? "Something went wrong. Please try again.", - }); - }); - }; - - return ( - -
-
- Plane background pattern -
-
-
-
- Plane Logo - Plane -
-
-
-
-
-
-

- Reset your password -

-

- Enter your user account{"'"}s verified email address and we will send you a password reset link. -

-
-
-
- - checkEmailValidity(value) || "Email is invalid", - }} - render={({ field: { value, onChange, ref } }) => ( - 0} - /> - )} - /> - {resendTimerCode > 0 && ( -

- - We sent the reset link to your email address -

- )} -
- - - Back to sign in - -
-
-
-
-
-
-
- ); -}; - -export default ForgotPasswordPage; diff --git a/space/pages/accounts/reset-password.tsx b/space/pages/accounts/reset-password.tsx deleted file mode 100644 index 773acb10e..000000000 --- a/space/pages/accounts/reset-password.tsx +++ /dev/null @@ -1,205 +0,0 @@ -import { useEffect, useMemo, useState } from "react"; -import { NextPage } from "next"; -import Image from "next/image"; -import { useRouter } from "next/router"; -// icons -import { useTheme } from "next-themes"; -import { Eye, EyeOff } from "lucide-react"; -// ui -import { Button, Input } from "@plane/ui"; -// components -import { PasswordStrengthMeter } from "@/components/accounts"; -// helpers -import { EPageTypes } from "@/helpers/authentication.helper"; -import { API_BASE_URL } from "@/helpers/common.helper"; -import { getPasswordStrength } from "@/helpers/password.helper"; -// wrappers -import { AuthWrapper } from "@/lib/wrappers"; -// services -import { AuthService } from "@/services/authentication.service"; -// images -import PlaneBackgroundPatternDark from "public/auth/background-pattern-dark.svg"; -import PlaneBackgroundPattern from "public/auth/background-pattern.svg"; -import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; - -type TResetPasswordFormValues = { - email: string; - password: string; - confirm_password?: string; -}; - -const defaultValues: TResetPasswordFormValues = { - email: "", - password: "", -}; - -// services -const authService = new AuthService(); - -const ResetPasswordPage: NextPage = () => { - // router - const router = useRouter(); - const { uidb64, token, email } = router.query; - // states - const [showPassword, setShowPassword] = useState(false); - const [resetFormData, setResetFormData] = useState({ - ...defaultValues, - email: email ? email.toString() : "", - }); - const [csrfToken, setCsrfToken] = useState(undefined); - const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false); - // hooks - const { resolvedTheme } = useTheme(); - - useEffect(() => { - if (email && !resetFormData.email) { - setResetFormData((prev) => ({ ...prev, email: email.toString() })); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [email]); - - const handleFormChange = (key: keyof TResetPasswordFormValues, value: string) => - setResetFormData((prev) => ({ ...prev, [key]: value })); - - useEffect(() => { - if (csrfToken === undefined) - authService.requestCSRFToken().then((data) => data?.csrf_token && setCsrfToken(data.csrf_token)); - }, [csrfToken]); - - const isButtonDisabled = useMemo( - () => - !!resetFormData.password && - getPasswordStrength(resetFormData.password) >= 3 && - resetFormData.password === resetFormData.confirm_password - ? false - : true, - [resetFormData] - ); - - return ( - -
-
- Plane background pattern -
-
-
-
- Plane Logo - Plane -
-
-
-
-
-
-

- Set new password -

-

Secure your account with a strong password

-
-
- -
- -
- -
-
-
- -
- handleFormChange("password", e.target.value)} - //hasError={Boolean(errors.password)} - placeholder="Enter password" - className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400" - minLength={8} - onFocus={() => setIsPasswordInputFocused(true)} - onBlur={() => setIsPasswordInputFocused(false)} - autoFocus - /> - {showPassword ? ( - setShowPassword(false)} - /> - ) : ( - setShowPassword(true)} - /> - )} -
- {isPasswordInputFocused && } -
- {getPasswordStrength(resetFormData.password) >= 3 && ( -
- -
- handleFormChange("confirm_password", e.target.value)} - placeholder="Confirm password" - className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400" - /> - {showPassword ? ( - setShowPassword(false)} - /> - ) : ( - setShowPassword(true)} - /> - )} -
- {!!resetFormData.confirm_password && - resetFormData.password !== resetFormData.confirm_password && ( - Passwords don{"'"}t match - )} -
- )} - -
-
-
-
-
-
-
- ); -}; - -export default ResetPasswordPage; diff --git a/space/pages/index.tsx b/space/pages/index.tsx deleted file mode 100644 index 8bba85cca..000000000 --- a/space/pages/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { observer } from "mobx-react-lite"; -import { NextPage } from "next"; -// components -import { AuthView } from "@/components/views"; -// helpers -import { EPageTypes } from "@/helpers/authentication.helper"; -// wrapper -import { AuthWrapper } from "@/lib/wrappers"; - -const Index: NextPage = observer(() => ( - - - -)); - -export default Index; diff --git a/space/pages/onboarding/index.tsx b/space/pages/onboarding/index.tsx deleted file mode 100644 index 98404f577..000000000 --- a/space/pages/onboarding/index.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import React from "react"; -import { observer } from "mobx-react-lite"; -import Image from "next/image"; -import { useRouter } from "next/router"; -import { useTheme } from "next-themes"; -// ui -import { Avatar } from "@plane/ui"; -// components -import { OnBoardingForm } from "@/components/accounts/onboarding-form"; -// helpers -import { EPageTypes } from "@/helpers/authentication.helper"; -import { ASSET_PREFIX } from "@/helpers/common.helper"; -// hooks -import { useUser, useUserProfile } from "@/hooks/store"; -// wrappers -import { AuthWrapper } from "@/lib/wrappers"; -// assets -import ProfileSetupDark from "public/onboarding/profile-setup-dark.svg"; -import ProfileSetup from "public/onboarding/profile-setup-light.svg"; - -const OnBoardingPage = observer(() => { - // router - const router = useRouter(); - const { next_path } = router.query; - - // hooks - const { resolvedTheme } = useTheme(); - - const { data: user } = useUser(); - const { data: currentUserProfile, updateUserProfile } = useUserProfile(); - - if (!user) { - router.push("/"); - return <>; - } - - // complete onboarding - const finishOnboarding = async () => { - if (!user) return; - - await updateUserProfile({ - onboarding_step: { - ...currentUserProfile?.onboarding_step, - profile_complete: true, - }, - }).catch(() => { - console.log("Failed to update onboarding status"); - }); - - if (next_path) router.push(next_path.toString()); - router.push("/"); - }; - - return ( - -
-
-
-
-
- Plane Logo -
-
-
-
-
- {user?.avatar && ( - - )} - - {user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email} - -
-
-
-
-
-
-

Welcome to Plane!

-

- Let’s setup your profile, tell us a bit about yourself. -

-
- -
-
-
-
-
- {user?.avatar && ( - - )} - - {user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email} - -
-
-
- Profile setup -
-
-
-
- ); -}); - -export default OnBoardingPage; diff --git a/space/pages/project-not-published/index.tsx b/space/pages/project-not-published/index.tsx deleted file mode 100644 index 0bd25dd6e..000000000 --- a/space/pages/project-not-published/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -// next imports -import { observer } from "mobx-react-lite"; -import Image from "next/image"; -// helpers -import { EPageTypes } from "@/helpers/authentication.helper"; -// hooks -import { useInstance } from "@/hooks/store"; -// wrappers -import { AuthWrapper } from "@/lib/wrappers"; -// images -import projectNotPublishedImage from "@/public/project-not-published.svg"; - -const CustomProjectNotPublishedError = observer(() => { - // hooks - const { instance } = useInstance(); - - const redirectionUrl = instance?.config?.app_base_url || "/"; - - return ( - -
-
-
-
- 404- Page not found -
-
- Oops! The page you{`'`}re looking for isn{`'`}t live at the moment. -
-
- If this is your project, login to your workspace to adjust its visibility settings and make it public. -
-
- - -
-
-
- ); -}); - -export default CustomProjectNotPublishedError; diff --git a/space/services/api.service.ts b/space/services/api.service.ts index b6d353ccc..a5fe3e93d 100644 --- a/space/services/api.service.ts +++ b/space/services/api.service.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import axios, { AxiosInstance } from "axios"; // store -import { rootStore } from "@/lib/store-context"; +// import { rootStore } from "@/lib/store-context"; abstract class APIService { protected baseURL: string; @@ -18,14 +18,14 @@ abstract class APIService { } private setupInterceptors() { - this.axiosInstance.interceptors.response.use( - (response) => response, - (error) => { - const store = rootStore; - if (error.response && error.response.status === 401 && store.user.data) store.user.reset(); - return Promise.reject(error); - } - ); + // this.axiosInstance.interceptors.response.use( + // (response) => response, + // (error) => { + // const store = rootStore; + // if (error.response && error.response.status === 401 && store.user.data) store.user.reset(); + // return Promise.reject(error); + // } + // ); } get(url: string, params = {}) { diff --git a/space/services/authentication.service.ts b/space/services/auth.service.ts similarity index 100% rename from space/services/authentication.service.ts rename to space/services/auth.service.ts diff --git a/space/services/issue.service.ts b/space/services/issue.service.ts index b6f2e3be2..aa54e500e 100644 --- a/space/services/issue.service.ts +++ b/space/services/issue.service.ts @@ -1,6 +1,6 @@ +import { API_BASE_URL } from "@/helpers/common.helper"; // services import APIService from "@/services/api.service"; -import { API_BASE_URL } from "@/helpers/common.helper"; class IssueService extends APIService { constructor() { diff --git a/space/services/project.service.ts b/space/services/project.service.ts index 2e173d282..bff754595 100644 --- a/space/services/project.service.ts +++ b/space/services/project.service.ts @@ -1,6 +1,6 @@ +import { API_BASE_URL } from "@/helpers/common.helper"; // services import APIService from "@/services/api.service"; -import { API_BASE_URL } from "@/helpers/common.helper"; class ProjectService extends APIService { constructor() { diff --git a/space/store/instance.store.ts b/space/store/instance.store.ts index db4fd87a9..4a410d851 100644 --- a/space/store/instance.store.ts +++ b/space/store/instance.store.ts @@ -18,15 +18,18 @@ type TError = { export interface IInstanceStore { // issues isLoading: boolean; - instance: IInstance | undefined; + data: IInstance | NonNullable; + config: Record; error: TError | undefined; // action fetchInstanceInfo: () => Promise; + hydrate: (data: Record, config: Record) => void; } export class InstanceStore implements IInstanceStore { isLoading: boolean = true; - instance: IInstance | undefined = undefined; + data: IInstance | Record = {}; + config: Record = {}; error: TError | undefined = undefined; // services instanceService; @@ -35,15 +38,22 @@ export class InstanceStore implements IInstanceStore { makeObservable(this, { // observable isLoading: observable.ref, - instance: observable, + data: observable, + config: observable, error: observable, // actions fetchInstanceInfo: action, + hydrate: action, }); // services this.instanceService = new InstanceService(); } + hydrate = (data: Record, config: Record) => { + this.data = { ...this.data, ...data }; + this.config = { ...this.config, ...config }; + }; + /** * @description fetching instance information */ @@ -51,10 +61,11 @@ export class InstanceStore implements IInstanceStore { try { this.isLoading = true; this.error = undefined; - const instance = await this.instanceService.getInstanceInfo(); + const instanceDetails = await this.instanceService.getInstanceInfo(); runInAction(() => { this.isLoading = false; - this.instance = instance; + this.data = instanceDetails.instance; + this.config = instanceDetails.config; }); } catch (error) { runInAction(() => { diff --git a/space/store/issue_details.ts b/space/store/issue-detail.store.ts similarity index 99% rename from space/store/issue_details.ts rename to space/store/issue-detail.store.ts index 3bbf0e581..b6734640b 100644 --- a/space/store/issue_details.ts +++ b/space/store/issue-detail.store.ts @@ -55,7 +55,7 @@ export interface IIssueDetailStore { removeIssueVote: (workspaceId: string, projectId: string, issueId: string) => Promise; } -class IssueDetailStore implements IIssueDetailStore { +export class IssueDetailStore implements IIssueDetailStore { loader: boolean = false; error: any = null; peekId: string | null = null; @@ -431,5 +431,3 @@ class IssueDetailStore implements IIssueDetailStore { } }; } - -export default IssueDetailStore; diff --git a/space/store/issues/issue-filters.store.ts b/space/store/issue-filters.store.ts similarity index 55% rename from space/store/issues/issue-filters.store.ts rename to space/store/issue-filters.store.ts index e62e57933..d137753be 100644 --- a/space/store/issues/issue-filters.store.ts +++ b/space/store/issue-filters.store.ts @@ -1,15 +1,16 @@ import { action, makeObservable, observable, runInAction, computed } from "mobx"; -// types +// constants +import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue"; +// store import { RootStore } from "@/store/root.store"; -import { IIssueFilterOptions, TIssueParams } from "./types"; -import { handleIssueQueryParamsByLayout } from "./helpers"; -import { IssueFilterBaseStore } from "./base-issue-filter.store"; +// types +import { TIssueBoardKeys, IIssueFilterOptions, TIssueParams } from "@/types/issue"; interface IFiltersOptions { filters: IIssueFilterOptions; } -export interface IIssuesFilterStore { +export interface IIssueFilterStore { // observables projectIssueFilters: { [projectId: string]: IFiltersOptions } | undefined; // computed @@ -21,15 +22,13 @@ export interface IIssuesFilterStore { updateFilters: (projectId: string, filters: IIssueFilterOptions) => Promise; } -export class IssuesFilterStore extends IssueFilterBaseStore implements IIssuesFilterStore { +export class IssueFilterStore implements IIssueFilterStore { // observables projectIssueFilters: { [projectId: string]: IFiltersOptions } | undefined = undefined; // root store rootStore; constructor(_rootStore: RootStore) { - super(_rootStore); - makeObservable(this, { // observables projectIssueFilters: observable.ref, @@ -43,35 +42,61 @@ export class IssuesFilterStore extends IssueFilterBaseStore implements IIssuesFi this.rootStore = _rootStore; } + // helper methods + computedFilter = (filters: any, filteredParams: any) => { + const computedFilters: any = {}; + Object.keys(filters).map((key) => { + if (filters[key] != undefined && filteredParams.includes(key)) + computedFilters[key] = + typeof filters[key] === "string" || typeof filters[key] === "boolean" ? filters[key] : filters[key].join(","); + }); + + return computedFilters; + }; + // helpers issueDisplayFilters = (projectId: string) => { if (!projectId) return undefined; return this.projectIssueFilters?.[projectId] || undefined; }; - // actions + handleIssueQueryParamsByLayout = (layout: TIssueBoardKeys | undefined, viewType: "issues"): TIssueParams[] | null => { + const queryParams: TIssueParams[] = []; + if (!layout) return null; + + const layoutOptions = ISSUE_DISPLAY_FILTERS_BY_LAYOUT[viewType][layout]; + + // add filters query params + layoutOptions.filters.forEach((option: any) => { + queryParams.push(option); + }); + + return queryParams; + }; + + // actions updateFilters = async (projectId: string, filters: IIssueFilterOptions) => { try { - let _projectIssueFilters = { ...this.projectIssueFilters }; - if (!_projectIssueFilters) _projectIssueFilters = {}; - if (!_projectIssueFilters[projectId]) _projectIssueFilters[projectId] = { filters: {} }; + let issueFilters = { ...this.projectIssueFilters }; + if (!issueFilters) issueFilters = {}; + if (!issueFilters[projectId]) issueFilters[projectId] = { filters: {} }; - const _filters = { - filters: { ..._projectIssueFilters[projectId].filters }, + const newFilters = { + filters: { ...issueFilters[projectId].filters }, }; - _filters.filters = { ..._filters.filters, ...filters }; + newFilters.filters = { ...newFilters.filters, ...filters }; - _projectIssueFilters[projectId] = { - filters: _filters.filters, + issueFilters[projectId] = { + filters: newFilters.filters, }; runInAction(() => { - this.projectIssueFilters = _projectIssueFilters; + this.projectIssueFilters = issueFilters; }); - return _filters; + return newFilters; } catch (error) { throw error; } @@ -89,7 +114,7 @@ export class IssuesFilterStore extends IssueFilterBaseStore implements IIssuesFi get appliedFilters() { const userFilters = this.issueFilters; - const layout = this.rootStore.project?.activeBoard; + const layout = this.rootStore.project?.activeLayout; if (!userFilters || !layout) return undefined; let filteredRouteParams: any = { @@ -98,7 +123,7 @@ export class IssuesFilterStore extends IssueFilterBaseStore implements IIssuesFi labels: userFilters?.filters?.labels || undefined, }; - const filteredParams = handleIssueQueryParamsByLayout(layout, "issues"); + const filteredParams = this.handleIssueQueryParamsByLayout(layout, "issues"); if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); return filteredRouteParams; diff --git a/space/store/issue.ts b/space/store/issue.store.ts similarity index 82% rename from space/store/issue.ts rename to space/store/issue.store.ts index c6ed8ee71..bbaf47f79 100644 --- a/space/store/issue.ts +++ b/space/store/issue.store.ts @@ -1,11 +1,11 @@ -import { observable, action, computed, makeObservable, runInAction } from "mobx"; +import { observable, action, makeObservable, runInAction } from "mobx"; // services import IssueService from "@/services/issue.service"; +// types +import { IIssue, IIssueState, IIssueLabel } from "@/types/issue"; // store import { RootStore } from "./root.store"; -// types // import { IssueDetailType, TIssueBoardKeys } from "types/issue"; -import { IIssue, IIssueState, IIssueLabel } from "types/issue"; export interface IIssueStore { loader: boolean; @@ -26,7 +26,7 @@ export interface IIssueStore { getFilteredIssuesByState: (state: string) => IIssue[]; } -class IssueStore implements IIssueStore { +export class IssueStore implements IIssueStore { loader: boolean = false; error: any | null = null; @@ -75,13 +75,13 @@ class IssueStore implements IIssueStore { const response = await this.issueService.getPublicIssues(workspaceSlug, projectId, params); if (response) { - const _states: IIssueState[] = [...response?.states]; - const _labels: IIssueLabel[] = [...response?.labels]; - const _issues: IIssue[] = [...response?.issues]; + const states: IIssueState[] = [...response?.states]; + const labels: IIssueLabel[] = [...response?.labels]; + const issues: IIssue[] = [...response?.issues]; runInAction(() => { - this.states = _states; - this.labels = _labels; - this.issues = _issues; + this.states = states; + this.labels = labels; + this.issues = issues; this.loader = false; }); } @@ -99,5 +99,3 @@ class IssueStore implements IIssueStore { getFilteredIssuesByState = (state_id: string): IIssue[] | [] => this.issues?.filter((issue) => issue.state == state_id) || []; } - -export default IssueStore; diff --git a/space/store/issues/base-issue-filter.store.ts b/space/store/issues/base-issue-filter.store.ts deleted file mode 100644 index d3aaae5f7..000000000 --- a/space/store/issues/base-issue-filter.store.ts +++ /dev/null @@ -1,29 +0,0 @@ -// types -import { RootStore } from "@/store/root.store"; - -export interface IIssueFilterBaseStore { - // helper methods - computedFilter(filters: any, filteredParams: any): any; -} - -export class IssueFilterBaseStore implements IIssueFilterBaseStore { - // root store - rootStore; - - constructor(_rootStore: RootStore) { - // root store - this.rootStore = _rootStore; - } - - // helper methods - computedFilter = (filters: any, filteredParams: any) => { - const computedFilters: any = {}; - Object.keys(filters).map((key) => { - if (filters[key] != undefined && filteredParams.includes(key)) - computedFilters[key] = - typeof filters[key] === "string" || typeof filters[key] === "boolean" ? filters[key] : filters[key].join(","); - }); - - return computedFilters; - }; -} diff --git a/space/store/issues/helpers.ts b/space/store/issues/helpers.ts deleted file mode 100644 index a862ca6e0..000000000 --- a/space/store/issues/helpers.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { TIssueBoardKeys } from "types/issue"; -import { IIssueFilterOptions, TIssueParams } from "./types"; - -export const isNil = (value: any) => { - if (value === undefined || value === null) return true; - - return false; -}; - -export interface ILayoutDisplayFiltersOptions { - filters: (keyof IIssueFilterOptions)[]; - display_properties: boolean | null; - display_filters: null; - extra_options: null; -} - -export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { - [pageType: string]: { [layoutType: string]: ILayoutDisplayFiltersOptions }; -} = { - issues: { - list: { - filters: ["priority", "state", "labels"], - display_properties: null, - display_filters: null, - extra_options: null, - }, - kanban: { - filters: ["priority", "state", "labels"], - display_properties: null, - display_filters: null, - extra_options: null, - }, - }, -}; - -export const handleIssueQueryParamsByLayout = ( - layout: TIssueBoardKeys | undefined, - viewType: "issues" -): TIssueParams[] | null => { - const queryParams: TIssueParams[] = []; - - if (!layout) return null; - - const layoutOptions = ISSUE_DISPLAY_FILTERS_BY_LAYOUT[viewType][layout]; - - // add filters query params - layoutOptions.filters.forEach((option) => { - queryParams.push(option); - }); - - return queryParams; -}; diff --git a/space/store/issues/types.ts b/space/store/issues/types.ts deleted file mode 100644 index d1de0a5ea..000000000 --- a/space/store/issues/types.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { IIssue } from "types/issue"; - -export type TIssueGroupByOptions = "state" | "priority" | "labels" | null; - -export type TIssueParams = "priority" | "state" | "labels"; - -export interface IIssueFilterOptions { - state?: string[] | null; - labels?: string[] | null; - priority?: string[] | null; -} - -// issues -export interface IGroupedIssues { - [group_id: string]: string[]; -} - -export interface ISubGroupedIssues { - [sub_grouped_id: string]: { - [group_id: string]: string[]; - }; -} - -export type TUnGroupedIssues = string[]; - -export interface IIssueResponse { - [issue_id: string]: IIssue; -} - -export type TLoader = "init-loader" | "mutation" | undefined; - -export interface ViewFlags { - enableQuickAdd: boolean; - enableIssueCreation: boolean; - enableInlineEditing: boolean; -} diff --git a/space/store/user/profile.store.ts b/space/store/profile.store.ts similarity index 100% rename from space/store/user/profile.store.ts rename to space/store/profile.store.ts diff --git a/space/store/project.ts b/space/store/project.store.ts similarity index 57% rename from space/store/project.ts rename to space/store/project.store.ts index dd2c37c00..e382b6792 100644 --- a/space/store/project.ts +++ b/space/store/project.store.ts @@ -11,11 +11,15 @@ export interface IProjectStore { error: any | null; workspace: IWorkspace | null; project: IProject | null; - deploySettings: IProjectSettings | null; - viewOptions: any; - activeBoard: TIssueBoardKeys | null; + settings: IProjectSettings | null; + activeLayout: TIssueBoardKeys; + layoutOptions: Record; + canReact: boolean; + canComment: boolean; + canVote: boolean; fetchProjectSettings: (workspace_slug: string, project_slug: string) => Promise; - setActiveBoard: (value: TIssueBoardKeys) => void; + setActiveLayout: (value: TIssueBoardKeys) => void; + hydrate: (projectSettings: any) => void; } export class ProjectStore implements IProjectStore { @@ -24,9 +28,18 @@ export class ProjectStore implements IProjectStore { // data workspace: IWorkspace | null = null; project: IProject | null = null; - deploySettings: IProjectSettings | null = null; - viewOptions: any = null; - activeBoard: TIssueBoardKeys | null = null; + settings: IProjectSettings | null = null; + activeLayout: TIssueBoardKeys = "list"; + layoutOptions: Record = { + list: true, + kanban: true, + calendar: false, + gantt: false, + spreadsheet: false, + }; + canReact: boolean = false; + canComment: boolean = false; + canVote: boolean = false; // root store rootStore; // service @@ -38,14 +51,18 @@ export class ProjectStore implements IProjectStore { loader: observable, error: observable.ref, // observable - workspace: observable.ref, - project: observable.ref, - deploySettings: observable.ref, - viewOptions: observable.ref, - activeBoard: observable.ref, + workspace: observable, + project: observable, + settings: observable, + layoutOptions: observable, + activeLayout: observable.ref, + canReact: observable.ref, + canComment: observable.ref, + canVote: observable.ref, // actions fetchProjectSettings: action, - setActiveBoard: action, + setActiveLayout: action, + hydrate: action, // computed }); @@ -53,6 +70,20 @@ export class ProjectStore implements IProjectStore { this.projectService = new ProjectService(); } + hydrate = (projectSettings: any) => { + const { workspace_detail, project_details, views, votes, comments, reactions } = projectSettings; + this.workspace = workspace_detail; + this.project = project_details; + this.layoutOptions = views; + this.canComment = comments; + this.canVote = votes; + this.canReact = reactions; + }; + + setActiveLayout = (boardValue: TIssueBoardKeys) => { + this.activeLayout = boardValue; + }; + fetchProjectSettings = async (workspace_slug: string, project_slug: string) => { try { this.loader = true; @@ -68,8 +99,8 @@ export class ProjectStore implements IProjectStore { runInAction(() => { this.project = currentProject; this.workspace = currentWorkspace; - this.viewOptions = currentViewOptions; - this.deploySettings = currentDeploySettings; + this.layoutOptions = currentViewOptions; + this.settings = currentDeploySettings; this.loader = false; }); } @@ -80,8 +111,4 @@ export class ProjectStore implements IProjectStore { return error; } }; - - setActiveBoard = (boardValue: TIssueBoardKeys) => { - this.activeBoard = boardValue; - }; } diff --git a/space/store/root.store.ts b/space/store/root.store.ts index 77fce9613..8b6b10f51 100644 --- a/space/store/root.store.ts +++ b/space/store/root.store.ts @@ -1,13 +1,11 @@ -// mobx lite import { enableStaticRendering } from "mobx-react-lite"; // store imports import { IInstanceStore, InstanceStore } from "@/store/instance.store"; -import { IProjectStore, ProjectStore } from "@/store/project"; -import { IUserStore, UserStore } from "@/store/user"; - -import IssueStore, { IIssueStore } from "./issue"; -import IssueDetailStore, { IIssueDetailStore } from "./issue_details"; -import { IIssuesFilterStore, IssuesFilterStore } from "./issues/issue-filters.store"; +import { IssueDetailStore, IIssueDetailStore } from "@/store/issue-detail.store"; +import { IssueStore, IIssueStore } from "@/store/issue.store"; +import { IProjectStore, ProjectStore } from "@/store/project.store"; +import { IUserStore, UserStore } from "@/store/user.store"; +import { IssueFilterStore, IIssueFilterStore } from "./issue-filters.store"; import { IMentionsStore, MentionsStore } from "./mentions.store"; enableStaticRendering(typeof window === "undefined"); @@ -16,33 +14,36 @@ export class RootStore { instance: IInstanceStore; user: IUserStore; project: IProjectStore; - issue: IIssueStore; - issueDetails: IIssueDetailStore; - mentionsStore: IMentionsStore; - issuesFilter: IIssuesFilterStore; + issueDetail: IIssueDetailStore; + mentionStore: IMentionsStore; + issueFilter: IIssueFilterStore; constructor() { this.instance = new InstanceStore(this); this.user = new UserStore(this); - this.project = new ProjectStore(this); this.issue = new IssueStore(this); - this.issueDetails = new IssueDetailStore(this); - this.mentionsStore = new MentionsStore(this); - this.issuesFilter = new IssuesFilterStore(this); + this.issueDetail = new IssueDetailStore(this); + this.mentionStore = new MentionsStore(this); + this.issueFilter = new IssueFilterStore(this); } - resetOnSignOut = () => { - localStorage.setItem("theme", "system"); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + hydrate = (data: any) => { + if (!data) return; + this.instance.hydrate(data?.instance || {}, data?.config || {}); + this.user.hydrate(data?.user || {}); + }; + reset = () => { + localStorage.setItem("theme", "system"); this.instance = new InstanceStore(this); this.user = new UserStore(this); this.project = new ProjectStore(this); - this.issue = new IssueStore(this); - this.issueDetails = new IssueDetailStore(this); - this.mentionsStore = new MentionsStore(this); - this.issuesFilter = new IssuesFilterStore(this); + this.issueDetail = new IssueDetailStore(this); + this.mentionStore = new MentionsStore(this); + this.issueFilter = new IssueFilterStore(this); }; } diff --git a/space/store/user/index.ts b/space/store/user.store.ts similarity index 90% rename from space/store/user/index.ts rename to space/store/user.store.ts index e5bfc41c6..2f228b629 100644 --- a/space/store/user/index.ts +++ b/space/store/user.store.ts @@ -3,11 +3,11 @@ import { action, computed, makeObservable, observable, runInAction } from "mobx" // types import { IUser } from "@plane/types"; // services -import { AuthService } from "@/services/authentication.service"; +import { AuthService } from "@/services/auth.service"; import { UserService } from "@/services/user.service"; // stores import { RootStore } from "@/store/root.store"; -import { ProfileStore, IProfileStore } from "@/store/user/profile.store"; +import { ProfileStore, IProfileStore } from "@/store/profile.store"; import { ActorDetail } from "@/types/issue"; type TUserErrorStatus = { @@ -22,12 +22,13 @@ export interface IUserStore { error: TUserErrorStatus | undefined; data: IUser | undefined; // store observables - userProfile: IProfileStore; + profile: IProfileStore; // computed currentActor: ActorDetail; // actions fetchCurrentUser: () => Promise; updateCurrentUser: (data: Partial) => Promise; + hydrate: (data: IUser) => void; reset: () => void; signOut: () => Promise; } @@ -39,14 +40,14 @@ export class UserStore implements IUserStore { error: TUserErrorStatus | undefined = undefined; data: IUser | undefined = undefined; // store observables - userProfile: IProfileStore; + profile: IProfileStore; // service userService: UserService; authService: AuthService; constructor(private store: RootStore) { // stores - this.userProfile = new ProfileStore(store); + this.profile = new ProfileStore(store); // service this.userService = new UserService(); this.authService = new AuthService(); @@ -58,7 +59,7 @@ export class UserStore implements IUserStore { error: observable, // model observables data: observable, - userProfile: observable, + profile: observable, // computed currentActor: computed, // actions @@ -94,7 +95,7 @@ export class UserStore implements IUserStore { }); const user = await this.userService.currentUser(); if (user && user?.id) { - await this.userProfile.fetchUserProfile(); + await this.profile.fetchUserProfile(); runInAction(() => { this.data = user; this.isLoading = false; @@ -153,6 +154,10 @@ export class UserStore implements IUserStore { } }; + hydrate = (data: IUser): void => { + this.data = { ...this.data, ...data }; + }; + /** * @description resets the user store * @returns {void} @@ -163,7 +168,7 @@ export class UserStore implements IUserStore { this.isLoading = false; this.error = undefined; this.data = undefined; - this.userProfile = new ProfileStore(this.store); + this.profile = new ProfileStore(this.store); }); }; diff --git a/space/tsconfig.json b/space/tsconfig.json index 9d3e164be..1305e698f 100644 --- a/space/tsconfig.json +++ b/space/tsconfig.json @@ -1,12 +1,23 @@ { "extends": "tsconfig/nextjs.json", - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "additional.d.ts"], + "plugins": [ + { + "name": "next" + } + ], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "additional.d.ts", ".next/types/**/*.ts"], "exclude": ["node_modules"], "compilerOptions": { "baseUrl": ".", "jsx": "preserve", "paths": { "@/*": ["*"] - } + }, + "plugins": [ + { + "name": "next" + } + ], + "strictNullChecks": true } } diff --git a/space/types/issue-filters.d.ts b/space/types/issue-filters.d.ts new file mode 100644 index 000000000..0ec82f40e --- /dev/null +++ b/space/types/issue-filters.d.ts @@ -0,0 +1,6 @@ +export interface ILayoutDisplayFiltersOptions { + filters: (keyof IIssueFilterOptions)[]; + display_properties: boolean | null; + display_filters: null; + extra_options: null; +} diff --git a/space/types/issue.d.ts b/space/types/issue.d.ts index 4b76c75e8..2b7d3e673 100644 --- a/space/types/issue.d.ts +++ b/space/types/issue.d.ts @@ -170,3 +170,38 @@ export interface IssueDetailType { votes: any[]; }; } + +export type TIssueGroupByOptions = "state" | "priority" | "labels" | null; + +export type TIssueParams = "priority" | "state" | "labels"; + +export interface IIssueFilterOptions { + state?: string[] | null; + labels?: string[] | null; + priority?: string[] | null; +} + +// issues +export interface IGroupedIssues { + [group_id: string]: string[]; +} + +export interface ISubGroupedIssues { + [sub_grouped_id: string]: { + [group_id: string]: string[]; + }; +} + +export type TUnGroupedIssues = string[]; + +export interface IIssueResponse { + [issue_id: string]: IIssue; +} + +export type TLoader = "init-loader" | "mutation" | undefined; + +export interface ViewFlags { + enableQuickAdd: boolean; + enableIssueCreation: boolean; + enableInlineEditing: boolean; +}