diff --git a/admin/package.json b/admin/package.json
index 713a83e57..8d20fd5c4 100644
--- a/admin/package.json
+++ b/admin/package.json
@@ -14,6 +14,7 @@
"@headlessui/react": "^1.7.19",
"@plane/types": "*",
"@plane/ui": "*",
+ "@plane/constants": "*",
"@tailwindcss/typography": "^0.5.9",
"@types/lodash": "^4.17.0",
"autoprefixer": "10.4.14",
diff --git a/package.json b/package.json
index 05c1c7f24..a63bbf340 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,8 @@
"packages/tailwind-config-custom",
"packages/tsconfig",
"packages/ui",
- "packages/types"
+ "packages/types",
+ "packages/constants"
],
"scripts": {
"build": "turbo run build",
@@ -25,7 +26,7 @@
"devDependencies": {
"autoprefixer": "^10.4.15",
"eslint-config-custom": "*",
- "postcss": "^8.4.38",
+ "postcss": "^8.4.29",
"prettier": "latest",
"prettier-plugin-tailwindcss": "^0.5.4",
"tailwindcss": "^3.3.3",
diff --git a/packages/constants/package.json b/packages/constants/package.json
new file mode 100644
index 000000000..be581d08a
--- /dev/null
+++ b/packages/constants/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "@plane/constants",
+ "version": "0.0.1",
+ "private": true,
+ "main": "./src/index.ts",
+ "exports": {
+ ".": "./src/index.ts",
+ "./*": "./src/*"
+ }
+}
diff --git a/packages/constants/src/auth.ts b/packages/constants/src/auth.ts
new file mode 100644
index 000000000..a7e467153
--- /dev/null
+++ b/packages/constants/src/auth.ts
@@ -0,0 +1,360 @@
+import { ReactNode } from "react";
+import Link from "next/link";
+
+export enum EPageTypes {
+ PUBLIC = "PUBLIC",
+ NON_AUTHENTICATED = "NON_AUTHENTICATED",
+ SET_PASSWORD = "SET_PASSWORD",
+ ONBOARDING = "ONBOARDING",
+ AUTHENTICATED = "AUTHENTICATED",
+}
+
+export enum EAuthModes {
+ SIGN_IN = "SIGN_IN",
+ SIGN_UP = "SIGN_UP",
+}
+
+export enum EAuthSteps {
+ EMAIL = "EMAIL",
+ PASSWORD = "PASSWORD",
+ UNIQUE_CODE = "UNIQUE_CODE",
+}
+
+// TODO: remove this
+export enum EErrorAlertType {
+ BANNER_ALERT = "BANNER_ALERT",
+ INLINE_FIRST_NAME = "INLINE_FIRST_NAME",
+ INLINE_EMAIL = "INLINE_EMAIL",
+ INLINE_PASSWORD = "INLINE_PASSWORD",
+ INLINE_EMAIL_CODE = "INLINE_EMAIL_CODE",
+}
+
+export enum EAuthErrorCodes {
+ // Global
+ INSTANCE_NOT_CONFIGURED = "5000",
+ INVALID_EMAIL = "5005",
+ EMAIL_REQUIRED = "5010",
+ SIGNUP_DISABLED = "5015",
+ MAGIC_LINK_LOGIN_DISABLED = "5017",
+ PASSWORD_LOGIN_DISABLED = "5019",
+ SMTP_NOT_CONFIGURED = "5025",
+ // Password strength
+ INVALID_PASSWORD = "5020",
+ // Sign Up
+ USER_ALREADY_EXIST = "5030",
+ AUTHENTICATION_FAILED_SIGN_UP = "5035",
+ REQUIRED_EMAIL_PASSWORD_SIGN_UP = "5040",
+ INVALID_EMAIL_SIGN_UP = "5045",
+ INVALID_EMAIL_MAGIC_SIGN_UP = "5050",
+ MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED = "5055",
+ // Sign In
+ USER_DOES_NOT_EXIST = "5060",
+ AUTHENTICATION_FAILED_SIGN_IN = "5065",
+ REQUIRED_EMAIL_PASSWORD_SIGN_IN = "5070",
+ INVALID_EMAIL_SIGN_IN = "5075",
+ INVALID_EMAIL_MAGIC_SIGN_IN = "5080",
+ MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED = "5085",
+ // Both Sign in and Sign up for magic
+ INVALID_MAGIC_CODE = "5090",
+ EXPIRED_MAGIC_CODE = "5095",
+ EMAIL_CODE_ATTEMPT_EXHAUSTED = "5100",
+ // Oauth
+ GOOGLE_NOT_CONFIGURED = "5105",
+ GITHUB_NOT_CONFIGURED = "5110",
+ GOOGLE_OAUTH_PROVIDER_ERROR = "5115",
+ GITHUB_OAUTH_PROVIDER_ERROR = "5120",
+ // Reset Password
+ INVALID_PASSWORD_TOKEN = "5125",
+ EXPIRED_PASSWORD_TOKEN = "5130",
+ // Change password
+ INCORRECT_OLD_PASSWORD = "5135",
+ INVALID_NEW_PASSWORD = "5140",
+ // set passowrd
+ PASSWORD_ALREADY_SET = "5145",
+ // Admin
+ ADMIN_ALREADY_EXIST = "5150",
+ REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME = "5155",
+ INVALID_ADMIN_EMAIL = "5160",
+ INVALID_ADMIN_PASSWORD = "5165",
+ REQUIRED_ADMIN_EMAIL_PASSWORD = "5170",
+ ADMIN_AUTHENTICATION_FAILED = "5175",
+ ADMIN_USER_ALREADY_EXIST = "5180",
+ ADMIN_USER_DOES_NOT_EXIST = "5185",
+}
+
+export type TAuthErrorInfo = {
+ type: EErrorAlertType;
+ code: EAuthErrorCodes;
+ title: string;
+ message: ReactNode;
+};
+
+const errorCodeMessages: {
+ [key in EAuthErrorCodes]: { title: string; message: (email?: string | undefined) => ReactNode };
+} = {
+ // global
+ [EAuthErrorCodes.INSTANCE_NOT_CONFIGURED]: {
+ title: `Instance not configured`,
+ message: () => `Instance not configured. Please contact your administrator.`,
+ },
+ [EAuthErrorCodes.SIGNUP_DISABLED]: {
+ title: `Sign up disabled`,
+ message: () => `Sign up disabled. Please contact your administrator.`,
+ },
+ [EAuthErrorCodes.INVALID_PASSWORD]: {
+ title: `Invalid password`,
+ message: () => `Invalid password. Please try again.`,
+ },
+ [EAuthErrorCodes.SMTP_NOT_CONFIGURED]: {
+ title: `SMTP not configured`,
+ message: () => `SMTP not configured. Please contact your administrator.`,
+ },
+
+ // email check in both sign up and sign in
+ [EAuthErrorCodes.INVALID_EMAIL]: {
+ title: `Invalid email`,
+ message: () => `Invalid email. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.EMAIL_REQUIRED]: {
+ title: `Email required`,
+ message: () => `Email required. Please try again.`,
+ },
+
+ // sign up
+ [EAuthenticationErrorCodes.USER_ALREADY_EXIST]: {
+ title: `User already exists`,
+ message: (email = undefined) => (
+
+ Your account is already registered.
+
+ Sign In
+
+ now.
+
+ ),
+ },
+ [EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_UP]: {
+ title: `Email and password required`,
+ message: () => `Email and password required. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_UP]: {
+ title: `Authentication failed`,
+ message: () => `Authentication failed. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_UP]: {
+ title: `Invalid email`,
+ message: () => `Invalid email. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED]: {
+ title: `Email and code required`,
+ message: () => `Email and code required. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_UP]: {
+ title: `Invalid email`,
+ message: () => `Invalid email. Please try again.`,
+ },
+
+ // sign in
+ [EAuthenticationErrorCodes.USER_DOES_NOT_EXIST]: {
+ title: `User does not exist`,
+ message: (email = undefined) => (
+
+ No account found.
+
+ Create one
+
+ to get started.
+
+ ),
+ },
+ [EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN]: {
+ title: `Email and password required`,
+ message: () => `Email and password required. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_IN]: {
+ title: `Authentication failed`,
+ message: () => `Authentication failed. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_IN]: {
+ title: `Invalid email`,
+ message: () => `Invalid email. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED]: {
+ title: `Email and code required`,
+ message: () => `Email and code required. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN]: {
+ title: `Invalid email`,
+ message: () => `Invalid email. Please try again.`,
+ },
+
+ // Both Sign in and Sign up
+ [EAuthenticationErrorCodes.INVALID_MAGIC_CODE]: {
+ title: `Authentication failed`,
+ message: () => `Invalid magic code. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.EXPIRED_MAGIC_CODE]: {
+ title: `Expired magic code`,
+ message: () => `Expired magic code. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED]: {
+ title: `Expired magic code`,
+ message: () => `Expired magic code. Please try again.`,
+ },
+
+ // Oauth
+ [EAuthenticationErrorCodes.GOOGLE_NOT_CONFIGURED]: {
+ title: `Google not configured`,
+ message: () => `Google not configured. Please contact your administrator.`,
+ },
+ [EAuthenticationErrorCodes.GITHUB_NOT_CONFIGURED]: {
+ title: `GitHub not configured`,
+ message: () => `GitHub not configured. Please contact your administrator.`,
+ },
+ [EAuthenticationErrorCodes.GOOGLE_OAUTH_PROVIDER_ERROR]: {
+ title: `Google OAuth provider error`,
+ message: () => `Google OAuth provider error. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.GITHUB_OAUTH_PROVIDER_ERROR]: {
+ title: `GitHub OAuth provider error`,
+ message: () => `GitHub OAuth provider error. Please try again.`,
+ },
+
+ // Reset Password
+ [EAuthenticationErrorCodes.INVALID_PASSWORD_TOKEN]: {
+ title: `Invalid password token`,
+ message: () => `Invalid password token. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.EXPIRED_PASSWORD_TOKEN]: {
+ title: `Expired password token`,
+ message: () => `Expired password token. Please try again.`,
+ },
+
+ // Change password
+ [EAuthenticationErrorCodes.INCORRECT_OLD_PASSWORD]: {
+ title: `Incorrect old password`,
+ message: () => `Incorrect old password. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.INVALID_NEW_PASSWORD]: {
+ title: `Invalid new password`,
+ message: () => `Invalid new password. Please try again.`,
+ },
+
+ // set password
+ [EAuthenticationErrorCodes.PASSWORD_ALREADY_SET]: {
+ title: `Password already set`,
+ message: () => `Password already set. Please try again.`,
+ },
+
+ // admin
+ [EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST]: {
+ title: `Admin already exists`,
+ message: () => `Admin already exists. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME]: {
+ title: `Email, password and first name required`,
+ message: () => `Email, password and first name required. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL]: {
+ title: `Invalid admin email`,
+ message: () => `Invalid admin email. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD]: {
+ title: `Invalid admin password`,
+ message: () => `Invalid admin password. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD]: {
+ title: `Email and password required`,
+ message: () => `Email and password required. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED]: {
+ title: `Authentication failed`,
+ message: () => `Authentication failed. Please try again.`,
+ },
+ [EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST]: {
+ title: `Admin user already exists`,
+ message: () => (
+
+ Admin user already exists.
+
+ Sign In
+
+ now.
+
+ ),
+ },
+ [EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST]: {
+ title: `Admin user does not exist`,
+ message: () => (
+
+ Admin user does not exist.
+
+ Sign In
+
+ now.
+
+ ),
+ },
+};
+
+export const authErrorHandler = (
+ errorCode: EAuthenticationErrorCodes,
+ email?: string | undefined
+): TAuthErrorInfo | undefined => {
+ const bannerAlertErrorCodes = [
+ EAuthenticationErrorCodes.INSTANCE_NOT_CONFIGURED,
+ EAuthenticationErrorCodes.INVALID_EMAIL,
+ EAuthenticationErrorCodes.EMAIL_REQUIRED,
+ EAuthenticationErrorCodes.SIGNUP_DISABLED,
+ EAuthenticationErrorCodes.INVALID_PASSWORD,
+ EAuthenticationErrorCodes.SMTP_NOT_CONFIGURED,
+ EAuthenticationErrorCodes.USER_ALREADY_EXIST,
+ EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_UP,
+ EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_UP,
+ EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_UP,
+ EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_UP,
+ EAuthenticationErrorCodes.MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED,
+ EAuthenticationErrorCodes.USER_DOES_NOT_EXIST,
+ EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_IN,
+ EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN,
+ EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_IN,
+ EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN,
+ EAuthenticationErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED,
+ EAuthenticationErrorCodes.INVALID_MAGIC_CODE,
+ EAuthenticationErrorCodes.EXPIRED_MAGIC_CODE,
+ EAuthenticationErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED,
+ EAuthenticationErrorCodes.GOOGLE_NOT_CONFIGURED,
+ EAuthenticationErrorCodes.GITHUB_NOT_CONFIGURED,
+ EAuthenticationErrorCodes.GOOGLE_OAUTH_PROVIDER_ERROR,
+ EAuthenticationErrorCodes.GITHUB_OAUTH_PROVIDER_ERROR,
+ EAuthenticationErrorCodes.INVALID_PASSWORD_TOKEN,
+ EAuthenticationErrorCodes.EXPIRED_PASSWORD_TOKEN,
+ EAuthenticationErrorCodes.INCORRECT_OLD_PASSWORD,
+ EAuthenticationErrorCodes.INVALID_NEW_PASSWORD,
+ EAuthenticationErrorCodes.PASSWORD_ALREADY_SET,
+ EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST,
+ EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME,
+ EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL,
+ EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD,
+ EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD,
+ EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED,
+ EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST,
+ EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST,
+ ];
+
+ if (bannerAlertErrorCodes.includes(errorCode))
+ return {
+ type: EErrorAlertType.BANNER_ALERT,
+ code: errorCode,
+ title: errorCodeMessages[errorCode]?.title || "Error",
+ message: errorCodeMessages[errorCode]?.message(email) || "Something went wrong. Please try again.",
+ };
+
+ return undefined;
+};
diff --git a/packages/constants/src/index.ts b/packages/constants/src/index.ts
new file mode 100644
index 000000000..97ccf7649
--- /dev/null
+++ b/packages/constants/src/index.ts
@@ -0,0 +1 @@
+export * from "./auth";
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..fdc85700c
--- /dev/null
+++ b/space/app/[workspace_slug]/[project_id]/layout.tsx
@@ -0,0 +1,38 @@
+import Image from "next/image";
+// components
+import IssueNavbar from "@/components/issues/navbar";
+// hooks
+import { useProject } from "@/hooks/store";
+// 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 (
+
+ );
+}
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..ee262cb38
--- /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, ...rest } = params;
+
+ return ;
+}
diff --git a/space/app/error.tsx b/space/app/error.tsx
new file mode 100644
index 000000000..0c1e9d907
--- /dev/null
+++ b/space/app/error.tsx
@@ -0,0 +1,5 @@
+"use client";
+
+export default function InstanceError() {
+ return Instance Error
;
+}
diff --git a/space/app/layout.tsx b/space/app/layout.tsx
new file mode 100644
index 000000000..24e23a372
--- /dev/null
+++ b/space/app/layout.tsx
@@ -0,0 +1,49 @@
+import { Metadata } from "next";
+// styles
+import "@/styles/globals.css";
+// components
+import { InstanceNotReady } from "@/components/instance";
+// lib
+import { AppProvider } from "@/lib/app-providers";
+// services
+import { InstanceService } from "@/services/instance.service";
+
+const instanceService = new InstanceService();
+
+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 }) {
+ const instanceDetails = await instanceService.getInstanceInfo();
+
+ return (
+
+
+ {/*
+
+
+
+ */}
+
+
+ {!instanceDetails?.instance?.is_setup_done ? (
+
+ ) : (
+ {children}
+ )}
+
+
+ );
+}
diff --git a/space/app/page.tsx b/space/app/page.tsx
new file mode 100644
index 000000000..bdd42c3af
--- /dev/null
+++ b/space/app/page.tsx
@@ -0,0 +1,31 @@
+// components
+import { UserLoggedIn } from "@/components/accounts";
+import { AuthView } from "@/components/views";
+// helpers
+// import { EPageTypes } from "@/helpers/authentication.helper";
+// import { useInstance, useUser } from "@/hooks/store";
+// wrapper
+// import { AuthWrapper } from "@/lib/wrappers";
+// services
+import { UserService } from "@/services/user.service";
+
+const userServices = new UserService();
+
+export default async function HomePage() {
+ const user = await userServices
+ .currentUser()
+ .then((user) => ({ ...user, isAuthenticated: true }))
+ .catch(() => ({ isAuthenticated: false }));
+
+ // const { data } = useInstance();
+
+ // console.log("data", data);
+ console.log("user", user);
+
+ if (user.isAuthenticated) {
+ return ;
+ }
+
+ // return <>Login View>;
+ 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..bd64f24d1 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 { useRouter } 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,12 @@ 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;
// 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..bcb696a1f 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 { useRouter } 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";
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/not-ready-view.tsx b/space/components/instance/not-ready-view.tsx
index 815e0d1fe..5b7944671 100644
--- a/space/components/instance/not-ready-view.tsx
+++ b/space/components/instance/not-ready-view.tsx
@@ -1,6 +1,7 @@
+"use client";
+
import { FC } from "react";
import Image from "next/image";
-import Link from "next/link";
// ui
import { Button } from "@plane/ui";
// helpers
@@ -9,10 +10,10 @@ import { ADMIN_BASE_URL, ADMIN_BASE_PATH } from "@/helpers/common.helper";
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..24e6f1fb1 100644
--- a/space/components/issues/board-views/kanban/block.tsx
+++ b/space/components/issues/board-views/kanban/block.tsx
@@ -2,7 +2,7 @@
// mobx react lite
import { observer } from "mobx-react-lite";
-import { useRouter } from "next/router";
+import { useRouter } from "next/navigation";
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";
diff --git a/space/components/issues/board-views/list/block.tsx b/space/components/issues/board-views/list/block.tsx
index 63b589066..60ecf53a1 100644
--- a/space/components/issues/board-views/list/block.tsx
+++ b/space/components/issues/board-views/list/block.tsx
@@ -1,6 +1,6 @@
import { FC } from "react";
import { observer } from "mobx-react-lite";
-import { useRouter } from "next/router";
+import { useRouter } from "next/navigation";
// components
import { IssueBlockDueDate } from "@/components/issues/board-views/block-due-date";
import { IssueBlockLabels } from "@/components/issues/board-views/block-labels";
diff --git a/space/components/issues/board-views/list/index.tsx b/space/components/issues/board-views/list/index.tsx
index 03ca07998..f5f5adb6b 100644
--- a/space/components/issues/board-views/list/index.tsx
+++ b/space/components/issues/board-views/list/index.tsx
@@ -2,27 +2,24 @@ 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();
+ 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/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(() => {
{
- const {
- project: projectStore,
- issuesFilter: { updateFilters },
- }: RootStore = useMobxStore();
- const { data: user } = useUser();
- // router
+type IssueNavbarProps = {
+ projectSettings: any;
+};
+
+const IssueNavbar: FC = observer((props) => {
+ const { projectSettings } = props;
+ const { project_details, views } = projectSettings;
+
+ console.log("projectSettings", projectSettings);
+ // 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 } = useProject();
+ hydrate(projectSettings);
+ const { data: user } = useUser();
+ console.log("user", user);
- useEffect(() => {
- if (workspace_slug && project_slug) {
- projectStore.fetchProjectSettings(workspace_slug.toString(), project_slug.toString());
- }
- }, [projectStore, workspace_slug, project_slug]);
+ // return <>layout>;
- useEffect(() => {
- if (workspace_slug && project_slug && projectStore?.deploySettings) {
- const viewsAcceptable: string[] = [];
- let currentBoard: TIssueBoardKeys | null = null;
+ // useEffect(() => {
+ // if (workspace_slug && project_slug && 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())) {
- currentBoard = board.toString() as TIssueBoardKeys;
- } else {
- if (viewsAcceptable && viewsAcceptable.length > 0) {
- currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
- }
- }
- } else {
- if (viewsAcceptable && viewsAcceptable.length > 0) {
- currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
- }
- }
+ // if (board) {
+ // if (viewsAcceptable.includes(board.toString())) {
+ // currentBoard = board.toString() as TIssueBoardKeys;
+ // } else {
+ // if (viewsAcceptable && viewsAcceptable.length > 0) {
+ // currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
+ // }
+ // }
+ // } else {
+ // if (viewsAcceptable && viewsAcceptable.length > 0) {
+ // currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
+ // }
+ // }
- if (currentBoard) {
- if (projectStore?.activeBoard === null || projectStore?.activeBoard !== 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 };
+ // if (currentBoard) {
+ // if (projectStore?.layout === null || projectStore?.activeBoard !== 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 };
- 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(",") };
+ // 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);
+ // if (storeParams) updateFilters(project_slug, storeParams);
- projectStore.setActiveBoard(currentBoard);
- router.push({
- pathname: `/${workspace_slug}/${project_slug}`,
- query: { ...params },
- });
- }
- }
- }
- }, [
- board,
- workspace_slug,
- project_slug,
- router,
- projectStore,
- projectStore?.deploySettings,
- updateFilters,
- labels,
- states,
- priorities,
- peekId,
- ]);
+ // projectStore.setActiveBoard(currentBoard);
+ // router.push({
+ // pathname: `/${workspace_slug}/${project_slug}`,
+ // query: { ...params },
+ // });
+ // }
+ // }
+ // }
+ // }, [
+ // board,
+ // workspace_slug,
+ // project_slug,
+ // router,
+ // projectStore,
+ // projectStore?.deploySettings,
+ // updateFilters,
+ // labels,
+ // states,
+ // priorities,
+ // peekId,
+ // ]);
return (
{/* project detail */}
- {projectStore.project ? (
+ {project_details ? (
-
+
) : (
@@ -115,16 +110,13 @@ const IssueNavbar = observer(() => {
)}
- {projectStore?.project?.name || `...`}
+ {project_details?.name || `...`}
- {/* issue search bar */}
-
{/* */}
-
{/* issue views */}
-
+
{/* issue filters */}
@@ -137,7 +129,7 @@ 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..878d2fab4 100644
--- a/space/components/issues/peek-overview/comment/add-comment.tsx
+++ b/space/components/issues/peek-overview/comment/add-comment.tsx
@@ -1,6 +1,6 @@
import React, { useRef } from "react";
import { observer } from "mobx-react-lite";
-import { useRouter } from "next/router";
+import { useRouter } from "next/navigation";
import { useForm, Controller } from "react-hook-form";
// components
import { EditorRefApi } from "@plane/lite-text-editor";
diff --git a/space/components/issues/peek-overview/comment/comment-reactions.tsx b/space/components/issues/peek-overview/comment/comment-reactions.tsx
index ca451f7b4..f6ce62fc4 100644
--- a/space/components/issues/peek-overview/comment/comment-reactions.tsx
+++ b/space/components/issues/peek-overview/comment/comment-reactions.tsx
@@ -1,6 +1,6 @@
import React from "react";
import { observer } from "mobx-react-lite";
-import { useRouter } from "next/router";
+import { useRouter } from "next/navigation";
import { Tooltip } from "@plane/ui";
// ui
import { ReactionSelector } from "@/components/ui";
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..617bbc687 100644
--- a/space/components/issues/peek-overview/full-screen-peek-view.tsx
+++ b/space/components/issues/peek-overview/full-screen-peek-view.tsx
@@ -13,7 +13,7 @@ import { IIssue } from "@/types/issue";
type Props = {
handleClose: () => void;
issueDetails: IIssue | undefined;
- workspace_slug: string;
+ workspaceSlug: string;
};
export const FullScreenPeekView: React.FC = observer((props) => {
diff --git a/space/components/issues/peek-overview/header.tsx b/space/components/issues/peek-overview/header.tsx
index 7aba40305..17defbe36 100644
--- a/space/components/issues/peek-overview/header.tsx
+++ b/space/components/issues/peek-overview/header.tsx
@@ -9,7 +9,7 @@ import { Icon } from "@/components/ui";
import { copyTextToClipboard } from "@/helpers/string.helper";
// store
import { useMobxStore } from "@/hooks/store";
-import { IPeekMode } from "@/store/issue_details";
+import { IPeekMode } from "@/store/issue-detail.store";
import { RootStore } from "@/store/root.store";
// lib
import useToast from "hooks/use-toast";
diff --git a/space/components/issues/peek-overview/issue-activity.tsx b/space/components/issues/peek-overview/issue-activity.tsx
index aaa7dc688..4fe50dc4a 100644
--- a/space/components/issues/peek-overview/issue-activity.tsx
+++ b/space/components/issues/peek-overview/issue-activity.tsx
@@ -1,7 +1,7 @@
import React from "react";
import { observer } from "mobx-react-lite";
import Link from "next/link";
-import { useRouter } from "next/router";
+import { useRouter } from "next/navigation";
import { Button } from "@plane/ui";
// components
import { CommentCard, AddComment } from "@/components/issues/peek-overview";
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..7daaa29e5 100644
--- a/space/components/issues/peek-overview/issue-reaction.tsx
+++ b/space/components/issues/peek-overview/issue-reaction.tsx
@@ -1,12 +1,18 @@
import { IssueEmojiReactions, IssueVotes } from "@/components/issues/peek-overview";
-import { useMobxStore } from "@/hooks/store";
+import { useProject } from "@/hooks/store";
-export const IssueReactions: React.FC = () => {
- const { project: projectStore } = useMobxStore();
+type IssueReactionsProps = {
+ workspaceSlug: string;
+ projectId: string;
+};
+
+export const IssueReactions: React.FC = (props) => {
+ const { workspaceSlug, projectId } = props;
+ const { canVote, canReact } = useProject();
return (
- {projectStore?.deploySettings?.votes && (
+ {canVote && (
<>
@@ -14,9 +20,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..f75f03f5f 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(() => {
@@ -118,7 +106,7 @@ export const IssuePeekOverview: React.FC = observer(() => {
)}
{issueDetailStore.peekMode === "full" && (
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 ? (
-
- ) : (
-
-
-
+
+
+
+
+
>
)}
>
diff --git a/space/components/views/project-details.tsx b/space/components/views/project-details.tsx
index ef51a4512..d9a9d9f59 100644
--- a/space/components/views/project-details.tsx
+++ b/space/components/views/project-details.tsx
@@ -1,7 +1,9 @@
-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 useSWR from "swr";
// components
import { IssueCalendarView } from "@/components/issues/board-views/calendar";
import { IssueGanttView } from "@/components/issues/board-views/gantt";
@@ -11,16 +13,30 @@ 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;
+ params: any;
+};
- const { issue: issueStore, project: projectStore, issueDetails: issueDetailStore }: RootStore = useMobxStore();
+export const ProjectDetailsView: FC
= observer((props) => {
+ const { workspaceSlug, projectId, params } = props;
+ const { states, labels, priorities, peekId } = params;
+ // 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
+ );
+
+ const issueStore = useIssue();
+ const issueDetailStore = useIssueDetails();
const { data: currentUser, fetchCurrentUser } = useUser();
useEffect(() => {
@@ -30,25 +46,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 +72,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/helpers/common.helper.ts b/space/helpers/common.helper.ts
index 99e04e559..c6b305ef1 100644
--- a/space/helpers/common.helper.ts
+++ b/space/helpers/common.helper.ts
@@ -12,4 +12,9 @@ export const WEB_BASE_URL = process.env.NEXT_PUBLIC_WEB_BASE_URL || "";
export const ASSET_PREFIX = SPACE_BASE_PATH;
+export const ADMIN_BASE_URL = process.env.NEXT_PUBLIC_ADMIN_BASE_URL ?? "";
+export const ADMIN_BASE_PATH = process.env.NEXT_PUBLIC_ADMIN_BASE_PATH ?? "";
+
+export const WEB_BASE_URL = process.env.NEXT_PUBLIC_WEB_BASE_URL ?? "";
+
export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));
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/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/package.json b/space/package.json
index a10d190d2..48fe001b7 100644
--- a/space/package.json
+++ b/space/package.json
@@ -22,6 +22,7 @@
"@plane/rich-text-editor": "*",
"@plane/types": "*",
"@plane/ui": "*",
+ "@plane/constants": "*",
"@sentry/nextjs": "^7.108.0",
"axios": "^1.3.4",
"clsx": "^2.0.0",
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 (
-
-
-
-
-
-
-
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 (
-
-
-
-
-
-
-
-
-
-
-
-
- Reset your password
-
-
- Enter your user account{"'"}s verified email address and we will send you a password reset link.
-
-
-
-
-
-
-
-
-
- );
-};
-
-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 (
-
-
-
-
-
-
-
-
-
-
-
-
- Set new password
-
-
Secure your account with a strong password
-
-
-
-
-
-
-
-
- );
-};
-
-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 (
-
-
-
-
-
-
-
-
- {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}
-
-
-
-
-
-
-
-
-
- );
-});
-
-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 (
-
-
-
-
-
-
-
-
- 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..f7833dff1 100644
--- a/space/tsconfig.json
+++ b/space/tsconfig.json
@@ -1,12 +1,22 @@
{
"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"
+ }
+ ]
}
}
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;
+}
diff --git a/web/package.json b/web/package.json
index 06efa5a84..c3de0c238 100644
--- a/web/package.json
+++ b/web/package.json
@@ -30,6 +30,7 @@
"@plane/rich-text-editor": "*",
"@plane/types": "*",
"@plane/ui": "*",
+ "@plane/constants": "*",
"@popperjs/core": "^2.11.8",
"@sentry/nextjs": "^7.108.0",
"axios": "^1.1.3",
diff --git a/yarn.lock b/yarn.lock
index 053bb68d1..6f2a4aa34 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6917,7 +6917,7 @@ postcss@8.4.31:
picocolors "^1.0.0"
source-map-js "^1.0.2"
-postcss@^8.4.23, postcss@^8.4.38:
+postcss@^8.4.23, postcss@^8.4.29, postcss@^8.4.38:
version "8.4.38"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e"
integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
@@ -7945,8 +7945,16 @@ streamx@^2.15.0, streamx@^2.16.1:
optionalDependencies:
bare-events "^2.2.0"
-"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
- name string-width-cjs
+"string-width-cjs@npm:string-width@^4.2.0":
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string-width@^4.1.0:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -8026,7 +8034,14 @@ stringify-object@^3.3.0:
is-obj "^1.0.1"
is-regexp "^1.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==